feat(reate-runtime-help,vue-runtime-help): 新增组件状态hook

This commit is contained in:
roymondchen 2025-03-06 20:20:01 +08:00
parent 63fe6ec68b
commit 6f5bb84c04
8 changed files with 332 additions and 63 deletions

View File

@ -1,5 +1,5 @@
{
"version": "0.0.4",
"version": "0.1.0",
"name": "@tmagic/react-runtime-help",
"type": "module",
"sideEffects": false,

View File

@ -19,27 +19,74 @@
import { useContext, useEffect, useState } from 'react';
import type TMagicApp from '@tmagic/core';
import type { Id, MNodeInstance } from '@tmagic/core';
import type { Id, MNodeInstance, Node as TMagicNode } from '@tmagic/core';
import { isDslNode } from '@tmagic/core';
import AppContent from '../AppContent';
interface UseAppOptions<T extends MNodeInstance = MNodeInstance> {
interface Methods {
[key: string]: (...args: any[]) => any;
}
export interface UseAppOptions<T extends MNodeInstance = MNodeInstance> {
config: T;
iteratorContainerId?: Id[];
iteratorIndex?: number[];
methods?: {
[key: string]: Function;
[key: string]: (...args: any[]) => any;
};
}
export const useNode = (
props: Pick<UseAppOptions, 'config' | 'iteratorContainerId' | 'iteratorIndex'>,
app = useContext(AppContent),
) =>
isDslNode(props.config) && props.config.id
? app?.getNode(props.config.id, props.iteratorContainerId, props.iteratorIndex)
: undefined;
export const registerNodeHooks = (node?: TMagicNode, methods: Methods = {}) => {
if (!node) {
return;
}
const emitData = {
config: node.data,
...methods,
};
const [created, setCreated] = useState(false);
if (!created) {
// 只需要触发一次 created
setCreated(true);
node?.emit('created', emitData);
}
useEffect(() => {
node?.emit('mounted', emitData);
return () => {
node?.emit('destroy', emitData);
};
}, []);
};
export const useApp = ({ methods = {}, config, iteratorContainerId, iteratorIndex }: UseAppOptions) => {
const app: TMagicApp | undefined = useContext(AppContent);
const emitData = {
config,
...methods,
};
const node = useNode(
{
config,
iteratorContainerId,
iteratorIndex,
},
app,
);
if (node) {
registerNodeHooks(node, methods);
}
const display = <T extends MNodeInstance>(config: T) => {
if (config.visible === false) return false;
@ -48,30 +95,11 @@ export const useApp = ({ methods = {}, config, iteratorContainerId, iteratorInde
const displayCfg = config.display;
if (typeof displayCfg === 'function') {
return displayCfg(app);
return displayCfg({ app, node });
}
return displayCfg !== false;
};
const node = isDslNode(config) && config.id ? app?.getNode(config.id, iteratorContainerId, iteratorIndex) : undefined;
const [created, setCreated] = useState(false);
if (node) {
if (!created) {
// 只需要触发一次 created
setCreated(true);
node?.emit('created', emitData);
}
useEffect(() => {
node?.emit('mounted', emitData);
return () => {
node?.emit('destroy', emitData);
};
}, []);
}
return { app, node, display };
};

View File

@ -0,0 +1,108 @@
import { useContext, useEffect, useState } from 'react';
import type TMagicApp from '@tmagic/core';
import { type MComponent, type StyleSchema, toLine } from '@tmagic/core';
import AppContent from '../AppContent';
export interface StatusData {
style?: StyleSchema;
className?: string;
[key: string]: any;
}
export const useComponentStatus = (props: { config: Omit<MComponent, 'id'> }) => {
const app: TMagicApp | undefined = useContext(AppContent);
const [status, setStatus] = useState('default');
const [style, setStyle] = useState({});
const [className, setClassName] = useState('');
const styleStatusMap = new Map<string, StyleSchema>();
const classStatusMap = new Map<string, string>();
const statusMap = new Map<string, Omit<StatusData, 'style' | 'className'>>();
const registerStatus = (type: string, { style, className, ...data }: StatusData) => {
if (style) {
styleStatusMap.set(type, style);
}
if (className) {
classStatusMap.set(type, className);
}
statusMap.set(type, data);
};
useEffect(() => {
registerStatus('default', {
style: props.config.style,
className: props.config.className,
});
return () => {
styleStatusMap.clear();
classStatusMap.clear();
statusMap.clear();
};
});
useEffect(() => {
const type = status || 'default';
const defaultStyle = styleStatusMap.get('default') || {};
const statusStyle = styleStatusMap.get(type);
let style = app?.transformStyle(defaultStyle) || {};
if (type !== 'default' && statusStyle) {
style = Object.keys(statusStyle).reduce((obj, key) => {
const value = statusStyle[key];
if (value === null || typeof value === 'undefined' || isNaN(value) || value === '') {
return {
...obj,
[key]: statusStyle[key],
};
}
return { ...obj };
}, style);
}
if (props.config.displayHidden) {
style.display = 'none';
}
if (typeof props.config.condResult !== 'undefined' && props.config.displayRenderModel === 'mount') {
if (props.config.condResult === false) {
style.display = 'none';
}
}
setStyle(style);
const className = classStatusMap.get(type) ?? '';
const list = [];
if (props.config.type) {
list.push(`magic-ui-${toLine(props.config.type)}`);
}
if (props.config.layout) {
list.push(`magic-layout-${props.config.layout}`);
}
if (className) {
list.push(className);
}
setClassName(list.join(' '));
}, [status, props.config]);
return {
status,
style,
className,
setStatus,
registerStatus,
};
};

View File

@ -19,3 +19,4 @@ export { default as AppContent } from './AppContent';
export * from './hooks/use-editor-dsl';
export * from './hooks/use-dsl';
export * from './hooks/use-app';
export * from './hooks/use-component-status';

View File

@ -1,5 +1,5 @@
{
"version": "1.0.1",
"version": "1.1.0",
"name": "@tmagic/vue-runtime-help",
"type": "module",
"sideEffects": false,

View File

@ -19,57 +19,72 @@
import { inject, onBeforeUnmount, onMounted } from 'vue-demi';
import type TMagicApp from '@tmagic/core';
import type { Id, MNodeInstance } from '@tmagic/core';
import type { Id, MNodeInstance, Node as TMagicNode } from '@tmagic/core';
import { isDslNode } from '@tmagic/core';
interface Methods {
[key: string]: (...args: any[]) => any;
}
interface UseAppOptions<T extends MNodeInstance = MNodeInstance> {
config: T;
iteratorContainerId?: Id[];
iteratorIndex?: number[];
methods?: {
[key: string]: Function;
};
methods?: Methods;
}
export const useApp = ({ methods = {}, config, iteratorContainerId, iteratorIndex }: UseAppOptions) => {
const app = inject<TMagicApp>('app');
export const useNode = (
props: Pick<UseAppOptions, 'config' | 'iteratorContainerId' | 'iteratorIndex'>,
app = inject<TMagicApp>('app'),
) =>
isDslNode(props.config) && props.config.id
? app?.getNode(props.config.id, props.iteratorContainerId, props.iteratorIndex)
: undefined;
export const registerNodeHooks = (node?: TMagicNode, methods: Methods = {}) => {
if (!node) {
return;
}
const emitData = {
config,
config: node.data,
...methods,
};
const display = <T extends MNodeInstance>(config: T) => {
if (config.visible === false) return false;
if (config.condResult === false) return false;
node.emit('created', emitData);
const displayCfg = config.display;
onMounted(() => {
node.emit('mounted', emitData);
});
if (typeof displayCfg === 'function') {
return displayCfg(app);
}
onBeforeUnmount(() => {
node.emit('destroy', emitData);
});
};
return displayCfg !== false;
};
export const useApp = <T extends TMagicApp = TMagicApp>({
methods = {},
config,
iteratorContainerId,
iteratorIndex,
}: UseAppOptions) => {
const app = inject<T>('app');
const node = isDslNode(config) && config.id ? app?.getNode(config.id, iteratorContainerId, iteratorIndex) : undefined;
const node = useNode(
{
config,
iteratorContainerId,
iteratorIndex,
},
app,
);
if (node) {
node.emit('created', emitData);
onMounted(() => {
node.emit('mounted', emitData);
});
onBeforeUnmount(() => {
node.emit('destroy', emitData);
});
registerNodeHooks(node, methods);
}
return {
app,
node,
display,
};
};

View File

@ -0,0 +1,108 @@
import { computed, inject, onScopeDispose, ref, shallowReactive, watchEffect } from 'vue-demi';
import type TMagicCore from '@tmagic/core';
import { type MComponent, type StyleSchema, toLine } from '@tmagic/core';
export interface StatusData {
style?: StyleSchema;
className?: string;
[key: string]: any;
}
export const useComponentStatus = (props: { config: Omit<MComponent, 'id'> }) => {
const app = inject<TMagicCore>('app');
const status = ref('default');
const styleStatusMap = new Map<string, StyleSchema>();
const classStatusMap = new Map<string, string>();
const statusMap = new Map<string, Omit<StatusData, 'style' | 'className'>>();
const setStatus = (value: string) => {
status.value = value;
};
const registerStatus = (type: string, { style, className, ...data }: StatusData) => {
if (style) {
styleStatusMap.set(type, style);
}
if (className) {
classStatusMap.set(type, className);
}
statusMap.set(type, shallowReactive(data));
};
watchEffect(() => {
registerStatus('default', {
style: props.config.style,
className: props.config.className,
});
});
onScopeDispose(() => {
statusMap.clear();
});
return {
status: computed(() => status.value),
style: computed(() => {
const type = status.value || 'default';
const defaultStyle = styleStatusMap.get('default') || {};
const statusStyle = styleStatusMap.get(type);
let style = app?.transformStyle(defaultStyle) || {};
if (type !== 'default' && statusStyle) {
style = Object.keys(statusStyle).reduce((obj, key) => {
const value = statusStyle[key];
if (value === null || typeof value === 'undefined' || isNaN(value) || value === '') {
return {
...obj,
[key]: statusStyle[key],
};
}
return { ...obj };
}, style);
}
if (props.config.displayHidden) {
style.display = 'none';
}
if (typeof props.config.condResult !== 'undefined' && props.config.displayRenderModel === 'mount') {
if (props.config.condResult === false) {
style.display = 'none';
}
}
return style;
}),
className: computed(() => {
const type = status.value || 'default';
const className = classStatusMap.get(type) ?? '';
const list = [];
if (props.config.type) {
list.push(`magic-ui-${toLine(props.config.type)}`);
}
if (props.config.layout) {
list.push(`magic-layout-${props.config.layout}`);
}
if (className) {
list.push(className);
}
return list.join(' ');
}),
setStatus,
registerStatus,
};
};

View File

@ -1,20 +1,29 @@
import { h } from 'vue-demi';
import type { MComponent } from '@tmagic/core';
import type { MComponent, StyleSchema } from '@tmagic/core';
export * from './hooks/use-editor-dsl';
export * from './hooks/use-dsl';
export * from './hooks/use-app';
export * from './hooks/use-component-status';
export { useComponent } from './hooks/use-component';
export interface userRenderFunctionOptions {
h: typeof h;
type: Parameters<typeof h>[0];
props: any;
attrs: any;
className: string | string[];
style: any;
config: MComponent;
props?: {
[key: string]: any;
};
attrs?: {
[key: string]: any;
};
className?: string | string[];
style?: StyleSchema;
config: Omit<MComponent, 'id'>;
on?: {
[key: string]: (...args: any[]) => any;
};
directives?: { name: string; value: any; modifiers: any }[];
}
export type UserRenderFunction = (options: userRenderFunctionOptions) => any;