@digdir/designsystemet-web 1.21.1 → 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.
- package/dist/cjs/_virtual/_rolldown/runtime.cjs +1 -1
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/packages/web/package.cjs +1 -1
- package/dist/cjs/suggestion/suggestion.cjs +1 -1
- package/dist/cjs/suggestion/suggestion.cjs.map +1 -1
- package/dist/cjs/toggle-group/toggle-group.cjs.map +1 -1
- package/dist/cjs/tooltip/tooltip.cjs +1 -1
- package/dist/cjs/tooltip/tooltip.cjs.map +1 -1
- package/dist/cjs/utils/utils.cjs +1 -1
- package/dist/cjs/utils/utils.cjs.map +1 -1
- package/dist/custom-elements.json +8 -8
- package/dist/esm/packages/web/package.js +1 -1
- package/dist/esm/suggestion/suggestion.js +1 -1
- package/dist/esm/suggestion/suggestion.js.map +1 -1
- package/dist/esm/toggle-group/toggle-group.js.map +1 -1
- package/dist/esm/tooltip/tooltip.js +1 -1
- package/dist/esm/tooltip/tooltip.js.map +1 -1
- package/dist/esm/utils/utils.js +1 -1
- package/dist/esm/utils/utils.js.map +1 -1
- package/dist/index.js +42 -29
- package/dist/umd/index.js +8 -8
- package/dist/umd/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),
|
|
1
|
+
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,n)=>{let r={};for(var i in e)t(r,i,{get:e[i],enumerable:!0});return n||t(r,Symbol.toStringTag,{value:`Module`}),r},s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(e,t,n)=>(s(e,t,`default`),n&&s(n,t,`default`)),l=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));exports.__exportAll=o,exports.__reExport=c,exports.__toESM=l;
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("./clickdelegatefor/clickdelegatefor.cjs"),require("./details/details.cjs"),require("./dialog/dialog.cjs"),require("./fieldset/fieldset.cjs"),require("./focusgroup/focusgroup.cjs"),require("./invokers/invokers.cjs"),require("./popover/popover.cjs"),require("./readonly/readonly.cjs"),require("./toggle-group/toggle-group.cjs");const e=require("./tooltip/tooltip.cjs"),t=require("./breadcrumbs/breadcrumbs.cjs"),n=require("./error-summary/error-summary.cjs"),r=require("./field/field.cjs"),i=require("./pagination/pagination.cjs"),a=require("./suggestion/suggestion.cjs"),o=require("./tabs/tabs.cjs");exports.DSBreadcrumbsElement=t.DSBreadcrumbsElement,exports.DSErrorSummaryElement=n.DSErrorSummaryElement,exports.DSFieldElement=r.DSFieldElement,exports.DSPaginationElement=i.DSPaginationElement,exports.DSSuggestionElement=a.DSSuggestionElement,exports.DSTabElement=o.DSTabElement,exports.DSTabListElement=o.DSTabListElement,exports.DSTabPanelElement=o.DSTabPanelElement,exports.DSTabsElement=o.DSTabsElement,exports.initTooltips=e.initTooltips,exports.pagination=i.pagination,exports.setTooltipElement=e.setTooltipElement;var s=require("@u-elements/u-datalist");Object.keys(s).forEach(function(e){e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return s[e]}})});
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require("./_virtual/_rolldown/runtime.cjs"),require("./clickdelegatefor/clickdelegatefor.cjs"),require("./details/details.cjs"),require("./dialog/dialog.cjs"),require("./fieldset/fieldset.cjs"),require("./focusgroup/focusgroup.cjs"),require("./invokers/invokers.cjs"),require("./popover/popover.cjs"),require("./readonly/readonly.cjs"),require("./toggle-group/toggle-group.cjs");const e=require("./tooltip/tooltip.cjs"),t=require("./breadcrumbs/breadcrumbs.cjs"),n=require("./error-summary/error-summary.cjs"),r=require("./field/field.cjs"),i=require("./pagination/pagination.cjs"),a=require("./suggestion/suggestion.cjs"),o=require("./tabs/tabs.cjs");exports.DSBreadcrumbsElement=t.DSBreadcrumbsElement,exports.DSErrorSummaryElement=n.DSErrorSummaryElement,exports.DSFieldElement=r.DSFieldElement,exports.DSPaginationElement=i.DSPaginationElement,exports.DSSuggestionElement=a.DSSuggestionElement,exports.DSTabElement=o.DSTabElement,exports.DSTabListElement=o.DSTabListElement,exports.DSTabPanelElement=o.DSTabPanelElement,exports.DSTabsElement=o.DSTabsElement,exports.initTooltips=e.initTooltips,exports.pagination=i.pagination,exports.setTooltipElement=e.setTooltipElement;var s=require("@u-elements/u-datalist");Object.keys(s).forEach(function(e){e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return s[e]}})});
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
Object.defineProperty(exports,"version",{enumerable:!0,get:function(){return`1.
|
|
1
|
+
Object.defineProperty(exports,"version",{enumerable:!0,get:function(){return`1.22.0`}});
|
|
2
2
|
//# sourceMappingURL=package.cjs.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../utils/utils.cjs");let t=require("@u-elements/u-combobox");const n=`data-empty
|
|
1
|
+
require("../_virtual/_rolldown/runtime.cjs");const e=require("../utils/utils.cjs");let t=require("@u-elements/u-combobox");const n=`data-empty`,r=`comboboxafterselect comboboxprogrammaticinput input`,i=/\{value\}|%s/,a=`data-sr-singular`,o=`data-sr-plural`,s=`added,clear,empty,found,invalid,items,of,plural,remove,removed,singular,toggle`.split(`,`).map(e=>`data-sr-${e}`);var c=class extends t.UHTMLComboboxElement{_unmutate;connectedCallback(){for(let t of s)e.attr(this,t,e.attrOrCSS(this,t));super.connectedCallback(),this._unmutate=e.onMutation(this,l,{childList:!0}),e.on(this,r,u,e.QUICK_EVENT),e.on(this,`toggle`,d,e.QUICK_EVENT)}disconnectedCallback(){super.disconnectedCallback(),this._unmutate?.(),this._unmutate=void 0,e.off(this,r,u,e.QUICK_EVENT),e.off(this,`toggle`,d,e.QUICK_EVENT)}};const l=t=>{let{control:n,list:r}=t;r||=t.querySelector(`u-datalist`),n&&e.attr(n,`popovertarget`,r?e.useId(r):null),r&&(e.attr(r,o)||e.attr(r,o,e.attr(t,o)),e.attr(r,a)||e.attr(r,a,e.attr(t,a)),e.attr(r,`data-is-floating`,`true`),e.attr(r,`popover`,`manual`)),u({currentTarget:t})},u=({currentTarget:t})=>{let{creatable:r,control:a,options:o,values:s}=t;if(!o)return;let c=a?.value.trim()||``,l=c.toLowerCase(),u,d=!1;for(let e of o)if(!u&&e.hasAttribute(n)?u=e:!d&&(!l||e.label?.toLowerCase()===l)&&(d=!0),d&&u)break;if(u&&(u.hidden=d,e.attr(u,`label`,c),e.attr(u,`value`,r?c:``),r)){let t=s.some(e=>e===c),r=e.attrOrCSS(u,n),a=!t&&r?.replace(i,()=>c)||c;r?e.attr(u,n,r):e.warn(`Missing ${n} value on:`,u),e.attr(u,`disabled`,t&&u.textContent?`true`:null),e.attr(u,`data-create`,a)}},d=e=>{let t=e.currentTarget,n=e.newState===`open`&&t.control;n&&t.list?.dispatchEvent(new CustomEvent(`ds-toggle-source`,{bubbles:!0,composed:!0,detail:n}))};e.customElements.define(`ds-suggestion`,c),exports.DSSuggestionElement=c;var f=require("@u-elements/u-datalist");Object.keys(f).forEach(function(e){e!=="default"&&!Object.prototype.hasOwnProperty.call(exports,e)&&Object.defineProperty(exports,e,{enumerable:!0,get:function(){return f[e]}})});
|
|
2
2
|
//# sourceMappingURL=suggestion.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggestion.cjs","names":["UHTMLComboboxElement","onMutation","QUICK_EVENT","useId","
|
|
1
|
+
{"version":3,"file":"suggestion.cjs","names":["UHTMLComboboxElement","attrOrCSS","onMutation","QUICK_EVENT","useId","attr","customElements"],"sources":["../../../src/suggestion/suggestion.ts"],"sourcesContent":["import { UHTMLComboboxElement } from '@u-elements/u-combobox';\nimport {\n attr,\n attrOrCSS,\n customElements,\n off,\n on,\n onMutation,\n QUICK_EVENT,\n useId,\n warn,\n} from '../utils/utils';\n\n// Load and export u-datalist since this is a pure polyfill and not custom Designsystemet elements, should run before suggestion\nexport * from '@u-elements/u-datalist';\n\nconst ATTR_EMPTY = 'data-empty';\nconst ATTR_CREATE = 'data-create';\nconst EVENTS_EMPTY = 'comboboxafterselect comboboxprogrammaticinput input';\nconst REGEX_CREATE = /\\{value\\}|%s/; // Support both new %s and old {value} syntax\nconst SINGULAR = 'data-sr-singular';\nconst PLURAL = 'data-sr-plural';\nconst TEXTS =\n 'added,clear,empty,found,invalid,items,of,plural,remove,removed,singular,toggle'\n .split(',')\n .map((key) => `data-sr-${key}`);\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ds-suggestion': DSSuggestionElement;\n }\n}\n\nexport class DSSuggestionElement extends UHTMLComboboxElement {\n _unmutate?: ReturnType<typeof onMutation>; // Using underscore instead of private fields for backwards compatibility\n\n connectedCallback() {\n for (const key of TEXTS) attr(this, key, attrOrCSS(this, key)); // Convert CSS variables to data-sr-attributes\n super.connectedCallback(); // Run after setting data-sr-attributes\n\n this._unmutate = onMutation(this, render, { childList: true }); // .control and .list are direct children of the custom element\n on(this, EVENTS_EMPTY, handleEmpty, QUICK_EVENT);\n on(this, 'toggle', polyfillToggleSource, QUICK_EVENT);\n }\n disconnectedCallback() {\n super.disconnectedCallback();\n this._unmutate?.();\n this._unmutate = undefined;\n off(this, EVENTS_EMPTY, handleEmpty, QUICK_EVENT);\n off(this, 'toggle', polyfillToggleSource, QUICK_EVENT);\n }\n}\n\nconst render = (self: DSSuggestionElement) => {\n let { control, list } = self;\n if (!list) list = self.querySelector('u-datalist'); // Fallback to u-datalist since React can render the ds-suggestion before u-datalist is connected\n\n if (control) attr(control, 'popovertarget', list ? useId(list) : null);\n if (list) {\n if (!attr(list, PLURAL)) attr(list, PLURAL, attr(self, PLURAL)); // Inherit translations from u-combobox to u-datalist\n if (!attr(list, SINGULAR)) attr(list, SINGULAR, attr(self, SINGULAR)); // Inherit translations from u-combobox to u-datalist\n attr(list, 'data-is-floating', 'true'); // identifier for css to toggle opacity when it is placed by floating-ui.\n attr(list, 'popover', 'manual'); // Ensure popover attribute is set on the list\n }\n handleEmpty({ currentTarget: self });\n};\n\nconst handleEmpty = ({ currentTarget: self }: Pick<Event, 'currentTarget'>) => {\n const { creatable, control, options, values } = self as DSSuggestionElement;\n if (!options) return;\n\n const value = control?.value.trim() || '';\n const query = value.toLowerCase();\n let emptyOp: HTMLOptionElement | undefined;\n let hasMatch = false;\n\n for (const opt of options) {\n if (!emptyOp && opt.hasAttribute(ATTR_EMPTY)) emptyOp = opt;\n else if (!hasMatch && (!query || opt.label?.toLowerCase() === query))\n hasMatch = true;\n if (hasMatch && emptyOp) break; // Speed up if both conditions are met\n }\n if (!emptyOp) return;\n\n emptyOp.hidden = hasMatch; // Hide initial empty state when options exist, or when the query already exists\n attr(emptyOp, 'label', value); // Ensures option is not filtered out by <u-combobox>\n attr(emptyOp, 'value', creatable ? value : ''); // Ensures clicking option does nothing\n\n if (creatable) {\n const found = values.some((val) => val === value); // Only show \"Legg til\" if not already created\n const text = attrOrCSS(emptyOp, ATTR_EMPTY);\n const hint = (!found && text?.replace(REGEX_CREATE, () => value)) || value;\n\n if (!text) warn(`Missing ${ATTR_EMPTY} value on:`, emptyOp);\n else attr(emptyOp, ATTR_EMPTY, text); // Speed up by caching attribute value\n\n attr(emptyOp, 'disabled', found && emptyOp.textContent ? 'true' : null); // Hide permanent hint if created\n attr(emptyOp, ATTR_CREATE, hint);\n }\n};\n\n// Since showPopover({ source }) is not supported in all browsers yet:\nconst polyfillToggleSource = (event: Partial<ToggleEvent>) => {\n const self = event.currentTarget as DSSuggestionElement;\n const detail = event.newState === 'open' && self.control; // .control comes from UHTMLComboboxElement\n\n if (detail)\n self.list?.dispatchEvent(\n new CustomEvent('ds-toggle-source', {\n bubbles: true,\n composed: true, // Enable bubbling out of shadow DOM boundaries\n detail, // Since showPopover({ source }) is not supported in all browsers yet\n }),\n );\n};\n\n// Ensure u-datalist is defined before ds-suggestion\ncustomElements.define('ds-suggestion', DSSuggestionElement);\n"],"mappings":"2HAgBA,MAAM,EAAa,aAEb,EAAe,sDACf,EAAe,eACf,EAAW,mBACX,EAAS,iBACT,EACJ,iFACG,MAAM,GAAG,CAAC,CACV,IAAK,GAAQ,WAAW,GAAK,EAQlC,IAAa,EAAb,cAAyCA,EAAAA,oBAAqB,CAC5D,UAEA,mBAAoB,CAClB,IAAK,IAAM,KAAO,EAAO,EAAA,KAAK,KAAM,EAAKC,EAAAA,UAAU,KAAM,CAAG,CAAC,EAC7D,MAAM,kBAAkB,EAExB,KAAK,UAAYC,EAAAA,WAAW,KAAM,EAAQ,CAAE,UAAW,EAAK,CAAC,EAC7D,EAAA,GAAG,KAAM,EAAc,EAAaC,EAAAA,WAAW,EAC/C,EAAA,GAAG,KAAM,SAAU,EAAsBA,EAAAA,WAAW,CACtD,CACA,sBAAuB,CACrB,MAAM,qBAAqB,EAC3B,KAAK,YAAY,EACjB,KAAK,UAAY,IAAA,GACjB,EAAA,IAAI,KAAM,EAAc,EAAaA,EAAAA,WAAW,EAChD,EAAA,IAAI,KAAM,SAAU,EAAsBA,EAAAA,WAAW,CACvD,CACF,EAEA,MAAM,EAAU,GAA8B,CAC5C,GAAI,CAAE,UAAS,QAAS,EACxB,AAAW,IAAO,EAAK,cAAc,YAAY,EAE7C,GAAS,EAAA,KAAK,EAAS,gBAAiB,EAAOC,EAAAA,MAAM,CAAI,EAAI,IAAI,EACjE,IACGC,EAAAA,KAAK,EAAM,CAAM,GAAG,EAAA,KAAK,EAAM,EAAQA,EAAAA,KAAK,EAAM,CAAM,CAAC,EACzDA,EAAAA,KAAK,EAAM,CAAQ,GAAG,EAAA,KAAK,EAAM,EAAUA,EAAAA,KAAK,EAAM,CAAQ,CAAC,EACpE,EAAA,KAAK,EAAM,mBAAoB,MAAM,EACrC,EAAA,KAAK,EAAM,UAAW,QAAQ,GAEhC,EAAY,CAAE,cAAe,CAAK,CAAC,CACrC,EAEM,GAAe,CAAE,cAAe,KAAyC,CAC7E,GAAM,CAAE,YAAW,UAAS,UAAS,UAAW,EAChD,GAAI,CAAC,EAAS,OAEd,IAAM,EAAQ,GAAS,MAAM,KAAK,GAAK,GACjC,EAAQ,EAAM,YAAY,EAC5B,EACA,EAAW,GAEf,IAAK,IAAM,KAAO,EAIhB,GAHI,CAAC,GAAW,EAAI,aAAa,CAAU,EAAG,EAAU,EAC/C,CAAC,IAAa,CAAC,GAAS,EAAI,OAAO,YAAY,IAAM,KAC5D,EAAW,IACT,GAAY,EAAS,MAEtB,OAEL,EAAQ,OAAS,EACjB,EAAA,KAAK,EAAS,QAAS,CAAK,EAC5B,EAAA,KAAK,EAAS,QAAS,EAAY,EAAQ,EAAE,EAEzC,GAAW,CACb,IAAM,EAAQ,EAAO,KAAM,GAAQ,IAAQ,CAAK,EAC1C,EAAOJ,EAAAA,UAAU,EAAS,CAAU,EACpC,EAAQ,CAAC,GAAS,GAAM,QAAQ,MAAoB,CAAK,GAAM,EAEhE,EACA,EAAA,KAAK,EAAS,EAAY,CAAI,EADxB,EAAA,KAAK,WAAW,EAAW,YAAa,CAAO,EAG1D,EAAA,KAAK,EAAS,WAAY,GAAS,EAAQ,YAAc,OAAS,IAAI,EACtE,EAAA,KAAK,EAAS,cAAa,CAAI,CACjC,CACF,EAGM,EAAwB,GAAgC,CAC5D,IAAM,EAAO,EAAM,cACb,EAAS,EAAM,WAAa,QAAU,EAAK,QAE7C,GACF,EAAK,MAAM,cACT,IAAI,YAAY,mBAAoB,CAClC,QAAS,GACT,SAAU,GACV,QACF,CAAC,CACH,CACJ,EAGAK,EAAAA,eAAe,OAAO,gBAAiB,CAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toggle-group.cjs","names":["getComposedTarget","getComposedPath","attr","attrOrCSS","ARIA_LABELLEDBY","ARIA_LABEL","onHotReload","on","onMutation"],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n ARIA_LABEL,\n ARIA_LABELLEDBY,\n attr,\n attrOrCSS,\n getComposedPath,\n getComposedTarget,\n on,\n onHotReload,\n onMutation,\n warn,\n} from '../utils/utils';\n\nconst ATTR_FOCUSGROUP = 'focusgroup';\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\nlet IS_ARROW: boolean | undefined; // Used to prevent clicks that are a result of keyboard navigation\n\nconst handleMutations = (root: Document, records?: MutationRecord[]) => {\n for (const r of records || [null]) {\n if (r?.attributeName === ATTR_TOGGLEGROUP) deprecate(r.target as Element);\n else if (!r || r.addedNodes.length) {\n const scope = (r?.target as Element) || root;\n for (const el of scope.querySelectorAll(SELECTOR_TOGGLEGROUP))\n deprecate(el);\n }\n }\n};\n\n// Prevent clicks that are a result of keyboard navigation\nconst handleRadioClick = (e: Event & Partial<PointerEvent>) => {\n if (IS_ARROW && getRadioInFocusGroup(e)) {\n e.preventDefault(); // Only handle clicks that are a result of keyboard navigation\n e.stopImmediatePropagation(); // Prevent stopped \"click\" from reaching React and other listeners\n }\n};\n\nconst resetIsArrow = () => (IS_ARROW = false);\nconst handleKeydown = (e: Event & Partial<KeyboardEvent>) => {\n IS_ARROW = e.key?.startsWith('Arrow');\n if (e.key === 'Enter') getRadioInFocusGroup(e)?.click(); // Allow Enter to activate the radio input\n if (IS_ARROW) setTimeout(resetIsArrow, 0); // Reset after event loop so we can check if next focus event is a result of arrow key navigation\n};\n\n// This function does not handle nested focusgroups, but toggle-groups should not be nested so this is not a problem\nconst getRadioInFocusGroup = (e: Event) => {\n const el = getComposedTarget(e) as HTMLInputElement | null;\n if (el?.nodeName !== 'INPUT' || el.type !== 'radio' || !el.name) return;\n for (const group of getComposedPath(el) as Set<Element>) {\n if (group.nodeType !== 1 || !group.hasAttribute(ATTR_FOCUSGROUP)) continue; // Ignore non-group nodes\n if (!attr(group, ATTR_FOCUSGROUP)?.includes('radiogroup')) return; // Ignore invalid groups\n return el;\n }\n};\n\nconst deprecate = (el: Element) => {\n if (el.hasAttribute(ATTR_FOCUSGROUP)) return; // Ignore if already migrated\n const label = attrOrCSS(el, ATTR_TOGGLEGROUP);\n const labelledby = attr(el, ARIA_LABELLEDBY)?.trim();\n const message = `Please use ${ATTR_FOCUSGROUP}=\"radiogroup\" and ${labelledby ? ARIA_LABELLEDBY : ARIA_LABEL}=\"${labelledby || label}\" instead of deprecated ${ATTR_TOGGLEGROUP} on:`;\n\n attr(el, ARIA_LABEL, labelledby ? null : label);\n attr(el, ATTR_FOCUSGROUP, 'radiogroup');\n warn(message, el);\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'click', handleRadioClick, true), // Use capture to ensure we run before React and other listeners\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleMutations, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"sCAaM,EAAkB,aAClB,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAClD,IAAI,EAEJ,MAAM,GAAmB,EAAgB,IAA+B,CACtE,IAAK,IAAM,KAAK,GAAW,CAAC,IAAI,EAC9B,GAAI,GAAG,gBAAkB,EAAkB,EAAU,EAAE,MAAiB,OACnE,GAAI,CAAC,GAAK,EAAE,WAAW,OAAQ,CAClC,IAAM,EAAS,GAAG,QAAsB,EACxC,IAAK,IAAM,KAAM,EAAM,iBAAiB,CAAoB,EAC1D,EAAU,CAAE,CAChB,CAEJ,EAGM,EAAoB,GAAqC,CACzD,GAAY,EAAqB,CAAC,IACpC,EAAE,eAAe,EACjB,EAAE,yBAAyB,EAE/B,EAEM,MAAsB,EAAW,GACjC,EAAiB,GAAsC,CAC3D,EAAW,EAAE,KAAK,WAAW,OAAO,EAChC,EAAE,MAAQ,SAAS,EAAqB,CAAC,CAAC,EAAE,MAAM,EAClD,GAAU,WAAW,EAAc,CAAC,CAC1C,EAGM,EAAwB,GAAa,CACzC,IAAM,EAAKA,EAAAA,kBAAkB,CAAC,EAC1B,QAAI,WAAa,SAAW,EAAG,OAAS,SAAW,CAAC,EAAG,MAC3D,KAAK,IAAM,KAASC,EAAAA,gBAAgB,CAAE,EAChC,OAAM,WAAa,GAAK,CAAC,EAAM,aAAa,CAAe,GAE/D,OADKC,EAAAA,KAAK,EAAO,CAAe,CAAC,EAAE,SAAS,YAAY,EACjD,EADoD,MAE7D,CACF,
|
|
1
|
+
{"version":3,"file":"toggle-group.cjs","names":["getComposedTarget","getComposedPath","attr","attrOrCSS","ARIA_LABELLEDBY","ARIA_LABEL","onHotReload","on","onMutation"],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n ARIA_LABEL,\n ARIA_LABELLEDBY,\n attr,\n attrOrCSS,\n getComposedPath,\n getComposedTarget,\n on,\n onHotReload,\n onMutation,\n warn,\n} from '../utils/utils';\n\nconst ATTR_FOCUSGROUP = 'focusgroup';\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\nlet IS_ARROW: boolean | undefined; // Used to prevent clicks that are a result of keyboard navigation\n\nconst handleMutations = (root: Document, records?: MutationRecord[]) => {\n for (const r of records || [null]) {\n if (r?.attributeName === ATTR_TOGGLEGROUP) deprecate(r.target as Element);\n else if (!r || r.addedNodes.length) {\n const scope = (r?.target as Element) || root;\n for (const el of scope.querySelectorAll(SELECTOR_TOGGLEGROUP))\n deprecate(el);\n }\n }\n};\n\n// Prevent clicks that are a result of keyboard navigation\nconst handleRadioClick = (e: Event & Partial<PointerEvent>) => {\n if (IS_ARROW && getRadioInFocusGroup(e)) {\n e.preventDefault(); // Only handle clicks that are a result of keyboard navigation\n e.stopImmediatePropagation(); // Prevent stopped \"click\" from reaching React and other listeners\n }\n};\n\nconst resetIsArrow = () => (IS_ARROW = false);\nconst handleKeydown = (e: Event & Partial<KeyboardEvent>) => {\n IS_ARROW = e.key?.startsWith('Arrow');\n if (e.key === 'Enter') getRadioInFocusGroup(e)?.click(); // Allow Enter to activate the radio input\n if (IS_ARROW) setTimeout(resetIsArrow, 0); // Reset after event loop so we can check if next focus event is a result of arrow key navigation\n};\n\n// This function does not handle nested focusgroups, but toggle-groups should not be nested so this is not a problem\nconst getRadioInFocusGroup = (e: Event) => {\n const el = getComposedTarget(e) as HTMLInputElement | null;\n if (el?.nodeName !== 'INPUT' || el.type !== 'radio' || !el.name) return;\n for (const group of getComposedPath(el) as Set<Element>) {\n if (group.nodeType !== 1 || !group.hasAttribute(ATTR_FOCUSGROUP)) continue; // Ignore non-group nodes\n if (!attr(group, ATTR_FOCUSGROUP)?.includes('radiogroup')) return; // Ignore invalid groups\n return el;\n }\n};\n\n// @deprecated: Supports migration from data-toggle-group to focusgroup=\"radiogroup\".\nconst deprecate = (el: Element) => {\n if (el.hasAttribute(ATTR_FOCUSGROUP)) return; // Ignore if already migrated\n const label = attrOrCSS(el, ATTR_TOGGLEGROUP);\n const labelledby = attr(el, ARIA_LABELLEDBY)?.trim();\n const message = `Please use ${ATTR_FOCUSGROUP}=\"radiogroup\" and ${labelledby ? ARIA_LABELLEDBY : ARIA_LABEL}=\"${labelledby || label}\" instead of deprecated ${ATTR_TOGGLEGROUP} on:`;\n\n attr(el, ARIA_LABEL, labelledby ? null : label);\n attr(el, ATTR_FOCUSGROUP, 'radiogroup');\n warn(message, el);\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'click', handleRadioClick, true), // Use capture to ensure we run before React and other listeners\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleMutations, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"sCAaM,EAAkB,aAClB,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAClD,IAAI,EAEJ,MAAM,GAAmB,EAAgB,IAA+B,CACtE,IAAK,IAAM,KAAK,GAAW,CAAC,IAAI,EAC9B,GAAI,GAAG,gBAAkB,EAAkB,EAAU,EAAE,MAAiB,OACnE,GAAI,CAAC,GAAK,EAAE,WAAW,OAAQ,CAClC,IAAM,EAAS,GAAG,QAAsB,EACxC,IAAK,IAAM,KAAM,EAAM,iBAAiB,CAAoB,EAC1D,EAAU,CAAE,CAChB,CAEJ,EAGM,EAAoB,GAAqC,CACzD,GAAY,EAAqB,CAAC,IACpC,EAAE,eAAe,EACjB,EAAE,yBAAyB,EAE/B,EAEM,MAAsB,EAAW,GACjC,EAAiB,GAAsC,CAC3D,EAAW,EAAE,KAAK,WAAW,OAAO,EAChC,EAAE,MAAQ,SAAS,EAAqB,CAAC,CAAC,EAAE,MAAM,EAClD,GAAU,WAAW,EAAc,CAAC,CAC1C,EAGM,EAAwB,GAAa,CACzC,IAAM,EAAKA,EAAAA,kBAAkB,CAAC,EAC1B,QAAI,WAAa,SAAW,EAAG,OAAS,SAAW,CAAC,EAAG,MAC3D,KAAK,IAAM,KAASC,EAAAA,gBAAgB,CAAE,EAChC,OAAM,WAAa,GAAK,CAAC,EAAM,aAAa,CAAe,GAE/D,OADKC,EAAAA,KAAK,EAAO,CAAe,CAAC,EAAE,SAAS,YAAY,EACjD,EADoD,MAE7D,CACF,EAGM,EAAa,GAAgB,CACjC,GAAI,EAAG,aAAa,CAAe,EAAG,OACtC,IAAM,EAAQC,EAAAA,UAAU,EAAI,CAAgB,EACtC,EAAaD,EAAAA,KAAK,EAAIE,EAAAA,eAAe,CAAC,EAAE,KAAK,EAC7C,EAAU,cAAc,EAAgB,oBAAoB,EAAaA,EAAAA,gBAAkBC,EAAAA,WAAW,IAAI,GAAc,EAAM,0BAA0B,EAAiB,MAE/K,EAAA,KAAK,EAAIA,EAAAA,WAAY,EAAa,KAAO,CAAK,EAC9C,EAAA,KAAK,EAAI,EAAiB,YAAY,EACtC,EAAA,KAAK,EAAS,CAAE,CAClB,EAEAC,EAAAA,YAAY,mBAAsB,CAChCC,EAAAA,GAAG,SAAU,QAAS,EAAkB,EAAI,EAC5CA,EAAAA,GAAG,SAAU,UAAW,CAAa,EACrCC,EAAAA,WAAW,SAAU,EAAiB,CACpC,gBAAiB,CAAC,CAAgB,EAClC,WAAY,GACZ,UAAW,GACX,QAAS,EACX,CAAC,CACH,CAAC"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../utils/utils.cjs");require("../popover/popover.cjs");let t,n,r,i,a=0;const o=`data-color`,s=`data-color-scheme`,c=`data-size`,l=`data-tooltip`,u=e.isBrowser()&&/iPad|iPhone|iPod/.test(navigator.userAgent),d=`[${o}]`,f=`[${s}]`,p=`[${c}]`,m=`[${l}]`,h=n=>{n&&!(n instanceof HTMLElement)&&e.warn(`setTooltipElement expects an HTMLElement, got: `,n),x(),a=0,t=n||void 0},g=(e=document)=>{for(let t of e?.querySelectorAll(m)||[])v(t)},_=(e,n)=>{if(!n)return g();for(let e of n)if(e.target!==t)if(e.attributeName===l)v(e.target);else for(let t of e.addedNodes)t.nodeType===1&&(t.hasAttribute(l)?v(t):g(t))},v=(n,r=!0)=>{let a=e.attrOrCSS(n,l)||``;if(a[0]===`#`&&(a=e.getRoot(n).getElementById(a.slice(1))?.textContent?.trim()||``),a!==(n.getAttribute(`aria-label`)||n.getAttribute(`aria-description`))){let t=e.attr(n,`role`)!==`img`&&n.textContent?.trim();e.attr(n,l,a),e.attr(n,e.ARIA_LABEL,t?null:a),e.attr(n,e.ARIA_DESC,t?a:null),n.tabIndex===-1&&e.warn(`Missing tabindex="0" attribute on: `,n)}n===i&&t?.textContent!==a&&(t&&(t.textContent=a),a&&r&&document.activeElement===n&&e.announce(a))},y=o=>{let s=e.getComposedTarget(o);if(!s||r===s||t?.contains(s))return;r=s;let c=r?.closest?.(m)||void 0;if(i!==c&&(i&&x(),i=c,c)){if(o.type===`focus`||u||300>Date.now()-a)return b();o.type===`mousemove`&&(n=setTimeout(b,300))}},b=()=>{if(!i)return x();t||=e.tag(`div`,{class:`ds-tooltip`}),t.isConnected||document.body.appendChild(t);let n=i.closest(d),r=i.closest(f),a=i.closest(p),l=n!==r&&n?.contains(r);e.attr(t,`popover`,`manual`),e.attr(t,s,r?.getAttribute(s)||null),e.attr(t,o,l&&n?.getAttribute(o)||null),e.attr(t,c,a?.getAttribute(c)||null),v(i,!1),t.showPopover(),t.dispatchEvent(new CustomEvent(`ds-toggle-source`,{bubbles:!0,composed:!0,detail:i}))},x=e=>{(e?.type!==`keydown`||e?.key===`Escape`)&&(i&&t?.isConnected&&t.popover&&t.hidePopover(),e||(a=Date.now()),clearTimeout(n),i=void 0)};e.onHotReload(`tooltip`,()=>[e.on(document,`focus mousemove`,y,e.QUICK_EVENT),e.on(document,`keydown`,x,e.QUICK_EVENT),e.onMutation(document,_,{attributeFilter:[l],attributes:!0,childList:!0,subtree:!0})]),exports.initTooltips=g,exports.setTooltipElement=h;
|
|
1
|
+
const e=require("../utils/utils.cjs");require("../popover/popover.cjs");let t,n,r,i,a=0;const o=`data-color`,s=`data-color-scheme`,c=`data-size`,l=`data-tooltip`,u=e.isBrowser()&&/iPad|iPhone|iPod/.test(navigator.userAgent),d=`[${o}]`,f=`[${s}]`,p=`[${c}]`,m=`[${l}]`,h=n=>{n&&!(n instanceof HTMLElement)&&e.warn(`setTooltipElement expects an HTMLElement, got: `,n),x(),a=0,t=n||void 0},g=(e=document)=>{for(let t of e?.querySelectorAll(m)||[])v(t)},_=(e,n)=>{if(!n)return g();for(let e of n)if(e.target!==t)if(e.attributeName===l)v(e.target);else for(let t of e.addedNodes)t.nodeType===1&&(t.hasAttribute(l)?v(t):g(t))},v=(n,r=!0)=>{let a=e.attrOrCSS(n,l)||``;if(a[0]===`#`&&(a=e.getRoot(n).getElementById(a.slice(1))?.textContent?.trim()||``),a!==(n.getAttribute(`aria-label`)||n.getAttribute(`aria-description`))){let t=e.attr(n,`role`)!==`img`&&n.textContent?.trim();e.attr(n,l,a),e.attr(n,e.ARIA_LABEL,t?null:a),e.attr(n,e.ARIA_DESC,t?a:null),n.tabIndex===-1&&!n.hasAttribute(`tabindex`)&&n.nodeName!==`LABEL`&&e.warn(`Missing tabindex="0" attribute on: `,n)}n===i&&t?.textContent!==a&&(t&&(t.textContent=a),a&&r&&document.activeElement===n&&e.announce(a))},y=o=>{let s=e.getComposedTarget(o);if(!s||r===s||t?.contains(s))return;r=s;let c=r?.closest?.(m)||void 0;if(i!==c&&(i&&x(),i=c,c)){if(o.type===`focus`||u||300>Date.now()-a)return b();o.type===`mousemove`&&(n=setTimeout(b,300))}},b=()=>{if(!i)return x();t||=e.tag(`div`,{class:`ds-tooltip`}),t.isConnected||document.body.appendChild(t);let n=i.closest(d),r=i.closest(f),a=i.closest(p),l=n!==r&&n?.contains(r);e.attr(t,`popover`,`manual`),e.attr(t,s,r?.getAttribute(s)||null),e.attr(t,o,l&&n?.getAttribute(o)||null),e.attr(t,c,a?.getAttribute(c)||null),v(i,!1),t.showPopover(),t.dispatchEvent(new CustomEvent(`ds-toggle-source`,{bubbles:!0,composed:!0,detail:i}))},x=e=>{(e?.type!==`keydown`||e?.key===`Escape`)&&(i&&t?.isConnected&&t.popover&&t.hidePopover(),e||(a=Date.now()),clearTimeout(n),i=void 0)};e.onHotReload(`tooltip`,()=>[e.on(document,`focus mousemove`,y,e.QUICK_EVENT),e.on(document,`keydown`,x,e.QUICK_EVENT),e.onMutation(document,_,{attributeFilter:[l],attributes:!0,childList:!0,subtree:!0})]),exports.initTooltips=g,exports.setTooltipElement=h;
|
|
2
2
|
//# sourceMappingURL=tooltip.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.cjs","names":["isBrowser","attrOrCSS","getRoot","attr","ARIA_LABEL","ARIA_DESC","getComposedTarget","tag","onHotReload","on","QUICK_EVENT","onMutation"],"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":"wEAkBA,IAAI,EACA,EACA,EACA,EACA,EAAY,EAChB,MAAM,EAAa,aACb,EAAc,oBACd,EAAY,YACZ,EAAe,eAGf,EAASA,EAAAA,UAAU,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,EAAA,KAAK,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,GAAI,EAAE,gBAAkB,EAAc,EAAU,EAAE,MAAiB,OAEjE,IAAK,IAAM,KAAM,EAAE,WACb,EAAG,WAAa,IAChB,EAAG,aAAa,CAAY,EAAG,EAAU,CAAE,EAC1C,EAAa,CAAE,EAG5B,EAEM,GAAa,EAAa,EAAc,KAAS,CACrD,IAAI,EAAOC,EAAAA,UAAU,EAAI,CAAY,GAAK,GAQ1C,GAHI,EAAK,KAAO,MACd,EAAOC,EAAAA,QAAQ,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,EAAUC,EAAAA,KAAK,EAAI,MAAM,IAAM,OAAS,EAAG,aAAa,KAAK,EACnE,EAAA,KAAK,EAAI,EAAc,CAAI,EAC3B,EAAA,KAAK,EAAIC,EAAAA,WAAY,EAAU,KAAO,CAAI,EAC1C,EAAA,KAAK,EAAIC,EAAAA,UAAW,EAAU,EAAO,IAAI,
|
|
1
|
+
{"version":3,"file":"tooltip.cjs","names":["isBrowser","attrOrCSS","getRoot","attr","ARIA_LABEL","ARIA_DESC","getComposedTarget","tag","onHotReload","on","QUICK_EVENT","onMutation"],"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\n if (\n (el as HTMLElement).tabIndex === -1 && // Default tabindex property of all Elements is -1\n !el.hasAttribute('tabindex') && // But we want to allow attribute set to tabindex=\"-1\" for example in focus groups\n el.nodeName !== 'LABEL' // And labels will have focusable inputs, so their text is reactable anyway\n )\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":"wEAkBA,IAAI,EACA,EACA,EACA,EACA,EAAY,EAChB,MAAM,EAAa,aACb,EAAc,oBACd,EAAY,YACZ,EAAe,eAGf,EAASA,EAAAA,UAAU,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,EAAA,KAAK,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,GAAI,EAAE,gBAAkB,EAAc,EAAU,EAAE,MAAiB,OAEjE,IAAK,IAAM,KAAM,EAAE,WACb,EAAG,WAAa,IAChB,EAAG,aAAa,CAAY,EAAG,EAAU,CAAE,EAC1C,EAAa,CAAE,EAG5B,EAEM,GAAa,EAAa,EAAc,KAAS,CACrD,IAAI,EAAOC,EAAAA,UAAU,EAAI,CAAY,GAAK,GAQ1C,GAHI,EAAK,KAAO,MACd,EAAOC,EAAAA,QAAQ,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,EAAUC,EAAAA,KAAK,EAAI,MAAM,IAAM,OAAS,EAAG,aAAa,KAAK,EACnE,EAAA,KAAK,EAAI,EAAc,CAAI,EAC3B,EAAA,KAAK,EAAIC,EAAAA,WAAY,EAAU,KAAO,CAAI,EAC1C,EAAA,KAAK,EAAIC,EAAAA,UAAW,EAAU,EAAO,IAAI,EAGtC,EAAmB,WAAa,IACjC,CAAC,EAAG,aAAa,UAAU,GAC3B,EAAG,WAAa,SAEhB,EAAA,KAAK,sCAAuC,CAAE,CAClD,CACI,IAAO,GAAQ,GAAK,cAAgB,IAClC,IAAK,EAAI,YAAc,GACvB,GAAQ,GAAe,SAAS,gBAAkB,GAAI,EAAA,SAAS,CAAI,EAE3E,EAEM,EAAkB,GAAa,CACnC,IAAM,EAASC,EAAAA,kBAAkB,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,IAAMC,EAAAA,IAAI,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,EAAA,KAAK,EAAK,UAAW,QAAQ,EAC7B,EAAA,KAAK,EAAK,EAAa,GAAQ,aAAa,CAAW,GAAK,IAAI,EAChE,EAAA,KAAK,EAAK,EAAa,GAAW,GAAO,aAAa,CAAU,GAAM,IAAI,EAC1E,EAAA,KAAK,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,EAEAC,EAAAA,YAAY,cAAiB,CAC3BC,EAAAA,GAAG,SAAU,kBAAmB,EAAgBC,EAAAA,WAAW,EAC3DD,EAAAA,GAAG,SAAU,UAAW,EAAMC,EAAAA,WAAW,EACzCC,EAAAA,WAAW,SAAU,EAAiB,CACpC,gBAAiB,CAAC,CAAY,EAC9B,WAAY,GACZ,UAAW,GACX,QAAS,EACX,CAAC,CACH,CAAC"}
|
package/dist/cjs/utils/utils.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require("../packages/web/package.cjs"),t={passive:!0,capture:!0},n=()=>typeof window<`u`&&typeof document<`u`,r=()=>n()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),i=typeof HTMLElement>`u`?class{}:HTMLElement;function a(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const o=(e,...t)=>!n()||window.dsWarnings===!1||console.log(`\x1B[1mDesignsystemet:\x1B[m ${e}`,...t),s=(e,t,n)=>n===void 0?e.getAttribute(t):(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),c=(e,t)=>getComputedStyle(e).getPropertyValue(t).trim(),l=/^["']|["']$/g,u=(e,t)=>{let n=s(e,t);return n||=c(e,`--_ds-${t}`).replace(l,``).trim(),n||null},
|
|
1
|
+
const e=require("../packages/web/package.cjs"),t={passive:!0,capture:!0},n=()=>typeof window<`u`&&typeof document<`u`,r=()=>n()&&/^Win/i.test(navigator.userAgentData?.platform||navigator.platform),i=typeof HTMLElement>`u`?class{}:HTMLElement;function a(e,t){let n;return function(...r){clearTimeout(n),n=setTimeout(()=>e.apply(this,r),t)}}const o=(e,...t)=>!n()||window.dsWarnings===!1||console.log(`\x1B[1mDesignsystemet:\x1B[m ${e}`,...t),s=(e,t,n)=>n===void 0?e.getAttribute(t):(n===null?e.removeAttribute(t):e.getAttribute(t)!==n&&e.setAttribute(t,n),null),c=(e,t)=>getComputedStyle(e).getPropertyValue(t).trim(),l=/^["']|["']$/g,u=/\\(["'])/g,d=(e,t)=>{let n=s(e,t);return n||=c(e,`--_ds-${t}`).replace(l,``).replace(u,`$1`).trim(),n||null},f=e=>{let t=e?.getRootNode?.()||e?.ownerDocument;return t instanceof Document||t instanceof ShadowRoot?t:e?.ownerDocument||document},p=e=>{let t=e.target?.shadowRoot&&e.composedPath()[0]||e.target;return t?.nodeType===1?t:null},m=e=>{let t=new Set;for(;e;)t.add(e),e=e.nodeType===11?e.host:e.assignedSlot||e.parentNode;return t},h=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.addEventListener(t,...r);return()=>g(e,...t)},g=(e,...t)=>{let[n,...r]=t;for(let t of n.split(` `))e.removeEventListener(t,...r)},_=`_dsHotReloadCleanup${e.version}`,v=(e,t)=>{if(n()){window[_]||(window[_]=new Map);for(let t of window[_]?.get(e)||[])t();window[_]?.set(e,t())}},y=(e,t,r)=>{let i=()=>a.disconnect(),a=new MutationObserver(r=>{if(!n()||!e.isConnected)return i();t(e,r)});return t(e),a.observe(e,r),i},b=(e,t)=>{let n=document.createElement(e);if(t)for(let[e,r]of Object.entries(t))s(n,e,r);return n},x={define:(e,t)=>!n()||window.customElements.get(e)||window.customElements.define(e,t)};let S=0;function C(e){return n()?(window.dsUseId||(window.dsUseId=0),e&&!e.id&&(e.id=`:ds:${++window.dsUseId}`),e?.id||``):`:ds:${++S}`}let w,T=0,E=0;const D=e=>{clearTimeout(E),w&&(w.textContent=`${e}${T++%2?`\xA0`:``}`),e&&(E=setTimeout(D,2e3,``))},O=()=>{document.readyState===`complete`&&(w||(w=b(`div`,{"aria-live":`assertive`}),w.style.overflow=`hidden`,w.style.position=`fixed`,w.style.whiteSpace=`nowrap`,w.style.width=`1px`),w.isConnected||document.body.appendChild(w))};v(`announce`,()=>[h(document,`focus mouseover`,O,t)]),exports.ARIA_DESC=`aria-description`,exports.ARIA_LABEL=`aria-label`,exports.ARIA_LABELLEDBY=`aria-labelledby`,exports.DSElement=i,exports.QUICK_EVENT=t,exports.announce=D,exports.attr=s,exports.attrOrCSS=d,exports.customElements=x,exports.debounce=a,exports.getCSSProp=c,exports.getComposedPath=m,exports.getComposedTarget=p,exports.getRoot=f,exports.isBrowser=n,exports.isWindows=r,exports.off=g,exports.on=h,exports.onHotReload=v,exports.onMutation=y,exports.tag=b,exports.useId=C,exports.warn=o;
|
|
2
2
|
//# sourceMappingURL=utils.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.cjs","names":["version"],"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":"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"}
|
|
1
|
+
{"version":3,"file":"utils.cjs","names":["version"],"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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"}
|
|
@@ -7,14 +7,6 @@
|
|
|
7
7
|
"path": "src/index.ts",
|
|
8
8
|
"declarations": [],
|
|
9
9
|
"exports": [
|
|
10
|
-
{
|
|
11
|
-
"kind": "js",
|
|
12
|
-
"name": "*",
|
|
13
|
-
"declaration": {
|
|
14
|
-
"name": "*",
|
|
15
|
-
"package": "@u-elements/u-datalist"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
10
|
{
|
|
19
11
|
"kind": "js",
|
|
20
12
|
"name": "*",
|
|
@@ -535,6 +527,14 @@
|
|
|
535
527
|
}
|
|
536
528
|
],
|
|
537
529
|
"exports": [
|
|
530
|
+
{
|
|
531
|
+
"kind": "js",
|
|
532
|
+
"name": "*",
|
|
533
|
+
"declaration": {
|
|
534
|
+
"name": "*",
|
|
535
|
+
"package": "@u-elements/u-datalist"
|
|
536
|
+
}
|
|
537
|
+
},
|
|
538
538
|
{
|
|
539
539
|
"kind": "js",
|
|
540
540
|
"name": "DSSuggestionElement",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var e=`1.
|
|
1
|
+
var e=`1.22.0`;export{e as version};
|
|
2
2
|
//# sourceMappingURL=package.js.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{QUICK_EVENT as e,attr as t,attrOrCSS as n,customElements as r,off as i,on as a,onMutation as o,useId as s,warn as c}from"../utils/utils.js";import{UHTMLComboboxElement as l}from"@u-elements/u-combobox";const u=`data-empty
|
|
1
|
+
import{QUICK_EVENT as e,attr as t,attrOrCSS as n,customElements as r,off as i,on as a,onMutation as o,useId as s,warn as c}from"../utils/utils.js";import{UHTMLComboboxElement as l}from"@u-elements/u-combobox";export*from"@u-elements/u-datalist";const u=`data-empty`,d=`comboboxafterselect comboboxprogrammaticinput input`,f=/\{value\}|%s/,p=`data-sr-singular`,m=`data-sr-plural`,h=`added,clear,empty,found,invalid,items,of,plural,remove,removed,singular,toggle`.split(`,`).map(e=>`data-sr-${e}`);var g=class extends l{_unmutate;connectedCallback(){for(let e of h)t(this,e,n(this,e));super.connectedCallback(),this._unmutate=o(this,_,{childList:!0}),a(this,d,v,e),a(this,`toggle`,y,e)}disconnectedCallback(){super.disconnectedCallback(),this._unmutate?.(),this._unmutate=void 0,i(this,d,v,e),i(this,`toggle`,y,e)}};const _=e=>{let{control:n,list:r}=e;r||=e.querySelector(`u-datalist`),n&&t(n,`popovertarget`,r?s(r):null),r&&(t(r,m)||t(r,m,t(e,m)),t(r,p)||t(r,p,t(e,p)),t(r,`data-is-floating`,`true`),t(r,`popover`,`manual`)),v({currentTarget:e})},v=({currentTarget:e})=>{let{creatable:r,control:i,options:a,values:o}=e;if(!a)return;let s=i?.value.trim()||``,l=s.toLowerCase(),d,p=!1;for(let e of a)if(!d&&e.hasAttribute(u)?d=e:!p&&(!l||e.label?.toLowerCase()===l)&&(p=!0),p&&d)break;if(d&&(d.hidden=p,t(d,`label`,s),t(d,`value`,r?s:``),r)){let e=o.some(e=>e===s),r=n(d,u),i=!e&&r?.replace(f,()=>s)||s;r?t(d,u,r):c(`Missing ${u} value on:`,d),t(d,`disabled`,e&&d.textContent?`true`:null),t(d,`data-create`,i)}},y=e=>{let t=e.currentTarget,n=e.newState===`open`&&t.control;n&&t.list?.dispatchEvent(new CustomEvent(`ds-toggle-source`,{bubbles:!0,composed:!0,detail:n}))};r.define(`ds-suggestion`,g);export{g as DSSuggestionElement};
|
|
2
2
|
//# sourceMappingURL=suggestion.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"suggestion.js","names":[],"sources":["../../../src/suggestion/suggestion.ts"],"sourcesContent":["import { UHTMLComboboxElement } from '@u-elements/u-combobox';\nimport {\n attr,\n attrOrCSS,\n customElements,\n off,\n on,\n onMutation,\n QUICK_EVENT,\n useId,\n warn,\n} from '../utils/utils';\n\nconst ATTR_EMPTY = 'data-empty';\nconst ATTR_CREATE = 'data-create';\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ds-suggestion': DSSuggestionElement;\n }\n}\n\nexport class DSSuggestionElement extends UHTMLComboboxElement {\n _unmutate?: ReturnType<typeof onMutation>; // Using underscore instead of private fields for backwards compatibility\n\n connectedCallback() {\n super.connectedCallback()
|
|
1
|
+
{"version":3,"file":"suggestion.js","names":[],"sources":["../../../src/suggestion/suggestion.ts"],"sourcesContent":["import { UHTMLComboboxElement } from '@u-elements/u-combobox';\nimport {\n attr,\n attrOrCSS,\n customElements,\n off,\n on,\n onMutation,\n QUICK_EVENT,\n useId,\n warn,\n} from '../utils/utils';\n\n// Load and export u-datalist since this is a pure polyfill and not custom Designsystemet elements, should run before suggestion\nexport * from '@u-elements/u-datalist';\n\nconst ATTR_EMPTY = 'data-empty';\nconst ATTR_CREATE = 'data-create';\nconst EVENTS_EMPTY = 'comboboxafterselect comboboxprogrammaticinput input';\nconst REGEX_CREATE = /\\{value\\}|%s/; // Support both new %s and old {value} syntax\nconst SINGULAR = 'data-sr-singular';\nconst PLURAL = 'data-sr-plural';\nconst TEXTS =\n 'added,clear,empty,found,invalid,items,of,plural,remove,removed,singular,toggle'\n .split(',')\n .map((key) => `data-sr-${key}`);\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'ds-suggestion': DSSuggestionElement;\n }\n}\n\nexport class DSSuggestionElement extends UHTMLComboboxElement {\n _unmutate?: ReturnType<typeof onMutation>; // Using underscore instead of private fields for backwards compatibility\n\n connectedCallback() {\n for (const key of TEXTS) attr(this, key, attrOrCSS(this, key)); // Convert CSS variables to data-sr-attributes\n super.connectedCallback(); // Run after setting data-sr-attributes\n\n this._unmutate = onMutation(this, render, { childList: true }); // .control and .list are direct children of the custom element\n on(this, EVENTS_EMPTY, handleEmpty, QUICK_EVENT);\n on(this, 'toggle', polyfillToggleSource, QUICK_EVENT);\n }\n disconnectedCallback() {\n super.disconnectedCallback();\n this._unmutate?.();\n this._unmutate = undefined;\n off(this, EVENTS_EMPTY, handleEmpty, QUICK_EVENT);\n off(this, 'toggle', polyfillToggleSource, QUICK_EVENT);\n }\n}\n\nconst render = (self: DSSuggestionElement) => {\n let { control, list } = self;\n if (!list) list = self.querySelector('u-datalist'); // Fallback to u-datalist since React can render the ds-suggestion before u-datalist is connected\n\n if (control) attr(control, 'popovertarget', list ? useId(list) : null);\n if (list) {\n if (!attr(list, PLURAL)) attr(list, PLURAL, attr(self, PLURAL)); // Inherit translations from u-combobox to u-datalist\n if (!attr(list, SINGULAR)) attr(list, SINGULAR, attr(self, SINGULAR)); // Inherit translations from u-combobox to u-datalist\n attr(list, 'data-is-floating', 'true'); // identifier for css to toggle opacity when it is placed by floating-ui.\n attr(list, 'popover', 'manual'); // Ensure popover attribute is set on the list\n }\n handleEmpty({ currentTarget: self });\n};\n\nconst handleEmpty = ({ currentTarget: self }: Pick<Event, 'currentTarget'>) => {\n const { creatable, control, options, values } = self as DSSuggestionElement;\n if (!options) return;\n\n const value = control?.value.trim() || '';\n const query = value.toLowerCase();\n let emptyOp: HTMLOptionElement | undefined;\n let hasMatch = false;\n\n for (const opt of options) {\n if (!emptyOp && opt.hasAttribute(ATTR_EMPTY)) emptyOp = opt;\n else if (!hasMatch && (!query || opt.label?.toLowerCase() === query))\n hasMatch = true;\n if (hasMatch && emptyOp) break; // Speed up if both conditions are met\n }\n if (!emptyOp) return;\n\n emptyOp.hidden = hasMatch; // Hide initial empty state when options exist, or when the query already exists\n attr(emptyOp, 'label', value); // Ensures option is not filtered out by <u-combobox>\n attr(emptyOp, 'value', creatable ? value : ''); // Ensures clicking option does nothing\n\n if (creatable) {\n const found = values.some((val) => val === value); // Only show \"Legg til\" if not already created\n const text = attrOrCSS(emptyOp, ATTR_EMPTY);\n const hint = (!found && text?.replace(REGEX_CREATE, () => value)) || value;\n\n if (!text) warn(`Missing ${ATTR_EMPTY} value on:`, emptyOp);\n else attr(emptyOp, ATTR_EMPTY, text); // Speed up by caching attribute value\n\n attr(emptyOp, 'disabled', found && emptyOp.textContent ? 'true' : null); // Hide permanent hint if created\n attr(emptyOp, ATTR_CREATE, hint);\n }\n};\n\n// Since showPopover({ source }) is not supported in all browsers yet:\nconst polyfillToggleSource = (event: Partial<ToggleEvent>) => {\n const self = event.currentTarget as DSSuggestionElement;\n const detail = event.newState === 'open' && self.control; // .control comes from UHTMLComboboxElement\n\n if (detail)\n self.list?.dispatchEvent(\n new CustomEvent('ds-toggle-source', {\n bubbles: true,\n composed: true, // Enable bubbling out of shadow DOM boundaries\n detail, // Since showPopover({ source }) is not supported in all browsers yet\n }),\n );\n};\n\n// Ensure u-datalist is defined before ds-suggestion\ncustomElements.define('ds-suggestion', DSSuggestionElement);\n"],"mappings":"qPAgBA,MAAM,EAAa,aAEb,EAAe,sDACf,EAAe,eACf,EAAW,mBACX,EAAS,iBACT,EACJ,iFACG,MAAM,GAAG,CAAC,CACV,IAAK,GAAQ,WAAW,GAAK,EAQlC,IAAa,EAAb,cAAyC,CAAqB,CAC5D,UAEA,mBAAoB,CAClB,IAAK,IAAM,KAAO,EAAO,EAAK,KAAM,EAAK,EAAU,KAAM,CAAG,CAAC,EAC7D,MAAM,kBAAkB,EAExB,KAAK,UAAY,EAAW,KAAM,EAAQ,CAAE,UAAW,EAAK,CAAC,EAC7D,EAAG,KAAM,EAAc,EAAa,CAAW,EAC/C,EAAG,KAAM,SAAU,EAAsB,CAAW,CACtD,CACA,sBAAuB,CACrB,MAAM,qBAAqB,EAC3B,KAAK,YAAY,EACjB,KAAK,UAAY,IAAA,GACjB,EAAI,KAAM,EAAc,EAAa,CAAW,EAChD,EAAI,KAAM,SAAU,EAAsB,CAAW,CACvD,CACF,EAEA,MAAM,EAAU,GAA8B,CAC5C,GAAI,CAAE,UAAS,QAAS,EACxB,AAAW,IAAO,EAAK,cAAc,YAAY,EAE7C,GAAS,EAAK,EAAS,gBAAiB,EAAO,EAAM,CAAI,EAAI,IAAI,EACjE,IACG,EAAK,EAAM,CAAM,GAAG,EAAK,EAAM,EAAQ,EAAK,EAAM,CAAM,CAAC,EACzD,EAAK,EAAM,CAAQ,GAAG,EAAK,EAAM,EAAU,EAAK,EAAM,CAAQ,CAAC,EACpE,EAAK,EAAM,mBAAoB,MAAM,EACrC,EAAK,EAAM,UAAW,QAAQ,GAEhC,EAAY,CAAE,cAAe,CAAK,CAAC,CACrC,EAEM,GAAe,CAAE,cAAe,KAAyC,CAC7E,GAAM,CAAE,YAAW,UAAS,UAAS,UAAW,EAChD,GAAI,CAAC,EAAS,OAEd,IAAM,EAAQ,GAAS,MAAM,KAAK,GAAK,GACjC,EAAQ,EAAM,YAAY,EAC5B,EACA,EAAW,GAEf,IAAK,IAAM,KAAO,EAIhB,GAHI,CAAC,GAAW,EAAI,aAAa,CAAU,EAAG,EAAU,EAC/C,CAAC,IAAa,CAAC,GAAS,EAAI,OAAO,YAAY,IAAM,KAC5D,EAAW,IACT,GAAY,EAAS,MAEtB,OAEL,EAAQ,OAAS,EACjB,EAAK,EAAS,QAAS,CAAK,EAC5B,EAAK,EAAS,QAAS,EAAY,EAAQ,EAAE,EAEzC,GAAW,CACb,IAAM,EAAQ,EAAO,KAAM,GAAQ,IAAQ,CAAK,EAC1C,EAAO,EAAU,EAAS,CAAU,EACpC,EAAQ,CAAC,GAAS,GAAM,QAAQ,MAAoB,CAAK,GAAM,EAEhE,EACA,EAAK,EAAS,EAAY,CAAI,EADxB,EAAK,WAAW,EAAW,YAAa,CAAO,EAG1D,EAAK,EAAS,WAAY,GAAS,EAAQ,YAAc,OAAS,IAAI,EACtE,EAAK,EAAS,cAAa,CAAI,CACjC,CACF,EAGM,EAAwB,GAAgC,CAC5D,IAAM,EAAO,EAAM,cACb,EAAS,EAAM,WAAa,QAAU,EAAK,QAE7C,GACF,EAAK,MAAM,cACT,IAAI,YAAY,mBAAoB,CAClC,QAAS,GACT,SAAU,GACV,QACF,CAAC,CACH,CACJ,EAGA,EAAe,OAAO,gBAAiB,CAAmB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toggle-group.js","names":[],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n ARIA_LABEL,\n ARIA_LABELLEDBY,\n attr,\n attrOrCSS,\n getComposedPath,\n getComposedTarget,\n on,\n onHotReload,\n onMutation,\n warn,\n} from '../utils/utils';\n\nconst ATTR_FOCUSGROUP = 'focusgroup';\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\nlet IS_ARROW: boolean | undefined; // Used to prevent clicks that are a result of keyboard navigation\n\nconst handleMutations = (root: Document, records?: MutationRecord[]) => {\n for (const r of records || [null]) {\n if (r?.attributeName === ATTR_TOGGLEGROUP) deprecate(r.target as Element);\n else if (!r || r.addedNodes.length) {\n const scope = (r?.target as Element) || root;\n for (const el of scope.querySelectorAll(SELECTOR_TOGGLEGROUP))\n deprecate(el);\n }\n }\n};\n\n// Prevent clicks that are a result of keyboard navigation\nconst handleRadioClick = (e: Event & Partial<PointerEvent>) => {\n if (IS_ARROW && getRadioInFocusGroup(e)) {\n e.preventDefault(); // Only handle clicks that are a result of keyboard navigation\n e.stopImmediatePropagation(); // Prevent stopped \"click\" from reaching React and other listeners\n }\n};\n\nconst resetIsArrow = () => (IS_ARROW = false);\nconst handleKeydown = (e: Event & Partial<KeyboardEvent>) => {\n IS_ARROW = e.key?.startsWith('Arrow');\n if (e.key === 'Enter') getRadioInFocusGroup(e)?.click(); // Allow Enter to activate the radio input\n if (IS_ARROW) setTimeout(resetIsArrow, 0); // Reset after event loop so we can check if next focus event is a result of arrow key navigation\n};\n\n// This function does not handle nested focusgroups, but toggle-groups should not be nested so this is not a problem\nconst getRadioInFocusGroup = (e: Event) => {\n const el = getComposedTarget(e) as HTMLInputElement | null;\n if (el?.nodeName !== 'INPUT' || el.type !== 'radio' || !el.name) return;\n for (const group of getComposedPath(el) as Set<Element>) {\n if (group.nodeType !== 1 || !group.hasAttribute(ATTR_FOCUSGROUP)) continue; // Ignore non-group nodes\n if (!attr(group, ATTR_FOCUSGROUP)?.includes('radiogroup')) return; // Ignore invalid groups\n return el;\n }\n};\n\nconst deprecate = (el: Element) => {\n if (el.hasAttribute(ATTR_FOCUSGROUP)) return; // Ignore if already migrated\n const label = attrOrCSS(el, ATTR_TOGGLEGROUP);\n const labelledby = attr(el, ARIA_LABELLEDBY)?.trim();\n const message = `Please use ${ATTR_FOCUSGROUP}=\"radiogroup\" and ${labelledby ? ARIA_LABELLEDBY : ARIA_LABEL}=\"${labelledby || label}\" instead of deprecated ${ATTR_TOGGLEGROUP} on:`;\n\n attr(el, ARIA_LABEL, labelledby ? null : label);\n attr(el, ATTR_FOCUSGROUP, 'radiogroup');\n warn(message, el);\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'click', handleRadioClick, true), // Use capture to ensure we run before React and other listeners\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleMutations, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"4LAaA,MAAM,EAAkB,aAClB,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAClD,IAAI,EAEJ,MAAM,GAAmB,EAAgB,IAA+B,CACtE,IAAK,IAAM,KAAK,GAAW,CAAC,IAAI,EAC9B,GAAI,GAAG,gBAAkB,EAAkB,EAAU,EAAE,MAAiB,OACnE,GAAI,CAAC,GAAK,EAAE,WAAW,OAAQ,CAClC,IAAM,EAAS,GAAG,QAAsB,EACxC,IAAK,IAAM,KAAM,EAAM,iBAAiB,CAAoB,EAC1D,EAAU,CAAE,CAChB,CAEJ,EAGM,EAAoB,GAAqC,CACzD,GAAY,EAAqB,CAAC,IACpC,EAAE,eAAe,EACjB,EAAE,yBAAyB,EAE/B,EAEM,MAAsB,EAAW,GACjC,EAAiB,GAAsC,CAC3D,EAAW,EAAE,KAAK,WAAW,OAAO,EAChC,EAAE,MAAQ,SAAS,EAAqB,CAAC,CAAC,EAAE,MAAM,EAClD,GAAU,WAAW,EAAc,CAAC,CAC1C,EAGM,EAAwB,GAAa,CACzC,IAAM,EAAK,EAAkB,CAAC,EAC1B,QAAI,WAAa,SAAW,EAAG,OAAS,SAAW,CAAC,EAAG,MAC3D,KAAK,IAAM,KAAS,EAAgB,CAAE,EAChC,OAAM,WAAa,GAAK,CAAC,EAAM,aAAa,CAAe,GAE/D,OADK,EAAK,EAAO,CAAe,CAAC,EAAE,SAAS,YAAY,EACjD,EADoD,MAE7D,CACF,
|
|
1
|
+
{"version":3,"file":"toggle-group.js","names":[],"sources":["../../../src/toggle-group/toggle-group.ts"],"sourcesContent":["import {\n ARIA_LABEL,\n ARIA_LABELLEDBY,\n attr,\n attrOrCSS,\n getComposedPath,\n getComposedTarget,\n on,\n onHotReload,\n onMutation,\n warn,\n} from '../utils/utils';\n\nconst ATTR_FOCUSGROUP = 'focusgroup';\nconst ATTR_TOGGLEGROUP = 'data-toggle-group';\nconst SELECTOR_TOGGLEGROUP = `[${ATTR_TOGGLEGROUP}]`;\nlet IS_ARROW: boolean | undefined; // Used to prevent clicks that are a result of keyboard navigation\n\nconst handleMutations = (root: Document, records?: MutationRecord[]) => {\n for (const r of records || [null]) {\n if (r?.attributeName === ATTR_TOGGLEGROUP) deprecate(r.target as Element);\n else if (!r || r.addedNodes.length) {\n const scope = (r?.target as Element) || root;\n for (const el of scope.querySelectorAll(SELECTOR_TOGGLEGROUP))\n deprecate(el);\n }\n }\n};\n\n// Prevent clicks that are a result of keyboard navigation\nconst handleRadioClick = (e: Event & Partial<PointerEvent>) => {\n if (IS_ARROW && getRadioInFocusGroup(e)) {\n e.preventDefault(); // Only handle clicks that are a result of keyboard navigation\n e.stopImmediatePropagation(); // Prevent stopped \"click\" from reaching React and other listeners\n }\n};\n\nconst resetIsArrow = () => (IS_ARROW = false);\nconst handleKeydown = (e: Event & Partial<KeyboardEvent>) => {\n IS_ARROW = e.key?.startsWith('Arrow');\n if (e.key === 'Enter') getRadioInFocusGroup(e)?.click(); // Allow Enter to activate the radio input\n if (IS_ARROW) setTimeout(resetIsArrow, 0); // Reset after event loop so we can check if next focus event is a result of arrow key navigation\n};\n\n// This function does not handle nested focusgroups, but toggle-groups should not be nested so this is not a problem\nconst getRadioInFocusGroup = (e: Event) => {\n const el = getComposedTarget(e) as HTMLInputElement | null;\n if (el?.nodeName !== 'INPUT' || el.type !== 'radio' || !el.name) return;\n for (const group of getComposedPath(el) as Set<Element>) {\n if (group.nodeType !== 1 || !group.hasAttribute(ATTR_FOCUSGROUP)) continue; // Ignore non-group nodes\n if (!attr(group, ATTR_FOCUSGROUP)?.includes('radiogroup')) return; // Ignore invalid groups\n return el;\n }\n};\n\n// @deprecated: Supports migration from data-toggle-group to focusgroup=\"radiogroup\".\nconst deprecate = (el: Element) => {\n if (el.hasAttribute(ATTR_FOCUSGROUP)) return; // Ignore if already migrated\n const label = attrOrCSS(el, ATTR_TOGGLEGROUP);\n const labelledby = attr(el, ARIA_LABELLEDBY)?.trim();\n const message = `Please use ${ATTR_FOCUSGROUP}=\"radiogroup\" and ${labelledby ? ARIA_LABELLEDBY : ARIA_LABEL}=\"${labelledby || label}\" instead of deprecated ${ATTR_TOGGLEGROUP} on:`;\n\n attr(el, ARIA_LABEL, labelledby ? null : label);\n attr(el, ATTR_FOCUSGROUP, 'radiogroup');\n warn(message, el);\n};\n\nonHotReload('toggle-group', () => [\n on(document, 'click', handleRadioClick, true), // Use capture to ensure we run before React and other listeners\n on(document, 'keydown', handleKeydown),\n onMutation(document, handleMutations, {\n attributeFilter: [ATTR_TOGGLEGROUP],\n attributes: true,\n childList: true,\n subtree: true,\n }),\n]);\n"],"mappings":"4LAaA,MAAM,EAAkB,aAClB,EAAmB,oBACnB,EAAuB,IAAI,EAAiB,GAClD,IAAI,EAEJ,MAAM,GAAmB,EAAgB,IAA+B,CACtE,IAAK,IAAM,KAAK,GAAW,CAAC,IAAI,EAC9B,GAAI,GAAG,gBAAkB,EAAkB,EAAU,EAAE,MAAiB,OACnE,GAAI,CAAC,GAAK,EAAE,WAAW,OAAQ,CAClC,IAAM,EAAS,GAAG,QAAsB,EACxC,IAAK,IAAM,KAAM,EAAM,iBAAiB,CAAoB,EAC1D,EAAU,CAAE,CAChB,CAEJ,EAGM,EAAoB,GAAqC,CACzD,GAAY,EAAqB,CAAC,IACpC,EAAE,eAAe,EACjB,EAAE,yBAAyB,EAE/B,EAEM,MAAsB,EAAW,GACjC,EAAiB,GAAsC,CAC3D,EAAW,EAAE,KAAK,WAAW,OAAO,EAChC,EAAE,MAAQ,SAAS,EAAqB,CAAC,CAAC,EAAE,MAAM,EAClD,GAAU,WAAW,EAAc,CAAC,CAC1C,EAGM,EAAwB,GAAa,CACzC,IAAM,EAAK,EAAkB,CAAC,EAC1B,QAAI,WAAa,SAAW,EAAG,OAAS,SAAW,CAAC,EAAG,MAC3D,KAAK,IAAM,KAAS,EAAgB,CAAE,EAChC,OAAM,WAAa,GAAK,CAAC,EAAM,aAAa,CAAe,GAE/D,OADK,EAAK,EAAO,CAAe,CAAC,EAAE,SAAS,YAAY,EACjD,EADoD,MAE7D,CACF,EAGM,EAAa,GAAgB,CACjC,GAAI,EAAG,aAAa,CAAe,EAAG,OACtC,IAAM,EAAQ,EAAU,EAAI,CAAgB,EACtC,EAAa,EAAK,EAAI,CAAe,CAAC,EAAE,KAAK,EAC7C,EAAU,cAAc,EAAgB,oBAAoB,EAAa,EAAkB,EAAW,IAAI,GAAc,EAAM,0BAA0B,EAAiB,MAE/K,EAAK,EAAI,EAAY,EAAa,KAAO,CAAK,EAC9C,EAAK,EAAI,EAAiB,YAAY,EACtC,EAAK,EAAS,CAAE,CAClB,EAEA,EAAY,mBAAsB,CAChC,EAAG,SAAU,QAAS,EAAkB,EAAI,EAC5C,EAAG,SAAU,UAAW,CAAa,EACrC,EAAW,SAAU,EAAiB,CACpC,gBAAiB,CAAC,CAAgB,EAClC,WAAY,GACZ,UAAW,GACX,QAAS,EACX,CAAC,CACH,CAAC"}
|
|
@@ -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&&!n.hasAttribute(`tabindex`)&&n.nodeName!==`LABEL`&&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":"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,GAAI,EAAE,gBAAkB,EAAc,EAAU,EAAE,MAAiB,OAEjE,IAAK,IAAM,KAAM,EAAE,WACb,EAAG,WAAa,IAChB,EAAG,aAAa,CAAY,EAAG,EAAU,CAAE,EAC1C,EAAa,CAAE,EAG5B,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,
|
|
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\n if (\n (el as HTMLElement).tabIndex === -1 && // Default tabindex property of all Elements is -1\n !el.hasAttribute('tabindex') && // But we want to allow attribute set to tabindex=\"-1\" for example in focus groups\n el.nodeName !== 'LABEL' // And labels will have focusable inputs, so their text is reactable anyway\n )\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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"}
|
package/dist/esm/utils/utils.js
CHANGED
|
@@ -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},
|
|
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":"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"}
|
|
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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"}
|