@pure-ds/core 0.7.27 → 0.7.29

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 (33) hide show
  1. package/.cursorrules +15 -0
  2. package/.github/copilot-instructions.md +15 -0
  3. package/custom-elements.json +160 -1
  4. package/dist/types/pds.d.ts +55 -1
  5. package/dist/types/public/assets/js/pds-ask.d.ts +1 -2
  6. package/dist/types/public/assets/js/pds-ask.d.ts.map +1 -1
  7. package/dist/types/public/assets/js/pds-autocomplete.d.ts +36 -25
  8. package/dist/types/public/assets/js/pds-autocomplete.d.ts.map +1 -1
  9. package/dist/types/public/assets/js/pds-enhancers.d.ts +4 -4
  10. package/dist/types/public/assets/js/pds-enhancers.d.ts.map +1 -1
  11. package/dist/types/public/assets/js/pds-manager.d.ts +159 -444
  12. package/dist/types/public/assets/js/pds-manager.d.ts.map +1 -1
  13. package/dist/types/public/assets/js/pds-toast.d.ts +6 -7
  14. package/dist/types/public/assets/js/pds-toast.d.ts.map +1 -1
  15. package/dist/types/public/assets/js/pds.d.ts +3 -4
  16. package/dist/types/public/assets/js/pds.d.ts.map +1 -1
  17. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts +46 -0
  18. package/dist/types/public/assets/pds/components/pds-omnibox.d.ts.map +1 -1
  19. package/dist/types/src/js/common/ask.d.ts.map +1 -1
  20. package/dist/types/src/js/pds-core/pds-generator.d.ts.map +1 -1
  21. package/package.json +1 -1
  22. package/public/assets/js/app.js +1 -1
  23. package/public/assets/js/pds-ask.js +6 -6
  24. package/public/assets/js/pds-manager.js +110 -56
  25. package/public/assets/pds/components/pds-omnibox.js +70 -3
  26. package/public/assets/pds/components/pds-tags.js +160 -122
  27. package/public/assets/pds/core/pds-ask.js +6 -6
  28. package/public/assets/pds/core/pds-manager.js +110 -56
  29. package/public/assets/pds/custom-elements.json +175 -1
  30. package/public/assets/pds/pds-css-complete.json +1 -1
  31. package/public/assets/pds/vscode-custom-data.json +32 -0
  32. package/src/js/pds-core/pds-generator.js +98 -26
  33. package/src/js/pds.d.ts +55 -1
@@ -1,3 +1,21 @@
1
+ /**
2
+ * @typedef {Object} PdsOmniboxCategoryOptions
3
+ * @property {(options?: Object) => boolean} [trigger] - Determines whether the category is active for the current query
4
+ * @property {(options?: Object) => Promise<Array<Object>>|Array<Object>} [getItems] - Returns result items for this category
5
+ * @property {(item?: Object) => any} [action] - Called when a result item is selected
6
+ * @property {boolean} [useIconForInput] - Uses first result icon in the input field when enabled
7
+ * @property {number} [sortIndex] - Category ordering hint (higher values appear first)
8
+ */
9
+
10
+ /**
11
+ * @typedef {Object} PdsOmniboxSettings
12
+ * @property {boolean} [hideCategory] - Hides category labels in suggestions
13
+ * @property {string} [itemGrid] - CSS grid-template-columns used for result rows
14
+ * @property {(item?: Object) => (string|null)} [iconHandler] - Custom renderer for item icons
15
+ * @property {boolean} [useIconForInput] - Global fallback for using an item icon in the input
16
+ * @property {Object<string, PdsOmniboxCategoryOptions>} categories - Category map keyed by category name
17
+ */
18
+
1
19
  /**
2
20
  * Omnibox search input with PDS styling and form-associated behavior.
3
21
  *
@@ -11,7 +29,7 @@
11
29
  * @attr {boolean} required - Mark the input as required
12
30
  * @attr {string} autocomplete - Native autocomplete attribute (default: off)
13
31
  *
14
- * @property {Object} settings - AutoComplete settings object (required by consumer)
32
+ * @property {PdsOmniboxSettings} settings - AutoComplete settings object (required by consumer)
15
33
  */
16
34
  import { PDS } from "#pds";
17
35
 
@@ -43,6 +61,7 @@ export class PdsOmnibox extends HTMLElement {
43
61
  #autoCompleteResizeHandler;
44
62
  #autoCompleteScrollHandler;
45
63
  #autoCompleteViewportHandler;
64
+ #autoCompleteFocusTimer;
46
65
  #lengthProbe;
47
66
  #suggestionsUpdatedHandler;
48
67
  #suggestionsObserver;
@@ -71,6 +90,10 @@ export class PdsOmnibox extends HTMLElement {
71
90
  }
72
91
 
73
92
  disconnectedCallback() {
93
+ if (this.#autoCompleteFocusTimer) {
94
+ clearTimeout(this.#autoCompleteFocusTimer);
95
+ this.#autoCompleteFocusTimer = null;
96
+ }
74
97
  this.#teardownAutoCompleteSizing();
75
98
  this.#teardownSuggestionsObserver();
76
99
  const autoComplete = this.#input?._autoComplete;
@@ -601,8 +624,19 @@ export class PdsOmnibox extends HTMLElement {
601
624
  }
602
625
 
603
626
  this.#wrapAutoCompleteResultsHandler(this.#input._autoComplete);
604
- setTimeout(() => {
605
- this.#input._autoComplete.focusHandler(e);
627
+ this.#wrapAutoCompleteController(this.#input._autoComplete);
628
+ if (this.#autoCompleteFocusTimer) {
629
+ clearTimeout(this.#autoCompleteFocusTimer);
630
+ }
631
+ const input = this.#input;
632
+ this.#autoCompleteFocusTimer = setTimeout(() => {
633
+ this.#autoCompleteFocusTimer = null;
634
+ if (!this.isConnected || !input || this.#input !== input) return;
635
+ const autoComplete = input._autoComplete;
636
+ if (!autoComplete || typeof autoComplete.focusHandler !== "function") {
637
+ return;
638
+ }
639
+ autoComplete.focusHandler({ target: input });
606
640
  this.#setupAutoCompleteSizing();
607
641
  this.#updateSuggestionMaxHeight();
608
642
  this.#setupSuggestionsObserver();
@@ -669,6 +703,39 @@ export class PdsOmnibox extends HTMLElement {
669
703
  };
670
704
  }
671
705
 
706
+ #wrapAutoCompleteController(autoComplete) {
707
+ if (!autoComplete || autoComplete.__pdsControllerWrapped) return;
708
+ const originalController = autoComplete.controller?.bind(autoComplete);
709
+ if (typeof originalController !== "function") return;
710
+
711
+ autoComplete.__pdsControllerWrapped = true;
712
+ autoComplete.controller = () => {
713
+ const controller = originalController();
714
+ if (!controller || typeof controller !== "object") return controller;
715
+
716
+ return {
717
+ ...controller,
718
+ clear: (reason, ...args) => {
719
+ const reasonText = String(reason ?? "").toLowerCase();
720
+ if (reasonText === "blurred" && this.#isInputFocused()) {
721
+ return;
722
+ }
723
+ if (typeof controller.clear === "function") {
724
+ return controller.clear.call(controller, reason, ...args);
725
+ }
726
+ return undefined;
727
+ },
728
+ };
729
+ };
730
+ }
731
+
732
+ #isInputFocused() {
733
+ if (!this.#input) return false;
734
+ const activeInShadow = this.#root?.activeElement === this.#input;
735
+ const activeInDocument = document.activeElement === this.#input;
736
+ return activeInShadow || activeInDocument;
737
+ }
738
+
672
739
  #setupAutoCompleteSizing() {
673
740
  if (this.#autoCompleteResizeHandler) return;
674
741
  this.#autoCompleteResizeHandler = () => this.#updateSuggestionMaxHeight();
@@ -1,22 +1,45 @@
1
1
  import { PDS } from "#pds";
2
+ import "./pds-omnibox.js";
3
+
4
+ const DEFAULT_OMNIBOX_OPTIONS = {
5
+ hideCategory: true,
6
+ itemGrid: "0 1fr 0",
7
+ iconHandler: () => "",
8
+ };
2
9
 
3
10
  /**
4
- * Multi-select tags control built on top of pds-omnibox.
11
+ * Form-associated multi-select tags control built on top of `pds-omnibox`.
12
+ *
13
+ * Users select suggestions from the omnibox and each selection is rendered as a removable
14
+ * chip. The component keeps selection state synchronized across:
15
+ * - the `value` attribute (comma-separated ids)
16
+ * - the `value` property (`string[]`)
17
+ * - form value via ElementInternals
18
+ *
19
+ * The `options` object uses the same structure as `pds-omnibox.settings`.
20
+ * See `pds-omnibox` JSDoc for the full options schema.
21
+ * Before applying it, the component deep-merges internal defaults:
22
+ * - `hideCategory: true`
23
+ * - `itemGrid: "0 1fr 0"`
24
+ * - `iconHandler: () => ""`
25
+ *
26
+ * When `required` is set, at least one selected value is required for validity.
5
27
  *
6
28
  * @element pds-tags
7
29
  * @formAssociated
8
30
  *
9
- * @attr {string} name - Form field name used for submitted values
10
- * @attr {string} placeholder - Search placeholder for the omnibox
31
+ * @attr {string} name - Form field name; selected values are submitted under this name
32
+ * @attr {string} placeholder - Placeholder shown in omnibox input (default: "Search tags...")
11
33
  * @attr {string} value - Comma-separated selected item ids
12
- * @attr {string} options - JSON array of options (string[] or object[])
13
- * @attr {string} settings - JSON omnibox settings (optional)
14
- * @attr {boolean} disabled - Disables interaction
15
- * @attr {boolean} required - Requires at least one selected value
34
+ * @attr {string} options - JSON object with the same shape as `pds-omnibox.settings`
35
+ * @attr {boolean} disabled - Disables omnibox interaction and chip remove actions
36
+ * @attr {boolean} required - Requires at least one selected value for validity
37
+ *
38
+ * @property {PdsOmniboxSettings|null} options - Omnibox options object (see `pds-omnibox.js` typedef)
39
+ * @property {string[]} value - Selected option ids (array form)
16
40
  *
17
- * @property {Array<string|Object>} options - Option source for omnibox suggestions
18
- * @property {Object} settings - Omnibox-compatible settings source
19
- * @property {string[]} value - Selected option ids
41
+ * @fires {Event} input - Fired whenever selection changes
42
+ * @fires {Event} change - Fired whenever selection changes
20
43
  */
21
44
  class PdsTags extends HTMLElement {
22
45
  static formAssociated = true;
@@ -26,7 +49,6 @@ class PdsTags extends HTMLElement {
26
49
  "placeholder",
27
50
  "value",
28
51
  "options",
29
- "settings",
30
52
  "disabled",
31
53
  "required",
32
54
  ];
@@ -63,7 +85,6 @@ class PdsTags extends HTMLElement {
63
85
  this.#upgradeProperty("value");
64
86
  this.#upgradeProperty("name");
65
87
  this.#upgradeProperty("placeholder");
66
- this.#upgradeProperty("settings");
67
88
  this.#upgradeProperty("disabled");
68
89
  this.#upgradeProperty("required");
69
90
 
@@ -135,26 +156,21 @@ class PdsTags extends HTMLElement {
135
156
  if (name === "value") {
136
157
  if (this.#syncingValueAttribute) return;
137
158
  this.#selectedIds = new Set(this.#parseValueList(newValue));
159
+ this.#reconcileSelection();
138
160
  this.#renderChips();
161
+ void this.#hydrateSelectedItemsFromSource();
139
162
  this.#syncFormValue();
140
163
  return;
141
164
  }
142
165
 
143
166
  if (name === "options") {
144
- this.#items = this.#normalizeItems(this.#parseOptionsAttribute(newValue));
145
- this.#syncSelectedItemsFromItems();
167
+ this.#sourceSettings = this.#parseOptionsAttribute(newValue);
146
168
  this.#reconcileSelection();
147
169
  this.#renderChips();
148
- if (!this.#sourceSettings) this.#settingsInitialized = false;
149
- this.#applyOmniboxSettings();
150
- this.#syncFormValue();
151
- return;
152
- }
153
-
154
- if (name === "settings") {
155
- this.#sourceSettings = this.#parseSettingsAttribute(newValue);
170
+ void this.#hydrateSelectedItemsFromSource();
156
171
  this.#settingsInitialized = false;
157
172
  this.#applyOmniboxSettings();
173
+ this.#syncFormValue();
158
174
  return;
159
175
  }
160
176
 
@@ -184,38 +200,23 @@ class PdsTags extends HTMLElement {
184
200
  }
185
201
 
186
202
  get options() {
187
- return this.#items.slice();
203
+ return this.#sourceSettings;
188
204
  }
189
205
 
190
206
  set options(value) {
191
207
  this.#debug("set options", {
192
208
  receivedType: value == null ? String(value) : Array.isArray(value) ? "array" : typeof value,
193
- receivedLength: Array.isArray(value) ? value.length : undefined,
209
+ hasCategories: Boolean(value?.categories),
194
210
  });
195
- if (value == null) return;
196
- const nextItems = this.#normalizeItems(Array.isArray(value) ? value : []);
197
- this.#items = nextItems;
198
- this.#syncSelectedItemsFromItems();
199
- this.#debug("set options:normalized", { itemsCount: this.#items.length });
211
+ this.#sourceSettings = value && typeof value === "object" && !Array.isArray(value)
212
+ ? value
213
+ : null;
200
214
  this.#reconcileSelection();
201
215
  this.#renderChips();
202
- if (!this.#sourceSettings) this.#settingsInitialized = false;
203
- this.#applyOmniboxSettings();
204
- this.#syncFormValue();
205
- }
206
-
207
- get settings() {
208
- return this.#sourceSettings;
209
- }
210
-
211
- set settings(value) {
212
- this.#debug("set settings", {
213
- receivedType: value == null ? String(value) : typeof value,
214
- hasCategories: Boolean(value?.categories),
215
- });
216
- this.#sourceSettings = value && typeof value === "object" ? value : null;
216
+ void this.#hydrateSelectedItemsFromSource();
217
217
  this.#settingsInitialized = false;
218
218
  this.#applyOmniboxSettings();
219
+ this.#syncFormValue();
219
220
  }
220
221
 
221
222
  get value() {
@@ -228,10 +229,10 @@ class PdsTags extends HTMLElement {
228
229
  ? value.map((item) => String(item))
229
230
  : this.#parseValueList(String(value ?? ""));
230
231
  this.#selectedIds = new Set(incoming);
231
- this.#syncSelectedItemsFromItems();
232
232
  this.#debug("set value:parsed", { selected: Array.from(this.#selectedIds) });
233
233
  this.#reconcileSelection();
234
234
  this.#renderChips();
235
+ void this.#hydrateSelectedItemsFromSource();
235
236
  this.#applyOmniboxSettings();
236
237
  this.#syncFormValue();
237
238
  }
@@ -297,6 +298,7 @@ class PdsTags extends HTMLElement {
297
298
 
298
299
  this.#syncAttributes();
299
300
  this.#applyOmniboxSettings();
301
+ await this.#hydrateSelectedItemsFromSource();
300
302
  this.#renderChips();
301
303
  this.#syncFormValue();
302
304
  this.#debug("ensureOmnibox:end", {
@@ -326,17 +328,10 @@ class PdsTags extends HTMLElement {
326
328
  hasValueAttr: this.hasAttribute("value"),
327
329
  });
328
330
  if (this.hasAttribute("options")) {
329
- this.#items = this.#normalizeItems(
330
- this.#parseOptionsAttribute(this.getAttribute("options")),
331
- );
332
- this.#syncSelectedItemsFromItems();
333
- this.#debug("hydrateFromAttributes:options", { itemsCount: this.#items.length });
334
- }
335
-
336
- if (this.hasAttribute("settings")) {
337
- this.#sourceSettings = this.#parseSettingsAttribute(this.getAttribute("settings"));
338
- this.#debug("hydrateFromAttributes:settings", {
339
- hasSettings: Boolean(this.#sourceSettings),
331
+ this.#sourceSettings = this.#parseOptionsAttribute(this.getAttribute("options"));
332
+ this.#items = [];
333
+ this.#debug("hydrateFromAttributes:options", {
334
+ hasOptions: Boolean(this.#sourceSettings),
340
335
  hasCategories: Boolean(this.#sourceSettings?.categories),
341
336
  });
342
337
  }
@@ -348,14 +343,71 @@ class PdsTags extends HTMLElement {
348
343
 
349
344
  this.#reconcileSelection();
350
345
  this.#syncAttributes();
351
- this.#renderChips();
346
+ if (!this.#selectedIds.size || !this.#sourceSettings?.categories) {
347
+ this.#renderChips();
348
+ }
352
349
  this.#syncFormValue();
353
350
  this.#debug("hydrateFromAttributes:end", {
354
- itemsCount: this.#items.length,
351
+ knownItemsCount: this.#items.length,
355
352
  selected: Array.from(this.#selectedIds),
356
353
  });
357
354
  }
358
355
 
356
+ async #hydrateSelectedItemsFromSource() {
357
+ if (!this.#selectedIds.size) return;
358
+ const sourceCategories = this.#sourceSettings?.categories;
359
+ if (!sourceCategories || typeof sourceCategories !== "object") return;
360
+
361
+ const knownIds = new Set(this.#items.map((item) => item.id));
362
+ const unresolved = new Set(
363
+ Array.from(this.#selectedIds).filter((id) => !this.#selectedItems.has(id) && !knownIds.has(id)),
364
+ );
365
+ if (!unresolved.size) return;
366
+
367
+ this.#debug("hydrateSelectedItemsFromSource:start", {
368
+ selected: Array.from(this.#selectedIds),
369
+ unresolved: Array.from(unresolved),
370
+ categoryCount: Object.keys(sourceCategories).length,
371
+ });
372
+
373
+ for (const [categoryName, categoryConfig] of Object.entries(sourceCategories)) {
374
+ if (!unresolved.size) break;
375
+ const sourceGetItems = categoryConfig?.getItems;
376
+ if (typeof sourceGetItems !== "function") continue;
377
+
378
+ try {
379
+ const incoming = await sourceGetItems({ search: "" });
380
+ const normalized = this.#normalizeResultItems(incoming);
381
+ if (!normalized.length) continue;
382
+
383
+ this.#rememberKnownItems(normalized);
384
+ for (const item of normalized) {
385
+ if (!unresolved.has(item.id)) continue;
386
+ this.#selectedItems.set(item.id, item);
387
+ unresolved.delete(item.id);
388
+ }
389
+
390
+ this.#debug("hydrateSelectedItemsFromSource:category", {
391
+ categoryName,
392
+ resolvedCount: normalized.length,
393
+ remaining: Array.from(unresolved),
394
+ });
395
+ } catch (error) {
396
+ this.#debug("hydrateSelectedItemsFromSource:error", {
397
+ categoryName,
398
+ error: String(error),
399
+ });
400
+ }
401
+ }
402
+
403
+ this.#reconcileSelection();
404
+ if (this.#selectedIds.size) this.#renderChips();
405
+ this.#debug("hydrateSelectedItemsFromSource:end", {
406
+ remaining: Array.from(unresolved),
407
+ selectedItemsCount: this.#selectedItems.size,
408
+ });
409
+ }
410
+
359
411
  #syncAttributes() {
360
412
  this.#debug("syncAttributes:start", {
361
413
  hasOmnibox: Boolean(this.#omnibox),
@@ -427,6 +479,7 @@ class PdsTags extends HTMLElement {
427
479
  : [];
428
480
 
429
481
  const normalized = this.#normalizeResultItems(incoming);
482
+ this.#rememberKnownItems(normalized);
430
483
  for (const item of normalized) {
431
484
  this.#selectedItems.set(item.id, item);
432
485
  }
@@ -462,14 +515,12 @@ class PdsTags extends HTMLElement {
462
515
  });
463
516
 
464
517
  const { categories: _ignoredCategories, ...sourceSettingsWithoutCategories } = sourceSettings;
465
-
466
- return {
518
+ const normalizedSettings = {
467
519
  ...sourceSettingsWithoutCategories,
468
520
  categories,
469
- hideCategory: true,
470
- itemGrid: "0 1fr 0",
471
- iconHandler: () => "",
472
521
  };
522
+
523
+ return this.#deepMergeOptions(DEFAULT_OMNIBOX_OPTIONS, normalizedSettings);
473
524
  }
474
525
 
475
526
  #matchesQuery(item, query) {
@@ -601,30 +652,19 @@ class PdsTags extends HTMLElement {
601
652
 
602
653
  #parseOptionsAttribute(value) {
603
654
  this.#debug("parseOptionsAttribute:start", { hasValue: Boolean(value), rawType: typeof value });
604
- if (!value) return [];
605
- try {
606
- const parsed = JSON.parse(value);
607
- this.#debug("parseOptionsAttribute:parsed", { isArray: Array.isArray(parsed), length: Array.isArray(parsed) ? parsed.length : undefined });
608
- return Array.isArray(parsed) ? parsed : [];
609
- } catch {
610
- this.#debug("parseOptionsAttribute:error", { value });
611
- return [];
612
- }
613
- }
614
-
615
- #parseSettingsAttribute(value) {
616
- this.#debug("parseSettingsAttribute:start", { hasValue: Boolean(value), rawType: typeof value });
617
655
  if (!value) return null;
618
656
  try {
619
657
  const parsed = JSON.parse(value);
620
- const valid = parsed && typeof parsed === "object" ? parsed : null;
621
- this.#debug("parseSettingsAttribute:parsed", {
658
+ const valid = parsed && typeof parsed === "object" && !Array.isArray(parsed)
659
+ ? parsed
660
+ : null;
661
+ this.#debug("parseOptionsAttribute:parsed", {
622
662
  valid: Boolean(valid),
623
663
  hasCategories: Boolean(valid?.categories),
624
664
  });
625
665
  return valid;
626
666
  } catch {
627
- this.#debug("parseSettingsAttribute:error", { value });
667
+ this.#debug("parseOptionsAttribute:error", { value });
628
668
  return null;
629
669
  }
630
670
  }
@@ -709,11 +749,12 @@ class PdsTags extends HTMLElement {
709
749
 
710
750
  #reconcileSelection() {
711
751
  this.#debug("reconcileSelection:start", { selectedBefore: Array.from(this.#selectedIds) });
712
- if (!this.#selectedIds.size) return;
713
- const available = new Set(this.#items.map((item) => item.id));
714
- this.#selectedIds = new Set(
715
- Array.from(this.#selectedIds).filter((id) => available.has(id)),
716
- );
752
+ if (!this.#selectedIds.size) {
753
+ for (const id of Array.from(this.#selectedItems.keys())) {
754
+ this.#selectedItems.delete(id);
755
+ }
756
+ return;
757
+ }
717
758
  for (const id of Array.from(this.#selectedItems.keys())) {
718
759
  if (!this.#selectedIds.has(id)) this.#selectedItems.delete(id);
719
760
  }
@@ -721,52 +762,49 @@ class PdsTags extends HTMLElement {
721
762
  }
722
763
 
723
764
  #resolveSourceSettings() {
724
- if (this.#sourceSettings) return this.#sourceSettings;
725
- if (!Array.isArray(this.#items) || this.#items.length === 0) return null;
726
- return this.#buildOptionsSettings();
765
+ if (this.#sourceSettings && typeof this.#sourceSettings === "object") {
766
+ return this.#sourceSettings;
767
+ }
768
+ return null;
727
769
  }
728
770
 
729
- #buildOptionsSettings() {
730
- const categoryMap = new Map();
731
- for (const item of this.#items) {
732
- const categoryName = this.#normalizeCategory(item.category);
733
- if (!categoryMap.has(categoryName)) categoryMap.set(categoryName, []);
734
- categoryMap.get(categoryName).push(item);
771
+ #rememberKnownItems(items = []) {
772
+ if (!Array.isArray(items) || items.length === 0) return;
773
+ const byId = new Map(this.#items.map((item) => [item.id, item]));
774
+ for (const item of items) {
775
+ if (!item?.id) continue;
776
+ byId.set(item.id, item);
735
777
  }
778
+ this.#items = Array.from(byId.values());
779
+ }
736
780
 
737
- const categories = {};
738
- for (const [categoryName, entries] of categoryMap.entries()) {
739
- categories[categoryName] = {
740
- trigger: () => true,
741
- getItems: async (options) => {
742
- const query = String(options?.search || "").trim().toLowerCase();
743
- return entries
744
- .filter((item) => this.#matchesQuery(item, query))
745
- .map((item) => ({
746
- id: item.id,
747
- text: item.text,
748
- icon: item.icon,
749
- description: item.description,
750
- }));
751
- },
752
- };
781
+ #deepMergeOptions(base = {}, override = {}) {
782
+ const output = this.#clonePlainObject(base);
783
+ for (const [key, value] of Object.entries(override || {})) {
784
+ if (
785
+ this.#isPlainObject(value)
786
+ && this.#isPlainObject(output[key])
787
+ ) {
788
+ output[key] = this.#deepMergeOptions(output[key], value);
789
+ continue;
790
+ }
791
+ output[key] = value;
753
792
  }
793
+ return output;
794
+ }
754
795
 
755
- return {
756
- hideCategory: true,
757
- itemGrid: "0 1fr 0",
758
- iconHandler: () => "",
759
- categories,
760
- };
796
+ #clonePlainObject(value) {
797
+ if (!this.#isPlainObject(value)) return value;
798
+ return Object.fromEntries(
799
+ Object.entries(value).map(([key, entry]) => [
800
+ key,
801
+ this.#isPlainObject(entry) ? this.#clonePlainObject(entry) : entry,
802
+ ]),
803
+ );
761
804
  }
762
805
 
763
- #syncSelectedItemsFromItems() {
764
- if (!this.#selectedIds.size || !this.#items.length) return;
765
- for (const item of this.#items) {
766
- if (this.#selectedIds.has(item.id)) {
767
- this.#selectedItems.set(item.id, item);
768
- }
769
- }
806
+ #isPlainObject(value) {
807
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
770
808
  }
771
809
 
772
810
  #resetOmniboxSearchState(reason = "unknown") {
@@ -1,16 +1,16 @@
1
- function g(o){let e=Array.isArray(o?.strings)?o.strings:[],d=Array.isArray(o?.values)?o.values:[],m=new Set,f=[],p=/(\s)(\.[\w-]+)=\s*$/;for(let t=0;t<e.length;t+=1){let s=e[t]??"",c=s.match(p);if(c&&t<d.length){let u=c[2].slice(1),y=`pds-val-${t}`;s=s.replace(p,`$1data-pds-prop="${u}:${y}"`),m.add(t)}f.push(s),t<d.length&&!m.has(t)&&f.push(`<!--pds-val-${t}-->`)}let n=document.createElement("template");n.innerHTML=f.join("");let a=(t,s)=>{let c=t.parentNode;if(!c)return;if(s==null){c.removeChild(t);return}let h=u=>{if(u!=null){if(u instanceof Node){c.insertBefore(u,t);return}if(Array.isArray(u)){u.forEach(y=>h(y));return}c.insertBefore(document.createTextNode(String(u)),t)}};h(s),c.removeChild(t)},l=document.createTreeWalker(n.content,NodeFilter.SHOW_COMMENT),r=[];for(;l.nextNode();){let t=l.currentNode;t?.nodeValue?.startsWith("pds-val-")&&r.push(t)}return r.forEach(t=>{let s=Number(t.nodeValue.replace("pds-val-",""));a(t,d[s])}),n.content.querySelectorAll("*").forEach(t=>{let s=t.getAttribute("data-pds-prop");if(!s)return;let[c,h]=s.split(":"),u=Number(String(h).replace("pds-val-",""));c&&Number.isInteger(u)&&(t[c]=d[u]),t.removeAttribute("data-pds-prop")}),n.content}function b(o,e){if(e==null)return;if(typeof e=="object"&&Array.isArray(e.strings)&&Array.isArray(e.values)){o.appendChild(g(e));return}if(e instanceof Node){o.appendChild(e);return}if(Array.isArray(e)){e.forEach(m=>b(o,m));return}let d=typeof e=="string"?e:String(e);o.appendChild(document.createTextNode(d))}function A(o){if(!o)return!0;let e=!0,d=a=>{if(!a||typeof a!="object")return"<unknown>";let l=a.tagName?String(a.tagName).toLowerCase():"node",r=a.id?`#${a.id}`:"",i=typeof a.getAttribute=="function"?a.getAttribute("name"):null,t=i?`[name="${i}"]`:"";return`${l}${r}${t}`},m=(a,l)=>{if(!a||typeof a.querySelectorAll!="function")return;let r=Array.from(a.querySelectorAll(":invalid"));if(!r.length)return;let i=r.map(t=>{let s=typeof t.validationMessage=="string"?t.validationMessage:"";return`${d(t)}${s?` \u2014 ${s}`:""}`});console.warn(`ask.validateDialogFormTree: invalid controls in ${l}:`,i)},f=(a,l)=>{try{let r=typeof a.reportValidity=="function"?a.reportValidity():a.checkValidity?.()??!0;return r||m(a,l),r}catch(r){return console.error(`ask.validateDialogFormTree: validation threw in ${l}`,r),!1}};e=f(o,"host dialog form")&&e;let p=Array.from(o.querySelectorAll("form"));for(let a of p){if(a===o)continue;e=f(a,`nested light DOM form ${d(a)}`)&&e}let n=Array.from(o.querySelectorAll("*"));for(let a of n){let l=a?.shadowRoot;if(!l)continue;let r=Array.from(l.querySelectorAll("form"));for(let i of r)e=f(i,`shadow form under ${d(a)}`)&&e}return e}function v(){let o=navigator.userAgent,e=/Safari/i.test(o),d=/(Chrome|Chromium|CriOS|FxiOS|EdgiOS|OPiOS|Opera)/i.test(o);return e&&!d}function $(o){if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches)return;let e=window.matchMedia?.("(max-width: 639px)").matches,d=o.classList.contains("dialog-no-scale-animation")?"pds-dialog-fade-enter":e?"pds-dialog-enter-mobile":"pds-dialog-enter";o.style.animation="none",o.offsetWidth,o.style.animation=`${d} var(--transition-normal) ease`,o.addEventListener("animationend",()=>{o.style.animation=""},{once:!0})}function C(o={}){return o?.liquidGlassEffects===!0}async function S(o,e={}){return e={...{title:"Confirm",type:"confirm",buttons:{ok:{name:"OK",primary:!0},cancel:{name:"Cancel",cancel:!0}}},...e},new Promise(m=>{let f=!1,p=(r,i)=>{f||(f=!0,i.close(),m(r))},n=document.createElement("dialog");v()&&n.classList.add("dialog-no-scale-animation"),C(e)&&n.classList.add("liquid-glass"),e.size&&n.classList.add(`dialog-${e.size}`),e.type&&n.classList.add(`dialog-${e.type}`),e.class&&(Array.isArray(e.class)?n.classList.add(...e.class):n.classList.add(e.class)),e.maxHeight&&n.style.setProperty("--dialog-max-height",e.maxHeight);let a=Object.entries(e.buttons).map(([r,i])=>{let t=i.primary?"btn-primary btn-sm":"btn-outline btn-sm",s=i.cancel?"button":"submit",c=i.formNoValidate?" formnovalidate":"";return`<button type="${s}" class="${t}" value="${r}"${c}>${i.name}</button>`});if(e.useForm){let r=document.createElement("div");b(r,o);let i=r.querySelector("form");if(i){n.innerHTML=`
1
+ function E(i){let e=Array.isArray(i?.strings)?i.strings:[],c=Array.isArray(i?.values)?i.values:[],p=new Set,h=[],g=/(\s)(\.[\w-]+)=\s*$/;for(let o=0;o<e.length;o+=1){let t=e[o]??"",d=t.match(g);if(d&&o<c.length){let r=d[2].slice(1),a=`pds-val-${o}`;t=t.replace(g,`$1data-pds-prop="${r}:${a}"`),p.add(o)}h.push(t),o<c.length&&!p.has(o)&&h.push(`<!--pds-val-${o}-->`)}let s=document.createElement("template");s.innerHTML=h.join("");let n=(o,t)=>{let d=o.parentNode;if(!d)return;if(t==null){d.removeChild(o);return}let b=r=>{if(r!=null){if(r instanceof Node){d.insertBefore(r,o);return}if(Array.isArray(r)){r.forEach(a=>b(a));return}d.insertBefore(document.createTextNode(String(r)),o)}};b(t),d.removeChild(o)},l=document.createTreeWalker(s.content,NodeFilter.SHOW_COMMENT),u=[];for(;l.nextNode();){let o=l.currentNode;o?.nodeValue?.startsWith("pds-val-")&&u.push(o)}return u.forEach(o=>{let t=Number(o.nodeValue.replace("pds-val-",""));n(o,c[t])}),s.content.querySelectorAll("*").forEach(o=>{let t=o.getAttribute("data-pds-prop");if(!t)return;let[d,b]=t.split(":"),r=Number(String(b).replace("pds-val-",""));d&&Number.isInteger(r)&&(o[d]=c[r]),o.removeAttribute("data-pds-prop")}),s.content}function S(i,e){if(e==null)return;if(typeof e=="object"&&Array.isArray(e.strings)&&Array.isArray(e.values)){i.appendChild(E(e));return}if(e instanceof Node){i.appendChild(e);return}if(Array.isArray(e)){e.forEach(p=>S(i,p));return}let c=typeof e=="string"?e:String(e);i.appendChild(document.createTextNode(c))}function q(i){if(!i)return!0;let e=!0,c=n=>{if(!n||typeof n!="object")return"<unknown>";let l=n.tagName?String(n.tagName).toLowerCase():"node",u=n.id?`#${n.id}`:"",y=typeof n.getAttribute=="function"?n.getAttribute("name"):null,o=y?`[name="${y}"]`:"";return`${l}${u}${o}`},p=(n,l)=>{if(!n||typeof n.querySelectorAll!="function")return;let u=Array.from(n.querySelectorAll(":invalid"));if(!u.length)return;let y=u.map(o=>{let t=typeof o.validationMessage=="string"?o.validationMessage:"";return`${c(o)}${t?` \u2014 ${t}`:""}`});console.warn(`ask.validateDialogFormTree: invalid controls in ${l}:`,y)},h=(n,l)=>{try{let u=typeof n.reportValidity=="function"?n.reportValidity():n.checkValidity?.()??!0;return u||p(n,l),u}catch(u){return console.error(`ask.validateDialogFormTree: validation threw in ${l}`,u),!1}};e=h(i,"host dialog form")&&e;let g=Array.from(i.querySelectorAll("form"));for(let n of g){if(n===i)continue;e=h(n,`nested light DOM form ${c(n)}`)&&e}let s=Array.from(i.querySelectorAll("*"));for(let n of s){let l=n?.shadowRoot;if(!l)continue;let u=Array.from(l.querySelectorAll("form"));for(let y of u)e=h(y,`shadow form under ${c(n)}`)&&e}return e}function L(){let i=navigator.userAgent,e=/Safari/i.test(i),c=/(Chrome|Chromium|CriOS|FxiOS|EdgiOS|OPiOS|Opera)/i.test(i);return e&&!c}function M(i){if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches)return;let e=window.matchMedia?.("(max-width: 639px)").matches,c=i.classList.contains("dialog-no-scale-animation")?"pds-dialog-fade-enter":e?"pds-dialog-enter-mobile":"pds-dialog-enter";i.style.animation="none",i.offsetWidth,i.style.animation=`${c} var(--transition-normal) ease`,i.addEventListener("animationend",()=>{i.style.animation=""},{once:!0})}function T(i={}){return i?.liquidGlassEffects===!0}async function V(i,e={}){let c={title:"Confirm",type:"confirm",buttons:{ok:{name:"OK",primary:!0},cancel:{name:"Cancel",cancel:!0}}};e={...c,...e};let p=e.buttons&&typeof e.buttons=="object"?e.buttons:c.buttons,h=s=>{if(s==null)return{actionCode:"dismiss",actionKind:"dismiss",button:null};let n=p?.[s]??null,l=s==="ok"?"ok":s==="dismiss"?"dismiss":n?.cancel||s==="cancel"?"cancel":"custom";return{actionCode:s,actionKind:l,button:n}},g=s=>{if(typeof s>"u"||s===null||s===!0)return{allow:!0};if(s===!1)return{allow:!1};if(typeof s=="object"){let n=Object.prototype.hasOwnProperty.call(s,"result")||Object.prototype.hasOwnProperty.call(s,"value");return{allow:s.allow!==!1,hasResult:n,result:Object.prototype.hasOwnProperty.call(s,"result")?s.result:s.value}}return{allow:!!s}};return new Promise(s=>{let n=!1,l=(r,a,{shouldClose:f=!0}={})=>{if(!n&&(n=!0,s(r),!(!f||!a?.open)))try{a.close()}catch(m){console.warn("ask: dialog.close() failed",m)}},u=async r=>{if(r.actionKind!=="ok"||typeof e.beforeClose!="function")return{allow:!0};try{let a=await e.beforeClose(r);return g(a)}catch(a){return console.error("ask.beforeClose: validation failed",a),{allow:!1}}},y=({actionKind:r,form:a})=>r==="ok"?e.useForm&&a?new FormData(a):!0:!1,o=async({actionCode:r,form:a,submitter:f,originalEvent:m,bypassValidation:w=!1,shouldClose:O=!0})=>{if(n)return;let{actionKind:v,button:F}=h(r),A=a||t.querySelector("form")||null;if(e.useForm&&v==="ok"&&A&&!w&&!q(A))return;let C=y({actionKind:v,form:A}),$=await u({actionCode:r,actionKind:v,dialog:t,form:A,formData:e.useForm&&v==="ok"&&A?C:null,submitter:f,originalEvent:m,options:e,button:F,defaultResult:C});if(!$.allow)return;let x=$.hasResult?$.result:C;l(x,t,{shouldClose:O})},t=document.createElement("dialog");L()&&t.classList.add("dialog-no-scale-animation"),T(e)&&t.classList.add("liquid-glass"),e.size&&t.classList.add(`dialog-${e.size}`),e.type&&t.classList.add(`dialog-${e.type}`),e.class&&(Array.isArray(e.class)?t.classList.add(...e.class):t.classList.add(e.class)),e.maxHeight&&t.style.setProperty("--dialog-max-height",e.maxHeight);let d=Object.entries(p).map(([r,a])=>{let f=a.primary?"btn-primary btn-sm":"btn-outline btn-sm",m=a.cancel?"button":"submit",w=a.formNoValidate?" formnovalidate":"";return`<button type="${m}" class="${f}" value="${r}"${w}>${a.name}</button>`});if(e.useForm){let r=document.createElement("div");S(r,i);let a=r.querySelector("form");if(a){t.innerHTML=`
2
2
  <header>
3
3
  <h2>${e.title}</h2>
4
4
  </header>
5
- `;let t=document.createElement("article");for(t.className="dialog-body";i.firstChild;)t.appendChild(i.firstChild);i.appendChild(t);let s=document.createElement("footer");s.innerHTML=a.join(""),i.appendChild(s),n.appendChild(i)}else n.innerHTML=`
5
+ `;let f=document.createElement("article");for(f.className="dialog-body";a.firstChild;)f.appendChild(a.firstChild);a.appendChild(f);let m=document.createElement("footer");m.innerHTML=d.join(""),a.appendChild(m),t.appendChild(a)}else t.innerHTML=`
6
6
  <header>
7
7
  <h2>${e.title}</h2>
8
8
  </header>
9
9
  <article id="msg-container"></article>
10
10
  <footer>
11
- ${a.join("")}
11
+ ${d.join("")}
12
12
  </footer>
13
- `,n.querySelector("#msg-container").appendChild(r)}else{n.innerHTML=`
13
+ `,t.querySelector("#msg-container").appendChild(r)}else{t.innerHTML=`
14
14
  <form method="dialog">
15
15
  <header>
16
16
  <h2>${e.title}</h2>
@@ -19,7 +19,7 @@ function g(o){let e=Array.isArray(o?.strings)?o.strings:[],d=Array.isArray(o?.va
19
19
  <article id="msg-container"></article>
20
20
 
21
21
  <footer>
22
- ${a.join("")}
22
+ ${d.join("")}
23
23
  </footer>
24
24
  </form>
25
- `;let r=n.querySelector("#msg-container");b(r,o)}n.addEventListener("click",r=>{let i=r.target.closest('button[value="ok"]');if(i&&e.useForm){r.preventDefault();let s=n.querySelector("form");if(!s||!!!i.hasAttribute("formnovalidate")&&!A(s))return;let h=new FormData(s);p(h,n);return}r.target.closest('button[value="cancel"]')&&p(!1,n)});let l=()=>{let r=n.querySelector("form");if(r){if(r.dataset.askSubmitBound==="true")return;r.dataset.askSubmitBound="true",r.addEventListener("submit",i=>{i.preventDefault();let t=i.submitter?.value??(e.useForm?"ok":void 0),s=!!i.submitter?.hasAttribute("formnovalidate");if(e.useForm&&t==="ok"&&!s&&!A(r))return;let c;e.useForm&&t==="ok"?c=new FormData(r):c=t==="ok",p(c,n)})}else requestAnimationFrame(l)};n.addEventListener("close",()=>{setTimeout(()=>n.remove(),200)}),document.body.appendChild(n),requestAnimationFrame(l),typeof e.rendered=="function"&&e.rendered(n),n.showModal(),requestAnimationFrame(()=>$(n))})}export{S as ask};
25
+ `;let r=t.querySelector("#msg-container");S(r,i)}t.addEventListener("click",r=>{let a=r.target.closest('button[value="cancel"]');a&&o({actionCode:"cancel",form:t.querySelector("form"),submitter:a,originalEvent:r})});let b=()=>{let r=t.querySelector("form");if(r){if(r.dataset.askSubmitBound==="true")return;r.dataset.askSubmitBound="true",r.addEventListener("submit",a=>{a.preventDefault();let f=a.submitter?.value??(e.useForm?"ok":void 0),m=!!a.submitter?.hasAttribute("formnovalidate");o({actionCode:f,form:r,submitter:a.submitter,originalEvent:a,bypassValidation:m})})}else requestAnimationFrame(b)};t.addEventListener("cancel",r=>{r.preventDefault(),o({actionCode:"dismiss",form:t.querySelector("form"),originalEvent:r})}),t.addEventListener("close",()=>{n||l(!1,t,{shouldClose:!1}),setTimeout(()=>t.remove(),200)}),document.body.appendChild(t),requestAnimationFrame(b),typeof e.rendered=="function"&&e.rendered(t),t.showModal(),requestAnimationFrame(()=>M(t))})}export{V as ask};