@digdir/designsystemet-web 1.19.1 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/cjs/_vendors/@oddbird/popover-polyfill/dist/popover-fn.cjs +2 -2
  2. package/dist/cjs/_vendors/@oddbird/popover-polyfill/dist/popover-fn.cjs.map +1 -1
  3. package/dist/cjs/clickdelegatefor/clickdelegatefor.cjs +1 -1
  4. package/dist/cjs/clickdelegatefor/clickdelegatefor.cjs.map +1 -1
  5. package/dist/cjs/field/field.cjs +1 -1
  6. package/dist/cjs/field/field.cjs.map +1 -1
  7. package/dist/cjs/fieldset/fieldset.cjs +1 -1
  8. package/dist/cjs/fieldset/fieldset.cjs.map +1 -1
  9. package/dist/cjs/packages/web/package.cjs +1 -1
  10. package/dist/cjs/popover/popover.cjs +1 -1
  11. package/dist/cjs/popover/popover.cjs.map +1 -1
  12. package/dist/cjs/suggestion/suggestion.cjs +1 -1
  13. package/dist/cjs/suggestion/suggestion.cjs.map +1 -1
  14. package/dist/cjs/tooltip/tooltip.cjs +1 -1
  15. package/dist/cjs/tooltip/tooltip.cjs.map +1 -1
  16. package/dist/esm/_vendors/@oddbird/popover-polyfill/dist/popover-fn.js +2 -2
  17. package/dist/esm/_vendors/@oddbird/popover-polyfill/dist/popover-fn.js.map +1 -1
  18. package/dist/esm/clickdelegatefor/clickdelegatefor.js +1 -1
  19. package/dist/esm/clickdelegatefor/clickdelegatefor.js.map +1 -1
  20. package/dist/esm/field/field.js +1 -1
  21. package/dist/esm/field/field.js.map +1 -1
  22. package/dist/esm/fieldset/fieldset.js +1 -1
  23. package/dist/esm/fieldset/fieldset.js.map +1 -1
  24. package/dist/esm/packages/web/package.js +1 -1
  25. package/dist/esm/popover/popover.js +1 -1
  26. package/dist/esm/popover/popover.js.map +1 -1
  27. package/dist/esm/suggestion/suggestion.js +1 -1
  28. package/dist/esm/suggestion/suggestion.js.map +1 -1
  29. package/dist/esm/tooltip/tooltip.js +1 -1
  30. package/dist/esm/tooltip/tooltip.js.map +1 -1
  31. package/dist/index.js +95 -52
  32. package/dist/umd/index.js +8 -7
  33. package/dist/umd/index.js.map +1 -1
  34. package/package.json +8 -5
@@ -1,2 +1,2 @@
1
- import{ARIA_DESC as e,ARIA_LABEL as t,QUICK_EVENT as n,announce as r,attr as i,attrOrCSS as a,getComposedTarget as o,getRoot as s,isBrowser as c,on as l,onHotReload as u,onMutation as d,tag as f,warn as p}from"../utils/utils.js";import"../popover/popover.js";let m,h,g,_,v=0;const y=`data-color`,b=`data-color-scheme`,x=`data-size`,S=`data-tooltip`,C=c()&&/iPad|iPhone|iPod/.test(navigator.userAgent),w=`[${y}]`,T=`[${b}]`,E=`[${x}]`,D=`[${S}]`,O=e=>{e&&!(e instanceof HTMLElement)&&p(`setTooltipElement expects an HTMLElement, got: `,e),P(),v=0,m=e||void 0},k=(e=document)=>{for(let t of e?.querySelectorAll(D)||[])j(t)},A=(e,t)=>{if(!t)return k();for(let e of t)if(e.target!==m)if(e.attributeName===S)j(e.target);else for(let t of e.addedNodes)t.nodeType===1&&(t.hasAttribute(S)?j(t):k(t))},j=(n,o=!0)=>{let c=a(n,S)||``;if(c[0]===`#`&&(c=s(n).getElementById(c.slice(1))?.textContent?.trim()||``),c!==(n.getAttribute(`aria-label`)||n.getAttribute(`aria-description`))){let r=i(n,`role`)!==`img`&&n.textContent?.trim();i(n,S,c),i(n,t,r?null:c),i(n,e,r?c:null),n.tabIndex===-1&&p(`Missing tabindex="0" attribute on: `,n)}n===_&&m?.textContent!==c&&(m&&(m.textContent=c),c&&o&&document.activeElement===n&&r(c))},M=e=>{let t=o(e);if(!t||g===t||m?.contains(t))return;g=t;let n=g?.closest?.(D)||void 0;if(_!==n&&(_&&P(),_=n,n)){if(e.type===`focus`||C||300>Date.now()-v)return N();e.type===`mousemove`&&(h=setTimeout(N,300))}},N=()=>{if(!_)return P();m||=f(`div`,{class:`ds-tooltip`}),m.isConnected||document.body.appendChild(m);let e=_.closest(w),t=_.closest(T),n=_.closest(E),r=e!==t&&e?.contains(t);i(m,`popover`,`manual`),i(m,b,t?.getAttribute(b)||null),i(m,y,r&&e?.getAttribute(y)||null),i(m,x,n?.getAttribute(x)||null),j(_,!1),m.showPopover(),m.dispatchEvent(new CustomEvent(`ds-toggle-source`,{bubbles:!0,composed:!0,detail:_}))},P=e=>{(e?.type!==`keydown`||e?.key===`Escape`)&&(_&&m?.isConnected&&m.popover&&m.hidePopover(),e||(v=Date.now()),clearTimeout(h),_=void 0)};u(`tooltip`,()=>[l(document,`focus mousemove`,M,n),l(document,`keydown`,P,n),d(document,A,{attributeFilter:[S],attributes:!0,childList:!0,subtree:!0})]);export{k as initTooltips,O as setTooltipElement};
1
+ import{ARIA_DESC as e,ARIA_LABEL as t,QUICK_EVENT as n,announce as r,attr as i,attrOrCSS as a,getComposedTarget as o,getRoot as s,isBrowser as c,on as l,onHotReload as u,onMutation as d,tag as f,warn as p}from"../utils/utils.js";import"../popover/popover.js";let m,h,g,_,v=0;const y=`data-color`,b=`data-color-scheme`,x=`data-size`,S=`data-tooltip`,C=c()&&/iPad|iPhone|iPod/.test(navigator.userAgent),w=`[${y}]`,T=`[${b}]`,E=`[${x}]`,D=`[${S}]`,O=e=>{e&&!(e instanceof HTMLElement)&&p(`setTooltipElement expects an HTMLElement, got: `,e),P(),v=0,m=e||void 0},k=(e=document)=>{for(let t of e?.querySelectorAll(D)||[])j(t)},A=(e,t)=>{if(!t)return k();for(let e of t)if(e.target!==m){if(e.attributeName===S)j(e.target);else for(let t of e.addedNodes)t.nodeType===1&&(t.hasAttribute(S)?j(t):k(t))}},j=(n,o=!0)=>{let c=a(n,S)||``;if(c[0]===`#`&&(c=s(n).getElementById(c.slice(1))?.textContent?.trim()||``),c!==(n.getAttribute(`aria-label`)||n.getAttribute(`aria-description`))){let r=i(n,`role`)!==`img`&&n.textContent?.trim();i(n,S,c),i(n,t,r?null:c),i(n,e,r?c:null),n.tabIndex===-1&&p(`Missing tabindex="0" attribute on: `,n)}n===_&&m?.textContent!==c&&(m&&(m.textContent=c),c&&o&&document.activeElement===n&&r(c))},M=e=>{let t=o(e);if(!t||g===t||m?.contains(t))return;g=t;let n=g?.closest?.(D)||void 0;if(_!==n&&(_&&P(),_=n,n)){if(e.type===`focus`||C||300>Date.now()-v)return N();e.type===`mousemove`&&(h=setTimeout(N,300))}},N=()=>{if(!_)return P();m||=f(`div`,{class:`ds-tooltip`}),m.isConnected||document.body.appendChild(m);let e=_.closest(w),t=_.closest(T),n=_.closest(E),r=e!==t&&e?.contains(t);i(m,`popover`,`manual`),i(m,b,t?.getAttribute(b)||null),i(m,y,r&&e?.getAttribute(y)||null),i(m,x,n?.getAttribute(x)||null),j(_,!1),m.showPopover(),m.dispatchEvent(new CustomEvent(`ds-toggle-source`,{bubbles:!0,composed:!0,detail:_}))},P=e=>{(e?.type!==`keydown`||e?.key===`Escape`)&&(_&&m?.isConnected&&m.popover&&m.hidePopover(),e||(v=Date.now()),clearTimeout(h),_=void 0)};u(`tooltip`,()=>[l(document,`focus mousemove`,M,n),l(document,`keydown`,P,n),d(document,A,{attributeFilter:[S],attributes:!0,childList:!0,subtree:!0})]);export{k as initTooltips,O as setTooltipElement};
2
2
  //# sourceMappingURL=tooltip.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/tooltip/tooltip.ts"],"sourcesContent":["import '../popover/popover'; // Ensure popover is imported when using individual imports, since tooltip relies on it\nimport {\n ARIA_DESC,\n ARIA_LABEL,\n announce,\n attr,\n attrOrCSS,\n getComposedTarget,\n getRoot,\n isBrowser,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n tag,\n warn,\n} from '../utils/utils';\n\nlet TIP: HTMLElement | undefined;\nlet TIMER: ReturnType<typeof setTimeout> | undefined;\nlet TARGET: Element | undefined; // Used to speed up mousemove handling\nlet OPEN: Element | undefined;\nlet LAST_HIDE = 0;\nconst ATTR_COLOR = 'data-color';\nconst ATTR_SCHEME = 'data-color-scheme';\nconst ATTR_SIZE = 'data-size';\nconst ATTR_TOOLTIP = 'data-tooltip';\nconst DELAY_HOVER = 300;\nconst DELAY_SKIP = 300;\nconst IS_IOS = isBrowser() && /iPad|iPhone|iPod/.test(navigator.userAgent); // Needed to omit DELAY_HOVER since iOS triggers mousemove before click\nconst SELECTOR_COLOR = `[${ATTR_COLOR}]`;\nconst SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;\nconst SELECTOR_SIZE = `[${ATTR_SIZE}]`;\nconst SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;\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 hide(); // Reset when changing source\n LAST_HIDE = 0; // Reset last hide time to re-enable delay\n TIP = el || undefined;\n};\n\nexport const initTooltips = (\n scope: Element | ShadowRoot | Document | null = document,\n) => {\n for (const el of scope?.querySelectorAll(SELECTOR_TOOLTIP) || [])\n setupText(el);\n};\n\n// Initial run has no MutationRecords, so we set records to [null] to ensure we run the querySelectorAll for any existing elements with data-tooltip\nconst handleMutations = (_: Document, records?: MutationRecord[]) => {\n if (!records) return initTooltips();\n for (const r of records) {\n if (r.target === TIP) continue; // Ignore mutations on tooltip itself\n if (r.attributeName === ATTR_TOOLTIP) setupText(r.target as Element);\n else\n for (const el of r.addedNodes as NodeListOf<Element>) {\n if (el.nodeType !== 1) continue;\n if (el.hasAttribute(ATTR_TOOLTIP)) setupText(el);\n else initTooltips(el); // Check for any child elements with data-tooltip\n }\n }\n};\n\nconst setupText = (el: Element, canAnnounce = true) => {\n let text = attrOrCSS(el, ATTR_TOOLTIP) || '';\n\n // Allow using another element as source.\n // Note: Only checks on initial mutation, as we do not want to keep checking if the source element is removed or changed,\n // since this would be a performance issue. If the source element is removed, the tooltip will be empty and not shown.\n if (text[0] === '#')\n text = getRoot(el).getElementById(text.slice(1))?.textContent?.trim() || '';\n\n if (text !== (el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC))) {\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 as HTMLElement).tabIndex === -1)\n warn('Missing tabindex=\"0\" attribute on: ', el);\n }\n if (el === OPEN && TIP?.textContent !== text) {\n if (TIP) TIP.textContent = text; // Update tooltip text if it is already open\n if (text && canAnnounce && document.activeElement === el) announce(text); // Only announce if focus is on the source\n }\n};\n\nconst handleInterest = (e: Event) => {\n const target = getComposedTarget(e);\n if (!target || TARGET === target || TIP?.contains(target as Node)) return; // Same target, or allow tooltip to be hovered (following https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus)\n TARGET = target;\n\n const source = TARGET?.closest?.(SELECTOR_TOOLTIP) || undefined;\n if (OPEN === source) return; // Same source, no need to update\n if (OPEN) hide(); // Reset previous tooltip, since we are moving to a new source\n OPEN = source;\n\n if (!source) return; // No source, no need to show tooltip\n if (e.type === 'focus' || IS_IOS || DELAY_SKIP > Date.now() - LAST_HIDE)\n return show(); // Instantly show if focus or if we just closed a tooltip\n if (e.type === 'mousemove') TIMER = setTimeout(show, DELAY_HOVER); // Delay mouse showing tooltip if not already shown\n};\n\nconst show = () => {\n if (!OPEN) return hide(); // If no new anchor, cleanup previous autoUpdate\n if (!TIP) TIP = tag('div', { class: 'ds-tooltip' });\n if (!TIP.isConnected) document.body.appendChild(TIP);\n\n const color = OPEN.closest(SELECTOR_COLOR); // Match source color of source element\n const scheme = OPEN.closest(SELECTOR_SCHEME); // Match source color-scheme of source element\n const size = OPEN.closest(SELECTOR_SIZE); // Match source size 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\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 attr(TIP, ATTR_SIZE, size?.getAttribute(ATTR_SIZE) || null); // Fallback to null to reset if not size found\n setupText(OPEN, false); // If mutation observer is not triggered, ensure tooltip text is updated\n TIP.showPopover();\n TIP.dispatchEvent(\n new CustomEvent('ds-toggle-source', {\n bubbles: true,\n composed: true, // Enable bubbling out of shadow DOM boundaries\n detail: OPEN, // Since showPopover({ source }) is not supported in all browsers yet\n }),\n );\n};\n\nconst hide = (event?: Partial<KeyboardEvent>) => {\n if (event?.type === 'keydown' && event?.key !== 'Escape') return;\n if (OPEN && TIP?.isConnected && TIP.popover) TIP.hidePopover(); // Only hide if connected and activated\n if (!event) LAST_HIDE = Date.now(); // If closing with keyboard, do not show next tooltip instantly\n clearTimeout(TIMER);\n OPEN = undefined;\n};\n\nonHotReload('tooltip', () => [\n on(document, 'focus mousemove', handleInterest, QUICK_EVENT),\n on(document, 'keydown', hide, QUICK_EVENT),\n onMutation(document, handleMutations, {\n attributeFilter: [ATTR_TOOLTIP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"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"}
1
+ {"version":3,"file":"tooltip.js","names":[],"sources":["../../../src/tooltip/tooltip.ts"],"sourcesContent":["import '../popover/popover'; // Ensure popover is imported when using individual imports, since tooltip relies on it\nimport {\n ARIA_DESC,\n ARIA_LABEL,\n announce,\n attr,\n attrOrCSS,\n getComposedTarget,\n getRoot,\n isBrowser,\n on,\n onHotReload,\n onMutation,\n QUICK_EVENT,\n tag,\n warn,\n} from '../utils/utils';\n\nlet TIP: HTMLElement | undefined;\nlet TIMER: ReturnType<typeof setTimeout> | undefined;\nlet TARGET: Element | undefined; // Used to speed up mousemove handling\nlet OPEN: Element | undefined;\nlet LAST_HIDE = 0;\nconst ATTR_COLOR = 'data-color';\nconst ATTR_SCHEME = 'data-color-scheme';\nconst ATTR_SIZE = 'data-size';\nconst ATTR_TOOLTIP = 'data-tooltip';\nconst DELAY_HOVER = 300;\nconst DELAY_SKIP = 300;\nconst IS_IOS = isBrowser() && /iPad|iPhone|iPod/.test(navigator.userAgent); // Needed to omit DELAY_HOVER since iOS triggers mousemove before click\nconst SELECTOR_COLOR = `[${ATTR_COLOR}]`;\nconst SELECTOR_SCHEME = `[${ATTR_SCHEME}]`;\nconst SELECTOR_SIZE = `[${ATTR_SIZE}]`;\nconst SELECTOR_TOOLTIP = `[${ATTR_TOOLTIP}]`;\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 hide(); // Reset when changing source\n LAST_HIDE = 0; // Reset last hide time to re-enable delay\n TIP = el || undefined;\n};\n\nexport const initTooltips = (\n scope: Element | ShadowRoot | Document | null = document,\n) => {\n for (const el of scope?.querySelectorAll(SELECTOR_TOOLTIP) || [])\n setupText(el);\n};\n\n// Initial run has no MutationRecords, so we set records to [null] to ensure we run the querySelectorAll for any existing elements with data-tooltip\nconst handleMutations = (_: Document, records?: MutationRecord[]) => {\n if (!records) return initTooltips();\n for (const r of records) {\n if (r.target === TIP) continue; // Ignore mutations on tooltip itself\n if (r.attributeName === ATTR_TOOLTIP) setupText(r.target as Element);\n else\n for (const el of r.addedNodes as NodeListOf<Element>) {\n if (el.nodeType !== 1) continue;\n if (el.hasAttribute(ATTR_TOOLTIP)) setupText(el);\n else initTooltips(el); // Check for any child elements with data-tooltip\n }\n }\n};\n\nconst setupText = (el: Element, canAnnounce = true) => {\n let text = attrOrCSS(el, ATTR_TOOLTIP) || '';\n\n // Allow using another element as source.\n // Note: Only checks on initial mutation, as we do not want to keep checking if the source element is removed or changed,\n // since this would be a performance issue. If the source element is removed, the tooltip will be empty and not shown.\n if (text[0] === '#')\n text = getRoot(el).getElementById(text.slice(1))?.textContent?.trim() || '';\n\n if (text !== (el.getAttribute(ARIA_LABEL) || el.getAttribute(ARIA_DESC))) {\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 as HTMLElement).tabIndex === -1)\n warn('Missing tabindex=\"0\" attribute on: ', el);\n }\n if (el === OPEN && TIP?.textContent !== text) {\n if (TIP) TIP.textContent = text; // Update tooltip text if it is already open\n if (text && canAnnounce && document.activeElement === el) announce(text); // Only announce if focus is on the source\n }\n};\n\nconst handleInterest = (e: Event) => {\n const target = getComposedTarget(e);\n if (!target || TARGET === target || TIP?.contains(target as Node)) return; // Same target, or allow tooltip to be hovered (following https://www.w3.org/TR/WCAG21/#content-on-hover-or-focus)\n TARGET = target;\n\n const source = TARGET?.closest?.(SELECTOR_TOOLTIP) || undefined;\n if (OPEN === source) return; // Same source, no need to update\n if (OPEN) hide(); // Reset previous tooltip, since we are moving to a new source\n OPEN = source;\n\n if (!source) return; // No source, no need to show tooltip\n if (e.type === 'focus' || IS_IOS || DELAY_SKIP > Date.now() - LAST_HIDE)\n return show(); // Instantly show if focus or if we just closed a tooltip\n if (e.type === 'mousemove') TIMER = setTimeout(show, DELAY_HOVER); // Delay mouse showing tooltip if not already shown\n};\n\nconst show = () => {\n if (!OPEN) return hide(); // If no new anchor, cleanup previous autoUpdate\n if (!TIP) TIP = tag('div', { class: 'ds-tooltip' });\n if (!TIP.isConnected) document.body.appendChild(TIP);\n\n const color = OPEN.closest(SELECTOR_COLOR); // Match source color of source element\n const scheme = OPEN.closest(SELECTOR_SCHEME); // Match source color-scheme of source element\n const size = OPEN.closest(SELECTOR_SIZE); // Match source size 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\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 attr(TIP, ATTR_SIZE, size?.getAttribute(ATTR_SIZE) || null); // Fallback to null to reset if not size found\n setupText(OPEN, false); // If mutation observer is not triggered, ensure tooltip text is updated\n TIP.showPopover();\n TIP.dispatchEvent(\n new CustomEvent('ds-toggle-source', {\n bubbles: true,\n composed: true, // Enable bubbling out of shadow DOM boundaries\n detail: OPEN, // Since showPopover({ source }) is not supported in all browsers yet\n }),\n );\n};\n\nconst hide = (event?: Partial<KeyboardEvent>) => {\n if (event?.type === 'keydown' && event?.key !== 'Escape') return;\n if (OPEN && TIP?.isConnected && TIP.popover) TIP.hidePopover(); // Only hide if connected and activated\n if (!event) LAST_HIDE = Date.now(); // If closing with keyboard, do not show next tooltip instantly\n clearTimeout(TIMER);\n OPEN = undefined;\n};\n\nonHotReload('tooltip', () => [\n on(document, 'focus mousemove', handleInterest, QUICK_EVENT),\n on(document, 'keydown', hide, QUICK_EVENT),\n onMutation(document, handleMutations, {\n attributeFilter: [ATTR_TOOLTIP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"mQAkBA,IAAI,EACA,EACA,EACA,EACA,EAAY,EAChB,MAAM,EAAa,aACb,EAAc,oBACd,EAAY,YACZ,EAAe,eAGf,EAAS,EAAU,GAAK,mBAAmB,KAAK,UAAU,SAAS,EACnE,EAAiB,IAAI,EAAW,GAChC,EAAkB,IAAI,EAAY,GAClC,EAAgB,IAAI,EAAU,GAC9B,EAAmB,IAAI,EAAa,GAO7B,EAAqB,GAA4B,CACxD,GAAM,EAAE,aAAc,cACxB,EAAK,kDAAmD,CAAE,EAC5D,EAAK,EACL,EAAY,EACZ,EAAM,GAAM,IAAA,EACd,EAEa,GACX,EAAgD,WAC7C,CACH,IAAK,IAAM,KAAM,GAAO,iBAAiB,CAAgB,GAAK,CAAC,EAC7D,EAAU,CAAE,CAChB,EAGM,GAAmB,EAAa,IAA+B,CACnE,GAAI,CAAC,EAAS,OAAO,EAAa,EAClC,IAAK,IAAM,KAAK,EACV,KAAE,SAAW,EACjB,IAAI,EAAE,gBAAkB,EAAc,EAAU,EAAE,MAAiB,OAEjE,IAAK,IAAM,KAAM,EAAE,WACb,EAAG,WAAa,IAChB,EAAG,aAAa,CAAY,EAAG,EAAU,CAAE,EAC1C,EAAa,CAAE,EACtB,CAEN,EAEM,GAAa,EAAa,EAAc,KAAS,CACrD,IAAI,EAAO,EAAU,EAAI,CAAY,GAAK,GAQ1C,GAHI,EAAK,KAAO,MACd,EAAO,EAAQ,CAAE,CAAC,CAAC,eAAe,EAAK,MAAM,CAAC,CAAC,CAAC,EAAE,aAAa,KAAK,GAAK,IAEvE,KAAU,EAAG,aAAA,YAAuB,GAAK,EAAG,aAAA,kBAAsB,GAAI,CACxE,IAAM,EAAU,EAAK,EAAI,MAAM,IAAM,OAAS,EAAG,aAAa,KAAK,EACnE,EAAK,EAAI,EAAc,CAAI,EAC3B,EAAK,EAAI,EAAY,EAAU,KAAO,CAAI,EAC1C,EAAK,EAAI,EAAW,EAAU,EAAO,IAAI,EACpC,EAAmB,WAAa,IACnC,EAAK,sCAAuC,CAAE,CAClD,CACI,IAAO,GAAQ,GAAK,cAAgB,IAClC,IAAK,EAAI,YAAc,GACvB,GAAQ,GAAe,SAAS,gBAAkB,GAAI,EAAS,CAAI,EAE3E,EAEM,EAAkB,GAAa,CACnC,IAAM,EAAS,EAAkB,CAAC,EAClC,GAAI,CAAC,GAAU,IAAW,GAAU,GAAK,SAAS,CAAc,EAAG,OACnE,EAAS,EAET,IAAM,EAAS,GAAQ,UAAU,CAAgB,GAAK,IAAA,GAClD,OAAS,IACT,GAAM,EAAK,EACf,EAAO,EAEF,GACL,IAAI,EAAE,OAAS,SAAW,GAAU,IAAa,KAAK,IAAI,EAAI,EAC5D,OAAO,EAAK,EACV,EAAE,OAAS,cAAa,EAAQ,WAAW,EAAM,GAAW,EADlD,CAEhB,EAEM,MAAa,CACjB,GAAI,CAAC,EAAM,OAAO,EAAK,EACvB,AAAU,IAAM,EAAI,MAAO,CAAE,MAAO,YAAa,CAAC,EAC7C,EAAI,aAAa,SAAS,KAAK,YAAY,CAAG,EAEnD,IAAM,EAAQ,EAAK,QAAQ,CAAc,EACnC,EAAS,EAAK,QAAQ,CAAe,EACrC,EAAO,EAAK,QAAQ,CAAa,EACjC,EAAU,IAAU,GAAU,GAAO,SAAS,CAAc,EAElE,EAAK,EAAK,UAAW,QAAQ,EAC7B,EAAK,EAAK,EAAa,GAAQ,aAAa,CAAW,GAAK,IAAI,EAChE,EAAK,EAAK,EAAa,GAAW,GAAO,aAAa,CAAU,GAAM,IAAI,EAC1E,EAAK,EAAK,EAAW,GAAM,aAAa,CAAS,GAAK,IAAI,EAC1D,EAAU,EAAM,EAAK,EACrB,EAAI,YAAY,EAChB,EAAI,cACF,IAAI,YAAY,mBAAoB,CAClC,QAAS,GACT,SAAU,GACV,OAAQ,CACV,CAAC,CACH,CACF,EAEM,EAAQ,GAAmC,EAC3C,GAAO,OAAS,WAAa,GAAO,MAAQ,YAC5C,GAAQ,GAAK,aAAe,EAAI,SAAS,EAAI,YAAY,EACxD,IAAO,EAAY,KAAK,IAAI,GACjC,aAAa,CAAK,EAClB,EAAO,IAAA,GACT,EAEA,EAAY,cAAiB,CAC3B,EAAG,SAAU,kBAAmB,EAAgB,CAAW,EAC3D,EAAG,SAAU,UAAW,EAAM,CAAW,EACzC,EAAW,SAAU,EAAiB,CACpC,gBAAiB,CAAC,CAAY,EAC9B,WAAY,GACZ,UAAW,GACX,QAAS,EACX,CAAC,CACH,CAAC"}
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { UHTMLComboboxElement } from "@u-elements/u-combobox";
4
4
  import * as UTabs from "@u-elements/u-tabs";
5
5
  export * from "@u-elements/u-datalist";
6
6
  //#region package.json
7
- var version = "1.19.1";
7
+ var version = "1.20.0";
8
8
  //#endregion
9
9
  //#region src/utils/utils.ts
10
10
  const ARIA_DESC = "aria-description";
@@ -212,9 +212,10 @@ const handleClickDelegateFor = (event) => {
212
212
  const target = getComposedTarget(event);
213
213
  const delegateTarget = event.button < 2 && getDelegateTarget(event);
214
214
  if (!delegateTarget || delegateTarget.contains(target)) return;
215
+ for (const el of getComposedPath(target)) if (el.control === delegateTarget) return;
215
216
  if (isNewTab && delegateTarget instanceof HTMLAnchorElement) return window.open(delegateTarget.href, void 0, delegateTarget.rel);
216
217
  event.stopImmediatePropagation();
217
- if (delegateTarget.nodeName !== "LABEL") delegateTarget.click();
218
+ delegateTarget.click();
218
219
  };
219
220
  let HOVER;
220
221
  let TARGET$1;
@@ -280,9 +281,14 @@ onHotReload("dialog", () => [
280
281
  //#region src/fieldset/fieldset.ts
281
282
  const FIELDSETS = isBrowser() ? document.getElementsByTagName("fieldset") : [];
282
283
  const handleFieldsetMutations = () => {
283
- for (const el of FIELDSETS) {
284
- if (el.hasAttribute("aria-labelledby")) continue;
285
- attr(el, ARIA_LABELLEDBY, `${useId(el.querySelector("legend"))} ${useId(el.querySelector(":scope > :is([data-field=\"description\"],legend + p)"))}`.trim() || null);
284
+ for (const fieldset of FIELDSETS) {
285
+ if (fieldset.hasAttribute("aria-labelledby")) continue;
286
+ let labelledby = "";
287
+ for (const el of fieldset.children) {
288
+ const name = el.nodeName;
289
+ if (name === "LEGEND" || el.getAttribute("data-field") === "description" || name === "P" && el.previousElementSibling?.nodeName === "LEGEND") labelledby += `${useId(el)} `;
290
+ }
291
+ attr(fieldset, ARIA_LABELLEDBY, labelledby.trim() || null);
286
292
  }
287
293
  };
288
294
  onHotReload("fieldset", () => [onMutation(document, handleFieldsetMutations, {
@@ -699,7 +705,7 @@ function apply$1() {
699
705
  //#region src/invokers/invokers.ts
700
706
  if (isBrowser() && !isSupported$1()) apply$1();
701
707
  //#endregion
702
- //#region ../../node_modules/.pnpm/@oddbird+popover-polyfill@0.7.1/node_modules/@oddbird/popover-polyfill/dist/popover-fn.js
708
+ //#region ../../node_modules/.pnpm/@oddbird+popover-polyfill@0.7.1_patch_hash=6227913935a153774e0e2569cb889d63767e7a5d572dbc6beeadfb2e77ed5c11/node_modules/@oddbird/popover-polyfill/dist/popover-fn.js
703
709
  var ToggleEvent = class extends Event {
704
710
  oldState;
705
711
  newState;
@@ -1035,7 +1041,7 @@ function setInvokerAriaExpanded(el, force = false) {
1035
1041
  var ShadowRoot2 = globalThis.ShadowRoot || function() {};
1036
1042
  var DEFAULT_LAYER_NAME = "popover-polyfill";
1037
1043
  function isSupported() {
1038
- return typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype === "object" && "popover" in HTMLElement.prototype;
1044
+ return typeof HTMLElement !== "undefined" && typeof HTMLElement.prototype === "object" && "popover" in HTMLElement.prototype && "showPopover" in HTMLElement.prototype;
1039
1045
  }
1040
1046
  function isPolyfilled() {
1041
1047
  var _a;
@@ -1054,7 +1060,7 @@ function hasLayerSupport() {
1054
1060
  function getStyles(layerName) {
1055
1061
  var _a;
1056
1062
  const useLayer = hasLayerSupport();
1057
- const layerNameEscaped = (layerName ?? ((_a = window.POPOVER_POLYFILL_OPTIONS) == null ? void 0 : _a.layerName) ?? DEFAULT_LAYER_NAME).split(".").map(CSS.escape).join(".");
1063
+ const layerNameEscaped = (layerName ?? ((_a = window.POPOVER_POLYFILL_OPTIONS) == null ? void 0 : _a.layerName) ?? DEFAULT_LAYER_NAME).split(".").map((c) => CSS.escape(c)).join(".");
1058
1064
  return `
1059
1065
  ${useLayer ? `@layer ${layerNameEscaped} {` : ""}
1060
1066
  :where([popover]) {
@@ -1128,7 +1134,7 @@ function injectStyles(root, layerName) {
1128
1134
  } catch {
1129
1135
  popoverStyleSheet = false;
1130
1136
  }
1131
- if (popoverStyleSheet === false) {
1137
+ if (popoverStyleSheet === false || !root.adoptedStyleSheets) {
1132
1138
  const sheet = document.createElement("style");
1133
1139
  sheet.textContent = styles;
1134
1140
  if (root instanceof Document) root.head.prepend(sheet);
@@ -1169,6 +1175,7 @@ function apply(opts) {
1169
1175
  showPopover: {
1170
1176
  enumerable: true,
1171
1177
  configurable: true,
1178
+ writable: true,
1172
1179
  value(options = {}) {
1173
1180
  showPopover(this);
1174
1181
  }
@@ -1176,6 +1183,7 @@ function apply(opts) {
1176
1183
  hidePopover: {
1177
1184
  enumerable: true,
1178
1185
  configurable: true,
1186
+ writable: true,
1179
1187
  value() {
1180
1188
  hidePopover(this, true, true);
1181
1189
  }
@@ -1183,6 +1191,7 @@ function apply(opts) {
1183
1191
  togglePopover: {
1184
1192
  enumerable: true,
1185
1193
  configurable: true,
1194
+ writable: true,
1186
1195
  value(options = {}) {
1187
1196
  if (typeof options === "boolean") options = { force: options };
1188
1197
  if (visibilityState.get(this) === "showing" && options.force === void 0 || options.force === false) hidePopover(this, true, true);
@@ -1282,7 +1291,7 @@ function apply(opts) {
1282
1291
  const onKeydown = (event) => {
1283
1292
  const key = event.key;
1284
1293
  const target = event.target;
1285
- if (!event.defaultPrevented && target && (key === "Escape" || key === "Esc")) hideAllPopoversUntil(target.ownerDocument, true, true);
1294
+ if (!event.defaultPrevented && target && (key === "Escape" || key === "Esc")) hideAllPopoversUntil(target.ownerDocument || target, true, true);
1286
1295
  };
1287
1296
  const addEventListeners = (root) => {
1288
1297
  root.addEventListener("click", handleInvokerActivation);
@@ -1321,6 +1330,7 @@ function toggle(el, newState, oldState, source) {
1321
1330
  const shiftProp = placement.match(/left|right/gi) ? "Height" : "Width";
1322
1331
  const shiftLimit = source[`offset${shiftProp}`] / 2 + arrowSize;
1323
1332
  if (placement === "none") return;
1333
+ let sized = false;
1324
1334
  const options = {
1325
1335
  strategy: "absolute",
1326
1336
  placement,
@@ -1336,8 +1346,14 @@ function toggle(el, newState, oldState, source) {
1336
1346
  crossAxis: false
1337
1347
  })] : [],
1338
1348
  ...overscroll ? [size({ apply({ availableHeight }) {
1339
- if (overscroll === "fit") el.style.width = `${source.offsetWidth}px`;
1340
- el.style.maxHeight = `${Math.max(50, availableHeight - padding * 2)}px`;
1349
+ if (sized) return;
1350
+ sized = true;
1351
+ const width = `${source.offsetWidth}px`;
1352
+ const maxHeight = `${Math.max(50, availableHeight - padding * 2)}px`;
1353
+ requestAnimationFrame(() => {
1354
+ if (overscroll === "fit" && el.style.width !== width) el.style.width = width;
1355
+ if (el.style.maxHeight !== maxHeight) el.style.maxHeight = maxHeight;
1356
+ });
1341
1357
  } })] : []
1342
1358
  ]
1343
1359
  };
@@ -1363,40 +1379,62 @@ const handleClick$1 = (e) => {
1363
1379
  if (root instanceof ShadowRoot && !ROOTS.has(root)) ROOTS.set(root, on(root, "toggle", handleToggle, QUICK_EVENT));
1364
1380
  };
1365
1381
  onHotReload("popover", () => {
1366
- const isFunction = (fn) => typeof fn === "function";
1382
+ const descriptors = Object.getOwnPropertyDescriptors(HTMLElement.prototype);
1367
1383
  const togglePopover = HTMLElement.prototype.togglePopover;
1368
1384
  const showPopover = HTMLElement.prototype.showPopover;
1369
1385
  const hidePopover = HTMLElement.prototype.hidePopover;
1370
- if (isFunction(togglePopover)) HTMLElement.prototype.togglePopover = function(opt) {
1371
- const isOpen = this.matches(":popover-open");
1372
- const isBool = typeof opt === "boolean";
1373
- const prev = isOpen ? "open" : "closed";
1374
- const next = (isBool ? opt : opt?.force ?? !isOpen) ? "open" : "closed";
1375
- const source = isBool ? void 0 : opt?.source;
1376
- const result = togglePopover.call(this, opt);
1377
- toggle(this, next, prev, source);
1378
- return result;
1379
- };
1380
- if (isFunction(showPopover)) HTMLElement.prototype.showPopover = function(opt) {
1381
- const prev = this.matches(":popover-open") ? "open" : "closed";
1382
- const result = showPopover.call(this, opt);
1383
- toggle(this, "open", prev, opt?.source);
1384
- return result;
1385
- };
1386
- if (isFunction(hidePopover)) HTMLElement.prototype.hidePopover = function() {
1387
- const prev = this.matches(":popover-open") ? "open" : "closed";
1388
- const result = hidePopover.call(this);
1389
- toggle(this, "closed", prev);
1390
- return result;
1391
- };
1386
+ Object.defineProperties(HTMLElement.prototype, {
1387
+ togglePopover: {
1388
+ ...descriptors.togglePopover,
1389
+ value(opt) {
1390
+ const isOpen = this.matches(":popover-open");
1391
+ const isBool = typeof opt === "boolean";
1392
+ const prev = isOpen ? "open" : "closed";
1393
+ const next = (isBool ? opt : opt?.force ?? !isOpen) ? "open" : "closed";
1394
+ const source = isBool ? void 0 : opt?.source;
1395
+ const result = togglePopover?.call(this, opt);
1396
+ toggle(this, next, prev, source);
1397
+ return result;
1398
+ }
1399
+ },
1400
+ showPopover: {
1401
+ ...descriptors.showPopover,
1402
+ value(opt) {
1403
+ const prev = this.matches(":popover-open") ? "open" : "closed";
1404
+ const result = showPopover?.call(this, opt);
1405
+ toggle(this, "open", prev, opt?.source);
1406
+ return result;
1407
+ }
1408
+ },
1409
+ hidePopover: {
1410
+ ...descriptors.hidePopover,
1411
+ value() {
1412
+ const prev = this.matches(":popover-open") ? "open" : "closed";
1413
+ const result = hidePopover?.call(this);
1414
+ toggle(this, "closed", prev);
1415
+ return result;
1416
+ }
1417
+ }
1418
+ });
1392
1419
  return [
1393
1420
  on(document, "click", handleClick$1, QUICK_EVENT),
1394
1421
  on(document, "pointerdown pointerup scroll", handleScrollbar, QUICK_EVENT),
1395
1422
  on(document, "toggle ds-toggle-source", handleToggle, QUICK_EVENT),
1396
1423
  () => {
1397
- HTMLElement.prototype.togglePopover = togglePopover;
1398
- HTMLElement.prototype.showPopover = showPopover;
1399
- HTMLElement.prototype.hidePopover = hidePopover;
1424
+ Object.defineProperties(HTMLElement.prototype, {
1425
+ togglePopover: {
1426
+ ...descriptors.togglePopover,
1427
+ value: togglePopover
1428
+ },
1429
+ showPopover: {
1430
+ ...descriptors.showPopover,
1431
+ value: showPopover
1432
+ },
1433
+ hidePopover: {
1434
+ ...descriptors.hidePopover,
1435
+ value: hidePopover
1436
+ }
1437
+ });
1400
1438
  for (const [, off] of ROOTS) off();
1401
1439
  ROOTS.clear();
1402
1440
  }
@@ -1688,9 +1726,12 @@ const render$2 = (self) => {
1688
1726
  customElements.define("ds-error-summary", DSErrorSummaryElement);
1689
1727
  //#endregion
1690
1728
  //#region src/field/field.ts
1729
+ const ATTR_FIELD = "data-field";
1730
+ const ATTR_LIMIT = "data-limit";
1691
1731
  const ATTR_INVALID = "aria-invalid";
1692
1732
  const ATTR_DESCRIBEDBY = "aria-describedby";
1693
1733
  const ATTR_INDETERMINATE = "data-indeterminate";
1734
+ const SELECTOR_VALIDATION = `:scope > [${ATTR_FIELD}="validation"]`;
1694
1735
  const COUNTER_DEBOUNCE = isWindows() ? 800 : 200;
1695
1736
  const WARNING_MULTIPLE_INPUTS = `Fields should only have one input element. Use <fieldset> to group multiple fields:`;
1696
1737
  const handleFieldMutation = (field) => {
@@ -1706,22 +1747,22 @@ const handleFieldMutation = (field) => {
1706
1747
  if (isInputLike(el)) if (field._input?.isConnected && field._input !== el) warn(WARNING_MULTIPLE_INPUTS, field);
1707
1748
  else field._input = el;
1708
1749
  else {
1709
- const type = el.getAttribute("data-field");
1750
+ const type = el.getAttribute(ATTR_FIELD);
1710
1751
  if (type === "counter") field._counter = el;
1711
1752
  if (type === "validation") {
1712
1753
  nextDescs.unshift(useId(el));
1713
1754
  hasValidation = true;
1714
- invalid = invalid || isInvalid(el);
1755
+ invalid = invalid || isInvalidColor(el);
1715
1756
  } else if (type) nextDescs.push(useId(el));
1716
1757
  }
1717
1758
  }
1718
1759
  if (!field._input?.isConnected) return;
1719
1760
  const input = field._input;
1720
1761
  for (const label of labels) attr(label, "for", useId(input) || null);
1721
- const fieldsetValidation = field.closest("fieldset")?.querySelector(":scope > [data-field=\"validation\"]");
1762
+ const fieldsetValidation = field.closest("fieldset")?.querySelector(SELECTOR_VALIDATION);
1722
1763
  if (fieldsetValidation && !fieldsetValidation?.hidden) {
1723
1764
  hasValidation = true;
1724
- invalid = invalid || isInvalid(fieldsetValidation);
1765
+ invalid = invalid || isInvalidColor(fieldsetValidation);
1725
1766
  nextDescs.unshift(useId(fieldsetValidation));
1726
1767
  }
1727
1768
  const indeterminate = attr(input, ATTR_INDETERMINATE);
@@ -1732,7 +1773,7 @@ const handleFieldMutation = (field) => {
1732
1773
  field._descs = nextDescs;
1733
1774
  const hadValidation = field._validation;
1734
1775
  if (hasValidation && !hadValidation && !input.hasAttribute(ATTR_INVALID)) {
1735
- attr(input, ATTR_INVALID, "true");
1776
+ attr(input, ATTR_INVALID, invalid ? "true" : null);
1736
1777
  field._validation = true;
1737
1778
  } else if (!hasValidation && hadValidation) {
1738
1779
  attr(input, ATTR_INVALID, null);
@@ -1745,9 +1786,12 @@ const TEXTS = {
1745
1786
  under: "%d tegn igjen"
1746
1787
  };
1747
1788
  const debouncedCounterLiveRegion = debounce((input, text) => {
1748
- if (document.activeElement === input) announce(text);
1789
+ if (document?.activeElement === input) announce(text);
1749
1790
  }, COUNTER_DEBOUNCE);
1750
- const isInvalid = (el) => el.getAttribute("data-color") !== "success";
1791
+ const isInvalidColor = (el) => {
1792
+ const color = el.getAttribute("data-color");
1793
+ return !color || color === "danger";
1794
+ };
1751
1795
  const isInputLike = (el) => el instanceof HTMLElement && "validity" in el && !(el instanceof HTMLButtonElement) && el.type !== "hidden";
1752
1796
  var DSFieldElement = class extends DSElement {
1753
1797
  _counter;
@@ -1760,8 +1804,8 @@ var DSFieldElement = class extends DSElement {
1760
1804
  this._unevents = on(this, "input", this, QUICK_EVENT);
1761
1805
  this._unmutate = onMutation(this, () => handleFieldMutation(this), {
1762
1806
  attributeFilter: [
1763
- "data-field",
1764
- "data-limit",
1807
+ ATTR_FIELD,
1808
+ ATTR_LIMIT,
1765
1809
  "hidden",
1766
1810
  "id",
1767
1811
  "value",
@@ -1775,13 +1819,14 @@ var DSFieldElement = class extends DSElement {
1775
1819
  handleEvent(event) {
1776
1820
  const { _counter, _input } = this;
1777
1821
  if (_counter?.isConnected && _input) {
1778
- const count = (Number(attr(_counter, "data-limit")) || 0) - _input.value.length;
1822
+ const count = (Number(attr(_counter, ATTR_LIMIT)) || 0) - _input.value.length;
1779
1823
  const state = count < 0 ? "over" : "under";
1780
1824
  const label = (attrOrCSS(_counter, `data-${state}`) || TEXTS[state])?.replace("%d", `${Math.abs(count)}`);
1781
1825
  if (!label) warn(`Missing data-${state} on:`, _counter);
1782
1826
  attr(_counter, "data-label", label);
1783
1827
  attr(_counter, "data-state", state);
1784
1828
  attr(_counter, "data-color", count < 0 ? "danger" : null);
1829
+ attr(_input, "aria-invalid", count < 0 ? "true" : null);
1785
1830
  if (event?.type === "input" && label) debouncedCounterLiveRegion(_input, label);
1786
1831
  }
1787
1832
  if (_input instanceof HTMLTextAreaElement) {
@@ -1902,14 +1947,12 @@ var DSSuggestionElement = class extends UHTMLComboboxElement {
1902
1947
  const render = (self) => {
1903
1948
  const { control, list } = self;
1904
1949
  const datalist = list || self.querySelector("u-datalist");
1905
- if (control) {
1906
- attr(control, "popovertarget", list ? useId(list) : null);
1907
- if (!control.placeholder) attr(control, "placeholder", " ");
1908
- }
1950
+ if (control) attr(control, "popovertarget", list ? useId(list) : null);
1909
1951
  if (datalist) {
1910
1952
  attr(datalist, "popover", "manual");
1911
1953
  attr(datalist, "data-is-floating", "true");
1912
1954
  }
1955
+ handleEmpty({ currentTarget: self });
1913
1956
  };
1914
1957
  const handleEmpty = ({ currentTarget: self }) => {
1915
1958
  const { creatable, control, options } = self;