@digdir/designsystemet-web 1.18.0 → 1.19.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 (78) hide show
  1. package/README.md +13 -15
  2. package/dist/cjs/_vendors/@oddbird/popover-polyfill/dist/popover-fn.cjs +4 -4
  3. package/dist/cjs/_vendors/@oddbird/popover-polyfill/dist/popover-fn.cjs.map +1 -1
  4. package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -1
  5. package/dist/cjs/breadcrumbs/breadcrumbs.cjs +1 -1
  6. package/dist/cjs/breadcrumbs/breadcrumbs.cjs.map +1 -1
  7. package/dist/cjs/clickdelegatefor/clickdelegatefor.cjs +1 -1
  8. package/dist/cjs/clickdelegatefor/clickdelegatefor.cjs.map +1 -1
  9. package/dist/cjs/dialog/dialog.cjs +1 -1
  10. package/dist/cjs/dialog/dialog.cjs.map +1 -1
  11. package/dist/cjs/error-summary/error-summary.cjs +1 -2
  12. package/dist/cjs/error-summary/error-summary.cjs.map +1 -1
  13. package/dist/cjs/field/field.cjs +1 -1
  14. package/dist/cjs/field/field.cjs.map +1 -1
  15. package/dist/cjs/fieldset/fieldset.cjs +1 -1
  16. package/dist/cjs/fieldset/fieldset.cjs.map +1 -1
  17. package/dist/cjs/focusgroup/focusgroup.cjs +2 -0
  18. package/dist/cjs/focusgroup/focusgroup.cjs.map +1 -0
  19. package/dist/cjs/index.cjs +1 -1
  20. package/dist/cjs/invokers/invokers.cjs.map +1 -1
  21. package/dist/cjs/packages/web/package.cjs +2 -0
  22. package/dist/cjs/packages/web/package.cjs.map +1 -0
  23. package/dist/cjs/pagination/pagination.cjs +1 -1
  24. package/dist/cjs/pagination/pagination.cjs.map +1 -1
  25. package/dist/cjs/popover/popover.cjs +1 -1
  26. package/dist/cjs/popover/popover.cjs.map +1 -1
  27. package/dist/cjs/readonly/readonly.cjs +1 -1
  28. package/dist/cjs/readonly/readonly.cjs.map +1 -1
  29. package/dist/cjs/suggestion/suggestion.cjs +1 -1
  30. package/dist/cjs/suggestion/suggestion.cjs.map +1 -1
  31. package/dist/cjs/toggle-group/toggle-group.cjs +1 -1
  32. package/dist/cjs/toggle-group/toggle-group.cjs.map +1 -1
  33. package/dist/cjs/tooltip/tooltip.cjs +1 -1
  34. package/dist/cjs/tooltip/tooltip.cjs.map +1 -1
  35. package/dist/cjs/utils/utils.cjs +1 -1
  36. package/dist/cjs/utils/utils.cjs.map +1 -1
  37. package/dist/custom-elements.json +189 -4
  38. package/dist/esm/_vendors/@oddbird/popover-polyfill/dist/popover-fn.js +4 -4
  39. package/dist/esm/_vendors/@oddbird/popover-polyfill/dist/popover-fn.js.map +1 -1
  40. package/dist/esm/breadcrumbs/breadcrumbs.js +1 -1
  41. package/dist/esm/breadcrumbs/breadcrumbs.js.map +1 -1
  42. package/dist/esm/clickdelegatefor/clickdelegatefor.js +1 -1
  43. package/dist/esm/clickdelegatefor/clickdelegatefor.js.map +1 -1
  44. package/dist/esm/dialog/dialog.js +1 -1
  45. package/dist/esm/dialog/dialog.js.map +1 -1
  46. package/dist/esm/error-summary/error-summary.js +1 -2
  47. package/dist/esm/error-summary/error-summary.js.map +1 -1
  48. package/dist/esm/field/field.js +1 -1
  49. package/dist/esm/field/field.js.map +1 -1
  50. package/dist/esm/fieldset/fieldset.js +1 -1
  51. package/dist/esm/fieldset/fieldset.js.map +1 -1
  52. package/dist/esm/focusgroup/focusgroup.js +2 -0
  53. package/dist/esm/focusgroup/focusgroup.js.map +1 -0
  54. package/dist/esm/index.js +1 -1
  55. package/dist/esm/invokers/invokers.js.map +1 -1
  56. package/dist/esm/packages/web/package.js +2 -0
  57. package/dist/esm/packages/web/package.js.map +1 -0
  58. package/dist/esm/pagination/pagination.js +1 -1
  59. package/dist/esm/pagination/pagination.js.map +1 -1
  60. package/dist/esm/popover/popover.js +1 -1
  61. package/dist/esm/popover/popover.js.map +1 -1
  62. package/dist/esm/readonly/readonly.js +1 -1
  63. package/dist/esm/readonly/readonly.js.map +1 -1
  64. package/dist/esm/suggestion/suggestion.js +1 -1
  65. package/dist/esm/suggestion/suggestion.js.map +1 -1
  66. package/dist/esm/toggle-group/toggle-group.js +1 -1
  67. package/dist/esm/toggle-group/toggle-group.js.map +1 -1
  68. package/dist/esm/tooltip/tooltip.js +1 -1
  69. package/dist/esm/tooltip/tooltip.js.map +1 -1
  70. package/dist/esm/utils/utils.js +1 -1
  71. package/dist/esm/utils/utils.js.map +1 -1
  72. package/dist/index.d.ts +18 -22
  73. package/dist/index.js +636 -263
  74. package/dist/umd/index.js +16 -11
  75. package/dist/umd/index.js.map +1 -1
  76. package/package.json +15 -10
  77. package/dist/index.d.ts.map +0 -1
  78. package/dist/index.js.map +0 -1
package/dist/index.js CHANGED
@@ -3,7 +3,13 @@ import { autoUpdate, computePosition, flip, limitShift, offset, shift, size } fr
3
3
  import { UHTMLComboboxElement } from "@u-elements/u-combobox";
4
4
  import * as UTabs from "@u-elements/u-tabs";
5
5
  export * from "@u-elements/u-datalist";
6
+ //#region package.json
7
+ var version = "1.19.0";
8
+ //#endregion
6
9
  //#region src/utils/utils.ts
10
+ const ARIA_DESC = "aria-description";
11
+ const ARIA_LABEL = "aria-label";
12
+ const ARIA_LABELLEDBY = "aria-labelledby";
7
13
  const QUICK_EVENT = {
8
14
  passive: true,
9
15
  capture: true
@@ -27,7 +33,7 @@ const warn = (message, ...args) => !isBrowser() || window.dsWarnings === false |
27
33
  * @param value A valid attribute value or null to remove attribute
28
34
  */
29
35
  const attr = (el, name, value) => {
30
- if (value === void 0) return el.getAttribute(name) ?? null;
36
+ if (value === void 0) return el.getAttribute(name);
31
37
  if (value === null) el.removeAttribute(name);
32
38
  else if (el.getAttribute(name) !== value) el.setAttribute(name, value);
33
39
  return null;
@@ -51,10 +57,45 @@ const STRIP_QUOTES = /^["']|["']$/g;
51
57
  const attrOrCSS = (el, name) => {
52
58
  let value = attr(el, name);
53
59
  if (!value) value = getCSSProp(el, `--_ds-${name}`).replace(STRIP_QUOTES, "").trim();
54
- if (!value) warn(`Missing ${name} on:`, el);
55
60
  return value || null;
56
61
  };
57
62
  /**
63
+ * getRoot
64
+ * @description Helper for better compatibility
65
+ * @param node The target node
66
+ * @return The shadow root or document
67
+ */
68
+ const getRoot = (node) => {
69
+ const root = node?.getRootNode?.() || node?.ownerDocument;
70
+ if (root instanceof Document || root instanceof ShadowRoot) return root;
71
+ return node?.ownerDocument || document;
72
+ };
73
+ /**
74
+ * getComposedTarget
75
+ * @description Helper to get the composed target of an event, supporting ShadowDOM rendering
76
+ * @param event The event
77
+ * @return The target Element
78
+ */
79
+ const getComposedTarget = (e) => {
80
+ const el = e.target?.shadowRoot && e.composedPath()[0] || e.target;
81
+ return el?.nodeType === 1 ? el : null;
82
+ };
83
+ /**
84
+ * getComposedPath
85
+ * @description Helper to get the full composed path even if listener is bound to a ShadowRoot (unlike event.composedPath())
86
+ * @param el The element to start traversing from
87
+ * @return Set of nodes in the path
88
+ */
89
+ const getComposedPath = (el) => {
90
+ const path = /* @__PURE__ */ new Set();
91
+ while (el) {
92
+ path.add(el);
93
+ if (el.nodeType === 11) el = el.host;
94
+ else el = el.assignedSlot || el.parentNode;
95
+ }
96
+ return path;
97
+ };
98
+ /**
58
99
  * on
59
100
  * @param el The Element to use as EventTarget
60
101
  * @param types A space separated string of event types
@@ -75,6 +116,7 @@ const off = (el, ...rest) => {
75
116
  const [types, ...options] = rest;
76
117
  for (const type of types.split(" ")) el.removeEventListener(type, ...options);
77
118
  };
119
+ const HOT_RELOAD_KEY = `_dsHotReloadCleanup${version}`;
78
120
  /**
79
121
  * onHotReload
80
122
  * @description Runs a callback when window is loaded in browser, and ensures cleanup when hot-reloading
@@ -83,9 +125,9 @@ const off = (el, ...rest) => {
83
125
  */
84
126
  const onHotReload = (key, setup) => {
85
127
  if (!isBrowser()) return;
86
- if (!window._dsHotReloadCleanup) window._dsHotReloadCleanup = /* @__PURE__ */ new Map();
87
- window._dsHotReloadCleanup?.get(key)?.map((cleanup) => cleanup());
88
- window._dsHotReloadCleanup?.set(key, setup());
128
+ if (!window[HOT_RELOAD_KEY]) window[HOT_RELOAD_KEY] = /* @__PURE__ */ new Map();
129
+ for (const cleanup of window[HOT_RELOAD_KEY]?.get(key) || []) cleanup();
130
+ window[HOT_RELOAD_KEY]?.set(key, setup());
89
131
  };
90
132
  /**
91
133
  * MutationObserver wrapper with automatic cleanup
@@ -153,60 +195,86 @@ const announceMount = () => {
153
195
  onHotReload("announce", () => [on(document, "focus mouseover", announceMount, QUICK_EVENT)]);
154
196
  //#endregion
155
197
  //#region src/clickdelegatefor/clickdelegatefor.ts
156
- const CLASS_HOVER = ":click-delegate-hover";
157
198
  const ATTR_CLICKDELEGATEFOR = "data-clickdelegatefor";
158
- const SELECTOR_CLICKDELEGATEFOR = `[${ATTR_CLICKDELEGATEFOR}]`;
159
- const SELECTOR_SKIP = "a,button,label,input,select,textarea,details,dialog,[role=\"button\"],[popover],[contenteditable]";
199
+ const CLASS_HOVER = ":click-delegate-hover";
200
+ const SKIP = /* @__PURE__ */ new Set([
201
+ "A",
202
+ "BUTTON",
203
+ "DETAILS",
204
+ "DIALOG",
205
+ "INPUT",
206
+ "LABEL",
207
+ "SELECT",
208
+ "TEXTAREA"
209
+ ]);
160
210
  const handleClickDelegateFor = (event) => {
161
211
  const isNewTab = event.button === 1 || event.metaKey || event.ctrlKey;
212
+ const target = getComposedTarget(event);
162
213
  const delegateTarget = event.button < 2 && getDelegateTarget(event);
163
- if (!delegateTarget || delegateTarget.contains(event.target)) return;
214
+ if (!delegateTarget || delegateTarget.contains(target)) return;
164
215
  if (isNewTab && delegateTarget instanceof HTMLAnchorElement) return window.open(delegateTarget.href, void 0, delegateTarget.rel);
165
216
  event.stopImmediatePropagation();
166
- delegateTarget.click();
217
+ if (delegateTarget.nodeName !== "LABEL") delegateTarget.click();
167
218
  };
168
219
  let HOVER;
169
- const handleMouseOver = (event) => {
220
+ let TARGET$1;
221
+ const handleMouseMove = (event) => {
222
+ const target = getComposedTarget(event);
223
+ if (!target || target === TARGET$1) return;
224
+ TARGET$1 = target;
170
225
  const delegateTarget = getDelegateTarget(event);
171
226
  if (HOVER === delegateTarget) return;
172
227
  if (HOVER) HOVER.classList.remove(CLASS_HOVER);
173
228
  if (delegateTarget) delegateTarget.classList.add(CLASS_HOVER);
174
229
  HOVER = delegateTarget;
175
230
  };
176
- const getDelegateTarget = ({ target: el }) => {
177
- const id = (el instanceof Element ? el.closest(SELECTOR_CLICKDELEGATEFOR) : null)?.getAttribute(ATTR_CLICKDELEGATEFOR);
178
- const target = id && document.getElementById(id) || void 0;
179
- const skip = target && el.closest(SELECTOR_SKIP);
180
- return (!skip || skip === target) && !target?.disabled ? target : void 0;
231
+ const getDelegateTarget = (event) => {
232
+ let interactive;
233
+ for (let el of event.composedPath()) {
234
+ if (el.nodeType !== 1) continue;
235
+ if (el.nodeName === "LABEL") el = el.control || el;
236
+ const id = el.getAttribute(ATTR_CLICKDELEGATEFOR);
237
+ if (id) {
238
+ const to = getRoot(el).getElementById(id);
239
+ return to && !(interactive && interactive !== to) && !to.disabled && !to.readOnly ? to : void 0;
240
+ }
241
+ if (!interactive && isInteractive(el)) interactive = el;
242
+ }
181
243
  };
182
- onHotReload("clickdelegatefor", () => [on(window, "click auxclick", handleClickDelegateFor, true), on(document, "mouseover", handleMouseOver, QUICK_EVENT)]);
244
+ const isInteractive = (el) => el.isContentEditable || el.popover || SKIP.has(el.nodeName) || attr(el, "role") === "button";
245
+ onHotReload("clickdelegatefor", () => [on(window, "click auxclick", handleClickDelegateFor, true), on(document, "mousemove", handleMouseMove, QUICK_EVENT)]);
183
246
  //#endregion
184
247
  //#region src/dialog/dialog.ts
185
248
  let DOWN_INSIDE = false;
186
- const handleClosedbyAny = ({ type, target: el, clientX: x = 0, clientY: y = 0 }) => {
187
- if (type === "pointerdown") {
188
- const r = el?.closest?.("dialog")?.getBoundingClientRect();
189
- DOWN_INSIDE = !!(r && r.top <= y && y <= r.bottom && r.left <= x && x <= r.right);
190
- } else {
191
- const isClose = el instanceof HTMLDialogElement && !DOWN_INSIDE && attr(el, "closedby") === "any";
249
+ const handleClosedbyAny = (event) => {
250
+ const { type, clientX: x = 0, clientY: y = 0 } = event;
251
+ const el = getComposedTarget(event);
252
+ if (el && type === "pointerdown") for (const dialog of getComposedPath(el)) {
253
+ if (dialog.nodeName !== "DIALOG") continue;
254
+ const r = dialog.getBoundingClientRect();
255
+ DOWN_INSIDE = r.top <= y && y <= r.bottom && r.left <= x && x <= r.right;
256
+ return;
257
+ }
258
+ else {
259
+ const isClose = el?.nodeName === "DIALOG" && !DOWN_INSIDE && attr(el, "closedby") === "any";
192
260
  DOWN_INSIDE = false;
193
- if (isClose) requestAnimationFrame(() => el.open && el.close());
261
+ if (isClose) setTimeout(close, 0, el);
194
262
  }
195
263
  };
196
- const BUTTONS = isBrowser() ? document.getElementsByTagName("button") : [];
197
- const handleAriaAttributes$2 = () => {
198
- for (const btn of BUTTONS) if (btn.getAttribute("command")?.endsWith("-modal")) btn.setAttribute("aria-haspopup", "dialog");
264
+ const close = (dialog) => dialog.open && dialog.close();
265
+ const MODAL = "show-modal";
266
+ const NON_MODAL = "--show-non-modal";
267
+ const handleAriaAttributes = (event) => {
268
+ for (const el of event.composedPath()) {
269
+ const command = el.nodeType === 1 && attr(el, "command");
270
+ if (command === MODAL || command === NON_MODAL) return attr(el, "aria-haspopup", "dialog");
271
+ }
199
272
  };
200
- const handleCommand = ({ command, target }) => command === "--show-non-modal" && target instanceof HTMLDialogElement && target.show();
273
+ const handleCommand = ({ command, target }) => command === NON_MODAL && target instanceof HTMLDialogElement && target.show();
201
274
  onHotReload("dialog", () => [
202
275
  on(document, "command", handleCommand, QUICK_EVENT),
203
- on(document, "pointerdown pointerup", handleClosedbyAny, QUICK_EVENT),
204
- onMutation(document, handleAriaAttributes$2, {
205
- attributeFilter: ["command"],
206
- attributes: true,
207
- childList: true,
208
- subtree: true
209
- })
276
+ on(document, "focus", handleAriaAttributes, QUICK_EVENT),
277
+ on(document, "pointerdown pointerup", handleClosedbyAny, QUICK_EVENT)
210
278
  ]);
211
279
  //#endregion
212
280
  //#region src/fieldset/fieldset.ts
@@ -214,7 +282,7 @@ const FIELDSETS = isBrowser() ? document.getElementsByTagName("fieldset") : [];
214
282
  const handleFieldsetMutations = () => {
215
283
  for (const el of FIELDSETS) {
216
284
  if (el.hasAttribute("aria-labelledby")) continue;
217
- attr(el, "aria-labelledby", `${useId(el.querySelector("legend"))} ${useId(el.querySelector(":scope > :is([data-field=\"description\"],legend + p)"))}`.trim() || null);
285
+ attr(el, ARIA_LABELLEDBY, `${useId(el.querySelector("legend"))} ${useId(el.querySelector(":scope > :is([data-field=\"description\"],legend + p)"))}`.trim() || null);
218
286
  }
219
287
  };
220
288
  onHotReload("fieldset", () => [onMutation(document, handleFieldsetMutations, {
@@ -222,6 +290,187 @@ onHotReload("fieldset", () => [onMutation(document, handleFieldsetMutations, {
222
290
  subtree: true
223
291
  })]);
224
292
  //#endregion
293
+ //#region src/focusgroup/focusgroup.ts
294
+ const ATTR_GROUP = "focusgroup";
295
+ const PROP_GROUP = "focusGroup";
296
+ const ATTR_START = `${ATTR_GROUP}start`;
297
+ const GROUPS = /* @__PURE__ */ new WeakMap();
298
+ const ROOTS$1 = /* @__PURE__ */ new Map();
299
+ const ROLES = {
300
+ listbox: {
301
+ block: true,
302
+ wrap: false,
303
+ items: "option"
304
+ },
305
+ menu: {
306
+ block: true,
307
+ wrap: true,
308
+ items: "menuitem"
309
+ },
310
+ menubar: {
311
+ block: false,
312
+ wrap: true,
313
+ items: "menuitem"
314
+ },
315
+ radiogroup: {
316
+ block: null,
317
+ wrap: true,
318
+ items: "radio"
319
+ },
320
+ tablist: {
321
+ block: false,
322
+ wrap: true,
323
+ items: "tab"
324
+ },
325
+ toolbar: {
326
+ block: false,
327
+ wrap: false,
328
+ items: null
329
+ }
330
+ };
331
+ let IS_TAB = false;
332
+ const IS_CONFLICT = /* @__PURE__ */ new Set([
333
+ "AUDIO",
334
+ "VIDEO",
335
+ "TEXTAREA",
336
+ "SELECT",
337
+ "date",
338
+ "datetime-local",
339
+ "email",
340
+ "month",
341
+ "number",
342
+ "password",
343
+ "range",
344
+ "search",
345
+ "tel",
346
+ "text",
347
+ "time",
348
+ "url",
349
+ "week"
350
+ ]);
351
+ const IS_SUPPORTED = isBrowser() && (ATTR_GROUP in HTMLElement.prototype || PROP_GROUP in HTMLElement.prototype);
352
+ const setIsTab = (state) => IS_TAB = state;
353
+ const handleKeydown$2 = (e) => {
354
+ if (e.defaultPrevented || e.altKey || e.metaKey || e.ctrlKey) return;
355
+ const isTab = e.key === "Tab";
356
+ const isBlock = e.key === "ArrowUp" || e.key === "ArrowDown";
357
+ const isArrow = isBlock || e.key === "ArrowLeft" || e.key === "ArrowRight";
358
+ if (!isTab && !isArrow && e.key !== "Home" && e.key !== "End") return;
359
+ if (isTab) {
360
+ setIsTab(true);
361
+ setTimeout(setIsTab, 100, false);
362
+ }
363
+ const target = getComposedTarget(e);
364
+ if (!target || isConflict(target)) return;
365
+ let group = getGroup(getComposedPath(target));
366
+ if (group?.role && group?.el === target) group = getGroup(getComposedPath(group.el.parentNode));
367
+ if (!group?.role) return;
368
+ const items = getItems(group.el, target);
369
+ let next = 0;
370
+ if (isTab) return setTimeout(setTab, 0, items, setTab(items));
371
+ if (e.key === "Home") next = 0;
372
+ else if (e.key === "End") next = items.length - 1;
373
+ else {
374
+ const { direction: dir, writingMode: mode } = getComputedStyle(target);
375
+ const isFlipped = mode.startsWith("vertical");
376
+ const isReverse = isFlipped ? mode === "vertical-rl" : dir === "rtl";
377
+ const isForward = e.key === "ArrowDown" || e.key === "ArrowRight";
378
+ const moveBlock = isBlock !== isFlipped;
379
+ const moveForward = isForward !== (!isBlock && isReverse);
380
+ if ((group.block ?? moveBlock) !== moveBlock) return;
381
+ next = items.indexOf(target) + (moveForward ? 1 : -1);
382
+ if (group.wrap) next = (items.length + next) % items.length;
383
+ else next = Math.max(0, Math.min(next, items.length - 1));
384
+ }
385
+ if (items[next] !== target) e.preventDefault();
386
+ items[next]?.focus?.();
387
+ };
388
+ const handleFocus = (e) => {
389
+ const target = getComposedTarget(e);
390
+ const path = getComposedPath(target);
391
+ for (const [el, off] of ROOTS$1) if (!path.has(el) && ROOTS$1.delete(el)) off();
392
+ for (const el of path) if (el.nodeType === 11 && !ROOTS$1.has(el)) ROOTS$1.set(el, on(el, "focus", handleFocus, QUICK_EVENT));
393
+ if (e.target?.shadowRoot) return;
394
+ const group = getGroup(path);
395
+ if (!group?.role) return;
396
+ if (IS_TAB && !isConflict(group.focus)) {
397
+ const segment = getSegment(getItems(group.el, null), target);
398
+ const start = group.memory && segment.includes(group.focus) && group.focus || segment.find((el) => el?.hasAttribute(ATTR_START)) || segment[0];
399
+ if (start !== target) return start?.focus?.();
400
+ }
401
+ group.focus = target;
402
+ };
403
+ const getGroup = (path) => {
404
+ for (const el of path) {
405
+ if (el.nodeType !== Node.ELEMENT_NODE) continue;
406
+ const key = el.getAttribute(ATTR_GROUP);
407
+ if (key !== null) {
408
+ let group = GROUPS.get(el);
409
+ if (group?.key === key) return group;
410
+ group = parseGroup(el, key);
411
+ GROUPS.set(el, group);
412
+ for (const item of getItems(el)) if (item && !item.hasAttribute("role")) attr(item, "role", group.items);
413
+ if (!el.hasAttribute("role")) attr(el, "role", group.role);
414
+ return group;
415
+ }
416
+ if (isTopLayer(el)) return;
417
+ }
418
+ };
419
+ const parseGroup = (el, key) => {
420
+ const opts = new Set(key.toLowerCase().split(" "));
421
+ const role = [...opts].find((t) => t in ROLES);
422
+ const base = ROLES[role];
423
+ const x = opts.has("inline");
424
+ const y = opts.has("block");
425
+ return {
426
+ key,
427
+ el,
428
+ role,
429
+ block: x && y ? null : y || (x ? false : base?.block),
430
+ focus: null,
431
+ items: base?.items,
432
+ memory: !opts.has("nomemory"),
433
+ wrap: opts.has("wrap") || (opts.has("nowrap") ? false : base?.wrap)
434
+ };
435
+ };
436
+ const getItems = (root, keep, items = [], isNested = false) => {
437
+ const children = root?.nodeName === "SLOT" ? root.assignedElements({ flatten: true }) : (root?.shadowRoot || root)?.children;
438
+ for (let i = 0, l = children?.length || i; children && i < l; i++) {
439
+ const el = children[i];
440
+ if (el.nodeName === "SLOT") getItems(el, keep, items, isNested);
441
+ else if (isKeyboardReachable(el) && !isTopLayer(el) && isVisible(el)) {
442
+ const group = el.getAttribute(ATTR_GROUP);
443
+ if (el === keep || !isNested && group !== "none" && isFocusable$1(el)) items.push(el);
444
+ else if (group !== null && keep === null) items.push(null);
445
+ else getItems(el, keep, items, isNested || group !== null);
446
+ }
447
+ }
448
+ return items;
449
+ };
450
+ const getSegment = (acc, item) => {
451
+ const at = acc.indexOf(item);
452
+ const to = acc.indexOf(null, at);
453
+ return acc.slice(acc.lastIndexOf(null, at) + 1, to === -1 ? void 0 : to);
454
+ };
455
+ const isKeyboardReachable = (el) => !el.inert && !el.hidden && !el.disabled;
456
+ const isTopLayer = (el) => el?.nodeName === "DIALOG" || el?.hasAttribute("popover");
457
+ const isVisible = isBrowser() && typeof Element.prototype.checkVisibility === "function" ? (el) => el.checkVisibility() : (el) => el.offsetParent !== null;
458
+ const isFocusable$1 = (el) => el.isContentEditable || el.tabIndex >= 0 && !el.disabled;
459
+ const isConflict = (el) => el?.isContentEditable || IS_CONFLICT.has(el?.nodeName) || el?.nodeName === "INPUT" && IS_CONFLICT.has(el.type);
460
+ const setTab = (items, prevs) => Array.from(items, (item, index) => {
461
+ const prev = item?.getAttribute("tabindex");
462
+ if (item) attr(item, "tabindex", prevs ? prevs[index] : "-1");
463
+ return prev;
464
+ });
465
+ if (!IS_SUPPORTED) onHotReload(ATTR_GROUP, () => [
466
+ on(document, "keydown", handleKeydown$2),
467
+ on(document, "focus", handleFocus, QUICK_EVENT),
468
+ () => {
469
+ for (const [, off] of ROOTS$1) off();
470
+ ROOTS$1.clear();
471
+ }
472
+ ]);
473
+ //#endregion
225
474
  //#region ../../node_modules/.pnpm/invokers-polyfill@1.0.3/node_modules/invokers-polyfill/invoker.js
226
475
  function isSupported$1() {
227
476
  return typeof HTMLButtonElement !== "undefined" && "command" in HTMLButtonElement.prototype && "source" in ((globalThis.CommandEvent || {}).prototype || {});
@@ -450,14 +699,17 @@ function apply$1() {
450
699
  //#region src/invokers/invokers.ts
451
700
  if (isBrowser() && !isSupported$1()) apply$1();
452
701
  //#endregion
453
- //#region ../../node_modules/.pnpm/@oddbird+popover-polyfill@0.6.1/node_modules/@oddbird/popover-polyfill/dist/popover-fn.js
702
+ //#region ../../node_modules/.pnpm/@oddbird+popover-polyfill@0.7.1/node_modules/@oddbird/popover-polyfill/dist/popover-fn.js
454
703
  var ToggleEvent = class extends Event {
455
704
  oldState;
456
705
  newState;
457
- constructor(type, { oldState = "", newState = "", ...init } = {}) {
458
- super(type, init);
459
- this.oldState = String(oldState || "");
460
- this.newState = String(newState || "");
706
+ constructor(type, init = {}) {
707
+ const eventInit = Object.assign({}, init);
708
+ delete eventInit.oldState;
709
+ delete eventInit.newState;
710
+ super(type, eventInit);
711
+ this.oldState = String(init.oldState || "");
712
+ this.newState = String(init.newState || "");
461
713
  }
462
714
  };
463
715
  var popoverToggleTaskQueue = /* @__PURE__ */ new WeakMap();
@@ -575,7 +827,6 @@ function topMostPopoverAncestor(newPopover, list) {
575
827
  if (!candidate) return;
576
828
  let okNesting = false;
577
829
  let candidateAncestor = null;
578
- let candidatePosition = null;
579
830
  while (!okNesting) {
580
831
  candidateAncestor = nearestInclusiveOpenPopover(candidate) || null;
581
832
  if (candidateAncestor === null) return;
@@ -583,7 +834,7 @@ function topMostPopoverAncestor(newPopover, list) {
583
834
  if (newPopover.popover === "hint" || candidateAncestor.popover === "auto") okNesting = true;
584
835
  if (!okNesting) candidate = candidateAncestor.parentElement;
585
836
  }
586
- candidatePosition = popoverPositions.get(candidateAncestor);
837
+ const candidatePosition = popoverPositions.get(candidateAncestor) ?? null;
587
838
  if (topMostPopoverAncestor2 === null || popoverPositions.get(topMostPopoverAncestor2) < candidatePosition) topMostPopoverAncestor2 = candidateAncestor;
588
839
  }
589
840
  checkAncestor(newPopover.parentElement || getRootNode(newPopover));
@@ -782,9 +1033,14 @@ function setInvokerAriaExpanded(el, force = false) {
782
1033
  }
783
1034
  }
784
1035
  var ShadowRoot2 = globalThis.ShadowRoot || function() {};
1036
+ var DEFAULT_LAYER_NAME = "popover-polyfill";
785
1037
  function isSupported() {
786
1038
  return typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype === "object" && "popover" in HTMLElement.prototype;
787
1039
  }
1040
+ function isPolyfilled() {
1041
+ var _a;
1042
+ return Boolean(((_a = document.body) == null ? void 0 : _a.showPopover) && !/native code/i.test(document.body.showPopover.toString()));
1043
+ }
788
1044
  function patchSelectorFn(object, name, mapper) {
789
1045
  const original = object[name];
790
1046
  Object.defineProperty(object, name, { value(selector) {
@@ -795,10 +1051,12 @@ var nonEscapedPopoverSelector = /(^|[^\\]):popover-open\b/g;
795
1051
  function hasLayerSupport() {
796
1052
  return typeof globalThis.CSSLayerBlockRule === "function";
797
1053
  }
798
- function getStyles() {
1054
+ function getStyles(layerName) {
1055
+ var _a;
799
1056
  const useLayer = hasLayerSupport();
1057
+ const layerNameEscaped = (layerName ?? ((_a = window.POPOVER_POLYFILL_OPTIONS) == null ? void 0 : _a.layerName) ?? DEFAULT_LAYER_NAME).split(".").map(CSS.escape).join(".");
800
1058
  return `
801
- ${useLayer ? "@layer popover-polyfill {" : ""}
1059
+ ${useLayer ? `@layer ${layerNameEscaped} {` : ""}
802
1060
  :where([popover]) {
803
1061
  position: fixed;
804
1062
  z-index: 2147483647;
@@ -862,8 +1120,8 @@ ${useLayer ? "}" : ""}
862
1120
  `;
863
1121
  }
864
1122
  var popoverStyleSheet = null;
865
- function injectStyles(root) {
866
- const styles = getStyles();
1123
+ function injectStyles(root, layerName) {
1124
+ const styles = getStyles(layerName);
867
1125
  if (popoverStyleSheet === null) try {
868
1126
  popoverStyleSheet = new CSSStyleSheet();
869
1127
  popoverStyleSheet.replaceSync(styles);
@@ -877,8 +1135,9 @@ function injectStyles(root) {
877
1135
  else root.prepend(sheet);
878
1136
  } else root.adoptedStyleSheets = [popoverStyleSheet, ...root.adoptedStyleSheets];
879
1137
  }
880
- function apply() {
1138
+ function apply(opts) {
881
1139
  if (typeof window === "undefined") return;
1140
+ const layerName = opts == null ? void 0 : opts.layerName;
882
1141
  window.ToggleEvent = window.ToggleEvent || ToggleEvent;
883
1142
  function rewriteSelector(selector) {
884
1143
  if (selector == null ? void 0 : selector.includes(":popover-open")) selector = selector.replace(nonEscapedPopoverSelector, "$1.\\:popover-open");
@@ -939,7 +1198,7 @@ function apply() {
939
1198
  writable: true,
940
1199
  value(options) {
941
1200
  const shadowRoot = originalAttachShadow.call(this, options);
942
- injectStyles(shadowRoot);
1201
+ injectStyles(shadowRoot, layerName);
943
1202
  return shadowRoot;
944
1203
  }
945
1204
  } });
@@ -950,7 +1209,7 @@ function apply() {
950
1209
  writable: true,
951
1210
  value() {
952
1211
  const internals = originalAttachInternals.call(this);
953
- if (internals.shadowRoot) injectStyles(internals.shadowRoot);
1212
+ if (internals.shadowRoot) injectStyles(internals.shadowRoot, layerName);
954
1213
  return internals;
955
1214
  }
956
1215
  } });
@@ -1032,23 +1291,25 @@ function apply() {
1032
1291
  root.addEventListener("pointerup", lightDismissOpenPopovers);
1033
1292
  };
1034
1293
  addEventListeners(document);
1035
- injectStyles(document);
1294
+ injectStyles(document, layerName);
1036
1295
  }
1037
1296
  //#endregion
1038
1297
  //#region src/popover/popover.ts
1039
- if (isBrowser() && !isSupported()) apply();
1298
+ if (isBrowser() && !isSupported() && !isPolyfilled()) apply({ layerName: "ds.base" });
1299
+ const ROOTS = /* @__PURE__ */ new Map();
1040
1300
  const ATTR_PLACE = "data-placement";
1041
1301
  const ATTR_AUTO = "data-autoplacement";
1042
1302
  const POPOVERS = /* @__PURE__ */ new Map();
1043
- function handleToggle(event) {
1044
- let { newState, oldState, target: el, source = event.detail } = event;
1303
+ const handleToggle = (e) => toggle(getComposedTarget(e), e.newState, e.oldState, e.source || e.detail);
1304
+ function toggle(el, newState, oldState, source) {
1045
1305
  const float = el instanceof HTMLElement && attr(el, "popover") !== null && getCSSProp(el, "--_ds-floating");
1046
- if (!float) return;
1047
- if (newState === "closed") return POPOVERS.get(el)?.();
1306
+ const prev = POPOVERS.get(el);
1307
+ if (newState === "open" && prev && prev.source === source) return;
1308
+ prev?.cleanup();
1309
+ if (newState === "closed" || !float) return;
1048
1310
  if (!source) {
1049
- const root = el.getRootNode();
1050
- const css = `[popovertarget="${el.id}"],[commandfor="${el.id}"]`;
1051
- source = el.id && root?.querySelector?.(css) || void 0;
1311
+ const css = el.id && `[popovertarget="${el.id}"],[commandfor="${el.id}"]`;
1312
+ source = css && getRoot(el).querySelector(css) || void 0;
1052
1313
  }
1053
1314
  if (!source || source === el || oldState && oldState === newState) return;
1054
1315
  el.style.scrollMarginBottom = `var(--_ds-floating-arrow-size)`;
@@ -1081,19 +1342,66 @@ function handleToggle(event) {
1081
1342
  ]
1082
1343
  };
1083
1344
  const unfloat = autoUpdate(source, el, async () => {
1084
- if (!source?.isConnected) return POPOVERS.get(el)?.();
1345
+ if (!source?.isConnected) return POPOVERS.get(el)?.cleanup();
1085
1346
  const { x, y } = await computePosition(source, el, options);
1086
1347
  el.style.translate = `${x}px ${y}px`;
1087
1348
  });
1088
- POPOVERS.set(el, () => POPOVERS.delete(el) && unfloat());
1349
+ POPOVERS.set(el, {
1350
+ source,
1351
+ cleanup: () => POPOVERS.delete(el) && unfloat()
1352
+ });
1089
1353
  }
1090
1354
  let IS_SCROLL;
1091
- const handleScrollbar = ({ type }) => {
1092
- if (type === "mousedown") IS_SCROLL = false;
1093
- if (type === "scroll" && IS_SCROLL === false) IS_SCROLL = true;
1094
- if (type === "mouseup" && IS_SCROLL) for (const [popover] of POPOVERS) popover.showPopover();
1355
+ const handleScrollbar = (e) => {
1356
+ if (e.type === "pointerdown") IS_SCROLL = false;
1357
+ if (e.type === "scroll" && IS_SCROLL === false) IS_SCROLL = true;
1358
+ if (e.type === "pointerup" && IS_SCROLL) for (const [popover] of POPOVERS) popover.showPopover();
1359
+ };
1360
+ const handleClick$1 = (e) => {
1361
+ for (const [root, off] of ROOTS) if (!root.host?.isConnected && ROOTS.delete(root)) off();
1362
+ const root = getRoot(getComposedTarget(e));
1363
+ if (root instanceof ShadowRoot && !ROOTS.has(root)) ROOTS.set(root, on(root, "toggle", handleToggle, QUICK_EVENT));
1095
1364
  };
1096
- onHotReload("popover", () => [on(document, "mousedown scroll mouseup", handleScrollbar, true), on(document, "toggle ds-toggle-source", handleToggle, QUICK_EVENT)]);
1365
+ onHotReload("popover", () => {
1366
+ const isFunction = (fn) => typeof fn === "function";
1367
+ const togglePopover = HTMLElement.prototype.togglePopover;
1368
+ const showPopover = HTMLElement.prototype.showPopover;
1369
+ const hidePopover = HTMLElement.prototype.hidePopover;
1370
+ if (isFunction(togglePopover)) HTMLElement.prototype.togglePopover = function(opt) {
1371
+ const isOpen = this.matches(":popover-open");
1372
+ const isBool = typeof opt === "boolean";
1373
+ const prev = isOpen ? "open" : "closed";
1374
+ const next = (isBool ? opt : opt?.force ?? !isOpen) ? "open" : "closed";
1375
+ const source = isBool ? void 0 : opt?.source;
1376
+ const result = togglePopover.call(this, opt);
1377
+ toggle(this, next, prev, source);
1378
+ return result;
1379
+ };
1380
+ if (isFunction(showPopover)) HTMLElement.prototype.showPopover = function(opt) {
1381
+ const prev = this.matches(":popover-open") ? "open" : "closed";
1382
+ const result = showPopover.call(this, opt);
1383
+ toggle(this, "open", prev, opt?.source);
1384
+ return result;
1385
+ };
1386
+ if (isFunction(hidePopover)) HTMLElement.prototype.hidePopover = function() {
1387
+ const prev = this.matches(":popover-open") ? "open" : "closed";
1388
+ const result = hidePopover.call(this);
1389
+ toggle(this, "closed", prev);
1390
+ return result;
1391
+ };
1392
+ return [
1393
+ on(document, "click", handleClick$1, QUICK_EVENT),
1394
+ on(document, "pointerdown pointerup scroll", handleScrollbar, QUICK_EVENT),
1395
+ on(document, "toggle ds-toggle-source", handleToggle, QUICK_EVENT),
1396
+ () => {
1397
+ HTMLElement.prototype.togglePopover = togglePopover;
1398
+ HTMLElement.prototype.showPopover = showPopover;
1399
+ HTMLElement.prototype.hidePopover = hidePopover;
1400
+ for (const [, off] of ROOTS) off();
1401
+ ROOTS.clear();
1402
+ }
1403
+ ];
1404
+ });
1097
1405
  const arrowPseudo = () => ({
1098
1406
  name: "arrowPseudo",
1099
1407
  fn(data) {
@@ -1109,89 +1417,104 @@ const arrowPseudo = () => ({
1109
1417
  });
1110
1418
  //#endregion
1111
1419
  //#region src/readonly/readonly.ts
1112
- const isReadOnly = (el) => (el instanceof HTMLSelectElement || el instanceof HTMLInputElement) && (el.hasAttribute("readonly") || attr(el, "aria-readonly") === "true");
1113
- const handleKeyDown = (e) => {
1114
- if (e.key !== "Tab" && isReadOnly(e.target)) {
1115
- const isArrow = e.key?.startsWith("Arrow");
1116
- const isModifier = e.altKey || e.ctrlKey || e.metaKey;
1117
- if (isArrow || !isModifier) e.preventDefault();
1118
- if (isArrow && attr(e.target, "type") === "radio") {
1119
- const all = document.querySelectorAll(`input[name="${e.target.name}"]`);
1120
- const move = e.key?.match(/Arrow(Right|Down)/) ? 1 : -1;
1121
- all[(all.length + [...all].indexOf(e.target) + move) % all.length]?.focus();
1122
- }
1123
- }
1420
+ const isReadOnly = (el) => attr(el, "readonly") !== null || attr(el, "aria-readonly") === "true";
1421
+ const handleKeydown$1 = (e) => {
1422
+ if (!(e.key === "Tab" || e.altKey || e.ctrlKey || e.metaKey) || e.key?.startsWith("Arrow")) handleSelect(e);
1423
+ };
1424
+ const handleSelect = (e) => {
1425
+ const el = getComposedTarget(e);
1426
+ if (el?.nodeName === "SELECT" && isReadOnly(el)) e.preventDefault();
1124
1427
  };
1125
1428
  const handleClick = (e) => {
1126
- const input = e.target?.closest?.("label")?.control || e.target;
1127
- if (isReadOnly(input)) {
1128
- e.preventDefault();
1129
- input.focus();
1429
+ for (let el of e.composedPath()) {
1430
+ if (el?.nodeName === "LABEL") el = el.control;
1431
+ if (el?.nodeName === "INPUT" || el?.nodeName === "SELECT") {
1432
+ if (isReadOnly(el)) {
1433
+ e.stopImmediatePropagation();
1434
+ e.preventDefault();
1435
+ el[el.nodeName === "SELECT" ? "blur" : "focus"]();
1436
+ requestAnimationFrame(() => el.isConnected && el.focus());
1437
+ }
1438
+ return;
1439
+ }
1130
1440
  }
1131
1441
  };
1132
- const handleMouseDown = (e) => {
1133
- if (e.target instanceof HTMLSelectElement && isReadOnly(e.target)) e.preventDefault();
1134
- };
1135
1442
  onHotReload("readonly", () => [
1136
- on(document, "keydown", handleKeyDown),
1137
- on(document, "click", handleClick),
1138
- on(document, "mousedown", handleMouseDown)
1443
+ on(document, "keydown", handleKeydown$1),
1444
+ on(document, "click", handleClick, true),
1445
+ on(document, "mousedown", handleSelect, true)
1139
1446
  ]);
1140
1447
  //#endregion
1141
1448
  //#region src/toggle-group/toggle-group.ts
1142
- const ARIA_LABELLEDBY = "aria-labelledby";
1143
- const ARIA_LABEL$1 = "aria-label";
1449
+ const ATTR_FOCUSGROUP = "focusgroup";
1144
1450
  const ATTR_TOGGLEGROUP = "data-toggle-group";
1145
1451
  const SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;
1146
- const handleAriaAttributes$1 = () => {
1147
- for (const group of document.querySelectorAll(SELECTOR_TOGGLEGROUP)) attr(group, "aria-label", attrOrCSS(group, ATTR_TOGGLEGROUP));
1452
+ let IS_ARROW;
1453
+ const handleMutations$1 = (root, records) => {
1454
+ for (const r of records || [null]) if (r?.attributeName === ATTR_TOGGLEGROUP) deprecate(r.target);
1455
+ else if (!r || r.addedNodes.length) {
1456
+ const scope = r?.target || root;
1457
+ for (const el of scope.querySelectorAll(SELECTOR_TOGGLEGROUP)) deprecate(el);
1458
+ }
1148
1459
  };
1149
- const handleKeydown = (event) => {
1150
- const { key, target: el } = event;
1151
- const group = el instanceof HTMLInputElement && el.closest(SELECTOR_TOGGLEGROUP);
1152
- if (!group) return;
1153
- if (!attr(group, ARIA_LABEL$1) && !attr(group, ARIA_LABELLEDBY)) attr(group, ARIA_LABEL$1, attrOrCSS(group, ATTR_TOGGLEGROUP));
1154
- if (key === "Enter") el.click();
1155
- if (key?.startsWith("Arrow")) {
1156
- event.preventDefault?.();
1157
- const inputs = [...group.getElementsByTagName("input")];
1158
- const index = inputs.indexOf(el);
1159
- const move = key.match(/Arrow(Right|Down)/) ? 1 : -1;
1160
- let nextIndex = index;
1161
- for (let i = 0; i < inputs.length; i++) {
1162
- nextIndex = (inputs.length + nextIndex + move) % inputs.length;
1163
- if (!inputs[nextIndex]?.disabled) {
1164
- inputs[nextIndex]?.focus();
1165
- break;
1166
- }
1167
- }
1460
+ const handleRadioClick = (e) => {
1461
+ if (IS_ARROW && getRadioInFocusGroup(e)) {
1462
+ e.preventDefault();
1463
+ e.stopImmediatePropagation();
1168
1464
  }
1169
1465
  };
1170
- onHotReload("toggle-group", () => [on(document, "keydown", handleKeydown), onMutation(document, handleAriaAttributes$1, {
1171
- attributeFilter: [ATTR_TOGGLEGROUP],
1172
- attributes: true,
1173
- childList: true,
1174
- subtree: true
1175
- })]);
1466
+ const resetIsArrow = () => IS_ARROW = false;
1467
+ const handleKeydown = (e) => {
1468
+ IS_ARROW = e.key?.startsWith("Arrow");
1469
+ if (e.key === "Enter") getRadioInFocusGroup(e)?.click();
1470
+ if (IS_ARROW) setTimeout(resetIsArrow, 0);
1471
+ };
1472
+ const getRadioInFocusGroup = (e) => {
1473
+ const el = getComposedTarget(e);
1474
+ if (el?.nodeName !== "INPUT" || el.type !== "radio" || !el.name) return;
1475
+ for (const group of getComposedPath(el)) {
1476
+ if (group.nodeType !== 1 || !group.hasAttribute(ATTR_FOCUSGROUP)) continue;
1477
+ if (!attr(group, ATTR_FOCUSGROUP)?.includes("radiogroup")) return;
1478
+ return el;
1479
+ }
1480
+ };
1481
+ const deprecate = (el) => {
1482
+ if (el.hasAttribute(ATTR_FOCUSGROUP)) return;
1483
+ const label = attrOrCSS(el, ATTR_TOGGLEGROUP);
1484
+ const labelledby = attr(el, ARIA_LABELLEDBY)?.trim();
1485
+ const message = `Please use ${ATTR_FOCUSGROUP}="radiogroup" and ${labelledby ? ARIA_LABELLEDBY : ARIA_LABEL}="${labelledby || label}" instead of deprecated ${ATTR_TOGGLEGROUP} on:`;
1486
+ attr(el, ARIA_LABEL, labelledby ? null : label);
1487
+ attr(el, ATTR_FOCUSGROUP, "radiogroup");
1488
+ warn(message, el);
1489
+ };
1490
+ onHotReload("toggle-group", () => [
1491
+ on(document, "click", handleRadioClick, true),
1492
+ on(document, "keydown", handleKeydown),
1493
+ onMutation(document, handleMutations$1, {
1494
+ attributeFilter: [ATTR_TOGGLEGROUP],
1495
+ attributes: true,
1496
+ childList: true,
1497
+ subtree: true
1498
+ })
1499
+ ]);
1176
1500
  //#endregion
1177
1501
  //#region src/tooltip/tooltip.ts
1178
1502
  let TIP;
1179
- let SOURCE;
1180
- let IS_HOVERING = false;
1181
- let HOVER_TIMER = 0;
1182
- let SKIP_TIMER = 0;
1183
- const IS_IOS = isBrowser() && /iPad|iPhone|iPod/.test(navigator.userAgent);
1184
- const ATTR_TOOLTIP = "data-tooltip";
1503
+ let TIMER;
1504
+ let TARGET;
1505
+ let OPEN;
1506
+ let LAST_HIDE = 0;
1185
1507
  const ATTR_COLOR = "data-color";
1186
- const ARIA_LABEL = "aria-label";
1187
- const ARIA_DESC = "aria-description";
1188
- const SELECTOR_COLOR = `[${ATTR_COLOR}]`;
1189
- const SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;
1190
1508
  const ATTR_SCHEME = "data-color-scheme";
1191
- const SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;
1192
- const SELECTOR_INTERACTIVE = "a,button,input,label,select,textarea,[tabindex]";
1509
+ const ATTR_SIZE = "data-size";
1510
+ const ATTR_TOOLTIP = "data-tooltip";
1193
1511
  const DELAY_HOVER = 300;
1194
1512
  const DELAY_SKIP = 300;
1513
+ const IS_IOS = isBrowser() && /iPad|iPhone|iPod/.test(navigator.userAgent);
1514
+ const SELECTOR_COLOR = `[${ATTR_COLOR}]`;
1515
+ const SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;
1516
+ const SELECTOR_SIZE = `[${ATTR_SIZE}]`;
1517
+ const SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;
1195
1518
  /**
1196
1519
  * setTooltipElement
1197
1520
  * @description Allows setting a custom tooltip element. It does not need to, and should not, be injected to document.body, as we inject on hover to ensure React hydration works as expected.
@@ -1199,75 +1522,83 @@ const DELAY_SKIP = 300;
1199
1522
  */
1200
1523
  const setTooltipElement = (el) => {
1201
1524
  if (el && !(el instanceof HTMLElement)) warn("setTooltipElement expects an HTMLElement, got: ", el);
1202
- clearTimeout(SKIP_TIMER);
1203
- clearTimeout(HOVER_TIMER);
1204
- SOURCE = void 0;
1205
- IS_HOVERING = false;
1525
+ hide();
1526
+ LAST_HIDE = 0;
1206
1527
  TIP = el || void 0;
1207
1528
  };
1208
- const handleAriaAttributes = () => {
1209
- for (const el of document.querySelectorAll(SELECTOR_TOOLTIP)) {
1210
- const text = attrOrCSS(el, ATTR_TOOLTIP);
1211
- if (!text) return;
1212
- if (text !== (el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC))) {
1213
- const hasText = attr(el, "role") !== "img" && el.textContent?.trim();
1214
- attr(el, ATTR_TOOLTIP, text);
1215
- attr(el, ARIA_LABEL, hasText ? null : text);
1216
- attr(el, ARIA_DESC, hasText ? text : null);
1217
- if (!el.matches(SELECTOR_INTERACTIVE)) warn("Missing tabindex=\"0\" attribute on: ", el);
1218
- }
1219
- const isCurrent = el === SOURCE && TIP?.offsetHeight && TIP?.offsetWidth;
1220
- const isChanged = isCurrent && text && TIP?.textContent !== text;
1221
- if (isCurrent && isChanged) {
1222
- if (TIP) TIP.textContent = text;
1223
- if (document.activeElement === el) announce(text);
1529
+ const initTooltips = (scope = document) => {
1530
+ for (const el of scope?.querySelectorAll(SELECTOR_TOOLTIP) || []) setupText(el);
1531
+ };
1532
+ const handleMutations = (_, records) => {
1533
+ if (!records) return initTooltips();
1534
+ for (const r of records) {
1535
+ if (r.target === TIP) continue;
1536
+ if (r.attributeName === ATTR_TOOLTIP) setupText(r.target);
1537
+ else for (const el of r.addedNodes) {
1538
+ if (el.nodeType !== 1) continue;
1539
+ if (el.hasAttribute(ATTR_TOOLTIP)) setupText(el);
1540
+ else initTooltips(el);
1224
1541
  }
1225
1542
  }
1226
1543
  };
1227
- const handleInterest = (event) => {
1228
- const { type, target } = event;
1229
- clearTimeout(HOVER_TIMER);
1230
- if (target === TIP) return;
1231
- const source = target?.closest?.(SELECTOR_TOOLTIP);
1232
- if (type === "blur") {
1233
- const next = event.relatedTarget;
1234
- if (source === SOURCE && !next?.closest?.(SELECTOR_TOOLTIP)) hideTooltip();
1235
- return;
1236
- }
1237
- if (type === "mouseover" && !IS_HOVERING && !IS_IOS) {
1238
- HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target });
1239
- return;
1544
+ const setupText = (el, canAnnounce = true) => {
1545
+ let text = attrOrCSS(el, ATTR_TOOLTIP) || "";
1546
+ if (text[0] === "#") text = getRoot(el).getElementById(text.slice(1))?.textContent?.trim() || "";
1547
+ if (text !== (el.getAttribute("aria-label") || el.getAttribute("aria-description"))) {
1548
+ const hasText = attr(el, "role") !== "img" && el.textContent?.trim();
1549
+ attr(el, ATTR_TOOLTIP, text);
1550
+ attr(el, ARIA_LABEL, hasText ? null : text);
1551
+ attr(el, ARIA_DESC, hasText ? text : null);
1552
+ if (el.tabIndex === -1) warn("Missing tabindex=\"0\" attribute on: ", el);
1553
+ }
1554
+ if (el === OPEN && TIP?.textContent !== text) {
1555
+ if (TIP) TIP.textContent = text;
1556
+ if (text && canAnnounce && document.activeElement === el) announce(text);
1240
1557
  }
1241
- if (source === SOURCE) return;
1242
- if (!source) return hideTooltip();
1558
+ };
1559
+ const handleInterest = (e) => {
1560
+ const target = getComposedTarget(e);
1561
+ if (!target || TARGET === target || TIP?.contains(target)) return;
1562
+ TARGET = target;
1563
+ const source = TARGET?.closest?.(SELECTOR_TOOLTIP) || void 0;
1564
+ if (OPEN === source) return;
1565
+ if (OPEN) hide();
1566
+ OPEN = source;
1567
+ if (!source) return;
1568
+ if (e.type === "focus" || IS_IOS || DELAY_SKIP > Date.now() - LAST_HIDE) return show();
1569
+ if (e.type === "mousemove") TIMER = setTimeout(show, DELAY_HOVER);
1570
+ };
1571
+ const show = () => {
1572
+ if (!OPEN) return hide();
1243
1573
  if (!TIP) TIP = tag("div", { class: "ds-tooltip" });
1244
1574
  if (!TIP.isConnected) document.body.appendChild(TIP);
1245
- const color = source.closest(SELECTOR_COLOR);
1246
- const scheme = source.closest(SELECTOR_SCHEME);
1575
+ const color = OPEN.closest(SELECTOR_COLOR);
1576
+ const scheme = OPEN.closest(SELECTOR_SCHEME);
1577
+ const size = OPEN.closest(SELECTOR_SIZE);
1247
1578
  const isReset = color !== scheme && color?.contains(scheme);
1248
- clearTimeout(SKIP_TIMER);
1249
1579
  attr(TIP, "popover", "manual");
1250
1580
  attr(TIP, ATTR_SCHEME, scheme?.getAttribute(ATTR_SCHEME) || null);
1251
1581
  attr(TIP, ATTR_COLOR, isReset && color?.getAttribute(ATTR_COLOR) || null);
1252
- TIP.textContent = attr(source, ATTR_TOOLTIP);
1582
+ attr(TIP, ATTR_SIZE, size?.getAttribute(ATTR_SIZE) || null);
1583
+ setupText(OPEN, false);
1253
1584
  TIP.showPopover();
1254
- TIP.dispatchEvent(new CustomEvent("ds-toggle-source", { detail: source }));
1255
- IS_HOVERING = true;
1256
- SOURCE = source;
1585
+ TIP.dispatchEvent(new CustomEvent("ds-toggle-source", {
1586
+ bubbles: true,
1587
+ composed: true,
1588
+ detail: OPEN
1589
+ }));
1257
1590
  };
1258
- const hideTooltip = () => TIP?.isConnected && TIP.popover && TIP.hidePopover();
1259
- const handleClose = (event) => {
1260
- if (event?.type === "keydown") return event?.key === "Escape" && hideTooltip();
1261
- if (!event) IS_HOVERING = false;
1262
- else if (event.target === TIP && event.newState === "closed") {
1263
- SOURCE = void 0;
1264
- SKIP_TIMER = setTimeout(handleClose, DELAY_SKIP);
1265
- }
1591
+ const hide = (event) => {
1592
+ if (event?.type === "keydown" && event?.key !== "Escape") return;
1593
+ if (OPEN && TIP?.isConnected && TIP.popover) TIP.hidePopover();
1594
+ if (!event) LAST_HIDE = Date.now();
1595
+ clearTimeout(TIMER);
1596
+ OPEN = void 0;
1266
1597
  };
1267
1598
  onHotReload("tooltip", () => [
1268
- on(document, "blur focus mouseover", handleInterest, QUICK_EVENT),
1269
- on(document, "toggle keydown", handleClose, QUICK_EVENT),
1270
- onMutation(document, handleAriaAttributes, {
1599
+ on(document, "focus mousemove", handleInterest, QUICK_EVENT),
1600
+ on(document, "keydown", hide, QUICK_EVENT),
1601
+ onMutation(document, handleMutations, {
1271
1602
  attributeFilter: [ATTR_TOOLTIP],
1272
1603
  attributes: true,
1273
1604
  childList: true,
@@ -1276,18 +1607,19 @@ onHotReload("tooltip", () => [
1276
1607
  ]);
1277
1608
  //#endregion
1278
1609
  //#region src/breadcrumbs/breadcrumbs.ts
1279
- const ATTR_LABEL$1 = "aria-label";
1280
1610
  var DSBreadcrumbsElement = class extends DSElement {
1281
1611
  _items;
1282
- _label = null;
1612
+ _label = {
1613
+ key: ARIA_LABEL,
1614
+ value: null
1615
+ };
1283
1616
  _unresize;
1284
1617
  _unmutate;
1285
1618
  static get observedAttributes() {
1286
- return [ATTR_LABEL$1];
1619
+ return [ARIA_LABEL, ARIA_LABELLEDBY];
1287
1620
  }
1288
1621
  connectedCallback() {
1289
1622
  const resize = debounce(() => render$3(this), 100);
1290
- this._label = attrOrCSS(this, ATTR_LABEL$1);
1291
1623
  this._items = this.getElementsByTagName("a");
1292
1624
  this._unresize = on(window, "resize", resize);
1293
1625
  this._unmutate = onMutation(this, render$3, {
@@ -1295,10 +1627,8 @@ var DSBreadcrumbsElement = class extends DSElement {
1295
1627
  subtree: true
1296
1628
  });
1297
1629
  }
1298
- attributeChangedCallback(_name, _prev, next) {
1299
- if (!this._unmutate || !next) return;
1300
- this._label = next;
1301
- render$3(this);
1630
+ attributeChangedCallback() {
1631
+ if (this._unmutate) render$3(this);
1302
1632
  }
1303
1633
  disconnectedCallback() {
1304
1634
  this._unresize?.();
@@ -1311,8 +1641,18 @@ const render$3 = (self) => {
1311
1641
  const lastItemInList = lastItem?.parentElement === self ? null : lastItem;
1312
1642
  const isListHidden = !lastItemInList?.offsetHeight;
1313
1643
  attr(self, "role", isListHidden ? null : "navigation");
1314
- attr(self, ATTR_LABEL$1, isListHidden ? null : self._label);
1315
1644
  for (const item of self._items || []) attr(item, "aria-current", item === lastItemInList ? "page" : null);
1645
+ const label = attrOrCSS(self, ARIA_LABEL)?.trim();
1646
+ const labelledby = attr(self, ARIA_LABELLEDBY)?.trim();
1647
+ const prevLabel = label || labelledby;
1648
+ if (prevLabel) self._label.value = prevLabel;
1649
+ if (prevLabel) self._label.key = labelledby ? ARIA_LABELLEDBY : ARIA_LABEL;
1650
+ const nextLabel = isListHidden ? null : self._label.value;
1651
+ if (prevLabel !== nextLabel) {
1652
+ attr(self, ARIA_LABELLEDBY, null);
1653
+ attr(self, ARIA_LABEL, null);
1654
+ attr(self, self._label.key, nextLabel);
1655
+ }
1316
1656
  };
1317
1657
  customElements.define("ds-breadcrumbs", DSBreadcrumbsElement);
1318
1658
  //#endregion
@@ -1339,11 +1679,11 @@ var DSErrorSummaryElement = class extends DSElement {
1339
1679
  }
1340
1680
  };
1341
1681
  const render$2 = (self) => {
1342
- const label = attr(self, "aria-label")?.trim();
1343
- const labelledBy = attr(self, "aria-labelledby")?.trim();
1682
+ const label = attr(self, ARIA_LABEL)?.trim();
1683
+ const labelledBy = attr(self, ARIA_LABELLEDBY)?.trim();
1344
1684
  const heading = self.querySelector("h2,h3,h4,h5,h6");
1345
- if (heading && !label && !labelledBy) attr(self, "aria-labelledby", useId(heading));
1346
- if (!heading && !label && !labelledBy) warn("Missing accessible name on:", self, "\nAdd a heading (h2–h6), or set aria-label or aria-labelledby to provide an accessible name for screen readers.");
1685
+ if (heading && !label && !labelledBy) attr(self, ARIA_LABELLEDBY, useId(heading));
1686
+ if (!heading && !label && !labelledBy) warn("Missing accessible name on:", self, `\nAdd a heading (h2–h6), or set ${ARIA_LABEL} or ${ARIA_LABELLEDBY} to provide an accessible name for screen readers.`);
1347
1687
  };
1348
1688
  customElements.define("ds-error-summary", DSErrorSummaryElement);
1349
1689
  //#endregion
@@ -1352,32 +1692,22 @@ const ATTR_INVALID = "aria-invalid";
1352
1692
  const ATTR_DESCRIBEDBY = "aria-describedby";
1353
1693
  const ATTR_INDETERMINATE = "data-indeterminate";
1354
1694
  const COUNTER_DEBOUNCE = isWindows() ? 800 : 200;
1355
- const COUNTS = /* @__PURE__ */ new WeakMap();
1356
- const FIELDS = /* @__PURE__ */ new Map();
1357
- const VALIDATIONS = /* @__PURE__ */ new WeakSet();
1358
1695
  const WARNING_MULTIPLE_INPUTS = `Fields should only have one input element. Use <fieldset> to group multiple fields:`;
1359
- const handleFieldMutations = (_doc, records = []) => {
1360
- for (const { target } of records) {
1361
- const isFieldset = target instanceof HTMLFieldSetElement;
1362
- for (const [field] of FIELDS) if (isFieldset ? target.contains(field) : field.contains(target)) handleFieldMutation(field);
1363
- }
1364
- };
1365
1696
  const handleFieldMutation = (field) => {
1366
1697
  const labels = [];
1367
1698
  const nextDescs = [];
1368
- const prevDescs = FIELDS.get(field) || [];
1369
- let input;
1370
- let counter;
1699
+ const prevDescs = field._descs || [];
1371
1700
  let hasValidation = false;
1372
1701
  let invalid = false;
1702
+ if (field._input?.hidden) field._input = void 0;
1373
1703
  for (const el of field.getElementsByTagName("*")) {
1374
1704
  if (el instanceof HTMLLabelElement) labels.push(el);
1375
1705
  if (el.hidden) continue;
1376
- if (isInputLike(el)) if (input) warn(WARNING_MULTIPLE_INPUTS, field);
1377
- else input = el;
1706
+ if (isInputLike(el)) if (field._input?.isConnected && field._input !== el) warn(WARNING_MULTIPLE_INPUTS, field);
1707
+ else field._input = el;
1378
1708
  else {
1379
1709
  const type = el.getAttribute("data-field");
1380
- if (type === "counter") counter = el;
1710
+ if (type === "counter") field._counter = el;
1381
1711
  if (type === "validation") {
1382
1712
  nextDescs.unshift(useId(el));
1383
1713
  hasValidation = true;
@@ -1385,9 +1715,9 @@ const handleFieldMutation = (field) => {
1385
1715
  } else if (type) nextDescs.push(useId(el));
1386
1716
  }
1387
1717
  }
1388
- if (!input) return;
1389
- if (counter) COUNTS.set(input, counter);
1390
- for (const label of labels) attr(label, "for", useId(input));
1718
+ if (!field._input?.isConnected) return;
1719
+ const input = field._input;
1720
+ for (const label of labels) attr(label, "for", useId(input) || null);
1391
1721
  const fieldsetValidation = field.closest("fieldset")?.querySelector(":scope > [data-field=\"validation\"]");
1392
1722
  if (fieldsetValidation && !fieldsetValidation?.hidden) {
1393
1723
  hasValidation = true;
@@ -1399,69 +1729,76 @@ const handleFieldMutation = (field) => {
1399
1729
  if (input.type === "radio" || input.type === "checkbox") attr(field, "data-clickdelegatefor", useId(input));
1400
1730
  const keep = (attr(input, ATTR_DESCRIBEDBY)?.trim().split(/\s+/))?.filter((id) => !prevDescs.includes(id)) || [];
1401
1731
  attr(input, ATTR_DESCRIBEDBY, [...nextDescs, ...keep].join(" ") || null);
1402
- FIELDS.set(field, nextDescs);
1403
- const hadValidation = VALIDATIONS.has(input);
1732
+ field._descs = nextDescs;
1733
+ const hadValidation = field._validation;
1404
1734
  if (hasValidation && !hadValidation && !input.hasAttribute(ATTR_INVALID)) {
1405
1735
  attr(input, ATTR_INVALID, "true");
1406
- VALIDATIONS.add(input);
1736
+ field._validation = true;
1407
1737
  } else if (!hasValidation && hadValidation) {
1408
1738
  attr(input, ATTR_INVALID, null);
1409
- VALIDATIONS.delete(input);
1739
+ field._validation = void 0;
1410
1740
  }
1411
- handleFieldInput(input);
1741
+ field.handleEvent();
1412
1742
  };
1413
1743
  const TEXTS = {
1414
1744
  over: "%d tegn for mye",
1415
1745
  under: "%d tegn igjen"
1416
1746
  };
1417
- const handleFieldInput = (e) => {
1418
- const input = e.target || e;
1419
- const counter = COUNTS.get(input);
1420
- if (counter?.isConnected) {
1421
- const count = (Number(attr(counter, "data-limit")) || 0) - input.value.length;
1422
- const state = count < 0 ? "over" : "under";
1423
- const label = (attrOrCSS(counter, `data-${state}`) || TEXTS[state])?.replace("%d", `${Math.abs(count)}`);
1424
- attr(counter, "data-label", label);
1425
- attr(counter, "data-state", state);
1426
- attr(counter, "data-color", count < 0 ? "danger" : null);
1427
- if (e.type === "input" && label) debouncedCounterLiveRegion(input, label);
1428
- }
1429
- if (input instanceof HTMLTextAreaElement) {
1430
- input.style.setProperty("--_ds-field-sizing", "auto");
1431
- input.style.setProperty("--_ds-field-sizing", `${input.scrollHeight}px`);
1432
- }
1433
- };
1434
1747
  const debouncedCounterLiveRegion = debounce((input, text) => {
1435
1748
  if (document.activeElement === input) announce(text);
1436
1749
  }, COUNTER_DEBOUNCE);
1437
1750
  const isInvalid = (el) => el.getAttribute("data-color") !== "success";
1438
1751
  const isInputLike = (el) => el instanceof HTMLElement && "validity" in el && !(el instanceof HTMLButtonElement) && el.type !== "hidden";
1439
1752
  var DSFieldElement = class extends DSElement {
1753
+ _counter;
1754
+ _descs;
1755
+ _input;
1756
+ _validation;
1757
+ _unevents;
1758
+ _unmutate;
1440
1759
  connectedCallback() {
1441
- FIELDS.set(this, []);
1442
- handleFieldMutation(this);
1760
+ this._unevents = on(this, "input", this, QUICK_EVENT);
1761
+ this._unmutate = onMutation(this, () => handleFieldMutation(this), {
1762
+ attributeFilter: [
1763
+ "data-field",
1764
+ "data-limit",
1765
+ "hidden",
1766
+ "id",
1767
+ "value",
1768
+ ATTR_INDETERMINATE
1769
+ ],
1770
+ attributes: true,
1771
+ childList: true,
1772
+ subtree: true
1773
+ });
1774
+ }
1775
+ handleEvent(event) {
1776
+ const { _counter, _input } = this;
1777
+ if (_counter?.isConnected && _input) {
1778
+ const count = (Number(attr(_counter, "data-limit")) || 0) - _input.value.length;
1779
+ const state = count < 0 ? "over" : "under";
1780
+ const label = (attrOrCSS(_counter, `data-${state}`) || TEXTS[state])?.replace("%d", `${Math.abs(count)}`);
1781
+ if (!label) warn(`Missing data-${state} on:`, _counter);
1782
+ attr(_counter, "data-label", label);
1783
+ attr(_counter, "data-state", state);
1784
+ attr(_counter, "data-color", count < 0 ? "danger" : null);
1785
+ if (event?.type === "input" && label) debouncedCounterLiveRegion(_input, label);
1786
+ }
1787
+ if (_input instanceof HTMLTextAreaElement) {
1788
+ _input.style.setProperty("--_ds-field-sizing", "auto");
1789
+ _input.style.setProperty("--_ds-field-sizing", `${_input.scrollHeight}px`);
1790
+ }
1443
1791
  }
1444
1792
  disconnectedCallback() {
1445
- FIELDS.delete(this);
1793
+ this._unevents?.();
1794
+ this._unmutate?.();
1795
+ this._unevents = this._unmutate = void 0;
1796
+ this._input = this._counter = this._descs = this._validation = void 0;
1446
1797
  }
1447
1798
  };
1448
1799
  customElements.define("ds-field", DSFieldElement);
1449
- onHotReload("field", () => [on(document, "input", handleFieldInput, QUICK_EVENT), onMutation(document, handleFieldMutations, {
1450
- attributeFilter: [
1451
- "data-field",
1452
- "data-limit",
1453
- "hidden",
1454
- "id",
1455
- "value",
1456
- ATTR_INDETERMINATE
1457
- ],
1458
- attributes: true,
1459
- childList: true,
1460
- subtree: true
1461
- })]);
1462
1800
  //#endregion
1463
1801
  //#region src/pagination/pagination.ts
1464
- const ATTR_LABEL = "aria-label";
1465
1802
  const ATTR_CURRENT = "data-current";
1466
1803
  const ATTR_TOTAL = "data-total";
1467
1804
  const ATTR_HREF = "data-href";
@@ -1479,7 +1816,8 @@ var DSPaginationElement = class extends DSElement {
1479
1816
  _render;
1480
1817
  static get observedAttributes() {
1481
1818
  return [
1482
- ATTR_LABEL,
1819
+ ARIA_LABEL,
1820
+ ARIA_LABELLEDBY,
1483
1821
  ATTR_CURRENT,
1484
1822
  ATTR_TOTAL,
1485
1823
  ATTR_HREF
@@ -1490,7 +1828,10 @@ var DSPaginationElement = class extends DSElement {
1490
1828
  const current = attr(this, ATTR_CURRENT);
1491
1829
  if (current && !total) warn(`Missing ${ATTR_TOTAL} attribute on:`, this);
1492
1830
  if (total && !current) warn(`Missing ${ATTR_CURRENT} attribute on:`, this);
1493
- attr(this, ATTR_LABEL, attrOrCSS(this, ATTR_LABEL));
1831
+ const label = attrOrCSS(this, ARIA_LABEL);
1832
+ const labelledby = attr(this, ARIA_LABELLEDBY)?.trim();
1833
+ if (label || labelledby) attr(this, ARIA_LABEL, labelledby ? null : label);
1834
+ else warn(`Missing ${ARIA_LABEL} on:`, this);
1494
1835
  attr(this, "role", "navigation");
1495
1836
  this._unmutate = onMutation(this, render$1, {
1496
1837
  childList: true,
@@ -1520,7 +1861,7 @@ const render$1 = (self) => {
1520
1861
  items.forEach((item, i) => {
1521
1862
  const page = i ? items[i + 1] ? pages[i - 1]?.page : next : prev;
1522
1863
  attr(item, "aria-current", pages[i - 1]?.current ? "true" : null);
1523
- attr(item, "aria-label", `${page ?? "hidden"}`);
1864
+ attr(item, ARIA_LABEL, `${page ?? "hidden"}`);
1524
1865
  attr(item, "role", page ? null : "none");
1525
1866
  attr(item, "tabindex", page ? null : "-1");
1526
1867
  if (item instanceof HTMLButtonElement) attr(item, "value", `${page}`);
@@ -1540,32 +1881,66 @@ const getSteps = (now, max, show = Number.POSITIVE_INFINITY) => {
1540
1881
  customElements.define("ds-pagination", DSPaginationElement);
1541
1882
  //#endregion
1542
1883
  //#region src/suggestion/suggestion.ts
1884
+ const ATTR_EMPTY = "data-empty";
1885
+ const ATTR_CREATE = "data-create";
1543
1886
  var DSSuggestionElement = class extends UHTMLComboboxElement {
1544
1887
  _unmutate;
1545
1888
  connectedCallback() {
1546
1889
  super.connectedCallback();
1547
1890
  this._unmutate = onMutation(this, render, { childList: true });
1891
+ on(this, "comboboxafterselect input", handleEmpty, QUICK_EVENT);
1548
1892
  on(this, "toggle", polyfillToggleSource, QUICK_EVENT);
1549
1893
  }
1550
1894
  disconnectedCallback() {
1551
1895
  super.disconnectedCallback();
1552
1896
  this._unmutate?.();
1553
1897
  this._unmutate = void 0;
1898
+ off(this, "comboboxafterselect input", handleEmpty, QUICK_EVENT);
1554
1899
  off(this, "toggle", polyfillToggleSource, QUICK_EVENT);
1555
1900
  }
1556
1901
  };
1557
1902
  const render = (self) => {
1558
1903
  const { control, list } = self;
1559
1904
  const datalist = list || self.querySelector("u-datalist");
1560
- if (control && !control.placeholder) attr(control, "placeholder", " ");
1561
- if (control) attr(control, "popovertarget", useId(list) || null);
1562
- if (datalist) attr(datalist, "popover", "manual");
1563
- if (datalist) attr(datalist, "data-is-floating", "true");
1905
+ if (control) {
1906
+ attr(control, "popovertarget", list ? useId(list) : null);
1907
+ if (!control.placeholder) attr(control, "placeholder", " ");
1908
+ }
1909
+ if (datalist) {
1910
+ attr(datalist, "popover", "manual");
1911
+ attr(datalist, "data-is-floating", "true");
1912
+ }
1913
+ };
1914
+ const handleEmpty = ({ currentTarget: self }) => {
1915
+ const { creatable, control, options } = self;
1916
+ if (!options) return;
1917
+ const value = control?.value.trim() || "";
1918
+ const query = value.toLowerCase();
1919
+ let empty;
1920
+ let exists = !value;
1921
+ for (const opt of options) {
1922
+ if (!empty && opt.hasAttribute(ATTR_EMPTY)) empty = opt;
1923
+ else if (!exists && opt.label?.toLowerCase() === query) exists = true;
1924
+ if (exists && empty) break;
1925
+ }
1926
+ if (!empty) return;
1927
+ empty.hidden = exists;
1928
+ empty.label = value;
1929
+ empty.value = creatable ? value : "";
1930
+ if (!creatable || empty.textContent) return;
1931
+ const text = attrOrCSS(empty, ATTR_EMPTY);
1932
+ if (!text) warn(`Missing ${ATTR_EMPTY} value on:`, empty);
1933
+ else attr(empty, ATTR_EMPTY, text);
1934
+ attr(empty, ATTR_CREATE, text?.replace("{value}", value));
1564
1935
  };
1565
1936
  const polyfillToggleSource = (event) => {
1566
1937
  const self = event.currentTarget;
1567
1938
  const detail = event.newState === "open" && self.control;
1568
- if (detail) self.list?.dispatchEvent(new CustomEvent("ds-toggle-source", { detail }));
1939
+ if (detail) self.list?.dispatchEvent(new CustomEvent("ds-toggle-source", {
1940
+ bubbles: true,
1941
+ composed: true,
1942
+ detail
1943
+ }));
1569
1944
  };
1570
1945
  customElements.define("ds-suggestion", DSSuggestionElement);
1571
1946
  //#endregion
@@ -1579,6 +1954,4 @@ customElements.define("ds-tablist", DSTabListElement);
1579
1954
  customElements.define("ds-tab", DSTabElement);
1580
1955
  customElements.define("ds-tabpanel", DSTabPanelElement);
1581
1956
  //#endregion
1582
- export { DSBreadcrumbsElement, DSErrorSummaryElement, DSFieldElement, DSPaginationElement, DSSuggestionElement, DSTabElement, DSTabListElement, DSTabPanelElement, DSTabsElement, pagination, setTooltipElement };
1583
-
1584
- //# sourceMappingURL=index.js.map
1957
+ export { DSBreadcrumbsElement, DSErrorSummaryElement, DSFieldElement, DSPaginationElement, DSSuggestionElement, DSTabElement, DSTabListElement, DSTabPanelElement, DSTabsElement, initTooltips, pagination, setTooltipElement };