@nordhealth/components 5.0.0 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/custom-elements.json +26062 -25769
- package/lib/AsideDrawer.js +2 -2
- package/lib/AsideDrawer.js.map +1 -1
- package/lib/AsideTrigger.js +2 -2
- package/lib/AsideTrigger.js.map +1 -1
- package/lib/Autocomplete.js +1 -1
- package/lib/Autocomplete.js.map +1 -1
- package/lib/Button.js +2 -2
- package/lib/Button.js.map +1 -1
- package/lib/Calendar.js +1 -1
- package/lib/Combobox.js +1 -1
- package/lib/Combobox.js.map +1 -1
- package/lib/ComboboxBase.js +1 -1
- package/lib/ComboboxBase.js.map +1 -1
- package/lib/CommandMenu.js +1 -1
- package/lib/CommandMenu.js.map +1 -1
- package/lib/DatePicker.js +1 -1
- package/lib/DatePicker.js.map +1 -1
- package/lib/DateRangePicker.js +1 -1
- package/lib/DateRangePicker.js.map +1 -1
- package/lib/Dropdown.js +1 -1
- package/lib/Dropdown.js.map +1 -1
- package/lib/DropdownSubmenu.js +1 -1
- package/lib/FilterAddButton.js +1 -1
- package/lib/FilterAddButton.js.map +1 -1
- package/lib/FilterDateRange.js +1 -1
- package/lib/FilterDateRange.js.map +1 -1
- package/lib/FilterDropdown.js +1 -1
- package/lib/FilterDropdown.js.map +1 -1
- package/lib/FilterField.js +1 -1
- package/lib/FilterField.js.map +1 -1
- package/lib/FilterInput.js +1 -1
- package/lib/FilterInput.js.map +1 -1
- package/lib/FilterResetButton.js +1 -1
- package/lib/FilterResetButton.js.map +1 -1
- package/lib/Header.js +1 -1
- package/lib/Header.js.map +1 -1
- package/lib/IconManager.js +1 -1
- package/lib/Input.js +1 -1
- package/lib/Item.js +1 -1
- package/lib/Item.js.map +1 -1
- package/lib/Layout.js +1 -1
- package/lib/Layout.js.map +1 -1
- package/lib/LocalizeController.js +1 -1
- package/lib/LocalizeController.js.map +1 -1
- package/lib/Message.js +1 -1
- package/lib/Message.js.map +1 -1
- package/lib/Modal.js +1 -1
- package/lib/Modal.js.map +1 -1
- package/lib/NavGroup.js +1 -1
- package/lib/NavGroup.js.map +1 -1
- package/lib/NavItem.js +1 -1
- package/lib/NavItem.js.map +1 -1
- package/lib/NavToggle-ROsuaJkW.js +2 -0
- package/lib/{NavToggle-BY83xpx0.js.map → NavToggle-ROsuaJkW.js.map} +1 -1
- package/lib/NavToggle.js +1 -1
- package/lib/Navigation.js +1 -1
- package/lib/Navigation.js.map +1 -1
- package/lib/Notification.js +1 -1
- package/lib/Notification.js.map +1 -1
- package/lib/NumberField.js +1 -1
- package/lib/NumberField.js.map +1 -1
- package/lib/OtpField.js +1 -1
- package/lib/OtpField.js.map +1 -1
- package/lib/Outline.js +1 -1
- package/lib/Outline.js.map +1 -1
- package/lib/Pagination.js +1 -1
- package/lib/Pagination.js.map +1 -1
- package/lib/Popout-B6Hirj3y.js +2 -0
- package/lib/{Popout-CFtFoCaA.js.map → Popout-B6Hirj3y.js.map} +1 -1
- package/lib/Popout.js +1 -1
- package/lib/Range.js +1 -1
- package/lib/SegmentedControlItem.js +1 -1
- package/lib/Tag.js +1 -1
- package/lib/Tag.js.map +1 -1
- package/lib/TextField-BYSciLeB.js +2 -0
- package/lib/TextField-BYSciLeB.js.map +1 -0
- package/lib/Textarea.js +1 -1
- package/lib/Textarea.js.map +1 -1
- package/lib/TimePicker.js +1 -1
- package/lib/TimePicker.js.map +1 -1
- package/lib/bundle.js +13 -13
- package/lib/bundle.js.map +1 -1
- package/lib/en-us.js +1 -1
- package/lib/en-us.js.map +1 -1
- package/lib/fi-fi.js +1 -1
- package/lib/fi-fi.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/localization13.js +1 -1
- package/lib/localization13.js.map +1 -1
- package/lib/localization14.js +1 -1
- package/lib/localization14.js.map +1 -1
- package/lib/localization15.js +1 -1
- package/lib/localization15.js.map +1 -1
- package/lib/localization16.js +1 -1
- package/lib/localization16.js.map +1 -1
- package/lib/localization17.js +1 -1
- package/lib/localization17.js.map +1 -1
- package/lib/localization18.js +1 -1
- package/lib/localization18.js.map +1 -1
- package/lib/localization19.js +1 -1
- package/lib/localization19.js.map +1 -1
- package/lib/localization20.js +1 -1
- package/lib/localization20.js.map +1 -1
- package/lib/localization21.js +1 -1
- package/lib/localization21.js.map +1 -1
- package/lib/localization22.js +1 -1
- package/lib/localization22.js.map +1 -1
- package/lib/localization23.js +1 -1
- package/lib/localization23.js.map +1 -1
- package/lib/localization24.js +2 -0
- package/lib/localization24.js.map +1 -0
- package/lib/nav-context.js +2 -0
- package/lib/nav-context.js.map +1 -0
- package/lib/src/aside-trigger/AsideTrigger.d.ts +28 -2
- package/lib/src/button/Button.d.ts +7 -0
- package/lib/src/dropdown/Dropdown.d.ts +3 -0
- package/lib/src/layout/Layout.d.ts +12 -0
- package/lib/src/layout/localization.d.ts +5 -0
- package/lib/src/localization/en-us.d.ts +4 -0
- package/lib/src/localization/translation.d.ts +4 -0
- package/lib/src/nav-group/NavGroup.d.ts +1 -0
- package/lib/src/nav-group/nav-context.d.ts +11 -0
- package/lib/src/nav-item/NavItem.d.ts +45 -0
- package/lib/src/navigation/Navigation.d.ts +18 -0
- package/lib/translation.js +1 -1
- package/lib/translation.js.map +1 -1
- package/package.json +8 -8
- package/lib/NavToggle-BY83xpx0.js +0 -2
- package/lib/Popout-CFtFoCaA.js +0 -2
- package/lib/TextField-CSKvieoG.js +0 -2
- package/lib/TextField-CSKvieoG.js.map +0 -1
package/lib/AsideDrawer.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{_ as e}from"./tslib.es6-CmLYFWVC.js";import{css as i,LitElement as
|
|
1
|
+
import{_ as e}from"./tslib.es6-CmLYFWVC.js";import{css as i,LitElement as t,html as r,isServer as n}from"lit";import{property as a,state as d,customElement as s}from"lit/decorators.js";import{o as l}from"./observe-D0n0zOfU.js";import{N as o}from"./events-Bv6wNHwJ.js";import{c as h}from"./number-Dg2vCfGd.js";import{s as u}from"./storage-CGZ-YX4-.js";import{s as c}from"./Component-BJ5O5n8k.js";const m=i`:host{--_n-aside-drawer-inline-size:var(--n-aside-drawer-inline-size,420px);--_n-aside-drawer-min-inline-size:var(--n-aside-drawer-min-inline-size,280px);--_n-aside-drawer-max-inline-size:var(--n-aside-drawer-max-inline-size,none);--_n-aside-drawer-background-color:var(--n-aside-drawer-background-color,var(--n-color-surface));--_n-aside-drawer-border-color:var(--n-aside-drawer-border-color,var(--n-color-border));--_n-aside-drawer-border-width:var(--n-aside-drawer-border-width,0);--_n-aside-drawer-border-inline-end-width:var(
|
|
2
2
|
--n-aside-drawer-border-inline-end-width,var(--_n-aside-drawer-border-width)
|
|
3
3
|
);--_n-aside-drawer-border-block-start-width:var(
|
|
4
4
|
--n-aside-drawer-border-block-start-width,var(--_n-aside-drawer-border-width)
|
|
@@ -6,5 +6,5 @@ import{_ as e}from"./tslib.es6-CmLYFWVC.js";import{css as i,LitElement as r,html
|
|
|
6
6
|
--n-aside-drawer-border-block-end-width,var(--_n-aside-drawer-border-width)
|
|
7
7
|
);--_n-aside-drawer-border-radius:var(--n-aside-drawer-border-radius,0);--_n-aside-drawer-box-shadow:var(--n-aside-drawer-box-shadow,var(--n-box-shadow-card));--_n-aside-drawer-margin-block:var(--n-aside-drawer-margin-block,0);--_n-aside-drawer-margin-inline-start:var(--n-aside-drawer-margin-inline-start,0);--_n-aside-drawer-margin-inline-end:var(--n-aside-drawer-margin-inline-end,0);--_n-aside-drawer-fullscreen-inset-block-start:var(
|
|
8
8
|
--n-aside-drawer-fullscreen-inset-block-start,var(--n-layout-header-size,0)
|
|
9
|
-
);--_n-aside-drawer-z-index:var(--n-aside-z-index,var(--n-index-nav));--_n-aside-drawer-fullscreen-z-index:var(--n-aside-z-index-fullscreen,var(--n-index-top-bar,1000));background:var(--_n-aside-drawer-background-color);box-shadow:var(--_n-aside-drawer-box-shadow);box-sizing:border-box;display:block;inline-size:var(--_n-aside-drawer-inline-size);margin-block-end:var(--_n-aside-drawer-margin-block);margin-block-start:var(--_n-aside-drawer-margin-block);margin-inline-end:var(--_n-aside-drawer-margin-inline-end);margin-inline-start:var(--_n-aside-drawer-margin-inline-start);max-inline-size:var(--_n-aside-drawer-max-inline-size);min-inline-size:var(--_n-aside-drawer-min-inline-size);position:relative;z-index:var(--_n-aside-drawer-z-index)}.n-aside-drawer{block-size:100%;inline-size:100%}::slotted(*){block-size:100%}::slotted(nord-drawer){--n-drawer-border-color:var(--_n-aside-drawer-border-color);--n-drawer-border-radius:var(--_n-aside-drawer-border-radius);--n-drawer-border-inline-start-width:var(--_n-aside-drawer-border-width);--n-drawer-border-inline-end-width:var(--_n-aside-drawer-border-inline-end-width);--n-drawer-border-block-start-width:var(--_n-aside-drawer-border-block-start-width);--n-drawer-border-block-end-width:var(--_n-aside-drawer-border-block-end-width)}@media (min-width:768px){:host([data-animating-fullscreen]),:host([fullscreen]){inset-block-end:var(--n-layout-footer-block-size,0);inset-block-start:var(--_n-aside-drawer-fullscreen-inset-block-start);inset-inline-end:calc(var(--n-aside-rail-width, 48px) + var(--_n-aside-rail-gap, 0px));margin-inline:0;position:fixed;z-index:var(--_n-aside-drawer-fullscreen-z-index)}:host([fullscreen]){inline-size:min(var(--_n-aside-drawer-fullscreen-max,100vw),var(--_n-aside-drawer-fullscreen-available,100vw));max-inline-size:min(var(--_n-aside-drawer-fullscreen-max,100vw),var(--_n-aside-drawer-fullscreen-available,100vw));min-inline-size:0}}`;class
|
|
9
|
+
);--_n-aside-drawer-z-index:var(--n-aside-z-index,var(--n-index-nav));--_n-aside-drawer-fullscreen-z-index:var(--n-aside-z-index-fullscreen,var(--n-index-top-bar,1000));background:var(--_n-aside-drawer-background-color);box-shadow:var(--_n-aside-drawer-box-shadow);box-sizing:border-box;display:block;inline-size:var(--_n-aside-drawer-inline-size);margin-block-end:var(--_n-aside-drawer-margin-block);margin-block-start:var(--_n-aside-drawer-margin-block);margin-inline-end:var(--_n-aside-drawer-margin-inline-end);margin-inline-start:var(--_n-aside-drawer-margin-inline-start);max-inline-size:var(--_n-aside-drawer-max-inline-size);min-inline-size:var(--_n-aside-drawer-min-inline-size);position:relative;z-index:var(--_n-aside-drawer-z-index)}.n-aside-drawer{block-size:100%;inline-size:100%}::slotted(*){block-size:100%}::slotted(nord-drawer){--n-drawer-border-color:var(--_n-aside-drawer-border-color);--n-drawer-border-radius:var(--_n-aside-drawer-border-radius);--n-drawer-border-inline-start-width:var(--_n-aside-drawer-border-width);--n-drawer-border-inline-end-width:var(--_n-aside-drawer-border-inline-end-width);--n-drawer-border-block-start-width:var(--_n-aside-drawer-border-block-start-width);--n-drawer-border-block-end-width:var(--_n-aside-drawer-border-block-end-width)}@media (min-width:768px){:host([data-animating-fullscreen]),:host([fullscreen]){inset-block-end:var(--n-layout-footer-block-size,0);inset-block-start:var(--_n-aside-drawer-fullscreen-inset-block-start);inset-inline-end:calc(var(--n-aside-rail-width, 48px) + var(--_n-aside-rail-gap, 0px));margin-inline:0;position:fixed;z-index:var(--_n-aside-drawer-fullscreen-z-index)}:host([fullscreen]){inline-size:min(var(--_n-aside-drawer-fullscreen-max,100vw),var(--_n-aside-drawer-fullscreen-available,100vw));max-inline-size:min(var(--_n-aside-drawer-fullscreen-max,100vw),var(--_n-aside-drawer-fullscreen-available,100vw));min-inline-size:0}}`;class w extends o{constructor(e,i){super("width-change"),Object.defineProperty(this,"width",{value:e,enumerable:!0,writable:!1}),Object.defineProperty(this,"source",{value:i,enumerable:!0,writable:!1})}}const b="nord-aside.";function v(e){return`${b}${e}.width`}let p=class extends t{constructor(){super(...arguments),this.defaultWidth=420,this.minWidth=280,this.maxWidth=Number.MAX_SAFE_INTEGER,this.maxFullscreenWidth=Number.MAX_SAFE_INTEGER,this.disabled=!1,this.fullscreen=!1,this.floating=!1,this.width=420,this.widthSeeded=!1,this.fullscreenSeeded=!1,this.observedWidth=0}render(){return r`<div class="n-aside-drawer" part="drawer"><slot></slot></div>`}connectedCallback(){super.connectedCallback(),this.applyFloatingRole(),this.applyAriaLabelledby(),n||(this.observedWidth=this.getBoundingClientRect().width,"undefined"!=typeof ResizeObserver&&(this.fullscreenResizeObserver=new ResizeObserver((e=>{var i,t,r;if(this.hasAttribute("data-animating-fullscreen"))return;const n=e[0];if(!n)return;const a=null!==(r=null===(t=null===(i=n.borderBoxSize)||void 0===i?void 0:i[0])||void 0===t?void 0:t.inlineSize)&&void 0!==r?r:n.contentRect.width;this.observedWidth=a})),this.fullscreenResizeObserver.observe(this)))}disconnectedCallback(){var e,i;super.disconnectedCallback(),null===(e=this.fullscreenResizeObserver)||void 0===e||e.disconnect(),this.fullscreenResizeObserver=void 0,null===(i=this.fullscreenAnimation)||void 0===i||i.cancel(),this.fullscreenAnimation=void 0,this.fullscreenAnimationCleanup=void 0,this.removeAttribute("data-animating-fullscreen"),this.style.removeProperty("inline-size"),this.style.removeProperty("max-inline-size"),this.style.removeProperty("min-inline-size")}willUpdate(){this.widthSeeded||(this.seedWidth(),this.widthSeeded=!0)}handleFullscreenChange(e,i){if(n)return;if(!this.fullscreenSeeded)return this.fullscreenSeeded=!0,void(this.observedWidth=this.getBoundingClientRect().width);if("undefined"!=typeof matchMedia&&!matchMedia("(min-width: 768px)").matches)return;this.fullscreenAnimation&&(this.observedWidth=this.getBoundingClientRect().width,this.fullscreenAnimation.cancel(),this.fullscreenAnimation=void 0,this.fullscreenAnimationCleanup=void 0);const t=this.observedWidth;this.setAttribute("data-animating-fullscreen",""),i||this.setWidth(this.defaultWidth,"attribute"),this.style.setProperty("inline-size",`${t}px`),this.style.setProperty("max-inline-size",`${t}px`),this.style.setProperty("min-inline-size",`${t}px`),requestAnimationFrame((()=>requestAnimationFrame((()=>{this.style.removeProperty("inline-size"),this.style.removeProperty("max-inline-size"),this.style.removeProperty("min-inline-size");const e=this.getBoundingClientRect().width;if(Math.abs(t-e)<1)return this.removeAttribute("data-animating-fullscreen"),this.observedWidth=e,void(this.fullscreenAnimationCleanup=void 0);const i=this.animate([{inlineSize:`${t}px`,maxInlineSize:`${t}px`,minInlineSize:`${t}px`},{inlineSize:`${e}px`,maxInlineSize:`${e}px`,minInlineSize:`${e}px`}],{duration:300,easing:"cubic-bezier(0.4, 0, 0.2, 1)",fill:"none"});this.fullscreenAnimation=i;const r=()=>{this.fullscreenAnimation===i&&(this.fullscreenAnimation=void 0,this.fullscreenAnimationCleanup=void 0,this.removeAttribute("data-animating-fullscreen"),this.observedWidth=this.getBoundingClientRect().width)};this.fullscreenAnimationCleanup=r,i.onfinish=r,i.oncancel=r}))))}handleBoundsChange(){if(!this.widthSeeded)return;const e=h(this.width,this.minWidth,this.maxWidth);e!==this.width?this.setWidth(e,"attribute"):this.applyWidthVars()}handleFloatingChange(){this.applyFloatingRole()}applyFloatingRole(){this.floating?(this.setAttribute("role","dialog"),this.setAttribute("aria-modal","true")):(this.setAttribute("role","region"),this.removeAttribute("aria-modal"))}applyWidthVars(){this.style.setProperty("--_n-aside-drawer-inline-size",`${this.width}px`),this.style.setProperty("--_n-aside-drawer-min-inline-size",`${this.minWidth}px`);const e=this.maxWidth>=Number.MAX_SAFE_INTEGER;this.style.setProperty("--_n-aside-drawer-max-inline-size",e?"none":`${this.maxWidth}px`);const i=this.maxFullscreenWidth>=Number.MAX_SAFE_INTEGER;this.style.setProperty("--_n-aside-drawer-fullscreen-max",i?"100vw":`max(${this.minWidth}px, ${this.maxFullscreenWidth}px)`)}setWidth(e,i){const t=h(Math.round(e),this.minWidth,this.maxWidth);t!==this.width&&(this.width=t,!n&&this.id&&(u(v(this.id),this.defaultWidth).value=this.width),this.dispatchEvent(new w(this.width,i)))}get isPinned(){return this.minWidth===this.maxWidth}seedWidth(){if(n||!this.id)return this.width=h(this.defaultWidth,this.minWidth,this.maxWidth),void this.applyWidthVars();const e=u(v(this.id),this.defaultWidth),i=u((t=this.id,`${b}${t}.defaultWidth`),this.defaultWidth);var t;let r,a;i.value!==this.defaultWidth?(r=this.defaultWidth,e.value=r,i.value=this.defaultWidth,a="attribute"):(r=e.value,a="storage"),this.width=h(Math.round(r),this.minWidth,this.maxWidth),this.applyWidthVars(),this.dispatchEvent(new w(this.width,a))}applyAriaLabelledby(){if(!this.id)return void this.removeAttribute("aria-labelledby");const e=this.closest("nord-aside");if(!e)return;const i=e.querySelector(`nord-aside-trigger[drawer="${this.id}"]`);i&&(i.id||(i.id=`${this.id}-trigger`),this.setAttribute("aria-labelledby",i.id))}};p.styles=[c,m],e([a({reflect:!0,type:Number,attribute:"default-width"})],p.prototype,"defaultWidth",void 0),e([a({reflect:!0,type:Number,attribute:"min-width"})],p.prototype,"minWidth",void 0),e([a({reflect:!1,type:Number,attribute:"max-width"})],p.prototype,"maxWidth",void 0),e([a({reflect:!1,type:Number,attribute:"max-fullscreen-width"})],p.prototype,"maxFullscreenWidth",void 0),e([a({type:Boolean,reflect:!0})],p.prototype,"disabled",void 0),e([a({type:Boolean,reflect:!0})],p.prototype,"fullscreen",void 0),e([a({type:Boolean,reflect:!1})],p.prototype,"floating",void 0),e([d()],p.prototype,"width",void 0),e([l("fullscreen")],p.prototype,"handleFullscreenChange",null),e([l("defaultWidth"),l("minWidth"),l("maxWidth"),l("maxFullscreenWidth")],p.prototype,"handleBoundsChange",null),e([l("floating")],p.prototype,"handleFloatingChange",null),e([l("width")],p.prototype,"applyWidthVars",null),p=e([s("nord-aside-drawer")],p);var f=p;export{w as AsideDrawerWidthChangeEvent,f as default};
|
|
10
10
|
//# sourceMappingURL=AsideDrawer.js.map
|
package/lib/AsideDrawer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AsideDrawer.js","sources":["../src/aside-drawer/AsideDrawer.ts"],"sourcesContent":["import { html, isServer, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { observe } from '../common/decorators/observe.js'\nimport { NordEvent } from '../common/events.js'\nimport { clamp } from '../common/number.js'\nimport { storage } from '../common/storage.js'\n\nimport componentStyle from '../common/styles/Component.css'\nimport style from './AsideDrawer.css'\n\nexport type AsideDrawerWidthSource = 'drag' | 'keyboard' | 'attribute' | 'storage'\n\n/**\n * Event dispatched when the drawer's width changes. The `width` property is\n * the new applied inline-size in px, and `source` is the cause of the change.\n */\nexport class AsideDrawerWidthChangeEvent extends NordEvent {\n declare width: number\n declare source: AsideDrawerWidthSource\n\n constructor(width: number, source: AsideDrawerWidthSource) {\n super('width-change')\n Object.defineProperty(this, 'width', {\n value: width,\n enumerable: true,\n writable: false,\n })\n Object.defineProperty(this, 'source', {\n value: source,\n enumerable: true,\n writable: false,\n })\n }\n}\n\nconst STORAGE_KEY_PREFIX = 'nord-aside.'\n\nfunction widthKey(id: string) {\n return `${STORAGE_KEY_PREFIX}${id}.width`\n}\n\nfunction defaultWidthKey(id: string) {\n return `${STORAGE_KEY_PREFIX}${id}.defaultWidth`\n}\n\n/**\n * A drawer panel inside `<nord-aside>`.\n *\n * @status new\n * @category layout\n * @slot - Default slot. Place arbitrary drawer content here.\n *\n * @fires {AsideDrawerWidthChangeEvent} width-change - Dispatched when the\n * drawer's width changes. The event exposes `width` (the new applied\n * inline-size in px) and `source` (`'drag' | 'keyboard' | 'attribute' | 'storage'`).\n *\n * @parent Aside\n */\n@customElement('nord-aside-drawer')\nexport default class AsideDrawer extends LitElement {\n static styles = [componentStyle, style]\n\n /**\n * Initial width in px. The persisted value overrides this on load; if\n * `default-width` changes between sessions, the persisted value is\n * re-seeded to the new default.\n */\n @property({ reflect: true, type: Number, attribute: 'default-width' }) defaultWidth: number = 420\n\n /**\n * Resize clamp lower bound in px.\n */\n @property({ reflect: true, type: Number, attribute: 'min-width' }) minWidth: number = 280\n\n /**\n * Resize clamp upper bound in px. Defaults to `Number.MAX_SAFE_INTEGER`\n * so an unset `max-width` doesn't constrain the drawer — the consumer's\n * layout (or the rail / nav anchoring) determines the practical ceiling.\n * Set an explicit value to clamp.\n *\n * Not reflected: an unset cap is the `Number.MAX_SAFE_INTEGER` sentinel,\n * which must never render into the DOM as `max-width=\"9007199254740991\"`.\n */\n @property({ reflect: false, type: Number, attribute: 'max-width' }) maxWidth: number = Number.MAX_SAFE_INTEGER\n\n /**\n * Maximum inline-size in px the drawer can reach when `[fullscreen]`\n * is active. Independent of `max-width` (which only caps the docked\n * drag / keyboard resize) — fullscreen typically wants to grow\n * larger than the docked maximum. Defaults to `Number.MAX_SAFE_INTEGER`\n * so an unset value lets fullscreen fill all available room (bounded\n * by the layout's left-cap rule in `<nord-aside>`).\n *\n * Not reflected: an unset cap is the `Number.MAX_SAFE_INTEGER` sentinel,\n * which must never render into the DOM.\n */\n @property({ reflect: false, type: Number, attribute: 'max-fullscreen-width' }) maxFullscreenWidth: number = Number.MAX_SAFE_INTEGER\n\n /**\n * When set, the drawer cannot be opened. Programmatic opens are silently\n * ignored; if the drawer is already open and this flips on, the drawer\n * stays open until closed normally.\n */\n @property({ type: Boolean, reflect: true }) disabled: boolean = false\n\n /**\n * Reflected. Phase 3 wires the overlay sizing internally.\n */\n @property({ type: Boolean, reflect: true }) fullscreen: boolean = false\n\n /**\n * Set by the parent `<nord-aside>` when the drawer renders as a narrow\n * floating overlay (rather than a docked region). When `true` the host\n * becomes an accessible modal dialog (`role=\"dialog\"` + `aria-modal`);\n * when `false` the host is a `role=\"region\"` landmark. Drive this only\n * through the parent — it owns the floating-vs-docked decision.\n */\n @property({ type: Boolean, reflect: false }) floating: boolean = false\n\n /**\n * Currently-applied width in px. Public read-only-ish; use `setWidth()`\n * (or drive via `default-width` + min/max attrs) rather than poking\n * this directly.\n * @internal\n */\n @state() width = 420\n\n private widthSeeded = false\n private fullscreenSeeded = false\n private fullscreenAnimationCleanup?: () => void\n private fullscreenAnimation?: Animation\n // Last painted inline-size, kept up to date by ResizeObserver. Used as\n // the animation's `fromWidth` on toggle — read from this cache instead\n // of `getBoundingClientRect()` (which would force sync layout against\n // the already-mutated `fullscreen` attribute).\n private observedWidth = 0\n private fullscreenResizeObserver?: ResizeObserver\n\n render() {\n return html`\n <div class=\"n-aside-drawer\" part=\"drawer\">\n <slot></slot>\n </div>\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.applyFloatingRole()\n this.applyAriaLabelledby()\n if (isServer) {\n return\n }\n // Seed observedWidth synchronously so the first toggle has a valid\n // fromWidth even before ResizeObserver has fired.\n this.observedWidth = this.getBoundingClientRect().width\n if (typeof ResizeObserver !== 'undefined') {\n this.fullscreenResizeObserver = new ResizeObserver((entries) => {\n // Don't overwrite while an animation is running — intermediate\n // frames aren't valid `fromWidth` candidates for the NEXT toggle.\n if (this.hasAttribute('data-animating-fullscreen'))\n return\n const entry = entries[0]\n if (!entry)\n return\n const size = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width\n this.observedWidth = size\n })\n this.fullscreenResizeObserver.observe(this)\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.fullscreenResizeObserver?.disconnect()\n this.fullscreenResizeObserver = undefined\n this.fullscreenAnimation?.cancel()\n this.fullscreenAnimation = undefined\n this.fullscreenAnimationCleanup = undefined\n }\n\n willUpdate() {\n if (!this.widthSeeded) {\n this.seedWidth()\n this.widthSeeded = true\n }\n }\n\n /**\n * Animate the fullscreen toggle ONLY when the user flips the attribute.\n * Not on initial mount, not on drag-resize, not on show/hide, not on\n * nav-state-driven size changes.\n *\n * Uses Web Animations API with explicit `from`/`to` keyframes because\n * the drawer's `position` flips between `relative` (docked, in flex\n * flow) and `fixed` (fullscreen, overlay). CSS transitions don't\n * reliably interpolate `inline-size` across a position-type change.\n *\n * `fromWidth` comes from `observedWidth` — the last size painted by\n * the browser, captured by ResizeObserver BEFORE the consumer flipped\n * the attribute. Reading `getBoundingClientRect()` here would return\n * the post-toggle size (Lit has already reflected the attribute), so\n * the cached value is the only reliable pre-toggle width.\n *\n * `toWidth` is measured inside rAF, after Lit has committed the new\n * attribute and Aside has updated `--_n-aside-drawer-fullscreen-size`.\n *\n * Exiting also resets the drawer's docked width to its declared\n * `default-width` so the user lands at a predictable size after\n * collapsing.\n */\n @observe('fullscreen')\n protected handleFullscreenChange(_oldValue: boolean | undefined, newValue: boolean) {\n if (isServer) {\n return\n }\n if (!this.fullscreenSeeded) {\n this.fullscreenSeeded = true\n this.observedWidth = this.getBoundingClientRect().width\n return\n }\n // At narrow viewports the `fullscreen` attribute has no visual effect\n // (the parent <nord-aside> renders the drawer as a floating overlay\n // regardless). The CSS rules for [fullscreen] are gated behind\n // `@media (min-width: 768px)` — skip the animation here too so we\n // don't run a no-op WAAPI animation against zero size delta.\n if (typeof matchMedia !== 'undefined' && !matchMedia('(min-width: 768px)').matches) {\n return\n }\n\n // Reverse-mid-flight: snapshot the current visible width BEFORE\n // cancelling, because `animation.cancel()` synchronously reverts\n // the element to its CSS layout — measuring after cancel would\n // give us the underlying CSS width, not what the user is seeing.\n // oncancel fires synchronously on cancel(), so cleanup runs from there —\n // do NOT call cleanupFn manually here or it will run twice.\n if (this.fullscreenAnimation) {\n this.observedWidth = this.getBoundingClientRect().width\n this.fullscreenAnimation.cancel()\n }\n\n const fromWidth = this.observedWidth\n\n // Pin `position: fixed` for the entire animation in both directions\n // via `:host([data-animating-fullscreen])`. Setting this here also\n // tells the ResizeObserver to stop updating `observedWidth`.\n this.setAttribute('data-animating-fullscreen', '')\n\n if (!newValue) {\n this.setWidth(this.defaultWidth, 'attribute')\n }\n\n // Double-rAF: the first frame lets Aside's scheduleLeftCapReconcile rAF\n // (queued synchronously above via syncFullscreenAttr) write\n // `--_n-aside-drawer-fullscreen-available`. The second frame then reads\n // the post-reconcile toWidth so the animation end-state is correct.\n requestAnimationFrame(() => requestAnimationFrame(() => {\n const toWidth = this.getBoundingClientRect().width\n\n if (Math.abs(fromWidth - toWidth) < 1) {\n this.removeAttribute('data-animating-fullscreen')\n this.observedWidth = toWidth\n this.fullscreenAnimationCleanup = undefined\n return\n }\n\n const animation = this.animate(\n [\n {\n inlineSize: `${fromWidth}px`,\n maxInlineSize: `${fromWidth}px`,\n minInlineSize: `${fromWidth}px`,\n },\n {\n inlineSize: `${toWidth}px`,\n maxInlineSize: `${toWidth}px`,\n minInlineSize: `${toWidth}px`,\n },\n ],\n {\n duration: 300,\n easing: 'cubic-bezier(0.4, 0, 0.2, 1)',\n fill: 'none',\n },\n )\n\n this.fullscreenAnimation = animation\n\n const cleanup = () => {\n if (this.fullscreenAnimation !== animation)\n return\n this.fullscreenAnimation = undefined\n this.fullscreenAnimationCleanup = undefined\n this.removeAttribute('data-animating-fullscreen')\n this.observedWidth = this.getBoundingClientRect().width\n }\n this.fullscreenAnimationCleanup = cleanup\n animation.onfinish = cleanup\n animation.oncancel = cleanup\n }))\n }\n\n @observe('defaultWidth')\n @observe('minWidth')\n @observe('maxWidth')\n @observe('maxFullscreenWidth')\n protected handleBoundsChange() {\n // Re-seeding happens once on willUpdate; subsequent bound changes\n // just re-clamp the current width.\n if (!this.widthSeeded) {\n return\n }\n const clamped = clamp(this.width, this.minWidth, this.maxWidth)\n if (clamped !== this.width) {\n this.setWidth(clamped, 'attribute')\n }\n else {\n this.applyWidthVars()\n }\n }\n\n @observe('floating')\n protected handleFloatingChange() {\n this.applyFloatingRole()\n }\n\n /**\n * Single writer for the host's landmark/dialog role. A floating overlay\n * is an accessible modal (`role=\"dialog\"` + `aria-modal=\"true\"`); a docked\n * drawer is a `role=\"region\"` landmark with no aria-modal.\n */\n private applyFloatingRole() {\n if (this.floating) {\n this.setAttribute('role', 'dialog')\n this.setAttribute('aria-modal', 'true')\n }\n else {\n this.setAttribute('role', 'region')\n this.removeAttribute('aria-modal')\n }\n }\n\n @observe('width')\n protected applyWidthVars() {\n this.style.setProperty('--_n-aside-drawer-inline-size', `${this.width}px`)\n this.style.setProperty('--_n-aside-drawer-min-inline-size', `${this.minWidth}px`)\n // An unset `max-width` defaults to MAX_SAFE_INTEGER — too large for\n // CSS to handle reliably. Translate to `none` so the drawer has no\n // CSS-side cap; the clamp in `setWidth` still runs against the\n // numeric value so drag/keyboard don't escape JS-side bounds.\n const isMaxUnbounded = this.maxWidth >= Number.MAX_SAFE_INTEGER\n this.style.setProperty('--_n-aside-drawer-max-inline-size', isMaxUnbounded ? 'none' : `${this.maxWidth}px`)\n // Fullscreen rendered inline-size is the CSS-time `min()` of two vars:\n // - `--_n-aside-drawer-fullscreen-max` (this var, drawer-owned)\n // - `--_n-aside-drawer-fullscreen-available` (written by <nord-aside>\n // to the layout's left-cap; absent when there's no aside parent)\n // The drawer's own var floors at `minWidth` so a tiny consumer value\n // can never drop the drawer below its declared minimum. Unset\n // `max-fullscreen-width` resolves to `100vw` — a sensible standalone\n // fallback when there's no aside to publish the available width.\n const isFullscreenUnbounded = this.maxFullscreenWidth >= Number.MAX_SAFE_INTEGER\n this.style.setProperty(\n '--_n-aside-drawer-fullscreen-max',\n isFullscreenUnbounded\n ? '100vw'\n : `max(${this.minWidth}px, ${this.maxFullscreenWidth}px)`,\n )\n }\n\n /**\n * Update the drawer width, clamped to `[min-width, max-width]`. Persists\n * to localStorage and dispatches `width-change` with the given source.\n */\n setWidth(width: number, source: AsideDrawerWidthSource) {\n const next = clamp(Math.round(width), this.minWidth, this.maxWidth)\n if (next === this.width) {\n return\n }\n this.width = next\n if (!isServer && this.id) {\n storage(widthKey(this.id), this.defaultWidth).value = this.width\n }\n this.dispatchEvent(new AsideDrawerWidthChangeEvent(this.width, source))\n }\n\n /**\n * Whether the drawer is pinned — `min === max === default`. A pinned\n * drawer's resize handle is a drag no-op.\n */\n get isPinned(): boolean {\n return this.minWidth === this.maxWidth\n }\n\n /**\n * Lift Layout.ts's re-seed pattern: if the consumer-provided\n * `default-width` differs from the last default we wrote for this id,\n * the persisted width is reset to the new default.\n */\n private seedWidth() {\n // On the server there's no localStorage and no layout to paint; clamp\n // the declared default and skip both the storage read/write and the\n // `width-change` dispatch.\n if (isServer || !this.id) {\n this.width = clamp(this.defaultWidth, this.minWidth, this.maxWidth)\n this.applyWidthVars()\n return\n }\n const widthStore = storage(widthKey(this.id), this.defaultWidth)\n const defaultStore = storage(defaultWidthKey(this.id), this.defaultWidth)\n let next: number\n let source: AsideDrawerWidthSource\n if (defaultStore.value !== this.defaultWidth) {\n next = this.defaultWidth\n widthStore.value = next\n defaultStore.value = this.defaultWidth\n source = 'attribute'\n }\n else {\n next = widthStore.value\n source = 'storage'\n }\n this.width = clamp(Math.round(next), this.minWidth, this.maxWidth)\n this.applyWidthVars()\n this.dispatchEvent(new AsideDrawerWidthChangeEvent(this.width, source))\n }\n\n /**\n * Resolve the matching `<nord-aside-trigger drawer=\"<this.id>\">` inside\n * the ancestor `<nord-aside>` and use it as the drawer's aria-labelledby\n * target. Falls back to no label when no matching trigger exists.\n */\n private applyAriaLabelledby() {\n if (!this.id) {\n this.removeAttribute('aria-labelledby')\n return\n }\n const aside = this.closest('nord-aside')\n if (!aside) {\n return\n }\n const trigger = aside.querySelector<HTMLElement>(\n `nord-aside-trigger[drawer=\"${this.id}\"]`,\n )\n if (!trigger) {\n return\n }\n if (!trigger.id) {\n trigger.id = `${this.id}-trigger`\n }\n this.setAttribute('aria-labelledby', trigger.id)\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-aside-drawer': AsideDrawer\n }\n}\n"],"names":["AsideDrawerWidthChangeEvent","NordEvent","constructor","width","source","super","Object","defineProperty","this","value","enumerable","writable","STORAGE_KEY_PREFIX","widthKey","id","AsideDrawer","LitElement","defaultWidth","minWidth","maxWidth","Number","MAX_SAFE_INTEGER","maxFullscreenWidth","disabled","fullscreen","floating","widthSeeded","fullscreenSeeded","observedWidth","render","html","connectedCallback","applyFloatingRole","applyAriaLabelledby","isServer","getBoundingClientRect","ResizeObserver","fullscreenResizeObserver","entries","hasAttribute","entry","size","_c","_a","borderBoxSize","_b","inlineSize","contentRect","observe","disconnectedCallback","disconnect","undefined","fullscreenAnimation","cancel","fullscreenAnimationCleanup","willUpdate","seedWidth","handleFullscreenChange","_oldValue","newValue","matchMedia","matches","fromWidth","setAttribute","setWidth","requestAnimationFrame","toWidth","Math","abs","removeAttribute","animation","animate","maxInlineSize","minInlineSize","duration","easing","fill","cleanup","onfinish","oncancel","handleBoundsChange","clamped","clamp","applyWidthVars","handleFloatingChange","style","setProperty","isMaxUnbounded","isFullscreenUnbounded","next","round","storage","dispatchEvent","isPinned","widthStore","defaultStore","aside","closest","trigger","querySelector","styles","componentStyle","__decorate","property","reflect","type","attribute","prototype","Boolean","state","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AsideDrawer.js","sources":["../src/aside-drawer/AsideDrawer.ts"],"sourcesContent":["import { html, isServer, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { observe } from '../common/decorators/observe.js'\nimport { NordEvent } from '../common/events.js'\nimport { clamp } from '../common/number.js'\nimport { storage } from '../common/storage.js'\n\nimport componentStyle from '../common/styles/Component.css'\nimport style from './AsideDrawer.css'\n\nexport type AsideDrawerWidthSource = 'drag' | 'keyboard' | 'attribute' | 'storage'\n\n/**\n * Event dispatched when the drawer's width changes. The `width` property is\n * the new applied inline-size in px, and `source` is the cause of the change.\n */\nexport class AsideDrawerWidthChangeEvent extends NordEvent {\n declare width: number\n declare source: AsideDrawerWidthSource\n\n constructor(width: number, source: AsideDrawerWidthSource) {\n super('width-change')\n Object.defineProperty(this, 'width', {\n value: width,\n enumerable: true,\n writable: false,\n })\n Object.defineProperty(this, 'source', {\n value: source,\n enumerable: true,\n writable: false,\n })\n }\n}\n\nconst STORAGE_KEY_PREFIX = 'nord-aside.'\n\nfunction widthKey(id: string) {\n return `${STORAGE_KEY_PREFIX}${id}.width`\n}\n\nfunction defaultWidthKey(id: string) {\n return `${STORAGE_KEY_PREFIX}${id}.defaultWidth`\n}\n\n/**\n * A drawer panel inside `<nord-aside>`.\n *\n * @status new\n * @category layout\n * @slot - Default slot. Place arbitrary drawer content here.\n *\n * @fires {AsideDrawerWidthChangeEvent} width-change - Dispatched when the\n * drawer's width changes. The event exposes `width` (the new applied\n * inline-size in px) and `source` (`'drag' | 'keyboard' | 'attribute' | 'storage'`).\n *\n * @parent Aside\n */\n@customElement('nord-aside-drawer')\nexport default class AsideDrawer extends LitElement {\n static styles = [componentStyle, style]\n\n /**\n * Initial width in px. The persisted value overrides this on load; if\n * `default-width` changes between sessions, the persisted value is\n * re-seeded to the new default.\n */\n @property({ reflect: true, type: Number, attribute: 'default-width' }) defaultWidth: number = 420\n\n /**\n * Resize clamp lower bound in px.\n */\n @property({ reflect: true, type: Number, attribute: 'min-width' }) minWidth: number = 280\n\n /**\n * Resize clamp upper bound in px. Defaults to `Number.MAX_SAFE_INTEGER`\n * so an unset `max-width` doesn't constrain the drawer — the consumer's\n * layout (or the rail / nav anchoring) determines the practical ceiling.\n * Set an explicit value to clamp.\n *\n * Not reflected: an unset cap is the `Number.MAX_SAFE_INTEGER` sentinel,\n * which must never render into the DOM as `max-width=\"9007199254740991\"`.\n */\n @property({ reflect: false, type: Number, attribute: 'max-width' }) maxWidth: number = Number.MAX_SAFE_INTEGER\n\n /**\n * Maximum inline-size in px the drawer can reach when `[fullscreen]`\n * is active. Independent of `max-width` (which only caps the docked\n * drag / keyboard resize) — fullscreen typically wants to grow\n * larger than the docked maximum. Defaults to `Number.MAX_SAFE_INTEGER`\n * so an unset value lets fullscreen fill all available room (bounded\n * by the layout's left-cap rule in `<nord-aside>`).\n *\n * Not reflected: an unset cap is the `Number.MAX_SAFE_INTEGER` sentinel,\n * which must never render into the DOM.\n */\n @property({ reflect: false, type: Number, attribute: 'max-fullscreen-width' }) maxFullscreenWidth: number = Number.MAX_SAFE_INTEGER\n\n /**\n * When set, the drawer cannot be opened. Programmatic opens are silently\n * ignored; if the drawer is already open and this flips on, the drawer\n * stays open until closed normally.\n */\n @property({ type: Boolean, reflect: true }) disabled: boolean = false\n\n /**\n * Reflected. Phase 3 wires the overlay sizing internally.\n */\n @property({ type: Boolean, reflect: true }) fullscreen: boolean = false\n\n /**\n * Set by the parent `<nord-aside>` when the drawer renders as a narrow\n * floating overlay (rather than a docked region). When `true` the host\n * becomes an accessible modal dialog (`role=\"dialog\"` + `aria-modal`);\n * when `false` the host is a `role=\"region\"` landmark. Drive this only\n * through the parent — it owns the floating-vs-docked decision.\n */\n @property({ type: Boolean, reflect: false }) floating: boolean = false\n\n /**\n * Currently-applied width in px. Public read-only-ish; use `setWidth()`\n * (or drive via `default-width` + min/max attrs) rather than poking\n * this directly.\n * @internal\n */\n @state() width = 420\n\n private widthSeeded = false\n private fullscreenSeeded = false\n private fullscreenAnimationCleanup?: () => void\n private fullscreenAnimation?: Animation\n // Last painted inline-size, kept up to date by ResizeObserver. Used as\n // the animation's `fromWidth` on toggle — read from this cache instead\n // of `getBoundingClientRect()` (which would force sync layout against\n // the already-mutated `fullscreen` attribute).\n private observedWidth = 0\n private fullscreenResizeObserver?: ResizeObserver\n\n render() {\n return html`\n <div class=\"n-aside-drawer\" part=\"drawer\">\n <slot></slot>\n </div>\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.applyFloatingRole()\n this.applyAriaLabelledby()\n if (isServer) {\n return\n }\n // Seed observedWidth synchronously so the first toggle has a valid\n // fromWidth even before ResizeObserver has fired.\n this.observedWidth = this.getBoundingClientRect().width\n if (typeof ResizeObserver !== 'undefined') {\n this.fullscreenResizeObserver = new ResizeObserver((entries) => {\n // Don't overwrite while an animation is running — intermediate\n // frames aren't valid `fromWidth` candidates for the NEXT toggle.\n if (this.hasAttribute('data-animating-fullscreen'))\n return\n const entry = entries[0]\n if (!entry)\n return\n const size = entry.borderBoxSize?.[0]?.inlineSize ?? entry.contentRect.width\n this.observedWidth = size\n })\n this.fullscreenResizeObserver.observe(this)\n }\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.fullscreenResizeObserver?.disconnect()\n this.fullscreenResizeObserver = undefined\n this.fullscreenAnimation?.cancel()\n this.fullscreenAnimation = undefined\n this.fullscreenAnimationCleanup = undefined\n // The cancel EVENT is queued, not synchronous, and its cleanup no-ops\n // once `fullscreenAnimation` is cleared — so remove the animation\n // state directly. Without this, a drawer disconnected mid-toggle\n // (or while the tab is hidden and rAFs are paused) reconnects with a\n // stale width pin and a stuck `position: fixed`.\n this.removeAttribute('data-animating-fullscreen')\n this.style.removeProperty('inline-size')\n this.style.removeProperty('max-inline-size')\n this.style.removeProperty('min-inline-size')\n }\n\n willUpdate() {\n if (!this.widthSeeded) {\n this.seedWidth()\n this.widthSeeded = true\n }\n }\n\n /**\n * Animate the fullscreen toggle ONLY when the user flips the attribute.\n * Not on initial mount, not on drag-resize, not on show/hide, not on\n * nav-state-driven size changes.\n *\n * Uses Web Animations API with explicit `from`/`to` keyframes because\n * the drawer's `position` flips between `relative` (docked, in flex\n * flow) and `fixed` (fullscreen, overlay). CSS transitions don't\n * reliably interpolate `inline-size` across a position-type change.\n *\n * `fromWidth` comes from `observedWidth` — the last size painted by\n * the browser, captured by ResizeObserver BEFORE the consumer flipped\n * the attribute. Reading `getBoundingClientRect()` here would return\n * the post-toggle size (Lit has already reflected the attribute), so\n * the cached value is the only reliable pre-toggle width.\n *\n * `toWidth` is measured inside rAF, after Lit has committed the new\n * attribute and Aside has updated `--_n-aside-drawer-fullscreen-size`.\n *\n * Exiting also resets the drawer's docked width to its declared\n * `default-width` so the user lands at a predictable size after\n * collapsing.\n */\n @observe('fullscreen')\n protected handleFullscreenChange(_oldValue: boolean | undefined, newValue: boolean) {\n if (isServer) {\n return\n }\n if (!this.fullscreenSeeded) {\n this.fullscreenSeeded = true\n this.observedWidth = this.getBoundingClientRect().width\n return\n }\n // At narrow viewports the `fullscreen` attribute has no visual effect\n // (the parent <nord-aside> renders the drawer as a floating overlay\n // regardless). The CSS rules for [fullscreen] are gated behind\n // `@media (min-width: 768px)` — skip the animation here too so we\n // don't run a no-op WAAPI animation against zero size delta.\n if (typeof matchMedia !== 'undefined' && !matchMedia('(min-width: 768px)').matches) {\n return\n }\n\n // Reverse-mid-flight: snapshot the current visible width BEFORE\n // cancelling, because `animation.cancel()` synchronously reverts\n // the element to its CSS layout — measuring after cancel would\n // give us the underlying CSS width, not what the user is seeing.\n // The cancel EVENT however is queued, firing in the next rendering\n // update — AFTER this handler has re-set `data-animating-fullscreen`\n // below. Clear `fullscreenAnimation` here so the stale animation's\n // cleanup fails its identity guard and can't strip the attribute\n // this new toggle now owns (which would drop `position: fixed`\n // mid-animation and push the drawer back into flex flow).\n if (this.fullscreenAnimation) {\n this.observedWidth = this.getBoundingClientRect().width\n this.fullscreenAnimation.cancel()\n this.fullscreenAnimation = undefined\n this.fullscreenAnimationCleanup = undefined\n }\n\n const fromWidth = this.observedWidth\n\n // Pin `position: fixed` for the entire animation in both directions\n // via `:host([data-animating-fullscreen])`. Setting this here also\n // tells the ResizeObserver to stop updating `observedWidth`.\n this.setAttribute('data-animating-fullscreen', '')\n\n if (!newValue) {\n this.setWidth(this.defaultWidth, 'attribute')\n }\n\n // Pin the pre-toggle width inline for the frame(s) between the\n // attribute flip and the animation start below — without this the\n // browser paints the CSS end-state (fullscreen or re-docked) for a\n // frame, flashing the final size before the animation snaps back to\n // `fromWidth`. The pin is removed just before measuring the animation\n // target; once running, the animation's keyframes outrank inline\n // styles anyway.\n this.style.setProperty('inline-size', `${fromWidth}px`)\n this.style.setProperty('max-inline-size', `${fromWidth}px`)\n this.style.setProperty('min-inline-size', `${fromWidth}px`)\n\n // Double-rAF: the first frame lets Aside's scheduleLeftCapReconcile rAF\n // (queued synchronously above via syncFullscreenAttr) write\n // `--_n-aside-drawer-fullscreen-available`. The second frame then reads\n // the post-reconcile toWidth so the animation end-state is correct.\n requestAnimationFrame(() => requestAnimationFrame(() => {\n this.style.removeProperty('inline-size')\n this.style.removeProperty('max-inline-size')\n this.style.removeProperty('min-inline-size')\n\n const toWidth = this.getBoundingClientRect().width\n\n if (Math.abs(fromWidth - toWidth) < 1) {\n this.removeAttribute('data-animating-fullscreen')\n this.observedWidth = toWidth\n this.fullscreenAnimationCleanup = undefined\n return\n }\n\n const animation = this.animate(\n [\n {\n inlineSize: `${fromWidth}px`,\n maxInlineSize: `${fromWidth}px`,\n minInlineSize: `${fromWidth}px`,\n },\n {\n inlineSize: `${toWidth}px`,\n maxInlineSize: `${toWidth}px`,\n minInlineSize: `${toWidth}px`,\n },\n ],\n {\n duration: 300,\n easing: 'cubic-bezier(0.4, 0, 0.2, 1)',\n fill: 'none',\n },\n )\n\n this.fullscreenAnimation = animation\n\n const cleanup = () => {\n if (this.fullscreenAnimation !== animation)\n return\n this.fullscreenAnimation = undefined\n this.fullscreenAnimationCleanup = undefined\n this.removeAttribute('data-animating-fullscreen')\n this.observedWidth = this.getBoundingClientRect().width\n }\n this.fullscreenAnimationCleanup = cleanup\n animation.onfinish = cleanup\n animation.oncancel = cleanup\n }))\n }\n\n @observe('defaultWidth')\n @observe('minWidth')\n @observe('maxWidth')\n @observe('maxFullscreenWidth')\n protected handleBoundsChange() {\n // Re-seeding happens once on willUpdate; subsequent bound changes\n // just re-clamp the current width.\n if (!this.widthSeeded) {\n return\n }\n const clamped = clamp(this.width, this.minWidth, this.maxWidth)\n if (clamped !== this.width) {\n this.setWidth(clamped, 'attribute')\n }\n else {\n this.applyWidthVars()\n }\n }\n\n @observe('floating')\n protected handleFloatingChange() {\n this.applyFloatingRole()\n }\n\n /**\n * Single writer for the host's landmark/dialog role. A floating overlay\n * is an accessible modal (`role=\"dialog\"` + `aria-modal=\"true\"`); a docked\n * drawer is a `role=\"region\"` landmark with no aria-modal.\n */\n private applyFloatingRole() {\n if (this.floating) {\n this.setAttribute('role', 'dialog')\n this.setAttribute('aria-modal', 'true')\n }\n else {\n this.setAttribute('role', 'region')\n this.removeAttribute('aria-modal')\n }\n }\n\n @observe('width')\n protected applyWidthVars() {\n this.style.setProperty('--_n-aside-drawer-inline-size', `${this.width}px`)\n this.style.setProperty('--_n-aside-drawer-min-inline-size', `${this.minWidth}px`)\n // An unset `max-width` defaults to MAX_SAFE_INTEGER — too large for\n // CSS to handle reliably. Translate to `none` so the drawer has no\n // CSS-side cap; the clamp in `setWidth` still runs against the\n // numeric value so drag/keyboard don't escape JS-side bounds.\n const isMaxUnbounded = this.maxWidth >= Number.MAX_SAFE_INTEGER\n this.style.setProperty('--_n-aside-drawer-max-inline-size', isMaxUnbounded ? 'none' : `${this.maxWidth}px`)\n // Fullscreen rendered inline-size is the CSS-time `min()` of two vars:\n // - `--_n-aside-drawer-fullscreen-max` (this var, drawer-owned)\n // - `--_n-aside-drawer-fullscreen-available` (written by <nord-aside>\n // to the layout's left-cap; absent when there's no aside parent)\n // The drawer's own var floors at `minWidth` so a tiny consumer value\n // can never drop the drawer below its declared minimum. Unset\n // `max-fullscreen-width` resolves to `100vw` — a sensible standalone\n // fallback when there's no aside to publish the available width.\n const isFullscreenUnbounded = this.maxFullscreenWidth >= Number.MAX_SAFE_INTEGER\n this.style.setProperty(\n '--_n-aside-drawer-fullscreen-max',\n isFullscreenUnbounded\n ? '100vw'\n : `max(${this.minWidth}px, ${this.maxFullscreenWidth}px)`,\n )\n }\n\n /**\n * Update the drawer width, clamped to `[min-width, max-width]`. Persists\n * to localStorage and dispatches `width-change` with the given source.\n */\n setWidth(width: number, source: AsideDrawerWidthSource) {\n const next = clamp(Math.round(width), this.minWidth, this.maxWidth)\n if (next === this.width) {\n return\n }\n this.width = next\n if (!isServer && this.id) {\n storage(widthKey(this.id), this.defaultWidth).value = this.width\n }\n this.dispatchEvent(new AsideDrawerWidthChangeEvent(this.width, source))\n }\n\n /**\n * Whether the drawer is pinned — `min === max === default`. A pinned\n * drawer's resize handle is a drag no-op.\n */\n get isPinned(): boolean {\n return this.minWidth === this.maxWidth\n }\n\n /**\n * Lift Layout.ts's re-seed pattern: if the consumer-provided\n * `default-width` differs from the last default we wrote for this id,\n * the persisted width is reset to the new default.\n */\n private seedWidth() {\n // On the server there's no localStorage and no layout to paint; clamp\n // the declared default and skip both the storage read/write and the\n // `width-change` dispatch.\n if (isServer || !this.id) {\n this.width = clamp(this.defaultWidth, this.minWidth, this.maxWidth)\n this.applyWidthVars()\n return\n }\n const widthStore = storage(widthKey(this.id), this.defaultWidth)\n const defaultStore = storage(defaultWidthKey(this.id), this.defaultWidth)\n let next: number\n let source: AsideDrawerWidthSource\n if (defaultStore.value !== this.defaultWidth) {\n next = this.defaultWidth\n widthStore.value = next\n defaultStore.value = this.defaultWidth\n source = 'attribute'\n }\n else {\n next = widthStore.value\n source = 'storage'\n }\n this.width = clamp(Math.round(next), this.minWidth, this.maxWidth)\n this.applyWidthVars()\n this.dispatchEvent(new AsideDrawerWidthChangeEvent(this.width, source))\n }\n\n /**\n * Resolve the matching `<nord-aside-trigger drawer=\"<this.id>\">` inside\n * the ancestor `<nord-aside>` and use it as the drawer's aria-labelledby\n * target. Falls back to no label when no matching trigger exists.\n */\n private applyAriaLabelledby() {\n if (!this.id) {\n this.removeAttribute('aria-labelledby')\n return\n }\n const aside = this.closest('nord-aside')\n if (!aside) {\n return\n }\n const trigger = aside.querySelector<HTMLElement>(\n `nord-aside-trigger[drawer=\"${this.id}\"]`,\n )\n if (!trigger) {\n return\n }\n if (!trigger.id) {\n trigger.id = `${this.id}-trigger`\n }\n this.setAttribute('aria-labelledby', trigger.id)\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-aside-drawer': AsideDrawer\n }\n}\n"],"names":["AsideDrawerWidthChangeEvent","NordEvent","constructor","width","source","super","Object","defineProperty","this","value","enumerable","writable","STORAGE_KEY_PREFIX","widthKey","id","AsideDrawer","LitElement","defaultWidth","minWidth","maxWidth","Number","MAX_SAFE_INTEGER","maxFullscreenWidth","disabled","fullscreen","floating","widthSeeded","fullscreenSeeded","observedWidth","render","html","connectedCallback","applyFloatingRole","applyAriaLabelledby","isServer","getBoundingClientRect","ResizeObserver","fullscreenResizeObserver","entries","hasAttribute","entry","size","_c","_a","borderBoxSize","_b","inlineSize","contentRect","observe","disconnectedCallback","disconnect","undefined","fullscreenAnimation","cancel","fullscreenAnimationCleanup","removeAttribute","style","removeProperty","willUpdate","seedWidth","handleFullscreenChange","_oldValue","newValue","matchMedia","matches","fromWidth","setAttribute","setWidth","setProperty","requestAnimationFrame","toWidth","Math","abs","animation","animate","maxInlineSize","minInlineSize","duration","easing","fill","cleanup","onfinish","oncancel","handleBoundsChange","clamped","clamp","applyWidthVars","handleFloatingChange","isMaxUnbounded","isFullscreenUnbounded","next","round","storage","dispatchEvent","isPinned","widthStore","defaultStore","aside","closest","trigger","querySelector","styles","componentStyle","__decorate","property","reflect","type","attribute","prototype","Boolean","state","customElement"],"mappings":";;;;;;;;s4DAgBM,MAAOA,UAAoCC,EAI/C,WAAAC,CAAYC,EAAeC,GACzBC,MAAM,gBACNC,OAAOC,eAAeC,KAAM,QAAS,CACnCC,MAAON,EACPO,YAAY,EACZC,UAAU,IAEZL,OAAOC,eAAeC,KAAM,SAAU,CACpCC,MAAOL,EACPM,YAAY,EACZC,UAAU,GAEb,EAGH,MAAMC,EAAqB,cAE3B,SAASC,EAASC,GAChB,MAAO,GAAGF,IAAqBE,SACjC,CAoBe,IAAMC,EAAN,cAA0BC,EAA1B,WAAAd,uBAQ0DM,KAAYS,aAAW,IAK3BT,KAAQU,SAAW,IAWlBV,KAAAW,SAAmBC,OAAOC,iBAafb,KAAAc,mBAA6BF,OAAOC,iBAOvEb,KAAQe,UAAY,EAKpBf,KAAUgB,YAAY,EASrBhB,KAAQiB,UAAY,EAQxDjB,KAAKL,MAAG,IAETK,KAAWkB,aAAG,EACdlB,KAAgBmB,kBAAG,EAOnBnB,KAAaoB,cAAG,CA0VzB,CAvVC,MAAAC,GACE,OAAOC,CAAI,+DAKZ,CAED,iBAAAC,GACE1B,MAAM0B,oBACNvB,KAAKwB,oBACLxB,KAAKyB,sBACDC,IAKJ1B,KAAKoB,cAAgBpB,KAAK2B,wBAAwBhC,MACpB,oBAAnBiC,iBACT5B,KAAK6B,yBAA2B,IAAID,gBAAgBE,cAGlD,GAAI9B,KAAK+B,aAAa,6BACpB,OACF,MAAMC,EAAQF,EAAQ,GACtB,IAAKE,EACH,OACF,MAAMC,EAA+C,QAAxCC,EAAwB,UAAL,QAAnBC,EAAAH,EAAMI,qBAAa,IAAAD,OAAA,EAAAA,EAAG,UAAE,IAAAE,OAAA,EAAAA,EAAEC,kBAAc,IAAAJ,EAAAA,EAAAF,EAAMO,YAAY5C,MACvEK,KAAKoB,cAAgBa,CAAI,IAE3BjC,KAAK6B,yBAAyBW,QAAQxC,OAEzC,CAED,oBAAAyC,WACE5C,MAAM4C,uBACyB,QAA/BN,EAAAnC,KAAK6B,gCAA0B,IAAAM,GAAAA,EAAAO,aAC/B1C,KAAK6B,8BAA2Bc,EACN,QAA1BN,EAAArC,KAAK4C,2BAAqB,IAAAP,GAAAA,EAAAQ,SAC1B7C,KAAK4C,yBAAsBD,EAC3B3C,KAAK8C,gCAA6BH,EAMlC3C,KAAK+C,gBAAgB,6BACrB/C,KAAKgD,MAAMC,eAAe,eAC1BjD,KAAKgD,MAAMC,eAAe,mBAC1BjD,KAAKgD,MAAMC,eAAe,kBAC3B,CAED,UAAAC,GACOlD,KAAKkB,cACRlB,KAAKmD,YACLnD,KAAKkB,aAAc,EAEtB,CA0BS,sBAAAkC,CAAuBC,EAAgCC,GAC/D,GAAI5B,EACF,OAEF,IAAK1B,KAAKmB,iBAGR,OAFAnB,KAAKmB,kBAAmB,OACxBnB,KAAKoB,cAAgBpB,KAAK2B,wBAAwBhC,OAQpD,GAA0B,oBAAf4D,aAA+BA,WAAW,sBAAsBC,QACzE,OAaExD,KAAK4C,sBACP5C,KAAKoB,cAAgBpB,KAAK2B,wBAAwBhC,MAClDK,KAAK4C,oBAAoBC,SACzB7C,KAAK4C,yBAAsBD,EAC3B3C,KAAK8C,gCAA6BH,GAGpC,MAAMc,EAAYzD,KAAKoB,cAKvBpB,KAAK0D,aAAa,4BAA6B,IAE1CJ,GACHtD,KAAK2D,SAAS3D,KAAKS,aAAc,aAUnCT,KAAKgD,MAAMY,YAAY,cAAe,GAAGH,OACzCzD,KAAKgD,MAAMY,YAAY,kBAAmB,GAAGH,OAC7CzD,KAAKgD,MAAMY,YAAY,kBAAmB,GAAGH,OAM7CI,uBAAsB,IAAMA,uBAAsB,KAChD7D,KAAKgD,MAAMC,eAAe,eAC1BjD,KAAKgD,MAAMC,eAAe,mBAC1BjD,KAAKgD,MAAMC,eAAe,mBAE1B,MAAMa,EAAU9D,KAAK2B,wBAAwBhC,MAE7C,GAAIoE,KAAKC,IAAIP,EAAYK,GAAW,EAIlC,OAHA9D,KAAK+C,gBAAgB,6BACrB/C,KAAKoB,cAAgB0C,OACrB9D,KAAK8C,gCAA6BH,GAIpC,MAAMsB,EAAYjE,KAAKkE,QACrB,CACE,CACE5B,WAAY,GAAGmB,MACfU,cAAe,GAAGV,MAClBW,cAAe,GAAGX,OAEpB,CACEnB,WAAY,GAAGwB,MACfK,cAAe,GAAGL,MAClBM,cAAe,GAAGN,QAGtB,CACEO,SAAU,IACVC,OAAQ,+BACRC,KAAM,SAIVvE,KAAK4C,oBAAsBqB,EAE3B,MAAMO,EAAU,KACVxE,KAAK4C,sBAAwBqB,IAEjCjE,KAAK4C,yBAAsBD,EAC3B3C,KAAK8C,gCAA6BH,EAClC3C,KAAK+C,gBAAgB,6BACrB/C,KAAKoB,cAAgBpB,KAAK2B,wBAAwBhC,MAAK,EAEzDK,KAAK8C,2BAA6B0B,EAClCP,EAAUQ,SAAWD,EACrBP,EAAUS,SAAWF,CAAO,KAE/B,CAMS,kBAAAG,GAGR,IAAK3E,KAAKkB,YACR,OAEF,MAAM0D,EAAUC,EAAM7E,KAAKL,MAAOK,KAAKU,SAAUV,KAAKW,UAClDiE,IAAY5E,KAAKL,MACnBK,KAAK2D,SAASiB,EAAS,aAGvB5E,KAAK8E,gBAER,CAGS,oBAAAC,GACR/E,KAAKwB,mBACN,CAOO,iBAAAA,GACFxB,KAAKiB,UACPjB,KAAK0D,aAAa,OAAQ,UAC1B1D,KAAK0D,aAAa,aAAc,UAGhC1D,KAAK0D,aAAa,OAAQ,UAC1B1D,KAAK+C,gBAAgB,cAExB,CAGS,cAAA+B,GACR9E,KAAKgD,MAAMY,YAAY,gCAAiC,GAAG5D,KAAKL,WAChEK,KAAKgD,MAAMY,YAAY,oCAAqC,GAAG5D,KAAKU,cAKpE,MAAMsE,EAAiBhF,KAAKW,UAAYC,OAAOC,iBAC/Cb,KAAKgD,MAAMY,YAAY,oCAAqCoB,EAAiB,OAAS,GAAGhF,KAAKW,cAS9F,MAAMsE,EAAwBjF,KAAKc,oBAAsBF,OAAOC,iBAChEb,KAAKgD,MAAMY,YACT,mCACAqB,EACI,QACA,OAAOjF,KAAKU,eAAeV,KAAKc,wBAEvC,CAMD,QAAA6C,CAAShE,EAAeC,GACtB,MAAMsF,EAAOL,EAAMd,KAAKoB,MAAMxF,GAAQK,KAAKU,SAAUV,KAAKW,UACtDuE,IAASlF,KAAKL,QAGlBK,KAAKL,MAAQuF,GACRxD,GAAY1B,KAAKM,KACpB8E,EAAQ/E,EAASL,KAAKM,IAAKN,KAAKS,cAAcR,MAAQD,KAAKL,OAE7DK,KAAKqF,cAAc,IAAI7F,EAA4BQ,KAAKL,MAAOC,IAChE,CAMD,YAAI0F,GACF,OAAOtF,KAAKU,WAAaV,KAAKW,QAC/B,CAOO,SAAAwC,GAIN,GAAIzB,IAAa1B,KAAKM,GAGpB,OAFAN,KAAKL,MAAQkF,EAAM7E,KAAKS,aAAcT,KAAKU,SAAUV,KAAKW,eAC1DX,KAAK8E,iBAGP,MAAMS,EAAaH,EAAQ/E,EAASL,KAAKM,IAAKN,KAAKS,cAC7C+E,EAAeJ,GA7YA9E,EA6YwBN,KAAKM,GA5Y7C,GAAGF,IAAqBE,kBA4Y0BN,KAAKS,cA7YhE,IAAyBH,EA8YrB,IAAI4E,EACAtF,EACA4F,EAAavF,QAAUD,KAAKS,cAC9ByE,EAAOlF,KAAKS,aACZ8E,EAAWtF,MAAQiF,EACnBM,EAAavF,MAAQD,KAAKS,aAC1Bb,EAAS,cAGTsF,EAAOK,EAAWtF,MAClBL,EAAS,WAEXI,KAAKL,MAAQkF,EAAMd,KAAKoB,MAAMD,GAAOlF,KAAKU,SAAUV,KAAKW,UACzDX,KAAK8E,iBACL9E,KAAKqF,cAAc,IAAI7F,EAA4BQ,KAAKL,MAAOC,GAChE,CAOO,mBAAA6B,GACN,IAAKzB,KAAKM,GAER,YADAN,KAAK+C,gBAAgB,mBAGvB,MAAM0C,EAAQzF,KAAK0F,QAAQ,cAC3B,IAAKD,EACH,OAEF,MAAME,EAAUF,EAAMG,cACpB,8BAA8B5F,KAAKM,QAEhCqF,IAGAA,EAAQrF,KACXqF,EAAQrF,GAAK,GAAGN,KAAKM,cAEvBN,KAAK0D,aAAa,kBAAmBiC,EAAQrF,IAC9C,GApaMC,EAAAsF,OAAS,CAACC,EAAgB9C,GAOsC+C,EAAA,CAAtEC,EAAS,CAAEC,SAAS,EAAMC,KAAMtF,OAAQuF,UAAW,mBAA6C5F,EAAA6F,UAAA,oBAAA,GAK9BL,EAAA,CAAlEC,EAAS,CAAEC,SAAS,EAAMC,KAAMtF,OAAQuF,UAAW,eAAqC5F,EAAA6F,UAAA,gBAAA,GAWrBL,EAAA,CAAnEC,EAAS,CAAEC,SAAS,EAAOC,KAAMtF,OAAQuF,UAAW,eAAyD5F,EAAA6F,UAAA,gBAAA,GAa/BL,EAAA,CAA9EC,EAAS,CAAEC,SAAS,EAAOC,KAAMtF,OAAQuF,UAAW,0BAA8E5F,EAAA6F,UAAA,0BAAA,GAOvFL,EAAA,CAA3CC,EAAS,CAAEE,KAAMG,QAASJ,SAAS,KAAiC1F,EAAA6F,UAAA,gBAAA,GAKzBL,EAAA,CAA3CC,EAAS,CAAEE,KAAMG,QAASJ,SAAS,KAAmC1F,EAAA6F,UAAA,kBAAA,GAS1BL,EAAA,CAA5CC,EAAS,CAAEE,KAAMG,QAASJ,SAAS,KAAkC1F,EAAA6F,UAAA,gBAAA,GAQ7DL,EAAA,CAARO,KAAmB/F,EAAA6F,UAAA,aAAA,GAgGVL,EAAA,CADTvD,EAAQ,eA8GRjC,EAAA6F,UAAA,yBAAA,MAMSL,EAAA,CAJTvD,EAAQ,gBACRA,EAAQ,YACRA,EAAQ,YACRA,EAAQ,uBAcRjC,EAAA6F,UAAA,qBAAA,MAGSL,EAAA,CADTvD,EAAQ,aAGRjC,EAAA6F,UAAA,uBAAA,MAmBSL,EAAA,CADTvD,EAAQ,UAyBRjC,EAAA6F,UAAA,iBAAA,MAlVkB7F,EAAWwF,EAAA,CAD/BQ,EAAc,sBACMhG,SAAAA"}
|
package/lib/AsideTrigger.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{css as i,LitElement as e,html as r,nothing as s}from"lit";import{property as o,state as a,customElement as n}from"lit/decorators.js";import{S as d}from"./SlotController-Z6eG7LSZ.js";import{o as l}from"./observe-D0n0zOfU.js";import{s as
|
|
1
|
+
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{css as i,LitElement as e,html as r,nothing as s}from"lit";import{property as o,state as a,customElement as n}from"lit/decorators.js";import{S as d}from"./SlotController-Z6eG7LSZ.js";import{o as l}from"./observe-D0n0zOfU.js";import{s as h}from"./Component-BJ5O5n8k.js";import"./Button.js";import"./Icon.js";import"./Tooltip.js";import"./EventController-BBOmvfLa.js";import"lit/directives/ref.js";import"./LightDomController-DIwtVelV.js";import"./cond-CI1KbneT.js";import"./FocusableMixin-BlQLNPdJ.js";import"./InputMixin-LetXsCyv.js";import"./Spinner.js";import"lit/directives/if-defined.js";import"lit/directives/unsafe-html.js";import"./IconManager.js";import"./positioning-D-K8Mueq.js";import"./events-Bv6wNHwJ.js";import"./fsm-Bq5jMQrK.js";const c=i`:host{--_n-aside-trigger-size:var(--n-aside-trigger-size,32px);--_n-aside-trigger-background-color:var(--n-aside-trigger-background-color,#0000);--_n-aside-trigger-background-color-active:var(
|
|
2
2
|
--n-aside-trigger-background-color-active,var(--n-color-button-hover)
|
|
3
|
-
);display:block;inline-size:100%}.n-aside-trigger{display:block;inline-size:100%;position:relative;text-align:center}.n-aside-trigger nord-button{--n-button-min-block-size:var(--_n-aside-trigger-size);--n-button-min-inline-size:var(--_n-aside-trigger-size);--n-button-background-color:var(--_n-aside-trigger-background-color)}:host([aria-expanded=true]){--_n-aside-trigger-background-color:var(--_n-aside-trigger-background-color-active)}:host(:hover),:host([aria-expanded=true]){--n-button-color:var(--n-color-text);--n-color-icon:var(--n-color-text)}.n-aside-trigger-dot{background:var(--n-color-status-notification);block-size:5px;border-radius:var(--n-border-radius-circle);inline-size:5px;inset-block-start:3px;inset-inline-end:2px;pointer-events:none;position:absolute}`;let p=0,v=class extends e{constructor(){super(...arguments),this.defaultSlot=new d(this),this.tooltipSlot=new d(this,"tooltip"),this.drawer="",this.label="",this.disabled=!1,this.tooltip=!0,this.active=!1,this.targetDisabled=!1,this.tooltipId="n-aside-trigger-tip-"+ ++p,this.handleActiveDrawerChange=()=>{this.syncFromAside()},this.handleClick=t=>{var i;if(this.disabled||this.targetDisabled)return t.stopPropagation(),void t.preventDefault();const e=this.aside;if(
|
|
3
|
+
);display:block;inline-size:100%}.n-aside-trigger{display:block;inline-size:100%;position:relative;text-align:center}.n-aside-trigger nord-button{--n-button-min-block-size:var(--_n-aside-trigger-size);--n-button-min-inline-size:var(--_n-aside-trigger-size);--n-button-background-color:var(--_n-aside-trigger-background-color)}:host([aria-expanded=true]){--_n-aside-trigger-background-color:var(--_n-aside-trigger-background-color-active)}:host(:hover),:host([aria-expanded=true]){--n-button-color:var(--n-color-text);--n-color-icon:var(--n-color-text)}.n-aside-trigger-dot{background:var(--n-color-status-notification);block-size:5px;border-radius:var(--n-border-radius-circle);inline-size:5px;inset-block-start:3px;inset-inline-end:2px;pointer-events:none;position:absolute}`;let p=0,v=class extends e{constructor(){super(...arguments),this.defaultSlot=new d(this),this.tooltipSlot=new d(this,"tooltip"),this.drawer="",this.label="",this.disabled=!1,this.tooltip=!0,this.active=!1,this.targetDisabled=!1,this.tooltipId="n-aside-trigger-tip-"+ ++p,this.handleActiveDrawerChange=()=>{this.syncFromAside()},this.handleClick=t=>{var i;if(this.disabled||this.targetDisabled)return t.stopPropagation(),void t.preventDefault();const e=this.aside;if(this.isLink||!e||!this.drawer)return;const r=null!==(i=e.getAttribute("active-drawer"))&&void 0!==i?i:"";e.activeDrawer=r===this.drawer?"":this.drawer}}render(){const t=!this.defaultSlot.isEmpty,i=!t&&!!this.icon,e=void 0!==this.badge&&null!==this.badge&&""!==this.badge,o=!this.tooltipSlot.isEmpty,a=this.tooltip&&(!!this.label||o);return r`<div class="n-aside-trigger"><nord-button square size="s" variant="plain" type="button" href="${this.href||s}" target="${this.isLink&&this.target||s}" rel="${this.isLink&&this.rel||s}" aria-label="${this.label||s}" aria-describedby="${a?this.tooltipId:s}" ?disabled="${this.disabled}" @click="${this.handleClick}">${t?r`<slot></slot>`:s} ${i?r`<nord-icon name="${this.icon}" size="m"></nord-icon>`:s}</nord-button>${e?r`<span class="n-aside-trigger-dot" aria-hidden="true"></span>`:s} ${a?r`<nord-tooltip id="${this.tooltipId}" position="inline-start"><slot name="tooltip">${this.label}</slot></nord-tooltip>`:s}</div>`}connectedCallback(){super.connectedCallback(),this.startObservingAside()}disconnectedCallback(){super.disconnectedCallback(),this.stopObservingAside()}handleDrawerChange(){this.syncFromAside()}handleHrefChange(){this.syncFromAside()}handleDisabledChange(){this.syncAria()}get aside(){return this.closest("nord-aside")}get isLink(){return!!this.href}startObservingAside(){var t;this.observedAside=null!==(t=this.aside)&&void 0!==t?t:void 0,this.observedAside&&(this.observedAside.addEventListener("active-drawer-change",this.handleActiveDrawerChange),this.syncFromAside())}stopObservingAside(){var t;null===(t=this.observedAside)||void 0===t||t.removeEventListener("active-drawer-change",this.handleActiveDrawerChange),this.observedAside=void 0}syncFromAside(){var t,i,e;const r=null!==(t=this.observedAside)&&void 0!==t?t:this.aside;if(this.isLink||!r)return this.active=!1,this.targetDisabled=!1,void this.syncAria();const s=null!==(e=null!==(i=r.activeDrawer)&&void 0!==i?i:r.getAttribute("active-drawer"))&&void 0!==e?e:"";this.active=!!this.drawer&&s===this.drawer;const o=this.drawer?r.querySelector(`:scope > nord-aside-drawer#${CSS.escape(this.drawer)}`):null;this.targetDisabled=!o||o.hasAttribute("disabled"),this.syncAria()}syncAria(){this.drawer&&!this.isLink?this.setAttribute("aria-controls",this.drawer):this.removeAttribute("aria-controls"),this.isLink?this.removeAttribute("aria-expanded"):this.setAttribute("aria-expanded",this.active?"true":"false"),this.disabled||this.targetDisabled?this.setAttribute("aria-disabled","true"):this.removeAttribute("aria-disabled")}};v.styles=[h,c],t([o({reflect:!0})],v.prototype,"drawer",void 0),t([o({reflect:!0})],v.prototype,"href",void 0),t([o()],v.prototype,"target",void 0),t([o()],v.prototype,"rel",void 0),t([o({reflect:!0})],v.prototype,"icon",void 0),t([o({reflect:!0})],v.prototype,"label",void 0),t([o({reflect:!0})],v.prototype,"badge",void 0),t([o({type:Boolean,reflect:!0})],v.prototype,"disabled",void 0),t([o({type:Boolean,reflect:!0})],v.prototype,"tooltip",void 0),t([a()],v.prototype,"active",void 0),t([a()],v.prototype,"targetDisabled",void 0),t([l("drawer")],v.prototype,"handleDrawerChange",null),t([l("href")],v.prototype,"handleHrefChange",null),t([l("disabled")],v.prototype,"handleDisabledChange",null),v=t([n("nord-aside-trigger")],v);var g=v;export{g as default};
|
|
4
4
|
//# sourceMappingURL=AsideTrigger.js.map
|
package/lib/AsideTrigger.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AsideTrigger.js","sources":["../src/aside-trigger/AsideTrigger.ts"],"sourcesContent":["import { html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\nimport { observe } from '../common/decorators/observe.js'\n\nimport componentStyle from '../common/styles/Component.css'\nimport style from './AsideTrigger.css'\nimport '../button/Button.js'\nimport '../icon/Icon.js'\nimport '../tooltip/Tooltip.js'\n\nlet asideTriggerTooltipIdCounter = 0\n\n/**\n * Rail button that toggles a `<nord-aside-drawer>` inside `<nord-aside>`.\n *\n * @status new\n * @category action\n * @slot - Overrides the `icon` attribute for custom leading content.\n * @slot tooltip - Overrides the tooltip body (falls back to `label`).\n * Lets consumers add icons, shortcut hints, etc. — anything beyond the\n * plain string label.\n *\n * @parent Aside\n */\n@customElement('nord-aside-trigger')\nexport default class AsideTrigger extends LitElement {\n static styles = [componentStyle, style]\n\n private defaultSlot = new SlotController(this)\n private tooltipSlot = new SlotController(this, 'tooltip')\n\n /**\n * The id of the `<nord-aside-drawer>` this trigger toggles.\n * Required.\n */\n @property({ reflect: true }) drawer: string = ''\n\n /**\n * Nord icon name. Shortcut for the common case; equivalent to placing\n * `<nord-icon name=\"…\">` in the default slot.\n */\n @property({ reflect: true }) icon?: string\n\n /**\n * Accessible label (used for `aria-label` on the host).\n */\n @property({ reflect: true }) label: string = ''\n\n /**\n * Optional badge content (forwarded to inner `<nord-badge>`).\n */\n @property({ reflect: true }) badge?: string | number\n\n /**\n * Makes the trigger disabled. Takes precedence over the target\n * drawer's `disabled` state.\n */\n @property({ type: Boolean, reflect: true }) disabled: boolean = false\n\n /**\n * Whether to render a `<nord-tooltip>` showing `label` on hover/focus.\n * Defaults to `true` — set `tooltip=\"false\"` (or omit `label`) to opt out.\n */\n @property({ type: Boolean, reflect: true }) tooltip: boolean = true\n\n /**\n * Whether the ancestor `<nord-aside>`'s `active-drawer` currently\n * matches this trigger's `drawer` id.\n * @internal\n */\n @state() private active: boolean = false\n\n /**\n * Whether the target drawer exists and is not disabled. Drives\n * `aria-disabled` on the host.\n * @internal\n */\n @state() private targetDisabled: boolean = false\n\n private observedAside?: HTMLElement\n private readonly tooltipId = `n-aside-trigger-tip-${++asideTriggerTooltipIdCounter}`\n\n render() {\n const showCustomLeading = !this.defaultSlot.isEmpty\n const showIcon = !showCustomLeading && !!this.icon\n const showBadge = this.badge !== undefined && this.badge !== null && this.badge !== ''\n\n // Tooltip is rendered when explicitly enabled AND there's something to\n // show — either the `label` attr (default body) or a custom `tooltip`\n // slot.\n const hasTooltipSlot = !this.tooltipSlot.isEmpty\n const showTooltip = this.tooltip && (!!this.label || hasTooltipSlot)\n return html`\n <div class=\"n-aside-trigger\">\n <nord-button\n square\n size=\"s\"\n variant=\"plain\"\n type=\"button\"\n aria-label=${this.label || nothing}\n aria-describedby=${showTooltip ? this.tooltipId : nothing}\n ?disabled=${this.disabled}\n @click=${this.handleClick}\n >\n ${showCustomLeading ? html`<slot></slot>` : nothing}\n ${showIcon ? html`<nord-icon name=${this.icon!} size=\"m\"></nord-icon>` : nothing}\n </nord-button>\n ${showBadge ? html`<span class=\"n-aside-trigger-dot\" aria-hidden=\"true\"></span>` : nothing}\n ${showTooltip\n ? html`\n <nord-tooltip id=${this.tooltipId} position=\"inline-start\">\n <slot name=\"tooltip\">${this.label}</slot>\n </nord-tooltip>\n `\n : nothing}\n </div>\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.startObservingAside()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.stopObservingAside()\n }\n\n @observe('drawer')\n protected handleDrawerChange() {\n this.syncFromAside()\n }\n\n @observe('disabled')\n protected handleDisabledChange() {\n this.syncAria()\n }\n\n private get aside(): HTMLElement | null {\n return this.closest('nord-aside')\n }\n\n private readonly handleActiveDrawerChange = () => {\n this.syncFromAside()\n }\n\n /**\n * Listen to the aside's own `active-drawer-change` event instead of each\n * trigger installing its own subtree MutationObserver on the whole aside.\n * One shared event covers open / switch / close, so N triggers no longer\n * mean N observers waking on every aside DOM mutation.\n */\n private startObservingAside() {\n this.observedAside = this.aside ?? undefined\n if (!this.observedAside) {\n return\n }\n this.observedAside.addEventListener('active-drawer-change', this.handleActiveDrawerChange)\n this.syncFromAside()\n }\n\n private stopObservingAside() {\n this.observedAside?.removeEventListener('active-drawer-change', this.handleActiveDrawerChange)\n this.observedAside = undefined\n }\n\n private syncFromAside() {\n const aside = this.observedAside ?? this.aside\n if (!aside) {\n this.active = false\n this.targetDisabled = false\n this.syncAria()\n return\n }\n\n // Prefer the live `activeDrawer` property over the reflected attribute:\n // the `active-drawer-change` event fires during the aside's update cycle,\n // before Lit reflects the property to the `active-drawer` attribute, so\n // reading the attribute here would lag one tick behind. Fall back to the\n // attribute only while the aside element has not upgraded yet.\n const activeDrawer\n = (aside as HTMLElement & { activeDrawer?: string }).activeDrawer\n ?? aside.getAttribute('active-drawer')\n ?? ''\n this.active = !!this.drawer && activeDrawer === this.drawer\n\n const target = this.drawer\n ? aside.querySelector(`:scope > nord-aside-drawer#${CSS.escape(this.drawer)}`)\n : null\n this.targetDisabled = !target || target.hasAttribute('disabled')\n\n this.syncAria()\n }\n\n private syncAria() {\n if (this.drawer) {\n this.setAttribute('aria-controls', this.drawer)\n }\n else {\n this.removeAttribute('aria-controls')\n }\n this.setAttribute('aria-expanded', this.active ? 'true' : 'false')\n if (this.disabled || this.targetDisabled) {\n this.setAttribute('aria-disabled', 'true')\n }\n else {\n this.removeAttribute('aria-disabled')\n }\n }\n\n private handleClick = (event: Event) => {\n if (this.disabled || this.targetDisabled) {\n event.stopPropagation()\n event.preventDefault()\n return\n }\n const aside = this.aside\n if (!aside || !this.drawer) {\n return\n }\n const current = aside.getAttribute('active-drawer') ?? ''\n ;(aside as HTMLElement & { activeDrawer?: string }).activeDrawer\n = current === this.drawer ? '' : this.drawer\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-aside-trigger': AsideTrigger\n }\n}\n"],"names":["asideTriggerTooltipIdCounter","AsideTrigger","LitElement","constructor","this","defaultSlot","SlotController","tooltipSlot","drawer","label","disabled","tooltip","active","targetDisabled","tooltipId","handleActiveDrawerChange","syncFromAside","handleClick","event","stopPropagation","preventDefault","aside","current","_a","getAttribute","activeDrawer","render","showCustomLeading","isEmpty","showIcon","icon","showBadge","undefined","badge","hasTooltipSlot","showTooltip","html","nothing","connectedCallback","super","startObservingAside","disconnectedCallback","stopObservingAside","handleDrawerChange","handleDisabledChange","syncAria","closest","observedAside","addEventListener","removeEventListener","_c","_b","target","querySelector","CSS","escape","hasAttribute","setAttribute","removeAttribute","styles","componentStyle","style","__decorate","property","reflect","prototype","type","Boolean","state","observe","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AsideTrigger.js","sources":["../src/aside-trigger/AsideTrigger.ts"],"sourcesContent":["import { html, LitElement, nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\nimport { observe } from '../common/decorators/observe.js'\n\nimport componentStyle from '../common/styles/Component.css'\nimport style from './AsideTrigger.css'\nimport '../button/Button.js'\nimport '../icon/Icon.js'\nimport '../tooltip/Tooltip.js'\n\nlet asideTriggerTooltipIdCounter = 0\n\n/**\n * Rail button that toggles a `<nord-aside-drawer>` inside `<nord-aside>`,\n * or navigates like a link when `href` is set.\n *\n * @status new\n * @category action\n * @slot - Overrides the `icon` attribute for custom leading content.\n * @slot tooltip - Overrides the tooltip body (falls back to `label`).\n * Lets consumers add icons, shortcut hints, etc. — anything beyond the\n * plain string label.\n *\n * @parent Aside\n */\n@customElement('nord-aside-trigger')\nexport default class AsideTrigger extends LitElement {\n static styles = [componentStyle, style]\n\n private defaultSlot = new SlotController(this)\n private tooltipSlot = new SlotController(this, 'tooltip')\n\n /**\n * The id of the `<nord-aside-drawer>` this trigger toggles.\n * Mutually exclusive with `href` — when both are set, `href` wins and\n * the trigger acts as a link instead of a drawer toggle.\n */\n @property({ reflect: true }) drawer: string = ''\n\n /**\n * When provided, renders the trigger as a link that navigates to the\n * given URL instead of toggling a drawer. Mutually exclusive with\n * `drawer`. Mirrors `nord-button`'s link mode, so middle-click,\n * cmd/ctrl-click and right-click behave like a real anchor.\n */\n @property({ reflect: true }) href?: string\n\n /**\n * When provided together with a href property, determines where to open\n * the linked URL. Forwarded to the underlying anchor.\n */\n @property() target?: '_self' | '_blank' | '_parent' | '_top'\n\n /**\n * When provided together with a href property, sets the relationship\n * between the linked URL and the current document, e.g. “noopener\n * noreferrer” when linking to untrusted content in a new tab.\n */\n @property() rel?: string\n\n /**\n * Nord icon name. Shortcut for the common case; equivalent to placing\n * `<nord-icon name=\"…\">` in the default slot.\n */\n @property({ reflect: true }) icon?: string\n\n /**\n * Accessible label (used for `aria-label` on the host).\n */\n @property({ reflect: true }) label: string = ''\n\n /**\n * Optional badge content (forwarded to inner `<nord-badge>`).\n */\n @property({ reflect: true }) badge?: string | number\n\n /**\n * Makes the trigger disabled. Takes precedence over the target\n * drawer's `disabled` state.\n */\n @property({ type: Boolean, reflect: true }) disabled: boolean = false\n\n /**\n * Whether to render a `<nord-tooltip>` showing `label` on hover/focus.\n * Defaults to `true` — set `tooltip=\"false\"` (or omit `label`) to opt out.\n */\n @property({ type: Boolean, reflect: true }) tooltip: boolean = true\n\n /**\n * Whether the ancestor `<nord-aside>`'s `active-drawer` currently\n * matches this trigger's `drawer` id.\n * @internal\n */\n @state() private active: boolean = false\n\n /**\n * Whether the target drawer exists and is not disabled. Drives\n * `aria-disabled` on the host.\n * @internal\n */\n @state() private targetDisabled: boolean = false\n\n private observedAside?: HTMLElement\n private readonly tooltipId = `n-aside-trigger-tip-${++asideTriggerTooltipIdCounter}`\n\n render() {\n const showCustomLeading = !this.defaultSlot.isEmpty\n const showIcon = !showCustomLeading && !!this.icon\n const showBadge = this.badge !== undefined && this.badge !== null && this.badge !== ''\n\n // Tooltip is rendered when explicitly enabled AND there's something to\n // show — either the `label` attr (default body) or a custom `tooltip`\n // slot.\n const hasTooltipSlot = !this.tooltipSlot.isEmpty\n const showTooltip = this.tooltip && (!!this.label || hasTooltipSlot)\n return html`\n <div class=\"n-aside-trigger\">\n <nord-button\n square\n size=\"s\"\n variant=\"plain\"\n type=\"button\"\n href=${this.href || nothing}\n target=${(this.isLink && this.target) || nothing}\n rel=${(this.isLink && this.rel) || nothing}\n aria-label=${this.label || nothing}\n aria-describedby=${showTooltip ? this.tooltipId : nothing}\n ?disabled=${this.disabled}\n @click=${this.handleClick}\n >\n ${showCustomLeading ? html`<slot></slot>` : nothing}\n ${showIcon ? html`<nord-icon name=${this.icon!} size=\"m\"></nord-icon>` : nothing}\n </nord-button>\n ${showBadge ? html`<span class=\"n-aside-trigger-dot\" aria-hidden=\"true\"></span>` : nothing}\n ${showTooltip\n ? html`\n <nord-tooltip id=${this.tooltipId} position=\"inline-start\">\n <slot name=\"tooltip\">${this.label}</slot>\n </nord-tooltip>\n `\n : nothing}\n </div>\n `\n }\n\n connectedCallback() {\n super.connectedCallback()\n this.startObservingAside()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.stopObservingAside()\n }\n\n @observe('drawer')\n protected handleDrawerChange() {\n this.syncFromAside()\n }\n\n @observe('href')\n protected handleHrefChange() {\n this.syncFromAside()\n }\n\n @observe('disabled')\n protected handleDisabledChange() {\n this.syncAria()\n }\n\n private get aside(): HTMLElement | null {\n return this.closest('nord-aside')\n }\n\n /**\n * `href` takes precedence over `drawer`: a trigger with both acts as a\n * link and never toggles a drawer.\n */\n private get isLink(): boolean {\n return !!this.href\n }\n\n private readonly handleActiveDrawerChange = () => {\n this.syncFromAside()\n }\n\n /**\n * Listen to the aside's own `active-drawer-change` event instead of each\n * trigger installing its own subtree MutationObserver on the whole aside.\n * One shared event covers open / switch / close, so N triggers no longer\n * mean N observers waking on every aside DOM mutation.\n */\n private startObservingAside() {\n this.observedAside = this.aside ?? undefined\n if (!this.observedAside) {\n return\n }\n this.observedAside.addEventListener('active-drawer-change', this.handleActiveDrawerChange)\n this.syncFromAside()\n }\n\n private stopObservingAside() {\n this.observedAside?.removeEventListener('active-drawer-change', this.handleActiveDrawerChange)\n this.observedAside = undefined\n }\n\n private syncFromAside() {\n const aside = this.observedAside ?? this.aside\n if (this.isLink || !aside) {\n this.active = false\n this.targetDisabled = false\n this.syncAria()\n return\n }\n\n // Prefer the live `activeDrawer` property over the reflected attribute:\n // the `active-drawer-change` event fires during the aside's update cycle,\n // before Lit reflects the property to the `active-drawer` attribute, so\n // reading the attribute here would lag one tick behind. Fall back to the\n // attribute only while the aside element has not upgraded yet.\n const activeDrawer\n = (aside as HTMLElement & { activeDrawer?: string }).activeDrawer\n ?? aside.getAttribute('active-drawer')\n ?? ''\n this.active = !!this.drawer && activeDrawer === this.drawer\n\n const target = this.drawer\n ? aside.querySelector(`:scope > nord-aside-drawer#${CSS.escape(this.drawer)}`)\n : null\n this.targetDisabled = !target || target.hasAttribute('disabled')\n\n this.syncAria()\n }\n\n private syncAria() {\n // Link mode has no expand/collapse semantics — no aria-controls or\n // aria-expanded, and the inner nord-button anchor handles disabled\n // link semantics itself.\n if (this.drawer && !this.isLink) {\n this.setAttribute('aria-controls', this.drawer)\n }\n else {\n this.removeAttribute('aria-controls')\n }\n if (this.isLink) {\n this.removeAttribute('aria-expanded')\n }\n else {\n this.setAttribute('aria-expanded', this.active ? 'true' : 'false')\n }\n if (this.disabled || this.targetDisabled) {\n this.setAttribute('aria-disabled', 'true')\n }\n else {\n this.removeAttribute('aria-disabled')\n }\n }\n\n private handleClick = (event: Event) => {\n if (this.disabled || this.targetDisabled) {\n event.stopPropagation()\n event.preventDefault()\n return\n }\n const aside = this.aside\n if (this.isLink || !aside || !this.drawer) {\n return\n }\n const current = aside.getAttribute('active-drawer') ?? ''\n ;(aside as HTMLElement & { activeDrawer?: string }).activeDrawer\n = current === this.drawer ? '' : this.drawer\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-aside-trigger': AsideTrigger\n }\n}\n"],"names":["asideTriggerTooltipIdCounter","AsideTrigger","LitElement","constructor","this","defaultSlot","SlotController","tooltipSlot","drawer","label","disabled","tooltip","active","targetDisabled","tooltipId","handleActiveDrawerChange","syncFromAside","handleClick","event","stopPropagation","preventDefault","aside","isLink","current","_a","getAttribute","activeDrawer","render","showCustomLeading","isEmpty","showIcon","icon","showBadge","undefined","badge","hasTooltipSlot","showTooltip","html","href","nothing","target","rel","connectedCallback","super","startObservingAside","disconnectedCallback","stopObservingAside","handleDrawerChange","handleHrefChange","handleDisabledChange","syncAria","closest","observedAside","addEventListener","removeEventListener","_c","_b","querySelector","CSS","escape","hasAttribute","setAttribute","removeAttribute","styles","componentStyle","style","__decorate","property","reflect","prototype","type","Boolean","state","observe","customElement"],"mappings":"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"}
|
package/lib/Autocomplete.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{nothing as i}from"lit";import{property as e,state as o,customElement as s}from"lit/decorators.js";import{s as r}from"./Combobox-DBxXlbY7.js";import
|
|
1
|
+
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{nothing as i}from"lit";import{property as e,state as o,customElement as s}from"lit/decorators.js";import{s as r}from"./Combobox-DBxXlbY7.js";import l,{retagComboboxStyles as n}from"./ComboboxBase.js";import{ComboboxClearEvent as a}from"./ComboboxEvents.js";import{i as c}from"./light-dom-styles-DXsbiKU9.js";import{LocalizeController as p}from"./LocalizeController.js";import"./ActiveDescendantController-BnMqM79O.js";import"./number-Dg2vCfGd.js";import"./interface-close-small-CnpAFMO3.js";import"./interface-dropdown-small-CYvgxudU.js";import"lit/directives/class-map.js";import"lit/directives/if-defined.js";import"lit/directives/ref.js";import"lit/directives/repeat.js";import"lit/directives/style-map.js";import"lit/directives/unsafe-html.js";import"./collection-Dvg2XbxV.js";import"./FormDataController-OUt5L5uC.js";import"./LightDismissController-wcL1ziJa.js";import"./EventController-BBOmvfLa.js";import"./ShortcutController-CV5b0BZl.js";import"./tinykeys-DwvDXsEb.js";import"./ResizeController-14PUwRQy.js";import"./StateAttributesController-BIh96sXZ.js";import"./observe-D0n0zOfU.js";import"./cond-CI1KbneT.js";import"./events-Bv6wNHwJ.js";import"./AutocompleteMixin-D8eiOxvO.js";import"./DraftComponentMixin-4qug5Rky.js";import"./FieldControlMixin-BXNZJXu9.js";import"./context-consumer-BJVUulGf.js";import"./context-request-event-CCRlXoeR.js";import"./field-context.js";import"./create-context-CJ69ntlY.js";import"./SlotController-Z6eG7LSZ.js";import"./VisuallyHidden.js";import"./Popout-B6Hirj3y.js";import"./positioning-D-K8Mueq.js";import"./DirectionController-ChvNGESZ.js";import"./ScrollbarController-BFC67Y2x.js";import"./Component-BJ5O5n8k.js";import"./InputMixin-LetXsCyv.js";import"./SizeMixin-CU9cLbLC.js";import"./Icon.js";import"./IconManager.js";import"./Spinner.js";import"./translation.js";import"./en-us.js";import"./localization.js";import"./localization2.js";import"./localization3.js";import"./localization4.js";import"./localization5.js";import"./localization6.js";import"./localization7.js";import"./localization8.js";import"./localization9.js";import"./localization10.js";import"./localization11.js";import"./localization12.js";import"./localization13.js";import"./localization14.js";import"./localization15.js";import"./localization16.js";import"./localization17.js";import"./localization18.js";import"./localization19.js";import"./localization20.js";import"./localization21.js";import"./localization22.js";import"./localization23.js";import"./localization24.js";const h=n(r,"nord-autocomplete");let m=class extends l{constructor(){super(...arguments),this.value="",this.inline=!1,this.clearable=!1,this.openOnInputClick=!1,this.localize=new p(this),this.keyboardActive=!1}connectedCallback(){super.connectedCallback(),c(this,h)}get filterQuery(){return this.value}get inlineQuery(){return this.value}get hasInlineGhost(){if(!this.inline||!this.keyboardActive||!this.open)return!1;const t=this.activeOption,i=this.inlineQuery;if(!t||!i)return!1;const e=t.label;return e.length>i.length&&e.toLocaleLowerCase().startsWith(i.toLocaleLowerCase())}get displayValue(){if(this.hasInlineGhost){const t=this.inlineQuery,{label:i}=this.activeOption;return t+i.slice(t.length)}return this.value}get hasValue(){return this.value.length>0}get formValue(){return this.value||void 0}get showClear(){return this.clearable&&this.hasValue&&!this.disabled&&!this.readonly}get showToggle(){return!1}get ariaAutocomplete(){return this.inline?this.externalFiltering?"inline":"both":this.externalFiltering?"none":"list"}isSelected(){return!1}get ariaMultiselectable(){}showCheck(){return!1}get rendersCheck(){return!1}optionAriaSelected(){return i}onHide(){}onInput(t){this.keyboardActive=!1,this.value=t.value,this.commit()}onKeydown(t){switch(t.key){case"ArrowDown":case"ArrowUp":case"Home":this.open&&(this.keyboardActive=!0);break;case"ArrowRight":case"End":if(this.inline&&this.open&&this.hasInlineGhost){const i=this.inputRef.value;if(!i||i.selectionEnd===i.value.length){t.preventDefault(),this.acceptInline();break}}"End"===t.key&&this.open&&(this.keyboardActive=!0);break;case"Backspace":case"Delete":this.keyboardActive=!1}}onMove(){this.keyboardActive=!0}handleEnter(t){return this.inline&&this.hasInlineGhost&&this.activeOption?(t.preventDefault(),this.acceptInline(),!0):(this.navigated||this.autoHighlight&&this.hasQuery)&&this.activeOption?(t.preventDefault(),this.selectOption(this.activeOption),!0):(this.hide(),!0)}get hasQuery(){return this.filterQuery.length>0}onOptionHover(){this.keyboardActive=!1}onBlur(){this.inline&&this.hasInlineGhost&&this.revertInline(),this.keyboardActive=!1}afterUpdate(){if(this.hasInlineGhost){const t=this.inputRef.value;t&&t.setSelectionRange(this.inlineQuery.length,t.value.length)}}selectOption(t){t.disabled||(this.labelCache.set(t.value,t.label),this.keyboardActive=!1,this.value=t.label,this.search=t.label,this.commit(),this.hide(),this.focus())}acceptInline(){const t=this.activeOption;t&&this.selectOption(t)}revertInline(){this.keyboardActive=!1;const t=this.inputRef.value;t&&(t.value=this.value,t.setSelectionRange(this.value.length,this.value.length))}resetActiveIndex(){super.resetActiveIndex(),this.keyboardActive=!1}clear(){this.value="",this.search="",this.keyboardActive=!1,this.dispatchEvent(new a),this.commit(),this.focus()}};t([e({attribute:!1})],m.prototype,"value",void 0),t([e({reflect:!0,type:Boolean})],m.prototype,"inline",void 0),t([e({reflect:!0,type:Boolean})],m.prototype,"clearable",void 0),t([e({reflect:!0,type:Boolean,attribute:"open-on-input-click"})],m.prototype,"openOnInputClick",void 0),t([o()],m.prototype,"keyboardActive",void 0),m=t([s("nord-autocomplete")],m);var u=m;export{a as ComboboxClearEvent,u as default};
|
|
2
2
|
//# sourceMappingURL=Autocomplete.js.map
|
package/lib/Autocomplete.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Autocomplete.js","sources":["../src/autocomplete/Autocomplete.ts"],"sourcesContent":["import type { ComboboxOption } from '../combobox/ComboboxBase.js'\nimport { nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport comboboxStyle from '../combobox/Combobox.css'\nimport ComboboxBase, { retagComboboxStyles } from '../combobox/ComboboxBase.js'\nimport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\nimport { injectLightDomStyles } from '../common/light-dom-styles.js'\nimport { LocalizeController } from '../localization/LocalizeController.js'\n\nexport type { ComboboxOption } from '../combobox/ComboboxBase.js'\nexport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\n\n/**\n * The shared combobox stylesheet is authored once against the `nord-combobox`\n * tag (see `Combobox.css`); re-prefix it for `nord-autocomplete` so this sibling\n * light-DOM control reuses the exact same `.n-*` rules without duplicating them.\n */\nconst style = retagComboboxStyles(comboboxStyle, 'nord-autocomplete')\n\n/**\n * Autocomplete is a free-text input with suggestions: the text the user types\n * is itself the committed `value` (a string), and the options are completion\n * aids. Picking a suggestion fills the input with the option's `label` and commits\n * that label. Use it when the answer is open-ended text but you can offer\n * helpful completions (a city, a tag, a username); reach for\n * [Combobox](/components/combobox/) instead when the value must be one of a\n * fixed set of options.\n *\n * Autocomplete is a control only — it has no built-in label, hint or error.\n * Compose it inside a [Field](/components/field/) to give it a label, hint and\n * error message, the same way you would a native input.\n *\n * Base UI parity: Autocomplete is the Combobox primitive with\n * `selectionMode=\"none\"` + `fillInputOnItemPress`.\n *\n * @status draft\n * @category form\n * @lightDom true\n *\n * @slot start - Optional slot used to place an icon or prefix at the start of the control.\n * @slot end - Optional slot used to place an icon or suffix at the end of the control.\n *\n * @fires {NordEvent} change - Dispatched when the committed value changes via user interaction.\n * @fires {NordEvent} input - Dispatched as the user types into the input.\n * @fires {NordEvent} open - Dispatched when the listbox is opened.\n * @fires {NordEvent} close - Dispatched when the listbox is closed.\n * @fires {ComboboxClearEvent} clear - Dispatched when the value is cleared via the clear button or `clear()`.\n *\n * @localization clearLabel - Accessible label for the clear button.\n * @localization noResults - Message shown when no options match the query.\n *\n * @cssprop [--n-combobox-list-inline-size] - Controls the inline size, or width, of the dropdown list. Defaults to the width of the control.\n * @cssprop [--n-combobox-block-size] - Controls the block size, or height, of the control. The medium default is `var(--n-space-xl)`; the s and l sizes scale from it.\n *\n * @attr {boolean} data-invalid - Present while the control is invalid (mirrors the `invalid` property). Style hook for the whole control.\n * @attr {boolean} data-focused - Present while the control has focus. Style hook for focus-within treatments.\n */\n@customElement('nord-autocomplete')\nexport default class Autocomplete extends ComboboxBase {\n /**\n * The value of the autocomplete. This is the free text the user has typed (or\n * the `label` of a picked suggestion) — always a single string.\n */\n @property({ attribute: false }) value = ''\n\n /**\n * Enables inline ghost-text completion. While an option is highlighted with\n * the keyboard, the input shows the typed query followed by the remainder of\n * the highlighted option's label, with that remainder text-selected so\n * continued typing overwrites it. Accepting (Enter, ArrowRight/End at the end\n * of the text, or an option click) commits the full label.\n */\n @property({ reflect: true, type: Boolean }) inline = false\n\n /**\n * Shows a clear (✕) button while there is text. Pressing it resets the value\n * to `''`, then fires `clear` and `change`.\n */\n @property({ reflect: true, type: Boolean }) clearable = false\n\n /**\n * Whether clicking the input opens the suggestion list. Defaults to `false`\n * for autocomplete — matching Base UI's `openOnInputClick={false}`, the list\n * appears as the user types rather than on click. Set to `true` to also open\n * on click.\n */\n @property({ reflect: true, type: Boolean, attribute: 'open-on-input-click' }) override openOnInputClick = false\n\n protected override localize = new LocalizeController<'nord-autocomplete'>(this)\n\n /**\n * Whether the active option last changed via the keyboard (arrows/Home/End)\n * rather than pointer hover. Inline ghost completion only fills on keyboard\n * navigation, mirroring Base UI's `keyboardActiveRef`. Reactive so toggling it\n * (even when `activeIndex` is unchanged, e.g. a single filtered option)\n * re-renders the ghost.\n */\n @state() private keyboardActive = false\n\n connectedCallback() {\n super.connectedCallback()\n injectLightDomStyles(this, style)\n }\n\n /**\n * The committed free-text `value` *is* the query, so a programmatically-set\n * `value` (e.g. `el.value = 'fea'`) filters the open list without the user\n * having typed, matching the text the input already displays.\n */\n protected override get filterQuery(): string {\n return this.value\n }\n\n /** The typed query an inline ghost is composed against (the committed value). */\n private get inlineQuery(): string {\n return this.value\n }\n\n /**\n * Whether an inline ghost suffix is currently showing: only in `inline` mode,\n * when the active option (reached via the keyboard) has a label that starts\n * with the typed query and extends it.\n */\n private get hasInlineGhost(): boolean {\n if (!this.inline || !this.keyboardActive || !this.open) {\n return false\n }\n\n const option = this.activeOption\n const query = this.inlineQuery\n\n if (!option || !query) {\n return false\n }\n\n const label = option.label\n return (\n label.length > query.length\n && label.toLocaleLowerCase().startsWith(query.toLocaleLowerCase())\n )\n }\n\n /** The text shown in the input. */\n protected override get displayValue(): string {\n // While an inline ghost is showing, present the typed query plus the\n // highlighted option's remaining label (with the original casing).\n if (this.hasInlineGhost) {\n const query = this.inlineQuery\n const { label } = this.activeOption!\n return query + label.slice(query.length)\n }\n\n // Otherwise the input always reflects the committed free-text value.\n return this.value\n }\n\n /** Free text is always present once non-empty (drives the clear affordance). */\n protected override get hasValue(): boolean {\n return this.value.length > 0\n }\n\n protected override get formValue(): string | undefined {\n return this.value || undefined\n }\n\n protected override get showClear(): boolean {\n return this.clearable && this.hasValue && !this.disabled && !this.readonly\n }\n\n /**\n * Autocomplete is a search input with suggestions, not a picker — like Base\n * UI's autocomplete it renders no dropdown chevron (its `AriaCombobox` has no\n * `Trigger`). The list appears as you type, not on a toggle click.\n */\n protected override get showToggle(): boolean {\n return false\n }\n\n /**\n * The `aria-autocomplete` value for the inner input, mapping the `inline` +\n * `external-filtering` axes onto the four ARIA values:\n * - inline + internal filter → `both`\n * - inline + external-filtering → `inline` (static list + ghost)\n * - external-filtering (no inline) → `none` (static list, no ghost)\n * - otherwise → `list` (internal filter, no ghost)\n */\n protected override get ariaAutocomplete(): 'list' | 'both' | 'inline' | 'none' {\n if (this.inline) {\n return this.externalFiltering ? 'inline' : 'both'\n }\n return this.externalFiltering ? 'none' : 'list'\n }\n\n /**\n * Autocomplete renders no selection state — `aria-selected` is omitted\n * (`optionAriaSelected` → nothing) and the check is never shown\n * (`showCheck` → false) — so nothing is ever \"selected\".\n */\n protected override isSelected(): boolean {\n return false\n }\n\n /**\n * Autocomplete has no selection model, so the listbox does not advertise\n * `aria-multiselectable` at all (the base's `'false'` would imply a single-\n * select semantic it doesn't have).\n */\n protected override get ariaMultiselectable(): undefined {\n return undefined\n }\n\n /** Autocomplete never shows the selected-check icon (it is not a select). */\n protected override showCheck(): boolean {\n return false\n }\n\n /** No selection model, so no leading check gutter (avoids indenting suggestions). */\n protected override get rendersCheck(): boolean {\n return false\n }\n\n /** Autocomplete options are completion aids, not selectable values — omit aria-selected. */\n protected override optionAriaSelected(): typeof nothing {\n return nothing\n }\n\n /** Autocomplete keeps its committed free-text query when the listbox closes. */\n protected override onHide() {\n // Intentionally empty: the free-text value is the query, so it is not\n // discarded the way select mode's transient filter is.\n }\n\n /** Typing commits the typed text as the live value. */\n protected override onInput(input: HTMLInputElement): void {\n // Typing breaks any inline ghost suffix the keyboard had composed.\n this.keyboardActive = false\n // The typed text *is* the value, so it tracks the input live. `input` has\n // already fired (in the base); `commit()` emits the live `change`.\n this.value = input.value\n this.commit()\n }\n\n /**\n * Runs before the base's own key switch. Marks keyboard navigation (so the\n * inline ghost may compose) and owns the ArrowRight/End inline-accept. After\n * an accept the listbox is closed, so the base's own `End` branch (gated on\n * `this.open`) is skipped; the base still performs the moves for the arrows\n * and Home.\n */\n protected override onKeydown(e: KeyboardEvent): void {\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp':\n case 'Home':\n // The base performs the actual move (and opens a closed listbox); mark\n // it as keyboard navigation so the ghost composes after it.\n if (this.open) {\n this.keyboardActive = true\n }\n break\n case 'ArrowRight':\n case 'End':\n // Accept an inline ghost suffix: End always sits at the text end, and\n // ArrowRight accepts when the selection already reaches the end. When the\n // ghost suffix is selected, `selectionEnd` is the text length, so both\n // cases collapse to \"is the selection at the end?\". A bare `End` (no\n // ghost) falls through to the base's jump-to-last navigation, which we\n // mark as keyboard nav so the ghost composes for the newly-active row.\n if (this.inline && this.open && this.hasInlineGhost) {\n const input = this.inputRef.value\n const atEnd = !input || input.selectionEnd === input.value.length\n if (atEnd) {\n e.preventDefault()\n this.acceptInline()\n break\n }\n }\n if (e.key === 'End' && this.open) {\n this.keyboardActive = true\n }\n break\n case 'Backspace':\n case 'Delete':\n // A deletion breaks any inline ghost suffix.\n this.keyboardActive = false\n break\n }\n }\n\n protected override onMove(): void {\n this.keyboardActive = true\n }\n\n /**\n * Fully owns Enter when the listbox is open. An inline ghost or any\n * highlighted option fills+commits its label; otherwise the typed text is\n * already committed, so Enter just closes and does NOT preventDefault (the\n * owning form may submit). Returns `true` so the base does no further handling.\n */\n protected override handleEnter(e: KeyboardEvent): boolean {\n if (this.inline && this.hasInlineGhost && this.activeOption) {\n e.preventDefault()\n this.acceptInline()\n return true\n }\n\n // A navigated (arrow-key) highlight always commits. `autoHighlight` only\n // implicitly commits the first row when there is a query to complete — Base\n // UI does not highlight index 0 on an empty query, so a bare Enter with no\n // typed text just closes (and may submit the form).\n if ((this.navigated || (this.autoHighlight && this.hasQuery)) && this.activeOption) {\n e.preventDefault()\n this.selectOption(this.activeOption)\n return true\n }\n\n this.hide()\n return true\n }\n\n /** Whether the user has typed/committed any free text to complete against. */\n private get hasQuery(): boolean {\n return this.filterQuery.length > 0\n }\n\n /** Pointer hover never composes an inline ghost (mirrors keyboard-vs-pointer). */\n protected override onOptionHover(): void {\n this.keyboardActive = false\n }\n\n /**\n * Never silently commit an un-accepted inline suggestion: on blur drop the\n * ghost suffix and restore the input to the typed query (the actual value).\n */\n protected override onBlur(): void {\n if (this.inline && this.hasInlineGhost) {\n this.revertInline()\n }\n this.keyboardActive = false\n }\n\n /**\n * Lit binds the inner input's `.value` to the ghost-composed string, but the\n * suffix selection has to be applied imperatively after the DOM update so\n * continued typing overwrites it.\n */\n protected override afterUpdate(): void {\n if (this.hasInlineGhost) {\n const input = this.inputRef.value\n if (input) {\n input.setSelectionRange(this.inlineQuery.length, input.value.length)\n }\n }\n }\n\n /**\n * Picking a suggestion is a completion aid: fill the input with its label and\n * commit that label as the free-text value. No chips, no array, no toggle.\n */\n protected override selectOption(option: ComboboxOption): void {\n if (option.disabled) {\n return\n }\n\n // Remember the label so it survives the option leaving the result set.\n this.labelCache.set(option.value, option.label)\n\n this.keyboardActive = false\n this.value = option.label\n this.search = option.label\n this.commit()\n this.hide()\n // Restore focus to the input. Focus alone never opens the listbox.\n this.focus()\n }\n\n /** Commit the currently ghost-highlighted option's full label (inline mode). */\n private acceptInline() {\n const option = this.activeOption\n if (!option) {\n return\n }\n this.selectOption(option)\n }\n\n /**\n * Drop the transient inline ghost suffix, restoring the input to the typed\n * query (the actual committed `value`). Does not change `value`.\n */\n private revertInline() {\n this.keyboardActive = false\n\n const input = this.inputRef.value\n if (input) {\n input.value = this.value\n // Collapse any lingering suffix selection to the end of the typed text.\n input.setSelectionRange(this.value.length, this.value.length)\n }\n }\n\n /** A new query supersedes any inline ghost from the previous one. */\n protected override resetActiveIndex() {\n super.resetActiveIndex()\n this.keyboardActive = false\n }\n\n /** Clear the current value, firing `clear` then `change`. */\n override clear() {\n this.value = ''\n this.search = ''\n this.keyboardActive = false\n this.dispatchEvent(new ComboboxClearEvent())\n this.commit()\n this.focus()\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-autocomplete': Autocomplete\n }\n}\n"],"names":["style","retagComboboxStyles","comboboxStyle","Autocomplete","ComboboxBase","constructor","this","value","inline","clearable","openOnInputClick","localize","LocalizeController","keyboardActive","connectedCallback","super","injectLightDomStyles","filterQuery","inlineQuery","hasInlineGhost","open","option","activeOption","query","label","length","toLocaleLowerCase","startsWith","displayValue","slice","hasValue","formValue","undefined","showClear","disabled","readonly","showToggle","ariaAutocomplete","externalFiltering","isSelected","ariaMultiselectable","showCheck","rendersCheck","optionAriaSelected","nothing","onHide","onInput","input","commit","onKeydown","e","key","inputRef","selectionEnd","preventDefault","acceptInline","onMove","handleEnter","navigated","autoHighlight","hasQuery","selectOption","hide","onOptionHover","onBlur","revertInline","afterUpdate","setSelectionRange","labelCache","set","search","focus","resetActiveIndex","clear","dispatchEvent","ComboboxClearEvent","__decorate","property","attribute","prototype","reflect","type","Boolean","state","customElement"],"mappings":"y9EAkBA,MAAMA,EAAQC,EAAoBC,EAAe,qBAyClC,IAAMC,EAAN,cAA2BC,EAA3B,WAAAC,uBAKmBC,KAAKC,MAAG,GASID,KAAME,QAAG,EAMTF,KAASG,WAAG,EAQ+BH,KAAgBI,kBAAG,EAEvFJ,KAAAK,SAAW,IAAIC,EAAwCN,MASzDA,KAAcO,gBAAG,CA8TnC,CA5TC,iBAAAC,GACEC,MAAMD,oBACNE,EAAqBV,KAAMN,EAC5B,CAOD,eAAuBiB,GACrB,OAAOX,KAAKC,KACb,CAGD,eAAYW,GACV,OAAOZ,KAAKC,KACb,CAOD,kBAAYY,GACV,IAAKb,KAAKE,SAAWF,KAAKO,iBAAmBP,KAAKc,KAChD,OAAO,EAGT,MAAMC,EAASf,KAAKgB,aACdC,EAAQjB,KAAKY,YAEnB,IAAKG,IAAWE,EACd,OAAO,EAGT,MAAMC,EAAQH,EAAOG,MACrB,OACEA,EAAMC,OAASF,EAAME,QAClBD,EAAME,oBAAoBC,WAAWJ,EAAMG,oBAEjD,CAGD,gBAAuBE,GAGrB,GAAItB,KAAKa,eAAgB,CACvB,MAAMI,EAAQjB,KAAKY,aACbM,MAAEA,GAAUlB,KAAKgB,aACvB,OAAOC,EAAQC,EAAMK,MAAMN,EAAME,OAClC,CAGD,OAAOnB,KAAKC,KACb,CAGD,YAAuBuB,GACrB,OAAOxB,KAAKC,MAAMkB,OAAS,CAC5B,CAED,aAAuBM,GACrB,OAAOzB,KAAKC,YAASyB,CACtB,CAED,aAAuBC,GACrB,OAAO3B,KAAKG,WAAaH,KAAKwB,WAAaxB,KAAK4B,WAAa5B,KAAK6B,QACnE,CAOD,cAAuBC,GACrB,OAAO,CACR,CAUD,oBAAuBC,GACrB,OAAI/B,KAAKE,OACAF,KAAKgC,kBAAoB,SAAW,OAEtChC,KAAKgC,kBAAoB,OAAS,MAC1C,CAOkB,UAAAC,GACjB,OAAO,CACR,CAOD,uBAAuBC,GAEtB,CAGkB,SAAAC,GACjB,OAAO,CACR,CAGD,gBAAuBC,GACrB,OAAO,CACR,CAGkB,kBAAAC,GACjB,OAAOC,CACR,CAGkB,MAAAC,GAGlB,CAGkB,OAAAC,CAAQC,GAEzBzC,KAAKO,gBAAiB,EAGtBP,KAAKC,MAAQwC,EAAMxC,MACnBD,KAAK0C,QACN,CASkB,SAAAC,CAAUC,GAC3B,OAAQA,EAAEC,KACR,IAAK,YACL,IAAK,UACL,IAAK,OAGC7C,KAAKc,OACPd,KAAKO,gBAAiB,GAExB,MACF,IAAK,aACL,IAAK,MAOH,GAAIP,KAAKE,QAAUF,KAAKc,MAAQd,KAAKa,eAAgB,CACnD,MAAM4B,EAAQzC,KAAK8C,SAAS7C,MAE5B,IADewC,GAASA,EAAMM,eAAiBN,EAAMxC,MAAMkB,OAChD,CACTyB,EAAEI,iBACFhD,KAAKiD,eACL,KACD,CACF,CACa,QAAVL,EAAEC,KAAiB7C,KAAKc,OAC1Bd,KAAKO,gBAAiB,GAExB,MACF,IAAK,YACL,IAAK,SAEHP,KAAKO,gBAAiB,EAG3B,CAEkB,MAAA2C,GACjBlD,KAAKO,gBAAiB,CACvB,CAQkB,WAAA4C,CAAYP,GAC7B,OAAI5C,KAAKE,QAAUF,KAAKa,gBAAkBb,KAAKgB,cAC7C4B,EAAEI,iBACFhD,KAAKiD,gBACE,IAOJjD,KAAKoD,WAAcpD,KAAKqD,eAAiBrD,KAAKsD,WAActD,KAAKgB,cACpE4B,EAAEI,iBACFhD,KAAKuD,aAAavD,KAAKgB,eAChB,IAGThB,KAAKwD,QACE,EACR,CAGD,YAAYF,GACV,OAAOtD,KAAKW,YAAYQ,OAAS,CAClC,CAGkB,aAAAsC,GACjBzD,KAAKO,gBAAiB,CACvB,CAMkB,MAAAmD,GACb1D,KAAKE,QAAUF,KAAKa,gBACtBb,KAAK2D,eAEP3D,KAAKO,gBAAiB,CACvB,CAOkB,WAAAqD,GACjB,GAAI5D,KAAKa,eAAgB,CACvB,MAAM4B,EAAQzC,KAAK8C,SAAS7C,MACxBwC,GACFA,EAAMoB,kBAAkB7D,KAAKY,YAAYO,OAAQsB,EAAMxC,MAAMkB,OAEhE,CACF,CAMkB,YAAAoC,CAAaxC,GAC1BA,EAAOa,WAKX5B,KAAK8D,WAAWC,IAAIhD,EAAOd,MAAOc,EAAOG,OAEzClB,KAAKO,gBAAiB,EACtBP,KAAKC,MAAQc,EAAOG,MACpBlB,KAAKgE,OAASjD,EAAOG,MACrBlB,KAAK0C,SACL1C,KAAKwD,OAELxD,KAAKiE,QACN,CAGO,YAAAhB,GACN,MAAMlC,EAASf,KAAKgB,aACfD,GAGLf,KAAKuD,aAAaxC,EACnB,CAMO,YAAA4C,GACN3D,KAAKO,gBAAiB,EAEtB,MAAMkC,EAAQzC,KAAK8C,SAAS7C,MACxBwC,IACFA,EAAMxC,MAAQD,KAAKC,MAEnBwC,EAAMoB,kBAAkB7D,KAAKC,MAAMkB,OAAQnB,KAAKC,MAAMkB,QAEzD,CAGkB,gBAAA+C,GACjBzD,MAAMyD,mBACNlE,KAAKO,gBAAiB,CACvB,CAGQ,KAAA4D,GACPnE,KAAKC,MAAQ,GACbD,KAAKgE,OAAS,GACdhE,KAAKO,gBAAiB,EACtBP,KAAKoE,cAAc,IAAIC,GACvBrE,KAAK0C,SACL1C,KAAKiE,OACN,GA/V+BK,EAAA,CAA/BC,EAAS,CAAEC,WAAW,KAAmB3E,EAAA4E,UAAA,aAAA,GASEH,EAAA,CAA3CC,EAAS,CAAEG,SAAS,EAAMC,KAAMC,WAAyB/E,EAAA4E,UAAA,cAAA,GAMdH,EAAA,CAA3CC,EAAS,CAAEG,SAAS,EAAMC,KAAMC,WAA4B/E,EAAA4E,UAAA,iBAAA,GAQ0BH,EAAA,CAAtFC,EAAS,CAAEG,SAAS,EAAMC,KAAMC,QAASJ,UAAW,yBAA0D3E,EAAA4E,UAAA,wBAAA,GAW9FH,EAAA,CAAhBO,KAAsChF,EAAA4E,UAAA,sBAAA,GAvCpB5E,EAAYyE,EAAA,CADhCQ,EAAc,sBACMjF,SAAAA"}
|
|
1
|
+
{"version":3,"file":"Autocomplete.js","sources":["../src/autocomplete/Autocomplete.ts"],"sourcesContent":["import type { ComboboxOption } from '../combobox/ComboboxBase.js'\nimport { nothing } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\n\nimport comboboxStyle from '../combobox/Combobox.css'\nimport ComboboxBase, { retagComboboxStyles } from '../combobox/ComboboxBase.js'\nimport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\nimport { injectLightDomStyles } from '../common/light-dom-styles.js'\nimport { LocalizeController } from '../localization/LocalizeController.js'\n\nexport type { ComboboxOption } from '../combobox/ComboboxBase.js'\nexport { ComboboxClearEvent } from '../combobox/ComboboxEvents.js'\n\n/**\n * The shared combobox stylesheet is authored once against the `nord-combobox`\n * tag (see `Combobox.css`); re-prefix it for `nord-autocomplete` so this sibling\n * light-DOM control reuses the exact same `.n-*` rules without duplicating them.\n */\nconst style = retagComboboxStyles(comboboxStyle, 'nord-autocomplete')\n\n/**\n * Autocomplete is a free-text input with suggestions: the text the user types\n * is itself the committed `value` (a string), and the options are completion\n * aids. Picking a suggestion fills the input with the option's `label` and commits\n * that label. Use it when the answer is open-ended text but you can offer\n * helpful completions (a city, a tag, a username); reach for\n * [Combobox](/components/combobox/) instead when the value must be one of a\n * fixed set of options.\n *\n * Autocomplete is a control only — it has no built-in label, hint or error.\n * Compose it inside a [Field](/components/field/) to give it a label, hint and\n * error message, the same way you would a native input.\n *\n * Base UI parity: Autocomplete is the Combobox primitive with\n * `selectionMode=\"none\"` + `fillInputOnItemPress`.\n *\n * @status draft\n * @category form\n * @lightDom true\n *\n * @slot start - Optional slot used to place an icon or prefix at the start of the control.\n * @slot end - Optional slot used to place an icon or suffix at the end of the control.\n *\n * @fires {NordEvent} change - Dispatched when the committed value changes via user interaction.\n * @fires {NordEvent} input - Dispatched as the user types into the input.\n * @fires {NordEvent} open - Dispatched when the listbox is opened.\n * @fires {NordEvent} close - Dispatched when the listbox is closed.\n * @fires {ComboboxClearEvent} clear - Dispatched when the value is cleared via the clear button or `clear()`.\n *\n * @localization clearLabel - Accessible label for the clear button.\n * @localization noResults - Message shown when no options match the query.\n *\n * @cssprop [--n-combobox-list-inline-size] - Controls the inline size, or width, of the dropdown list. Defaults to the width of the control.\n * @cssprop [--n-combobox-block-size] - Controls the block size, or height, of the control. The medium default is `var(--n-space-xl)`; the s and l sizes scale from it.\n *\n * @attr {boolean} data-invalid - Present while the control is invalid (mirrors the `invalid` property). Style hook for the whole control.\n * @attr {boolean} data-focused - Present while the control has focus. Style hook for focus-within treatments.\n */\n@customElement('nord-autocomplete')\nexport default class Autocomplete extends ComboboxBase {\n /**\n * The value of the autocomplete. This is the free text the user has typed (or\n * the `label` of a picked suggestion) — always a single string.\n */\n @property({ attribute: false }) value = ''\n\n /**\n * Enables inline ghost-text completion. While an option is highlighted with\n * the keyboard, the input shows the typed query followed by the remainder of\n * the highlighted option's label, with that remainder text-selected so\n * continued typing overwrites it. Accepting (Enter, ArrowRight/End at the end\n * of the text, or an option click) commits the full label.\n */\n @property({ reflect: true, type: Boolean }) inline = false\n\n /**\n * Shows a clear (✕) button while there is text. Pressing it resets the value\n * to `''`, then fires `clear` and `change`.\n */\n @property({ reflect: true, type: Boolean }) clearable = false\n\n /**\n * Whether clicking the input opens the suggestion list. Defaults to `false`\n * for autocomplete — matching Base UI's `openOnInputClick={false}`, the list\n * appears as the user types rather than on click. Set to `true` to also open\n * on click.\n */\n @property({ reflect: true, type: Boolean, attribute: 'open-on-input-click' }) override openOnInputClick = false\n\n protected override localize = new LocalizeController<'nord-autocomplete'>(this)\n\n /**\n * Whether the active option last changed via the keyboard (arrows/Home/End)\n * rather than pointer hover. Inline ghost completion only fills on keyboard\n * navigation, mirroring Base UI's `keyboardActiveRef`. Reactive so toggling it\n * (even when `activeIndex` is unchanged, e.g. a single filtered option)\n * re-renders the ghost.\n */\n @state() private keyboardActive = false\n\n connectedCallback() {\n super.connectedCallback()\n injectLightDomStyles(this, style)\n }\n\n /**\n * The committed free-text `value` *is* the query, so a programmatically-set\n * `value` (e.g. `el.value = 'fea'`) filters the open list without the user\n * having typed, matching the text the input already displays.\n */\n protected override get filterQuery(): string {\n return this.value\n }\n\n /** The typed query an inline ghost is composed against (the committed value). */\n private get inlineQuery(): string {\n return this.value\n }\n\n /**\n * Whether an inline ghost suffix is currently showing: only in `inline` mode,\n * when the active option (reached via the keyboard) has a label that starts\n * with the typed query and extends it.\n */\n private get hasInlineGhost(): boolean {\n if (!this.inline || !this.keyboardActive || !this.open) {\n return false\n }\n\n const option = this.activeOption\n const query = this.inlineQuery\n\n if (!option || !query) {\n return false\n }\n\n const label = option.label\n return (\n label.length > query.length\n && label.toLocaleLowerCase().startsWith(query.toLocaleLowerCase())\n )\n }\n\n /** The text shown in the input. */\n protected override get displayValue(): string {\n // While an inline ghost is showing, present the typed query plus the\n // highlighted option's remaining label (with the original casing).\n if (this.hasInlineGhost) {\n const query = this.inlineQuery\n const { label } = this.activeOption!\n return query + label.slice(query.length)\n }\n\n // Otherwise the input always reflects the committed free-text value.\n return this.value\n }\n\n /** Free text is always present once non-empty (drives the clear affordance). */\n protected override get hasValue(): boolean {\n return this.value.length > 0\n }\n\n protected override get formValue(): string | undefined {\n return this.value || undefined\n }\n\n protected override get showClear(): boolean {\n return this.clearable && this.hasValue && !this.disabled && !this.readonly\n }\n\n /**\n * Autocomplete is a search input with suggestions, not a picker — like Base\n * UI's autocomplete it renders no dropdown chevron (its `AriaCombobox` has no\n * `Trigger`). The list appears as you type, not on a toggle click.\n */\n protected override get showToggle(): boolean {\n return false\n }\n\n /**\n * The `aria-autocomplete` value for the inner input, mapping the `inline` +\n * `external-filtering` axes onto the four ARIA values:\n * - inline + internal filter → `both`\n * - inline + external-filtering → `inline` (static list + ghost)\n * - external-filtering (no inline) → `none` (static list, no ghost)\n * - otherwise → `list` (internal filter, no ghost)\n */\n protected override get ariaAutocomplete(): 'list' | 'both' | 'inline' | 'none' {\n if (this.inline) {\n return this.externalFiltering ? 'inline' : 'both'\n }\n return this.externalFiltering ? 'none' : 'list'\n }\n\n /**\n * Autocomplete renders no selection state — `aria-selected` is omitted\n * (`optionAriaSelected` → nothing) and the check is never shown\n * (`showCheck` → false) — so nothing is ever \"selected\".\n */\n protected override isSelected(): boolean {\n return false\n }\n\n /**\n * Autocomplete has no selection model, so the listbox does not advertise\n * `aria-multiselectable` at all (the base's `'false'` would imply a single-\n * select semantic it doesn't have).\n */\n protected override get ariaMultiselectable(): undefined {\n return undefined\n }\n\n /** Autocomplete never shows the selected-check icon (it is not a select). */\n protected override showCheck(): boolean {\n return false\n }\n\n /** No selection model, so no leading check gutter (avoids indenting suggestions). */\n protected override get rendersCheck(): boolean {\n return false\n }\n\n /** Autocomplete options are completion aids, not selectable values — omit aria-selected. */\n protected override optionAriaSelected(): typeof nothing {\n return nothing\n }\n\n /** Autocomplete keeps its committed free-text query when the listbox closes. */\n protected override onHide() {\n // Intentionally empty: the free-text value is the query, so it is not\n // discarded the way select mode's transient filter is.\n }\n\n /** Typing commits the typed text as the live value. */\n protected override onInput(input: HTMLInputElement): void {\n // Typing breaks any inline ghost suffix the keyboard had composed.\n this.keyboardActive = false\n // The typed text *is* the value, so it tracks the input live. `input` has\n // already fired (in the base); `commit()` emits the live `change`.\n this.value = input.value\n this.commit()\n }\n\n /**\n * Runs before the base's own key switch. Marks keyboard navigation (so the\n * inline ghost may compose) and owns the ArrowRight/End inline-accept. After\n * an accept the listbox is closed, so the base's own `End` branch (gated on\n * `this.open`) is skipped; the base still performs the moves for the arrows\n * and Home.\n */\n protected override onKeydown(e: KeyboardEvent): void {\n switch (e.key) {\n case 'ArrowDown':\n case 'ArrowUp':\n case 'Home':\n // The base performs the actual move (and opens a closed listbox); mark\n // it as keyboard navigation so the ghost composes after it.\n if (this.open) {\n this.keyboardActive = true\n }\n break\n case 'ArrowRight':\n case 'End':\n // Accept an inline ghost suffix: End always sits at the text end, and\n // ArrowRight accepts when the selection already reaches the end. When the\n // ghost suffix is selected, `selectionEnd` is the text length, so both\n // cases collapse to \"is the selection at the end?\". A bare `End` (no\n // ghost) falls through to the base's jump-to-last navigation, which we\n // mark as keyboard nav so the ghost composes for the newly-active row.\n if (this.inline && this.open && this.hasInlineGhost) {\n const input = this.inputRef.value\n const atEnd = !input || input.selectionEnd === input.value.length\n if (atEnd) {\n e.preventDefault()\n this.acceptInline()\n break\n }\n }\n if (e.key === 'End' && this.open) {\n this.keyboardActive = true\n }\n break\n case 'Backspace':\n case 'Delete':\n // A deletion breaks any inline ghost suffix.\n this.keyboardActive = false\n break\n }\n }\n\n protected override onMove(): void {\n this.keyboardActive = true\n }\n\n /**\n * Fully owns Enter when the listbox is open. An inline ghost or any\n * highlighted option fills+commits its label; otherwise the typed text is\n * already committed, so Enter just closes and does NOT preventDefault (the\n * owning form may submit). Returns `true` so the base does no further handling.\n */\n protected override handleEnter(e: KeyboardEvent): boolean {\n if (this.inline && this.hasInlineGhost && this.activeOption) {\n e.preventDefault()\n this.acceptInline()\n return true\n }\n\n // A navigated (arrow-key) highlight always commits. `autoHighlight` only\n // implicitly commits the first row when there is a query to complete — Base\n // UI does not highlight index 0 on an empty query, so a bare Enter with no\n // typed text just closes (and may submit the form).\n if ((this.navigated || (this.autoHighlight && this.hasQuery)) && this.activeOption) {\n e.preventDefault()\n this.selectOption(this.activeOption)\n return true\n }\n\n this.hide()\n return true\n }\n\n /** Whether the user has typed/committed any free text to complete against. */\n private get hasQuery(): boolean {\n return this.filterQuery.length > 0\n }\n\n /** Pointer hover never composes an inline ghost (mirrors keyboard-vs-pointer). */\n protected override onOptionHover(): void {\n this.keyboardActive = false\n }\n\n /**\n * Never silently commit an un-accepted inline suggestion: on blur drop the\n * ghost suffix and restore the input to the typed query (the actual value).\n */\n protected override onBlur(): void {\n if (this.inline && this.hasInlineGhost) {\n this.revertInline()\n }\n this.keyboardActive = false\n }\n\n /**\n * Lit binds the inner input's `.value` to the ghost-composed string, but the\n * suffix selection has to be applied imperatively after the DOM update so\n * continued typing overwrites it.\n */\n protected override afterUpdate(): void {\n if (this.hasInlineGhost) {\n const input = this.inputRef.value\n if (input) {\n input.setSelectionRange(this.inlineQuery.length, input.value.length)\n }\n }\n }\n\n /**\n * Picking a suggestion is a completion aid: fill the input with its label and\n * commit that label as the free-text value. No chips, no array, no toggle.\n */\n protected override selectOption(option: ComboboxOption): void {\n if (option.disabled) {\n return\n }\n\n // Remember the label so it survives the option leaving the result set.\n this.labelCache.set(option.value, option.label)\n\n this.keyboardActive = false\n this.value = option.label\n this.search = option.label\n this.commit()\n this.hide()\n // Restore focus to the input. Focus alone never opens the listbox.\n this.focus()\n }\n\n /** Commit the currently ghost-highlighted option's full label (inline mode). */\n private acceptInline() {\n const option = this.activeOption\n if (!option) {\n return\n }\n this.selectOption(option)\n }\n\n /**\n * Drop the transient inline ghost suffix, restoring the input to the typed\n * query (the actual committed `value`). Does not change `value`.\n */\n private revertInline() {\n this.keyboardActive = false\n\n const input = this.inputRef.value\n if (input) {\n input.value = this.value\n // Collapse any lingering suffix selection to the end of the typed text.\n input.setSelectionRange(this.value.length, this.value.length)\n }\n }\n\n /** A new query supersedes any inline ghost from the previous one. */\n protected override resetActiveIndex() {\n super.resetActiveIndex()\n this.keyboardActive = false\n }\n\n /** Clear the current value, firing `clear` then `change`. */\n override clear() {\n this.value = ''\n this.search = ''\n this.keyboardActive = false\n this.dispatchEvent(new ComboboxClearEvent())\n this.commit()\n this.focus()\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-autocomplete': Autocomplete\n }\n}\n"],"names":["style","retagComboboxStyles","comboboxStyle","Autocomplete","ComboboxBase","constructor","this","value","inline","clearable","openOnInputClick","localize","LocalizeController","keyboardActive","connectedCallback","super","injectLightDomStyles","filterQuery","inlineQuery","hasInlineGhost","open","option","activeOption","query","label","length","toLocaleLowerCase","startsWith","displayValue","slice","hasValue","formValue","undefined","showClear","disabled","readonly","showToggle","ariaAutocomplete","externalFiltering","isSelected","ariaMultiselectable","showCheck","rendersCheck","optionAriaSelected","nothing","onHide","onInput","input","commit","onKeydown","e","key","inputRef","selectionEnd","preventDefault","acceptInline","onMove","handleEnter","navigated","autoHighlight","hasQuery","selectOption","hide","onOptionHover","onBlur","revertInline","afterUpdate","setSelectionRange","labelCache","set","search","focus","resetActiveIndex","clear","dispatchEvent","ComboboxClearEvent","__decorate","property","attribute","prototype","reflect","type","Boolean","state","customElement"],"mappings":"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"}
|
package/lib/Button.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{css as o,html as n,nothing as r,LitElement as e}from"lit";import{property as a,customElement as i}from"lit/decorators.js";import{createRef as s,ref as l}from"lit/directives/ref.js";import{E as b}from"./EventController-BBOmvfLa.js";import{L as d}from"./LightDomController-DIwtVelV.js";import{S as c}from"./SlotController-Z6eG7LSZ.js";import{c as u}from"./cond-CI1KbneT.js";import{F as v}from"./FocusableMixin-BlQLNPdJ.js";import{I as h}from"./InputMixin-LetXsCyv.js";import{s as p}from"./Component-BJ5O5n8k.js";import"./Spinner.js";import"lit/directives/if-defined.js";const g=o`:host{--_n-button-border-radius:var(--n-button-border-radius,var(--n-border-radius-s));--_n-button-gap:var(--n-button-gap,var(--n-space-s));--_n-button-gradient:var(
|
|
2
2
|
--n-button-gradient,linear-gradient(to bottom,#0000 50%,rgba(0,0,0,.013) 100%)
|
|
3
|
-
);--_n-button-background-color:var(--n-button-background-color,var(--n-color-button));--_n-button-border-color:var(--n-button-border-color,var(--n-color-border-strong));--_n-button-text-align:var(--n-button-text-align,center);--_n-button-box-shadow:var(--n-button-box-shadow,var(--n-box-shadow));--_n-button-color:var(--n-button-color,var(--n-color-text));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-m)/1.2));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-m));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight));--_n-button-min-block-size:var(--n-button-min-block-size,var(--n-space-xl));--_n-button-toggle-icon-color:var(--n-button-toggle-icon-color,var(--n-color-icon));--_n-button-overflow:var(--n-button-overflow,hidden);--_n-button-user-select:var(--n-button-user-select,none);--_n-button-opacity:1;--_n-button-border-style:solid;--_n-button-inline-size:fit-content;--_n-button-padding-block:calc(var(--n-space-s)/1.6);--_n-button-icon-size:var(--n-size-icon-s);display:inline-block}.n-button{align-items:center;appearance:none;background:var(--_n-button-background-color);border:1px var(--_n-button-border-style) var(--_n-button-border-color);border-radius:var(--_n-button-border-radius);box-shadow:var(--_n-button-box-shadow);color:var(--_n-button-color);cursor:pointer;display:flex;font-family:var(--n-font-family);font-feature-settings:var(--n-font-features);font-size:var(--_n-button-font-size);font-weight:var(--_n-button-font-weight);gap:var(--_n-button-gap);inline-size:var(--_n-button-inline-size);justify-content:var(--n-button-justify-content,flex-start);line-height:var(--n-line-height-form);margin:0;min-block-size:var(--_n-button-min-block-size);opacity:var(--_n-button-opacity);padding:var(--_n-button-padding-block)
|
|
3
|
+
);--_n-button-background-color:var(--n-button-background-color,var(--n-color-button));--_n-button-border-color:var(--n-button-border-color,var(--n-color-border-strong));--_n-button-text-align:var(--n-button-text-align,center);--_n-button-box-shadow:var(--n-button-box-shadow,var(--n-box-shadow));--_n-button-color:var(--n-button-color,var(--n-color-text));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-m)/1.2));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-m));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight));--_n-button-min-block-size:var(--n-button-min-block-size,var(--n-space-xl));--_n-button-toggle-icon-color:var(--n-button-toggle-icon-color,var(--n-color-icon));--_n-button-overflow:var(--n-button-overflow,hidden);--_n-button-user-select:var(--n-button-user-select,none);--_n-button-opacity:1;--_n-button-border-style:solid;--_n-button-inline-size:fit-content;--_n-button-padding-block:calc(var(--n-space-s)/1.6);--_n-button-icon-size:var(--n-size-icon-s);display:inline-block}.n-button{align-items:center;appearance:none;background:var(--_n-button-background-color);border:1px var(--_n-button-border-style) var(--_n-button-border-color);border-radius:var(--_n-button-border-radius);box-shadow:var(--_n-button-box-shadow);color:var(--_n-button-color);cursor:pointer;display:flex;font-family:var(--n-font-family);font-feature-settings:var(--n-font-features);font-size:var(--_n-button-font-size);font-weight:var(--_n-button-font-weight);gap:var(--_n-button-gap);inline-size:var(--_n-button-inline-size);justify-content:var(--n-button-justify-content,flex-start);line-height:var(--n-line-height-form);margin:0;min-block-size:var(--_n-button-min-block-size);opacity:var(--_n-button-opacity);padding-block:var(--_n-button-padding-block);padding-inline:var(--_n-button-padding-inline);position:relative;text-align:var(--_n-button-text-align);text-decoration:none;transition:var(--n-transition-quickly);transition-property:background-color,opacity,color,box-shadow,border-color;-webkit-user-select:var(--_n-button-user-select);user-select:var(--_n-button-user-select)}.n-button:after{background:#0000;background-image:var(--_n-button-gradient);background-repeat:repeat-x;block-size:100%;border-radius:var(--_n-button-border-radius);content:"";inline-size:100%;inset-block-start:0;inset-inline:0;position:absolute}:host([expand]){--_n-button-inline-size:100%;display:block}.n-content{display:var(--n-button-content-display,block);flex:1;overflow:var(--_n-button-overflow);position:relative;text-overflow:ellipsis;white-space:nowrap}.n-button:hover{--_n-button-border-color:var(--n-button-border-color,var(--n-color-border-hover));--_n-button-background-color:var(--n-button-background-color,var(--n-color-button-hover))}.n-button:focus{outline:none}.n-button:focus-visible{--_n-button-border-color:var(--n-button-border-color,var(--n-color-accent));--_n-button-box-shadow:var(--n-button-box-shadow,0 0 0 1px var(--n-color-accent),var(--n-box-shadow));--_n-button-border-style:solid}.n-button:active{opacity:.8;transform:translateY(1px);transition:none}:host([variant=primary]){--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-on-accent));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight-active))}:host([variant=primary]),:host([variant=primary]) .n-button:hover{--_n-button-border-color:var(--n-button-border-color,#0000);--_n-button-background-color:var(--n-button-background-color,var(--n-color-accent))}:host([variant=primary]) .n-button:hover{filter:brightness(93%)}:host([variant=primary]) .n-button:focus{--_n-button-box-shadow:var(
|
|
4
4
|
--n-button-box-shadow,0 0 0 1px var(--n-color-surface),0 0 0 3px var(--_n-button-border-color)
|
|
5
5
|
)}:host([variant=dashed]){--_n-button-border-color:var(--n-button-border-color,var(--n-color-border-hover));--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-weaker));--_n-button-border-style:dashed}:host([variant=dashed]) .n-button:hover{--_n-button-color:var(--n-button-color,var(--n-color-text))}:host([variant=dashed]) .n-button:after{display:none}:host([variant=plain]){--_n-button-box-shadow:var(--n-button-box-shadow,none)}:host([variant=plain]),:host([variant=plain]) .n-button:hover{--_n-button-border-color:var(--n-button-border-color,#0000)}:host([variant=plain]) .n-button:after{display:none}:host([variant=danger]){--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-on-accent));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight-active))}:host([variant=danger]),:host([variant=danger]) .n-button:hover{--_n-button-border-color:var(--n-button-border-color,#0000);--_n-button-background-color:var(--n-button-background-color,var(--n-color-status-danger))}:host([variant=danger]) .n-button:hover{filter:brightness(93%)}:host([variant=danger]) .n-button:focus{--_n-button-border-color:var(--n-button-border-color,var(--n-color-status-danger));--_n-button-box-shadow:var(
|
|
6
6
|
--n-button-box-shadow,0 0 0 1px var(--n-color-surface),0 0 0 3px var(--n-color-status-danger)
|
|
7
|
-
)}:host([disabled]){--_n-button-border-color:var(--n-button-border-color,var(--_n-button-background-color));--_n-button-background-color:var(--n-button-background-color,var(--n-color-border));--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-weak));--_n-button-opacity:0.5;pointer-events:none}:host([disabled]) .n-button:after{display:none}.n-button-spinner{inset-block-start:50%;inset-inline-start:50%;position:absolute;transform:translateX(-50%) translateY(-50%)}:host([loading]:not([href])){pointer-events:none}:host([loading]:not([href])) .n-content,:host([loading]:not([href])) ::slotted([slot=end]),:host([loading]:not([href])) ::slotted([slot=start]){opacity:0}:host([size=s]){--_n-button-gap:var(--n-button-gap,var(--n-space-xs));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-s) + 1px));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-s));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-l) + var(--n-space-xs)));--_n-button-padding-block:calc(var(--n-space-xs) - 1px);--_n-button-icon-size:var(--n-size-icon-xs)}:host([size=l]){--_n-button-border-radius:var(--n-button-border-radius,var(--n-border-radius));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-l)/1.3));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-l));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight-active));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-xxl) - var(--n-space-l)));--_n-button-icon-size:var(--n-size-icon-m)}:host([square]){--_n-button-inline-size:var(--_n-button-min-block-size);--_n-button-padding-block:0;--_n-button-padding-inline:0}:host([square]) .n-content{align-items:center;display:flex;justify-content:center}::slotted(*){color:inherit;pointer-events:none}::slotted(svg){color:var(--n-color-icon)}::slotted(button[slot=proxy]){display:none}:host(:not([variant=primary],[variant=danger])) ::slotted(nord-icon){color:var(--n-color-icon)}:host(:not([variant=primary],[variant=danger])) .n-toggle-icon{color:var(--_n-button-toggle-icon-color)}:host(:not([square])) slot:not([name])::slotted(nord-icon){transform:translateY(1px)}::slotted(nord-icon:not([size])),nord-icon{--_n-icon-size:var(--_n-button-icon-size)}`;let f=class extends(h(v(e))){constructor(){super(...arguments),this.defaultSlot=new c(this),this.buttonRef=s(),this.events=new b(this),this.lightDom=new d(this,{render:()=>this.renderLightDom()}),this.variant="default",this.type="submit",this.size="m",this.download=!1,this.target="_self",this.expand=!1,this.square=!1,this.loading=!1,this.hideDropdownIcon=!1,this.handleOuterClick=t=>{t.composedPath().some((t=>t===this.focusableRef.value||t===this.buttonRef.value))||t.stopPropagation()}}connectedCallback(){super.connectedCallback(),this.events.listen(this,"click",this.handleOuterClick,!0)}render(){var t;const o="toggle"===(null===(t=this.assignedSlot)||void 0===t?void 0:t.name),e=this.defaultSlot.assigned.some((t=>"nord-icon"===t.localName)),a=o&&!this.hideDropdownIcon&&!e&&!this.href,i=n`<slot name="start"></slot><div class="n-content"><slot></slot></div><nord-spinner class="n-button-spinner" color="currentColor" ?hidden="${!this.loading||Boolean(this.href)}"></nord-spinner><slot name="end">${a?n`<nord-icon name="interface-dropdown-small" class="n-toggle-icon"></nord-icon>`:r}</slot>`;return this.href?this.renderLink(i):this.renderButton(i)}renderLink(t){return n`<a ${l(this.focusableRef)} class="n-button" target="${this.target}" ?download="${this.download}" href="${u(this.disabled,r,this.href)}" tabindex="${u(this.disabled,"-1")}" aria-disabled="${u(this.disabled,"true")}" role="${u(this.disabled,"link")}">${t}</a>`}renderButton(t){return n`<slot name="proxy" @slotchange="${this.handleProxyChange}"></slot><button ${l(this.focusableRef)} class="n-button" ?disabled="${this.disabled}" name="${u(this.name)}" value="${u(this.value)}" @click="${this.handleClick}" aria-disabled="${u(this.loading,"true")}" aria-expanded="${u(this.accessibleExpanded)}" aria-haspopup="${u(this.accessibleHasPopup)}">${t}</button>`}renderLightDom(){return this.href||!this.form?r:n`<button ${l(this.buttonRef)} slot="proxy" name="${u(this.name)}" value="${u(this.value)}" ?disabled="${this.disabled}" form="${u(this._formId)}" type="${this.type}"></button>`}handleClick(t){this.buttonRef.value&&(t.stopPropagation(),this.buttonRef.value.click())}handleProxyChange(t){const o=t.target,n=this.buttonRef.value;n&&n.assignedSlot!==o&&this.appendChild(n)}};f.shadowRootOptions={...e.shadowRootOptions,delegatesFocus:!0},f.styles=[p,g],t([a({reflect:!0})],f.prototype,"variant",void 0),t([a({reflect:!0})],f.prototype,"type",void 0),t([a({reflect:!0})],f.prototype,"size",void 0),t([a({attribute:"aria-expanded"})],f.prototype,"accessibleExpanded",void 0),t([a({attribute:"aria-haspopup"})],f.prototype,"accessibleHasPopup",void 0),t([a({reflect:!0})],f.prototype,"href",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"download",void 0),t([a()],f.prototype,"target",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"expand",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"square",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"loading",void 0),t([a({reflect:!0,type:Boolean,attribute:"hide-dropdown-icon"})],f.prototype,"hideDropdownIcon",void 0),f=t([i("nord-button")],f);var _=f;export{_ as default};
|
|
7
|
+
)}:host([disabled]){--_n-button-border-color:var(--n-button-border-color,var(--_n-button-background-color));--_n-button-background-color:var(--n-button-background-color,var(--n-color-border));--_n-button-box-shadow:var(--n-button-box-shadow,none);--_n-button-color:var(--n-button-color,var(--n-color-text-weak));--_n-button-opacity:0.5;pointer-events:none}:host([disabled]) .n-button:after{display:none}.n-button-spinner{inset-block-start:50%;inset-inline-start:50%;position:absolute;transform:translateX(-50%) translateY(-50%)}:host([loading]:not([href])){pointer-events:none}:host([loading]:not([href])) .n-content,:host([loading]:not([href])) ::slotted([slot=end]),:host([loading]:not([href])) ::slotted([slot=start]){opacity:0}:host([size=s]){--_n-button-gap:var(--n-button-gap,var(--n-space-xs));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-s) + 1px));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-s));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-l) + var(--n-space-xs)));--_n-button-padding-block:calc(var(--n-space-xs) - 1px);--_n-button-icon-size:var(--n-size-icon-xs)}:host([size=l]){--_n-button-border-radius:var(--n-button-border-radius,var(--n-border-radius));--_n-button-padding-inline:var(--n-button-padding-inline,calc(var(--n-space-l)/1.3));--_n-button-font-size:var(--n-button-font-size,var(--n-font-size-l));--_n-button-font-weight:var(--n-button-font-weight,var(--n-font-weight-active));--_n-button-min-block-size:var(--n-button-min-block-size,calc(var(--n-space-xxl) - var(--n-space-l)));--_n-button-icon-size:var(--n-size-icon-m)}:host([square]){--_n-button-inline-size:var(--_n-button-min-block-size);--_n-button-padding-block:0;--_n-button-padding-inline:0}:host([square]) .n-content{align-items:center;display:flex;justify-content:center}::slotted(*){color:inherit;pointer-events:none}::slotted(svg){color:var(--n-color-icon)}::slotted(button[slot=proxy]){display:none}:host(:not([variant=primary],[variant=danger])) ::slotted(nord-icon){color:var(--n-color-icon)}:host(:not([variant=primary],[variant=danger])) .n-toggle-icon{color:var(--_n-button-toggle-icon-color)}:host(:not([square])) slot:not([name])::slotted(nord-icon){transform:translateY(1px)}::slotted(nord-icon:not([size])),nord-icon{--_n-icon-size:var(--_n-button-icon-size)}`;let f=class extends(h(v(e))){constructor(){super(...arguments),this.defaultSlot=new c(this),this.buttonRef=s(),this.events=new b(this),this.lightDom=new d(this,{render:()=>this.renderLightDom()}),this.variant="default",this.type="submit",this.size="m",this.download=!1,this.target="_self",this.expand=!1,this.square=!1,this.loading=!1,this.hideDropdownIcon=!1,this.handleOuterClick=t=>{t.composedPath().some((t=>t===this.focusableRef.value||t===this.buttonRef.value))||t.stopPropagation()}}connectedCallback(){super.connectedCallback(),this.events.listen(this,"click",this.handleOuterClick,!0)}render(){var t;const o="toggle"===(null===(t=this.assignedSlot)||void 0===t?void 0:t.name),e=this.defaultSlot.assigned.some((t=>"nord-icon"===t.localName)),a=o&&!this.hideDropdownIcon&&!e&&!this.href,i=n`<slot name="start"></slot><div class="n-content"><slot></slot></div><nord-spinner class="n-button-spinner" color="currentColor" ?hidden="${!this.loading||Boolean(this.href)}"></nord-spinner><slot name="end">${a?n`<nord-icon name="interface-dropdown-small" class="n-toggle-icon"></nord-icon>`:r}</slot>`;return this.href?this.renderLink(i):this.renderButton(i)}get isHostNonFocusable(){return"-1"===this.getAttribute("tabindex")}renderLink(t){return n`<a ${l(this.focusableRef)} class="n-button" target="${this.target}" rel="${u(this.rel)}" ?download="${this.download}" href="${u(this.disabled,r,this.href)}" tabindex="${u(this.disabled||this.isHostNonFocusable,"-1")}" aria-disabled="${u(this.disabled,"true")}" role="${u(this.disabled,"link")}">${t}</a>`}renderButton(t){return n`<slot name="proxy" @slotchange="${this.handleProxyChange}"></slot><button ${l(this.focusableRef)} class="n-button" ?disabled="${this.disabled}" name="${u(this.name)}" value="${u(this.value)}" tabindex="${u(this.isHostNonFocusable,"-1")}" @click="${this.handleClick}" aria-disabled="${u(this.loading,"true")}" aria-expanded="${u(this.accessibleExpanded)}" aria-haspopup="${u(this.accessibleHasPopup)}">${t}</button>`}renderLightDom(){return this.href||!this.form?r:n`<button ${l(this.buttonRef)} slot="proxy" name="${u(this.name)}" value="${u(this.value)}" ?disabled="${this.disabled}" form="${u(this._formId)}" type="${this.type}"></button>`}handleClick(t){this.buttonRef.value&&(t.stopPropagation(),this.buttonRef.value.click())}handleProxyChange(t){const o=t.target,n=this.buttonRef.value;n&&n.assignedSlot!==o&&this.appendChild(n)}};f.shadowRootOptions={...e.shadowRootOptions,delegatesFocus:!0},f.styles=[p,g],t([a({reflect:!0})],f.prototype,"variant",void 0),t([a({reflect:!0})],f.prototype,"type",void 0),t([a({reflect:!0})],f.prototype,"size",void 0),t([a({attribute:"aria-expanded"})],f.prototype,"accessibleExpanded",void 0),t([a({attribute:"aria-haspopup"})],f.prototype,"accessibleHasPopup",void 0),t([a({reflect:!0})],f.prototype,"href",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"download",void 0),t([a()],f.prototype,"target",void 0),t([a()],f.prototype,"rel",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"expand",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"square",void 0),t([a({reflect:!0,type:Boolean})],f.prototype,"loading",void 0),t([a({reflect:!0,type:Boolean,attribute:"hide-dropdown-icon"})],f.prototype,"hideDropdownIcon",void 0),f=t([i("nord-button")],f);var _=f;export{_ as default};
|
|
8
8
|
//# sourceMappingURL=Button.js.map
|
package/lib/Button.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","sources":["../src/button/Button.ts"],"sourcesContent":["/* eslint-disable lit-a11y/accessible-name */\nimport type { TemplateResult } from 'lit'\nimport { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { createRef, ref } from 'lit/directives/ref.js'\nimport { EventController } from '../common/controllers/EventController.js'\nimport { LightDomController } from '../common/controllers/LightDomController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\n\nimport { cond } from '../common/directives/cond.js'\n\nimport { FocusableMixin } from '../common/mixins/FocusableMixin.js'\nimport { InputMixin } from '../common/mixins/InputMixin.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Button.css'\nimport '../spinner/Spinner.js'\n\n/**\n * Buttons are used for interface actions. Primary style should be\n * used only once per section for main call-to-action, while other\n * styles can appear more frequently.\n *\n * @status ready\n * @category action\n * @slot - The button content\n * @slot start - Used to place content at the start of button text. Typically used for icons.\n * @slot end - Used to place content at the end of button text. Typically used for icons.\n *\n * @cssprop [--n-button-border-radius=var(--n-border-radius-s)] - Controls the rounded corners of the button, using [border radius tokens](/tokens/#border-radius).\n * @cssprop [--n-button-gap=var(--n-space-s)] - Controls the spacing between items within the button, using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-gradient=linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.013) 100%))] - Controls the overlayed gradient background on the button.\n * @cssprop [--n-button-background-color=var(--n-color-button)] - Controls the background color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-border-color=var(--n-color-border-strong)] - Controls the border color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-text-align=center] - Controls the text alignment for the text in the button.\n * @cssprop [--n-button-box-shadow=var(--n-box-shadow)] - Controls the surrounding shadow, using our [box shadow tokens](/tokens/#box-shadow).\n * @cssprop [--n-button-color=var(--n-color-text)] - Controls the color of the text within the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-padding-inline=calc(var(--n-space-m) / 1.2)] - Controls the inline, or left and right, padding of the button.\n * @cssprop [--n-button-font-size=var(--n-font-size-m)] - Controls the size of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-font-weight=var(--n-font-weight)] - Controls the weight of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-min-block-size=var(--n-space-xl)] - Controls the minimum block size, or height, of the button using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-toggle-icon-color=var(--n-color-icon)] - Controls the color of toggle icon that gets rendered when the button is used as a [Dropdown](/components/dropdown/) toggle.\n * @cssprop [--n-button-user-select=none] - Controls the text selection behavior of the button text.\n */\n@customElement('nord-button')\nexport default class Button extends InputMixin(FocusableMixin(LitElement)) {\n static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n\n static styles = [componentStyle, style]\n\n private defaultSlot = new SlotController(this)\n private buttonRef = createRef<HTMLButtonElement>()\n private events = new EventController(this)\n private lightDom = new LightDomController(this, {\n render: () => this.renderLightDom(),\n })\n\n /**\n * The style variant of the button.\n */\n @property({ reflect: true }) variant: 'default' | 'primary' | 'dashed' | 'plain' | 'danger' = 'default'\n\n /**\n * The type of the button.\n */\n @property({ reflect: true }) type: 'button' | 'submit' | 'reset' = 'submit'\n\n /**\n * The size of the button.\n * This affects font-size and padding.\n */\n @property({ reflect: true }) size: 's' | 'm' | 'l' = 'm'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-expanded attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-expanded' }) accessibleExpanded?: 'true' | 'false'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-haspopup attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-haspopup' }) accessibleHasPopup?:\n | 'false'\n | 'true'\n | 'menu'\n | 'listbox'\n | 'tree'\n | 'grid'\n | 'dialog'\n\n /**\n * When provided, renders the button as a link,\n * with its href attribute set to the given value.\n */\n @property({ reflect: true }) href?: string\n\n /**\n * When provided together with a href property, the button will\n * trigger a file download instead of a page visit.\n */\n @property({ reflect: true, type: Boolean }) download = false\n\n /**\n * When provided together with a href property, determines where\n * to open the linked URL. The keywords have special meanings for\n * where to load the URL: “_self” means the current browsing context,\n * “_blank” usually a new tab but users can configure browsers this to\n * open a new window instead, “_parent” means the parent browsing\n * context of the current one, but if no parent exists, behaves as\n * _self, and finally “top” means the topmost browsing context.\n */\n @property() target: '_self' | '_blank' | '_parent' | '_top' = '_self'\n\n /**\n * Controls whether the button expands to fill the width of its container.\n */\n @property({ reflect: true, type: Boolean }) expand = false\n\n /**\n * When provided, the button is rendered as a square button. Use this for icon only buttons.\n */\n @property({ reflect: true, type: Boolean }) square = false\n\n /**\n * Controls whether the button is in loading state. Please note that the spinner\n * is hidden from assistive technologies, so you need to make sure to announce\n * the loading state to e.g. screen reader users. We also recommend disabling\n * all user interactions on the button itself while in loading state.\n */\n @property({ reflect: true, type: Boolean }) loading = false\n\n /**\n * Controls whether the `interface-dropdown-small` icon from Nordicons is hidden\n * in the `end` slot when used as a dropdown toggle.\n */\n @property({ reflect: true, type: Boolean, attribute: 'hide-dropdown-icon' }) hideDropdownIcon = false\n\n connectedCallback() {\n super.connectedCallback()\n this.events.listen(this, 'click', this.handleOuterClick, true)\n }\n\n render() {\n const isDropdownToggle = this.assignedSlot?.name === 'toggle'\n const isIconButton = this.defaultSlot.assigned.some(node => node.localName === 'nord-icon')\n const shouldShowDropdownIcon = isDropdownToggle && !this.hideDropdownIcon && !isIconButton && !this.href\n\n const innards = html`\n <slot name=\"start\"></slot>\n <div class=\"n-content\">\n <slot></slot>\n </div>\n <nord-spinner\n class=\"n-button-spinner\"\n color=\"currentColor\"\n ?hidden=${!this.loading || Boolean(this.href)}\n ></nord-spinner>\n <slot name=\"end\">\n ${shouldShowDropdownIcon\n ? html`<nord-icon name=\"interface-dropdown-small\" class=\"n-toggle-icon\"></nord-icon>`\n : nothing}\n </slot>\n `\n\n return this.href ? this.renderLink(innards) : this.renderButton(innards)\n }\n\n /**\n * We jump through some hoops here to ensure the link is treated correctly when \"disabled\".\n * Links cannot be disabled natively, so we need to rely on some aria magic to get the correct semantics.\n * Along with the advice in the article below, we also set tabindex to \"-1\", so it is taken out of tab order.\n *\n * @see https://www.scottohara.me/blog/2021/05/28/disabled-links.html\n */\n private renderLink(innards: TemplateResult) {\n return html`\n <a\n ${ref(this.focusableRef)}\n class=\"n-button\"\n target=${this.target}\n ?download=${this.download}\n href=${cond(this.disabled, nothing, this.href)}\n tabindex=${cond(this.disabled, '-1')}\n aria-disabled=${cond(this.disabled, 'true')}\n role=${cond(this.disabled, 'link')}\n >${innards}\n </a>\n `\n }\n\n private renderButton(innards: TemplateResult) {\n return html`\n <slot name=\"proxy\" @slotchange=${this.handleProxyChange}></slot>\n <button\n ${ref(this.focusableRef)}\n class=\"n-button\"\n ?disabled=${this.disabled}\n name=${cond(this.name)}\n value=${cond(this.value)}\n @click=${this.handleClick}\n aria-disabled=${cond(this.loading, 'true')}\n aria-expanded=${cond(this.accessibleExpanded)}\n aria-haspopup=${cond(this.accessibleHasPopup)}\n >\n ${innards}\n </button>\n `\n }\n\n private renderLightDom() {\n if (this.href || !this.form) {\n return nothing\n }\n\n return html`\n <button\n ${ref(this.buttonRef)}\n slot=\"proxy\"\n name=${cond(this.name)}\n value=${cond(this.value)}\n ?disabled=${this.disabled}\n form=${cond(this._formId)}\n type=${this.type}\n ></button>\n `\n }\n\n private handleOuterClick = (e: MouseEvent) => {\n // we want to avoid emitting click events when a click\n // happens in blank space in the host, but not on the button\n // so we stop propagation of any events if click didn't happen on the internal or proxy button\n const isInternalButton = e\n .composedPath()\n .some(node => node === this.focusableRef.value || node === this.buttonRef.value)\n\n if (!isInternalButton) {\n e.stopPropagation()\n }\n }\n\n private handleClick(e: Event) {\n if (this.buttonRef.value) {\n // prevents two events: one from internal button, one from proxy button\n e.stopPropagation()\n this.buttonRef.value.click()\n }\n }\n\n /**\n * React/Vue etc may remove our proxy button when updating button text, since they are unaware of its existence.\n * So we listen for a slotchange event, and if the element is no longer connected to the DOM we add it back in.\n */\n private handleProxyChange(e: Event) {\n const slot = e.target as HTMLSlotElement\n const proxyButton = this.buttonRef.value\n\n if (proxyButton && proxyButton.assignedSlot !== slot) {\n this.appendChild(proxyButton)\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-button': Button\n }\n}\n"],"names":["Button","InputMixin","FocusableMixin","LitElement","constructor","this","defaultSlot","SlotController","buttonRef","createRef","events","EventController","lightDom","LightDomController","render","renderLightDom","variant","type","size","download","target","expand","square","loading","hideDropdownIcon","handleOuterClick","e","composedPath","some","node","focusableRef","value","stopPropagation","connectedCallback","super","listen","isDropdownToggle","_a","assignedSlot","name","isIconButton","assigned","localName","shouldShowDropdownIcon","href","innards","html","Boolean","nothing","renderLink","renderButton","ref","cond","disabled","handleProxyChange","handleClick","accessibleExpanded","accessibleHasPopup","form","_formId","click","slot","proxyButton","appendChild","shadowRootOptions","delegatesFocus","styles","componentStyle","style","__decorate","property","reflect","prototype","attribute","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Button.js","sources":["../src/button/Button.ts"],"sourcesContent":["/* eslint-disable lit-a11y/accessible-name */\nimport type { TemplateResult } from 'lit'\nimport { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { createRef, ref } from 'lit/directives/ref.js'\nimport { EventController } from '../common/controllers/EventController.js'\nimport { LightDomController } from '../common/controllers/LightDomController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\n\nimport { cond } from '../common/directives/cond.js'\n\nimport { FocusableMixin } from '../common/mixins/FocusableMixin.js'\nimport { InputMixin } from '../common/mixins/InputMixin.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Button.css'\nimport '../spinner/Spinner.js'\n\n/**\n * Buttons are used for interface actions. Primary style should be\n * used only once per section for main call-to-action, while other\n * styles can appear more frequently.\n *\n * @status ready\n * @category action\n * @slot - The button content\n * @slot start - Used to place content at the start of button text. Typically used for icons.\n * @slot end - Used to place content at the end of button text. Typically used for icons.\n *\n * @cssprop [--n-button-border-radius=var(--n-border-radius-s)] - Controls the rounded corners of the button, using [border radius tokens](/tokens/#border-radius).\n * @cssprop [--n-button-gap=var(--n-space-s)] - Controls the spacing between items within the button, using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-gradient=linear-gradient(to bottom, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.013) 100%))] - Controls the overlayed gradient background on the button.\n * @cssprop [--n-button-background-color=var(--n-color-button)] - Controls the background color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-border-color=var(--n-color-border-strong)] - Controls the border color of the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-text-align=center] - Controls the text alignment for the text in the button.\n * @cssprop [--n-button-box-shadow=var(--n-box-shadow)] - Controls the surrounding shadow, using our [box shadow tokens](/tokens/#box-shadow).\n * @cssprop [--n-button-color=var(--n-color-text)] - Controls the color of the text within the button, using our [color tokens](/tokens/#color).\n * @cssprop [--n-button-padding-inline=calc(var(--n-space-m) / 1.2)] - Controls the inline, or left and right, padding of the button.\n * @cssprop [--n-button-font-size=var(--n-font-size-m)] - Controls the size of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-font-weight=var(--n-font-weight)] - Controls the weight of the text within the button, using our [font tokens](/tokens/#font).\n * @cssprop [--n-button-min-block-size=var(--n-space-xl)] - Controls the minimum block size, or height, of the button using our [spacing tokens](/tokens/#space).\n * @cssprop [--n-button-toggle-icon-color=var(--n-color-icon)] - Controls the color of toggle icon that gets rendered when the button is used as a [Dropdown](/components/dropdown/) toggle.\n * @cssprop [--n-button-user-select=none] - Controls the text selection behavior of the button text.\n */\n@customElement('nord-button')\nexport default class Button extends InputMixin(FocusableMixin(LitElement)) {\n static shadowRootOptions = { ...LitElement.shadowRootOptions, delegatesFocus: true }\n\n static styles = [componentStyle, style]\n\n private defaultSlot = new SlotController(this)\n private buttonRef = createRef<HTMLButtonElement>()\n private events = new EventController(this)\n private lightDom = new LightDomController(this, {\n render: () => this.renderLightDom(),\n })\n\n /**\n * The style variant of the button.\n */\n @property({ reflect: true }) variant: 'default' | 'primary' | 'dashed' | 'plain' | 'danger' = 'default'\n\n /**\n * The type of the button.\n */\n @property({ reflect: true }) type: 'button' | 'submit' | 'reset' = 'submit'\n\n /**\n * The size of the button.\n * This affects font-size and padding.\n */\n @property({ reflect: true }) size: 's' | 'm' | 'l' = 'm'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-expanded attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-expanded' }) accessibleExpanded?: 'true' | 'false'\n\n /**\n * This does not need to be documented,\n * since it is only for forwarding the aria-haspopup attribute\n * to the internal button element.\n * @private\n */\n @property({ attribute: 'aria-haspopup' }) accessibleHasPopup?:\n | 'false'\n | 'true'\n | 'menu'\n | 'listbox'\n | 'tree'\n | 'grid'\n | 'dialog'\n\n /**\n * When provided, renders the button as a link,\n * with its href attribute set to the given value.\n */\n @property({ reflect: true }) href?: string\n\n /**\n * When provided together with a href property, the button will\n * trigger a file download instead of a page visit.\n */\n @property({ reflect: true, type: Boolean }) download = false\n\n /**\n * When provided together with a href property, determines where\n * to open the linked URL. The keywords have special meanings for\n * where to load the URL: “_self” means the current browsing context,\n * “_blank” usually a new tab but users can configure browsers this to\n * open a new window instead, “_parent” means the parent browsing\n * context of the current one, but if no parent exists, behaves as\n * _self, and finally “top” means the topmost browsing context.\n */\n @property() target: '_self' | '_blank' | '_parent' | '_top' = '_self'\n\n /**\n * When provided together with a href property, sets the relationship\n * between the linked URL and the current document, e.g. “noopener\n * noreferrer” when linking to untrusted content in a new tab.\n */\n @property() rel?: string\n\n /**\n * Controls whether the button expands to fill the width of its container.\n */\n @property({ reflect: true, type: Boolean }) expand = false\n\n /**\n * When provided, the button is rendered as a square button. Use this for icon only buttons.\n */\n @property({ reflect: true, type: Boolean }) square = false\n\n /**\n * Controls whether the button is in loading state. Please note that the spinner\n * is hidden from assistive technologies, so you need to make sure to announce\n * the loading state to e.g. screen reader users. We also recommend disabling\n * all user interactions on the button itself while in loading state.\n */\n @property({ reflect: true, type: Boolean }) loading = false\n\n /**\n * Controls whether the `interface-dropdown-small` icon from Nordicons is hidden\n * in the `end` slot when used as a dropdown toggle.\n */\n @property({ reflect: true, type: Boolean, attribute: 'hide-dropdown-icon' }) hideDropdownIcon = false\n\n connectedCallback() {\n super.connectedCallback()\n this.events.listen(this, 'click', this.handleOuterClick, true)\n }\n\n render() {\n const isDropdownToggle = this.assignedSlot?.name === 'toggle'\n const isIconButton = this.defaultSlot.assigned.some(node => node.localName === 'nord-icon')\n const shouldShowDropdownIcon = isDropdownToggle && !this.hideDropdownIcon && !isIconButton && !this.href\n\n const innards = html`\n <slot name=\"start\"></slot>\n <div class=\"n-content\">\n <slot></slot>\n </div>\n <nord-spinner\n class=\"n-button-spinner\"\n color=\"currentColor\"\n ?hidden=${!this.loading || Boolean(this.href)}\n ></nord-spinner>\n <slot name=\"end\">\n ${shouldShowDropdownIcon\n ? html`<nord-icon name=\"interface-dropdown-small\" class=\"n-toggle-icon\"></nord-icon>`\n : nothing}\n </slot>\n `\n\n return this.href ? this.renderLink(innards) : this.renderButton(innards)\n }\n\n /**\n * We jump through some hoops here to ensure the link is treated correctly when \"disabled\".\n * Links cannot be disabled natively, so we need to rely on some aria magic to get the correct semantics.\n * Along with the advice in the article below, we also set tabindex to \"-1\", so it is taken out of tab order.\n *\n * @see https://www.scottohara.me/blog/2021/05/28/disabled-links.html\n */\n // When the host is explicitly removed from the tab order (`tabindex=\"-1\"`,\n // e.g. a decorative/`aria-hidden` button), forward that to the internal\n // control. `delegatesFocus` otherwise keeps the shadow button focusable, which\n // fails the `aria-hidden-focus` a11y rule (focusable descendant of a hidden\n // element). Only `-1` is forwarded — positive/zero values are left to\n // `delegatesFocus`.\n private get isHostNonFocusable(): boolean {\n return this.getAttribute('tabindex') === '-1'\n }\n\n private renderLink(innards: TemplateResult) {\n return html`\n <a\n ${ref(this.focusableRef)}\n class=\"n-button\"\n target=${this.target}\n rel=${cond(this.rel)}\n ?download=${this.download}\n href=${cond(this.disabled, nothing, this.href)}\n tabindex=${cond(this.disabled || this.isHostNonFocusable, '-1')}\n aria-disabled=${cond(this.disabled, 'true')}\n role=${cond(this.disabled, 'link')}\n >${innards}\n </a>\n `\n }\n\n private renderButton(innards: TemplateResult) {\n return html`\n <slot name=\"proxy\" @slotchange=${this.handleProxyChange}></slot>\n <button\n ${ref(this.focusableRef)}\n class=\"n-button\"\n ?disabled=${this.disabled}\n name=${cond(this.name)}\n value=${cond(this.value)}\n tabindex=${cond(this.isHostNonFocusable, '-1')}\n @click=${this.handleClick}\n aria-disabled=${cond(this.loading, 'true')}\n aria-expanded=${cond(this.accessibleExpanded)}\n aria-haspopup=${cond(this.accessibleHasPopup)}\n >\n ${innards}\n </button>\n `\n }\n\n private renderLightDom() {\n if (this.href || !this.form) {\n return nothing\n }\n\n return html`\n <button\n ${ref(this.buttonRef)}\n slot=\"proxy\"\n name=${cond(this.name)}\n value=${cond(this.value)}\n ?disabled=${this.disabled}\n form=${cond(this._formId)}\n type=${this.type}\n ></button>\n `\n }\n\n private handleOuterClick = (e: MouseEvent) => {\n // we want to avoid emitting click events when a click\n // happens in blank space in the host, but not on the button\n // so we stop propagation of any events if click didn't happen on the internal or proxy button\n const isInternalButton = e\n .composedPath()\n .some(node => node === this.focusableRef.value || node === this.buttonRef.value)\n\n if (!isInternalButton) {\n e.stopPropagation()\n }\n }\n\n private handleClick(e: Event) {\n if (this.buttonRef.value) {\n // prevents two events: one from internal button, one from proxy button\n e.stopPropagation()\n this.buttonRef.value.click()\n }\n }\n\n /**\n * React/Vue etc may remove our proxy button when updating button text, since they are unaware of its existence.\n * So we listen for a slotchange event, and if the element is no longer connected to the DOM we add it back in.\n */\n private handleProxyChange(e: Event) {\n const slot = e.target as HTMLSlotElement\n const proxyButton = this.buttonRef.value\n\n if (proxyButton && proxyButton.assignedSlot !== slot) {\n this.appendChild(proxyButton)\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-button': Button\n }\n}\n"],"names":["Button","InputMixin","FocusableMixin","LitElement","constructor","this","defaultSlot","SlotController","buttonRef","createRef","events","EventController","lightDom","LightDomController","render","renderLightDom","variant","type","size","download","target","expand","square","loading","hideDropdownIcon","handleOuterClick","e","composedPath","some","node","focusableRef","value","stopPropagation","connectedCallback","super","listen","isDropdownToggle","_a","assignedSlot","name","isIconButton","assigned","localName","shouldShowDropdownIcon","href","innards","html","Boolean","nothing","renderLink","renderButton","isHostNonFocusable","getAttribute","ref","cond","rel","disabled","handleProxyChange","handleClick","accessibleExpanded","accessibleHasPopup","form","_formId","click","slot","proxyButton","appendChild","shadowRootOptions","delegatesFocus","styles","componentStyle","style","__decorate","property","reflect","prototype","attribute","customElement"],"mappings":"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"}
|