feat: 静态类中的函数继承范性

model onClose and ondestory函数
createDOMElemnet 从jsx
This commit is contained in:
熊潇 2024-09-21 17:40:53 +08:00
parent e2c0f80f04
commit 1fbf1b64d9
14 changed files with 1778 additions and 44 deletions

View File

@ -3,6 +3,7 @@
"version": "0.0.1", "version": "0.0.1",
"description": "", "description": "",
"main": "dist/editor.js", "main": "dist/editor.js",
"privite": false,
"scripts": { "scripts": {
"build": "rimraf -rf dist && rollup -c" "build": "rimraf -rf dist && rollup -c"
}, },
@ -25,5 +26,9 @@
"rollup": "^4.22.2", "rollup": "^4.22.2",
"tslib": "^2.7.0", "tslib": "^2.7.0",
"typescript": "^5.6.2" "typescript": "^5.6.2"
},
"publishConfig": {
"registry": "https://registry.npmjs.org/",
"access": "public"
} }
} }

View File

@ -14,7 +14,7 @@ export const App = () => {
<Router> <Router>
<Routes> <Routes>
<Route path='/' element={<Navigate to='/model/' />} /> <Route path='/' element={<Navigate to='/model/' />} />
<Route path='/model/*' element={<FlowApps />} /> <Route path='/modal/*' element={<FlowApps />} />
<Route path='/codemirror/*' element={<CodeMirrorApp />} /> <Route path='/codemirror/*' element={<CodeMirrorApp />} />
<Route path='/404' element={<div>404</div>} /> <Route path='/404' element={<div>404</div>} />
<Route path='*' element={<div>404</div>} /> <Route path='*' element={<div>404</div>} />

View File

@ -0,0 +1,79 @@
export function createDOMElement(jsxElement: JSX.Element) {
// 如果 jsxElement 是 null, undefined 或者是布尔值,则直接跳过处理
if (jsxElement == null || typeof jsxElement === 'boolean') {
console.warn('Invalid JSX element:', jsxElement);
return null;
}
const { type, props } = jsxElement;
// React Fragment 的处理
if (type === Symbol.for('react.fragment')) {
const fragment = document.createDocumentFragment();
if (props.children) {
if (Array.isArray(props.children)) {
props.children.forEach((child) => {
const childElement = createDOMElement(child);
if (childElement) {
fragment.appendChild(childElement);
}
});
} else {
const childElement = createDOMElement(props.children);
if (childElement) {
fragment.appendChild(childElement);
}
}
}
return fragment;
}
const domElement = document.createElement(type);
// 处理 props
Object.keys(props).forEach((prop) => {
if (prop === 'children') {
// 递归处理 children
if (Array.isArray(props.children)) {
props.children.forEach((child) => {
const childElement = createDOMElement(child);
if (childElement) {
domElement.appendChild(childElement);
}
});
} else if (typeof props.children === 'string') {
domElement.appendChild(document.createTextNode(props.children));
} else if (typeof props.children === 'object' && props.children !== null) {
const childElement = createDOMElement(props.children);
if (childElement) {
domElement.appendChild(childElement);
}
}
} else if (prop.startsWith('on')) {
// 处理事件监听器
const eventType = prop.slice(2).toLowerCase(); // 提取事件类型(如 onClick -> click
domElement.addEventListener(eventType, props[prop]);
} else if (prop === 'style' && typeof props[prop] === 'object') {
// 处理 style 属性
Object.assign(domElement.style, props[prop]);
} else if (prop === 'dangerouslySetInnerHTML') {
// 处理 dangerouslySetInnerHTML
if (props[prop] && typeof props[prop].__html === 'string') {
domElement.innerHTML = props[prop].__html;
} else {
console.warn('Invalid dangerouslySetInnerHTML content:', props[prop]);
}
} else if (prop === 'ref') {
// React 的 ref 在手动创建 DOM 时没有用处
console.warn('Ref prop is not supported in manual DOM creation');
} else if (prop === 'key') {
// React 的 key 属性是用于虚拟 DOM 的,不影响实际 DOM
console.warn('Key prop is not applicable in manual DOM creation');
} else {
// 处理其他普通属性
domElement.setAttribute(prop, props[prop]);
}
});
return domElement;
}

View File

@ -1,7 +1,9 @@
import { modalStore, Modal, DialogModal } from '@kevisual/ui';
import { calc } from 'antd/es/theme/internal'; import { calc } from 'antd/es/theme/internal';
import { DialogModal, Modal, modalStore } from '@kevisual/ui';
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import '@kevisual/ui/src/index.css'; import '@kevisual/ui/src/index.css';
import { createDOMElement } from './create-dom';
// import '@kevisual/ui/src/components/modal/index.css';
export const App = () => { export const App = () => {
const showModel = () => { const showModel = () => {
// //
@ -17,6 +19,8 @@ export const App = () => {
}}> }}>
<ModelOne /> <ModelOne />
<ModelTwo /> <ModelTwo />
<ModelTwo2 />
<ModelThree />
</div> </div>
</div> </div>
); );
@ -29,7 +33,7 @@ const ModelOne = () => {
}; };
return ( return (
<div> <div>
<div className='w-100 h-100 p-4 rounded-md shadow-md bg-slate-200'> <div className='w-96 p-4 rounded-md shadow-md bg-slate-200'>
model one model one
<div className='cursor-pointer p-2 border' onClick={showModel}> <div className='cursor-pointer p-2 border' onClick={showModel}>
show show
@ -50,12 +54,12 @@ const ModelTwo = () => {
contentStyle: { contentStyle: {
// maxHeight: '100px', // maxHeight: '100px',
// overflow: 'auto', // overflow: 'auto',
} },
}); });
}; };
return ( return (
<div> <div>
<div className='w-100 h-100 p-4 rounded-md shadow-md bg-slate-200'> <div className='w-96 p-4 rounded-md shadow-md bg-slate-200'>
model two model two
<div className='cursor-pointer p-2 border' onClick={showModel}> <div className='cursor-pointer p-2 border' onClick={showModel}>
show show
@ -71,3 +75,130 @@ const ModelTwo = () => {
</div> </div>
); );
}; };
const ModelTwo2 = () => {
const ref = useRef<HTMLDivElement>(null);
const showModel = () => {
const div = createDOMElement(refHide);
const model = DialogModal.render(ref.current! || div, {
dialogTitle: 'Dialog Modal',
width: '400px',
dialogTitleCloseIcon: true,
contentStyle: {
// maxHeight: '100px',
// overflow: 'auto',
},
});
};
const refHide = (
<div ref={ref}>
Modal Dialog
<div></div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
);
return (
<div>
<div className='w-96 p-4 rounded-md shadow-md bg-slate-200'>
model two -ref的模块没有真实渲染到节点createDOMElement
<div className='cursor-pointer p-2 border' onClick={showModel}>
show
</div>
<div className='hidden'>{refHide}</div>
</div>
</div>
);
};
const ModelThree = () => {
const ref = useRef<HTMLDivElement>(null);
let dialog = useRef<DialogModal | null>(null);
const showModel = () => {
const model = DialogModal.render(ref.current!, {
dialogTitle: 'Dialog Modal',
width: '400px',
dialogTitleCloseIcon: true,
open: false,
contentStyle: {
// maxHeight: '100px',
// overflow: 'auto',
},
});
const modals = modalStore.getState().modals;
console.log('modals', modals.length, model);
dialog.current = model;
};
return (
<div>
<div className='w-96 p-4 rounded-md shadow-md bg-slate-200'>
model 3
<div className='cursor-pointer p-2 border' onClick={showModel}>
show
</div>
<div ref={ref}>
Modal Dialog
<div></div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<div
onClick={() => {
if (dialog.current) {
dialog.current.setOpen(true);
}
console.log('open', dialog.current);
}}>
</div>
</div>
</div>
);
};
const Model4 = () => {
const ref = useRef<HTMLDivElement>(null);
let dialog = useRef<DialogModal | null>(null);
const showModel = () => {
const model = DialogModal.create({
dialogTitle: 'Dialog Modal',
width: '400px',
dialogTitleCloseIcon: true,
open: false,
contentStyle: {
// maxHeight: '100px',
// overflow: 'auto',
},
});
const modals = modalStore.getState().modals;
console.log('modals', modals.length, model);
dialog.current = model;
};
return (
<div>
<div className='w-96 p-4 rounded-md shadow-md bg-slate-200'>
model 4
<div className='cursor-pointer p-2 border' onClick={showModel}>
show
</div>
<div ref={ref}>
Modal Dialog
<div></div>
<div>2</div>
<div>3</div>
<div>4</div>
</div>
<div
onClick={() => {
if (dialog.current) {
dialog.current.setOpen(true);
}
console.log('open', dialog.current);
}}>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,20 @@
class A {
constructor() {}
// 使用泛型和类构造函数签名来推断返回的类型
static render<T extends new (...args: any[]) => any>(this: T, el: string | HTMLDivElement): InstanceType<T> {
return new this();
}
}
class B extends A {
constructor() {
super();
}
}
const a = A.render('div');
const b = B.render('div');
// type a == A
// type b == B

View File

@ -1,10 +1,14 @@
{ {
"name": "@kevisual/ui", "name": "@kevisual/ui",
"version": "0.0.1", "version": "0.0.2",
"description": "", "description": "",
"main": "dist/index.js", "main": "dist/index.js",
"privite": false,
"scripts": { "scripts": {
"build": "tsc" "tsc": "tsc",
"dev": "rollup -c -w",
"build": "npm run clean && rollup -c",
"clean": "rimraf dist"
}, },
"files": [ "files": [
"dist", "dist",
@ -15,10 +19,18 @@
"author": "abearxiong", "author": "abearxiong",
"devDependencies": { "devDependencies": {
"@emotion/serialize": "^1.3.1", "@emotion/serialize": "^1.3.1",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-typescript": "^11.1.6",
"@types/postcss-import": "^14.0.3",
"@types/react": "^18.3.8", "@types/react": "^18.3.8",
"autoprefixer": "^10.4.20",
"cssnano": "^7.0.6",
"immer": "^10.1.1", "immer": "^10.1.1",
"nanoid": "^5.0.7", "nanoid": "^5.0.7",
"postcss-import": "^16.1.0",
"rollup": "^4.22.2", "rollup": "^4.22.2",
"rollup-plugin-postcss": "^4.0.2",
"ts-lib": "^0.0.5",
"typescript": "^5.6.2", "typescript": "^5.6.2",
"zustand": "5.0.0-rc.2" "zustand": "5.0.0-rc.2"
}, },

View File

@ -0,0 +1,47 @@
import resolve from '@rollup/plugin-node-resolve';
import typescript from '@rollup/plugin-typescript';
import postcss from 'rollup-plugin-postcss';
import autoprefixer from 'autoprefixer';
import cssnano from 'cssnano';
import postcssImport from 'postcss-import';
const entrys = ['index'];
const configs = entrys.map((entry) => ({
input: `./src/${entry}.ts`, // 修改输入文件为 TypeScript 文件
output: {
file: `./dist/${entry}.js`,
},
plugins: [
// resolve(),
typescript({
tsconfig: './tsconfig.json',
compilerOptions: {
declaration: true, // 生成声明文件
declarationDir: './dist', // 声明文件输出目录
},
}), // 添加 TypeScript 插件
],
}));
const entryCss = ['index'];
const configsCss = entryCss.map((entry) => ({
input: `./src/${entry}.css`, // 修改输入文件为 TypeScript 文件
output: {
file: `./dist/${entry}.css`,
},
include: ['src/**/*.css'],
plugins: [
// resolve(),
postcss({
// extract: true,
extract: true,
plugins: [
postcssImport(), // 处理 @import 语句
autoprefixer(),
],
}),
],
}));
export default [...configs, ...configsCss];

View File

@ -98,12 +98,12 @@ export class DialogModal extends Modal<DialogModalOpts, DialogDefaultStyle> {
...opts?.defaultStyle?.defaultDialogFooterStyle, ...opts?.defaultStyle?.defaultDialogFooterStyle,
}); });
} }
static render(el: string | HTMLDivElement, id: string, opts?: DialogModalOpts): any; // static render(el: string | HTMLDivElement, id: string, opts?: DialogModalOpts): DialogModal;
static render(el: string | HTMLDivElement, opts?: DialogModalOpts): any; // static render(el: string | HTMLDivElement, opts?: DialogModalOpts): DialogModal;
static render(...args: any[]) { // static render(...args: any[]) {
const [el, id, opts] = args; // const [el, id, opts] = args;
super.render(el, id, opts); // return super.render(el, id, opts);
} // }
appendRoot(documentFragment: DocumentFragment): void { appendRoot(documentFragment: DocumentFragment): void {
const cacheFragment = document.createDocumentFragment(); const cacheFragment = document.createDocumentFragment();
// 拿出来 // 拿出来

View File

@ -8,10 +8,9 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: 200; z-index: 200;
display: none;
} }
.ui-modal-open { .ui-modal-close {
display: block; display: none;
} }
.ui-modal-mask { .ui-modal-mask {

View File

@ -23,8 +23,9 @@ export type ModalOpts<
contentClassName?: string; contentClassName?: string;
contentStyle?: ElStyle; contentStyle?: ElStyle;
destroyOnClose?: boolean; // 关闭把Element移动到cacheFragment中
destroyOnClose?: boolean; hideOnClose?: boolean; // 关闭后是否销毁设置display:none
open?: boolean;
onClose?: () => void; onClose?: () => void;
defaultStyle?: DefaultStyle<U>; defaultStyle?: DefaultStyle<U>;
@ -49,6 +50,7 @@ export class Modal<T = any, U = KV> {
contentStyle?: ElStyle; contentStyle?: ElStyle;
destroyOnClose?: boolean; destroyOnClose?: boolean;
hideOnClose?: boolean;
open?: boolean; open?: boolean;
isUse = true; isUse = true;
@ -67,12 +69,18 @@ export class Modal<T = any, U = KV> {
this.maskClose = opts.maskClose ?? true; this.maskClose = opts.maskClose ?? true;
this.contentClassName = opts.contentClassName; this.contentClassName = opts.contentClassName;
this.contentStyle = opts.contentStyle; this.contentStyle = opts.contentStyle;
this.destroyOnClose = opts.destroyOnClose ?? false; this.destroyOnClose = opts.destroyOnClose ?? true;
this.hideOnClose = opts.hideOnClose ?? true;
if (!this.destroyOnClose && !this.hideOnClose) {
this.destroyOnClose = true; // 必须要有一个为true
console.warn('destroyOnClose Or hideOnClose must one is true');
}
this.cacheFragment = new DocumentFragment(); this.cacheFragment = new DocumentFragment();
this.defaultStyle = opts.defaultStyle || ({} as DefaultStyle<U>); this.defaultStyle = opts.defaultStyle || ({} as DefaultStyle<U>);
this.open = opts.open ?? true;
this.onClose = opts.onClose; this.onClose = opts.onClose;
} }
initRoot(root: ModalOpts['root']) { protected initRoot(root: ModalOpts['root']) {
let _root = querySelector(root); let _root = querySelector(root);
if (!_root) { if (!_root) {
// 查询ui-modal元素,不存在则创建一个ui-modal元素并添加到body上 // 查询ui-modal元素,不存在则创建一个ui-modal元素并添加到body上
@ -96,8 +104,8 @@ export class Modal<T = any, U = KV> {
return _root; return _root;
} }
static render(el: string | HTMLDivElement, id: string, opts?: ModalOpts): any; static render<T extends new (...args: any[]) => any>(this: T,el: string | HTMLDivElement, id: string, opts?: ModalOpts): InstanceType<T>;
static render(el: string | HTMLDivElement, opts?: ModalOpts): any; static render<T extends new (...args: any[]) => any>(this: T,el: string | HTMLDivElement, opts?: ModalOpts): InstanceType<T>;
static render(...args: any[]) { static render(...args: any[]) {
let [el, id, opts] = args; let [el, id, opts] = args;
const _el = querySelector(el); const _el = querySelector(el);
@ -131,6 +139,26 @@ export class Modal<T = any, U = KV> {
}); });
} }
_modal.renderEl(_el); _modal.renderEl(_el);
return _modal;
}
static create<T extends new (...args: any[]) => any>(this:T, opts: ModalOpts):InstanceType<T> {
let _id = opts.id;
let _modal: Modal | undefined;
const modalState = modalStore.getState();
if (_id) {
// 如果存在id,则判断是否已经存在该id的modal
_modal = modalStore.getState().getModal(_id);
}
if (!_modal) {
// 不存在modal,则创建一个modal
// console.log('create modal', id, opts);
const newModal = new this({ ...opts, id: _id });
_modal = newModal;
modalStore.setState({
modals: [...modalState.modals, newModal],
});
}
return _modal as InstanceType<T>;
} }
createMask() { createMask() {
const mask = document.createElement('div'); const mask = document.createElement('div');
@ -178,20 +206,26 @@ export class Modal<T = any, U = KV> {
} }
appendRoot(document: DocumentFragment) { appendRoot(document: DocumentFragment) {
this.root.appendChild(document); this.root.appendChild(document);
this.setOpen(true); // 第一次渲染open为true显示弹窗
this.setOpen(this.open);
} }
setOpen(open: boolean) { setOpen(open: boolean) {
this.open = open; this.open = open;
if (this.destroyOnClose && open === false) { if (this.destroyOnClose) {
this.unMount();
}
if (open) { if (open) {
this.modalElement.classList.add('ui-modal-open');
this.root.appendChild(this.modalElement); this.root.appendChild(this.modalElement);
} else { } else {
// this.modalElement.classList.remove('ui-modal-open');
this.cacheFragment.appendChild(this.modalElement); this.cacheFragment.appendChild(this.modalElement);
} }
return;
}
if (this.hideOnClose) {
if (open) {
this.modalElement.classList.remove('ui-modal-close');
} else {
this.modalElement.classList.add('ui-modal-close');
}
}
} }
unMount() { unMount() {
// 返回渲染的的Element, 然后删除modalElement // 返回渲染的的Element, 然后删除modalElement
@ -204,10 +238,12 @@ export class Modal<T = any, U = KV> {
modals: modalState.modals.filter((modal) => modal.id !== this.id), modals: modalState.modals.filter((modal) => modal.id !== this.id),
}); });
this.isUse = false; this.isUse = false;
this.cacheFragment = new DocumentFragment();
return fragment; return fragment;
} }
/** /**
* *
* // TODO: 研究
* @param force * @param force
* @param opts * @param opts
* @returns * @returns

View File

@ -1 +1 @@
@import 'components/model/index.css'; @import './components/modal/index.css';

View File

@ -1,3 +1,7 @@
import { Modal, modalStore, BlankModal, DialogModal } from './components/modal'; import { Modal, modalStore, BlankModal, DialogModal } from './components/modal';
export { Modal, modalStore, BlankModal, DialogModal }; export { Modal, modalStore, BlankModal, DialogModal };
import { createDOMElement } from './utils/dom/create-dom-element';
export { createDOMElement };

View File

@ -0,0 +1,82 @@
type JSXElement = {
type: string | symbol;
props: Record<string, any>;
key?: string | number;
};
export function createDOMElement(jsxElement: JSXElement) {
// 如果 jsxElement 是 null, undefined 或者是布尔值,则直接跳过处理
if (jsxElement == null || typeof jsxElement === 'boolean') {
console.warn('Invalid JSX element:', jsxElement);
return null;
}
const { type, props } = jsxElement;
// React Fragment 的处理
if (type === Symbol.for('react.fragment')) {
const fragment = document.createDocumentFragment();
if (props.children) {
if (Array.isArray(props.children)) {
props.children.forEach((child) => {
const childElement = createDOMElement(child);
if (childElement) {
fragment.appendChild(childElement);
}
});
} else {
const childElement = createDOMElement(props.children);
if (childElement) {
fragment.appendChild(childElement);
}
}
}
return fragment;
}
const domElement = document.createElement(type as string);
// 处理 props
Object.keys(props).forEach((prop) => {
if (prop === 'children') {
// 递归处理 children
if (Array.isArray(props.children)) {
props.children.forEach((child) => {
const childElement = createDOMElement(child);
if (childElement) {
domElement.appendChild(childElement);
}
});
} else if (typeof props.children === 'string') {
domElement.appendChild(document.createTextNode(props.children));
} else if (typeof props.children === 'object' && props.children !== null) {
const childElement = createDOMElement(props.children);
if (childElement) {
domElement.appendChild(childElement);
}
}
} else if (prop.startsWith('on')) {
// 处理事件监听器
const eventType = prop.slice(2).toLowerCase(); // 提取事件类型(如 onClick -> click
domElement.addEventListener(eventType, props[prop]);
} else if (prop === 'style' && typeof props[prop] === 'object') {
// 处理 style 属性
Object.assign(domElement.style, props[prop]);
} else if (prop === 'dangerouslySetInnerHTML') {
// 处理 dangerouslySetInnerHTML
if (props[prop] && typeof props[prop].__html === 'string') {
domElement.innerHTML = props[prop].__html;
} else {
console.warn('Invalid dangerouslySetInnerHTML content:', props[prop]);
}
} else if (prop === 'ref') {
// React 的 ref 在手动创建 DOM 时没有用处
console.warn('Ref prop is not supported in manual DOM creation');
} else if (prop === 'key') {
// React 的 key 属性是用于虚拟 DOM 的,不影响实际 DOM
console.warn('Key prop is not applicable in manual DOM creation');
} else {
// 处理其他普通属性
domElement.setAttribute(prop, props[prop]);
}
});
return domElement;
}

1343
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff