@es-joy/jsoe 0.28.2 → 0.30.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 (167) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/CHANGES.md +25 -0
  3. package/README.md +4 -0
  4. package/dist/formatAndTypeChoices.d.ts +6 -6
  5. package/dist/formatAndTypeChoices.d.ts.map +1 -1
  6. package/dist/formats/schema.d.ts +5 -0
  7. package/dist/formats/schema.d.ts.map +1 -1
  8. package/dist/formats.d.ts +2 -2
  9. package/dist/formats.d.ts.map +1 -1
  10. package/dist/fundamentalTypes/dateType.d.ts +26 -2
  11. package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
  12. package/dist/fundamentalTypes/domexceptionType.d.ts +7 -0
  13. package/dist/fundamentalTypes/domexceptionType.d.ts.map +1 -1
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -2
  17. package/dist/index.js.map +1 -1
  18. package/dist/jtltExtensions.d.ts +112 -0
  19. package/dist/jtltExtensions.d.ts.map +1 -0
  20. package/dist/search/fundamentalTypes/arraySearchType.d.ts +22 -0
  21. package/dist/search/fundamentalTypes/arraySearchType.d.ts.map +1 -0
  22. package/dist/search/fundamentalTypes/bigintSearchType.d.ts +10 -0
  23. package/dist/search/fundamentalTypes/bigintSearchType.d.ts.map +1 -0
  24. package/dist/search/fundamentalTypes/blobSearchType.d.ts +19 -0
  25. package/dist/search/fundamentalTypes/blobSearchType.d.ts.map +1 -0
  26. package/dist/search/fundamentalTypes/booleanSearchType.d.ts +11 -0
  27. package/dist/search/fundamentalTypes/booleanSearchType.d.ts.map +1 -0
  28. package/dist/search/fundamentalTypes/catchSearchType.d.ts +18 -0
  29. package/dist/search/fundamentalTypes/catchSearchType.d.ts.map +1 -0
  30. package/dist/search/fundamentalTypes/dateSearchType.d.ts +15 -0
  31. package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -0
  32. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts +16 -0
  33. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -0
  34. package/dist/search/fundamentalTypes/enumSearchType.d.ts +26 -0
  35. package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -0
  36. package/dist/search/fundamentalTypes/errorSearchType.d.ts +9 -0
  37. package/dist/search/fundamentalTypes/errorSearchType.d.ts.map +1 -0
  38. package/dist/search/fundamentalTypes/fileSearchType.d.ts +28 -0
  39. package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -0
  40. package/dist/search/fundamentalTypes/filelistSearchType.d.ts +24 -0
  41. package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -0
  42. package/dist/search/fundamentalTypes/functionSearchType.d.ts +18 -0
  43. package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -0
  44. package/dist/search/fundamentalTypes/mapSearchType.d.ts +22 -0
  45. package/dist/search/fundamentalTypes/mapSearchType.d.ts.map +1 -0
  46. package/dist/search/fundamentalTypes/nanSearchType.d.ts +7 -0
  47. package/dist/search/fundamentalTypes/nanSearchType.d.ts.map +1 -0
  48. package/dist/search/fundamentalTypes/nullSearchType.d.ts +7 -0
  49. package/dist/search/fundamentalTypes/nullSearchType.d.ts.map +1 -0
  50. package/dist/search/fundamentalTypes/numberSearchType.d.ts +11 -0
  51. package/dist/search/fundamentalTypes/numberSearchType.d.ts.map +1 -0
  52. package/dist/search/fundamentalTypes/objectSearchType.d.ts +21 -0
  53. package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -0
  54. package/dist/search/fundamentalTypes/promiseSearchType.d.ts +20 -0
  55. package/dist/search/fundamentalTypes/promiseSearchType.d.ts.map +1 -0
  56. package/dist/search/fundamentalTypes/regexpSearchType.d.ts +20 -0
  57. package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -0
  58. package/dist/search/fundamentalTypes/setSearchType.d.ts +15 -0
  59. package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -0
  60. package/dist/search/fundamentalTypes/stringSearchType.d.ts +21 -0
  61. package/dist/search/fundamentalTypes/stringSearchType.d.ts.map +1 -0
  62. package/dist/search/fundamentalTypes/symbolSearchType.d.ts +16 -0
  63. package/dist/search/fundamentalTypes/symbolSearchType.d.ts.map +1 -0
  64. package/dist/search/fundamentalTypes/undefinedSearchType.d.ts +7 -0
  65. package/dist/search/fundamentalTypes/undefinedSearchType.d.ts.map +1 -0
  66. package/dist/search/index.d.ts +70 -0
  67. package/dist/search/index.d.ts.map +1 -0
  68. package/dist/search/queryTree.d.ts +12 -1
  69. package/dist/search/queryTree.d.ts.map +1 -1
  70. package/dist/search/queryTreeBuilders.d.ts +18 -0
  71. package/dist/search/queryTreeBuilders.d.ts.map +1 -1
  72. package/dist/search/searchDispatch.d.ts +44 -0
  73. package/dist/search/searchDispatch.d.ts.map +1 -1
  74. package/dist/search/searchElementUtils.d.ts +106 -0
  75. package/dist/search/searchElementUtils.d.ts.map +1 -0
  76. package/dist/search/searchUtils.d.ts +550 -0
  77. package/dist/search/searchUtils.d.ts.map +1 -0
  78. package/dist/search/subTypes/blobHTMLSearchType.d.ts +36 -0
  79. package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -0
  80. package/dist/search/subTypes/recordSearchType.d.ts +18 -0
  81. package/dist/search/subTypes/recordSearchType.d.ts.map +1 -0
  82. package/dist/search/subTypes/tupleSearchType.d.ts +21 -0
  83. package/dist/search/subTypes/tupleSearchType.d.ts.map +1 -0
  84. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts +14 -0
  85. package/dist/search/superTypes/SpecialRealNumberSearchType.d.ts.map +1 -0
  86. package/dist/search/superTypes/buffersourceSearchType.d.ts +17 -0
  87. package/dist/search/superTypes/buffersourceSearchType.d.ts.map +1 -0
  88. package/dist/search/superTypes/dommatrixSearchType.d.ts +11 -0
  89. package/dist/search/superTypes/dommatrixSearchType.d.ts.map +1 -0
  90. package/dist/search/superTypes/dompointSearchType.d.ts +9 -0
  91. package/dist/search/superTypes/dompointSearchType.d.ts.map +1 -0
  92. package/dist/search/superTypes/domrectSearchType.d.ts +9 -0
  93. package/dist/search/superTypes/domrectSearchType.d.ts.map +1 -0
  94. package/dist/search/superTypes/errorsSpecialSearchType.d.ts +11 -0
  95. package/dist/search/superTypes/errorsSpecialSearchType.d.ts.map +1 -0
  96. package/dist/search/unions/discriminatedUnionSearchType.d.ts +12 -0
  97. package/dist/search/unions/discriminatedUnionSearchType.d.ts.map +1 -0
  98. package/dist/search/unions/unionFamilySearchType.d.ts +36 -0
  99. package/dist/search/unions/unionFamilySearchType.d.ts.map +1 -0
  100. package/dist/search/unions/unionSearchType.d.ts +8 -0
  101. package/dist/search/unions/unionSearchType.d.ts.map +1 -0
  102. package/dist/search/unions/xorSearchType.d.ts +12 -0
  103. package/dist/search/unions/xorSearchType.d.ts.map +1 -0
  104. package/dist/superTypes/buffersourceType.d.ts.map +1 -1
  105. package/dist/typeChoices.d.ts +3 -3
  106. package/dist/typeChoices.d.ts.map +1 -1
  107. package/dist/types.d.ts +3 -3
  108. package/dist/types.d.ts.map +1 -1
  109. package/package.json +5 -4
  110. package/pnpm-workspace.yaml +1 -1
  111. package/rollup.config.js +12 -0
  112. package/src/formatAndTypeChoices.js +4 -4
  113. package/src/formats/schema.js +1 -1
  114. package/src/formats.js +1 -1
  115. package/src/fundamentalTypes/dateType.js +40 -14
  116. package/src/fundamentalTypes/domexceptionType.js +41 -33
  117. package/src/fundamentalTypes/regexpType.js +1 -1
  118. package/src/index.js +6 -0
  119. package/src/jsoe.css +204 -0
  120. package/src/jtltExtensions.js +103 -0
  121. package/src/search/fundamentalTypes/arraySearchType.js +101 -0
  122. package/src/search/fundamentalTypes/bigintSearchType.js +68 -0
  123. package/src/search/fundamentalTypes/blobSearchType.js +42 -0
  124. package/src/search/fundamentalTypes/booleanSearchType.js +40 -0
  125. package/src/search/fundamentalTypes/catchSearchType.js +59 -0
  126. package/src/search/fundamentalTypes/dateSearchType.js +166 -0
  127. package/src/search/fundamentalTypes/domexceptionSearchType.js +84 -0
  128. package/src/search/fundamentalTypes/enumSearchType.js +62 -0
  129. package/src/search/fundamentalTypes/errorSearchType.js +11 -0
  130. package/src/search/fundamentalTypes/fileSearchType.js +94 -0
  131. package/src/search/fundamentalTypes/filelistSearchType.js +103 -0
  132. package/src/search/fundamentalTypes/functionSearchType.js +102 -0
  133. package/src/search/fundamentalTypes/mapSearchType.js +117 -0
  134. package/src/search/fundamentalTypes/nanSearchType.js +11 -0
  135. package/src/search/fundamentalTypes/nullSearchType.js +11 -0
  136. package/src/search/fundamentalTypes/numberSearchType.js +59 -0
  137. package/src/search/fundamentalTypes/objectSearchType.js +367 -0
  138. package/src/search/fundamentalTypes/promiseSearchType.js +61 -0
  139. package/src/search/fundamentalTypes/regexpSearchType.js +72 -0
  140. package/src/search/fundamentalTypes/setSearchType.js +91 -0
  141. package/src/search/fundamentalTypes/stringSearchType.js +44 -0
  142. package/src/search/fundamentalTypes/symbolSearchType.js +39 -0
  143. package/src/search/fundamentalTypes/undefinedSearchType.js +11 -0
  144. package/src/search/index.js +96 -0
  145. package/src/search/queryTree.js +244 -0
  146. package/src/search/queryTreeBuilders.js +233 -0
  147. package/src/search/searchDispatch.js +291 -0
  148. package/src/search/searchElementUtils.js +373 -0
  149. package/src/search/searchUtils.js +847 -0
  150. package/src/search/subTypes/blobHTMLSearchType.js +122 -0
  151. package/src/search/subTypes/recordSearchType.js +105 -0
  152. package/src/search/subTypes/tupleSearchType.js +152 -0
  153. package/src/search/superTypes/SpecialRealNumberSearchType.js +42 -0
  154. package/src/search/superTypes/buffersourceSearchType.js +53 -0
  155. package/src/search/superTypes/dommatrixSearchType.js +24 -0
  156. package/src/search/superTypes/dompointSearchType.js +15 -0
  157. package/src/search/superTypes/domrectSearchType.js +15 -0
  158. package/src/search/superTypes/errorsSpecialSearchType.js +15 -0
  159. package/src/search/unions/discriminatedUnionSearchType.js +16 -0
  160. package/src/search/unions/unionFamilySearchType.js +168 -0
  161. package/src/search/unions/unionSearchType.js +12 -0
  162. package/src/search/unions/xorSearchType.js +16 -0
  163. package/src/superTypes/buffersourceType.js +13 -15
  164. package/src/typeChoices.js +2 -2
  165. package/src/types.js +2 -2
  166. package/tsconfig-prod.json +12 -0
  167. package/tsconfig.json +5 -10
@@ -0,0 +1,117 @@
1
+ import {
2
+ buildPathLabel, buildCheckbox, readCheckbox, buildLengthSizeControls,
3
+ readLengthSizeQuery, buildOptInFieldset, readOptInChecked, wireOptInFieldset,
4
+ buildAtLeastOneSentinel, syncAtLeastOneCheck
5
+ } from '../searchUtils.js';
6
+ import {makeMapRecordJointLeaf, combineAnd} from '../queryTreeBuilders.js';
7
+ import {findSearchElement, getQueryViaElement, hasGetQuery} from '../searchElementUtils.js';
8
+ import {buildSearchWidget} from '../searchDispatch.js';
9
+
10
+ /**
11
+ * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
12
+ */
13
+
14
+ /**
15
+ * @param {Element} root - a `jsoe-search-map` element
16
+ * @returns {void}
17
+ */
18
+ function syncMapValidity (root) {
19
+ syncAtLeastOneCheck(
20
+ root, () => readOptInChecked(root, 'key') || readOptInChecked(root, 'value') ||
21
+ readLengthSizeQuery(root, '') !== undefined
22
+ );
23
+ }
24
+
25
+ /**
26
+ * Map, Record: string-type searches of keys, values; ideally would allow
27
+ * search to insist on match of key and value (README) - structurally the
28
+ * same as `recordSearchType.js` (`key`/`value` recursion at `*key`/`*value`
29
+ * segments, a "require same entry" toggle producing one `mapRecordJoint`
30
+ * leaf), plus a `lengthSize` control for `min`/`max` (a `Map`, unlike a
31
+ * `Record`, carries its own size bounds in the schema - the same
32
+ * affordance `setSearchType.js` gets for the analogous reason), combined
33
+ * with the joint leaf via `$and`. Key and value are each wrapped in their
34
+ * own `buildOptInFieldset` (so leaving either at "no constraint" doesn't
35
+ * force it via a `required` control inside), but *leaving all three*
36
+ * (key, value, and size) unconfigured is itself invalid via
37
+ * `buildAtLeastOneSentinel` - unlike `recordSearchType.js` (which has no
38
+ * size control of its own and so needs key or value specifically), a
39
+ * length/size-only search is already meaningful here, the same as
40
+ * `arraySearchType.js`/`setSearchType.js`.
41
+ * @type {SearchTypeObject}
42
+ */
43
+ const mapSearchType = {
44
+ buildUI ({schemaObject, path, typeNamespace, topRoot, types, originalJSON}) {
45
+ const label = buildPathLabel(schemaObject, path);
46
+ const name = `${typeNamespace}-map`;
47
+ const mapSchemaObject = /** @type {import('zodexy').SzMap<any, any>} */ (
48
+ schemaObject
49
+ );
50
+ const keyPath = `${path}/*key`;
51
+ const valuePath = `${path}/*value`;
52
+ const keyArr = buildSearchWidget({
53
+ schemaObject: mapSchemaObject.key,
54
+ path: keyPath,
55
+ typeNamespace,
56
+ topRoot,
57
+ types,
58
+ originalJSON
59
+ });
60
+ const valueArr = buildSearchWidget({
61
+ schemaObject: mapSchemaObject.value,
62
+ path: valuePath,
63
+ typeNamespace,
64
+ topRoot,
65
+ types,
66
+ originalJSON
67
+ });
68
+ return ['jsoe-search-map', {
69
+ dataset: {searchPath: path, searchKind: 'map'},
70
+ title: label,
71
+ $define: {
72
+ /** @this {HTMLElement} */
73
+ connectedCallback () {
74
+ wireOptInFieldset(this, 'key', () => syncMapValidity(this));
75
+ wireOptInFieldset(this, 'value', () => syncMapValidity(this));
76
+ this.querySelector('input.jsoeSearchSize')?.addEventListener(
77
+ 'input', () => syncMapValidity(this)
78
+ );
79
+ syncMapValidity(this);
80
+ },
81
+ /** @this {HTMLElement} */
82
+ getQuery () {
83
+ const searchPath = this.dataset.searchPath ?? '';
84
+ const lengthLeaf = readLengthSizeQuery(this, searchPath);
85
+ const keyEl = findSearchElement(this, `${searchPath}/*key`);
86
+ const valueEl = findSearchElement(this, `${searchPath}/*value`);
87
+ const keyQuery = keyEl && hasGetQuery(keyEl) && readOptInChecked(this, 'key')
88
+ ? keyEl.getQuery()
89
+ : undefined;
90
+ const valueQuery = valueEl && hasGetQuery(valueEl) && readOptInChecked(this, 'value')
91
+ ? valueEl.getQuery()
92
+ : undefined;
93
+ const jointLeaf = keyQuery || valueQuery
94
+ ? makeMapRecordJointLeaf(searchPath, readCheckbox(this), keyQuery, valueQuery)
95
+ : undefined;
96
+ return combineAnd([lengthLeaf, jointLeaf]);
97
+ }
98
+ }
99
+ }, [
100
+ ['span', {class: 'searchLabel'}, [label]],
101
+ ...buildLengthSizeControls({
102
+ name, min: mapSchemaObject.min, max: mapSchemaObject.max, includeSparse: false
103
+ }),
104
+ ...buildOptInFieldset({
105
+ name: `${name}-key`, key: 'key', label: 'Key matches', children: [keyArr]
106
+ }),
107
+ ...buildOptInFieldset({
108
+ name: `${name}-value`, key: 'value', label: 'Value matches', children: [valueArr]
109
+ }),
110
+ buildCheckbox({name: `${name}-joint`, label: 'Require same entry'}),
111
+ buildAtLeastOneSentinel()
112
+ ]];
113
+ },
114
+ getQuery: getQueryViaElement
115
+ };
116
+
117
+ export default mapSearchType;
@@ -0,0 +1,11 @@
1
+ import {makePresenceOnlySearchType} from '../searchElementUtils.js';
2
+
3
+ /**
4
+ * `NaN`: "no variants to allow for distinct search" (README).
5
+ * @type {import('../searchDispatch.js').SearchTypeObject}
6
+ */
7
+ const nanSearchType = makePresenceOnlySearchType({
8
+ tagName: 'jsoe-search-nan'
9
+ });
10
+
11
+ export default nanSearchType;
@@ -0,0 +1,11 @@
1
+ import {makePresenceOnlySearchType} from '../searchElementUtils.js';
2
+
3
+ /**
4
+ * `null`: "no variants to allow for distinct search" (README).
5
+ * @type {import('../searchDispatch.js').SearchTypeObject}
6
+ */
7
+ const nullSearchType = makePresenceOnlySearchType({
8
+ tagName: 'jsoe-search-null'
9
+ });
10
+
11
+ export default nullSearchType;
@@ -0,0 +1,59 @@
1
+ import {
2
+ buildPathLabel, buildRangeInputsPair, readRangeInputsPair, buildTriStateSelect,
3
+ readTriStateSelect, syncRangeValidity
4
+ } from '../searchUtils.js';
5
+ import {makeRangeLeaf, makeIntegerCheckLeaf, combineAnd} from '../queryTreeBuilders.js';
6
+ import {getQueryViaElement} from '../searchElementUtils.js';
7
+
8
+ /**
9
+ * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
10
+ */
11
+
12
+ /**
13
+ * OR Ranges/Is Not Range, Is/Is Not Integer (README).
14
+ * @type {SearchTypeObject}
15
+ */
16
+ const numberSearchType = {
17
+ buildUI ({schemaObject, path, typeNamespace}) {
18
+ const label = buildPathLabel(schemaObject, path);
19
+ const name = `${typeNamespace}-number`;
20
+ return ['jsoe-search-number', {
21
+ dataset: {searchPath: path, searchKind: 'number'},
22
+ title: label,
23
+ $define: {
24
+ /** @this {HTMLElement} */
25
+ connectedCallback () {
26
+ syncRangeValidity(this);
27
+ },
28
+ /** @this {HTMLElement} */
29
+ getQuery () {
30
+ const searchPath = this.dataset.searchPath ?? '';
31
+ const {gte, lte} = readRangeInputsPair(this);
32
+ const rangeLeaf = gte === '' && lte === ''
33
+ ? undefined
34
+ : makeRangeLeaf(searchPath, 'number', {
35
+ ...(gte === '' ? {} : {$gte: Number(gte)}),
36
+ ...(lte === '' ? {} : {$lte: Number(lte)})
37
+ });
38
+ const isInteger = readTriStateSelect(this);
39
+ const integerLeaf = isInteger === undefined
40
+ ? undefined
41
+ : makeIntegerCheckLeaf(searchPath, isInteger);
42
+ return combineAnd([rangeLeaf, integerLeaf]);
43
+ }
44
+ }
45
+ }, [
46
+ ['span', {class: 'searchLabel'}, [label]],
47
+ ...buildRangeInputsPair({name}),
48
+ ['label', [
49
+ 'Integer: ',
50
+ buildTriStateSelect({
51
+ name: `${name}-integer`, trueLabel: 'Integer', falseLabel: 'Not integer'
52
+ })
53
+ ]]
54
+ ]];
55
+ },
56
+ getQuery: getQueryViaElement
57
+ };
58
+
59
+ export default numberSearchType;
@@ -0,0 +1,367 @@
1
+ import {jml} from '../../vendor-imports.js';
2
+ import {escapeJSONPointer} from '../../utils/jsonPointer.js';
3
+ import {
4
+ buildPathLabel, buildHasPropertyToggle, readTriStateSelect, findOwnControl,
5
+ buildCheckbox, readCheckbox, buildAtLeastOneSentinel, syncAtLeastOneCheck,
6
+ setDescendantsRequired, revalidateDescendants, resyncAtLeastOne
7
+ } from '../searchUtils.js';
8
+ import {combineAnd, makeHasPropertyLeaf} from '../queryTreeBuilders.js';
9
+ import {
10
+ findSearchElement, getQueryViaElement, hasGetQuery
11
+ } from '../searchElementUtils.js';
12
+ import {buildSearchWidget} from '../searchDispatch.js';
13
+
14
+ /**
15
+ * @typedef {import('../searchDispatch.js').SearchTypeObject} SearchTypeObject
16
+ */
17
+
18
+ /**
19
+ * An object with zero active rows (nothing added from the pull-down, and no
20
+ * required property opted into) expresses no constraint at all - the same
21
+ * "no absent values" reasoning as every other leaf's own required control,
22
+ * just with nothing native to hang a `required` attribute directly off of,
23
+ * since what counts as "active" is a dynamically-changing count of child
24
+ * rows rather than one fixed input. `searchUtils.js`'s
25
+ * `buildAtLeastOneSentinel`/`syncAtLeastOneCheck` stand in for that.
26
+ * @param {Element} root - a `jsoe-search-object` element
27
+ * @returns {void}
28
+ */
29
+ function syncObjectValidity (root) {
30
+ const addedRowCount = root.querySelectorAll(':scope > jsoe-search-has-property').length;
31
+ const checkedRequiredCount = [
32
+ ...root.querySelectorAll(
33
+ ':scope > jsoe-search-required-property > label > input.jsoeSearchCheckbox'
34
+ )
35
+ ].filter((checkbox) => /** @type {HTMLInputElement} */ (checkbox).checked).length;
36
+ syncAtLeastOneCheck(root, () => addedRowCount + checkedRequiredCount > 0);
37
+ }
38
+
39
+ /**
40
+ * One added property's row: the fixed "has property" tri-state toggle
41
+ * (`buildHasPropertyToggle`) plus, immediately recursing (search plan §6
42
+ * build order: "Object / Array / Set - first types recursing into child
43
+ * schemas via `getSearchTypeObject`"), that property's own full search
44
+ * widget - so a user can combine "has property X" with "X matches Y" in
45
+ * one row rather than only being able to ask about existence.
46
+ * @param {{
47
+ * propertyName: string,
48
+ * propSchema: import('../../formats/schema.js').ZodexSchema,
49
+ * path: string,
50
+ * typeNamespace: string|undefined,
51
+ * topRoot: import('../../types.js').RootElement|undefined,
52
+ * types: import('../../types.js').default|undefined,
53
+ * originalJSON: import('../../formats/schema.js').ZodexSchema|undefined,
54
+ * addPropertySelect: HTMLSelectElement
55
+ * }} cfg
56
+ * @returns {HTMLElement}
57
+ */
58
+ function buildHasPropertyRow ({
59
+ propertyName, propSchema, path, typeNamespace, topRoot, types, originalJSON,
60
+ addPropertySelect
61
+ }) {
62
+ const childPath = `${path}/${escapeJSONPointer(propertyName)}`;
63
+ const name = `${typeNamespace}-hasProperty-${propertyName}`;
64
+ const childArr = buildSearchWidget({
65
+ schemaObject: propSchema, path: childPath, typeNamespace, topRoot, types,
66
+ originalJSON
67
+ });
68
+ const row = /** @type {HTMLElement} */ (jml('jsoe-search-has-property', {
69
+ dataset: {searchPath: childPath, searchKind: 'hasProperty', propertyName},
70
+ $define: {
71
+ // Reads `childPath` off `this.dataset` rather than closing over the
72
+ // outer `path`/`propertyName`: `$define`'s mixin is installed once
73
+ // on the shared prototype the first time this tag is defined, so
74
+ // every later `<jsoe-search-has-property>` instance must read its
75
+ // own per-row state off `this`. `readTriStateSelect`/
76
+ // `findSearchElement` are themselves closure-free (class- and
77
+ // `dataset`-driven; see `searchUtils.js`'s `findOwnControl`), so no
78
+ // further per-instance state needs storing here.
79
+ /** @this {HTMLElement} */
80
+ getQuery () {
81
+ const searchPath = this.dataset.searchPath ?? '';
82
+ const existsCheck = readTriStateSelect(this);
83
+ const existsLeaf = existsCheck === undefined
84
+ ? undefined
85
+ : makeHasPropertyLeaf(searchPath, existsCheck);
86
+ // "Doesn't have" already hides the child (its own `syncChildState`),
87
+ // since a value/presence constraint on an asserted-absent property
88
+ // is meaningless - skip its query outright rather than combining a
89
+ // stale/leftover constraint with a contradictory `$exists: false`.
90
+ // This matters most for `makePresenceOnlySearchType`'s leaves
91
+ // (undef/null/NaN), whose checkbox is permanently checked
92
+ // (`buildCheckbox`'s doc) and so would otherwise always contribute
93
+ // its `presence` leaf regardless of this row's own answer.
94
+ const childEl = existsCheck === false
95
+ ? undefined
96
+ : findSearchElement(this, searchPath);
97
+ const childLeaf = childEl && hasGetQuery(childEl)
98
+ ? childEl.getQuery()
99
+ : undefined;
100
+ return combineAnd([existsLeaf, childLeaf]);
101
+ }
102
+ }
103
+ }, [
104
+ buildHasPropertyToggle({name, propertyName}),
105
+ ['button', {
106
+ type: 'button',
107
+ class: 'removePropertyButton',
108
+ $on: {
109
+ // `row`/`addPropertySelect`/`propertyName` are all closed over
110
+ // directly - safe since `buildHasPropertyRow` (unlike `$define`)
111
+ // runs fresh per row, so nothing here is shared across instances.
112
+ click () {
113
+ const option = /** @type {HTMLOptionElement|null} */ (
114
+ addPropertySelect.querySelector(
115
+ `option[value="${CSS.escape(propertyName)}"]`
116
+ )
117
+ );
118
+ if (option) {
119
+ option.disabled = false;
120
+ }
121
+ const objectRoot = row.closest('jsoe-search-object');
122
+ row.remove();
123
+ if (objectRoot) {
124
+ syncObjectValidity(/** @type {HTMLElement} */ (objectRoot));
125
+ }
126
+ }
127
+ }
128
+ }, ['Remove']],
129
+ childArr
130
+ ]));
131
+
132
+ // Asserting the property is absent ("Doesn't have") makes a value
133
+ // constraint on it meaningless, so hide the recursed child widget while
134
+ // that's selected rather than leaving it showing (and usable) alongside a
135
+ // contradictory "doesn't have" answer - `buildUI` runs fresh per row, so
136
+ // (unlike `$define`) it's safe for this listener to close over `select`/
137
+ // `childRoot` directly.
138
+ const select = /** @type {HTMLSelectElement|undefined} */ (
139
+ findOwnControl(row, 'select.jsoeSearchTriState--')
140
+ );
141
+ const childRoot = findSearchElement(row, childPath);
142
+ // "Doesn't have" hides the child entirely (a value constraint on an
143
+ // asserted-absent property is meaningless) - a hidden control is already
144
+ // exempt from constraint validation on its own. "Has" keeps the child
145
+ // visible and interactive (so "Has property X" AND "X matches Y" can
146
+ // still be combined), but that existence assertion is *already* a
147
+ // complete constraint by itself, so its own required inputs (whatever
148
+ // type the child turns out to be) are relaxed via `setDescendantsRequired`
149
+ // rather than disabled - only "(any)" (no existence assertion) leaves
150
+ // them actually required, since the child's own value is then the row's
151
+ // only possible contribution. `revalidateDescendants` covers anything
152
+ // that (unlike a plain `required` attribute) re-asserts its own
153
+ // constraint imperatively on every keystroke regardless of this row's
154
+ // state - a range pair's `isExemptedByAncestorHasProperty` check
155
+ // (`searchUtils.js`) only takes effect once something re-runs it, which
156
+ // otherwise wouldn't happen until the user next touches that field.
157
+ // `resyncAtLeastOne` covers the same gap for a child that's itself an
158
+ // `object`/`array`/`map`/etc. with its own "at least one facet" sentinel
159
+ // (`buildAtLeastOneSentinel`) - `revalidateDescendants`' event-redispatch
160
+ // reaches `array`/`map`/etc. fine (their own answer changes via a plain
161
+ // `input`/`change` on a real control), but not `objectSearchType.js`
162
+ // itself, whose sentinel changes when a row is added/removed - a button
163
+ // click and a structural DOM change, neither of which redispatching
164
+ // `input`/`change` events can simulate.
165
+ const syncChildState = () => {
166
+ if (!childRoot) {
167
+ return;
168
+ }
169
+ childRoot.hidden = select?.value === 'false';
170
+ setDescendantsRequired(childRoot, select?.value === '');
171
+ revalidateDescendants(childRoot);
172
+ resyncAtLeastOne(childRoot);
173
+ };
174
+ select?.addEventListener('change', syncChildState);
175
+ syncChildState();
176
+
177
+ return row;
178
+ }
179
+
180
+ /**
181
+ * A required property's own value-match widget, wrapped in an opt-in
182
+ * "Search on this property" checkbox (default unchecked) around a disabled
183
+ * `<fieldset>`: a required property has no existence to ask about, but it
184
+ * still auto-appears with no "Remove" affordance the moment its parent
185
+ * object is added (search plan build order never lists it in the "Add
186
+ * property" pull-down at all) - and several leaf widgets now mark their own
187
+ * value input `required` (`buildLiteralRegexControls`'s doc), which would
188
+ * otherwise make the *whole* form invalid the instant such a property's
189
+ * object exists, before the user has asked to search on it at all.
190
+ * `<fieldset disabled>` is a native way to exempt a whole subtree from
191
+ * constraint validation in one step (every descendant control, whatever
192
+ * type it turns out to be, stops blocking `checkValidity`/`reportValidity`
193
+ * while disabled) - checking the box re-enables the fieldset, making its
194
+ * contents both interactive and, if left with a `required` field empty,
195
+ * actually invalid again.
196
+ * @param {{
197
+ * propertyName: string,
198
+ * propSchema: import('../../formats/schema.js').ZodexSchema,
199
+ * path: string,
200
+ * typeNamespace: string|undefined,
201
+ * topRoot: import('../../types.js').RootElement|undefined,
202
+ * types: import('../../types.js').default|undefined,
203
+ * originalJSON: import('../../formats/schema.js').ZodexSchema|undefined
204
+ * }} cfg
205
+ * @returns {HTMLElement}
206
+ */
207
+ function buildRequiredPropertyRow ({
208
+ propertyName, propSchema, path, typeNamespace, topRoot, types, originalJSON
209
+ }) {
210
+ const childPath = `${path}/${escapeJSONPointer(propertyName)}`;
211
+ const name = `${typeNamespace}-requiredProperty-${propertyName}`;
212
+ const childArr = buildSearchWidget({
213
+ schemaObject: propSchema, path: childPath, typeNamespace, topRoot, types,
214
+ originalJSON
215
+ });
216
+ const row = /** @type {HTMLElement} */ (jml('jsoe-search-required-property', {
217
+ dataset: {searchPath: childPath, searchKind: 'requiredProperty', propertyName},
218
+ $define: {
219
+ /** @this {HTMLElement} */
220
+ getQuery () {
221
+ if (!readCheckbox(this)) {
222
+ return undefined;
223
+ }
224
+ const searchPath = this.dataset.searchPath ?? '';
225
+ const childEl = findSearchElement(this, searchPath);
226
+ return childEl && hasGetQuery(childEl) ? childEl.getQuery() : undefined;
227
+ }
228
+ }
229
+ }, [
230
+ buildCheckbox({name, label: `Search on "${propertyName}"`}),
231
+ ['fieldset', {disabled: true, class: 'searchRequiredPropertyFieldset'}, [childArr]]
232
+ ]));
233
+
234
+ const checkbox = /** @type {HTMLInputElement|undefined} */ (
235
+ findOwnControl(row, 'input.jsoeSearchCheckbox')
236
+ );
237
+ const fieldset = /** @type {HTMLFieldSetElement|null} */ (
238
+ row.querySelector('fieldset.searchRequiredPropertyFieldset')
239
+ );
240
+ checkbox?.addEventListener('change', () => {
241
+ if (fieldset) {
242
+ fieldset.disabled = !checkbox.checked;
243
+ }
244
+ const objectRoot = row.closest('jsoe-search-object');
245
+ if (objectRoot) {
246
+ syncObjectValidity(/** @type {HTMLElement} */ (objectRoot));
247
+ }
248
+ });
249
+
250
+ return row;
251
+ }
252
+
253
+ /**
254
+ * Has property &lt;property pull-down&gt; (README), additive: only
255
+ * optional properties are offered ("avoid listing required" - a required
256
+ * property is guaranteed present, so asking about its existence is
257
+ * meaningless), and picking one from the pull-down adds a row for it
258
+ * (disabled in the pull-down, rather than removed, so it can't be added a
259
+ * second time while its row exists). Each row's own "Remove" button
260
+ * discards the row and re-enables its property in the pull-down, for
261
+ * undoing an added-by-mistake property - a coarser-grained option than
262
+ * setting the row's toggle back to "(any)" (which already expresses "no
263
+ * constraint from this row" without removing it). A required property gets
264
+ * no pull-down entry (its existence is never in question), but does still
265
+ * get its own row - `buildRequiredPropertyRow`'s opt-in checkbox, defaulting
266
+ * unchecked/disabled, so it doesn't force a search constraint the user never
267
+ * asked for just by existing.
268
+ * @type {SearchTypeObject}
269
+ */
270
+ const objectSearchType = {
271
+ buildUI ({schemaObject, path, typeNamespace, topRoot, types, originalJSON}) {
272
+ const label = buildPathLabel(schemaObject, path);
273
+ const objectSchemaObject = /** @type {import('zodexy').SzObject} */ (
274
+ schemaObject
275
+ );
276
+ const propertyEntries = Object.entries(objectSchemaObject.properties);
277
+ const availableProperties = propertyEntries.filter(
278
+ ([, propSchema]) => propSchema.isOptional === true
279
+ );
280
+ // A required property is guaranteed present, so a has/doesn't-have
281
+ // toggle would be meaningless for it (same reasoning `availableProperties`
282
+ // already uses to leave it out of the "Add property" pull-down) - but it
283
+ // still gets its own row, via `buildRequiredPropertyRow`'s opt-in
284
+ // checkbox, so it doesn't silently force a search constraint the user
285
+ // never asked for just by its parent object existing.
286
+ const requiredProperties = propertyEntries.filter(
287
+ ([, propSchema]) => propSchema.isOptional !== true
288
+ );
289
+ const requiredArr = requiredProperties.map(([propertyName, propSchema]) => (
290
+ buildRequiredPropertyRow({
291
+ propertyName, propSchema, path, typeNamespace, topRoot, types,
292
+ originalJSON
293
+ })
294
+ ));
295
+
296
+ return ['jsoe-search-object', {
297
+ dataset: {searchPath: path, searchKind: 'object'},
298
+ title: label,
299
+ $define: {
300
+ /** @this {HTMLElement} */
301
+ connectedCallback () {
302
+ if (propertyEntries.length > 0) {
303
+ syncObjectValidity(this);
304
+ }
305
+ },
306
+ /** @this {HTMLElement} */
307
+ getQuery () {
308
+ const rows = [...this.children].filter(hasGetQuery);
309
+ return combineAnd(rows.map((row) => row.getQuery()));
310
+ }
311
+ }
312
+ }, [
313
+ ['div', {class: 'searchObjectControls'}, [
314
+ ['span', {class: 'searchLabel'}, [label]],
315
+ availableProperties.length
316
+ ? ['label', [
317
+ 'Add property: ',
318
+ ['select', {class: 'addPropertySelect'}, [
319
+ ['option', {value: ''}, ['(choose a property)']],
320
+ ...availableProperties.map(([propertyName]) => (
321
+ ['option', {value: propertyName}, [propertyName]]
322
+ ))
323
+ ]]
324
+ ]]
325
+ : ['span', ['(no optional properties to search on)']],
326
+ ['button', {
327
+ type: 'button',
328
+ $on: {
329
+ click () {
330
+ const container = this.closest('jsoe-search-object');
331
+ const select = /** @type {HTMLSelectElement|null} */ (
332
+ container?.querySelector('select.addPropertySelect')
333
+ );
334
+ const propertyName = select?.value;
335
+ if (!container || !select || !propertyName) {
336
+ return;
337
+ }
338
+ const propSchema = objectSchemaObject.properties[propertyName];
339
+ const row = buildHasPropertyRow({
340
+ propertyName, propSchema, path, typeNamespace, topRoot, types,
341
+ originalJSON, addPropertySelect: select
342
+ });
343
+ container.append(row);
344
+ const option = /** @type {HTMLOptionElement|null} */ (
345
+ select.querySelector(
346
+ `option[value="${CSS.escape(propertyName)}"]`
347
+ )
348
+ );
349
+ if (option) {
350
+ option.disabled = true;
351
+ }
352
+ select.value = '';
353
+ syncObjectValidity(/** @type {HTMLElement} */ (container));
354
+ }
355
+ }
356
+ }, ['Add']]
357
+ ]],
358
+ ...requiredArr,
359
+ // Placed last purely for markup order; a plain rendered-but-off-
360
+ // screen input, so its position among siblings has no visual effect.
361
+ ...(propertyEntries.length > 0 ? [buildAtLeastOneSentinel()] : [])
362
+ ]];
363
+ },
364
+ getQuery: getQueryViaElement
365
+ };
366
+
367
+ export default objectSearchType;
@@ -0,0 +1,61 @@
1
+ import {buildPathLabel} from '../searchUtils.js';
2
+ import {makePassThroughLeaf} 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
+ * Promise, Literal, catch: pass on children (README) - a promise wraps
12
+ * exactly one value at the same conceptual path (no path segment to add,
13
+ * unlike array's `*`/record's `*key`/`*value`), so this recurses into
14
+ * `.value`'s own search widget and wraps whatever it produces in a
15
+ * `passThrough` leaf (`queryTree.js`) purely so the tree keeps visible that
16
+ * the leaf was reached through a promise, rather than returning the child's
17
+ * leaf bare. ("Literal" needs no module of its own: `getSearchSchemaType`
18
+ * already resolves a `literal` schema straight to its value's own type via
19
+ * `getSchemaType`, e.g. a literal string dispatches to `stringSearchType.js`
20
+ * unchanged).
21
+ * @type {SearchTypeObject}
22
+ */
23
+ const promiseSearchType = {
24
+ buildUI ({schemaObject, path, typeNamespace, topRoot, types, originalJSON}) {
25
+ const label = buildPathLabel(schemaObject, path);
26
+ const promiseSchemaObject = /** @type {import('zodexy').SzPromise} */ (
27
+ schemaObject
28
+ );
29
+ const childArr = buildSearchWidget({
30
+ schemaObject: promiseSchemaObject.value,
31
+ path,
32
+ typeNamespace,
33
+ topRoot,
34
+ types,
35
+ originalJSON
36
+ });
37
+ return ['jsoe-search-promise', {
38
+ dataset: {searchPath: path, searchKind: 'promise'},
39
+ title: label,
40
+ $define: {
41
+ /** @this {HTMLElement} */
42
+ getQuery () {
43
+ const searchPath = this.dataset.searchPath ?? '';
44
+ const childEl = findSearchElement(this, searchPath);
45
+ const childQuery = childEl && hasGetQuery(childEl)
46
+ ? childEl.getQuery()
47
+ : undefined;
48
+ return childQuery === undefined
49
+ ? undefined
50
+ : makePassThroughLeaf(searchPath, childQuery);
51
+ }
52
+ }
53
+ }, [
54
+ ['span', {class: 'searchLabel'}, [`${label} (resolved value)`]],
55
+ childArr
56
+ ]];
57
+ },
58
+ getQuery: getQueryViaElement
59
+ };
60
+
61
+ export default promiseSearchType;