@ukhomeoffice/formio-gds-template 2.0.2 → 3.0.0-bravo

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 (150) hide show
  1. package/.github/workflows/build-and-test.yml +3 -3
  2. package/LICENCE.md +21 -0
  3. package/README.md +8 -0
  4. package/eslint.config.mjs +59 -0
  5. package/lib/components/datamap/GDSDataMap.d.ts +5 -14
  6. package/lib/components/datamap/GDSDataMap.d.ts.map +1 -0
  7. package/lib/components/datamap/GDSDataMap.js +9 -29
  8. package/lib/components/datetime/GDSDatetimeComponent.d.ts +43 -22
  9. package/lib/components/datetime/GDSDatetimeComponent.d.ts.map +1 -0
  10. package/lib/components/datetime/GDSDatetimeComponent.js +167 -178
  11. package/lib/components/index.d.ts +3 -0
  12. package/lib/components/index.d.ts.map +1 -0
  13. package/lib/components/index.js +6 -4
  14. package/lib/components/select/GDSSelect.d.ts +143 -0
  15. package/lib/components/select/GDSSelect.d.ts.map +1 -0
  16. package/lib/components/select/GDSSelect.js +340 -0
  17. package/lib/components/selectboxes/GDSCheckBoxes.d.ts +5 -2
  18. package/lib/components/selectboxes/GDSCheckBoxes.d.ts.map +1 -0
  19. package/lib/components/selectboxes/GDSCheckBoxes.js +7 -33
  20. package/lib/components/time/GDSTimeComponent.d.ts +13 -10
  21. package/lib/components/time/GDSTimeComponent.d.ts.map +1 -0
  22. package/lib/components/time/GDSTimeComponent.js +59 -84
  23. package/lib/components/util/TimeHelper.d.ts +6 -4
  24. package/lib/components/util/TimeHelper.d.ts.map +1 -0
  25. package/lib/components/util/TimeHelper.js +13 -16
  26. package/lib/index.d.ts +4 -2
  27. package/lib/index.d.ts.map +1 -0
  28. package/lib/index.js +2 -2
  29. package/lib/templates/gds/button/index.d.ts +1 -0
  30. package/lib/templates/gds/button/index.d.ts.map +1 -0
  31. package/lib/templates/gds/button/index.js +1 -1
  32. package/lib/templates/gds/checkbox/index.d.ts +1 -0
  33. package/lib/templates/gds/checkbox/index.d.ts.map +1 -0
  34. package/lib/templates/gds/checkbox/index.js +1 -1
  35. package/lib/templates/gds/columns/index.d.ts +1 -0
  36. package/lib/templates/gds/columns/index.d.ts.map +1 -0
  37. package/lib/templates/gds/columns/index.js +1 -1
  38. package/lib/templates/gds/component/index.d.ts +1 -0
  39. package/lib/templates/gds/component/index.d.ts.map +1 -0
  40. package/lib/templates/gds/component/index.js +1 -1
  41. package/lib/templates/gds/container/index.d.ts +1 -0
  42. package/lib/templates/gds/container/index.d.ts.map +1 -0
  43. package/lib/templates/gds/container/index.js +1 -1
  44. package/lib/templates/gds/cssClasses.d.ts +1 -0
  45. package/lib/templates/gds/cssClasses.d.ts.map +1 -0
  46. package/lib/templates/gds/datagrid/form.ejs.js +2 -2
  47. package/lib/templates/gds/datagrid/index.d.ts +1 -0
  48. package/lib/templates/gds/datagrid/index.d.ts.map +1 -0
  49. package/lib/templates/gds/datagrid/index.js +1 -1
  50. package/lib/templates/gds/datamap/index.d.ts +1 -0
  51. package/lib/templates/gds/datamap/index.d.ts.map +1 -0
  52. package/lib/templates/gds/datamap/index.js +1 -1
  53. package/lib/templates/gds/datetime/index.d.ts +1 -0
  54. package/lib/templates/gds/datetime/index.d.ts.map +1 -0
  55. package/lib/templates/gds/datetime/index.js +1 -1
  56. package/lib/templates/gds/day/index.d.ts +1 -0
  57. package/lib/templates/gds/day/index.d.ts.map +1 -0
  58. package/lib/templates/gds/day/index.js +1 -1
  59. package/lib/templates/gds/editgrid/index.d.ts +1 -0
  60. package/lib/templates/gds/editgrid/index.d.ts.map +1 -0
  61. package/lib/templates/gds/editgrid/index.js +1 -1
  62. package/lib/templates/gds/field/index.d.ts +1 -0
  63. package/lib/templates/gds/field/index.d.ts.map +1 -0
  64. package/lib/templates/gds/field/index.js +1 -1
  65. package/lib/templates/gds/fieldset/index.d.ts +1 -0
  66. package/lib/templates/gds/fieldset/index.d.ts.map +1 -0
  67. package/lib/templates/gds/fieldset/index.js +1 -1
  68. package/lib/templates/gds/file/index.d.ts +1 -0
  69. package/lib/templates/gds/file/index.d.ts.map +1 -0
  70. package/lib/templates/gds/file/index.js +1 -1
  71. package/lib/templates/gds/iconClass.d.ts +2 -1
  72. package/lib/templates/gds/iconClass.d.ts.map +1 -0
  73. package/lib/templates/gds/iconClass.js +3 -3
  74. package/lib/templates/gds/index.d.ts +3 -2
  75. package/lib/templates/gds/index.d.ts.map +1 -0
  76. package/lib/templates/gds/index.js +72 -64
  77. package/lib/templates/gds/input/index.d.ts +1 -0
  78. package/lib/templates/gds/input/index.d.ts.map +1 -0
  79. package/lib/templates/gds/input/index.js +1 -1
  80. package/lib/templates/gds/label/index.d.ts +1 -0
  81. package/lib/templates/gds/label/index.d.ts.map +1 -0
  82. package/lib/templates/gds/label/index.js +1 -1
  83. package/lib/templates/gds/message/index.d.ts +1 -0
  84. package/lib/templates/gds/message/index.d.ts.map +1 -0
  85. package/lib/templates/gds/message/index.js +1 -1
  86. package/lib/templates/gds/panel/index.d.ts +1 -0
  87. package/lib/templates/gds/panel/index.d.ts.map +1 -0
  88. package/lib/templates/gds/panel/index.js +1 -1
  89. package/lib/templates/gds/radio/index.d.ts +1 -0
  90. package/lib/templates/gds/radio/index.d.ts.map +1 -0
  91. package/lib/templates/gds/radio/index.js +1 -1
  92. package/lib/templates/gds/select/index.d.ts +1 -0
  93. package/lib/templates/gds/select/index.d.ts.map +1 -0
  94. package/lib/templates/gds/select/index.js +1 -1
  95. package/lib/templates/gds/selectOption/index.d.ts +1 -0
  96. package/lib/templates/gds/selectOption/index.d.ts.map +1 -0
  97. package/lib/templates/gds/selectOption/index.js +1 -1
  98. package/lib/templates/gds/selectboxes/index.d.ts +1 -0
  99. package/lib/templates/gds/selectboxes/index.d.ts.map +1 -0
  100. package/lib/templates/gds/selectboxes/index.js +1 -1
  101. package/lib/templates/gds/survey/index.d.ts +1 -0
  102. package/lib/templates/gds/survey/index.d.ts.map +1 -0
  103. package/lib/templates/gds/survey/index.js +1 -1
  104. package/lib/templates/gds/tab/index.d.ts +1 -0
  105. package/lib/templates/gds/tab/index.d.ts.map +1 -0
  106. package/lib/templates/gds/tab/index.js +1 -1
  107. package/lib/templates/gds/table/index.d.ts +1 -0
  108. package/lib/templates/gds/table/index.d.ts.map +1 -0
  109. package/lib/templates/gds/table/index.js +1 -1
  110. package/lib/templates/gds/template.css +27 -0
  111. package/lib/templates/gds/template.css.map +1 -1
  112. package/lib/templates/gds/time/index.d.ts +1 -0
  113. package/lib/templates/gds/time/index.d.ts.map +1 -0
  114. package/lib/templates/gds/time/index.js +1 -1
  115. package/lib/templates/gds/warning/index.d.ts +1 -0
  116. package/lib/templates/gds/warning/index.d.ts.map +1 -0
  117. package/lib/templates/gds/warning/index.js +1 -1
  118. package/lib/templates/gds/wizard/index.d.ts +1 -0
  119. package/lib/templates/gds/wizard/index.d.ts.map +1 -0
  120. package/lib/templates/gds/wizard/index.js +1 -1
  121. package/lib/templates/gds/wizardHeader/index.d.ts +1 -0
  122. package/lib/templates/gds/wizardHeader/index.d.ts.map +1 -0
  123. package/lib/templates/gds/wizardHeader/index.js +1 -1
  124. package/lib/templates/gds/wizardNav/index.d.ts +1 -0
  125. package/lib/templates/gds/wizardNav/index.d.ts.map +1 -0
  126. package/lib/templates/gds/wizardNav/index.js +1 -1
  127. package/lib/templates/index.d.ts +3 -2
  128. package/lib/templates/index.d.ts.map +1 -0
  129. package/lib/templates/index.js +1 -1
  130. package/lib/types.d.ts +37 -0
  131. package/lib/types.d.ts.map +1 -0
  132. package/lib/types.js +2 -0
  133. package/package.json +14 -7
  134. package/src/components/datamap/GDSDataMap.ts +5 -14
  135. package/src/components/datetime/GDSDatetimeComponent.ts +128 -91
  136. package/src/components/index.ts +2 -0
  137. package/src/components/select/GDSSelect.spec.ts +538 -0
  138. package/src/components/select/GDSSelect.ts +418 -0
  139. package/src/components/selectboxes/GDSCheckBoxes.ts +7 -8
  140. package/src/components/time/GDSTimeComponent.ts +37 -27
  141. package/src/components/util/TimeHelper.ts +6 -5
  142. package/src/css.d.ts +4 -0
  143. package/src/templates/gds/datagrid/form.ejs +1 -2
  144. package/src/templates/gds/iconClass.ts +1 -1
  145. package/src/templates/gds/index.ts +43 -33
  146. package/src/templates/gds/template.scss +48 -1
  147. package/src/types.ts +56 -0
  148. package/tsconfig.json +21 -5
  149. package/webpack.config.js +1 -1
  150. package/tslint.json +0 -22
@@ -0,0 +1,340 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const js_1 = require("@formio/js");
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const Select = js_1.Components.components.select;
9
+ class GDSSelect extends Select {
10
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
11
+ constructor(component, options, data) {
12
+ super(component, options, data);
13
+ }
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ init() {
16
+ var _a, _b;
17
+ // Clear searchField so Form.io does not trigger server-side queries while
18
+ // the complete reference dataset is filtered locally.
19
+ if (this.component.searchField) {
20
+ this.component.searchField = '';
21
+ }
22
+ // Reference-data selects are filtered locally. Do not truncate the
23
+ // alphabetically sorted API response before local filtering.
24
+ const url = (_a = this.component.data) === null || _a === void 0 ? void 0 : _a.url;
25
+ if (typeof url === 'string' && url.includes('/v2/entities/') && ((_b = this.component.limit) !== null && _b !== void 0 ? _b : 100) < 1000) {
26
+ this.component.disableLimit = true;
27
+ }
28
+ return super.init();
29
+ }
30
+ /**
31
+ * Patch the choices.js instance attached to this component so that every
32
+ * search query uses a plain case-insensitive substring filter instead of
33
+ * fuse.js. The patch is applied lazily the first time items are set so
34
+ * that `this.choices` is guaranteed to exist.
35
+ */
36
+ _patchChoicesSearch() {
37
+ const choices = this.choices;
38
+ if (!choices || choices === this._gdsSearchPatchedChoices) {
39
+ return;
40
+ }
41
+ this._gdsSearchPatchedChoices = choices;
42
+ // Replace the internal search method with a substring implementation.
43
+ choices._searchChoices = (needle) => {
44
+ var _a;
45
+ if (!needle) {
46
+ choices._isSearching = false;
47
+ choices._currentValue = '';
48
+ choices._store.dispatch({ type: 'ACTIVATE_CHOICES', active: true });
49
+ return 0;
50
+ }
51
+ const normalizedNeedle = needle.trim().toLowerCase().replace(/\s{2,}/g, ' ');
52
+ if (!normalizedNeedle || normalizedNeedle === choices._currentValue) {
53
+ return null;
54
+ }
55
+ const words = normalizedNeedle.split(/\s+/).filter(Boolean);
56
+ const allChoices = choices._store.choices;
57
+ const matched = [];
58
+ const prefix = [];
59
+ const contains = [];
60
+ for (const choice of allChoices) {
61
+ const renderedLabel = ((_a = choice.choiceEl) === null || _a === void 0 ? void 0 : _a.textContent) || '';
62
+ const label = [choice.label, renderedLabel, choice.value]
63
+ .map((value) => String(value !== null && value !== void 0 ? value : '').toLowerCase())
64
+ .join(' ');
65
+ const allMatch = words.every((w) => label.includes(w));
66
+ if (!allMatch) {
67
+ choice.active = false;
68
+ choice.rank = 0;
69
+ continue;
70
+ }
71
+ if (label.startsWith(normalizedNeedle)) {
72
+ prefix.push(choice);
73
+ }
74
+ else {
75
+ contains.push(choice);
76
+ }
77
+ }
78
+ // Prefix matches first, then other substring matches, both sorted
79
+ // alphabetically within their group.
80
+ prefix.sort((a, b) => a.label.localeCompare(b.label));
81
+ contains.sort((a, b) => a.label.localeCompare(b.label));
82
+ [...prefix, ...contains].forEach((choice, index) => {
83
+ matched.push({
84
+ item: choice,
85
+ score: 0,
86
+ rank: index + 1,
87
+ });
88
+ });
89
+ choices._isSearching = true;
90
+ choices._currentValue = normalizedNeedle;
91
+ choices._store.dispatch({ type: 'FILTER_CHOICES', results: matched });
92
+ return matched.length;
93
+ };
94
+ }
95
+ /**
96
+ * True when this select's `filter` is interpolated from other form data
97
+ * (e.g. a port list filtered by the selected country).
98
+ */
99
+ get _gdsHasDynamicFilter() {
100
+ return typeof this.component.filter === 'string' && this.component.filter.includes('{{');
101
+ }
102
+ /** The interpolated filter, used to detect when the dependency changed. */
103
+ _gdsCurrentFilterKey() {
104
+ return this._gdsHasDynamicFilter ? this.interpolate(this.component.filter) : '';
105
+ }
106
+ // Preserve Form.io's item lifecycle, then patch the Choices instance that
107
+ // Form.io has populated.
108
+ setItems(items, fromSearch) {
109
+ super.setItems(items, fromSearch);
110
+ if (!fromSearch) {
111
+ this._gdsFilterKey = this._gdsCurrentFilterKey();
112
+ }
113
+ this._patchChoicesSearch();
114
+ }
115
+ // Also patch on attach in case setItems was already called.
116
+ attach(element) {
117
+ const result = super.attach(element);
118
+ this._patchChoicesSearch();
119
+ return Promise.resolve(result);
120
+ }
121
+ // Form.io only refreshes a URL select with an interpolated filter when the
122
+ // component is redrawn, or when it transitions hidden -> visible (see the
123
+ // `visible` setter in Select). A select that is already visible when its
124
+ // filter dependency changes is therefore never re-fetched, leaving stale
125
+ // options (e.g. changing country a second time keeps the first country's
126
+ // ports). checkComponentConditions runs for every component on every data
127
+ // change, so compare the interpolated filter here and reload when it moves.
128
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
129
+ checkComponentConditions(data, flags, row) {
130
+ var _a;
131
+ const visible = super.checkComponentConditions(data, flags, row);
132
+ // `_gdsFilterKey` is only set once options have been loaded, so this
133
+ // cannot force a premature fetch for a lazyLoad select that the user has
134
+ // not opened yet. Note `active` is deliberately not checked: Form.io's
135
+ // `visible` setter calls triggerUpdate() without calling activate(), so
136
+ // a loaded lazyLoad select can still report active === false.
137
+ if (visible &&
138
+ this.component.dataSrc === 'url' &&
139
+ this._gdsHasDynamicFilter &&
140
+ this._gdsFilterKey !== undefined &&
141
+ !this.loading) {
142
+ const filterKey = this._gdsCurrentFilterKey();
143
+ if (filterKey !== this._gdsFilterKey) {
144
+ // Record before requesting so a change that arrives while the
145
+ // request is in flight does not queue a duplicate fetch.
146
+ this._gdsFilterKey = filterKey;
147
+ (_a = this.triggerUpdate) === null || _a === void 0 ? void 0 : _a.call(this, null, true);
148
+ }
149
+ }
150
+ return visible;
151
+ }
152
+ // formio's redraw() and activate() both call triggerUpdate() -> updateItems(null, false),
153
+ // which would cause a URL re-fetch even when items are already cached. We suppress
154
+ // the re-fetch when the cache is valid, but we must still re-populate choices.js
155
+ // with the cached items. This matters because activate() calls setLoadingItem()
156
+ // before triggerUpdate(), which replaces the choices list with a spinner. Without
157
+ // re-calling setItems the spinner stays even though data is already available.
158
+ //
159
+ // Filter-dependency changes (refreshOn) always arrive with forceUpdate=true via
160
+ // Select.refresh(), so they bypass this guard and fetch fresh data as expected.
161
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
+ updateItems(searchInput, forceUpdate) {
163
+ if (!forceUpdate &&
164
+ !searchInput &&
165
+ this.component.dataSrc === 'url' &&
166
+ this._gdsFilterKey === this._gdsCurrentFilterKey() &&
167
+ Array.isArray(this.downloadedResources) &&
168
+ this.downloadedResources.length > 0 &&
169
+ !this.loading) {
170
+ // Re-populate choices from the cache so any spinner set by activate()
171
+ // is replaced with the actual items, then resolve the loaded promise.
172
+ this.setItems(this.downloadedResources, false);
173
+ return;
174
+ }
175
+ return super.updateItems(searchInput, forceUpdate);
176
+ }
177
+ /**
178
+ * Override getOptionTemplate to escape item data values with `_.escape()`
179
+ * before interpolating them into the template.
180
+ *
181
+ * ## Security model
182
+ *
183
+ * formio 5.x `getOptionTemplate` (ListComponent) passes the assembled template
184
+ * string through `DOMPurify.sanitize()` — which internally calls
185
+ * `DOMParser.parseFromString()`. With thousands of reference-data items this
186
+ * is the dominant performance cost (~300 ms).
187
+ *
188
+ * We replace that post-hoc sanitization with pre-interpolation escaping:
189
+ *
190
+ * 1. Every string value in the item object is passed through `_.escape()`
191
+ * **before** it is substituted into the template. `_.escape` converts
192
+ * `< > & " '` to their HTML-entity equivalents (`&lt;` etc.).
193
+ * 2. The assembled template string — now containing only safe entity-encoded
194
+ * text where user/API data appears — is returned directly without any
195
+ * further DOMPurify call.
196
+ *
197
+ * ### Why this is equivalent security to DOMPurify for text-content templates
198
+ *
199
+ * A template such as `<span>{{ item.name }}</span>` embeds the API value as
200
+ * the *text content* of an element. The only injection vector is:
201
+ *
202
+ * ```
203
+ * item.name = '</span><script>alert(1)</script>'
204
+ * ```
205
+ *
206
+ * After `_.escape()` this becomes:
207
+ *
208
+ * ```
209
+ * &lt;/span&gt;&lt;script&gt;alert(1)&lt;/script&gt;
210
+ * ```
211
+ *
212
+ * The browser renders this as literal text, never as markup or script.
213
+ * DOMPurify would produce the same effective result by stripping the tags.
214
+ *
215
+ * ### Limitation — attribute / URL contexts
216
+ *
217
+ * `_.escape()` is **not** sufficient if the template places a value inside
218
+ * an HTML attribute or a URL (e.g. `<a href="{{ item.url }}">`).
219
+ * HTML-entity encoding does not prevent `javascript:` injection in `href`.
220
+ * If any form schema introduces such a template, this override must be
221
+ * revisited and either DOMPurify reinstated or the attribute value
222
+ * sanitized separately.
223
+ *
224
+ * Current GDS templates are all text-content only (`<span>{{ item.name }}</span>`
225
+ * etc.) so this limitation does not apply today.
226
+ */
227
+ getOptionTemplate(data, value, index) {
228
+ var _a, _b;
229
+ if (!this.component.template) {
230
+ return String((_a = data.label) !== null && _a !== void 0 ? _a : '');
231
+ }
232
+ const options = { noeval: true, data: {} };
233
+ // Escape all top-level string values in the item before interpolation.
234
+ // Nested objects are left as-is; they would need to be accessed via
235
+ // item.nested.field in the template, which is not used by current schemas.
236
+ const escapedData = lodash_1.default.mapValues(data, (v) => (typeof v === 'string' ? lodash_1.default.escape(v) : v));
237
+ const template = this.component.template
238
+ ? this.interpolate(this.component.template, { item: escapedData }, options)
239
+ : String((_b = data.label) !== null && _b !== void 0 ? _b : '');
240
+ // Store template data for reference-field selected-value display.
241
+ // The `reference` flag is not in the generated CoreSelectComponent type
242
+ // but is a valid runtime formio property.
243
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
244
+ const isReference = Boolean(this.component.reference);
245
+ const rawId = value === null || value === void 0 ? void 0 : value._id;
246
+ const templateValue = isReference && rawId != null
247
+ ? String(rawId)
248
+ : value;
249
+ if (templateValue != null &&
250
+ !lodash_1.default.isObject(templateValue) &&
251
+ options.data.item) {
252
+ this.templateData[templateValue] = options.data.item;
253
+ }
254
+ if (lodash_1.default.isNumber(index)) {
255
+ this.templateData[index] = options.data.item;
256
+ }
257
+ return template;
258
+ }
259
+ /**
260
+ * Override itemTemplate to skip the redundant outer `sanitize()` call that
261
+ * formio 5.x `ListComponent.itemTemplate` wraps around `getOptionTemplate()`.
262
+ *
263
+ * ## Why the outer sanitize is now safe to skip
264
+ *
265
+ * Our `getOptionTemplate` override (above) pre-escapes all API data values
266
+ * with `_.escape()` before they are interpolated into the template. The
267
+ * assembled string therefore contains no unescaped HTML from variable sources.
268
+ * Running DOMPurify on that string a second time is idempotent — it produces
269
+ * the same output — but costs a full `DOMParser.parseFromString` call per item.
270
+ *
271
+ * This override handles only the common URL-datasource path (object data with
272
+ * a component template). All other branches — primitives, arrays, read-only,
273
+ * selectData, reference fields — fall through to `super.itemTemplate` which
274
+ * retains its full sanitize() call unchanged.
275
+ */
276
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
277
+ itemTemplate(data, value, index) {
278
+ if (typeof data === 'object' &&
279
+ data !== null &&
280
+ !Array.isArray(data) &&
281
+ this.component.template) {
282
+ // getOptionTemplate pre-escapes data values — no further sanitize needed.
283
+ const template = this.getOptionTemplate(data, value, index);
284
+ if (!template) {
285
+ return this.sanitize(JSON.stringify(data), this.shouldSanitizeValue);
286
+ }
287
+ // Strip tags to extract the visible label text for i18n translation,
288
+ // then re-embed it. This mirrors ListComponent.itemTemplate exactly.
289
+ const label = template.replace(/<\/?[^>]+(>|$)/g, '');
290
+ if (!label)
291
+ return template;
292
+ return template.replace(label, this.t(label, { _userInput: true }));
293
+ }
294
+ // Fall back to the full Select → ListComponent chain for all other cases.
295
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
296
+ return super.itemTemplate(data, value, index);
297
+ }
298
+ // Disable Choices' fuzzy search and use the deterministic matcher above.
299
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
300
+ choicesOptions() {
301
+ const options = super.choicesOptions();
302
+ // Force Choices.js client-side searching.
303
+ // The instance method is replaced in _patchChoicesSearch with the GDS matcher.
304
+ options.searchChoices = true;
305
+ options.searchFloor = 0;
306
+ options.searchFields = ['label', 'value'];
307
+ // If searchResultLimit is configured too low, raise it to at least limit or 100.
308
+ const itemLimit = this.component.limit || 100;
309
+ if (typeof options.searchResultLimit === 'number' && options.searchResultLimit < itemLimit) {
310
+ options.searchResultLimit = itemLimit;
311
+ }
312
+ if (typeof options.renderChoiceLimit === 'number' && options.renderChoiceLimit < itemLimit) {
313
+ options.renderChoiceLimit = itemLimit;
314
+ }
315
+ // formio's default valueComparer is lodash.isEqual — a deep recursive comparison.
316
+ // choices.js calls this for every choice in the store when resolving which item
317
+ // is selected (setChoices / _isSelectedByValue). With thousands of object values
318
+ // {id, name} this produces O(n × depth) recursive comparisons (~5 s).
319
+ //
320
+ // For reference-data objects we compare by `id` alone: each port/currency/etc.
321
+ // has a unique id, so two values are equal iff their ids match — O(1) per call.
322
+ // Primitives fall back to ===, matching choices.js's own default.
323
+ options.valueComparer = (a, b) => {
324
+ if (a === b)
325
+ return true;
326
+ if (a == null || b == null)
327
+ return false;
328
+ if (typeof a === 'object' && typeof b === 'object') {
329
+ const aId = a.id;
330
+ const bId = b.id;
331
+ if (aId !== undefined && bId !== undefined) {
332
+ return aId === bId;
333
+ }
334
+ }
335
+ return false;
336
+ };
337
+ return options;
338
+ }
339
+ }
340
+ exports.default = GDSSelect;
@@ -1,5 +1,8 @@
1
- declare const SelectBoxes: import("formiojs").ClassWithEditForm<typeof import("formiojs/types/components/_classes/field/field").Field>;
1
+ import { SelectBoxesComponent } from '../../types';
2
+ declare const SelectBoxes: any;
2
3
  export default class GDSCheckBoxes extends SelectBoxes {
3
- render(): any;
4
+ component: SelectBoxesComponent;
5
+ render(): string;
4
6
  }
5
7
  export {};
8
+ //# sourceMappingURL=GDSCheckBoxes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GDSCheckBoxes.d.ts","sourceRoot":"","sources":["../../../src/components/selectboxes/GDSCheckBoxes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAGnD,QAAA,MAAM,WAAW,KAAoC,CAAC;AAEtD,MAAM,CAAC,OAAO,OAAO,aAAc,SAAQ,WAAW;IAE1C,SAAS,EAAE,oBAAoB,CAAC;IAEjC,MAAM,IAAI,MAAM;CAS1B"}
@@ -1,43 +1,17 @@
1
1
  "use strict";
2
- var __extends = (this && this.__extends) || (function () {
3
- var extendStatics = function (d, b) {
4
- extendStatics = Object.setPrototypeOf ||
5
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
- function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
- return extendStatics(d, b);
8
- };
9
- return function (d, b) {
10
- if (typeof b !== "function" && b !== null)
11
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
- extendStatics(d, b);
13
- function __() { this.constructor = d; }
14
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
- };
16
- })();
17
2
  Object.defineProperty(exports, "__esModule", { value: true });
18
- var formiojs_1 = require("formiojs");
19
- var Field = formiojs_1.Components.components.field;
20
- var SelectBoxes = formiojs_1.Components.components.selectboxes;
21
- var GDSCheckBoxes = /** @class */ (function (_super) {
22
- __extends(GDSCheckBoxes, _super);
23
- function GDSCheckBoxes() {
24
- return _super !== null && _super.apply(this, arguments) || this;
25
- }
26
- GDSCheckBoxes.prototype.render = function () {
27
- // @ts-ignore
3
+ const js_1 = require("@formio/js");
4
+ const Field = js_1.Components.components.field;
5
+ const SelectBoxes = js_1.Components.components.selectboxes;
6
+ class GDSCheckBoxes extends SelectBoxes {
7
+ render() {
28
8
  return Field.prototype.render.call(this, this.renderTemplate('selectboxes', {
29
- // @ts-ignore
30
9
  input: this.inputInfo,
31
- // @ts-ignore
32
10
  inline: this.component.inline,
33
- // @ts-ignore
34
11
  values: this.component.values,
35
- // @ts-ignore
36
12
  value: this.dataValue,
37
- // @ts-ignore
38
13
  row: this.row,
39
14
  }));
40
- };
41
- return GDSCheckBoxes;
42
- }(SelectBoxes));
15
+ }
16
+ }
43
17
  exports.default = GDSCheckBoxes;
@@ -1,18 +1,21 @@
1
- declare const Time: import("formiojs").ClassWithEditForm<typeof import("formiojs/types/components/_classes/widgetcomponent/widgetComponent").WidgetComponent>;
1
+ import { CoreTimeComponent } from '../../types';
2
+ declare const Time: any;
2
3
  export default class GDSTimeComponent extends Time {
3
4
  static MAX_LENGTH: number;
4
- private refs;
5
- private component;
6
- private loadRefs;
7
- private checkComponentValidity;
8
- private updateValue;
5
+ component: CoreTimeComponent;
6
+ refs: {
7
+ hour: HTMLInputElement;
8
+ minute: HTMLInputElement;
9
+ };
10
+ data: Record<string, unknown>;
9
11
  private timeHelper;
10
- attach(element: any): any;
12
+ attach(element: any): Promise<void>;
11
13
  setErrorClasses(elements: any, dirty: any, hasError: any): void;
12
- addInputError(message: any, dirty: boolean, element: any): void;
14
+ addInputError(message: any, dirty: any, element: any): void;
13
15
  removeInputError(elements: any): void;
14
16
  getValue(): string;
15
- setValue(value: any, flags: any): boolean;
16
- render(): any;
17
+ setValue(value: any, _flags: any): boolean;
18
+ render(): string;
17
19
  }
18
20
  export {};
21
+ //# sourceMappingURL=GDSTimeComponent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GDSTimeComponent.d.ts","sourceRoot":"","sources":["../../../src/components/time/GDSTimeComponent.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGhD,QAAA,MAAM,IAAI,KAA6B,CAAC;AAExC,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,IAAI;IAC9C,OAAc,UAAU,SAAK;IAGrB,SAAS,EAAE,iBAAiB,CAAC;IAG7B,IAAI,EAAE;QACV,IAAI,EAAE,gBAAgB,CAAC;QACvB,MAAM,EAAE,gBAAgB,CAAC;KAC5B,CAAC;IAGM,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAEtC,OAAO,CAAC,UAAU,CAAgC;IAI3C,MAAM,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC;IAiCnC,eAAe,CAAC,QAAQ,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,GAAG,IAAI;IAM/D,aAAa,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI;IAO3D,gBAAgB,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAKrC,QAAQ,IAAI,MAAM;IAUlB,QAAQ,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,GAAG,OAAO;IAU1C,MAAM,IAAI,MAAM;CAM1B"}
@@ -1,111 +1,86 @@
1
1
  "use strict";
2
- var __extends = (this && this.__extends) || (function () {
3
- var extendStatics = function (d, b) {
4
- extendStatics = Object.setPrototypeOf ||
5
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
- function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
- return extendStatics(d, b);
8
- };
9
- return function (d, b) {
10
- if (typeof b !== "function" && b !== null)
11
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
- extendStatics(d, b);
13
- function __() { this.constructor = d; }
14
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
- };
16
- })();
17
- var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
18
- if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
19
- if (ar || !(i in from)) {
20
- if (!ar) ar = Array.prototype.slice.call(from, 0, i);
21
- ar[i] = from[i];
22
- }
23
- }
24
- return to.concat(ar || Array.prototype.slice.call(from));
25
- };
26
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
27
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
28
4
  };
29
5
  Object.defineProperty(exports, "__esModule", { value: true });
30
- var formiojs_1 = require("formiojs");
31
- var moment_1 = __importDefault(require("moment"));
32
- var TimeHelper_1 = __importDefault(require("../util/TimeHelper"));
33
- var Field = formiojs_1.Components.components.field;
34
- var Time = formiojs_1.Components.components.time;
35
- // @ts-ignore
36
- var GDSTimeComponent = /** @class */ (function (_super) {
37
- __extends(GDSTimeComponent, _super);
38
- function GDSTimeComponent() {
39
- var _this = _super !== null && _super.apply(this, arguments) || this;
40
- _this.timeHelper = new TimeHelper_1.default();
41
- return _this;
6
+ const js_1 = require("@formio/js");
7
+ const moment_1 = __importDefault(require("moment"));
8
+ const TimeHelper_1 = __importDefault(require("../util/TimeHelper"));
9
+ const Field = js_1.Components.components.field;
10
+ const Time = js_1.Components.components.time;
11
+ class GDSTimeComponent extends Time {
12
+ constructor() {
13
+ super(...arguments);
14
+ this.timeHelper = new TimeHelper_1.default();
42
15
  }
43
- GDSTimeComponent.prototype.attach = function (element) {
44
- var _this = this;
16
+ // element typed as `any` to match the base component attach(element: any) override signature
17
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
18
+ attach(element) {
45
19
  this.loadRefs(element, {
46
20
  hour: 'single',
47
21
  minute: 'single',
48
22
  });
49
- this.addEventListener(this.refs.hour, 'input', function () {
50
- _this.timeHelper.checkAndValidateHour(_this.refs.hour);
51
- _this.setPristine(false);
52
- _this.checkComponentValidity(_this.data, true);
53
- _this.updateValue(null, { modified: true });
23
+ this.addEventListener(this.refs.hour, 'input', () => {
24
+ this.timeHelper.checkAndValidateHour(this.refs.hour);
25
+ this.setPristine(false);
26
+ this.checkComponentValidity(this.data, true);
27
+ this.updateValue(null, { modified: true });
54
28
  });
55
- this.addEventListener(this.refs.minute, 'input', function () {
56
- _this.timeHelper.checkAndValidateMinutes(_this.refs.minute);
57
- _this.setPristine(false);
58
- _this.checkComponentValidity(_this.data, true);
59
- _this.updateValue(null, { modified: true });
29
+ this.addEventListener(this.refs.minute, 'input', () => {
30
+ this.timeHelper.checkAndValidateMinutes(this.refs.minute);
31
+ this.setPristine(false);
32
+ this.checkComponentValidity(this.data, true);
33
+ this.updateValue(null, { modified: true });
60
34
  });
61
- this.addEventListener(this.refs.hour, 'keypress', function (evt) {
62
- _this.timeHelper.preventNonNumericKeyPress(evt);
35
+ this.addEventListener(this.refs.hour, 'keypress', (evt) => {
36
+ this.timeHelper.preventNonNumericKeyPress(evt);
63
37
  });
64
- this.addEventListener(this.refs.minute, 'keypress', function (evt) {
65
- _this.timeHelper.preventNonNumericKeyPress(evt);
38
+ this.addEventListener(this.refs.minute, 'keypress', (evt) => {
39
+ this.timeHelper.preventNonNumericKeyPress(evt);
66
40
  });
67
- return _super.prototype.attach.call(this, element);
68
- };
69
- GDSTimeComponent.prototype.setErrorClasses = function (elements, dirty, hasError) {
70
- // @ts-ignore
71
- _super.prototype.setErrorClasses.call(this, __spreadArray([this.refs.hour, this.refs.minute], elements, true), dirty, hasError);
72
- };
73
- // @ts-ignore
74
- GDSTimeComponent.prototype.addInputError = function (message, dirty, element) {
75
- // @ts-ignore
76
- _super.prototype.addInputError.call(this, message, dirty, [this.refs.hour, this.refs.minute]);
77
- // @ts-ignore
78
- _super.prototype.addInputError.call(this, message, dirty, element);
79
- };
80
- GDSTimeComponent.prototype.removeInputError = function (elements) {
81
- // @ts-ignore
82
- _super.prototype.removeInputError.call(this, [this.refs.hour, this.refs.minute]);
83
- // @ts-ignore
84
- _super.prototype.removeInputError.call(this, elements);
85
- };
86
- GDSTimeComponent.prototype.getValue = function () {
41
+ return super.attach(element);
42
+ }
43
+ // elements typed as `any` to match the base Day.setErrorClasses(elements: any, ...) override signature
44
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
45
+ setErrorClasses(elements, dirty, hasError) {
46
+ super.setErrorClasses([this.refs.hour, this.refs.minute, ...elements], dirty, hasError);
47
+ }
48
+ // message/dirty/element typed as `any` to match the base Component.addInputError(message: any, dirty: any, elements: any) signature
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
50
+ addInputError(message, dirty, element) {
51
+ super.addInputError(message, dirty, [this.refs.hour, this.refs.minute]);
52
+ super.addInputError(message, dirty, element);
53
+ }
54
+ // elements typed as `any` to match the base Component.removeInputError(elements: any) signature
55
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
56
+ removeInputError(elements) {
57
+ super.removeInputError([this.refs.hour, this.refs.minute]);
58
+ super.removeInputError(elements);
59
+ }
60
+ getValue() {
87
61
  if (!this.refs.hour.value || !this.refs.minute.value) {
88
62
  return '';
89
63
  }
90
- var value = "".concat(this.refs.hour.value, ":").concat(this.refs.minute.value);
64
+ const value = `${this.refs.hour.value}:${this.refs.minute.value}`;
91
65
  return (0, moment_1.default)(value, this.component.format).format(this.component.dataFormat);
92
- };
93
- GDSTimeComponent.prototype.setValue = function (value, flags) {
66
+ }
67
+ // value/flags typed as `any` to match the base Component.setValue(value: any, flags: any) override signature
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
69
+ setValue(value, _flags) {
94
70
  if (this.refs.hour && this.refs.minute && value) {
95
- var parts = value.split(':');
71
+ const parts = value.split(':');
96
72
  this.refs.hour.value = parts[0];
97
73
  this.refs.minute.value = parts[1];
98
74
  return true;
99
75
  }
100
76
  return false;
101
- };
102
- GDSTimeComponent.prototype.render = function () {
77
+ }
78
+ render() {
103
79
  return Field.prototype.render.call(this, this.renderTemplate('time', {
104
- hour: _super.prototype.getValue.call(this).split(':')[0],
105
- minute: _super.prototype.getValue.call(this).split(':')[1],
80
+ hour: super.getValue().split(':')[0],
81
+ minute: super.getValue().split(':')[1],
106
82
  }));
107
- };
108
- GDSTimeComponent.MAX_LENGTH = 2;
109
- return GDSTimeComponent;
110
- }(Time));
83
+ }
84
+ }
85
+ GDSTimeComponent.MAX_LENGTH = 2;
111
86
  exports.default = GDSTimeComponent;
@@ -1,7 +1,9 @@
1
+ import { InputElementLike } from '../../types';
1
2
  export default class TimeHelper {
2
3
  static MAX_LENGTH: number;
3
- checkAndValidateMinutes(element: any): void;
4
- checkAndValidateHour(element: any): void;
5
- preventNonNumericKeyPress(evt: any): void;
6
- validateDateInput(element: any, length: number): void;
4
+ checkAndValidateMinutes(element: InputElementLike): void;
5
+ checkAndValidateHour(element: InputElementLike): void;
6
+ preventNonNumericKeyPress(evt: KeyboardEvent): void;
7
+ validateDateInput(element: InputElementLike, length: number): void;
7
8
  }
9
+ //# sourceMappingURL=TimeHelper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TimeHelper.d.ts","sourceRoot":"","sources":["../../../src/components/util/TimeHelper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE/C,MAAM,CAAC,OAAO,OAAO,UAAU;IAE3B,OAAc,UAAU,SAAK;IAEtB,uBAAuB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAexD,oBAAoB,CAAC,OAAO,EAAE,gBAAgB,GAAG,IAAI;IAgBrD,yBAAyB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI;IAMnD,iBAAiB,CAAC,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;CAK5E"}