Merge branch 'main' into feat/style-trait-widget

* main:
  feat(widget): add the color widget
  fix: fix the type errors
  fix(widget): fix the crash problem when opening the event tab
  refactor(slot): slotSchema -> SlotSpec
  chore(examples): change the util method's examples
  refactor(arco): fix type
  update the collapse component impl
  refine slotElements interface to avoid re-mount
  memomize slotElements to avoid extra re-mount
  adapt new slots spec in editor
  migrate all in-tree components to the new slot spec
  impl #388, support slot props and apply to tabs component

# Conflicts:
#	packages/editor-sdk/src/components/Widgets/StyleWidgets/SizeWidget.tsx
#	packages/editor-sdk/src/components/Widgets/index.ts
#	packages/editor/src/components/ComponentForm/StyleTraitForm/StyleTraitForm.tsx
This commit is contained in:
Bowen Tan 2022-06-06 10:33:52 +08:00
commit 61731f998e
111 changed files with 819 additions and 430 deletions

View File

@ -43,7 +43,7 @@
{
"componentId": "$utils",
"method": {
"name": "toast.open",
"name": "chakra_ui/v1/openToast",
"parameters": {
"id": "createSuccessToast",
"title": "恭喜",

View File

@ -43,7 +43,7 @@
{
"componentId": "$utils",
"method": {
"name": "toast.open",
"name": "chakra_ui/v1/openToast",
"parameters": {
"id": "createSuccessToast",
"title": "恭喜",

View File

@ -37,7 +37,7 @@
"type": "chakra_ui/v1/button",
"properties": {
"text": {
"raw": "in tab1",
"raw": "only in tab {{ $slot.tabIndex + 1 }}",
"format": "plain"
}
},
@ -48,7 +48,8 @@
"container": {
"id": "tabs",
"slot": "content"
}
},
"ifCondition": "{{ $slot.tabIndex === 0 }}"
}
}
]

View File

@ -59,7 +59,7 @@
"type": "onClick",
"componentId": "$utils",
"method": {
"name": "toast.open",
"name": "chakra_ui/v1/openToast",
"parameters": {
"id": "{{open_button.lastToast}}",
"title": "i am a title",
@ -103,7 +103,7 @@
"type": "onClick",
"componentId": "$utils",
"method": {
"name": "toast.close",
"name": "chakra_ui/v1/closeToast",
"parameters": {
"id": "{{open_button.lastToast}}"
}
@ -156,7 +156,7 @@
"type": "onClick",
"componentId": "$utils",
"method": {
"name": "toast.close"
"name": "chakra_ui/v1/closeToast"
},
"wait": {}
}

View File

@ -32,7 +32,10 @@ export const Alert = implementRuntimeComponent({
properties: AlertPropsSpec,
state: AlertStateSpec,
methods: {},
slots: ['action', 'icon'],
slots: {
action: { slotProps: Type.Object({}) },
icon: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['onClose', 'afterClose'],
},
@ -43,8 +46,8 @@ export const Alert = implementRuntimeComponent({
return (
<BaseAlert
ref={elementRef}
action={slotsElements.action}
icon={slotsElements.icon}
action={slotsElements.action ? slotsElements.action({}) : null}
icon={slotsElements.icon ? slotsElements.icon({}) : null}
onClose={_e => {
callbackMap?.onClose?.();
}}

View File

@ -34,7 +34,9 @@ export const Avatar = implementRuntimeComponent({
properties: AvatarPropsSpec,
state: AvatarStateSpec,
methods: {},
slots: ['triggerIcon'],
slots: {
triggerIcon: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['onClick'],
},
@ -47,7 +49,7 @@ export const Avatar = implementRuntimeComponent({
ref={elementRef}
className={css(customStyle?.content)}
{...cProps}
triggerIcon={slotsElements.triggerIcon}
triggerIcon={slotsElements.triggerIcon ? slotsElements.triggerIcon({}) : null}
onClick={_e => {
callbackMap?.onClick?.();
}}

View File

@ -31,7 +31,9 @@ export const Badge = implementRuntimeComponent({
properties: BadgePropsSpec,
state: BadgeStateSpec,
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -55,7 +57,7 @@ export const Badge = implementRuntimeComponent({
{...cProps}
color={cProps.dotColor}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseBadge>
);
});

View File

@ -36,7 +36,9 @@ export const Button = implementRuntimeComponent({
properties: ButtonPropsSpec,
state: ButtonStateSpec,
methods: {},
slots: ['icon'],
slots: {
icon: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['onClick'],
},
@ -49,7 +51,7 @@ export const Button = implementRuntimeComponent({
ref={elementRef}
className={css(customStyle?.content)}
onClick={callbackMap?.onClick}
icon={slotsElements.icon}
icon={slotsElements.icon ? slotsElements.icon({}) : null}
{...cProps}
loadingFixedWidth
>

View File

@ -8,7 +8,6 @@ import {
CascaderValueSpec,
} from '../generated/types/Cascader';
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { isArray } from 'lodash-es';
import { SelectViewHandle } from '@arco-design/web-react/es/_class/select-view';
const CascaderPropsSpec = Type.Object(BaseCascaderPropsSpec);
@ -96,7 +95,9 @@ export const Cascader = implementRuntimeComponent({
properties: CascaderPropsSpec,
state: CascaderStateSpec,
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['onChange'],
},
@ -106,9 +107,7 @@ export const Cascader = implementRuntimeComponent({
const { multiple, options, placeholder, ...cProps } = getComponentProps(props);
const ref = useRef<SelectViewHandle | null>(null);
const content = isArray(slotsElements.content)
? slotsElements.content[0]
: slotsElements.content;
const content = slotsElements.content ? slotsElements.content({}) : null;
const mode = multiple ? 'multiple' : undefined;

View File

@ -65,7 +65,7 @@ export const Checkbox = implementRuntimeComponent({
}),
},
styleSlots: ['content'],
slots: [],
slots: {},
events: ['onChange'],
},
})(props => {
@ -79,7 +79,7 @@ export const Checkbox = implementRuntimeComponent({
...checkboxProps
} = getComponentProps(props);
const [checkedValues, setCheckedValues] = useStateValue<string[]>(
const [checkedValues, setCheckedValues] = useStateValue(
defaultCheckedValues,
mergeState,
updateWhenDefaultValueChanges,

View File

@ -51,7 +51,14 @@ export const Collapse = implementRuntimeComponent({
methods: {
setActiveKey: Type.String(),
},
slots: ['content'],
slots: {
content: {
slotProps: Type.Object({
activeKey: Type.Array(Type.String()),
index: Type.Number(),
}),
},
},
styleSlots: ['content'],
events: ['onChange'],
},
@ -60,7 +67,7 @@ export const Collapse = implementRuntimeComponent({
getComponentProps(props);
const { elementRef, mergeState, slotsElements, customStyle, callbackMap } = props;
const [activeKey, setActiveKey] = useStateValue<string[]>(
const [activeKey, setActiveKey] = useStateValue(
defaultActiveKey.map(String),
mergeState,
updateWhenDefaultValueChanges,
@ -76,10 +83,6 @@ export const Collapse = implementRuntimeComponent({
[callbackMap, mergeState]
);
const collapseItems = slotsElements.content
? ([] as React.ReactElement[]).concat(slotsElements.content)
: [];
return (
<BaseCollapse
ref={elementRef}
@ -88,12 +91,12 @@ export const Collapse = implementRuntimeComponent({
activeKey={activeKey}
onChange={onChange}
>
{options.map((o, idx) => {
{options.map((o, index) => {
const { key, ...props } = o;
return (
<BaseCollapse.Item key={key} name={String(key)} {...props}>
{collapseItems.length ? (
collapseItems[idx]
{slotsElements?.content ? (
slotsElements.content({ activeKey, index })
) : (
<EmptyPlaceholder componentName="" />
)}

View File

@ -77,7 +77,14 @@ export const DatePicker = implementRuntimeComponent({
properties: DatePickerPropsSpec,
state: DatePickerStateSpec,
methods: {},
slots: ['footer', 'triggerElement'],
slots: {
footer: {
slotProps: Type.Object({}),
},
triggerElement: {
slotProps: Type.Object({}),
},
},
styleSlots: ['content'],
events: ['onChange', 'onClear', 'onVisibleChange'],
},

View File

@ -60,7 +60,7 @@ export const Descriptions = implementRuntimeComponent({
properties: DescriptionPropsSpec,
state: DescriptionStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -30,7 +30,7 @@ export const Divider = implementRuntimeComponent({
properties: DividerPropsSpec,
state: DividerStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -44,7 +44,9 @@ export const Dropdown = implementRuntimeComponent({
properties: DropdownPropsSpec,
state: DropdownStateSpec,
methods: {},
slots: ['trigger'],
slots: {
trigger: { slotProps: Type.Object({}) },
},
styleSlots: [],
events: ['onClickMenuItem', 'onVisibleChange', 'onButtonClick'],
},
@ -87,7 +89,9 @@ export const Dropdown = implementRuntimeComponent({
onClick={callbackMap?.onButtonClick}
triggerProps={{ autoAlignPopupMinWidth: autoAlignPopupWidth }}
>
<div ref={elementRef}>{slotsElements.trigger || <Button>Click</Button>}</div>
<div ref={elementRef}>
{slotsElements.trigger ? slotsElements.trigger({}) : <Button>Click</Button>}
</div>
</Dropdown>
);
});

View File

@ -60,7 +60,9 @@ export const FormControl = implementRuntimeComponent({
properties: FormControlPropsSpec,
state: Type.Object({}),
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -81,7 +83,7 @@ export const FormControl = implementRuntimeComponent({
{...cProps}
>
{slotsElements.content ? (
slotsElements.content
slotsElements.content({})
) : (
<EmptyPlaceholder componentName="Input" />
)}

View File

@ -28,7 +28,9 @@ export const Row = implementRuntimeComponent({
properties: Type.Object(RowPropsSpec),
state: Type.Object({}),
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['wrapper'],
events: [],
},
@ -38,7 +40,11 @@ export const Row = implementRuntimeComponent({
return (
<Grid.Row className={css(customStyle?.wrapper)} ref={elementRef} {...cProps}>
{slotsElements.content || <EmptyPlaceholder componentName="" />}
{slotsElements.content ? (
slotsElements.content({})
) : (
<EmptyPlaceholder componentName="" />
)}
</Grid.Row>
);
});
@ -67,7 +73,9 @@ export const Col = implementRuntimeComponent({
properties: Type.Object(ColPropsSpec),
state: Type.Object({}),
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['wrapper'],
events: [],
},
@ -76,7 +84,11 @@ export const Col = implementRuntimeComponent({
const { ...cProps } = getComponentProps(props);
return (
<Grid.Col className={css(customStyle?.wrapper)} ref={elementRef} {...cProps}>
{slotsElements.content || <EmptyPlaceholder componentName="" />}
{slotsElements.content ? (
slotsElements.content({})
) : (
<EmptyPlaceholder componentName="" />
)}
</Grid.Col>
);
});

View File

@ -31,7 +31,7 @@ export const Icon = implementRuntimeComponent({
properties: IconPropsSpec,
state: Type.Object({}),
methods: {},
slots: ['slot'],
slots: {},
styleSlots: ['content'],
events: ['event'],
},

View File

@ -20,7 +20,7 @@ const exampleProperties: Static<typeof ImagePropsSpec> = {
preview: false,
width: 200,
height: 200,
error:''
error: '',
};
const options = {
@ -38,7 +38,7 @@ const options = {
properties: ImagePropsSpec,
state: ImageStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onClick'],
},
@ -101,7 +101,7 @@ export const ImageGroup = implementRuntimeComponent({
current: Type.Number(),
}),
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onChange'],
},

View File

@ -41,7 +41,12 @@ const options = {
properties: InputPropsSpec,
state: InputStateSpec,
methods: {},
slots: ['addAfter', 'prefix', 'suffix', 'addBefore'],
slots: {
addAfter: { slotProps: Type.Object({}) },
prefix: { slotProps: Type.Object({}) },
suffix: { slotProps: Type.Object({}) },
addBefore: { slotProps: Type.Object({}) },
},
styleSlots: ['input'],
events: ['onChange', 'onBlur', 'onFocus', 'onClear', 'onPressEnter'],
},
@ -79,10 +84,10 @@ export const Input = implementRuntimeComponent(options)(props => {
<BaseInput
className={css(customStyle?.input)}
ref={ref}
addAfter={slotsElements.addAfter}
addBefore={slotsElements.addBefore}
prefix={slotsElements.prefix}
suffix={slotsElements.suffix}
addAfter={slotsElements.addAfter ? slotsElements.addAfter({}) : null}
addBefore={slotsElements.addBefore ? slotsElements.addBefore({}) : null}
prefix={slotsElements.prefix ? slotsElements.prefix({}) : null}
suffix={slotsElements.suffix ? slotsElements.suffix({}) : null}
value={value}
onChange={onChange}
onClear={() => {

View File

@ -48,7 +48,12 @@ export const Layout = implementRuntimeComponent({
}),
state: LayoutStateSpec,
methods: {},
slots: ['header', 'content', 'sidebar', 'footer'],
slots: {
header: { slotProps: Type.Object({}) },
content: { slotProps: Type.Object({}) },
sidebar: { slotProps: Type.Object({}) },
footer: { slotProps: Type.Object({}) },
},
styleSlots: ['layout', 'header', 'content', 'sidebar', 'footer'],
events: [],
},
@ -99,16 +104,24 @@ export const Layout = implementRuntimeComponent({
return (
<BaseLayout {...baseProps}>
{showHeader && (
<BaseLayout.Header {...headerProps}>{slotsElements.header}</BaseLayout.Header>
<BaseLayout.Header {...headerProps}>
{slotsElements.header ? slotsElements.header({}) : null}
</BaseLayout.Header>
)}
<BaseLayout>
{showSideBar && (
<BaseLayout.Sider {...siderProps}>{slotsElements.sidebar}</BaseLayout.Sider>
<BaseLayout.Sider {...siderProps}>
{slotsElements.sidebar ? slotsElements.sidebar({}) : null}
</BaseLayout.Sider>
)}
<BaseLayout.Content {...contentProps}>{slotsElements.content}</BaseLayout.Content>
<BaseLayout.Content {...contentProps}>
{slotsElements.content ? slotsElements.content({}) : null}
</BaseLayout.Content>
</BaseLayout>
{showFooter && (
<BaseLayout.Footer {...footerProps}>{slotsElements.footer}</BaseLayout.Footer>
<BaseLayout.Footer {...footerProps}>
{slotsElements.footer ? slotsElements.footer({}) : null}
</BaseLayout.Footer>
)}
</BaseLayout>
);

View File

@ -16,7 +16,6 @@ const exampleProperties: Static<typeof LinkPropsSpec> = {
content: 'Link',
};
const statusMap = {
default: undefined,
success: 'success',
@ -39,7 +38,7 @@ export const Link = implementRuntimeComponent({
properties: LinkPropsSpec,
state: LinkStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -37,7 +37,7 @@ export const Mentions = implementRuntimeComponent({
properties: MentionsPropsSpec,
state: MentionsStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onChange', 'onClear', 'onPressEnter', 'onFocus', 'onBlur'],
},

View File

@ -45,7 +45,7 @@ export const Menu = implementRuntimeComponent({
active: Type.String(),
}),
},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onClick'],
},

View File

@ -39,7 +39,10 @@ export const Modal = implementRuntimeComponent({
openModal: Type.String(),
closeModal: Type.String(),
},
slots: ['content', 'footer'],
slots: {
content: { slotProps: Type.Object({}) },
footer: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['afterOpen', 'afterClose', 'onCancel', 'onOk'],
},
@ -87,12 +90,14 @@ export const Modal = implementRuntimeComponent({
}}
afterClose={afterClose}
afterOpen={afterOpen}
footer={slotsElements.footer}
footer={slotsElements.footer ? slotsElements.footer({}) : null}
className={css(customStyle?.content)}
mountOnEnter={true}
{...cProps}
>
<div ref={contentRef}>{slotsElements.content}</div>
<div ref={contentRef}>
{slotsElements.content ? slotsElements.content({}) : null}
</div>
</BaseModal>
</ConfigProvider>
);

View File

@ -40,7 +40,7 @@ export const Pagination = implementRuntimeComponent({
properties: PaginationPropsSpec,
state: PaginationStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onChange'],
},
@ -49,7 +49,7 @@ export const Pagination = implementRuntimeComponent({
getComponentProps(props);
const { elementRef, customStyle, mergeState, callbackMap } = props;
const [current, setCurrent] = useStateValue<number>(
const [current, setCurrent] = useStateValue(
defaultCurrent || 0,
mergeState,
updateWhenDefaultValueChanges,

View File

@ -39,7 +39,7 @@ export const PasswordInput = implementRuntimeComponent({
properties: InputPropsSpec,
state: InputStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['input'],
events: ['onChange', 'onBlur', 'onFocus', 'onPressEnter'],
},

View File

@ -37,7 +37,10 @@ export const Popover = implementRuntimeComponent({
openPopover: Type.String(),
closePopover: Type.String(),
},
slots: ['popupContent', 'content'],
slots: {
popupContent: { slotProps: Type.Object({}) },
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -62,21 +65,25 @@ export const Popover = implementRuntimeComponent({
<BasePopover
className={css(customStyle?.content)}
{...cProps}
content={slotsElements.popupContent}
content={slotsElements.popupContent ? slotsElements.popupContent({}) : null}
>
<span ref={elementRef}>{slotsElements.content || <Button>Hover Me</Button>}</span>
<span ref={elementRef}>
{slotsElements.content ? slotsElements.content({}) : <Button>Hover Me</Button>}
</span>
</BasePopover>
) : (
<BasePopover
className={css(customStyle?.content)}
{...cProps}
content={slotsElements.popupContent}
content={slotsElements.popupContent ? slotsElements.popupContent({}) : null}
popupVisible={popupVisible}
onVisibleChange={visible => {
setPopupVisible(visible);
}}
>
<span ref={elementRef}>{slotsElements.content || <Button>Hover Me</Button>}</span>
<span ref={elementRef}>
{slotsElements.content ? slotsElements.content({}) : <Button>Hover Me</Button>}
</span>
</BasePopover>
);
});

View File

@ -35,7 +35,7 @@ export const Progress = implementRuntimeComponent({
properties: ProgressPropsSpec,
state: ProgressStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -46,7 +46,7 @@ export const Radio = implementRuntimeComponent({
value: Type.String(),
}),
},
slots: [],
slots: {},
styleSlots: ['group'],
events: ['onChange'],
},
@ -54,7 +54,7 @@ export const Radio = implementRuntimeComponent({
const { customStyle, callbackMap, mergeState, subscribeMethods, elementRef } = props;
const { defaultCheckedValue, updateWhenDefaultValueChanges, ...cProps } =
getComponentProps(props);
const [checkedValue, setCheckedValue] = useStateValue<string>(
const [checkedValue, setCheckedValue] = useStateValue(
defaultCheckedValue,
mergeState,
updateWhenDefaultValueChanges,

View File

@ -52,7 +52,9 @@ export const Select = implementRuntimeComponent({
properties: SelectPropsSpec,
state: SelectStateSpec,
methods: {},
slots: ['dropdownRenderSlot'],
slots: {
dropdownRenderSlot: { slotProps: Type.Object({}) },
},
styleSlots: ['content', 'dropdownRenderWrap'],
events: ['onChange', 'onClear', 'onBlur', 'onFocus'],
},
@ -112,7 +114,9 @@ export const Select = implementRuntimeComponent({
return (
<div className={css(customStyle?.dropdownRenderWrap)}>
{menu}
{slotsElements.dropdownRenderSlot}
{slotsElements.dropdownRenderSlot
? slotsElements.dropdownRenderSlot({})
: null}
</div>
);
}}

View File

@ -30,7 +30,9 @@ export const Skeleton = implementRuntimeComponent({
properties: SkeletonPropsSpec,
state: SkeletonStateSpec,
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -40,7 +42,7 @@ export const Skeleton = implementRuntimeComponent({
return (
<BaseSkeleton ref={elementRef} className={css(customStyle?.content)} {...cProps}>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseSkeleton>
);
});

View File

@ -1,9 +1,9 @@
import { Slider as BaseSlider } from "@arco-design/web-react";
import { implementRuntimeComponent } from "@sunmao-ui/runtime";
import { css } from "@emotion/css";
import { Type, Static } from "@sinclair/typebox";
import { FALLBACK_METADATA, getComponentProps } from "../sunmao-helper";
import { SliderPropsSpec as BaseSliderPropsSpec } from "../generated/types/Slider";
import { Slider as BaseSlider } from '@arco-design/web-react';
import { implementRuntimeComponent } from '@sunmao-ui/runtime';
import { css } from '@emotion/css';
import { Type, Static } from '@sinclair/typebox';
import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper';
import { SliderPropsSpec as BaseSliderPropsSpec } from '../generated/types/Slider';
const SliderPropsSpec = Type.Object(BaseSliderPropsSpec);
const SliderStateSpec = Type.Object({});
@ -18,41 +18,41 @@ const exampleProperties: Static<typeof SliderPropsSpec> = {
marks: {},
onlyMarkValue: false,
reverse: false,
step:1,
showTicks:false
step: 1,
showTicks: false,
};
export const Slider = implementRuntimeComponent({
version: "arco/v1",
version: 'arco/v1',
metadata: {
...FALLBACK_METADATA,
name: "slider",
displayName: "Slider",
name: 'slider',
displayName: 'Slider',
exampleProperties,
annotations: {
category: "Display",
category: 'Display',
},
},
spec: {
properties: SliderPropsSpec,
state: SliderStateSpec,
methods: {},
slots: [],
styleSlots: ["content"],
events: ["onChange", "onAfterChange"],
slots: {},
styleSlots: ['content'],
events: ['onChange', 'onAfterChange'],
},
})((props) => {
})(props => {
const { ...cProps } = getComponentProps(props);
const { customStyle, elementRef, callbackMap, mergeState } = props;
return (
<BaseSlider
ref={elementRef}
onChange={(val) => {
onChange={val => {
mergeState({ value: val });
callbackMap?.onChange?.();
}}
onAfterChange={(val) => {
onAfterChange={val => {
mergeState({ value: val });
callbackMap?.onAfterChange?.();
}}

View File

@ -31,7 +31,9 @@ export const Space = implementRuntimeComponent({
properties: SpacePropsSpec,
state: SpaceStateSpec,
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['onClick'],
},
@ -41,7 +43,7 @@ export const Space = implementRuntimeComponent({
return (
<BaseSpace ref={elementRef} className={css(customStyle?.content)} {...cProps}>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseSpace>
);
});

View File

@ -42,7 +42,9 @@ export const Steps = implementRuntimeComponent({
properties: StepsPropsSpec,
state: StepsStateSpec,
methods: {},
slots: ['icons'],
slots: {
icons: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -63,7 +65,7 @@ export const Steps = implementRuntimeComponent({
items.map((stepItem: StepItem, idx: number) => {
return (
<BaseSteps.Step
icon={slotsElements.icons}
icon={slotsElements.icons ? slotsElements.icons({}) : null}
key={idx}
title={stepItem.title}
description={stepItem.description}

View File

@ -37,7 +37,7 @@ export const Switch = implementRuntimeComponent({
properties: SwitchPropsSpec,
state: SwitchStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onChange'],
},
@ -45,7 +45,7 @@ export const Switch = implementRuntimeComponent({
const { elementRef, customStyle, mergeState, callbackMap } = props;
const { defaultChecked, updateWhenDefaultValueChanges, ...cProps } =
getComponentProps(props);
const [value, setValue] = useStateValue<boolean>(
const [value, setValue] = useStateValue(
defaultChecked,
mergeState,
updateWhenDefaultValueChanges

View File

@ -155,7 +155,7 @@ export const Table = implementRuntimeComponent({
properties: TablePropsSpec,
state: TableStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [
'onRowClick',

View File

@ -57,7 +57,13 @@ export const Tabs = implementRuntimeComponent({
activeTab: Type.Number(),
}),
},
slots: ['content'],
slots: {
content: {
slotProps: Type.Object({
tabIndex: Type.Number(),
}),
},
},
styleSlots: ['content'],
events: ['onChange', 'onClickTab'],
},
@ -73,7 +79,7 @@ export const Tabs = implementRuntimeComponent({
slotsElements,
} = props;
const ref = useRef<{ current: HTMLDivElement }>(null);
const [activeTab, setActiveTab] = useStateValue<number>(
const [activeTab, setActiveTab] = useStateValue(
defaultActiveTab ?? 0,
mergeState,
updateWhenDefaultValueChanges,
@ -87,10 +93,6 @@ export const Tabs = implementRuntimeComponent({
}
}, [getElement, ref]);
const slots = Array.isArray(slotsElements.content)
? slotsElements.content
: [slotsElements.content];
useEffect(() => {
subscribeMethods({
setActiveTab: ({ activeTab }) => {
@ -117,13 +119,15 @@ export const Tabs = implementRuntimeComponent({
activeTab={String(activeTab)}
ref={ref}
>
{tabs.map((tab, idx) =>
tab.hidden ? null : (
<TabPane key={idx} title={tab.title} destroyOnHide={tab.destroyOnHide}>
{slots[idx]}
</TabPane>
)
)}
{tabs.map((tabName, idx) => (
<TabPane key={String(idx)} title={tabName}>
{slotsElements?.content
? slotsElements.content({
tabIndex: idx,
})
: null}
</TabPane>
))}
</BaseTabs>
);
});

View File

@ -43,7 +43,7 @@ export const TextArea = implementRuntimeComponent({
properties: TextAreaPropsSpec,
state: TextAreaStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['TextArea'],
events: ['onChange', 'onBlur', 'onFocus', 'onClear', 'onPressEnter'],
},

View File

@ -61,7 +61,7 @@ export const Timeline = implementRuntimeComponent({
properties: TimelinePropsSpec,
state: TimelineStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -35,7 +35,9 @@ export const Tooltip = implementRuntimeComponent({
openTooltip: Type.String(),
closeTooltip: Type.String(),
},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -65,13 +67,17 @@ export const Tooltip = implementRuntimeComponent({
popupVisible={popupVisible}
>
{/* need the child node of Tooltip accepts onMouseEnter, onMouseLeave, onFocus, onClick events */}
<span ref={elementRef}>{slotsElements.content || <Button>Hover Me</Button>}</span>
<span ref={elementRef}>
{slotsElements.content ? slotsElements.content({}) : <Button>Hover Me</Button>}
</span>
</BaseTooltip>
</div>
) : (
<div>
<BaseTooltip className={css(customStyle?.content)} {...cProps}>
<span ref={elementRef}>{slotsElements.content || <Button>Hover Me</Button>}</span>
<span ref={elementRef}>
{slotsElements.content ? slotsElements.content({}) : <Button>Hover Me</Button>}
</span>
</BaseTooltip>
</div>
);

View File

@ -87,7 +87,7 @@ export const Tree = implementRuntimeComponent({
properties: TreePropsSpec,
state: TreeStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onSelect'],
},

View File

@ -71,7 +71,7 @@ export const TreeSelect = implementRuntimeComponent({
properties: TreeSelectPropsSpec,
state: TreeSelectStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onChange'],
},
@ -81,7 +81,7 @@ export const TreeSelect = implementRuntimeComponent({
const { getElement, customStyle, mergeState, callbackMap } = props;
const ref = useRef<RefTreeSelectType | null>(null);
const [selectedOptions, setSelectedOptions] = useStateValue<string[]>(
const [selectedOptions, setSelectedOptions] = useStateValue(
defaultValue!,
mergeState,
updateWhenDefaultValueChanges,

View File

@ -16,9 +16,9 @@ export const LinkPropsSpec = {
hoverable: Type.Boolean({
title: 'Hoverable',
category: Category.Style,
description: 'Whether to hide background when hover'
description: 'Whether to hide background when hover',
}),
status: StringUnion(['default','success', 'warning', 'error'], {
status: StringUnion(['default', 'success', 'warning', 'error'], {
title: 'Status',
category: Category.Style,
}),

View File

@ -4,53 +4,61 @@ import { Category } from '../../constants/category';
import { CORE_VERSION, CoreWidgetName } from '@sunmao-ui/editor-sdk';
export const SliderPropsSpec = {
min: Type.Number({
title: 'Min',
category: Category.Basic
}),
max: Type.Number({
title: 'Max',
category: Category.Basic
}),
disabled: Type.Boolean({
title: 'Disabled',
category: Category.Basic
}),
toolTipPosition: Type.Optional(StringUnion(['top', 'tl', 'tr', 'bottom', 'bl', 'br', 'left', 'lt', 'lb', 'right', 'rt', 'rb'], {
min: Type.Number({
title: 'Min',
category: Category.Basic,
}),
max: Type.Number({
title: 'Max',
category: Category.Basic,
}),
disabled: Type.Boolean({
title: 'Disabled',
category: Category.Basic,
}),
toolTipPosition: Type.Optional(
StringUnion(
['top', 'tl', 'tr', 'bottom', 'bl', 'br', 'left', 'lt', 'lb', 'right', 'rt', 'rb'],
{
category: Category.Layout,
title: 'Tooltip Position'
})),
vertical: Type.Boolean({
title: 'Vertical',
category: Category.Layout,
}),
tooltipVisible: Type.Boolean({
title: 'Show Tooltip',
category: Category.Behavior
}),
range: Type.Boolean({
title: 'Enable Range',
category: Category.Behavior
}),
step: Type.Number({
title: 'Step',
category: Category.Behavior
}),
showTicks: Type.Boolean({
title: 'Show Ticks',
category: Category.Behavior
}),
marks: Type.Object({}, {
title: 'Marks',
widget: `${CORE_VERSION}/${CoreWidgetName.RecordField}`,
category: Category.Behavior
}),
onlyMarkValue: Type.Boolean({
title: 'Only Mark Value',
category: Category.Behavior
}),
reverse: Type.Boolean({
title: 'Reverse',
category: Category.Behavior
})
};
title: 'Tooltip Position',
}
)
),
vertical: Type.Boolean({
title: 'Vertical',
category: Category.Layout,
}),
tooltipVisible: Type.Boolean({
title: 'Show Tooltip',
category: Category.Behavior,
}),
range: Type.Boolean({
title: 'Enable Range',
category: Category.Behavior,
}),
step: Type.Number({
title: 'Step',
category: Category.Behavior,
}),
showTicks: Type.Boolean({
title: 'Show Ticks',
category: Category.Behavior,
}),
marks: Type.Object(
{},
{
title: 'Marks',
widget: `${CORE_VERSION}/${CoreWidgetName.RecordField}`,
category: Category.Behavior,
}
),
onlyMarkValue: Type.Boolean({
title: 'Only Mark Value',
category: Category.Behavior,
}),
reverse: Type.Boolean({
title: 'Reverse',
category: Category.Behavior,
}),
};

View File

@ -1,9 +1,14 @@
import { RuntimeFunctions } from '@sunmao-ui/runtime';
import { useState, useEffect } from 'react';
import { SlotSpec } from '@sunmao-ui/core';
export const useStateValue = <T>(
export const useStateValue = <
T,
TMethods = any,
TSlots extends Record<string, SlotSpec> = Record<string, SlotSpec>
>(
defaultValue: T,
mergeState?: RuntimeFunctions<Record<string, T>, any>['mergeState'],
mergeState?: RuntimeFunctions<Record<string, T>, TMethods, TSlots>['mergeState'],
updateWhenDefaultValueChanges?: boolean,
key = 'value'
): [T, React.Dispatch<React.SetStateAction<T>>] => {
@ -13,6 +18,7 @@ export const useStateValue = <T>(
if (mergeState) {
mergeState({ [key]: defaultValue });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
@ -20,7 +26,7 @@ export const useStateValue = <T>(
setValue(defaultValue);
mergeState({ [key]: defaultValue });
}
}, [defaultValue, updateWhenDefaultValueChanges, mergeState]);
}, [defaultValue, updateWhenDefaultValueChanges, mergeState, key]);
return [value, setValue];
};

View File

@ -3,6 +3,7 @@
import { ComponentMetadata } from '@sunmao-ui/core/lib/metadata';
import { ComponentImplProps } from '@sunmao-ui/runtime';
import { TLiteral, Type } from '@sinclair/typebox';
import { SlotSpec } from '@sunmao-ui/core';
export type IntoStringUnion<T> = {
[K in keyof T]: T[K] extends string ? TLiteral<T[K]> : never;
@ -37,11 +38,11 @@ export const getComponentProps = <
T,
TState,
TMethods,
KSlot extends string,
TSlots extends Record<string, SlotSpec>,
KStyleSlot extends string,
KEvent extends string
>(
props: T & ComponentImplProps<TState, TMethods, KSlot, KStyleSlot, KEvent>
props: T & ComponentImplProps<TState, TMethods, TSlots, KStyleSlot, KEvent>
) => {
const {
/* eslint-disable @typescript-eslint/no-unused-vars */

View File

@ -144,7 +144,9 @@ export default implementRuntimeComponent({
properties: StyleSpec,
state: Type.Object({}),
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -164,7 +166,7 @@ export default implementRuntimeComponent({
`}
ref={elementRef}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseBox>
);
});

View File

@ -50,7 +50,7 @@ export default implementRuntimeComponent({
methods: {
click: undefined,
},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onClick'],
},

View File

@ -100,7 +100,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: CheckboxStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -43,7 +43,9 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: [],
events: [],
},
@ -61,7 +63,7 @@ export default implementRuntimeComponent({
isDisabled={isDisabled}
onChange={val => setValue(val)}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseCheckboxGroup>
</Box>
);

View File

@ -76,7 +76,9 @@ export default implementRuntimeComponent({
confirmDialog: undefined,
cancelDialog: undefined,
},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['cancelDialog', 'confirmDialog'],
},
@ -160,7 +162,9 @@ export default implementRuntimeComponent({
ref={contentRef}
>
<AlertDialogHeader>{title}</AlertDialogHeader>
<AlertDialogBody>{slotsElements.content}</AlertDialogBody>
<AlertDialogBody>
{slotsElements.content ? slotsElements.content({}) : null}
</AlertDialogBody>
<AlertDialogFooter>
<Button

View File

@ -21,7 +21,7 @@ export default implementRuntimeComponent({
properties: Type.Object({}),
state: Type.Object({}),
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -37,7 +37,9 @@ export default implementRuntimeComponent({
methods: {
resetForm: undefined,
},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: ['onSubmit'],
},
@ -158,7 +160,7 @@ export default implementRuntimeComponent({
`}
ref={elementRef}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
{hideSubmit ? undefined : (
<Button
marginInlineStart="auto !important"

View File

@ -66,7 +66,9 @@ export default implementRuntimeComponent({
value: Type.Any(),
}),
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -149,7 +151,7 @@ export default implementRuntimeComponent({
}, [inputId, fieldName, isInvalid, isRequired, inputValue, mergeState]);
const placeholder = <Text color="gray.200">Please Add Input Here</Text>;
const slotView = slotsElements.content;
const slotView = slotsElements.content ? slotsElements.content({}) : null;
return (
<FormControl

View File

@ -41,7 +41,9 @@ export default implementRuntimeComponent({
spec: {
properties: PropsSpec,
state: Type.Object({}),
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
methods: {},
events: [],
@ -72,7 +74,7 @@ export default implementRuntimeComponent({
ref={elementRef}
{...{ direction, wrap, align, justify, spacing }}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseHStack>
);
}

View File

@ -159,7 +159,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: ['onLoad', 'onError'],
},

View File

@ -133,7 +133,7 @@ export default implementRuntimeComponent({
}),
resetInputValue: undefined,
},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -35,7 +35,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -43,7 +43,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: Type.Object({}),
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -50,7 +50,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
methods: {},
state: Type.Object({}),
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -115,7 +115,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -54,51 +54,58 @@ const PropsSpec = Type.Object({
category: APPEARANCE,
}
),
customerIncrement: Type.Optional(Type.Object(
{
bg: Type.String({
title: 'Background',
}),
children: Type.String({
title: 'Text',
}),
_active: Type.Object({
customerIncrement: Type.Optional(
Type.Object(
{
bg: Type.String({
title: 'Active Background',
title: 'Background',
}),
}, {
title: 'Active',
}),
},
{
title: 'Increment Button',
category: APPEARANCE,
}
)),
customerDecrement: Type.Optional(Type.Object(
{
bg: Type.String({
title: 'Background',
}),
children: Type.String({
title: 'Text',
}),
_active: Type.Object(
{
bg: Type.String({
title: 'Active Background',
}),
},
{
title: 'Active',
}
),
},
{
title: 'Decrement Button',
category: APPEARANCE,
}
)),
children: Type.String({
title: 'Text',
}),
_active: Type.Object(
{
bg: Type.String({
title: 'Active Background',
}),
},
{
title: 'Active',
}
),
},
{
title: 'Increment Button',
category: APPEARANCE,
}
)
),
customerDecrement: Type.Optional(
Type.Object(
{
bg: Type.String({
title: 'Background',
}),
children: Type.String({
title: 'Text',
}),
_active: Type.Object(
{
bg: Type.String({
title: 'Active Background',
}),
},
{
title: 'Active',
}
),
},
{
title: 'Decrement Button',
category: APPEARANCE,
}
)
),
});
const StateSpec = Type.Object({
@ -137,7 +144,7 @@ export default implementRuntimeComponent({
}),
resetInputValue: undefined,
},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -87,7 +87,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -42,7 +42,9 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -67,7 +69,7 @@ export default implementRuntimeComponent({
`}
ref={elementRef}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseRadioGroup>
);
}

View File

@ -20,7 +20,11 @@ export default implementRuntimeComponent({
properties: Type.Object({}),
state: Type.Object({}),
methods: {},
slots: ['root'],
slots: {
root: {
slotProps: Type.Object({}),
},
},
styleSlots: [],
events: [],
},
@ -32,7 +36,7 @@ export default implementRuntimeComponent({
useSystemColorMode: false,
})}
>
<div ref={elementRef}>{slotsElements.root}</div>
<div ref={elementRef}>{slotsElements.root ? slotsElements.root({}) : null}</div>
</ChakraProvider>
);
});

View File

@ -127,7 +127,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -82,14 +82,16 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: Type.Object({}),
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: [],
events: [],
},
})(({ direction, wrap, align, justify, spacing, slotsElements, elementRef }) => {
return (
<BaseStack {...{ direction, wrap, align, justify, spacing }} ref={elementRef}>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseStack>
);
});

View File

@ -48,7 +48,7 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: StateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: ['content'],
events: [],
},

View File

@ -33,8 +33,8 @@ const exampleProperties = {
type: 'text',
displayValue: '',
buttonConfig: {
handlers: []
}
handlers: [],
},
},
],
majorKey: 'id',
@ -62,7 +62,7 @@ export const implementTable = implementRuntimeComponent({
properties: PropsSpec,
state: TableStateSpec,
methods: {},
slots: [],
slots: {},
styleSlots: [],
events: [],
},

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { css } from '@emotion/css';
import {
Tabs as BaseTabs,
@ -49,7 +49,13 @@ export default implementRuntimeComponent({
state: StateSpec,
methods: {},
// tab slot is dynamic
slots: ['content'],
slots: {
content: {
slotProps: Type.Object({
tabIndex: Type.Number(),
}),
},
},
styleSlots: ['tabItem', 'tabContent'],
events: [],
},
@ -91,9 +97,6 @@ export default implementRuntimeComponent({
</TabList>
<TabPanels>
{tabNames.map((_, idx) => {
const ele = slotsElements.content
? ([] as React.ReactElement[]).concat(slotsElements.content)[idx]
: placeholder;
return (
<TabPanel
key={idx}
@ -101,7 +104,9 @@ export default implementRuntimeComponent({
${customStyle?.tabContent}
`}
>
{ele}
{slotsElements?.content
? slotsElements.content({ tabIndex: idx })
: placeholder}
</TabPanel>
);
})}

View File

@ -82,7 +82,9 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: Type.Object({}),
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: [],
events: [],
},
@ -113,7 +115,7 @@ export default implementRuntimeComponent({
shouldWrapChildren={shouldWrapChildren}
ref={elementRef}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</Tooltip>
);
}

View File

@ -41,7 +41,9 @@ export default implementRuntimeComponent({
spec: {
properties: PropsSpec,
state: Type.Object({}),
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
methods: {},
events: [],
@ -72,7 +74,7 @@ export default implementRuntimeComponent({
ref={elementRef}
{...{ direction, wrap, align, justify, spacing }}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseVStack>
);
}

View File

@ -33,7 +33,7 @@ describe('component', () => {
},
],
styleSlots: ['content'],
slots: [],
slots: {},
events: [],
},
});

View File

@ -2,6 +2,7 @@ import { JSONSchema7 } from 'json-schema';
import { parseVersion, Version } from './version';
import { ComponentMetadata } from './metadata';
import { MethodSchema } from './method';
import { SlotSpec } from './slot';
type ComponentSpec<
KMethodName extends string,
@ -13,7 +14,7 @@ type ComponentSpec<
state: JSONSchema7;
methods: Record<KMethodName, MethodSchema['parameters']>;
styleSlots: ReadonlyArray<KStyleSlot>;
slots: ReadonlyArray<KSlot>;
slots: Record<KSlot, SlotSpec>;
events: ReadonlyArray<KEvent>;
};

View File

@ -5,4 +5,5 @@ export * from './application';
export * from './method';
export * from './module';
export * from './version';
export * from './slot';
export * from './utilMethod';

View File

@ -0,0 +1,5 @@
import { JSONSchema7 } from 'json-schema';
export type SlotSpec = {
slotProps?: JSONSchema7;
};

View File

@ -39,6 +39,7 @@
"mitt": "^3.0.0",
"mobx-react-lite": "^3.2.2",
"react": "^17.0.2",
"react-color": "^2.19.3",
"react-dom": "^17.0.2",
"tern": "^0.24.3"
},

View File

@ -156,6 +156,7 @@ type ExpressionEditorStyleProps = {
height?: string;
maxHeight?: string;
paddingY?: string;
isHiddenExpand?: boolean;
};
type BaseExpressionEditorProps = CommonExpressionEditorProps &
ExpressionEditorStyleProps & {
@ -377,20 +378,22 @@ export const ExpressionEditor = React.forwardRef<
onFocus={onExpressionFocus}
onBlur={onExpressionBlur}
/>
<IconButton
aria-label="expand editor"
position="absolute"
right="0"
bottom="0"
size="xs"
variant="ghost"
colorScheme="blue"
zIndex="9"
className="expand-icon"
onClick={onExpand}
>
<ExternalLinkIcon />
</IconButton>
{compactOptions.isHiddenExpand ? null : (
<IconButton
aria-label="expand editor"
position="absolute"
right="0"
bottom="0"
size="xs"
variant="ghost"
colorScheme="blue"
zIndex="9"
className="expand-icon"
onClick={onExpand}
>
<ExternalLinkIcon />
</IconButton>
)}
</Box>
{isFocus ? (
<Box

View File

@ -1,6 +1,6 @@
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { FormControl, FormLabel, Input, Select } from '@chakra-ui/react';
import { Type, Static } from '@sinclair/typebox';
import { Type, Static, TSchema } from '@sinclair/typebox';
import { useFormik } from 'formik';
import { GLOBAL_UTIL_METHOD_ID } from '@sunmao-ui/runtime';
import { ComponentSchema } from '@sunmao-ui/core';
@ -9,7 +9,7 @@ import { implementWidget, mergeWidgetOptionsIntoSpec } from '../../utils/widget'
import { RecordWidget } from './RecordField';
import { SpecWidget } from './SpecWidget';
import { observer } from 'mobx-react-lite';
import { CORE_VERSION, CoreWidgetName } from '@sunmao-ui/shared';
import { CORE_VERSION, CoreWidgetName, parseTypeBox } from '@sunmao-ui/shared';
const EventWidgetOptions = Type.Object({});
@ -90,13 +90,12 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
]);
const params = useMemo(() => {
const params: Record<string, string> = {};
const parameters = formik.values.method.parameters;
for (const key in paramsSpec?.properties ?? {}) {
const defaultValue = (paramsSpec?.properties?.[key] as WidgetProps['spec'])
.defaultValue;
const spec = paramsSpec!.properties![key] as TSchema;
const defaultValue = spec.defaultValue;
params[key] = parameters?.[key] ?? defaultValue ?? '';
params[key] = defaultValue ?? parseTypeBox(spec);
}
return params;
@ -177,7 +176,9 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
const typeField = (
<FormControl>
<FormLabel fontSize='14px' fontWeight='normal'>Event Type</FormLabel>
<FormLabel fontSize="14px" fontWeight="normal">
Event Type
</FormLabel>
<Select
name="type"
onBlur={onSubmit}
@ -195,7 +196,9 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
);
const targetField = (
<FormControl>
<FormLabel fontSize='14px' fontWeight='normal'>Target Component</FormLabel>
<FormLabel fontSize="14px" fontWeight="normal">
Target Component
</FormLabel>
<Select
name="componentId"
onBlur={onSubmit}
@ -213,7 +216,9 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
);
const methodField = (
<FormControl>
<FormLabel fontSize='14px' fontWeight='normal'>Method</FormLabel>
<FormLabel fontSize="14px" fontWeight="normal">
Method
</FormLabel>
<Select
name="method.name"
onBlur={onSubmit}
@ -232,7 +237,9 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
const parametersField = (
<FormControl>
<FormLabel fontSize='14px' fontWeight='normal'>Parameters</FormLabel>
<FormLabel fontSize="14px" fontWeight="normal">
Parameters
</FormLabel>
<RecordWidget
component={component}
path={parametersPath}
@ -247,7 +254,9 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
const waitTypeField = (
<FormControl>
<FormLabel fontSize='14px' fontWeight='normal'>Wait Type</FormLabel>
<FormLabel fontSize="14px" fontWeight="normal">
Wait Type
</FormLabel>
<Select
name="wait.type"
onBlur={onSubmit}
@ -263,7 +272,9 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
const waitTimeField = (
<FormControl>
<FormLabel fontSize='14px' fontWeight='normal'>Wait Time</FormLabel>
<FormLabel fontSize="14px" fontWeight="normal">
Wait Time
</FormLabel>
<Input
name="wait.time"
onBlur={onSubmit}
@ -275,7 +286,9 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
const disabledField = (
<FormControl>
<FormLabel fontSize='14px' fontWeight='normal'>Disabled</FormLabel>
<FormLabel fontSize="14px" fontWeight="normal">
Disabled
</FormLabel>
<SpecWidget
{...props}
spec={disabledSpec}

View File

@ -0,0 +1,74 @@
import React from 'react';
import { CORE_VERSION, StyleWidgetName } from '@sunmao-ui/shared';
import { WidgetProps } from '../../../types/widget';
import { implementWidget, mergeWidgetOptionsIntoSpec } from '../../../utils/widget';
import { ExpressionWidget } from '../ExpressionWidget';
import {
Box,
InputGroup,
InputRightElement,
Popover,
PopoverTrigger,
PopoverContent,
PopoverArrow,
PopoverBody,
Portal,
} from '@chakra-ui/react';
import { SketchPicker } from 'react-color';
export const ColorWidget: React.FC<WidgetProps<{}, string>> = props => {
const { value, onChange } = props;
const onColorChange = ({ rgb }: any) => {
onChange(`rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, ${rgb.a})`);
};
const onInputChange = (value: string) => {
onChange(value);
};
return (
<InputGroup>
<ExpressionWidget
{...props}
spec={mergeWidgetOptionsIntoSpec(props.spec, {
compactOptions: { isHiddenExpand: true, height: '32px' },
})}
value={value}
onChange={onInputChange}
/>
<InputRightElement>
<Popover arrowSize={8} placement="left" matchWidth>
<PopoverTrigger>
<Box
cursor="pointer"
backgroundColor={value || '#fff'}
borderRadius="3px"
border="1px solid #eee"
width="20px"
height="20px"
boxShadow="rgba(149, 157, 165, 0.2) 0px 8px 24px"
/>
</PopoverTrigger>
<Portal>
<PopoverContent w="auto">
<PopoverArrow />
<PopoverBody padding={0}>
<SketchPicker
width="250px"
color={value || '#fff'}
onChangeComplete={onColorChange}
/>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
</InputRightElement>
</InputGroup>
);
};
export default implementWidget({
version: CORE_VERSION,
metadata: {
name: StyleWidgetName.Color,
},
})(ColorWidget);

View File

@ -1,9 +1,9 @@
import React from 'react';
import { HStack, Text } from '@chakra-ui/react';
import { HStack, Text, Box } from '@chakra-ui/react';
import { CORE_VERSION, StyleWidgetName } from '@sunmao-ui/shared';
import { WidgetProps } from '../../../types/widget';
import { implementWidget } from '../../../utils/widget';
import { ExpressionEditor } from '../../Form';
import { implementWidget, mergeWidgetOptionsIntoSpec } from '../../../utils/widget';
import { ExpressionWidget } from '../ExpressionWidget';
type Size = {
width?: number | string;
@ -14,31 +14,45 @@ export const SizeWidget: React.FC<WidgetProps<{}, Size>> = props => {
const { value, onChange } = props;
return (
<HStack width="full">
<Text>W</Text>
<ExpressionEditor
compact={true}
defaultCode={value.width === undefined ? '' : String(value.width) || ''}
onBlur={v => {
const newSize = {
...value,
width: v,
};
onChange(newSize);
}}
/>
<Text>H</Text>
<ExpressionEditor
compact={true}
defaultCode={value.height === undefined ? '' : String(value.height) || ''}
onBlur={v => {
const newSize = {
...value,
height: v,
};
onChange(newSize);
}}
/>
<HStack>
<HStack flex={1} minW={0}>
<Text>W</Text>
<Box flex={1} minW={0}>
<ExpressionWidget
{...props}
spec={mergeWidgetOptionsIntoSpec(props.spec, {
compactOptions: { height: '32px' },
})}
value={value.width === undefined ? '' : String(value.width) || ''}
onChange={v => {
const newSize = {
...value,
width: v,
};
onChange(newSize);
}}
/>
</Box>
</HStack>
<HStack flex={1} minW={0}>
<Text>H</Text>
<Box flex={1} minW={0}>
<ExpressionWidget
{...props}
spec={mergeWidgetOptionsIntoSpec(props.spec, {
compactOptions: { height: '32px' },
})}
value={value.height === undefined ? '' : String(value.height) || ''}
onChange={v => {
const newSize = {
...value,
height: v,
};
onChange(newSize);
}}
/>
</Box>
</HStack>
</HStack>
);
};

View File

@ -16,6 +16,7 @@ import eventWidgetSpec from './EventWidget';
import popoverWidgetSpec from './PopoverWidget';
import sizeWidgetSpec from './StyleWidgets/SizeWidget';
import fontWidgetSpec from './StyleWidgets/FontWidget';
import colorWidgetSpec from './StyleWidgets/ColorWidget';
export * from './SpecWidget';
export * from './ArrayField';
@ -34,6 +35,7 @@ export * from './EventWidget';
export * from './PopoverWidget';
export * from './StyleWidgets/SizeWidget';
export * from './StyleWidgets/FontWidget';
export * from './StyleWidgets/ColorWidget';
export const widgets: ImplementedWidget<any>[] = [
specWidgetSpec,
@ -53,4 +55,5 @@ export const widgets: ImplementedWidget<any>[] = [
popoverWidgetSpec,
sizeWidgetSpec,
fontWidgetSpec,
colorWidgetSpec,
];

View File

@ -0,0 +1,3 @@
declare module 'react-color' {
export { SketchPicker } from 'react-color';
}

View File

@ -60,7 +60,7 @@ export class ComponentModel implements IComponentModel {
}
get slots() {
return (this.spec ? this.spec.spec.slots : []) as SlotName[];
return (this.spec ? Object.keys(this.spec.spec.slots) : []) as SlotName[];
}
get events() {

View File

@ -2,6 +2,7 @@ import React, { useCallback, useMemo, useState } from 'react';
import produce from 'immer';
import { AddIcon, ChevronDownIcon, ChevronUpIcon, CloseIcon } from '@chakra-ui/icons';
import {
Box,
FormControl,
FormLabel,
VStack,
@ -18,7 +19,7 @@ import {
} from '@chakra-ui/react';
import { ComponentSchema } from '@sunmao-ui/core';
import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared';
import { FontWidget, SizeWidget } from '@sunmao-ui/editor-sdk';
import { FontWidget, SizeWidget, ColorWidget } from '@sunmao-ui/editor-sdk';
import { CssEditor } from '../../../components/CodeEditor';
import { genOperation } from '../../../operations';
import { formWrapperCSS } from '../style';
@ -193,9 +194,9 @@ export const StyleTraitForm: React.FC<Props> = props => {
</CollapsibleFormControl>
<CollapsibleFormControl label="Size">
<SizeWidget
value={_cssProperties || {}}
onChange={changeCssProperties}
{...widgetProps}
value={_cssProperties}
onChange={changeCssProperties}
/>
</CollapsibleFormControl>
<CollapsibleFormControl label="Font">
@ -205,6 +206,28 @@ export const StyleTraitForm: React.FC<Props> = props => {
{...widgetProps}
/>
</CollapsibleFormControl>
<CollapsibleFormControl label="Color">
<Box mb="8px">
<Text mb="8px">Text Color</Text>
<ColorWidget
{...widgetProps}
value={_cssProperties.color || ''}
onChange={(color: string) =>
changeCssProperties({ ..._cssProperties, color })
}
/>
</Box>
<Box mb="8px">
<Text mb="8px">Background Color</Text>
<ColorWidget
{...widgetProps}
value={_cssProperties.backgroundColor || ''}
onChange={(color: string) =>
changeCssProperties({ ..._cssProperties, backgroundColor: color })
}
/>
</Box>
</CollapsibleFormControl>
<CollapsibleFormControl label="CSS">
<CssEditor defaultCode={style} onBlur={v => changeStyleContent(i, v)} />
</CollapsibleFormControl>

View File

@ -21,10 +21,11 @@ export const DropSlotMask: React.FC<Props> = observer((props: Props) => {
const maskRef = useRef<HTMLDivElement>(null);
const hoverComponentType =
editorStore.components.find(c => c.id === hoverId)?.type || `${CORE_VERSION}/${CoreComponentName.Text}`;
editorStore.components.find(c => c.id === hoverId)?.type ||
`${CORE_VERSION}/${CoreComponentName.Text}`;
const slots = useMemo(() => {
return registry.getComponentByType(hoverComponentType).spec.slots || [];
return Object.keys(registry.getComponentByType(hoverComponentType).spec.slots) || [];
}, [hoverComponentType, registry]);
// calculate the slot which is being dragged over
@ -59,7 +60,7 @@ export const DropSlotMask: React.FC<Props> = observer((props: Props) => {
display="flex"
flexDirection={vertical ? 'column' : 'row'}
ref={maskRef}
border='1px solid orange'
border="1px solid orange"
>
{slots.map(slot => {
return (

View File

@ -102,7 +102,12 @@ export const ComponentItemView: React.FC<Props> = props => {
onClick={onClick}
>
{highlightBackground}
<HStack width="full" justify="space-between" spacing="0" paddingLeft={noChevron ? '6' : '0'}>
<HStack
width="full"
justify="space-between"
spacing="0"
paddingLeft={noChevron ? '6' : '0'}
>
{noChevron ? null : expandIcon}
<Text
cursor="pointer"

View File

@ -35,9 +35,7 @@ const observeSelected = (Component: React.FC<ComponentTreeProps>) => {
}
}, [selectedComponentId, component.id, onSelected]);
return (
<Component {...props} isSelected={selectedComponentId === component.id} />
);
return <Component {...props} isSelected={selectedComponentId === component.id} />;
};
return observer(ObserveActive);
@ -57,14 +55,17 @@ const ComponentTree = (props: ComponentTreeProps) => {
depth,
} = props;
const { registry, eventBus } = services;
const slots = registry.getComponentByType(component.type).spec.slots;
const slots = Object.keys(registry.getComponentByType(component.type).spec.slots);
const [isExpanded, setIsExpanded] = useState(true);
const [isDragging, setIsDragging] = useState(false);
const onChildSelected = useCallback((selectedId) => {
setIsExpanded(true);
onSelected?.(selectedId);
}, [onSelected]);
const onChildSelected = useCallback(
selectedId => {
setIsExpanded(true);
onSelected?.(selectedId);
},
[onSelected]
);
const slotsEle = useMemo(() => {
if (slots.length === 0) {

View File

@ -101,7 +101,7 @@ export function initSunmaoUIEditor(props: SunmaoUIEditorProps = {}) {
const onRefresh = useCallback(() => {
// need to reregister all the traits to clear the trait states which like `HasInitializedMap`
const traits = registry.getAllTraits();
stateManager.clear();
setStore(stateManager.store);
registry.unregisterAllTraits();

View File

@ -1,7 +1,4 @@
import {
StateManager,
ExpressionError,
} from '../src/services/StateManager';
import { StateManager, ExpressionError } from '../src/services/StateManager';
describe('evalExpression function', () => {
const scope = {
@ -111,7 +108,7 @@ describe('evalExpression function', () => {
text: 'hello',
},
noConsoleError: true,
ignoreEvalError: true
ignoreEvalError: true,
})
).toEqual(`hello {{myModule__state0.value}}`);
});

View File

@ -14,7 +14,14 @@ export const ImplWrapper = React.memo<ImplWrapperProps>(
if (prevChildren && nextChildren) {
isEqual = shallowCompareArray(prevChildren, nextChildren);
} else if (prevChildren === nextChildren) {
isEqual = true;
}
return isEqual && prevComponent === nextComponent;
return (
isEqual &&
prevComponent === nextComponent &&
// TODO: keep ImplWrapper memorized and get slot props from store
prevProps.slotProps === nextProps.slotProps
);
}
);

View File

@ -1,16 +1,16 @@
import React, { useEffect, useMemo, useRef, useState } from 'react';
import { merge, mergeWith, isArray } from 'lodash-es';
import { merge, mergeWith, isArray, omit } from 'lodash-es';
import { RuntimeTraitSchema } from '@sunmao-ui/core';
import { watch } from '../../../utils/watchReactivity';
import { ImplWrapperProps, TraitResult } from '../../../types';
import { useRuntimeFunctions } from './hooks/useRuntimeFunctions';
import { useSlotElements } from './hooks/useSlotChildren';
import { getSlotElements } from './hooks/useSlotChildren';
import { useGlobalHandlerMap } from './hooks/useGlobalHandlerMap';
import { useEleRef } from './hooks/useEleMap';
import { useGridLayout } from './hooks/useGridLayout';
export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps>(
(props, ref) => {
function ImplWrapperMain(props, ref) {
const { component: c, children } = props;
const { registry, stateManager } = props.services;
@ -24,7 +24,15 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
const [traitResults, setTraitResults] = useState<TraitResult<string, string>[]>(
() => {
return c.traits.map(t => executeTrait(t, stateManager.deepEval(t.properties, { fallbackWhenError: () => undefined })));
return c.traits.map(t =>
executeTrait(
t,
stateManager.deepEval(t.properties, {
scopeObject: { $slot: props.slotProps },
fallbackWhenError: () => undefined,
})
)
);
}
);
@ -50,7 +58,10 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
return newResults;
});
},
{ fallbackWhenError: () => undefined }
{
scopeObject: { $slot: props.slotProps },
fallbackWhenError: () => undefined,
}
);
stops.push(stop);
properties.push(result);
@ -59,7 +70,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]);
}, [c.id, c.traits, executeTrait, stateManager, props.slotProps]);
// reduce traitResults
const propsFromTraits: TraitResult<string, string>['props'] = useMemo(() => {
@ -84,7 +95,10 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
// component properties
const [evaledComponentProperties, setEvaledComponentProperties] = useState(() => {
return merge(
stateManager.deepEval(c.properties, { fallbackWhenError: () => undefined }),
stateManager.deepEval(c.properties, {
fallbackWhenError: () => undefined,
scopeObject: { $slot: props.slotProps },
}),
propsFromTraits
);
});
@ -95,13 +109,13 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
({ result: newResult }: any) => {
setEvaledComponentProperties({ ...newResult });
},
{ fallbackWhenError: () => undefined }
{ fallbackWhenError: () => undefined, scopeObject: { $slot: props.slotProps } }
);
// must keep this line, reason is the same as above
setEvaledComponentProperties({ ...result });
return stop;
}, [c.properties, stateManager]);
}, [c.properties, stateManager, props.slotProps]);
useEffect(() => {
const clearFunctions = propsFromTraits?.componentDidMount?.map(e => e());
@ -128,12 +142,21 @@ export const ImplWrapperMain = React.forwardRef<HTMLDivElement, ImplWrapperProps
);
const unmount = traitResults.some(result => result.unmount);
const slotElements = useSlotElements(props);
const slotElements = getSlotElements({
app: props.app,
childrenMap: props.childrenMap,
children: props.children,
component: props.component,
gridCallbacks: props.gridCallbacks,
services: props.services,
hooks: props.hooks,
isInModule: props.isInModule,
});
const C = unmount ? null : (
<Impl
key={c.id}
{...props}
{...omit(props, 'slotProps')}
{...mergedProps}
slotsElements={slotElements}
mergeState={mergeState}

View File

@ -7,19 +7,24 @@ import { ImplWrapperProps, TraitResult } from '../../../types';
import { watch } from '../../..';
export const UnmountImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperProps>(
(props, ref) => {
function UnmountImplWrapper(props, ref) {
const { component: c, services } = props;
const { stateManager, registry } = services;
const { executeTrait } = useRuntimeFunctions(props);
const unmountTraits = useMemo(
() => c.traits.filter(t => registry.getTraitByType(t.type).metadata.annotations?.beforeRender),
() =>
c.traits.filter(
t => registry.getTraitByType(t.type).metadata.annotations?.beforeRender
),
[c.traits, registry]
);
const [isHidden, setIsHidden] = useState(() => {
const results: TraitResult<string, string>[] = unmountTraits.map(t => {
const properties = stateManager.deepEval(t.properties);
const properties = stateManager.deepEval(t.properties, {
scopeObject: { $slot: props.slotProps },
});
return executeTrait(t, properties);
});
return results.some(result => result.unmount);
@ -42,8 +47,12 @@ export const UnmountImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperPr
const stops: ReturnType<typeof watch>[] = [];
if (unmountTraits.length > 0) {
unmountTraits.forEach(t => {
const { result, stop } = stateManager.deepEvalAndWatch(t.properties, newValue =>
traitChangeCallback(t, newValue.result)
const { result, stop } = stateManager.deepEvalAndWatch(
t.properties,
newValue => traitChangeCallback(t, newValue.result),
{
scopeObject: { $slot: props.slotProps },
}
);
traitChangeCallback(t, result);
stops.push(stop);
@ -52,7 +61,14 @@ export const UnmountImplWrapper = React.forwardRef<HTMLDivElement, ImplWrapperPr
return () => {
stops.forEach(stop => stop());
};
}, [c, executeTrait, unmountTraits, stateManager, traitChangeCallback]);
}, [
c,
executeTrait,
unmountTraits,
stateManager,
traitChangeCallback,
props.slotProps,
]);
// If a component is unmount, its state would be removed.
// So if it mount again, we should init its state again.

View File

@ -1,26 +1,25 @@
import React from 'react';
import { RuntimeComponentSchema } from '@sunmao-ui/core';
import { ImplWrapperProps } from '../../../../types';
import { SlotSpec } from '@sunmao-ui/core';
import { ImplWrapperProps, SlotsElements } from '../../../../types';
import { ImplWrapper } from '../ImplWrapper';
export function useSlotElements(props: ImplWrapperProps) {
export function getSlotElements(
props: ImplWrapperProps & { children?: React.ReactNode }
): SlotsElements<Record<string, SlotSpec>> {
const { component: c, childrenMap } = props;
const childrenCache = new Map<RuntimeComponentSchema, React.ReactElement>();
if (!childrenMap[c.id]) {
return {};
}
const slotElements: Record<string, React.ReactElement[] | React.ReactElement> = {};
const slotElements: SlotsElements<Record<string, SlotSpec>> = {};
for (const slot in childrenMap[c.id]) {
const slotChildren = childrenMap[c.id][slot].map(child => {
if (!childrenCache.get(child)) {
const ele = <ImplWrapper key={child.id} {...props} component={child} />;
childrenCache.set(child, ele);
}
return childrenCache.get(child)!;
return <ImplWrapper key={child.id} {...props} component={child} />;
});
slotElements[slot] = slotChildren.length === 1 ? slotChildren[0] : slotChildren;
slotElements[slot] = function getSlot(slotProps) {
return slotChildren.map(child => React.cloneElement(child, { slotProps }));
};
}
return slotElements;
}

View File

@ -150,13 +150,7 @@ const ModuleRendererContent = React.forwardRef<
services.apiService.off('moduleEvent', h);
});
};
}, [
evalScope,
handlers,
moduleId,
services.apiService,
services.stateManager,
]);
}, [evalScope, handlers, moduleId, services.apiService, services.stateManager]);
const result = useMemo(() => {
// Must init components' state, otherwise store cannot listen these components' state changing

View File

@ -20,7 +20,7 @@ export default implementRuntimeComponent({
properties: Type.Object({}),
state: Type.Object({}),
methods: {},
slots: [],
slots: {},
styleSlots: [],
events: [],
},

View File

@ -2,7 +2,7 @@ import { Type } from '@sinclair/typebox';
import { css } from '@emotion/css';
import { implementRuntimeComponent } from '../../utils/buildKit';
import React, { useEffect, useRef } from 'react';
import { CORE_VERSION } from '@sunmao-ui/shared';
import { CORE_VERSION } from '@sunmao-ui/shared';
const PropsSpec = Type.Object({
multiple: Type.Boolean({
@ -57,7 +57,9 @@ export default implementRuntimeComponent({
methods: {
selectFile: Type.Object({}),
},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -105,7 +107,7 @@ export default implementRuntimeComponent({
accept={fileTypes.join(',')}
onChange={onChange}
/>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</div>
);
}

View File

@ -54,7 +54,9 @@ export default implementRuntimeComponent({
properties: PropsSpec,
state: {},
methods: {},
slots: ['content'],
slots: {
content: { slotProps: Type.Object({}) },
},
styleSlots: ['content'],
events: [],
},
@ -78,7 +80,7 @@ export default implementRuntimeComponent({
${customStyle?.content}
`}
>
{slotsElements.content}
{slotsElements.content ? slotsElements.content({}) : null}
</BaseGridLayout>
</Suspense>
);

View File

@ -23,7 +23,7 @@ export default implementRuntimeComponent({
properties: ModuleSpec,
state: {},
methods: {},
slots: [],
slots: {},
styleSlots: [],
events: [],
},

Some files were not shown because too many files have changed in this diff Show More