@digdir/designsystemet-web 1.13.1 → 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 +9 -5
  64. package/dist/index.js +467 -148
  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 -15
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) => !isBrowser() || 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);
@@ -92,8 +365,7 @@ var LIVE_FIX = 0;
92
365
  var LIVE_CLEAR = 0;
93
366
  var announce = (text) => {
94
367
  clearTimeout(LIVE_CLEAR);
95
- if (LIVE_EL)
96
- setTextWithoutMutation(LIVE_EL, `${text}${LIVE_FIX++ % 2 ? "\xA0" : ""}`);
368
+ if (LIVE_EL) LIVE_EL.textContent = `${text}${LIVE_FIX++ % 2 ? "\xA0" : ""}`;
97
369
  if (text) LIVE_CLEAR = setTimeout(announce, 2e3, "");
98
370
  };
99
371
  var announceMount = () => {
@@ -121,8 +393,7 @@ var SELECTOR_CLICKDELEGATEFOR = `[${ATTR_CLICKDELEGATEFOR}]`;
121
393
  var SELECTOR_SKIP = 'a,button,label,input,select,textarea,details,dialog,[role="button"],[popover],[contenteditable]';
122
394
  var handleClickDelegateFor = (event) => {
123
395
  const isNewTab = event.button === 1 || event.metaKey || event.ctrlKey;
124
- const isUserLeftOrMiddleClick = event.isTrusted && event.button < 2;
125
- const delegateTarget = isUserLeftOrMiddleClick && getDelegateTarget(event);
396
+ const delegateTarget = event.button < 2 && getDelegateTarget(event);
126
397
  if (!delegateTarget || delegateTarget.contains(event.target)) return;
127
398
  if (isNewTab && delegateTarget instanceof HTMLAnchorElement)
128
399
  return window.open(delegateTarget.href, void 0, delegateTarget.rel);
@@ -170,9 +441,11 @@ var handleClosedbyAny = ({
170
441
  if (isClose) requestAnimationFrame(() => el.open && el.close());
171
442
  }
172
443
  };
444
+ var BUTTONS = isBrowser() ? document.getElementsByTagName("button") : [];
173
445
  var handleAriaAttributes = () => {
174
- for (const btn of document.querySelectorAll('button[command="show-modal"]'))
175
- attr(btn, "aria-haspopup", "dialog");
446
+ for (const btn of BUTTONS)
447
+ if (btn.getAttribute("command")?.endsWith("-modal"))
448
+ btn.setAttribute("aria-haspopup", "dialog");
176
449
  };
177
450
  var handleCommand = ({ command, target }) => command === "--show-non-modal" && target instanceof HTMLDialogElement && target.show();
178
451
  onHotReload("dialog", () => [
@@ -186,6 +459,22 @@ onHotReload("dialog", () => [
186
459
  })
187
460
  ]);
188
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
+
189
478
  // src/popover/popover.ts
190
479
  import {
191
480
  autoUpdate,
@@ -262,7 +551,6 @@ onHotReload("popover", () => [
262
551
  on(document, "toggle ds-toggle-source", handleToggle, QUICK_EVENT)
263
552
  // Use capture since the toggle event does not bubble
264
553
  ]);
265
- var getCSSProp = (el, prop) => getComputedStyle(el).getPropertyValue(prop).trim();
266
554
  var arrowPseudo = () => ({
267
555
  name: "arrowPseudo",
268
556
  fn(data) {
@@ -281,8 +569,10 @@ var arrowPseudo = () => ({
281
569
  var isReadOnly = (el) => (el instanceof HTMLSelectElement || el instanceof HTMLInputElement) && (el.hasAttribute("readonly") || attr(el, "aria-readonly") === "true");
282
570
  var handleKeyDown = (e) => {
283
571
  if (e.key !== "Tab" && isReadOnly(e.target)) {
284
- e.preventDefault();
285
- 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") {
286
576
  const all = document.querySelectorAll(`input[name="${e.target.name}"]`);
287
577
  const move = e.key?.match(/Arrow(Right|Down)/) ? 1 : -1;
288
578
  const next = all.length + [...all].indexOf(e.target) + move;
@@ -310,21 +600,26 @@ onHotReload("readonly", () => [
310
600
  ]);
311
601
 
312
602
  // src/toggle-group/toggle-group.ts
603
+ var ARIA_LABELLEDBY = "aria-labelledby";
604
+ var ARIA_LABEL = "aria-label";
313
605
  var ATTR_TOGGLEGROUP = "data-toggle-group";
314
606
  var SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;
315
- var handleAriaAttributes2 = debounce(() => {
607
+ var handleAriaAttributes2 = () => {
316
608
  for (const group of document.querySelectorAll(SELECTOR_TOGGLEGROUP))
317
609
  attr(group, "aria-label", attrOrCSS(group, ATTR_TOGGLEGROUP));
318
- }, 0);
610
+ };
319
611
  var handleKeydown = (event) => {
320
- 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);
321
614
  if (!group) return;
322
- if (event.key === "Enter") event.target.click();
323
- 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")) {
324
619
  event.preventDefault?.();
325
620
  const inputs = [...group.getElementsByTagName("input")];
326
- const index = inputs.indexOf(event.target);
327
- 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;
328
623
  let nextIndex = index;
329
624
  for (let i = 0; i < inputs.length; i++) {
330
625
  nextIndex = (inputs.length + nextIndex + move) % inputs.length;
@@ -348,12 +643,13 @@ onHotReload("toggle-group", () => [
348
643
  // src/tooltip/tooltip.ts
349
644
  var TIP;
350
645
  var SOURCE;
646
+ var IS_HOVERING = false;
351
647
  var HOVER_TIMER = 0;
352
648
  var SKIP_TIMER = 0;
353
649
  var IS_IOS = isBrowser() && /iPad|iPhone|iPod/.test(navigator.userAgent);
354
650
  var ATTR_TOOLTIP = "data-tooltip";
355
651
  var ATTR_COLOR = "data-color";
356
- var ARIA_LABEL = "aria-label";
652
+ var ARIA_LABEL2 = "aria-label";
357
653
  var ARIA_DESC = "aria-description";
358
654
  var SELECTOR_COLOR = `[${ATTR_COLOR}]`;
359
655
  var SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;
@@ -365,32 +661,36 @@ var DELAY_SKIP = 300;
365
661
  var setTooltipElement = (el) => {
366
662
  if (el && !(el instanceof HTMLElement))
367
663
  warn("setTooltipElement expects an HTMLElement, got: ", el);
664
+ clearTimeout(SKIP_TIMER);
665
+ clearTimeout(HOVER_TIMER);
666
+ SOURCE = void 0;
667
+ IS_HOVERING = false;
368
668
  TIP = el || void 0;
369
669
  };
370
- var handleAriaAttributes3 = debounce(() => {
670
+ var handleAriaAttributes3 = () => {
371
671
  for (const el of document.querySelectorAll(SELECTOR_TOOLTIP)) {
372
- const aria = el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC);
373
- const text = el.getAttribute(ATTR_TOOLTIP) || attrOrCSS(el, ATTR_TOOLTIP);
374
- 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))) {
375
675
  const hasText = attr(el, "role") !== "img" && el.textContent?.trim();
376
676
  attr(el, ATTR_TOOLTIP, text);
377
- attr(el, ARIA_LABEL, hasText ? null : text);
677
+ attr(el, ARIA_LABEL2, hasText ? null : text);
378
678
  attr(el, ARIA_DESC, hasText ? text : null);
379
679
  if (!el.matches(SELECTOR_INTERACTIVE))
380
680
  warn('Missing tabindex="0" attribute on: ', el);
381
681
  }
382
- const isCurrent = el === SOURCE && TIP?.matches(":popover-open");
682
+ const isCurrent = el === SOURCE && TIP?.offsetHeight && TIP?.offsetWidth;
383
683
  const isChanged = isCurrent && text && TIP?.textContent !== text;
384
684
  if (isCurrent && isChanged) {
385
- if (TIP) setTextWithoutMutation(TIP, text);
685
+ if (TIP) TIP.textContent = text;
386
686
  if (document.activeElement === el) announce(text);
387
687
  }
388
688
  }
389
- }, 0);
689
+ };
390
690
  var handleInterest = ({ type, target }) => {
391
691
  clearTimeout(HOVER_TIMER);
392
692
  if (target === TIP) return;
393
- if (type === "mouseover" && !SOURCE && !IS_IOS) {
693
+ if (type === "mouseover" && !IS_HOVERING && !IS_IOS) {
394
694
  HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target });
395
695
  return;
396
696
  }
@@ -406,18 +706,21 @@ var handleInterest = ({ type, target }) => {
406
706
  attr(TIP, "popover", "manual");
407
707
  attr(TIP, ATTR_SCHEME, scheme?.getAttribute(ATTR_SCHEME) || null);
408
708
  attr(TIP, ATTR_COLOR, isReset && color?.getAttribute(ATTR_COLOR) || null);
409
- setTextWithoutMutation(TIP, attr(source, ATTR_TOOLTIP));
709
+ TIP.textContent = attr(source, ATTR_TOOLTIP);
410
710
  TIP.showPopover();
411
711
  TIP.dispatchEvent(new CustomEvent("ds-toggle-source", { detail: source }));
712
+ IS_HOVERING = true;
412
713
  SOURCE = source;
413
714
  };
414
715
  var hideTooltip = () => TIP?.isConnected && TIP.popover && TIP.hidePopover();
415
716
  var handleClose = (event) => {
416
717
  if (event?.type === "keydown")
417
718
  return event?.key === "Escape" && hideTooltip();
418
- if (!event) SOURCE = void 0;
419
- 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;
420
722
  SKIP_TIMER = setTimeout(handleClose, DELAY_SKIP);
723
+ }
421
724
  };
422
725
  onHotReload("tooltip", () => [
423
726
  on(document, "blur focus mouseover", handleInterest, QUICK_EVENT),
@@ -439,30 +742,30 @@ var DSBreadcrumbsElement = class extends DSElement {
439
742
  _items;
440
743
  // Using underscore instead of private fields for backwards compatibility
441
744
  _label = null;
442
- _render;
443
745
  _unresize;
444
746
  _unmutate;
445
747
  static get observedAttributes() {
446
748
  return [ATTR_LABEL];
447
749
  }
448
750
  connectedCallback() {
751
+ const resize = debounce(() => render(this), 100);
449
752
  this._label = attrOrCSS(this, ATTR_LABEL);
450
753
  this._items = this.getElementsByTagName("a");
451
- this._render = debounce(() => render(this), 100);
452
- this._unresize = on(window, "resize", this._render);
453
- this._unmutate = onMutation(this, this._render, {
754
+ this._unresize = on(window, "resize", resize);
755
+ this._unmutate = onMutation(this, render, {
454
756
  childList: true,
455
757
  subtree: true
456
758
  });
457
759
  }
458
760
  attributeChangedCallback(_name, _prev, next) {
459
- if (next) this._label = next;
460
- this._render?.();
761
+ if (!this._unmutate || !next) return;
762
+ this._label = next;
763
+ render(this);
461
764
  }
462
765
  disconnectedCallback() {
463
766
  this._unresize?.();
464
767
  this._unmutate?.();
465
- this._unresize = this._unmutate = this._render = this._items = void 0;
768
+ this._unresize = this._unmutate = this._items = void 0;
466
769
  }
467
770
  };
468
771
  var render = (self) => {
@@ -478,104 +781,118 @@ customElements.define("ds-breadcrumbs", DSBreadcrumbsElement);
478
781
 
479
782
  // src/error-summary/error-summary.ts
480
783
  var DSErrorSummaryElement = class extends DSElement {
784
+ _unmutate;
785
+ // Using underscore instead of private fields for backwards compatibility
481
786
  connectedCallback() {
482
787
  on(this, "animationend", this, QUICK_EVENT);
483
- requestAnimationFrame(() => this.handleEvent({ target: this }));
484
- }
485
- handleEvent({ target }) {
486
- if (target !== this) return;
487
- const heading = this.querySelector("h2,h3,h4,h5,h6");
488
- if (heading) attr(this, "aria-labelledby", useId(heading));
489
788
  attr(this, "tabindex", "-1");
789
+ this._unmutate = onMutation(this, render2, {
790
+ childList: true,
791
+ subtree: true
792
+ });
490
793
  this.focus();
491
794
  }
795
+ handleEvent({ target }) {
796
+ if (target === this) this.focus();
797
+ }
492
798
  disconnectedCallback() {
493
799
  off(this, "animationend", this, QUICK_EVENT);
800
+ this._unmutate?.();
801
+ this._unmutate = void 0;
494
802
  }
495
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
+ };
496
808
  customElements.define("ds-error-summary", DSErrorSummaryElement);
497
809
 
498
810
  // src/field/field.ts
499
- var INDETERMINATE = "data-indeterminate";
500
- var FIELDS = /* @__PURE__ */ new Set();
501
- var COUNTS = /* @__PURE__ */ new WeakMap();
502
- var FIELDSETS = isBrowser() ? document.getElementsByTagName("fieldset") : [];
503
- var HAS_FIELD_SIZING = isBrowser() && CSS.supports("field-sizing", "content");
811
+ var ATTR_DESCRIBEDBY = "aria-describedby";
812
+ var ATTR_INDETERMINATE = "data-indeterminate";
504
813
  var COUNTER_DEBOUNCE = isWindows() ? 800 : 200;
505
- var HAS_VALIDATION = /* @__PURE__ */ new WeakSet();
506
- var handleMutations = debounce(() => {
507
- for (const el of FIELDSETS) {
508
- const labelledby = `${useId(el.querySelector("legend"))} ${useId(el.querySelector(':scope > :is([data-field="description"],legend + p)'))}`;
509
- 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);
510
824
  }
511
- for (const field of FIELDS) {
512
- const descs = [];
513
- const labels = [];
514
- let input;
515
- let counter;
516
- let hasValidation = false;
517
- let invalid = false;
518
- for (const el of field.getElementsByTagName("*")) {
519
- if (el instanceof HTMLLabelElement) labels.push(el);
520
- if (el.hidden) continue;
521
- if (isInputLike(el)) {
522
- if (input)
523
- warn(
524
- `Fields should only have one input element. Use <fieldset> to group multiple fields:`,
525
- field
526
- );
527
- else input = el;
528
- } else {
529
- const type = el.getAttribute("data-field");
530
- if (type === "counter") counter = el;
531
- if (type === "validation") {
532
- descs.unshift(el);
533
- hasValidation = true;
534
- invalid = invalid || isInvalid(el);
535
- } else if (type) descs.push(el);
536
- }
537
- }
538
- if (!input) warn(`Field is missing input element:`, field);
539
- else {
540
- if (counter) COUNTS.set(input, counter);
541
- for (const label of labels) attr(label, "for", useId(input));
542
- const isBoolish = input.type === "radio" || input.type === "checkbox";
543
- const fieldsetValidation = field.closest("fieldset")?.querySelector(':scope > [data-field="validation"]');
544
- 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));
545
845
  hasValidation = true;
546
- invalid = invalid || isInvalid(fieldsetValidation);
547
- descs.unshift(fieldsetValidation);
548
- }
549
- const indeterminate = attr(input, INDETERMINATE);
550
- if (indeterminate) input.indeterminate = indeterminate === "true";
551
- attr(field, "data-clickdelegatefor", isBoolish ? useId(input) : null);
552
- attr(input, "aria-describedby", descs.map(useId).join(" ") || null);
553
- if (hasValidation || HAS_VALIDATION.has(input)) {
554
- HAS_VALIDATION[hasValidation ? "add" : "delete"](input);
555
- attr(input, "aria-invalid", `${invalid}`);
556
- }
557
- updateField(input);
846
+ invalid = invalid || isInvalid(el);
847
+ } else if (type) nextDescs.push(useId(el));
558
848
  }
559
849
  }
560
- }, 0);
561
- 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) => {
562
882
  const input = e.target || e;
563
883
  const counter = COUNTS.get(input);
564
884
  if (counter?.isConnected) {
565
885
  const limit = Number(attr(counter, "data-limit")) || 0;
566
886
  const count = limit - input.value.length;
567
887
  const state = count < 0 ? "over" : "under";
568
- const label = attrOrCSS(counter, `data-${state}`)?.replace(
569
- "%d",
570
- `${Math.abs(count)}`
571
- );
888
+ const label = (attrOrCSS(counter, `data-${state}`) || TEXTS[state])?.replace("%d", `${Math.abs(count)}`);
572
889
  attr(counter, "data-label", label);
573
890
  attr(counter, "data-state", state);
574
891
  attr(counter, "data-color", count < 0 ? "danger" : null);
575
892
  if (e.type === "input" && label)
576
893
  debouncedCounterLiveRegion(input, label);
577
894
  }
578
- if (!HAS_FIELD_SIZING && input instanceof HTMLTextAreaElement) {
895
+ if (input instanceof HTMLTextAreaElement) {
579
896
  input.style.setProperty("--_ds-field-sizing", "auto");
580
897
  input.style.setProperty("--_ds-field-sizing", `${input.scrollHeight}px`);
581
898
  }
@@ -589,8 +906,8 @@ var isInputLike = (el) => el instanceof HTMLElement && "validity" in el && // Ad
589
906
  el.type !== "hidden";
590
907
  var DSFieldElement = class extends DSElement {
591
908
  connectedCallback() {
592
- FIELDS.add(this);
593
- handleMutations();
909
+ FIELDS.set(this, []);
910
+ handleFieldMutation(this);
594
911
  }
595
912
  disconnectedCallback() {
596
913
  FIELDS.delete(this);
@@ -598,14 +915,18 @@ var DSFieldElement = class extends DSElement {
598
915
  };
599
916
  customElements.define("ds-field", DSFieldElement);
600
917
  onHotReload("field", () => [
601
- on(document, "input", updateField, QUICK_EVENT),
602
- onMutation(document, handleMutations, {
918
+ on(document, "input", handleFieldInput, QUICK_EVENT),
919
+ onMutation(document, handleFieldMutations, {
603
920
  attributeFilter: [
604
921
  "data-field",
605
922
  "data-limit",
606
923
  "hidden",
924
+ // Needed to check validation visibility
925
+ "id",
926
+ // Needed to sync label "for" when ID of input/selec/textarea changes
607
927
  "value",
608
- INDETERMINATE
928
+ // Needed to detect changes in controlled React inputs as they do not trigger input events
929
+ ATTR_INDETERMINATE
609
930
  ],
610
931
  attributes: true,
611
932
  childList: true,
@@ -641,21 +962,20 @@ var DSPaginationElement = class extends DSElement {
641
962
  if (total && !current) warn(`Missing ${ATTR_CURRENT} attribute on:`, this);
642
963
  attr(this, ATTR_LABEL2, attrOrCSS(this, ATTR_LABEL2));
643
964
  attr(this, "role", "navigation");
644
- this._render = debounce(() => render2(this), 0);
645
- this._unmutate = onMutation(this, this._render, {
965
+ this._unmutate = onMutation(this, render3, {
646
966
  childList: true,
647
967
  subtree: true
648
968
  });
649
969
  }
650
970
  attributeChangedCallback() {
651
- this._render?.();
971
+ if (this._unmutate) render3(this);
652
972
  }
653
973
  disconnectedCallback() {
654
974
  this._unmutate?.();
655
975
  this._unmutate = this._render = void 0;
656
976
  }
657
977
  };
658
- var render2 = (self) => {
978
+ var render3 = (self) => {
659
979
  const current = Number(attr(self, ATTR_CURRENT));
660
980
  const total = Number(attr(self, ATTR_TOTAL));
661
981
  if (current && total) {
@@ -689,13 +1009,12 @@ customElements.define("ds-pagination", DSPaginationElement);
689
1009
  // src/suggestion/suggestion.ts
690
1010
  import { UHTMLComboboxElement } from "@u-elements/u-combobox";
691
1011
  var DSSuggestionElement = class extends UHTMLComboboxElement {
1012
+ _render;
692
1013
  _unmutate;
693
1014
  // Using underscore instead of private fields for backwards compatibility
694
- _render;
695
1015
  connectedCallback() {
696
1016
  super.connectedCallback();
697
- this._render = () => render3(this);
698
- this._unmutate = onMutation(this, this._render, { childList: true });
1017
+ this._unmutate = onMutation(this, render4, { childList: true });
699
1018
  on(this, "toggle", polyfillToggleSource, QUICK_EVENT);
700
1019
  }
701
1020
  disconnectedCallback() {
@@ -705,7 +1024,7 @@ var DSSuggestionElement = class extends UHTMLComboboxElement {
705
1024
  off(this, "toggle", polyfillToggleSource, QUICK_EVENT);
706
1025
  }
707
1026
  };
708
- var render3 = ({ control, list }) => {
1027
+ var render4 = ({ control, list }) => {
709
1028
  if (control && !control.placeholder) attr(control, "placeholder", " ");
710
1029
  if (control) attr(control, "popovertarget", useId(list) || null);
711
1030
  if (list) attr(list, "popover", "manual");
@@ -734,7 +1053,7 @@ customElements.define("ds-tab", DSTabElement);
734
1053
  customElements.define("ds-tabpanel", DSTabPanelElement);
735
1054
 
736
1055
  // src/index.ts
737
- if (isBrowser() && !isSupported()) polyfillInvokers();
1056
+ if (isBrowser() && !isSupported()) apply();
738
1057
  export {
739
1058
  DSBreadcrumbsElement,
740
1059
  DSErrorSummaryElement,