@es-joy/jsoe 0.30.0 → 0.32.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 (82) hide show
  1. package/CHANGES.md +9 -0
  2. package/README.md +1 -0
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/search/fundamentalTypes/arraySearchType.d.ts.map +1 -1
  6. package/dist/search/fundamentalTypes/bigintSearchType.d.ts.map +1 -1
  7. package/dist/search/fundamentalTypes/blobSearchType.d.ts.map +1 -1
  8. package/dist/search/fundamentalTypes/booleanSearchType.d.ts.map +1 -1
  9. package/dist/search/fundamentalTypes/catchSearchType.d.ts.map +1 -1
  10. package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -1
  11. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -1
  12. package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -1
  13. package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -1
  14. package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -1
  15. package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -1
  16. package/dist/search/fundamentalTypes/mapSearchType.d.ts.map +1 -1
  17. package/dist/search/fundamentalTypes/numberSearchType.d.ts.map +1 -1
  18. package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -1
  19. package/dist/search/fundamentalTypes/promiseSearchType.d.ts.map +1 -1
  20. package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -1
  21. package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -1
  22. package/dist/search/fundamentalTypes/stringSearchType.d.ts.map +1 -1
  23. package/dist/search/fundamentalTypes/symbolSearchType.d.ts.map +1 -1
  24. package/dist/search/index.d.ts +2 -0
  25. package/dist/search/index.d.ts.map +1 -1
  26. package/dist/search/queryTree.d.ts +3 -0
  27. package/dist/search/queryTree.d.ts.map +1 -1
  28. package/dist/search/queryTreeBuilders.d.ts +2 -1
  29. package/dist/search/queryTreeBuilders.d.ts.map +1 -1
  30. package/dist/search/searchDispatch.d.ts +11 -3
  31. package/dist/search/searchDispatch.d.ts.map +1 -1
  32. package/dist/search/searchElementUtils.d.ts +31 -1
  33. package/dist/search/searchElementUtils.d.ts.map +1 -1
  34. package/dist/search/searchUtils.d.ts +197 -10
  35. package/dist/search/searchUtils.d.ts.map +1 -1
  36. package/dist/search/subTypes/blobHTMLSearchType.d.ts +11 -4
  37. package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -1
  38. package/dist/search/subTypes/recordSearchType.d.ts.map +1 -1
  39. package/dist/search/subTypes/tupleSearchType.d.ts.map +1 -1
  40. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts.map +1 -1
  41. package/dist/search/superTypes/buffersourceSearchType.d.ts.map +1 -1
  42. package/dist/search/unions/unionFamilySearchType.d.ts.map +1 -1
  43. package/dist/utils/json6Stringify.d.ts +2 -0
  44. package/dist/utils/json6Stringify.d.ts.map +1 -0
  45. package/dist/utils/rawTypesonEditor.d.ts.map +1 -1
  46. package/package.json +3 -3
  47. package/pnpm-workspace.yaml +26 -26
  48. package/src/search/fundamentalTypes/arraySearchType.js +28 -5
  49. package/src/search/fundamentalTypes/bigintSearchType.js +14 -3
  50. package/src/search/fundamentalTypes/blobSearchType.js +21 -3
  51. package/src/search/fundamentalTypes/booleanSearchType.js +15 -3
  52. package/src/search/fundamentalTypes/catchSearchType.js +20 -3
  53. package/src/search/fundamentalTypes/dateSearchType.js +42 -3
  54. package/src/search/fundamentalTypes/domexceptionSearchType.js +18 -4
  55. package/src/search/fundamentalTypes/enumSearchType.js +15 -3
  56. package/src/search/fundamentalTypes/fileSearchType.js +15 -3
  57. package/src/search/fundamentalTypes/filelistSearchType.js +25 -5
  58. package/src/search/fundamentalTypes/functionSearchType.js +31 -4
  59. package/src/search/fundamentalTypes/mapSearchType.js +33 -5
  60. package/src/search/fundamentalTypes/numberSearchType.js +15 -3
  61. package/src/search/fundamentalTypes/objectSearchType.js +94 -5
  62. package/src/search/fundamentalTypes/promiseSearchType.js +20 -3
  63. package/src/search/fundamentalTypes/regexpSearchType.js +24 -4
  64. package/src/search/fundamentalTypes/setSearchType.js +25 -5
  65. package/src/search/fundamentalTypes/stringSearchType.js +25 -3
  66. package/src/search/fundamentalTypes/symbolSearchType.js +21 -3
  67. package/src/search/index.js +22 -0
  68. package/src/search/queryTree.js +4 -1
  69. package/src/search/queryTreeBuilders.js +3 -2
  70. package/src/search/searchDispatch.js +14 -5
  71. package/src/search/searchElementUtils.js +98 -10
  72. package/src/search/searchUtils.js +440 -12
  73. package/src/search/subTypes/blobHTMLSearchType.js +186 -10
  74. package/src/search/subTypes/recordSearchType.js +30 -4
  75. package/src/search/subTypes/tupleSearchType.js +43 -5
  76. package/src/search/superTypes/SpecialRealNumberSearchType.js +15 -3
  77. package/src/search/superTypes/buffersourceSearchType.js +14 -3
  78. package/src/search/unions/unionFamilySearchType.js +45 -3
  79. package/src/utils/json6Stringify.js +50 -0
  80. package/src/utils/rawTypesonEditor.js +1 -51
  81. package/vendor/zod/classic/schemas.d.ts +3 -1
  82. package/vendor/zod/core/versions.js +1 -1
@@ -1,13 +1,105 @@
1
1
  import {
2
- buildPathLabel, findOwnControl, isExemptedByAncestorHasProperty
2
+ buildPathLabel, findOwnControl, isExemptedByAncestorHasProperty, extractLeafOfKind
3
3
  } from '../searchUtils.js';
4
4
  import {makeBlobHTMLLeaf} from '../queryTreeBuilders.js';
5
- import {getQueryViaElement} from '../searchElementUtils.js';
5
+ import {getQueryViaElement, applyQueryViaElement} from '../searchElementUtils.js';
6
+ import regexpType from '../../fundamentalTypes/regexpType.js';
6
7
 
7
8
  /**
8
9
  * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
9
10
  */
10
11
 
12
+ /**
13
+ * Live syntax-checks a blobHTML Value against its currently-chosen Mode -
14
+ * a CSS selector and an XPath expression are each tried against a detached,
15
+ * inert target (a fragment for the selector, the live `document` itself,
16
+ * required by `Element.prototype.evaluate`'s own contract, for the XPath -
17
+ * neither actually has to *match* anything, only parse without throwing),
18
+ * and a regex is tried via the same `new RegExp(value, flags)` constructor
19
+ * `readLiteralRegexQuery` relies on elsewhere - a "Full text search" value
20
+ * is a plain phrase with no format to violate, so it always passes.
21
+ * @param {string} mode
22
+ * @param {string} value
23
+ * @param {string} flags
24
+ * @returns {string}
25
+ */
26
+ function computeBlobHTMLValueMessage (mode, value, flags) {
27
+ if (!value) {
28
+ return '';
29
+ }
30
+ try {
31
+ switch (mode) {
32
+ case 'cssSelector':
33
+ document.createDocumentFragment().querySelector(value);
34
+ break;
35
+ case 'xpath':
36
+ document.evaluate(value, document, null, XPathResult.ANY_TYPE, null);
37
+ break;
38
+ case 'rawHTMLRegex':
39
+ // eslint-disable-next-line no-new -- Testing
40
+ new RegExp(value, flags);
41
+ break;
42
+ default:
43
+ break;
44
+ }
45
+ return '';
46
+ } catch {
47
+ switch (mode) {
48
+ case 'cssSelector':
49
+ return 'Enter a valid CSS selector.';
50
+ case 'xpath':
51
+ return 'Enter a valid XPath expression.';
52
+ case 'rawHTMLRegex':
53
+ return 'Enter a valid regular expression.';
54
+ default:
55
+ return '';
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Re-runs `computeBlobHTMLValueMessage` against whichever of the input/
62
+ * textarea pair the current Mode makes active, clearing the other one's
63
+ * custom validity (a hidden-but-still-`required` control would otherwise
64
+ * keep blocking the form, the same reasoning `buildUI`'s own doc gives for
65
+ * toggling `required` alongside `hidden`) - call on every `input` on either
66
+ * value control, `change` on the Mode `<select>`, and `change` on the Flags
67
+ * multi-select (changing flags can itself flip a regex between valid and
68
+ * invalid, e.g. the `u`/`v` flags' stricter escape rules).
69
+ * @param {Element|null} container
70
+ * @returns {void}
71
+ */
72
+ function syncBlobHTMLValueValidity (container) {
73
+ if (!container) {
74
+ return;
75
+ }
76
+ const mode = /** @type {HTMLSelectElement|undefined} */ (
77
+ findOwnControl(container, 'select.jsoeSearchBlobHTMLMode')
78
+ )?.value ?? '';
79
+ const input = /** @type {HTMLInputElement|undefined} */ (
80
+ findOwnControl(container, 'input.jsoeSearchBlobHTMLValue')
81
+ );
82
+ const textarea = /** @type {HTMLTextAreaElement|undefined} */ (
83
+ findOwnControl(container, 'textarea.jsoeSearchBlobHTMLValue')
84
+ );
85
+ const flagsSelect = /** @type {HTMLSelectElement|undefined} */ (
86
+ findOwnControl(container, 'select.jsoeSearchBlobHTMLFlags')
87
+ );
88
+ const flags = mode === 'rawHTMLRegex'
89
+ ? [...(flagsSelect?.selectedOptions ?? [])].map((opt) => opt.value).join('')
90
+ : '';
91
+ const active = mode === 'fullText' ? textarea : input;
92
+ if (input && input !== active) {
93
+ input.setCustomValidity('');
94
+ }
95
+ if (textarea && textarea !== active) {
96
+ textarea.setCustomValidity('');
97
+ }
98
+ if (active) {
99
+ active.setCustomValidity(computeBlobHTMLValueMessage(mode, active.value, flags));
100
+ }
101
+ }
102
+
11
103
  /**
12
104
  * Blob HTML: XPath, CSS selectors, full text search, regex search of raw
13
105
  * HTML (README) - a mode `<select>` plus one value control, the same
@@ -23,7 +115,8 @@ import {getQueryViaElement} from '../searchElementUtils.js';
23
115
  * of the pair is ever `required` (toggled alongside `hidden` by the mode
24
116
  * `change` handler): a `required` field that's merely hidden, rather than
25
117
  * also un-required, still blocks the form's validity even though the user
26
- * has no way to see or fill it.
118
+ * has no way to see or fill it. "CSS selector" is listed ahead of "XPath"
119
+ * (and is the default mode) as the option most jsoe users will recognize.
27
120
  *
28
121
  * The `change` handler also checks `isExemptedByAncestorHasProperty` before
29
122
  * (re-)asserting `required` on the now-visible control: nested as an
@@ -35,6 +128,15 @@ import {getQueryViaElement} from '../searchElementUtils.js';
35
128
  * check, that synthetic event would immediately reassert `required` on
36
129
  * whichever control the current mode makes visible, undoing the exemption
37
130
  * `setDescendantsRequired` had just granted.
131
+ *
132
+ * "Regex search of raw HTML" gets its own Flags multi-select (the same
133
+ * `regexpType.js` `allowedFlags` list `stringSearchType.js`'s literal/regex
134
+ * control passes as `buildLiteralRegexControls`'s `flagOptions`), shown only
135
+ * while that mode is chosen, folded into the `blobHTML` leaf's own
136
+ * `$options` (mirroring `QueryRegexLeaf`'s field of the same name) - and,
137
+ * via `syncBlobHTMLValueValidity`, included when syntax-checking the Value
138
+ * itself, since the chosen flags can affect whether a given pattern is
139
+ * actually valid.
38
140
  * @type {SearchTypeObject}
39
141
  */
40
142
  const blobHTMLSearchType = {
@@ -60,11 +162,56 @@ const blobHTMLSearchType = {
60
162
  if (!value || !mode) {
61
163
  return undefined;
62
164
  }
165
+ const flags = mode === 'rawHTMLRegex'
166
+ ? [...(/** @type {HTMLSelectElement|undefined} */ (
167
+ findOwnControl(this, 'select.jsoeSearchBlobHTMLFlags')
168
+ )?.selectedOptions ?? [])].map((opt) => opt.value).join('')
169
+ : '';
63
170
  return makeBlobHTMLLeaf(
64
171
  searchPath,
65
172
  /** @type {import('../queryTree.js').QueryBlobHTMLLeaf['mode']} */ (mode),
66
- value
173
+ value,
174
+ flags || undefined
175
+ );
176
+ },
177
+ /**
178
+ * @this {HTMLElement}
179
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
180
+ * @returns {void}
181
+ */
182
+ applyQuery (queryNode) {
183
+ const {matched} = extractLeafOfKind(queryNode, 'blobHTML');
184
+ const modeEl = /** @type {HTMLSelectElement|undefined} */ (
185
+ findOwnControl(this, 'select.jsoeSearchBlobHTMLMode')
186
+ );
187
+ if (!modeEl) {
188
+ return;
189
+ }
190
+ const mode = matched?.mode ?? 'cssSelector';
191
+ modeEl.value = mode;
192
+ // Dispatching `change` runs the Mode `<select>`'s own handler
193
+ // first, which shows/hides the correct Value control and the
194
+ // Flags label before either is touched below.
195
+ modeEl.dispatchEvent(new Event('change'));
196
+ const activeEl = /** @type {HTMLInputElement|HTMLTextAreaElement|undefined} */ (
197
+ findOwnControl(
198
+ this, `${mode === 'fullText' ? 'textarea' : 'input'}.jsoeSearchBlobHTMLValue`
199
+ )
200
+ );
201
+ if (activeEl) {
202
+ activeEl.value = matched?.value ?? '';
203
+ activeEl.dispatchEvent(new Event('input'));
204
+ }
205
+ const flagsEl = /** @type {HTMLSelectElement|undefined} */ (
206
+ findOwnControl(this, 'select.jsoeSearchBlobHTMLFlags')
67
207
  );
208
+ if (flagsEl) {
209
+ const flagChars = new Set((matched?.$options ?? '').split(''));
210
+ [...flagsEl.options].forEach((opt) => {
211
+ opt.selected = flagChars.has(opt.value);
212
+ });
213
+ flagsEl.dispatchEvent(new Event('change'));
214
+ }
68
215
  }
69
216
  }
70
217
  }, [
@@ -83,8 +230,11 @@ const blobHTMLSearchType = {
83
230
  const textarea = /** @type {HTMLTextAreaElement|null|undefined} */ (
84
231
  container?.querySelector('textarea.jsoeSearchBlobHTMLValue')
85
232
  );
86
- const isFullText = /** @type {HTMLSelectElement} */ (this).value ===
87
- 'fullText';
233
+ const flagsLabel = /** @type {HTMLElement|null|undefined} */ (
234
+ container?.querySelector('.jsoeSearchBlobHTMLFlagsLabel')
235
+ );
236
+ const mode = /** @type {HTMLSelectElement} */ (this).value;
237
+ const isFullText = mode === 'fullText';
88
238
  const exempted = container !== null &&
89
239
  isExemptedByAncestorHasProperty(container);
90
240
  if (input) {
@@ -95,11 +245,15 @@ const blobHTMLSearchType = {
95
245
  textarea.hidden = !isFullText;
96
246
  textarea.required = isFullText && !exempted;
97
247
  }
248
+ if (flagsLabel) {
249
+ flagsLabel.hidden = mode !== 'rawHTMLRegex';
250
+ }
251
+ syncBlobHTMLValueValidity(container);
98
252
  }
99
253
  }
100
254
  }, [
101
- ['option', {value: 'xpath'}, ['XPath']],
102
255
  ['option', {value: 'cssSelector'}, ['CSS selector']],
256
+ ['option', {value: 'xpath'}, ['XPath']],
103
257
  ['option', {value: 'fullText'}, ['Full text search']],
104
258
  ['option', {value: 'rawHTMLRegex'}, ['Regex search of raw HTML']]
105
259
  ]]
@@ -108,15 +262,37 @@ const blobHTMLSearchType = {
108
262
  'Value: ',
109
263
  ['input', {
110
264
  type: 'text', name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue',
111
- required: true
265
+ required: true,
266
+ $on: {
267
+ input () {
268
+ syncBlobHTMLValueValidity(this.closest('jsoe-search-blob-html'));
269
+ }
270
+ }
112
271
  }],
113
272
  ['textarea', {
114
- name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue', hidden: true
273
+ name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue', hidden: true,
274
+ $on: {
275
+ input () {
276
+ syncBlobHTMLValueValidity(this.closest('jsoe-search-blob-html'));
277
+ }
278
+ }
115
279
  }]
280
+ ]],
281
+ ['label', {class: 'jsoeSearchBlobHTMLFlagsLabel', hidden: true}, [
282
+ 'Flags: ',
283
+ ['select', {
284
+ name: `${name}-flags`, multiple: true, class: 'jsoeSearchBlobHTMLFlags',
285
+ $on: {
286
+ change () {
287
+ syncBlobHTMLValueValidity(this.closest('jsoe-search-blob-html'));
288
+ }
289
+ }
290
+ }, regexpType.allowedFlags.map((flag) => ['option', {value: flag}, [flag]])]
116
291
  ]]
117
292
  ]];
118
293
  },
119
- getQuery: getQueryViaElement
294
+ getQuery: getQueryViaElement,
295
+ applyQuery: applyQueryViaElement
120
296
  };
121
297
 
122
298
  export default blobHTMLSearchType;
@@ -1,9 +1,13 @@
1
1
  import {
2
- buildPathLabel, buildCheckbox, readCheckbox, buildOptInFieldset,
3
- readOptInChecked, wireOptInFieldset, buildAtLeastOneSentinel, syncAtLeastOneCheck
2
+ buildPathLabel, buildCheckbox, readCheckbox, applyCheckbox, buildOptInFieldset,
3
+ readOptInChecked, wireOptInFieldset, applyOptIn, buildAtLeastOneSentinel,
4
+ syncAtLeastOneCheck, extractLeafOfKind
4
5
  } from '../searchUtils.js';
5
6
  import {makeMapRecordJointLeaf} from '../queryTreeBuilders.js';
6
- import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
7
+ import {
8
+ findSearchElement, getQueryViaElement, hasGetQuery,
9
+ applyQueryViaElement, hasApplyQuery
10
+ } from '../searchElementUtils.js';
7
11
  import {buildSearchWidget} from '../searchDispatch.js';
8
12
 
9
13
  /**
@@ -85,6 +89,27 @@ const recordSearchType = {
85
89
  }
86
90
  const joint = readCheckbox(this);
87
91
  return makeMapRecordJointLeaf(searchPath, joint, keyQuery, valueQuery);
92
+ },
93
+ /**
94
+ * @this {HTMLElement}
95
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
96
+ * @returns {void}
97
+ */
98
+ applyQuery (queryNode) {
99
+ const searchPath = this.dataset.searchPath ?? '';
100
+ const {matched: jointLeaf} = extractLeafOfKind(queryNode, 'mapRecordJoint');
101
+ applyCheckbox(this, Boolean(jointLeaf?.joint));
102
+ applyOptIn(this, jointLeaf?.keyQuery !== undefined, 'key');
103
+ applyOptIn(this, jointLeaf?.valueQuery !== undefined, 'value');
104
+ const keyEl = findSearchElement(this, `${searchPath}/*key`);
105
+ if (keyEl && hasApplyQuery(keyEl)) {
106
+ keyEl.applyQuery(jointLeaf?.keyQuery);
107
+ }
108
+ const valueEl = findSearchElement(this, `${searchPath}/*value`);
109
+ if (valueEl && hasApplyQuery(valueEl)) {
110
+ valueEl.applyQuery(jointLeaf?.valueQuery);
111
+ }
112
+ syncKeyValueValidity(this);
88
113
  }
89
114
  }
90
115
  }, [
@@ -99,7 +124,8 @@ const recordSearchType = {
99
124
  buildAtLeastOneSentinel()
100
125
  ]];
101
126
  },
102
- getQuery: getQueryViaElement
127
+ getQuery: getQueryViaElement,
128
+ applyQuery: applyQueryViaElement
103
129
  };
104
130
 
105
131
  export default recordSearchType;
@@ -1,10 +1,13 @@
1
1
  import {
2
- buildPathLabel, buildLengthSizeControls, readLengthSizeQuery,
3
- buildOptInFieldset, readOptInChecked, wireOptInFieldset,
4
- buildAtLeastOneSentinel, syncAtLeastOneCheck
2
+ buildPathLabel, buildLengthSizeControls, readLengthSizeQuery, applyLengthSizeQuery,
3
+ buildOptInFieldset, readOptInChecked, wireOptInFieldset, applyOptIn,
4
+ buildAtLeastOneSentinel, syncAtLeastOneCheck, extractLeafOfKind, extractClauseForPath
5
5
  } from '../searchUtils.js';
6
6
  import {combineAnd} from '../queryTreeBuilders.js';
7
- import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
7
+ import {
8
+ findSearchElement, getQueryViaElement, hasGetQuery,
9
+ applyQueryViaElement, hasApplyQuery
10
+ } from '../searchElementUtils.js';
8
11
  import {buildSearchWidget} from '../searchDispatch.js';
9
12
 
10
13
  /**
@@ -142,11 +145,46 @@ const tupleSearchType = {
142
145
  ? restEl.getQuery()
143
146
  : undefined;
144
147
  return combineAnd([...itemLeaves, lengthLeaf, restLeaf]);
148
+ },
149
+ /**
150
+ * @this {HTMLElement}
151
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
152
+ * @returns {void}
153
+ */
154
+ applyQuery (queryNode) {
155
+ const searchPath = this.dataset.searchPath ?? '';
156
+ const itemCount = Number(this.dataset.itemCount ?? '0');
157
+ const hasRest = this.dataset.hasRest === 'true';
158
+ let remaining = queryNode;
159
+ Array.from({length: itemCount}, (_v, idx) => idx).forEach((idx) => {
160
+ const childPath = `${searchPath}/${idx}`;
161
+ const {matched, rest} = extractClauseForPath(remaining, childPath);
162
+ remaining = rest;
163
+ applyOptIn(this, matched !== undefined, String(idx));
164
+ const itemEl = findSearchElement(this, childPath);
165
+ if (itemEl && hasApplyQuery(itemEl)) {
166
+ itemEl.applyQuery(matched);
167
+ }
168
+ });
169
+ if (hasRest) {
170
+ const {matched: lengthLeaf, rest: afterLength} = extractLeafOfKind(remaining, 'lengthSize');
171
+ applyLengthSizeQuery(this, lengthLeaf);
172
+ remaining = afterLength;
173
+ const restPath = `${searchPath}/*`;
174
+ const {matched: restMatched} = extractClauseForPath(remaining, restPath);
175
+ applyOptIn(this, restMatched !== undefined, 'rest');
176
+ const restEl = findSearchElement(this, restPath);
177
+ if (restEl && hasApplyQuery(restEl)) {
178
+ restEl.applyQuery(restMatched);
179
+ }
180
+ }
181
+ syncTupleValidity(this);
145
182
  }
146
183
  }
147
184
  }, children];
148
185
  },
149
- getQuery: getQueryViaElement
186
+ getQuery: getQueryViaElement,
187
+ applyQuery: applyQueryViaElement
150
188
  };
151
189
 
152
190
  export default tupleSearchType;
@@ -1,6 +1,8 @@
1
- import {buildPathLabel, buildMultiSelect, readMultiSelect} from '../searchUtils.js';
1
+ import {
2
+ buildPathLabel, buildMultiSelect, readMultiSelect, applyMultiSelect, extractLeafOfKind
3
+ } from '../searchUtils.js';
2
4
  import {makeMultiSelectLeaf} from '../queryTreeBuilders.js';
3
- import {getQueryViaElement} from '../searchElementUtils.js';
5
+ import {getQueryViaElement, applyQueryViaElement} from '../searchElementUtils.js';
4
6
 
5
7
  /**
6
8
  * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
@@ -27,6 +29,15 @@ const SpecialRealNumberSearchType = {
27
29
  return selected.length
28
30
  ? makeMultiSelectLeaf(this.dataset.searchPath ?? '', {$in: selected})
29
31
  : undefined;
32
+ },
33
+ /**
34
+ * @this {HTMLElement}
35
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
36
+ * @returns {void}
37
+ */
38
+ applyQuery (queryNode) {
39
+ const {matched} = extractLeafOfKind(queryNode, 'multiSelect');
40
+ applyMultiSelect(this, matched?.$in ?? []);
30
41
  }
31
42
  }
32
43
  }, [
@@ -36,7 +47,8 @@ const SpecialRealNumberSearchType = {
36
47
  })
37
48
  ]];
38
49
  },
39
- getQuery: getQueryViaElement
50
+ getQuery: getQueryViaElement,
51
+ applyQuery: applyQueryViaElement
40
52
  };
41
53
 
42
54
  export default SpecialRealNumberSearchType;
@@ -1,8 +1,9 @@
1
1
  import {
2
- buildPathLabel, buildRangeInputsPair, readRangeInputsPair, syncRangeValidity
2
+ buildPathLabel, buildRangeInputsPair, readRangeInputsPair, syncRangeValidity,
3
+ applyRangeQuery, extractLeafOfKind
3
4
  } from '../searchUtils.js';
4
5
  import {makeRangeLeaf} from '../queryTreeBuilders.js';
5
- import {getQueryViaElement} from '../searchElementUtils.js';
6
+ import {getQueryViaElement, applyQueryViaElement} from '../searchElementUtils.js';
6
7
 
7
8
  /**
8
9
  * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
@@ -40,6 +41,15 @@ const buffersourceSearchType = {
40
41
  ...(gte === '' ? {} : {$gte: Number(gte)}),
41
42
  ...(lte === '' ? {} : {$lte: Number(lte)})
42
43
  });
44
+ },
45
+ /**
46
+ * @this {HTMLElement}
47
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
48
+ * @returns {void}
49
+ */
50
+ applyQuery (queryNode) {
51
+ const {matched} = extractLeafOfKind(queryNode, 'range');
52
+ applyRangeQuery(this, matched);
43
53
  }
44
54
  }
45
55
  }, [
@@ -47,7 +57,8 @@ const buffersourceSearchType = {
47
57
  ...buildRangeInputsPair({name, min: 0})
48
58
  ]];
49
59
  },
50
- getQuery: getQueryViaElement
60
+ getQuery: getQueryViaElement,
61
+ applyQuery: applyQueryViaElement
51
62
  };
52
63
 
53
64
  export default buffersourceSearchType;
@@ -1,7 +1,10 @@
1
1
  import {jml} from '../../vendor-imports.js';
2
- import {buildPathLabel, findOwnControl} from '../searchUtils.js';
2
+ import {buildPathLabel, findOwnControl, extractLeafOfKind} from '../searchUtils.js';
3
3
  import {makeTypeOfLeaf, combineAnd} from '../queryTreeBuilders.js';
4
- import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
4
+ import {
5
+ findSearchElement, getQueryViaElement, hasGetQuery,
6
+ applyQueryViaElement, hasApplyQuery
7
+ } from '../searchElementUtils.js';
5
8
  import {getSearchSchemaType, buildSearchWidget} from '../searchDispatch.js';
6
9
 
7
10
  /**
@@ -101,6 +104,44 @@ export function makeUnionFamilySearchType ({tagName, discriminated}) {
101
104
  ? branchEl.getQuery()
102
105
  : undefined;
103
106
  return combineAnd([typeOfLeaf, branchQuery]);
107
+ },
108
+ /**
109
+ * Drives the existing "Has type" `<select>`'s own `change`
110
+ * listener (defined below, per-instance and so already safe to
111
+ * close over `branches`) rather than reimplementing branch-DOM
112
+ * construction here - `$define` methods are installed once on the
113
+ * tag's shared prototype the first time it's defined (this
114
+ * function's own doc), so `applyQuery` can't close over
115
+ * `branches` itself, but dispatching a real `change` event lets
116
+ * the per-instance listener that *can* do the rebuild for us.
117
+ * @this {HTMLElement}
118
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
119
+ * @returns {void}
120
+ */
121
+ applyQuery (queryNode) {
122
+ const select = /** @type {HTMLSelectElement|undefined} */ (
123
+ findOwnControl(this, 'select.jsoeSearchTypeOf')
124
+ );
125
+ if (!select) {
126
+ return;
127
+ }
128
+ const {matched, rest} = extractLeafOfKind(queryNode, 'typeOf');
129
+ const optionMatch = matched && [...select.options].find((opt) => (
130
+ opt.dataset.searchType === matched.searchType &&
131
+ (opt.dataset.discriminatorValue === undefined
132
+ ? matched.discriminatorValue === undefined
133
+ : JSON.parse(opt.dataset.discriminatorValue) === matched.discriminatorValue)
134
+ ));
135
+ select.value = optionMatch ? optionMatch.value : '';
136
+ select.dispatchEvent(new Event('change'));
137
+ if (!optionMatch) {
138
+ return;
139
+ }
140
+ const searchPath = this.dataset.searchPath ?? '';
141
+ const branchEl = findSearchElement(this, searchPath);
142
+ if (branchEl && hasApplyQuery(branchEl)) {
143
+ branchEl.applyQuery(rest);
144
+ }
104
145
  }
105
146
  }
106
147
  }, [
@@ -163,6 +204,7 @@ export function makeUnionFamilySearchType ({tagName, discriminated}) {
163
204
  ['div', {class: 'searchUnionBranch'}]
164
205
  ]];
165
206
  },
166
- getQuery: getQueryViaElement
207
+ getQuery: getQueryViaElement,
208
+ applyQuery: applyQueryViaElement
167
209
  };
168
210
  }
@@ -0,0 +1,50 @@
1
+ const identifierKeyRegex = (/^[A-Za-z_$][\w$]*$/u);
2
+
3
+ /**
4
+ * A minimal JSON6-*subset* stringifier: unquotes object keys that are valid
5
+ * identifiers (for the "simplified" display the JSON6 mode is meant to
6
+ * give), while always fully quoting string *values* via `JSON.stringify`.
7
+ *
8
+ * `JSON6.stringify` (from the `json-6` package) was tried first but has a
9
+ * real bug: it runs the same identifier-unquoting logic it uses for keys
10
+ * on string *values* too, so e.g. the value `"hello"` is emitted as the
11
+ * bare word `hello` — not valid JS/JSON6 syntax as a value, and not even
12
+ * re-parseable by the package's own `JSON6.parse`. Hand-rolling this
13
+ * avoids depending on that broken path while still using `JSON6.parse`
14
+ * (which is correct) to read the text back after editing.
15
+ * @param {unknown} value
16
+ * @param {string} indent
17
+ * @param {string} curIndent
18
+ * @returns {string}
19
+ */
20
+ export const stringifyJSON6 = (value, indent, curIndent) => {
21
+ if (value === null || typeof value !== 'object') {
22
+ return JSON.stringify(value);
23
+ }
24
+ const nextIndent = curIndent + indent;
25
+ if (Array.isArray(value)) {
26
+ if (!value.length) {
27
+ return '[]';
28
+ }
29
+ const items = value.map((item) => {
30
+ return `${nextIndent}${stringifyJSON6(item, indent, nextIndent)}`;
31
+ });
32
+ return `[\n${items.join(',\n')}\n${curIndent}]`;
33
+ }
34
+ const keys = Object.keys(value);
35
+ if (!keys.length) {
36
+ return '{}';
37
+ }
38
+ const items = keys.map((key) => {
39
+ const keyText = identifierKeyRegex.test(key)
40
+ ? key
41
+ : JSON.stringify(key);
42
+ return `${nextIndent}${keyText}: ${
43
+ stringifyJSON6(
44
+ /** @type {{[key: string]: unknown}} */ (value)[key], indent,
45
+ nextIndent
46
+ )
47
+ }`;
48
+ });
49
+ return `{\n${items.join(',\n')}\n${curIndent}}`;
50
+ };
@@ -6,6 +6,7 @@ import {
6
6
  } from '../vendor-imports.js';
7
7
  import {structuredCloningJsoe} from '../formats/structuredCloning.js';
8
8
  import {parseValue} from '../formats/schema.js';
9
+ import {stringifyJSON6} from './json6Stringify.js';
9
10
 
10
11
  /**
11
12
  * @returns {import('typeson').Typeson}
@@ -14,8 +15,6 @@ const buildTypeson = () => {
14
15
  return new Typeson().register(structuredCloningJsoe);
15
16
  };
16
17
 
17
- const identifierKeyRegex = (/^[A-Za-z_$][\w$]*$/u);
18
-
19
18
  /** @type {ReadonlySet<string>} */
20
19
  const typedArrayTagNames = new Set([
21
20
  'Int8Array', 'Uint8Array', 'Uint8ClampedArray', 'Int16Array',
@@ -45,55 +44,6 @@ const bytesToBase64 = (bytes) => {
45
44
  return btoa(binary);
46
45
  };
47
46
 
48
- /**
49
- * A minimal JSON6-*subset* stringifier: unquotes object keys that are valid
50
- * identifiers (for the "simplified" display the JSON6 mode is meant to
51
- * give), while always fully quoting string *values* via `JSON.stringify`.
52
- *
53
- * `JSON6.stringify` (from the `json-6` package) was tried first but has a
54
- * real bug: it runs the same identifier-unquoting logic it uses for keys
55
- * on string *values* too, so e.g. the value `"hello"` is emitted as the
56
- * bare word `hello` — not valid JS/JSON6 syntax as a value, and not even
57
- * re-parseable by the package's own `JSON6.parse`. Hand-rolling this
58
- * avoids depending on that broken path while still using `JSON6.parse`
59
- * (which is correct) to read the text back after editing.
60
- * @param {unknown} value
61
- * @param {string} indent
62
- * @param {string} curIndent
63
- * @returns {string}
64
- */
65
- const stringifyJSON6 = (value, indent, curIndent) => {
66
- if (value === null || typeof value !== 'object') {
67
- return JSON.stringify(value);
68
- }
69
- const nextIndent = curIndent + indent;
70
- if (Array.isArray(value)) {
71
- if (!value.length) {
72
- return '[]';
73
- }
74
- const items = value.map((item) => {
75
- return `${nextIndent}${stringifyJSON6(item, indent, nextIndent)}`;
76
- });
77
- return `[\n${items.join(',\n')}\n${curIndent}]`;
78
- }
79
- const keys = Object.keys(value);
80
- if (!keys.length) {
81
- return '{}';
82
- }
83
- const items = keys.map((key) => {
84
- const keyText = identifierKeyRegex.test(key)
85
- ? key
86
- : JSON.stringify(key);
87
- return `${nextIndent}${keyText}: ${
88
- stringifyJSON6(
89
- /** @type {{[key: string]: unknown}} */ (value)[key], indent,
90
- nextIndent
91
- )
92
- }`;
93
- });
94
- return `{\n${items.join(',\n')}\n${curIndent}}`;
95
- };
96
-
97
47
  /**
98
48
  * Builds the Typeson/JSON6 text shown by both the view-mode "View raw"
99
49
  * button and, seeded, the edit-mode "Edit raw" button's Typeson mode.
@@ -788,7 +788,9 @@ export declare const describe: typeof core.describe;
788
788
  export declare const meta: typeof core.meta;
789
789
  type ZodInstanceOfParams = core.Params<ZodCustom, core.$ZodIssueCustom, "type" | "check" | "checks" | "fn" | "abort" | "error" | "params" | "path">;
790
790
  export interface ZodInstanceOf<T = unknown> extends ZodCustom<T, T> {
791
- properties<Shape extends core.$ZodShape>(shape: Shape, params?: string | core.$ZodCheckPropertiesParams): ZodInstanceOf<T & core.$InferObjectInput<Shape, {}>>;
791
+ properties<Shape extends {
792
+ [k in keyof T as T[k] extends Function ? never : k]?: core.$ZodType<unknown, T[k]>;
793
+ }>(shape: Shape, params?: string | core.$ZodCheckPropertiesParams): ZodInstanceOf<T & core.$InferObjectInput<Shape, {}>>;
792
794
  }
793
795
  export declare const ZodInstanceOf: core.$constructor<ZodInstanceOf>;
794
796
  declare function _instanceof<T extends typeof util.Class>(cls: T, params?: ZodInstanceOfParams): ZodInstanceOf<InstanceType<T>>;
@@ -1,5 +1,5 @@
1
1
  export const version = {
2
2
  major: 4,
3
3
  minor: 6,
4
- patch: 4,
4
+ patch: 5,
5
5
  };