mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-06 21:40:23 +08:00
Merge branch 'main' of github.com:webzard-io/sunmao-ui into feat/arco
This commit is contained in:
commit
08376c41ae
145
examples/list/listComponent.json
Normal file
145
examples/list/listComponent.json
Normal file
@ -0,0 +1,145 @@
|
||||
{
|
||||
"modules": [],
|
||||
"app": {
|
||||
"version": "example/v1",
|
||||
"metadata": {
|
||||
"name": "list_component",
|
||||
"description": "list component example"
|
||||
},
|
||||
"spec": {
|
||||
"components": [
|
||||
{
|
||||
"id": "root",
|
||||
"type": "chakra_ui/v1/root",
|
||||
"properties": {},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/state",
|
||||
"properties": {
|
||||
"key": "listTitle",
|
||||
"initialValue": "Customers List"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/arrayState",
|
||||
"properties": {
|
||||
"key": "listData",
|
||||
"initialValue": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Tom",
|
||||
"email": "tom@deck.com"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "Jack",
|
||||
"email": "jack@conversation.com"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "Pony",
|
||||
"email": "pony@response.com"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Peter",
|
||||
"email": "peter@example.com"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "John",
|
||||
"email": "john@widget.org"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "list",
|
||||
"type": "core/v1/list",
|
||||
"properties": {
|
||||
"listData": "{{ root.listData }}"
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "root",
|
||||
"slot": "root"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "listSlotText",
|
||||
"type": "core/v1/text",
|
||||
"properties": {
|
||||
"value": {
|
||||
"raw": "{{$slot.$listItem.name}}",
|
||||
"format": "md"
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "list",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "listSlotButton",
|
||||
"type": "chakra_ui/v1/button",
|
||||
"properties": {
|
||||
"text": {
|
||||
"raw": "Click Me",
|
||||
"format": "md"
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "$utils",
|
||||
"method": {
|
||||
"name": "chakra_ui/v1/openToast",
|
||||
"parameters": {
|
||||
"id": "createSuccessToast",
|
||||
"title": "Tost",
|
||||
"description": "Hello, {{$slot.$listItem.name}}!",
|
||||
"position": "top",
|
||||
"duration": null,
|
||||
"isClosable": true
|
||||
}
|
||||
},
|
||||
"wait": {},
|
||||
"disabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "list",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
@ -1,133 +1,135 @@
|
||||
{
|
||||
"modules": [{
|
||||
"version": "core/v1",
|
||||
"kind": "Module",
|
||||
"parsedVersion": {
|
||||
"category": "core/v1",
|
||||
"value": "littleItem"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "littleItem"
|
||||
},
|
||||
"spec": {
|
||||
"properties": {},
|
||||
"events": ["onEdit"],
|
||||
"stateMap": {
|
||||
"value": "{{ $moduleId }}__input.value"
|
||||
}
|
||||
},
|
||||
"impl": [
|
||||
{
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"type": "chakra_ui/v1/hstack",
|
||||
"modules": [
|
||||
{
|
||||
"version": "custom/v1",
|
||||
"kind": "Module",
|
||||
"parsedVersion": {
|
||||
"category": "custom/v1",
|
||||
"value": "listItem"
|
||||
},
|
||||
"metadata": {
|
||||
"name": "listItem"
|
||||
},
|
||||
"spec": {
|
||||
"properties": {},
|
||||
"traits": []
|
||||
"events": ["onEdit"],
|
||||
"stateMap": {
|
||||
"value": "{{ $moduleId }}__input.value"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "{{ $moduleId }}__text",
|
||||
"type": "core/v1/text",
|
||||
"properties": {
|
||||
"value": {
|
||||
"raw": "**{{value}}**",
|
||||
"format": "md"
|
||||
}
|
||||
"impl": [
|
||||
{
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"type": "chakra_ui/v1/hstack",
|
||||
"properties": {},
|
||||
"traits": []
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "{{ $moduleId }}__inputValueText",
|
||||
"type": "core/v1/text",
|
||||
"properties": {
|
||||
"value": {
|
||||
"raw": "**{{ {{ $moduleId }}__input.value }}**",
|
||||
"format": "md"
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "{{ $moduleId }}__input",
|
||||
"type": "chakra_ui/v1/input",
|
||||
"properties": {},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "{{ $moduleId }}__button",
|
||||
"type": "chakra_ui/v1/button",
|
||||
"properties": {
|
||||
"text": {
|
||||
"raw": "click{{value}}",
|
||||
"format": "md"
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "$module",
|
||||
"method": {
|
||||
"name": "onEdit",
|
||||
"parameters": {
|
||||
"moduleId": "{{$moduleId}}"
|
||||
}
|
||||
},
|
||||
"wait": {},
|
||||
"disabled": false
|
||||
}
|
||||
]
|
||||
{
|
||||
"id": "{{ $moduleId }}__text",
|
||||
"type": "core/v1/text",
|
||||
"properties": {
|
||||
"value": {
|
||||
"raw": "**{{value}}**",
|
||||
"format": "md"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}],
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "{{ $moduleId }}__inputValueText",
|
||||
"type": "core/v1/text",
|
||||
"properties": {
|
||||
"value": {
|
||||
"raw": "**{{ {{ $moduleId }}__input.value }}**",
|
||||
"format": "md"
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "{{ $moduleId }}__input",
|
||||
"type": "chakra_ui/v1/input",
|
||||
"properties": {},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "{{ $moduleId }}__button",
|
||||
"type": "chakra_ui/v1/button",
|
||||
"properties": {
|
||||
"text": {
|
||||
"raw": "click{{value}}",
|
||||
"format": "md"
|
||||
}
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/event",
|
||||
"properties": {
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onClick",
|
||||
"componentId": "$module",
|
||||
"method": {
|
||||
"name": "onEdit",
|
||||
"parameters": {
|
||||
"moduleId": "{{$moduleId}}"
|
||||
}
|
||||
},
|
||||
"wait": {},
|
||||
"disabled": false
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "{{ $moduleId }}__hstack",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"app": {
|
||||
"version": "example/v1",
|
||||
"version": "sunmao/v1",
|
||||
"kind": "Application",
|
||||
"metadata": {
|
||||
"name": "list_component",
|
||||
"description": "list component example"
|
||||
"name": "some App"
|
||||
},
|
||||
"spec": {
|
||||
"components": [
|
||||
@ -140,7 +142,7 @@
|
||||
"type": "core/v1/state",
|
||||
"properties": {
|
||||
"key": "listTitle",
|
||||
"initialValue": "客户列表"
|
||||
"initialValue": "Customers List"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -150,28 +152,28 @@
|
||||
"initialValue": [
|
||||
{
|
||||
"id": 1,
|
||||
"name": "马云",
|
||||
"email": "jack.ma@deck.com"
|
||||
"name": "Tom",
|
||||
"email": "tom@deck.com"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"name": "马化腾",
|
||||
"email": "pony.ma@conversation.com"
|
||||
"name": "Jack",
|
||||
"email": "jack@conversation.com"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"name": "李彦宏",
|
||||
"email": "robin.li@response.com"
|
||||
"name": "Pony",
|
||||
"email": "pony@response.com"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "张一鸣",
|
||||
"email": "yiming.zhang@example.com"
|
||||
"name": "Peter",
|
||||
"email": "peter@example.com"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "王兴",
|
||||
"email": "xing.wang@widget.org"
|
||||
"name": "John",
|
||||
"email": "john@widget.org"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -180,12 +182,12 @@
|
||||
},
|
||||
{
|
||||
"id": "list",
|
||||
"type": "chakra_ui/v1/list",
|
||||
"type": "core/v1/list",
|
||||
"properties": {
|
||||
"listData": "{{ root.listData }}",
|
||||
"template": {
|
||||
"id": "littleItem{{$listItem.id}}",
|
||||
"type": "core/v1/littleItem",
|
||||
"id": "listItem{{$listItem.id}}",
|
||||
"type": "custom/v1/listItem",
|
||||
"properties": {
|
||||
"value": "{{$listItem.name}}"
|
||||
},
|
||||
@ -214,6 +216,47 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "moduleContainer2",
|
||||
"type": "core/v1/moduleContainer",
|
||||
"properties": {
|
||||
"id": "listItem{{$slot.$listItem.id}}",
|
||||
"type": "custom/v1/listItem",
|
||||
"properties": {
|
||||
"value": "{{$slot.$listItem.name}}"
|
||||
},
|
||||
"handlers": [
|
||||
{
|
||||
"type": "onEdit",
|
||||
"componentId": "$utils",
|
||||
"method": {
|
||||
"name": "chakra_ui/v1/openToast",
|
||||
"parameters": {
|
||||
"position": "top",
|
||||
"duration": 1000,
|
||||
"title": "Hello,{{$slot.$listItem.name}}!",
|
||||
"description": "",
|
||||
"isClosable": false,
|
||||
"variant": "subtle",
|
||||
"status": "info",
|
||||
"id": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"type": "core/v1/slot",
|
||||
"properties": {
|
||||
"container": {
|
||||
"id": "list",
|
||||
"slot": "content"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -34,6 +34,7 @@
|
||||
"@sunmao-ui/core": "^0.6.0",
|
||||
"@sunmao-ui/editor-sdk": "^0.2.0",
|
||||
"@sunmao-ui/runtime": "^0.6.0",
|
||||
"@sunmao-ui/shared": "^0.1.0",
|
||||
"eslint-plugin-react-hooks": "^4.3.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react": "^17.0.2",
|
||||
|
@ -4,7 +4,7 @@ import { css, cx } from '@emotion/css';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
|
||||
import { PaginationPropsSpec as BasePaginationPropsSpec } from '../generated/types/Pagination';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { useStateValue } from '../hooks/useStateValue';
|
||||
|
||||
const PaginationPropsSpec = Type.Object(BasePaginationPropsSpec);
|
||||
const PaginationStateSpec = Type.Object({
|
||||
|
@ -5,7 +5,7 @@ import { Type, Static } from '@sinclair/typebox';
|
||||
import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
|
||||
import { RadioPropsSpec as BaseRadioPropsSpec } from '../generated/types/Radio';
|
||||
import { useEffect } from 'react';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { useStateValue } from '../hooks/useStateValue';
|
||||
|
||||
const RadioPropsSpec = Type.Object({
|
||||
...BaseRadioPropsSpec,
|
||||
|
@ -6,7 +6,7 @@ import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
|
||||
import { SelectPropsSpec as BaseSelectPropsSpec } from '../generated/types/Select';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { SelectHandle } from '@arco-design/web-react/es/Select/interface';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { useStateValue } from '../hooks/useStateValue';
|
||||
|
||||
const SelectPropsSpec = Type.Object({
|
||||
...BaseSelectPropsSpec,
|
||||
|
@ -4,7 +4,7 @@ import { css } from '@emotion/css';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
|
||||
import { SwitchPropsSpec as BaseSwitchPropsSpec } from '../generated/types/Switch';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { useStateValue } from '../hooks/useStateValue';
|
||||
|
||||
const SwitchPropsSpec = Type.Object({
|
||||
...BaseSwitchPropsSpec,
|
||||
|
@ -1,4 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { sortBy } from 'lodash-es';
|
||||
import { ResizeCallbackData } from 'react-resizable';
|
||||
import { TableInstance } from '@arco-design/web-react/es/Table/table';
|
||||
import { ColumnProps } from '@arco-design/web-react/es/Table';
|
||||
import { RefInputType } from '@arco-design/web-react/es/Input/interface';
|
||||
import {
|
||||
Button,
|
||||
Link,
|
||||
@ -11,19 +18,14 @@ import {
|
||||
LIST_ITEM_INDEX_EXP,
|
||||
ModuleRenderer,
|
||||
implementRuntimeComponent,
|
||||
ImplWrapper,
|
||||
} from '@sunmao-ui/runtime';
|
||||
import { css } from '@emotion/css';
|
||||
import { sortBy } from 'lodash-es';
|
||||
import { Type, Static } from '@sinclair/typebox';
|
||||
import { ResizableTitle } from './ResizableTitle';
|
||||
|
||||
import { FALLBACK_METADATA, getComponentProps } from '../../sunmao-helper';
|
||||
import { TablePropsSpec, ColumnSpec } from '../../generated/types/Table';
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { TableInstance } from '@arco-design/web-react/es/Table/table';
|
||||
import { ColumnProps } from '@arco-design/web-react/es/Table';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { ResizableTitle } from './ResizableTitle';
|
||||
import { RefInputType } from '@arco-design/web-react/es/Input/interface';
|
||||
import { ResizeCallbackData } from 'react-resizable';
|
||||
import { useStateValue } from '../../hooks/useStateValue';
|
||||
|
||||
const TableStateSpec = Type.Object({
|
||||
clickedRow: Type.Optional(Type.Any()),
|
||||
@ -80,6 +82,7 @@ export const exampleProperties: Static<typeof TablePropsSpec> = {
|
||||
type: 'text',
|
||||
filter: true,
|
||||
displayValue: '',
|
||||
componentSlotIndex: 0,
|
||||
},
|
||||
{
|
||||
title: 'Salary',
|
||||
@ -89,6 +92,7 @@ export const exampleProperties: Static<typeof TablePropsSpec> = {
|
||||
filter: false,
|
||||
type: 'text',
|
||||
displayValue: '',
|
||||
componentSlotIndex: 0,
|
||||
},
|
||||
{
|
||||
title: 'Link',
|
||||
@ -97,19 +101,7 @@ export const exampleProperties: Static<typeof TablePropsSpec> = {
|
||||
filter: true,
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
displayValue: '',
|
||||
},
|
||||
{
|
||||
title: 'CustomComponent',
|
||||
dataIndex: 'customComponent',
|
||||
type: 'module',
|
||||
filter: false,
|
||||
module: {
|
||||
id: 'clistItemName-{{$listItem.id}}',
|
||||
handlers: [],
|
||||
properties: [],
|
||||
type: 'core/v1/text',
|
||||
},
|
||||
displayValue: '',
|
||||
componentSlotIndex: 0,
|
||||
},
|
||||
],
|
||||
data: Array(13)
|
||||
@ -156,7 +148,14 @@ export const Table = implementRuntimeComponent({
|
||||
properties: TablePropsSpec,
|
||||
state: TableStateSpec,
|
||||
methods: {},
|
||||
slots: {},
|
||||
slots: {
|
||||
content: {
|
||||
slotProps: Type.Object({
|
||||
[LIST_ITEM_EXP]: Type.Any(),
|
||||
[LIST_ITEM_INDEX_EXP]: Type.Number(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
styleSlots: ['content'],
|
||||
events: ['onRowClick', 'onSearch', 'onPageChange', 'onFilter', 'onSort', 'onChange'],
|
||||
},
|
||||
@ -355,6 +354,46 @@ export const Table = implementRuntimeComponent({
|
||||
/>
|
||||
);
|
||||
break;
|
||||
|
||||
case 'component':
|
||||
const childrenSchema = app.spec.components.filter(c => {
|
||||
return c.traits.find(
|
||||
t =>
|
||||
t.type === 'core/v1/slot' &&
|
||||
(t.properties.container as any).id === component.id
|
||||
);
|
||||
});
|
||||
|
||||
const childSchema = childrenSchema[evaledColumn.componentSlotIndex || 0];
|
||||
if (!childSchema) {
|
||||
return (
|
||||
<div>
|
||||
Cannot find child with index {column.componentSlotIndex} in slot.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const _childrenSchema = {
|
||||
...childSchema,
|
||||
id: `${component.id}_${childSchema.id}_${index}`,
|
||||
};
|
||||
|
||||
colItem = (
|
||||
<ImplWrapper
|
||||
key={_childrenSchema.id}
|
||||
component={_childrenSchema}
|
||||
app={app}
|
||||
services={services}
|
||||
childrenMap={{}}
|
||||
isInModule
|
||||
evalListItem
|
||||
slotProps={{
|
||||
[LIST_ITEM_EXP]: record,
|
||||
[LIST_ITEM_INDEX_EXP]: index,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const text = evaledColumn.displayValue || value;
|
||||
colItem = <span title={column.ellipsis ? text : ''}>{text}</span>;
|
||||
@ -365,7 +404,15 @@ export const Table = implementRuntimeComponent({
|
||||
return newColumn;
|
||||
})
|
||||
);
|
||||
}, [cProps.columns]);
|
||||
}, [
|
||||
app,
|
||||
cProps.columns,
|
||||
callbackMap,
|
||||
component.id,
|
||||
component.properties.columns,
|
||||
services,
|
||||
useDefaultFilter,
|
||||
]);
|
||||
|
||||
const handleChange = (
|
||||
pagination: PaginationProps,
|
||||
|
@ -5,7 +5,7 @@ import { Type, Static } from '@sinclair/typebox';
|
||||
import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
|
||||
import { TabsPropsSpec as BaseTabsPropsSpec } from '../generated/types/Tabs';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { useStateValue } from '../hooks/useStateValue';
|
||||
|
||||
const TabsPropsSpec = Type.Object(BaseTabsPropsSpec);
|
||||
const TabsStateSpec = Type.Object({
|
||||
|
@ -6,7 +6,7 @@ import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
|
||||
import { TextAreaPropsSpec as BaseTextAreaPropsSpec } from '../generated/types/TextArea';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { RefInputType } from '@arco-design/web-react/es/Input/interface';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { useStateValue } from '../hooks/useStateValue';
|
||||
|
||||
const TextAreaPropsSpec = Type.Object({
|
||||
...BaseTextAreaPropsSpec,
|
||||
|
@ -6,7 +6,7 @@ import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
|
||||
import { TreeSelectPropsSpec as BaseTreeSelectPropsSpec } from '../generated/types/TreeSelect';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { RefTreeSelectType } from '@arco-design/web-react/es/TreeSelect';
|
||||
import { useStateValue } from 'src/hooks/useStateValue';
|
||||
import { useStateValue } from '../hooks/useStateValue';
|
||||
|
||||
const TreeSelectPropsSpec = Type.Object(BaseTreeSelectPropsSpec);
|
||||
const TreeSelectStateSpec = Type.Object({
|
||||
|
209
packages/arco-lib/src/examples/pages/table/customComponent.ts
Normal file
209
packages/arco-lib/src/examples/pages/table/customComponent.ts
Normal file
@ -0,0 +1,209 @@
|
||||
import { Application } from '@sunmao-ui/core';
|
||||
|
||||
export const customComponent: Application = {
|
||||
version: 'sunmao/v1',
|
||||
kind: 'Application',
|
||||
metadata: {
|
||||
name: 'some App',
|
||||
},
|
||||
spec: {
|
||||
components: [
|
||||
{
|
||||
id: 'table',
|
||||
type: 'arco/v1/table',
|
||||
properties: {
|
||||
columns: [
|
||||
{
|
||||
title: 'Name',
|
||||
dataIndex: 'name',
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
type: 'text',
|
||||
filter: true,
|
||||
displayValue: '',
|
||||
componentSlotIndex: 0,
|
||||
},
|
||||
{
|
||||
title: 'Salary',
|
||||
dataIndex: 'salary',
|
||||
sorter: true,
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
filter: false,
|
||||
type: 'text',
|
||||
displayValue: '',
|
||||
componentSlotIndex: 0,
|
||||
},
|
||||
{
|
||||
title: 'Link',
|
||||
dataIndex: 'link',
|
||||
type: 'link',
|
||||
filter: true,
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
displayValue: '',
|
||||
componentSlotIndex: 0,
|
||||
},
|
||||
{
|
||||
title: 'Hello',
|
||||
type: 'component',
|
||||
componentSlotIndex: 0,
|
||||
dataIndex: '',
|
||||
displayValue: '',
|
||||
filter: false,
|
||||
},
|
||||
],
|
||||
data: [
|
||||
{
|
||||
key: 'key 0',
|
||||
name: 'Naomi Cook0',
|
||||
link: 'link-A',
|
||||
salary: 272,
|
||||
},
|
||||
{
|
||||
key: 'key 1',
|
||||
name: 'Kevin Sandra1',
|
||||
link: 'link-B',
|
||||
salary: 911,
|
||||
},
|
||||
{
|
||||
key: 'key 2',
|
||||
name: 'Kevin Sandra2',
|
||||
link: 'link-B',
|
||||
salary: 527,
|
||||
},
|
||||
{
|
||||
key: 'key 3',
|
||||
name: 'Kevin Sandra3',
|
||||
link: 'link-B',
|
||||
salary: 906,
|
||||
},
|
||||
{
|
||||
key: 'key 4',
|
||||
name: 'Naomi Cook4',
|
||||
link: 'link-A',
|
||||
salary: 261,
|
||||
},
|
||||
{
|
||||
key: 'key 5',
|
||||
name: 'Naomi Cook5',
|
||||
link: 'link-A',
|
||||
salary: 134,
|
||||
},
|
||||
{
|
||||
key: 'key 6',
|
||||
name: 'Kevin Sandra6',
|
||||
link: 'link-A',
|
||||
salary: 877,
|
||||
},
|
||||
{
|
||||
key: 'key 7',
|
||||
name: 'Kevin Sandra7',
|
||||
link: 'link-A',
|
||||
salary: 287,
|
||||
},
|
||||
{
|
||||
key: 'key 8',
|
||||
name: 'Naomi Cook8',
|
||||
link: 'link-B',
|
||||
salary: 319,
|
||||
},
|
||||
{
|
||||
key: 'key 9',
|
||||
name: 'Kevin Sandra9',
|
||||
link: 'link-B',
|
||||
salary: 105,
|
||||
},
|
||||
{
|
||||
key: 'key 10',
|
||||
name: 'Naomi Cook10',
|
||||
link: 'link-B',
|
||||
salary: 468,
|
||||
},
|
||||
{
|
||||
key: 'key 11',
|
||||
name: 'Naomi Cook11',
|
||||
link: 'link-A',
|
||||
salary: 53,
|
||||
},
|
||||
{
|
||||
key: 'key 12',
|
||||
name: 'Naomi Cook12',
|
||||
link: 'link-A',
|
||||
salary: 195,
|
||||
},
|
||||
],
|
||||
checkCrossPage: true,
|
||||
pagination: {
|
||||
enablePagination: true,
|
||||
pageSize: 6,
|
||||
defaultCurrent: 1,
|
||||
updateWhenDefaultPageChanges: false,
|
||||
useCustomPagination: false,
|
||||
},
|
||||
rowClick: false,
|
||||
tableLayoutFixed: false,
|
||||
borderCell: false,
|
||||
stripe: false,
|
||||
size: 'default',
|
||||
useDefaultFilter: true,
|
||||
useDefaultSort: true,
|
||||
pagePosition: 'bottomCenter',
|
||||
rowSelectionType: 'single',
|
||||
border: true,
|
||||
loading: false,
|
||||
},
|
||||
traits: [],
|
||||
},
|
||||
{
|
||||
id: 'button4',
|
||||
type: 'arco/v1/button',
|
||||
properties: {
|
||||
type: 'primary',
|
||||
status: 'default',
|
||||
long: false,
|
||||
size: 'small',
|
||||
disabled: false,
|
||||
loading: false,
|
||||
shape: 'round',
|
||||
text: 'Click',
|
||||
},
|
||||
traits: [
|
||||
{
|
||||
type: 'core/v1/slot',
|
||||
properties: {
|
||||
container: {
|
||||
id: 'table',
|
||||
slot: 'content',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'core/v1/event',
|
||||
properties: {
|
||||
handlers: [
|
||||
{
|
||||
type: 'onClick',
|
||||
componentId: '$utils',
|
||||
method: {
|
||||
name: 'arco/v1/message',
|
||||
parameters: {
|
||||
type: 'info',
|
||||
content: 'Hello{{$slot.$listItem.name}}',
|
||||
position: 'top',
|
||||
closable: false,
|
||||
duration: 1000,
|
||||
},
|
||||
},
|
||||
disabled: false,
|
||||
wait: {
|
||||
type: 'delay',
|
||||
time: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
@ -4,6 +4,7 @@ import { basicUsage } from './basicUsage';
|
||||
import { selection } from './selection';
|
||||
import { attributes } from './attributes';
|
||||
import { sortAndFilter } from './sortAndFilter';
|
||||
import { customComponent } from './customComponent';
|
||||
|
||||
const { Title, Text, Paragraph } = Typography;
|
||||
|
||||
@ -24,6 +25,12 @@ export const TableDemoPage: React.FC = () => {
|
||||
<Paragraph>You can easily open or close the properties of the table</Paragraph>
|
||||
<DemoWrapper application={attributes} />
|
||||
<Divider />
|
||||
<Title heading={3}>Custom Column Component</Title>
|
||||
<Paragraph>
|
||||
You can use any Sunmao component as column element instead of plain text.
|
||||
</Paragraph>
|
||||
<DemoWrapper application={customComponent} />
|
||||
<Divider />
|
||||
<Title heading={3}>Sort and filter</Title>
|
||||
<Paragraph>
|
||||
Configure the <Text code>sortable</Text> or <Text code>filterable</Text> of{' '}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { StringUnion } from '../../sunmao-helper';
|
||||
import { EventHandlerSpec } from '@sunmao-ui/runtime';
|
||||
import { EventHandlerSpec } from '@sunmao-ui/shared';
|
||||
import { Category } from '../../constants/category';
|
||||
|
||||
const PaginationSpec = Type.Object(
|
||||
@ -182,12 +182,22 @@ export const ColumnSpec = Type.Object({
|
||||
link: Type.String(),
|
||||
button: Type.String(),
|
||||
module: Type.String(),
|
||||
component: Type.String(),
|
||||
}),
|
||||
{
|
||||
title: 'Type',
|
||||
category: Category.Basic,
|
||||
}
|
||||
),
|
||||
componentSlotIndex: Type.Number({
|
||||
title: 'Component Slot Index',
|
||||
conditions: [
|
||||
{
|
||||
key: 'type',
|
||||
value: 'component',
|
||||
},
|
||||
],
|
||||
}),
|
||||
dataIndex: Type.String({
|
||||
title: 'Key',
|
||||
category: Category.Basic,
|
||||
@ -198,6 +208,20 @@ export const ColumnSpec = Type.Object({
|
||||
title: 'Display Value',
|
||||
category: Category.Basic,
|
||||
description: 'The text you want to display instead of raw text',
|
||||
conditions: [
|
||||
{
|
||||
or: [
|
||||
{
|
||||
key: 'type',
|
||||
value: 'link',
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
value: 'text',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}),
|
||||
width: Type.Optional(
|
||||
Type.Number({
|
||||
|
@ -36,6 +36,7 @@
|
||||
"@sunmao-ui/core": "^0.6.0",
|
||||
"@sunmao-ui/editor-sdk": "^0.2.0",
|
||||
"@sunmao-ui/runtime": "^0.6.0",
|
||||
"@sunmao-ui/shared": "^0.1.0",
|
||||
"chakra-react-select": "^1.3.2",
|
||||
"framer-motion": "^4",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
@ -4,9 +4,9 @@ import {
|
||||
implementRuntimeComponent,
|
||||
LIST_ITEM_EXP,
|
||||
LIST_ITEM_INDEX_EXP,
|
||||
ModuleSpec,
|
||||
ModuleRenderer,
|
||||
} from '@sunmao-ui/runtime';
|
||||
import { ModuleRenderSpec } from '@sunmao-ui/shared';
|
||||
import { css } from '@emotion/css';
|
||||
import { BASIC } from './constants/category';
|
||||
import { CORE_VERSION, CoreWidgetName } from '@sunmao-ui/editor-sdk';
|
||||
@ -17,7 +17,7 @@ const PropsSpec = Type.Object({
|
||||
category: BASIC,
|
||||
widget: `${CORE_VERSION}/${CoreWidgetName.Expression}`,
|
||||
}),
|
||||
template: ModuleSpec,
|
||||
template: ModuleRenderSpec,
|
||||
});
|
||||
|
||||
const exampleProperties = {
|
||||
|
@ -190,10 +190,17 @@ export const TableImpl = implementTable(
|
||||
{columns.map((column, j) => (
|
||||
<TableTd
|
||||
index={i}
|
||||
component={component}
|
||||
key={column.key}
|
||||
item={item}
|
||||
onClickItem={() => selectItem(item)}
|
||||
rawColumn={(component.properties.columns as Static<typeof ColumnsPropertySpec>)[j]}
|
||||
rawColumn={
|
||||
(
|
||||
component.properties.columns as Static<
|
||||
typeof ColumnsPropertySpec
|
||||
>
|
||||
)[j]
|
||||
}
|
||||
column={column}
|
||||
services={services}
|
||||
app={app}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { RuntimeApplication } from '@sunmao-ui/core';
|
||||
import React from 'react';
|
||||
import { RuntimeApplication, RuntimeComponentSchema } from '@sunmao-ui/core';
|
||||
import { Static } from '@sinclair/typebox';
|
||||
import { ColumnSpec, ColumnsPropertySpec } from './TableTypes';
|
||||
import { Button, Link, Td, Text } from '@chakra-ui/react';
|
||||
@ -8,6 +9,7 @@ import {
|
||||
ModuleRenderer,
|
||||
UIServices,
|
||||
ExpressionError,
|
||||
ImplWrapper,
|
||||
} from '@sunmao-ui/runtime';
|
||||
|
||||
export const TableTd: React.FC<{
|
||||
@ -17,9 +19,10 @@ export const TableTd: React.FC<{
|
||||
rawColumn: Static<typeof ColumnsPropertySpec>[0];
|
||||
onClickItem: () => void;
|
||||
services: UIServices;
|
||||
app?: RuntimeApplication;
|
||||
component: RuntimeComponentSchema;
|
||||
app: RuntimeApplication;
|
||||
}> = props => {
|
||||
const { item, index, column, rawColumn, onClickItem, services, app } = props;
|
||||
const { item, index, component, column, rawColumn, onClickItem, services, app } = props;
|
||||
const evalOptions = {
|
||||
evalListItem: true,
|
||||
scopeObject: {
|
||||
@ -70,10 +73,6 @@ export const TableTd: React.FC<{
|
||||
content = <Button onClick={onClick}>{buttonConfig.text}</Button>;
|
||||
break;
|
||||
case 'module':
|
||||
const evalScope = {
|
||||
[LIST_ITEM_EXP]: item,
|
||||
[LIST_ITEM_INDEX_EXP]: index,
|
||||
};
|
||||
content = (
|
||||
<ModuleRenderer
|
||||
id={column.module.id}
|
||||
@ -81,11 +80,51 @@ export const TableTd: React.FC<{
|
||||
properties={column.module.properties}
|
||||
handlers={column.module.handlers}
|
||||
services={services}
|
||||
evalScope={evalScope}
|
||||
evalScope={{
|
||||
[LIST_ITEM_EXP]: item,
|
||||
[LIST_ITEM_INDEX_EXP]: index,
|
||||
}}
|
||||
app={app}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'component':
|
||||
const childrenSchema = app.spec.components.filter(c => {
|
||||
return c.traits.find(
|
||||
t =>
|
||||
t.type === 'core/v1/slot' &&
|
||||
(t.properties.container as any).id === component.id
|
||||
);
|
||||
});
|
||||
|
||||
const childSchema = childrenSchema[column.componentSlotIndex];
|
||||
if (!childSchema) {
|
||||
return (
|
||||
<div>Cannot find child with index {column.componentSlotIndex} in slot.</div>
|
||||
);
|
||||
}
|
||||
|
||||
const _childrenSchema = {
|
||||
...childSchema,
|
||||
id: `${component.id}_${childSchema.id}_${index}`,
|
||||
};
|
||||
|
||||
content = (
|
||||
<ImplWrapper
|
||||
key={_childrenSchema.id}
|
||||
component={_childrenSchema}
|
||||
app={app}
|
||||
services={services}
|
||||
childrenMap={{}}
|
||||
isInModule
|
||||
evalListItem
|
||||
slotProps={{
|
||||
[LIST_ITEM_EXP]: item,
|
||||
[LIST_ITEM_INDEX_EXP]: index,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { ModuleSpec, EventCallBackHandlerSpec } from '@sunmao-ui/runtime';
|
||||
import { ModuleRenderSpec, EventCallBackHandlerSpec } from '@sunmao-ui/shared';
|
||||
import { BASIC, APPEARANCE, BEHAVIOR } from '../constants/category';
|
||||
|
||||
export const MajorKeyPropertySpec = Type.String({
|
||||
@ -30,6 +30,7 @@ export const TableSizePropertySpec = Type.KeyOf(
|
||||
export const TdTypeSpec = Type.KeyOf(
|
||||
Type.Object({
|
||||
text: Type.String(),
|
||||
component: Type.String(),
|
||||
image: Type.String(),
|
||||
link: Type.String(),
|
||||
button: Type.String(),
|
||||
@ -52,6 +53,15 @@ export const ColumnSpec = Type.Object(
|
||||
title: 'Display value',
|
||||
}),
|
||||
type: TdTypeSpec,
|
||||
componentSlotIndex: Type.Number({
|
||||
title: 'Component Slot Index',
|
||||
conditions: [
|
||||
{
|
||||
key: 'type',
|
||||
value: 'component',
|
||||
},
|
||||
],
|
||||
}),
|
||||
buttonConfig: Type.Object(
|
||||
{
|
||||
text: Type.String({
|
||||
@ -71,7 +81,7 @@ export const ColumnSpec = Type.Object(
|
||||
],
|
||||
}
|
||||
),
|
||||
module: { ...ModuleSpec, conditions: [{ key: 'type', value: 'module' }] },
|
||||
module: { ...ModuleRenderSpec, conditions: [{ key: 'type', value: 'module' }] },
|
||||
},
|
||||
{
|
||||
title: 'Column',
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { implementRuntimeComponent } from '@sunmao-ui/runtime';
|
||||
import {
|
||||
implementRuntimeComponent,
|
||||
LIST_ITEM_EXP,
|
||||
LIST_ITEM_INDEX_EXP,
|
||||
} from '@sunmao-ui/runtime';
|
||||
import {
|
||||
ColumnsPropertySpec,
|
||||
DataPropertySpec,
|
||||
@ -62,7 +66,14 @@ export const implementTable = implementRuntimeComponent({
|
||||
properties: PropsSpec,
|
||||
state: TableStateSpec,
|
||||
methods: {},
|
||||
slots: {},
|
||||
slots: {
|
||||
content: {
|
||||
slotProps: Type.Object({
|
||||
[LIST_ITEM_EXP]: Type.Any(),
|
||||
[LIST_ITEM_INDEX_EXP]: Type.Number(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
styleSlots: [],
|
||||
events: [],
|
||||
},
|
||||
|
@ -13,7 +13,7 @@ import {
|
||||
import { Static } from '@sinclair/typebox';
|
||||
import { JSONSchema7 } from 'json-schema';
|
||||
import { CloseIcon, ArrowUpIcon, ArrowDownIcon } from '@chakra-ui/icons';
|
||||
import { EventHandlerSpec, EventCallBackHandlerSpec } from '@sunmao-ui/runtime';
|
||||
import { EventHandlerSpec, EventCallBackHandlerSpec } from '@sunmao-ui/shared';
|
||||
import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { formWrapperCSS } from '../style';
|
||||
import { EditorServices } from '../../../types';
|
||||
|
@ -4,8 +4,7 @@ import { Button, VStack } from '@chakra-ui/react';
|
||||
import { Static } from '@sinclair/typebox';
|
||||
import produce from 'immer';
|
||||
import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { EventHandlerSpec } from '@sunmao-ui/runtime';
|
||||
import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared';
|
||||
import { CORE_VERSION, CoreTraitName, EventHandlerSpec } from '@sunmao-ui/shared';
|
||||
import { genOperation } from '../../../operations';
|
||||
import { EditorServices } from '../../../types';
|
||||
import { EventHandlerForm } from './EventHandlerForm';
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react';
|
||||
import { EditorServices } from '../../types';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
@ -66,6 +66,26 @@ export const EditorMaskWrapper: React.FC<Props> = observer(props => {
|
||||
);
|
||||
};
|
||||
|
||||
const onClickIframe = useCallback(() => {
|
||||
if (document.activeElement?.tagName === 'IFRAME') {
|
||||
setSelectedComponentId(document.activeElement?.getAttribute('title') || '');
|
||||
setTimeout(() => {
|
||||
window.focus();
|
||||
});
|
||||
}
|
||||
}, [setSelectedComponentId]);
|
||||
|
||||
// can't capture the iframe click event
|
||||
// use window's blur event to detect whether clicking the iframes
|
||||
useEffect(() => {
|
||||
window.focus();
|
||||
window.addEventListener('blur', onClickIframe);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('blur', onClickIframe);
|
||||
};
|
||||
}, [onClickIframe]);
|
||||
|
||||
const mousePositionWithOffset: [number, number] = [
|
||||
mousePosition[0] + scrollOffset[0],
|
||||
mousePosition[1] + scrollOffset[1],
|
||||
|
@ -4,10 +4,10 @@ import {
|
||||
TraitValidateContext,
|
||||
ValidateErrorResult,
|
||||
} from '../interfaces';
|
||||
import { EventHandlerSpec, GLOBAL_UTIL_METHOD_ID } from '@sunmao-ui/runtime';
|
||||
import { GLOBAL_UTIL_METHOD_ID } from '@sunmao-ui/runtime';
|
||||
import { isExpression } from '../utils';
|
||||
import { ComponentId, EventName } from '../../AppModel/IAppModel';
|
||||
import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared';
|
||||
import { CORE_VERSION, CoreTraitName, EventHandlerSpec } from '@sunmao-ui/shared';
|
||||
|
||||
class EventHandlerValidatorRule implements TraitValidatorRule {
|
||||
kind: 'trait' = 'trait';
|
||||
|
@ -29,8 +29,11 @@
|
||||
const rootEl = document.querySelector('#root');
|
||||
const render = example => {
|
||||
ReactDOM.unmountComponentAtNode(rootEl);
|
||||
const { App, registry } = initSunmaoUI({libs: [sunmaoChakraUILib]});
|
||||
const { App, registry } = initSunmaoUI({ libs: [sunmaoChakraUILib] });
|
||||
const { app, modules = [] } = example.value;
|
||||
console.log('example', example);
|
||||
console.log('modules', modules);
|
||||
window.registry = registry;
|
||||
modules.forEach(m => {
|
||||
registry.registerModule(m);
|
||||
});
|
||||
|
@ -8,16 +8,23 @@ import { getSlotElements } from './hooks/useSlotChildren';
|
||||
import { useGlobalHandlerMap } from './hooks/useGlobalHandlerMap';
|
||||
import { useEleRef } from './hooks/useEleMap';
|
||||
import { useGridLayout } from './hooks/useGridLayout';
|
||||
import { initStateAndMethod } from '../../../utils/initStateAndMethod';
|
||||
|
||||
export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps>(
|
||||
function ImplWrapperMain(props, ref) {
|
||||
const { component: c, children } = props;
|
||||
const { component: c, children, slotProps, evalListItem } = props;
|
||||
const { registry, stateManager } = props.services;
|
||||
|
||||
const Impl = registry.getComponent(c.parsedType.version, c.parsedType.name).impl;
|
||||
|
||||
useGlobalHandlerMap(props);
|
||||
|
||||
// This code is to init dynamic generated components
|
||||
// because they have not be initialized before
|
||||
if (!stateManager.store[c.id]) {
|
||||
initStateAndMethod(registry, stateManager, [c]);
|
||||
}
|
||||
|
||||
const { eleRef, onRef } = useEleRef(props);
|
||||
|
||||
const { mergeState, subscribeMethods, executeTrait } = useRuntimeFunctions(props);
|
||||
@ -28,7 +35,8 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
executeTrait(
|
||||
t,
|
||||
stateManager.deepEval(t.properties, {
|
||||
scopeObject: { $slot: props.slotProps },
|
||||
evalListItem,
|
||||
scopeObject: { $slot: slotProps },
|
||||
fallbackWhenError: () => undefined,
|
||||
})
|
||||
)
|
||||
@ -59,7 +67,8 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
});
|
||||
},
|
||||
{
|
||||
scopeObject: { $slot: props.slotProps },
|
||||
evalListItem,
|
||||
scopeObject: { $slot: slotProps },
|
||||
fallbackWhenError: () => undefined,
|
||||
}
|
||||
);
|
||||
@ -70,7 +79,7 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
// because mergeState will be called during the first render of component, and state will change
|
||||
setTraitResults(c.traits.map((trait, i) => executeTrait(trait, properties[i])));
|
||||
return () => stops.forEach(s => s());
|
||||
}, [c.id, c.traits, executeTrait, stateManager, props.slotProps]);
|
||||
}, [c.id, c.traits, executeTrait, stateManager, slotProps, evalListItem]);
|
||||
|
||||
// reduce traitResults
|
||||
const propsFromTraits: TraitResult<string, string>['props'] = useMemo(() => {
|
||||
@ -97,7 +106,8 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
return merge(
|
||||
stateManager.deepEval(c.properties, {
|
||||
fallbackWhenError: () => undefined,
|
||||
scopeObject: { $slot: props.slotProps },
|
||||
evalListItem,
|
||||
scopeObject: { $slot: slotProps },
|
||||
}),
|
||||
propsFromTraits
|
||||
);
|
||||
@ -109,13 +119,17 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
|
||||
({ result: newResult }: any) => {
|
||||
setEvaledComponentProperties({ ...newResult });
|
||||
},
|
||||
{ fallbackWhenError: () => undefined, scopeObject: { $slot: props.slotProps } }
|
||||
{
|
||||
evalListItem,
|
||||
fallbackWhenError: () => undefined,
|
||||
scopeObject: { $slot: slotProps },
|
||||
}
|
||||
);
|
||||
// must keep this line, reason is the same as above
|
||||
setEvaledComponentProperties({ ...result });
|
||||
|
||||
return stop;
|
||||
}, [c.properties, stateManager, props.slotProps]);
|
||||
}, [c.properties, stateManager, slotProps]);
|
||||
|
||||
useEffect(() => {
|
||||
const clearFunctions = propsFromTraits?.componentDidMount?.map(e => e());
|
||||
|
@ -5,7 +5,7 @@ import { merge } from 'lodash';
|
||||
import { HandlerMap } from '../../../../services/handler';
|
||||
|
||||
export function useRuntimeFunctions(props: ImplWrapperProps) {
|
||||
const { component: c, services } = props;
|
||||
const { component: c, services, slotProps, evalListItem } = props;
|
||||
const { stateManager, registry, globalHandlerMap } = services;
|
||||
|
||||
const mergeState = useCallback(
|
||||
@ -35,9 +35,11 @@ export function useRuntimeFunctions(props: ImplWrapperProps) {
|
||||
mergeState,
|
||||
subscribeMethods,
|
||||
services,
|
||||
slotProps,
|
||||
evalListItem,
|
||||
});
|
||||
},
|
||||
[c.id, mergeState, registry, services, subscribeMethods]
|
||||
[c.id, evalListItem, mergeState, registry, services, slotProps, subscribeMethods]
|
||||
);
|
||||
return {
|
||||
mergeState,
|
||||
|
@ -11,15 +11,15 @@ import {
|
||||
import { ImplWrapper } from './ImplWrapper';
|
||||
import { watch } from '../../utils/watchReactivity';
|
||||
import { ImplementedRuntimeModule, UIServices } from '../../types';
|
||||
import { EventHandlerSpec, ModuleSpec } from '@sunmao-ui/shared';
|
||||
import { EventHandlerSpec, ModuleRenderSpec } from '@sunmao-ui/shared';
|
||||
import { resolveChildrenMap } from '../../utils/resolveChildrenMap';
|
||||
import { initStateAndMethod } from '../../utils/initStateAndMethod';
|
||||
import { ExpressionError } from '../../services/StateManager';
|
||||
|
||||
type Props = Static<typeof ModuleSpec> & {
|
||||
type Props = Static<typeof ModuleRenderSpec> & {
|
||||
evalScope?: Record<string, any>;
|
||||
services: UIServices;
|
||||
app?: RuntimeApplication;
|
||||
app: RuntimeApplication;
|
||||
};
|
||||
|
||||
export const ModuleRenderer = React.forwardRef<HTMLDivElement, Props>((props, ref) => {
|
||||
|
118
packages/runtime/src/components/core/Iframe.tsx
Normal file
118
packages/runtime/src/components/core/Iframe.tsx
Normal file
@ -0,0 +1,118 @@
|
||||
import { implementRuntimeComponent } from '../../utils/buildKit';
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { CORE_VERSION, CoreComponentName, StringUnion } from '@sunmao-ui/shared';
|
||||
import { css } from '@emotion/css';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export default implementRuntimeComponent({
|
||||
version: CORE_VERSION,
|
||||
metadata: {
|
||||
name: CoreComponentName.Iframe,
|
||||
displayName: 'Iframe',
|
||||
description: '',
|
||||
isDraggable: false,
|
||||
isResizable: false,
|
||||
exampleProperties: {
|
||||
src: 'https://www.openstreetmap.org/export/embed.html?bbox=-0.004017949104309083%2C51.47612752641776%2C0.00030577182769775396%2C51.478569861898606&layer=mapnik',
|
||||
referrerpolicy: 'unset',
|
||||
sandbox: 'unset',
|
||||
fetchpriority: 'auto',
|
||||
},
|
||||
exampleSize: [1, 1],
|
||||
annotations: {
|
||||
category: 'Advance',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
properties: Type.Object({
|
||||
src: Type.String({ title: 'Src' }),
|
||||
referrerpolicy: StringUnion(
|
||||
[
|
||||
'unset',
|
||||
'no-referrer',
|
||||
'no-referrer-when-downgrade',
|
||||
'origin',
|
||||
'origin-when-cross-origin',
|
||||
'same-origin',
|
||||
'strict-origin',
|
||||
'strict-origin-when-cross-origin',
|
||||
'unsafe-url',
|
||||
],
|
||||
{
|
||||
title: 'Referrer Policy',
|
||||
description:
|
||||
"Indicates which referrer to send when fetching the frame's resource.",
|
||||
}
|
||||
),
|
||||
sandbox: StringUnion(
|
||||
[
|
||||
'unset',
|
||||
'allow-forms',
|
||||
'allow-modals',
|
||||
'allow-orientation-lock',
|
||||
'allow-pointer-lock',
|
||||
'allow-popups',
|
||||
'allow-popups-to-escape-sandbox',
|
||||
'allow-presentation',
|
||||
'allow-same-origin',
|
||||
'allow-scripts',
|
||||
'allow-top-navigation',
|
||||
'allow-top-navigation-by-user-activation',
|
||||
],
|
||||
{
|
||||
title: 'Sandbox',
|
||||
description: 'Applies extra restrictions to the content in the frame.',
|
||||
}
|
||||
),
|
||||
fetchpriority: StringUnion(['auto', 'low', 'high'], {
|
||||
title: 'Fetch Priority',
|
||||
description:
|
||||
'Provides a hint of the relative priority to use when fetching the iframe document',
|
||||
}),
|
||||
}),
|
||||
state: Type.Object({}),
|
||||
methods: {
|
||||
requestFullscreen: Type.Object({}),
|
||||
},
|
||||
slots: {},
|
||||
styleSlots: ['content'],
|
||||
events: [],
|
||||
},
|
||||
})(
|
||||
({
|
||||
component,
|
||||
src,
|
||||
referrerpolicy,
|
||||
sandbox,
|
||||
fetchpriority,
|
||||
elementRef,
|
||||
customStyle,
|
||||
subscribeMethods,
|
||||
}) => {
|
||||
const iframeProps: Record<string, unknown> = {
|
||||
title: component.id,
|
||||
src,
|
||||
fetchpriority,
|
||||
};
|
||||
|
||||
if (referrerpolicy !== 'unset') {
|
||||
iframeProps.referrerpolicy = referrerpolicy;
|
||||
}
|
||||
|
||||
if (sandbox !== 'unset') {
|
||||
iframeProps.sandbox = sandbox;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
subscribeMethods({
|
||||
requestFullscreen() {
|
||||
elementRef?.current.requestFullscreen();
|
||||
},
|
||||
});
|
||||
}, [elementRef, subscribeMethods]);
|
||||
|
||||
return (
|
||||
<iframe ref={elementRef} {...iframeProps} className={css(customStyle?.content)} />
|
||||
);
|
||||
}
|
||||
);
|
112
packages/runtime/src/components/core/List.tsx
Normal file
112
packages/runtime/src/components/core/List.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { css } from '@emotion/css';
|
||||
import { LIST_ITEM_EXP, LIST_ITEM_INDEX_EXP } from '../../constants';
|
||||
import { implementRuntimeComponent } from '../../utils/buildKit';
|
||||
import { ImplWrapper } from '../_internal/ImplWrapper';
|
||||
|
||||
const PropsSpec = Type.Object({
|
||||
listData: Type.Array(Type.Record(Type.String(), Type.String()), {
|
||||
title: 'Data',
|
||||
category: 'Basic',
|
||||
widget: `core/v1/expression`,
|
||||
}),
|
||||
});
|
||||
|
||||
const exampleProperties = {
|
||||
listData: [{ id: '1' }, { id: '2' }, { id: '3' }],
|
||||
};
|
||||
|
||||
export default implementRuntimeComponent({
|
||||
version: 'core/v1',
|
||||
metadata: {
|
||||
name: 'list',
|
||||
description: 'core list',
|
||||
displayName: 'List',
|
||||
isDraggable: true,
|
||||
isResizable: true,
|
||||
exampleProperties,
|
||||
exampleSize: [6, 6],
|
||||
annotations: {
|
||||
category: 'Display',
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
properties: PropsSpec,
|
||||
methods: {},
|
||||
state: Type.Object({}),
|
||||
slots: {
|
||||
content: {
|
||||
slotProps: Type.Object({
|
||||
[LIST_ITEM_EXP]: Type.Any(),
|
||||
[LIST_ITEM_INDEX_EXP]: Type.Number(),
|
||||
}),
|
||||
},
|
||||
},
|
||||
styleSlots: ['content'],
|
||||
events: [],
|
||||
},
|
||||
})(({ listData, component, app, services, customStyle, elementRef }) => {
|
||||
if (!listData) {
|
||||
return null;
|
||||
}
|
||||
const childrenSchema = app.spec.components.filter(c => {
|
||||
return c.traits.find(
|
||||
t =>
|
||||
t.type === 'core/v1/slot' && (t.properties.container as any).id === component.id
|
||||
);
|
||||
});
|
||||
|
||||
if (childrenSchema.length === 0) {
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
${customStyle?.content}
|
||||
`}
|
||||
ref={elementRef}
|
||||
>
|
||||
List has no children. Drag components into here to add listItems.
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const listItems = listData.map((listItem, i) => {
|
||||
const _childrenSchema = childrenSchema.map(child => {
|
||||
return {
|
||||
...child,
|
||||
id: `${component.id}_${child.id}_${i}`,
|
||||
};
|
||||
});
|
||||
|
||||
const childrenEles = _childrenSchema.map(child => {
|
||||
return (
|
||||
<ImplWrapper
|
||||
key={child.id}
|
||||
component={child}
|
||||
app={app}
|
||||
services={services}
|
||||
childrenMap={{}}
|
||||
isInModule
|
||||
evalListItem
|
||||
slotProps={{
|
||||
[LIST_ITEM_EXP]: listItem,
|
||||
[LIST_ITEM_INDEX_EXP]: i,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
||||
return <li key={i}>{childrenEles}</li>;
|
||||
});
|
||||
|
||||
return (
|
||||
<ul
|
||||
className={css`
|
||||
list-style: none;
|
||||
${customStyle?.content}
|
||||
`}
|
||||
ref={elementRef}
|
||||
>
|
||||
{listItems}
|
||||
</ul>
|
||||
);
|
||||
});
|
@ -1,5 +1,5 @@
|
||||
import { implementRuntimeComponent } from '../../utils/buildKit';
|
||||
import { ModuleSpec, CORE_VERSION, CoreComponentName } from '@sunmao-ui/shared';
|
||||
import { ModuleRenderSpec, CORE_VERSION, CoreComponentName } from '@sunmao-ui/shared';
|
||||
import { ModuleRenderer } from '../_internal/ModuleRenderer';
|
||||
|
||||
export default implementRuntimeComponent({
|
||||
@ -20,7 +20,7 @@ export default implementRuntimeComponent({
|
||||
},
|
||||
},
|
||||
spec: {
|
||||
properties: ModuleSpec,
|
||||
properties: ModuleRenderSpec,
|
||||
state: {},
|
||||
methods: {},
|
||||
slots: {},
|
||||
|
@ -67,12 +67,8 @@ export type { RegistryInterface, SunmaoLib } from './services/Registry';
|
||||
export { ExpressionError } from './services/StateManager';
|
||||
export type { StateManagerInterface } from './services/StateManager';
|
||||
export { ModuleRenderer } from './components/_internal/ModuleRenderer';
|
||||
export { ImplWrapper } from './components/_internal/ImplWrapper';
|
||||
export { default as Text, TextPropertySpec } from './components/_internal/Text';
|
||||
export {
|
||||
EventHandlerSpec,
|
||||
EventCallBackHandlerSpec,
|
||||
ModuleSpec,
|
||||
} from '@sunmao-ui/shared';
|
||||
|
||||
// TODO: check this export
|
||||
export { watch } from './utils/watchReactivity';
|
||||
|
@ -8,6 +8,8 @@ import CoreDummy from '../components/core/Dummy';
|
||||
import CoreModuleContainer from '../components/core/ModuleContainer';
|
||||
import CoreStack from '../components/core/Stack';
|
||||
import CoreFileInput from '../components/core/FileInput';
|
||||
import CoreList from '../components/core/List';
|
||||
import CoreIframe from '../components/core/Iframe';
|
||||
|
||||
// traits
|
||||
import CoreArrayState from '../traits/core/ArrayState';
|
||||
@ -242,6 +244,8 @@ export function initRegistry(
|
||||
registry.registerComponent(CoreModuleContainer);
|
||||
registry.registerComponent(CoreStack);
|
||||
registry.registerComponent(CoreFileInput);
|
||||
registry.registerComponent(CoreList);
|
||||
registry.registerComponent(CoreIframe);
|
||||
|
||||
registry.registerTrait(CoreState);
|
||||
registry.registerTrait(CoreArrayState);
|
||||
|
@ -11,11 +11,16 @@ export const EventTraitPropertiesSpec = Type.Object({
|
||||
export const generateCallback = (
|
||||
handler: Omit<Static<typeof EventHandlerSpec>, 'type'>,
|
||||
rawHandler: Static<typeof EventHandlerSpec>,
|
||||
services: UIServices
|
||||
services: UIServices,
|
||||
slotProps?: unknown,
|
||||
evalListItem?: boolean
|
||||
) => {
|
||||
const send = () => {
|
||||
// Eval before sending event to assure the handler object is evaled from the latest state.
|
||||
const evaledHandler = services.stateManager.deepEval(rawHandler);
|
||||
const evaledHandler = services.stateManager.deepEval(rawHandler, {
|
||||
scopeObject: { $slot: slotProps },
|
||||
evalListItem,
|
||||
});
|
||||
|
||||
if (evaledHandler.disabled && typeof evaledHandler.disabled === 'boolean') {
|
||||
return;
|
||||
@ -54,7 +59,7 @@ export default implementRuntimeTrait({
|
||||
state: {},
|
||||
},
|
||||
})(() => {
|
||||
return ({ trait, handlers, services }) => {
|
||||
return ({ trait, handlers, services, slotProps, evalListItem }) => {
|
||||
const callbackQueueMap: Record<string, Array<() => void>> = {};
|
||||
const rawHandlers = trait.properties.handlers as Static<typeof EventHandlerSpec>[];
|
||||
// setup current handlers
|
||||
@ -64,7 +69,7 @@ export default implementRuntimeTrait({
|
||||
callbackQueueMap[handler.type] = [];
|
||||
}
|
||||
callbackQueueMap[handler.type].push(
|
||||
generateCallback(handler, rawHandlers[i], services)
|
||||
generateCallback(handler, rawHandlers[i], services, slotProps, evalListItem)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,8 @@ export type ImplWrapperProps<KSlot extends string = string> = {
|
||||
childrenMap: ChildrenMap<KSlot>;
|
||||
services: UIServices;
|
||||
isInModule: boolean;
|
||||
app?: RuntimeApplication;
|
||||
app: RuntimeApplication;
|
||||
evalListItem?: boolean;
|
||||
slotProps?: unknown;
|
||||
} & ComponentParamsFromApp;
|
||||
|
||||
@ -28,7 +29,7 @@ export type ComponentImplProps<
|
||||
> = ImplWrapperProps &
|
||||
TraitResult<KStyleSlot, KEvent>['props'] &
|
||||
RuntimeFunctions<TState, TMethods, TSlots> & {
|
||||
elementRef?: React.Ref<any>;
|
||||
elementRef?: React.MutableRefObject<any>;
|
||||
getElement?: (ele: HTMLElement) => void;
|
||||
};
|
||||
|
||||
|
@ -20,6 +20,8 @@ export type TraitImpl<T = any> = (
|
||||
trait: RuntimeTraitSchema;
|
||||
componentId: string;
|
||||
services: UIServices;
|
||||
evalListItem?: boolean;
|
||||
slotProps?: unknown;
|
||||
}
|
||||
) => TraitResult<string, string>;
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { Static, TSchema } from '@sinclair/typebox';
|
||||
import { RegistryInterface } from '../services/Registry';
|
||||
import { StateManagerInterface } from '../services/StateManager';
|
||||
import {
|
||||
ModuleSpec,
|
||||
ModuleRenderSpec,
|
||||
parseTypeBox,
|
||||
CORE_VERSION,
|
||||
CoreComponentName,
|
||||
@ -36,7 +36,7 @@ export function initSingleComponentState(
|
||||
stateManager.store[c.id] = state;
|
||||
|
||||
if (c.type === `${CORE_VERSION}/${CoreComponentName.ModuleContainer}`) {
|
||||
const moduleSchema = c.properties as Static<typeof ModuleSpec>;
|
||||
const moduleSchema = c.properties as Static<typeof ModuleRenderSpec>;
|
||||
try {
|
||||
const mSpec = registry.getModuleByType(moduleSchema.type).spec;
|
||||
const moduleInitState: Record<string, unknown> = {};
|
||||
|
@ -6,6 +6,7 @@ export enum CoreComponentName {
|
||||
ModuleContainer = 'moduleContainer',
|
||||
GridLayout = 'grid_layout',
|
||||
Text = 'text',
|
||||
Iframe = 'iframe',
|
||||
}
|
||||
// core traits
|
||||
export enum CoreTraitName {
|
||||
|
@ -2,7 +2,7 @@ import { EventHandlerSpec } from './event';
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import { CORE_VERSION, CoreWidgetName } from '../constants/core';
|
||||
|
||||
export const ModuleSpec = Type.Object(
|
||||
export const ModuleRenderSpec = Type.Object(
|
||||
{
|
||||
id: Type.String({
|
||||
title: 'Module ID',
|
||||
|
Loading…
x
Reference in New Issue
Block a user