mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-27 08:39:59 +08:00
Merge pull request #4 from webzard-io/runtime
trait: trait framework, state trait, event trait
This commit is contained in:
commit
81c123cfe8
@ -55,6 +55,10 @@ describe("application", () => {
|
||||
},
|
||||
"traits": Array [
|
||||
Object {
|
||||
"parsedType": Object {
|
||||
"name": "test_trait",
|
||||
"version": "core/v1",
|
||||
},
|
||||
"properties": Object {
|
||||
"width": 2,
|
||||
},
|
||||
|
@ -29,7 +29,7 @@ type ComponentTrait = {
|
||||
properties: object;
|
||||
};
|
||||
|
||||
type ComponentType = {
|
||||
type VersionAndName = {
|
||||
version: string;
|
||||
name: string;
|
||||
};
|
||||
@ -39,20 +39,23 @@ export type RuntimeApplication = Omit<Application, "spec"> & {
|
||||
parsedVersion: Version;
|
||||
spec: Omit<ApplicationSpec, "components"> & {
|
||||
components: Array<
|
||||
ApplicationComponent & {
|
||||
parsedType: ComponentType;
|
||||
Omit<ApplicationComponent, "traits"> & {
|
||||
parsedType: VersionAndName;
|
||||
traits: Array<
|
||||
ComponentTrait & {
|
||||
parsedType: VersionAndName;
|
||||
}
|
||||
>;
|
||||
}
|
||||
>;
|
||||
};
|
||||
};
|
||||
|
||||
type A = RuntimeApplication["spec"]["components"];
|
||||
|
||||
const TYPE_REG = /^([a-zA-Z0-9_\d]+\/[a-zA-Z0-9_\d]+)\/([a-zA-Z0-9_\d]+)$/;
|
||||
function isValidType(v: string): boolean {
|
||||
return TYPE_REG.test(v);
|
||||
}
|
||||
function parseType(v: string): ComponentType {
|
||||
function parseType(v: string): VersionAndName {
|
||||
if (!isValidType(v)) {
|
||||
throw new Error(`Invalid type string: "${v}"`);
|
||||
}
|
||||
@ -77,6 +80,12 @@ export function createApplication(
|
||||
return {
|
||||
...c,
|
||||
parsedType: parseType(c.type),
|
||||
traits: c.traits.map((t) => {
|
||||
return {
|
||||
...t,
|
||||
parsedType: parseType(t.type),
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
},
|
||||
|
@ -2,3 +2,4 @@ export * from "./component";
|
||||
export * from "./trait";
|
||||
export * from "./scope";
|
||||
export * from "./application";
|
||||
export * from "./method";
|
||||
|
@ -19,14 +19,65 @@
|
||||
components: [
|
||||
{
|
||||
id: "del_btn",
|
||||
type: "plain/v1/button",
|
||||
type: "chakra_ui/v1/button",
|
||||
properties: {
|
||||
text: {
|
||||
raw: `{{ del_btn.count < 3 ? '*Markdown Button*' : "**I'm** Done!" }}`,
|
||||
raw: `{{ del_btn.count > 0 ? 'CLICK TO CONFIRM' : '**DELETE** Button' }}`,
|
||||
format: "md",
|
||||
},
|
||||
isLoading: false,
|
||||
colorScheme: "twitter",
|
||||
},
|
||||
traits: [],
|
||||
traits: [
|
||||
{
|
||||
type: "core/v1/state",
|
||||
properties: {
|
||||
key: "count",
|
||||
initialValue: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "core/v1/event",
|
||||
properties: {
|
||||
events: [
|
||||
{
|
||||
event: "click",
|
||||
componentId: "$utils",
|
||||
method: {
|
||||
name: "alert",
|
||||
parameters: "{{ 'deleted!' }}",
|
||||
},
|
||||
wait: {},
|
||||
disabled: "{{ del_btn.count === 0 }}",
|
||||
},
|
||||
{
|
||||
event: "click",
|
||||
componentId: "del_btn",
|
||||
method: {
|
||||
name: "setValue",
|
||||
parameters:
|
||||
"{{ del_btn.count > 0 ? 0 : del_btn.count + 1 }}",
|
||||
},
|
||||
wait: {},
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
event: "click",
|
||||
componentId: "del_btn",
|
||||
method: {
|
||||
name: "setValue",
|
||||
parameters: "0",
|
||||
},
|
||||
wait: {
|
||||
type: "delay",
|
||||
time: 2000,
|
||||
},
|
||||
disabled: "{{ del_btn.count === 0 }}",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "debug_text",
|
||||
|
@ -5,9 +5,14 @@
|
||||
"dev": "vite"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chakra-ui/react": "^1.6.5",
|
||||
"@emotion/react": "^11",
|
||||
"@emotion/styled": "^11",
|
||||
"@meta-ui/core": "^0.1.0",
|
||||
"framer-motion": "^4",
|
||||
"lodash": "^4.17.21",
|
||||
"mitt": "^3.0.0",
|
||||
"nanoid": "^3.1.23",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-markdown": "^6.0.2",
|
||||
|
@ -1,9 +1,10 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
import {
|
||||
Application,
|
||||
createApplication,
|
||||
RuntimeApplication,
|
||||
} from "@meta-ui/core";
|
||||
import { merge } from "lodash";
|
||||
import { registry } from "./registry";
|
||||
import { setStore, useStore, emitter } from "./store";
|
||||
|
||||
@ -30,14 +31,43 @@ const ImplWrapper: React.FC<{
|
||||
};
|
||||
}, []);
|
||||
|
||||
const mergeState = useCallback(
|
||||
(partial: any) => {
|
||||
setStore((cur) => {
|
||||
return { [c.id]: { ...cur[c.id], ...partial } };
|
||||
});
|
||||
},
|
||||
[c.id]
|
||||
);
|
||||
const subscribeMethods = useCallback(
|
||||
(map: any) => {
|
||||
handlerMap.current = merge(handlerMap.current, map);
|
||||
},
|
||||
[handlerMap.current]
|
||||
);
|
||||
|
||||
// traits
|
||||
const traitsProps = {};
|
||||
for (const t of c.traits) {
|
||||
const tImpl = registry.getTrait(
|
||||
t.parsedType.version,
|
||||
t.parsedType.name
|
||||
).impl;
|
||||
const tProps = tImpl({
|
||||
...t.properties,
|
||||
mergeState,
|
||||
subscribeMethods,
|
||||
});
|
||||
merge(traitsProps, tProps);
|
||||
}
|
||||
|
||||
return (
|
||||
<Impl
|
||||
key={c.id}
|
||||
{...c.properties}
|
||||
mergeState={(partial: any) => setStore({ [c.id]: partial })}
|
||||
subscribeMethods={(map: any) => {
|
||||
handlerMap.current = map;
|
||||
}}
|
||||
{...traitsProps}
|
||||
mergeState={mergeState}
|
||||
subscribeMethods={subscribeMethods}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -48,15 +78,45 @@ const DebugStore: React.FC = () => {
|
||||
return <pre>{JSON.stringify(store, null, 2)}</pre>;
|
||||
};
|
||||
|
||||
const DebugEvent: React.FC = () => {
|
||||
const [events, setEvents] = useState<unknown[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
const handler = (type: string, event: unknown) => {
|
||||
setEvents((cur) =>
|
||||
cur.concat({ type, event, t: new Date().toLocaleString() })
|
||||
);
|
||||
};
|
||||
emitter.on("*", handler);
|
||||
return () => emitter.off("*", handler);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "0.5em",
|
||||
border: "2px solid black",
|
||||
maxHeight: "200px",
|
||||
overflow: "auto",
|
||||
}}
|
||||
>
|
||||
{events.map((event, idx) => (
|
||||
<pre key={idx}>{JSON.stringify(event)}</pre>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const App: React.FC<{ options: Application }> = ({ options }) => {
|
||||
const app = createApplication(options);
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<DebugStore />
|
||||
{app.spec.components.map((c) => {
|
||||
return <ImplWrapper key={c.id} component={c} />;
|
||||
})}
|
||||
<DebugStore />
|
||||
<DebugEvent />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
109
packages/runtime/src/components/chakra-ui/Button.tsx
Normal file
109
packages/runtime/src/components/chakra-ui/Button.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { createComponent } from "@meta-ui/core";
|
||||
import {
|
||||
Button as BaseButton,
|
||||
ChakraProvider,
|
||||
ButtonProps as BaseButtonProps,
|
||||
} from "@chakra-ui/react";
|
||||
import Text, { TextProps } from "../_internal/Text";
|
||||
import { ComponentImplementation } from "../../registry";
|
||||
import { useExpression } from "../../store";
|
||||
|
||||
const Button: ComponentImplementation<
|
||||
BaseButtonProps & {
|
||||
text: TextProps["value"];
|
||||
onClick?: () => void;
|
||||
}
|
||||
> = ({ text, mergeState, subscribeMethods, onClick, ...rest }) => {
|
||||
const raw = useExpression(text.raw);
|
||||
useEffect(() => {
|
||||
mergeState({ value: raw });
|
||||
}, [raw]);
|
||||
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => {
|
||||
subscribeMethods({
|
||||
click() {
|
||||
ref.current?.click();
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ChakraProvider>
|
||||
<BaseButton {...rest} ref={ref} onClick={onClick}>
|
||||
<Text value={{ ...text, raw }} />
|
||||
</BaseButton>
|
||||
</ChakraProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
...createComponent({
|
||||
version: "chakra_ui/v1",
|
||||
metadata: {
|
||||
name: "button",
|
||||
description: "chakra-ui button",
|
||||
},
|
||||
spec: {
|
||||
properties: [
|
||||
{
|
||||
name: "text",
|
||||
type: "object",
|
||||
properties: {
|
||||
raw: {
|
||||
type: "string",
|
||||
},
|
||||
format: {
|
||||
type: "string",
|
||||
enum: ["plain", "md"],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "colorScheme",
|
||||
type: "string",
|
||||
enum: [
|
||||
"whiteAlpha",
|
||||
"blackAlpha",
|
||||
"gray",
|
||||
"red",
|
||||
"orange",
|
||||
"yellow",
|
||||
"green",
|
||||
"teal",
|
||||
"blue",
|
||||
"cyan",
|
||||
"purple",
|
||||
"pink",
|
||||
"linkedin",
|
||||
"facebook",
|
||||
"messenger",
|
||||
"whatsapp",
|
||||
"twitter",
|
||||
"telegram",
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "isLoading",
|
||||
type: "boolean",
|
||||
},
|
||||
],
|
||||
acceptTraits: [],
|
||||
state: {
|
||||
type: "object",
|
||||
properties: {
|
||||
value: {
|
||||
type: "string",
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: [
|
||||
{
|
||||
name: "click",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
impl: Button,
|
||||
};
|
@ -1,10 +1,10 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { createComponent } from "@meta-ui/core";
|
||||
import { Implementation } from "../../registry";
|
||||
import { ComponentImplementation } from "../../registry";
|
||||
import _Text, { TextProps } from "../_internal/Text";
|
||||
import { useExpression } from "../../store";
|
||||
|
||||
const Text: Implementation<TextProps> = ({ value, mergeState }) => {
|
||||
const Text: ComponentImplementation<TextProps> = ({ value, mergeState }) => {
|
||||
const raw = useExpression(value.raw);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -1,19 +1,17 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import React, { useEffect, useRef } from "react";
|
||||
import { createComponent } from "@meta-ui/core";
|
||||
import Text, { TextProps } from "../_internal/Text";
|
||||
import { Implementation } from "../../registry";
|
||||
import { ComponentImplementation } from "../../registry";
|
||||
import { useExpression } from "../../store";
|
||||
|
||||
const Button: Implementation<{ text: TextProps["value"] }> = ({
|
||||
text,
|
||||
mergeState,
|
||||
subscribeMethods,
|
||||
}) => {
|
||||
const [count, add] = useState(0);
|
||||
const Button: ComponentImplementation<{
|
||||
text: TextProps["value"];
|
||||
onClick?: () => void;
|
||||
}> = ({ text, mergeState, subscribeMethods, onClick }) => {
|
||||
const raw = useExpression(text.raw);
|
||||
useEffect(() => {
|
||||
mergeState({ value: raw, count });
|
||||
}, [raw, count]);
|
||||
mergeState({ value: raw });
|
||||
}, [raw]);
|
||||
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
useEffect(() => {
|
||||
@ -22,10 +20,10 @@ const Button: Implementation<{ text: TextProps["value"] }> = ({
|
||||
ref.current?.click();
|
||||
},
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<button ref={ref} onClick={() => add(count + 1)}>
|
||||
<button ref={ref} onClick={onClick}>
|
||||
<Text value={{ ...text, raw }} />
|
||||
</button>
|
||||
);
|
||||
|
@ -1,27 +1,46 @@
|
||||
import React from "react";
|
||||
import { RuntimeComponent } from "@meta-ui/core";
|
||||
import { RuntimeComponent, RuntimeTrait } from "@meta-ui/core";
|
||||
import { setStore } from "./store";
|
||||
// components
|
||||
import PlainButton from "./components/plain/Button";
|
||||
import CoreText from "./components/core/Text";
|
||||
import ChakraUIButton from "./components/chakra-ui/Button";
|
||||
// traits
|
||||
import CoreState from "./traits/core/state";
|
||||
import CoreEvent from "./traits/core/event";
|
||||
|
||||
type ImplementedRuntimeComponent = RuntimeComponent & {
|
||||
impl: Implementation;
|
||||
impl: ComponentImplementation;
|
||||
};
|
||||
|
||||
export type Implementation<T = any> = React.FC<
|
||||
type ImplementedRuntimeTrait = RuntimeTrait & {
|
||||
impl: TraitImplementation;
|
||||
};
|
||||
|
||||
type SubscribeMethods = <U>(
|
||||
map: {
|
||||
[K in keyof U]: (parameters: U[K]) => void;
|
||||
}
|
||||
) => void;
|
||||
type MergeState = (partialState: Parameters<typeof setStore>[0]) => void;
|
||||
|
||||
export type ComponentImplementation<T = any> = React.FC<
|
||||
T & {
|
||||
mergeState: (partialState: Parameters<typeof setStore>[0]) => void;
|
||||
subscribeMethods: <U>(
|
||||
map: {
|
||||
[K in keyof U]: (parameters: U[K]) => void;
|
||||
}
|
||||
) => void;
|
||||
mergeState: MergeState;
|
||||
subscribeMethods: SubscribeMethods;
|
||||
}
|
||||
>;
|
||||
|
||||
export type TraitImplementation<T = any> = (
|
||||
props: T & {
|
||||
mergeState: MergeState;
|
||||
subscribeMethods: SubscribeMethods;
|
||||
}
|
||||
) => any;
|
||||
|
||||
class Registry {
|
||||
components: Map<string, Map<string, ImplementedRuntimeComponent>> = new Map();
|
||||
traits: Map<string, Map<string, ImplementedRuntimeTrait>> = new Map();
|
||||
|
||||
registerComponent(c: ImplementedRuntimeComponent) {
|
||||
if (this.components.get(c.version)?.has(c.metadata.name)) {
|
||||
@ -36,11 +55,31 @@ class Registry {
|
||||
}
|
||||
|
||||
getComponent(version: string, name: string): ImplementedRuntimeComponent {
|
||||
const irc = this.components.get(version)?.get(name);
|
||||
if (!irc) {
|
||||
const c = this.components.get(version)?.get(name);
|
||||
if (!c) {
|
||||
throw new Error(`Component ${version}/${name} has not registered yet.`);
|
||||
}
|
||||
return irc;
|
||||
return c;
|
||||
}
|
||||
|
||||
registerTrait(t: ImplementedRuntimeTrait) {
|
||||
if (this.traits.get(t.version)?.has(t.metadata.name)) {
|
||||
throw new Error(
|
||||
`Already has trait ${t.version}/${t.metadata.name} in this registry.`
|
||||
);
|
||||
}
|
||||
if (!this.traits.has(t.version)) {
|
||||
this.traits.set(t.version, new Map());
|
||||
}
|
||||
this.traits.get(t.version)!.set(t.metadata.name, t);
|
||||
}
|
||||
|
||||
getTrait(version: string, name: string): ImplementedRuntimeTrait {
|
||||
const t = this.traits.get(version)?.get(name);
|
||||
if (!t) {
|
||||
throw new Error(`Trait ${version}/${name} has not registered yet.`);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,3 +87,7 @@ export const registry = new Registry();
|
||||
|
||||
registry.registerComponent(PlainButton);
|
||||
registry.registerComponent(CoreText);
|
||||
registry.registerComponent(ChakraUIButton);
|
||||
|
||||
registry.registerTrait(CoreState);
|
||||
registry.registerTrait(CoreEvent);
|
||||
|
@ -8,7 +8,7 @@ export const useStore = create<Record<string, any>>(() => ({}));
|
||||
export const setStore = useStore.setState;
|
||||
|
||||
// TODO: use web worker
|
||||
function evalInContext(expression: string, ctx: Record<string, any>) {
|
||||
export function evalInContext(expression: string, ctx: Record<string, any>) {
|
||||
try {
|
||||
Object.keys(ctx).forEach((key) => {
|
||||
// @ts-ignore
|
||||
@ -46,7 +46,9 @@ export function useExpression(raw: string) {
|
||||
};
|
||||
}, [raw]);
|
||||
|
||||
const [state, setState] = useState<any>(expression);
|
||||
const [state, setState] = useState<any>(
|
||||
evalInContext(expression, useStore.getState())
|
||||
);
|
||||
|
||||
if (!dynamic) {
|
||||
return state;
|
||||
@ -65,3 +67,11 @@ export function useExpression(raw: string) {
|
||||
}
|
||||
|
||||
export const emitter = mitt<Record<string, any>>();
|
||||
|
||||
// EXPERIMENT: utils
|
||||
emitter.on("$utils", ({ name, parameters, ...rest }) => {
|
||||
console.log(name, parameters, rest);
|
||||
if (name === "alert") {
|
||||
window.alert(parameters);
|
||||
}
|
||||
});
|
||||
|
105
packages/runtime/src/traits/core/event.ts
Normal file
105
packages/runtime/src/traits/core/event.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import { useEffect, useMemo, useRef } from "react";
|
||||
import { createTrait } from "@meta-ui/core";
|
||||
import { nanoid } from "nanoid";
|
||||
import { debounce, throttle, delay } from "lodash";
|
||||
import { TraitImplementation } from "../../registry";
|
||||
import { emitter, evalInContext, useStore } from "../../store";
|
||||
|
||||
const useEventTrait: TraitImplementation<{
|
||||
events: Array<{
|
||||
event: string;
|
||||
componentId: string;
|
||||
method: {
|
||||
name: string;
|
||||
parameters: string;
|
||||
};
|
||||
wait: {
|
||||
type: "debounce" | "throttle" | "delay";
|
||||
time: number;
|
||||
};
|
||||
disabled: boolean | string;
|
||||
}>;
|
||||
}> = ({ events }) => {
|
||||
const hookId = useMemo(() => {
|
||||
return nanoid();
|
||||
}, []);
|
||||
const handlerMap = useRef<Record<string, Array<(parameters?: any) => void>>>(
|
||||
{}
|
||||
);
|
||||
useEffect(() => {
|
||||
const handler = (s: { name: string; parameters?: any }) => {
|
||||
if (!handlerMap.current[s.name]) {
|
||||
// maybe log?
|
||||
return;
|
||||
}
|
||||
handlerMap.current[s.name].forEach((fn) => fn(s.parameters));
|
||||
};
|
||||
emitter.on(hookId, handler);
|
||||
return () => {
|
||||
emitter.off(hookId, handler);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
// tear down previous handlers
|
||||
handlerMap.current = {};
|
||||
// setup current handlers
|
||||
for (const event of events) {
|
||||
const handler = () => {
|
||||
let disabled = false;
|
||||
const currentStoreState = useStore.getState();
|
||||
if (typeof event.disabled === "boolean") {
|
||||
disabled = event.disabled;
|
||||
} else if (typeof event.disabled === "string") {
|
||||
disabled = evalInContext(event.disabled, currentStoreState);
|
||||
}
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
emitter.emit(event.componentId, {
|
||||
name: event.method.name,
|
||||
parameters: evalInContext(event.method.parameters, currentStoreState),
|
||||
});
|
||||
};
|
||||
if (!handlerMap.current[event.event]) {
|
||||
handlerMap.current[event.event] = [];
|
||||
}
|
||||
handlerMap.current[event.event].push(
|
||||
event.wait.type === "debounce"
|
||||
? debounce(handler, event.wait.time)
|
||||
: event.wait.type === "throttle"
|
||||
? throttle(handler, event.wait.time)
|
||||
: event.wait.type === "delay"
|
||||
? () => delay(handler, event.wait.time)
|
||||
: handler
|
||||
);
|
||||
}
|
||||
}, [events]);
|
||||
|
||||
const hub = useMemo(() => {
|
||||
return {
|
||||
// HARDCODE
|
||||
onClick() {
|
||||
emitter.emit(hookId, { name: "click" });
|
||||
},
|
||||
};
|
||||
}, []);
|
||||
|
||||
return hub;
|
||||
};
|
||||
|
||||
export default {
|
||||
...createTrait({
|
||||
version: "core/v1",
|
||||
metadata: {
|
||||
name: "event",
|
||||
description: "export component events with advance features",
|
||||
},
|
||||
spec: {
|
||||
properties: [],
|
||||
state: {},
|
||||
methods: [],
|
||||
},
|
||||
}),
|
||||
impl: useEventTrait,
|
||||
};
|
58
packages/runtime/src/traits/core/state.ts
Normal file
58
packages/runtime/src/traits/core/state.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import { useEffect } from "react";
|
||||
import { createTrait } from "@meta-ui/core";
|
||||
import { TraitImplementation } from "../../registry";
|
||||
|
||||
const useStateTrait: TraitImplementation<{
|
||||
key: string;
|
||||
initialValue: any;
|
||||
}> = ({ key, initialValue, mergeState, subscribeMethods }) => {
|
||||
useEffect(() => {
|
||||
mergeState({ [key]: initialValue });
|
||||
|
||||
subscribeMethods({
|
||||
setValue(value) {
|
||||
mergeState({ [key]: value });
|
||||
},
|
||||
reset() {
|
||||
mergeState({ [key]: initialValue });
|
||||
},
|
||||
});
|
||||
}, []);
|
||||
};
|
||||
|
||||
export default {
|
||||
...createTrait({
|
||||
version: "core/v1",
|
||||
metadata: {
|
||||
name: "state",
|
||||
description: "add state to component",
|
||||
},
|
||||
spec: {
|
||||
properties: [
|
||||
{
|
||||
name: "key",
|
||||
type: "string",
|
||||
},
|
||||
{
|
||||
name: "initialValue",
|
||||
type: "any",
|
||||
},
|
||||
],
|
||||
state: {
|
||||
type: "any",
|
||||
},
|
||||
methods: [
|
||||
{
|
||||
name: "setValue",
|
||||
parameters: {
|
||||
type: "any",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "reset",
|
||||
},
|
||||
],
|
||||
},
|
||||
}),
|
||||
impl: useStateTrait,
|
||||
};
|
Loading…
Reference in New Issue
Block a user