@digdir/designsystemet-web 1.12.1 → 1.13.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.
package/README.md CHANGED
@@ -4,8 +4,9 @@
4
4
 
5
5
  - [`@digdir/designsystemet-web`](#digdirdesignsystemet-web)
6
6
  - [Table of contents](#table-of-contents)
7
- - [Get started](#get-started)
8
- - [Types](#types)
7
+ - [Get started](#get-started)
8
+ - [Individual imports](#individual-imports)
9
+ - [Types](#types)
9
10
  - [Warnings:](#warnings)
10
11
  - [`<ds-breadcrumbs>`](#ds-breadcrumbs)
11
12
  - [`<ds-error-summary>`](#ds-error-summary)
@@ -26,14 +27,23 @@
26
27
  - [`popover`](#popover)
27
28
 
28
29
 
29
- ### Get started
30
+ ## Get started
30
31
 
31
32
  We recommend to import the whole package.
32
33
  This will register all web components and observers globally, so you only need to do this once.
33
34
  ```ts
34
35
  import '@digdir/designsystemet-web';
35
36
  ```
36
- #### Types
37
+
38
+ ### Individual imports
39
+
40
+ The package supports sub-path exports which means you can import individual parts of the package if needed, but this is used at own risk.
41
+
42
+ For example - importing `tooltip`, you need to also import `popover` as its built using native popover functionality.
43
+
44
+ The [invokers-polyfill](#invokers-polyfill) will **not be automatically attached using individual imports**.
45
+
46
+ ### Types
37
47
  Add the package to your `types` for types:
38
48
  ```json
39
49
  {
@@ -192,10 +202,12 @@ An observer will look for `[data-toggle-group]` and add proper arrow navigation
192
202
  ## `data-tooltip`
193
203
  Using a single element for rendering next to elements with `data-tooltip` attribute.
194
204
  Also automatically sets `aria-label` or `aria-description` as needed.
205
+ Uses native popover functionality with our [`popover`](#popover) polyfill.
195
206
 
196
207
  ```html
197
208
  <button data-placement="left" data-tooltip="left" class="ds-button">left</button>
198
209
  ```
210
+
199
211
  ## `data-clickdelegatefor`
200
212
  Used for delegating click event. For example, you can use this to delegate click events from a parent element to child elements that are added dynamically.
201
213
 
@@ -1,2 +1,2 @@
1
- const e=require(`../utils/utils.cjs`),t=`data-indeterminate`,n=new Set,r=new WeakMap,i=e.isBrowser()?document.getElementsByTagName(`fieldset`):[],a=e.isBrowser()&&CSS.supports(`field-sizing`,`content`),o=e.isWindows()?800:200,s=new WeakSet,c=e.debounce(()=>{for(let t of i)e.attr(t,`aria-labelledby`,`${e.useId(t.querySelector(`legend`))} ${e.useId(t.querySelector(`:scope > :is([data-field="description"],legend + p)`))}`.trim()||null);for(let i of n){let n=[],a=[],o,c,l=!1,d=!1;for(let t of i.getElementsByTagName(`*`))if(t instanceof HTMLLabelElement&&a.push(t),!t.hidden)if(p(t))o?e.warn(`Fields should only have one input element. Use <fieldset> to group multiple fields:`,i):o=t;else{let e=t.getAttribute(`data-field`);e===`counter`&&(c=t),e===`validation`?(n.unshift(t),l=!0,d||=f(t)):e&&n.push(t)}if(!o)e.warn(`Field is missing input element:`,i);else{c&&r.set(o,c);for(let t of a)e.attr(t,`for`,e.useId(o));let p=o.type===`radio`||o.type===`checkbox`,m=i.closest(`fieldset`)?.querySelector(`:scope > [data-field="validation"]`);m&&!m?.hidden&&(l=!0,d||=f(m),n.unshift(m));let h=e.attr(o,t);h&&(o.indeterminate=h===`true`),e.attr(i,`data-clickdelegatefor`,p?e.useId(o):null),e.attr(o,`aria-describedby`,n.map(e.useId).join(` `)||null),(l||s.has(o))&&(s[l?`add`:`delete`](o),e.attr(o,`aria-invalid`,`${d}`)),u(o)}}},0),l=e.isBrowser()?e.tag(`div`,{"aria-live":`polite`,style:`position:fixed;white-space:nowrap;clip:rect(0 0 0 0)`}):null,u=t=>{let n=t.target||t,i=r.get(n);if(i?.isConnected){let r=(Number(e.attr(i,`data-limit`))||0)-n.value.length,a=r<0?`over`:`under`,o=e.attrOrCSS(i,`data-${a}`)?.replace(`%d`,`${Math.abs(r)}`);e.attr(i,`data-label`,o),e.attr(i,`data-state`,a),e.attr(i,`data-color`,r<0?`danger`:null),t.type===`input`&&l&&o&&(l?.isConnected||document.body.appendChild(l),d(n,o))}!a&&n instanceof HTMLTextAreaElement&&(n.style.setProperty(`--_ds-field-sizing`,`auto`),n.style.setProperty(`--_ds-field-sizing`,`${n.scrollHeight}px`))},d=e.debounce((t,n)=>{let r=document.activeElement===t;l?.isConnected&&r&&e.setTextWithoutMutation(l,n)},o),f=e=>e.getAttribute(`data-color`)!==`success`,p=e=>e instanceof HTMLElement&&`validity`in e&&!(e instanceof HTMLButtonElement)&&e.type!==`hidden`;var m=class extends e.DSElement{connectedCallback(){n.add(this),c()}disconnectedCallback(){n.delete(this)}};e.customElements.define(`ds-field`,m),e.onHotReload(`field`,()=>[e.on(document,`input`,u,e.QUICK_EVENT),e.onMutation(document,c,{attributeFilter:[`data-field`,`data-limit`,`hidden`,`value`,t],attributes:!0,childList:!0,subtree:!0})]),exports.DSFieldElement=m;
1
+ const e=require(`../utils/utils.cjs`),t=`data-indeterminate`,n=new Set,r=new WeakMap,i=e.isBrowser()?document.getElementsByTagName(`fieldset`):[],a=e.isBrowser()&&CSS.supports(`field-sizing`,`content`),o=e.isWindows()?800:200,s=new WeakSet,c=e.debounce(()=>{for(let t of i)e.attr(t,`aria-labelledby`,`${e.useId(t.querySelector(`legend`))} ${e.useId(t.querySelector(`:scope > :is([data-field="description"],legend + p)`))}`.trim()||null);for(let i of n){let n=[],a=[],o,c,u=!1,p=!1;for(let t of i.getElementsByTagName(`*`))if(t instanceof HTMLLabelElement&&a.push(t),!t.hidden)if(f(t))o?e.warn(`Fields should only have one input element. Use <fieldset> to group multiple fields:`,i):o=t;else{let e=t.getAttribute(`data-field`);e===`counter`&&(c=t),e===`validation`?(n.unshift(t),u=!0,p||=d(t)):e&&n.push(t)}if(!o)e.warn(`Field is missing input element:`,i);else{c&&r.set(o,c);for(let t of a)e.attr(t,`for`,e.useId(o));let f=o.type===`radio`||o.type===`checkbox`,m=i.closest(`fieldset`)?.querySelector(`:scope > [data-field="validation"]`);m&&!m?.hidden&&(u=!0,p||=d(m),n.unshift(m));let h=e.attr(o,t);h&&(o.indeterminate=h===`true`),e.attr(i,`data-clickdelegatefor`,f?e.useId(o):null),e.attr(o,`aria-describedby`,n.map(e.useId).join(` `)||null),(u||s.has(o))&&(s[u?`add`:`delete`](o),e.attr(o,`aria-invalid`,`${p}`)),l(o)}}},0),l=t=>{let n=t.target||t,i=r.get(n);if(i?.isConnected){let r=(Number(e.attr(i,`data-limit`))||0)-n.value.length,a=r<0?`over`:`under`,o=e.attrOrCSS(i,`data-${a}`)?.replace(`%d`,`${Math.abs(r)}`);e.attr(i,`data-label`,o),e.attr(i,`data-state`,a),e.attr(i,`data-color`,r<0?`danger`:null),t.type===`input`&&o&&u(n,o)}!a&&n instanceof HTMLTextAreaElement&&(n.style.setProperty(`--_ds-field-sizing`,`auto`),n.style.setProperty(`--_ds-field-sizing`,`${n.scrollHeight}px`))},u=e.debounce((t,n)=>{document.activeElement===t&&e.announce(n)},o),d=e=>e.getAttribute(`data-color`)!==`success`,f=e=>e instanceof HTMLElement&&`validity`in e&&!(e instanceof HTMLButtonElement)&&e.type!==`hidden`;var p=class extends e.DSElement{connectedCallback(){n.add(this),c()}disconnectedCallback(){n.delete(this)}};e.customElements.define(`ds-field`,p),e.onHotReload(`field`,()=>[e.on(document,`input`,l,e.QUICK_EVENT),e.onMutation(document,c,{attributeFilter:[`data-field`,`data-limit`,`hidden`,`value`,t],attributes:!0,childList:!0,subtree:!0})]),exports.DSFieldElement=p;
2
2
  //# sourceMappingURL=field.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"field.cjs","names":["isBrowser","isWindows","debounce","useId","attr","tag","attrOrCSS","DSElement","customElements","onHotReload","on","QUICK_EVENT","onMutation"],"sources":["../../../src/field/field.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n customElements,\n DSElement,\n debounce,\n isBrowser,\n isWindows,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n setTextWithoutMutation,\n tag,\n useId,\n warn,\n} from '../utils/utils';\n\n// TODO: Document that Validation must be hidden with \"hidden\" attribute (or completely removed from DOM), not display: none\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ds-field': DSFieldElement;\n }\n}\n\nconst INDETERMINATE = 'data-indeterminate';\nconst FIELDS = new Set<DSFieldElement>(); // Set of Field\nconst COUNTS = new WeakMap<HTMLInputElement, Element>(); // Using WeakMap so removed inputs/counts does not cause memory leaks\nconst FIELDSETS = isBrowser() ? document.getElementsByTagName('fieldset') : [];\nconst HAS_FIELD_SIZING = isBrowser() && CSS.supports('field-sizing', 'content');\nconst COUNTER_DEBOUNCE = isWindows() ? 800 : 200; // Longer debounce on Windows due to NVDA performance\nconst HAS_VALIDATION = new WeakSet<HTMLInputElement>(); // Used to store inputs that have/had validation elements to manage aria-invalid\n\nconst handleMutations = debounce(() => {\n for (const el of FIELDSETS) {\n const labelledby = `${useId(el.querySelector('legend'))} ${useId(el.querySelector(':scope > :is([data-field=\"description\"],legend + p)'))}`;\n attr(el, 'aria-labelledby', labelledby.trim() || null);\n }\n for (const field of FIELDS) {\n const descs: Element[] = [];\n const labels: HTMLLabelElement[] = [];\n let input: HTMLInputElement | undefined;\n let counter: Element | undefined;\n let hasValidation = false;\n let invalid = false;\n\n for (const el of field.getElementsByTagName('*')) {\n if (el instanceof HTMLLabelElement) labels.push(el);\n if ((el as HTMLElement).hidden) continue; // Skip hidden elements except labels\n if (isInputLike(el)) {\n if (input)\n warn(\n `Fields should only have one input element. Use <fieldset> to group multiple fields:`,\n field,\n );\n else input = el; // Only register if visible input\n } else {\n const type = el.getAttribute('data-field'); // Using getAttribute instead of attr for best performance\n if (type === 'counter') counter = el;\n if (type === 'validation') {\n descs.unshift(el);\n hasValidation = true;\n invalid = invalid || isInvalid(el);\n } else if (type) descs.push(el); // Adds both counter and descriptions\n }\n }\n\n if (!input) warn(`Field is missing input element:`, field);\n else {\n if (counter) COUNTS.set(input, counter);\n for (const label of labels) attr(label, 'for', useId(input));\n\n const isBoolish = input.type === 'radio' || input.type === 'checkbox';\n const fieldsetValidation = field\n .closest('fieldset')\n ?.querySelector<HTMLElement>(':scope > [data-field=\"validation\"]');\n if (fieldsetValidation && !fieldsetValidation?.hidden) {\n hasValidation = true;\n invalid = invalid || isInvalid(fieldsetValidation);\n descs.unshift(fieldsetValidation);\n }\n\n const indeterminate = attr(input, INDETERMINATE);\n if (indeterminate) input.indeterminate = indeterminate === 'true';\n\n attr(field, 'data-clickdelegatefor', isBoolish ? useId(input) : null); // Expand click area to ds-field if radio/checkbox\n attr(input, 'aria-describedby', descs.map(useId).join(' ') || null);\n if (hasValidation || HAS_VALIDATION.has(input)) {\n HAS_VALIDATION[hasValidation ? 'add' : 'delete'](input); // Track if field has validation elements to avoid managing aria-invalid on every mutation\n attr(input, 'aria-invalid', `${invalid}`); // Only manage aria-invalid when field has validation elements\n }\n updateField(input); // Update counter and textarea sizing\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst SR_ONLY = 'position:fixed;white-space:nowrap;clip:rect(0 0 0 0)';\nconst SR_LIVE = isBrowser()\n ? tag('div', { 'aria-live': 'polite', style: SR_ONLY })\n : null;\n\nconst updateField = (e: Event | Element) => {\n const input = ((e as Event).target || e) as HTMLInputElement;\n const counter = COUNTS.get(input);\n\n if (counter?.isConnected) {\n const limit = Number(attr(counter, 'data-limit')) || 0;\n const count = limit - input.value.length;\n const state = count < 0 ? 'over' : 'under';\n const label = attrOrCSS(counter, `data-${state}`)?.replace(\n '%d',\n `${Math.abs(count)}`,\n );\n\n attr(counter, 'data-label', label); // Using attribute to prevent hydation errors, not using aria-label to make axe tests happy\n attr(counter, 'data-state', state);\n attr(counter, 'data-color', count < 0 ? 'danger' : null);\n\n // Only update live region when user is actually typing\n if ((e as Event).type === 'input' && SR_LIVE && label) {\n if (!SR_LIVE?.isConnected) document.body.appendChild(SR_LIVE); // Prepare live region\n debouncedCounterLiveRegion(input, label); // Debounce live region to avoid NVDA interupting announcing typed text\n }\n }\n if (!HAS_FIELD_SIZING && input instanceof HTMLTextAreaElement) {\n input.style.setProperty('--_ds-field-sizing', 'auto');\n input.style.setProperty('--_ds-field-sizing', `${input.scrollHeight}px`);\n }\n};\n\nconst debouncedCounterLiveRegion = debounce((input: Element, text: string) => {\n const hasFocus = document.activeElement === input; // Only announce if input is still focused\n if (SR_LIVE?.isConnected && hasFocus) setTextWithoutMutation(SR_LIVE, text);\n}, COUNTER_DEBOUNCE);\n\nconst isInvalid = (el: Element) => el.getAttribute('data-color') !== 'success';\nconst isInputLike = (el: unknown): el is HTMLInputElement =>\n el instanceof HTMLElement &&\n 'validity' in el && // Adds support for custom elements implemeted with attachInternals()\n !(el instanceof HTMLButtonElement) && // But skip <button> elements\n (el as HTMLInputElement).type !== 'hidden'; // And skip input type=\"hidden\"\n\n// Custom element is used to performantly keep track of fields on the page\nexport class DSFieldElement extends DSElement {\n connectedCallback() {\n FIELDS.add(this); // Register field\n handleMutations(); // Initial setup\n }\n disconnectedCallback() {\n FIELDS.delete(this);\n }\n}\n\ncustomElements.define('ds-field', DSFieldElement);\n\n// Listen for hidden to detect hidden validations, and listen for value to detect controlled React inputs\nonHotReload('field', () => [\n on(document, 'input', updateField, QUICK_EVENT),\n onMutation(document, handleMutations, {\n attributeFilter: [\n 'data-field',\n 'data-limit',\n 'hidden',\n 'value',\n INDETERMINATE,\n ],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"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"}
1
+ {"version":3,"file":"field.cjs","names":["isBrowser","isWindows","debounce","useId","attr","attrOrCSS","DSElement","customElements","onHotReload","on","QUICK_EVENT","onMutation"],"sources":["../../../src/field/field.ts"],"sourcesContent":["import {\n announce,\n attr,\n attrOrCSS,\n customElements,\n DSElement,\n debounce,\n isBrowser,\n isWindows,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n useId,\n warn,\n} from '../utils/utils';\n\n// TODO: Document that Validation must be hidden with \"hidden\" attribute (or completely removed from DOM), not display: none\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ds-field': DSFieldElement;\n }\n}\n\nconst INDETERMINATE = 'data-indeterminate';\nconst FIELDS = new Set<DSFieldElement>(); // Set of Field\nconst COUNTS = new WeakMap<HTMLInputElement, Element>(); // Using WeakMap so removed inputs/counts does not cause memory leaks\nconst FIELDSETS = isBrowser() ? document.getElementsByTagName('fieldset') : [];\nconst HAS_FIELD_SIZING = isBrowser() && CSS.supports('field-sizing', 'content');\nconst COUNTER_DEBOUNCE = isWindows() ? 800 : 200; // Longer debounce on Windows due to NVDA performance\nconst HAS_VALIDATION = new WeakSet<HTMLInputElement>(); // Used to store inputs that have/had validation elements to manage aria-invalid\n\nconst handleMutations = debounce(() => {\n for (const el of FIELDSETS) {\n const labelledby = `${useId(el.querySelector('legend'))} ${useId(el.querySelector(':scope > :is([data-field=\"description\"],legend + p)'))}`;\n attr(el, 'aria-labelledby', labelledby.trim() || null);\n }\n for (const field of FIELDS) {\n const descs: Element[] = [];\n const labels: HTMLLabelElement[] = [];\n let input: HTMLInputElement | undefined;\n let counter: Element | undefined;\n let hasValidation = false;\n let invalid = false;\n\n for (const el of field.getElementsByTagName('*')) {\n if (el instanceof HTMLLabelElement) labels.push(el);\n if ((el as HTMLElement).hidden) continue; // Skip hidden elements except labels\n if (isInputLike(el)) {\n if (input)\n warn(\n `Fields should only have one input element. Use <fieldset> to group multiple fields:`,\n field,\n );\n else input = el; // Only register if visible input\n } else {\n const type = el.getAttribute('data-field'); // Using getAttribute instead of attr for best performance\n if (type === 'counter') counter = el;\n if (type === 'validation') {\n descs.unshift(el);\n hasValidation = true;\n invalid = invalid || isInvalid(el);\n } else if (type) descs.push(el); // Adds both counter and descriptions\n }\n }\n\n if (!input) warn(`Field is missing input element:`, field);\n else {\n if (counter) COUNTS.set(input, counter);\n for (const label of labels) attr(label, 'for', useId(input));\n\n const isBoolish = input.type === 'radio' || input.type === 'checkbox';\n const fieldsetValidation = field\n .closest('fieldset')\n ?.querySelector<HTMLElement>(':scope > [data-field=\"validation\"]');\n if (fieldsetValidation && !fieldsetValidation?.hidden) {\n hasValidation = true;\n invalid = invalid || isInvalid(fieldsetValidation);\n descs.unshift(fieldsetValidation);\n }\n\n const indeterminate = attr(input, INDETERMINATE);\n if (indeterminate) input.indeterminate = indeterminate === 'true';\n\n attr(field, 'data-clickdelegatefor', isBoolish ? useId(input) : null); // Expand click area to ds-field if radio/checkbox\n attr(input, 'aria-describedby', descs.map(useId).join(' ') || null);\n if (hasValidation || HAS_VALIDATION.has(input)) {\n HAS_VALIDATION[hasValidation ? 'add' : 'delete'](input); // Track if field has validation elements to avoid managing aria-invalid on every mutation\n attr(input, 'aria-invalid', `${invalid}`); // Only manage aria-invalid when field has validation elements\n }\n updateField(input); // Update counter and textarea sizing\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst updateField = (e: Event | Element) => {\n const input = ((e as Event).target || e) as HTMLInputElement;\n const counter = COUNTS.get(input);\n\n if (counter?.isConnected) {\n const limit = Number(attr(counter, 'data-limit')) || 0;\n const count = limit - input.value.length;\n const state = count < 0 ? 'over' : 'under';\n const label = attrOrCSS(counter, `data-${state}`)?.replace(\n '%d',\n `${Math.abs(count)}`,\n );\n\n attr(counter, 'data-label', label); // Using attribute to prevent hydation errors, not using aria-label to make axe tests happy\n attr(counter, 'data-state', state);\n attr(counter, 'data-color', count < 0 ? 'danger' : null);\n\n // Only update live region when user is actually typing\n if ((e as Event).type === 'input' && label)\n debouncedCounterLiveRegion(input, label); // Debounce live region to avoid NVDA interupting announcing typed text\n }\n if (!HAS_FIELD_SIZING && input instanceof HTMLTextAreaElement) {\n input.style.setProperty('--_ds-field-sizing', 'auto');\n input.style.setProperty('--_ds-field-sizing', `${input.scrollHeight}px`);\n }\n};\n\nconst debouncedCounterLiveRegion = debounce((input: Element, text: string) => {\n if (document.activeElement === input) announce(text); // Only announce if input is still focused\n}, COUNTER_DEBOUNCE);\n\nconst isInvalid = (el: Element) => el.getAttribute('data-color') !== 'success';\nconst isInputLike = (el: unknown): el is HTMLInputElement =>\n el instanceof HTMLElement &&\n 'validity' in el && // Adds support for custom elements implemeted with attachInternals()\n !(el instanceof HTMLButtonElement) && // But skip <button> elements\n (el as HTMLInputElement).type !== 'hidden'; // And skip input type=\"hidden\"\n\n// Custom element is used to performantly keep track of fields on the page\nexport class DSFieldElement extends DSElement {\n connectedCallback() {\n FIELDS.add(this); // Register field\n handleMutations(); // Initial setup\n }\n disconnectedCallback() {\n FIELDS.delete(this);\n }\n}\n\ncustomElements.define('ds-field', DSFieldElement);\n\n// Listen for hidden to detect hidden validations, and listen for value to detect controlled React inputs\nonHotReload('field', () => [\n on(document, 'input', updateField, QUICK_EVENT),\n onMutation(document, handleMutations, {\n attributeFilter: [\n 'data-field',\n 'data-limit',\n 'hidden',\n 'value',\n INDETERMINATE,\n ],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require(`../utils/utils.cjs`),t=`data-toggle-group`,n=`[${t}]`,r=e.debounce(()=>{for(let r of document.querySelectorAll(n))e.attr(r,`aria-label`,e.attrOrCSS(r,t))},0),i=e=>{let t=e.target instanceof HTMLInputElement&&e.target.closest(n);if(t&&(e.key===`Enter`&&e.target.click(),e.key?.startsWith(`Arrow`))){e.preventDefault?.();let n=t.getElementsByTagName(`input`),r=[...n].indexOf(e.target),i=e.key.match(/Arrow(Right|Down)/)?1:-1;n[(n.length+r+i)%n.length]?.focus()}};e.onHotReload(`toggle-group`,()=>[e.on(document,`keydown`,i),e.onMutation(document,r,{attributeFilter:[t],attributes:!0,childList:!0,subtree:!0})]);
1
+ const e=require(`../utils/utils.cjs`),t=`data-toggle-group`,n=`[${t}]`,r=e.debounce(()=>{for(let r of document.querySelectorAll(n))e.attr(r,`aria-label`,e.attrOrCSS(r,t))},0),i=e=>{let t=e.target instanceof HTMLInputElement&&e.target.closest(n);if(t&&(e.key===`Enter`&&e.target.click(),e.key?.startsWith(`Arrow`))){e.preventDefault?.();let n=[...t.getElementsByTagName(`input`)],r=n.indexOf(e.target),i=e.key.match(/Arrow(Right|Down)/)?1:-1,a=r;for(let e=0;e<n.length;e++)if(a=(n.length+a+i)%n.length,!n[a]?.disabled){n[a]?.focus();break}}};e.onHotReload(`toggle-group`,()=>[e.on(document,`keydown`,i),e.onMutation(document,r,{attributeFilter:[t],attributes:!0,childList:!0,subtree:!0})]);
2
2
  //# sourceMappingURL=toggle-group.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"toggle-group.cjs","names":["debounce","attrOrCSS","onHotReload","on","onMutation"],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n} from '../utils/utils';\n\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\n\nconst handleAriaAttributes = debounce(() => {\n for (const group of document.querySelectorAll(SELECTOR_TOGGLEGROUP))\n attr(group, 'aria-label', attrOrCSS(group, ATTR_TOGGLEGROUP));\n}, 0); // Debounce to merge multiple mutations\n\nconst handleKeydown = (event: Partial<KeyboardEvent>) => {\n const group =\n event.target instanceof HTMLInputElement &&\n event.target.closest(SELECTOR_TOGGLEGROUP);\n\n if (!group) return;\n if (event.key === 'Enter') event.target.click(); // Forward Enter, but no need to listen for space key, as this is handled by the browser\n if (event.key?.startsWith('Arrow')) {\n event.preventDefault?.();\n const inputs = group.getElementsByTagName('input');\n const index = [...inputs].indexOf(event.target);\n const move = event.key.match(/Arrow(Right|Down)/) ? 1 : -1;\n inputs[(inputs.length + index + move) % inputs.length]?.focus();\n }\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"sCASM,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAE5C,EAAuBA,EAAAA,aAAe,CAC1C,IAAK,IAAM,KAAS,SAAS,iBAAiB,EAAqB,CACjE,EAAA,KAAK,EAAO,aAAcC,EAAAA,UAAU,EAAO,EAAiB,CAAC,EAC9D,EAAE,CAEC,EAAiB,GAAkC,CACvD,IAAM,EACJ,EAAM,kBAAkB,kBACxB,EAAM,OAAO,QAAQ,EAAqB,CAEvC,OACD,EAAM,MAAQ,SAAS,EAAM,OAAO,OAAO,CAC3C,EAAM,KAAK,WAAW,QAAQ,EAAE,CAClC,EAAM,kBAAkB,CACxB,IAAM,EAAS,EAAM,qBAAqB,QAAQ,CAC5C,EAAQ,CAAC,GAAG,EAAO,CAAC,QAAQ,EAAM,OAAO,CACzC,EAAO,EAAM,IAAI,MAAM,oBAAoB,CAAG,EAAI,GACxD,GAAQ,EAAO,OAAS,EAAQ,GAAQ,EAAO,SAAS,OAAO,GAInEC,EAAAA,YAAY,mBAAsB,CAChCC,EAAAA,GAAG,SAAU,UAAW,EAAc,CACtCC,EAAAA,WAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAiB,CACnC,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
1
+ {"version":3,"file":"toggle-group.cjs","names":["debounce","attrOrCSS","onHotReload","on","onMutation"],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n} from '../utils/utils';\n\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\n\nconst handleAriaAttributes = debounce(() => {\n for (const group of document.querySelectorAll(SELECTOR_TOGGLEGROUP))\n attr(group, 'aria-label', attrOrCSS(group, ATTR_TOGGLEGROUP));\n}, 0); // Debounce to merge multiple mutations\n\nconst handleKeydown = (event: Partial<KeyboardEvent>) => {\n const group =\n event.target instanceof HTMLInputElement &&\n event.target.closest(SELECTOR_TOGGLEGROUP);\n\n if (!group) return;\n if (event.key === 'Enter') event.target.click(); // Forward Enter, but no need to listen for space key, as this is handled by the browser\n if (event.key?.startsWith('Arrow')) {\n event.preventDefault?.();\n const inputs = [...group.getElementsByTagName('input')];\n const index = inputs.indexOf(event.target);\n const move = event.key.match(/Arrow(Right|Down)/) ? 1 : -1;\n let nextIndex = index;\n\n for (let i = 0; i < inputs.length; i++) {\n nextIndex = (inputs.length + nextIndex + move) % inputs.length;\n if (!inputs[nextIndex]?.disabled) {\n inputs[nextIndex]?.focus();\n break;\n }\n }\n }\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"sCASM,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAE5C,EAAuBA,EAAAA,aAAe,CAC1C,IAAK,IAAM,KAAS,SAAS,iBAAiB,EAAqB,CACjE,EAAA,KAAK,EAAO,aAAcC,EAAAA,UAAU,EAAO,EAAiB,CAAC,EAC9D,EAAE,CAEC,EAAiB,GAAkC,CACvD,IAAM,EACJ,EAAM,kBAAkB,kBACxB,EAAM,OAAO,QAAQ,EAAqB,CAEvC,OACD,EAAM,MAAQ,SAAS,EAAM,OAAO,OAAO,CAC3C,EAAM,KAAK,WAAW,QAAQ,EAAE,CAClC,EAAM,kBAAkB,CACxB,IAAM,EAAS,CAAC,GAAG,EAAM,qBAAqB,QAAQ,CAAC,CACjD,EAAQ,EAAO,QAAQ,EAAM,OAAO,CACpC,EAAO,EAAM,IAAI,MAAM,oBAAoB,CAAG,EAAI,GACpD,EAAY,EAEhB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAEjC,GADA,GAAa,EAAO,OAAS,EAAY,GAAQ,EAAO,OACpD,CAAC,EAAO,IAAY,SAAU,CAChC,EAAO,IAAY,OAAO,CAC1B,SAMRC,EAAAA,YAAY,mBAAsB,CAChCC,EAAAA,GAAG,SAAU,UAAW,EAAc,CACtCC,EAAAA,WAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAiB,CACnC,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
@@ -1,2 +1,2 @@
1
- const e=require(`../utils/utils.cjs`);let t,n,r=0,i=0;const a=`data-tooltip`,o=`data-color`,s=`aria-label`,c=`aria-description`,l=`[${o}]`,u=`[${a}]`,d=`data-color-scheme`,f=`[${d}]`,p=n=>{n&&!(n instanceof HTMLElement)&&e.warn(`setTooltipElement expects an HTMLElement, got: `,n),t=n||void 0},m=e.debounce(()=>{for(let t of document.querySelectorAll(u)){let n=t.getAttribute(s)||t.getAttribute(c),r=t.getAttribute(a)||e.attrOrCSS(t,a);if(n!==r){let n=e.attr(t,`role`)!==`img`&&t.textContent?.trim();e.attr(t,a,r),e.attr(t,s,n?null:r),e.attr(t,c,n?r:null),t.matches(`a,button,input,label,select,textarea,[tabindex]`)||e.warn(`Missing tabindex="0" attribute on: `,t)}}},0),h=({type:s,target:c})=>{if(clearTimeout(r),c===t)return;if(s===`mouseover`&&!n){r=setTimeout(h,300,{target:c});return}let u=c?.closest?.(`[${a}]`);if(u===n)return;if(!u)return g();t||=e.tag(`div`,{class:`ds-tooltip`}),t.isConnected||document.body.appendChild(t);let p=u.closest(l),m=u.closest(f),_=p!==m&&p?.contains(m);clearTimeout(i),e.attr(t,`popover`,`manual`),e.attr(t,d,m?.getAttribute(d)||null),e.attr(t,o,_&&p?.getAttribute(o)||null),e.setTextWithoutMutation(t,e.attr(u,a)),t.showPopover(),t.dispatchEvent(new CustomEvent(`ds-toggle-source`,{detail:u})),n=u},g=()=>t?.isConnected&&t.popover&&t.hidePopover(),_=e=>{if(e?.type===`keydown`)return e?.key===`Escape`&&g();e?e.target===t&&e.newState===`closed`&&(i=setTimeout(_,300)):n=void 0};e.onHotReload(`tooltip`,()=>[e.on(document,`blur focus mouseover`,h,e.QUICK_EVENT),e.on(document,`toggle keydown`,_,e.QUICK_EVENT),e.onMutation(document,m,{attributeFilter:[a],attributes:!0,childList:!0,subtree:!0})]),exports.setTooltipElement=p;
1
+ const e=require(`../utils/utils.cjs`);let t,n,r=0,i=0;const a=`data-tooltip`,o=`data-color`,s=`aria-label`,c=`aria-description`,l=`[${o}]`,u=`[${a}]`,d=`data-color-scheme`,f=`[${d}]`,p=n=>{n&&!(n instanceof HTMLElement)&&e.warn(`setTooltipElement expects an HTMLElement, got: `,n),t=n||void 0},m=e.debounce(()=>{for(let r of document.querySelectorAll(u)){let i=r.getAttribute(s)||r.getAttribute(c),o=r.getAttribute(a)||e.attrOrCSS(r,a);if(i!==o){let t=e.attr(r,`role`)!==`img`&&r.textContent?.trim();e.attr(r,a,o),e.attr(r,s,t?null:o),e.attr(r,c,t?o:null),r.matches(`a,button,input,label,select,textarea,[tabindex]`)||e.warn(`Missing tabindex="0" attribute on: `,r)}let l=r===n&&t?.matches(`:popover-open`),u=l&&o&&t?.textContent!==o;l&&u&&(t&&e.setTextWithoutMutation(t,o),document.activeElement===r&&e.announce(o))}},0),h=({type:s,target:c})=>{if(clearTimeout(r),c===t)return;if(s===`mouseover`&&!n){r=setTimeout(h,300,{target:c});return}let u=c?.closest?.(`[${a}]`);if(u===n)return;if(!u)return g();t||=e.tag(`div`,{class:`ds-tooltip`}),t.isConnected||document.body.appendChild(t);let p=u.closest(l),m=u.closest(f),_=p!==m&&p?.contains(m);clearTimeout(i),e.attr(t,`popover`,`manual`),e.attr(t,d,m?.getAttribute(d)||null),e.attr(t,o,_&&p?.getAttribute(o)||null),e.setTextWithoutMutation(t,e.attr(u,a)),t.showPopover(),t.dispatchEvent(new CustomEvent(`ds-toggle-source`,{detail:u})),n=u},g=()=>t?.isConnected&&t.popover&&t.hidePopover(),_=e=>{if(e?.type===`keydown`)return e?.key===`Escape`&&g();e?e.target===t&&e.newState===`closed`&&(i=setTimeout(_,300)):n=void 0};e.onHotReload(`tooltip`,()=>[e.on(document,`blur focus mouseover`,h,e.QUICK_EVENT),e.on(document,`toggle keydown`,_,e.QUICK_EVENT),e.onMutation(document,m,{attributeFilter:[a],attributes:!0,childList:!0,subtree:!0})]),exports.setTooltipElement=p;
2
2
  //# sourceMappingURL=tooltip.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.cjs","names":["debounce","attrOrCSS","attr","tag","onHotReload","on","QUICK_EVENT","onMutation"],"sources":["../../../src/tooltip/tooltip.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n setTextWithoutMutation,\n tag,\n warn,\n} from '../utils/utils';\n\nlet TIP: HTMLElement | undefined;\nlet SOURCE: Element | undefined;\nlet HOVER_TIMER: number | ReturnType<typeof setTimeout> = 0;\nlet SKIP_TIMER: number | ReturnType<typeof setTimeout> = 0;\nconst ATTR_TOOLTIP = 'data-tooltip';\nconst ATTR_COLOR = 'data-color';\nconst ARIA_LABEL = 'aria-label';\nconst ARIA_DESC = 'aria-description';\nconst SELECTOR_COLOR = `[${ATTR_COLOR}]`;\nconst SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;\nconst ATTR_SCHEME = 'data-color-scheme';\nconst SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;\nconst SELECTOR_INTERACTIVE = 'a,button,input,label,select,textarea,[tabindex]';\nconst DELAY_HOVER = 300;\nconst DELAY_SKIP = 300;\n\n/**\n * setTooltipElement\n * @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.\n * @param el The HTMLElement to use as tooltip\n */\nexport const setTooltipElement = (el?: HTMLElement | null) => {\n if (el && !(el instanceof HTMLElement))\n warn('setTooltipElement expects an HTMLElement, got: ', el);\n TIP = el || undefined;\n};\n\nconst handleAriaAttributes = debounce(() => {\n for (const el of document.querySelectorAll(SELECTOR_TOOLTIP)) {\n const aria = el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC); // Using getAttribute for best performance\n const text = el.getAttribute(ATTR_TOOLTIP) || attrOrCSS(el, ATTR_TOOLTIP); // Only parse CSS if attribute is empty for better performance\n\n if (aria !== text) {\n const hasText = attr(el, 'role') !== 'img' && el.textContent?.trim(); // If role=\"img\", ignore text\n attr(el, ATTR_TOOLTIP, text); // Set data-tooltip attribute to speed up future mutations\n attr(el, ARIA_LABEL, hasText ? null : text); // Set aria-label if element does not have text\n attr(el, ARIA_DESC, hasText ? text : null); // Set aria-description if element has text\n if (!el.matches(SELECTOR_INTERACTIVE))\n warn('Missing tabindex=\"0\" attribute on: ', el);\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst handleInterest = ({ type, target }: Event) => {\n clearTimeout(HOVER_TIMER);\n\n if (target === TIP) return; // Allow tooltip to be hovered, following https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus\n if (type === 'mouseover' && !SOURCE) {\n HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target }); // Delay mouse showing tooltip if not already shown\n return;\n }\n\n const source = (target as Element)?.closest?.(`[${ATTR_TOOLTIP}]`);\n if (source === SOURCE) return; // No need to update\n if (!source) return hideTooltip(); // If no new anchor, cleanup previous autoUpdate\n if (!TIP) TIP = tag('div', { class: 'ds-tooltip' });\n if (!TIP.isConnected) document.body.appendChild(TIP); // Ensure connected\n\n const color = source.closest(SELECTOR_COLOR); // Match source color of source element\n const scheme = source.closest(SELECTOR_SCHEME); // Match source color-scheme of source element\n const isReset = color !== scheme && color?.contains(scheme as Node); // If data-scheme is closer to target, it will reset data-color\n clearTimeout(SKIP_TIMER);\n attr(TIP, 'popover', 'manual'); // Ensure popover behavior\n attr(TIP, ATTR_SCHEME, scheme?.getAttribute(ATTR_SCHEME) || null); // Fallback to null to reset if not scheme found\n attr(TIP, ATTR_COLOR, (isReset && color?.getAttribute(ATTR_COLOR)) || null); // Fallback to null to reset if not scheme found\n setTextWithoutMutation(TIP, attr(source, ATTR_TOOLTIP));\n TIP.showPopover();\n TIP.dispatchEvent(new CustomEvent('ds-toggle-source', { detail: source })); // Since showPopover({ source }) is not supported in all browsers yet\n SOURCE = source;\n};\n\nconst hideTooltip = () => TIP?.isConnected && TIP.popover && TIP.hidePopover(); // Only hide if connected and activated\n\nconst handleClose = (event?: Partial<ToggleEvent & KeyboardEvent>) => {\n if (event?.type === 'keydown')\n return event?.key === 'Escape' && hideTooltip();\n if (!event) SOURCE = undefined;\n else if (event.target === TIP && event.newState === 'closed')\n SKIP_TIMER = setTimeout(handleClose, DELAY_SKIP);\n};\n\nonHotReload('tooltip', () => [\n on(document, 'blur focus mouseover', handleInterest, QUICK_EVENT),\n on(document, 'toggle keydown', handleClose, QUICK_EVENT),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOOLTIP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"sCAaA,IAAI,EACA,EACA,EAAsD,EACtD,EAAqD,EACzD,MAAM,EAAe,eACf,EAAa,aACb,EAAa,aACb,EAAY,mBACZ,EAAiB,IAAI,EAAW,GAChC,EAAmB,IAAI,EAAa,GACpC,EAAc,oBACd,EAAkB,IAAI,EAAY,GAU3B,EAAqB,GAA4B,CACxD,GAAM,EAAE,aAAc,cACxB,EAAA,KAAK,kDAAmD,EAAG,CAC7D,EAAM,GAAM,IAAA,IAGR,EAAuBA,EAAAA,aAAe,CAC1C,IAAK,IAAM,KAAM,SAAS,iBAAiB,EAAiB,CAAE,CAC5D,IAAM,EAAO,EAAG,aAAa,EAAW,EAAI,EAAG,aAAa,EAAU,CAChE,EAAO,EAAG,aAAa,EAAa,EAAIC,EAAAA,UAAU,EAAI,EAAa,CAEzE,GAAI,IAAS,EAAM,CACjB,IAAM,EAAUC,EAAAA,KAAK,EAAI,OAAO,GAAK,OAAS,EAAG,aAAa,MAAM,CACpE,EAAA,KAAK,EAAI,EAAc,EAAK,CAC5B,EAAA,KAAK,EAAI,EAAY,EAAU,KAAO,EAAK,CAC3C,EAAA,KAAK,EAAI,EAAW,EAAU,EAAO,KAAK,CACrC,EAAG,QAAQ,kDAAqB,EACnC,EAAA,KAAK,sCAAuC,EAAG,IAGpD,EAAE,CAEC,GAAkB,CAAE,OAAM,YAAoB,CAGlD,GAFA,aAAa,EAAY,CAErB,IAAW,EAAK,OACpB,GAAI,IAAS,aAAe,CAAC,EAAQ,CACnC,EAAc,WAAW,EAAgB,IAAa,CAAE,SAAQ,CAAC,CACjE,OAGF,IAAM,EAAU,GAAoB,UAAU,IAAI,EAAa,GAAG,CAClE,GAAI,IAAW,EAAQ,OACvB,GAAI,CAAC,EAAQ,OAAO,GAAa,CACjC,AAAU,IAAMC,EAAAA,IAAI,MAAO,CAAE,MAAO,aAAc,CAAC,CAC9C,EAAI,aAAa,SAAS,KAAK,YAAY,EAAI,CAEpD,IAAM,EAAQ,EAAO,QAAQ,EAAe,CACtC,EAAS,EAAO,QAAQ,EAAgB,CACxC,EAAU,IAAU,GAAU,GAAO,SAAS,EAAe,CACnE,aAAa,EAAW,CACxB,EAAA,KAAK,EAAK,UAAW,SAAS,CAC9B,EAAA,KAAK,EAAK,EAAa,GAAQ,aAAa,EAAY,EAAI,KAAK,CACjE,EAAA,KAAK,EAAK,EAAa,GAAW,GAAO,aAAa,EAAW,EAAK,KAAK,CAC3E,EAAA,uBAAuB,EAAKD,EAAAA,KAAK,EAAQ,EAAa,CAAC,CACvD,EAAI,aAAa,CACjB,EAAI,cAAc,IAAI,YAAY,mBAAoB,CAAE,OAAQ,EAAQ,CAAC,CAAC,CAC1E,EAAS,GAGL,MAAoB,GAAK,aAAe,EAAI,SAAW,EAAI,aAAa,CAExE,EAAe,GAAiD,CACpE,GAAI,GAAO,OAAS,UAClB,OAAO,GAAO,MAAQ,UAAY,GAAa,CAC5C,EACI,EAAM,SAAW,GAAO,EAAM,WAAa,WAClD,EAAa,WAAW,EAAa,IAAW,EAFtC,EAAS,IAAA,IAKvBE,EAAAA,YAAY,cAAiB,CAC3BC,EAAAA,GAAG,SAAU,uBAAwB,EAAgBC,EAAAA,YAAY,CACjED,EAAAA,GAAG,SAAU,iBAAkB,EAAaC,EAAAA,YAAY,CACxDC,EAAAA,WAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAa,CAC/B,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
1
+ {"version":3,"file":"tooltip.cjs","names":["debounce","attrOrCSS","attr","tag","onHotReload","on","QUICK_EVENT","onMutation"],"sources":["../../../src/tooltip/tooltip.ts"],"sourcesContent":["import {\n announce,\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n setTextWithoutMutation,\n tag,\n warn,\n} from '../utils/utils';\n\nlet TIP: HTMLElement | undefined;\nlet SOURCE: Element | undefined;\nlet HOVER_TIMER: number | ReturnType<typeof setTimeout> = 0;\nlet SKIP_TIMER: number | ReturnType<typeof setTimeout> = 0;\nconst ATTR_TOOLTIP = 'data-tooltip';\nconst ATTR_COLOR = 'data-color';\nconst ARIA_LABEL = 'aria-label';\nconst ARIA_DESC = 'aria-description';\nconst SELECTOR_COLOR = `[${ATTR_COLOR}]`;\nconst SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;\nconst ATTR_SCHEME = 'data-color-scheme';\nconst SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;\nconst SELECTOR_INTERACTIVE = 'a,button,input,label,select,textarea,[tabindex]';\nconst DELAY_HOVER = 300;\nconst DELAY_SKIP = 300;\n\n/**\n * setTooltipElement\n * @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.\n * @param el The HTMLElement to use as tooltip\n */\nexport const setTooltipElement = (el?: HTMLElement | null) => {\n if (el && !(el instanceof HTMLElement))\n warn('setTooltipElement expects an HTMLElement, got: ', el);\n TIP = el || undefined;\n};\n\nconst handleAriaAttributes = debounce(() => {\n for (const el of document.querySelectorAll(SELECTOR_TOOLTIP)) {\n const aria = el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC); // Using getAttribute for best performance\n const text = el.getAttribute(ATTR_TOOLTIP) || attrOrCSS(el, ATTR_TOOLTIP); // Only parse CSS if attribute is empty for better performance\n\n if (aria !== text) {\n const hasText = attr(el, 'role') !== 'img' && el.textContent?.trim(); // If role=\"img\", ignore text\n attr(el, ATTR_TOOLTIP, text); // Set data-tooltip attribute to speed up future mutations\n attr(el, ARIA_LABEL, hasText ? null : text); // Set aria-label if element does not have text\n attr(el, ARIA_DESC, hasText ? text : null); // Set aria-description if element has text\n if (!el.matches(SELECTOR_INTERACTIVE))\n warn('Missing tabindex=\"0\" attribute on: ', el);\n }\n\n // If an existing tooltip has changed programmatically, update tooltip text and announce change\n const isCurrent = el === SOURCE && TIP?.matches(':popover-open');\n const isChanged = isCurrent && text && TIP?.textContent !== text; // Only update if mutation is on source element and tooltip is open to avoid unnecessary updates\n if (isCurrent && isChanged) {\n if (TIP) setTextWithoutMutation(TIP, text);\n if (document.activeElement === el) announce(text); // Only announce if focus is on the button\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst handleInterest = ({ type, target }: Event) => {\n clearTimeout(HOVER_TIMER);\n\n if (target === TIP) return; // Allow tooltip to be hovered, following https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus\n if (type === 'mouseover' && !SOURCE) {\n HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target }); // Delay mouse showing tooltip if not already shown\n return;\n }\n\n const source = (target as Element)?.closest?.(`[${ATTR_TOOLTIP}]`);\n if (source === SOURCE) return; // No need to update\n if (!source) return hideTooltip(); // If no new anchor, cleanup previous autoUpdate\n if (!TIP) TIP = tag('div', { class: 'ds-tooltip' });\n if (!TIP.isConnected) document.body.appendChild(TIP); // Ensure connected\n\n const color = source.closest(SELECTOR_COLOR); // Match source color of source element\n const scheme = source.closest(SELECTOR_SCHEME); // Match source color-scheme of source element\n const isReset = color !== scheme && color?.contains(scheme as Node); // If data-scheme is closer to target, it will reset data-color\n clearTimeout(SKIP_TIMER);\n attr(TIP, 'popover', 'manual'); // Ensure popover behavior\n attr(TIP, ATTR_SCHEME, scheme?.getAttribute(ATTR_SCHEME) || null); // Fallback to null to reset if not scheme found\n attr(TIP, ATTR_COLOR, (isReset && color?.getAttribute(ATTR_COLOR)) || null); // Fallback to null to reset if not scheme found\n setTextWithoutMutation(TIP, attr(source, ATTR_TOOLTIP));\n TIP.showPopover();\n TIP.dispatchEvent(new CustomEvent('ds-toggle-source', { detail: source })); // Since showPopover({ source }) is not supported in all browsers yet\n SOURCE = source;\n};\n\nconst hideTooltip = () => TIP?.isConnected && TIP.popover && TIP.hidePopover(); // Only hide if connected and activated\n\nconst handleClose = (event?: Partial<ToggleEvent & KeyboardEvent>) => {\n if (event?.type === 'keydown')\n return event?.key === 'Escape' && hideTooltip();\n if (!event) SOURCE = undefined;\n else if (event.target === TIP && event.newState === 'closed')\n SKIP_TIMER = setTimeout(handleClose, DELAY_SKIP);\n};\n\nonHotReload('tooltip', () => [\n on(document, 'blur focus mouseover', handleInterest, QUICK_EVENT),\n on(document, 'toggle keydown', handleClose, QUICK_EVENT),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOOLTIP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"sCAcA,IAAI,EACA,EACA,EAAsD,EACtD,EAAqD,EACzD,MAAM,EAAe,eACf,EAAa,aACb,EAAa,aACb,EAAY,mBACZ,EAAiB,IAAI,EAAW,GAChC,EAAmB,IAAI,EAAa,GACpC,EAAc,oBACd,EAAkB,IAAI,EAAY,GAU3B,EAAqB,GAA4B,CACxD,GAAM,EAAE,aAAc,cACxB,EAAA,KAAK,kDAAmD,EAAG,CAC7D,EAAM,GAAM,IAAA,IAGR,EAAuBA,EAAAA,aAAe,CAC1C,IAAK,IAAM,KAAM,SAAS,iBAAiB,EAAiB,CAAE,CAC5D,IAAM,EAAO,EAAG,aAAa,EAAW,EAAI,EAAG,aAAa,EAAU,CAChE,EAAO,EAAG,aAAa,EAAa,EAAIC,EAAAA,UAAU,EAAI,EAAa,CAEzE,GAAI,IAAS,EAAM,CACjB,IAAM,EAAUC,EAAAA,KAAK,EAAI,OAAO,GAAK,OAAS,EAAG,aAAa,MAAM,CACpE,EAAA,KAAK,EAAI,EAAc,EAAK,CAC5B,EAAA,KAAK,EAAI,EAAY,EAAU,KAAO,EAAK,CAC3C,EAAA,KAAK,EAAI,EAAW,EAAU,EAAO,KAAK,CACrC,EAAG,QAAQ,kDAAqB,EACnC,EAAA,KAAK,sCAAuC,EAAG,CAInD,IAAM,EAAY,IAAO,GAAU,GAAK,QAAQ,gBAAgB,CAC1D,EAAY,GAAa,GAAQ,GAAK,cAAgB,EACxD,GAAa,IACX,GAAK,EAAA,uBAAuB,EAAK,EAAK,CACtC,SAAS,gBAAkB,GAAI,EAAA,SAAS,EAAK,IAGpD,EAAE,CAEC,GAAkB,CAAE,OAAM,YAAoB,CAGlD,GAFA,aAAa,EAAY,CAErB,IAAW,EAAK,OACpB,GAAI,IAAS,aAAe,CAAC,EAAQ,CACnC,EAAc,WAAW,EAAgB,IAAa,CAAE,SAAQ,CAAC,CACjE,OAGF,IAAM,EAAU,GAAoB,UAAU,IAAI,EAAa,GAAG,CAClE,GAAI,IAAW,EAAQ,OACvB,GAAI,CAAC,EAAQ,OAAO,GAAa,CACjC,AAAU,IAAMC,EAAAA,IAAI,MAAO,CAAE,MAAO,aAAc,CAAC,CAC9C,EAAI,aAAa,SAAS,KAAK,YAAY,EAAI,CAEpD,IAAM,EAAQ,EAAO,QAAQ,EAAe,CACtC,EAAS,EAAO,QAAQ,EAAgB,CACxC,EAAU,IAAU,GAAU,GAAO,SAAS,EAAe,CACnE,aAAa,EAAW,CACxB,EAAA,KAAK,EAAK,UAAW,SAAS,CAC9B,EAAA,KAAK,EAAK,EAAa,GAAQ,aAAa,EAAY,EAAI,KAAK,CACjE,EAAA,KAAK,EAAK,EAAa,GAAW,GAAO,aAAa,EAAW,EAAK,KAAK,CAC3E,EAAA,uBAAuB,EAAKD,EAAAA,KAAK,EAAQ,EAAa,CAAC,CACvD,EAAI,aAAa,CACjB,EAAI,cAAc,IAAI,YAAY,mBAAoB,CAAE,OAAQ,EAAQ,CAAC,CAAC,CAC1E,EAAS,GAGL,MAAoB,GAAK,aAAe,EAAI,SAAW,EAAI,aAAa,CAExE,EAAe,GAAiD,CACpE,GAAI,GAAO,OAAS,UAClB,OAAO,GAAO,MAAQ,UAAY,GAAa,CAC5C,EACI,EAAM,SAAW,GAAO,EAAM,WAAa,WAClD,EAAa,WAAW,EAAa,IAAW,EAFtC,EAAS,IAAA,IAKvBE,EAAAA,YAAY,cAAiB,CAC3BC,EAAAA,GAAG,SAAU,uBAAwB,EAAgBC,EAAAA,YAAY,CACjED,EAAAA,GAAG,SAAU,iBAAkB,EAAaC,EAAAA,YAAY,CACxDC,EAAAA,WAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAa,CAC/B,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
@@ -1,2 +1,2 @@
1
- const e={passive:!0,capture:!0},t=()=>typeof window<`u`&&typeof document<`u`,n=()=>t()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),r=typeof HTMLElement>`u`?class{}:HTMLElement;function i(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const a=(e,...t)=>typeof window>`u`||window.dsWarnings===!1||console.warn(`Designsystemet: ${e}`,...t),o=(e,t,n)=>n===void 0?e.getAttribute(t)??null:(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),s=/^["']|["']$/g,c=(e,t)=>{let n=o(e,t);return n||=getComputedStyle(e).getPropertyValue(`--_ds-${t}`).replace(s,``).trim()||null,n||a(`Missing ${t} on:`,e),n},l=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.addEventListener(t,...r);return()=>u(e,...t)},u=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.removeEventListener(t,...r)},d=(e,n)=>{t()&&(window._dsHotReloadCleanup||(window._dsHotReloadCleanup=new Map),window._dsHotReloadCleanup?.get(e)?.map(e=>e()),window._dsHotReloadCleanup?.set(e,n()))};let f=!1;const p=(e,t,n)=>{let r=0,i=()=>{if(!e.isConnected)return a();t(o),o.takeRecords(),r=0},a=()=>o?.disconnect?.(),o=new MutationObserver(()=>{!f&&!r&&(r=requestAnimationFrame(i))});return o.observe(e,n),requestAnimationFrame(i),a},m=(e,t)=>{f=!0,e.textContent=t,requestAnimationFrame(h)},h=()=>{f=!1},g=(e,t)=>{let n=document.createElement(e);if(t)for(let[e,r]of Object.entries(t))o(n,e,r);return n},_={define:(e,n)=>!t()||window.customElements.get(e)||window.customElements.define(e,n)};let v=0;const y=`${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`;function b(e){return e&&!e.id&&(e.id=`${y}${++v}`),e?.id||``}exports.DSElement=r,exports.QUICK_EVENT=e,exports.attr=o,exports.attrOrCSS=c,exports.customElements=_,exports.debounce=i,exports.isBrowser=t,exports.isWindows=n,exports.off=u,exports.on=l,exports.onHotReload=d,exports.onMutation=p,exports.setTextWithoutMutation=m,exports.tag=g,exports.useId=b,exports.warn=a;
1
+ const e={passive:!0,capture:!0},t=()=>typeof window<`u`&&typeof document<`u`,n=()=>t()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),r=typeof HTMLElement>`u`?class{}:HTMLElement;function i(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const a=(e,...t)=>typeof window>`u`||window.dsWarnings===!1||console.warn(`Designsystemet: ${e}`,...t),o=(e,t,n)=>n===void 0?e.getAttribute(t)??null:(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),s=/^["']|["']$/g,c=(e,t)=>{let n=o(e,t);return n||=getComputedStyle(e).getPropertyValue(`--_ds-${t}`).replace(s,``).trim()||null,n||a(`Missing ${t} on:`,e),n},l=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.addEventListener(t,...r);return()=>u(e,...t)},u=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.removeEventListener(t,...r)},d=(e,n)=>{t()&&(window._dsHotReloadCleanup||(window._dsHotReloadCleanup=new Map),window._dsHotReloadCleanup?.get(e)?.map(e=>e()),window._dsHotReloadCleanup?.set(e,n()))};let f=!1;const p=(e,t,n)=>{let r=0,i=()=>{if(!e.isConnected)return a();t(o),o.takeRecords(),r=0},a=()=>o?.disconnect?.(),o=new MutationObserver(()=>{!f&&!r&&(r=requestAnimationFrame(i))});return o.observe(e,n),requestAnimationFrame(i),a},m=(e,t)=>{f=!0,e.textContent=t,requestAnimationFrame(h)},h=()=>{f=!1},g=(e,t)=>{let n=document.createElement(e);if(t)for(let[e,r]of Object.entries(t))o(n,e,r);return n},_={define:(e,n)=>!t()||window.customElements.get(e)||window.customElements.define(e,n)};let v=0;const y=`${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`;function b(e){return e&&!e.id&&(e.id=`${y}${++v}`),e?.id||``}let x,S=0,C=0;const w=e=>{clearTimeout(C),x&&m(x,`${e}${S++%2?`\xA0`:``}`),e&&(C=setTimeout(w,2e3,``))},T=()=>{document.readyState===`complete`&&(x||(x=g(`div`,{"aria-live":`assertive`}),x.style.overflow=`hidden`,x.style.position=`fixed`,x.style.whiteSpace=`nowrap`,x.style.width=`1px`),x.isConnected||document.body.appendChild(x))};d(`announce`,()=>[l(document,`focus mouseover`,T,e)]),exports.DSElement=r,exports.QUICK_EVENT=e,exports.announce=w,exports.attr=o,exports.attrOrCSS=c,exports.customElements=_,exports.debounce=i,exports.isBrowser=t,exports.isWindows=n,exports.off=u,exports.on=l,exports.onHotReload=d,exports.onMutation=p,exports.setTextWithoutMutation=m,exports.tag=g,exports.useId=b,exports.warn=a;
2
2
  //# sourceMappingURL=utils.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.cjs","names":[],"sources":["../../../src/utils/utils.ts"],"sourcesContent":["export const QUICK_EVENT = { passive: true, capture: true };\n\n// Using function instead of constant to support evnironments where DOM can be unloaded (like Vitest with jsdom)\nexport const isBrowser = () =>\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\nexport const isWindows = () =>\n isBrowser() &&\n // @ts-expect-error Typescript has not implemented userAgentData yet https://stackoverflow.com/a/71392474\n /^Win/i.test(navigator.userAgentData?.platform || navigator.platform);\n\n// Make sure we have a HTMLElement to extend (for server side rendering)\nexport const DSElement =\n typeof HTMLElement === 'undefined'\n ? (class {} as typeof HTMLElement)\n : HTMLElement;\n\nexport function debounce<T extends unknown[]>(\n callback: (...args: T) => void,\n delay: number,\n) {\n let timer: ReturnType<typeof setTimeout>;\n\n return function (this: unknown, ...args: T) {\n clearTimeout(timer);\n timer = setTimeout(() => callback.apply(this, args), delay);\n };\n}\n\n/**\n * warn\n * @description Utility to console.warn, but can be silenced in production with window.dsWarnings = false;\n */\ndeclare global {\n interface Window {\n dsWarnings?: boolean;\n }\n}\nexport const warn = (\n message: string,\n ...args: Parameters<typeof console.warn>\n) =>\n typeof window === 'undefined' ||\n window.dsWarnings === false ||\n console.warn(`Designsystemet: ${message}`, ...args);\n\n/**\n * attr\n * @description Utility to quickly get, set and remove attributes\n * @param el The Element to read/write attributes from\n * @param name The attribute name to get, set or remove, or a object to set multiple attributes\n * @param value A valid attribute value or null to remove attribute\n */\nexport const attr = (\n el: Element,\n name: string,\n value?: string | null,\n): string | null => {\n if (value === undefined) return el.getAttribute(name) ?? null; // Fallback to null only if el is undefined\n if (value === null) el.removeAttribute(name);\n else if (el.getAttribute(name) !== value) el.setAttribute(name, value);\n return null;\n};\n\n/**\n * attrOrCSS\n * @description Retrieves and updates attribute based on attribute or CSS property value\n * @param el The Element to read attributes/CSS from\n * @param name Attribute or CSS property to get\n * @return string attribute or CSS property value\n */\nconst STRIP_SURROUNDING_QUOTES = /^[\"']|[\"']$/g; // Matches surrounding single or double quotes\nexport const attrOrCSS = (el: Element, name: string) => {\n let value = attr(el, name);\n if (!value) {\n const prop = getComputedStyle(el).getPropertyValue(`--_ds-${name}`);\n value = prop.replace(STRIP_SURROUNDING_QUOTES, '').trim() || null;\n }\n if (!value) warn(`Missing ${name} on:`, el);\n return value;\n};\n\n/**\n * on\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const on = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.addEventListener>\n): (() => void) => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.addEventListener(type, ...options);\n return () => off(el, ...rest);\n};\n\n/**\n * off\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const off = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.removeEventListener>\n): void => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.removeEventListener(type, ...options);\n};\n\n// Used to store cleanup functions for hot-reloading\ndeclare global {\n interface Window {\n _dsHotReloadCleanup?: Map<string, Array<() => void>>;\n }\n}\n\n/**\n * onHotReload\n * @description Runs a callback when window is loaded in browser, and ensures cleanup when hot-reloading\n * @param key The key to identify setup and corresponding cleanup\n * @param callback The callback to run when the page is ready\n */\nexport const onHotReload = (key: string, setup: () => Array<() => void>) => {\n if (!isBrowser()) return; // Skip if not in modern browser environment, but on each call as Vitest might have unloaded jsdom between tests\n if (!window._dsHotReloadCleanup) window._dsHotReloadCleanup = new Map(); // Hot reload cleanup support supporting all build tools\n\n window._dsHotReloadCleanup?.get(key)?.map((cleanup) => cleanup()); // Run previous cleanup\n window._dsHotReloadCleanup?.set(key, setup()); // Store new cleanup\n};\n\n/**\n * Speed up MutationObserver by debouncing and only running when page is visible\n * @return new MutaionObserver\n */\nlet SKIP_MUTATIONS = false;\nexport const onMutation = (\n el: Node,\n callback: (observer: MutationObserver) => void,\n options: MutationObserverInit,\n) => {\n let queue = 0;\n const onFrame = () => {\n if (!el.isConnected) return cleanup(); // Stop observing if element is removed from DOM\n callback(observer);\n observer.takeRecords(); // Clear records in case mutations happened during callback\n queue = 0;\n };\n const cleanup = () => observer?.disconnect?.();\n const observer = new MutationObserver(() => {\n if (!SKIP_MUTATIONS && !queue) queue = requestAnimationFrame(onFrame); // requestAnimationFrame only runs when page is visible\n });\n\n observer.observe(el, options);\n requestAnimationFrame(onFrame); // Initial run when page is visible and children has mounted\n return cleanup;\n};\n\n/**\n * Many mutation observers need to watch childNodes, thus running on all `textContent` changes\n * This utility allows skipping mutation observers while updating textContent\n */\nexport const setTextWithoutMutation = (el: Element, text: string | null) => {\n SKIP_MUTATIONS = true;\n el.textContent = text;\n requestAnimationFrame(enableMutations); // Let all mutationobservers run before enabling again\n};\nconst enableMutations = () => {\n SKIP_MUTATIONS = false;\n};\n\n/**\n * tag\n * @description creates element and assigns properties\n * @param tagName The tagname of element to create\n * @param attrs Optional attributes to add to the element\n * @param text Optional text content to add to the element\n * @return HTMLElement with props\n */\nexport const tag = <TagName extends keyof HTMLElementTagNameMap>(\n tagName: TagName,\n attrs?: Record<string, string | null> | null,\n): HTMLElementTagNameMap[TagName] => {\n const el = document.createElement(tagName);\n if (attrs) for (const [key, val] of Object.entries(attrs)) attr(el, key, val);\n return el;\n};\n\n/**\n * customElements.define\n * @description Defines a customElement if running in browser and if not already registered\n * Scoped/named \"customElements.define\" so @custom-elements-manifest/analyzer can find tag names\n */\nexport const customElements = {\n define: (name: string, instance: CustomElementConstructor) =>\n !isBrowser() ||\n window.customElements.get(name) ||\n window.customElements.define(name, instance),\n};\n\n/**\n * useId\n * @return A generated unique ID\n */\nlet id = 0;\nconst hash = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;\nexport function useId(el?: Element | null) {\n if (el && !el.id) el.id = `${hash}${++id}`;\n return el?.id || '';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.cjs","names":[],"sources":["../../../src/utils/utils.ts"],"sourcesContent":["export const QUICK_EVENT = { passive: true, capture: true };\n\n// Using function instead of constant to support evnironments where DOM can be unloaded (like Vitest with jsdom)\nexport const isBrowser = () =>\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\nexport const isWindows = () =>\n isBrowser() &&\n // @ts-expect-error Typescript has not implemented userAgentData yet https://stackoverflow.com/a/71392474\n /^Win/i.test(navigator.userAgentData?.platform || navigator.platform);\n\n// Make sure we have a HTMLElement to extend (for server side rendering)\nexport const DSElement =\n typeof HTMLElement === 'undefined'\n ? (class {} as typeof HTMLElement)\n : HTMLElement;\n\nexport function debounce<T extends unknown[]>(\n callback: (...args: T) => void,\n delay: number,\n) {\n let timer: ReturnType<typeof setTimeout>;\n\n return function (this: unknown, ...args: T) {\n clearTimeout(timer);\n timer = setTimeout(() => callback.apply(this, args), delay);\n };\n}\n\n/**\n * warn\n * @description Utility to console.warn, but can be silenced in production with window.dsWarnings = false;\n */\ndeclare global {\n interface Window {\n dsWarnings?: boolean;\n }\n}\nexport const warn = (\n message: string,\n ...args: Parameters<typeof console.warn>\n) =>\n typeof window === 'undefined' ||\n window.dsWarnings === false ||\n console.warn(`Designsystemet: ${message}`, ...args);\n\n/**\n * attr\n * @description Utility to quickly get, set and remove attributes\n * @param el The Element to read/write attributes from\n * @param name The attribute name to get, set or remove, or a object to set multiple attributes\n * @param value A valid attribute value or null to remove attribute\n */\nexport const attr = (\n el: Element,\n name: string,\n value?: string | null,\n): string | null => {\n if (value === undefined) return el.getAttribute(name) ?? null; // Fallback to null only if el is undefined\n if (value === null) el.removeAttribute(name);\n else if (el.getAttribute(name) !== value) el.setAttribute(name, value);\n return null;\n};\n\nconst STRIP_SURROUNDING_QUOTES = /^[\"']|[\"']$/g; // Matches surrounding single or double quotes\n/**\n * attrOrCSS\n * @description Retrieves and updates attribute based on attribute or CSS property value\n * @param el The Element to read attributes/CSS from\n * @param name Attribute or CSS property to get\n * @return string attribute or CSS property value\n */\nexport const attrOrCSS = (el: Element, name: string) => {\n let value = attr(el, name);\n if (!value) {\n const prop = getComputedStyle(el).getPropertyValue(`--_ds-${name}`);\n value = prop.replace(STRIP_SURROUNDING_QUOTES, '').trim() || null;\n }\n if (!value) warn(`Missing ${name} on:`, el);\n return value;\n};\n\n/**\n * on\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const on = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.addEventListener>\n): (() => void) => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.addEventListener(type, ...options);\n return () => off(el, ...rest);\n};\n\n/**\n * off\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const off = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.removeEventListener>\n): void => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.removeEventListener(type, ...options);\n};\n\n// Used to store cleanup functions for hot-reloading\ndeclare global {\n interface Window {\n _dsHotReloadCleanup?: Map<string, Array<() => void>>;\n }\n}\n\n/**\n * onHotReload\n * @description Runs a callback when window is loaded in browser, and ensures cleanup when hot-reloading\n * @param key The key to identify setup and corresponding cleanup\n * @param callback The callback to run when the page is ready\n */\nexport const onHotReload = (key: string, setup: () => Array<() => void>) => {\n if (!isBrowser()) return; // Skip if not in modern browser environment, but on each call as Vitest might have unloaded jsdom between tests\n if (!window._dsHotReloadCleanup) window._dsHotReloadCleanup = new Map(); // Hot reload cleanup support supporting all build tools\n\n window._dsHotReloadCleanup?.get(key)?.map((cleanup) => cleanup()); // Run previous cleanup\n window._dsHotReloadCleanup?.set(key, setup()); // Store new cleanup\n};\n\n/**\n * Speed up MutationObserver by debouncing and only running when page is visible\n * @return new MutaionObserver\n */\nlet SKIP_MUTATIONS = false;\nexport const onMutation = (\n el: Node,\n callback: (observer: MutationObserver) => void,\n options: MutationObserverInit,\n) => {\n let queue = 0;\n const onFrame = () => {\n if (!el.isConnected) return cleanup(); // Stop observing if element is removed from DOM\n callback(observer);\n observer.takeRecords(); // Clear records in case mutations happened during callback\n queue = 0;\n };\n const cleanup = () => observer?.disconnect?.();\n const observer = new MutationObserver(() => {\n if (!SKIP_MUTATIONS && !queue) queue = requestAnimationFrame(onFrame); // requestAnimationFrame only runs when page is visible\n });\n\n observer.observe(el, options);\n requestAnimationFrame(onFrame); // Initial run when page is visible and children has mounted\n return cleanup;\n};\n\n/**\n * Many mutation observers need to watch childNodes, thus running on all `textContent` changes\n * This utility allows skipping mutation observers while updating textContent\n */\nexport const setTextWithoutMutation = (el: Element, text: string | null) => {\n SKIP_MUTATIONS = true;\n el.textContent = text;\n requestAnimationFrame(enableMutations); // Let all mutationobservers run before enabling again\n};\nconst enableMutations = () => {\n SKIP_MUTATIONS = false;\n};\n\n/**\n * tag\n * @description creates element and assigns properties\n * @param tagName The tagname of element to create\n * @param attrs Optional attributes to add to the element\n * @param text Optional text content to add to the element\n * @return HTMLElement with props\n */\nexport const tag = <TagName extends keyof HTMLElementTagNameMap>(\n tagName: TagName,\n attrs?: Record<string, string | null> | null,\n): HTMLElementTagNameMap[TagName] => {\n const el = document.createElement(tagName);\n if (attrs) for (const [key, val] of Object.entries(attrs)) attr(el, key, val);\n return el;\n};\n\n/**\n * customElements.define\n * @description Defines a customElement if running in browser and if not already registered\n * Scoped/named \"customElements.define\" so @custom-elements-manifest/analyzer can find tag names\n */\nexport const customElements = {\n define: (name: string, instance: CustomElementConstructor) =>\n !isBrowser() ||\n window.customElements.get(name) ||\n window.customElements.define(name, instance),\n};\n\n/**\n * useId\n * @return A generated unique ID\n */\nlet id = 0;\nconst hash = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;\nexport function useId(el?: Element | null) {\n if (el && !el.id) el.id = `${hash}${++id}`;\n return el?.id || '';\n}\n\n/**\n * @description Based off speak function from [U-elements](https://github.com/u-elements/u-elements/blob/main/packages/utils.ts#L210)\n * @param text The text to announce\n */\nlet LIVE_EL: HTMLElement | undefined;\nlet LIVE_FIX = 0;\nlet LIVE_CLEAR: ReturnType<typeof setTimeout> | number = 0;\nexport const announce = (text: string) => {\n clearTimeout(LIVE_CLEAR);\n if (LIVE_EL)\n setTextWithoutMutation(LIVE_EL, `${text}${LIVE_FIX++ % 2 ? '\\u00A0' : ''}`); // Non-breaking space to ensure screen reader announces\n if (text) LIVE_CLEAR = setTimeout(announce, 2000, ''); // Clear prevent old announcements being found by screen readers, with 2 seconds brace period to avoid cutting of Android Talkback\n};\n\n// Mount live region on first focus so its ready to be used\nconst announceMount = () => {\n if (document.readyState !== 'complete') return; // Ensure page is loaded trying to avoid issues with React hydration\n if (!LIVE_EL) {\n LIVE_EL = tag('div', { 'aria-live': 'assertive' });\n LIVE_EL.style.overflow = 'hidden'; // Settings styles individually to prevent issues with CSP\n LIVE_EL.style.position = 'fixed';\n LIVE_EL.style.whiteSpace = 'nowrap';\n LIVE_EL.style.width = '1px';\n }\n if (!LIVE_EL.isConnected) document.body.appendChild(LIVE_EL);\n};\nonHotReload('announce', () => [\n on(document, 'focus mouseover', announceMount, QUICK_EVENT),\n]);\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{DSElement as e,QUICK_EVENT as t,attr as n,attrOrCSS as r,customElements as i,debounce as a,isBrowser as o,isWindows as s,on as c,onHotReload as l,onMutation as u,setTextWithoutMutation as d,tag as f,useId as p,warn as m}from"../utils/utils.js";const h=`data-indeterminate`,g=new Set,_=new WeakMap,v=o()?document.getElementsByTagName(`fieldset`):[],y=o()&&CSS.supports(`field-sizing`,`content`),b=s()?800:200,x=new WeakSet,S=a(()=>{for(let e of v)n(e,`aria-labelledby`,`${p(e.querySelector(`legend`))} ${p(e.querySelector(`:scope > :is([data-field="description"],legend + p)`))}`.trim()||null);for(let e of g){let t=[],r=[],i,a,o=!1,s=!1;for(let n of e.getElementsByTagName(`*`))if(n instanceof HTMLLabelElement&&r.push(n),!n.hidden)if(D(n))i?m(`Fields should only have one input element. Use <fieldset> to group multiple fields:`,e):i=n;else{let e=n.getAttribute(`data-field`);e===`counter`&&(a=n),e===`validation`?(t.unshift(n),o=!0,s||=E(n)):e&&t.push(n)}if(!i)m(`Field is missing input element:`,e);else{a&&_.set(i,a);for(let e of r)n(e,`for`,p(i));let c=i.type===`radio`||i.type===`checkbox`,l=e.closest(`fieldset`)?.querySelector(`:scope > [data-field="validation"]`);l&&!l?.hidden&&(o=!0,s||=E(l),t.unshift(l));let u=n(i,h);u&&(i.indeterminate=u===`true`),n(e,`data-clickdelegatefor`,c?p(i):null),n(i,`aria-describedby`,t.map(p).join(` `)||null),(o||x.has(i))&&(x[o?`add`:`delete`](i),n(i,`aria-invalid`,`${s}`)),w(i)}}},0),C=o()?f(`div`,{"aria-live":`polite`,style:`position:fixed;white-space:nowrap;clip:rect(0 0 0 0)`}):null,w=e=>{let t=e.target||e,i=_.get(t);if(i?.isConnected){let a=(Number(n(i,`data-limit`))||0)-t.value.length,o=a<0?`over`:`under`,s=r(i,`data-${o}`)?.replace(`%d`,`${Math.abs(a)}`);n(i,`data-label`,s),n(i,`data-state`,o),n(i,`data-color`,a<0?`danger`:null),e.type===`input`&&C&&s&&(C?.isConnected||document.body.appendChild(C),T(t,s))}!y&&t instanceof HTMLTextAreaElement&&(t.style.setProperty(`--_ds-field-sizing`,`auto`),t.style.setProperty(`--_ds-field-sizing`,`${t.scrollHeight}px`))},T=a((e,t)=>{let n=document.activeElement===e;C?.isConnected&&n&&d(C,t)},b),E=e=>e.getAttribute(`data-color`)!==`success`,D=e=>e instanceof HTMLElement&&`validity`in e&&!(e instanceof HTMLButtonElement)&&e.type!==`hidden`;var O=class extends e{connectedCallback(){g.add(this),S()}disconnectedCallback(){g.delete(this)}};i.define(`ds-field`,O),l(`field`,()=>[c(document,`input`,w,t),u(document,S,{attributeFilter:[`data-field`,`data-limit`,`hidden`,`value`,h],attributes:!0,childList:!0,subtree:!0})]);export{O as DSFieldElement};
1
+ import{DSElement as e,QUICK_EVENT as t,announce as n,attr as r,attrOrCSS as i,customElements as a,debounce as o,isBrowser as s,isWindows as c,on as l,onHotReload as u,onMutation as d,useId as f,warn as p}from"../utils/utils.js";const m=`data-indeterminate`,h=new Set,g=new WeakMap,_=s()?document.getElementsByTagName(`fieldset`):[],v=s()&&CSS.supports(`field-sizing`,`content`),y=c()?800:200,b=new WeakSet,x=o(()=>{for(let e of _)r(e,`aria-labelledby`,`${f(e.querySelector(`legend`))} ${f(e.querySelector(`:scope > :is([data-field="description"],legend + p)`))}`.trim()||null);for(let e of h){let t=[],n=[],i,a,o=!1,s=!1;for(let r of e.getElementsByTagName(`*`))if(r instanceof HTMLLabelElement&&n.push(r),!r.hidden)if(T(r))i?p(`Fields should only have one input element. Use <fieldset> to group multiple fields:`,e):i=r;else{let e=r.getAttribute(`data-field`);e===`counter`&&(a=r),e===`validation`?(t.unshift(r),o=!0,s||=w(r)):e&&t.push(r)}if(!i)p(`Field is missing input element:`,e);else{a&&g.set(i,a);for(let e of n)r(e,`for`,f(i));let c=i.type===`radio`||i.type===`checkbox`,l=e.closest(`fieldset`)?.querySelector(`:scope > [data-field="validation"]`);l&&!l?.hidden&&(o=!0,s||=w(l),t.unshift(l));let u=r(i,m);u&&(i.indeterminate=u===`true`),r(e,`data-clickdelegatefor`,c?f(i):null),r(i,`aria-describedby`,t.map(f).join(` `)||null),(o||b.has(i))&&(b[o?`add`:`delete`](i),r(i,`aria-invalid`,`${s}`)),S(i)}}},0),S=e=>{let t=e.target||e,n=g.get(t);if(n?.isConnected){let a=(Number(r(n,`data-limit`))||0)-t.value.length,o=a<0?`over`:`under`,s=i(n,`data-${o}`)?.replace(`%d`,`${Math.abs(a)}`);r(n,`data-label`,s),r(n,`data-state`,o),r(n,`data-color`,a<0?`danger`:null),e.type===`input`&&s&&C(t,s)}!v&&t instanceof HTMLTextAreaElement&&(t.style.setProperty(`--_ds-field-sizing`,`auto`),t.style.setProperty(`--_ds-field-sizing`,`${t.scrollHeight}px`))},C=o((e,t)=>{document.activeElement===e&&n(t)},y),w=e=>e.getAttribute(`data-color`)!==`success`,T=e=>e instanceof HTMLElement&&`validity`in e&&!(e instanceof HTMLButtonElement)&&e.type!==`hidden`;var E=class extends e{connectedCallback(){h.add(this),x()}disconnectedCallback(){h.delete(this)}};a.define(`ds-field`,E),u(`field`,()=>[l(document,`input`,S,t),d(document,x,{attributeFilter:[`data-field`,`data-limit`,`hidden`,`value`,m],attributes:!0,childList:!0,subtree:!0})]);export{E as DSFieldElement};
2
2
  //# sourceMappingURL=field.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"field.js","names":[],"sources":["../../../src/field/field.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n customElements,\n DSElement,\n debounce,\n isBrowser,\n isWindows,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n setTextWithoutMutation,\n tag,\n useId,\n warn,\n} from '../utils/utils';\n\n// TODO: Document that Validation must be hidden with \"hidden\" attribute (or completely removed from DOM), not display: none\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ds-field': DSFieldElement;\n }\n}\n\nconst INDETERMINATE = 'data-indeterminate';\nconst FIELDS = new Set<DSFieldElement>(); // Set of Field\nconst COUNTS = new WeakMap<HTMLInputElement, Element>(); // Using WeakMap so removed inputs/counts does not cause memory leaks\nconst FIELDSETS = isBrowser() ? document.getElementsByTagName('fieldset') : [];\nconst HAS_FIELD_SIZING = isBrowser() && CSS.supports('field-sizing', 'content');\nconst COUNTER_DEBOUNCE = isWindows() ? 800 : 200; // Longer debounce on Windows due to NVDA performance\nconst HAS_VALIDATION = new WeakSet<HTMLInputElement>(); // Used to store inputs that have/had validation elements to manage aria-invalid\n\nconst handleMutations = debounce(() => {\n for (const el of FIELDSETS) {\n const labelledby = `${useId(el.querySelector('legend'))} ${useId(el.querySelector(':scope > :is([data-field=\"description\"],legend + p)'))}`;\n attr(el, 'aria-labelledby', labelledby.trim() || null);\n }\n for (const field of FIELDS) {\n const descs: Element[] = [];\n const labels: HTMLLabelElement[] = [];\n let input: HTMLInputElement | undefined;\n let counter: Element | undefined;\n let hasValidation = false;\n let invalid = false;\n\n for (const el of field.getElementsByTagName('*')) {\n if (el instanceof HTMLLabelElement) labels.push(el);\n if ((el as HTMLElement).hidden) continue; // Skip hidden elements except labels\n if (isInputLike(el)) {\n if (input)\n warn(\n `Fields should only have one input element. Use <fieldset> to group multiple fields:`,\n field,\n );\n else input = el; // Only register if visible input\n } else {\n const type = el.getAttribute('data-field'); // Using getAttribute instead of attr for best performance\n if (type === 'counter') counter = el;\n if (type === 'validation') {\n descs.unshift(el);\n hasValidation = true;\n invalid = invalid || isInvalid(el);\n } else if (type) descs.push(el); // Adds both counter and descriptions\n }\n }\n\n if (!input) warn(`Field is missing input element:`, field);\n else {\n if (counter) COUNTS.set(input, counter);\n for (const label of labels) attr(label, 'for', useId(input));\n\n const isBoolish = input.type === 'radio' || input.type === 'checkbox';\n const fieldsetValidation = field\n .closest('fieldset')\n ?.querySelector<HTMLElement>(':scope > [data-field=\"validation\"]');\n if (fieldsetValidation && !fieldsetValidation?.hidden) {\n hasValidation = true;\n invalid = invalid || isInvalid(fieldsetValidation);\n descs.unshift(fieldsetValidation);\n }\n\n const indeterminate = attr(input, INDETERMINATE);\n if (indeterminate) input.indeterminate = indeterminate === 'true';\n\n attr(field, 'data-clickdelegatefor', isBoolish ? useId(input) : null); // Expand click area to ds-field if radio/checkbox\n attr(input, 'aria-describedby', descs.map(useId).join(' ') || null);\n if (hasValidation || HAS_VALIDATION.has(input)) {\n HAS_VALIDATION[hasValidation ? 'add' : 'delete'](input); // Track if field has validation elements to avoid managing aria-invalid on every mutation\n attr(input, 'aria-invalid', `${invalid}`); // Only manage aria-invalid when field has validation elements\n }\n updateField(input); // Update counter and textarea sizing\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst SR_ONLY = 'position:fixed;white-space:nowrap;clip:rect(0 0 0 0)';\nconst SR_LIVE = isBrowser()\n ? tag('div', { 'aria-live': 'polite', style: SR_ONLY })\n : null;\n\nconst updateField = (e: Event | Element) => {\n const input = ((e as Event).target || e) as HTMLInputElement;\n const counter = COUNTS.get(input);\n\n if (counter?.isConnected) {\n const limit = Number(attr(counter, 'data-limit')) || 0;\n const count = limit - input.value.length;\n const state = count < 0 ? 'over' : 'under';\n const label = attrOrCSS(counter, `data-${state}`)?.replace(\n '%d',\n `${Math.abs(count)}`,\n );\n\n attr(counter, 'data-label', label); // Using attribute to prevent hydation errors, not using aria-label to make axe tests happy\n attr(counter, 'data-state', state);\n attr(counter, 'data-color', count < 0 ? 'danger' : null);\n\n // Only update live region when user is actually typing\n if ((e as Event).type === 'input' && SR_LIVE && label) {\n if (!SR_LIVE?.isConnected) document.body.appendChild(SR_LIVE); // Prepare live region\n debouncedCounterLiveRegion(input, label); // Debounce live region to avoid NVDA interupting announcing typed text\n }\n }\n if (!HAS_FIELD_SIZING && input instanceof HTMLTextAreaElement) {\n input.style.setProperty('--_ds-field-sizing', 'auto');\n input.style.setProperty('--_ds-field-sizing', `${input.scrollHeight}px`);\n }\n};\n\nconst debouncedCounterLiveRegion = debounce((input: Element, text: string) => {\n const hasFocus = document.activeElement === input; // Only announce if input is still focused\n if (SR_LIVE?.isConnected && hasFocus) setTextWithoutMutation(SR_LIVE, text);\n}, COUNTER_DEBOUNCE);\n\nconst isInvalid = (el: Element) => el.getAttribute('data-color') !== 'success';\nconst isInputLike = (el: unknown): el is HTMLInputElement =>\n el instanceof HTMLElement &&\n 'validity' in el && // Adds support for custom elements implemeted with attachInternals()\n !(el instanceof HTMLButtonElement) && // But skip <button> elements\n (el as HTMLInputElement).type !== 'hidden'; // And skip input type=\"hidden\"\n\n// Custom element is used to performantly keep track of fields on the page\nexport class DSFieldElement extends DSElement {\n connectedCallback() {\n FIELDS.add(this); // Register field\n handleMutations(); // Initial setup\n }\n disconnectedCallback() {\n FIELDS.delete(this);\n }\n}\n\ncustomElements.define('ds-field', DSFieldElement);\n\n// Listen for hidden to detect hidden validations, and listen for value to detect controlled React inputs\nonHotReload('field', () => [\n on(document, 'input', updateField, QUICK_EVENT),\n onMutation(document, handleMutations, {\n attributeFilter: [\n 'data-field',\n 'data-limit',\n 'hidden',\n 'value',\n INDETERMINATE,\n ],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"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"}
1
+ {"version":3,"file":"field.js","names":[],"sources":["../../../src/field/field.ts"],"sourcesContent":["import {\n announce,\n attr,\n attrOrCSS,\n customElements,\n DSElement,\n debounce,\n isBrowser,\n isWindows,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n useId,\n warn,\n} from '../utils/utils';\n\n// TODO: Document that Validation must be hidden with \"hidden\" attribute (or completely removed from DOM), not display: none\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ds-field': DSFieldElement;\n }\n}\n\nconst INDETERMINATE = 'data-indeterminate';\nconst FIELDS = new Set<DSFieldElement>(); // Set of Field\nconst COUNTS = new WeakMap<HTMLInputElement, Element>(); // Using WeakMap so removed inputs/counts does not cause memory leaks\nconst FIELDSETS = isBrowser() ? document.getElementsByTagName('fieldset') : [];\nconst HAS_FIELD_SIZING = isBrowser() && CSS.supports('field-sizing', 'content');\nconst COUNTER_DEBOUNCE = isWindows() ? 800 : 200; // Longer debounce on Windows due to NVDA performance\nconst HAS_VALIDATION = new WeakSet<HTMLInputElement>(); // Used to store inputs that have/had validation elements to manage aria-invalid\n\nconst handleMutations = debounce(() => {\n for (const el of FIELDSETS) {\n const labelledby = `${useId(el.querySelector('legend'))} ${useId(el.querySelector(':scope > :is([data-field=\"description\"],legend + p)'))}`;\n attr(el, 'aria-labelledby', labelledby.trim() || null);\n }\n for (const field of FIELDS) {\n const descs: Element[] = [];\n const labels: HTMLLabelElement[] = [];\n let input: HTMLInputElement | undefined;\n let counter: Element | undefined;\n let hasValidation = false;\n let invalid = false;\n\n for (const el of field.getElementsByTagName('*')) {\n if (el instanceof HTMLLabelElement) labels.push(el);\n if ((el as HTMLElement).hidden) continue; // Skip hidden elements except labels\n if (isInputLike(el)) {\n if (input)\n warn(\n `Fields should only have one input element. Use <fieldset> to group multiple fields:`,\n field,\n );\n else input = el; // Only register if visible input\n } else {\n const type = el.getAttribute('data-field'); // Using getAttribute instead of attr for best performance\n if (type === 'counter') counter = el;\n if (type === 'validation') {\n descs.unshift(el);\n hasValidation = true;\n invalid = invalid || isInvalid(el);\n } else if (type) descs.push(el); // Adds both counter and descriptions\n }\n }\n\n if (!input) warn(`Field is missing input element:`, field);\n else {\n if (counter) COUNTS.set(input, counter);\n for (const label of labels) attr(label, 'for', useId(input));\n\n const isBoolish = input.type === 'radio' || input.type === 'checkbox';\n const fieldsetValidation = field\n .closest('fieldset')\n ?.querySelector<HTMLElement>(':scope > [data-field=\"validation\"]');\n if (fieldsetValidation && !fieldsetValidation?.hidden) {\n hasValidation = true;\n invalid = invalid || isInvalid(fieldsetValidation);\n descs.unshift(fieldsetValidation);\n }\n\n const indeterminate = attr(input, INDETERMINATE);\n if (indeterminate) input.indeterminate = indeterminate === 'true';\n\n attr(field, 'data-clickdelegatefor', isBoolish ? useId(input) : null); // Expand click area to ds-field if radio/checkbox\n attr(input, 'aria-describedby', descs.map(useId).join(' ') || null);\n if (hasValidation || HAS_VALIDATION.has(input)) {\n HAS_VALIDATION[hasValidation ? 'add' : 'delete'](input); // Track if field has validation elements to avoid managing aria-invalid on every mutation\n attr(input, 'aria-invalid', `${invalid}`); // Only manage aria-invalid when field has validation elements\n }\n updateField(input); // Update counter and textarea sizing\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst updateField = (e: Event | Element) => {\n const input = ((e as Event).target || e) as HTMLInputElement;\n const counter = COUNTS.get(input);\n\n if (counter?.isConnected) {\n const limit = Number(attr(counter, 'data-limit')) || 0;\n const count = limit - input.value.length;\n const state = count < 0 ? 'over' : 'under';\n const label = attrOrCSS(counter, `data-${state}`)?.replace(\n '%d',\n `${Math.abs(count)}`,\n );\n\n attr(counter, 'data-label', label); // Using attribute to prevent hydation errors, not using aria-label to make axe tests happy\n attr(counter, 'data-state', state);\n attr(counter, 'data-color', count < 0 ? 'danger' : null);\n\n // Only update live region when user is actually typing\n if ((e as Event).type === 'input' && label)\n debouncedCounterLiveRegion(input, label); // Debounce live region to avoid NVDA interupting announcing typed text\n }\n if (!HAS_FIELD_SIZING && input instanceof HTMLTextAreaElement) {\n input.style.setProperty('--_ds-field-sizing', 'auto');\n input.style.setProperty('--_ds-field-sizing', `${input.scrollHeight}px`);\n }\n};\n\nconst debouncedCounterLiveRegion = debounce((input: Element, text: string) => {\n if (document.activeElement === input) announce(text); // Only announce if input is still focused\n}, COUNTER_DEBOUNCE);\n\nconst isInvalid = (el: Element) => el.getAttribute('data-color') !== 'success';\nconst isInputLike = (el: unknown): el is HTMLInputElement =>\n el instanceof HTMLElement &&\n 'validity' in el && // Adds support for custom elements implemeted with attachInternals()\n !(el instanceof HTMLButtonElement) && // But skip <button> elements\n (el as HTMLInputElement).type !== 'hidden'; // And skip input type=\"hidden\"\n\n// Custom element is used to performantly keep track of fields on the page\nexport class DSFieldElement extends DSElement {\n connectedCallback() {\n FIELDS.add(this); // Register field\n handleMutations(); // Initial setup\n }\n disconnectedCallback() {\n FIELDS.delete(this);\n }\n}\n\ncustomElements.define('ds-field', DSFieldElement);\n\n// Listen for hidden to detect hidden validations, and listen for value to detect controlled React inputs\nonHotReload('field', () => [\n on(document, 'input', updateField, QUICK_EVENT),\n onMutation(document, handleMutations, {\n attributeFilter: [\n 'data-field',\n 'data-limit',\n 'hidden',\n 'value',\n INDETERMINATE,\n ],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{attr as e,attrOrCSS as t,debounce as n,on as r,onHotReload as i,onMutation as a}from"../utils/utils.js";const o=`data-toggle-group`,s=`[${o}]`,c=n(()=>{for(let n of document.querySelectorAll(s))e(n,`aria-label`,t(n,o))},0),l=e=>{let t=e.target instanceof HTMLInputElement&&e.target.closest(s);if(t&&(e.key===`Enter`&&e.target.click(),e.key?.startsWith(`Arrow`))){e.preventDefault?.();let n=t.getElementsByTagName(`input`),r=[...n].indexOf(e.target),i=e.key.match(/Arrow(Right|Down)/)?1:-1;n[(n.length+r+i)%n.length]?.focus()}};i(`toggle-group`,()=>[r(document,`keydown`,l),a(document,c,{attributeFilter:[o],attributes:!0,childList:!0,subtree:!0})]);
1
+ import{attr as e,attrOrCSS as t,debounce as n,on as r,onHotReload as i,onMutation as a}from"../utils/utils.js";const o=`data-toggle-group`,s=`[${o}]`,c=n(()=>{for(let n of document.querySelectorAll(s))e(n,`aria-label`,t(n,o))},0),l=e=>{let t=e.target instanceof HTMLInputElement&&e.target.closest(s);if(t&&(e.key===`Enter`&&e.target.click(),e.key?.startsWith(`Arrow`))){e.preventDefault?.();let n=[...t.getElementsByTagName(`input`)],r=n.indexOf(e.target),i=e.key.match(/Arrow(Right|Down)/)?1:-1,a=r;for(let e=0;e<n.length;e++)if(a=(n.length+a+i)%n.length,!n[a]?.disabled){n[a]?.focus();break}}};i(`toggle-group`,()=>[r(document,`keydown`,l),a(document,c,{attributeFilter:[o],attributes:!0,childList:!0,subtree:!0})]);
2
2
  //# sourceMappingURL=toggle-group.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"toggle-group.js","names":[],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n} from '../utils/utils';\n\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\n\nconst handleAriaAttributes = debounce(() => {\n for (const group of document.querySelectorAll(SELECTOR_TOGGLEGROUP))\n attr(group, 'aria-label', attrOrCSS(group, ATTR_TOGGLEGROUP));\n}, 0); // Debounce to merge multiple mutations\n\nconst handleKeydown = (event: Partial<KeyboardEvent>) => {\n const group =\n event.target instanceof HTMLInputElement &&\n event.target.closest(SELECTOR_TOGGLEGROUP);\n\n if (!group) return;\n if (event.key === 'Enter') event.target.click(); // Forward Enter, but no need to listen for space key, as this is handled by the browser\n if (event.key?.startsWith('Arrow')) {\n event.preventDefault?.();\n const inputs = group.getElementsByTagName('input');\n const index = [...inputs].indexOf(event.target);\n const move = event.key.match(/Arrow(Right|Down)/) ? 1 : -1;\n inputs[(inputs.length + index + move) % inputs.length]?.focus();\n }\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"+GASA,MAAM,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAE5C,EAAuB,MAAe,CAC1C,IAAK,IAAM,KAAS,SAAS,iBAAiB,EAAqB,CACjE,EAAK,EAAO,aAAc,EAAU,EAAO,EAAiB,CAAC,EAC9D,EAAE,CAEC,EAAiB,GAAkC,CACvD,IAAM,EACJ,EAAM,kBAAkB,kBACxB,EAAM,OAAO,QAAQ,EAAqB,CAEvC,OACD,EAAM,MAAQ,SAAS,EAAM,OAAO,OAAO,CAC3C,EAAM,KAAK,WAAW,QAAQ,EAAE,CAClC,EAAM,kBAAkB,CACxB,IAAM,EAAS,EAAM,qBAAqB,QAAQ,CAC5C,EAAQ,CAAC,GAAG,EAAO,CAAC,QAAQ,EAAM,OAAO,CACzC,EAAO,EAAM,IAAI,MAAM,oBAAoB,CAAG,EAAI,GACxD,GAAQ,EAAO,OAAS,EAAQ,GAAQ,EAAO,SAAS,OAAO,GAInE,EAAY,mBAAsB,CAChC,EAAG,SAAU,UAAW,EAAc,CACtC,EAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAiB,CACnC,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
1
+ {"version":3,"file":"toggle-group.js","names":[],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n} from '../utils/utils';\n\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\n\nconst handleAriaAttributes = debounce(() => {\n for (const group of document.querySelectorAll(SELECTOR_TOGGLEGROUP))\n attr(group, 'aria-label', attrOrCSS(group, ATTR_TOGGLEGROUP));\n}, 0); // Debounce to merge multiple mutations\n\nconst handleKeydown = (event: Partial<KeyboardEvent>) => {\n const group =\n event.target instanceof HTMLInputElement &&\n event.target.closest(SELECTOR_TOGGLEGROUP);\n\n if (!group) return;\n if (event.key === 'Enter') event.target.click(); // Forward Enter, but no need to listen for space key, as this is handled by the browser\n if (event.key?.startsWith('Arrow')) {\n event.preventDefault?.();\n const inputs = [...group.getElementsByTagName('input')];\n const index = inputs.indexOf(event.target);\n const move = event.key.match(/Arrow(Right|Down)/) ? 1 : -1;\n let nextIndex = index;\n\n for (let i = 0; i < inputs.length; i++) {\n nextIndex = (inputs.length + nextIndex + move) % inputs.length;\n if (!inputs[nextIndex]?.disabled) {\n inputs[nextIndex]?.focus();\n break;\n }\n }\n }\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"+GASA,MAAM,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAE5C,EAAuB,MAAe,CAC1C,IAAK,IAAM,KAAS,SAAS,iBAAiB,EAAqB,CACjE,EAAK,EAAO,aAAc,EAAU,EAAO,EAAiB,CAAC,EAC9D,EAAE,CAEC,EAAiB,GAAkC,CACvD,IAAM,EACJ,EAAM,kBAAkB,kBACxB,EAAM,OAAO,QAAQ,EAAqB,CAEvC,OACD,EAAM,MAAQ,SAAS,EAAM,OAAO,OAAO,CAC3C,EAAM,KAAK,WAAW,QAAQ,EAAE,CAClC,EAAM,kBAAkB,CACxB,IAAM,EAAS,CAAC,GAAG,EAAM,qBAAqB,QAAQ,CAAC,CACjD,EAAQ,EAAO,QAAQ,EAAM,OAAO,CACpC,EAAO,EAAM,IAAI,MAAM,oBAAoB,CAAG,EAAI,GACpD,EAAY,EAEhB,IAAK,IAAI,EAAI,EAAG,EAAI,EAAO,OAAQ,IAEjC,GADA,GAAa,EAAO,OAAS,EAAY,GAAQ,EAAO,OACpD,CAAC,EAAO,IAAY,SAAU,CAChC,EAAO,IAAY,OAAO,CAC1B,SAMR,EAAY,mBAAsB,CAChC,EAAG,SAAU,UAAW,EAAc,CACtC,EAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAiB,CACnC,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
@@ -1,2 +1,2 @@
1
- import{QUICK_EVENT as e,attr as t,attrOrCSS as n,debounce as r,on as i,onHotReload as a,onMutation as o,setTextWithoutMutation as s,tag as c,warn as l}from"../utils/utils.js";let u,d,f=0,p=0;const m=`data-tooltip`,h=`data-color`,g=`aria-label`,_=`aria-description`,v=`[${h}]`,y=`[${m}]`,b=`data-color-scheme`,x=`[${b}]`,S=e=>{e&&!(e instanceof HTMLElement)&&l(`setTooltipElement expects an HTMLElement, got: `,e),u=e||void 0},C=r(()=>{for(let e of document.querySelectorAll(y)){let r=e.getAttribute(g)||e.getAttribute(_),i=e.getAttribute(m)||n(e,m);if(r!==i){let n=t(e,`role`)!==`img`&&e.textContent?.trim();t(e,m,i),t(e,g,n?null:i),t(e,_,n?i:null),e.matches(`a,button,input,label,select,textarea,[tabindex]`)||l(`Missing tabindex="0" attribute on: `,e)}}},0),w=({type:e,target:n})=>{if(clearTimeout(f),n===u)return;if(e===`mouseover`&&!d){f=setTimeout(w,300,{target:n});return}let r=n?.closest?.(`[${m}]`);if(r===d)return;if(!r)return T();u||=c(`div`,{class:`ds-tooltip`}),u.isConnected||document.body.appendChild(u);let i=r.closest(v),a=r.closest(x),o=i!==a&&i?.contains(a);clearTimeout(p),t(u,`popover`,`manual`),t(u,b,a?.getAttribute(b)||null),t(u,h,o&&i?.getAttribute(h)||null),s(u,t(r,m)),u.showPopover(),u.dispatchEvent(new CustomEvent(`ds-toggle-source`,{detail:r})),d=r},T=()=>u?.isConnected&&u.popover&&u.hidePopover(),E=e=>{if(e?.type===`keydown`)return e?.key===`Escape`&&T();e?e.target===u&&e.newState===`closed`&&(p=setTimeout(E,300)):d=void 0};a(`tooltip`,()=>[i(document,`blur focus mouseover`,w,e),i(document,`toggle keydown`,E,e),o(document,C,{attributeFilter:[m],attributes:!0,childList:!0,subtree:!0})]);export{S as setTooltipElement};
1
+ import{QUICK_EVENT as e,announce as t,attr as n,attrOrCSS as r,debounce as i,on as a,onHotReload as o,onMutation as s,setTextWithoutMutation as c,tag as l,warn as u}from"../utils/utils.js";let d,f,p=0,m=0;const h=`data-tooltip`,g=`data-color`,_=`aria-label`,v=`aria-description`,y=`[${g}]`,b=`[${h}]`,x=`data-color-scheme`,S=`[${x}]`,C=e=>{e&&!(e instanceof HTMLElement)&&u(`setTooltipElement expects an HTMLElement, got: `,e),d=e||void 0},w=i(()=>{for(let e of document.querySelectorAll(b)){let i=e.getAttribute(_)||e.getAttribute(v),a=e.getAttribute(h)||r(e,h);if(i!==a){let t=n(e,`role`)!==`img`&&e.textContent?.trim();n(e,h,a),n(e,_,t?null:a),n(e,v,t?a:null),e.matches(`a,button,input,label,select,textarea,[tabindex]`)||u(`Missing tabindex="0" attribute on: `,e)}let o=e===f&&d?.matches(`:popover-open`),s=o&&a&&d?.textContent!==a;o&&s&&(d&&c(d,a),document.activeElement===e&&t(a))}},0),T=({type:e,target:t})=>{if(clearTimeout(p),t===d)return;if(e===`mouseover`&&!f){p=setTimeout(T,300,{target:t});return}let r=t?.closest?.(`[${h}]`);if(r===f)return;if(!r)return E();d||=l(`div`,{class:`ds-tooltip`}),d.isConnected||document.body.appendChild(d);let i=r.closest(y),a=r.closest(S),o=i!==a&&i?.contains(a);clearTimeout(m),n(d,`popover`,`manual`),n(d,x,a?.getAttribute(x)||null),n(d,g,o&&i?.getAttribute(g)||null),c(d,n(r,h)),d.showPopover(),d.dispatchEvent(new CustomEvent(`ds-toggle-source`,{detail:r})),f=r},E=()=>d?.isConnected&&d.popover&&d.hidePopover(),D=e=>{if(e?.type===`keydown`)return e?.key===`Escape`&&E();e?e.target===d&&e.newState===`closed`&&(m=setTimeout(D,300)):f=void 0};o(`tooltip`,()=>[a(document,`blur focus mouseover`,T,e),a(document,`toggle keydown`,D,e),s(document,w,{attributeFilter:[h],attributes:!0,childList:!0,subtree:!0})]);export{C as setTooltipElement};
2
2
  //# sourceMappingURL=tooltip.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/tooltip/tooltip.ts"],"sourcesContent":["import {\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n setTextWithoutMutation,\n tag,\n warn,\n} from '../utils/utils';\n\nlet TIP: HTMLElement | undefined;\nlet SOURCE: Element | undefined;\nlet HOVER_TIMER: number | ReturnType<typeof setTimeout> = 0;\nlet SKIP_TIMER: number | ReturnType<typeof setTimeout> = 0;\nconst ATTR_TOOLTIP = 'data-tooltip';\nconst ATTR_COLOR = 'data-color';\nconst ARIA_LABEL = 'aria-label';\nconst ARIA_DESC = 'aria-description';\nconst SELECTOR_COLOR = `[${ATTR_COLOR}]`;\nconst SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;\nconst ATTR_SCHEME = 'data-color-scheme';\nconst SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;\nconst SELECTOR_INTERACTIVE = 'a,button,input,label,select,textarea,[tabindex]';\nconst DELAY_HOVER = 300;\nconst DELAY_SKIP = 300;\n\n/**\n * setTooltipElement\n * @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.\n * @param el The HTMLElement to use as tooltip\n */\nexport const setTooltipElement = (el?: HTMLElement | null) => {\n if (el && !(el instanceof HTMLElement))\n warn('setTooltipElement expects an HTMLElement, got: ', el);\n TIP = el || undefined;\n};\n\nconst handleAriaAttributes = debounce(() => {\n for (const el of document.querySelectorAll(SELECTOR_TOOLTIP)) {\n const aria = el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC); // Using getAttribute for best performance\n const text = el.getAttribute(ATTR_TOOLTIP) || attrOrCSS(el, ATTR_TOOLTIP); // Only parse CSS if attribute is empty for better performance\n\n if (aria !== text) {\n const hasText = attr(el, 'role') !== 'img' && el.textContent?.trim(); // If role=\"img\", ignore text\n attr(el, ATTR_TOOLTIP, text); // Set data-tooltip attribute to speed up future mutations\n attr(el, ARIA_LABEL, hasText ? null : text); // Set aria-label if element does not have text\n attr(el, ARIA_DESC, hasText ? text : null); // Set aria-description if element has text\n if (!el.matches(SELECTOR_INTERACTIVE))\n warn('Missing tabindex=\"0\" attribute on: ', el);\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst handleInterest = ({ type, target }: Event) => {\n clearTimeout(HOVER_TIMER);\n\n if (target === TIP) return; // Allow tooltip to be hovered, following https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus\n if (type === 'mouseover' && !SOURCE) {\n HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target }); // Delay mouse showing tooltip if not already shown\n return;\n }\n\n const source = (target as Element)?.closest?.(`[${ATTR_TOOLTIP}]`);\n if (source === SOURCE) return; // No need to update\n if (!source) return hideTooltip(); // If no new anchor, cleanup previous autoUpdate\n if (!TIP) TIP = tag('div', { class: 'ds-tooltip' });\n if (!TIP.isConnected) document.body.appendChild(TIP); // Ensure connected\n\n const color = source.closest(SELECTOR_COLOR); // Match source color of source element\n const scheme = source.closest(SELECTOR_SCHEME); // Match source color-scheme of source element\n const isReset = color !== scheme && color?.contains(scheme as Node); // If data-scheme is closer to target, it will reset data-color\n clearTimeout(SKIP_TIMER);\n attr(TIP, 'popover', 'manual'); // Ensure popover behavior\n attr(TIP, ATTR_SCHEME, scheme?.getAttribute(ATTR_SCHEME) || null); // Fallback to null to reset if not scheme found\n attr(TIP, ATTR_COLOR, (isReset && color?.getAttribute(ATTR_COLOR)) || null); // Fallback to null to reset if not scheme found\n setTextWithoutMutation(TIP, attr(source, ATTR_TOOLTIP));\n TIP.showPopover();\n TIP.dispatchEvent(new CustomEvent('ds-toggle-source', { detail: source })); // Since showPopover({ source }) is not supported in all browsers yet\n SOURCE = source;\n};\n\nconst hideTooltip = () => TIP?.isConnected && TIP.popover && TIP.hidePopover(); // Only hide if connected and activated\n\nconst handleClose = (event?: Partial<ToggleEvent & KeyboardEvent>) => {\n if (event?.type === 'keydown')\n return event?.key === 'Escape' && hideTooltip();\n if (!event) SOURCE = undefined;\n else if (event.target === TIP && event.newState === 'closed')\n SKIP_TIMER = setTimeout(handleClose, DELAY_SKIP);\n};\n\nonHotReload('tooltip', () => [\n on(document, 'blur focus mouseover', handleInterest, QUICK_EVENT),\n on(document, 'toggle keydown', handleClose, QUICK_EVENT),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOOLTIP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"+KAaA,IAAI,EACA,EACA,EAAsD,EACtD,EAAqD,EACzD,MAAM,EAAe,eACf,EAAa,aACb,EAAa,aACb,EAAY,mBACZ,EAAiB,IAAI,EAAW,GAChC,EAAmB,IAAI,EAAa,GACpC,EAAc,oBACd,EAAkB,IAAI,EAAY,GAU3B,EAAqB,GAA4B,CACxD,GAAM,EAAE,aAAc,cACxB,EAAK,kDAAmD,EAAG,CAC7D,EAAM,GAAM,IAAA,IAGR,EAAuB,MAAe,CAC1C,IAAK,IAAM,KAAM,SAAS,iBAAiB,EAAiB,CAAE,CAC5D,IAAM,EAAO,EAAG,aAAa,EAAW,EAAI,EAAG,aAAa,EAAU,CAChE,EAAO,EAAG,aAAa,EAAa,EAAI,EAAU,EAAI,EAAa,CAEzE,GAAI,IAAS,EAAM,CACjB,IAAM,EAAU,EAAK,EAAI,OAAO,GAAK,OAAS,EAAG,aAAa,MAAM,CACpE,EAAK,EAAI,EAAc,EAAK,CAC5B,EAAK,EAAI,EAAY,EAAU,KAAO,EAAK,CAC3C,EAAK,EAAI,EAAW,EAAU,EAAO,KAAK,CACrC,EAAG,QAAQ,kDAAqB,EACnC,EAAK,sCAAuC,EAAG,IAGpD,EAAE,CAEC,GAAkB,CAAE,OAAM,YAAoB,CAGlD,GAFA,aAAa,EAAY,CAErB,IAAW,EAAK,OACpB,GAAI,IAAS,aAAe,CAAC,EAAQ,CACnC,EAAc,WAAW,EAAgB,IAAa,CAAE,SAAQ,CAAC,CACjE,OAGF,IAAM,EAAU,GAAoB,UAAU,IAAI,EAAa,GAAG,CAClE,GAAI,IAAW,EAAQ,OACvB,GAAI,CAAC,EAAQ,OAAO,GAAa,CACjC,AAAU,IAAM,EAAI,MAAO,CAAE,MAAO,aAAc,CAAC,CAC9C,EAAI,aAAa,SAAS,KAAK,YAAY,EAAI,CAEpD,IAAM,EAAQ,EAAO,QAAQ,EAAe,CACtC,EAAS,EAAO,QAAQ,EAAgB,CACxC,EAAU,IAAU,GAAU,GAAO,SAAS,EAAe,CACnE,aAAa,EAAW,CACxB,EAAK,EAAK,UAAW,SAAS,CAC9B,EAAK,EAAK,EAAa,GAAQ,aAAa,EAAY,EAAI,KAAK,CACjE,EAAK,EAAK,EAAa,GAAW,GAAO,aAAa,EAAW,EAAK,KAAK,CAC3E,EAAuB,EAAK,EAAK,EAAQ,EAAa,CAAC,CACvD,EAAI,aAAa,CACjB,EAAI,cAAc,IAAI,YAAY,mBAAoB,CAAE,OAAQ,EAAQ,CAAC,CAAC,CAC1E,EAAS,GAGL,MAAoB,GAAK,aAAe,EAAI,SAAW,EAAI,aAAa,CAExE,EAAe,GAAiD,CACpE,GAAI,GAAO,OAAS,UAClB,OAAO,GAAO,MAAQ,UAAY,GAAa,CAC5C,EACI,EAAM,SAAW,GAAO,EAAM,WAAa,WAClD,EAAa,WAAW,EAAa,IAAW,EAFtC,EAAS,IAAA,IAKvB,EAAY,cAAiB,CAC3B,EAAG,SAAU,uBAAwB,EAAgB,EAAY,CACjE,EAAG,SAAU,iBAAkB,EAAa,EAAY,CACxD,EAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAa,CAC/B,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
1
+ {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/tooltip/tooltip.ts"],"sourcesContent":["import {\n announce,\n attr,\n attrOrCSS,\n debounce,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n setTextWithoutMutation,\n tag,\n warn,\n} from '../utils/utils';\n\nlet TIP: HTMLElement | undefined;\nlet SOURCE: Element | undefined;\nlet HOVER_TIMER: number | ReturnType<typeof setTimeout> = 0;\nlet SKIP_TIMER: number | ReturnType<typeof setTimeout> = 0;\nconst ATTR_TOOLTIP = 'data-tooltip';\nconst ATTR_COLOR = 'data-color';\nconst ARIA_LABEL = 'aria-label';\nconst ARIA_DESC = 'aria-description';\nconst SELECTOR_COLOR = `[${ATTR_COLOR}]`;\nconst SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;\nconst ATTR_SCHEME = 'data-color-scheme';\nconst SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;\nconst SELECTOR_INTERACTIVE = 'a,button,input,label,select,textarea,[tabindex]';\nconst DELAY_HOVER = 300;\nconst DELAY_SKIP = 300;\n\n/**\n * setTooltipElement\n * @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.\n * @param el The HTMLElement to use as tooltip\n */\nexport const setTooltipElement = (el?: HTMLElement | null) => {\n if (el && !(el instanceof HTMLElement))\n warn('setTooltipElement expects an HTMLElement, got: ', el);\n TIP = el || undefined;\n};\n\nconst handleAriaAttributes = debounce(() => {\n for (const el of document.querySelectorAll(SELECTOR_TOOLTIP)) {\n const aria = el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC); // Using getAttribute for best performance\n const text = el.getAttribute(ATTR_TOOLTIP) || attrOrCSS(el, ATTR_TOOLTIP); // Only parse CSS if attribute is empty for better performance\n\n if (aria !== text) {\n const hasText = attr(el, 'role') !== 'img' && el.textContent?.trim(); // If role=\"img\", ignore text\n attr(el, ATTR_TOOLTIP, text); // Set data-tooltip attribute to speed up future mutations\n attr(el, ARIA_LABEL, hasText ? null : text); // Set aria-label if element does not have text\n attr(el, ARIA_DESC, hasText ? text : null); // Set aria-description if element has text\n if (!el.matches(SELECTOR_INTERACTIVE))\n warn('Missing tabindex=\"0\" attribute on: ', el);\n }\n\n // If an existing tooltip has changed programmatically, update tooltip text and announce change\n const isCurrent = el === SOURCE && TIP?.matches(':popover-open');\n const isChanged = isCurrent && text && TIP?.textContent !== text; // Only update if mutation is on source element and tooltip is open to avoid unnecessary updates\n if (isCurrent && isChanged) {\n if (TIP) setTextWithoutMutation(TIP, text);\n if (document.activeElement === el) announce(text); // Only announce if focus is on the button\n }\n }\n}, 0); // Debounce to merge multiple mutations\n\nconst handleInterest = ({ type, target }: Event) => {\n clearTimeout(HOVER_TIMER);\n\n if (target === TIP) return; // Allow tooltip to be hovered, following https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus\n if (type === 'mouseover' && !SOURCE) {\n HOVER_TIMER = setTimeout(handleInterest, DELAY_HOVER, { target }); // Delay mouse showing tooltip if not already shown\n return;\n }\n\n const source = (target as Element)?.closest?.(`[${ATTR_TOOLTIP}]`);\n if (source === SOURCE) return; // No need to update\n if (!source) return hideTooltip(); // If no new anchor, cleanup previous autoUpdate\n if (!TIP) TIP = tag('div', { class: 'ds-tooltip' });\n if (!TIP.isConnected) document.body.appendChild(TIP); // Ensure connected\n\n const color = source.closest(SELECTOR_COLOR); // Match source color of source element\n const scheme = source.closest(SELECTOR_SCHEME); // Match source color-scheme of source element\n const isReset = color !== scheme && color?.contains(scheme as Node); // If data-scheme is closer to target, it will reset data-color\n clearTimeout(SKIP_TIMER);\n attr(TIP, 'popover', 'manual'); // Ensure popover behavior\n attr(TIP, ATTR_SCHEME, scheme?.getAttribute(ATTR_SCHEME) || null); // Fallback to null to reset if not scheme found\n attr(TIP, ATTR_COLOR, (isReset && color?.getAttribute(ATTR_COLOR)) || null); // Fallback to null to reset if not scheme found\n setTextWithoutMutation(TIP, attr(source, ATTR_TOOLTIP));\n TIP.showPopover();\n TIP.dispatchEvent(new CustomEvent('ds-toggle-source', { detail: source })); // Since showPopover({ source }) is not supported in all browsers yet\n SOURCE = source;\n};\n\nconst hideTooltip = () => TIP?.isConnected && TIP.popover && TIP.hidePopover(); // Only hide if connected and activated\n\nconst handleClose = (event?: Partial<ToggleEvent & KeyboardEvent>) => {\n if (event?.type === 'keydown')\n return event?.key === 'Escape' && hideTooltip();\n if (!event) SOURCE = undefined;\n else if (event.target === TIP && event.newState === 'closed')\n SKIP_TIMER = setTimeout(handleClose, DELAY_SKIP);\n};\n\nonHotReload('tooltip', () => [\n on(document, 'blur focus mouseover', handleInterest, QUICK_EVENT),\n on(document, 'toggle keydown', handleClose, QUICK_EVENT),\n onMutation(document, handleAriaAttributes, {\n attributeFilter: [ATTR_TOOLTIP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"6LAcA,IAAI,EACA,EACA,EAAsD,EACtD,EAAqD,EACzD,MAAM,EAAe,eACf,EAAa,aACb,EAAa,aACb,EAAY,mBACZ,EAAiB,IAAI,EAAW,GAChC,EAAmB,IAAI,EAAa,GACpC,EAAc,oBACd,EAAkB,IAAI,EAAY,GAU3B,EAAqB,GAA4B,CACxD,GAAM,EAAE,aAAc,cACxB,EAAK,kDAAmD,EAAG,CAC7D,EAAM,GAAM,IAAA,IAGR,EAAuB,MAAe,CAC1C,IAAK,IAAM,KAAM,SAAS,iBAAiB,EAAiB,CAAE,CAC5D,IAAM,EAAO,EAAG,aAAa,EAAW,EAAI,EAAG,aAAa,EAAU,CAChE,EAAO,EAAG,aAAa,EAAa,EAAI,EAAU,EAAI,EAAa,CAEzE,GAAI,IAAS,EAAM,CACjB,IAAM,EAAU,EAAK,EAAI,OAAO,GAAK,OAAS,EAAG,aAAa,MAAM,CACpE,EAAK,EAAI,EAAc,EAAK,CAC5B,EAAK,EAAI,EAAY,EAAU,KAAO,EAAK,CAC3C,EAAK,EAAI,EAAW,EAAU,EAAO,KAAK,CACrC,EAAG,QAAQ,kDAAqB,EACnC,EAAK,sCAAuC,EAAG,CAInD,IAAM,EAAY,IAAO,GAAU,GAAK,QAAQ,gBAAgB,CAC1D,EAAY,GAAa,GAAQ,GAAK,cAAgB,EACxD,GAAa,IACX,GAAK,EAAuB,EAAK,EAAK,CACtC,SAAS,gBAAkB,GAAI,EAAS,EAAK,IAGpD,EAAE,CAEC,GAAkB,CAAE,OAAM,YAAoB,CAGlD,GAFA,aAAa,EAAY,CAErB,IAAW,EAAK,OACpB,GAAI,IAAS,aAAe,CAAC,EAAQ,CACnC,EAAc,WAAW,EAAgB,IAAa,CAAE,SAAQ,CAAC,CACjE,OAGF,IAAM,EAAU,GAAoB,UAAU,IAAI,EAAa,GAAG,CAClE,GAAI,IAAW,EAAQ,OACvB,GAAI,CAAC,EAAQ,OAAO,GAAa,CACjC,AAAU,IAAM,EAAI,MAAO,CAAE,MAAO,aAAc,CAAC,CAC9C,EAAI,aAAa,SAAS,KAAK,YAAY,EAAI,CAEpD,IAAM,EAAQ,EAAO,QAAQ,EAAe,CACtC,EAAS,EAAO,QAAQ,EAAgB,CACxC,EAAU,IAAU,GAAU,GAAO,SAAS,EAAe,CACnE,aAAa,EAAW,CACxB,EAAK,EAAK,UAAW,SAAS,CAC9B,EAAK,EAAK,EAAa,GAAQ,aAAa,EAAY,EAAI,KAAK,CACjE,EAAK,EAAK,EAAa,GAAW,GAAO,aAAa,EAAW,EAAK,KAAK,CAC3E,EAAuB,EAAK,EAAK,EAAQ,EAAa,CAAC,CACvD,EAAI,aAAa,CACjB,EAAI,cAAc,IAAI,YAAY,mBAAoB,CAAE,OAAQ,EAAQ,CAAC,CAAC,CAC1E,EAAS,GAGL,MAAoB,GAAK,aAAe,EAAI,SAAW,EAAI,aAAa,CAExE,EAAe,GAAiD,CACpE,GAAI,GAAO,OAAS,UAClB,OAAO,GAAO,MAAQ,UAAY,GAAa,CAC5C,EACI,EAAM,SAAW,GAAO,EAAM,WAAa,WAClD,EAAa,WAAW,EAAa,IAAW,EAFtC,EAAS,IAAA,IAKvB,EAAY,cAAiB,CAC3B,EAAG,SAAU,uBAAwB,EAAgB,EAAY,CACjE,EAAG,SAAU,iBAAkB,EAAa,EAAY,CACxD,EAAW,SAAU,EAAsB,CACzC,gBAAiB,CAAC,EAAa,CAC/B,WAAY,GACZ,UAAW,GACX,QAAS,GACV,CAAC,CACH,CAAC"}
@@ -1,2 +1,2 @@
1
- const e={passive:!0,capture:!0},t=()=>typeof window<`u`&&typeof document<`u`,n=()=>t()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),r=typeof HTMLElement>`u`?class{}:HTMLElement;function i(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const a=(e,...t)=>typeof window>`u`||window.dsWarnings===!1||console.warn(`Designsystemet: ${e}`,...t),o=(e,t,n)=>n===void 0?e.getAttribute(t)??null:(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),s=/^["']|["']$/g,c=(e,t)=>{let n=o(e,t);return n||=getComputedStyle(e).getPropertyValue(`--_ds-${t}`).replace(s,``).trim()||null,n||a(`Missing ${t} on:`,e),n},l=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.addEventListener(t,...r);return()=>u(e,...t)},u=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.removeEventListener(t,...r)},d=(e,n)=>{t()&&(window._dsHotReloadCleanup||(window._dsHotReloadCleanup=new Map),window._dsHotReloadCleanup?.get(e)?.map(e=>e()),window._dsHotReloadCleanup?.set(e,n()))};let f=!1;const p=(e,t,n)=>{let r=0,i=()=>{if(!e.isConnected)return a();t(o),o.takeRecords(),r=0},a=()=>o?.disconnect?.(),o=new MutationObserver(()=>{!f&&!r&&(r=requestAnimationFrame(i))});return o.observe(e,n),requestAnimationFrame(i),a},m=(e,t)=>{f=!0,e.textContent=t,requestAnimationFrame(h)},h=()=>{f=!1},g=(e,t)=>{let n=document.createElement(e);if(t)for(let[e,r]of Object.entries(t))o(n,e,r);return n},_={define:(e,n)=>!t()||window.customElements.get(e)||window.customElements.define(e,n)};let v=0;const y=`${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`;function b(e){return e&&!e.id&&(e.id=`${y}${++v}`),e?.id||``}export{r as DSElement,e as QUICK_EVENT,o as attr,c as attrOrCSS,_ as customElements,i as debounce,t as isBrowser,n as isWindows,u as off,l as on,d as onHotReload,p as onMutation,m as setTextWithoutMutation,g as tag,b as useId,a as warn};
1
+ const e={passive:!0,capture:!0},t=()=>typeof window<`u`&&typeof document<`u`,n=()=>t()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),r=typeof HTMLElement>`u`?class{}:HTMLElement;function i(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const a=(e,...t)=>typeof window>`u`||window.dsWarnings===!1||console.warn(`Designsystemet: ${e}`,...t),o=(e,t,n)=>n===void 0?e.getAttribute(t)??null:(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),s=/^["']|["']$/g,c=(e,t)=>{let n=o(e,t);return n||=getComputedStyle(e).getPropertyValue(`--_ds-${t}`).replace(s,``).trim()||null,n||a(`Missing ${t} on:`,e),n},l=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.addEventListener(t,...r);return()=>u(e,...t)},u=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.removeEventListener(t,...r)},d=(e,n)=>{t()&&(window._dsHotReloadCleanup||(window._dsHotReloadCleanup=new Map),window._dsHotReloadCleanup?.get(e)?.map(e=>e()),window._dsHotReloadCleanup?.set(e,n()))};let f=!1;const p=(e,t,n)=>{let r=0,i=()=>{if(!e.isConnected)return a();t(o),o.takeRecords(),r=0},a=()=>o?.disconnect?.(),o=new MutationObserver(()=>{!f&&!r&&(r=requestAnimationFrame(i))});return o.observe(e,n),requestAnimationFrame(i),a},m=(e,t)=>{f=!0,e.textContent=t,requestAnimationFrame(h)},h=()=>{f=!1},g=(e,t)=>{let n=document.createElement(e);if(t)for(let[e,r]of Object.entries(t))o(n,e,r);return n},_={define:(e,n)=>!t()||window.customElements.get(e)||window.customElements.define(e,n)};let v=0;const y=`${Date.now().toString(36)}${Math.random().toString(36).slice(2,5)}`;function b(e){return e&&!e.id&&(e.id=`${y}${++v}`),e?.id||``}let x,S=0,C=0;const w=e=>{clearTimeout(C),x&&m(x,`${e}${S++%2?`\xA0`:``}`),e&&(C=setTimeout(w,2e3,``))},T=()=>{document.readyState===`complete`&&(x||(x=g(`div`,{"aria-live":`assertive`}),x.style.overflow=`hidden`,x.style.position=`fixed`,x.style.whiteSpace=`nowrap`,x.style.width=`1px`),x.isConnected||document.body.appendChild(x))};d(`announce`,()=>[l(document,`focus mouseover`,T,e)]);export{r as DSElement,e as QUICK_EVENT,w as announce,o as attr,c as attrOrCSS,_ as customElements,i as debounce,t as isBrowser,n as isWindows,u as off,l as on,d as onHotReload,p as onMutation,m as setTextWithoutMutation,g as tag,b as useId,a as warn};
2
2
  //# sourceMappingURL=utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../src/utils/utils.ts"],"sourcesContent":["export const QUICK_EVENT = { passive: true, capture: true };\n\n// Using function instead of constant to support evnironments where DOM can be unloaded (like Vitest with jsdom)\nexport const isBrowser = () =>\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\nexport const isWindows = () =>\n isBrowser() &&\n // @ts-expect-error Typescript has not implemented userAgentData yet https://stackoverflow.com/a/71392474\n /^Win/i.test(navigator.userAgentData?.platform || navigator.platform);\n\n// Make sure we have a HTMLElement to extend (for server side rendering)\nexport const DSElement =\n typeof HTMLElement === 'undefined'\n ? (class {} as typeof HTMLElement)\n : HTMLElement;\n\nexport function debounce<T extends unknown[]>(\n callback: (...args: T) => void,\n delay: number,\n) {\n let timer: ReturnType<typeof setTimeout>;\n\n return function (this: unknown, ...args: T) {\n clearTimeout(timer);\n timer = setTimeout(() => callback.apply(this, args), delay);\n };\n}\n\n/**\n * warn\n * @description Utility to console.warn, but can be silenced in production with window.dsWarnings = false;\n */\ndeclare global {\n interface Window {\n dsWarnings?: boolean;\n }\n}\nexport const warn = (\n message: string,\n ...args: Parameters<typeof console.warn>\n) =>\n typeof window === 'undefined' ||\n window.dsWarnings === false ||\n console.warn(`Designsystemet: ${message}`, ...args);\n\n/**\n * attr\n * @description Utility to quickly get, set and remove attributes\n * @param el The Element to read/write attributes from\n * @param name The attribute name to get, set or remove, or a object to set multiple attributes\n * @param value A valid attribute value or null to remove attribute\n */\nexport const attr = (\n el: Element,\n name: string,\n value?: string | null,\n): string | null => {\n if (value === undefined) return el.getAttribute(name) ?? null; // Fallback to null only if el is undefined\n if (value === null) el.removeAttribute(name);\n else if (el.getAttribute(name) !== value) el.setAttribute(name, value);\n return null;\n};\n\n/**\n * attrOrCSS\n * @description Retrieves and updates attribute based on attribute or CSS property value\n * @param el The Element to read attributes/CSS from\n * @param name Attribute or CSS property to get\n * @return string attribute or CSS property value\n */\nconst STRIP_SURROUNDING_QUOTES = /^[\"']|[\"']$/g; // Matches surrounding single or double quotes\nexport const attrOrCSS = (el: Element, name: string) => {\n let value = attr(el, name);\n if (!value) {\n const prop = getComputedStyle(el).getPropertyValue(`--_ds-${name}`);\n value = prop.replace(STRIP_SURROUNDING_QUOTES, '').trim() || null;\n }\n if (!value) warn(`Missing ${name} on:`, el);\n return value;\n};\n\n/**\n * on\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const on = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.addEventListener>\n): (() => void) => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.addEventListener(type, ...options);\n return () => off(el, ...rest);\n};\n\n/**\n * off\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const off = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.removeEventListener>\n): void => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.removeEventListener(type, ...options);\n};\n\n// Used to store cleanup functions for hot-reloading\ndeclare global {\n interface Window {\n _dsHotReloadCleanup?: Map<string, Array<() => void>>;\n }\n}\n\n/**\n * onHotReload\n * @description Runs a callback when window is loaded in browser, and ensures cleanup when hot-reloading\n * @param key The key to identify setup and corresponding cleanup\n * @param callback The callback to run when the page is ready\n */\nexport const onHotReload = (key: string, setup: () => Array<() => void>) => {\n if (!isBrowser()) return; // Skip if not in modern browser environment, but on each call as Vitest might have unloaded jsdom between tests\n if (!window._dsHotReloadCleanup) window._dsHotReloadCleanup = new Map(); // Hot reload cleanup support supporting all build tools\n\n window._dsHotReloadCleanup?.get(key)?.map((cleanup) => cleanup()); // Run previous cleanup\n window._dsHotReloadCleanup?.set(key, setup()); // Store new cleanup\n};\n\n/**\n * Speed up MutationObserver by debouncing and only running when page is visible\n * @return new MutaionObserver\n */\nlet SKIP_MUTATIONS = false;\nexport const onMutation = (\n el: Node,\n callback: (observer: MutationObserver) => void,\n options: MutationObserverInit,\n) => {\n let queue = 0;\n const onFrame = () => {\n if (!el.isConnected) return cleanup(); // Stop observing if element is removed from DOM\n callback(observer);\n observer.takeRecords(); // Clear records in case mutations happened during callback\n queue = 0;\n };\n const cleanup = () => observer?.disconnect?.();\n const observer = new MutationObserver(() => {\n if (!SKIP_MUTATIONS && !queue) queue = requestAnimationFrame(onFrame); // requestAnimationFrame only runs when page is visible\n });\n\n observer.observe(el, options);\n requestAnimationFrame(onFrame); // Initial run when page is visible and children has mounted\n return cleanup;\n};\n\n/**\n * Many mutation observers need to watch childNodes, thus running on all `textContent` changes\n * This utility allows skipping mutation observers while updating textContent\n */\nexport const setTextWithoutMutation = (el: Element, text: string | null) => {\n SKIP_MUTATIONS = true;\n el.textContent = text;\n requestAnimationFrame(enableMutations); // Let all mutationobservers run before enabling again\n};\nconst enableMutations = () => {\n SKIP_MUTATIONS = false;\n};\n\n/**\n * tag\n * @description creates element and assigns properties\n * @param tagName The tagname of element to create\n * @param attrs Optional attributes to add to the element\n * @param text Optional text content to add to the element\n * @return HTMLElement with props\n */\nexport const tag = <TagName extends keyof HTMLElementTagNameMap>(\n tagName: TagName,\n attrs?: Record<string, string | null> | null,\n): HTMLElementTagNameMap[TagName] => {\n const el = document.createElement(tagName);\n if (attrs) for (const [key, val] of Object.entries(attrs)) attr(el, key, val);\n return el;\n};\n\n/**\n * customElements.define\n * @description Defines a customElement if running in browser and if not already registered\n * Scoped/named \"customElements.define\" so @custom-elements-manifest/analyzer can find tag names\n */\nexport const customElements = {\n define: (name: string, instance: CustomElementConstructor) =>\n !isBrowser() ||\n window.customElements.get(name) ||\n window.customElements.define(name, instance),\n};\n\n/**\n * useId\n * @return A generated unique ID\n */\nlet id = 0;\nconst hash = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;\nexport function useId(el?: Element | null) {\n if (el && !el.id) el.id = `${hash}${++id}`;\n return el?.id || '';\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/utils/utils.ts"],"sourcesContent":["export const QUICK_EVENT = { passive: true, capture: true };\n\n// Using function instead of constant to support evnironments where DOM can be unloaded (like Vitest with jsdom)\nexport const isBrowser = () =>\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\nexport const isWindows = () =>\n isBrowser() &&\n // @ts-expect-error Typescript has not implemented userAgentData yet https://stackoverflow.com/a/71392474\n /^Win/i.test(navigator.userAgentData?.platform || navigator.platform);\n\n// Make sure we have a HTMLElement to extend (for server side rendering)\nexport const DSElement =\n typeof HTMLElement === 'undefined'\n ? (class {} as typeof HTMLElement)\n : HTMLElement;\n\nexport function debounce<T extends unknown[]>(\n callback: (...args: T) => void,\n delay: number,\n) {\n let timer: ReturnType<typeof setTimeout>;\n\n return function (this: unknown, ...args: T) {\n clearTimeout(timer);\n timer = setTimeout(() => callback.apply(this, args), delay);\n };\n}\n\n/**\n * warn\n * @description Utility to console.warn, but can be silenced in production with window.dsWarnings = false;\n */\ndeclare global {\n interface Window {\n dsWarnings?: boolean;\n }\n}\nexport const warn = (\n message: string,\n ...args: Parameters<typeof console.warn>\n) =>\n typeof window === 'undefined' ||\n window.dsWarnings === false ||\n console.warn(`Designsystemet: ${message}`, ...args);\n\n/**\n * attr\n * @description Utility to quickly get, set and remove attributes\n * @param el The Element to read/write attributes from\n * @param name The attribute name to get, set or remove, or a object to set multiple attributes\n * @param value A valid attribute value or null to remove attribute\n */\nexport const attr = (\n el: Element,\n name: string,\n value?: string | null,\n): string | null => {\n if (value === undefined) return el.getAttribute(name) ?? null; // Fallback to null only if el is undefined\n if (value === null) el.removeAttribute(name);\n else if (el.getAttribute(name) !== value) el.setAttribute(name, value);\n return null;\n};\n\nconst STRIP_SURROUNDING_QUOTES = /^[\"']|[\"']$/g; // Matches surrounding single or double quotes\n/**\n * attrOrCSS\n * @description Retrieves and updates attribute based on attribute or CSS property value\n * @param el The Element to read attributes/CSS from\n * @param name Attribute or CSS property to get\n * @return string attribute or CSS property value\n */\nexport const attrOrCSS = (el: Element, name: string) => {\n let value = attr(el, name);\n if (!value) {\n const prop = getComputedStyle(el).getPropertyValue(`--_ds-${name}`);\n value = prop.replace(STRIP_SURROUNDING_QUOTES, '').trim() || null;\n }\n if (!value) warn(`Missing ${name} on:`, el);\n return value;\n};\n\n/**\n * on\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const on = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.addEventListener>\n): (() => void) => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.addEventListener(type, ...options);\n return () => off(el, ...rest);\n};\n\n/**\n * off\n * @param el The Element to use as EventTarget\n * @param types A space separated string of event types\n * @param listener An event listener function or listener object\n */\nexport const off = (\n el: Node | Window | ShadowRoot,\n ...rest: Parameters<typeof Element.prototype.removeEventListener>\n): void => {\n const [types, ...options] = rest;\n for (const type of types.split(' ')) el.removeEventListener(type, ...options);\n};\n\n// Used to store cleanup functions for hot-reloading\ndeclare global {\n interface Window {\n _dsHotReloadCleanup?: Map<string, Array<() => void>>;\n }\n}\n\n/**\n * onHotReload\n * @description Runs a callback when window is loaded in browser, and ensures cleanup when hot-reloading\n * @param key The key to identify setup and corresponding cleanup\n * @param callback The callback to run when the page is ready\n */\nexport const onHotReload = (key: string, setup: () => Array<() => void>) => {\n if (!isBrowser()) return; // Skip if not in modern browser environment, but on each call as Vitest might have unloaded jsdom between tests\n if (!window._dsHotReloadCleanup) window._dsHotReloadCleanup = new Map(); // Hot reload cleanup support supporting all build tools\n\n window._dsHotReloadCleanup?.get(key)?.map((cleanup) => cleanup()); // Run previous cleanup\n window._dsHotReloadCleanup?.set(key, setup()); // Store new cleanup\n};\n\n/**\n * Speed up MutationObserver by debouncing and only running when page is visible\n * @return new MutaionObserver\n */\nlet SKIP_MUTATIONS = false;\nexport const onMutation = (\n el: Node,\n callback: (observer: MutationObserver) => void,\n options: MutationObserverInit,\n) => {\n let queue = 0;\n const onFrame = () => {\n if (!el.isConnected) return cleanup(); // Stop observing if element is removed from DOM\n callback(observer);\n observer.takeRecords(); // Clear records in case mutations happened during callback\n queue = 0;\n };\n const cleanup = () => observer?.disconnect?.();\n const observer = new MutationObserver(() => {\n if (!SKIP_MUTATIONS && !queue) queue = requestAnimationFrame(onFrame); // requestAnimationFrame only runs when page is visible\n });\n\n observer.observe(el, options);\n requestAnimationFrame(onFrame); // Initial run when page is visible and children has mounted\n return cleanup;\n};\n\n/**\n * Many mutation observers need to watch childNodes, thus running on all `textContent` changes\n * This utility allows skipping mutation observers while updating textContent\n */\nexport const setTextWithoutMutation = (el: Element, text: string | null) => {\n SKIP_MUTATIONS = true;\n el.textContent = text;\n requestAnimationFrame(enableMutations); // Let all mutationobservers run before enabling again\n};\nconst enableMutations = () => {\n SKIP_MUTATIONS = false;\n};\n\n/**\n * tag\n * @description creates element and assigns properties\n * @param tagName The tagname of element to create\n * @param attrs Optional attributes to add to the element\n * @param text Optional text content to add to the element\n * @return HTMLElement with props\n */\nexport const tag = <TagName extends keyof HTMLElementTagNameMap>(\n tagName: TagName,\n attrs?: Record<string, string | null> | null,\n): HTMLElementTagNameMap[TagName] => {\n const el = document.createElement(tagName);\n if (attrs) for (const [key, val] of Object.entries(attrs)) attr(el, key, val);\n return el;\n};\n\n/**\n * customElements.define\n * @description Defines a customElement if running in browser and if not already registered\n * Scoped/named \"customElements.define\" so @custom-elements-manifest/analyzer can find tag names\n */\nexport const customElements = {\n define: (name: string, instance: CustomElementConstructor) =>\n !isBrowser() ||\n window.customElements.get(name) ||\n window.customElements.define(name, instance),\n};\n\n/**\n * useId\n * @return A generated unique ID\n */\nlet id = 0;\nconst hash = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 5)}`;\nexport function useId(el?: Element | null) {\n if (el && !el.id) el.id = `${hash}${++id}`;\n return el?.id || '';\n}\n\n/**\n * @description Based off speak function from [U-elements](https://github.com/u-elements/u-elements/blob/main/packages/utils.ts#L210)\n * @param text The text to announce\n */\nlet LIVE_EL: HTMLElement | undefined;\nlet LIVE_FIX = 0;\nlet LIVE_CLEAR: ReturnType<typeof setTimeout> | number = 0;\nexport const announce = (text: string) => {\n clearTimeout(LIVE_CLEAR);\n if (LIVE_EL)\n setTextWithoutMutation(LIVE_EL, `${text}${LIVE_FIX++ % 2 ? '\\u00A0' : ''}`); // Non-breaking space to ensure screen reader announces\n if (text) LIVE_CLEAR = setTimeout(announce, 2000, ''); // Clear prevent old announcements being found by screen readers, with 2 seconds brace period to avoid cutting of Android Talkback\n};\n\n// Mount live region on first focus so its ready to be used\nconst announceMount = () => {\n if (document.readyState !== 'complete') return; // Ensure page is loaded trying to avoid issues with React hydration\n if (!LIVE_EL) {\n LIVE_EL = tag('div', { 'aria-live': 'assertive' });\n LIVE_EL.style.overflow = 'hidden'; // Settings styles individually to prevent issues with CSP\n LIVE_EL.style.position = 'fixed';\n LIVE_EL.style.whiteSpace = 'nowrap';\n LIVE_EL.style.width = '1px';\n }\n if (!LIVE_EL.isConnected) document.body.appendChild(LIVE_EL);\n};\nonHotReload('announce', () => [\n on(document, 'focus mouseover', announceMount, QUICK_EVENT),\n]);\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -86,6 +86,29 @@ function useId(el) {
86
86
  if (el && !el.id) el.id = `${hash}${++id}`;
87
87
  return el?.id || "";
88
88
  }
89
+ var LIVE_EL;
90
+ var LIVE_FIX = 0;
91
+ var LIVE_CLEAR = 0;
92
+ var announce = (text) => {
93
+ clearTimeout(LIVE_CLEAR);
94
+ if (LIVE_EL)
95
+ setTextWithoutMutation(LIVE_EL, `${text}${LIVE_FIX++ % 2 ? "\xA0" : ""}`);
96
+ if (text) LIVE_CLEAR = setTimeout(announce, 2e3, "");
97
+ };
98
+ var announceMount = () => {
99
+ if (document.readyState !== "complete") return;
100
+ if (!LIVE_EL) {
101
+ LIVE_EL = tag("div", { "aria-live": "assertive" });
102
+ LIVE_EL.style.overflow = "hidden";
103
+ LIVE_EL.style.position = "fixed";
104
+ LIVE_EL.style.whiteSpace = "nowrap";
105
+ LIVE_EL.style.width = "1px";
106
+ }
107
+ if (!LIVE_EL.isConnected) document.body.appendChild(LIVE_EL);
108
+ };
109
+ onHotReload("announce", () => [
110
+ on(document, "focus mouseover", announceMount, QUICK_EVENT)
111
+ ]);
89
112
 
90
113
  // src/index.ts
91
114
  import "@u-elements/u-details/polyfill";
@@ -297,10 +320,17 @@ var handleKeydown = (event) => {
297
320
  if (event.key === "Enter") event.target.click();
298
321
  if (event.key?.startsWith("Arrow")) {
299
322
  event.preventDefault?.();
300
- const inputs = group.getElementsByTagName("input");
301
- const index = [...inputs].indexOf(event.target);
323
+ const inputs = [...group.getElementsByTagName("input")];
324
+ const index = inputs.indexOf(event.target);
302
325
  const move = event.key.match(/Arrow(Right|Down)/) ? 1 : -1;
303
- inputs[(inputs.length + index + move) % inputs.length]?.focus();
326
+ let nextIndex = index;
327
+ for (let i = 0; i < inputs.length; i++) {
328
+ nextIndex = (inputs.length + nextIndex + move) % inputs.length;
329
+ if (!inputs[nextIndex]?.disabled) {
330
+ inputs[nextIndex]?.focus();
331
+ break;
332
+ }
333
+ }
304
334
  }
305
335
  };
306
336
  onHotReload("toggle-group", () => [
@@ -346,6 +376,12 @@ var handleAriaAttributes3 = debounce(() => {
346
376
  if (!el.matches(SELECTOR_INTERACTIVE))
347
377
  warn('Missing tabindex="0" attribute on: ', el);
348
378
  }
379
+ const isCurrent = el === SOURCE && TIP?.matches(":popover-open");
380
+ const isChanged = isCurrent && text && TIP?.textContent !== text;
381
+ if (isCurrent && isChanged) {
382
+ if (TIP) setTextWithoutMutation(TIP, text);
383
+ if (document.activeElement === el) announce(text);
384
+ }
349
385
  }
350
386
  }, 0);
351
387
  var handleInterest = ({ type, target }) => {
@@ -519,8 +555,6 @@ var handleMutations = debounce(() => {
519
555
  }
520
556
  }
521
557
  }, 0);
522
- var SR_ONLY = "position:fixed;white-space:nowrap;clip:rect(0 0 0 0)";
523
- var SR_LIVE = isBrowser() ? tag("div", { "aria-live": "polite", style: SR_ONLY }) : null;
524
558
  var updateField = (e) => {
525
559
  const input = e.target || e;
526
560
  const counter = COUNTS.get(input);
@@ -535,10 +569,8 @@ var updateField = (e) => {
535
569
  attr(counter, "data-label", label);
536
570
  attr(counter, "data-state", state);
537
571
  attr(counter, "data-color", count < 0 ? "danger" : null);
538
- if (e.type === "input" && SR_LIVE && label) {
539
- if (!SR_LIVE?.isConnected) document.body.appendChild(SR_LIVE);
572
+ if (e.type === "input" && label)
540
573
  debouncedCounterLiveRegion(input, label);
541
- }
542
574
  }
543
575
  if (!HAS_FIELD_SIZING && input instanceof HTMLTextAreaElement) {
544
576
  input.style.setProperty("--_ds-field-sizing", "auto");
@@ -546,8 +578,7 @@ var updateField = (e) => {
546
578
  }
547
579
  };
548
580
  var debouncedCounterLiveRegion = debounce((input, text) => {
549
- const hasFocus = document.activeElement === input;
550
- if (SR_LIVE?.isConnected && hasFocus) setTextWithoutMutation(SR_LIVE, text);
581
+ if (document.activeElement === input) announce(text);
551
582
  }, COUNTER_DEBOUNCE);
552
583
  var isInvalid = (el) => el.getAttribute("data-color") !== "success";
553
584
  var isInputLike = (el) => el instanceof HTMLElement && "validity" in el && // Adds support for custom elements implemeted with attachInternals()