@@ -34,6 +34,8 @@ Both the UI component library itself and the low-code editor support custom exte
You are in type safety both when developing Sunmao components and when using the Sunmao editor. Sunmao heavily uses Typescript and JSON schema for a great type system.
+For more details, read [Sunmao: A truly extensible low-code UI framework](./docs/en/what-is-sunmao.md).
+
## Directory Structure
Sunmao is a monorepo project that includes the following packages:
@@ -84,8 +86,8 @@ The user's responsibility is to use the Sunmao components encapsulated by develo
We have prepared two tutorials for different roles. The user only needs to read the user's tutorial, but the developer has to read both.
-- [User's Tutorial](./docs/zh/user.md)
-- [Developer's Tutorial](./docs/zh/developer.md)
+- [User's Tutorial](./docs/en/user.md)
+- [Developer's Tutorial](./docs/en/developer.md)
## Online Demo
diff --git a/docs/en/component.md b/docs/en/component.md
new file mode 100644
index 00000000..19bc50cc
--- /dev/null
+++ b/docs/en/component.md
@@ -0,0 +1,522 @@
+# Component Development Documentation
+
+A *Component* consists of two parts, a *Spec* and an *Implementation*; if you think of a *Component* as a class, a *Spec* is the interface of the class and an *Implementation* is the implementation of the class.
+
+- Spec: a data structure used to describe the meta information, parameters, state, and behavior of a Component
+- Implementation: The function that is specifically responsible for rendering HTML elements.
+
+## Component Development Tutorial
+
+Let's learn how to develop a *Component* by using an example of an Input Component. This Input Component has the following capabilities.
+
+- Configurable parameters such as placeholder, disabled, etc.
+- Allow external access to the current value
+- Can emit events such as `onBlur`
+- Allow updating its values from outside.
+- Allow insert child components, such as prefixes and suffixes, etc.
+
+### Writing a Component Spec
+
+A *Spec* is essentially a JSON that describes the parameters, behavior, and other information of a *Component*. All of our above information will be reflected in the *Spec*.
+
+First, let's look at this Input Component *Spec* example.
+
+```javascript
+{
+ version: "arco/v1",
+ metadata: {
+ name: "input",
+ displayName: "Input",
+ exampleProperties: {
+ placeholder: "Input here",
+ disabled: fasle,
+ },
+ },
+ spec: {
+ properties: Type.Object({
+ placeholder: Type.String(),
+ disabled: Type.Boolean(),
+ }),
+ state: Type.Object({
+ value: Type.String(),
+ }),
+ methods: {
+ updateValue: Type.Object({
+ value: Type.String(),
+ }),
+ },
+ slots: {
+ prefix: {
+ slotProps: Type.Object({}),
+ },
+ suffix: {
+ slotProps: Type.Object({}),
+ },
+ },
+ styleSlots: ["content"],
+ events: ["onBlur"],
+ },
+};
+```
+It may be confusing to face so many fields at first, let's explain the meaning of each field one by one.
+
+> For the detailed type and description of each parameter, please refer to the API reference below.
+
+#### Component Spec Metadata
+
+`metadata` is the meta-information of a *Component*, including information such as name and etc.
+
+First, let's take a look at the complete type definition of *Spec*:
+
+#### Component Spec Properties
+
+`properties` describe the parameter names and types that the Component can accept. Two parameters are defined here, `placeholder` and `disabled`, of type *String* and *Boolean* respectively.
+
+````
+properties: Type.Object({
+ placeholder: Type.String(),
+ disabled: Type.Boolean(),
+})
+````
+
+You may be unfamiliar with this way of declaring types. As mentioned earlier, *Spec* is essentially a JSON, but JSON can not declare types like Typescript, so when we want to declare types in *Spec*, we use [JSONSchema](https://json-schema.org/). JSONSchema itself is JSON but can be used to declare the type of a JSON data structure.
+
+But handwritten JSONSchema is very difficult, so we recommend using libraries, like [TypeBox](https://github.com/sinclairzx81/typebox) to assist in generating JSONSchema.
+
+#### Component Spec State
+
+`state` describes the state exposed by the *Component*. The input Component will only expose a `value`. The definition is similar to `properties`.
+
+```
+state: Type.Object({
+ value: Type.String(),
+})
+```
+
+#### Component Spec Method
+
+`methods` describe the methods exposed by the Component. Our Input is going to expose the `updateValue` method so that the outside world can update its value.
+
+The value corresponding to the key of `updateValue` in *Spec* is the acceptable parameter of `updateValue`, which is also defined by TypeBox.
+
+```
+methods: {
+ updateValue: Type.Object({
+ value: Type.String(),
+ }),
+}
+```
+
+#### Other properties of Component Spec
+
+`slots` represent the *Slots* reserved by the *Component*. Each *Slot* can insert child Components. `slotProps` represents additional props that this slot will pass to the child Component when the child Component renders.
+
+`styleSlots` represent the slots reserved by Component to insert styles. Generally, each Component needs to reserve a `content` *styleSlot*.
+
+`events` represent events that the Component can emit for the outside world to listen to.
+
+```
+slots: {
+ prefix: {
+ slotProps: Type.Object({}),
+ },
+ suffix: {
+ slotProps: Type.Object({}),
+ },
+},
+styleSlots: ["content"],
+events: ["onBlur"],
+```
+
+#### Component Spec example explanation
+
+Now let's look at the example at the beginning and explain it again.
+
+````javascript
+const InputSpec = {
+ version: 'arco/v1',
+ metadata: {
+ name: 'input',
+ displayName: 'Input',
+ exampleProperties: {
+ placeholder: 'Input here',
+ disabled: fade,
+ },
+ },
+ spec: {
+ properties: Type.Object({
+ placeholder: Type.String(),
+ disabled: Type.Boolean(),
+ }),
+ state: Type.Object({
+ value: Type.String(),
+ }),
+ methods: {
+ updateValue: Type.Object({
+ value: Type.String(),
+ }),
+ },
+ slots: {
+ prefix: {
+ slotProps: Type.Object({}),
+ },
+ suffix: {
+ slotProps: Type.Object({}),
+ },
+ },
+ styleSlots: ['content'],
+ events: ['onBlur'],
+ },
+};
+````
+
+This *Spec* declares an Input Component. It is part of the *arco/v1* component library and named input. Its only identifier is *arco/v1/input*.
+
+The properties of this *Component* are declared with TypeBox. Its properties contain two parameters, `placeholder` and `disabled`. It also exposes a state `value` to external access.
+
+In terms of behavior, it has an `updateValue` method that can allow externals to update their own value. As an Input, it also emits the `onBlur` event.
+
+It also has two slots for inserting child Components, representing `prefix` and `suffix` respectively. These two slots have no additional properties to pass. There is also a *styleSlot* for content where custom styles can be added.
+
+This is all the logic of this Input component. Let's take a look at how to implement the *Implementation* of this *Component*.
+
+### Component Implementation
+
+After completing the *Spec* of *Component*, we will develop the specific implementation of *Component*, which we call *Component Implementation*. In theory, a *Spec* can correspond to many components, just as an interface can correspond to the implementation of multiple classes.
+
+Component Implementation is responsible for the specific rendering work. It is essentially a function. Its parameters are more complicated, and we will introduce them one by one according to the actual needs of the Input Component.
+
+> Currently, Component Implementation must be a React functional component. In the future, we plan to let Sunmao support components using any technology stack, as long as the function returns a DOM element.
+
+#### Read parameters of Component
+
+First, the Component Implementation should accept the `properties` defined in the Spec, that is, `placeholder` and `disabled`. We can get it directly from the parameters. Then, we pass this parameter to an input JSX element and return it.
+
+```jsx
+const InputImpl = props => {
+ const { disabled, placeholder } = props;
+
+ return ;
+};
+````
+
+It's that simple! In fact, this is already a complete Component Implementation, but we still have many features to implement.
+
+#### Expose the state of the Component
+
+Our Input will expose its own state, which requires a Sunmao built-in function `mergeState`. This function will be automatically injected into the Component Implementation and can be read like `properties`, called as follows.
+
+```tsx
+const InputComponent = props => {
+ const { mergeState } = props;
+ const [value, setValue] = useState('');
+
+ // When the value changes, call the mergeState method.
+ // Each time mergeState is called, the latest value will be merged into the Sunmao state tree for other components to access.
+ useEffect(() => {
+ mergeState({
+ value,
+ });
+ }, [mergeState, value]);
+
+ return setValue(newVal)} />;
+};
+````
+
+#### Expose Component's methods
+
+Our Input also exposes its own method `updateValue`. This also requires the use of a built-in function `subscribeMethods`.
+
+```typescript
+const InputComponent = props => {
+ const { subscribeMethods } = props;
+ const [value, setValue] = useState('');
+
+ // When the dom element is mounted, call subscribeMethods to register the updateValue method.
+ // This way, the external Component can call updateValue to change the value of the input.
+ useEffect(() => {
+ subscribeMethods({
+ updateValue: ({ value: newValue }) => {
+ setValue(newValue);
+ },
+ });
+ }, [subscribeMethods]);
+
+ return ;
+};
+````
+
+#### Emit Event
+
+Our Input will also publish an `onBlur` event to notify other Components that it has lost focus. This requires another built-in parameter `callbackMap`.
+
+```jsx
+const InputComponent = props => {
+ const { callbackMap } = props;
+
+ // callbackMap already has the callback functions of the corresponding event, which can be called directly at the corresponding time.
+ const onBlur = () => {
+ if (callbackMap.onBlur) {
+ callbackMap.onBlur();
+ }
+ };
+
+ return ;
+};
+````
+
+#### Reserve the position of Slot and StyleSlot
+
+*Slot* and *StyleSlot* are slots that can insert custom content, and their positions need to be reserved in advance. They also need corresponding parameters, namely: `slotsElements` and `customStyle`. Both are js objects, and the corresponding content can be accessed through the names of slot and styleSlot.
+
+The content of `slotsElements` is a function that takes `slotProps` and returns JSX elements. `slotProps` is optional and defined in the Spec.
+
+`customStyle` is a map of styleSlot and CSS strings. Because it is a CSS string, it needs to be processed before it can be used. We recommend using `emotion` as a CSS-in-JS solution.
+
+```tsx
+const InputImpl = props => {
+ const { slotsElements, customStyle } = props;
+
+ return (
+
+ );
+};
+````
+
+> In fact, `customStyle` and `callbackMap` are actually parameters from Trait, but you don't need to know this for now. For details, please refer to the API documentation below.
+
+#### Expose DOM elements to Sunmao
+
+#### elementRef & getElement
+
+Here is one last step, which has nothing to do with the logic of the Component itself, but Sunmao needs to get the DOM element of the Component runtime to get this component in the Editor. So this step needs to pass the DOM element of the Component to Sunmao.
+
+Sunmao provides two methods to pass DOM elements: `elementRef` and `getElement`. The functions of the two methods are the same, but they are suitable for different scenarios. Just choose one implementation.
+
+If the Component is implemented by React, it is more convenient to use `elementRef`, just pass `elementRef` to the `ref` property of the React component. If this method does not work, you can only use the generic `getElement` method to register the DOM element of the component.
+
+```typescript
+const InputComponent = props => {
+ const { getElement } = props;
+ const ref = useRef(null);
+
+ useEffect(() => {
+ const ele = ref.current?.dom;
+ if (getElement && ele) {
+ getElement(ele);
+ }
+ }, [getElement, ref]);
+
+ return ;
+};
+
+// or
+
+const InputComponent = props => {
+ const { elementRef } = props;
+
+ return ;
+};
+````
+
+#### Complete Component Implementation
+
+Finally, we combine all the functions to implement all the logic of the Input Component Spec at the beginning.
+
+```tsx
+const InputImpl = props => {
+ const {
+ disabled,
+ placeholder,
+ elementRef,
+ slotsElements,
+ customStyle,
+ callbackMap,
+ mergeState,
+ subscribeMethods,
+ } = props;
+
+ const [value, setValue] = useState('');
+
+ useEffect(() => {
+ mergeState({
+ value,
+ });
+ }, [mergeState, value]);
+
+ useEffect(() => {
+ subscribeMethods({
+ updateValue: newValue => {
+ setValue(newValue);
+ },
+ });
+ }, [subscribeMethods]);
+
+ const onChange = e => {
+ setValue(e.target.value);
+ };
+
+ const onBlur = () => {
+ if (callbackMap.onBlur) {
+ callbackMap.onBlur();
+ }
+ };
+
+ return (
+
+ );
+};
+````
+
+### Combine Spec and Implementation
+
+After writing the *Spec* and *Implementation* of *Component*, the only step away from success is to encapsulate the two into a format acceptable to Sunmao runtime. This step is very simple, just call the `implementRuntimeComponent` function.
+
+````javascript
+import { implementRuntimeComponent } from '@sunmao-ui/runtime';
+
+const InputComponent = implementRuntimeComponent(InputSpec)(InputImpl);
+````
+
+Finally, add this component to lib and pass it to `initSunmaoUI` when Sunmao starts up and you're done.
+
+````javascript
+const lib: SunmaoLib = {
+ components: [InputComponent],
+ traits: [],
+ modules: [],
+ utilMethods: [],
+};
+````
+
+## Component API Documentation
+
+### Component Spec
+
+The first-level fields of Spec are relatively straightforward.
+
+| parameter name | type | description |
+| -------------- | ------------- | ------------------------------------------------------------ |
+| version | string | Component classification in Sunmao. The `version` of the same set of Components is usually the same. The format is "xxx/vx" , eg "`arco/v1`". |
+| kind | `"Component"` | Fixed, indicating that this is a Component Spec. |
+| metadata | | See below for details |
+| spec | | See below for details |
+
+#### Metadata of Component Spec
+
+Metadata contains the meta information of the Component.
+
+| parameter name | type | remarks |
+| ----------------- | -------------------- | ------------------------------------------------------------ |
+| name | string | The name of the Component. The `version` and `name` of a Component together constitute the unique identifier of the Component. |
+| description | string? | |
+| displayName | string? | The name to display in the Component list in the Editor. |
+| exampleProperties | Record | The initial `properties` for the Component when it was created in the Editor. |
+| annotations | Record? | Some fields can be custom declared. |
+
+#### Component Spec remaining fields
+
+When defining `properties` and `state`, we use [JSONSchema](https://json-schema.org/). JSONSchema itself is JSON but can be used to declare the type of a JSON data structure. With type help, Sunmao can checksum and autocomplete for parameters and expressions.
+
+| parameter name | type | remarks |
+| -------------- | --------------------------------------- | ------------------------------------------------------------ |
+| properties | JSONSchema | Parameters accepted by Component. |
+| state | JSONSchema | State exposed by Component. |
+| methods | Record | Method exposed by Component. The key is the name of the Method, and the value is the parameter of the Method. |
+| events | string[] | Events that the Component will emit. The array element is the name of the Event. |
+| slots | Record | Component reserved slots that can be inserted into child Component. |
+| styleSlots | string[] | Component Reserved slots for adding styles. |
+
+### Component Implementation Parameters
+
+The parameter of Component Implementation is essentially an object, but it is actually a combination of several components. It can be roughly divided into:
+
+- Properties declared in the Component Spec. This part is completely Component custom.
+- Sunmao Component API. This is what Sunmao injects into the Component.
+- Trait execution result. This is the result of the Trait passed to the component.
+- services. These are the individual service instances of the Sunmao runtime.
+
+| Parameter Name | Type | Remarks | Source |
+| --------------- | ------------------------------------------------------------ | ---------------------------------------------------------- | -------- |
+| component | ComponentSchema | Component's Schema | API |
+| app | ApplicationSchema | Schema of the entire Application | API |
+| slotsElements | Record ReactElement[]> | List of child Component, see below for details | API |
+| mergeState | (partialState: object) => void | See below for details | API |
+| subscribeMethod | (methodsMap: Record void>) => void | See below for details | API |
+| elementRef | React.Ref | see below | API |
+| getElement | (ele: HTMLElement) => void | See below for details | API |
+| services | object | Various service instances of Sunmao, see below for details | services |
+| customStyle | Record | Custom style from Trait, see below | Trait |
+| callbackMap | Record | Callback function from Trait, see below for details | Trait |
+
+#### Services
+
+Services are instances of Sunmao's various services, including state management, event monitoring, component registration, and more. These Services are globally unique instances.
+
+| parameter name | type | remarks |
+| ---------------- | ------------------------ | ------------------------------------------------------------ |
+| registry | Registry | All Sunmao Components, Traits, and Modules are registered on the Registry, where you can find their corresponding Spec and Implementation. |
+| stateManager | StateManager | StateManager manages Sunmao's global state store and also has the function of eval expression. |
+| globalHandlerMap | GlobalHandlerMap | GlobalHandlerMap manages all Component Method instances. |
+| apiService | ApiService | ApiService is the global event bus. |
+| eleMap | Map | eleMap stores all Component's DOM elements. |
+
+> ⚠️ In general, you do not need to use these services. They may only be used when implementing some special requirements.
+
+#### Sunmao Component API
+
+##### `mergeState`
+
+A Component can have its own local state, but if a Component exposes its own local state to other Sunmao components, the state must be merged into Sunmao's global state store through the `mergeState` function.
+
+When `mergeState` is called, all expressions referencing the state are updated immediately, as are their corresponding components.
+
+##### `subscribeMethods`
+
+The function of `subscribeMethods` is to register the behavior of components in Sunmao in the form of functions for other components to call.
+
+There is no limit to the Method registered by Component, it can accept custom parameters, and the parameter type should have been declared in Component Spec. Parameters will be passed by Sunmao when calling.
+
+#### Trait execution result
+
+All Trait execution results will be passed to Component as parameters. These parameters are generated according to the agreed interface. Trait and Component can only interact through this interface. **Components must handle the following parameters correctly**, otherwise, Components cannot interact with other Traits.
+
+##### `customStyle`
+
+In Sunmao, styles are expressed in CSS. customStyle is a map of styleSlot and CSS. You need to decide for yourself how to use CSS. Sunmao uses *emotion* as the runtime CSS-In-JS scheme, you can also choose your preferred library.
+
+**We agree that a Component must implement at least one `content` styleSlot as the default styleSlot. **
+
+##### `callbackMap`
+
+`callbackMap` is how components expose events to the outside world. It is a Map of Event names and callback functions. If another Component listens to a Component's Event, the event callback function will be passed to the Component through `callbackMap`. You need to call this callback function in the code corresponding to the Event, so that other Component can successfully listen to the Component's Event.
+
+#### Sunmao Runtime API
+
+Sunmao does not limit the internal logic and implementation of Component, but there are some interfaces that must be implemented, otherwise Component will not be able to interact with Sunmao. These interfaces will be passed to Component Implementation as parameters, the parameters are as follows:
+
+##### slotsElements
+
+`slotsElements` is a list of child Components in each slot. A Component can declare its own Slot, and each Slot is where the child Component is inserted.
+
+> If the Component has only one slot, we agree that the slot name is `content`.
+
+##### elementRef & getElement
+
+The role of these two APIs is to register the DOM elements rendered by the Component with Sunmao. Sunmao must obtain the DOM elements of each Component to implement some functions, such as the ability to highlight Components in the editor. Other Components and Traits can also use the Component's DOM elements to achieve functions.
\ No newline at end of file
diff --git a/docs/en/developer.md b/docs/en/developer.md
new file mode 100644
index 00000000..18dd7233
--- /dev/null
+++ b/docs/en/developer.md
@@ -0,0 +1,13 @@
+## Sunmao Developer Tutorial
+
+### [Installing Sunmao](./install.md)
+
+### [Component Development](./component.md)
+
+### [Trait Development](./trait.md)
+
+### Advanced (TODO)
+
+- [Expression](./expression)
+- `ExpressionOption` for Property
+- Life cycle
\ No newline at end of file
diff --git a/docs/en/expression.md b/docs/en/expression.md
new file mode 100644
index 00000000..74dba248
--- /dev/null
+++ b/docs/en/expression.md
@@ -0,0 +1,33 @@
+# Expression expression design
+
+# Supported usages
+
+`'{{ value }}'` '100'
+
+`'{{ value.toUppercase() }}'` 'ABC'
+
+`'Hello, {{ value }}!'` 'Hello, world'
+
+`'{{ $listItem.name }} is {{ $listItem.age }} years old'` 'Tom is 10 years old'
+
+`'{{ $listItem.name }} is in {{ root.listTitle }} list'` 'Tom is in UserList list'
+
+# nested expressions
+
+Expressions support nesting, which is useful in the context of lists and modules. E.g:
+
+`'{{ {{$listItem.value}}Input.value + {{$moduleId}}Fetch.value }}!'`
+
+The parser will `eval` and concatenate strings from the inside out.
+
+# some wrong usages
+
+`{{ [1,2,3] }}string` (result will be `'[Object Object]'`).
+
+# special keywords
+
+`$i` `$listItem` and `$moduleId` are keywords, used in lists and modules.
+
+# error handling
+
+When the expression cannot be parsed or an error is reported during the parsing process, the expression itself will be returned directly.
\ No newline at end of file
diff --git a/docs/en/install.md b/docs/en/install.md
new file mode 100644
index 00000000..cf244c7f
--- /dev/null
+++ b/docs/en/install.md
@@ -0,0 +1,114 @@
+# Install Sunmao Tutorial
+
+Sunmao is a low-code framework. You can use Sunmao's Cloud version, or you can deploy a Sunmao application yourself.
+
+### Install Sunmao
+
+Sunmao has two packages, runtime and editor. The runtime is the package used to run Sunmao applications, and the editor is used to run the Sunmao editor. The developer selects the corresponding package according to the needs of the scenario. If you are experiencing Sunmao for the first time, it is recommended to choose the editor.
+
+No matter whether you choose runtime or editor, you need to install react, because Sunmao runs based on react.
+
+#### Install Sunmao runtime
+
+````
+yarn add @sunmao-ui/runtime
+yarn add react
+yarn add react-dom
+````
+
+#### Install Sunmao editor
+
+````
+yarn add @sunmao-ui/editor
+yarn add react
+yarn add react-dom
+````
+
+### Start Sunmao
+
+The runtime and editor are started in roughly the same way, but with different method names and parameters.
+
+#### Start Sunmao runtime
+
+````
+import { initSunmaoUI } from "@sunmao-ui/runtime";
+import React from "react";
+import ReactDOM from "react-dom";
+
+const { App } = initSunmaoUIEditor({});
+
+ReactDOM.render(, document.getElementById("root"));
+````
+
+#### Start Sunmao editor
+
+````
+import { initSunmaoUIEditor } from "@sunmao-ui/editor";
+import React from "react";
+import ReactDOM from "react-dom";
+
+const { Editor } = initSunmaoUIEditor({});
+
+ReactDOM.render(, document.getElementById("root"));
+````
+
+### Import Sunmao Lib
+
+Sunmao Lib is a data structure composed of customized Sunmao Component, Trait, Module, and UtilMethod, and is an interface for developers to import custom component libraries.
+
+````javascript
+const lib = {
+ components: [],
+ traits: [],
+ modules: [],
+ utilMethods: [],
+};
+````
+
+When importing, just pass lib to `initSunmaoUI`.
+
+````javascript
+const { App } = initSunmaoUIEditor({ libs: [lib] });
+````
+
+For how to encapsulate custom Component, etc., you can refer to the Component development documentation.
+
+### API
+
+Sunmao's initialization function accepts some parameters. These parameters are optional and are used to implement some custom logic.
+
+#### Parameters of `initSunmaoUI`
+
+| Name | Type | Introduction |
+| ------------ | ------------------------------------ -------------------------------------------------- -------------- | ----------------------------------- -------------------- |
+| dependencies | Record | Dependencies that can be used when the expression is evaluated, can be any JS variable. |
+| hooks | { didMount?: () => void; didUpdate?: () => void; didDomUpdate?: () => void; } | Sunmao Application lifecycle hooks. |
+| libs | SunmaoLib[] | Used to load custom Component, Trait, Module, UtilMethod. |
+
+#### The return result of `initSunmaoUI`
+
+| Name | Type | Introduction |
+| -------- | --------------- | ------------------------ ------- |
+| App | React Component | The React component that renders the Sunmao app. |
+| Registry | Registry | Register custom Component, Trait. |
+
+#### Parameters of `initSunmaoUIEditor`
+
+| Name | Type | Introduction |
+| ------------------ | ------------------------------ -------------------------------------------------- --------------------- | ---------------------------- ------------------------------ |
+| App | React Component | The React component that renders the Sunmao app. |
+| widgets | ImplementedWidget[] | Custom editor widget. |
+| storageHandler | { onSaveApp?: (app: Application) => void, onSaveModules?: (module: Module[]) => void } | callback function for persistent storage of Application and Module. |
+| defaultApplication | Application | Default Application Schema. |
+| defaultModules | Module[] | Default Module. |
+| runtimeProps | Sunmao Runtime Props | Same as `initSunmaoUI` parameter |
+
+#### Return result of `initSunmaoUIEditor`
+
+Same as the return result of `initSunmaoUI`.
+
+### Parameters of App component
+
+| Name | Type | Introduction |
+| ------- | ----------- | ----------------------------- ----- |
+| options | Application | Application Schema for Sunmao applications. |
\ No newline at end of file
diff --git a/docs/en/trait.md b/docs/en/trait.md
new file mode 100644
index 00000000..b145cdd4
--- /dev/null
+++ b/docs/en/trait.md
@@ -0,0 +1,251 @@
+# Trait development documentation
+
+In this chapter you can learn how to implement a custom *Trait* through an example of a Timer Trait.
+
+*Trait* development and *Component* have many similarities.
+
+| | Trait | Component |
+| ------------------------------- | ----- | --------- |
+| Owned Sepc | ✅ | ✅ |
+| Has properties, state, methods | ✅ | ✅ |
+| Has Implementation | ✅ | ✅ |
+| Implementation is a function | ✅ | ✅ |
+
+*Trait* is essentially a function. The role of Trait is to increase the capabilities of the Component. If the object-oriented analogy is still used, Trait is a decorator of Component. For example, adding styles, adding state, etc.
+
+The main idea of Trait is to accept a series of parameters, run some logic, and return *TraitResult*, thereby enhancing the capabilities of the Component. Obviously, this is the logic of a pure function. Therefore, we recommend implementing Trait as a pure function. Even if it is not a pure function, the implementation needs to consider the result when the Trait is repeatedly executed, and try to avoid unexpected side effects.
+
+> ⚠️ Since Trait is a pure function, React hooks cannot be used in Trait.
+
+Since Trait doesn't have its own id, Trait and Component share id. That is to say, all the States and Methods exposed by the Trait will be added to the Component, and accessing and calling them is no different from what the Component implements itself.
+
+## Trait development tutorial
+
+Next, let's learn how to develop a Trait with an example. We want to implement a Timer Trait that has the following capabilities:
+
+- Specify a timer for a period of time, and an alert will pop up after the timer expires
+- Provides the function of clearing the timer
+- You can choose whether to start a timer immediately after the Component is mounted
+- Get timer status (`waiting`, `finished`, `stopped`)
+
+### Trait Spec
+
+#### Trait Spec Example
+
+First, let's write a Trait Spec and define some basic information for the Trait, such as version and name:
+
+```jsx
+{
+ version: 'custom/v1',
+ metadata: {
+ name: 'timer',
+ description: 'Create a timer to alert, and could be clear.',
+ },
+ spec: {
+ properties: PropsSpec, // see below for details
+ state: StateSpec, // see below for details
+ methods, // see below
+ },
+}
+````
+
+#### Trait Properties Spec
+
+Next, we can start thinking about how to design our Timer Trait. First, we need to design the parameters that need to be passed into the Timer Trait, that is, the Spec of Properties.
+
+First you need a `time` parameter to control the time of the timer, and a `content` parameter to display the content of the alert.
+
+In addition, you need to control whether to trigger immediately after the Component is mounted, so there is also an `immediate` parameter.
+
+So we can get our Props Spec:
+
+```jsx
+const PropsSpec = Type.Object({
+ time: Type.Number(),
+ content: Type.String(),
+ immediate: Type.Boolean(),
+});
+````
+
+> Trait's Props Spec is also defined by TypeBox.
+
+#### Trait State Spec
+
+Since Timer Trait also needs to expose state, we also need to add State Spec to define the State exposed by Timer Trait and its type.
+
+```jsx
+const StateSpec = Type.Object({
+ status: Type.KeyOf(
+ Type.Object({
+ waiting: Type.Boolean(),
+ finished: Type.Boolean(),
+ stopped: Type.Boolean(),
+ })
+ ),
+});
+````
+
+#### Trait Methods Spec
+
+We also need to provide Method to call externally to start or clear the timer, so we also need to define Method. Here we provide `start` and `stop` two methods.
+
+```jsx
+const methods = [
+ {
+ name: 'start',
+ parameters: Type.Object({}),
+ },
+ {
+ name: 'clear',
+ parameters: Type.Object({}),
+ },
+];
+````
+
+### Trait Factory & Implementation
+
+After defining the complete Trait Spec, we can start implementing the logic of Timer Trait. The first thing to do is to implement a factory function of a Trait, which returns the Implementation of the real Trait.
+
+In the factory function, we can declare some variables to store data, such as Timer Trait Here we need to create a Map to record the Timer corresponding to each Component so that it can be used to clear the timer.
+
+> Since Trait is essentially a pure function and cannot have state, Trait needs a factory function. The factory function provides a closure to store state for the trait. It should be noted that for the same Trait, all Trait Implementation instances share the same js closure.
+
+Let's take a look at the complete implementation code:
+
+```jsx
+const TimerTraitFactory = () => {
+ const map = new Map();
+
+ return props => {
+ const {
+ time,
+ content,
+ immediate,
+ services,
+ componentId,
+ subscribeMethods,
+ mergeState,
+ } = props;
+ // implement clear method
+ const clear = () => {
+ const timer = map.get(componentId);
+
+ if (timer) {
+ clearTimeout(timer);
+ mergeState({
+ status: 'stopped',
+ });
+ map.delete(componentId);
+ }
+ };
+ // implement start method
+ const start = () => {
+ clear();
+
+ const timer = setTimeout(() => {
+ // When the timer expires, an alert pops up
+ alert(content);
+ mergeState({
+ status: 'finished',
+ });
+ }, time || 0);
+
+ mergeState({
+ status: 'waiting',
+ });
+ map.set(componentId, timer);
+ };
+
+ // register the method to the component
+ subscribeMethods({
+ start,
+ clear,
+ });
+
+ // return Trait Result
+ // Use the componentDidMount and componentDidUnmount lifecycles to start and clear timers
+ return {
+ props: {
+ // start the timer after the Component hangs
+ componentDidMount: [
+ () => {
+ if (immediate) {
+ start();
+ }
+ },
+ ],
+ componentDidUnmount: [
+ () => {
+ clear();
+ },
+ ],
+ },
+ };
+ };
+};
+````
+
+### Create Trait
+
+Finally, generate the final Trait through the `implementRuntimeTrait` provided by `runtime`. So far, the complete implementation of Timer Trait is completed here.
+
+```jsx
+export default implementRuntimeTrait({
+ version: 'custom/v1',
+ metadata: {
+ name: 'timer',
+ description: 'Create a timer to alert, and could be clear.',
+ },
+ spec: {
+ properties: PropsSpec,
+ state: StateSpec,
+ methods,
+ },
+})(TimerTraitFactory);
+````
+
+## Trait API Documentation
+
+Trait Spec and Component are basically the same, with only a few differences.
+
+| parameter name | type | description |
+| -------- | --------- | ------------------------------ -------- |
+| version | string | |
+| kind | `"Trait"` | Fixed, indicating that this is a Trait Spec. |
+| metadata | | See below for details |
+| spec | | See below for details |
+
+### Metadata of Trait Spec
+
+The metadata content of Trait Spec is less than that of Component.
+
+| parameter name | type | remarks |
+| ----------- | ------------------- | ------------ |
+| name | string | Trait's name |
+| annotations | Record | |
+
+### Spec field of Trait Spec
+
+| parameter name | type | remarks |
+| ---------- | ------------------------------- | ---- |
+| properties | JSONSchema | |
+| state | JSONSchema | |
+| methods | Record | |
+
+### Parameters of Trait Implementation
+
+The parameters accepted by Trait Implementation are almost the same as Component, you can refer to the relevant chapter of Component.
+
+### TraitResult
+
+TraitResult is the most important part of Trait. It is the return result of the Trait function and will be passed directly to the Component as a parameter. For more details, please refer to the relevant chapters of the parameters of Component Implementation.
+
+TraitResult is an object with the following properties:
+
+| Parameter name | Type | Required | Description |
+| ------------------- | ------------------------ | ---- ---- | --------------------------------------------- ------------------- |
+| customStyle | Record | No | Style map passed to the Component. The value of the object should be a CSS string. |
+| callbackMap | Record | No | Map of callback functions for Component. Mainly used for Event Trait. |
+| componentDidUnmount | Function[] | No | Lifecycle hook. Will be executed after the Component is unloaded. |
+| componentDidMount | Function[] | No | Lifecycle hook. Will be executed after the Component is mounted. |
+| componentDidUpdate | Function[] | No | Lifecycle hook. Will be executed after the Component is updated. |
\ No newline at end of file
diff --git a/docs/en/user.md b/docs/en/user.md
new file mode 100644
index 00000000..aa8e7c7b
--- /dev/null
+++ b/docs/en/user.md
@@ -0,0 +1,113 @@
+# Sunmao User Tutorials
+
+## Basic concepts
+
+### Application
+
+Application is the biggest concept in Sunmao. An Application is a complete working front-end application.
+
+### Component
+
+Component is the smallest unit in Sunmao that is responsible for rendering UI.
+
+Components are mostly common UI components, such as Button, Input, etc., but can also be custom highly encapsulated business logic components.
+
+We can define the style and behavior of a Component by passing parameters to it.
+
+### DataSource
+
+DataSource is the component that carries data in SunmaoUI. DataSource can be of various types: global state, API request, LocalStorage ......
+
+Essentially, DataSource is a Component, but it does not render the UI, it only fetches data.
+
+### Trait
+
+Trait is a unique Sunmao concept; a Trait can be understood as a feature that a Component has when it is added to a Component. For example, if a **State Trait** is added to a Component, then a state is added to that Component. For another example, if a **Style Trait** is added to a Component, then a custom style is added to the Component.
+
+## Sunmao Development Guide
+
+### How to layout?
+
+Sunmao has a built-in Stack Component called `core/v1/stack`. With this component, you can adjust the spacing, orientation, and alignment of the Component ......
+
+> The Stack layout is essentially a Flex layout.
+
+When using the Stack layout, the order and parent-child relationship between Components are important. Here we will describe how to express the parent-child relationship of Components in Sunmao.
+
+#### Slot
+
+In Sunmao, the parent-child relationship is represented by the Slot.
+
+A Slot is a slot where a Component inserts its child components. Each Slot can insert an unlimited number of child components. A Component can have more than one Slot, so the number of Slots and their names are defined by the Component. Generally, we agree that if a Component has only one Slot, the default name of that Slot is `content`.
+
+In the following example.
+
+`modal6` has two Slots, `content` and `footer`. Neither Slot currently has a Component, so it is rendered as Empty.
+
+`button5` has no Slot, so there is no expanded arrow on the left.
+
+`hstack4` has a Slot, which is `content`. Because it has only one slot, the slot name is omitted from the display.
+
+`vstack2` has a Slot, which currently has a `text3` Component.
+
+
+
+### How do I configure parameters for a component?
+
+First, select a component in the canvas or in the left column, then the right column will show the component's parameter form.
+
+If you need to enter complex logic or use the state of another Component, you'll need to use expressions.
+
+#### expressions
+
+For example, you want to decide whether to disable a button based on the value of an Input. Then, you need to set the `disabled` value of the button to `{{input.value ! == ''}}`. This is an expression. Where `input` is the ID of the object you want to access, and you must use the correct ID to access the object. `value` is the state exposed by Input.
+
+The expression is a string enclosed by `{{ }}`. This string can be any legal Javascript code. Expressions support nesting and splicing with normal strings.
+
+For more expression usage, please refer to: [Expression Expression Design](. /expression.md).
+
+> For non-input controls on the form, you can switch this space to input by clicking the JS button next to the control, and then you can enter the expression.
+
+### How do you manage data?
+
+An application needs data in addition to UI. In Sunmao, the logic of data is generally managed by DataSource. sunmao has some common DataSource built in, such as State, LocalStorage, API and so on.
+
+In the expression, you can access the value of the DataSource just like a normal Component.
+
+| DataSource | Usage |
+| ------------ | -------------------------- |
+| state | Add global state |
+| localStorage | Save data to localStorage |
+| API | Sending HTTP requests |
+
+### How do I modify the style?
+
+To modify the style, you need to click the "+" button on the right side of the Style in the Component's form.
+
+Then select the Style Slot you want to add style to, which is the HTML element you want to modify the style.
+
+Finally, enter the CSS code in the black field and the style will take effect on the Component.
+
+
+
+### How do I listen to the component's events?
+
+Each Component has its own Event and Method; Event is the event that the Component will trigger, and Method is the behavior that the Component can be called on. For example, when a Button is clicked, it opens a Dialog, which requires the Button to emit an `onlick` Event and the Dialog to have an `openDialog ` Method.
+
+#### Event Handler
+
+To listen for an Event, you must add an Event Handler to the Component that emits the Event.
+
+The Event Handler has these elements.
+
+- The Event it is listening to
+- The id of the calling Component
+- The Method of the calling Component
+- The parameters passed when the Method is called
+- Some other configurations
+
+> Multiple Event Handlers can be added to a Component.
+
+The following is an example showing how to open a Dialog when a Button is clicked.
+
+
diff --git a/docs/en/what-is-sunmao-ui.md b/docs/en/what-is-sunmao.md
similarity index 61%
rename from docs/en/what-is-sunmao-ui.md
rename to docs/en/what-is-sunmao.md
index 29610bf3..79044e75 100644
--- a/docs/en/what-is-sunmao-ui.md
+++ b/docs/en/what-is-sunmao.md
@@ -1,4 +1,4 @@
-# sunmao-ui: A truly extensible low-code UI framework
+# Sunmao: A truly extensible low-code UI framework
Although more and more people are becoming interested in low-code development, they still have some reservations.
@@ -8,37 +8,37 @@ That is reasonable because no user wants to be told that they cannot implement c
Some products, wisely, limit the use of low-code to specific areas, such as internal tools and landing pages, where users value productivity over flexibility. It works until developers want to boost their productivity in more general scenarios.
-That's why we started working on sunmao-ui, an open-source low-code UI framework with a focus on extensibility.
+That's why we started working on Sunmao, an open-source low-code UI framework with a focus on extensibility.
## Design principles
-Sunmao is a Chinese phrase '榫卯' that refers to one of the oldest and most durable wooden architectural techniques used in Chinese history. We love this name because it represents how we put together building blocks and make solid applications in sunmao-ui.
+Sunmao is a Chinese phrase '榫卯' that refers to one of the oldest and most durable wooden architectural techniques used in Chinese history. We love this name because it represents how we put together building blocks and make solid applications in Sunmao.
-Several design principles are followed while developing sunmao-ui to ensure proper abstraction.
+Several design principles are followed while developing Sunmao to ensure proper abstraction.
### 1. Clarify the jobs of different roles
-Sunmao-ui's aha moment came when we learned that persons who develop applications are normally separated into two groups: component developers and application builders.
+Sunmao's aha moment came when we learned that persons who develop applications are normally separated into two groups: component developers and application builders.
-Component developers are concerned with code quality, performance, and user experience when creating reusable components. After creating a component in their favourite way, they can wrap it as a sunmao-ui component and register it to the platform.
+Component developers are concerned with code quality, performance, and user experience when creating reusable components. After creating a component in their favourite way, they can wrap it as a Sunmao component and register it to the platform.
-Application builders pick reusable components and incorporate business logic into them. They can complete this task quickly and effectively in sunmao-ui with much less code.
+Application builders pick reusable components and incorporate business logic into them. They can complete this task quickly and effectively in Sunmao with much less code.
Applications are made every hour, but components are created significantly less frequently. With sunmao-roles ui's division, users may delegate component creation to senior frontend developers while junior frontend developers, backend developers, and even non-coders build and iterate apps.
### 2. Leverage the power of coding, not limit
-As previously stated, sunmao-ui will not compel users to create applications only with simple components like buttons and inputs. Instead, component developers may register sophisticated, domain-specific components that meet your requirements and follow your design system.
+As previously stated, Sunmao will not compel users to create applications only with simple components like buttons and inputs. Instead, component developers may register sophisticated, domain-specific components that meet your requirements and follow your design system.
-We also put a lot of effort to ensure that sunmao-ui's implementation does not impose any limit on users. For example, many low-code tools' GUI editors offer highlight when you hover over or select a component. Some of them implement this by wrapping the component with a `
` element and styling it.
+We also put a lot of effort to ensure that Sunmao's implementation does not impose any limit on users. For example, many low-code tools' GUI editors offer highlight when you hover over or select a component. Some of them implement this by wrapping the component with a `
` element and styling it.
-However, this is unsatisfactory for sunmao-ui because it increases the number of DOM nodes, changes the hirarchy of DOM nodes, and prevents components from having inline layout.
+However, this is unsatisfactory for Sunmao because it increases the number of DOM nodes, changes the hirarchy of DOM nodes, and prevents components from having inline layout.
Although It took us longer to develop a solution that overcames all of the drawbacks, but we believe it was worthwhile because it does not limit the power of coding.
### 3. Extensible in every aspect
-Sunmao-ui contains three layers: the core spec, the runtime, and the editor.
+Sunmao contains three layers: the core spec, the runtime, and the editor.
The schema of a component or an application is described in the core spec. Aside from the usual fields, users can add annotations and use them later in the runtime or editor.
@@ -48,13 +48,13 @@ Users can also change how a component's property is inputted and displayed in th
### 4. Concentration rather than divergence
-Instead of creating a full-stack low-code solution, we concentrate on the UI part, and sunmao-ui is currently just available on the web.
+Instead of creating a full-stack low-code solution, we concentrate on the UI part, and Sunmao is currently just available on the web.
Since we believe that backend technologies are fast improving these days ,creating a UI-only solution that can be utilized with any type of backend service would provide our users with the most flexibility.
## How does it work
-We believe that web UI development is already quite mature, and sunmao-ui just adds a few necessary primitives for the low-code scenario, namely:
+We believe that web UI development is already quite mature, and Sunmao just adds a few necessary primitives for the low-code scenario, namely:
- reactive
- events and methods
@@ -65,7 +65,7 @@ We believe that web UI development is already quite mature, and sunmao-ui just a
### Respond to the latest state
-Reactive means when a state changes, all the UIs that depend on it will re-render automatically. Sunmao-ui creates a performant, reactive state store in which all components expose their state and can access the state of others.
+Reactive means when a state changes, all the UIs that depend on it will re-render automatically. Sunmao creates a performant, reactive state store in which all components expose their state and can access the state of others.
For example, we can access demo input's value state in the demo text component:
@@ -81,7 +81,7 @@ Modern UI frameworks emphasize state-driven and declarative concepts, which are
If you want to switch to the dark theme when you click the theme button, all you need is an `onClick` event and a `changeTheme` method.
-Sunmao-ui's spec lets every component declare the events they will dispatch and a set of immersive methods for interacting with itself from any other components.
+Sunmao's spec lets every component declare the events they will dispatch and a set of immersive methods for interacting with itself from any other components.
To add an event handler as follows:
@@ -91,7 +91,7 @@ To add an event handler as follows:
When we develop an application, we usually end up with a hierarchical structure of components, such as a DOM tree.
-Sunmao-ui's schema on the other hand, is straightforward to store and modify because we utilize an array to contain all the components without nesting. As a result, we introduce slots to aid in the organization of the hierarchy.
+Sunmao's schema on the other hand, is straightforward to store and modify because we utilize an array to contain all the components without nesting. As a result, we introduce slots to aid in the organization of the hierarchy.
To declare and implement slots in a component as follows:
@@ -115,7 +115,7 @@ To insert customized styles to a style slot:
### Live in a type-safe world
-We at sunmao-ui feel that type-safe will increase DX and efficiency for component developers as well as application builders. As a result, typescript and JSON schema are heavily used in our implementation.
+We at Sunmao feel that type-safe will increase DX and efficiency for component developers as well as application builders. As a result, typescript and JSON schema are heavily used in our implementation.
If you use typescript to create a component, we will infer types from the JSON schema, which will help you write safe code.
@@ -133,14 +133,14 @@ We believe that providing props like `dataUrl`, `hidden`, or `handlers` to every
### A extensible GUI editor
-Sunmao-ui's GUI editor will read all of the components' specs and generate a form based on its JSON schema description.
+Sunmao's GUI editor will read all of the components' specs and generate a form based on its JSON schema description.
-If some of the form widgets require extensive customization, component developers can implement their own widgets and allow specific components to use them. A detailed design of the customized widgets can be found [here](https://github.com/webzard-io/sunmao-ui/issues/313).
+If some of the form widgets require extensive customization, component developers can implement their own widgets and allow specific components to use them. A detailed design of the customized widgets can be found [here](https://github.com/webzard-io/Sunmao/issues/313).
-## Sunmao-ui is open
+## Sunmao is open
-Sunmao-ui is an open-source project from day 1. But when we say 'open,' we don't just mean open-sourcing the code under the MIT license.
+Sunmao is an open-source project from day 1. But when we say 'open,' we don't just mean open-sourcing the code under the MIT license.
-Although sunmao-ui starts as an internal project from SmartX, we chose to place all the proposals, discussions, and design decisions into our public repo rather than any internal channels. Because we believe that 'be open' is the cornerstone of sunmao-ui, and that this project will shine as more developers construct their own low-code solution on top of sunmao-ui.
+Although Sunmao starts as an internal project from SmartX, we chose to place all the proposals, discussions, and design decisions into our public repo rather than any internal channels. Because we believe that 'be open' is the cornerstone of Sunmao, and that this project will shine as more developers construct their own low-code solution on top of Sunmao.
-If you're interested in sunmao-ui, please visit our Github repository and join the slack channel to stay connected with the community.
+If you're interested in Sunmao, please visit our Github repository and join the slack channel to stay connected with the community.
diff --git a/docs/images/eventHandler-en.png b/docs/images/eventHandler-en.png
new file mode 100644
index 00000000..11c28621
Binary files /dev/null and b/docs/images/eventHandler-en.png differ
diff --git a/docs/zh/README.md b/docs/zh/README.md
index e34c4c88..cf6debf6 100644
--- a/docs/zh/README.md
+++ b/docs/zh/README.md
@@ -2,7 +2,7 @@