@digdir/designsystemet-web 1.13.0 → 1.13.2

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 (68) hide show
  1. package/README.md +12 -1
  2. package/dist/cjs/_vendors/invokers-polyfill/invoker.cjs +2 -0
  3. package/dist/cjs/_vendors/invokers-polyfill/invoker.cjs.map +1 -0
  4. package/dist/cjs/breadcrumbs/breadcrumbs.cjs +1 -1
  5. package/dist/cjs/breadcrumbs/breadcrumbs.cjs.map +1 -1
  6. package/dist/cjs/clickdelegatefor/clickdelegatefor.cjs +1 -1
  7. package/dist/cjs/clickdelegatefor/clickdelegatefor.cjs.map +1 -1
  8. package/dist/cjs/dialog/dialog.cjs +1 -1
  9. package/dist/cjs/dialog/dialog.cjs.map +1 -1
  10. package/dist/cjs/error-summary/error-summary.cjs +1 -1
  11. package/dist/cjs/error-summary/error-summary.cjs.map +1 -1
  12. package/dist/cjs/field/field.cjs +1 -1
  13. package/dist/cjs/field/field.cjs.map +1 -1
  14. package/dist/cjs/fieldset/fieldset.cjs +2 -0
  15. package/dist/cjs/fieldset/fieldset.cjs.map +1 -0
  16. package/dist/cjs/index.cjs +1 -1
  17. package/dist/cjs/index.cjs.map +1 -1
  18. package/dist/cjs/pagination/pagination.cjs +1 -1
  19. package/dist/cjs/pagination/pagination.cjs.map +1 -1
  20. package/dist/cjs/popover/popover.cjs +1 -1
  21. package/dist/cjs/popover/popover.cjs.map +1 -1
  22. package/dist/cjs/readonly/readonly.cjs +1 -1
  23. package/dist/cjs/readonly/readonly.cjs.map +1 -1
  24. package/dist/cjs/suggestion/suggestion.cjs +1 -1
  25. package/dist/cjs/suggestion/suggestion.cjs.map +1 -1
  26. package/dist/cjs/toggle-group/toggle-group.cjs +1 -1
  27. package/dist/cjs/toggle-group/toggle-group.cjs.map +1 -1
  28. package/dist/cjs/tooltip/tooltip.cjs +1 -1
  29. package/dist/cjs/tooltip/tooltip.cjs.map +1 -1
  30. package/dist/cjs/utils/utils.cjs +1 -1
  31. package/dist/cjs/utils/utils.cjs.map +1 -1
  32. package/dist/custom-elements.json +18 -12
  33. package/dist/esm/_vendors/invokers-polyfill/invoker.js +2 -0
  34. package/dist/esm/_vendors/invokers-polyfill/invoker.js.map +1 -0
  35. package/dist/esm/breadcrumbs/breadcrumbs.js +1 -1
  36. package/dist/esm/breadcrumbs/breadcrumbs.js.map +1 -1
  37. package/dist/esm/clickdelegatefor/clickdelegatefor.js +1 -1
  38. package/dist/esm/clickdelegatefor/clickdelegatefor.js.map +1 -1
  39. package/dist/esm/dialog/dialog.js +1 -1
  40. package/dist/esm/dialog/dialog.js.map +1 -1
  41. package/dist/esm/error-summary/error-summary.js +1 -1
  42. package/dist/esm/error-summary/error-summary.js.map +1 -1
  43. package/dist/esm/field/field.js +1 -1
  44. package/dist/esm/field/field.js.map +1 -1
  45. package/dist/esm/fieldset/fieldset.js +2 -0
  46. package/dist/esm/fieldset/fieldset.js.map +1 -0
  47. package/dist/esm/index.js +1 -1
  48. package/dist/esm/index.js.map +1 -1
  49. package/dist/esm/pagination/pagination.js +1 -1
  50. package/dist/esm/pagination/pagination.js.map +1 -1
  51. package/dist/esm/popover/popover.js +1 -1
  52. package/dist/esm/popover/popover.js.map +1 -1
  53. package/dist/esm/readonly/readonly.js +1 -1
  54. package/dist/esm/readonly/readonly.js.map +1 -1
  55. package/dist/esm/suggestion/suggestion.js +1 -1
  56. package/dist/esm/suggestion/suggestion.js.map +1 -1
  57. package/dist/esm/toggle-group/toggle-group.js +1 -1
  58. package/dist/esm/toggle-group/toggle-group.js.map +1 -1
  59. package/dist/esm/tooltip/tooltip.js +1 -1
  60. package/dist/esm/tooltip/tooltip.js.map +1 -1
  61. package/dist/esm/utils/utils.js +1 -1
  62. package/dist/esm/utils/utils.js.map +1 -1
  63. package/dist/index.d.ts +18 -5
  64. package/dist/index.js +473 -151
  65. package/dist/umd/index.js +4 -4
  66. package/dist/umd/index.js.map +1 -1
  67. package/dist/web.manifest.json +64 -42
  68. package/package.json +19 -14
package/dist/index.js CHANGED
@@ -1,5 +1,294 @@
1
- // src/index.ts
2
- import { isSupported, apply as polyfillInvokers } from "invokers-polyfill/fn";
1
+ // ../../node_modules/.pnpm/invokers-polyfill@1.0.2_patch_hash=d5677be15320f04cdc552d82cb4a79242bd1cf8b26bcbb0a13e1153e7bed3b5a/node_modules/invokers-polyfill/invoker.js
2
+ function isSupported() {
3
+ return typeof HTMLButtonElement !== "undefined" && "command" in HTMLButtonElement.prototype && "source" in ((globalThis.CommandEvent || {}).prototype || {});
4
+ }
5
+ function apply() {
6
+ document.addEventListener(
7
+ "invoke",
8
+ (e) => {
9
+ if (e.type == "invoke" && e.isTrusted) {
10
+ e.stopImmediatePropagation();
11
+ e.preventDefault();
12
+ }
13
+ },
14
+ true
15
+ );
16
+ document.addEventListener(
17
+ "command",
18
+ (e) => {
19
+ if (e.type == "command" && e.isTrusted) {
20
+ e.stopImmediatePropagation();
21
+ e.preventDefault();
22
+ }
23
+ },
24
+ true
25
+ );
26
+ function enumerate(obj, key, enumerable = true) {
27
+ Object.defineProperty(obj, key, {
28
+ ...Object.getOwnPropertyDescriptor(obj, key),
29
+ enumerable
30
+ });
31
+ }
32
+ function getRootNode(node) {
33
+ if (node && typeof node.getRootNode === "function") {
34
+ return node.getRootNode();
35
+ }
36
+ if (node && node.parentNode) return getRootNode(node.parentNode);
37
+ return node;
38
+ }
39
+ const commandEventSourceElements = /* @__PURE__ */ new WeakMap();
40
+ const commandEventActions = /* @__PURE__ */ new WeakMap();
41
+ class CommandEvent extends Event {
42
+ constructor(type, invokeEventInit = {}) {
43
+ super(type, invokeEventInit);
44
+ const { source, command } = invokeEventInit;
45
+ if (source != null && !(source instanceof Element)) {
46
+ throw new TypeError(`source must be an element`);
47
+ }
48
+ commandEventSourceElements.set(this, source || null);
49
+ commandEventActions.set(
50
+ this,
51
+ command !== void 0 ? String(command) : ""
52
+ );
53
+ }
54
+ get [Symbol.toStringTag]() {
55
+ return "CommandEvent";
56
+ }
57
+ get source() {
58
+ if (!commandEventSourceElements.has(this)) {
59
+ throw new TypeError("illegal invocation");
60
+ }
61
+ const source = commandEventSourceElements.get(this);
62
+ if (!(source instanceof Element)) return null;
63
+ const invokerRoot = getRootNode(source);
64
+ if (invokerRoot !== getRootNode(this.target || document)) {
65
+ return invokerRoot.host;
66
+ }
67
+ return source;
68
+ }
69
+ get command() {
70
+ if (!commandEventActions.has(this)) {
71
+ throw new TypeError("illegal invocation");
72
+ }
73
+ return commandEventActions.get(this);
74
+ }
75
+ }
76
+ enumerate(CommandEvent.prototype, "source");
77
+ enumerate(CommandEvent.prototype, "command");
78
+ const invokerAssociatedElements = /* @__PURE__ */ new WeakMap();
79
+ function applyInvokerMixin(ElementClass) {
80
+ Object.defineProperties(ElementClass.prototype, {
81
+ commandForElement: {
82
+ enumerable: true,
83
+ configurable: true,
84
+ set(targetElement) {
85
+ if (targetElement === null) {
86
+ this.removeAttribute("commandfor");
87
+ invokerAssociatedElements.delete(this);
88
+ } else if (!(targetElement instanceof Element)) {
89
+ throw new TypeError(`commandForElement must be an element or null`);
90
+ } else {
91
+ this.setAttribute("commandfor", "");
92
+ const targetRootNode = getRootNode(targetElement);
93
+ const thisRootNode = getRootNode(this);
94
+ if (thisRootNode === targetRootNode || targetRootNode === this.ownerDocument) {
95
+ invokerAssociatedElements.set(this, targetElement);
96
+ } else {
97
+ invokerAssociatedElements.delete(this);
98
+ }
99
+ }
100
+ },
101
+ get() {
102
+ if (this.localName !== "button") {
103
+ return null;
104
+ }
105
+ if (this.disabled) {
106
+ return null;
107
+ }
108
+ if (this.form && this.getAttribute("type") !== "button") {
109
+ console.warn(
110
+ "Element with `commandFor` is a form participant. It should explicitly set `type=button` in order for `commandFor` to work"
111
+ );
112
+ return null;
113
+ }
114
+ const targetElement = invokerAssociatedElements.get(this);
115
+ if (targetElement) {
116
+ if (targetElement.isConnected) {
117
+ return targetElement;
118
+ } else {
119
+ invokerAssociatedElements.delete(this);
120
+ return null;
121
+ }
122
+ }
123
+ const root = getRootNode(this);
124
+ const idref = this.getAttribute("commandfor");
125
+ if ((root instanceof Document || root instanceof ShadowRoot) && idref) {
126
+ return root.getElementById(idref) || null;
127
+ }
128
+ return null;
129
+ }
130
+ },
131
+ command: {
132
+ enumerable: true,
133
+ configurable: true,
134
+ get() {
135
+ const value = this.getAttribute("command") || "";
136
+ if (value.startsWith("--")) return value;
137
+ const valueLower = value.toLowerCase();
138
+ switch (valueLower) {
139
+ case "show-modal":
140
+ case "request-close":
141
+ case "close":
142
+ case "toggle-popover":
143
+ case "hide-popover":
144
+ case "show-popover":
145
+ return valueLower;
146
+ }
147
+ return "";
148
+ },
149
+ set(value) {
150
+ this.setAttribute("command", value);
151
+ }
152
+ }
153
+ });
154
+ }
155
+ const onHandlers = /* @__PURE__ */ new WeakMap();
156
+ Object.defineProperties(HTMLElement.prototype, {
157
+ oncommand: {
158
+ enumerable: true,
159
+ configurable: true,
160
+ get() {
161
+ oncommandObserver.takeRecords();
162
+ return onHandlers.get(this) || null;
163
+ },
164
+ set(handler) {
165
+ const existing = onHandlers.get(this) || null;
166
+ if (existing) {
167
+ this.removeEventListener("command", existing);
168
+ }
169
+ onHandlers.set(
170
+ this,
171
+ typeof handler === "object" || typeof handler === "function" ? handler : null
172
+ );
173
+ if (typeof handler == "function") {
174
+ this.addEventListener("command", handler);
175
+ }
176
+ }
177
+ }
178
+ });
179
+ function applyOnCommandHandler(els) {
180
+ for (const el of els) {
181
+ el.oncommand = new Function("event", el.getAttribute("oncommand"));
182
+ }
183
+ }
184
+ const oncommandObserver = new MutationObserver((records) => {
185
+ for (const record of records) {
186
+ const { target } = record;
187
+ if (record.type === "childList") {
188
+ applyOnCommandHandler(target.querySelectorAll("[oncommand]"));
189
+ } else {
190
+ applyOnCommandHandler([target]);
191
+ }
192
+ }
193
+ });
194
+ oncommandObserver.observe(document, {
195
+ subtree: true,
196
+ childList: true,
197
+ attributeFilter: ["oncommand"]
198
+ });
199
+ applyOnCommandHandler(document.querySelectorAll("[oncommand]"));
200
+ const processedEvents = /* @__PURE__ */ new WeakSet();
201
+ function handleInvokerActivation(event) {
202
+ if (processedEvents.has(event)) return;
203
+ processedEvents.add(event);
204
+ if (event.defaultPrevented) return;
205
+ if (event.type !== "click") return;
206
+ const source = event.composedPath().find((el) => el.matches?.("button[commandfor], button[command]"));
207
+ if (!source) return;
208
+ if (source.form && source.getAttribute("type") !== "button") {
209
+ event.preventDefault();
210
+ throw new Error(
211
+ "Element with `commandFor` is a form participant. It should explicitly set `type=button` in order for `commandFor` to work. In order for it to act as a Submit button, it must not have command or commandfor attributes"
212
+ );
213
+ }
214
+ if (source.hasAttribute("command") !== source.hasAttribute("commandfor")) {
215
+ const attr2 = source.hasAttribute("command") ? "command" : "commandfor";
216
+ const missing = source.hasAttribute("command") ? "commandfor" : "command";
217
+ throw new Error(
218
+ `Element with ${attr2} attribute must also have a ${missing} attribute to function.`
219
+ );
220
+ }
221
+ if (source.command !== "show-popover" && source.command !== "hide-popover" && source.command !== "toggle-popover" && source.command !== "show-modal" && source.command !== "request-close" && source.command !== "close" && !source.command.startsWith("--")) {
222
+ console.warn(
223
+ `"${source.command}" is not a valid command value. Custom commands must begin with --`
224
+ );
225
+ return;
226
+ }
227
+ const invokee = source.commandForElement;
228
+ if (!invokee) return;
229
+ const invokeEvent = new CommandEvent("command", {
230
+ command: source.command,
231
+ source,
232
+ cancelable: true
233
+ });
234
+ invokee.dispatchEvent(invokeEvent);
235
+ if (invokeEvent.defaultPrevented) return;
236
+ const command = invokeEvent.command.toLowerCase();
237
+ if (invokee.popover) {
238
+ const canShow = !invokee.matches(":popover-open");
239
+ const shouldShow = canShow && (command === "toggle-popover" || command === "show-popover");
240
+ const shouldHide = !canShow && command === "hide-popover";
241
+ if (shouldShow) {
242
+ invokee.showPopover({ source });
243
+ } else if (shouldHide) {
244
+ invokee.hidePopover();
245
+ }
246
+ } else if (invokee.localName === "dialog") {
247
+ const canShow = !invokee.hasAttribute("open");
248
+ if (canShow && command == "show-modal") {
249
+ invokee.showModal();
250
+ } else if (!canShow && command == "close") {
251
+ invokee.close(source.value ? source.value : void 0);
252
+ } else if (!canShow && command == "request-close") {
253
+ if (!HTMLDialogElement.prototype.requestClose) {
254
+ HTMLDialogElement.prototype.requestClose = function() {
255
+ const cancelEvent = new Event("cancel", { cancelable: true });
256
+ this.dispatchEvent(cancelEvent);
257
+ if (!cancelEvent.defaultPrevented) {
258
+ this.close();
259
+ }
260
+ };
261
+ }
262
+ invokee.requestClose(source.value ? source.value : void 0);
263
+ }
264
+ }
265
+ }
266
+ function setupInvokeListeners(target) {
267
+ target.addEventListener("click", handleInvokerActivation, true);
268
+ }
269
+ function observeShadowRoots(ElementClass, callback) {
270
+ const attachShadow = ElementClass.prototype.attachShadow;
271
+ ElementClass.prototype.attachShadow = function(init) {
272
+ const shadow = attachShadow.call(this, init);
273
+ callback(shadow);
274
+ return shadow;
275
+ };
276
+ const attachInternals = ElementClass.prototype.attachInternals;
277
+ ElementClass.prototype.attachInternals = function() {
278
+ const internals = attachInternals.call(this);
279
+ if (internals.shadowRoot) callback(internals.shadowRoot);
280
+ return internals;
281
+ };
282
+ }
283
+ applyInvokerMixin(HTMLButtonElement);
284
+ observeShadowRoots(HTMLElement, (shadow) => {
285
+ setupInvokeListeners(shadow);
286
+ oncommandObserver.observe(shadow, { attributeFilter: ["oncommand"] });
287
+ applyOnCommandHandler(shadow.querySelectorAll("[oncommand]"));
288
+ });
289
+ setupInvokeListeners(document);
290
+ Object.assign(globalThis, { CommandEvent });
291
+ }
3
292
 
4
293
  // src/utils/utils.ts
5
294
  var QUICK_EVENT = { passive: true, capture: true };
@@ -15,22 +304,21 @@ function debounce(callback, delay) {
15
304
  timer = setTimeout(() => callback.apply(this, args), delay);
16
305
  };
17
306
  }
18
- var warn = (message, ...args) => typeof window === "undefined" || window.dsWarnings === false || console.warn(`Designsystemet: ${message}`, ...args);
307
+ var warn = (message, ...args) => !isBrowser() || window.dsWarnings === false || console.log(`\x1B[1mDesignsystemet:\x1B[m ${message}`, ...args);
19
308
  var attr = (el, name, value) => {
20
309
  if (value === void 0) return el.getAttribute(name) ?? null;
21
310
  if (value === null) el.removeAttribute(name);
22
311
  else if (el.getAttribute(name) !== value) el.setAttribute(name, value);
23
312
  return null;
24
313
  };
25
- var STRIP_SURROUNDING_QUOTES = /^["']|["']$/g;
314
+ var getCSSProp = (el, prop) => getComputedStyle(el).getPropertyValue(prop).trim();
315
+ var STRIP_QUOTES = /^["']|["']$/g;
26
316
  var attrOrCSS = (el, name) => {
27
317
  let value = attr(el, name);
28
- if (!value) {
29
- const prop = getComputedStyle(el).getPropertyValue(`--_ds-${name}`);
30
- value = prop.replace(STRIP_SURROUNDING_QUOTES, "").trim() || null;
31
- }
318
+ if (!value)
319
+ value = getCSSProp(el, `--_ds-${name}`).replace(STRIP_QUOTES, "").trim();
32
320
  if (!value) warn(`Missing ${name} on:`, el);
33
- return value;
321
+ return value || null;
34
322
  };
35
323
  var on = (el, ...rest) => {
36
324
  const [types, ...options] = rest;
@@ -47,31 +335,16 @@ var onHotReload = (key, setup) => {
47
335
  window._dsHotReloadCleanup?.get(key)?.map((cleanup) => cleanup());
48
336
  window._dsHotReloadCleanup?.set(key, setup());
49
337
  };
50
- var SKIP_MUTATIONS = false;
51
338
  var onMutation = (el, callback, options) => {
52
- let queue = 0;
53
- const onFrame = () => {
54
- if (!el.isConnected) return cleanup();
55
- callback(observer);
56
- observer.takeRecords();
57
- queue = 0;
58
- };
59
- const cleanup = () => observer?.disconnect?.();
60
- const observer = new MutationObserver(() => {
61
- if (!SKIP_MUTATIONS && !queue) queue = requestAnimationFrame(onFrame);
339
+ const cleanup = () => observer.disconnect();
340
+ const observer = new MutationObserver((records) => {
341
+ if (!isBrowser() || !el.isConnected) return cleanup();
342
+ callback(el, records);
62
343
  });
344
+ callback(el);
63
345
  observer.observe(el, options);
64
- requestAnimationFrame(onFrame);
65
346
  return cleanup;
66
347
  };
67
- var setTextWithoutMutation = (el, text) => {
68
- SKIP_MUTATIONS = true;
69
- el.textContent = text;
70
- requestAnimationFrame(enableMutations);
71
- };
72
- var enableMutations = () => {
73
- SKIP_MUTATIONS = false;
74
- };
75
348
  var tag = (tagName, attrs) => {
76
349
  const el = document.createElement(tagName);
77
350
  if (attrs) for (const [key, val] of Object.entries(attrs)) attr(el, key, val);
@@ -81,9 +354,10 @@ var customElements = {
81
354
  define: (name, instance) => !isBrowser() || window.customElements.get(name) || window.customElements.define(name, instance)
82
355
  };
83
356
  var id = 0;
84
- var hash = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;
85
357
  function useId(el) {
86
- if (el && !el.id) el.id = `${hash}${++id}`;
358
+ if (!isBrowser()) return `:ds:${++id}`;
359
+ if (!window.dsUseId) window.dsUseId = 0;
360
+ if (el && !el.id) el.id = `:ds:${++window.dsUseId}`;
87
361
  return el?.id || "";
88
362
  }
89
363
  var LIVE_EL;
@@ -91,8 +365,7 @@ var LIVE_FIX = 0;
91
365
  var LIVE_CLEAR = 0;
92
366
  var announce = (text) => {
93
367
  clearTimeout(LIVE_CLEAR);
94
- if (LIVE_EL)
95
- setTextWithoutMutation(LIVE_EL, `${text}${LIVE_FIX++ % 2 ? "\xA0" : ""}`);
368
+ if (LIVE_EL) LIVE_EL.textContent = `${text}${LIVE_FIX++ % 2 ? "\xA0" : ""}`;
96
369
  if (text) LIVE_CLEAR = setTimeout(announce, 2e3, "");
97
370
  };
98
371
  var announceMount = () => {
@@ -120,8 +393,7 @@ var SELECTOR_CLICKDELEGATEFOR = `[${ATTR_CLICKDELEGATEFOR}]`;
120
393
  var SELECTOR_SKIP = 'a,button,label,input,select,textarea,details,dialog,[role="button"],[popover],[contenteditable]';
121
394
  var handleClickDelegateFor = (event) => {
122
395
  const isNewTab = event.button === 1 || event.metaKey || event.ctrlKey;
123
- const isUserLeftOrMiddleClick = event.isTrusted && event.button < 2;
124
- const delegateTarget = isUserLeftOrMiddleClick && getDelegateTarget(event);
396
+ const delegateTarget = event.button < 2 && getDelegateTarget(event);
125
397
  if (!delegateTarget || delegateTarget.contains(event.target)) return;
126
398
  if (isNewTab && delegateTarget instanceof HTMLAnchorElement)
127
399
  return window.open(delegateTarget.href, void 0, delegateTarget.rel);
@@ -169,9 +441,11 @@ var handleClosedbyAny = ({
169
441
  if (isClose) requestAnimationFrame(() => el.open && el.close());
170
442
  }
171
443
  };
444
+ var BUTTONS = isBrowser() ? document.getElementsByTagName("button") : [];
172
445
  var handleAriaAttributes = () => {
173
- for (const btn of document.querySelectorAll('button[command="show-modal"]'))
174
- attr(btn, "aria-haspopup", "dialog");
446
+ for (const btn of BUTTONS)
447
+ if (btn.getAttribute("command")?.endsWith("-modal"))
448
+ btn.setAttribute("aria-haspopup", "dialog");
175
449
  };
176
450
  var handleCommand = ({ command, target }) => command === "--show-non-modal" && target instanceof HTMLDialogElement && target.show();
177
451
  onHotReload("dialog", () => [
@@ -185,6 +459,22 @@ onHotReload("dialog", () => [
185
459
  })
186
460
  ]);
187
461
 
462
+ // src/fieldset/fieldset.ts
463
+ var FIELDSETS = isBrowser() ? document.getElementsByTagName("fieldset") : [];
464
+ var handleFieldsetMutations = () => {
465
+ for (const el of FIELDSETS) {
466
+ if (el.hasAttribute("aria-labelledby")) continue;
467
+ const labelledby = `${useId(el.querySelector("legend"))} ${useId(el.querySelector(':scope > :is([data-field="description"],legend + p)'))}`;
468
+ attr(el, "aria-labelledby", labelledby.trim() || null);
469
+ }
470
+ };
471
+ onHotReload("fieldset", () => [
472
+ onMutation(document, handleFieldsetMutations, {
473
+ childList: true,
474
+ subtree: true
475
+ })
476
+ ]);
477
+
188
478
  // src/popover/popover.ts
189
479
  import {
190
480
  autoUpdate,
@@ -200,7 +490,8 @@ var ATTR_AUTO = "data-autoplacement";
200
490
  var POPOVERS = /* @__PURE__ */ new Map();
201
491
  function handleToggle(event) {
202
492
  let { newState, oldState, target: el, source = event.detail } = event;
203
- const float = el instanceof HTMLElement && getCSSProp(el, "--_ds-floating");
493
+ const isPopover = el instanceof HTMLElement && attr(el, "popover") !== null;
494
+ const float = isPopover && getCSSProp(el, "--_ds-floating");
204
495
  if (!float) return;
205
496
  if (newState === "closed") return POPOVERS.get(el)?.();
206
497
  if (!source) {
@@ -260,7 +551,6 @@ onHotReload("popover", () => [
260
551
  on(document, "toggle ds-toggle-source", handleToggle, QUICK_EVENT)
261
552
  // Use capture since the toggle event does not bubble
262
553
  ]);
263
- var getCSSProp = (el, prop) => getComputedStyle(el).getPropertyValue(prop).trim();
264
554
  var arrowPseudo = () => ({
265
555
  name: "arrowPseudo",
266
556
  fn(data) {
@@ -279,8 +569,10 @@ var arrowPseudo = () => ({
279
569
  var isReadOnly = (el) => (el instanceof HTMLSelectElement || el instanceof HTMLInputElement) && (el.hasAttribute("readonly") || attr(el, "aria-readonly") === "true");
280
570
  var handleKeyDown = (e) => {
281
571
  if (e.key !== "Tab" && isReadOnly(e.target)) {
282
- e.preventDefault();
283
- if (e.key?.startsWith("Arrow") && attr(e.target, "type") === "radio") {
572
+ const isArrow = e.key?.startsWith("Arrow");
573
+ const isModifier = e.altKey || e.ctrlKey || e.metaKey;
574
+ if (isArrow || !isModifier) e.preventDefault();
575
+ if (isArrow && attr(e.target, "type") === "radio") {
284
576
  const all = document.querySelectorAll(`input[name="${e.target.name}"]`);
285
577
  const move = e.key?.match(/Arrow(Right|Down)/) ? 1 : -1;
286
578
  const next = all.length + [...all].indexOf(e.target) + move;
@@ -308,21 +600,26 @@ onHotReload("readonly", () => [
308
600
  ]);
309
601
 
310
602
  // src/toggle-group/toggle-group.ts
603
+ var ARIA_LABELLEDBY = "aria-labelledby";
604
+ var ARIA_LABEL = "aria-label";
311
605
  var ATTR_TOGGLEGROUP = "data-toggle-group";
312
606
  var SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;
313
- var handleAriaAttributes2 = debounce(() => {
607
+ var handleAriaAttributes2 = () => {
314
608
  for (const group of document.querySelectorAll(SELECTOR_TOGGLEGROUP))
315
609
  attr(group, "aria-label", attrOrCSS(group, ATTR_TOGGLEGROUP));
316
- }, 0);
610
+ };
317
611
  var handleKeydown = (event) => {
318
- const group = event.target instanceof HTMLInputElement && event.target.closest(SELECTOR_TOGGLEGROUP);
612
+ const { key, target: el } = event;
613
+ const group = el instanceof HTMLInputElement && el.closest(SELECTOR_TOGGLEGROUP);
319
614
  if (!group) return;
320
- if (event.key === "Enter") event.target.click();
321
- if (event.key?.startsWith("Arrow")) {
615
+ if (!attr(group, ARIA_LABEL) && !attr(group, ARIA_LABELLEDBY))
616
+ attr(group, ARIA_LABEL, attrOrCSS(group, ATTR_TOGGLEGROUP));
617
+ if (key === "Enter") el.click();
618
+ if (key?.startsWith("Arrow")) {
322
619
  event.preventDefault?.();
323
620
  const inputs = [...group.getElementsByTagName("input")];
324
- const index = inputs.indexOf(event.target);
325
- const move = event.key.match(/Arrow(Right|Down)/) ? 1 : -1;
621
+ const index = inputs.indexOf(el);
622
+ const move = key.match(/Arrow(Right|Down)/) ? 1 : -1;
326
623
  let nextIndex = index;
327
624
  for (let i = 0; i < inputs.length; i++) {
328
625
  nextIndex = (inputs.length + nextIndex + move) % inputs.length;
@@ -346,11 +643,13 @@ onHotReload("toggle-group", () => [
346
643
  // src/tooltip/tooltip.ts
347
644
  var TIP;
348
645
  var SOURCE;
646
+ var IS_HOVERING = false;
349
647
  var HOVER_TIMER = 0;
350
648
  var SKIP_TIMER = 0;
649
+ var IS_IOS = isBrowser() && /iPad|iPhone|iPod/.test(navigator.userAgent);
351
650
  var ATTR_TOOLTIP = "data-tooltip";
352
651
  var ATTR_COLOR = "data-color";
353
- var ARIA_LABEL = "aria-label";
652
+ var ARIA_LABEL2 = "aria-label";
354
653
  var ARIA_DESC = "aria-description";
355
654
  var SELECTOR_COLOR = `[${ATTR_COLOR}]`;
356
655
  var SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;
@@ -362,32 +661,36 @@ var DELAY_SKIP = 300;
362
661
  var setTooltipElement = (el) => {
363
662
  if (el && !(el instanceof HTMLElement))
364
663
  warn("setTooltipElement expects an HTMLElement, got: ", el);
664
+ clearTimeout(SKIP_TIMER);
665
+ clearTimeout(HOVER_TIMER);
666
+ SOURCE = void 0;
667
+ IS_HOVERING = false;
365
668
  TIP = el || void 0;
366
669
  };
367
- var handleAriaAttributes3 = debounce(() => {
670
+ var handleAriaAttributes3 = () => {
368
671
  for (const el of document.querySelectorAll(SELECTOR_TOOLTIP)) {
369
- const aria = el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC);
370
- const text = el.getAttribute(ATTR_TOOLTIP) || attrOrCSS(el, ATTR_TOOLTIP);
371
- if (aria !== text) {
672
+ const text = attrOrCSS(el, ATTR_TOOLTIP);
673
+ if (!text) return;
674
+ if (text !== (el.getAttribute(ARIA_LABEL2) || el.getAttribute(ARIA_DESC))) {
372
675
  const hasText = attr(el, "role") !== "img" && el.textContent?.trim();
373
676
  attr(el, ATTR_TOOLTIP, text);
374
- attr(el, ARIA_LABEL, hasText ? null : text);
677
+ attr(el, ARIA_LABEL2, hasText ? null : text);
375
678
  attr(el, ARIA_DESC, hasText ? text : null);
376
679
  if (!el.matches(SELECTOR_INTERACTIVE))
377
680
  warn('Missing tabindex="0" attribute on: ', el);
378
681
  }
379
- const isCurrent = el === SOURCE && TIP?.matches(":popover-open");
682
+ const isCurrent = el === SOURCE && TIP?.offsetHeight && TIP?.offsetWidth;
380
683
  const isChanged = isCurrent && text && TIP?.textContent !== text;
381
684
  if (isCurrent && isChanged) {
382
- if (TIP) setTextWithoutMutation(TIP, text);
685
+ if (TIP) TIP.textContent = text;
383
686
  if (document.activeElement === el) announce(text);
384
687
  }
385
688
  }
386
- }, 0);
689
+ };
387
690
  var handleInterest = ({ type, target }) => {
388
691
  clearTimeout(HOVER_TIMER);
389
692
  if (target === TIP) return;
390
- if (type === "mouseover" && !SOURCE) {
693
+ if (type === "mouseover" && !IS_HOVERING && !IS_IOS) {
391
694
  HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target });
392
695
  return;
393
696
  }
@@ -403,18 +706,21 @@ var handleInterest = ({ type, target }) => {
403
706
  attr(TIP, "popover", "manual");
404
707
  attr(TIP, ATTR_SCHEME, scheme?.getAttribute(ATTR_SCHEME) || null);
405
708
  attr(TIP, ATTR_COLOR, isReset && color?.getAttribute(ATTR_COLOR) || null);
406
- setTextWithoutMutation(TIP, attr(source, ATTR_TOOLTIP));
709
+ TIP.textContent = attr(source, ATTR_TOOLTIP);
407
710
  TIP.showPopover();
408
711
  TIP.dispatchEvent(new CustomEvent("ds-toggle-source", { detail: source }));
712
+ IS_HOVERING = true;
409
713
  SOURCE = source;
410
714
  };
411
715
  var hideTooltip = () => TIP?.isConnected && TIP.popover && TIP.hidePopover();
412
716
  var handleClose = (event) => {
413
717
  if (event?.type === "keydown")
414
718
  return event?.key === "Escape" && hideTooltip();
415
- if (!event) SOURCE = void 0;
416
- else if (event.target === TIP && event.newState === "closed")
719
+ if (!event) IS_HOVERING = false;
720
+ else if (event.target === TIP && event.newState === "closed") {
721
+ SOURCE = void 0;
417
722
  SKIP_TIMER = setTimeout(handleClose, DELAY_SKIP);
723
+ }
418
724
  };
419
725
  onHotReload("tooltip", () => [
420
726
  on(document, "blur focus mouseover", handleInterest, QUICK_EVENT),
@@ -436,30 +742,30 @@ var DSBreadcrumbsElement = class extends DSElement {
436
742
  _items;
437
743
  // Using underscore instead of private fields for backwards compatibility
438
744
  _label = null;
439
- _render;
440
745
  _unresize;
441
746
  _unmutate;
442
747
  static get observedAttributes() {
443
748
  return [ATTR_LABEL];
444
749
  }
445
750
  connectedCallback() {
751
+ const resize = debounce(() => render(this), 100);
446
752
  this._label = attrOrCSS(this, ATTR_LABEL);
447
753
  this._items = this.getElementsByTagName("a");
448
- this._render = debounce(() => render(this), 100);
449
- this._unresize = on(window, "resize", this._render);
450
- this._unmutate = onMutation(this, this._render, {
754
+ this._unresize = on(window, "resize", resize);
755
+ this._unmutate = onMutation(this, render, {
451
756
  childList: true,
452
757
  subtree: true
453
758
  });
454
759
  }
455
760
  attributeChangedCallback(_name, _prev, next) {
456
- if (next) this._label = next;
457
- this._render?.();
761
+ if (!this._unmutate || !next) return;
762
+ this._label = next;
763
+ render(this);
458
764
  }
459
765
  disconnectedCallback() {
460
766
  this._unresize?.();
461
767
  this._unmutate?.();
462
- this._unresize = this._unmutate = this._render = this._items = void 0;
768
+ this._unresize = this._unmutate = this._items = void 0;
463
769
  }
464
770
  };
465
771
  var render = (self) => {
@@ -475,104 +781,118 @@ customElements.define("ds-breadcrumbs", DSBreadcrumbsElement);
475
781
 
476
782
  // src/error-summary/error-summary.ts
477
783
  var DSErrorSummaryElement = class extends DSElement {
784
+ _unmutate;
785
+ // Using underscore instead of private fields for backwards compatibility
478
786
  connectedCallback() {
479
787
  on(this, "animationend", this, QUICK_EVENT);
480
- requestAnimationFrame(() => this.handleEvent({ target: this }));
481
- }
482
- handleEvent({ target }) {
483
- if (target !== this) return;
484
- const heading = this.querySelector("h2,h3,h4,h5,h6");
485
- if (heading) attr(this, "aria-labelledby", useId(heading));
486
788
  attr(this, "tabindex", "-1");
789
+ this._unmutate = onMutation(this, render2, {
790
+ childList: true,
791
+ subtree: true
792
+ });
487
793
  this.focus();
488
794
  }
795
+ handleEvent({ target }) {
796
+ if (target === this) this.focus();
797
+ }
489
798
  disconnectedCallback() {
490
799
  off(this, "animationend", this, QUICK_EVENT);
800
+ this._unmutate?.();
801
+ this._unmutate = void 0;
491
802
  }
492
803
  };
804
+ var render2 = (self) => {
805
+ const heading = self.querySelector("h2,h3,h4,h5,h6");
806
+ if (heading) attr(self, "aria-labelledby", useId(heading));
807
+ };
493
808
  customElements.define("ds-error-summary", DSErrorSummaryElement);
494
809
 
495
810
  // src/field/field.ts
496
- var INDETERMINATE = "data-indeterminate";
497
- var FIELDS = /* @__PURE__ */ new Set();
498
- var COUNTS = /* @__PURE__ */ new WeakMap();
499
- var FIELDSETS = isBrowser() ? document.getElementsByTagName("fieldset") : [];
500
- var HAS_FIELD_SIZING = isBrowser() && CSS.supports("field-sizing", "content");
811
+ var ATTR_DESCRIBEDBY = "aria-describedby";
812
+ var ATTR_INDETERMINATE = "data-indeterminate";
501
813
  var COUNTER_DEBOUNCE = isWindows() ? 800 : 200;
502
- var HAS_VALIDATION = /* @__PURE__ */ new WeakSet();
503
- var handleMutations = debounce(() => {
504
- for (const el of FIELDSETS) {
505
- const labelledby = `${useId(el.querySelector("legend"))} ${useId(el.querySelector(':scope > :is([data-field="description"],legend + p)'))}`;
506
- attr(el, "aria-labelledby", labelledby.trim() || null);
814
+ var COUNTS = /* @__PURE__ */ new WeakMap();
815
+ var FIELDS = /* @__PURE__ */ new Map();
816
+ var VALIDATIONS = /* @__PURE__ */ new WeakMap();
817
+ var WARNING_MULTIPLE_INPUTS = `Fields should only have one input element. Use <fieldset> to group multiple fields:`;
818
+ var handleFieldMutations = (_doc, records = []) => {
819
+ for (const { target } of records) {
820
+ const isFieldset = target instanceof HTMLFieldSetElement;
821
+ for (const [field] of FIELDS)
822
+ if (isFieldset ? target.contains(field) : field.contains(target))
823
+ handleFieldMutation(field);
507
824
  }
508
- for (const field of FIELDS) {
509
- const descs = [];
510
- const labels = [];
511
- let input;
512
- let counter;
513
- let hasValidation = false;
514
- let invalid = false;
515
- for (const el of field.getElementsByTagName("*")) {
516
- if (el instanceof HTMLLabelElement) labels.push(el);
517
- if (el.hidden) continue;
518
- if (isInputLike(el)) {
519
- if (input)
520
- warn(
521
- `Fields should only have one input element. Use <fieldset> to group multiple fields:`,
522
- field
523
- );
524
- else input = el;
525
- } else {
526
- const type = el.getAttribute("data-field");
527
- if (type === "counter") counter = el;
528
- if (type === "validation") {
529
- descs.unshift(el);
530
- hasValidation = true;
531
- invalid = invalid || isInvalid(el);
532
- } else if (type) descs.push(el);
533
- }
534
- }
535
- if (!input) warn(`Field is missing input element:`, field);
536
- else {
537
- if (counter) COUNTS.set(input, counter);
538
- for (const label of labels) attr(label, "for", useId(input));
539
- const isBoolish = input.type === "radio" || input.type === "checkbox";
540
- const fieldsetValidation = field.closest("fieldset")?.querySelector(':scope > [data-field="validation"]');
541
- if (fieldsetValidation && !fieldsetValidation?.hidden) {
825
+ };
826
+ var handleFieldMutation = (field) => {
827
+ const labels = [];
828
+ const nextDescs = [];
829
+ const prevDescs = FIELDS.get(field) || [];
830
+ let input;
831
+ let counter;
832
+ let hasValidation = false;
833
+ let invalid = false;
834
+ for (const el of field.getElementsByTagName("*")) {
835
+ if (el instanceof HTMLLabelElement) labels.push(el);
836
+ if (el.hidden) continue;
837
+ if (isInputLike(el)) {
838
+ if (input) warn(WARNING_MULTIPLE_INPUTS, field);
839
+ else input = el;
840
+ } else {
841
+ const type = el.getAttribute("data-field");
842
+ if (type === "counter") counter = el;
843
+ if (type === "validation") {
844
+ nextDescs.unshift(useId(el));
542
845
  hasValidation = true;
543
- invalid = invalid || isInvalid(fieldsetValidation);
544
- descs.unshift(fieldsetValidation);
545
- }
546
- const indeterminate = attr(input, INDETERMINATE);
547
- if (indeterminate) input.indeterminate = indeterminate === "true";
548
- attr(field, "data-clickdelegatefor", isBoolish ? useId(input) : null);
549
- attr(input, "aria-describedby", descs.map(useId).join(" ") || null);
550
- if (hasValidation || HAS_VALIDATION.has(input)) {
551
- HAS_VALIDATION[hasValidation ? "add" : "delete"](input);
552
- attr(input, "aria-invalid", `${invalid}`);
553
- }
554
- updateField(input);
846
+ invalid = invalid || isInvalid(el);
847
+ } else if (type) nextDescs.push(useId(el));
555
848
  }
556
849
  }
557
- }, 0);
558
- var updateField = (e) => {
850
+ if (!input) return;
851
+ if (counter) COUNTS.set(input, counter);
852
+ for (const label of labels) attr(label, "for", useId(input));
853
+ const fieldsetValidation = field.closest("fieldset")?.querySelector(':scope > [data-field="validation"]');
854
+ if (fieldsetValidation && !fieldsetValidation?.hidden) {
855
+ hasValidation = true;
856
+ invalid = invalid || isInvalid(fieldsetValidation);
857
+ nextDescs.unshift(useId(fieldsetValidation));
858
+ }
859
+ const indeterminate = attr(input, ATTR_INDETERMINATE);
860
+ if (indeterminate) input.indeterminate = indeterminate === "true";
861
+ const isBoolish = input.type === "radio" || input.type === "checkbox";
862
+ if (isBoolish) attr(field, "data-clickdelegatefor", useId(input));
863
+ const describedby = attr(input, ATTR_DESCRIBEDBY)?.trim().split(/\s+/);
864
+ const keep = describedby?.filter((id2) => !prevDescs.includes(id2)) || [];
865
+ attr(input, ATTR_DESCRIBEDBY, [...nextDescs, ...keep].join(" ") || null);
866
+ FIELDS.set(field, nextDescs);
867
+ const hadValidation = VALIDATIONS.has(input);
868
+ if (hasValidation && !hadValidation) {
869
+ VALIDATIONS.set(input, attr(input, "aria-invalid"));
870
+ attr(input, "aria-invalid", "true");
871
+ } else if (!hasValidation && hadValidation) {
872
+ attr(input, "aria-invalid", VALIDATIONS.get(input));
873
+ VALIDATIONS.delete(input);
874
+ }
875
+ handleFieldInput(input);
876
+ };
877
+ var TEXTS = {
878
+ over: "%d tegn for mye",
879
+ under: "%d tegn igjen"
880
+ };
881
+ var handleFieldInput = (e) => {
559
882
  const input = e.target || e;
560
883
  const counter = COUNTS.get(input);
561
884
  if (counter?.isConnected) {
562
885
  const limit = Number(attr(counter, "data-limit")) || 0;
563
886
  const count = limit - input.value.length;
564
887
  const state = count < 0 ? "over" : "under";
565
- const label = attrOrCSS(counter, `data-${state}`)?.replace(
566
- "%d",
567
- `${Math.abs(count)}`
568
- );
888
+ const label = (attrOrCSS(counter, `data-${state}`) || TEXTS[state])?.replace("%d", `${Math.abs(count)}`);
569
889
  attr(counter, "data-label", label);
570
890
  attr(counter, "data-state", state);
571
891
  attr(counter, "data-color", count < 0 ? "danger" : null);
572
892
  if (e.type === "input" && label)
573
893
  debouncedCounterLiveRegion(input, label);
574
894
  }
575
- if (!HAS_FIELD_SIZING && input instanceof HTMLTextAreaElement) {
895
+ if (input instanceof HTMLTextAreaElement) {
576
896
  input.style.setProperty("--_ds-field-sizing", "auto");
577
897
  input.style.setProperty("--_ds-field-sizing", `${input.scrollHeight}px`);
578
898
  }
@@ -586,8 +906,8 @@ var isInputLike = (el) => el instanceof HTMLElement && "validity" in el && // Ad
586
906
  el.type !== "hidden";
587
907
  var DSFieldElement = class extends DSElement {
588
908
  connectedCallback() {
589
- FIELDS.add(this);
590
- handleMutations();
909
+ FIELDS.set(this, []);
910
+ handleFieldMutation(this);
591
911
  }
592
912
  disconnectedCallback() {
593
913
  FIELDS.delete(this);
@@ -595,14 +915,18 @@ var DSFieldElement = class extends DSElement {
595
915
  };
596
916
  customElements.define("ds-field", DSFieldElement);
597
917
  onHotReload("field", () => [
598
- on(document, "input", updateField, QUICK_EVENT),
599
- onMutation(document, handleMutations, {
918
+ on(document, "input", handleFieldInput, QUICK_EVENT),
919
+ onMutation(document, handleFieldMutations, {
600
920
  attributeFilter: [
601
921
  "data-field",
602
922
  "data-limit",
603
923
  "hidden",
924
+ // Needed to check validation visibility
925
+ "id",
926
+ // Needed to sync label "for" when ID of input/selec/textarea changes
604
927
  "value",
605
- INDETERMINATE
928
+ // Needed to detect changes in controlled React inputs as they do not trigger input events
929
+ ATTR_INDETERMINATE
606
930
  ],
607
931
  attributes: true,
608
932
  childList: true,
@@ -638,21 +962,20 @@ var DSPaginationElement = class extends DSElement {
638
962
  if (total && !current) warn(`Missing ${ATTR_CURRENT} attribute on:`, this);
639
963
  attr(this, ATTR_LABEL2, attrOrCSS(this, ATTR_LABEL2));
640
964
  attr(this, "role", "navigation");
641
- this._render = debounce(() => render2(this), 0);
642
- this._unmutate = onMutation(this, this._render, {
965
+ this._unmutate = onMutation(this, render3, {
643
966
  childList: true,
644
967
  subtree: true
645
968
  });
646
969
  }
647
970
  attributeChangedCallback() {
648
- this._render?.();
971
+ if (this._unmutate) render3(this);
649
972
  }
650
973
  disconnectedCallback() {
651
974
  this._unmutate?.();
652
975
  this._unmutate = this._render = void 0;
653
976
  }
654
977
  };
655
- var render2 = (self) => {
978
+ var render3 = (self) => {
656
979
  const current = Number(attr(self, ATTR_CURRENT));
657
980
  const total = Number(attr(self, ATTR_TOTAL));
658
981
  if (current && total) {
@@ -686,13 +1009,12 @@ customElements.define("ds-pagination", DSPaginationElement);
686
1009
  // src/suggestion/suggestion.ts
687
1010
  import { UHTMLComboboxElement } from "@u-elements/u-combobox";
688
1011
  var DSSuggestionElement = class extends UHTMLComboboxElement {
1012
+ _render;
689
1013
  _unmutate;
690
1014
  // Using underscore instead of private fields for backwards compatibility
691
- _render;
692
1015
  connectedCallback() {
693
1016
  super.connectedCallback();
694
- this._render = () => render3(this);
695
- this._unmutate = onMutation(this, this._render, { childList: true });
1017
+ this._unmutate = onMutation(this, render4, { childList: true });
696
1018
  on(this, "toggle", polyfillToggleSource, QUICK_EVENT);
697
1019
  }
698
1020
  disconnectedCallback() {
@@ -702,7 +1024,7 @@ var DSSuggestionElement = class extends UHTMLComboboxElement {
702
1024
  off(this, "toggle", polyfillToggleSource, QUICK_EVENT);
703
1025
  }
704
1026
  };
705
- var render3 = ({ control, list }) => {
1027
+ var render4 = ({ control, list }) => {
706
1028
  if (control && !control.placeholder) attr(control, "placeholder", " ");
707
1029
  if (control) attr(control, "popovertarget", useId(list) || null);
708
1030
  if (list) attr(list, "popover", "manual");
@@ -731,7 +1053,7 @@ customElements.define("ds-tab", DSTabElement);
731
1053
  customElements.define("ds-tabpanel", DSTabPanelElement);
732
1054
 
733
1055
  // src/index.ts
734
- if (isBrowser() && !isSupported()) polyfillInvokers();
1056
+ if (isBrowser() && !isSupported()) apply();
735
1057
  export {
736
1058
  DSBreadcrumbsElement,
737
1059
  DSErrorSummaryElement,