@digdir/designsystemet-web 1.21.0 → 1.22.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 (32) hide show
  1. package/dist/cjs/_vendors/@oddbird/popover-polyfill/dist/popover-fn.cjs +1 -1
  2. package/dist/cjs/_vendors/@oddbird/popover-polyfill/dist/popover-fn.cjs.map +1 -1
  3. package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -1
  4. package/dist/cjs/focusgroup/focusgroup.cjs +1 -1
  5. package/dist/cjs/focusgroup/focusgroup.cjs.map +1 -1
  6. package/dist/cjs/index.cjs +1 -1
  7. package/dist/cjs/packages/web/package.cjs +1 -1
  8. package/dist/cjs/suggestion/suggestion.cjs +1 -1
  9. package/dist/cjs/suggestion/suggestion.cjs.map +1 -1
  10. package/dist/cjs/toggle-group/toggle-group.cjs.map +1 -1
  11. package/dist/cjs/tooltip/tooltip.cjs +1 -1
  12. package/dist/cjs/tooltip/tooltip.cjs.map +1 -1
  13. package/dist/cjs/utils/utils.cjs +1 -1
  14. package/dist/cjs/utils/utils.cjs.map +1 -1
  15. package/dist/custom-elements.json +8 -8
  16. package/dist/esm/_vendors/@oddbird/popover-polyfill/dist/popover-fn.js +1 -1
  17. package/dist/esm/_vendors/@oddbird/popover-polyfill/dist/popover-fn.js.map +1 -1
  18. package/dist/esm/focusgroup/focusgroup.js +1 -1
  19. package/dist/esm/focusgroup/focusgroup.js.map +1 -1
  20. package/dist/esm/packages/web/package.js +1 -1
  21. package/dist/esm/suggestion/suggestion.js +1 -1
  22. package/dist/esm/suggestion/suggestion.js.map +1 -1
  23. package/dist/esm/toggle-group/toggle-group.js.map +1 -1
  24. package/dist/esm/tooltip/tooltip.js +1 -1
  25. package/dist/esm/tooltip/tooltip.js.map +1 -1
  26. package/dist/esm/utils/utils.js +1 -1
  27. package/dist/esm/utils/utils.js.map +1 -1
  28. package/dist/index.d.ts +6 -6
  29. package/dist/index.js +49 -33
  30. package/dist/umd/index.js +8 -8
  31. package/dist/umd/index.js.map +1 -1
  32. package/package.json +7 -7
@@ -1,2 +1,2 @@
1
- import{version as e}from"../packages/web/package.js";const t=`aria-description`,n=`aria-label`,r=`aria-labelledby`,i={passive:!0,capture:!0},a=()=>typeof window<`u`&&typeof document<`u`,o=()=>a()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),s=typeof HTMLElement>`u`?class{}:HTMLElement;function c(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const l=(e,...t)=>!a()||window.dsWarnings===!1||console.log(`\x1B[1mDesignsystemet:\x1B[m ${e}`,...t),u=(e,t,n)=>n===void 0?e.getAttribute(t):(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),d=(e,t)=>getComputedStyle(e).getPropertyValue(t).trim(),f=/^["']|["']$/g,p=(e,t)=>{let n=u(e,t);return n||=d(e,`--_ds-${t}`).replace(f,``).trim(),n||null},m=e=>{let t=e?.getRootNode?.()||e?.ownerDocument;return t instanceof Document||t instanceof ShadowRoot?t:e?.ownerDocument||document},h=e=>{let t=e.target?.shadowRoot&&e.composedPath()[0]||e.target;return t?.nodeType===1?t:null},g=e=>{let t=new Set;for(;e;)t.add(e),e=e.nodeType===11?e.host:e.assignedSlot||e.parentNode;return t},_=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.addEventListener(t,...r);return()=>v(e,...t)},v=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.removeEventListener(t,...r)},y=`_dsHotReloadCleanup${e}`,b=(e,t)=>{if(a()){window[y]||(window[y]=new Map);for(let t of window[y]?.get(e)||[])t();window[y]?.set(e,t())}},x=(e,t,n)=>{let r=()=>i.disconnect(),i=new MutationObserver(n=>{if(!a()||!e.isConnected)return r();t(e,n)});return t(e),i.observe(e,n),r},S=(e,t)=>{let n=document.createElement(e);if(t)for(let[e,r]of Object.entries(t))u(n,e,r);return n},C={define:(e,t)=>!a()||window.customElements.get(e)||window.customElements.define(e,t)};let w=0;function T(e){return a()?(window.dsUseId||(window.dsUseId=0),e&&!e.id&&(e.id=`:ds:${++window.dsUseId}`),e?.id||``):`:ds:${++w}`}let E,D=0,O=0;const k=e=>{clearTimeout(O),E&&(E.textContent=`${e}${D++%2?`\xA0`:``}`),e&&(O=setTimeout(k,2e3,``))},A=()=>{document.readyState===`complete`&&(E||(E=S(`div`,{"aria-live":`assertive`}),E.style.overflow=`hidden`,E.style.position=`fixed`,E.style.whiteSpace=`nowrap`,E.style.width=`1px`),E.isConnected||document.body.appendChild(E))};b(`announce`,()=>[_(document,`focus mouseover`,A,i)]);export{t as ARIA_DESC,n as ARIA_LABEL,r as ARIA_LABELLEDBY,s as DSElement,i as QUICK_EVENT,k as announce,u as attr,p as attrOrCSS,C as customElements,c as debounce,d as getCSSProp,g as getComposedPath,h as getComposedTarget,m as getRoot,a as isBrowser,o as isWindows,v as off,_ as on,b as onHotReload,x as onMutation,S as tag,T as useId,l as warn};
1
+ import{version as e}from"../packages/web/package.js";const t=`aria-description`,n=`aria-label`,r=`aria-labelledby`,i={passive:!0,capture:!0},a=()=>typeof window<`u`&&typeof document<`u`,o=()=>a()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),s=typeof HTMLElement>`u`?class{}:HTMLElement;function c(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const l=(e,...t)=>!a()||window.dsWarnings===!1||console.log(`\x1B[1mDesignsystemet:\x1B[m ${e}`,...t),u=(e,t,n)=>n===void 0?e.getAttribute(t):(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),d=(e,t)=>getComputedStyle(e).getPropertyValue(t).trim(),f=/^["']|["']$/g,p=/\\(["'])/g,m=(e,t)=>{let n=u(e,t);return n||=d(e,`--_ds-${t}`).replace(f,``).replace(p,`$1`).trim(),n||null},h=e=>{let t=e?.getRootNode?.()||e?.ownerDocument;return t instanceof Document||t instanceof ShadowRoot?t:e?.ownerDocument||document},g=e=>{let t=e.target?.shadowRoot&&e.composedPath()[0]||e.target;return t?.nodeType===1?t:null},_=e=>{let t=new Set;for(;e;)t.add(e),e=e.nodeType===11?e.host:e.assignedSlot||e.parentNode;return t},v=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.addEventListener(t,...r);return()=>y(e,...t)},y=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.removeEventListener(t,...r)},b=`_dsHotReloadCleanup${e}`,x=(e,t)=>{if(a()){window[b]||(window[b]=new Map);for(let t of window[b]?.get(e)||[])t();window[b]?.set(e,t())}},S=(e,t,n)=>{let r=()=>i.disconnect(),i=new MutationObserver(n=>{if(!a()||!e.isConnected)return r();t(e,n)});return t(e),i.observe(e,n),r},C=(e,t)=>{let n=document.createElement(e);if(t)for(let[e,r]of Object.entries(t))u(n,e,r);return n},w={define:(e,t)=>!a()||window.customElements.get(e)||window.customElements.define(e,t)};let T=0;function E(e){return a()?(window.dsUseId||(window.dsUseId=0),e&&!e.id&&(e.id=`:ds:${++window.dsUseId}`),e?.id||``):`:ds:${++T}`}let D,O=0,k=0;const A=e=>{clearTimeout(k),D&&(D.textContent=`${e}${O++%2?`\xA0`:``}`),e&&(k=setTimeout(A,2e3,``))},j=()=>{document.readyState===`complete`&&(D||(D=C(`div`,{"aria-live":`assertive`}),D.style.overflow=`hidden`,D.style.position=`fixed`,D.style.whiteSpace=`nowrap`,D.style.width=`1px`),D.isConnected||document.body.appendChild(D))};x(`announce`,()=>[v(document,`focus mouseover`,j,i)]);export{t as ARIA_DESC,n as ARIA_LABEL,r as ARIA_LABELLEDBY,s as DSElement,i as QUICK_EVENT,A as announce,u as attr,m as attrOrCSS,w as customElements,c as debounce,d as getCSSProp,_ as getComposedPath,g as getComposedTarget,h as getRoot,a as isBrowser,o as isWindows,y as off,v as on,x as onHotReload,S as onMutation,C as tag,E as useId,l 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 ARIA_DESC = 'aria-description';\nexport const ARIA_LABEL = 'aria-label';\nexport const ARIA_LABELLEDBY = 'aria-labelledby';\nexport const QUICK_EVENT = { passive: true, capture: true };\n\nimport { version } from '../../package.json' with { type: 'json' };\n\n// Using function instead of constant to support environments 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.log, 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.log> // Using console.log, not console.warn, to prevent stopping test runners such as Jest\n) =>\n !isBrowser() ||\n window.dsWarnings === false ||\n console.log(`\\x1B[1mDesignsystemet:\\x1B[m ${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);\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 * getCSSProp\n * @description Retrieves and CSS property value and trims it\n * @param el The Element to read attributes/CSS from\n * @param name Attribute or CSS property to get\n * @return string CSS property value\n */\nexport const getCSSProp = (el: Element, prop: string) =>\n getComputedStyle(el).getPropertyValue(prop).trim();\n\nconst STRIP_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 value = getCSSProp(el, `--_ds-${name}`).replace(STRIP_QUOTES, '').trim();\n return value || null;\n};\n\n/**\n * getRoot\n * @description Helper for better compatibility\n * @param node The target node\n * @return The shadow root or document\n */\nexport const getRoot = (node?: Node | null): Document | ShadowRoot => {\n const root = node?.getRootNode?.() || node?.ownerDocument;\n if (root instanceof Document || root instanceof ShadowRoot) return root;\n return node?.ownerDocument || document;\n};\n\n/**\n * getComposedTarget\n * @description Helper to get the composed target of an event, supporting ShadowDOM rendering\n * @param event The event\n * @return The target Element\n */\nexport const getComposedTarget = (e: Event): Element | null => {\n const el =\n ((e.target as Element)?.shadowRoot && e.composedPath()[0]) || e.target; // Only use composedPath if the target has a shadowRoot to boost performance\n return (el as Node)?.nodeType === 1 ? (el as Element) : null;\n};\n\n/**\n * getComposedPath\n * @description Helper to get the full composed path even if listener is bound to a ShadowRoot (unlike event.composedPath())\n * @param el The element to start traversing from\n * @return Set of nodes in the path\n */\nexport const getComposedPath = (el: Node | null) => {\n const path = new Set<Node>();\n while (el) {\n path.add(el);\n if (el.nodeType === 11) el = (el as ShadowRoot).host;\n else el = (el as Element).assignedSlot || el.parentNode;\n }\n return path;\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\nconst HOT_RELOAD_KEY = `_dsHotReloadCleanup${version}` as const; // Ensure multiple versions of Designsystemet can run on same page\ndeclare global {\n interface Window {\n [HOT_RELOAD_KEY]?: 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[HOT_RELOAD_KEY]) window[HOT_RELOAD_KEY] = new Map(); // Hot reload cleanup support supporting all build tools\n for (const cleanup of window[HOT_RELOAD_KEY]?.get(key) || []) cleanup(); // Run previous cleanup\n window[HOT_RELOAD_KEY]?.set(key, setup()); // Store new cleanup\n};\n\n/**\n * MutationObserver wrapper with automatic cleanup\n * @return new MutaionObserver\n */\nexport const onMutation = <T extends Node>(\n el: T,\n callback: (el: T, records?: MutationRecord[]) => void,\n options: MutationObserverInit,\n) => {\n const cleanup = () => observer.disconnect();\n const observer = new MutationObserver((records) => {\n if (!isBrowser() || !el.isConnected) return cleanup(); // Stop observing if element is removed from DOM or document is removed by jdsom tests\n callback(el, records);\n });\n\n callback(el); // Initial is run instantly to make test markup predictable\n observer.observe(el, options);\n return cleanup;\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 */\ndeclare global {\n interface Window {\n dsUseId?: number; // Use a global counter to ensure this works even when loading designsystemet multiple times\n }\n}\nlet id = 0;\nexport function useId(el?: Element | null) {\n if (!isBrowser()) return `:ds:${++id}`; // Emulate browser environment if window not available\n if (!window.dsUseId) window.dsUseId = 0; // Make sure we have a global to support multiple instances of designsystemet in same page\n if (el && !el.id) el.id = `:ds:${++window.dsUseId}`;\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) LIVE_EL.textContent = `${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":"qDAAA,MAAa,EAAY,mBACZ,EAAa,aACb,EAAkB,kBAClB,EAAc,CAAE,QAAS,GAAM,QAAS,EAAK,EAK7C,MACX,OAAO,OAAW,KAAe,OAAO,SAAa,IAE1C,MACX,EAAU,GAEV,QAAQ,KAAK,UAAU,eAAe,UAAY,UAAU,QAAQ,EAGzD,EACX,OAAO,YAAgB,IAClB,KAAM,CAAC,EACR,YAEN,SAAgB,EACd,EACA,EACA,CACA,IAAI,EAEJ,OAAO,SAAyB,GAAG,EAAS,CAC1C,aAAa,CAAK,EAClB,EAAQ,eAAiB,EAAS,MAAM,KAAM,CAAI,EAAG,CAAK,CAC5D,CACF,CAWA,MAAa,GACX,EACA,GAAG,IAEH,CAAC,EAAU,GACX,OAAO,aAAe,IACtB,QAAQ,IAAI,gCAAgC,IAAW,GAAG,CAAI,EASnD,GACX,EACA,EACA,IAEI,IAAU,IAAA,GAAkB,EAAG,aAAa,CAAI,GAChD,IAAU,KAAM,EAAG,gBAAgB,CAAI,EAClC,EAAG,aAAa,CAAI,IAAM,GAAO,EAAG,aAAa,EAAM,CAAK,EAC9D,MAUI,GAAc,EAAa,IACtC,iBAAiB,CAAE,CAAC,CAAC,iBAAiB,CAAI,CAAC,CAAC,KAAK,EAE7C,EAAe,eAQR,GAAa,EAAa,IAAiB,CACtD,IAAI,EAAQ,EAAK,EAAI,CAAI,EAGzB,MAFA,CACE,IAAQ,EAAW,EAAI,SAAS,GAAM,CAAC,CAAC,QAAQ,EAAc,EAAE,CAAC,CAAC,KAAK,EAClE,GAAS,IAClB,EAQa,EAAW,GAA8C,CACpE,IAAM,EAAO,GAAM,cAAc,GAAK,GAAM,cAE5C,OADI,aAAgB,UAAY,aAAgB,WAAmB,EAC5D,GAAM,eAAiB,QAChC,EAQa,EAAqB,GAA6B,CAC7D,IAAM,EACF,EAAE,QAAoB,YAAc,EAAE,aAAa,CAAC,CAAC,IAAO,EAAE,OAClE,OAAQ,GAAa,WAAa,EAAK,EAAiB,IAC1D,EAQa,EAAmB,GAAoB,CAClD,IAAM,EAAO,IAAI,IACjB,KAAO,GACL,EAAK,IAAI,CAAE,EACX,AACK,EADD,EAAG,WAAa,GAAU,EAAkB,KACrC,EAAe,cAAgB,EAAG,WAE/C,OAAO,CACT,EAQa,GACX,EACA,GAAG,IACc,CACjB,GAAM,CAAC,EAAO,GAAG,GAAW,EAC5B,IAAK,IAAM,KAAQ,EAAM,MAAM,GAAG,EAAG,EAAG,iBAAiB,EAAM,GAAG,CAAO,EACzE,UAAa,EAAI,EAAI,GAAG,CAAI,CAC9B,EAQa,GACX,EACA,GAAG,IACM,CACT,GAAM,CAAC,EAAO,GAAG,GAAW,EAC5B,IAAK,IAAM,KAAQ,EAAM,MAAM,GAAG,EAAG,EAAG,oBAAoB,EAAM,GAAG,CAAO,CAC9E,EAGM,EAAiB,sBAAsB,IAahC,GAAe,EAAa,IAAmC,CACrE,KAAU,EACf,CAAK,OAAO,KAAiB,OAAO,GAAkB,IAAI,KAC1D,IAAK,IAAM,KAAW,OAAO,EAAe,EAAE,IAAI,CAAG,GAAK,CAAC,EAAG,EAAQ,EACtE,OAAO,EAAe,EAAE,IAAI,EAAK,EAAM,CAAC,CAFsB,CAGhE,EAMa,GACX,EACA,EACA,IACG,CACH,IAAM,MAAgB,EAAS,WAAW,EACpC,EAAW,IAAI,iBAAkB,GAAY,CACjD,GAAI,CAAC,EAAU,GAAK,CAAC,EAAG,YAAa,OAAO,EAAQ,EACpD,EAAS,EAAI,CAAO,CACtB,CAAC,EAID,OAFA,EAAS,CAAE,EACX,EAAS,QAAQ,EAAI,CAAO,EACrB,CACT,EAUa,GACX,EACA,IACmC,CACnC,IAAM,EAAK,SAAS,cAAc,CAAO,EACzC,GAAI,EAAO,IAAK,GAAM,CAAC,EAAK,KAAQ,OAAO,QAAQ,CAAK,EAAG,EAAK,EAAI,EAAK,CAAG,EAC5E,OAAO,CACT,EAOa,EAAiB,CAC5B,QAAS,EAAc,IACrB,CAAC,EAAU,GACX,OAAO,eAAe,IAAI,CAAI,GAC9B,OAAO,eAAe,OAAO,EAAM,CAAQ,CAC/C,EAWA,IAAI,EAAK,EACT,SAAgB,EAAM,EAAqB,CAIzC,OAHK,EAAU,GACV,OAAO,UAAS,OAAO,QAAU,GAClC,GAAM,CAAC,EAAG,KAAI,EAAG,GAAK,OAAO,EAAE,OAAO,WACnC,GAAI,IAAM,IAHQ,OAAO,EAAE,GAIpC,CAMA,IAAI,EACA,EAAW,EACX,EAAqD,EACzD,MAAa,EAAY,GAAiB,CACxC,aAAa,CAAU,EACnB,IAAS,EAAQ,YAAc,GAAG,IAAO,IAAa,EAAI,OAAW,MACrE,IAAM,EAAa,WAAW,EAAU,IAAM,EAAE,EACtD,EAGM,MAAsB,CACtB,SAAS,aAAe,aACvB,IACH,EAAU,EAAI,MAAO,CAAE,YAAa,WAAY,CAAC,EACjD,EAAQ,MAAM,SAAW,SACzB,EAAQ,MAAM,SAAW,QACzB,EAAQ,MAAM,WAAa,SAC3B,EAAQ,MAAM,MAAQ,OAEnB,EAAQ,aAAa,SAAS,KAAK,YAAY,CAAO,EAC7D,EACA,EAAY,eAAkB,CAC5B,EAAG,SAAU,kBAAmB,EAAe,CAAW,CAC5D,CAAC"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/utils/utils.ts"],"sourcesContent":["export const ARIA_DESC = 'aria-description';\nexport const ARIA_LABEL = 'aria-label';\nexport const ARIA_LABELLEDBY = 'aria-labelledby';\nexport const QUICK_EVENT = { passive: true, capture: true };\n\nimport { version } from '../../package.json' with { type: 'json' };\n\n// Using function instead of constant to support environments 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.log, 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.log> // Using console.log, not console.warn, to prevent stopping test runners such as Jest\n) =>\n !isBrowser() ||\n window.dsWarnings === false ||\n console.log(`\\x1B[1mDesignsystemet:\\x1B[m ${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);\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 * getCSSProp\n * @description Retrieves and CSS property value and trims it\n * @param el The Element to read attributes/CSS from\n * @param name Attribute or CSS property to get\n * @return string CSS property value\n */\nexport const getCSSProp = (el: Element, prop: string) =>\n getComputedStyle(el).getPropertyValue(prop).trim();\n\nconst QUOTES_STRIP_SURROUNDING = /^[\"']|[\"']$/g; // Matches surrounding single or double quotes\nconst QUOTES_MATCH = /\\\\([\"'])/g;\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 */\nexport const attrOrCSS = (el: Element, name: string) => {\n let value = attr(el, name);\n if (!value)\n value = getCSSProp(el, `--_ds-${name}`)\n .replace(QUOTES_STRIP_SURROUNDING, '')\n .replace(QUOTES_MATCH, '$1') // Unescape quotes\n .trim();\n return value || null;\n};\n\n/**\n * getRoot\n * @description Helper for better compatibility\n * @param node The target node\n * @return The shadow root or document\n */\nexport const getRoot = (node?: Node | null): Document | ShadowRoot => {\n const root = node?.getRootNode?.() || node?.ownerDocument;\n if (root instanceof Document || root instanceof ShadowRoot) return root;\n return node?.ownerDocument || document;\n};\n\n/**\n * getComposedTarget\n * @description Helper to get the composed target of an event, supporting ShadowDOM rendering\n * @param event The event\n * @return The target Element\n */\nexport const getComposedTarget = (e: Event): Element | null => {\n const el =\n ((e.target as Element)?.shadowRoot && e.composedPath()[0]) || e.target; // Only use composedPath if the target has a shadowRoot to boost performance\n return (el as Node)?.nodeType === 1 ? (el as Element) : null;\n};\n\n/**\n * getComposedPath\n * @description Helper to get the full composed path even if listener is bound to a ShadowRoot (unlike event.composedPath())\n * @param el The element to start traversing from\n * @return Set of nodes in the path\n */\nexport const getComposedPath = (el: Node | null) => {\n const path = new Set<Node>();\n while (el) {\n path.add(el);\n if (el.nodeType === 11) el = (el as ShadowRoot).host;\n else el = (el as Element).assignedSlot || el.parentNode;\n }\n return path;\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\nconst HOT_RELOAD_KEY = `_dsHotReloadCleanup${version}` as const; // Ensure multiple versions of Designsystemet can run on same page\ndeclare global {\n interface Window {\n [HOT_RELOAD_KEY]?: 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[HOT_RELOAD_KEY]) window[HOT_RELOAD_KEY] = new Map(); // Hot reload cleanup support supporting all build tools\n for (const cleanup of window[HOT_RELOAD_KEY]?.get(key) || []) cleanup(); // Run previous cleanup\n window[HOT_RELOAD_KEY]?.set(key, setup()); // Store new cleanup\n};\n\n/**\n * MutationObserver wrapper with automatic cleanup\n * @return new MutaionObserver\n */\nexport const onMutation = <T extends Node>(\n el: T,\n callback: (el: T, records?: MutationRecord[]) => void,\n options: MutationObserverInit,\n) => {\n const cleanup = () => observer.disconnect();\n const observer = new MutationObserver((records) => {\n if (!isBrowser() || !el.isConnected) return cleanup(); // Stop observing if element is removed from DOM or document is removed by jdsom tests\n callback(el, records);\n });\n\n callback(el); // Initial is run instantly to make test markup predictable\n observer.observe(el, options);\n return cleanup;\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 */\ndeclare global {\n interface Window {\n dsUseId?: number; // Use a global counter to ensure this works even when loading designsystemet multiple times\n }\n}\nlet id = 0;\nexport function useId(el?: Element | null) {\n if (!isBrowser()) return `:ds:${++id}`; // Emulate browser environment if window not available\n if (!window.dsUseId) window.dsUseId = 0; // Make sure we have a global to support multiple instances of designsystemet in same page\n if (el && !el.id) el.id = `:ds:${++window.dsUseId}`;\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) LIVE_EL.textContent = `${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.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ import type * as PreactTypes from 'preact'
2
+ import type * as ReactTypes from 'react'
3
+ import type * as SvelteTypes from 'svelte/elements'
4
+ import type * as VueJSX from '@vue/runtime-dom'
5
+ import type { JSX as QwikJSX } from '@builder.io/qwik/jsx-runtime'
6
+ import type { JSX as SolidJSX } from 'solid-js'
1
7
  import "@u-elements/u-details/polyfill";
2
8
  import { UHTMLComboboxElement } from "@u-elements/u-combobox";
3
9
  import * as UTabs from "@u-elements/u-tabs";
@@ -157,7 +163,6 @@ declare class DSTabPanelElement extends UTabs.UHTMLTabPanelElement {}
157
163
  //#endregion
158
164
  export { DSBreadcrumbsElement, DSErrorSummaryElement, DSFieldElement, DSPaginationElement, DSSuggestionElement, DSTabElement, DSTabListElement, DSTabPanelElement, DSTabsElement, initTooltips, pagination, setTooltipElement };
159
165
 
160
-
161
166
  export type PreactDstabs = PreactTypes.JSX.HTMLAttributes<DSTabsElement> & { }
162
167
  export type ReactDstabs = ReactTypes.DetailedHTMLProps<ReactTypes.HTMLAttributes<DSTabsElement>, DSTabsElement> & { class?: string }
163
168
  export type QwikDstabs = QwikJSX.IntrinsicElements['div'] & { class?: string }
@@ -235,7 +240,6 @@ declare module 'solid-js' {
235
240
  }
236
241
  }
237
242
 
238
-
239
243
  export type PreactDssuggestion = PreactTypes.JSX.HTMLAttributes<DSSuggestionElement> & { }
240
244
  export type ReactDssuggestion = ReactTypes.DetailedHTMLProps<ReactTypes.HTMLAttributes<DSSuggestionElement>, DSSuggestionElement> & { class?: string }
241
245
  export type QwikDssuggestion = QwikJSX.IntrinsicElements['div'] & { class?: string }
@@ -256,7 +260,6 @@ declare module 'solid-js' {
256
260
  }
257
261
  }
258
262
 
259
-
260
263
  export type PreactDspagination = PreactTypes.JSX.HTMLAttributes<DSPaginationElement> & { }
261
264
  export type ReactDspagination = ReactTypes.DetailedHTMLProps<ReactTypes.HTMLAttributes<DSPaginationElement>, DSPaginationElement> & { class?: string }
262
265
  export type QwikDspagination = QwikJSX.IntrinsicElements['div'] & { class?: string }
@@ -277,7 +280,6 @@ declare module 'solid-js' {
277
280
  }
278
281
  }
279
282
 
280
-
281
283
  export type PreactDsfield = PreactTypes.JSX.HTMLAttributes<DSFieldElement> & { }
282
284
  export type ReactDsfield = ReactTypes.DetailedHTMLProps<ReactTypes.HTMLAttributes<DSFieldElement>, DSFieldElement> & { class?: string }
283
285
  export type QwikDsfield = QwikJSX.IntrinsicElements['div'] & { class?: string }
@@ -298,7 +300,6 @@ declare module 'solid-js' {
298
300
  }
299
301
  }
300
302
 
301
-
302
303
  export type PreactDserrorsummary = PreactTypes.JSX.HTMLAttributes<DSErrorSummaryElement> & { }
303
304
  export type ReactDserrorsummary = ReactTypes.DetailedHTMLProps<ReactTypes.HTMLAttributes<DSErrorSummaryElement>, DSErrorSummaryElement> & { class?: string }
304
305
  export type QwikDserrorsummary = QwikJSX.IntrinsicElements['div'] & { class?: string }
@@ -319,7 +320,6 @@ declare module 'solid-js' {
319
320
  }
320
321
  }
321
322
 
322
-
323
323
  export type PreactDsbreadcrumbs = PreactTypes.JSX.HTMLAttributes<DSBreadcrumbsElement> & { }
324
324
  export type ReactDsbreadcrumbs = ReactTypes.DetailedHTMLProps<ReactTypes.HTMLAttributes<DSBreadcrumbsElement>, DSBreadcrumbsElement> & { class?: string }
325
325
  export type QwikDsbreadcrumbs = QwikJSX.IntrinsicElements['div'] & { class?: string }
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.21.0";
7
+ var version = "1.22.0";
8
8
  //#endregion
9
9
  //#region src/utils/utils.ts
10
10
  const ARIA_DESC = "aria-description";
@@ -46,7 +46,8 @@ const attr = (el, name, value) => {
46
46
  * @return string CSS property value
47
47
  */
48
48
  const getCSSProp = (el, prop) => getComputedStyle(el).getPropertyValue(prop).trim();
49
- const STRIP_QUOTES = /^["']|["']$/g;
49
+ const QUOTES_STRIP_SURROUNDING = /^["']|["']$/g;
50
+ const QUOTES_MATCH = /\\(["'])/g;
50
51
  /**
51
52
  * attrOrCSS
52
53
  * @description Retrieves and updates attribute based on attribute or CSS property value
@@ -56,7 +57,7 @@ const STRIP_QUOTES = /^["']|["']$/g;
56
57
  */
57
58
  const attrOrCSS = (el, name) => {
58
59
  let value = attr(el, name);
59
- if (!value) value = getCSSProp(el, `--_ds-${name}`).replace(STRIP_QUOTES, "").trim();
60
+ if (!value) value = getCSSProp(el, `--_ds-${name}`).replace(QUOTES_STRIP_SURROUNDING, "").replace(QUOTES_MATCH, "$1").trim();
60
61
  return value || null;
61
62
  };
62
63
  /**
@@ -373,7 +374,10 @@ const handleKeydown$2 = (e) => {
373
374
  if (!group?.role) return;
374
375
  const items = getItems(group.el, target);
375
376
  let next = 0;
376
- if (isTab) return setTimeout(setTab, 0, items, setTab(items));
377
+ if (isTab) {
378
+ const others = items.filter((item) => item !== target);
379
+ return setTimeout(setTab, 0, others, setTab(others));
380
+ }
377
381
  if (e.key === "Home") next = 0;
378
382
  else if (e.key === "End") next = items.length - 1;
379
383
  else {
@@ -699,7 +703,7 @@ function apply$1() {
699
703
  //#region src/invokers/invokers.ts
700
704
  if (isBrowser() && !isSupported$1()) apply$1();
701
705
  //#endregion
702
- //#region ../../node_modules/.pnpm/@oddbird+popover-polyfill@0.7.1_patch_hash=6227913935a153774e0e2569cb889d63767e7a5d572dbc6beeadfb2e77ed5c11/node_modules/@oddbird/popover-polyfill/dist/popover-fn.js
706
+ //#region ../../node_modules/.pnpm/@oddbird+popover-polyfill@0.7.2/node_modules/@oddbird/popover-polyfill/dist/popover-fn.js
703
707
  var ToggleEvent = class extends Event {
704
708
  oldState;
705
709
  newState;
@@ -1010,7 +1014,7 @@ function lightDismissOpenPopovers(event) {
1010
1014
  if (!event.isTrusted) return;
1011
1015
  const target = event.composedPath()[0];
1012
1016
  if (!target) return;
1013
- const document2 = target.ownerDocument;
1017
+ const document2 = target.ownerDocument || target;
1014
1018
  if (!topmostAutoOrHintPopover(document2)) return;
1015
1019
  const ancestor = topMostClickedPopover(target);
1016
1020
  if (ancestor && event.type === "pointerdown") popoverPointerDownTargets.set(document2, ancestor);
@@ -1054,7 +1058,7 @@ function hasLayerSupport() {
1054
1058
  function getStyles(layerName) {
1055
1059
  var _a;
1056
1060
  const useLayer = hasLayerSupport();
1057
- const layerNameEscaped = (layerName ?? ((_a = window.POPOVER_POLYFILL_OPTIONS) == null ? void 0 : _a.layerName) ?? DEFAULT_LAYER_NAME).split(".").map((c) => CSS.escape(c)).join(".");
1061
+ const layerNameEscaped = (layerName ?? ((_a = window.POPOVER_POLYFILL_OPTIONS) == null ? void 0 : _a.layerName) ?? DEFAULT_LAYER_NAME).split(".").map((css) => CSS.escape(css)).join(".");
1058
1062
  return `
1059
1063
  ${useLayer ? `@layer ${layerNameEscaped} {` : ""}
1060
1064
  :where([popover]) {
@@ -1581,7 +1585,7 @@ const setupText = (el, canAnnounce = true) => {
1581
1585
  attr(el, ATTR_TOOLTIP, text);
1582
1586
  attr(el, ARIA_LABEL, hasText ? null : text);
1583
1587
  attr(el, ARIA_DESC, hasText ? text : null);
1584
- if (el.tabIndex === -1) warn("Missing tabindex=\"0\" attribute on: ", el);
1588
+ if (el.tabIndex === -1 && !el.hasAttribute("tabindex") && el.nodeName !== "LABEL") warn("Missing tabindex=\"0\" attribute on: ", el);
1585
1589
  }
1586
1590
  if (el === OPEN && TIP?.textContent !== text) {
1587
1591
  if (TIP) TIP.textContent = text;
@@ -1775,7 +1779,7 @@ const handleFieldMutation = (field) => {
1775
1779
  }
1776
1780
  field.handleEvent();
1777
1781
  };
1778
- const TEXTS = {
1782
+ const TEXTS$1 = {
1779
1783
  over: "%d tegn for mye",
1780
1784
  under: "%d tegn igjen"
1781
1785
  };
@@ -1815,7 +1819,7 @@ var DSFieldElement = class extends DSElement {
1815
1819
  if (_counter?.isConnected && _input) {
1816
1820
  const count = (Number(attr(_counter, ATTR_LIMIT)) || 0) - _input.value.length;
1817
1821
  const state = count < 0 ? "over" : "under";
1818
- const label = (attrOrCSS(_counter, `data-${state}`) || TEXTS[state])?.replace("%d", `${Math.abs(count)}`);
1822
+ const label = (attrOrCSS(_counter, `data-${state}`) || TEXTS$1[state])?.replace("%d", `${Math.abs(count)}`);
1819
1823
  if (!label) warn(`Missing data-${state} on:`, _counter);
1820
1824
  attr(_counter, "data-label", label);
1821
1825
  attr(_counter, "data-state", state);
@@ -1922,53 +1926,65 @@ customElements.define("ds-pagination", DSPaginationElement);
1922
1926
  //#region src/suggestion/suggestion.ts
1923
1927
  const ATTR_EMPTY = "data-empty";
1924
1928
  const ATTR_CREATE = "data-create";
1929
+ const EVENTS_EMPTY = "comboboxafterselect comboboxprogrammaticinput input";
1930
+ const REGEX_CREATE = /\{value\}|%s/;
1931
+ const SINGULAR = "data-sr-singular";
1932
+ const PLURAL = "data-sr-plural";
1933
+ const TEXTS = "added,clear,empty,found,invalid,items,of,plural,remove,removed,singular,toggle".split(",").map((key) => `data-sr-${key}`);
1925
1934
  var DSSuggestionElement = class extends UHTMLComboboxElement {
1926
1935
  _unmutate;
1927
1936
  connectedCallback() {
1937
+ for (const key of TEXTS) attr(this, key, attrOrCSS(this, key));
1928
1938
  super.connectedCallback();
1929
1939
  this._unmutate = onMutation(this, render, { childList: true });
1930
- on(this, "comboboxafterselect input", handleEmpty, QUICK_EVENT);
1940
+ on(this, EVENTS_EMPTY, handleEmpty, QUICK_EVENT);
1931
1941
  on(this, "toggle", polyfillToggleSource, QUICK_EVENT);
1932
1942
  }
1933
1943
  disconnectedCallback() {
1934
1944
  super.disconnectedCallback();
1935
1945
  this._unmutate?.();
1936
1946
  this._unmutate = void 0;
1937
- off(this, "comboboxafterselect input", handleEmpty, QUICK_EVENT);
1947
+ off(this, EVENTS_EMPTY, handleEmpty, QUICK_EVENT);
1938
1948
  off(this, "toggle", polyfillToggleSource, QUICK_EVENT);
1939
1949
  }
1940
1950
  };
1941
1951
  const render = (self) => {
1942
- const { control, list } = self;
1943
- const datalist = list || self.querySelector("u-datalist");
1952
+ let { control, list } = self;
1953
+ if (!list) list = self.querySelector("u-datalist");
1944
1954
  if (control) attr(control, "popovertarget", list ? useId(list) : null);
1945
- if (datalist) {
1946
- attr(datalist, "popover", "manual");
1947
- attr(datalist, "data-is-floating", "true");
1955
+ if (list) {
1956
+ if (!attr(list, PLURAL)) attr(list, PLURAL, attr(self, PLURAL));
1957
+ if (!attr(list, SINGULAR)) attr(list, SINGULAR, attr(self, SINGULAR));
1958
+ attr(list, "data-is-floating", "true");
1959
+ attr(list, "popover", "manual");
1948
1960
  }
1949
1961
  handleEmpty({ currentTarget: self });
1950
1962
  };
1951
1963
  const handleEmpty = ({ currentTarget: self }) => {
1952
- const { creatable, control, options } = self;
1964
+ const { creatable, control, options, values } = self;
1953
1965
  if (!options) return;
1954
1966
  const value = control?.value.trim() || "";
1955
1967
  const query = value.toLowerCase();
1956
- let empty;
1957
- let exists = !value;
1968
+ let emptyOp;
1969
+ let hasMatch = false;
1958
1970
  for (const opt of options) {
1959
- if (!empty && opt.hasAttribute(ATTR_EMPTY)) empty = opt;
1960
- else if (!exists && opt.label?.toLowerCase() === query) exists = true;
1961
- if (exists && empty) break;
1962
- }
1963
- if (!empty) return;
1964
- empty.hidden = exists;
1965
- empty.label = value;
1966
- empty.value = creatable ? value : "";
1967
- if (!creatable || empty.textContent) return;
1968
- const text = attrOrCSS(empty, ATTR_EMPTY);
1969
- if (!text) warn(`Missing ${ATTR_EMPTY} value on:`, empty);
1970
- else attr(empty, ATTR_EMPTY, text);
1971
- attr(empty, ATTR_CREATE, text?.replace("{value}", value));
1971
+ if (!emptyOp && opt.hasAttribute(ATTR_EMPTY)) emptyOp = opt;
1972
+ else if (!hasMatch && (!query || opt.label?.toLowerCase() === query)) hasMatch = true;
1973
+ if (hasMatch && emptyOp) break;
1974
+ }
1975
+ if (!emptyOp) return;
1976
+ emptyOp.hidden = hasMatch;
1977
+ attr(emptyOp, "label", value);
1978
+ attr(emptyOp, "value", creatable ? value : "");
1979
+ if (creatable) {
1980
+ const found = values.some((val) => val === value);
1981
+ const text = attrOrCSS(emptyOp, ATTR_EMPTY);
1982
+ const hint = !found && text?.replace(REGEX_CREATE, () => value) || value;
1983
+ if (!text) warn(`Missing ${ATTR_EMPTY} value on:`, emptyOp);
1984
+ else attr(emptyOp, ATTR_EMPTY, text);
1985
+ attr(emptyOp, "disabled", found && emptyOp.textContent ? "true" : null);
1986
+ attr(emptyOp, ATTR_CREATE, hint);
1987
+ }
1972
1988
  };
1973
1989
  const polyfillToggleSource = (event) => {
1974
1990
  const self = event.currentTarget;