Proposal: Custom Widget
See original GitHub issueDemand Overview
We would allow component developers to customize the form widgets for the properties. And we may support these features for the widget:
- Registering Widgets
- Widget Props
- Using Other Widgets In The Custom Widget
- Using One Widget To Configure Multiple Properties
- Widget Options
Scheme Design
Registering Widgets
We should register the widgets like registering the components, traits, and so on. Thus we also define a spec for widgets to describe their information:
interface WidgetSpec {
version: string;
metadata: {
name: string;
}
}
Widget Props
The interface of props which would pass to widget component:
interface WidgetProps {
// the property schema
schema: Schema & EditorSchema;
// the value of the property
value: any;
// runtime services
readonly services: Service;
// components in the editor
readonly components: Component[];
// the handler of value changed
onChange: (value: any)=> void;
// render the sub-property schema normally
renderBySchema: ({schema: Schema, onChange: (value: any)=> void, value: any})=> ReactElement;
}
Using Other Widgets In The Custom Widget
If component developers want to use other widgets for sub-properties, they can define the widgets in the sub-properties spec options and use renderBySchema
function in the custom widget component.
In The following, I will show you an example of implementing a custom widget for the Table
component’s column
property.
First of all, we should define the column
property’s spec.
{
"column": Type.Array(Type.Object({
...,
// use the common widget by its type
"key": Type.KeyOf(...),
// use the another widget defined in spec options
"handlers": Type.Object({}, { "widget": "EventHandler" }),
}, {
"widget": "TableColumn"
}))
}
And then we should implement the widget component and call renderBySchema
inside.
function TableColumn (props) {
const { schema, value, onChange, renderBySchema } = props;
const onValueChange = (key, value)=> {
onChange({
...value,
[key]: value
})
}
return (
<div>
{/* implement custom widget here */ }
{/* use others widgets for some properties */ }
{ renderBySchema({
schema: schema.properties.key,
value: value.key,
onChange: (v)=> onValueChange('key', value)
}) }
{ renderBySchema({
schema: schema.properties.handlers,
value: value.handlers,
onChange: (v)=> onValueChange('handlers', value)
}) }
</div>
)
}
Using One Widget To Config Multiple Properties
In some situations, some properties should be configured by the same widget is better.
For example, some components would have the location properties such as left
, right
, top
, bottom
, which should be configured by a location widget.
There are two ways that come to my mind. The first one is using Type.Object
to wrap the properties which should configure by one widget. And another way is adding a new field like widgetGroup
into property spec options to define a virtual group for properties.
The spec examples of these two ways are following this:
Type.Object
{
"location": Type.Object({
"left": Type.String(),
"right": Type.String(),
"top": Type.String(),
"bottom": Type.String(),
}, {
"widget": "Location"
})
}
Group
{
"left": Type.String({ "widgetGroup": "Location" }),
"right": Type.String({ "widgetGroup": "Location" }),
"top": Type.String({ "widgetGroup": "Location" }),
"bottom": Type.String({ "widgetGroup": "Location" }),
}
The Type.Object
way is already implemented and it’s easy to use. Although it makes the component developers need to unwrap the properties to take their values in the components, I think it’s doesn’t matter.
The second way needs more additional editor implementation to support it, which would break the currently onChange
logic.
Thus I prefer the first way, which is to use Type.Object
to define the properties group.
Widget Options
The component developers may also want to pass some additional options to the widget component. We would provide the new field widgetOptions
in properties spec options.
In the above location widget example, we may want to pass a keymap to the widget component for key transformed. For example, if we have paddingLeft
property instead of left
property, we should transform it.
So, we can define the map
in the widgetOptions
to tell the custom widget how to transform the keys.
{
"location": Type.Object({
"paddingLeft": Type.String(),
"paddingRight": Type.String(),
"paddingTop": Type.String(),
"paddingBottom": Type.String(),
}, {
"widget": "Location",
"wigetOptions": {
"map": {
"left": "paddingLeft",
"right" "paddingRight",
"top": "paddingTop",
"bottom": "paddingBottom"
}
}
})
}
And then we read the widgetOptions
in the widget component and implement the transformed logic.
function Location (props) {
const { schema, value, onChange } = props;
const { widgetOptions = {} } = schema;
const { map = {} } = widgetOptions;
const onValueChange = (key, value)=> {
onChange({
...value,
[map[key] ?? key]: value
});
}
return (
<div>
<input
value={value[map.left ?? 'left']}
onChange={(v)=> { onValueChange('left', v) }}
/>
...
</div>
)
}
Issue Analytics
- State:
- Created 2 years ago
- Comments:8 (7 by maintainers)
OK. I’m starting to implement it and I will do these jobs:
@sunmao-ui/editor-sdk
packagekit
object and exporting it@sunmao-ui/editor-sdk
packageSounds good. @tanbowensg @xzdry please confirm the latest design before @MrWindlike starts to implement it.