@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.
- package/CHANGES.md +9 -0
- package/README.md +1 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/search/fundamentalTypes/arraySearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/bigintSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/blobSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/booleanSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/catchSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/mapSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/numberSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/promiseSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/stringSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/symbolSearchType.d.ts.map +1 -1
- package/dist/search/index.d.ts +2 -0
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/queryTree.d.ts +3 -0
- package/dist/search/queryTree.d.ts.map +1 -1
- package/dist/search/queryTreeBuilders.d.ts +2 -1
- package/dist/search/queryTreeBuilders.d.ts.map +1 -1
- package/dist/search/searchDispatch.d.ts +11 -3
- package/dist/search/searchDispatch.d.ts.map +1 -1
- package/dist/search/searchElementUtils.d.ts +31 -1
- package/dist/search/searchElementUtils.d.ts.map +1 -1
- package/dist/search/searchUtils.d.ts +197 -10
- package/dist/search/searchUtils.d.ts.map +1 -1
- package/dist/search/subTypes/blobHTMLSearchType.d.ts +11 -4
- package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -1
- package/dist/search/subTypes/recordSearchType.d.ts.map +1 -1
- package/dist/search/subTypes/tupleSearchType.d.ts.map +1 -1
- package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts.map +1 -1
- package/dist/search/superTypes/buffersourceSearchType.d.ts.map +1 -1
- package/dist/search/unions/unionFamilySearchType.d.ts.map +1 -1
- package/dist/utils/json6Stringify.d.ts +2 -0
- package/dist/utils/json6Stringify.d.ts.map +1 -0
- package/dist/utils/rawTypesonEditor.d.ts.map +1 -1
- package/package.json +3 -3
- package/pnpm-workspace.yaml +26 -26
- package/src/search/fundamentalTypes/arraySearchType.js +28 -5
- package/src/search/fundamentalTypes/bigintSearchType.js +14 -3
- package/src/search/fundamentalTypes/blobSearchType.js +21 -3
- package/src/search/fundamentalTypes/booleanSearchType.js +15 -3
- package/src/search/fundamentalTypes/catchSearchType.js +20 -3
- package/src/search/fundamentalTypes/dateSearchType.js +42 -3
- package/src/search/fundamentalTypes/domexceptionSearchType.js +18 -4
- package/src/search/fundamentalTypes/enumSearchType.js +15 -3
- package/src/search/fundamentalTypes/fileSearchType.js +15 -3
- package/src/search/fundamentalTypes/filelistSearchType.js +25 -5
- package/src/search/fundamentalTypes/functionSearchType.js +31 -4
- package/src/search/fundamentalTypes/mapSearchType.js +33 -5
- package/src/search/fundamentalTypes/numberSearchType.js +15 -3
- package/src/search/fundamentalTypes/objectSearchType.js +94 -5
- package/src/search/fundamentalTypes/promiseSearchType.js +20 -3
- package/src/search/fundamentalTypes/regexpSearchType.js +24 -4
- package/src/search/fundamentalTypes/setSearchType.js +25 -5
- package/src/search/fundamentalTypes/stringSearchType.js +25 -3
- package/src/search/fundamentalTypes/symbolSearchType.js +21 -3
- package/src/search/index.js +22 -0
- package/src/search/queryTree.js +4 -1
- package/src/search/queryTreeBuilders.js +3 -2
- package/src/search/searchDispatch.js +14 -5
- package/src/search/searchElementUtils.js +98 -10
- package/src/search/searchUtils.js +440 -12
- package/src/search/subTypes/blobHTMLSearchType.js +186 -10
- package/src/search/subTypes/recordSearchType.js +30 -4
- package/src/search/subTypes/tupleSearchType.js +43 -5
- package/src/search/superTypes/SpecialRealNumberSearchType.js +15 -3
- package/src/search/superTypes/buffersourceSearchType.js +14 -3
- package/src/search/unions/unionFamilySearchType.js +45 -3
- package/src/utils/json6Stringify.js +50 -0
- package/src/utils/rawTypesonEditor.js +1 -51
- package/vendor/zod/classic/schemas.d.ts +3 -1
- package/vendor/zod/core/versions.js +1 -1
|
@@ -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
|
|
@@ -453,16 +454,23 @@ export function readCheckbox (el) {
|
|
|
453
454
|
* "Matches regex" is the chosen mode.
|
|
454
455
|
*
|
|
455
456
|
* `flagOptions`, when given, adds a Flags multi-select (options passed in
|
|
456
|
-
* by the caller -
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
*
|
|
463
|
-
* (
|
|
464
|
-
*
|
|
465
|
-
*
|
|
457
|
+
* by the caller - most callers pass `regexpType.js`'s own `allowedFlags`,
|
|
458
|
+
* the same list `regexpSearchType.js` uses for the actual regexp's own
|
|
459
|
+
* flags - kept out of this generic module to avoid it depending on a
|
|
460
|
+
* specific fundamental type), shown only while "Matches regex" is the
|
|
461
|
+
* chosen mode, the same `$options` a Mongo-flavored `$regex` accepts
|
|
462
|
+
* alongside it - a plain literal/substring match has no regex to apply
|
|
463
|
+
* flags to, so it stays hidden otherwise. `regexpSearchType.js`'s own call
|
|
464
|
+
* (matching against the regexp's `.source` text, not to be confused with
|
|
465
|
+
* its separate, bespoke Flags control for the regexp's *actual* flags)
|
|
466
|
+
* leaves this unset, since a flag there would have no real regex of its own
|
|
467
|
+
* to apply to.
|
|
468
|
+
*
|
|
469
|
+
* While "Matches regex" is chosen, the Value input is also live syntax-
|
|
470
|
+
* checked (`syncLiteralRegexValidity`, below) against
|
|
471
|
+
* `new RegExp(value, flags)` - an unparsable pattern (or one only invalid
|
|
472
|
+
* for the currently-selected flags, e.g. `u`/`v`'s stricter escape rules)
|
|
473
|
+
* sets a custom validity message rather than silently accepting it.
|
|
466
474
|
* @param {{
|
|
467
475
|
* name: string, key?: string, onModeChange?: (this: HTMLElement) => void,
|
|
468
476
|
* flagOptions?: string[]
|
|
@@ -470,6 +478,46 @@ export function readCheckbox (el) {
|
|
|
470
478
|
* @returns {JamilihArray}
|
|
471
479
|
*/
|
|
472
480
|
export function buildLiteralRegexControls ({name, key = '', onModeChange, flagOptions}) {
|
|
481
|
+
/**
|
|
482
|
+
* Live syntax-checks the Value input against `new RegExp(value, flags)`
|
|
483
|
+
* whenever the current Mode is "Matches regex" - a literal/does-not-
|
|
484
|
+
* contain Value is a plain string with no format to violate, so this only
|
|
485
|
+
* has anything to say once "regex" is chosen, and clears back to valid
|
|
486
|
+
* the moment it isn't. Flags are folded in (read fresh off the Flags
|
|
487
|
+
* multi-select, when this call has one) because they can themselves flip
|
|
488
|
+
* a pattern between valid and invalid - the `u`/`v` flags' stricter escape
|
|
489
|
+
* rules being the main example - so a Flags `change` needs to re-run this
|
|
490
|
+
* exactly like a Value `input` or a Mode `change` does.
|
|
491
|
+
* @param {HTMLElement} el - any one of the Mode/Value/Flags controls
|
|
492
|
+
* @returns {void}
|
|
493
|
+
*/
|
|
494
|
+
function syncLiteralRegexValidity (el) {
|
|
495
|
+
const root = el.closest('[data-search-path]');
|
|
496
|
+
const modeEl = /** @type {HTMLSelectElement|undefined} */ (
|
|
497
|
+
root ? findOwnControl(root, `select.jsoeSearchMode--${key}`) : undefined
|
|
498
|
+
);
|
|
499
|
+
const valueEl = /** @type {HTMLInputElement|undefined} */ (
|
|
500
|
+
root ? findOwnControl(root, `input.jsoeSearchValue--${key}`) : undefined
|
|
501
|
+
);
|
|
502
|
+
if (!modeEl || !valueEl) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
if (modeEl.value !== 'regex' || !valueEl.value) {
|
|
506
|
+
valueEl.setCustomValidity('');
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const flagsEl = /** @type {HTMLSelectElement|undefined} */ (
|
|
510
|
+
root ? findOwnControl(root, `select.jsoeSearchRegexFlags--${key}`) : undefined
|
|
511
|
+
);
|
|
512
|
+
const flags = [...(flagsEl?.selectedOptions ?? [])].map((opt) => opt.value).join('');
|
|
513
|
+
try {
|
|
514
|
+
// eslint-disable-next-line no-new -- Testing
|
|
515
|
+
new RegExp(valueEl.value, flags);
|
|
516
|
+
valueEl.setCustomValidity('');
|
|
517
|
+
} catch {
|
|
518
|
+
valueEl.setCustomValidity('Enter a valid regular expression.');
|
|
519
|
+
}
|
|
520
|
+
}
|
|
473
521
|
/**
|
|
474
522
|
* @this {HTMLElement}
|
|
475
523
|
* @returns {void}
|
|
@@ -482,6 +530,7 @@ export function buildLiteralRegexControls ({name, key = '', onModeChange, flagOp
|
|
|
482
530
|
/** @type {HTMLElement} */ (flagsLabel).hidden =
|
|
483
531
|
/** @type {HTMLSelectElement} */ (this).value !== 'regex';
|
|
484
532
|
}
|
|
533
|
+
syncLiteralRegexValidity(this);
|
|
485
534
|
onModeChange?.call(this);
|
|
486
535
|
}
|
|
487
536
|
/** @type {JamilihArray[]} */
|
|
@@ -490,7 +539,13 @@ export function buildLiteralRegexControls ({name, key = '', onModeChange, flagOp
|
|
|
490
539
|
flagsChildren.push(['label', {class: `jsoeSearchRegexFlagsLabel--${key}`, hidden: true}, [
|
|
491
540
|
'Flags: ',
|
|
492
541
|
['select', {
|
|
493
|
-
name: `${name}-flags`, multiple: true, class: `jsoeSearchRegexFlags--${key}
|
|
542
|
+
name: `${name}-flags`, multiple: true, class: `jsoeSearchRegexFlags--${key}`,
|
|
543
|
+
$on: {
|
|
544
|
+
/** @this {HTMLElement} */
|
|
545
|
+
change () {
|
|
546
|
+
syncLiteralRegexValidity(this);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
494
549
|
}, flagOptions.map((flag) => ['option', {value: flag}, [flag]])]
|
|
495
550
|
]]);
|
|
496
551
|
}
|
|
@@ -510,7 +565,13 @@ export function buildLiteralRegexControls ({name, key = '', onModeChange, flagOp
|
|
|
510
565
|
'Value: ',
|
|
511
566
|
['input', {
|
|
512
567
|
type: 'text', name: `${name}-value`, class: `jsoeSearchValue--${key}`,
|
|
513
|
-
required: true
|
|
568
|
+
required: true,
|
|
569
|
+
$on: {
|
|
570
|
+
/** @this {HTMLElement} */
|
|
571
|
+
input () {
|
|
572
|
+
syncLiteralRegexValidity(this);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
514
575
|
}]
|
|
515
576
|
]],
|
|
516
577
|
...flagsChildren
|
|
@@ -845,3 +906,370 @@ export function setDescendantsRequired (root, required) {
|
|
|
845
906
|
).required = required;
|
|
846
907
|
});
|
|
847
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
|
+
}
|