@es-joy/jsoe 0.28.2 → 0.29.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 (163) hide show
  1. package/CHANGES.md +19 -0
  2. package/README.md +2 -0
  3. package/dist/formatAndTypeChoices.d.ts +6 -6
  4. package/dist/formatAndTypeChoices.d.ts.map +1 -1
  5. package/dist/formats/schema.d.ts +5 -0
  6. package/dist/formats/schema.d.ts.map +1 -1
  7. package/dist/formats.d.ts +2 -2
  8. package/dist/formats.d.ts.map +1 -1
  9. package/dist/fundamentalTypes/dateType.d.ts +21 -2
  10. package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
  11. package/dist/fundamentalTypes/domexceptionType.d.ts +7 -0
  12. package/dist/fundamentalTypes/domexceptionType.d.ts.map +1 -1
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -2
  16. package/dist/index.js.map +1 -1
  17. package/dist/jtltExtensions.d.ts +112 -0
  18. package/dist/jtltExtensions.d.ts.map +1 -0
  19. package/dist/search/fundamentalTypes/arraySearchType.d.ts +15 -0
  20. package/dist/search/fundamentalTypes/arraySearchType.d.ts.map +1 -0
  21. package/dist/search/fundamentalTypes/bigintSearchType.d.ts +10 -0
  22. package/dist/search/fundamentalTypes/bigintSearchType.d.ts.map +1 -0
  23. package/dist/search/fundamentalTypes/blobSearchType.d.ts +19 -0
  24. package/dist/search/fundamentalTypes/blobSearchType.d.ts.map +1 -0
  25. package/dist/search/fundamentalTypes/booleanSearchType.d.ts +11 -0
  26. package/dist/search/fundamentalTypes/booleanSearchType.d.ts.map +1 -0
  27. package/dist/search/fundamentalTypes/catchSearchType.d.ts +18 -0
  28. package/dist/search/fundamentalTypes/catchSearchType.d.ts.map +1 -0
  29. package/dist/search/fundamentalTypes/dateSearchType.d.ts +10 -0
  30. package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -0
  31. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts +16 -0
  32. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -0
  33. package/dist/search/fundamentalTypes/enumSearchType.d.ts +22 -0
  34. package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -0
  35. package/dist/search/fundamentalTypes/errorSearchType.d.ts +9 -0
  36. package/dist/search/fundamentalTypes/errorSearchType.d.ts.map +1 -0
  37. package/dist/search/fundamentalTypes/fileSearchType.d.ts +19 -0
  38. package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -0
  39. package/dist/search/fundamentalTypes/filelistSearchType.d.ts +32 -0
  40. package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -0
  41. package/dist/search/fundamentalTypes/functionSearchType.d.ts +18 -0
  42. package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -0
  43. package/dist/search/fundamentalTypes/mapSearchType.d.ts +18 -0
  44. package/dist/search/fundamentalTypes/mapSearchType.d.ts.map +1 -0
  45. package/dist/search/fundamentalTypes/nanSearchType.d.ts +7 -0
  46. package/dist/search/fundamentalTypes/nanSearchType.d.ts.map +1 -0
  47. package/dist/search/fundamentalTypes/nullSearchType.d.ts +7 -0
  48. package/dist/search/fundamentalTypes/nullSearchType.d.ts.map +1 -0
  49. package/dist/search/fundamentalTypes/numberSearchType.d.ts +11 -0
  50. package/dist/search/fundamentalTypes/numberSearchType.d.ts.map +1 -0
  51. package/dist/search/fundamentalTypes/objectSearchType.d.ts +15 -0
  52. package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -0
  53. package/dist/search/fundamentalTypes/promiseSearchType.d.ts +20 -0
  54. package/dist/search/fundamentalTypes/promiseSearchType.d.ts.map +1 -0
  55. package/dist/search/fundamentalTypes/regexpSearchType.d.ts +14 -0
  56. package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -0
  57. package/dist/search/fundamentalTypes/setSearchType.d.ts +14 -0
  58. package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -0
  59. package/dist/search/fundamentalTypes/stringSearchType.d.ts +13 -0
  60. package/dist/search/fundamentalTypes/stringSearchType.d.ts.map +1 -0
  61. package/dist/search/fundamentalTypes/symbolSearchType.d.ts +16 -0
  62. package/dist/search/fundamentalTypes/symbolSearchType.d.ts.map +1 -0
  63. package/dist/search/fundamentalTypes/undefinedSearchType.d.ts +7 -0
  64. package/dist/search/fundamentalTypes/undefinedSearchType.d.ts.map +1 -0
  65. package/dist/search/index.d.ts +60 -0
  66. package/dist/search/index.d.ts.map +1 -0
  67. package/dist/search/queryTree.d.ts.map +1 -1
  68. package/dist/search/queryTreeBuilders.d.ts +12 -0
  69. package/dist/search/queryTreeBuilders.d.ts.map +1 -1
  70. package/dist/search/searchDispatch.d.ts +44 -0
  71. package/dist/search/searchDispatch.d.ts.map +1 -1
  72. package/dist/search/searchElementUtils.d.ts +97 -0
  73. package/dist/search/searchElementUtils.d.ts.map +1 -0
  74. package/dist/search/searchUtils.d.ts +225 -0
  75. package/dist/search/searchUtils.d.ts.map +1 -0
  76. package/dist/search/subTypes/blobHTMLSearchType.d.ts +16 -0
  77. package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -0
  78. package/dist/search/subTypes/recordSearchType.d.ts +18 -0
  79. package/dist/search/subTypes/recordSearchType.d.ts.map +1 -0
  80. package/dist/search/subTypes/tupleSearchType.d.ts +21 -0
  81. package/dist/search/subTypes/tupleSearchType.d.ts.map +1 -0
  82. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts +14 -0
  83. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts.map +1 -0
  84. package/dist/search/superTypes/buffersourceSearchType.d.ts +17 -0
  85. package/dist/search/superTypes/buffersourceSearchType.d.ts.map +1 -0
  86. package/dist/search/superTypes/dommatrixSearchType.d.ts +11 -0
  87. package/dist/search/superTypes/dommatrixSearchType.d.ts.map +1 -0
  88. package/dist/search/superTypes/dompointSearchType.d.ts +9 -0
  89. package/dist/search/superTypes/dompointSearchType.d.ts.map +1 -0
  90. package/dist/search/superTypes/domrectSearchType.d.ts +9 -0
  91. package/dist/search/superTypes/domrectSearchType.d.ts.map +1 -0
  92. package/dist/search/superTypes/errorsSpecialSearchType.d.ts +11 -0
  93. package/dist/search/superTypes/errorsSpecialSearchType.d.ts.map +1 -0
  94. package/dist/search/unions/discriminatedUnionSearchType.d.ts +12 -0
  95. package/dist/search/unions/discriminatedUnionSearchType.d.ts.map +1 -0
  96. package/dist/search/unions/unionFamilySearchType.d.ts +36 -0
  97. package/dist/search/unions/unionFamilySearchType.d.ts.map +1 -0
  98. package/dist/search/unions/unionSearchType.d.ts +8 -0
  99. package/dist/search/unions/unionSearchType.d.ts.map +1 -0
  100. package/dist/search/unions/xorSearchType.d.ts +12 -0
  101. package/dist/search/unions/xorSearchType.d.ts.map +1 -0
  102. package/dist/superTypes/buffersourceType.d.ts.map +1 -1
  103. package/dist/typeChoices.d.ts +3 -3
  104. package/dist/typeChoices.d.ts.map +1 -1
  105. package/dist/types.d.ts +3 -3
  106. package/dist/types.d.ts.map +1 -1
  107. package/package.json +5 -4
  108. package/pnpm-workspace.yaml +1 -1
  109. package/rollup.config.js +12 -0
  110. package/src/formatAndTypeChoices.js +4 -4
  111. package/src/formats/schema.js +1 -1
  112. package/src/formats.js +1 -1
  113. package/src/fundamentalTypes/dateType.js +35 -14
  114. package/src/fundamentalTypes/domexceptionType.js +41 -33
  115. package/src/index.js +6 -0
  116. package/src/jtltExtensions.js +103 -0
  117. package/src/search/fundamentalTypes/arraySearchType.js +66 -0
  118. package/src/search/fundamentalTypes/bigintSearchType.js +62 -0
  119. package/src/search/fundamentalTypes/blobSearchType.js +41 -0
  120. package/src/search/fundamentalTypes/booleanSearchType.js +40 -0
  121. package/src/search/fundamentalTypes/catchSearchType.js +59 -0
  122. package/src/search/fundamentalTypes/dateSearchType.js +73 -0
  123. package/src/search/fundamentalTypes/domexceptionSearchType.js +55 -0
  124. package/src/search/fundamentalTypes/enumSearchType.js +58 -0
  125. package/src/search/fundamentalTypes/errorSearchType.js +11 -0
  126. package/src/search/fundamentalTypes/fileSearchType.js +41 -0
  127. package/src/search/fundamentalTypes/filelistSearchType.js +78 -0
  128. package/src/search/fundamentalTypes/functionSearchType.js +78 -0
  129. package/src/search/fundamentalTypes/mapSearchType.js +87 -0
  130. package/src/search/fundamentalTypes/nanSearchType.js +11 -0
  131. package/src/search/fundamentalTypes/nullSearchType.js +11 -0
  132. package/src/search/fundamentalTypes/numberSearchType.js +52 -0
  133. package/src/search/fundamentalTypes/objectSearchType.js +148 -0
  134. package/src/search/fundamentalTypes/promiseSearchType.js +61 -0
  135. package/src/search/fundamentalTypes/regexpSearchType.js +48 -0
  136. package/src/search/fundamentalTypes/setSearchType.js +65 -0
  137. package/src/search/fundamentalTypes/stringSearchType.js +35 -0
  138. package/src/search/fundamentalTypes/symbolSearchType.js +38 -0
  139. package/src/search/fundamentalTypes/undefinedSearchType.js +11 -0
  140. package/src/search/index.js +79 -0
  141. package/src/search/queryTree.js +235 -0
  142. package/src/search/queryTreeBuilders.js +224 -0
  143. package/src/search/searchDispatch.js +291 -0
  144. package/src/search/searchElementUtils.js +289 -0
  145. package/src/search/searchUtils.js +355 -0
  146. package/src/search/subTypes/blobHTMLSearchType.js +65 -0
  147. package/src/search/subTypes/recordSearchType.js +84 -0
  148. package/src/search/subTypes/tupleSearchType.js +111 -0
  149. package/src/search/superTypes/SpecialRealNumberSearchType.js +42 -0
  150. package/src/search/superTypes/buffersourceSearchType.js +47 -0
  151. package/src/search/superTypes/dommatrixSearchType.js +24 -0
  152. package/src/search/superTypes/dompointSearchType.js +15 -0
  153. package/src/search/superTypes/domrectSearchType.js +15 -0
  154. package/src/search/superTypes/errorsSpecialSearchType.js +15 -0
  155. package/src/search/unions/discriminatedUnionSearchType.js +16 -0
  156. package/src/search/unions/unionFamilySearchType.js +160 -0
  157. package/src/search/unions/unionSearchType.js +12 -0
  158. package/src/search/unions/xorSearchType.js +16 -0
  159. package/src/superTypes/buffersourceType.js +13 -15
  160. package/src/typeChoices.js +2 -2
  161. package/src/types.js +2 -2
  162. package/tsconfig-prod.json +12 -0
  163. package/tsconfig.json +5 -10
@@ -0,0 +1,355 @@
1
+ import {schemaLabel} from '../utils/schemaMeta.js';
2
+ import {getJSONPointerParts} from '../utils/jsonPointer.js';
3
+
4
+ /**
5
+ * @typedef {import('../types.js').JamilihArray} JamilihArray
6
+ */
7
+
8
+ /**
9
+ * Label for a leaf/container's own control, preferring the schema's own
10
+ * `meta.title`/`meta.description`/`description` (via `schemaLabel`, same
11
+ * precedence the value-editing side uses) and falling back to the JSON
12
+ * Pointer path's final segment so every control still has a legible label
13
+ * even when the schema carries no metadata.
14
+ * @param {import('../formats/schema.js').ZodexSchema|undefined} schemaObject
15
+ * @param {string} path
16
+ * @returns {string}
17
+ */
18
+ export function buildPathLabel (schemaObject, path) {
19
+ const label = schemaLabel(schemaObject);
20
+ if (label) {
21
+ return label;
22
+ }
23
+ const parts = getJSONPointerParts(path);
24
+ return parts.length ? String(parts.at(-1)) : path;
25
+ }
26
+
27
+ /**
28
+ * Finds a control belonging directly to `root`'s own widget - as opposed to
29
+ * one belonging to a search widget recursively nested inside it (e.g.
30
+ * `arraySearchType.js`'s own "size" input vs. a nested array-of-arrays'
31
+ * element widget's own "size" input, both reachable from `root` via a plain
32
+ * `querySelectorAll`). A match only counts if the nearest ancestor of the
33
+ * match carrying `data-search-path` is `root` itself; every search element
34
+ * carries that attribute (`searchElementUtils.js`'s `findSearchElement`), so
35
+ * this reliably stops at the first nested search element's boundary.
36
+ *
37
+ * This is also why every `build*`/`read*` pair below reads its class-based
38
+ * selector back through this helper rather than a raw
39
+ * `root.querySelector(...)`: a fixed class name is safe to reuse across
40
+ * every instance of a given control on the page precisely because lookups
41
+ * are always scoped this way, and - unlike a `name` built from a
42
+ * `typeNamespace` closed over inside a `$define` mixin, which is installed
43
+ * once on the shared custom-element prototype the first time a tag is
44
+ * defined - nothing here depends on a per-instance closure at all.
45
+ * @param {HTMLElement} root
46
+ * @param {string} selector
47
+ * @returns {HTMLElement|undefined}
48
+ */
49
+ export function findOwnControl (root, selector) {
50
+ return /** @type {HTMLElement[]} */ (
51
+ [...root.querySelectorAll(selector)]
52
+ ).find((el) => el.closest('[data-search-path]') === root);
53
+ }
54
+
55
+ /**
56
+ * A "from"/"to" pair of native inputs sharing one `type`, for the OR-range/
57
+ * Is-Not-Range README affordance (number/bigint/buffersource; `date` uses
58
+ * `dateType.js`'s own `buildDateInputControl` instead, since a
59
+ * `datetime-local` input needs its own ISO-slicing).
60
+ *
61
+ * `key` distinguishes multiple range pairs *within one widget* (e.g.
62
+ * `domrectSearchType.js`'s `x`/`y`/`width`/`height` dimensions, or
63
+ * `errorSearchType.js`'s `lineNumber`/`columnNumber`): `findOwnControl`
64
+ * already isolates one widget's controls from another's, but can't tell
65
+ * apart two same-class controls belonging to the *same* widget, so those
66
+ * need distinct classes. Leave it at the default `''` for the common case
67
+ * of a widget with only one range pair.
68
+ * @param {{
69
+ * name: string,
70
+ * key?: string,
71
+ * type?: string,
72
+ * min?: string|number,
73
+ * max?: string|number,
74
+ * step?: string|number
75
+ * }} cfg
76
+ * @returns {JamilihArray[]}
77
+ */
78
+ export function buildRangeInputsPair ({
79
+ name, key = '', type = 'number', min, max, step
80
+ }) {
81
+ return [
82
+ ['label', [
83
+ 'From: ',
84
+ ['input', {name: `${name}-gte`, class: `jsoeSearchRangeGte--${key}`, type, min, max, step}]
85
+ ]],
86
+ ['label', [
87
+ 'To: ',
88
+ ['input', {name: `${name}-lte`, class: `jsoeSearchRangeLte--${key}`, type, min, max, step}]
89
+ ]]
90
+ ];
91
+ }
92
+
93
+ /**
94
+ * Reads back a `buildRangeInputsPair` - pass the same `key` it was built
95
+ * with.
96
+ * @param {HTMLElement} el
97
+ * @param {string} [key]
98
+ * @returns {{gte: string, lte: string}}
99
+ */
100
+ export function readRangeInputsPair (el, key = '') {
101
+ const gte = /** @type {HTMLInputElement|undefined} */ (
102
+ findOwnControl(el, `input.jsoeSearchRangeGte--${key}`)
103
+ )?.value ?? '';
104
+ const lte = /** @type {HTMLInputElement|undefined} */ (
105
+ findOwnControl(el, `input.jsoeSearchRangeLte--${key}`)
106
+ )?.value ?? '';
107
+ return {gte, lte};
108
+ }
109
+
110
+ /**
111
+ * A `<select multiple>` populated from a fixed candidate list - used by
112
+ * `enum`/`multiSelect`/`literalSet` leaf controls (README: "multiple
113
+ * select").
114
+ * @param {{
115
+ * name: string,
116
+ * options: (string|[value: string, title: string])[]
117
+ * }} cfg
118
+ * @returns {JamilihArray}
119
+ */
120
+ export function buildMultiSelect ({name, options}) {
121
+ return ['select', {name, multiple: true, class: 'jsoeSearchMultiSelect'}, options.map((opt) => {
122
+ const [value, title] = Array.isArray(opt) ? opt : [opt, opt];
123
+ return ['option', {value}, [title]];
124
+ })];
125
+ }
126
+
127
+ /**
128
+ * Reads back a `buildMultiSelect` into the selected values, `[]` if none.
129
+ * @param {HTMLElement} el
130
+ * @returns {string[]}
131
+ */
132
+ export function readMultiSelect (el) {
133
+ const select = /** @type {HTMLSelectElement|undefined} */ (
134
+ findOwnControl(el, 'select.jsoeSearchMultiSelect')
135
+ );
136
+ return [...(select?.selectedOptions ?? [])].map((opt) => opt.value);
137
+ }
138
+
139
+ /**
140
+ * A tri-state "has property" control for one already-chosen property name
141
+ * (README: "Has property &lt;property pull-down&gt;", "avoid listing
142
+ * required"). `objectSearchType.js` supplies the additive pull-down that
143
+ * lets a user pick *which* property to add one of these for; this helper is
144
+ * only the fixed per-property toggle it adds each time, so the "any" state
145
+ * (no constraint entered) is distinguishable from an explicit "has"/
146
+ * "doesn't have" - matching `SearchTypeObject.getQuery`'s
147
+ * `undefined`-means-"no constraint" convention. Shares `readTriStateSelect`
148
+ * with `buildTriStateSelect` below - same three-value shape, just different
149
+ * option labels.
150
+ * @param {{name: string, propertyName: string}} cfg
151
+ * @returns {JamilihArray}
152
+ */
153
+ export function buildHasPropertyToggle ({name, propertyName}) {
154
+ return ['label', [
155
+ `Has property "${propertyName}": `,
156
+ ['select', {name, class: 'jsoeSearchTriState--'}, [
157
+ ['option', {value: ''}, ['(any)']],
158
+ ['option', {value: 'true'}, ['Has']],
159
+ ['option', {value: 'false'}, ['Doesn’t have']]
160
+ ]]
161
+ ]];
162
+ }
163
+
164
+ /**
165
+ * A generic "(any)"/true/false `<select>`, shared by every leaf whose only
166
+ * constraint is a plain boolean choice - `booleanSearchType.js`'s "true or
167
+ * false" (README) and `numberSearchType.js`'s "Is/Is Not Integer" both read
168
+ * back through this same three-way convention rather than each rolling
169
+ * their own.
170
+ *
171
+ * `key` distinguishes multiple tri-states *within one widget* (e.g.
172
+ * `dommatrixSearchType.js`'s "Is/Is not Readonly" and "Is/Is not 3d" side
173
+ * by side); see `buildRangeInputsPair`'s doc for why. Leave it at the
174
+ * default `''` for the common case of a widget with only one tri-state.
175
+ * @param {{name: string, key?: string, trueLabel: string, falseLabel: string}} cfg
176
+ * @returns {JamilihArray}
177
+ */
178
+ export function buildTriStateSelect ({name, key = '', trueLabel, falseLabel}) {
179
+ return ['select', {name, class: `jsoeSearchTriState--${key}`}, [
180
+ ['option', {value: ''}, ['(any)']],
181
+ ['option', {value: 'true'}, [trueLabel]],
182
+ ['option', {value: 'false'}, [falseLabel]]
183
+ ]];
184
+ }
185
+
186
+ /**
187
+ * Reads back a `buildTriStateSelect`/`buildHasPropertyToggle` - pass the
188
+ * same `key` it was built with. `''` (any) maps to `undefined`.
189
+ * @param {HTMLElement} el
190
+ * @param {string} [key]
191
+ * @returns {boolean|undefined}
192
+ */
193
+ export function readTriStateSelect (el, key = '') {
194
+ const select = /** @type {HTMLSelectElement|undefined} */ (
195
+ findOwnControl(el, `select.jsoeSearchTriState--${key}`)
196
+ );
197
+ if (!select || select.value === '') {
198
+ return undefined;
199
+ }
200
+ return select.value === 'true';
201
+ }
202
+
203
+ /**
204
+ * A single checkbox - the only search affordance the README grants
205
+ * `undefined`/`void`/`null`/`NaN` ("Require present"; they have "no
206
+ * variants to allow for distinct search", since existence only becomes a
207
+ * meaningful question once the path is optional or nested in a union), and
208
+ * also used by `recordSearchType.js` for its "require same entry" toggle.
209
+ * @param {{name: string, label: string}} cfg
210
+ * @returns {JamilihArray}
211
+ */
212
+ export function buildCheckbox ({name, label}) {
213
+ return ['label', [
214
+ `${label}: `,
215
+ ['input', {type: 'checkbox', name, class: 'jsoeSearchCheckbox'}]
216
+ ]];
217
+ }
218
+
219
+ /**
220
+ * Reads back a `buildCheckbox`.
221
+ * @param {HTMLElement} el
222
+ * @returns {boolean}
223
+ */
224
+ export function readCheckbox (el) {
225
+ return Boolean(/** @type {HTMLInputElement|undefined} */ (
226
+ findOwnControl(el, 'input.jsoeSearchCheckbox')
227
+ )?.checked);
228
+ }
229
+
230
+ /**
231
+ * The mode selector for the README's "string, StringObject, Blob, File,
232
+ * regexp (source), symbol (description): OR literal or regex search/Does
233
+ * Not contain search" - one shared control (and reader, below) that
234
+ * `stringSearchType.js`, `symbolSearchType.js`, and `regexpSearchType.js`
235
+ * (for its source) each build their own custom element around, since the
236
+ * query semantics are identical and only the label/target facet differs.
237
+ *
238
+ * `key` distinguishes multiple literal/regex controls *within one widget*
239
+ * (e.g. `errorSearchType.js`'s `message`/`name`/`fileName`/`stack`); see
240
+ * `buildRangeInputsPair`'s doc for why. Leave it at the default `''` for
241
+ * the common case of a widget with only one such control.
242
+ * @param {{name: string, key?: string}} cfg
243
+ * @returns {JamilihArray}
244
+ */
245
+ export function buildLiteralRegexControls ({name, key = ''}) {
246
+ return ['span', [
247
+ ['label', [
248
+ 'Mode: ',
249
+ ['select', {name: `${name}-mode`, class: `jsoeSearchMode--${key}`}, [
250
+ ['option', {value: 'literal'}, ['One of (comma-separated)']],
251
+ ['option', {value: 'regex'}, ['Matches regex']],
252
+ ['option', {value: 'notContains'}, ['Does not contain']]
253
+ ]]
254
+ ]],
255
+ ['label', [
256
+ 'Value: ',
257
+ ['input', {type: 'text', name: `${name}-value`, class: `jsoeSearchValue--${key}`}]
258
+ ]]
259
+ ]];
260
+ }
261
+
262
+ /**
263
+ * Reads back `buildLiteralRegexControls` into the corresponding
264
+ * `literalSet`/`regex`/`notContains` leaf - pass the same `key` it was
265
+ * built with.
266
+ * @param {HTMLElement} el
267
+ * @param {string} path
268
+ * @param {string} [key]
269
+ * @returns {import('./queryTree.js').QueryLiteralSetLeaf|
270
+ * import('./queryTree.js').QueryRegexLeaf|
271
+ * import('./queryTree.js').QueryNotContainsLeaf|undefined}
272
+ */
273
+ export function readLiteralRegexQuery (el, path, key = '') {
274
+ const mode = /** @type {HTMLSelectElement|undefined} */ (
275
+ findOwnControl(el, `select.jsoeSearchMode--${key}`)
276
+ )?.value;
277
+ const value = /** @type {HTMLInputElement|undefined} */ (
278
+ findOwnControl(el, `input.jsoeSearchValue--${key}`)
279
+ )?.value;
280
+ if (!value) {
281
+ return undefined;
282
+ }
283
+ if (mode === 'regex') {
284
+ return {kind: 'regex', path, $regex: value};
285
+ }
286
+ if (mode === 'notContains') {
287
+ return {kind: 'notContains', path, value};
288
+ }
289
+ return {
290
+ kind: 'literalSet',
291
+ path,
292
+ $in: value.split(',').map((v) => v.trim()).filter(Boolean)
293
+ };
294
+ }
295
+
296
+ /**
297
+ * Has length/size of &lt;number&gt; (README; array/set/tuple-with-rest/
298
+ * filelist), + "Is/Is not sparse" for arrays only. Returns `[]` (no
299
+ * control) when the schema pins an exact length/size, since searching on a
300
+ * constant is uninteresting - the schema's `min`/`max` otherwise become the
301
+ * input's HTML `min`/`max` attributes.
302
+ * @param {{
303
+ * name: string,
304
+ * min?: number,
305
+ * max?: number,
306
+ * includeSparse?: boolean
307
+ * }} cfg
308
+ * @returns {JamilihArray[]}
309
+ */
310
+ export function buildLengthSizeControls ({name, min, max, includeSparse}) {
311
+ const fixed = min !== undefined && min === max;
312
+ /** @type {JamilihArray[]} */
313
+ const controls = [];
314
+ if (!fixed) {
315
+ controls.push(['label', [
316
+ 'Has length/size of: ',
317
+ ['input', {
318
+ type: 'number', name: `${name}-size`, class: 'jsoeSearchSize',
319
+ min, max, step: 1
320
+ }]
321
+ ]]);
322
+ }
323
+ if (includeSparse) {
324
+ controls.push(['label', [
325
+ 'Sparse: ',
326
+ buildTriStateSelect({
327
+ name: `${name}-sparse`, trueLabel: 'Sparse', falseLabel: 'Not sparse'
328
+ })
329
+ ]]);
330
+ }
331
+ return controls;
332
+ }
333
+
334
+ /**
335
+ * Reads back `buildLengthSizeControls` into one `lengthSize` leaf, or
336
+ * `undefined` if neither the size nor the sparse control was set.
337
+ * @param {HTMLElement} el
338
+ * @param {string} path
339
+ * @returns {import('./queryTree.js').QueryLengthSizeLeaf|undefined}
340
+ */
341
+ export function readLengthSizeQuery (el, path) {
342
+ const sizeStr = /** @type {HTMLInputElement|undefined} */ (
343
+ findOwnControl(el, 'input.jsoeSearchSize')
344
+ )?.value;
345
+ const sparseCheck = readTriStateSelect(el);
346
+ if (!sizeStr && sparseCheck === undefined) {
347
+ return undefined;
348
+ }
349
+ return {
350
+ kind: 'lengthSize',
351
+ path,
352
+ ...(sizeStr ? {$size: Number(sizeStr)} : {}),
353
+ ...(sparseCheck === undefined ? {} : {sparseCheck})
354
+ };
355
+ }
@@ -0,0 +1,65 @@
1
+ import {buildPathLabel, findOwnControl} from '../searchUtils.js';
2
+ import {makeBlobHTMLLeaf} from '../queryTreeBuilders.js';
3
+ import {getQueryViaElement} from '../searchElementUtils.js';
4
+
5
+ /**
6
+ * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
7
+ */
8
+
9
+ /**
10
+ * Blob HTML: XPath, CSS selectors, full text search, regex search of raw
11
+ * HTML (README) - a mode `<select>` plus one text `<input>`, the same
12
+ * "pick a mode, give it one value" shape as `buildLiteralRegexControls`
13
+ * (`searchUtils.js`), but not built through it: the four modes and the
14
+ * `blobHTML` leaf kind they produce are specific to this one type, with no
15
+ * second caller to share the helper with.
16
+ * @type {SearchTypeObject}
17
+ */
18
+ const blobHTMLSearchType = {
19
+ buildUI ({schemaObject, path, typeNamespace}) {
20
+ const label = buildPathLabel(schemaObject, path);
21
+ const name = `${typeNamespace}-blobHTML`;
22
+ return ['jsoe-search-blob-html', {
23
+ dataset: {searchPath: path, searchKind: 'blobHTML'},
24
+ title: label,
25
+ $define: {
26
+ /** @this {HTMLElement} */
27
+ getQuery () {
28
+ const searchPath = this.dataset.searchPath ?? '';
29
+ const mode = /** @type {HTMLSelectElement|undefined} */ (
30
+ findOwnControl(this, 'select.jsoeSearchBlobHTMLMode')
31
+ )?.value;
32
+ const value = /** @type {HTMLInputElement|undefined} */ (
33
+ findOwnControl(this, 'input.jsoeSearchBlobHTMLValue')
34
+ )?.value;
35
+ if (!value || !mode) {
36
+ return undefined;
37
+ }
38
+ return makeBlobHTMLLeaf(
39
+ searchPath,
40
+ /** @type {import('../queryTree.js').QueryBlobHTMLLeaf['mode']} */ (mode),
41
+ value
42
+ );
43
+ }
44
+ }
45
+ }, [
46
+ ['span', {class: 'searchLabel'}, [label]],
47
+ ['label', [
48
+ 'Mode: ',
49
+ ['select', {name: `${name}-mode`, class: 'jsoeSearchBlobHTMLMode'}, [
50
+ ['option', {value: 'xpath'}, ['XPath']],
51
+ ['option', {value: 'cssSelector'}, ['CSS selector']],
52
+ ['option', {value: 'fullText'}, ['Full text search']],
53
+ ['option', {value: 'rawHTMLRegex'}, ['Regex search of raw HTML']]
54
+ ]]
55
+ ]],
56
+ ['label', [
57
+ 'Value: ',
58
+ ['input', {type: 'text', name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue'}]
59
+ ]]
60
+ ]];
61
+ },
62
+ getQuery: getQueryViaElement
63
+ };
64
+
65
+ export default blobHTMLSearchType;
@@ -0,0 +1,84 @@
1
+ import {buildPathLabel, buildCheckbox, readCheckbox} from '../searchUtils.js';
2
+ import {makeMapRecordJointLeaf} from '../queryTreeBuilders.js';
3
+ import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
4
+ import {buildSearchWidget} from '../searchDispatch.js';
5
+
6
+ /**
7
+ * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
8
+ */
9
+
10
+ /**
11
+ * Record search is "key-schema search AND/OR value-schema search,"
12
+ * structurally unlike object's additive "has property" (search plan §1) -
13
+ * recurses into both the `key` and `value` schemas' own search widgets
14
+ * (`*key`/`*value` path segments, `queryTree.js`), and lets the user say
15
+ * whether a match must come from the *same* entry ("has key 2-4 and value
16
+ * 7-9", README) via the "joint" toggle on the resulting `mapRecordJoint`
17
+ * leaf - also used for `looseRecord`, aliased to this same module in
18
+ * `searchDispatch.js` since the search semantics are identical.
19
+ * @type {SearchTypeObject}
20
+ */
21
+ const recordSearchType = {
22
+ buildUI ({schemaObject, path, typeNamespace, topRoot, types, originalJSON}) {
23
+ const label = buildPathLabel(schemaObject, path);
24
+ const name = `${typeNamespace}-record`;
25
+ const recordSchemaObject = /** @type {import('zodexy').SzRecord} */ (
26
+ schemaObject
27
+ );
28
+ const keyPath = `${path}/*key`;
29
+ const valuePath = `${path}/*value`;
30
+ const keyArr = buildSearchWidget({
31
+ schemaObject: recordSchemaObject.key,
32
+ path: keyPath,
33
+ typeNamespace,
34
+ topRoot,
35
+ types,
36
+ originalJSON
37
+ });
38
+ const valueArr = buildSearchWidget({
39
+ schemaObject: recordSchemaObject.value,
40
+ path: valuePath,
41
+ typeNamespace,
42
+ topRoot,
43
+ types,
44
+ originalJSON
45
+ });
46
+ return ['jsoe-search-record', {
47
+ dataset: {searchPath: path, searchKind: 'record'},
48
+ title: label,
49
+ $define: {
50
+ /** @this {HTMLElement} */
51
+ getQuery () {
52
+ const searchPath = this.dataset.searchPath ?? '';
53
+ const keyEl = findSearchElement(this, `${searchPath}/*key`);
54
+ const valueEl = findSearchElement(this, `${searchPath}/*value`);
55
+ const keyQuery = keyEl && hasGetQuery(keyEl)
56
+ ? keyEl.getQuery()
57
+ : undefined;
58
+ const valueQuery = valueEl && hasGetQuery(valueEl)
59
+ ? valueEl.getQuery()
60
+ : undefined;
61
+ if (!keyQuery && !valueQuery) {
62
+ return undefined;
63
+ }
64
+ const joint = readCheckbox(this);
65
+ return makeMapRecordJointLeaf(searchPath, joint, keyQuery, valueQuery);
66
+ }
67
+ }
68
+ }, [
69
+ ['span', {class: 'searchLabel'}, [label]],
70
+ ['div', {class: 'searchRecordKey'}, [
71
+ ['span', ['Key matches: ']],
72
+ keyArr
73
+ ]],
74
+ ['div', {class: 'searchRecordValue'}, [
75
+ ['span', ['Value matches: ']],
76
+ valueArr
77
+ ]],
78
+ buildCheckbox({name: `${name}-joint`, label: 'Require same entry'})
79
+ ]];
80
+ },
81
+ getQuery: getQueryViaElement
82
+ };
83
+
84
+ export default recordSearchType;
@@ -0,0 +1,111 @@
1
+ import {buildPathLabel, buildLengthSizeControls, readLengthSizeQuery} from '../searchUtils.js';
2
+ import {combineAnd} from '../queryTreeBuilders.js';
3
+ import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
4
+ import {buildSearchWidget} from '../searchDispatch.js';
5
+
6
+ /**
7
+ * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
8
+ */
9
+
10
+ /**
11
+ * Each fixed position gets its own control from `.items[i]`, not one
12
+ * control repeated per element (search plan §1 - unlike the value-editing
13
+ * side, this gets a dedicated module rather than delegating to
14
+ * `arraySearchType.js`, since positions aren't interchangeable). A tuple
15
+ * with no `rest` has an inherently fixed length (`items.length`), so - as
16
+ * with a fixed-length array - no length control is shown for it; "with rest
17
+ * has size search as with arrays" (README) applies once `rest` is present:
18
+ * total length becomes a variable-length `lengthSize` control (`min:
19
+ * items.length`, no sparse toggle - not applicable to a tuple), and the
20
+ * `rest` element itself recurses into its own search widget under the same
21
+ * `*` path-segment convention `arraySearchType.js` uses for its element.
22
+ * @type {SearchTypeObject}
23
+ */
24
+ const tupleSearchType = {
25
+ buildUI ({schemaObject, path, typeNamespace, topRoot, types, originalJSON}) {
26
+ const label = buildPathLabel(schemaObject, path);
27
+ const name = `${typeNamespace}-tuple`;
28
+ const tupleSchemaObject = /** @type {import('zodexy').SzTuple} */ (
29
+ schemaObject
30
+ );
31
+ const {items, rest} = tupleSchemaObject;
32
+
33
+ const itemArrs = items.map((itemSchema, idx) => buildSearchWidget({
34
+ schemaObject: itemSchema,
35
+ path: `${path}/${idx}`,
36
+ typeNamespace,
37
+ topRoot,
38
+ types,
39
+ originalJSON
40
+ }));
41
+
42
+ const restElementPath = `${path}/*`;
43
+ const restArr = rest
44
+ ? buildSearchWidget({
45
+ schemaObject: rest,
46
+ path: restElementPath,
47
+ typeNamespace,
48
+ topRoot,
49
+ types,
50
+ originalJSON
51
+ })
52
+ : undefined;
53
+
54
+ /** @type {import('../../types.js').JamilihArray[]} */
55
+ const children = [['span', {class: 'searchLabel'}, [label]]];
56
+ itemArrs.forEach((itemArr, idx) => {
57
+ children.push(['div', {class: 'searchTupleItem'}, [
58
+ ['span', [`Position ${idx}: `]],
59
+ itemArr
60
+ ]]);
61
+ });
62
+ if (rest) {
63
+ children.push(
64
+ ...buildLengthSizeControls({
65
+ name, min: items.length, max: undefined, includeSparse: false
66
+ }),
67
+ ['div', {class: 'searchTupleRest'}, [
68
+ ['span', ['Rest element matches: ']],
69
+ /** @type {import('../../types.js').JamilihArray} */ (restArr)
70
+ ]]
71
+ );
72
+ }
73
+
74
+ return ['jsoe-search-tuple', {
75
+ dataset: {
76
+ searchPath: path, searchKind: 'tuple',
77
+ itemCount: String(items.length), hasRest: rest ? 'true' : ''
78
+ },
79
+ title: label,
80
+ $define: {
81
+ // Reads `itemCount`/`hasRest` off `this.dataset` rather than
82
+ // closing over `items`/`rest`: `$define`'s mixin is installed
83
+ // once on the shared prototype the first time this tag is
84
+ // defined, so a second tuple widget on the same page would
85
+ // otherwise silently reuse the first tuple's item count/rest-ness.
86
+ /** @this {HTMLElement} */
87
+ getQuery () {
88
+ const searchPath = this.dataset.searchPath ?? '';
89
+ const itemCount = Number(this.dataset.itemCount ?? '0');
90
+ const hasRest = this.dataset.hasRest === 'true';
91
+ const itemLeaves = Array.from({length: itemCount}, (_, idx) => {
92
+ const itemEl = findSearchElement(this, `${searchPath}/${idx}`);
93
+ return itemEl && hasGetQuery(itemEl) ? itemEl.getQuery() : undefined;
94
+ });
95
+ if (!hasRest) {
96
+ return combineAnd(itemLeaves);
97
+ }
98
+ const lengthLeaf = readLengthSizeQuery(this, searchPath);
99
+ const restEl = findSearchElement(this, `${searchPath}/*`);
100
+ const restLeaf = restEl && hasGetQuery(restEl)
101
+ ? restEl.getQuery()
102
+ : undefined;
103
+ return combineAnd([...itemLeaves, lengthLeaf, restLeaf]);
104
+ }
105
+ }
106
+ }, children];
107
+ },
108
+ getQuery: getQueryViaElement
109
+ };
110
+
111
+ export default tupleSearchType;
@@ -0,0 +1,42 @@
1
+ import {buildPathLabel, buildMultiSelect, readMultiSelect} from '../searchUtils.js';
2
+ import {makeMultiSelectLeaf} from '../queryTreeBuilders.js';
3
+ import {getQueryViaElement} from '../searchElementUtils.js';
4
+
5
+ /**
6
+ * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
7
+ */
8
+
9
+ /**
10
+ * Special number: multiple select of "Infinity", "-Infinity" (README;
11
+ * `NaN` is covered separately by `nanSearchType.js`'s presence-only
12
+ * control, since it's its own zodex schema type - `SpecialRealNumberType.js`
13
+ * itself covers exactly `Infinity`/`-Infinity`/`-0`, not `NaN`).
14
+ * @type {SearchTypeObject}
15
+ */
16
+ const SpecialRealNumberSearchType = {
17
+ buildUI ({schemaObject, path, typeNamespace}) {
18
+ const label = buildPathLabel(schemaObject, path);
19
+ const name = `${typeNamespace}-SpecialRealNumber`;
20
+ return ['jsoe-search-special-real-number', {
21
+ dataset: {searchPath: path, searchKind: 'SpecialRealNumber'},
22
+ title: label,
23
+ $define: {
24
+ /** @this {HTMLElement} */
25
+ getQuery () {
26
+ const selected = readMultiSelect(this);
27
+ return selected.length
28
+ ? makeMultiSelectLeaf(this.dataset.searchPath ?? '', {$in: selected})
29
+ : undefined;
30
+ }
31
+ }
32
+ }, [
33
+ ['span', {class: 'searchLabel'}, [label]],
34
+ buildMultiSelect({
35
+ name, options: ['Infinity', '-Infinity', '-0']
36
+ })
37
+ ]];
38
+ },
39
+ getQuery: getQueryViaElement
40
+ };
41
+
42
+ export default SpecialRealNumberSearchType;