@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,9 +1,9 @@
1
1
  import {
2
2
  buildPathLabel, buildRangeInputsPair, readRangeInputsPair, buildTriStateSelect,
3
- readTriStateSelect, syncRangeValidity
3
+ readTriStateSelect, syncRangeValidity, applyRangeQuery, applyTriState, extractLeafOfKind
4
4
  } from '../searchUtils.js';
5
5
  import {makeRangeLeaf, makeIntegerCheckLeaf, combineAnd} from '../queryTreeBuilders.js';
6
- import {getQueryViaElement} from '../searchElementUtils.js';
6
+ import {getQueryViaElement, applyQueryViaElement} from '../searchElementUtils.js';
7
7
 
8
8
  /**
9
9
  * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
@@ -40,6 +40,17 @@ const numberSearchType = {
40
40
  ? undefined
41
41
  : makeIntegerCheckLeaf(searchPath, isInteger);
42
42
  return combineAnd([rangeLeaf, integerLeaf]);
43
+ },
44
+ /**
45
+ * @this {HTMLElement}
46
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
47
+ * @returns {void}
48
+ */
49
+ applyQuery (queryNode) {
50
+ const {matched: rangeLeaf} = extractLeafOfKind(queryNode, 'range');
51
+ applyRangeQuery(this, rangeLeaf);
52
+ const {matched: integerLeaf} = extractLeafOfKind(queryNode, 'integerCheck');
53
+ applyTriState(this, integerLeaf?.isInteger);
43
54
  }
44
55
  }
45
56
  }, [
@@ -53,7 +64,8 @@ const numberSearchType = {
53
64
  ]]
54
65
  ]];
55
66
  },
56
- getQuery: getQueryViaElement
67
+ getQuery: getQueryViaElement,
68
+ applyQuery: applyQueryViaElement
57
69
  };
58
70
 
59
71
  export default numberSearchType;
@@ -1,13 +1,15 @@
1
1
  import {jml} from '../../vendor-imports.js';
2
2
  import {escapeJSONPointer} from '../../utils/jsonPointer.js';
3
3
  import {
4
- buildPathLabel, buildHasPropertyToggle, readTriStateSelect, findOwnControl,
5
- buildCheckbox, readCheckbox, buildAtLeastOneSentinel, syncAtLeastOneCheck,
6
- setDescendantsRequired, revalidateDescendants, resyncAtLeastOne
4
+ buildPathLabel, buildHasPropertyToggle, readTriStateSelect, applyTriState, findOwnControl,
5
+ buildCheckbox, readCheckbox, applyCheckbox, buildAtLeastOneSentinel, syncAtLeastOneCheck,
6
+ setDescendantsRequired, revalidateDescendants, resyncAtLeastOne,
7
+ extractLeafOfKind, extractClauseForPath
7
8
  } from '../searchUtils.js';
8
9
  import {combineAnd, makeHasPropertyLeaf} from '../queryTreeBuilders.js';
9
10
  import {
10
- findSearchElement, getQueryViaElement, hasGetQuery
11
+ findSearchElement, getQueryViaElement, hasGetQuery,
12
+ applyQueryViaElement, hasApplyQuery
11
13
  } from '../searchElementUtils.js';
12
14
  import {buildSearchWidget} from '../searchDispatch.js';
13
15
 
@@ -307,6 +309,91 @@ const objectSearchType = {
307
309
  getQuery () {
308
310
  const rows = [...this.children].filter(hasGetQuery);
309
311
  return combineAnd(rows.map((row) => row.getQuery()));
312
+ },
313
+ /**
314
+ * `$define` methods are shared once per tag (this file's other
315
+ * docs), so - like `getQuery` - this reads every per-instance fact
316
+ * (which properties exist, their names) off `this`'s own live DOM
317
+ * rather than closing over `objectSchemaObject`/`propertyEntries`.
318
+ * A property the query doesn't reference is reset to "no
319
+ * constraint" (its tri-state back to "(any)"/checkbox unchecked,
320
+ * its own child cleared) rather than removed outright - cheaper and
321
+ * just as correct a way to reach the same resulting query, and
322
+ * non-destructive (the row, and any of the user's other in-progress
323
+ * edits to it, stay put to remove by hand if truly unwanted).
324
+ * @this {HTMLElement}
325
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
326
+ * @returns {void}
327
+ */
328
+ applyQuery (queryNode) {
329
+ const searchPath = this.dataset.searchPath ?? '';
330
+ let remaining = queryNode;
331
+
332
+ [...this.querySelectorAll(':scope > jsoe-search-has-property')].forEach((row) => {
333
+ const propertyName = /** @type {HTMLElement} */ (row).dataset.propertyName ?? '';
334
+ const childPath = `${searchPath}/${escapeJSONPointer(propertyName)}`;
335
+ const {matched, rest} = extractClauseForPath(remaining, childPath);
336
+ remaining = rest;
337
+ const {matched: existsLeaf, rest: childQuery} = extractLeafOfKind(matched, 'hasProperty');
338
+ applyTriState(row, existsLeaf?.$exists);
339
+ const childEl = findSearchElement(row, childPath);
340
+ if (childEl && hasApplyQuery(childEl)) {
341
+ childEl.applyQuery(childQuery);
342
+ }
343
+ });
344
+
345
+ [...this.querySelectorAll(':scope > jsoe-search-required-property')].forEach((row) => {
346
+ const propertyName = /** @type {HTMLElement} */ (row).dataset.propertyName ?? '';
347
+ const childPath = `${searchPath}/${escapeJSONPointer(propertyName)}`;
348
+ const {matched, rest} = extractClauseForPath(remaining, childPath);
349
+ remaining = rest;
350
+ applyCheckbox(row, matched !== undefined);
351
+ const childEl = findSearchElement(row, childPath);
352
+ if (childEl && hasApplyQuery(childEl)) {
353
+ childEl.applyQuery(matched);
354
+ }
355
+ });
356
+
357
+ // Optional properties the query references but that aren't added
358
+ // yet: pick each in turn from the pull-down and click "Add" (that
359
+ // click handler is wired per-instance, so - unlike this method -
360
+ // it's safe for it to close over this widget's own schema/path),
361
+ // then apply the same way an already-added row above would.
362
+ const addPropertySelect = /** @type {HTMLSelectElement|undefined} */ (
363
+ findOwnControl(this, 'select.addPropertySelect')
364
+ );
365
+ const addButton = /** @type {HTMLButtonElement|undefined} */ (
366
+ findOwnControl(this, 'button.addPropertyButton')
367
+ );
368
+ [...(addPropertySelect?.options ?? [])].
369
+ filter((opt) => opt.value && !opt.disabled).
370
+ map((opt) => opt.value).
371
+ forEach((propertyName) => {
372
+ const childPath = `${searchPath}/${escapeJSONPointer(propertyName)}`;
373
+ const {matched, rest} = extractClauseForPath(remaining, childPath);
374
+ if (matched === undefined) {
375
+ return;
376
+ }
377
+ remaining = rest;
378
+ if (addPropertySelect) {
379
+ addPropertySelect.value = propertyName;
380
+ }
381
+ addButton?.click();
382
+ const row = [
383
+ ...this.querySelectorAll(':scope > jsoe-search-has-property')
384
+ ].find((r) => /** @type {HTMLElement} */ (r).dataset.propertyName === propertyName);
385
+ if (!row) {
386
+ return;
387
+ }
388
+ const {matched: existsLeaf, rest: childQuery} = extractLeafOfKind(matched, 'hasProperty');
389
+ applyTriState(row, existsLeaf?.$exists);
390
+ const childEl = findSearchElement(row, childPath);
391
+ if (childEl && hasApplyQuery(childEl)) {
392
+ childEl.applyQuery(childQuery);
393
+ }
394
+ });
395
+
396
+ syncObjectValidity(this);
310
397
  }
311
398
  }
312
399
  }, [
@@ -325,6 +412,7 @@ const objectSearchType = {
325
412
  : ['span', ['(no optional properties to search on)']],
326
413
  ['button', {
327
414
  type: 'button',
415
+ class: 'addPropertyButton',
328
416
  $on: {
329
417
  click () {
330
418
  const container = this.closest('jsoe-search-object');
@@ -361,7 +449,8 @@ const objectSearchType = {
361
449
  ...(propertyEntries.length > 0 ? [buildAtLeastOneSentinel()] : [])
362
450
  ]];
363
451
  },
364
- getQuery: getQueryViaElement
452
+ getQuery: getQueryViaElement,
453
+ applyQuery: applyQueryViaElement
365
454
  };
366
455
 
367
456
  export default objectSearchType;
@@ -1,6 +1,9 @@
1
- import {buildPathLabel} from '../searchUtils.js';
1
+ import {buildPathLabel, extractLeafOfKind} from '../searchUtils.js';
2
2
  import {makePassThroughLeaf} from '../queryTreeBuilders.js';
3
- import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
3
+ import {
4
+ findSearchElement, getQueryViaElement, hasGetQuery,
5
+ applyQueryViaElement, hasApplyQuery
6
+ } from '../searchElementUtils.js';
4
7
  import {buildSearchWidget} from '../searchDispatch.js';
5
8
 
6
9
  /**
@@ -48,6 +51,19 @@ const promiseSearchType = {
48
51
  return childQuery === undefined
49
52
  ? undefined
50
53
  : makePassThroughLeaf(searchPath, childQuery);
54
+ },
55
+ /**
56
+ * @this {HTMLElement}
57
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
58
+ * @returns {void}
59
+ */
60
+ applyQuery (queryNode) {
61
+ const {matched} = extractLeafOfKind(queryNode, 'passThrough');
62
+ const searchPath = this.dataset.searchPath ?? '';
63
+ const childEl = findSearchElement(this, searchPath);
64
+ if (childEl && hasApplyQuery(childEl)) {
65
+ childEl.applyQuery(matched?.query);
66
+ }
51
67
  }
52
68
  }
53
69
  }, [
@@ -55,7 +71,8 @@ const promiseSearchType = {
55
71
  childArr
56
72
  ]];
57
73
  },
58
- getQuery: getQueryViaElement
74
+ getQuery: getQueryViaElement,
75
+ applyQuery: applyQueryViaElement
59
76
  };
60
77
 
61
78
  export default promiseSearchType;
@@ -1,9 +1,9 @@
1
1
  import {
2
- buildPathLabel, buildLiteralRegexControls, readLiteralRegexQuery,
3
- buildMultiSelect, readMultiSelect, findOwnControl
2
+ buildPathLabel, buildLiteralRegexControls, readLiteralRegexQuery, applyLiteralRegexQuery,
3
+ buildMultiSelect, readMultiSelect, applyMultiSelect, findOwnControl, extractLeafOfKind
4
4
  } from '../searchUtils.js';
5
5
  import {makeMultiSelectLeaf, combineAnd} from '../queryTreeBuilders.js';
6
- import {getQueryViaElement} from '../searchElementUtils.js';
6
+ import {getQueryViaElement, applyQueryViaElement} from '../searchElementUtils.js';
7
7
  import regexpType from '../../fundamentalTypes/regexpType.js';
8
8
 
9
9
  /**
@@ -43,6 +43,25 @@ const regexpSearchType = {
43
43
  ? makeMultiSelectLeaf(searchPath, {$in: selectedFlags})
44
44
  : undefined;
45
45
  return combineAnd([sourceLeaf, flagsLeaf]);
46
+ },
47
+ /**
48
+ * @this {HTMLElement}
49
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
50
+ * @returns {void}
51
+ */
52
+ applyQuery (queryNode) {
53
+ const {matched: flagsLeaf, rest: sourceLeaf} = extractLeafOfKind(queryNode, 'multiSelect');
54
+ // Sets Mode/Value first (dispatching the `change` that also
55
+ // toggles the Flags multi-select's own `hidden` state via this
56
+ // widget's `onModeChange`), then Flags.
57
+ applyLiteralRegexQuery(
58
+ this,
59
+ /**
60
+ * @type {import('../queryTree.js').QueryLiteralSetLeaf|
61
+ *import('../queryTree.js').QueryRegexLeaf|
62
+ import('../queryTree.js').QueryNotContainsLeaf|undefined} */ (sourceLeaf)
63
+ );
64
+ applyMultiSelect(this, flagsLeaf?.$in ?? []);
46
65
  }
47
66
  }
48
67
  }, [
@@ -66,7 +85,8 @@ const regexpSearchType = {
66
85
  ]]
67
86
  ]];
68
87
  },
69
- getQuery: getQueryViaElement
88
+ getQuery: getQueryViaElement,
89
+ applyQuery: applyQueryViaElement
70
90
  };
71
91
 
72
92
  export default regexpSearchType;
@@ -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
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
  /**
@@ -69,6 +72,22 @@ const setSearchType = {
69
72
  ? elementEl.getQuery()
70
73
  : undefined;
71
74
  return combineAnd([lengthLeaf, elementLeaf]);
75
+ },
76
+ /**
77
+ * @this {HTMLElement}
78
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
79
+ * @returns {void}
80
+ */
81
+ applyQuery (queryNode) {
82
+ const searchPath = this.dataset.searchPath ?? '';
83
+ const {matched: lengthLeaf, rest} = extractLeafOfKind(queryNode, 'lengthSize');
84
+ applyLengthSizeQuery(this, lengthLeaf);
85
+ applyOptIn(this, rest !== undefined, '');
86
+ const elementEl = findSearchElement(this, `${searchPath}/*`);
87
+ if (elementEl && hasApplyQuery(elementEl)) {
88
+ elementEl.applyQuery(rest);
89
+ }
90
+ syncSetValidity(this);
72
91
  }
73
92
  }
74
93
  }, [
@@ -85,7 +104,8 @@ const setSearchType = {
85
104
  buildAtLeastOneSentinel()
86
105
  ]];
87
106
  },
88
- getQuery: getQueryViaElement
107
+ getQuery: getQueryViaElement,
108
+ applyQuery: applyQueryViaElement
89
109
  };
90
110
 
91
111
  export default setSearchType;
@@ -1,5 +1,7 @@
1
- import {buildPathLabel, buildLiteralRegexControls, readLiteralRegexQuery} from '../searchUtils.js';
2
- import {getQueryViaElement} from '../searchElementUtils.js';
1
+ import {
2
+ buildPathLabel, buildLiteralRegexControls, readLiteralRegexQuery, applyLiteralRegexQuery
3
+ } from '../searchUtils.js';
4
+ import {getQueryViaElement, applyQueryViaElement} from '../searchElementUtils.js';
3
5
  import regexpType from '../../fundamentalTypes/regexpType.js';
4
6
 
5
7
  /**
@@ -31,6 +33,25 @@ const stringSearchType = {
31
33
  /** @this {HTMLElement} */
32
34
  getQuery () {
33
35
  return readLiteralRegexQuery(this, this.dataset.searchPath ?? '');
36
+ },
37
+ /**
38
+ * `getQuery` returns a bare leaf here (no other facet to combine it
39
+ * with via `$and`), so `queryNode` normally already is one -
40
+ * unwrapping a trivial one-element `$and` too just tolerates a
41
+ * hand-edited raw query that wraps it anyway.
42
+ * @this {HTMLElement}
43
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
44
+ * @returns {void}
45
+ */
46
+ applyQuery (queryNode) {
47
+ const leaf = queryNode && '$and' in queryNode ? queryNode.$and[0] : queryNode;
48
+ applyLiteralRegexQuery(
49
+ this,
50
+ /**
51
+ * @type {import('../queryTree.js').QueryLiteralSetLeaf|
52
+ *import('../queryTree.js').QueryRegexLeaf|
53
+ import('../queryTree.js').QueryNotContainsLeaf|undefined} */ (leaf)
54
+ );
34
55
  }
35
56
  }
36
57
  }, [
@@ -38,7 +59,8 @@ const stringSearchType = {
38
59
  buildLiteralRegexControls({name, flagOptions: regexpType.allowedFlags})
39
60
  ]];
40
61
  },
41
- getQuery: getQueryViaElement
62
+ getQuery: getQueryViaElement,
63
+ applyQuery: applyQueryViaElement
42
64
  };
43
65
 
44
66
  export default stringSearchType;
@@ -1,5 +1,7 @@
1
- import {buildPathLabel, buildLiteralRegexControls, readLiteralRegexQuery} from '../searchUtils.js';
2
- import {getQueryViaElement} from '../searchElementUtils.js';
1
+ import {
2
+ buildPathLabel, buildLiteralRegexControls, readLiteralRegexQuery, applyLiteralRegexQuery
3
+ } from '../searchUtils.js';
4
+ import {getQueryViaElement, applyQueryViaElement} from '../searchElementUtils.js';
3
5
  import regexpType from '../../fundamentalTypes/regexpType.js';
4
6
 
5
7
  /**
@@ -26,6 +28,21 @@ const symbolSearchType = {
26
28
  /** @this {HTMLElement} */
27
29
  getQuery () {
28
30
  return readLiteralRegexQuery(this, this.dataset.searchPath ?? '');
31
+ },
32
+ /**
33
+ * @this {HTMLElement}
34
+ * @param {import('../queryTree.js').QueryNode|undefined} queryNode
35
+ * @returns {void}
36
+ */
37
+ applyQuery (queryNode) {
38
+ const leaf = queryNode && '$and' in queryNode ? queryNode.$and[0] : queryNode;
39
+ applyLiteralRegexQuery(
40
+ this,
41
+ /**
42
+ * @type {import('../queryTree.js').QueryLiteralSetLeaf|
43
+ *import('../queryTree.js').QueryRegexLeaf|
44
+ import('../queryTree.js').QueryNotContainsLeaf|undefined} */ (leaf)
45
+ );
29
46
  }
30
47
  }
31
48
  }, [
@@ -33,7 +50,8 @@ const symbolSearchType = {
33
50
  buildLiteralRegexControls({name, flagOptions: regexpType.allowedFlags})
34
51
  ]];
35
52
  },
36
- getQuery: getQueryViaElement
53
+ getQuery: getQueryViaElement,
54
+ applyQuery: applyQueryViaElement
37
55
  };
38
56
 
39
57
  export default symbolSearchType;
@@ -5,6 +5,7 @@ import {getSearchTypeObject, buildSearchWidget, resolveIntersection} from './sea
5
5
  * @typedef {{
6
6
  * container: HTMLFormElement,
7
7
  * $getQuery: () => import('./queryTree.js').QueryAnd,
8
+ * $applyQuery: (queryDoc: import('./queryTree.js').QueryAnd) => void,
8
9
  * whenReady: Promise<void>
9
10
  * }} SearchChoicesControl
10
11
  */
@@ -91,6 +92,27 @@ export function buildSearchChoices ({schemaContent, typeNamespace, topRoot, type
91
92
  });
92
93
  return {$and: result === undefined ? [] : [result]};
93
94
  },
95
+ /**
96
+ * The "Edit raw" round-trip's entry point - the inverse of `$getQuery`,
97
+ * unwrapping its own `{$and: [...]}` convention back to a bare
98
+ * `QueryNode` (or `undefined` for an empty `$and`, "no constraint")
99
+ * before handing off to the root schema's own `applyQuery`, which
100
+ * recurses through the whole tree exactly as `getQuery` does.
101
+ * @param {import('./queryTree.js').QueryAnd} queryDoc
102
+ * @returns {void}
103
+ */
104
+ $applyQuery (queryDoc) {
105
+ const {$and: clauses} = queryDoc;
106
+ let queryNode;
107
+ if (clauses.length === 1) {
108
+ [queryNode] = clauses;
109
+ } else if (clauses.length > 1) {
110
+ queryNode = {$and: clauses};
111
+ }
112
+ getSearchTypeObject(schemaObject).applyQuery({
113
+ root: container, path, queryNode
114
+ });
115
+ },
94
116
  whenReady: Promise.resolve()
95
117
  };
96
118
  }
@@ -167,11 +167,14 @@
167
167
 
168
168
  /**
169
169
  * XPath/CSS-selector/full-text/raw-HTML-regex; no Mongo equivalent.
170
+ * `$options`, mirroring `QueryRegexLeaf`'s field of the same name, is only
171
+ * ever populated for `mode: 'rawHTMLRegex'`.
170
172
  * @typedef {{
171
173
  * kind: 'blobHTML',
172
174
  * path: string,
173
175
  * mode: 'xpath'|'cssSelector'|'fullText'|'rawHTMLRegex',
174
- * value: string
176
+ * value: string,
177
+ * $options?: string
175
178
  * }} QueryBlobHTMLLeaf
176
179
  */
177
180
 
@@ -172,10 +172,11 @@ export function makeTypeOfLeaf (path, searchType, discriminatorValue) {
172
172
  * @param {string} path
173
173
  * @param {import('./queryTree.js').QueryBlobHTMLLeaf['mode']} mode
174
174
  * @param {string} value
175
+ * @param {string} [$options]
175
176
  * @returns {import('./queryTree.js').QueryBlobHTMLLeaf}
176
177
  */
177
- export function makeBlobHTMLLeaf (path, mode, value) {
178
- return {kind: 'blobHTML', path, mode, value};
178
+ export function makeBlobHTMLLeaf (path, mode, value, $options) {
179
+ return {kind: 'blobHTML', path, mode, value, ...($options ? {$options} : {})};
179
180
  }
180
181
 
181
182
  /**
@@ -41,9 +41,12 @@ import blobHTMLSearchType from './subTypes/blobHTMLSearchType.js';
41
41
  */
42
42
 
43
43
  /**
44
- * The search-side analogue of `TypeObject` (`src/types.js:308-408`): two
45
- * methods, `buildUI` (a `jml` array) and `getQuery` (reads the built DOM
46
- * back into a `QueryNode`, or `undefined` when no constraint was entered).
44
+ * The search-side analogue of `TypeObject` (`src/types.js:308-408`): three
45
+ * methods, `buildUI` (a `jml` array), `getQuery` (reads the built DOM back
46
+ * into a `QueryNode`, or `undefined` when no constraint was entered), and
47
+ * `applyQuery` (the inverse - drives the built DOM back into the state a
48
+ * previously-read, or hand-edited, `QueryNode` describes, for the "Edit raw"
49
+ * round-trip, `SearchChoicesControl.$applyQuery`, `src/search/index.js`).
47
50
  * @typedef {{
48
51
  * buildUI: (cfg: {
49
52
  * schemaObject: import('../formats/schema.js').ZodexSchema,
@@ -56,7 +59,12 @@ import blobHTMLSearchType from './subTypes/blobHTMLSearchType.js';
56
59
  * getQuery: (cfg: {
57
60
  * root: HTMLElement,
58
61
  * path: string
59
- * }) => QueryNode|undefined
62
+ * }) => QueryNode|undefined,
63
+ * applyQuery: (cfg: {
64
+ * root: HTMLElement,
65
+ * path: string,
66
+ * queryNode: QueryNode|undefined
67
+ * }) => void
60
68
  * }} SearchTypeObject
61
69
  */
62
70
 
@@ -71,7 +79,8 @@ import blobHTMLSearchType from './subTypes/blobHTMLSearchType.js';
71
79
  function stubSearchType (searchSchemaType) {
72
80
  return {
73
81
  buildUI: () => ['span', [`TODO: ${searchSchemaType}`]],
74
- getQuery: () => undefined
82
+ getQuery: () => undefined,
83
+ applyQuery () {}
75
84
  };
76
85
  }
77
86
 
@@ -1,10 +1,12 @@
1
1
  import {
2
2
  buildPathLabel, buildCheckbox, readCheckbox,
3
- buildLiteralRegexControls, readLiteralRegexQuery,
4
- buildRangeInputsPair, readRangeInputsPair, syncRangeValidity,
5
- buildTriStateSelect, readTriStateSelect,
6
- buildOptInFieldset, readOptInChecked, wireOptInFieldset,
7
- buildAtLeastOneSentinel, syncAtLeastOneCheck
3
+ buildLiteralRegexControls, readLiteralRegexQuery, applyOptInLiteralRegexFacet,
4
+ buildRangeInputsPair, readRangeInputsPair, syncRangeValidity, applyOptInRangeFacet,
5
+ applyRangeQuery,
6
+ buildTriStateSelect, readTriStateSelect, applyTriState,
7
+ buildOptInFieldset, readOptInChecked, wireOptInFieldset, applyOptIn,
8
+ buildAtLeastOneSentinel, syncAtLeastOneCheck,
9
+ extractLeafOfKind
8
10
  } from './searchUtils.js';
9
11
  import {combineAnd, makeRangeLeaf, makeDomShapeLeaf} from './queryTreeBuilders.js';
10
12
  import regexpType from '../fundamentalTypes/regexpType.js';
@@ -27,7 +29,8 @@ export function findSearchElement (root, path) {
27
29
 
28
30
  /**
29
31
  * @typedef {HTMLElement & {
30
- * getQuery: () => import('./queryTree.js').QueryNode|undefined
32
+ * getQuery: () => import('./queryTree.js').QueryNode|undefined,
33
+ * applyQuery?: (queryNode: import('./queryTree.js').QueryNode|undefined) => void
31
34
  * }} SearchElement
32
35
  */
33
36
 
@@ -45,6 +48,24 @@ export function getQueryViaElement ({root, path}) {
45
48
  return el?.getQuery();
46
49
  }
47
50
 
51
+ /**
52
+ * The `SearchTypeObject.applyQuery` every module implements identically -
53
+ * the "Edit raw" round-trip's counterpart to `getQueryViaElement`: locate
54
+ * the element built under `root` and delegate to its own `applyQuery(...)`
55
+ * method.
56
+ * @param {{
57
+ * root: HTMLElement, path: string,
58
+ * queryNode: import('./queryTree.js').QueryNode|undefined
59
+ * }} cfg
60
+ * @returns {void}
61
+ */
62
+ export function applyQueryViaElement ({root, path, queryNode}) {
63
+ const el = /** @type {SearchElement|undefined} */ (
64
+ findSearchElement(root, path)
65
+ );
66
+ el?.applyQuery?.(queryNode);
67
+ }
68
+
48
69
  /**
49
70
  * Type-predicate guard distinguishing a built search element (leaf or
50
71
  * container, either answers to `.getQuery()`) from a plain DOM node - used
@@ -60,6 +81,20 @@ export function hasGetQuery (el) {
60
81
  'function';
61
82
  }
62
83
 
84
+ /**
85
+ * Same as `hasGetQuery`, for `applyQuery` - a container walking its own
86
+ * children to recurse an "Edit raw" apply into each needs to know which
87
+ * ones answer to it, same reasoning as `hasGetQuery`'s own doc.
88
+ * @param {Element} el
89
+ * @returns {el is SearchElement & {applyQuery: (
90
+ * queryNode: import('./queryTree.js').QueryNode|undefined
91
+ * ) => void}}
92
+ */
93
+ export function hasApplyQuery (el) {
94
+ return typeof (/** @type {{applyQuery?: unknown}} */ (el)).applyQuery ===
95
+ 'function';
96
+ }
97
+
63
98
  /**
64
99
  * Factory for the README's "no variants to allow for distinct search"
65
100
  * leaves (`undefined`/`void`, `null`, `NaN`): the only meaningful question
@@ -89,7 +124,10 @@ export function makePresenceOnlySearchType ({tagName}) {
89
124
  return checked
90
125
  ? {kind: 'presence', path: this.dataset.searchPath ?? '', $exists: true}
91
126
  : undefined;
92
- }
127
+ },
128
+ // The checkbox is permanently checked and `disabled` - nothing
129
+ // for a raw query to change here either way.
130
+ applyQuery () {}
93
131
  }
94
132
  }, [
95
133
  ['span', {class: 'searchLabel'}, [label]],
@@ -98,7 +136,8 @@ export function makePresenceOnlySearchType ({tagName}) {
98
136
  })
99
137
  ]];
100
138
  },
101
- getQuery: getQueryViaElement
139
+ getQuery: getQueryViaElement,
140
+ applyQuery: applyQueryViaElement
102
141
  };
103
142
  }
104
143
 
@@ -211,11 +250,27 @@ export function makeErrorFamilySearchType ({tagName}) {
211
250
  });
212
251
  });
213
252
  return combineAnd([...stringLeaves, ...numberLeaves]);
253
+ },
254
+ /**
255
+ * @this {HTMLElement}
256
+ * @param {import('./queryTree.js').QueryNode|undefined} queryNode
257
+ * @returns {void}
258
+ */
259
+ applyQuery (queryNode) {
260
+ const searchPath = this.dataset.searchPath ?? '';
261
+ errorStringProps.forEach((prop) => {
262
+ applyOptInLiteralRegexFacet(this, queryNode, `${searchPath}/${prop}`, prop);
263
+ });
264
+ errorNumberProps.forEach((prop) => {
265
+ applyOptInRangeFacet(this, queryNode, `${searchPath}/${prop}`, prop);
266
+ });
267
+ syncErrorFamilyValidity(this);
214
268
  }
215
269
  }
216
270
  }, buildErrorFamilyChildren({label, name})];
217
271
  },
218
- getQuery: getQueryViaElement
272
+ getQuery: getQueryViaElement,
273
+ applyQuery: applyQueryViaElement
219
274
  };
220
275
  }
221
276
 
@@ -362,12 +417,45 @@ export function makeDomShapeSearchType ({
362
417
  ? {}
363
418
  : {dimensionCheck: dimensionCheck ? 3 : 2})
364
419
  });
420
+ },
421
+ /**
422
+ * @this {HTMLElement}
423
+ * @param {import('./queryTree.js').QueryNode|undefined} queryNode
424
+ * @returns {void}
425
+ */
426
+ applyQuery (queryNode) {
427
+ // A `domShape` leaf embeds each dimension's own `range` leaf
428
+ // directly (`dimensions`), not via `$and` - so, unlike
429
+ // `makeErrorFamilySearchType`'s flat properties,
430
+ // `extractClauseForPath` (which `applyOptInRangeFacet` uses)
431
+ // isn't needed here: read the whole leaf once and apply each
432
+ // dimension straight from it.
433
+ const {matched} = extractLeafOfKind(queryNode, 'domShape');
434
+ dimensionKeys.forEach((dim) => {
435
+ const dimLeaf = matched?.dimensions[dim];
436
+ applyOptIn(this, dimLeaf !== undefined, dim);
437
+ applyRangeQuery(this, dimLeaf, dim);
438
+ });
439
+ if (includeReadonly) {
440
+ applyTriState(this, matched?.readonlyCheck, 'readonly');
441
+ }
442
+ if (includeDimensionCheck) {
443
+ applyTriState(
444
+ this,
445
+ matched?.dimensionCheck === undefined
446
+ ? undefined
447
+ : matched.dimensionCheck === 3,
448
+ 'dimension'
449
+ );
450
+ }
451
+ syncDomShapeValidity({root: this, dimensionKeys, includeReadonly, includeDimensionCheck});
365
452
  }
366
453
  }
367
454
  }, buildDomShapeChildren({
368
455
  label, name, dimensionKeys, includeReadonly, includeDimensionCheck
369
456
  })];
370
457
  },
371
- getQuery: getQueryViaElement
458
+ getQuery: getQueryViaElement,
459
+ applyQuery: applyQueryViaElement
372
460
  };
373
461
  }