@es-joy/jsoe 0.25.1 → 0.26.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/CHANGES.md +15 -0
- package/dist/formatAndTypeChoices.d.ts +10 -0
- package/dist/formatAndTypeChoices.d.ts.map +1 -1
- package/dist/formats/schema.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayReferenceType.d.ts.map +1 -1
- package/dist/fundamentalTypes/arrayType.d.ts.map +1 -1
- package/dist/fundamentalTypes/functionType.d.ts.map +1 -1
- package/dist/fundamentalTypes/regexpType.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/subTypes/blobHTMLType.d.ts.map +1 -1
- package/dist/typeChoices.d.ts +3 -0
- package/dist/typeChoices.d.ts.map +1 -1
- package/dist/types.d.ts +12 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/timing.d.ts +7 -0
- package/dist/utils/timing.d.ts.map +1 -1
- package/eslint.config.js +2 -1
- package/package.json +2 -1
- package/src/formatAndTypeChoices.js +51 -14
- package/src/formats/schema.js +35 -13
- package/src/fundamentalTypes/arrayReferenceType.js +3 -2
- package/src/fundamentalTypes/arrayType.js +19 -9
- package/src/fundamentalTypes/functionType.js +55 -48
- package/src/fundamentalTypes/regexpType.js +9 -1
- package/src/subTypes/blobHTMLType.js +7 -10
- package/src/typeChoices.js +118 -80
- package/src/types.js +12 -8
- package/src/utils/timing.js +31 -0
|
@@ -270,65 +270,72 @@ const functionType = {
|
|
|
270
270
|
}
|
|
271
271
|
);
|
|
272
272
|
|
|
273
|
+
const buildFnTypeChoices =
|
|
274
|
+
/** @type {import('../typeChoices.js').BuildTypeChoices} */ (
|
|
275
|
+
buildTypeChoices
|
|
276
|
+
);
|
|
277
|
+
const argsChoices = buildFnTypeChoices({
|
|
278
|
+
// resultType,
|
|
279
|
+
topRoot: /** @type {HTMLDivElement} */ (topRoot),
|
|
280
|
+
format: 'schema', // We're always supplying a schema
|
|
281
|
+
// schemaOriginal: schemaContent,
|
|
282
|
+
schemaContent: args,
|
|
283
|
+
state: type,
|
|
284
|
+
// itemIndex,
|
|
285
|
+
typeNamespace
|
|
286
|
+
});
|
|
287
|
+
const bodyChoices = buildFnTypeChoices({
|
|
288
|
+
// resultType,
|
|
289
|
+
topRoot: /** @type {HTMLDivElement} */ (topRoot),
|
|
290
|
+
format: 'schema', // We're always supplying a schema
|
|
291
|
+
// schemaOriginal: schemaContent,
|
|
292
|
+
schemaContent: {type: 'string'},
|
|
293
|
+
state: type,
|
|
294
|
+
// itemIndex,
|
|
295
|
+
typeNamespace
|
|
296
|
+
});
|
|
297
|
+
|
|
273
298
|
const div = jml('div', {dataset: {type: 'function'}}, [
|
|
274
299
|
['b', ['Arguments']],
|
|
275
300
|
['br'],
|
|
276
|
-
|
|
277
|
-
...(/** @type {import('../typeChoices.js').BuildTypeChoices} */ (
|
|
278
|
-
buildTypeChoices
|
|
279
|
-
)({
|
|
280
|
-
// resultType,
|
|
281
|
-
topRoot: /** @type {HTMLDivElement} */ (topRoot),
|
|
282
|
-
format: 'schema', // We're always supplying a schema
|
|
283
|
-
// schemaOriginal: schemaContent,
|
|
284
|
-
schemaContent: args,
|
|
285
|
-
state: type,
|
|
286
|
-
// itemIndex,
|
|
287
|
-
typeNamespace
|
|
288
|
-
}).domArray),
|
|
289
|
-
|
|
301
|
+
...argsChoices.domArray,
|
|
290
302
|
['b', ['Function body']],
|
|
291
303
|
['br'],
|
|
292
|
-
...
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
state: type,
|
|
301
|
-
// itemIndex,
|
|
302
|
-
typeNamespace
|
|
303
|
-
}).domArray)
|
|
304
|
+
...bodyChoices.domArray
|
|
305
|
+
]);
|
|
306
|
+
|
|
307
|
+
// Both type-choice controls build their edit UI (the argument and body
|
|
308
|
+
// textareas) a tick after they are connected. Wait on their `whenReady`
|
|
309
|
+
// rather than guessing with nested `setTimeout`s.
|
|
310
|
+
const whenControlsReady = Promise.all([
|
|
311
|
+
argsChoices.whenReady, bodyChoices.whenReady
|
|
304
312
|
]);
|
|
305
313
|
|
|
306
314
|
if (value) {
|
|
307
|
-
|
|
315
|
+
(async () => {
|
|
308
316
|
// Fire-and-forget population; swallow rejections from a still-settling
|
|
309
317
|
// editor so they cannot surface as an unhandled rejection.
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
value,
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
-
}
|
|
318
|
+
try {
|
|
319
|
+
await whenControlsReady;
|
|
320
|
+
await setValueAndTooltips({root: div, value, specificSchemaObject});
|
|
321
|
+
} catch {
|
|
322
|
+
// best-effort
|
|
323
|
+
}
|
|
324
|
+
})();
|
|
317
325
|
} else if (specificSchemaObject) {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
}, 0);
|
|
326
|
+
(async () => {
|
|
327
|
+
await whenControlsReady;
|
|
328
|
+
// The auto-triggered controls have their textareas by now.
|
|
329
|
+
const textareas = /** @type {HTMLTextAreaElement[]} */ (
|
|
330
|
+
$$e(div, 'textarea')
|
|
331
|
+
);
|
|
332
|
+
const textareaBody = /** @type {HTMLTextAreaElement} */ (
|
|
333
|
+
textareas.pop()
|
|
334
|
+
);
|
|
335
|
+
setTooltips({
|
|
336
|
+
root: div, specificSchemaObject, textareas, textareaBody
|
|
337
|
+
});
|
|
338
|
+
})();
|
|
332
339
|
}
|
|
333
340
|
|
|
334
341
|
return [div];
|
|
@@ -106,7 +106,15 @@ const regexpType = {
|
|
|
106
106
|
'Source ',
|
|
107
107
|
['input', {
|
|
108
108
|
name: `${typeNamespace}-regexp`, type: 'text',
|
|
109
|
-
value: value.source
|
|
109
|
+
value: value.source,
|
|
110
|
+
$on: {
|
|
111
|
+
input () {
|
|
112
|
+
types.validate({type: 'regexp', root});
|
|
113
|
+
},
|
|
114
|
+
change () {
|
|
115
|
+
types.validate({type: 'regexp', root});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
110
118
|
}]
|
|
111
119
|
]],
|
|
112
120
|
['br'],
|
|
@@ -5,6 +5,7 @@ import {schemaLabel} from '../utils/schemaMeta.js';
|
|
|
5
5
|
|
|
6
6
|
import dialogs from '../utils/dialogs.js';
|
|
7
7
|
import {isNullish} from '../utils/types.js';
|
|
8
|
+
import {whenConnected} from '../utils/timing.js';
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @typedef {HTMLTextAreaElement & {
|
|
@@ -146,14 +147,11 @@ const blobHTMLType = {
|
|
|
146
147
|
// connected to the document. The caller attaches `root` after the build
|
|
147
148
|
// pipeline returns, and re-parenting an SCEditor iframe reloads it, so we
|
|
148
149
|
// wait here for connection rather than initialising eagerly (or letting
|
|
149
|
-
// the build pipeline await us, which would deadlock).
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (connectionTries++ < 250) {
|
|
155
|
-
setTimeout(createWhenConnected, 20);
|
|
156
|
-
}
|
|
150
|
+
// the build pipeline await us, which would deadlock). The wait is bounded
|
|
151
|
+
// (`whenConnected`) so a never-attached editor is abandoned, not polled
|
|
152
|
+
// forever.
|
|
153
|
+
(async () => {
|
|
154
|
+
if (!await whenConnected(textarea, 250)) {
|
|
157
155
|
return;
|
|
158
156
|
}
|
|
159
157
|
// Push onto these: https://www.sceditor.com/documentation/formats/xhtml/
|
|
@@ -199,8 +197,7 @@ const blobHTMLType = {
|
|
|
199
197
|
this
|
|
200
198
|
).setValue({root, value});
|
|
201
199
|
}
|
|
202
|
-
};
|
|
203
|
-
setTimeout(createWhenConnected, 0);
|
|
200
|
+
})();
|
|
204
201
|
return [root];
|
|
205
202
|
}
|
|
206
203
|
};
|
package/src/typeChoices.js
CHANGED
|
@@ -6,33 +6,9 @@ import {$e, DOM} from './utils/templateUtils.js';
|
|
|
6
6
|
import {isUnionLike} from './utils/types.js';
|
|
7
7
|
import {getXorBranchMatchInfo, valueMatchesSchema} from './formats/schema.js';
|
|
8
8
|
import dialogs from './utils/dialogs.js';
|
|
9
|
-
import {
|
|
9
|
+
import {whenConnected} from './utils/timing.js';
|
|
10
10
|
import deepEqual from 'fast-deep-equal/es6/index.js';
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* A deferred `setValue`/auto-select runs a tick after the control is built, but
|
|
14
|
-
* the caller may only connect it to the document on a deferred tick of its own
|
|
15
|
-
* (e.g. `setTimeout(0)` DOM insertion). Wait this many ticks for the connection
|
|
16
|
-
* before giving up, rather than dropping the pre-set value on the first miss.
|
|
17
|
-
*/
|
|
18
|
-
const MAX_CONNECT_WAIT_TICKS = 50;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* @param {HTMLSelectElement} sel
|
|
22
|
-
* @returns {Promise<boolean>} Whether `sel` became connected within the bound.
|
|
23
|
-
*/
|
|
24
|
-
const waitForConnection = async (sel) => {
|
|
25
|
-
for (
|
|
26
|
-
let waited = 0;
|
|
27
|
-
!sel.isConnected && waited < MAX_CONNECT_WAIT_TICKS;
|
|
28
|
-
waited++
|
|
29
|
-
) {
|
|
30
|
-
// eslint-disable-next-line no-await-in-loop -- Sequential by design
|
|
31
|
-
await tick();
|
|
32
|
-
}
|
|
33
|
-
return sel.isConnected;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
12
|
// This is technically just `import('./index.js').SetType`, but our
|
|
37
13
|
// redirect file causes problems, so we redefine here
|
|
38
14
|
/**
|
|
@@ -85,6 +61,10 @@ const waitForConnection = async (sel) => {
|
|
|
85
61
|
* @typedef {(info: {editUI: HTMLElement}) => void} AddEditUI
|
|
86
62
|
*/
|
|
87
63
|
|
|
64
|
+
/**
|
|
65
|
+
* @typedef {() => Promise<void>} WhenReady
|
|
66
|
+
*/
|
|
67
|
+
|
|
88
68
|
/**
|
|
89
69
|
* @typedef {HTMLSelectElement & {
|
|
90
70
|
* $addAndValidateEditUI: AddAndValidateEditUI,
|
|
@@ -94,7 +74,8 @@ const waitForConnection = async (sel) => {
|
|
|
94
74
|
* $getTopRoot: () => HTMLDivElement,
|
|
95
75
|
* $addEditUI: AddEditUI,
|
|
96
76
|
* $validate: Validate,
|
|
97
|
-
* $setTypeNoEditUI: SetTypeNoEditUI
|
|
77
|
+
* $setTypeNoEditUI: SetTypeNoEditUI,
|
|
78
|
+
* $whenReady: WhenReady
|
|
98
79
|
* }} TypeChoicesElementAPI
|
|
99
80
|
*/
|
|
100
81
|
|
|
@@ -149,7 +130,8 @@ const waitForConnection = async (sel) => {
|
|
|
149
130
|
* getValue: GetValue,
|
|
150
131
|
* getType: GetType,
|
|
151
132
|
* validValuesSet: ValidValuesSet,
|
|
152
|
-
* setValue: SetValue
|
|
133
|
+
* setValue: SetValue,
|
|
134
|
+
* whenReady: Promise<void>
|
|
153
135
|
* }}
|
|
154
136
|
*/
|
|
155
137
|
|
|
@@ -162,7 +144,7 @@ const waitForConnection = async (sel) => {
|
|
|
162
144
|
const typeChoicesAPIMethods = [
|
|
163
145
|
'$getValue', '$setType', '$setTypeNoEditUI', '$setStyles', '$getTypeRoot',
|
|
164
146
|
'$addAndValidateEditUI', '$addTypeAndEditUI', '$addEditUI', '$getContainer',
|
|
165
|
-
'$getTopRoot', '$validate'
|
|
147
|
+
'$getTopRoot', '$validate', '$whenReady'
|
|
166
148
|
];
|
|
167
149
|
|
|
168
150
|
/**
|
|
@@ -413,6 +395,25 @@ export const buildTypeChoices = ({
|
|
|
413
395
|
|
|
414
396
|
let editUI;
|
|
415
397
|
|
|
398
|
+
/**
|
|
399
|
+
* `whenReady` resolves once the deferred (auto-trigger / `setValue`) build
|
|
400
|
+
* work scheduled at the end of this function has finished and its edit UI
|
|
401
|
+
* is in place — or on the next tick when there is no deferred work.
|
|
402
|
+
* Callers that need to read from, validate, or drive the control
|
|
403
|
+
* immediately after building it can `await` it instead of guessing with a
|
|
404
|
+
* `setTimeout`. It always settles (the connection wait is bounded — see
|
|
405
|
+
* `whenConnected` — so it resolves even if the control never connects),
|
|
406
|
+
* so awaiting it cannot hang.
|
|
407
|
+
* @type {() => void}
|
|
408
|
+
*/
|
|
409
|
+
let markReady;
|
|
410
|
+
const whenReady = /** @type {Promise<void>} */ (
|
|
411
|
+
// eslint-disable-next-line promise/avoid-new, unicorn/prefer-promise-with-resolvers -- Target lib predates `Promise.withResolvers`
|
|
412
|
+
new Promise((resolve) => {
|
|
413
|
+
markReady = resolve;
|
|
414
|
+
})
|
|
415
|
+
);
|
|
416
|
+
|
|
416
417
|
/** @type {GetValue} */
|
|
417
418
|
const $getValue = (stateObj, currentPath) => {
|
|
418
419
|
const root = /** @type {HTMLDivElement} */ (
|
|
@@ -443,6 +444,12 @@ export const buildTypeChoices = ({
|
|
|
443
444
|
// is: 'type-choices',
|
|
444
445
|
$custom: {
|
|
445
446
|
$getValue,
|
|
447
|
+
/**
|
|
448
|
+
* @type {WhenReady}
|
|
449
|
+
*/
|
|
450
|
+
$whenReady () {
|
|
451
|
+
return whenReady;
|
|
452
|
+
},
|
|
446
453
|
/**
|
|
447
454
|
* @this {TypeChoicesElementAPI}
|
|
448
455
|
* @param {Parameters<SetType>[0]} cfg
|
|
@@ -464,7 +471,12 @@ export const buildTypeChoices = ({
|
|
|
464
471
|
this.value = type;
|
|
465
472
|
}
|
|
466
473
|
this.$setStyles();
|
|
467
|
-
this.$addAndValidateEditUI({
|
|
474
|
+
this.$addAndValidateEditUI({
|
|
475
|
+
baseValue,
|
|
476
|
+
bringIntoFocus,
|
|
477
|
+
schemaObject: specificSchema,
|
|
478
|
+
avoidReport
|
|
479
|
+
});
|
|
468
480
|
},
|
|
469
481
|
/**
|
|
470
482
|
* @this {TypeChoicesElementAPI}
|
|
@@ -544,7 +556,7 @@ export const buildTypeChoices = ({
|
|
|
544
556
|
typeNamespace,
|
|
545
557
|
type: /** @type {import('./types.js').AvailableArbitraryType} */ (type),
|
|
546
558
|
bringIntoFocus,
|
|
547
|
-
hasValue:
|
|
559
|
+
hasValue: baseValue !== undefined,
|
|
548
560
|
value: baseValue,
|
|
549
561
|
buildTypeChoices,
|
|
550
562
|
format,
|
|
@@ -581,7 +593,7 @@ export const buildTypeChoices = ({
|
|
|
581
593
|
});
|
|
582
594
|
topRoot = this.$getTopRoot(); // May be existing now
|
|
583
595
|
// Needed; Array/object ref somewhere could now be valid or invalid
|
|
584
|
-
types.validateAllReferences({topRoot});
|
|
596
|
+
types.validateAllReferences({topRoot, avoidReport});
|
|
585
597
|
},
|
|
586
598
|
|
|
587
599
|
/**
|
|
@@ -689,62 +701,81 @@ export const buildTypeChoices = ({
|
|
|
689
701
|
|
|
690
702
|
if (autoTrigger && !setValue && typeOptions.length === 1) {
|
|
691
703
|
setTimeout(async () => {
|
|
692
|
-
|
|
693
|
-
|
|
704
|
+
try {
|
|
705
|
+
if (!await whenConnected(sel)) {
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
sel.selectedIndex = 1;
|
|
709
|
+
sel.dispatchEvent(new Event('change'));
|
|
710
|
+
} finally {
|
|
711
|
+
markReady();
|
|
694
712
|
}
|
|
695
|
-
sel.selectedIndex = 1;
|
|
696
|
-
sel.dispatchEvent(new Event('change'));
|
|
697
713
|
}, 0);
|
|
698
714
|
} else if (setValue || (requireObject && !objectHasValue)) {
|
|
699
715
|
setTimeout(async () => {
|
|
700
|
-
if (!await waitForConnection(sel)) {
|
|
701
|
-
return;
|
|
702
|
-
}
|
|
703
|
-
if (!setValue) { // if (requireObject && !objectHasValue) {
|
|
704
|
-
// Todo (low): We could auto-populate keypath if has
|
|
705
|
-
// keypath (and we probably also only want if
|
|
706
|
-
// not autoincrement)
|
|
707
|
-
value = {};
|
|
708
|
-
}
|
|
709
716
|
try {
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
{
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
717
|
+
if (!await whenConnected(sel)) {
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
if (!setValue) { // if (requireObject && !objectHasValue) {
|
|
721
|
+
// Todo (low): We could auto-populate keypath if has
|
|
722
|
+
// keypath (and we probably also only want if
|
|
723
|
+
// not autoincrement)
|
|
724
|
+
value = {};
|
|
725
|
+
}
|
|
726
|
+
try {
|
|
727
|
+
const builtState = await formats.getControlsForFormatAndValue(
|
|
728
|
+
types,
|
|
729
|
+
format,
|
|
730
|
+
value,
|
|
731
|
+
{
|
|
732
|
+
readonly: false,
|
|
733
|
+
typeNamespace,
|
|
734
|
+
schema,
|
|
735
|
+
schemaContent,
|
|
736
|
+
formats,
|
|
737
|
+
types
|
|
738
|
+
}
|
|
739
|
+
);
|
|
740
|
+
const {
|
|
741
|
+
rootUI: rootEditUI,
|
|
742
|
+
specificSchemas
|
|
743
|
+
} = builtState;
|
|
744
|
+
const type =
|
|
745
|
+
Types.getTypeForRoot(/** @type {HTMLDivElement} */ (
|
|
746
|
+
rootEditUI
|
|
747
|
+
));
|
|
748
|
+
/**
|
|
749
|
+
* @type {HTMLSelectElement & {$addTypeAndEditUI: AddTypeAndEditUI}}
|
|
750
|
+
*/ (
|
|
751
|
+
sel
|
|
752
|
+
).$addTypeAndEditUI({
|
|
753
|
+
type,
|
|
754
|
+
editUI: rootEditUI,
|
|
755
|
+
// We do actually want the first one
|
|
756
|
+
specificSchema: specificSchemas?.[0]
|
|
757
|
+
});
|
|
758
|
+
await builtState.whenBuilt;
|
|
759
|
+
} catch (err) {
|
|
760
|
+
/* istanbul ignore next -- At least some errors handled earlier */
|
|
761
|
+
dialogs.alert({
|
|
762
|
+
message: 'The object to be added had types not supported ' +
|
|
763
|
+
'by the current format.'
|
|
764
|
+
});
|
|
765
|
+
/* istanbul ignore next -- How to trigger? */
|
|
766
|
+
console.log('err', err);
|
|
767
|
+
}
|
|
768
|
+
} finally {
|
|
769
|
+
markReady();
|
|
746
770
|
}
|
|
747
771
|
}, 0);
|
|
772
|
+
} else {
|
|
773
|
+
// No deferred build work; still settle on a later tick so `$whenReady()`
|
|
774
|
+
// behaves consistently for callers that connect `sel` on a deferred
|
|
775
|
+
// tick of their own.
|
|
776
|
+
setTimeout(() => {
|
|
777
|
+
markReady();
|
|
778
|
+
}, 0);
|
|
748
779
|
}
|
|
749
780
|
|
|
750
781
|
return {
|
|
@@ -753,6 +784,13 @@ export const buildTypeChoices = ({
|
|
|
753
784
|
typeContainer
|
|
754
785
|
],
|
|
755
786
|
|
|
787
|
+
/**
|
|
788
|
+
* Settles once the deferred build work (if any) scheduled above has
|
|
789
|
+
* finished; `await` it instead of guessing a `setTimeout` delay.
|
|
790
|
+
* @type {Promise<void>}
|
|
791
|
+
*/
|
|
792
|
+
whenReady,
|
|
793
|
+
|
|
756
794
|
/** @type {GetValue} */
|
|
757
795
|
getValue: $getValue,
|
|
758
796
|
|
package/src/types.js
CHANGED
|
@@ -202,7 +202,8 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
202
202
|
|
|
203
203
|
/**
|
|
204
204
|
* @typedef {(cfg: {
|
|
205
|
-
* topRoot: RootElement
|
|
205
|
+
* topRoot: RootElement,
|
|
206
|
+
* avoidReport?: boolean
|
|
206
207
|
* }) => void} ValidateAllReferences
|
|
207
208
|
*/
|
|
208
209
|
|
|
@@ -359,6 +360,7 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
359
360
|
* buildTypeChoices?: import('./typeChoices.js').BuildTypeChoices,
|
|
360
361
|
* schemaContent?: import('./formats/schema.js').ZodexSchema
|
|
361
362
|
* replaced?: any
|
|
363
|
+
* hasValue?: boolean
|
|
362
364
|
* }) => JamilihArray} viewUI
|
|
363
365
|
* @property {(info: {
|
|
364
366
|
* value?: StructuredCloneValue,
|
|
@@ -374,7 +376,8 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
374
376
|
* topRoot?: HTMLDivElement
|
|
375
377
|
* schemaContent?: import('./formats/schema.js').ZodexSchema,
|
|
376
378
|
* specificSchemaObject?: import('./formats/schema.js').ZodexSchema,
|
|
377
|
-
* schemaFallingBack?: boolean
|
|
379
|
+
* schemaFallingBack?: boolean,
|
|
380
|
+
* hasValue?: boolean
|
|
378
381
|
* }) => JamilihArray} editUI
|
|
379
382
|
* @property {(info: {root: HTMLDivElement}) =>
|
|
380
383
|
* HTMLInputElement|HTMLTextAreaElement|HTMLSelectElement|
|
|
@@ -391,7 +394,8 @@ export const getPropertyValueFromLegend = (legend) => {
|
|
|
391
394
|
* }} [validate] Message will be used if validity is false.
|
|
392
395
|
* @property {(info: {
|
|
393
396
|
* topRoot: HTMLDivElement,
|
|
394
|
-
* types: Types
|
|
397
|
+
* types: Types,
|
|
398
|
+
* avoidReport?: boolean
|
|
395
399
|
* }) => void} [validateAll] For
|
|
396
400
|
* validation of array and object references only.
|
|
397
401
|
* @property {{
|
|
@@ -766,7 +770,7 @@ class Types {
|
|
|
766
770
|
format, schemaContent,
|
|
767
771
|
resultType, topRoot, bringIntoFocus, value,
|
|
768
772
|
replaced,
|
|
769
|
-
specificSchemaObject, schemaFallingBack,
|
|
773
|
+
specificSchemaObject, schemaFallingBack, hasValue,
|
|
770
774
|
types: this
|
|
771
775
|
}
|
|
772
776
|
: {
|
|
@@ -774,7 +778,7 @@ class Types {
|
|
|
774
778
|
format, schemaContent,
|
|
775
779
|
resultType, topRoot, bringIntoFocus,
|
|
776
780
|
replaced,
|
|
777
|
-
specificSchemaObject, schemaFallingBack,
|
|
781
|
+
specificSchemaObject, schemaFallingBack, hasValue,
|
|
778
782
|
types: this
|
|
779
783
|
};
|
|
780
784
|
const root = /** @type {HTMLDivElement} */ (jml(
|
|
@@ -796,7 +800,7 @@ class Types {
|
|
|
796
800
|
}
|
|
797
801
|
|
|
798
802
|
/** @type {ValidateAllReferences} */
|
|
799
|
-
validateAllReferences ({topRoot}) {
|
|
803
|
+
validateAllReferences ({topRoot, avoidReport}) {
|
|
800
804
|
/* istanbul ignore if -- Unreachable? */
|
|
801
805
|
if (!topRoot) {
|
|
802
806
|
console.log('No references present');
|
|
@@ -808,7 +812,7 @@ class Types {
|
|
|
808
812
|
Object.values(this.availableTypes).forEach((typeObj) => {
|
|
809
813
|
const typeObject = /** @type {TypeObject} */ (typeObj);
|
|
810
814
|
if (typeObject.validateAll) {
|
|
811
|
-
typeObject.validateAll({topRoot, types: this});
|
|
815
|
+
typeObject.validateAll({topRoot, types: this, avoidReport});
|
|
812
816
|
}
|
|
813
817
|
});
|
|
814
818
|
|
|
@@ -858,7 +862,7 @@ class Types {
|
|
|
858
862
|
'message' in error
|
|
859
863
|
? error.message
|
|
860
864
|
: undefined;
|
|
861
|
-
if (errorMessage !== 'Not yet instantiated') {
|
|
865
|
+
if (errorMessage !== 'Not yet instantiated' && typeValidation.valid) {
|
|
862
866
|
return false;
|
|
863
867
|
}
|
|
864
868
|
valueReady = false;
|
package/src/utils/timing.js
CHANGED
|
@@ -10,3 +10,34 @@
|
|
|
10
10
|
export const tick = () => new Promise((resolve) => {
|
|
11
11
|
setTimeout(resolve, 0);
|
|
12
12
|
});
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default upper bound, in {@link tick}s, for {@link whenConnected}. A control
|
|
16
|
+
* that is never attached to the document should be given up on rather than
|
|
17
|
+
* awaited forever.
|
|
18
|
+
*/
|
|
19
|
+
export const MAX_CONNECT_WAIT_TICKS = 50;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Resolves once `el` is connected to the document, re-checking once per
|
|
23
|
+
* macrotask via {@link tick}. A control is built synchronously, but its
|
|
24
|
+
* consumer may only attach it a deferred tick later (e.g. a `setTimeout(0)`
|
|
25
|
+
* DOM insertion), and connection-dependent initialisation — an SCEditor
|
|
26
|
+
* iframe, an auto-`setValue`, a type-choices auto-trigger — has to wait for
|
|
27
|
+
* that. Bounded so a control that is never attached is abandoned rather than
|
|
28
|
+
* polled forever; the resolved value says which happened.
|
|
29
|
+
* @param {Element} el
|
|
30
|
+
* @param {number} [maxTicks]
|
|
31
|
+
* @returns {Promise<boolean>} Whether `el` became connected within the bound.
|
|
32
|
+
*/
|
|
33
|
+
export const whenConnected = async (el, maxTicks = MAX_CONNECT_WAIT_TICKS) => {
|
|
34
|
+
for (
|
|
35
|
+
let waited = 0;
|
|
36
|
+
!el.isConnected && waited < maxTicks;
|
|
37
|
+
waited++
|
|
38
|
+
) {
|
|
39
|
+
// eslint-disable-next-line no-await-in-loop -- Sequential by design
|
|
40
|
+
await tick();
|
|
41
|
+
}
|
|
42
|
+
return el.isConnected;
|
|
43
|
+
};
|