@es-joy/jsoe 0.29.0 → 0.31.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/.claude/scheduled_tasks.lock +1 -0
- package/CHANGES.md +11 -0
- package/README.md +3 -0
- package/dist/fundamentalTypes/dateType.d.ts +8 -3
- package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/search/fundamentalTypes/arraySearchType.d.ts +11 -4
- 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/dateSearchType.d.ts +8 -3
- package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts +5 -5
- package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/enumSearchType.d.ts +4 -0
- package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/fileSearchType.d.ts +19 -10
- package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/filelistSearchType.d.ts +4 -12
- package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/functionSearchType.d.ts +4 -4
- package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/mapSearchType.d.ts +8 -4
- 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 +10 -4
- package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/regexpSearchType.d.ts +7 -1
- package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/setSearchType.d.ts +5 -4
- package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -1
- package/dist/search/fundamentalTypes/stringSearchType.d.ts +9 -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 +18 -8
- package/dist/search/index.d.ts.map +1 -1
- package/dist/search/queryTree.d.ts +15 -1
- package/dist/search/queryTree.d.ts.map +1 -1
- package/dist/search/queryTreeBuilders.d.ts +8 -1
- package/dist/search/queryTreeBuilders.d.ts.map +1 -1
- package/dist/search/searchElementUtils.d.ts +17 -8
- package/dist/search/searchElementUtils.d.ts.map +1 -1
- package/dist/search/searchUtils.d.ts +350 -18
- package/dist/search/searchUtils.d.ts.map +1 -1
- package/dist/search/subTypes/blobHTMLSearchType.d.ts +32 -5
- package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -1
- package/dist/search/subTypes/recordSearchType.d.ts +4 -4
- package/dist/search/subTypes/recordSearchType.d.ts.map +1 -1
- package/dist/search/subTypes/tupleSearchType.d.ts +3 -3
- package/dist/search/subTypes/tupleSearchType.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/package.json +3 -3
- package/pnpm-workspace.yaml +26 -26
- package/src/fundamentalTypes/dateType.js +9 -4
- package/src/fundamentalTypes/regexpType.js +1 -1
- package/src/jsoe.css +204 -0
- package/src/search/fundamentalTypes/arraySearchType.js +42 -7
- package/src/search/fundamentalTypes/bigintSearchType.js +7 -1
- package/src/search/fundamentalTypes/blobSearchType.js +2 -1
- package/src/search/fundamentalTypes/booleanSearchType.js +1 -1
- package/src/search/fundamentalTypes/dateSearchType.js +113 -20
- package/src/search/fundamentalTypes/domexceptionSearchType.js +39 -10
- package/src/search/fundamentalTypes/enumSearchType.js +5 -1
- package/src/search/fundamentalTypes/fileSearchType.js +64 -11
- package/src/search/fundamentalTypes/filelistSearchType.js +32 -7
- package/src/search/fundamentalTypes/functionSearchType.js +36 -12
- package/src/search/fundamentalTypes/mapSearchType.js +43 -13
- package/src/search/fundamentalTypes/numberSearchType.js +8 -1
- package/src/search/fundamentalTypes/objectSearchType.js +236 -17
- package/src/search/fundamentalTypes/regexpSearchType.js +29 -5
- package/src/search/fundamentalTypes/setSearchType.js +33 -7
- package/src/search/fundamentalTypes/stringSearchType.js +11 -2
- package/src/search/fundamentalTypes/symbolSearchType.js +2 -1
- package/src/search/index.js +26 -9
- package/src/search/queryTree.js +14 -2
- package/src/search/queryTreeBuilders.js +12 -2
- package/src/search/searchElementUtils.js +108 -24
- package/src/search/searchUtils.js +576 -24
- package/src/search/subTypes/blobHTMLSearchType.js +202 -9
- package/src/search/subTypes/recordSearchType.js +34 -13
- package/src/search/subTypes/tupleSearchType.js +51 -10
- package/src/search/superTypes/SpecialRealNumberSearchType.js +1 -1
- package/src/search/superTypes/buffersourceSearchType.js +7 -1
- package/src/search/unions/unionFamilySearchType.js +8 -0
- package/vendor/zod/classic/schemas.d.ts +3 -1
- package/vendor/zod/core/versions.js +1 -1
|
@@ -1,18 +1,142 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildPathLabel, findOwnControl, isExemptedByAncestorHasProperty
|
|
3
|
+
} from '../searchUtils.js';
|
|
2
4
|
import {makeBlobHTMLLeaf} from '../queryTreeBuilders.js';
|
|
3
5
|
import {getQueryViaElement} from '../searchElementUtils.js';
|
|
6
|
+
import regexpType from '../../fundamentalTypes/regexpType.js';
|
|
4
7
|
|
|
5
8
|
/**
|
|
6
9
|
* @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
|
|
7
10
|
*/
|
|
8
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
|
+
|
|
9
103
|
/**
|
|
10
104
|
* Blob HTML: XPath, CSS selectors, full text search, regex search of raw
|
|
11
|
-
* HTML (README) - a mode `<select>` plus one
|
|
105
|
+
* HTML (README) - a mode `<select>` plus one value control, the same
|
|
12
106
|
* "pick a mode, give it one value" shape as `buildLiteralRegexControls`
|
|
13
107
|
* (`searchUtils.js`), but not built through it: the four modes and the
|
|
14
108
|
* `blobHTML` leaf kind they produce are specific to this one type, with no
|
|
15
|
-
* second caller to share the helper with.
|
|
109
|
+
* second caller to share the helper with. The value control is a single-line
|
|
110
|
+
* `<input>` for XPath/CSS-selector/regex, but swaps to a `<textarea>` for
|
|
111
|
+
* "Full text search" mode alone, since a search phrase in a body of text is
|
|
112
|
+
* the one mode actually expected to run to more than one line - both share
|
|
113
|
+
* the same class, so the mode `<select>`'s `change` handler (and `getQuery`)
|
|
114
|
+
* only need the tag name to tell them apart. Only the currently-visible one
|
|
115
|
+
* of the pair is ever `required` (toggled alongside `hidden` by the mode
|
|
116
|
+
* `change` handler): a `required` field that's merely hidden, rather than
|
|
117
|
+
* also un-required, still blocks the form's validity even though the user
|
|
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.
|
|
120
|
+
*
|
|
121
|
+
* The `change` handler also checks `isExemptedByAncestorHasProperty` before
|
|
122
|
+
* (re-)asserting `required` on the now-visible control: nested as an
|
|
123
|
+
* optional object property's own child widget, an enclosing "Has property"
|
|
124
|
+
* already provides a complete constraint on its own (the same relief
|
|
125
|
+
* `setDescendantsRequired` gives other `required`-attribute controls), but
|
|
126
|
+
* that relief is a one-time toggle applied *before* `revalidateDescendants`
|
|
127
|
+
* re-dispatches a synthetic `change` on this very `<select>` - without this
|
|
128
|
+
* check, that synthetic event would immediately reassert `required` on
|
|
129
|
+
* whichever control the current mode makes visible, undoing the exemption
|
|
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.
|
|
16
140
|
* @type {SearchTypeObject}
|
|
17
141
|
*/
|
|
18
142
|
const blobHTMLSearchType = {
|
|
@@ -29,16 +153,25 @@ const blobHTMLSearchType = {
|
|
|
29
153
|
const mode = /** @type {HTMLSelectElement|undefined} */ (
|
|
30
154
|
findOwnControl(this, 'select.jsoeSearchBlobHTMLMode')
|
|
31
155
|
)?.value;
|
|
32
|
-
const value = /** @type {HTMLInputElement|undefined} */ (
|
|
33
|
-
findOwnControl(
|
|
156
|
+
const value = /** @type {HTMLInputElement|HTMLTextAreaElement|undefined} */ (
|
|
157
|
+
findOwnControl(
|
|
158
|
+
this,
|
|
159
|
+
`${mode === 'fullText' ? 'textarea' : 'input'}.jsoeSearchBlobHTMLValue`
|
|
160
|
+
)
|
|
34
161
|
)?.value;
|
|
35
162
|
if (!value || !mode) {
|
|
36
163
|
return undefined;
|
|
37
164
|
}
|
|
165
|
+
const flags = mode === 'rawHTMLRegex'
|
|
166
|
+
? [...(/** @type {HTMLSelectElement|undefined} */ (
|
|
167
|
+
findOwnControl(this, 'select.jsoeSearchBlobHTMLFlags')
|
|
168
|
+
)?.selectedOptions ?? [])].map((opt) => opt.value).join('')
|
|
169
|
+
: '';
|
|
38
170
|
return makeBlobHTMLLeaf(
|
|
39
171
|
searchPath,
|
|
40
172
|
/** @type {import('../queryTree.js').QueryBlobHTMLLeaf['mode']} */ (mode),
|
|
41
|
-
value
|
|
173
|
+
value,
|
|
174
|
+
flags || undefined
|
|
42
175
|
);
|
|
43
176
|
}
|
|
44
177
|
}
|
|
@@ -46,16 +179,76 @@ const blobHTMLSearchType = {
|
|
|
46
179
|
['span', {class: 'searchLabel'}, [label]],
|
|
47
180
|
['label', [
|
|
48
181
|
'Mode: ',
|
|
49
|
-
['select', {
|
|
50
|
-
|
|
182
|
+
['select', {
|
|
183
|
+
name: `${name}-mode`,
|
|
184
|
+
class: 'jsoeSearchBlobHTMLMode',
|
|
185
|
+
$on: {
|
|
186
|
+
change () {
|
|
187
|
+
const container = this.closest('jsoe-search-blob-html');
|
|
188
|
+
const input = /** @type {HTMLInputElement|null|undefined} */ (
|
|
189
|
+
container?.querySelector('input.jsoeSearchBlobHTMLValue')
|
|
190
|
+
);
|
|
191
|
+
const textarea = /** @type {HTMLTextAreaElement|null|undefined} */ (
|
|
192
|
+
container?.querySelector('textarea.jsoeSearchBlobHTMLValue')
|
|
193
|
+
);
|
|
194
|
+
const flagsLabel = /** @type {HTMLElement|null|undefined} */ (
|
|
195
|
+
container?.querySelector('.jsoeSearchBlobHTMLFlagsLabel')
|
|
196
|
+
);
|
|
197
|
+
const mode = /** @type {HTMLSelectElement} */ (this).value;
|
|
198
|
+
const isFullText = mode === 'fullText';
|
|
199
|
+
const exempted = container !== null &&
|
|
200
|
+
isExemptedByAncestorHasProperty(container);
|
|
201
|
+
if (input) {
|
|
202
|
+
input.hidden = isFullText;
|
|
203
|
+
input.required = !isFullText && !exempted;
|
|
204
|
+
}
|
|
205
|
+
if (textarea) {
|
|
206
|
+
textarea.hidden = !isFullText;
|
|
207
|
+
textarea.required = isFullText && !exempted;
|
|
208
|
+
}
|
|
209
|
+
if (flagsLabel) {
|
|
210
|
+
flagsLabel.hidden = mode !== 'rawHTMLRegex';
|
|
211
|
+
}
|
|
212
|
+
syncBlobHTMLValueValidity(container);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}, [
|
|
51
216
|
['option', {value: 'cssSelector'}, ['CSS selector']],
|
|
217
|
+
['option', {value: 'xpath'}, ['XPath']],
|
|
52
218
|
['option', {value: 'fullText'}, ['Full text search']],
|
|
53
219
|
['option', {value: 'rawHTMLRegex'}, ['Regex search of raw HTML']]
|
|
54
220
|
]]
|
|
55
221
|
]],
|
|
56
222
|
['label', [
|
|
57
223
|
'Value: ',
|
|
58
|
-
['input', {
|
|
224
|
+
['input', {
|
|
225
|
+
type: 'text', name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue',
|
|
226
|
+
required: true,
|
|
227
|
+
$on: {
|
|
228
|
+
input () {
|
|
229
|
+
syncBlobHTMLValueValidity(this.closest('jsoe-search-blob-html'));
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}],
|
|
233
|
+
['textarea', {
|
|
234
|
+
name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue', hidden: true,
|
|
235
|
+
$on: {
|
|
236
|
+
input () {
|
|
237
|
+
syncBlobHTMLValueValidity(this.closest('jsoe-search-blob-html'));
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}]
|
|
241
|
+
]],
|
|
242
|
+
['label', {class: 'jsoeSearchBlobHTMLFlagsLabel', hidden: true}, [
|
|
243
|
+
'Flags: ',
|
|
244
|
+
['select', {
|
|
245
|
+
name: `${name}-flags`, multiple: true, class: 'jsoeSearchBlobHTMLFlags',
|
|
246
|
+
$on: {
|
|
247
|
+
change () {
|
|
248
|
+
syncBlobHTMLValueValidity(this.closest('jsoe-search-blob-html'));
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}, regexpType.allowedFlags.map((flag) => ['option', {value: flag}, [flag]])]
|
|
59
252
|
]]
|
|
60
253
|
]];
|
|
61
254
|
},
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildPathLabel, buildCheckbox, readCheckbox, buildOptInFieldset,
|
|
3
|
+
readOptInChecked, wireOptInFieldset, buildAtLeastOneSentinel, syncAtLeastOneCheck
|
|
4
|
+
} from '../searchUtils.js';
|
|
2
5
|
import {makeMapRecordJointLeaf} from '../queryTreeBuilders.js';
|
|
3
6
|
import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
|
|
4
7
|
import {buildSearchWidget} from '../searchDispatch.js';
|
|
@@ -7,6 +10,16 @@ import {buildSearchWidget} from '../searchDispatch.js';
|
|
|
7
10
|
* @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
|
|
8
11
|
*/
|
|
9
12
|
|
|
13
|
+
/**
|
|
14
|
+
* @param {Element} root - a `jsoe-search-record` element
|
|
15
|
+
* @returns {void}
|
|
16
|
+
*/
|
|
17
|
+
function syncKeyValueValidity (root) {
|
|
18
|
+
syncAtLeastOneCheck(
|
|
19
|
+
root, () => readOptInChecked(root, 'key') || readOptInChecked(root, 'value')
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
10
23
|
/**
|
|
11
24
|
* Record search is "key-schema search AND/OR value-schema search,"
|
|
12
25
|
* structurally unlike object's additive "has property" (search plan §1) -
|
|
@@ -15,7 +28,10 @@ import {buildSearchWidget} from '../searchDispatch.js';
|
|
|
15
28
|
* whether a match must come from the *same* entry ("has key 2-4 and value
|
|
16
29
|
* 7-9", README) via the "joint" toggle on the resulting `mapRecordJoint`
|
|
17
30
|
* leaf - also used for `looseRecord`, aliased to this same module in
|
|
18
|
-
* `searchDispatch.js` since the search semantics are identical.
|
|
31
|
+
* `searchDispatch.js` since the search semantics are identical. Key and
|
|
32
|
+
* value each get their own `buildOptInFieldset` (see `mapSearchType.js`'s
|
|
33
|
+
* matching doc for why), but leaving *both* unopted-in is itself invalid via
|
|
34
|
+
* `buildAtLeastOneSentinel`.
|
|
19
35
|
* @type {SearchTypeObject}
|
|
20
36
|
*/
|
|
21
37
|
const recordSearchType = {
|
|
@@ -47,15 +63,21 @@ const recordSearchType = {
|
|
|
47
63
|
dataset: {searchPath: path, searchKind: 'record'},
|
|
48
64
|
title: label,
|
|
49
65
|
$define: {
|
|
66
|
+
/** @this {HTMLElement} */
|
|
67
|
+
connectedCallback () {
|
|
68
|
+
wireOptInFieldset(this, 'key', () => syncKeyValueValidity(this));
|
|
69
|
+
wireOptInFieldset(this, 'value', () => syncKeyValueValidity(this));
|
|
70
|
+
syncKeyValueValidity(this);
|
|
71
|
+
},
|
|
50
72
|
/** @this {HTMLElement} */
|
|
51
73
|
getQuery () {
|
|
52
74
|
const searchPath = this.dataset.searchPath ?? '';
|
|
53
75
|
const keyEl = findSearchElement(this, `${searchPath}/*key`);
|
|
54
76
|
const valueEl = findSearchElement(this, `${searchPath}/*value`);
|
|
55
|
-
const keyQuery = keyEl && hasGetQuery(keyEl)
|
|
77
|
+
const keyQuery = keyEl && hasGetQuery(keyEl) && readOptInChecked(this, 'key')
|
|
56
78
|
? keyEl.getQuery()
|
|
57
79
|
: undefined;
|
|
58
|
-
const valueQuery = valueEl && hasGetQuery(valueEl)
|
|
80
|
+
const valueQuery = valueEl && hasGetQuery(valueEl) && readOptInChecked(this, 'value')
|
|
59
81
|
? valueEl.getQuery()
|
|
60
82
|
: undefined;
|
|
61
83
|
if (!keyQuery && !valueQuery) {
|
|
@@ -67,15 +89,14 @@ const recordSearchType = {
|
|
|
67
89
|
}
|
|
68
90
|
}, [
|
|
69
91
|
['span', {class: 'searchLabel'}, [label]],
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
buildCheckbox({name: `${name}-joint`, label: 'Require same entry'})
|
|
92
|
+
...buildOptInFieldset({
|
|
93
|
+
name: `${name}-key`, key: 'key', label: 'Key matches', children: [keyArr]
|
|
94
|
+
}),
|
|
95
|
+
...buildOptInFieldset({
|
|
96
|
+
name: `${name}-value`, key: 'value', label: 'Value matches', children: [valueArr]
|
|
97
|
+
}),
|
|
98
|
+
buildCheckbox({name: `${name}-joint`, label: 'Require same entry'}),
|
|
99
|
+
buildAtLeastOneSentinel()
|
|
79
100
|
]];
|
|
80
101
|
},
|
|
81
102
|
getQuery: getQueryViaElement
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildPathLabel, buildLengthSizeControls, readLengthSizeQuery,
|
|
3
|
+
buildOptInFieldset, readOptInChecked, wireOptInFieldset,
|
|
4
|
+
buildAtLeastOneSentinel, syncAtLeastOneCheck
|
|
5
|
+
} from '../searchUtils.js';
|
|
2
6
|
import {combineAnd} from '../queryTreeBuilders.js';
|
|
3
7
|
import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
|
|
4
8
|
import {buildSearchWidget} from '../searchDispatch.js';
|
|
@@ -7,6 +11,21 @@ import {buildSearchWidget} from '../searchDispatch.js';
|
|
|
7
11
|
* @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
|
|
8
12
|
*/
|
|
9
13
|
|
|
14
|
+
/**
|
|
15
|
+
* @param {Element} root - a `jsoe-search-tuple` element
|
|
16
|
+
* @returns {void}
|
|
17
|
+
*/
|
|
18
|
+
function syncTupleValidity (root) {
|
|
19
|
+
const itemCount = Number(/** @type {HTMLElement} */ (root).dataset.itemCount ?? '0');
|
|
20
|
+
const hasRest = /** @type {HTMLElement} */ (root).dataset.hasRest === 'true';
|
|
21
|
+
syncAtLeastOneCheck(root, () => (
|
|
22
|
+
Array.from({length: itemCount}, (_, idx) => String(idx)).some(
|
|
23
|
+
(key) => readOptInChecked(root, key)
|
|
24
|
+
) ||
|
|
25
|
+
(hasRest && (readOptInChecked(root, 'rest') || readLengthSizeQuery(root, '') !== undefined))
|
|
26
|
+
));
|
|
27
|
+
}
|
|
28
|
+
|
|
10
29
|
/**
|
|
11
30
|
* Each fixed position gets its own control from `.items[i]`, not one
|
|
12
31
|
* control repeated per element (search plan §1 - unlike the value-editing
|
|
@@ -19,6 +38,9 @@ import {buildSearchWidget} from '../searchDispatch.js';
|
|
|
19
38
|
* items.length`, no sparse toggle - not applicable to a tuple), and the
|
|
20
39
|
* `rest` element itself recurses into its own search widget under the same
|
|
21
40
|
* `*` path-segment convention `arraySearchType.js` uses for its element.
|
|
41
|
+
* Leaving every position, the rest element, and (when present) the length
|
|
42
|
+
* unconfigured at once is invalid (`buildAtLeastOneSentinel`), the same as
|
|
43
|
+
* `arraySearchType.js`/`setSearchType.js`/`filelistSearchType.js`.
|
|
22
44
|
* @type {SearchTypeObject}
|
|
23
45
|
*/
|
|
24
46
|
const tupleSearchType = {
|
|
@@ -54,22 +76,24 @@ const tupleSearchType = {
|
|
|
54
76
|
/** @type {import('../../types.js').JamilihArray[]} */
|
|
55
77
|
const children = [['span', {class: 'searchLabel'}, [label]]];
|
|
56
78
|
itemArrs.forEach((itemArr, idx) => {
|
|
57
|
-
children.push(
|
|
58
|
-
|
|
59
|
-
itemArr
|
|
60
|
-
|
|
79
|
+
children.push(...buildOptInFieldset({
|
|
80
|
+
name: `${name}-${idx}`, key: String(idx), label: `Position ${idx} matches`,
|
|
81
|
+
children: [itemArr]
|
|
82
|
+
}));
|
|
61
83
|
});
|
|
62
84
|
if (rest) {
|
|
85
|
+
const restChild = /** @type {import('../../types.js').JamilihArray} */ (restArr);
|
|
63
86
|
children.push(
|
|
64
87
|
...buildLengthSizeControls({
|
|
65
88
|
name, min: items.length, max: undefined, includeSparse: false
|
|
66
89
|
}),
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
90
|
+
...buildOptInFieldset({
|
|
91
|
+
name: `${name}-rest`, key: 'rest', label: 'Rest element matches',
|
|
92
|
+
children: [restChild]
|
|
93
|
+
})
|
|
71
94
|
);
|
|
72
95
|
}
|
|
96
|
+
children.push(buildAtLeastOneSentinel());
|
|
73
97
|
|
|
74
98
|
return ['jsoe-search-tuple', {
|
|
75
99
|
dataset: {
|
|
@@ -84,11 +108,28 @@ const tupleSearchType = {
|
|
|
84
108
|
// defined, so a second tuple widget on the same page would
|
|
85
109
|
// otherwise silently reuse the first tuple's item count/rest-ness.
|
|
86
110
|
/** @this {HTMLElement} */
|
|
111
|
+
connectedCallback () {
|
|
112
|
+
const itemCount = Number(this.dataset.itemCount ?? '0');
|
|
113
|
+
Array.from({length: itemCount}, (_, idx) => String(idx)).forEach(
|
|
114
|
+
(key) => wireOptInFieldset(this, key, () => syncTupleValidity(this))
|
|
115
|
+
);
|
|
116
|
+
if (this.dataset.hasRest === 'true') {
|
|
117
|
+
wireOptInFieldset(this, 'rest', () => syncTupleValidity(this));
|
|
118
|
+
this.querySelector('input.jsoeSearchSize')?.addEventListener(
|
|
119
|
+
'input', () => syncTupleValidity(this)
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
syncTupleValidity(this);
|
|
123
|
+
},
|
|
124
|
+
/** @this {HTMLElement} */
|
|
87
125
|
getQuery () {
|
|
88
126
|
const searchPath = this.dataset.searchPath ?? '';
|
|
89
127
|
const itemCount = Number(this.dataset.itemCount ?? '0');
|
|
90
128
|
const hasRest = this.dataset.hasRest === 'true';
|
|
91
129
|
const itemLeaves = Array.from({length: itemCount}, (_, idx) => {
|
|
130
|
+
if (!readOptInChecked(this, String(idx))) {
|
|
131
|
+
return undefined;
|
|
132
|
+
}
|
|
92
133
|
const itemEl = findSearchElement(this, `${searchPath}/${idx}`);
|
|
93
134
|
return itemEl && hasGetQuery(itemEl) ? itemEl.getQuery() : undefined;
|
|
94
135
|
});
|
|
@@ -97,7 +138,7 @@ const tupleSearchType = {
|
|
|
97
138
|
}
|
|
98
139
|
const lengthLeaf = readLengthSizeQuery(this, searchPath);
|
|
99
140
|
const restEl = findSearchElement(this, `${searchPath}/*`);
|
|
100
|
-
const restLeaf = restEl && hasGetQuery(restEl)
|
|
141
|
+
const restLeaf = restEl && hasGetQuery(restEl) && readOptInChecked(this, 'rest')
|
|
101
142
|
? restEl.getQuery()
|
|
102
143
|
: undefined;
|
|
103
144
|
return combineAnd([...itemLeaves, lengthLeaf, restLeaf]);
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildPathLabel, buildRangeInputsPair, readRangeInputsPair, syncRangeValidity
|
|
3
|
+
} from '../searchUtils.js';
|
|
2
4
|
import {makeRangeLeaf} from '../queryTreeBuilders.js';
|
|
3
5
|
import {getQueryViaElement} from '../searchElementUtils.js';
|
|
4
6
|
|
|
@@ -24,6 +26,10 @@ const buffersourceSearchType = {
|
|
|
24
26
|
dataset: {searchPath: path, searchKind: 'buffersource'},
|
|
25
27
|
title: label,
|
|
26
28
|
$define: {
|
|
29
|
+
/** @this {HTMLElement} */
|
|
30
|
+
connectedCallback () {
|
|
31
|
+
syncRangeValidity(this);
|
|
32
|
+
},
|
|
27
33
|
/** @this {HTMLElement} */
|
|
28
34
|
getQuery () {
|
|
29
35
|
const {gte, lte} = readRangeInputsPair(this);
|
|
@@ -110,6 +110,14 @@ export function makeUnionFamilySearchType ({tagName, discriminated}) {
|
|
|
110
110
|
['select', {
|
|
111
111
|
name,
|
|
112
112
|
class: 'jsoeSearchTypeOf',
|
|
113
|
+
// The "(any)" placeholder's `value: ''` below is exactly what
|
|
114
|
+
// native `required` treats as "nothing selected", so choosing
|
|
115
|
+
// any real branch already satisfies it - no custom validity
|
|
116
|
+
// code needed. A union/xor/discriminatedUnion has no other
|
|
117
|
+
// possible constraint (unlike `mapSearchType.js`'s key/value or
|
|
118
|
+
// `objectSearchType.js`'s properties), so this alone is "no
|
|
119
|
+
// absent values" for the whole widget, not just one facet of it.
|
|
120
|
+
required: true,
|
|
113
121
|
$on: {
|
|
114
122
|
change () {
|
|
115
123
|
if (!isSelectElement(this)) {
|
|
@@ -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
|
|
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>>;
|