@es-joy/jsoe 0.31.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 (71) hide show
  1. package/CHANGES.md +4 -0
  2. package/dist/index.js +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/search/fundamentalTypes/arraySearchType.d.ts.map +1 -1
  5. package/dist/search/fundamentalTypes/bigintSearchType.d.ts.map +1 -1
  6. package/dist/search/fundamentalTypes/blobSearchType.d.ts.map +1 -1
  7. package/dist/search/fundamentalTypes/booleanSearchType.d.ts.map +1 -1
  8. package/dist/search/fundamentalTypes/catchSearchType.d.ts.map +1 -1
  9. package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -1
  10. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -1
  11. package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -1
  12. package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -1
  13. package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -1
  14. package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -1
  15. package/dist/search/fundamentalTypes/mapSearchType.d.ts.map +1 -1
  16. package/dist/search/fundamentalTypes/numberSearchType.d.ts.map +1 -1
  17. package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -1
  18. package/dist/search/fundamentalTypes/promiseSearchType.d.ts.map +1 -1
  19. package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -1
  20. package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -1
  21. package/dist/search/fundamentalTypes/stringSearchType.d.ts.map +1 -1
  22. package/dist/search/fundamentalTypes/symbolSearchType.d.ts.map +1 -1
  23. package/dist/search/index.d.ts +2 -0
  24. package/dist/search/index.d.ts.map +1 -1
  25. package/dist/search/searchDispatch.d.ts +11 -3
  26. package/dist/search/searchDispatch.d.ts.map +1 -1
  27. package/dist/search/searchElementUtils.d.ts +31 -1
  28. package/dist/search/searchElementUtils.d.ts.map +1 -1
  29. package/dist/search/searchUtils.d.ts +180 -0
  30. package/dist/search/searchUtils.d.ts.map +1 -1
  31. package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -1
  32. package/dist/search/subTypes/recordSearchType.d.ts.map +1 -1
  33. package/dist/search/subTypes/tupleSearchType.d.ts.map +1 -1
  34. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts.map +1 -1
  35. package/dist/search/superTypes/buffersourceSearchType.d.ts.map +1 -1
  36. package/dist/search/unions/unionFamilySearchType.d.ts.map +1 -1
  37. package/dist/utils/json6Stringify.d.ts +2 -0
  38. package/dist/utils/json6Stringify.d.ts.map +1 -0
  39. package/dist/utils/rawTypesonEditor.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/search/fundamentalTypes/arraySearchType.js +28 -5
  42. package/src/search/fundamentalTypes/bigintSearchType.js +14 -3
  43. package/src/search/fundamentalTypes/blobSearchType.js +21 -3
  44. package/src/search/fundamentalTypes/booleanSearchType.js +15 -3
  45. package/src/search/fundamentalTypes/catchSearchType.js +20 -3
  46. package/src/search/fundamentalTypes/dateSearchType.js +42 -3
  47. package/src/search/fundamentalTypes/domexceptionSearchType.js +18 -4
  48. package/src/search/fundamentalTypes/enumSearchType.js +15 -3
  49. package/src/search/fundamentalTypes/fileSearchType.js +15 -3
  50. package/src/search/fundamentalTypes/filelistSearchType.js +25 -5
  51. package/src/search/fundamentalTypes/functionSearchType.js +31 -4
  52. package/src/search/fundamentalTypes/mapSearchType.js +33 -5
  53. package/src/search/fundamentalTypes/numberSearchType.js +15 -3
  54. package/src/search/fundamentalTypes/objectSearchType.js +94 -5
  55. package/src/search/fundamentalTypes/promiseSearchType.js +20 -3
  56. package/src/search/fundamentalTypes/regexpSearchType.js +24 -4
  57. package/src/search/fundamentalTypes/setSearchType.js +25 -5
  58. package/src/search/fundamentalTypes/stringSearchType.js +25 -3
  59. package/src/search/fundamentalTypes/symbolSearchType.js +21 -3
  60. package/src/search/index.js +22 -0
  61. package/src/search/searchDispatch.js +14 -5
  62. package/src/search/searchElementUtils.js +98 -10
  63. package/src/search/searchUtils.js +368 -0
  64. package/src/search/subTypes/blobHTMLSearchType.js +43 -3
  65. package/src/search/subTypes/recordSearchType.js +30 -4
  66. package/src/search/subTypes/tupleSearchType.js +43 -5
  67. package/src/search/superTypes/SpecialRealNumberSearchType.js +15 -3
  68. package/src/search/superTypes/buffersourceSearchType.js +14 -3
  69. package/src/search/unions/unionFamilySearchType.js +45 -3
  70. package/src/utils/json6Stringify.js +50 -0
  71. package/src/utils/rawTypesonEditor.js +1 -51
@@ -1,5 +1,6 @@
1
1
  import {schemaLabel} from '../utils/schemaMeta.js';
2
2
  import {getJSONPointerParts} from '../utils/jsonPointer.js';
3
+ import {combineAnd} from './queryTreeBuilders.js';
3
4
 
4
5
  /**
5
6
  * @typedef {import('../types.js').JamilihArray} JamilihArray
@@ -905,3 +906,370 @@ export function setDescendantsRequired (root, required) {
905
906
  ).required = required;
906
907
  });
907
908
  }
909
+
910
+ /**
911
+ * @typedef {import('./queryTree.js').QueryNode} QueryNode
912
+ */
913
+
914
+ /**
915
+ * The "Edit raw" round-trip (`SearchChoicesControl.$applyQuery`,
916
+ * `src/search/index.js`) needs the inverse of every `getQuery`: given a
917
+ * previously-serialized (or hand-edited) `QueryNode`, drive the same DOM
918
+ * controls `getQuery` reads back into that state, so the two stay in sync.
919
+ * `unwrapAndClauses`/`nodeTouchesPath`/`extractLeafOfKind`/
920
+ * `extractClauseForPath` below are the shared groundwork every
921
+ * `applyQuery` implementation builds on to undo `combineAnd`'s own
922
+ * flattening; the `apply*` functions below those are the direct inverse of
923
+ * one `build*`/`read*` pair each.
924
+ *
925
+ * A plain leaf becomes a one-element array; `undefined` (no constraint)
926
+ * becomes `[]`; an `$and` node's own `.$and` array is returned as-is (not
927
+ * further flattened - a nested `$and` stays a single clause, so
928
+ * `nodeTouchesPath` below is what actually looks inside one).
929
+ * @param {QueryNode|undefined} queryNode
930
+ * @returns {QueryNode[]}
931
+ */
932
+ export function unwrapAndClauses (queryNode) {
933
+ if (queryNode === undefined) {
934
+ return [];
935
+ }
936
+ if ('$and' in queryNode) {
937
+ return queryNode.$and;
938
+ }
939
+ return [queryNode];
940
+ }
941
+
942
+ /**
943
+ * Whether any leaf reachable from `node` (recursing through `$and`/`$or`
944
+ * combinators and a `not`/`passThrough` leaf's own wrapped `query`) targets
945
+ * `path` itself or somewhere nested under it - used to decide which of a
946
+ * container widget's several recursed children (`tupleSearchType.js`'s
947
+ * positions/rest, `functionSearchType.js`'s args/output,
948
+ * `objectSearchType.js`'s properties, `unionFamilySearchType.js`'s chosen
949
+ * branch) a given top-level clause belongs to, since a recursed child's own
950
+ * `getQuery()` result is threaded through untouched (whatever shape it is)
951
+ * rather than re-wrapped with the parent's path.
952
+ * @param {QueryNode|undefined} node
953
+ * @param {string} path
954
+ * @returns {boolean}
955
+ */
956
+ export function nodeTouchesPath (node, path) {
957
+ // `QueryNotLeaf` is the only leaf kind with no `path` of its own - unwrap
958
+ // its `query` (iteratively, in case of a `not` wrapping another `not`)
959
+ // before dispatching on the combinator/leaf shapes below.
960
+ let current = node;
961
+ while (
962
+ current !== undefined && !('$and' in current) && !('$or' in current) &&
963
+ !('path' in current) && 'query' in current
964
+ ) {
965
+ ({query: current} = current);
966
+ }
967
+ if (current === undefined) {
968
+ return false;
969
+ }
970
+ if ('$and' in current) {
971
+ return current.$and.some((child) => nodeTouchesPath(child, path));
972
+ }
973
+ if ('$or' in current) {
974
+ return current.$or.some((child) => nodeTouchesPath(child, path));
975
+ }
976
+ if ('path' in current) {
977
+ return current.path === path || current.path.startsWith(`${path}/`);
978
+ }
979
+ return false;
980
+ }
981
+
982
+ /**
983
+ * Pulls the (at most one) top-level clause of `queryNode` whose own `kind`
984
+ * matches out of the `$and` it's combined into, leaving the rest re-combined
985
+ * - the inverse half of `combineAnd([ownLeaf, ...])`. Safe to use for any
986
+ * facet whose own leaf kind is unique within its widget (every `kind` this
987
+ * module's callers pass is exactly that: `lengthSize`, `hasProperty`,
988
+ * `mapRecordJoint`, `typeOf`), since a recursed child's own (arbitrarily
989
+ * nested) contribution never surfaces as a *bare* top-level leaf sharing the
990
+ * parent's own facet kind.
991
+ * `kind`'s own type parameter narrows `matched`'s type to exactly the leaf
992
+ * shape that `kind` names (`Extract<QueryLeaf, {kind: K}>`), so a call like
993
+ * `extractLeafOfKind(queryNode, 'range')` gives back a properly-typed
994
+ * `QueryRangeLeaf|undefined` with no cast needed at the call site.
995
+ * @template {import('./queryTree.js').QueryLeaf['kind']} K
996
+ * @param {QueryNode|undefined} queryNode
997
+ * @param {K} kind
998
+ * @returns {{
999
+ * matched: Extract<import('./queryTree.js').QueryLeaf, {kind: K}>|undefined,
1000
+ * rest: QueryNode|undefined
1001
+ * }}
1002
+ */
1003
+ export function extractLeafOfKind (queryNode, kind) {
1004
+ const clauses = unwrapAndClauses(queryNode);
1005
+ const idx = clauses.findIndex((clause) => 'kind' in clause && clause.kind === kind);
1006
+ if (idx === -1) {
1007
+ return {matched: undefined, rest: queryNode};
1008
+ }
1009
+ return {
1010
+ matched: /** @type {Extract<import('./queryTree.js').QueryLeaf, {kind: K}>} */ (
1011
+ clauses[idx]
1012
+ ),
1013
+ rest: combineAnd(clauses.filter((_clause, i) => i !== idx))
1014
+ };
1015
+ }
1016
+
1017
+ /**
1018
+ * Pulls the (at most one) top-level clause of `queryNode` that
1019
+ * `nodeTouchesPath` says belongs to `path` out of the `$and` it's combined
1020
+ * into, leaving the rest re-combined - the path-based counterpart of
1021
+ * `extractLeafOfKind`, for a recursed child's own contribution (whose shape
1022
+ * isn't a single known leaf `kind`, unlike a container's own facets).
1023
+ * @param {QueryNode|undefined} queryNode
1024
+ * @param {string} path
1025
+ * @returns {{matched: QueryNode|undefined, rest: QueryNode|undefined}}
1026
+ */
1027
+ export function extractClauseForPath (queryNode, path) {
1028
+ const clauses = unwrapAndClauses(queryNode);
1029
+ const idx = clauses.findIndex((clause) => nodeTouchesPath(clause, path));
1030
+ if (idx === -1) {
1031
+ return {matched: undefined, rest: queryNode};
1032
+ }
1033
+ return {
1034
+ matched: clauses[idx],
1035
+ rest: combineAnd(clauses.filter((_clause, i) => i !== idx))
1036
+ };
1037
+ }
1038
+
1039
+ /**
1040
+ * The inverse of `readLiteralRegexQuery` - sets `buildLiteralRegexControls`'s
1041
+ * Mode/Value/Flags back from a previously-read leaf (or resets to defaults
1042
+ * for `undefined`, meaning the facet no longer has a constraint at all).
1043
+ * Dispatches `change` on the Mode select (so its own `handleModeChange`
1044
+ * shows/hides the Flags control and re-validates) and `input` on the Value
1045
+ * input (so `syncLiteralRegexValidity` re-runs), the same events a real user
1046
+ * interacting with these controls would fire.
1047
+ * @param {Element} el
1048
+ * @param {import('./queryTree.js').QueryLiteralSetLeaf|
1049
+ * import('./queryTree.js').QueryRegexLeaf|
1050
+ * import('./queryTree.js').QueryNotContainsLeaf|undefined} leaf
1051
+ * @param {string} [key]
1052
+ * @returns {void}
1053
+ */
1054
+ export function applyLiteralRegexQuery (el, leaf, key = '') {
1055
+ const modeEl = /** @type {HTMLSelectElement|undefined} */ (
1056
+ findOwnControl(el, `select.jsoeSearchMode--${key}`)
1057
+ );
1058
+ const valueEl = /** @type {HTMLInputElement|undefined} */ (
1059
+ findOwnControl(el, `input.jsoeSearchValue--${key}`)
1060
+ );
1061
+ if (!modeEl || !valueEl) {
1062
+ return;
1063
+ }
1064
+ const flagsEl = /** @type {HTMLSelectElement|undefined} */ (
1065
+ findOwnControl(el, `select.jsoeSearchRegexFlags--${key}`)
1066
+ );
1067
+ let mode = 'literal';
1068
+ let value = '';
1069
+ let flags = '';
1070
+ switch (leaf?.kind) {
1071
+ case 'regex': {
1072
+ mode = 'regex';
1073
+ ({$regex: value} = leaf);
1074
+ flags = leaf.$options ?? '';
1075
+
1076
+ break;
1077
+ }
1078
+ case 'notContains': {
1079
+ mode = 'notContains';
1080
+ ({value} = leaf);
1081
+
1082
+ break;
1083
+ }
1084
+ case 'literalSet': {
1085
+ value = (leaf.$in ?? []).map(String).join(', ');
1086
+
1087
+ break;
1088
+ }
1089
+ // No default
1090
+ }
1091
+ modeEl.value = mode;
1092
+ valueEl.value = value;
1093
+ if (flagsEl) {
1094
+ const flagChars = new Set(flags.split(''));
1095
+ [...flagsEl.options].forEach((opt) => {
1096
+ opt.selected = flagChars.has(opt.value);
1097
+ });
1098
+ }
1099
+ modeEl.dispatchEvent(new Event('change'));
1100
+ valueEl.dispatchEvent(new Event('input'));
1101
+ }
1102
+
1103
+ /**
1104
+ * The inverse of `readRangeInputsPair` - sets the From/To inputs back from a
1105
+ * previously-read `range` leaf (or clears both for `undefined`). An
1106
+ * exclusive `$gt`/`$lt` bound (never produced by any `getQuery` in this
1107
+ * codebase, but tolerated on a hand-edited raw query) is treated the same as
1108
+ * its inclusive `$gte`/`$lte` counterpart, since the UI has only one plain
1109
+ * bound per side, not a separate inclusive/exclusive toggle.
1110
+ * @param {Element} el
1111
+ * @param {import('./queryTree.js').QueryRangeLeaf|undefined} leaf
1112
+ * @param {string} [key]
1113
+ * @returns {void}
1114
+ */
1115
+ export function applyRangeQuery (el, leaf, key = '') {
1116
+ const gteEl = /** @type {HTMLInputElement|undefined} */ (
1117
+ findOwnControl(el, `input.jsoeSearchRangeGte--${key}`)
1118
+ );
1119
+ const lteEl = /** @type {HTMLInputElement|undefined} */ (
1120
+ findOwnControl(el, `input.jsoeSearchRangeLte--${key}`)
1121
+ );
1122
+ if (!gteEl || !lteEl) {
1123
+ return;
1124
+ }
1125
+ const gte = leaf?.$gte ?? leaf?.$gt;
1126
+ const lte = leaf?.$lte ?? leaf?.$lt;
1127
+ gteEl.value = gte === undefined ? '' : String(gte);
1128
+ lteEl.value = lte === undefined ? '' : String(lte);
1129
+ gteEl.dispatchEvent(new Event('input'));
1130
+ lteEl.dispatchEvent(new Event('input'));
1131
+ }
1132
+
1133
+ /**
1134
+ * The inverse of `readTriStateSelect`/`buildHasPropertyToggle` - `undefined`
1135
+ * maps back to "(any)".
1136
+ * @param {Element} el
1137
+ * @param {boolean|undefined} value
1138
+ * @param {string} [key]
1139
+ * @returns {void}
1140
+ */
1141
+ export function applyTriState (el, value, key = '') {
1142
+ const select = /** @type {HTMLSelectElement|undefined} */ (
1143
+ findOwnControl(el, `select.jsoeSearchTriState--${key}`)
1144
+ );
1145
+ if (!select) {
1146
+ return;
1147
+ }
1148
+ select.value = value === undefined ? '' : String(value);
1149
+ select.dispatchEvent(new Event('change'));
1150
+ }
1151
+
1152
+ /**
1153
+ * The inverse of `readCheckbox`.
1154
+ * @param {Element} el
1155
+ * @param {boolean} checked
1156
+ * @returns {void}
1157
+ */
1158
+ export function applyCheckbox (el, checked) {
1159
+ const checkbox = /** @type {HTMLInputElement|undefined} */ (
1160
+ findOwnControl(el, 'input.jsoeSearchCheckbox')
1161
+ );
1162
+ if (!checkbox) {
1163
+ return;
1164
+ }
1165
+ checkbox.checked = checked;
1166
+ checkbox.dispatchEvent(new Event('change'));
1167
+ }
1168
+
1169
+ /**
1170
+ * The inverse of `readMultiSelect` - `values` is compared against each
1171
+ * `<option>`'s own `value` as a string (`String(item)`), matching how
1172
+ * `enumSearchType.js`/`SpecialRealNumberSearchType.js` build their own
1173
+ * options from stringified values.
1174
+ * @param {Element} el
1175
+ * @param {unknown[]} values
1176
+ * @returns {void}
1177
+ */
1178
+ export function applyMultiSelect (el, values) {
1179
+ const select = /** @type {HTMLSelectElement|undefined} */ (
1180
+ findOwnControl(el, 'select.jsoeSearchMultiSelect')
1181
+ );
1182
+ if (!select) {
1183
+ return;
1184
+ }
1185
+ const strValues = new Set(values.map(String));
1186
+ [...select.options].forEach((opt) => {
1187
+ opt.selected = strValues.has(opt.value);
1188
+ });
1189
+ select.dispatchEvent(new Event('change'));
1190
+ }
1191
+
1192
+ /**
1193
+ * The inverse of `readLengthSizeQuery` - clears both the size input and the
1194
+ * sparse tri-state for `undefined`.
1195
+ * @param {Element} el
1196
+ * @param {import('./queryTree.js').QueryLengthSizeLeaf|undefined} leaf
1197
+ * @returns {void}
1198
+ */
1199
+ export function applyLengthSizeQuery (el, leaf) {
1200
+ const sizeEl = /** @type {HTMLInputElement|undefined} */ (
1201
+ findOwnControl(el, 'input.jsoeSearchSize')
1202
+ );
1203
+ if (sizeEl) {
1204
+ sizeEl.value = leaf?.$size === undefined ? '' : String(leaf.$size);
1205
+ sizeEl.dispatchEvent(new Event('input'));
1206
+ }
1207
+ applyTriState(el, leaf?.sparseCheck);
1208
+ }
1209
+
1210
+ /**
1211
+ * The inverse of `readOptInChecked` - also dispatches `change` so
1212
+ * `wireOptInFieldset`'s own listener toggles the paired fieldset's
1213
+ * `disabled` state and runs whatever `onToggle` it was wired with (e.g. a
1214
+ * widget's own `syncAtLeastOneCheck` re-run).
1215
+ * @param {Element} el
1216
+ * @param {boolean} checked
1217
+ * @param {string} [key]
1218
+ * @returns {void}
1219
+ */
1220
+ export function applyOptIn (el, checked, key = '') {
1221
+ const checkbox = /** @type {HTMLInputElement|undefined} */ (
1222
+ findOwnControl(el, `input.jsoeSearchOptIn--${key}`)
1223
+ );
1224
+ if (!checkbox) {
1225
+ return;
1226
+ }
1227
+ checkbox.checked = checked;
1228
+ checkbox.dispatchEvent(new Event('change'));
1229
+ }
1230
+
1231
+ /**
1232
+ * A `buildOptInFieldset`-wrapped `buildLiteralRegexControls` facet
1233
+ * (`fileSearchType.js`'s name/content-type, `domexceptionSearchType.js`'s
1234
+ * message, `makeErrorFamilySearchType`'s string properties): opts the facet
1235
+ * in exactly when `queryNode` has a clause touching `path`, and applies that
1236
+ * clause (a bare literal/regex/does-not-contain leaf, since none of these
1237
+ * facets recurse any deeper) to its Mode/Value/Flags controls.
1238
+ * @param {Element} root
1239
+ * @param {QueryNode|undefined} queryNode
1240
+ * @param {string} path
1241
+ * @param {string} [key]
1242
+ * @returns {void}
1243
+ */
1244
+ export function applyOptInLiteralRegexFacet (root, queryNode, path, key = '') {
1245
+ const {matched} = extractClauseForPath(queryNode, path);
1246
+ applyOptIn(root, matched !== undefined, key);
1247
+ applyLiteralRegexQuery(
1248
+ root,
1249
+ /**
1250
+ * @type {import('./queryTree.js').QueryLiteralSetLeaf|
1251
+ *import('./queryTree.js').QueryRegexLeaf|
1252
+ import('./queryTree.js').QueryNotContainsLeaf|undefined} */ (matched),
1253
+ key
1254
+ );
1255
+ }
1256
+
1257
+ /**
1258
+ * A `buildOptInFieldset`-wrapped `buildRangeInputsPair` facet
1259
+ * (`makeErrorFamilySearchType`'s number properties,
1260
+ * `makeDomShapeSearchType`'s dimensions): opts the facet in exactly when
1261
+ * `queryNode` has a clause touching `path`, and applies that clause (a bare
1262
+ * `range` leaf) to its From/To controls.
1263
+ * @param {Element} root
1264
+ * @param {QueryNode|undefined} queryNode
1265
+ * @param {string} path
1266
+ * @param {string} [key]
1267
+ * @returns {void}
1268
+ */
1269
+ export function applyOptInRangeFacet (root, queryNode, path, key = '') {
1270
+ const {matched} = extractClauseForPath(queryNode, path);
1271
+ applyOptIn(root, matched !== undefined, key);
1272
+ applyRangeQuery(
1273
+ root, /** @type {import('./queryTree.js').QueryRangeLeaf|undefined} */ (matched), key
1274
+ );
1275
+ }
@@ -1,8 +1,8 @@
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
6
  import regexpType from '../../fundamentalTypes/regexpType.js';
7
7
 
8
8
  /**
@@ -173,6 +173,45 @@ const blobHTMLSearchType = {
173
173
  value,
174
174
  flags || undefined
175
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')
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
+ }
176
215
  }
177
216
  }
178
217
  }, [
@@ -252,7 +291,8 @@ const blobHTMLSearchType = {
252
291
  ]]
253
292
  ]];
254
293
  },
255
- getQuery: getQueryViaElement
294
+ getQuery: getQueryViaElement,
295
+ applyQuery: applyQueryViaElement
256
296
  };
257
297
 
258
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;