@es-joy/jsoe 0.27.0 → 0.28.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/CHANGES.md +33 -0
- package/README.md +3 -1
- package/badges/coverage-badge.svg +1 -1
- package/badges/tests-badge.svg +1 -0
- package/dist/formats/schema.d.ts +11 -0
- package/dist/formats/schema.d.ts.map +1 -1
- package/dist/formats/structuredCloning.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/typeChoices.d.ts +32 -2
- package/dist/typeChoices.d.ts.map +1 -1
- package/dist/types.d.ts +8 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/rawTypesonEditor.d.ts +110 -0
- package/dist/utils/rawTypesonEditor.d.ts.map +1 -0
- package/dist/vendor-imports.d.ts +4 -0
- package/docs/proposals/raw-typeson-edit-view.md +575 -0
- package/mmr.json +6 -0
- package/package.json +13 -4
- package/pnpm-workspace.yaml +1 -0
- package/src/formats/schema.js +3 -1
- package/src/formats/structuredCloning.js +24 -17
- package/src/fundamentalTypes/arrayType.js +105 -1
- package/src/jsoe.css +12 -0
- package/src/typeChoices.js +36 -7
- package/src/types.js +13 -2
- package/src/utils/rawTypesonEditor.js +669 -0
- package/src/vendor-imports.js +12 -0
- package/tsconfig.json +1 -1
- package/typings/json-6.d.ts +12 -0
- package/vendor/@codemirror/autocomplete/dist/index.js +2125 -0
- package/vendor/@codemirror/commands/dist/index.js +1826 -0
- package/vendor/@codemirror/lang-javascript/dist/index.js +513 -0
- package/vendor/@codemirror/language/dist/index.js +2693 -0
- package/vendor/@codemirror/lint/dist/index.js +956 -0
- package/vendor/@codemirror/search/dist/index.js +1238 -0
- package/vendor/@codemirror/state/dist/index.js +3947 -0
- package/vendor/@codemirror/view/dist/index.js +11867 -0
- package/vendor/@lezer/common/dist/index.js +2202 -0
- package/vendor/@lezer/highlight/dist/index.js +927 -0
- package/vendor/@lezer/javascript/dist/index.js +192 -0
- package/vendor/@lezer/lr/dist/index.js +1889 -0
- package/vendor/@marijn/find-cluster-break/src/index.js +87 -0
- package/vendor/codemirror/dist/index.js +96 -0
- package/vendor/crelt/index.js +28 -0
- package/vendor/json-6/dist/index.mjs +1783 -0
- package/vendor/style-mod/src/style-mod.js +172 -0
- package/vendor/w3c-keyname/index.js +119 -0
package/src/formats/schema.js
CHANGED
|
@@ -194,13 +194,15 @@ const dezerializerCodecs = {
|
|
|
194
194
|
};
|
|
195
195
|
|
|
196
196
|
/**
|
|
197
|
+
* Exported for reuse by `src/utils/rawTypesonEditor.js`, which must enforce
|
|
198
|
+
* the same schema conformance check before accepting a raw-edited value.
|
|
197
199
|
* @param {InstanceType<typeof import('../types.js').default>} types
|
|
198
200
|
* @param {ZodexSchema} schemaObject
|
|
199
201
|
* @param {ZodexSchema} originalShape
|
|
200
202
|
* @param {unknown} value
|
|
201
203
|
* @returns {ReturnType<ReturnType<typeof dezerialize>['safeParse']>}
|
|
202
204
|
*/
|
|
203
|
-
function parseValue (types, schemaObject, originalShape, value) {
|
|
205
|
+
export function parseValue (types, schemaObject, originalShape, value) {
|
|
204
206
|
return dezerialize(schemaObject, {
|
|
205
207
|
checks: getChecks(types),
|
|
206
208
|
codecs: dezerializerCodecs,
|
|
@@ -274,23 +274,30 @@ const encapsulateObserver = (stateObj) => {
|
|
|
274
274
|
// console.log('but could set to', schema);
|
|
275
275
|
// console.log('arrayOrObjectPropertyName', arrayOrObjectPropertyName);
|
|
276
276
|
|
|
277
|
-
if (!
|
|
278
|
-
//
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
277
|
+
if (!parents['']) {
|
|
278
|
+
// A caller repopulating an *existing* container in place (e.g. the raw
|
|
279
|
+
// Typeson/JSON6 editor replacing one control's children after a
|
|
280
|
+
// bulk edit, `src/utils/rawTypesonEditor.js`) pre-seeds
|
|
281
|
+
// `stateObj.rootUI` with that container so it is reused here instead
|
|
282
|
+
// of a fresh one being built.
|
|
283
|
+
if (!stateObj.rootUI) {
|
|
284
|
+
// console.log('vvvv0', newType, newValue);
|
|
285
|
+
stateObj.rootUI = types?.getUIForModeAndType({
|
|
286
|
+
readonly,
|
|
287
|
+
typeNamespace,
|
|
288
|
+
type: newType,
|
|
289
|
+
bringIntoFocus: false,
|
|
290
|
+
buildTypeChoices,
|
|
291
|
+
format,
|
|
292
|
+
specificSchemaObject: schema,
|
|
293
|
+
schemaContent,
|
|
294
|
+
value: newValue,
|
|
295
|
+
hasValue: true,
|
|
296
|
+
// Not currently in use but may be convenient for a
|
|
297
|
+
// type wanting the serialized data
|
|
298
|
+
replaced
|
|
299
|
+
});
|
|
300
|
+
}
|
|
294
301
|
parents[''] = /**
|
|
295
302
|
* @type {HTMLElement &
|
|
296
303
|
* {$addAndSetArrayElement: AddAndSetArrayElement}}
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
schemaLabel, lastTypeChild, deprecatedClassSuffix
|
|
8
8
|
} from '../utils/schemaMeta.js';
|
|
9
9
|
import dialogs from '../utils/dialogs.js';
|
|
10
|
+
import {openRawEditorDialog} from '../utils/rawTypesonEditor.js';
|
|
10
11
|
import {
|
|
11
12
|
resolveJSONPointer, getJSONPointerParts, reduceJSONPointerParts
|
|
12
13
|
} from '../utils/jsonPointer.js';
|
|
@@ -428,6 +429,13 @@ const arrayType = {
|
|
|
428
429
|
specificSchemaObject?.type === 'looseRecord';
|
|
429
430
|
const tupleMode = specificSchemaObject?.type === 'tuple';
|
|
430
431
|
|
|
432
|
+
// See `docs/proposals/raw-typeson-edit-view.md` §1 for the scope
|
|
433
|
+
// rationale (why `filelist` is included, why record/tuple count).
|
|
434
|
+
const isRawEditable = types.showRawTypesonControls &&
|
|
435
|
+
((type === 'object' || type === 'array' ||
|
|
436
|
+
type === 'set' || type === 'map' || type === 'filelist') ||
|
|
437
|
+
recordMode || tupleMode);
|
|
438
|
+
|
|
431
439
|
/**
|
|
432
440
|
* @param {{
|
|
433
441
|
* itemIndex: number,
|
|
@@ -660,6 +668,31 @@ const arrayType = {
|
|
|
660
668
|
target
|
|
661
669
|
).textContent = arrayContents.hidden ? '+' : '-';
|
|
662
670
|
}}}, ['-']],
|
|
671
|
+
...(isRawEditable
|
|
672
|
+
? /** @type {import('jamilih').JamilihArray[]} */ ([
|
|
673
|
+
nbsp.repeat(2),
|
|
674
|
+
['button', {
|
|
675
|
+
class: 'viewRawTypeson',
|
|
676
|
+
$on: {click (/** @type {Event} */ e) {
|
|
677
|
+
e.preventDefault();
|
|
678
|
+
openRawEditorDialog({
|
|
679
|
+
types,
|
|
680
|
+
format: /** @type {import('../formats.js').AvailableFormat} */ (
|
|
681
|
+
format
|
|
682
|
+
),
|
|
683
|
+
type: /** @type {import('../types.js').AvailableArbitraryType} */ (
|
|
684
|
+
type
|
|
685
|
+
),
|
|
686
|
+
root: /** @type {HTMLDivElement} */ (div),
|
|
687
|
+
topRoot: /** @type {HTMLDivElement} */ (topRoot),
|
|
688
|
+
typeNamespace,
|
|
689
|
+
specificSchemaObject,
|
|
690
|
+
readonly: true
|
|
691
|
+
});
|
|
692
|
+
}}
|
|
693
|
+
}, ['View raw']]
|
|
694
|
+
])
|
|
695
|
+
: []),
|
|
663
696
|
['div', {class: 'arrayContents'}, [
|
|
664
697
|
!recordMode && this.array
|
|
665
698
|
? ['div', {
|
|
@@ -724,6 +757,13 @@ const arrayType = {
|
|
|
724
757
|
specificSchemaObject?.type === 'looseRecord';
|
|
725
758
|
const tupleMode = specificSchemaObject?.type === 'tuple';
|
|
726
759
|
|
|
760
|
+
// See `docs/proposals/raw-typeson-edit-view.md` §1 for the scope
|
|
761
|
+
// rationale (why `filelist` is included, why record/tuple count).
|
|
762
|
+
const isRawEditable = types.showRawTypesonControls &&
|
|
763
|
+
((type === 'object' || type === 'array' ||
|
|
764
|
+
type === 'set' || type === 'map' || type === 'filelist') ||
|
|
765
|
+
recordMode || tupleMode);
|
|
766
|
+
|
|
727
767
|
// A `record` refines `object` but, for legend numbering/reordering, behaves
|
|
728
768
|
// like the array-based types did when it was its own (`array: true`) type.
|
|
729
769
|
const parentIsArrayLike = parentTypeObject.array || recordMode;
|
|
@@ -2358,6 +2398,34 @@ const arrayType = {
|
|
|
2358
2398
|
).textContent = arrayContents.hidden ? '+' : '-';
|
|
2359
2399
|
}}}, ['-']];
|
|
2360
2400
|
|
|
2401
|
+
const rawEditButton = isRawEditable
|
|
2402
|
+
? [
|
|
2403
|
+
nbsp.repeat(2),
|
|
2404
|
+
['button', {
|
|
2405
|
+
class: 'editRawTypeson',
|
|
2406
|
+
$on: {click (/** @type {Event} */ e) {
|
|
2407
|
+
e.preventDefault();
|
|
2408
|
+
openRawEditorDialog({
|
|
2409
|
+
types,
|
|
2410
|
+
format: /** @type {import('../formats.js').AvailableFormat} */ (
|
|
2411
|
+
format
|
|
2412
|
+
),
|
|
2413
|
+
type: /** @type {import('../types.js').AvailableArbitraryType} */ (
|
|
2414
|
+
type
|
|
2415
|
+
),
|
|
2416
|
+
root: /** @type {HTMLDivElement} */ (div),
|
|
2417
|
+
// By click time `topRoot ||= div` below has already run if
|
|
2418
|
+
// this control is itself the root.
|
|
2419
|
+
topRoot: /** @type {HTMLDivElement} */ (topRoot),
|
|
2420
|
+
typeNamespace,
|
|
2421
|
+
specificSchemaObject,
|
|
2422
|
+
readonly: false
|
|
2423
|
+
});
|
|
2424
|
+
}}
|
|
2425
|
+
}, ['Edit raw']]
|
|
2426
|
+
]
|
|
2427
|
+
: [];
|
|
2428
|
+
|
|
2361
2429
|
const arrayContents = /** @type {import('jamilih').JamilihArray} */ (
|
|
2362
2430
|
['div', {class: 'arrayContents'}, [
|
|
2363
2431
|
arrayContentsFirstChild,
|
|
@@ -2433,7 +2501,8 @@ const arrayType = {
|
|
|
2433
2501
|
* $getTypeChoices: () => HTMLSelectElement & {
|
|
2434
2502
|
* $setType: import('../typeChoices.js').SetType,
|
|
2435
2503
|
* $getTypeRoot: import('../formatAndTypeChoices.js').TypeRootGetter
|
|
2436
|
-
* }
|
|
2504
|
+
* },
|
|
2505
|
+
* $resetItemIndex: () => void
|
|
2437
2506
|
* }} DivArrayOrObjectHolder
|
|
2438
2507
|
*/
|
|
2439
2508
|
|
|
@@ -2585,6 +2654,40 @@ const arrayType = {
|
|
|
2585
2654
|
this.$getAddArrayElement().previousElementSibling
|
|
2586
2655
|
);
|
|
2587
2656
|
},
|
|
2657
|
+
/**
|
|
2658
|
+
* Resets the `itemIndex`/legend-numbering counter back to its
|
|
2659
|
+
* empty-container baseline (`itemAdjust - 1`, the same value
|
|
2660
|
+
* it's initialized to before any item is ever added — *not*
|
|
2661
|
+
* the `-1` the "x All" button below hardcodes, which is only
|
|
2662
|
+
* correct for arrays; an object's baseline is `0`). Exposed so
|
|
2663
|
+
* a caller that repopulates this container's children from
|
|
2664
|
+
* outside this closure (bypassing the normal per-item
|
|
2665
|
+
* add/remove handlers that keep `itemIndex` in sync as they
|
|
2666
|
+
* go) — currently only
|
|
2667
|
+
* `src/utils/rawTypesonEditor.js`'s `commitValueToContainer`,
|
|
2668
|
+
* after a raw-value edit replaces every child — ends up with
|
|
2669
|
+
* exactly the same numbering a fresh container holding the
|
|
2670
|
+
* same new items would have, rather than continuing from
|
|
2671
|
+
* wherever the replaced content had left off (or, using the
|
|
2672
|
+
* wrong baseline, starting one lower than a fresh container
|
|
2673
|
+
* would: a single-property object's only property would be
|
|
2674
|
+
* renumbered from "1" to "0" by a bulk edit that didn't even
|
|
2675
|
+
* change its key, for example).
|
|
2676
|
+
* @this {DivArrayOrObjectHolder}
|
|
2677
|
+
* @returns {void}
|
|
2678
|
+
*/
|
|
2679
|
+
$resetItemIndex () {
|
|
2680
|
+
if (sparse) {
|
|
2681
|
+
decrementItemIndex(
|
|
2682
|
+
/**
|
|
2683
|
+
* @type {HTMLDivElement & {
|
|
2684
|
+
* $getPropertyInputs: GetPropertyInputs
|
|
2685
|
+
}} */ (this.$getArrayItems())
|
|
2686
|
+
);
|
|
2687
|
+
} else {
|
|
2688
|
+
itemIndex = itemAdjust - 1;
|
|
2689
|
+
}
|
|
2690
|
+
},
|
|
2588
2691
|
/**
|
|
2589
2692
|
* @this {DivArrayOrObjectHolder}
|
|
2590
2693
|
* @returns {HTMLSelectElement & {
|
|
@@ -2684,6 +2787,7 @@ const arrayType = {
|
|
|
2684
2787
|
}]
|
|
2685
2788
|
: '',
|
|
2686
2789
|
minusButton,
|
|
2790
|
+
...rawEditButton,
|
|
2687
2791
|
arrayContents
|
|
2688
2792
|
]))
|
|
2689
2793
|
);
|
package/src/jsoe.css
CHANGED
|
@@ -139,6 +139,18 @@ video.recordedMedia {
|
|
|
139
139
|
height: 300px;
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
.jsoe-raw-editor {
|
|
143
|
+
width: 500px;
|
|
144
|
+
max-width: 100%;
|
|
145
|
+
height: 300px;
|
|
146
|
+
overflow: auto;
|
|
147
|
+
border: 1px solid;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.jsoe-raw-editor-error:not(:empty) {
|
|
151
|
+
color: #b00;
|
|
152
|
+
}
|
|
153
|
+
|
|
142
154
|
.visualizer {
|
|
143
155
|
height: 60px;
|
|
144
156
|
}
|
package/src/typeChoices.js
CHANGED
|
@@ -66,17 +66,44 @@ import deepEqual from 'fast-deep-equal/es6/index.js';
|
|
|
66
66
|
*/
|
|
67
67
|
|
|
68
68
|
/**
|
|
69
|
-
*
|
|
69
|
+
* The `$`-methods every type-choices control exposes, whether it's a real
|
|
70
|
+
* `<select>` (`TypeChoicesElementAPI`) or the `xor` radio group's
|
|
71
|
+
* `<fieldset>` standing in for one (`TypeChoicesControl` — see its comment
|
|
72
|
+
* for why that one is *not* typed as `HTMLSelectElement`).
|
|
73
|
+
* @typedef {{
|
|
70
74
|
* $addAndValidateEditUI: AddAndValidateEditUI,
|
|
75
|
+
* $addTypeAndEditUI: AddTypeAndEditUI,
|
|
71
76
|
* $setStyles: SetStyles,
|
|
72
77
|
* $getTypeRoot: GetTypeRoot,
|
|
73
78
|
* $getContainer: () => HTMLElement,
|
|
74
79
|
* $getTopRoot: () => HTMLDivElement,
|
|
75
80
|
* $addEditUI: AddEditUI,
|
|
76
81
|
* $validate: Validate,
|
|
82
|
+
* $setType: SetType,
|
|
77
83
|
* $setTypeNoEditUI: SetTypeNoEditUI,
|
|
84
|
+
* $getValue: GetValue,
|
|
78
85
|
* $whenReady: WhenReady
|
|
79
|
-
* }}
|
|
86
|
+
* }} TypeChoicesAPIMethods
|
|
87
|
+
*/
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @typedef {HTMLSelectElement & TypeChoicesAPIMethods} TypeChoicesElementAPI
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The type-choices control surface downstream code actually relies on:
|
|
95
|
+
* the `$`-methods, plus the `value`/`selectedIndex`/`selectedOptions`
|
|
96
|
+
* surface a real `<select>` provides natively. Deliberately *not* typed as
|
|
97
|
+
* `HTMLSelectElement`, because `buildXorTypeChoices` below returns a real
|
|
98
|
+
* `<fieldset>` with that surface shimmed on via `Object.defineProperties`
|
|
99
|
+
* (and the `$`-methods copied by reference from the parallel `<select>`)
|
|
100
|
+
* rather than an actual `<select>` — asserting `HTMLSelectElement` there
|
|
101
|
+
* would be a false claim, not just a loose one.
|
|
102
|
+
* @typedef {HTMLElement & TypeChoicesAPIMethods & {
|
|
103
|
+
* value: string,
|
|
104
|
+
* selectedIndex: number,
|
|
105
|
+
* selectedOptions: ArrayLike<{value: string, dataset?: {idx?: string}}>
|
|
106
|
+
* }} TypeChoicesControl
|
|
80
107
|
*/
|
|
81
108
|
|
|
82
109
|
/**
|
|
@@ -126,7 +153,7 @@ import deepEqual from 'fast-deep-equal/es6/index.js';
|
|
|
126
153
|
* schemaContent?: import('./formatAndTypeChoices.js').ZodexSchema,
|
|
127
154
|
* }} cfg
|
|
128
155
|
* @returns {{
|
|
129
|
-
* domArray: [select:
|
|
156
|
+
* domArray: [select: TypeChoicesControl, typeContainer: HTMLElement],
|
|
130
157
|
* getValue: GetValue,
|
|
131
158
|
* getType: GetType,
|
|
132
159
|
* validValuesSet: ValidValuesSet,
|
|
@@ -200,9 +227,9 @@ function deriveXorBranchLabel (schemaObj, optText, idx) {
|
|
|
200
227
|
* types: InstanceType<typeof import('./types.js').default>,
|
|
201
228
|
* typeContainer: HTMLElement,
|
|
202
229
|
* xorSchema: import('zodexy').SzUnion|undefined,
|
|
203
|
-
* selectEl:
|
|
230
|
+
* selectEl: TypeChoicesElementAPI
|
|
204
231
|
* }} cfg
|
|
205
|
-
* @returns {
|
|
232
|
+
* @returns {TypeChoicesControl}
|
|
206
233
|
*/
|
|
207
234
|
function buildXorTypeChoices ({
|
|
208
235
|
typeNamespace, keySelectClass, typeOptions, schemaObjs, types,
|
|
@@ -359,7 +386,9 @@ function buildXorTypeChoices ({
|
|
|
359
386
|
}
|
|
360
387
|
});
|
|
361
388
|
|
|
362
|
-
return /** @type {
|
|
389
|
+
return /** @type {TypeChoicesControl} */ (
|
|
390
|
+
/** @type {unknown} */ (fieldset)
|
|
391
|
+
);
|
|
363
392
|
}
|
|
364
393
|
|
|
365
394
|
/**
|
|
@@ -437,7 +466,7 @@ export const buildTypeChoices = ({
|
|
|
437
466
|
// match indicator to value changes inside it
|
|
438
467
|
const typeContainer = jml('div', {class: 'typeContainer'});
|
|
439
468
|
|
|
440
|
-
const selectEl = /** @type {
|
|
469
|
+
const selectEl = /** @type {TypeChoicesElementAPI} */ (jml('select', {
|
|
441
470
|
hidden: requireObject || typeOptions.length === 1,
|
|
442
471
|
class: `typeChoices-${typeNamespace}${keySelectClass
|
|
443
472
|
? ' ' + keySelectClass
|
package/src/types.js
CHANGED
|
@@ -438,17 +438,28 @@ class Types {
|
|
|
438
438
|
/**
|
|
439
439
|
* @param {{
|
|
440
440
|
* useZodexyErrorMessages?: boolean,
|
|
441
|
-
* useZodexyErrorMessagesInTypes?: boolean
|
|
441
|
+
* useZodexyErrorMessagesInTypes?: boolean,
|
|
442
|
+
* allowUnsafeEval?: boolean,
|
|
443
|
+
* showRawTypesonControls?: boolean
|
|
442
444
|
* }} [cfg]
|
|
443
445
|
*/
|
|
444
446
|
constructor ({
|
|
445
447
|
useZodexyErrorMessages = false,
|
|
446
|
-
useZodexyErrorMessagesInTypes = false
|
|
448
|
+
useZodexyErrorMessagesInTypes = false,
|
|
449
|
+
allowUnsafeEval = false,
|
|
450
|
+
showRawTypesonControls = true
|
|
447
451
|
} = {}) {
|
|
448
452
|
this.formats = new Formats(); // Todo: Make customizable and test
|
|
449
453
|
|
|
450
454
|
this.useZodexyErrorMessages = useZodexyErrorMessages;
|
|
451
455
|
this.useZodexyErrorMessagesInTypes = useZodexyErrorMessagesInTypes;
|
|
456
|
+
// Gates the eval-mode sub-feature of the raw Typeson edit/view buttons
|
|
457
|
+
// (`src/utils/rawTypesonEditor.js`); disabled by default since eval'ing
|
|
458
|
+
// arbitrary user-supplied text is inherently unsafe.
|
|
459
|
+
this.allowUnsafeEval = allowUnsafeEval;
|
|
460
|
+
// Master on/off switch for the raw Typeson edit/view buttons themselves;
|
|
461
|
+
// when `false` neither button is rendered anywhere in the tree.
|
|
462
|
+
this.showRawTypesonControls = showRawTypesonControls;
|
|
452
463
|
/** @type {WeakMap<RootElement, import('./formats/schema.js').ZodexSchema>} */
|
|
453
464
|
this.schemasForRoots = new WeakMap();
|
|
454
465
|
|