@kubohiroya/turbowarp-extension-data-editor 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/build-info.json +4 -4
- package/dist/config/feature-flags.d.ts +5 -0
- package/dist/config/feature-flags.js +6 -0
- package/dist/form-presentation.d.ts +8 -0
- package/dist/form-presentation.js +61 -0
- package/dist/layout-view.d.ts +21 -0
- package/dist/layout-view.js +127 -0
- package/dist/layout.d.ts +31 -0
- package/dist/layout.js +63 -0
- package/dist/react.css +126 -0
- package/dist/react.d.ts +5 -0
- package/dist/react.js +10 -2
- package/dist/rjsf-templates.d.ts +18 -0
- package/dist/rjsf-templates.js +199 -0
- package/dist/rjsf.d.ts +11 -8
- package/dist/rjsf.js +35 -12
- package/dist/schema-presentation.d.ts +10 -0
- package/dist/schema-presentation.js +109 -0
- package/dist/source-editor.d.ts +2 -1
- package/dist/source-editor.js +23 -5
- package/dist/source-view.d.ts +1 -1
- package/dist/source-view.js +222 -122
- package/dist/turbowarp/appearance.d.ts +1 -1
- package/dist/turbowarp/appearance.js +23 -5
- package/dist/turbowarp/bridge.js +5 -1
- package/dist/turbowarp.d.ts +2 -1
- package/dist/turbowarp.js +3 -2
- package/dist/webpack4/metafile.json +162 -21
- package/dist/webpack4/react.js +12 -6
- package/dist/webpack4/react.js.map +2 -2
- package/dist/webpack4/rjsf.js +1117 -261
- package/dist/webpack4/rjsf.js.map +4 -4
- package/dist/webpack4/turbowarp.js +22 -5
- package/dist/webpack4/turbowarp.js.map +2 -2
- package/docs/README.md +1 -0
- package/docs/api.md +7 -0
- package/docs/common-layout.ja.md +117 -0
- package/docs/package-distribution.md +5 -0
- package/package.json +1 -7
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { ObjectLayout, useCommonLayout } from './layout-view.js';
|
|
3
|
+
import { resolveEditorLayout } from './layout.js';
|
|
4
|
+
import { FormHelp, propertyLabel } from './form-presentation.js';
|
|
5
|
+
const ItemNumber = React.createContext(1);
|
|
6
|
+
const PropertyKey = React.createContext(undefined);
|
|
7
|
+
function errorCount(value) {
|
|
8
|
+
if (!value || typeof value !== 'object')
|
|
9
|
+
return 0;
|
|
10
|
+
if (Array.isArray(value))
|
|
11
|
+
return value.length;
|
|
12
|
+
return Object.values(value).reduce((count, child) => count + errorCount(child), 0);
|
|
13
|
+
}
|
|
14
|
+
/** Reuse the v5 object template's title, description and add-property controls. */
|
|
15
|
+
export function layoutObjectTemplate(DefaultTemplate) {
|
|
16
|
+
return function LayoutObject(props) {
|
|
17
|
+
var _a, _b;
|
|
18
|
+
const enabled = useCommonLayout();
|
|
19
|
+
const title = enabled && !props.schema.title && !((_a = props.uiSchema) === null || _a === void 0 ? void 0 : _a['ui:title']) ? propertyLabel(props.title) : props.title;
|
|
20
|
+
const view = { ...props, title };
|
|
21
|
+
const knownFields = [...new Set([...Object.keys((_b = props.schema.properties) !== null && _b !== void 0 ? _b : {}), ...props.properties.map(property => property.name)])];
|
|
22
|
+
const resolution = enabled ? resolveEditorLayout(props.uiSchema, knownFields) : { kind: 'absent' };
|
|
23
|
+
if (resolution.kind === 'absent')
|
|
24
|
+
return React.createElement(DefaultTemplate, { ...view });
|
|
25
|
+
if (resolution.kind === 'invalid')
|
|
26
|
+
return React.createElement(React.Fragment, null,
|
|
27
|
+
React.createElement("p", { role: "status", "data-layout-diagnostic": "" },
|
|
28
|
+
"\u30EC\u30A4\u30A2\u30A6\u30C8\u3092\u9069\u7528\u3067\u304D\u307E\u305B\u3093: ",
|
|
29
|
+
resolution.diagnostic),
|
|
30
|
+
React.createElement(DefaultTemplate, { ...props }));
|
|
31
|
+
const content = React.createElement(ObjectLayout, { key: "__editor_layout__", ui: props.uiSchema, knownFields: knownFields, fallback: null, fields: props.properties.map(property => {
|
|
32
|
+
var _a;
|
|
33
|
+
return ({
|
|
34
|
+
name: property.name, content: property.content, errors: errorCount((_a = props.errorSchema) === null || _a === void 0 ? void 0 : _a[property.name])
|
|
35
|
+
});
|
|
36
|
+
}) });
|
|
37
|
+
return React.createElement(DefaultTemplate, { ...view, properties: [{ name: '__editor_layout__', content, hidden: false, disabled: !!props.disabled, readonly: !!props.readonly }] });
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function compactDescriptionTemplate(DefaultTemplate) {
|
|
41
|
+
return function CompactDescription(props) {
|
|
42
|
+
const enabled = useCommonLayout();
|
|
43
|
+
return enabled && typeof props.description === 'string' ? React.createElement(FormHelp, { id: props.id, description: props.description }) : React.createElement(DefaultTemplate, { ...props });
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function compactArrayTitleTemplate(DefaultTemplate) {
|
|
47
|
+
return function CompactArrayTitle(props) {
|
|
48
|
+
var _a;
|
|
49
|
+
const enabled = useCommonLayout();
|
|
50
|
+
if (!enabled || !props.title || props.schema.title || ((_a = props.uiSchema) === null || _a === void 0 ? void 0 : _a['ui:title']))
|
|
51
|
+
return React.createElement(DefaultTemplate, { ...props });
|
|
52
|
+
return React.createElement(DefaultTemplate, { ...props, title: propertyLabel(props.title) });
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function compactArrayTemplate(DefaultTemplate) {
|
|
56
|
+
return function CompactArray(props) {
|
|
57
|
+
const enabled = useCommonLayout();
|
|
58
|
+
return React.createElement(React.Fragment, null,
|
|
59
|
+
React.createElement(DefaultTemplate, { ...props }),
|
|
60
|
+
enabled && props.items.length === 0 && React.createElement("p", { className: "editor-form-hint" }, "\u9805\u76EE\u306A\u3057"));
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
/** Boolean widgets own their visible label instead of using the surrounding FieldTemplate. */
|
|
64
|
+
export function compactCheckboxWidget(DefaultWidget) {
|
|
65
|
+
return function CompactCheckbox(props) {
|
|
66
|
+
var _a;
|
|
67
|
+
const enabled = useCommonLayout();
|
|
68
|
+
const label = enabled && !props.schema.title && !((_a = props.uiSchema) === null || _a === void 0 ? void 0 : _a['ui:title']) && !props.options.title ? propertyLabel(props.label) : props.label;
|
|
69
|
+
return React.createElement(DefaultWidget, { ...props, label: label });
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Keep the property key out of the value's visible label. */
|
|
73
|
+
export function valueFieldTemplate(DefaultTemplate, flag) {
|
|
74
|
+
return function ValueField(props) {
|
|
75
|
+
var _a;
|
|
76
|
+
const enabled = useCommonLayout();
|
|
77
|
+
if (!(flag in props.schema))
|
|
78
|
+
return React.createElement(DefaultTemplate, { ...props, label: enabled && !props.schema.title && !((_a = props.uiSchema) === null || _a === void 0 ? void 0 : _a['ui:title']) ? propertyLabel(props.label) : props.label });
|
|
79
|
+
return React.createElement(PropertyKey.Provider, { value: props.label },
|
|
80
|
+
React.createElement(DefaultTemplate, { ...props, label: "\u5024" }));
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Keep RJSF's object layout while numbering each editable additional property. */
|
|
84
|
+
export function numberedObjectTemplate(DefaultTemplate, flag) {
|
|
85
|
+
return function ObjectItems(props) {
|
|
86
|
+
let number = 0;
|
|
87
|
+
const properties = props.properties.map(property => {
|
|
88
|
+
var _a;
|
|
89
|
+
const schema = (_a = props.schema.properties) === null || _a === void 0 ? void 0 : _a[property.name];
|
|
90
|
+
if (!schema || typeof schema !== 'object' || !(flag in schema) || property.hidden)
|
|
91
|
+
return property;
|
|
92
|
+
return { ...property, content: React.createElement(ItemNumber.Provider, { key: property.name, value: ++number }, property.content) };
|
|
93
|
+
});
|
|
94
|
+
return React.createElement(DefaultTemplate, { ...props, properties: properties });
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function additionalItemTemplate(flag) {
|
|
98
|
+
return function AdditionalItem(props) {
|
|
99
|
+
const number = React.useContext(ItemNumber);
|
|
100
|
+
const propertyKey = React.useContext(PropertyKey);
|
|
101
|
+
const { id, classNames, style, children, schema, label, disabled, readonly, onKeyChange, onDropPropertyClick } = props;
|
|
102
|
+
if (!(flag in schema))
|
|
103
|
+
return React.createElement("div", { className: classNames, style: style }, children);
|
|
104
|
+
const key = propertyKey !== null && propertyKey !== void 0 ? propertyKey : label;
|
|
105
|
+
return React.createElement("div", { className: classNames, style: style, "data-block-editor-item": "" },
|
|
106
|
+
React.createElement("div", { "data-block-editor-item-heading": "" },
|
|
107
|
+
React.createElement("span", null,
|
|
108
|
+
"\u30FB\u9805\u76EE",
|
|
109
|
+
number),
|
|
110
|
+
React.createElement(ItemMenu, { disabled: disabled || readonly, onClick: onDropPropertyClick(key) })),
|
|
111
|
+
React.createElement("div", { "data-block-editor-pair-row": "" },
|
|
112
|
+
React.createElement("label", { htmlFor: `${id}-key` }, "\u30AD\u30FC"),
|
|
113
|
+
React.createElement("input", { id: `${id}-key`, type: "text", defaultValue: key, disabled: disabled, readOnly: readonly, onBlur: event => onKeyChange(event.target.value) })),
|
|
114
|
+
React.createElement("div", { "data-block-editor-pair-row": "" }, children));
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/** Deletion is available only after explicitly opening the item's menu. */
|
|
118
|
+
export function ItemMenu({ disabled, onClick }) {
|
|
119
|
+
const [open, setOpen] = React.useState(false);
|
|
120
|
+
const container = React.useRef(null);
|
|
121
|
+
const trigger = React.useRef(null);
|
|
122
|
+
const item = React.useRef(null);
|
|
123
|
+
React.useEffect(() => {
|
|
124
|
+
var _a;
|
|
125
|
+
if (!open)
|
|
126
|
+
return;
|
|
127
|
+
(_a = item.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
128
|
+
const closeOutside = (event) => {
|
|
129
|
+
var _a;
|
|
130
|
+
if (!((_a = container.current) === null || _a === void 0 ? void 0 : _a.contains(event.target)))
|
|
131
|
+
setOpen(false);
|
|
132
|
+
};
|
|
133
|
+
// RJSF and its host modal have separate React roots. Stop Escape before either root delegates it.
|
|
134
|
+
const keepEscapeInMenu = (event) => {
|
|
135
|
+
var _a;
|
|
136
|
+
if (event.key !== 'Escape' || event.isComposing || event.keyCode === 229)
|
|
137
|
+
return;
|
|
138
|
+
event.preventDefault();
|
|
139
|
+
event.stopPropagation();
|
|
140
|
+
setOpen(false);
|
|
141
|
+
(_a = trigger.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
142
|
+
};
|
|
143
|
+
const menu = container.current;
|
|
144
|
+
menu === null || menu === void 0 ? void 0 : menu.addEventListener('keydown', keepEscapeInMenu);
|
|
145
|
+
document.addEventListener('mousedown', closeOutside);
|
|
146
|
+
return () => {
|
|
147
|
+
menu === null || menu === void 0 ? void 0 : menu.removeEventListener('keydown', keepEscapeInMenu);
|
|
148
|
+
document.removeEventListener('mousedown', closeOutside);
|
|
149
|
+
};
|
|
150
|
+
}, [open]);
|
|
151
|
+
React.useEffect(() => { if (disabled)
|
|
152
|
+
setOpen(false); }, [disabled]);
|
|
153
|
+
return React.createElement("div", { "data-block-editor-item-menu": "", ref: container, onBlur: event => {
|
|
154
|
+
if (!event.currentTarget.contains(event.relatedTarget))
|
|
155
|
+
setOpen(false);
|
|
156
|
+
}, onKeyDown: event => {
|
|
157
|
+
var _a, _b;
|
|
158
|
+
if (event.key === 'Escape' && open && !event.nativeEvent.isComposing && event.keyCode !== 229) {
|
|
159
|
+
event.preventDefault();
|
|
160
|
+
event.stopPropagation();
|
|
161
|
+
setOpen(false);
|
|
162
|
+
(_a = trigger.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
163
|
+
}
|
|
164
|
+
else if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(event.key)) {
|
|
165
|
+
event.preventDefault();
|
|
166
|
+
if (open)
|
|
167
|
+
(_b = item.current) === null || _b === void 0 ? void 0 : _b.focus();
|
|
168
|
+
else if (!disabled)
|
|
169
|
+
setOpen(true);
|
|
170
|
+
}
|
|
171
|
+
} },
|
|
172
|
+
React.createElement("button", { "aria-label": "\u9805\u76EE\u306E\u30E1\u30CB\u30E5\u30FC", "aria-haspopup": "menu", "aria-expanded": open && !disabled, disabled: disabled, ref: trigger, type: "button", onClick: () => setOpen(!open) }, "\u2026"),
|
|
173
|
+
open && !disabled ? React.createElement("div", { role: "menu", "aria-label": "\u9805\u76EE\u306E\u64CD\u4F5C" },
|
|
174
|
+
React.createElement("button", { ref: item, role: "menuitem", type: "button", onClick: event => {
|
|
175
|
+
var _a;
|
|
176
|
+
setOpen(false);
|
|
177
|
+
(_a = trigger.current) === null || _a === void 0 ? void 0 : _a.focus();
|
|
178
|
+
onClick === null || onClick === void 0 ? void 0 : onClick(event);
|
|
179
|
+
} }, "\uD83D\uDDD1\uFE0F\u3053\u306E\u9805\u76EE\u3092\u524A\u9664")) : null);
|
|
180
|
+
}
|
|
181
|
+
export function AddItemButton({ disabled, onClick, className }) {
|
|
182
|
+
return React.createElement("button", { className: className, "data-block-editor-add-item": "", disabled: disabled, type: "button", onClick: onClick }, "\u2795\u9805\u76EE\u3092\u8FFD\u52A0");
|
|
183
|
+
}
|
|
184
|
+
export function ArrayItem(props) {
|
|
185
|
+
const { children, className, disabled, readonly, index, hasMoveUp, hasMoveDown, hasCopy, hasRemove, onReorderClick, onCopyIndexClick, onDropIndexClick } = props;
|
|
186
|
+
const locked = disabled || readonly;
|
|
187
|
+
return React.createElement("div", { className: className, "data-block-editor-item": "" },
|
|
188
|
+
React.createElement("div", { "data-block-editor-item-heading": "" },
|
|
189
|
+
React.createElement("span", null,
|
|
190
|
+
"\u30FB\u9805\u76EE",
|
|
191
|
+
index + 1),
|
|
192
|
+
React.createElement("div", { "data-block-editor-item-actions": "" },
|
|
193
|
+
hasMoveUp || hasMoveDown ? React.createElement(React.Fragment, null,
|
|
194
|
+
React.createElement("button", { "aria-label": "\u9805\u76EE\u3092\u4E0A\u3078\u79FB\u52D5", disabled: locked || !hasMoveUp, type: "button", onClick: onReorderClick(index, index - 1) }, "\u2191"),
|
|
195
|
+
React.createElement("button", { "aria-label": "\u9805\u76EE\u3092\u4E0B\u3078\u79FB\u52D5", disabled: locked || !hasMoveDown, type: "button", onClick: onReorderClick(index, index + 1) }, "\u2193")) : null,
|
|
196
|
+
hasCopy ? React.createElement("button", { disabled: locked, type: "button", onClick: onCopyIndexClick(index) }, "\u9805\u76EE\u3092\u8907\u88FD") : null,
|
|
197
|
+
hasRemove ? React.createElement(ItemMenu, { disabled: locked, onClick: onDropIndexClick(index) }) : null)),
|
|
198
|
+
children);
|
|
199
|
+
}
|
package/dist/rjsf.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import type { EditorFeatureFlags } from './config/feature-flags.js';
|
|
2
|
+
export type { EditorFeatureFlags } from './config/feature-flags.js';
|
|
3
|
+
export type { EditorLayout, EditorLayoutItem } from './layout.js';
|
|
1
4
|
import type { FixedInputMessageSource } from './fixed-input-view.js';
|
|
2
5
|
export type { FixedInputViewMessages, FixedInputMessageSource } from './fixed-input-view.js';
|
|
3
6
|
import type { FixedInputEditorOptions } from './fixed-input-editor.js';
|
|
@@ -9,11 +12,11 @@ export { createSourceEditor } from './source-editor.js';
|
|
|
9
12
|
export type * from './source-types.js';
|
|
10
13
|
export type * from './prepared-types.js';
|
|
11
14
|
import type { RJSFSchema } from '@rjsf/utils';
|
|
12
|
-
import type { SchemaBundle, SchemaValidationContext, SyncOwnerValidator } from './schema-types.js';
|
|
15
|
+
import type { SchemaBundle, SchemaJsonObject, SchemaValidationContext, SyncOwnerValidator } from './schema-types.js';
|
|
13
16
|
import type { SourceFormat } from './codec.js';
|
|
14
17
|
import { EditorRegistry } from './registry.js';
|
|
15
18
|
import { EDITOR_API_VERSION } from './types.js';
|
|
16
|
-
import type { BlockSnapshot, EditorDefinition,
|
|
19
|
+
import type { BlockSnapshot, EditorDefinition, JsonValue, ValidationIssue } from './types.js';
|
|
17
20
|
export interface SchemaEditorOptions {
|
|
18
21
|
readonly apiVersion: typeof EDITOR_API_VERSION;
|
|
19
22
|
readonly extensionId: string;
|
|
@@ -24,7 +27,7 @@ export interface SchemaEditorOptions {
|
|
|
24
27
|
/** Defaults to JSON for compatibility with existing registrations. */
|
|
25
28
|
readonly format?: SourceFormat;
|
|
26
29
|
readonly schema: RJSFSchema;
|
|
27
|
-
readonly uiSchema?:
|
|
30
|
+
readonly uiSchema?: SchemaJsonObject;
|
|
28
31
|
/** Optional business validation, always run after schema validation when saving. */
|
|
29
32
|
validateData?(data: JsonValue, snapshot: BlockSnapshot): readonly ValidationIssue[] | Promise<readonly ValidationIssue[]>;
|
|
30
33
|
}
|
|
@@ -34,7 +37,7 @@ export interface SchemaBundleEditorOptions<C = null> extends Omit<SchemaEditorOp
|
|
|
34
37
|
readonly validationContext: () => SchemaValidationContext<C>;
|
|
35
38
|
readonly ownerValidate?: SyncOwnerValidator<C>;
|
|
36
39
|
}
|
|
37
|
-
export declare function createSchemaBundleEditor<C>(options: SchemaBundleEditorOptions<C
|
|
40
|
+
export declare function createSchemaBundleEditor<C>(options: SchemaBundleEditorOptions<C>, features?: EditorFeatureFlags): EditorDefinition;
|
|
38
41
|
export interface BlockEditorRegistrationAPI {
|
|
39
42
|
readonly apiVersion: typeof EDITOR_API_VERSION;
|
|
40
43
|
register(definition: EditorDefinition | SchemaEditorOptions): () => void;
|
|
@@ -60,21 +63,21 @@ export interface BlockEditorService {
|
|
|
60
63
|
subscribe(listener: () => void): () => void;
|
|
61
64
|
}
|
|
62
65
|
/** A host owns the service's lifetime; this module never stores VM or workspace instances. */
|
|
63
|
-
export declare function createBlockEditorService(options: {
|
|
66
|
+
export declare function createBlockEditorService(options: EditorFeatureFlags & {
|
|
64
67
|
readonly fixedInputs: true;
|
|
65
68
|
readonly fixedInputMessages?: FixedInputMessageSource;
|
|
66
69
|
}): BlockEditorService & {
|
|
67
70
|
readonly api: FixedBlockEditorRegistrationAPI;
|
|
68
71
|
};
|
|
69
|
-
export declare function createBlockEditorService(options?: {
|
|
72
|
+
export declare function createBlockEditorService(options?: EditorFeatureFlags & {
|
|
70
73
|
readonly fixedInputs?: false;
|
|
71
74
|
readonly fixedInputMessages?: FixedInputMessageSource;
|
|
72
75
|
}): BlockEditorService & {
|
|
73
76
|
readonly api: SourceBlockEditorRegistrationAPI;
|
|
74
77
|
};
|
|
75
|
-
export declare function createBlockEditorService(options: {
|
|
78
|
+
export declare function createBlockEditorService(options: EditorFeatureFlags & {
|
|
76
79
|
readonly fixedInputs?: boolean;
|
|
77
80
|
readonly fixedInputMessages?: FixedInputMessageSource;
|
|
78
81
|
}): BlockEditorService;
|
|
79
82
|
/** Build an editor with JSON/YAML serialization, schema validation and the standard RJSF body. */
|
|
80
|
-
export declare function createSchemaEditor(options: SchemaEditorOptions): EditorDefinition;
|
|
83
|
+
export declare function createSchemaEditor(options: SchemaEditorOptions, features?: EditorFeatureFlags): EditorDefinition;
|
package/dist/rjsf.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { captureFeatureFlags } from './config/feature-flags.js';
|
|
3
|
+
import { LayoutHost } from './layout-view.js';
|
|
2
4
|
import { createSourceEditor } from './source-editor.js';
|
|
3
5
|
import { createFixedInputEditor } from './fixed-input-editor.js';
|
|
4
6
|
import { captureFixedInputMessages, resolveFixedInputMessages, mountFixedInputView } from './fixed-input-view.js';
|
|
@@ -16,6 +18,7 @@ import { cloneJson } from './data.js';
|
|
|
16
18
|
import { decodeSource, encodeSource, sameData } from './codec.js';
|
|
17
19
|
import { EditorRegistry } from './registry.js';
|
|
18
20
|
import { EDITOR_API_VERSION } from './types.js';
|
|
21
|
+
import { AddItemButton, ArrayItem, ItemMenu, additionalItemTemplate, numberedObjectTemplate, valueFieldTemplate, layoutObjectTemplate, compactDescriptionTemplate, compactArrayTitleTemplate, compactArrayTemplate, compactCheckboxWidget } from './rjsf-templates.js';
|
|
19
22
|
// RJSF v5 exposes CommonJS to Node and ESM to browser bundlers.
|
|
20
23
|
function moduleExports(namespace) {
|
|
21
24
|
const fallback = namespace.default;
|
|
@@ -23,10 +26,18 @@ function moduleExports(namespace) {
|
|
|
23
26
|
? fallback : namespace;
|
|
24
27
|
}
|
|
25
28
|
const Form = moduleExports(RjsfCore).default;
|
|
26
|
-
const { getDefaultFormState } = moduleExports(RjsfUtils);
|
|
29
|
+
const { getDefaultFormState, ADDITIONAL_PROPERTY_FLAG } = moduleExports(RjsfUtils);
|
|
27
30
|
const { customizeValidator } = moduleExports(RjsfAjv);
|
|
31
|
+
const defaultTemplates = moduleExports(RjsfCore).getDefaultRegistry().templates;
|
|
32
|
+
const ObjectItems = numberedObjectTemplate(layoutObjectTemplate(defaultTemplates.ObjectFieldTemplate), ADDITIONAL_PROPERTY_FLAG);
|
|
33
|
+
const AdditionalItem = additionalItemTemplate(ADDITIONAL_PROPERTY_FLAG);
|
|
34
|
+
const ValueField = valueFieldTemplate(defaultTemplates.FieldTemplate, ADDITIONAL_PROPERTY_FLAG);
|
|
35
|
+
const Description = compactDescriptionTemplate(defaultTemplates.DescriptionFieldTemplate);
|
|
36
|
+
const ArrayTitle = compactArrayTitleTemplate(defaultTemplates.ArrayFieldTitleTemplate);
|
|
37
|
+
const ArrayBody = compactArrayTemplate(defaultTemplates.ArrayFieldTemplate);
|
|
38
|
+
const Checkbox = compactCheckboxWidget(moduleExports(RjsfCore).getDefaultRegistry().widgets.CheckboxWidget);
|
|
28
39
|
let nextViewId = 0;
|
|
29
|
-
export function createSchemaBundleEditor(options) {
|
|
40
|
+
export function createSchemaBundleEditor(options, features = {}) {
|
|
30
41
|
const { schemaBundle, validationContext: provider, ownerValidate, apiVersion, extensionId, editorId, opcodes, argument, format, uiSchema, validateData } = options;
|
|
31
42
|
let bundle;
|
|
32
43
|
try {
|
|
@@ -66,9 +77,10 @@ export function createSchemaBundleEditor(options) {
|
|
|
66
77
|
};
|
|
67
78
|
return createSchemaEditorImpl({ apiVersion, extensionId, editorId, opcodes, argument, schema,
|
|
68
79
|
...(format === undefined ? {} : { format }), ...(uiSchema === undefined ? {} : { uiSchema }),
|
|
69
|
-
...(validateData === undefined ? {} : { validateData }) }, { issuesFor, dialect: result.validator.dialect });
|
|
80
|
+
...(validateData === undefined ? {} : { validateData }) }, { issuesFor, dialect: result.validator.dialect }, captureFeatureFlags(features));
|
|
70
81
|
}
|
|
71
82
|
export function createBlockEditorService(options) {
|
|
83
|
+
const features = captureFeatureFlags(options);
|
|
72
84
|
if ((options === null || options === void 0 ? void 0 : options.fixedInputs) !== undefined && typeof options.fixedInputs !== 'boolean')
|
|
73
85
|
throw new TypeError('Invalid fixed input capability option.');
|
|
74
86
|
const messageSource = options === null || options === void 0 ? void 0 : options.fixedInputMessages;
|
|
@@ -96,7 +108,7 @@ export function createBlockEditorService(options) {
|
|
|
96
108
|
register: (definition) => {
|
|
97
109
|
const custom = definition;
|
|
98
110
|
const editor = !custom.mount && Object.hasOwn(definition, 'schema')
|
|
99
|
-
? createSchemaEditor(definition) : custom;
|
|
111
|
+
? createSchemaEditor(definition, features) : custom;
|
|
100
112
|
if (typeof editor.mount !== 'function')
|
|
101
113
|
throw new TypeError('A block editor must provide mount().');
|
|
102
114
|
const remove = registry.register(editor);
|
|
@@ -110,10 +122,10 @@ export function createBlockEditorService(options) {
|
|
|
110
122
|
notify();
|
|
111
123
|
};
|
|
112
124
|
},
|
|
113
|
-
registerSchema: (definition) => api.register(createSchemaEditor(definition)),
|
|
114
|
-
registerSchemaBundle: (definition) => api.register(createSchemaBundleEditor(definition)),
|
|
125
|
+
registerSchema: (definition) => api.register(createSchemaEditor(definition, features)),
|
|
126
|
+
registerSchemaBundle: (definition) => api.register(createSchemaBundleEditor(definition, features)),
|
|
115
127
|
registerSourceEditor: (definition) => {
|
|
116
|
-
const remove = registry.registerPrepared(createSourceEditor(definition));
|
|
128
|
+
const remove = registry.registerPrepared(createSourceEditor(definition, features));
|
|
117
129
|
notify();
|
|
118
130
|
let active = true;
|
|
119
131
|
return () => { if (active) {
|
|
@@ -129,10 +141,10 @@ export function createBlockEditorService(options) {
|
|
|
129
141
|
} };
|
|
130
142
|
}
|
|
131
143
|
/** Build an editor with JSON/YAML serialization, schema validation and the standard RJSF body. */
|
|
132
|
-
export function createSchemaEditor(options) {
|
|
133
|
-
return createSchemaEditorImpl(options);
|
|
144
|
+
export function createSchemaEditor(options, features = {}) {
|
|
145
|
+
return createSchemaEditorImpl(options, undefined, captureFeatureFlags(features));
|
|
134
146
|
}
|
|
135
|
-
function createSchemaEditorImpl(options, bundle) {
|
|
147
|
+
function createSchemaEditorImpl(options, bundle, features = captureFeatureFlags()) {
|
|
136
148
|
var _a, _b;
|
|
137
149
|
if (options.apiVersion !== EDITOR_API_VERSION || typeof options.argument !== 'string' || !options.argument) {
|
|
138
150
|
throw new TypeError('A supported API version and JSON argument name are required.');
|
|
@@ -142,7 +154,7 @@ function createSchemaEditorImpl(options, bundle) {
|
|
|
142
154
|
if (format !== 'json' && format !== 'yaml')
|
|
143
155
|
throw new TypeError('Unsupported source format.');
|
|
144
156
|
const schema = cloneJson(options.schema);
|
|
145
|
-
const uiSchema = options.uiSchema ?
|
|
157
|
+
const uiSchema = options.uiSchema ? snapshotJson(options.uiSchema) : {};
|
|
146
158
|
const validateData = options.validateData;
|
|
147
159
|
const validator = customizeValidator({
|
|
148
160
|
...((bundle === null || bundle === void 0 ? void 0 : bundle.dialect) === '2020-12' ? { AjvClass: Ajv2020 } : {}),
|
|
@@ -209,6 +221,17 @@ function createSchemaEditorImpl(options, bundle) {
|
|
|
209
221
|
const state = context.getState();
|
|
210
222
|
const props = {
|
|
211
223
|
schema, uiSchema, validator, idPrefix,
|
|
224
|
+
widgets: { CheckboxWidget: Checkbox },
|
|
225
|
+
templates: {
|
|
226
|
+
ArrayFieldItemTemplate: ArrayItem,
|
|
227
|
+
ArrayFieldTitleTemplate: ArrayTitle,
|
|
228
|
+
ArrayFieldTemplate: ArrayBody,
|
|
229
|
+
ObjectFieldTemplate: ObjectItems,
|
|
230
|
+
WrapIfAdditionalTemplate: AdditionalItem,
|
|
231
|
+
FieldTemplate: ValueField,
|
|
232
|
+
DescriptionFieldTemplate: Description,
|
|
233
|
+
ButtonTemplates: { AddButton: AddItemButton, RemoveButton: ItemMenu }
|
|
234
|
+
},
|
|
212
235
|
formData: dataFor(context.getDraft()),
|
|
213
236
|
// Defaults belong to read(), not subsequent renders or remounts.
|
|
214
237
|
experimental_defaultFormStateBehavior: {
|
|
@@ -230,7 +253,7 @@ function createSchemaEditorImpl(options, bundle) {
|
|
|
230
253
|
}
|
|
231
254
|
};
|
|
232
255
|
// Suppress RJSF's own submit control; the host's shared footer owns saving.
|
|
233
|
-
ReactDOM.render(React.createElement(Form, props, React.createElement('span', { hidden: true })), container);
|
|
256
|
+
ReactDOM.render(React.createElement(LayoutHost, { enabled: features.commonLayout, context, children: React.createElement(Form, props, React.createElement('span', { hidden: true })) }), container);
|
|
234
257
|
};
|
|
235
258
|
const schedule = () => {
|
|
236
259
|
if (scheduled || !active)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type SchemaNode = Record<string, unknown>;
|
|
2
|
+
export declare const schemaRecord: (value: unknown) => value is SchemaNode;
|
|
3
|
+
export declare const jsonTypes: readonly ["object", "array", "string", "number", "boolean", "null"];
|
|
4
|
+
/** Only schema constraints prove a type; a currently matching union branch does not. */
|
|
5
|
+
export declare function presentationTypes(schema: unknown, reference?: (ref: string) => unknown, seen?: Set<unknown>): ReadonlySet<string>;
|
|
6
|
+
/** Initial values are produced only after an explicit add/type-change action, never on mount. */
|
|
7
|
+
export declare function presentationInitial(schema: unknown): unknown;
|
|
8
|
+
/** A deliberately small tri-state evaluator for presentation-only if/then/else hints. */
|
|
9
|
+
export declare function presentationCondition(schema: unknown, data: unknown, reference: (ref: string) => unknown, seen?: Set<unknown>): boolean | undefined;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export const schemaRecord = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
2
|
+
export const jsonTypes = ['object', 'array', 'string', 'number', 'boolean', 'null'];
|
|
3
|
+
const valueType = (value) => value === null ? 'null' : Array.isArray(value) ? 'array' : typeof value;
|
|
4
|
+
/** Only schema constraints prove a type; a currently matching union branch does not. */
|
|
5
|
+
export function presentationTypes(schema, reference = () => undefined, seen = new Set()) {
|
|
6
|
+
if (schema === false)
|
|
7
|
+
return new Set();
|
|
8
|
+
if (!schemaRecord(schema) || seen.has(schema))
|
|
9
|
+
return new Set(jsonTypes);
|
|
10
|
+
const visited = new Set(seen);
|
|
11
|
+
visited.add(schema);
|
|
12
|
+
let types = new Set(jsonTypes);
|
|
13
|
+
const intersect = (other) => { types = new Set([...types].filter(type => other.has(type))); };
|
|
14
|
+
const infer = (child) => presentationTypes(child, reference, visited);
|
|
15
|
+
if (typeof schema.$ref === 'string')
|
|
16
|
+
intersect(infer(reference(schema.$ref)));
|
|
17
|
+
if (schema.type !== undefined) {
|
|
18
|
+
const declared = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
19
|
+
intersect(new Set(declared.map(type => type === 'integer' ? 'number' : type)));
|
|
20
|
+
}
|
|
21
|
+
if (Object.hasOwn(schema, 'const'))
|
|
22
|
+
intersect(new Set([valueType(schema.const)]));
|
|
23
|
+
if (Array.isArray(schema.enum))
|
|
24
|
+
intersect(new Set(schema.enum.map(valueType)));
|
|
25
|
+
if (Array.isArray(schema.allOf))
|
|
26
|
+
schema.allOf.forEach(child => intersect(infer(child)));
|
|
27
|
+
for (const keyword of ['anyOf', 'oneOf']) {
|
|
28
|
+
const children = schema[keyword];
|
|
29
|
+
if (Array.isArray(children))
|
|
30
|
+
intersect(new Set(children.flatMap(child => [...infer(child)])));
|
|
31
|
+
}
|
|
32
|
+
return types;
|
|
33
|
+
}
|
|
34
|
+
/** Initial values are produced only after an explicit add/type-change action, never on mount. */
|
|
35
|
+
export function presentationInitial(schema) {
|
|
36
|
+
if (schemaRecord(schema)) {
|
|
37
|
+
if (Object.hasOwn(schema, 'const'))
|
|
38
|
+
return schema.const;
|
|
39
|
+
if (Array.isArray(schema.enum) && schema.enum.length)
|
|
40
|
+
return schema.enum[0];
|
|
41
|
+
}
|
|
42
|
+
const types = presentationTypes(schema);
|
|
43
|
+
if (types.size !== 1)
|
|
44
|
+
return null;
|
|
45
|
+
switch ([...types][0]) {
|
|
46
|
+
case 'object': return {};
|
|
47
|
+
case 'array': return [];
|
|
48
|
+
case 'string': return '';
|
|
49
|
+
case 'number': return 0;
|
|
50
|
+
case 'boolean': return false;
|
|
51
|
+
default: return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** A deliberately small tri-state evaluator for presentation-only if/then/else hints. */
|
|
55
|
+
export function presentationCondition(schema, data, reference, seen = new Set()) {
|
|
56
|
+
if (typeof schema === 'boolean')
|
|
57
|
+
return schema;
|
|
58
|
+
if (!schemaRecord(schema) || seen.has(schema))
|
|
59
|
+
return undefined;
|
|
60
|
+
const supported = ['type', 'const', 'enum', 'required', 'properties', 'allOf', 'anyOf', 'oneOf', 'not', '$ref', '$id', '$schema', 'title', 'description', '$comment'];
|
|
61
|
+
if (Object.keys(schema).some(key => !supported.includes(key)))
|
|
62
|
+
return undefined;
|
|
63
|
+
const visited = new Set(seen);
|
|
64
|
+
visited.add(schema);
|
|
65
|
+
const test = (child, value = data) => presentationCondition(child, value, reference, visited);
|
|
66
|
+
const equal = (a, b) => {
|
|
67
|
+
if (a === b)
|
|
68
|
+
return true;
|
|
69
|
+
if (Array.isArray(a) && Array.isArray(b))
|
|
70
|
+
return a.length === b.length && a.every((child, index) => equal(child, b[index]));
|
|
71
|
+
return schemaRecord(a) && schemaRecord(b) && Object.keys(a).length === Object.keys(b).length && Object.keys(a).every(key => Object.hasOwn(b, key) && equal(a[key], b[key]));
|
|
72
|
+
};
|
|
73
|
+
const results = [];
|
|
74
|
+
if (typeof schema.$ref === 'string')
|
|
75
|
+
results.push(test(reference(schema.$ref)));
|
|
76
|
+
if (schema.type !== undefined) {
|
|
77
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
78
|
+
results.push(types.some(type => type === 'integer' ? typeof data === 'number' && Number.isInteger(data) : type === valueType(data)));
|
|
79
|
+
}
|
|
80
|
+
if (Object.hasOwn(schema, 'const'))
|
|
81
|
+
results.push(equal(data, schema.const));
|
|
82
|
+
if (Array.isArray(schema.enum))
|
|
83
|
+
results.push(schema.enum.some(item => equal(data, item)));
|
|
84
|
+
if (schemaRecord(data)) {
|
|
85
|
+
if (Array.isArray(schema.required))
|
|
86
|
+
results.push(schema.required.every(key => typeof key === 'string' && Object.hasOwn(data, key)));
|
|
87
|
+
if (schemaRecord(schema.properties))
|
|
88
|
+
for (const [key, child] of Object.entries(schema.properties))
|
|
89
|
+
if (Object.hasOwn(data, key))
|
|
90
|
+
results.push(test(child, data[key]));
|
|
91
|
+
}
|
|
92
|
+
if (Array.isArray(schema.allOf))
|
|
93
|
+
results.push(...schema.allOf.map(child => test(child)));
|
|
94
|
+
for (const keyword of ['anyOf', 'oneOf']) {
|
|
95
|
+
const children = schema[keyword];
|
|
96
|
+
if (!Array.isArray(children))
|
|
97
|
+
continue;
|
|
98
|
+
const states = children.map(child => test(child));
|
|
99
|
+
if (keyword === 'anyOf')
|
|
100
|
+
results.push(states.includes(true) ? true : states.includes(undefined) ? undefined : false);
|
|
101
|
+
else
|
|
102
|
+
results.push(states.includes(undefined) ? undefined : states.filter(Boolean).length === 1);
|
|
103
|
+
}
|
|
104
|
+
if (Object.hasOwn(schema, 'not')) {
|
|
105
|
+
const result = test(schema.not);
|
|
106
|
+
results.push(result === undefined ? undefined : !result);
|
|
107
|
+
}
|
|
108
|
+
return results.includes(false) ? false : results.includes(undefined) ? undefined : true;
|
|
109
|
+
}
|
package/dist/source-editor.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EditorFeatureFlags } from './config/feature-flags.js';
|
|
1
2
|
import type { CompiledSchema } from './schema-types.js';
|
|
2
3
|
import type { PreparedEditorDefinition } from './prepared-types.js';
|
|
3
4
|
import type { SourceBinding, SourceDocument, SourceEditorOptions, SourceVariant } from './source-types.js';
|
|
@@ -10,4 +11,4 @@ export declare function createSourceCandidate(format: 'json' | 'yaml', data: Jso
|
|
|
10
11
|
export declare function patchDecodedSource(document: SourceDocument, data: JsonValue, decode?: (source: string) => SourceDocument): string;
|
|
11
12
|
export declare function readSourceDocument<C>(binding: SourceBinding<C>, source: string): SourceDocument;
|
|
12
13
|
/** An additive path: legacy validate/createPatch deliberately refuse prepared definitions. */
|
|
13
|
-
export declare function createSourceEditor<C = null>(options: SourceEditorOptions<C
|
|
14
|
+
export declare function createSourceEditor<C = null>(options: SourceEditorOptions<C>, featureOptions?: EditorFeatureFlags): PreparedEditorDefinition;
|
package/dist/source-editor.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { captureFeatureFlags } from './config/feature-flags.js';
|
|
1
2
|
import { compileSchemaBundle } from './schema-engine.js';
|
|
2
3
|
import { snapshotJson, isJsonRecord } from './schema-json.js';
|
|
3
4
|
import { stringify } from 'yaml';
|
|
@@ -55,8 +56,9 @@ function entryFor(draft, argument) {
|
|
|
55
56
|
return entry;
|
|
56
57
|
}
|
|
57
58
|
/** An additive path: legacy validate/createPatch deliberately refuse prepared definitions. */
|
|
58
|
-
export function createSourceEditor(options) {
|
|
59
|
+
export function createSourceEditor(options, featureOptions = {}) {
|
|
59
60
|
var _a;
|
|
61
|
+
const features = captureFeatureFlags(featureOptions);
|
|
60
62
|
const { apiVersion, extensionId, editorId, validationContext: provider } = options;
|
|
61
63
|
if (apiVersion !== 1 || typeof provider !== 'function' || !Array.isArray(options.bindings) || !options.bindings.length) {
|
|
62
64
|
throw new TypeError('Invalid source editor options.');
|
|
@@ -143,7 +145,12 @@ export function createSourceEditor(options) {
|
|
|
143
145
|
try {
|
|
144
146
|
const entry = entryFor(draft, binding.argument);
|
|
145
147
|
if (entry.invalidForm === true) {
|
|
146
|
-
|
|
148
|
+
if (features.commonLayout && isJsonRecord(entry.invalidNumbers) && Object.keys(entry.invalidNumbers).length > 0) {
|
|
149
|
+
for (const pointer of Object.keys(entry.invalidNumbers))
|
|
150
|
+
issues.push(issue('form-value-invalid', path + pointer));
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
issues.push(issue('form-value-invalid', path));
|
|
147
154
|
continue;
|
|
148
155
|
}
|
|
149
156
|
const slot = snapshot.arguments[binding.argument];
|
|
@@ -198,7 +205,14 @@ export function createSourceEditor(options) {
|
|
|
198
205
|
if (validation.result.kind !== 'valid') {
|
|
199
206
|
if (!['invalid', 'execution-error', 'schema-error'].includes(validation.result.kind))
|
|
200
207
|
throw new TypeError();
|
|
201
|
-
|
|
208
|
+
if (features.commonLayout && validation.result.diagnostics.length) {
|
|
209
|
+
for (const diagnostic of validation.result.diagnostics) {
|
|
210
|
+
const pointer = typeof diagnostic.dataPath === 'string' && diagnostic.dataPath.startsWith('/') ? diagnostic.dataPath : '';
|
|
211
|
+
issues.push(issue('owner-input-invalid', path + pointer));
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
else
|
|
215
|
+
issues.push(issue('owner-input-invalid', path));
|
|
202
216
|
continue;
|
|
203
217
|
}
|
|
204
218
|
if (validation.result.diagnostics.length)
|
|
@@ -213,7 +227,11 @@ export function createSourceEditor(options) {
|
|
|
213
227
|
if (validator) {
|
|
214
228
|
const result = validator.validate(document.data, { consumerId: context.consumerId });
|
|
215
229
|
if (result.kind !== 'valid') {
|
|
216
|
-
|
|
230
|
+
if (features.commonLayout)
|
|
231
|
+
for (const diagnostic of result.diagnostics)
|
|
232
|
+
issues.push(issue('schema-data-invalid', path + ('dataPath' in diagnostic ? diagnostic.dataPath : '')));
|
|
233
|
+
else
|
|
234
|
+
issues.push(issue('schema-data-invalid', path));
|
|
217
235
|
continue;
|
|
218
236
|
}
|
|
219
237
|
}
|
|
@@ -230,6 +248,6 @@ export function createSourceEditor(options) {
|
|
|
230
248
|
}
|
|
231
249
|
return issues.length ? { kind: 'invalid', issues } : { kind: 'ready', changes };
|
|
232
250
|
},
|
|
233
|
-
mount: (container, context) => mountSourceView(container, context, bindings)
|
|
251
|
+
mount: (container, context) => mountSourceView(container, context, bindings, features.commonLayout)
|
|
234
252
|
});
|
|
235
253
|
}
|
package/dist/source-view.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import type { CapturedBinding } from './source-editor.js';
|
|
2
2
|
import type { EditorViewContext } from './types.js';
|
|
3
|
-
export declare function mountSourceView<C>(container: HTMLElement, context: EditorViewContext, bindings: readonly CapturedBinding<C>[]): () => void;
|
|
3
|
+
export declare function mountSourceView<C>(container: HTMLElement, context: EditorViewContext, bindings: readonly CapturedBinding<C>[], commonLayout?: boolean): () => void;
|