@es-joy/jsoe 0.29.0 → 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 (85) hide show
  1. package/.claude/scheduled_tasks.lock +1 -0
  2. package/CHANGES.md +6 -0
  3. package/README.md +2 -0
  4. package/dist/fundamentalTypes/dateType.d.ts +8 -3
  5. package/dist/fundamentalTypes/dateType.d.ts.map +1 -1
  6. package/dist/index.js +2 -2
  7. package/dist/index.js.map +1 -1
  8. package/dist/search/fundamentalTypes/arraySearchType.d.ts +11 -4
  9. package/dist/search/fundamentalTypes/arraySearchType.d.ts.map +1 -1
  10. package/dist/search/fundamentalTypes/bigintSearchType.d.ts.map +1 -1
  11. package/dist/search/fundamentalTypes/blobSearchType.d.ts.map +1 -1
  12. package/dist/search/fundamentalTypes/dateSearchType.d.ts +8 -3
  13. package/dist/search/fundamentalTypes/dateSearchType.d.ts.map +1 -1
  14. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts +5 -5
  15. package/dist/search/fundamentalTypes/domexceptionSearchType.d.ts.map +1 -1
  16. package/dist/search/fundamentalTypes/enumSearchType.d.ts +4 -0
  17. package/dist/search/fundamentalTypes/enumSearchType.d.ts.map +1 -1
  18. package/dist/search/fundamentalTypes/fileSearchType.d.ts +19 -10
  19. package/dist/search/fundamentalTypes/fileSearchType.d.ts.map +1 -1
  20. package/dist/search/fundamentalTypes/filelistSearchType.d.ts +4 -12
  21. package/dist/search/fundamentalTypes/filelistSearchType.d.ts.map +1 -1
  22. package/dist/search/fundamentalTypes/functionSearchType.d.ts +4 -4
  23. package/dist/search/fundamentalTypes/functionSearchType.d.ts.map +1 -1
  24. package/dist/search/fundamentalTypes/mapSearchType.d.ts +8 -4
  25. package/dist/search/fundamentalTypes/mapSearchType.d.ts.map +1 -1
  26. package/dist/search/fundamentalTypes/numberSearchType.d.ts.map +1 -1
  27. package/dist/search/fundamentalTypes/objectSearchType.d.ts +10 -4
  28. package/dist/search/fundamentalTypes/objectSearchType.d.ts.map +1 -1
  29. package/dist/search/fundamentalTypes/regexpSearchType.d.ts +7 -1
  30. package/dist/search/fundamentalTypes/regexpSearchType.d.ts.map +1 -1
  31. package/dist/search/fundamentalTypes/setSearchType.d.ts +5 -4
  32. package/dist/search/fundamentalTypes/setSearchType.d.ts.map +1 -1
  33. package/dist/search/fundamentalTypes/stringSearchType.d.ts +9 -1
  34. package/dist/search/fundamentalTypes/stringSearchType.d.ts.map +1 -1
  35. package/dist/search/fundamentalTypes/symbolSearchType.d.ts.map +1 -1
  36. package/dist/search/index.d.ts +18 -8
  37. package/dist/search/index.d.ts.map +1 -1
  38. package/dist/search/queryTree.d.ts +12 -1
  39. package/dist/search/queryTree.d.ts.map +1 -1
  40. package/dist/search/queryTreeBuilders.d.ts +6 -0
  41. package/dist/search/queryTreeBuilders.d.ts.map +1 -1
  42. package/dist/search/searchElementUtils.d.ts +17 -8
  43. package/dist/search/searchElementUtils.d.ts.map +1 -1
  44. package/dist/search/searchUtils.d.ts +343 -18
  45. package/dist/search/searchUtils.d.ts.map +1 -1
  46. package/dist/search/subTypes/blobHTMLSearchType.d.ts +22 -2
  47. package/dist/search/subTypes/blobHTMLSearchType.d.ts.map +1 -1
  48. package/dist/search/subTypes/recordSearchType.d.ts +4 -4
  49. package/dist/search/subTypes/recordSearchType.d.ts.map +1 -1
  50. package/dist/search/subTypes/tupleSearchType.d.ts +3 -3
  51. package/dist/search/subTypes/tupleSearchType.d.ts.map +1 -1
  52. package/dist/search/superTypes/buffersourceSearchType.d.ts.map +1 -1
  53. package/dist/search/unions/unionFamilySearchType.d.ts.map +1 -1
  54. package/package.json +1 -1
  55. package/src/fundamentalTypes/dateType.js +9 -4
  56. package/src/fundamentalTypes/regexpType.js +1 -1
  57. package/src/jsoe.css +204 -0
  58. package/src/search/fundamentalTypes/arraySearchType.js +42 -7
  59. package/src/search/fundamentalTypes/bigintSearchType.js +7 -1
  60. package/src/search/fundamentalTypes/blobSearchType.js +2 -1
  61. package/src/search/fundamentalTypes/booleanSearchType.js +1 -1
  62. package/src/search/fundamentalTypes/dateSearchType.js +113 -20
  63. package/src/search/fundamentalTypes/domexceptionSearchType.js +39 -10
  64. package/src/search/fundamentalTypes/enumSearchType.js +5 -1
  65. package/src/search/fundamentalTypes/fileSearchType.js +64 -11
  66. package/src/search/fundamentalTypes/filelistSearchType.js +32 -7
  67. package/src/search/fundamentalTypes/functionSearchType.js +36 -12
  68. package/src/search/fundamentalTypes/mapSearchType.js +43 -13
  69. package/src/search/fundamentalTypes/numberSearchType.js +8 -1
  70. package/src/search/fundamentalTypes/objectSearchType.js +236 -17
  71. package/src/search/fundamentalTypes/regexpSearchType.js +29 -5
  72. package/src/search/fundamentalTypes/setSearchType.js +33 -7
  73. package/src/search/fundamentalTypes/stringSearchType.js +11 -2
  74. package/src/search/fundamentalTypes/symbolSearchType.js +2 -1
  75. package/src/search/index.js +26 -9
  76. package/src/search/queryTree.js +10 -1
  77. package/src/search/queryTreeBuilders.js +9 -0
  78. package/src/search/searchElementUtils.js +108 -24
  79. package/src/search/searchUtils.js +516 -24
  80. package/src/search/subTypes/blobHTMLSearchType.js +64 -7
  81. package/src/search/subTypes/recordSearchType.js +34 -13
  82. package/src/search/subTypes/tupleSearchType.js +51 -10
  83. package/src/search/superTypes/SpecialRealNumberSearchType.js +1 -1
  84. package/src/search/superTypes/buffersourceSearchType.js +7 -1
  85. package/src/search/unions/unionFamilySearchType.js +8 -0
@@ -1,4 +1,6 @@
1
- import {buildPathLabel, findOwnControl} from '../searchUtils.js';
1
+ import {
2
+ buildPathLabel, findOwnControl, isExemptedByAncestorHasProperty
3
+ } from '../searchUtils.js';
2
4
  import {makeBlobHTMLLeaf} from '../queryTreeBuilders.js';
3
5
  import {getQueryViaElement} from '../searchElementUtils.js';
4
6
 
@@ -8,11 +10,31 @@ import {getQueryViaElement} from '../searchElementUtils.js';
8
10
 
9
11
  /**
10
12
  * Blob HTML: XPath, CSS selectors, full text search, regex search of raw
11
- * HTML (README) - a mode `<select>` plus one text `<input>`, the same
13
+ * HTML (README) - a mode `<select>` plus one value control, the same
12
14
  * "pick a mode, give it one value" shape as `buildLiteralRegexControls`
13
15
  * (`searchUtils.js`), but not built through it: the four modes and the
14
16
  * `blobHTML` leaf kind they produce are specific to this one type, with no
15
- * second caller to share the helper with.
17
+ * second caller to share the helper with. The value control is a single-line
18
+ * `<input>` for XPath/CSS-selector/regex, but swaps to a `<textarea>` for
19
+ * "Full text search" mode alone, since a search phrase in a body of text is
20
+ * the one mode actually expected to run to more than one line - both share
21
+ * the same class, so the mode `<select>`'s `change` handler (and `getQuery`)
22
+ * only need the tag name to tell them apart. Only the currently-visible one
23
+ * of the pair is ever `required` (toggled alongside `hidden` by the mode
24
+ * `change` handler): a `required` field that's merely hidden, rather than
25
+ * also un-required, still blocks the form's validity even though the user
26
+ * has no way to see or fill it.
27
+ *
28
+ * The `change` handler also checks `isExemptedByAncestorHasProperty` before
29
+ * (re-)asserting `required` on the now-visible control: nested as an
30
+ * optional object property's own child widget, an enclosing "Has property"
31
+ * already provides a complete constraint on its own (the same relief
32
+ * `setDescendantsRequired` gives other `required`-attribute controls), but
33
+ * that relief is a one-time toggle applied *before* `revalidateDescendants`
34
+ * re-dispatches a synthetic `change` on this very `<select>` - without this
35
+ * check, that synthetic event would immediately reassert `required` on
36
+ * whichever control the current mode makes visible, undoing the exemption
37
+ * `setDescendantsRequired` had just granted.
16
38
  * @type {SearchTypeObject}
17
39
  */
18
40
  const blobHTMLSearchType = {
@@ -29,8 +51,11 @@ const blobHTMLSearchType = {
29
51
  const mode = /** @type {HTMLSelectElement|undefined} */ (
30
52
  findOwnControl(this, 'select.jsoeSearchBlobHTMLMode')
31
53
  )?.value;
32
- const value = /** @type {HTMLInputElement|undefined} */ (
33
- findOwnControl(this, 'input.jsoeSearchBlobHTMLValue')
54
+ const value = /** @type {HTMLInputElement|HTMLTextAreaElement|undefined} */ (
55
+ findOwnControl(
56
+ this,
57
+ `${mode === 'fullText' ? 'textarea' : 'input'}.jsoeSearchBlobHTMLValue`
58
+ )
34
59
  )?.value;
35
60
  if (!value || !mode) {
36
61
  return undefined;
@@ -46,7 +71,33 @@ const blobHTMLSearchType = {
46
71
  ['span', {class: 'searchLabel'}, [label]],
47
72
  ['label', [
48
73
  'Mode: ',
49
- ['select', {name: `${name}-mode`, class: 'jsoeSearchBlobHTMLMode'}, [
74
+ ['select', {
75
+ name: `${name}-mode`,
76
+ class: 'jsoeSearchBlobHTMLMode',
77
+ $on: {
78
+ change () {
79
+ const container = this.closest('jsoe-search-blob-html');
80
+ const input = /** @type {HTMLInputElement|null|undefined} */ (
81
+ container?.querySelector('input.jsoeSearchBlobHTMLValue')
82
+ );
83
+ const textarea = /** @type {HTMLTextAreaElement|null|undefined} */ (
84
+ container?.querySelector('textarea.jsoeSearchBlobHTMLValue')
85
+ );
86
+ const isFullText = /** @type {HTMLSelectElement} */ (this).value ===
87
+ 'fullText';
88
+ const exempted = container !== null &&
89
+ isExemptedByAncestorHasProperty(container);
90
+ if (input) {
91
+ input.hidden = isFullText;
92
+ input.required = !isFullText && !exempted;
93
+ }
94
+ if (textarea) {
95
+ textarea.hidden = !isFullText;
96
+ textarea.required = isFullText && !exempted;
97
+ }
98
+ }
99
+ }
100
+ }, [
50
101
  ['option', {value: 'xpath'}, ['XPath']],
51
102
  ['option', {value: 'cssSelector'}, ['CSS selector']],
52
103
  ['option', {value: 'fullText'}, ['Full text search']],
@@ -55,7 +106,13 @@ const blobHTMLSearchType = {
55
106
  ]],
56
107
  ['label', [
57
108
  'Value: ',
58
- ['input', {type: 'text', name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue'}]
109
+ ['input', {
110
+ type: 'text', name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue',
111
+ required: true
112
+ }],
113
+ ['textarea', {
114
+ name: `${name}-value`, class: 'jsoeSearchBlobHTMLValue', hidden: true
115
+ }]
59
116
  ]]
60
117
  ]];
61
118
  },
@@ -1,4 +1,7 @@
1
- import {buildPathLabel, buildCheckbox, readCheckbox} from '../searchUtils.js';
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
- ['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'})
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 {buildPathLabel, buildLengthSizeControls, readLengthSizeQuery} from '../searchUtils.js';
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(['div', {class: 'searchTupleItem'}, [
58
- ['span', [`Position ${idx}: `]],
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
- ['div', {class: 'searchTupleRest'}, [
68
- ['span', ['Rest element matches: ']],
69
- /** @type {import('../../types.js').JamilihArray} */ (restArr)
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]);
@@ -32,7 +32,7 @@ const SpecialRealNumberSearchType = {
32
32
  }, [
33
33
  ['span', {class: 'searchLabel'}, [label]],
34
34
  buildMultiSelect({
35
- name, options: ['Infinity', '-Infinity', '-0']
35
+ name, options: ['Infinity', '-Infinity', '-0'], required: true
36
36
  })
37
37
  ]];
38
38
  },
@@ -1,4 +1,6 @@
1
- import {buildPathLabel, buildRangeInputsPair, readRangeInputsPair} from '../searchUtils.js';
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)) {