@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.
Files changed (40) hide show
  1. package/README.md +4 -2
  2. package/dist/build-info.json +4 -4
  3. package/dist/config/feature-flags.d.ts +5 -0
  4. package/dist/config/feature-flags.js +6 -0
  5. package/dist/form-presentation.d.ts +8 -0
  6. package/dist/form-presentation.js +61 -0
  7. package/dist/layout-view.d.ts +21 -0
  8. package/dist/layout-view.js +127 -0
  9. package/dist/layout.d.ts +31 -0
  10. package/dist/layout.js +63 -0
  11. package/dist/react.css +126 -0
  12. package/dist/react.d.ts +5 -0
  13. package/dist/react.js +10 -2
  14. package/dist/rjsf-templates.d.ts +18 -0
  15. package/dist/rjsf-templates.js +199 -0
  16. package/dist/rjsf.d.ts +11 -8
  17. package/dist/rjsf.js +35 -12
  18. package/dist/schema-presentation.d.ts +10 -0
  19. package/dist/schema-presentation.js +109 -0
  20. package/dist/source-editor.d.ts +2 -1
  21. package/dist/source-editor.js +23 -5
  22. package/dist/source-view.d.ts +1 -1
  23. package/dist/source-view.js +222 -122
  24. package/dist/turbowarp/appearance.d.ts +1 -1
  25. package/dist/turbowarp/appearance.js +23 -5
  26. package/dist/turbowarp/bridge.js +5 -1
  27. package/dist/turbowarp.d.ts +2 -1
  28. package/dist/turbowarp.js +3 -2
  29. package/dist/webpack4/metafile.json +162 -21
  30. package/dist/webpack4/react.js +12 -6
  31. package/dist/webpack4/react.js.map +2 -2
  32. package/dist/webpack4/rjsf.js +1117 -261
  33. package/dist/webpack4/rjsf.js.map +4 -4
  34. package/dist/webpack4/turbowarp.js +22 -5
  35. package/dist/webpack4/turbowarp.js.map +2 -2
  36. package/docs/README.md +1 -0
  37. package/docs/api.md +7 -0
  38. package/docs/common-layout.ja.md +117 -0
  39. package/docs/package-distribution.md +5 -0
  40. 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, JsonObject, JsonValue, ValidationIssue } from './types.js';
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?: JsonObject;
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>): EditorDefinition;
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 ? cloneJson(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
+ }
@@ -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>): PreparedEditorDefinition;
14
+ export declare function createSourceEditor<C = null>(options: SourceEditorOptions<C>, featureOptions?: EditorFeatureFlags): PreparedEditorDefinition;
@@ -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
- issues.push(issue('form-value-invalid', path));
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
- issues.push(issue('owner-input-invalid', path));
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
- issues.push(issue('schema-data-invalid', path));
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
  }
@@ -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;