@kubohiroya/turbowarp-extension-data-editor 0.2.2 → 0.3.1

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/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,7 +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';
19
- import { AddItemButton, ArrayItem, ItemMenu, additionalItemTemplate, numberedObjectTemplate, valueFieldTemplate } from './rjsf-templates.js';
21
+ import { AddItemButton, ArrayItem, ItemMenu, additionalItemTemplate, numberedObjectTemplate, valueFieldTemplate, layoutObjectTemplate, compactDescriptionTemplate, compactArrayTitleTemplate, compactArrayTemplate, compactCheckboxWidget } from './rjsf-templates.js';
20
22
  // RJSF v5 exposes CommonJS to Node and ESM to browser bundlers.
21
23
  function moduleExports(namespace) {
22
24
  const fallback = namespace.default;
@@ -27,11 +29,15 @@ const Form = moduleExports(RjsfCore).default;
27
29
  const { getDefaultFormState, ADDITIONAL_PROPERTY_FLAG } = moduleExports(RjsfUtils);
28
30
  const { customizeValidator } = moduleExports(RjsfAjv);
29
31
  const defaultTemplates = moduleExports(RjsfCore).getDefaultRegistry().templates;
30
- const ObjectItems = numberedObjectTemplate(defaultTemplates.ObjectFieldTemplate, ADDITIONAL_PROPERTY_FLAG);
32
+ const ObjectItems = numberedObjectTemplate(layoutObjectTemplate(defaultTemplates.ObjectFieldTemplate), ADDITIONAL_PROPERTY_FLAG);
31
33
  const AdditionalItem = additionalItemTemplate(ADDITIONAL_PROPERTY_FLAG);
32
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);
33
39
  let nextViewId = 0;
34
- export function createSchemaBundleEditor(options) {
40
+ export function createSchemaBundleEditor(options, features = {}) {
35
41
  const { schemaBundle, validationContext: provider, ownerValidate, apiVersion, extensionId, editorId, opcodes, argument, format, uiSchema, validateData } = options;
36
42
  let bundle;
37
43
  try {
@@ -71,9 +77,10 @@ export function createSchemaBundleEditor(options) {
71
77
  };
72
78
  return createSchemaEditorImpl({ apiVersion, extensionId, editorId, opcodes, argument, schema,
73
79
  ...(format === undefined ? {} : { format }), ...(uiSchema === undefined ? {} : { uiSchema }),
74
- ...(validateData === undefined ? {} : { validateData }) }, { issuesFor, dialect: result.validator.dialect });
80
+ ...(validateData === undefined ? {} : { validateData }) }, { issuesFor, dialect: result.validator.dialect }, captureFeatureFlags(features));
75
81
  }
76
82
  export function createBlockEditorService(options) {
83
+ const features = captureFeatureFlags(options);
77
84
  if ((options === null || options === void 0 ? void 0 : options.fixedInputs) !== undefined && typeof options.fixedInputs !== 'boolean')
78
85
  throw new TypeError('Invalid fixed input capability option.');
79
86
  const messageSource = options === null || options === void 0 ? void 0 : options.fixedInputMessages;
@@ -101,7 +108,7 @@ export function createBlockEditorService(options) {
101
108
  register: (definition) => {
102
109
  const custom = definition;
103
110
  const editor = !custom.mount && Object.hasOwn(definition, 'schema')
104
- ? createSchemaEditor(definition) : custom;
111
+ ? createSchemaEditor(definition, features) : custom;
105
112
  if (typeof editor.mount !== 'function')
106
113
  throw new TypeError('A block editor must provide mount().');
107
114
  const remove = registry.register(editor);
@@ -115,10 +122,10 @@ export function createBlockEditorService(options) {
115
122
  notify();
116
123
  };
117
124
  },
118
- registerSchema: (definition) => api.register(createSchemaEditor(definition)),
119
- registerSchemaBundle: (definition) => api.register(createSchemaBundleEditor(definition)),
125
+ registerSchema: (definition) => api.register(createSchemaEditor(definition, features)),
126
+ registerSchemaBundle: (definition) => api.register(createSchemaBundleEditor(definition, features)),
120
127
  registerSourceEditor: (definition) => {
121
- const remove = registry.registerPrepared(createSourceEditor(definition));
128
+ const remove = registry.registerPrepared(createSourceEditor(definition, features));
122
129
  notify();
123
130
  let active = true;
124
131
  return () => { if (active) {
@@ -134,10 +141,10 @@ export function createBlockEditorService(options) {
134
141
  } };
135
142
  }
136
143
  /** Build an editor with JSON/YAML serialization, schema validation and the standard RJSF body. */
137
- export function createSchemaEditor(options) {
138
- return createSchemaEditorImpl(options);
144
+ export function createSchemaEditor(options, features = {}) {
145
+ return createSchemaEditorImpl(options, undefined, captureFeatureFlags(features));
139
146
  }
140
- function createSchemaEditorImpl(options, bundle) {
147
+ function createSchemaEditorImpl(options, bundle, features = captureFeatureFlags()) {
141
148
  var _a, _b;
142
149
  if (options.apiVersion !== EDITOR_API_VERSION || typeof options.argument !== 'string' || !options.argument) {
143
150
  throw new TypeError('A supported API version and JSON argument name are required.');
@@ -147,7 +154,7 @@ function createSchemaEditorImpl(options, bundle) {
147
154
  if (format !== 'json' && format !== 'yaml')
148
155
  throw new TypeError('Unsupported source format.');
149
156
  const schema = cloneJson(options.schema);
150
- const uiSchema = options.uiSchema ? cloneJson(options.uiSchema) : {};
157
+ const uiSchema = options.uiSchema ? snapshotJson(options.uiSchema) : {};
151
158
  const validateData = options.validateData;
152
159
  const validator = customizeValidator({
153
160
  ...((bundle === null || bundle === void 0 ? void 0 : bundle.dialect) === '2020-12' ? { AjvClass: Ajv2020 } : {}),
@@ -214,11 +221,15 @@ function createSchemaEditorImpl(options, bundle) {
214
221
  const state = context.getState();
215
222
  const props = {
216
223
  schema, uiSchema, validator, idPrefix,
224
+ widgets: { CheckboxWidget: Checkbox },
217
225
  templates: {
218
226
  ArrayFieldItemTemplate: ArrayItem,
227
+ ArrayFieldTitleTemplate: ArrayTitle,
228
+ ArrayFieldTemplate: ArrayBody,
219
229
  ObjectFieldTemplate: ObjectItems,
220
230
  WrapIfAdditionalTemplate: AdditionalItem,
221
231
  FieldTemplate: ValueField,
232
+ DescriptionFieldTemplate: Description,
222
233
  ButtonTemplates: { AddButton: AddItemButton, RemoveButton: ItemMenu }
223
234
  },
224
235
  formData: dataFor(context.getDraft()),
@@ -242,7 +253,7 @@ function createSchemaEditorImpl(options, bundle) {
242
253
  }
243
254
  };
244
255
  // Suppress RJSF's own submit control; the host's shared footer owns saving.
245
- 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);
246
257
  };
247
258
  const schedule = () => {
248
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;