@nordhealth/components 4.26.0 → 4.26.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 +5744 -5347
- package/lib/Button.js +1 -1
- package/lib/Button.js.map +1 -1
- package/lib/ButtonGroup.js +1 -1
- package/lib/ButtonGroup.js.map +1 -1
- package/lib/DatePicker.js +1 -1
- package/lib/Drawer.js +1 -1
- package/lib/Drawer.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/KeyboardController.js +1 -1
- package/lib/KeyboardController.js.map +1 -1
- package/lib/Layout.js +2 -2
- package/lib/Layout.js.map +1 -1
- package/lib/NavItem.js +1 -1
- package/lib/NavItem.js.map +1 -1
- package/lib/Navigation.js +1 -1
- package/lib/Navigation.js.map +1 -1
- package/lib/Popout-CYqn_dZu.js +2 -0
- package/lib/Popout-CYqn_dZu.js.map +1 -0
- package/lib/Popout.js +1 -1
- package/lib/Textarea.js +1 -1
- package/lib/Textarea.js.map +1 -1
- package/lib/Tooltip.js +1 -1
- package/lib/Tooltip.js.map +1 -1
- package/lib/Truncate.js +1 -1
- package/lib/Truncate.js.map +1 -1
- package/lib/bundle.js +11 -11
- package/lib/bundle.js.map +1 -1
- package/lib/index.js +1 -1
- package/lib/src/button/Button.d.ts +6 -0
- package/lib/src/button-group/ButtonGroup.d.ts +1 -0
- package/lib/src/command-menu/ICommandMenuAction.d.ts +11 -0
- package/lib/src/common/mixins/FloatingComponentMixin.d.ts +2 -0
- package/lib/src/drawer/Drawer.d.ts +7 -0
- package/lib/src/layout/Layout.d.ts +1 -1
- package/lib/src/popout/Popout.d.ts +2 -2
- package/lib/src/textarea/Textarea.d.ts +2 -1
- package/lib/src/tooltip/Tooltip.d.ts +12 -0
- package/lib/src/truncate/Truncate.d.ts +12 -0
- package/package.json +2 -2
- package/lib/Popout-DtEL0HZ0.js +0 -2
- package/lib/Popout-DtEL0HZ0.js.map +0 -1
package/lib/Navigation.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as n}from"./tslib.es6-CmLYFWVC.js";import{css as o,LitElement as t,html as a,nothing as e}from"lit";import{property as i,customElement as r}from"lit/decorators.js";import{E as s}from"./EventController-BBOmvfLa.js";import{S as l}from"./SlotController-Z6eG7LSZ.js";import{s as d}from"./Component-DSU3Qp0O.js";const c=o`:host{--_n-navigation-background-color:var(--n-navigation-background-color, var(--n-color-nav-surface));--_n-navigation-scroll-shadow-color-start:var(--n-navigation-scroll-shadow-color-start, rgb(0, 0, 0, 0.1));--_n-navigation-scroll-shadow-color-end:var(--n-navigation-scroll-shadow-color-end, rgb(0, 0, 0, 0.1));--_n-navigation-scroll-mask:var(--n-navigation-scroll-mask, none);--_n-navigation-padding-block:var(--n-navigation-padding-block, var(--n-space-m));--_n-navigation-padding-inline:var(--n-navigation-padding-inline, var(--n-space-m));--_n-navigation-footer-padding-block:var(--n-navigation-footer-padding-block, var(--n-space-s));--_n-navigation-footer-padding-inline:var(--n-navigation-footer-padding-inline, var(--_n-navigation-padding-inline));--_n-navigation-header-block-size:var(--n-navigation-header-block-size, var(--n-space-xxl));--_n-navigation-header-padding-inline:var(--n-navigation-header-padding-inline);--_n-navigation-header-background:var(--n-navigation-header-background, var(--_n-navigation-background-color));--_n-navigation-header-divider:var(--n-navigation-header-divider, 1px solid var(--n-color-border));--_n-navigation-header-color:var(--n-navigation-header-color);--_n-navigation-header-toggle-icon-color:var(--n-navigation-header-toggle-icon-color);--_n-navigation-header-focus-style:var(--n-navigation-header-focus-style, var(--_n-navigation-dropdown-box-shadow));--_n-navigation-dropdown-box-shadow:0 0 0 1px var(--n-color-accent) inset;display:flex;flex-direction:column;block-size:100%;background:var(--_n-navigation-background-color)}.n-navigation-main{display:flex;flex-direction:column;flex:1;overflow-y:auto;overscroll-behavior-y:none;overflow-x:hidden}@-moz-document url-prefix(){.n-navigation-main{scrollbar-width:thin}}.n-navigation-main::-webkit-scrollbar{inline-size:6px;block-size:6px}.n-navigation-main::-webkit-scrollbar-thumb{background-color:var(--n-color-border-strong);border-radius:var(--n-border-radius-pill)}.n-navigation-main::-webkit-scrollbar-thumb:hover{background-color:var(--n-color-border-hover)}.n-navigation-main::-webkit-scrollbar-corner,.n-navigation-main::-webkit-scrollbar-track{background:0 0}nav{flex:1;padding-block-start:var(--_n-navigation-padding-block);padding-block-end:var(--_n-navigation-padding-block);padding-inline-start:var(--_n-navigation-padding-inline);padding-inline-end:var(--_n-navigation-padding-inline)}::slotted(nord-nav-group[heading]:first-of-type){margin-block-start:calc(var(--n-space-s) * -1)}slot[name=footer],slot[name=header]{display:flex;flex-direction:column;justify-content:center;--n-button-text-align:start;--n-button-background-color:transparent;--n-button-border-color:transparent;--n-button-box-shadow:none;--n-button-gradient:none}slot[name=header]{--n-button-border-radius:0;--n-button-color:var(--_n-navigation-header-color);--n-button-min-block-size:var(--_n-navigation-header-block-size);--n-select-block-size:var(--n-button-min-block-size);--n-button-font-weight:var(--n-font-weight-active);--n-button-toggle-icon-color:var(--_n-navigation-header-toggle-icon-color);--n-button-padding-inline:var(--_n-navigation-header-padding-inline);block-size:var(--_n-navigation-header-block-size);background-color:var(--_n-navigation-header-background);border-block-end:var(--_n-navigation-header-divider)}slot[name=footer]{--n-button-color:var(--n-color-text-weak);--n-button-padding-inline:var(--n-space-xs);margin:var(--n-space-m)}:host([data-rail-collapsed]) slot[name=header]{--n-button-padding-inline:0;--n-button-text-align:center;--n-button-content-display:none;overflow:hidden}:host([data-rail-collapsed]) slot[name=footer]{--n-button-padding-inline:0;--n-button-content-display:none;overflow:hidden}:is([name=header],[name=footer])::slotted(:is(nord-button,nord-dropdown):not([open]):focus-
|
|
1
|
+
import{_ as n}from"./tslib.es6-CmLYFWVC.js";import{css as o,LitElement as t,html as a,nothing as e}from"lit";import{property as i,customElement as r}from"lit/decorators.js";import{E as s}from"./EventController-BBOmvfLa.js";import{S as l}from"./SlotController-Z6eG7LSZ.js";import{s as d}from"./Component-DSU3Qp0O.js";const c=o`:host{--_n-navigation-background-color:var(--n-navigation-background-color, var(--n-color-nav-surface));--_n-navigation-scroll-shadow-color-start:var(--n-navigation-scroll-shadow-color-start, rgb(0, 0, 0, 0.1));--_n-navigation-scroll-shadow-color-end:var(--n-navigation-scroll-shadow-color-end, rgb(0, 0, 0, 0.1));--_n-navigation-scroll-mask:var(--n-navigation-scroll-mask, none);--_n-navigation-padding-block:var(--n-navigation-padding-block, var(--n-space-m));--_n-navigation-padding-inline:var(--n-navigation-padding-inline, var(--n-space-m));--_n-navigation-footer-padding-block:var(--n-navigation-footer-padding-block, var(--n-space-s));--_n-navigation-footer-padding-inline:var(--n-navigation-footer-padding-inline, var(--_n-navigation-padding-inline));--_n-navigation-header-block-size:var(--n-navigation-header-block-size, var(--n-space-xxl));--_n-navigation-header-padding-inline:var(--n-navigation-header-padding-inline);--_n-navigation-header-background:var(--n-navigation-header-background, var(--_n-navigation-background-color));--_n-navigation-header-divider:var(--n-navigation-header-divider, 1px solid var(--n-color-border));--_n-navigation-header-color:var(--n-navigation-header-color);--_n-navigation-header-toggle-icon-color:var(--n-navigation-header-toggle-icon-color);--_n-navigation-header-focus-style:var(--n-navigation-header-focus-style, var(--_n-navigation-dropdown-box-shadow));--_n-navigation-dropdown-box-shadow:0 0 0 1px var(--n-color-accent) inset;display:flex;flex-direction:column;block-size:100%;background:var(--_n-navigation-background-color)}.n-navigation-main{display:flex;flex-direction:column;flex:1;overflow-y:auto;overscroll-behavior-y:none;overflow-x:hidden}@-moz-document url-prefix(){.n-navigation-main{scrollbar-width:thin}}.n-navigation-main::-webkit-scrollbar{inline-size:6px;block-size:6px}.n-navigation-main::-webkit-scrollbar-thumb{background-color:var(--n-color-border-strong);border-radius:var(--n-border-radius-pill)}.n-navigation-main::-webkit-scrollbar-thumb:hover{background-color:var(--n-color-border-hover)}.n-navigation-main::-webkit-scrollbar-corner,.n-navigation-main::-webkit-scrollbar-track{background:0 0}nav{flex:1;padding-block-start:var(--_n-navigation-padding-block);padding-block-end:var(--_n-navigation-padding-block);padding-inline-start:var(--_n-navigation-padding-inline);padding-inline-end:var(--_n-navigation-padding-inline)}::slotted(nord-nav-group[heading]:first-of-type){margin-block-start:calc(var(--n-space-s) * -1)}slot[name=footer],slot[name=header]{display:flex;flex-direction:column;justify-content:center;--n-button-text-align:start;--n-button-background-color:transparent;--n-button-border-color:transparent;--n-button-box-shadow:none;--n-button-gradient:none}slot[name=header]{--n-button-border-radius:0;--n-button-color:var(--_n-navigation-header-color);--n-button-min-block-size:var(--_n-navigation-header-block-size);--n-select-block-size:var(--n-button-min-block-size);--n-button-font-weight:var(--n-font-weight-active);--n-button-toggle-icon-color:var(--_n-navigation-header-toggle-icon-color);--n-button-padding-inline:var(--_n-navigation-header-padding-inline);block-size:var(--_n-navigation-header-block-size);background-color:var(--_n-navigation-header-background);border-block-end:var(--_n-navigation-header-divider)}slot[name=footer]{--n-button-color:var(--n-color-text-weak);--n-button-padding-inline:var(--n-space-xs);margin:var(--n-space-m)}:host([data-rail-collapsed]) slot[name=header]{--n-button-padding-inline:0;--n-button-text-align:center;--n-button-content-display:none;overflow:hidden}:host([data-rail-collapsed]) slot[name=footer]{--n-button-padding-inline:0;--n-button-content-display:none;overflow:hidden}:is([name=header],[name=footer])::slotted(:is(nord-button,nord-dropdown):not([open]):focus-visible){--n-button-border-color:var(--n-color-accent);--n-button-box-shadow:var(--_n-navigation-dropdown-box-shadow)}slot[name=header]::slotted(:is(nord-button,nord-dropdown):not([open],:active):focus-visible){--n-button-box-shadow:var(--_n-navigation-header-focus-style)}[name=footer]::slotted(:is(nord-button,nord-dropdown):is(:hover,[open])){--n-button-border-color:var(--n-color-border-hover)}:host([sticky-footer]) slot[name=footer]{background:var(--_n-navigation-background-color);z-index:var(--n-index-sticky);padding-block-start:var(--_n-navigation-footer-padding-block);padding-block-end:var(--_n-navigation-footer-padding-block);padding-inline-start:var(--_n-navigation-footer-padding-inline);padding-inline-end:var(--_n-navigation-footer-padding-inline);margin:0;position:sticky;inset-block-end:0}:host([sticky-footer]) .n-navigation-main{background:linear-gradient(var(--_n-navigation-background-color) 30%,rgb(255,255,255,0)) center top,linear-gradient(rgb(255,255,255,0),var(--_n-navigation-background-color) 70%) center bottom,radial-gradient(farthest-side at 50% 0,var(--_n-navigation-scroll-shadow-color-start),rgb(0,0,0,0)) center top,radial-gradient(farthest-side at 50% 100%,var(--_n-navigation-scroll-shadow-color-end),rgb(0,0,0,0)) center bottom;background-repeat:no-repeat;background-size:100% 40px,100% 40px,100% 7px,100% 7px;background-attachment:local,local,scroll,scroll;-webkit-mask-image:var(--_n-navigation-scroll-mask);mask-image:var(--_n-navigation-scroll-mask)}`,v="undefined"!=typeof matchMedia?matchMedia("(min-width: 768px)"):{matches:!1,addEventListener(){},removeEventListener(){}};let g=class extends t{constructor(){super(...arguments),this.headerSlot=new l(this,"header"),this.events=new s(this),this.footerObserver=null,this.allowItemsToRemainOpen=!1,this.stickyFooter=!1,this.handleViewportChange=()=>this.updateRailState(),this.handleActivate=n=>{var o,t;const a=n.target,e=new Set;let i=null===(o=a.parentElement)||void 0===o?void 0:o.closest("nord-nav-item");for(;i;)e.add(i),i=null===(t=i.parentElement)||void 0===t?void 0:t.closest("nord-nav-item");for(const n of this.querySelectorAll("nord-nav-item"))if(n!==a){const o=e.has(n);n.open=o||this.allowItemsToRemainOpen&&n.open,n.active=o}this.allowItemsToRemainOpen=!1}}connectedCallback(){super.connectedCallback(),this.events.listen(this,"activate",this.handleActivate);const n=this.querySelector("nord-nav-item[active]");this.allowItemsToRemainOpen=null!=n,"undefined"!=typeof window&&this.observeFooter(),this.startObservingLayout(),v.addEventListener("change",this.handleViewportChange)}disconnectedCallback(){var n;super.disconnectedCallback(),null===(n=this.footerObserver)||void 0===n||n.disconnect(),this.footerObserver=null,this.stopObservingLayout(),v.removeEventListener("change",this.handleViewportChange)}startObservingLayout(){this.observedLayout=this.closest("nord-layout"),this.observedLayout&&(this.updateRailState(),this.layoutObserver=new MutationObserver((()=>this.updateRailState())),this.layoutObserver.observe(this.observedLayout,{attributes:!0,attributeFilter:["nav-open","collapse-mode"]}))}stopObservingLayout(){var n;null===(n=this.layoutObserver)||void 0===n||n.disconnect(),this.layoutObserver=void 0,this.observedLayout=void 0,this.toggleAttribute("data-rail-collapsed",!1)}updateRailState(){if(!this.observedLayout)return;const n=v.matches&&"rail"===this.observedLayout.getAttribute("collapse-mode")&&!this.observedLayout.hasAttribute("nav-open");this.toggleAttribute("data-rail-collapsed",n)}observeFooter(){this.footerObserver=new MutationObserver((()=>this.requestUpdate())),this.footerObserver.observe(this,{childList:!0});const n=this.querySelector(':scope > [slot="footer"]');n&&this.footerObserver.observe(n,{attributes:!0,attributeFilter:["style","class"]})}get isFooterVisible(){if("undefined"==typeof window)return!0;const n=this.querySelector(':scope > [slot="footer"]');if(!n)return!1;const o=window.getComputedStyle(n);return"none"!==o.display&&"hidden"!==o.visibility&&"collapse"!==o.visibility&&"0"!==o.opacity&&"hidden"!==o.contentVisibility}render(){return a`<slot name="header" ?hidden="${this.headerSlot.isEmpty}"></slot><div class="n-navigation-main"><nav><slot></slot></nav>${this.stickyFooter?e:a`<slot name="footer" ?hidden="${!this.isFooterVisible}"></slot>`}</div>${this.stickyFooter?a`<slot name="footer" ?hidden="${!this.isFooterVisible}"></slot>`:e}`}};g.styles=[d,c],n([i({type:Boolean,reflect:!0,attribute:"sticky-footer"})],g.prototype,"stickyFooter",void 0),g=n([r("nord-navigation")],g);var b=g;export{b as default};
|
|
2
2
|
//# sourceMappingURL=Navigation.js.map
|
package/lib/Navigation.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Navigation.js","sources":["../src/navigation/Navigation.ts"],"sourcesContent":["import type NavItem from '../nav-item/NavItem.js'\nimport { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { EventController } from '../common/controllers/EventController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Navigation.css'\n\n// Mirrors the layout's wide-viewport breakpoint. Used to drive the\n// `data-rail-collapsed` attribute below — Safari doesn't support\n// `:host-context()`, so we propagate the layout's rail state via an\n// observed attribute on the host instead.\nconst railMediaQuery\n = typeof matchMedia !== 'undefined'\n ? matchMedia('(min-width: 768px)')\n : ({\n matches: false,\n addEventListener() {\n /* noop */\n },\n removeEventListener() {\n /* noop */\n },\n } as unknown as MediaQueryList)\n\n/**\n * Navigation is used to display the primary navigation in the sidebar\n * of an application. Navigation includes a list of links that users\n * use to move between sections of the application.\n *\n * @status ready\n * @category navigation\n * @slot - The main section of the sidebar, for holding nav components.\n * @slot header - The top section of the sidebar.\n * @slot footer - The bottom section of the sidebar.\n *\n * @fires {NordEvent} toggle - Dispatched whenever a nav item's state changes between open and closed.\n * @fires {NordEvent} activate - Dispatched whenever a nav item has been marked as active\n *\n * @cssprop [--n-navigation-background-color=var(--n-color-nav-surface)] - Controls the background color of the navigation element.\n * @cssprop [--n-navigation-scroll-shadow-color-start=rgb(0, 0, 0, 0.1)] - Color of the scroll-affordance shadow shown at the top of the scroll area (below the header) when `sticky-footer` is set. Set to `transparent` to disable.\n * @cssprop [--n-navigation-scroll-shadow-color-end=rgb(0, 0, 0, 0.1)] - Color of the scroll-affordance shadow shown at the bottom of the scroll area (above the footer) when `sticky-footer` is set. Set to `transparent` to disable.\n * @cssprop [--n-navigation-scroll-mask=none] - `mask-image` applied to the scrollable area when `sticky-footer` is set. Use with the shadow colors set to `transparent` to replace the dark scroll-shadow with a soft content fade at the edges. Example: `linear-gradient(to bottom, transparent 0, black 24px, black calc(100% - 24px), transparent 100%)`.\n * @cssprop [--n-navigation-padding-block=var(--n-space-m)] - Block-axis (top/bottom) padding of the `<nav>` element that wraps the slotted nav groups.\n * @cssprop [--n-navigation-padding-inline=var(--n-space-m)] - Inline-axis (start/end) padding of the `<nav>` element that wraps the slotted nav groups.\n * @cssprop [--n-navigation-footer-padding-block=var(--n-space-s)] - Block-axis (top/bottom) padding inside the footer slot when `sticky-footer` is set. Has no visual effect when the footer slot is empty (slot is hidden in that case).\n * @cssprop [--n-navigation-footer-padding-inline=var(--n-navigation-padding-inline)] - Inline-axis (start/end) padding inside the footer slot when `sticky-footer` is set. Defaults to `--n-navigation-padding-inline` so the footer aligns with the nav content unless overridden. Has no visual effect when the footer slot is empty.\n */\n@customElement('nord-navigation')\nexport default class Navigation extends LitElement {\n static styles = [componentStyle, style]\n\n private headerSlot = new SlotController(this, 'header')\n private events = new EventController(this)\n private footerObserver: MutationObserver | null = null\n private layoutObserver?: MutationObserver\n private observedLayout?: HTMLElement | null\n private allowItemsToRemainOpen = false\n\n /**\n * Controls whether the navigations's footer has sticky positioning.\n */\n @property({ type: Boolean, reflect: true, attribute: 'sticky-footer' }) stickyFooter: boolean = false\n\n connectedCallback() {\n super.connectedCallback()\n // if we add this event listener during render\n // we seem to miss the first activate event.\n // so let's add it as early as possible.\n this.events.listen(this, 'activate', this.handleActivate)\n\n // in cases where there is an initially active nav-item,\n // we should allow any nav-items with the `open` attribute to remain open.\n const activeItem = this.querySelector('nord-nav-item[active]')\n this.allowItemsToRemainOpen = activeItem != null\n\n if (typeof window !== 'undefined') {\n this.observeFooter()\n }\n this.startObservingLayout()\n railMediaQuery.addEventListener('change', this.handleViewportChange)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.footerObserver?.disconnect()\n this.footerObserver = null\n this.stopObservingLayout()\n railMediaQuery.removeEventListener('change', this.handleViewportChange)\n }\n\n private handleViewportChange = () => this.updateRailState()\n\n private startObservingLayout() {\n this.observedLayout = this.closest<HTMLElement>('nord-layout')\n if (!this.observedLayout) {\n return\n }\n this.updateRailState()\n this.layoutObserver = new MutationObserver(() => this.updateRailState())\n this.layoutObserver.observe(this.observedLayout, {\n attributes: true,\n attributeFilter: ['nav-open', 'collapse-mode'],\n })\n }\n\n private stopObservingLayout() {\n this.layoutObserver?.disconnect()\n this.layoutObserver = undefined\n this.observedLayout = undefined\n this.toggleAttribute('data-rail-collapsed', false)\n }\n\n private updateRailState() {\n if (!this.observedLayout) {\n return\n }\n const next = railMediaQuery.matches\n && this.observedLayout.getAttribute('collapse-mode') === 'rail'\n && !this.observedLayout.hasAttribute('nav-open')\n this.toggleAttribute('data-rail-collapsed', next)\n }\n\n /**\n * Observe the footer slot element for visibility changes.\n * Watches style and class attributes on the footer element itself, and\n * childList on the host to detect footer being added/removed (e.g. v-if).\n */\n private observeFooter() {\n this.footerObserver = new MutationObserver(() => this.requestUpdate())\n\n // Watch for footer element being added/removed from the host\n this.footerObserver.observe(this, { childList: true })\n\n // Watch hidden attribute on the footer element directly\n const footerEl = this.querySelector(':scope > [slot=\"footer\"]')\n if (footerEl) {\n this.footerObserver.observe(footerEl, {\n attributes: true,\n attributeFilter: ['style', 'class'],\n })\n }\n }\n\n private get isFooterVisible() {\n // In SSR, the element is backed by a minimal DOM shim that does not implement\n // querySelector or window.getComputedStyle. Return true so the footer slot\n // renders as visible during server-side rendering.\n if (typeof window === 'undefined') {\n return true\n }\n\n const footerEl = this.querySelector<HTMLElement>(':scope > [slot=\"footer\"]')\n if (!footerEl) {\n return false\n }\n\n const style = window.getComputedStyle(footerEl)\n return (\n style.display !== 'none'\n && style.visibility !== 'hidden'\n && style.visibility !== 'collapse'\n && style.opacity !== '0'\n && style.contentVisibility !== 'hidden'\n )\n }\n\n render() {\n return html`\n <slot name=\"header\" ?hidden=${this.headerSlot.isEmpty}></slot>\n <div class=\"n-navigation-main\">\n <nav>\n <slot></slot>\n </nav>\n ${!this.stickyFooter ? html`<slot name=\"footer\" ?hidden=${!this.isFooterVisible}></slot>` : nothing}\n </div>\n ${this.stickyFooter ? html`<slot name=\"footer\" ?hidden=${!this.isFooterVisible}></slot>` : nothing}\n `\n }\n\n private handleActivate = (e: Event) => {\n const target = e.target as NavItem\n const ancestors = new Set<NavItem>()\n\n // walk up the DOM, gathering all ancestors from the target to its top-most nav-item.\n // it's more efficient to first gather ancestors here,\n // than to repeatedly call item.contains(target) in the for-loop below.\n let parent = target.parentElement?.closest('nord-nav-item')\n while (parent) {\n ancestors.add(parent)\n parent = parent.parentElement?.closest('nord-nav-item')\n }\n\n // open and activate any items on the path, otherwise close and deactivate.\n // however, if the item is set to open initially, we should not close it.\n for (const item of this.querySelectorAll('nord-nav-item')) {\n if (item !== target) {\n const isAncestor = ancestors.has(item)\n item.open = isAncestor || (this.allowItemsToRemainOpen && item.open)\n item.active = isAncestor\n }\n }\n\n // this is a one-time thing, purely for initial open state\n this.allowItemsToRemainOpen = false\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-navigation': Navigation\n }\n}\n"],"names":["railMediaQuery","matchMedia","matches","addEventListener","removeEventListener","Navigation","LitElement","constructor","this","headerSlot","SlotController","events","EventController","footerObserver","allowItemsToRemainOpen","stickyFooter","handleViewportChange","updateRailState","handleActivate","e","target","ancestors","Set","parent","_a","parentElement","closest","add","_b","item","querySelectorAll","isAncestor","has","open","active","connectedCallback","super","listen","activeItem","querySelector","window","observeFooter","startObservingLayout","disconnectedCallback","disconnect","stopObservingLayout","observedLayout","layoutObserver","MutationObserver","observe","attributes","attributeFilter","undefined","toggleAttribute","next","getAttribute","hasAttribute","requestUpdate","childList","footerEl","isFooterVisible","style","getComputedStyle","display","visibility","opacity","contentVisibility","render","html","isEmpty","nothing","styles","componentStyle","__decorate","property","type","Boolean","reflect","attribute","prototype","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Navigation.js","sources":["../src/navigation/Navigation.ts"],"sourcesContent":["import type NavItem from '../nav-item/NavItem.js'\nimport { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\n\nimport { EventController } from '../common/controllers/EventController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Navigation.css'\n\n// Mirrors the layout's wide-viewport breakpoint. Used to drive the\n// `data-rail-collapsed` attribute below — Safari doesn't support\n// `:host-context()`, so we propagate the layout's rail state via an\n// observed attribute on the host instead.\nconst railMediaQuery\n = typeof matchMedia !== 'undefined'\n ? matchMedia('(min-width: 768px)')\n : ({\n matches: false,\n addEventListener() {\n /* noop */\n },\n removeEventListener() {\n /* noop */\n },\n } as unknown as MediaQueryList)\n\n/**\n * Navigation is used to display the primary navigation in the sidebar\n * of an application. Navigation includes a list of links that users\n * use to move between sections of the application.\n *\n * @status ready\n * @category navigation\n * @slot - The main section of the sidebar, for holding nav components.\n * @slot header - The top section of the sidebar.\n * @slot footer - The bottom section of the sidebar.\n *\n * @fires {NordEvent} toggle - Dispatched whenever a nav item's state changes between open and closed.\n * @fires {NordEvent} activate - Dispatched whenever a nav item has been marked as active\n *\n * @cssprop [--n-navigation-background-color=var(--n-color-nav-surface)] - Controls the background color of the navigation element.\n * @cssprop [--n-navigation-scroll-shadow-color-start=rgb(0, 0, 0, 0.1)] - Color of the scroll-affordance shadow shown at the top of the scroll area (below the header) when `sticky-footer` is set. Set to `transparent` to disable.\n * @cssprop [--n-navigation-scroll-shadow-color-end=rgb(0, 0, 0, 0.1)] - Color of the scroll-affordance shadow shown at the bottom of the scroll area (above the footer) when `sticky-footer` is set. Set to `transparent` to disable.\n * @cssprop [--n-navigation-scroll-mask=none] - `mask-image` applied to the scrollable area when `sticky-footer` is set. Use with the shadow colors set to `transparent` to replace the dark scroll-shadow with a soft content fade at the edges. Example: `linear-gradient(to bottom, transparent 0, black 24px, black calc(100% - 24px), transparent 100%)`.\n * @cssprop [--n-navigation-padding-block=var(--n-space-m)] - Block-axis (top/bottom) padding of the `<nav>` element that wraps the slotted nav groups.\n * @cssprop [--n-navigation-padding-inline=var(--n-space-m)] - Inline-axis (start/end) padding of the `<nav>` element that wraps the slotted nav groups.\n * @cssprop [--n-navigation-footer-padding-block=var(--n-space-s)] - Block-axis (top/bottom) padding inside the footer slot when `sticky-footer` is set. Has no visual effect when the footer slot is empty (slot is hidden in that case).\n * @cssprop [--n-navigation-footer-padding-inline=var(--n-navigation-padding-inline)] - Inline-axis (start/end) padding inside the footer slot when `sticky-footer` is set. Defaults to `--n-navigation-padding-inline` so the footer aligns with the nav content unless overridden. Has no visual effect when the footer slot is empty.\n */\n@customElement('nord-navigation')\nexport default class Navigation extends LitElement {\n static styles = [componentStyle, style]\n\n private headerSlot = new SlotController(this, 'header')\n private events = new EventController(this)\n private footerObserver: MutationObserver | null = null\n private layoutObserver?: MutationObserver\n private observedLayout?: HTMLElement | null\n private allowItemsToRemainOpen = false\n\n /**\n * Controls whether the navigations's footer has sticky positioning.\n */\n @property({ type: Boolean, reflect: true, attribute: 'sticky-footer' }) stickyFooter: boolean = false\n\n connectedCallback() {\n super.connectedCallback()\n // if we add this event listener during render\n // we seem to miss the first activate event.\n // so let's add it as early as possible.\n this.events.listen(this, 'activate', this.handleActivate)\n\n // in cases where there is an initially active nav-item,\n // we should allow any nav-items with the `open` attribute to remain open.\n const activeItem = this.querySelector('nord-nav-item[active]')\n this.allowItemsToRemainOpen = activeItem != null\n\n if (typeof window !== 'undefined') {\n this.observeFooter()\n }\n this.startObservingLayout()\n railMediaQuery.addEventListener('change', this.handleViewportChange)\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.footerObserver?.disconnect()\n this.footerObserver = null\n this.stopObservingLayout()\n railMediaQuery.removeEventListener('change', this.handleViewportChange)\n }\n\n private handleViewportChange = () => this.updateRailState()\n\n private startObservingLayout() {\n this.observedLayout = this.closest<HTMLElement>('nord-layout')\n if (!this.observedLayout) {\n return\n }\n this.updateRailState()\n this.layoutObserver = new MutationObserver(() => this.updateRailState())\n this.layoutObserver.observe(this.observedLayout, {\n attributes: true,\n attributeFilter: ['nav-open', 'collapse-mode'],\n })\n }\n\n private stopObservingLayout() {\n this.layoutObserver?.disconnect()\n this.layoutObserver = undefined\n this.observedLayout = undefined\n this.toggleAttribute('data-rail-collapsed', false)\n }\n\n private updateRailState() {\n if (!this.observedLayout) {\n return\n }\n const next = railMediaQuery.matches\n && this.observedLayout.getAttribute('collapse-mode') === 'rail'\n && !this.observedLayout.hasAttribute('nav-open')\n this.toggleAttribute('data-rail-collapsed', next)\n }\n\n /**\n * Observe the footer slot element for visibility changes.\n * Watches style and class attributes on the footer element itself, and\n * childList on the host to detect footer being added/removed (e.g. v-if).\n */\n private observeFooter() {\n this.footerObserver = new MutationObserver(() => this.requestUpdate())\n\n // Watch for footer element being added/removed from the host\n this.footerObserver.observe(this, { childList: true })\n\n // Watch hidden attribute on the footer element directly\n const footerEl = this.querySelector(':scope > [slot=\"footer\"]')\n if (footerEl) {\n this.footerObserver.observe(footerEl, {\n attributes: true,\n attributeFilter: ['style', 'class'],\n })\n }\n }\n\n private get isFooterVisible() {\n // In SSR, the element is backed by a minimal DOM shim that does not implement\n // querySelector or window.getComputedStyle. Return true so the footer slot\n // renders as visible during server-side rendering.\n if (typeof window === 'undefined') {\n return true\n }\n\n const footerEl = this.querySelector<HTMLElement>(':scope > [slot=\"footer\"]')\n if (!footerEl) {\n return false\n }\n\n const style = window.getComputedStyle(footerEl)\n return (\n style.display !== 'none'\n && style.visibility !== 'hidden'\n && style.visibility !== 'collapse'\n && style.opacity !== '0'\n && style.contentVisibility !== 'hidden'\n )\n }\n\n render() {\n return html`\n <slot name=\"header\" ?hidden=${this.headerSlot.isEmpty}></slot>\n <div class=\"n-navigation-main\">\n <nav>\n <slot></slot>\n </nav>\n ${!this.stickyFooter ? html`<slot name=\"footer\" ?hidden=${!this.isFooterVisible}></slot>` : nothing}\n </div>\n ${this.stickyFooter ? html`<slot name=\"footer\" ?hidden=${!this.isFooterVisible}></slot>` : nothing}\n `\n }\n\n private handleActivate = (e: Event) => {\n const target = e.target as NavItem\n const ancestors = new Set<NavItem>()\n\n // walk up the DOM, gathering all ancestors from the target to its top-most nav-item.\n // it's more efficient to first gather ancestors here,\n // than to repeatedly call item.contains(target) in the for-loop below.\n let parent = target.parentElement?.closest('nord-nav-item')\n while (parent) {\n ancestors.add(parent)\n parent = parent.parentElement?.closest('nord-nav-item')\n }\n\n // open and activate any items on the path, otherwise close and deactivate.\n // however, if the item is set to open initially, we should not close it.\n for (const item of this.querySelectorAll('nord-nav-item')) {\n if (item !== target) {\n const isAncestor = ancestors.has(item)\n item.open = isAncestor || (this.allowItemsToRemainOpen && item.open)\n item.active = isAncestor\n }\n }\n\n // this is a one-time thing, purely for initial open state\n this.allowItemsToRemainOpen = false\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-navigation': Navigation\n }\n}\n"],"names":["railMediaQuery","matchMedia","matches","addEventListener","removeEventListener","Navigation","LitElement","constructor","this","headerSlot","SlotController","events","EventController","footerObserver","allowItemsToRemainOpen","stickyFooter","handleViewportChange","updateRailState","handleActivate","e","target","ancestors","Set","parent","_a","parentElement","closest","add","_b","item","querySelectorAll","isAncestor","has","open","active","connectedCallback","super","listen","activeItem","querySelector","window","observeFooter","startObservingLayout","disconnectedCallback","disconnect","stopObservingLayout","observedLayout","layoutObserver","MutationObserver","observe","attributes","attributeFilter","undefined","toggleAttribute","next","getAttribute","hasAttribute","requestUpdate","childList","footerEl","isFooterVisible","style","getComputedStyle","display","visibility","opacity","contentVisibility","render","html","isEmpty","nothing","styles","componentStyle","__decorate","property","type","Boolean","reflect","attribute","prototype","customElement"],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{o as e,a as i,s as o,h as s,l as n,f as a,c as r,b as l}from"./positioning-D-K8Mueq.js";import{css as h,html as p,LitElement as d}from"lit";import{property as c,query as m,state as u,customElement as f}from"lit/decorators.js";import{D as g}from"./DirectionController-ChvNGESZ.js";import{E as v}from"./EventController-BBOmvfLa.js";import{L as y}from"./LightDismissController-4pH8cdko.js";import{S as b}from"./ScrollbarController-BFC67Y2x.js";import{o as w}from"./observe-D0n0zOfU.js";import{N as x,t as E}from"./events-Bv6wNHwJ.js";import{s as A}from"./Component-DSU3Qp0O.js";function k(e){class i extends e{constructor(){super(...arguments),this.open=!1,this.align="start",this.position="block-end",this.sideOffset=8,this.alignOffset=0}}return t([c({type:Boolean,reflect:!0})],i.prototype,"open",void 0),t([c({reflect:!0})],i.prototype,"align",void 0),t([c({reflect:!0})],i.prototype,"position",void 0),t([c({reflect:!0,type:Number,attribute:"side-offset"})],i.prototype,"sideOffset",void 0),t([c({reflect:!0,type:Number,attribute:"align-offset"})],i.prototype,"alignOffset",void 0),i}const C=h`:host{position:fixed;pointer-events:none;z-index:var(--n-index-popout);color:var(--n-color-text);opacity:0;transition:opacity var(--n-transition-slowly);inset:auto;inline-size:auto;block-size:auto;margin:0;padding:0;border:none;background:0 0;overflow:visible;left:var(--_n-popout-position-x);top:var(--_n-popout-position-y)}.n-popout{pointer-events:none;transform:translateY(-10px) scale(.97);visibility:hidden;transition:transform var(--n-transition-slowly),visibility var(--n-transition-slowly);transform-origin:top left;will-change:transform,opacity,visibility;background:var(--n-popout-background,var(--n-color-surface));box-shadow:var(--n-popout-box-shadow,var(--n-box-shadow-popout));border-radius:var(--n-border-radius-s)}:host([open]){opacity:1}:host([open]) .n-popout{transition-property:transform;visibility:visible;pointer-events:auto;transform:translateY(0) translateX(0) scale(1)}@media (max-width:35.9375em){:host(:not([always-floating])){position:fixed;inset:0;overflow-y:auto;opacity:1;background:0 0;transition:background var(--n-transition-mobile)}:host([open]:not([always-floating])){pointer-events:auto;background:var(--n-color-overlay)}:host([open]:not([always-floating])) .n-popout{transform:translateY(0)}:host(:not([always-floating])) .n-popout{position:fixed;inset:0;inset-block-start:auto;transform:translateY(100%);transition:transform var(--n-transition-mobile),visibility var(--n-transition-mobile);transform-origin:bottom center;border-radius:0}}.top-end,.top-start{transform:translateY(10px) scale(.97)}.left-end,.left-start{transform:translateX(10px) scale(.97)}.right-end,.right-start{transform:translateX(-10px) scale(.97)}.bottom-start.is-rtl,.left-end,.top-end{transform-origin:bottom right}.bottom-end,.left-start,.top-start.is-rtl{transform-origin:top right}.bottom-end.is-rtl,.right-end,.right-start{transform-origin:bottom left}.right-start,.top-end.is-rtl{transform-origin:top left}`,O="undefined"==typeof matchMedia?{matches:!1,addEventListener(){}}:matchMedia("(max-width: 35.9375em)");let P=class extends(k(d)){constructor(){super(...arguments),this.scrollBar=new b(this),this.dismiss=new y(this,{isOpen:()=>this.open,onDismiss:t=>this.hide("click"!==t.type),isDismissible:t=>t!==this.popout&&t!==this.targetElement}),this.events=new v(this),this.direction=new g(this),this.smallViewport=O.matches,this.id="",this.alwaysFloating=!1,this.enableScroll=()=>{this.open||(this.scrollBar.unlockScroll(),this.leaveTopLayer())},this.updatePosition=async()=>{var t;if(!this.anchorElement)return;const l={strategy:"fixed"};"auto"===this.position?l.middleware=[e({mainAxis:this.sideOffset,alignmentAxis:this.alignOffset}),i({alignment:this.align,padding:8}),o({padding:8}),s()]:(l.placement=n(this.position,this.align,this.direction.dir),l.middleware=[e({mainAxis:this.sideOffset,alignmentAxis:this.alignOffset}),a(),o({padding:8}),s()]);const{x:h,y:p,placement:d,middlewareData:c}=await r(this.anchorElement,this,l);this.computedPosition=d,this.style.setProperty("--_n-popout-position-x",`${h}px`),this.style.setProperty("--_n-popout-position-y",`${p}px`),(null===(t=c.hide)||void 0===t?void 0:t.referenceHidden)&&this.hide()},this.toggleOpen=t=>{t.preventDefault(),this.open?this.hide(!1):!this.smallViewport||this.alwaysFloating?(this.enterTopLayer(),this.updatePosition().then((()=>this.show()))):this.show()},this.handleMediaQueryChange=()=>{var t;this.smallViewport=O.matches,null===(t=this.cleanupAutoUpdate)||void 0===t||t.call(this),(!this.smallViewport||this.alwaysFloating)&&this.open&&this.anchorElement?(this.cleanupAutoUpdate=l(this.anchorElement,this,this.updatePosition),this.scrollBar.unlockScroll()):this.open&&this.scrollBar.lockScroll()}}async show(){return this.open?Promise.resolve():(this.enterTopLayer(),this.open=!0,await this.updateComplete,this.dispatchEvent(new x("open")),E(this.popout))}async hide(t=!0){var e,i;if(!this.open)return Promise.resolve();this.open=!1,null===(e=this.cleanupAutoUpdate)||void 0===e||e.call(this),this.dispatchEvent(new x("close")),t&&(null===(i=this.targetElement)||void 0===i||i.focus({preventScroll:!0}));const o=await E(this.popout);return this.leaveTopLayer(),o}firstUpdated(){this.smallViewport&&!this.alwaysFloating||this.updatePosition()}connectedCallback(){super.connectedCallback(),this.events.listen(O,"change",this.handleMediaQueryChange),this.initializeElements()}disconnectedCallback(){var t,e;super.disconnectedCallback(),null===(t=this.cleanupAutoUpdate)||void 0===t||t.call(this),null===(e=this.targetElement)||void 0===e||e.removeAttribute("aria-expanded")}render(){return p`<div class="n-popout ${this.computedPosition} is-${this.direction.dir}" aria-hidden="${this.open?"false":"true"}" @transitionend="${this.enableScroll}"><slot></slot></div>`}handleIdChange(){var t;null===(t=this.targetElement)||void 0===t||t.removeEventListener("click",this.toggleOpen),this.id?this.initializeElements():console.warn("NORD: popout requires an id attribute and value")}initializeElements(){this.targetElement=this.getToggle(),this.anchorElement=this.getAnchor(),this.events.listen(this.targetElement,"click",this.toggleOpen)}handleOpenChange(){var t,e;null===(t=this.targetElement)||void 0===t||t.setAttribute("aria-expanded",`${this.open}`),this.open?(this.enterTopLayer(),this.smallViewport&&!this.alwaysFloating?this.scrollBar.lockScroll():this.anchorElement&&(this.cleanupAutoUpdate=l(this.anchorElement,this,this.updatePosition))):null===(e=this.cleanupAutoUpdate)||void 0===e||e.call(this)}handleAnchorChange(){var t;null===(t=this.cleanupAutoUpdate)||void 0===t||t.call(this),this.anchorElement=this.getAnchor(),this.open&&(!this.smallViewport||this.alwaysFloating)&&this.anchorElement&&(this.cleanupAutoUpdate=l(this.anchorElement,this,this.updatePosition))}enterTopLayer(){if("function"==typeof this.showPopover)try{this.setAttribute("popover","manual"),this.showPopover()}catch(t){this.removeAttribute("popover")}}leaveTopLayer(){this.removeAttribute("popover")}getToggle(){const t=this.getRootNode(),e=t.querySelectorAll(`[aria-controls='${this.id}']`);let i=e[0];if(e.length>1){const o=[...t.querySelectorAll(`[id='${this.id}']`)].indexOf(this);o>=0&&o<e.length&&(i=e[o])}return i instanceof HTMLSlotElement?i.assignedElements()[0]:i}getAnchor(){if(!this.anchor)return this.targetElement;const t=this.getRootNode().querySelector(`#${this.anchor}`);return t instanceof HTMLSlotElement?t.assignedElements()[0]:t}};P.styles=[A,C],t([m(".n-popout",!0)],P.prototype,"popout",void 0),t([u()],P.prototype,"computedPosition",void 0),t([u()],P.prototype,"smallViewport",void 0),t([c({reflect:!0})],P.prototype,"id",void 0),t([c({reflect:!0})],P.prototype,"anchor",void 0),t([c({reflect:!0,type:Boolean,attribute:"always-floating"})],P.prototype,"alwaysFloating",void 0),t([w("id")],P.prototype,"handleIdChange",null),t([w("open")],P.prototype,"handleOpenChange",null),t([w("anchor")],P.prototype,"handleAnchorChange",null),P=t([f("nord-popout")],P);var S=P;export{k as F,S as P};
|
|
2
|
+
//# sourceMappingURL=Popout-CYqn_dZu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Popout-CYqn_dZu.js","sources":["../src/common/mixins/FloatingComponentMixin.ts","../src/popout/Popout.ts"],"sourcesContent":["import type { Alignment } from '@floating-ui/dom'\nimport type { LitElement } from 'lit'\nimport type { LogicalSide } from '../positioning.js'\nimport { property } from 'lit/decorators.js'\n\ntype Constructor<T = Record<string, unknown>> = new (...args: any[]) => T\n\nexport declare class FloatingMixinInterface {\n open: boolean\n align: Alignment\n position: LogicalSide | 'auto'\n sideOffset: number\n alignOffset: number\n}\n\nexport function FloatingMixin<T extends Constructor<LitElement>>(\n superClass: T,\n): Constructor<FloatingMixinInterface> & T {\n class FloatingElement extends superClass {\n /**\n * Controls whether the component is open or not.\n */\n @property({ type: Boolean, reflect: true }) open = false\n\n /**\n * Set the alignment in relation to the toggle (or anchor) depending on the position.\n * `start` will align it to the left of the toggle (or anchor).\n * `end` will align it to the right of the toggle (or anchor).\n * Setting the `position` to `inline-start` or `inline-end` will switch\n * `start` and `end` to the top and bottom respectively.\n */\n @property({ reflect: true }) align: 'start' | 'end' = 'start'\n\n /**\n * Set the position in relation to the toggle (or anchor).\n * Options follow logical properties.\n * `block-start` and `block-end` referring to top and bottom respectively,\n * `inline-start` and `inline-end` referring to left and right respectively.\n * You can also set it to `auto` for automatic positioning depending on\n * which side has the most space available.\n */\n @property({ reflect: true }) position: 'block-end' | 'block-start' | 'inline-start' | 'inline-end' | 'auto'\n = 'block-end'\n\n /**\n * Distance in pixels from the toggle (or anchor) along the main axis — the\n * gap between the floating element and the side it opens against. Fed into\n * Floating UI's `offset` middleware as `mainAxis`. Defaults to `8`.\n */\n @property({ reflect: true, type: Number, attribute: 'side-offset' }) sideOffset = 8\n\n /**\n * Offset in pixels along the alignment axis — slides the floating element\n * toward the `start` or `end` of the toggle (or anchor). Fed into Floating\n * UI's `offset` middleware as `alignmentAxis`. Defaults to `0`.\n */\n @property({ reflect: true, type: Number, attribute: 'align-offset' }) alignOffset = 0\n }\n\n return FloatingElement\n}\n","import type {\n ComputePositionConfig,\n Placement,\n} from '@floating-ui/dom'\nimport {\n autoPlacement,\n autoUpdate,\n computePosition,\n flip,\n hide,\n offset,\n shift,\n} from '@floating-ui/dom'\nimport { html, LitElement } from 'lit'\nimport { customElement, property, query, state } from 'lit/decorators.js'\nimport { DirectionController } from '../common/controllers/DirectionController.js'\nimport { EventController } from '../common/controllers/EventController.js'\nimport { LightDismissController } from '../common/controllers/LightDismissController.js'\nimport { ScrollbarController } from '../common/controllers/ScrollbarController.js'\n\nimport { observe } from '../common/decorators/observe.js'\nimport { NordEvent, transition } from '../common/events.js'\nimport { FloatingMixin } from '../common/mixins/FloatingComponentMixin.js'\nimport { logicalToPhysical } from '../common/positioning.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Popout.css'\n\n/*\n * The breakpoint width to switch between \"sheet\" design and floating design\n */\nconst mediaQuery\n = typeof matchMedia === 'undefined'\n ? ({\n matches: false,\n addEventListener() {\n /* noop */\n },\n } as unknown as MediaQueryList)\n : matchMedia('(max-width: 35.9375em)')\n\n/**\n * Popouts are small overlays that open on demand. They let users access additional content and actions without cluttering the page.\n *\n * @status ready\n * @category overlay\n * @slot - The popout content.\n *\n * @cssprop [--n-popout-background=var(--n-color-surface)] - Background of the popout container. Set to `transparent` to suppress the popout's own surface (e.g. when a slotted element paints its own background).\n * @cssprop [--n-popout-box-shadow=var(--n-box-shadow-popout)] - Box-shadow of the popout container. Set to `none` to suppress the popout's own shadow.\n */\n@customElement('nord-popout')\nexport default class Popout extends FloatingMixin(LitElement) {\n static styles = [componentStyle, style]\n\n private targetElement?: HTMLElement\n private anchorElement?: HTMLElement\n private cleanupAutoUpdate?: ReturnType<typeof autoUpdate>\n\n @query('.n-popout', true) private popout!: HTMLDivElement\n\n private scrollBar = new ScrollbarController(this)\n\n /**\n * Handle dismissal of the popout, clicking outside the target button and popout.\n */\n private dismiss = new LightDismissController(this, {\n isOpen: () => this.open,\n onDismiss: e => this.hide(e.type !== 'click'),\n isDismissible: node => node !== this.popout && node !== this.targetElement,\n })\n\n private events = new EventController(this)\n private direction = new DirectionController(this)\n\n @state() private computedPosition?: Placement\n\n @state() private smallViewport = mediaQuery.matches\n\n /**\n * The id for the active element to reference via aria-controls.\n */\n\n @property({ reflect: true }) id: string = ''\n\n /**\n * Set an optional anchor element to align against, replacing the triggering element.\n */\n @property({ reflect: true }) anchor?: string\n\n /**\n * Set to true to always display the popout as a floating overlay, even on smaller viewports.\n */\n @property({ reflect: true, type: Boolean, attribute: 'always-floating' }) alwaysFloating: boolean = false\n\n /**\n * Show the popout.\n * A promise that resolves to a `TransitionEvent` when the popout's show animation ends or is cancelled.\n * If the popout is already open, the promise resolves immediately with `undefined`.\n */\n async show(): Promise<TransitionEvent | void> {\n if (this.open) {\n return Promise.resolve()\n }\n\n this.enterTopLayer()\n this.open = true\n\n // we should only focus once the popout is visible after render is complete\n await this.updateComplete\n\n /**\n * Dispatched when the popout is opened.\n */\n this.dispatchEvent(new NordEvent('open'))\n\n return transition(this.popout)\n }\n\n /**\n * Hide the popout.\n * Returns a promise that resolves to a `TransitionEvent` when the popout's hide animation ends or is cancelled.\n * If the popout is already closed, the promise resolves immediately with `undefined`.\n * @param {boolean} moveFocusToButton prevent focus returning to the target button. Default is true.\n */\n async hide(moveFocusToButton: boolean = true): Promise<TransitionEvent | void> {\n if (!this.open) {\n return Promise.resolve()\n }\n\n this.open = false\n\n this.cleanupAutoUpdate?.()\n\n /**\n * Dispatched when the popout is closed.\n */\n this.dispatchEvent(new NordEvent('close'))\n\n if (moveFocusToButton) {\n this.targetElement?.focus({ preventScroll: true })\n }\n\n const result = await transition(this.popout)\n this.leaveTopLayer()\n return result\n }\n\n /**\n * Position the popout on load.\n */\n firstUpdated() {\n if (!this.smallViewport || this.alwaysFloating) {\n this.updatePosition()\n }\n }\n\n connectedCallback() {\n super.connectedCallback()\n\n this.events.listen(mediaQuery, 'change', this.handleMediaQueryChange)\n\n this.initializeElements()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this.cleanupAutoUpdate?.()\n this.targetElement?.removeAttribute('aria-expanded')\n }\n\n render() {\n return html`\n <div\n class=\"n-popout ${this.computedPosition} is-${this.direction.dir}\"\n aria-hidden=${this.open ? 'false' : 'true'}\n @transitionend=${this.enableScroll}\n >\n <slot></slot>\n </div>\n `\n }\n\n @observe('id')\n protected handleIdChange() {\n // clean up any old listeners\n this.targetElement?.removeEventListener('click', this.toggleOpen)\n\n if (!this.id) {\n console.warn('NORD: popout requires an id attribute and value')\n }\n else {\n this.initializeElements()\n }\n }\n\n private initializeElements() {\n this.targetElement = this.getToggle()\n this.anchorElement = this.getAnchor()\n\n this.events.listen(this.targetElement, 'click', this.toggleOpen)\n }\n\n @observe('open')\n protected handleOpenChange() {\n this.targetElement?.setAttribute('aria-expanded', `${this.open}`)\n\n if (this.open) {\n this.enterTopLayer()\n\n if (this.smallViewport && !this.alwaysFloating) {\n // hide scrollbar and prevent scroll on body\n this.scrollBar.lockScroll()\n }\n else if (this.anchorElement) {\n this.cleanupAutoUpdate = autoUpdate(this.anchorElement, this, this.updatePosition)\n }\n }\n else {\n this.cleanupAutoUpdate?.()\n }\n }\n\n @observe('anchor')\n protected handleAnchorChange() {\n this.cleanupAutoUpdate?.()\n this.anchorElement = this.getAnchor()\n\n // if the popout is already open when the anchor changes, we should update its position\n if (this.open && (!this.smallViewport || this.alwaysFloating) && this.anchorElement) {\n this.cleanupAutoUpdate = autoUpdate(this.anchorElement, this, this.updatePosition)\n }\n }\n\n private enableScroll = () => {\n // scrollbar should only be restored when the backdrop has transitioned\n // that way we avoid awkward double scrollbars.\n if (!this.open) {\n this.scrollBar.unlockScroll()\n this.leaveTopLayer()\n }\n }\n\n /**\n * Place the element in the top layer via the Popover API so that\n * position: fixed works relative to the viewport regardless of\n * container queries or other containing block ancestors.\n */\n private enterTopLayer() {\n if (typeof this.showPopover !== 'function')\n return\n\n try {\n this.setAttribute('popover', 'manual')\n this.showPopover()\n }\n catch {\n this.removeAttribute('popover')\n }\n }\n\n private leaveTopLayer() {\n this.removeAttribute('popover')\n }\n\n /**\n * Get the position of the element toggling the popout\n * and position the popout underneath it, taking into account the optional placement.\n */\n private updatePosition = async () => {\n if (!this.anchorElement) {\n return\n }\n\n const computePositionConfig: ComputePositionConfig = {\n strategy: 'fixed',\n }\n\n if (this.position === 'auto') {\n computePositionConfig.middleware = [\n offset({ mainAxis: this.sideOffset, alignmentAxis: this.alignOffset }),\n autoPlacement({ alignment: this.align, padding: 8 }),\n shift({\n padding: 8,\n }),\n hide(),\n ]\n }\n else {\n computePositionConfig.placement = logicalToPhysical(this.position, this.align, this.direction.dir)\n computePositionConfig.middleware = [\n offset({ mainAxis: this.sideOffset, alignmentAxis: this.alignOffset }),\n flip(),\n shift({\n padding: 8,\n }),\n hide(),\n ]\n }\n\n const { x, y, placement, middlewareData } = await computePosition(this.anchorElement, this, computePositionConfig)\n\n this.computedPosition = placement\n\n // use physical properties here since floating-ui\n // works exclusively in physical dimensions\n // we do all the mapping in logicalToPhysical\n this.style.setProperty('--_n-popout-position-x', `${x}px`)\n this.style.setProperty('--_n-popout-position-y', `${y}px`)\n\n if (middlewareData.hide?.referenceHidden) {\n this.hide()\n }\n }\n\n /**\n * Toggle the popout open or closed using state.\n * Updating the position to underneath the target button before the popout is opened.\n */\n private toggleOpen = (e: Event) => {\n e.preventDefault()\n if (this.open) {\n this.hide(false)\n }\n else if (!this.smallViewport || this.alwaysFloating) {\n this.enterTopLayer()\n this.updatePosition().then(() => this.show())\n }\n else {\n this.show()\n }\n }\n\n private getToggle() {\n const rootNode = this.getRootNode() as Document | ShadowRoot\n const allToggles = rootNode.querySelectorAll<HTMLElement>(`[aria-controls='${this.id}']`)\n\n let toggle = allToggles[0]\n\n // When multiple toggles share the same aria-controls (duplicate IDs on page),\n // pair each toggle with its corresponding popout by document order.\n if (allToggles.length > 1) {\n const index = [...rootNode.querySelectorAll<HTMLElement>(`[id='${this.id}']`)].indexOf(this)\n if (index >= 0 && index < allToggles.length) {\n toggle = allToggles[index]\n }\n }\n\n if (toggle instanceof HTMLSlotElement) {\n return toggle.assignedElements()[0] as HTMLElement\n }\n\n return toggle\n }\n\n private getAnchor() {\n if (!this.anchor) {\n return this.targetElement\n }\n\n const rootNode = this.getRootNode() as Document | ShadowRoot\n const anchor = <HTMLElement>rootNode.querySelector(`#${this.anchor}`)\n\n if (anchor instanceof HTMLSlotElement) {\n return anchor.assignedElements()[0] as HTMLElement\n }\n\n return anchor\n }\n\n /**\n * Update the smallViewport flag to switch between \"sheet\" and \"floating\".\n * autoUpdate is needed when a viewport gets larger and the popout is open.\n */\n private handleMediaQueryChange = () => {\n this.smallViewport = mediaQuery.matches\n\n this.cleanupAutoUpdate?.()\n\n if ((!this.smallViewport || this.alwaysFloating) && this.open && this.anchorElement) {\n this.cleanupAutoUpdate = autoUpdate(this.anchorElement, this, this.updatePosition)\n this.scrollBar.unlockScroll()\n }\n else if (this.open) {\n this.scrollBar.lockScroll()\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-popout': Popout\n }\n}\n"],"names":["FloatingMixin","superClass","FloatingElement","constructor","this","open","align","position","sideOffset","alignOffset","__decorate","property","type","Boolean","reflect","prototype","Number","attribute","mediaQuery","matchMedia","matches","addEventListener","Popout","LitElement","scrollBar","ScrollbarController","dismiss","LightDismissController","isOpen","onDismiss","e","hide","isDismissible","node","popout","targetElement","events","EventController","direction","DirectionController","smallViewport","id","alwaysFloating","enableScroll","unlockScroll","leaveTopLayer","updatePosition","async","anchorElement","computePositionConfig","strategy","middleware","offset","mainAxis","alignmentAxis","autoPlacement","alignment","padding","shift","placement","logicalToPhysical","dir","flip","x","y","middlewareData","computePosition","computedPosition","style","setProperty","_a","referenceHidden","toggleOpen","preventDefault","enterTopLayer","then","show","handleMediaQueryChange","cleanupAutoUpdate","call","autoUpdate","lockScroll","Promise","resolve","updateComplete","dispatchEvent","NordEvent","transition","moveFocusToButton","_b","focus","preventScroll","result","firstUpdated","connectedCallback","super","listen","initializeElements","disconnectedCallback","removeAttribute","render","html","handleIdChange","removeEventListener","console","warn","getToggle","getAnchor","handleOpenChange","setAttribute","handleAnchorChange","showPopover","rootNode","getRootNode","allToggles","querySelectorAll","toggle","length","index","indexOf","HTMLSlotElement","assignedElements","anchor","querySelector","styles","componentStyle","query","state","observe","customElement"],"mappings":"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"}
|
package/lib/Popout.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import"./tslib.es6-CmLYFWVC.js";import"./positioning-D-K8Mueq.js";import"lit";import"lit/decorators.js";import"./DirectionController-ChvNGESZ.js";import"./EventController-BBOmvfLa.js";import"./LightDismissController-4pH8cdko.js";import"./ScrollbarController-BFC67Y2x.js";import"./observe-D0n0zOfU.js";import"./events-Bv6wNHwJ.js";export{P as default}from"./Popout-
|
|
1
|
+
import"./tslib.es6-CmLYFWVC.js";import"./positioning-D-K8Mueq.js";import"lit";import"lit/decorators.js";import"./DirectionController-ChvNGESZ.js";import"./EventController-BBOmvfLa.js";import"./LightDismissController-4pH8cdko.js";import"./ScrollbarController-BFC67Y2x.js";import"./observe-D0n0zOfU.js";import"./events-Bv6wNHwJ.js";export{P as default}from"./Popout-CYqn_dZu.js";import"./Component-DSU3Qp0O.js";import"./ShortcutController-BIb3WGzH.js";import"./tinykeys.module-_6MZt7MP.js";
|
|
2
2
|
//# sourceMappingURL=Popout.js.map
|
package/lib/Textarea.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as e}from"./tslib.es6-CmLYFWVC.js";import{css as t,html as r,nothing as
|
|
1
|
+
import{_ as e}from"./tslib.es6-CmLYFWVC.js";import{css as t,html as r,nothing as o,LitElement as n}from"lit";import{property as a,customElement as i}from"lit/decorators.js";import{ifDefined as l}from"lit/directives/if-defined.js";import{ref as s}from"lit/directives/ref.js";import{o as c}from"./observe-D0n0zOfU.js";import{N as p}from"./events-Bv6wNHwJ.js";import{A as d}from"./AutocompleteMixin-D8eiOxvO.js";import{F as u}from"./FocusableMixin-BlQLNPdJ.js";import{F as h}from"./FormAssociatedMixin-B4Qj-CQN.js";import{I as m}from"./InputMixin-LetXsCyv.js";import{R as v,s as x}from"./TextField-B955GOhe.js";import{S as b}from"./SizeMixin-CU9cLbLC.js";import{T as f}from"./TextSelectableMixin-Cfv__lHS.js";import{s as g}from"./Component-DSU3Qp0O.js";import{s as z}from"./FormField-BFaVzUjk.js";import{LocalizeController as j}from"./LocalizeController.js";import"./FormDataController-OUt5L5uC.js";import"./SlotController-Z6eG7LSZ.js";import"./EventController-BBOmvfLa.js";import"./VisuallyHidden.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";const y=t`:host{--_n-textarea-inline-size:var(--n-textarea-inline-size, 240px);--_n-textarea-block-size:var(--n-textarea-block-size, 76px);--_n-textarea-max-block-size:var(--n-textarea-max-block-size, 500px);--_n-input-background:var(--n-textarea-background, var(--n-color-surface));--_n-input-color:var(--n-textarea-color, var(--n-color-text));--_n-input-border-color:var(--n-textarea-border-color, var(--n-color-border-strong));--_n-input-border-radius:var(--n-textarea-border-radius, var(--n-border-radius-s))}.n-input-container{position:relative;inline-size:var(--_n-textarea-inline-size)}.n-input{min-block-size:var(--_n-textarea-block-size);max-block-size:var(--_n-textarea-max-block-size);transition:border var(--n-transition-slowly),box-shadow var(--n-transition-slowly),background var(--n-transition-slowly);display:block;resize:vertical}:host([resize=auto]) .n-input{resize:none;overflow:auto}:host([expand]){--_n-textarea-inline-size:100%}.n-character-counter{margin-block-start:var(--n-space-xs);font-size:var(--n-font-size-s);color:var(--n-color-text-weaker)}.n-input:hover,.n-label-container:hover+.n-input-container .n-input{--_n-input-border-color:var(--n-textarea-border-color, var(--n-color-border-hover))}.n-input:focus{--_n-input-border-color:var(--n-textarea-border-color, var(--n-color-accent));--_n-input-background:var(--n-textarea-background, var(--n-color-surface))}.n-input[aria-invalid=true]{--_n-input-border-color:var(--n-textarea-border-color, var(--n-color-status-danger))!important}.n-input:disabled,.n-input[readonly],.n-label-container:hover+.n-input-container .n-input:disabled,.n-label-container:hover+.n-input-container .n-input[readonly]{--_n-input-background:var(--n-textarea-background-inactive, var(--n-color-active));--_n-input-border-color:var(--n-textarea-border-color, var(--n-color-active));--_n-input-color:var(--n-textarea-color, var(--n-color-text-weakest))}.n-input[readonly],.n-label-container:hover+.n-input-container .n-input[readonly]{--_n-input-color:var(--n-textarea-color, var(--n-color-text))}.n-input[readonly]:focus{--_n-input-border-color:var(--n-textarea-border-color, var(--n-color-accent))}`;let $=class extends(b(h(d(v(f(m(u(n)))))))){constructor(){super(...arguments),this.inputId="textarea",this.localize=new j(this,{onLangChange:()=>this.handleLangChange()}),this.resize="vertical",this.expand=!1,this.characterCounter=!1}render(){var e;return r`${this.renderLabel()}<div class="n-input-container"><textarea ${s(this.textSelectableRef)} ${s(this.focusableRef)} id="${this.inputId}" class="n-input" ?disabled="${this.disabled}" ?readonly="${this.readonly}" ?required="${this.required}" name="${l(this.name)}" maxlength="${l(this.maxLength)}" .value="${null!==(e=this.value)&&void 0!==e?e:""}" placeholder="${l(this.placeholder)}" @change="${this.handleChange}" @input="${this.handleInput}" @select="${this.handleSelect}" aria-describedby="${l(this.getDescribedBy())}" aria-invalid="${l(this.getInvalid())}" autocomplete="${this.autocomplete}"></textarea> ${this.characterCounter?this.renderCharacterCounter():o}</div>${this.renderError()} ${this.isHintBelow?this.renderHint():o}`}handleSelect(e){e.stopPropagation(),this.dispatchEvent(new p("select"))}renderCharacterCounter(){const{value:e,maxLength:t}=this,o="string"==typeof e?this.lengthMeasurer(e):0,n=t?t-o:null,a=t?`${o}/${t}`:o;return r`<nord-visually-hidden aria-live="polite" aria-atomic="true">${null!=n&&n<=10?this.localize.term("remainingCharacters",n):""}</nord-visually-hidden><div class="n-character-counter">${a}</div>`}handleLangChange(){const e=this.localize.resolvedLang;this.lengthMeasurer=function(e){if(Intl.Segmenter){const t=new Intl.Segmenter(e);return e=>[...t.segment(e)].length}return e=>e.length}(e)}resizeToFitContent(){const e=this.focusableRef.value;if(e)if("auto"===this.resize){e.style.height="auto";const t=getComputedStyle(e),r=e.offsetHeight-e.clientHeight,o=Number.parseFloat(t.minBlockSize)||0,n=Number.parseFloat(t.maxBlockSize)||Number.POSITIVE_INFINITY,a=e.scrollHeight+r;e.style.height=`${Math.min(Math.max(a,o),n)}px`}else e.style.height=""}};$.styles=[g,z,x,y],e([a({reflect:!0})],$.prototype,"resize",void 0),e([a({reflect:!0,type:Boolean})],$.prototype,"expand",void 0),e([a({reflect:!0,attribute:"maxlength",type:Number})],$.prototype,"maxLength",void 0),e([a({reflect:!0,type:Boolean,attribute:"character-counter"})],$.prototype,"characterCounter",void 0),e([c("resize","updated"),c("value","updated")],$.prototype,"resizeToFitContent",null),$=e([i("nord-textarea")],$);var _=$;export{_ as default};
|
|
2
2
|
//# sourceMappingURL=Textarea.js.map
|
package/lib/Textarea.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Textarea.js","sources":["../src/textarea/Textarea.ts"],"sourcesContent":["import { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { ref } from 'lit/directives/ref.js'\nimport { observe } from '../common/decorators/observe.js'\n\nimport { NordEvent } from '../common/events.js'\nimport { AutocompleteMixin } from '../common/mixins/AutocompleteMixin.js'\nimport { FocusableMixin } from '../common/mixins/FocusableMixin.js'\nimport { FormAssociatedMixin } from '../common/mixins/FormAssociatedMixin.js'\nimport { InputMixin } from '../common/mixins/InputMixin.js'\nimport { ReadonlyMixin } from '../common/mixins/ReadonlyMixin.js'\nimport { SizeMixin } from '../common/mixins/SizeMixin.js'\n\nimport { TextSelectableMixin } from '../common/mixins/TextSelectableMixin.js'\nimport componentStyle from '../common/styles/Component.css'\nimport formFieldStyle from '../common/styles/FormField.css'\nimport textFieldStyle from '../common/styles/TextField.css'\nimport { LocalizeController } from '../localization/LocalizeController.js'\nimport style from './Textarea.css'\n\nfunction createLengthMeasurer(locale: string) {\n if (Intl.Segmenter) {\n const segmenter = new Intl.Segmenter(locale)\n return (value: string) => [...segmenter.segment(value)].length\n }\n\n return (value: string) => value.length\n}\n\n/**\n * Textarea is a component that allows user to write text over\n * multiple rows. Used when the expected user input is long.\n * For shorter input, use the Input component.\n *\n * @status ready\n * @category form\n * @slot label - Use when a label requires more than plain text.\n * @slot hint - Optional slot that holds hint text for the textarea.\n * @slot error - Optional slot that holds error text for the textarea.\n *\n * @cssprop [--n-textarea-inline-size=240px] - Controls the inline size, or width, of the textarea.\n * @cssprop [--n-textarea-block-size=76px] - Controls the block size, or height, of the textarea.\n * @cssprop [--n-textarea-background=var(--n-color-active)] - Controls the background of the textarea, using our [color tokens](/tokens/#color).\n * @cssprop [--n-textarea-color=var(--n-color-text)] - Controls the text color of the textarea, using our [color tokens](/tokens/#color).\n * @cssprop [--n-textarea-border-color=var(--n-color-border-strong)] - Controls the border color of the textarea, using our [color tokens](/tokens/#color).\n * @cssprop [--n-textarea-border-radius=var(--n-border-radius-s)] - Controls how rounded the corners are, using [border radius tokens](/tokens/#border-radius).\n * @cssprop [--n-label-color=var(--n-color-text)] - Controls the text color of the label, using our [color tokens](/tokens/#color).\n *\n * @localization remainingCharacters - A function which receives the number of remaining characters and returns a string to be used as the aria-live message.\n */\n@customElement('nord-textarea')\nexport default class Textarea extends SizeMixin(\n FormAssociatedMixin(AutocompleteMixin(ReadonlyMixin(TextSelectableMixin(InputMixin(FocusableMixin(LitElement)))))),\n) {\n static styles = [componentStyle, formFieldStyle, textFieldStyle, style]\n\n protected inputId = 'textarea'\n\n private lengthMeasurer!: (value: string) => number\n private localize = new LocalizeController<'nord-textarea'>(this, {\n onLangChange: () => this.handleLangChange(),\n })\n\n /**\n * Controls whether the textarea is resizable.\n * By default is manually resizable vertically.\n * Set to \"auto\" to enable auto-resizing as content grows.\n */\n @property({ reflect: true }) resize: 'vertical' | 'auto' = 'vertical'\n\n /**\n * Controls whether the textarea expands to fill the width of its container.\n */\n @property({ reflect: true, type: Boolean }) expand = false\n\n /**\n * Controls the max allowed length for the textarea.\n */\n @property({ reflect: true, attribute: 'maxlength', type: Number }) maxLength?: number\n\n /**\n * Controls whether to show a count of the number of characters in the textarea.\n * When combined with `maxlength`, both the count and the max length are shown.\n */\n @property({ reflect: true, type: Boolean, attribute: 'character-counter' }) characterCounter = false\n\n render() {\n return html`\n ${this.renderLabel()}\n\n <div class=\"n-input-container\">\n <textarea\n ${ref(this.textSelectableRef)}\n ${ref(this.focusableRef)}\n id=${this.inputId}\n class=\"n-input\"\n ?disabled=${this.disabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n name=${ifDefined(this.name)}\n maxlength=${ifDefined(this.maxLength)}\n .value=${this.value ?? ''}\n placeholder=${ifDefined(this.placeholder)}\n @change=${this.handleChange}\n @input=${this.handleInput}\n @select=${this.handleSelect}\n aria-describedby=${ifDefined(this.getDescribedBy())}\n aria-invalid=${ifDefined(this.getInvalid())}\n autocomplete=${this.autocomplete as any}\n ></textarea>\n\n ${this.characterCounter ? this.renderCharacterCounter() : nothing}\n </div>\n\n ${this.renderError()}\n ${this.isHintBelow ? this.renderHint() : nothing}\n `\n }\n\n protected handleSelect(e: Event) {\n e.stopPropagation()\n\n /**\n * Fired when some text has been selected.\n */\n this.dispatchEvent(new NordEvent('select'))\n }\n\n private renderCharacterCounter() {\n const { value, maxLength } = this\n const length = typeof value === 'string' ? this.lengthMeasurer(value) : 0\n\n const remainder = maxLength ? maxLength - length : null\n const counter = maxLength ? `${length}/${maxLength}` : length\n\n return html`\n <nord-visually-hidden aria-live=\"polite\" aria-atomic=\"true\">\n ${remainder != null && remainder <= 10 ? this.localize.term('remainingCharacters', remainder) : ''}\n </nord-visually-hidden>\n <div class=\"n-character-counter\">${counter}</div>\n `\n }\n\n private handleLangChange() {\n const lang = this.localize.resolvedLang\n this.lengthMeasurer = createLengthMeasurer(lang)\n }\n\n @observe('resize', 'updated')\n @observe('value', 'updated')\n protected resizeToFitContent() {\n const textarea = this.focusableRef.value\n\n if (!textarea) {\n return\n }\n\n if (this.resize === 'auto') {\n textarea.style.height = 'auto'\n textarea.style.height = `${textarea.scrollHeight}px`\n }\n else {\n textarea.style.height = ''\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-textarea': Textarea\n }\n}\n"],"names":["Textarea","SizeMixin","FormAssociatedMixin","AutocompleteMixin","ReadonlyMixin","TextSelectableMixin","InputMixin","FocusableMixin","LitElement","constructor","this","inputId","localize","LocalizeController","onLangChange","handleLangChange","resize","expand","characterCounter","render","html","renderLabel","ref","textSelectableRef","focusableRef","disabled","readonly","required","ifDefined","name","maxLength","_a","value","placeholder","handleChange","handleInput","handleSelect","getDescribedBy","getInvalid","autocomplete","renderCharacterCounter","nothing","renderError","isHintBelow","renderHint","e","stopPropagation","dispatchEvent","NordEvent","length","lengthMeasurer","remainder","counter","term","lang","resolvedLang","locale","Intl","Segmenter","segmenter","segment","createLengthMeasurer","resizeToFitContent","textarea","style","height","scrollHeight","styles","componentStyle","formFieldStyle","textFieldStyle","__decorate","property","reflect","prototype","type","Boolean","attribute","Number","observe","customElement"],"mappings":"8vGAoDe,IAAMA,EAAN,cAAuBC,EACpCC,EAAoBC,EAAkBC,EAAcC,EAAoBC,EAAWC,EAAeC,UADrF,WAAAC,uBAKHC,KAAOC,QAAG,WAGZD,KAAAE,SAAW,IAAIC,EAAoCH,KAAM,CAC/DI,aAAc,IAAMJ,KAAKK,qBAQEL,KAAMM,OAAwB,WAKfN,KAAMO,QAAG,EAWuBP,KAAgBQ,kBAAG,CAiFhG,CA/EC,MAAAC,SACE,OAAOC,CAAI,GACPV,KAAKW,yDAIDC,EAAIZ,KAAKa,sBACTD,EAAIZ,KAAKc,qBACNd,KAAKC,uCAEED,KAAKe,wBACLf,KAAKgB,wBACLhB,KAAKiB,mBACVC,EAAUlB,KAAKmB,qBACVD,EAAUlB,KAAKoB,uBACR,QAAVC,EAAArB,KAAKsB,aAAK,IAAAD,EAAAA,EAAI,oBACTH,EAAUlB,KAAKuB,0BACnBvB,KAAKwB,yBACNxB,KAAKyB,yBACJzB,KAAK0B,mCACIR,EAAUlB,KAAK2B,oCACnBT,EAAUlB,KAAK4B,gCACf5B,KAAK6B,6BAGpB7B,KAAKQ,iBAAmBR,KAAK8B,yBAA2BC,UAG1D/B,KAAKgC,iBACLhC,KAAKiC,YAAcjC,KAAKkC,aAAeH,GAE5C,CAES,YAAAL,CAAaS,GACrBA,EAAEC,kBAKFpC,KAAKqC,cAAc,IAAIC,EAAU,UAClC,CAEO,sBAAAR,GACN,MAAMR,MAAEA,EAAKF,UAAEA,GAAcpB,KACvBuC,EAA0B,iBAAVjB,EAAqBtB,KAAKwC,eAAelB,GAAS,EAElEmB,EAAYrB,EAAYA,EAAYmB,EAAS,KAC7CG,EAAUtB,EAAY,GAAGmB,KAAUnB,IAAcmB,EAEvD,OAAO7B,CAAI,+DAEQ,MAAb+B,GAAqBA,GAAa,GAAKzC,KAAKE,SAASyC,KAAK,sBAAuBF,GAAa,6DAE/DC,SAEtC,CAEO,gBAAArC,GACN,MAAMuC,EAAO5C,KAAKE,SAAS2C,aAC3B7C,KAAKwC,eA7HT,SAA8BM,GAC5B,GAAIC,KAAKC,UAAW,CAClB,MAAMC,EAAY,IAAIF,KAAKC,UAAUF,GACrC,OAAQxB,GAAkB,IAAI2B,EAAUC,QAAQ5B,IAAQiB,MACzD,CAED,OAAQjB,GAAkBA,EAAMiB,MAClC,CAsH0BY,CAAqBP,EAC5C,CAIS,kBAAAQ,GACR,MAAMC,EAAWrD,KAAKc,aAAaQ,MAE9B+B,IAIe,SAAhBrD,KAAKM,QACP+C,EAASC,MAAMC,OAAS,OACxBF,EAASC,MAAMC,OAAS,GAAGF,EAASG,kBAGpCH,EAASC,MAAMC,OAAS,GAE3B,GA9GMjE,EAAMmE,OAAG,CAACC,EAAgBC,EAAgBC,EAAgBN,GAcpCO,EAAA,CAA5BC,EAAS,CAAEC,SAAS,KAAgDzE,EAAA0E,UAAA,cAAA,GAKzBH,EAAA,CAA3CC,EAAS,CAAEC,SAAS,EAAME,KAAMC,WAAyB5E,EAAA0E,UAAA,cAAA,GAKSH,EAAA,CAAlEC,EAAS,CAAEC,SAAS,EAAMI,UAAW,YAAaF,KAAMG,UAA4B9E,EAAA0E,UAAA,iBAAA,GAMTH,EAAA,CAA3EC,EAAS,CAAEC,SAAS,EAAME,KAAMC,QAASC,UAAW,uBAA+C7E,EAAA0E,UAAA,wBAAA,GAkE1FH,EAAA,CAFTQ,EAAQ,SAAU,WAClBA,EAAQ,QAAS,YAejB/E,EAAA0E,UAAA,qBAAA,MAjHkB1E,EAAQuE,EAAA,CAD5BS,EAAc,kBACMhF,SAAAA"}
|
|
1
|
+
{"version":3,"file":"Textarea.js","sources":["../src/textarea/Textarea.ts"],"sourcesContent":["import { html, LitElement, nothing } from 'lit'\nimport { customElement, property } from 'lit/decorators.js'\nimport { ifDefined } from 'lit/directives/if-defined.js'\nimport { ref } from 'lit/directives/ref.js'\nimport { observe } from '../common/decorators/observe.js'\n\nimport { NordEvent } from '../common/events.js'\nimport { AutocompleteMixin } from '../common/mixins/AutocompleteMixin.js'\nimport { FocusableMixin } from '../common/mixins/FocusableMixin.js'\nimport { FormAssociatedMixin } from '../common/mixins/FormAssociatedMixin.js'\nimport { InputMixin } from '../common/mixins/InputMixin.js'\nimport { ReadonlyMixin } from '../common/mixins/ReadonlyMixin.js'\nimport { SizeMixin } from '../common/mixins/SizeMixin.js'\n\nimport { TextSelectableMixin } from '../common/mixins/TextSelectableMixin.js'\nimport componentStyle from '../common/styles/Component.css'\nimport formFieldStyle from '../common/styles/FormField.css'\nimport textFieldStyle from '../common/styles/TextField.css'\nimport { LocalizeController } from '../localization/LocalizeController.js'\nimport style from './Textarea.css'\n\nfunction createLengthMeasurer(locale: string) {\n if (Intl.Segmenter) {\n const segmenter = new Intl.Segmenter(locale)\n return (value: string) => [...segmenter.segment(value)].length\n }\n\n return (value: string) => value.length\n}\n\n/**\n * Textarea is a component that allows user to write text over\n * multiple rows. Used when the expected user input is long.\n * For shorter input, use the Input component.\n *\n * @status ready\n * @category form\n * @slot label - Use when a label requires more than plain text.\n * @slot hint - Optional slot that holds hint text for the textarea.\n * @slot error - Optional slot that holds error text for the textarea.\n *\n * @cssprop [--n-textarea-inline-size=240px] - Controls the inline size, or width, of the textarea.\n * @cssprop [--n-textarea-block-size=76px] - Controls the minimum block size, or height, of the textarea. When `resize=\"auto\"`, the textarea grows from this height as content is added.\n * @cssprop [--n-textarea-max-block-size=500px] - Controls the maximum block size, or height, of the textarea. When `resize=\"auto\"`, the textarea stops growing at this height and a scrollbar appears.\n * @cssprop [--n-textarea-background=var(--n-color-active)] - Controls the background of the textarea, using our [color tokens](/tokens/#color).\n * @cssprop [--n-textarea-color=var(--n-color-text)] - Controls the text color of the textarea, using our [color tokens](/tokens/#color).\n * @cssprop [--n-textarea-border-color=var(--n-color-border-strong)] - Controls the border color of the textarea, using our [color tokens](/tokens/#color).\n * @cssprop [--n-textarea-border-radius=var(--n-border-radius-s)] - Controls how rounded the corners are, using [border radius tokens](/tokens/#border-radius).\n * @cssprop [--n-label-color=var(--n-color-text)] - Controls the text color of the label, using our [color tokens](/tokens/#color).\n *\n * @localization remainingCharacters - A function which receives the number of remaining characters and returns a string to be used as the aria-live message.\n */\n@customElement('nord-textarea')\nexport default class Textarea extends SizeMixin(\n FormAssociatedMixin(AutocompleteMixin(ReadonlyMixin(TextSelectableMixin(InputMixin(FocusableMixin(LitElement)))))),\n) {\n static styles = [componentStyle, formFieldStyle, textFieldStyle, style]\n\n protected inputId = 'textarea'\n\n private lengthMeasurer!: (value: string) => number\n private localize = new LocalizeController<'nord-textarea'>(this, {\n onLangChange: () => this.handleLangChange(),\n })\n\n /**\n * Controls whether the textarea is resizable.\n * By default is manually resizable vertically.\n * Set to \"auto\" to enable auto-resizing as content grows.\n */\n @property({ reflect: true }) resize: 'vertical' | 'auto' = 'vertical'\n\n /**\n * Controls whether the textarea expands to fill the width of its container.\n */\n @property({ reflect: true, type: Boolean }) expand = false\n\n /**\n * Controls the max allowed length for the textarea.\n */\n @property({ reflect: true, attribute: 'maxlength', type: Number }) maxLength?: number\n\n /**\n * Controls whether to show a count of the number of characters in the textarea.\n * When combined with `maxlength`, both the count and the max length are shown.\n */\n @property({ reflect: true, type: Boolean, attribute: 'character-counter' }) characterCounter = false\n\n render() {\n return html`\n ${this.renderLabel()}\n\n <div class=\"n-input-container\">\n <textarea\n ${ref(this.textSelectableRef)}\n ${ref(this.focusableRef)}\n id=${this.inputId}\n class=\"n-input\"\n ?disabled=${this.disabled}\n ?readonly=${this.readonly}\n ?required=${this.required}\n name=${ifDefined(this.name)}\n maxlength=${ifDefined(this.maxLength)}\n .value=${this.value ?? ''}\n placeholder=${ifDefined(this.placeholder)}\n @change=${this.handleChange}\n @input=${this.handleInput}\n @select=${this.handleSelect}\n aria-describedby=${ifDefined(this.getDescribedBy())}\n aria-invalid=${ifDefined(this.getInvalid())}\n autocomplete=${this.autocomplete as any}\n ></textarea>\n\n ${this.characterCounter ? this.renderCharacterCounter() : nothing}\n </div>\n\n ${this.renderError()}\n ${this.isHintBelow ? this.renderHint() : nothing}\n `\n }\n\n protected handleSelect(e: Event) {\n e.stopPropagation()\n\n /**\n * Fired when some text has been selected.\n */\n this.dispatchEvent(new NordEvent('select'))\n }\n\n private renderCharacterCounter() {\n const { value, maxLength } = this\n const length = typeof value === 'string' ? this.lengthMeasurer(value) : 0\n\n const remainder = maxLength ? maxLength - length : null\n const counter = maxLength ? `${length}/${maxLength}` : length\n\n return html`\n <nord-visually-hidden aria-live=\"polite\" aria-atomic=\"true\">\n ${remainder != null && remainder <= 10 ? this.localize.term('remainingCharacters', remainder) : ''}\n </nord-visually-hidden>\n <div class=\"n-character-counter\">${counter}</div>\n `\n }\n\n private handleLangChange() {\n const lang = this.localize.resolvedLang\n this.lengthMeasurer = createLengthMeasurer(lang)\n }\n\n @observe('resize', 'updated')\n @observe('value', 'updated')\n protected resizeToFitContent() {\n const textarea = this.focusableRef.value\n\n if (!textarea) {\n return\n }\n\n if (this.resize === 'auto') {\n textarea.style.height = 'auto'\n // scrollHeight includes padding but not border. With box-sizing: border-box,\n // setting height directly to scrollHeight would shrink the content area by the\n // border width and clip the last line. Add the border so padding-block is preserved,\n // then clamp the result to the resolved min/max so the inline height never\n // overshoots the cap.\n const computed = getComputedStyle(textarea)\n const borderSize = textarea.offsetHeight - textarea.clientHeight\n const minHeight = Number.parseFloat(computed.minBlockSize) || 0\n const maxHeight = Number.parseFloat(computed.maxBlockSize) || Number.POSITIVE_INFINITY\n const desired = textarea.scrollHeight + borderSize\n\n textarea.style.height = `${Math.min(Math.max(desired, minHeight), maxHeight)}px`\n }\n else {\n textarea.style.height = ''\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-textarea': Textarea\n }\n}\n"],"names":["Textarea","SizeMixin","FormAssociatedMixin","AutocompleteMixin","ReadonlyMixin","TextSelectableMixin","InputMixin","FocusableMixin","LitElement","constructor","this","inputId","localize","LocalizeController","onLangChange","handleLangChange","resize","expand","characterCounter","render","html","renderLabel","ref","textSelectableRef","focusableRef","disabled","readonly","required","ifDefined","name","maxLength","_a","value","placeholder","handleChange","handleInput","handleSelect","getDescribedBy","getInvalid","autocomplete","renderCharacterCounter","nothing","renderError","isHintBelow","renderHint","e","stopPropagation","dispatchEvent","NordEvent","length","lengthMeasurer","remainder","counter","term","lang","resolvedLang","locale","Intl","Segmenter","segmenter","segment","createLengthMeasurer","resizeToFitContent","textarea","style","height","computed","getComputedStyle","borderSize","offsetHeight","clientHeight","minHeight","Number","parseFloat","minBlockSize","maxHeight","maxBlockSize","POSITIVE_INFINITY","desired","scrollHeight","Math","min","max","styles","componentStyle","formFieldStyle","textFieldStyle","__decorate","property","reflect","prototype","type","Boolean","attribute","observe","customElement"],"mappings":"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"}
|
package/lib/Tooltip.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{c as i,l as e,o,f as s,s as n}from"./positioning-D-K8Mueq.js";import{css as r,LitElement as a,html as l}from"lit";import{state as d,property as h,customElement as p}from"lit/decorators.js";import{E as c}from"./EventController-BBOmvfLa.js";import{S as u}from"./SlotController-Z6eG7LSZ.js";import{o as v}from"./observe-D0n0zOfU.js";import{f as m}from"./fsm-Bq5jMQrK.js";import{s as
|
|
1
|
+
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{c as i,l as e,o,f as s,s as n}from"./positioning-D-K8Mueq.js";import{css as r,LitElement as a,html as l}from"lit";import{state as d,property as h,customElement as p}from"lit/decorators.js";import{E as c}from"./EventController-BBOmvfLa.js";import{S as u}from"./SlotController-Z6eG7LSZ.js";import{o as v}from"./observe-D0n0zOfU.js";import{f as m}from"./fsm-Bq5jMQrK.js";import{s as f}from"./Component-DSU3Qp0O.js";function y(t,i){const e=t.getAttribute(i);return e?e.split(/\s+/):[]}function b(t,i,e){t.setAttribute(i,e.join(" "))}const g=r`:host{--_n-tooltip-max-size:var(--n-tooltip-max-size, 50ch);--_n-tooltip-background:rgb(20, 20, 20, 0.95);--_n-tooltip-color:#fff;--_n-tooltip-key-border:rgb(255, 255, 255, 0.03);--_n-tooltip-key-background:rgb(255, 255, 255, 0.1);position:fixed;pointer-events:none;visibility:hidden;opacity:0;transition:opacity var(--n-transition-slowly),visibility var(--n-transition-slowly);transition-timing-function:ease;z-index:var(--n-index-popout);inset:auto;inline-size:auto;block-size:auto;margin:0;padding:0;border:none;background:0 0;overflow:visible;color:inherit}.n-tooltip{gap:var(--n-space-s);font-family:var(--n-font-family);font-size:var(--n-font-size-xs);line-height:var(--n-line-height);color:var(--_n-tooltip-color);padding:calc(var(--n-space-s)/ 1.5) var(--n-space-s);background-color:var(--_n-tooltip-background);border-radius:var(--n-border-radius-s);word-break:break-word;max-inline-size:var(--_n-tooltip-max-size)}.n-tooltip,.n-tooltip-shortcut{display:flex;align-items:center}.n-tooltip-shortcut{gap:2px}::slotted([slot=shortcut]){box-sizing:border-box;margin:0;inline-size:var(--n-size-icon-m);block-size:var(--n-size-icon-m);border-radius:var(--n-border-radius-s);border:1px solid var(--_n-tooltip-key-border)!important;padding:1px!important;text-align:center;font-size:var(--n-font-size-xs);line-height:var(--n-line-height-tight);letter-spacing:-.5px;vertical-align:middle!important;background-color:var(--_n-tooltip-key-background)}[slot=shortcut]::slotted(nord-icon:not([size])){--_n-icon-size:var(--n-size-icon-s)}`;var x;function k(t){return t.nodeType===Node.ELEMENT_NODE}function w(t){var i;const e=null===(i=null==t?void 0:t.focusableRef)||void 0===i?void 0:i.value;return e&&"focusableRef"in e?w(e):e}const{transition:z}=m({hidden:{show:"waiting"},visible:{hide:"hidden",reposition:"positioning",show:"positioning"},waiting:{timeout:"positioning",hide:"hidden"},positioning:{positioned:"visible",hide:"hidden"}});let E=x=class extends a{constructor(){super(...arguments),this.shortcutSlot=new u(this,"shortcut"),this.events=new c(this),this.state="hidden",this.coords=[0,0],this.position="block-start",this.role="tooltip",this.id="",this.delay=500,this.open=!1,this.sideOffset=8,this.alignOffset=0,this.updatePosition=t=>i(t,this,{strategy:"fixed",placement:e(this.position),middleware:[o({mainAxis:this.sideOffset,alignmentAxis:this.alignOffset}),s(),n({padding:8})]}).then((({x:t,y:i})=>{this.coords=[t,i],this.state=z(this.state,"positioned")})),this.hideTooltip=()=>{this.state=z(this.state,"hide")},this.reposition=()=>{this.state=z(this.state,"reposition")},this.handleShow=t=>{const i=function(t,i){return i.id&&k(t)?t.closest(`[aria-describedby~="${CSS.escape(i.id)}"]`):null}(t.target,this);i&&(this.currentElement=i,this.state=z(this.state,"show"))},this.handleHide=t=>{const i=t.target;this.currentElement&&k(i)&&this.currentElement.contains(i)&&this.hideTooltip()},this.hideOnEscape=t=>{"Escape"===t.key&&this.hideTooltip()},this.addDescribedBy=()=>{const t=w(this.currentElement);t&&this.proxy&&(this.proxy.hidden=!0,this.proxy.id=this.id,this.proxy.textContent=this.textContent,t.insertAdjacentElement("afterend",this.proxy),function(t,i,e){const o=y(t,i);o.includes(e)||b(t,i,o.concat(e))}(t,"aria-describedby",this.id))},this.removeDescribedBy=()=>{const t=w(this.currentElement);t&&this.proxy&&(this.proxy.remove(),function(t,i,e){const o=y(t,i);o.includes(e)&&b(t,i,o.filter((t=>t!==e)))}(t,"aria-describedby",this.id))}}connectedCallback(){super.connectedCallback(),this.proxy=document.createElement("span");const t=this.getRootNode();this.events.listen(t,"keydown",this.hideOnEscape),this.events.listen(t,"mouseover",this.handleShow),this.events.listen(t,"focusin",this.handleShow),this.events.listen(t,"mouseout",this.handleHide),this.events.listen(t,"focusout",this.handleHide),this.events.listen(t,"click",this.handleHide,{capture:!0}),this.events.listen(window,"resize",this.reposition,{passive:!0}),this.events.listen(window,"scroll",this.reposition,{passive:!0})}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this.proxy)||void 0===t||t.remove(),this.proxy=void 0}render(){return l`<div class="n-tooltip"><slot></slot><div class="n-tooltip-shortcut" ?hidden="${this.shortcutSlot.isEmpty}"><slot class="n-tooltip-key" name="shortcut"></slot></div></div>`}handleIdChange(){this.id||console.warn("NORD: The tooltip requires an id attribute and value")}handleStateChange(t){var i;switch(this.state){case"hidden":"waiting"===t&&this.timeoutId&&clearTimeout(this.timeoutId),this.removeDescribedBy(),this.currentElement=void 0,this.style.visibility="hidden",this.style.opacity="0",this.open=!1,this.leaveTopLayer();break;case"visible":{this.timeoutId=void 0,x.lastOpened=this,this.addDescribedBy();const[t,i]=this.coords;this.style.left=`${t}px`,this.style.top=`${i}px`,this.style.visibility="visible",this.style.opacity="1",this.open=!0;break}case"waiting":this.timeoutId=setTimeout((()=>{this.state=z(this.state,"timeout")}),this.delay);break;case"positioning":x.lastOpened!==this&&(null===(i=x.lastOpened)||void 0===i||i.hideTooltip()),this.enterTopLayer(),this.currentElement&&this.updatePosition(this.currentElement)}}enterTopLayer(){if("function"==typeof this.showPopover)try{this.setAttribute("popover","manual"),this.showPopover()}catch(t){this.removeAttribute("popover")}}leaveTopLayer(){this.removeAttribute("popover")}};E.styles=[f,g],t([d()],E.prototype,"state",void 0),t([h({reflect:!0})],E.prototype,"position",void 0),t([h({reflect:!0})],E.prototype,"role",void 0),t([h({reflect:!0})],E.prototype,"id",void 0),t([h({reflect:!0,type:Number})],E.prototype,"delay",void 0),t([h({type:Boolean,reflect:!0})],E.prototype,"open",void 0),t([h({reflect:!0,type:Number,attribute:"side-offset"})],E.prototype,"sideOffset",void 0),t([h({reflect:!0,type:Number,attribute:"align-offset"})],E.prototype,"alignOffset",void 0),t([v("id")],E.prototype,"handleIdChange",null),t([v("state")],E.prototype,"handleStateChange",null),E=x=t([p("nord-tooltip")],E);var C=E;export{C as default};
|
|
2
2
|
//# sourceMappingURL=Tooltip.js.map
|
package/lib/Tooltip.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Tooltip.js","sources":["../src/common/attribute.ts","../src/tooltip/Tooltip.ts"],"sourcesContent":["function getTokens(element: Element, attr: string) {\n const value = element.getAttribute(attr)\n return value ? value.split(/\\s+/) : []\n}\n\nfunction setTokens(element: Element, attr: string, tokens: string[]) {\n element.setAttribute(attr, tokens.join(' '))\n}\n\n/**\n * Carefully adds a token to a space-separated attribute\n * Similar to classList, but for any attribute.\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/add\n */\nexport function add(element: Element, attr: string, token: string) {\n const tokens = getTokens(element, attr)\n\n if (!tokens.includes(token)) {\n setTokens(element, attr, tokens.concat(token))\n }\n}\n\n/**\n * Carefully removes a token from a space-separated attribute.\n * Similar to classList, but for any attribute.\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/remove\n */\nexport function remove(element: Element, attr: string, token: string) {\n const tokens = getTokens(element, attr)\n\n if (tokens.includes(token)) {\n setTokens(\n element,\n attr,\n tokens.filter(t => t !== token),\n )\n }\n}\n","import type { States } from '../common/fsm.js'\nimport type { FocusableMixinInterface } from '../common/mixins/FocusableMixin.js'\nimport { computePosition, flip, offset, shift } from '@floating-ui/dom'\n\nimport { html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport * as attr from '../common/attribute.js'\nimport { EventController } from '../common/controllers/EventController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\nimport { observe } from '../common/decorators/observe.js'\nimport { fsm } from '../common/fsm.js'\nimport { logicalToPhysical } from '../common/positioning.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Tooltip.css'\n\n// @ts-expect-error we're being naughty and accessing a protected field!\n// however this means we always get the correct types,\n// and it will ensure this file is not forgotten about if focusable mixin ever changes\ntype FocusableElement = HTMLElement & Pick<FocusableMixinInterface, 'focusableRef'>\n\nfunction isElement(el: Node): el is Element {\n return el.nodeType === Node.ELEMENT_NODE\n}\n\nfunction referencesTooltip(node: Node, tooltip: Tooltip): Element | null {\n if (!tooltip.id || !isElement(node))\n return null\n\n // Walk up so hovering a descendant (icon, span, etc.) still resolves to\n // the ancestor that carries aria-describedby.\n return node.closest(`[aria-describedby~=\"${CSS.escape(tooltip.id)}\"]`)\n}\n\nfunction getFocusable(el?: FocusableElement): HTMLElement | undefined {\n const focusable = el?.focusableRef?.value as HTMLElement | FocusableElement | undefined\n\n if (focusable && 'focusableRef' in focusable) {\n return getFocusable(focusable)\n }\n\n return focusable\n}\n\nconst { transition } = fsm({\n hidden: {\n show: 'waiting',\n },\n visible: {\n hide: 'hidden',\n reposition: 'positioning',\n show: 'positioning',\n },\n waiting: {\n timeout: 'positioning',\n hide: 'hidden',\n },\n positioning: {\n positioned: 'visible',\n hide: 'hidden',\n },\n})\n\ntype TooltipStates = States<typeof transition>\n\n/**\n * Tooltips are floating containers for displaying additional information\n * for the currently focused element. A tooltip can be useful when you want\n * to e.g. give a hint about an existing Command Menu shortcut.\n *\n * @status ready\n * @category overlay\n * @slot - The tooltip content\n * @slot shortcut - Optional slot that holds shortcut keys to access the subject\n * @cssprop [--n-tooltip-max-size=50ch] - Controls the maximum inline size, or width, of the tooltip.\n */\n@customElement('nord-tooltip')\nexport default class Tooltip extends LitElement {\n static styles = [componentStyle, style]\n\n // tracks the last tooltip opened, so we can enforce only one is ever open at a time\n private static lastOpened?: Tooltip\n\n private shortcutSlot = new SlotController(this, 'shortcut')\n private events = new EventController(this)\n\n // The current element which revealed the tooltip shown\n private currentElement?: FocusableElement\n private timeoutId?: ReturnType<typeof setTimeout>\n\n /**\n * the proxy element is for cases where the targetElement is a web component,\n * and the WC has a focusable child in its shadow root e.g. a button component.\n * in this case, when the tooltip is shown, we inject the proxy into targetElement's shadow root\n * and wire up aria-describedby from the focusable element to the proxy.\n * when the tooltip is hidden, we remove the proxy and remove the aria-describedby relationship.\n */\n private proxy?: HTMLSpanElement\n\n /**\n * The current state of the tooltip, dependent on the state machine\n */\n @state() private state: TooltipStates = 'hidden'\n\n // The current coordinates for the tooltip\n private coords: [number, number] = [0, 0]\n\n /**\n * Control the position of the tooltip component.\n * When set to \"none\", the tooltip will be shown above\n * but accommodate for browser boundaries.\n */\n @property({ reflect: true }) position: 'block-end' | 'block-start' | 'inline-start' | 'inline-end' = 'block-start'\n\n /**\n * The tooltip role, set on the component by default.\n */\n\n @property({ reflect: true }) role = 'tooltip'\n\n /**\n * The id for the active element to reference via aria-describedby.\n */\n\n @property({ reflect: true }) id: string = ''\n\n /**\n * The delay in milliseconds before the tooltip is opened.\n */\n @property({ reflect: true, type: Number }) delay: number = 500\n\n /**\n * Indicates whether the tooltip is currently open.\n */\n @property({ type: Boolean, reflect: true }) open: boolean = false\n\n /**\n * Apply all event listeners\n */\n connectedCallback() {\n super.connectedCallback()\n\n this.proxy = document.createElement('span')\n\n const rootNode = this.getRootNode() as Document\n\n this.events.listen(rootNode, 'keydown', this.hideOnEscape)\n\n // we treat mouseover and focusin the same, since they both show tooltip\n this.events.listen(rootNode, 'mouseover', this.handleShow)\n this.events.listen(rootNode, 'focusin', this.handleShow)\n\n // we treat focusout, mouseout, click the same, since they all hide tooltip\n this.events.listen(rootNode, 'mouseout', this.handleHide)\n this.events.listen(rootNode, 'focusout', this.handleHide)\n // we use event capture here to handle cases where e.g. a close button causes its ancestor to be removed from the DOM.\n // in this case the click event will never bubble up to the rootNode, so we never receive it, and the tooltip can remain open\n // by capturing, we get this event first, and can close the tooltip eagerly\n this.events.listen(rootNode, 'click', this.handleHide, { capture: true })\n\n this.events.listen(window, 'resize', this.reposition, { passive: true })\n this.events.listen(window, 'scroll', this.reposition, { passive: true })\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this.proxy?.remove()\n this.proxy = undefined\n }\n\n render() {\n return html`\n <div class=\"n-tooltip\">\n <slot></slot>\n <div class=\"n-tooltip-shortcut\" ?hidden=${this.shortcutSlot.isEmpty}>\n <slot class=\"n-tooltip-key\" name=\"shortcut\"></slot>\n </div>\n </div>\n `\n }\n\n @observe('id')\n protected handleIdChange() {\n if (!this.id) {\n console.warn('NORD: The tooltip requires an id attribute and value')\n }\n }\n\n @observe('state')\n private handleStateChange(prevState: TooltipStates) {\n switch (this.state) {\n case 'hidden': {\n if (prevState === 'waiting' && this.timeoutId) {\n clearTimeout(this.timeoutId)\n }\n\n this.removeDescribedBy()\n this.currentElement = undefined\n this.style.visibility = 'hidden'\n this.style.opacity = '0'\n this.open = false\n this.leaveTopLayer()\n break\n }\n\n case 'visible': {\n this.timeoutId = undefined\n Tooltip.lastOpened = this\n this.addDescribedBy()\n\n const [x, y] = this.coords\n\n // use physical properties here since floating-ui\n // works exclusively in physical dimensions\n // we do all the mapping in logicalToPhysical\n this.style.left = `${x}px`\n this.style.top = `${y}px`\n this.style.visibility = 'visible'\n this.style.opacity = '1'\n this.open = true\n break\n }\n\n case 'waiting': {\n this.timeoutId = setTimeout(() => {\n this.state = transition(this.state, 'timeout')\n }, this.delay)\n break\n }\n\n case 'positioning': {\n if (Tooltip.lastOpened !== this) {\n Tooltip.lastOpened?.hideTooltip()\n }\n\n this.enterTopLayer()\n if (this.currentElement) {\n this.updatePosition(this.currentElement)\n }\n break\n }\n }\n }\n\n /**\n * Setting and updating the position of the tooltip\n */\n private updatePosition = (currentElement: HTMLElement) =>\n computePosition(currentElement, this, {\n strategy: 'fixed',\n placement: logicalToPhysical(this.position),\n middleware: [\n offset(8),\n flip(),\n shift({\n padding: 8,\n }),\n ],\n }).then(({ x, y }) => {\n this.coords = [x, y]\n this.state = transition(this.state, 'positioned')\n })\n\n private hideTooltip = () => {\n this.state = transition(this.state, 'hide')\n }\n\n private reposition = () => {\n this.state = transition(this.state, 'reposition')\n }\n\n private handleShow = (e: Event) => {\n const node = e.target as Node\n const reference = referencesTooltip(node, this) as FocusableElement | null\n\n if (reference) {\n this.currentElement = reference\n this.state = transition(this.state, 'show')\n }\n }\n\n private handleHide = (e: Event) => {\n const node = e.target as Node\n if (this.currentElement && isElement(node) && this.currentElement.contains(node)) {\n this.hideTooltip()\n }\n }\n\n private hideOnEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n this.hideTooltip()\n }\n }\n\n private addDescribedBy = () => {\n const focusable = getFocusable(this.currentElement)\n\n if (focusable && this.proxy) {\n this.proxy.hidden = true\n this.proxy.id = this.id\n this.proxy.textContent = this.textContent\n\n focusable.insertAdjacentElement('afterend', this.proxy)\n attr.add(focusable, 'aria-describedby', this.id)\n }\n }\n\n /**\n * Place the element in the top layer via the Popover API so that\n * position: fixed works relative to the viewport regardless of\n * `overflow: hidden|auto` or `contain` on any ancestor.\n */\n private enterTopLayer() {\n if (typeof this.showPopover !== 'function')\n return\n\n try {\n this.setAttribute('popover', 'manual')\n this.showPopover()\n }\n catch {\n this.removeAttribute('popover')\n }\n }\n\n private leaveTopLayer() {\n this.removeAttribute('popover')\n }\n\n private removeDescribedBy = () => {\n const focusable = getFocusable(this.currentElement)\n\n if (focusable && this.proxy) {\n this.proxy.remove()\n attr.remove(focusable, 'aria-describedby', this.id)\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-tooltip': Tooltip\n }\n}\n"],"names":["getTokens","element","attr","value","getAttribute","split","setTokens","tokens","setAttribute","join","isElement","el","nodeType","Node","ELEMENT_NODE","getFocusable","focusable","_a","focusableRef","transition","fsm","hidden","show","visible","hide","reposition","waiting","timeout","positioning","positioned","Tooltip","Tooltip_1","LitElement","constructor","this","shortcutSlot","SlotController","events","EventController","state","coords","position","role","id","delay","open","updatePosition","currentElement","computePosition","strategy","placement","logicalToPhysical","middleware","offset","flip","shift","padding","then","x","y","hideTooltip","handleShow","e","reference","node","tooltip","closest","CSS","escape","referencesTooltip","target","handleHide","contains","hideOnEscape","key","addDescribedBy","proxy","textContent","insertAdjacentElement","token","includes","concat","attr.add","removeDescribedBy","remove","filter","t","attr.remove","connectedCallback","super","document","createElement","rootNode","getRootNode","listen","capture","window","passive","disconnectedCallback","undefined","render","html","isEmpty","handleIdChange","console","warn","handleStateChange","prevState","timeoutId","clearTimeout","style","visibility","opacity","leaveTopLayer","lastOpened","left","top","setTimeout","enterTopLayer","showPopover","removeAttribute","styles","componentStyle","__decorate","prototype","property","reflect","type","Number","Boolean","observe","customElement"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Tooltip.js","sources":["../src/common/attribute.ts","../src/tooltip/Tooltip.ts"],"sourcesContent":["function getTokens(element: Element, attr: string) {\n const value = element.getAttribute(attr)\n return value ? value.split(/\\s+/) : []\n}\n\nfunction setTokens(element: Element, attr: string, tokens: string[]) {\n element.setAttribute(attr, tokens.join(' '))\n}\n\n/**\n * Carefully adds a token to a space-separated attribute\n * Similar to classList, but for any attribute.\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/add\n */\nexport function add(element: Element, attr: string, token: string) {\n const tokens = getTokens(element, attr)\n\n if (!tokens.includes(token)) {\n setTokens(element, attr, tokens.concat(token))\n }\n}\n\n/**\n * Carefully removes a token from a space-separated attribute.\n * Similar to classList, but for any attribute.\n * @see https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/remove\n */\nexport function remove(element: Element, attr: string, token: string) {\n const tokens = getTokens(element, attr)\n\n if (tokens.includes(token)) {\n setTokens(\n element,\n attr,\n tokens.filter(t => t !== token),\n )\n }\n}\n","import type { States } from '../common/fsm.js'\nimport type { FocusableMixinInterface } from '../common/mixins/FocusableMixin.js'\nimport { computePosition, flip, offset, shift } from '@floating-ui/dom'\n\nimport { html, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport * as attr from '../common/attribute.js'\nimport { EventController } from '../common/controllers/EventController.js'\nimport { SlotController } from '../common/controllers/SlotController.js'\nimport { observe } from '../common/decorators/observe.js'\nimport { fsm } from '../common/fsm.js'\nimport { logicalToPhysical } from '../common/positioning.js'\nimport componentStyle from '../common/styles/Component.css'\nimport style from './Tooltip.css'\n\n// @ts-expect-error we're being naughty and accessing a protected field!\n// however this means we always get the correct types,\n// and it will ensure this file is not forgotten about if focusable mixin ever changes\ntype FocusableElement = HTMLElement & Pick<FocusableMixinInterface, 'focusableRef'>\n\nfunction isElement(el: Node): el is Element {\n return el.nodeType === Node.ELEMENT_NODE\n}\n\nfunction referencesTooltip(node: Node, tooltip: Tooltip): Element | null {\n if (!tooltip.id || !isElement(node))\n return null\n\n // Walk up so hovering a descendant (icon, span, etc.) still resolves to\n // the ancestor that carries aria-describedby.\n return node.closest(`[aria-describedby~=\"${CSS.escape(tooltip.id)}\"]`)\n}\n\nfunction getFocusable(el?: FocusableElement): HTMLElement | undefined {\n const focusable = el?.focusableRef?.value as HTMLElement | FocusableElement | undefined\n\n if (focusable && 'focusableRef' in focusable) {\n return getFocusable(focusable)\n }\n\n return focusable\n}\n\nconst { transition } = fsm({\n hidden: {\n show: 'waiting',\n },\n visible: {\n hide: 'hidden',\n reposition: 'positioning',\n show: 'positioning',\n },\n waiting: {\n timeout: 'positioning',\n hide: 'hidden',\n },\n positioning: {\n positioned: 'visible',\n hide: 'hidden',\n },\n})\n\ntype TooltipStates = States<typeof transition>\n\n/**\n * Tooltips are floating containers for displaying additional information\n * for the currently focused element. A tooltip can be useful when you want\n * to e.g. give a hint about an existing Command Menu shortcut.\n *\n * @status ready\n * @category overlay\n * @slot - The tooltip content\n * @slot shortcut - Optional slot that holds shortcut keys to access the subject\n * @cssprop [--n-tooltip-max-size=50ch] - Controls the maximum inline size, or width, of the tooltip.\n */\n@customElement('nord-tooltip')\nexport default class Tooltip extends LitElement {\n static styles = [componentStyle, style]\n\n // tracks the last tooltip opened, so we can enforce only one is ever open at a time\n private static lastOpened?: Tooltip\n\n private shortcutSlot = new SlotController(this, 'shortcut')\n private events = new EventController(this)\n\n // The current element which revealed the tooltip shown\n private currentElement?: FocusableElement\n private timeoutId?: ReturnType<typeof setTimeout>\n\n /**\n * the proxy element is for cases where the targetElement is a web component,\n * and the WC has a focusable child in its shadow root e.g. a button component.\n * in this case, when the tooltip is shown, we inject the proxy into targetElement's shadow root\n * and wire up aria-describedby from the focusable element to the proxy.\n * when the tooltip is hidden, we remove the proxy and remove the aria-describedby relationship.\n */\n private proxy?: HTMLSpanElement\n\n /**\n * The current state of the tooltip, dependent on the state machine\n */\n @state() private state: TooltipStates = 'hidden'\n\n // The current coordinates for the tooltip\n private coords: [number, number] = [0, 0]\n\n /**\n * Control the position of the tooltip component.\n * When set to \"none\", the tooltip will be shown above\n * but accommodate for browser boundaries.\n */\n @property({ reflect: true }) position: 'block-end' | 'block-start' | 'inline-start' | 'inline-end' = 'block-start'\n\n /**\n * The tooltip role, set on the component by default.\n */\n\n @property({ reflect: true }) role = 'tooltip'\n\n /**\n * The id for the active element to reference via aria-describedby.\n */\n\n @property({ reflect: true }) id: string = ''\n\n /**\n * The delay in milliseconds before the tooltip is opened.\n */\n @property({ reflect: true, type: Number }) delay: number = 500\n\n /**\n * Indicates whether the tooltip is currently open.\n */\n @property({ type: Boolean, reflect: true }) open: boolean = false\n\n /**\n * Distance in pixels from the trigger along the main axis — the gap between\n * the tooltip and the side it opens against. Fed into Floating UI's `offset`\n * middleware as `mainAxis`. Defaults to `8`.\n */\n @property({ reflect: true, type: Number, attribute: 'side-offset' }) sideOffset: number = 8\n\n /**\n * Offset in pixels along the alignment axis — skids the tooltip along the\n * trigger's edge. Fed into Floating UI's `offset` middleware as\n * `alignmentAxis`. Defaults to `0`.\n */\n @property({ reflect: true, type: Number, attribute: 'align-offset' }) alignOffset: number = 0\n\n /**\n * Apply all event listeners\n */\n connectedCallback() {\n super.connectedCallback()\n\n this.proxy = document.createElement('span')\n\n const rootNode = this.getRootNode() as Document\n\n this.events.listen(rootNode, 'keydown', this.hideOnEscape)\n\n // we treat mouseover and focusin the same, since they both show tooltip\n this.events.listen(rootNode, 'mouseover', this.handleShow)\n this.events.listen(rootNode, 'focusin', this.handleShow)\n\n // we treat focusout, mouseout, click the same, since they all hide tooltip\n this.events.listen(rootNode, 'mouseout', this.handleHide)\n this.events.listen(rootNode, 'focusout', this.handleHide)\n // we use event capture here to handle cases where e.g. a close button causes its ancestor to be removed from the DOM.\n // in this case the click event will never bubble up to the rootNode, so we never receive it, and the tooltip can remain open\n // by capturing, we get this event first, and can close the tooltip eagerly\n this.events.listen(rootNode, 'click', this.handleHide, { capture: true })\n\n this.events.listen(window, 'resize', this.reposition, { passive: true })\n this.events.listen(window, 'scroll', this.reposition, { passive: true })\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n\n this.proxy?.remove()\n this.proxy = undefined\n }\n\n render() {\n return html`\n <div class=\"n-tooltip\">\n <slot></slot>\n <div class=\"n-tooltip-shortcut\" ?hidden=${this.shortcutSlot.isEmpty}>\n <slot class=\"n-tooltip-key\" name=\"shortcut\"></slot>\n </div>\n </div>\n `\n }\n\n @observe('id')\n protected handleIdChange() {\n if (!this.id) {\n console.warn('NORD: The tooltip requires an id attribute and value')\n }\n }\n\n @observe('state')\n private handleStateChange(prevState: TooltipStates) {\n switch (this.state) {\n case 'hidden': {\n if (prevState === 'waiting' && this.timeoutId) {\n clearTimeout(this.timeoutId)\n }\n\n this.removeDescribedBy()\n this.currentElement = undefined\n this.style.visibility = 'hidden'\n this.style.opacity = '0'\n this.open = false\n this.leaveTopLayer()\n break\n }\n\n case 'visible': {\n this.timeoutId = undefined\n Tooltip.lastOpened = this\n this.addDescribedBy()\n\n const [x, y] = this.coords\n\n // use physical properties here since floating-ui\n // works exclusively in physical dimensions\n // we do all the mapping in logicalToPhysical.\n this.style.left = `${x}px`\n this.style.top = `${y}px`\n this.style.visibility = 'visible'\n this.style.opacity = '1'\n this.open = true\n break\n }\n\n case 'waiting': {\n this.timeoutId = setTimeout(() => {\n this.state = transition(this.state, 'timeout')\n }, this.delay)\n break\n }\n\n case 'positioning': {\n if (Tooltip.lastOpened !== this) {\n Tooltip.lastOpened?.hideTooltip()\n }\n\n this.enterTopLayer()\n if (this.currentElement) {\n this.updatePosition(this.currentElement)\n }\n break\n }\n }\n }\n\n /**\n * Setting and updating the position of the tooltip\n */\n private updatePosition = (currentElement: HTMLElement) =>\n computePosition(currentElement, this, {\n strategy: 'fixed',\n placement: logicalToPhysical(this.position),\n middleware: [\n offset({ mainAxis: this.sideOffset, alignmentAxis: this.alignOffset }),\n flip(),\n shift({\n padding: 8,\n }),\n ],\n }).then(({ x, y }) => {\n this.coords = [x, y]\n this.state = transition(this.state, 'positioned')\n })\n\n private hideTooltip = () => {\n this.state = transition(this.state, 'hide')\n }\n\n private reposition = () => {\n this.state = transition(this.state, 'reposition')\n }\n\n private handleShow = (e: Event) => {\n const node = e.target as Node\n const reference = referencesTooltip(node, this) as FocusableElement | null\n\n if (reference) {\n this.currentElement = reference\n this.state = transition(this.state, 'show')\n }\n }\n\n private handleHide = (e: Event) => {\n const node = e.target as Node\n if (this.currentElement && isElement(node) && this.currentElement.contains(node)) {\n this.hideTooltip()\n }\n }\n\n private hideOnEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape') {\n this.hideTooltip()\n }\n }\n\n private addDescribedBy = () => {\n const focusable = getFocusable(this.currentElement)\n\n if (focusable && this.proxy) {\n this.proxy.hidden = true\n this.proxy.id = this.id\n this.proxy.textContent = this.textContent\n\n focusable.insertAdjacentElement('afterend', this.proxy)\n attr.add(focusable, 'aria-describedby', this.id)\n }\n }\n\n /**\n * Place the element in the top layer via the Popover API so that\n * position: fixed works relative to the viewport regardless of\n * `overflow: hidden|auto` or `contain` on any ancestor.\n */\n private enterTopLayer() {\n if (typeof this.showPopover !== 'function')\n return\n\n try {\n this.setAttribute('popover', 'manual')\n this.showPopover()\n }\n catch {\n this.removeAttribute('popover')\n }\n }\n\n private leaveTopLayer() {\n this.removeAttribute('popover')\n }\n\n private removeDescribedBy = () => {\n const focusable = getFocusable(this.currentElement)\n\n if (focusable && this.proxy) {\n this.proxy.remove()\n attr.remove(focusable, 'aria-describedby', this.id)\n }\n }\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-tooltip': Tooltip\n }\n}\n"],"names":["getTokens","element","attr","value","getAttribute","split","setTokens","tokens","setAttribute","join","isElement","el","nodeType","Node","ELEMENT_NODE","getFocusable","focusable","_a","focusableRef","transition","fsm","hidden","show","visible","hide","reposition","waiting","timeout","positioning","positioned","Tooltip","Tooltip_1","LitElement","constructor","this","shortcutSlot","SlotController","events","EventController","state","coords","position","role","id","delay","open","sideOffset","alignOffset","updatePosition","currentElement","computePosition","strategy","placement","logicalToPhysical","middleware","offset","mainAxis","alignmentAxis","flip","shift","padding","then","x","y","hideTooltip","handleShow","e","reference","node","tooltip","closest","CSS","escape","referencesTooltip","target","handleHide","contains","hideOnEscape","key","addDescribedBy","proxy","textContent","insertAdjacentElement","token","includes","concat","attr.add","removeDescribedBy","remove","filter","t","attr.remove","connectedCallback","super","document","createElement","rootNode","getRootNode","listen","capture","window","passive","disconnectedCallback","undefined","render","html","isEmpty","handleIdChange","console","warn","handleStateChange","prevState","timeoutId","clearTimeout","style","visibility","opacity","leaveTopLayer","lastOpened","left","top","setTimeout","enterTopLayer","showPopover","removeAttribute","styles","componentStyle","__decorate","prototype","property","reflect","type","Number","Boolean","attribute","observe","customElement"],"mappings":"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"}
|
package/lib/Truncate.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{css as e,LitElement as i,isServer as r,html as s}from"lit";import{state as o,property as n,customElement as l}from"lit/decorators.js";import{createRef as a,ref as c}from"lit/directives/ref.js";import"./Tooltip.js";import"./positioning-D-K8Mueq.js";import"./EventController-BBOmvfLa.js";import"./SlotController-Z6eG7LSZ.js";import"./observe-D0n0zOfU.js";import"./fsm-Bq5jMQrK.js";import"./Component-DSU3Qp0O.js";const d=e`:host{display:inline-block;inline-size:100%;max-inline-size:100%;min-inline-size:0;vertical-align:bottom}.n-truncate{display:block;white-space:nowrap;overflow:hidden}:host([line-clamp]) .n-truncate{white-space:normal;max-block-size:calc(var(--_n-truncate-line-clamp,2) * 1lh)}.n-truncate-source{display:none}`;let p=0;let h=class extends i{constructor(){super(...arguments),this.spanRef=a(),this.tooltipId="n-truncate-"+ ++p,this.currentTrigger=null,this.isTruncated=!1,this.text="",this.position="block-start",this.delay=500,this.lineClamp=1,this.noTooltip=!1}connectedCallback(){super.connectedCallback(),r||(this.observer=new ResizeObserver((()=>{requestAnimationFrame((()=>this.measure()))})),this.contentObserver=new MutationObserver((()=>{this.captureText(),this.measure()})),this.contentObserver.observe(this,{characterData:!0,childList:!0,subtree:!0}),this.captureText())}firstUpdated(){const t=this.spanRef.value;t&&this.observer&&this.observer.observe(t)}updated(){this.syncTooltip()}disconnectedCallback(){var t,e,i;super.disconnectedCallback(),null===(t=this.observer)||void 0===t||t.disconnect(),this.observer=void 0,null===(e=this.contentObserver)||void 0===e||e.disconnect(),this.contentObserver=void 0,null===(i=this.tooltipElement)||void 0===i||i.remove(),this.tooltipElement=void 0,this.clearTrigger()}captureText(){var t;const e=(null!==(t=this.textContent)&&void 0!==t?t:"").trim();e!==this.text&&(this.text=e)}measure(){const t=this.spanRef.value;if(!t)return;const e=this.text;if(!e)return t.textContent="",void(this.isTruncated=!1);if(t.textContent=e,this.fits(t))return void(this.isTruncated=!1);let i=0,r=e.length;for(;i<r;){const s=Math.ceil((i+r)/2);t.textContent=m(e.slice(0,s))+"…",this.fits(t)?i=s:r=s-1}t.textContent=m(e.slice(0,i))+"…",this.isTruncated=!0}fits(t){return this.lineClamp>1?t.scrollHeight<=t.clientHeight:t.scrollWidth<=t.clientWidth}findTriggerElement(){let t=this;for(;t&&t!==document.body&&t!==document.documentElement;){if("none"!==getComputedStyle(t).pointerEvents)return t;t=t.parentElement}return this}addDescribedById(t,e){const i=t.getAttribute("aria-describedby"),r=i?i.split(/\s+/).filter(Boolean):[];r.includes(e)||(r.push(e),t.setAttribute("aria-describedby",r.join(" ")))}removeDescribedById(t,e){const i=t.getAttribute("aria-describedby");if(!i)return;const r=i.split(/\s+/).filter(Boolean).filter((t=>t!==e));r.length?t.setAttribute("aria-describedby",r.join(" ")):t.removeAttribute("aria-describedby")}clearTrigger(){this.currentTrigger&&(this.removeDescribedById(this.currentTrigger,this.tooltipId),this.currentTrigger=null)}syncTooltip(){var t,e;if(!this.isTruncated||!this.text)return this.removeAttribute("truncated"),null===(t=this.tooltipElement)||void 0===t||t.remove(),this.tooltipElement=void 0,void this.clearTrigger();if(this.setAttribute("truncated",""),this.noTooltip)return null===(e=this.tooltipElement)||void 0===e||e.remove(),this.tooltipElement=void 0,void this.clearTrigger();this.tooltipElement||(this.tooltipElement=document.createElement("nord-tooltip"),this.tooltipElement.id=this.tooltipId,document.body.appendChild(this.tooltipElement)),this.tooltipElement.setAttribute("position",this.position),this.tooltipElement.setAttribute("delay",String(this.delay)),this.tooltipElement.textContent=this.text;const i=this.findTriggerElement();i!==this.currentTrigger&&(this.currentTrigger&&this.removeDescribedById(this.currentTrigger,this.tooltipId),this.currentTrigger=i),this.addDescribedById(i,this.tooltipId)}willUpdate(t){t.has("lineClamp")&&(this.lineClamp>1?(this.setAttribute("line-clamp",String(this.lineClamp)),this.style.setProperty("--_n-truncate-line-clamp",String(this.lineClamp))):(this.removeAttribute("line-clamp"),this.style.removeProperty("--_n-truncate-line-clamp")))}render(){return s`<span ${c(this.spanRef)} class="n-truncate"></span><slot class="n-truncate-source" aria-hidden="true"></slot>`}};h.styles=d,t([o()],h.prototype,"isTruncated",void 0),t([o()],h.prototype,"text",void 0),t([n({reflect:!0})],h.prototype,"position",void 0),t([n({reflect:!0,type:Number})],h.prototype,"delay",void 0),t([n({type:Number,attribute:"line-clamp"})],h.prototype,"lineClamp",void 0),t([n({type:Boolean,reflect:!0,attribute:"no-tooltip"})],h.prototype,"noTooltip",void 0),h=t([l("nord-truncate")],h);var u=h;function m(t){return t.replace(/[\s.,;:!?-]+$/u,"")}export{u as default};
|
|
1
|
+
import{_ as t}from"./tslib.es6-CmLYFWVC.js";import{css as e,LitElement as i,isServer as r,html as s}from"lit";import{state as o,property as n,customElement as l}from"lit/decorators.js";import{createRef as a,ref as c}from"lit/directives/ref.js";import"./Tooltip.js";import"./positioning-D-K8Mueq.js";import"./EventController-BBOmvfLa.js";import"./SlotController-Z6eG7LSZ.js";import"./observe-D0n0zOfU.js";import"./fsm-Bq5jMQrK.js";import"./Component-DSU3Qp0O.js";const d=e`:host{display:inline-block;inline-size:100%;max-inline-size:100%;min-inline-size:0;vertical-align:bottom}.n-truncate{display:block;white-space:nowrap;overflow:hidden}:host([line-clamp]) .n-truncate{white-space:normal;max-block-size:calc(var(--_n-truncate-line-clamp,2) * 1lh)}.n-truncate-source{display:none}`;let p=0;let h=class extends i{constructor(){super(...arguments),this.spanRef=a(),this.tooltipId="n-truncate-"+ ++p,this.currentTrigger=null,this.isTruncated=!1,this.text="",this.position="block-start",this.delay=500,this.sideOffset=8,this.alignOffset=0,this.lineClamp=1,this.noTooltip=!1}connectedCallback(){super.connectedCallback(),r||(this.observer=new ResizeObserver((()=>{requestAnimationFrame((()=>this.measure()))})),this.contentObserver=new MutationObserver((()=>{this.captureText(),this.measure()})),this.contentObserver.observe(this,{characterData:!0,childList:!0,subtree:!0}),this.captureText())}firstUpdated(){const t=this.spanRef.value;t&&this.observer&&this.observer.observe(t)}updated(){this.syncTooltip()}disconnectedCallback(){var t,e,i;super.disconnectedCallback(),null===(t=this.observer)||void 0===t||t.disconnect(),this.observer=void 0,null===(e=this.contentObserver)||void 0===e||e.disconnect(),this.contentObserver=void 0,null===(i=this.tooltipElement)||void 0===i||i.remove(),this.tooltipElement=void 0,this.clearTrigger()}captureText(){var t;const e=(null!==(t=this.textContent)&&void 0!==t?t:"").trim();e!==this.text&&(this.text=e)}measure(){const t=this.spanRef.value;if(!t)return;const e=this.text;if(!e)return t.textContent="",void(this.isTruncated=!1);if(t.textContent=e,this.fits(t))return void(this.isTruncated=!1);let i=0,r=e.length;for(;i<r;){const s=Math.ceil((i+r)/2);t.textContent=m(e.slice(0,s))+"…",this.fits(t)?i=s:r=s-1}t.textContent=m(e.slice(0,i))+"…",this.isTruncated=!0}fits(t){return this.lineClamp>1?t.scrollHeight<=t.clientHeight:t.scrollWidth<=t.clientWidth}findTriggerElement(){let t=this;for(;t&&t!==document.body&&t!==document.documentElement;){if("none"!==getComputedStyle(t).pointerEvents)return t;t=t.parentElement}return this}addDescribedById(t,e){const i=t.getAttribute("aria-describedby"),r=i?i.split(/\s+/).filter(Boolean):[];r.includes(e)||(r.push(e),t.setAttribute("aria-describedby",r.join(" ")))}removeDescribedById(t,e){const i=t.getAttribute("aria-describedby");if(!i)return;const r=i.split(/\s+/).filter(Boolean).filter((t=>t!==e));r.length?t.setAttribute("aria-describedby",r.join(" ")):t.removeAttribute("aria-describedby")}clearTrigger(){this.currentTrigger&&(this.removeDescribedById(this.currentTrigger,this.tooltipId),this.currentTrigger=null)}syncTooltip(){var t,e;if(!this.isTruncated||!this.text)return this.removeAttribute("truncated"),null===(t=this.tooltipElement)||void 0===t||t.remove(),this.tooltipElement=void 0,void this.clearTrigger();if(this.setAttribute("truncated",""),this.noTooltip)return null===(e=this.tooltipElement)||void 0===e||e.remove(),this.tooltipElement=void 0,void this.clearTrigger();this.tooltipElement||(this.tooltipElement=document.createElement("nord-tooltip"),this.tooltipElement.id=this.tooltipId,document.body.appendChild(this.tooltipElement)),this.tooltipElement.setAttribute("position",this.position),this.tooltipElement.setAttribute("delay",String(this.delay)),this.tooltipElement.setAttribute("side-offset",String(this.sideOffset)),this.tooltipElement.setAttribute("align-offset",String(this.alignOffset)),this.tooltipElement.textContent=this.text;const i=this.findTriggerElement();i!==this.currentTrigger&&(this.currentTrigger&&this.removeDescribedById(this.currentTrigger,this.tooltipId),this.currentTrigger=i),this.addDescribedById(i,this.tooltipId)}willUpdate(t){t.has("lineClamp")&&(this.lineClamp>1?(this.setAttribute("line-clamp",String(this.lineClamp)),this.style.setProperty("--_n-truncate-line-clamp",String(this.lineClamp))):(this.removeAttribute("line-clamp"),this.style.removeProperty("--_n-truncate-line-clamp")))}render(){return s`<span ${c(this.spanRef)} class="n-truncate"></span><slot class="n-truncate-source" aria-hidden="true"></slot>`}};h.styles=d,t([o()],h.prototype,"isTruncated",void 0),t([o()],h.prototype,"text",void 0),t([n({reflect:!0})],h.prototype,"position",void 0),t([n({reflect:!0,type:Number})],h.prototype,"delay",void 0),t([n({reflect:!0,type:Number,attribute:"side-offset"})],h.prototype,"sideOffset",void 0),t([n({reflect:!0,type:Number,attribute:"align-offset"})],h.prototype,"alignOffset",void 0),t([n({type:Number,attribute:"line-clamp"})],h.prototype,"lineClamp",void 0),t([n({type:Boolean,reflect:!0,attribute:"no-tooltip"})],h.prototype,"noTooltip",void 0),h=t([l("nord-truncate")],h);var u=h;function m(t){return t.replace(/[\s.,;:!?-]+$/u,"")}export{u as default};
|
|
2
2
|
//# sourceMappingURL=Truncate.js.map
|
package/lib/Truncate.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Truncate.js","sources":["../src/truncate/Truncate.ts"],"sourcesContent":["import type { PropertyValues } from 'lit'\nimport { html, isServer, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { createRef, ref } from 'lit/directives/ref.js'\nimport style from './Truncate.css'\nimport '../tooltip/Tooltip.js'\n\nlet counter = 0\n\nconst ELLIPSIS = '…'\n\n/**\n * Truncate clips text with a single- or multi-line ellipsis and reveals a tooltip with the full text when the content overflows.\n *\n * @status ready\n * @category text\n * @slot - The full text content. Plain text is recommended; rich markup is\n * not supported (only `textContent` is read for truncation).\n */\n@customElement('nord-truncate')\nexport default class Truncate extends LitElement {\n static styles = style\n\n private spanRef = createRef<HTMLSpanElement>()\n private observer?: ResizeObserver\n private contentObserver?: MutationObserver\n private tooltipId = `n-truncate-${++counter}`\n // The tooltip lives in document.body (not in shadow DOM) so its\n // root-level mouseover listener can see hovers on the trigger element,\n // and so aria-describedby can resolve across the same document tree.\n private tooltipElement?: HTMLElement\n // The element that actually receives pointer events (often this host, but\n // when slotted into nord-button — which sets `::slotted(*) { pointer-events:\n // none }` — it resolves to the nord-button itself). aria-describedby is set\n // on this element so the tooltip's `closest()` lookup succeeds.\n private currentTrigger: Element | null = null\n\n @state() private isTruncated = false\n @state() private text = ''\n\n /**\n * Position of the tooltip relative to the truncated text. Mirrors `nord-tooltip`.\n */\n @property({ reflect: true })\n position: 'block-end' | 'block-start' | 'inline-start' | 'inline-end' = 'block-start'\n\n /**\n * Delay in milliseconds before the tooltip opens.\n */\n @property({ reflect: true, type: Number })\n delay = 500\n\n /**\n * Maximum number of lines before truncating. Defaults to `1` (single line).\n * Values `>= 2` allow the text to wrap up to that many lines before the\n * ellipsis kicks in.\n */\n @property({ type: Number, attribute: 'line-clamp' })\n lineClamp = 1\n\n /**\n * When set, the component still truncates the text but does not render\n * a hover tooltip with the full content. The `truncated` attribute is\n * still reflected so consumers can style it.\n */\n @property({ type: Boolean, reflect: true, attribute: 'no-tooltip' })\n noTooltip = false\n\n connectedCallback() {\n super.connectedCallback()\n\n if (isServer)\n return\n\n this.observer = new ResizeObserver(() => {\n // Defer to next frame so the browser has finished the layout pass\n // triggered by the resize before we read geometry.\n requestAnimationFrame(() => this.measure())\n })\n // Catch content changes the ResizeObserver can't see: text-only mutations\n // via reactive bindings (Lit/Vue/React often mutate characterData), or\n // children being replaced. Without this, swapping the label to a\n // longer/shorter string after first render would never re-evaluate\n // truncation.\n this.contentObserver = new MutationObserver(() => {\n this.captureText()\n this.measure()\n })\n this.contentObserver.observe(this, {\n characterData: true,\n childList: true,\n subtree: true,\n })\n this.captureText()\n }\n\n protected firstUpdated() {\n const span = this.spanRef.value\n if (span && this.observer) {\n this.observer.observe(span)\n }\n }\n\n protected updated() {\n this.syncTooltip()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.observer?.disconnect()\n this.observer = undefined\n this.contentObserver?.disconnect()\n this.contentObserver = undefined\n this.tooltipElement?.remove()\n this.tooltipElement = undefined\n this.clearTrigger()\n }\n\n private captureText() {\n const next = (this.textContent ?? '').trim()\n if (next !== this.text) {\n this.text = next\n }\n }\n\n /**\n * Measure overflow and, if needed, binary-search the longest prefix of the\n * full text that fits in the available space, then commit `<prefix>…` as\n * the visible text. Same algorithm as Ant Design Pro's `Ellipsis`.\n */\n private measure() {\n const span = this.spanRef.value\n if (!span)\n return\n\n const fullText = this.text\n if (!fullText) {\n span.textContent = ''\n this.isTruncated = false\n return\n }\n\n // Reset to full text and check whether it already fits.\n span.textContent = fullText\n if (this.fits(span)) {\n this.isTruncated = false\n return\n }\n\n // Binary search: largest prefix length such that `prefix + …` fits.\n let lo = 0\n let hi = fullText.length\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n span.textContent = trimEnd(fullText.slice(0, mid)) + ELLIPSIS\n if (this.fits(span)) {\n lo = mid\n }\n else {\n hi = mid - 1\n }\n }\n\n span.textContent = trimEnd(fullText.slice(0, lo)) + ELLIPSIS\n this.isTruncated = true\n }\n\n private fits(span: HTMLElement): boolean {\n if (this.lineClamp > 1) {\n return span.scrollHeight <= span.clientHeight\n }\n return span.scrollWidth <= span.clientWidth\n }\n\n /**\n * Walk up the DOM until we find an ancestor that actually receives pointer\n * events. Needed because `nord-button` (and similar) set\n * `::slotted(*) { pointer-events: none }`, so this host can never see hover.\n * Falls back to this host if no such ancestor is found.\n */\n private findTriggerElement(): Element {\n // pointer-events walk from the host itself so a standalone truncate\n // (default pointer-events: auto) is correctly returned as the trigger.\n // eslint-disable-next-line ts/no-this-alias -- intentional: start the\n let element: Element | null = this\n while (element && element !== document.body && element !== document.documentElement) {\n if (getComputedStyle(element).pointerEvents !== 'none') {\n return element\n }\n element = element.parentElement\n }\n return this\n }\n\n private addDescribedById(element: Element, id: string) {\n const existing = element.getAttribute('aria-describedby')\n const ids = existing ? existing.split(/\\s+/).filter(Boolean) : []\n if (!ids.includes(id)) {\n ids.push(id)\n element.setAttribute('aria-describedby', ids.join(' '))\n }\n }\n\n private removeDescribedById(element: Element, id: string) {\n const existing = element.getAttribute('aria-describedby')\n if (!existing)\n return\n const ids = existing.split(/\\s+/).filter(Boolean).filter(x => x !== id)\n if (ids.length) {\n element.setAttribute('aria-describedby', ids.join(' '))\n }\n else {\n element.removeAttribute('aria-describedby')\n }\n }\n\n private clearTrigger() {\n if (this.currentTrigger) {\n this.removeDescribedById(this.currentTrigger, this.tooltipId)\n this.currentTrigger = null\n }\n }\n\n private syncTooltip() {\n if (!this.isTruncated || !this.text) {\n this.removeAttribute('truncated')\n this.tooltipElement?.remove()\n this.tooltipElement = undefined\n this.clearTrigger()\n return\n }\n\n this.setAttribute('truncated', '')\n\n if (this.noTooltip) {\n this.tooltipElement?.remove()\n this.tooltipElement = undefined\n this.clearTrigger()\n return\n }\n\n if (!this.tooltipElement) {\n this.tooltipElement = document.createElement('nord-tooltip')\n this.tooltipElement.id = this.tooltipId\n document.body.appendChild(this.tooltipElement)\n }\n this.tooltipElement.setAttribute('position', this.position)\n this.tooltipElement.setAttribute('delay', String(this.delay))\n this.tooltipElement.textContent = this.text\n\n const trigger = this.findTriggerElement()\n if (trigger !== this.currentTrigger) {\n if (this.currentTrigger) {\n this.removeDescribedById(this.currentTrigger, this.tooltipId)\n }\n this.currentTrigger = trigger\n }\n this.addDescribedById(trigger, this.tooltipId)\n }\n\n protected willUpdate(changed: PropertyValues) {\n if (changed.has('lineClamp')) {\n if (this.lineClamp > 1) {\n this.setAttribute('line-clamp', String(this.lineClamp))\n this.style.setProperty('--_n-truncate-line-clamp', String(this.lineClamp))\n }\n else {\n this.removeAttribute('line-clamp')\n this.style.removeProperty('--_n-truncate-line-clamp')\n }\n }\n }\n\n render() {\n // The visible truncated text lives in a dedicated shadow-DOM span whose\n // `textContent` is managed imperatively in `measure()` (binary-search\n // updates would otherwise destroy Lit's ChildPart markers). The slot,\n // hidden visually, keeps the full text in light DOM so that assistive\n // tech and copy/paste of the host element still see the complete content.\n return html`\n <span ${ref(this.spanRef)} class=\"n-truncate\"></span>\n <slot class=\"n-truncate-source\" aria-hidden=\"true\"></slot>\n `\n }\n}\n\nfunction trimEnd(input: string): string {\n return input.replace(/[\\s.,;:!?-]+$/u, '')\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-truncate': Truncate\n }\n}\n"],"names":["counter","Truncate","LitElement","constructor","this","spanRef","createRef","tooltipId","currentTrigger","isTruncated","text","position","delay","lineClamp","noTooltip","connectedCallback","super","isServer","observer","ResizeObserver","requestAnimationFrame","measure","contentObserver","MutationObserver","captureText","observe","characterData","childList","subtree","firstUpdated","span","value","updated","syncTooltip","disconnectedCallback","_a","disconnect","undefined","_b","_c","tooltipElement","remove","clearTrigger","next","textContent","trim","fullText","fits","lo","hi","length","mid","Math","ceil","trimEnd","slice","scrollHeight","clientHeight","scrollWidth","clientWidth","findTriggerElement","element","document","body","documentElement","getComputedStyle","pointerEvents","parentElement","addDescribedById","id","existing","getAttribute","ids","split","filter","Boolean","includes","push","setAttribute","join","removeDescribedById","x","removeAttribute","createElement","appendChild","String","trigger","willUpdate","changed","has","style","setProperty","removeProperty","render","html","ref","styles","__decorate","state","prototype","property","reflect","type","Number","attribute","customElement","input","replace"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Truncate.js","sources":["../src/truncate/Truncate.ts"],"sourcesContent":["import type { PropertyValues } from 'lit'\nimport { html, isServer, LitElement } from 'lit'\nimport { customElement, property, state } from 'lit/decorators.js'\nimport { createRef, ref } from 'lit/directives/ref.js'\nimport style from './Truncate.css'\nimport '../tooltip/Tooltip.js'\n\nlet counter = 0\n\nconst ELLIPSIS = '…'\n\n/**\n * Truncate clips text with a single- or multi-line ellipsis and reveals a tooltip with the full text when the content overflows.\n *\n * @status ready\n * @category text\n * @slot - The full text content. Plain text is recommended; rich markup is\n * not supported (only `textContent` is read for truncation).\n */\n@customElement('nord-truncate')\nexport default class Truncate extends LitElement {\n static styles = style\n\n private spanRef = createRef<HTMLSpanElement>()\n private observer?: ResizeObserver\n private contentObserver?: MutationObserver\n private tooltipId = `n-truncate-${++counter}`\n // The tooltip lives in document.body (not in shadow DOM) so its\n // root-level mouseover listener can see hovers on the trigger element,\n // and so aria-describedby can resolve across the same document tree.\n private tooltipElement?: HTMLElement\n // The element that actually receives pointer events (often this host, but\n // when slotted into nord-button — which sets `::slotted(*) { pointer-events:\n // none }` — it resolves to the nord-button itself). aria-describedby is set\n // on this element so the tooltip's `closest()` lookup succeeds.\n private currentTrigger: Element | null = null\n\n @state() private isTruncated = false\n @state() private text = ''\n\n /**\n * Position of the tooltip relative to the truncated text. Mirrors `nord-tooltip`.\n */\n @property({ reflect: true })\n position: 'block-end' | 'block-start' | 'inline-start' | 'inline-end' = 'block-start'\n\n /**\n * Delay in milliseconds before the tooltip opens.\n */\n @property({ reflect: true, type: Number })\n delay = 500\n\n /**\n * Distance in pixels between the tooltip and the trigger along the main axis.\n * Mirrors `nord-tooltip`'s `side-offset` and is forwarded to the portaled\n * tooltip. Defaults to `8`.\n */\n @property({ reflect: true, type: Number, attribute: 'side-offset' })\n sideOffset = 8\n\n /**\n * Offset in pixels along the tooltip's alignment axis. Mirrors\n * `nord-tooltip`'s `align-offset` and is forwarded to the portaled tooltip.\n * Defaults to `0`.\n */\n @property({ reflect: true, type: Number, attribute: 'align-offset' })\n alignOffset = 0\n\n /**\n * Maximum number of lines before truncating. Defaults to `1` (single line).\n * Values `>= 2` allow the text to wrap up to that many lines before the\n * ellipsis kicks in.\n */\n @property({ type: Number, attribute: 'line-clamp' })\n lineClamp = 1\n\n /**\n * When set, the component still truncates the text but does not render\n * a hover tooltip with the full content. The `truncated` attribute is\n * still reflected so consumers can style it.\n */\n @property({ type: Boolean, reflect: true, attribute: 'no-tooltip' })\n noTooltip = false\n\n connectedCallback() {\n super.connectedCallback()\n\n if (isServer)\n return\n\n this.observer = new ResizeObserver(() => {\n // Defer to next frame so the browser has finished the layout pass\n // triggered by the resize before we read geometry.\n requestAnimationFrame(() => this.measure())\n })\n // Catch content changes the ResizeObserver can't see: text-only mutations\n // via reactive bindings (Lit/Vue/React often mutate characterData), or\n // children being replaced. Without this, swapping the label to a\n // longer/shorter string after first render would never re-evaluate\n // truncation.\n this.contentObserver = new MutationObserver(() => {\n this.captureText()\n this.measure()\n })\n this.contentObserver.observe(this, {\n characterData: true,\n childList: true,\n subtree: true,\n })\n this.captureText()\n }\n\n protected firstUpdated() {\n const span = this.spanRef.value\n if (span && this.observer) {\n this.observer.observe(span)\n }\n }\n\n protected updated() {\n this.syncTooltip()\n }\n\n disconnectedCallback() {\n super.disconnectedCallback()\n this.observer?.disconnect()\n this.observer = undefined\n this.contentObserver?.disconnect()\n this.contentObserver = undefined\n this.tooltipElement?.remove()\n this.tooltipElement = undefined\n this.clearTrigger()\n }\n\n private captureText() {\n const next = (this.textContent ?? '').trim()\n if (next !== this.text) {\n this.text = next\n }\n }\n\n /**\n * Measure overflow and, if needed, binary-search the longest prefix of the\n * full text that fits in the available space, then commit `<prefix>…` as\n * the visible text. Same algorithm as Ant Design Pro's `Ellipsis`.\n */\n private measure() {\n const span = this.spanRef.value\n if (!span)\n return\n\n const fullText = this.text\n if (!fullText) {\n span.textContent = ''\n this.isTruncated = false\n return\n }\n\n // Reset to full text and check whether it already fits.\n span.textContent = fullText\n if (this.fits(span)) {\n this.isTruncated = false\n return\n }\n\n // Binary search: largest prefix length such that `prefix + …` fits.\n let lo = 0\n let hi = fullText.length\n while (lo < hi) {\n const mid = Math.ceil((lo + hi) / 2)\n span.textContent = trimEnd(fullText.slice(0, mid)) + ELLIPSIS\n if (this.fits(span)) {\n lo = mid\n }\n else {\n hi = mid - 1\n }\n }\n\n span.textContent = trimEnd(fullText.slice(0, lo)) + ELLIPSIS\n this.isTruncated = true\n }\n\n private fits(span: HTMLElement): boolean {\n if (this.lineClamp > 1) {\n return span.scrollHeight <= span.clientHeight\n }\n return span.scrollWidth <= span.clientWidth\n }\n\n /**\n * Walk up the DOM until we find an ancestor that actually receives pointer\n * events. Needed because `nord-button` (and similar) set\n * `::slotted(*) { pointer-events: none }`, so this host can never see hover.\n * Falls back to this host if no such ancestor is found.\n */\n private findTriggerElement(): Element {\n // pointer-events walk from the host itself so a standalone truncate\n // (default pointer-events: auto) is correctly returned as the trigger.\n // eslint-disable-next-line ts/no-this-alias -- intentional: start the\n let element: Element | null = this\n while (element && element !== document.body && element !== document.documentElement) {\n if (getComputedStyle(element).pointerEvents !== 'none') {\n return element\n }\n element = element.parentElement\n }\n return this\n }\n\n private addDescribedById(element: Element, id: string) {\n const existing = element.getAttribute('aria-describedby')\n const ids = existing ? existing.split(/\\s+/).filter(Boolean) : []\n if (!ids.includes(id)) {\n ids.push(id)\n element.setAttribute('aria-describedby', ids.join(' '))\n }\n }\n\n private removeDescribedById(element: Element, id: string) {\n const existing = element.getAttribute('aria-describedby')\n if (!existing)\n return\n const ids = existing.split(/\\s+/).filter(Boolean).filter(x => x !== id)\n if (ids.length) {\n element.setAttribute('aria-describedby', ids.join(' '))\n }\n else {\n element.removeAttribute('aria-describedby')\n }\n }\n\n private clearTrigger() {\n if (this.currentTrigger) {\n this.removeDescribedById(this.currentTrigger, this.tooltipId)\n this.currentTrigger = null\n }\n }\n\n private syncTooltip() {\n if (!this.isTruncated || !this.text) {\n this.removeAttribute('truncated')\n this.tooltipElement?.remove()\n this.tooltipElement = undefined\n this.clearTrigger()\n return\n }\n\n this.setAttribute('truncated', '')\n\n if (this.noTooltip) {\n this.tooltipElement?.remove()\n this.tooltipElement = undefined\n this.clearTrigger()\n return\n }\n\n if (!this.tooltipElement) {\n this.tooltipElement = document.createElement('nord-tooltip')\n this.tooltipElement.id = this.tooltipId\n document.body.appendChild(this.tooltipElement)\n }\n this.tooltipElement.setAttribute('position', this.position)\n this.tooltipElement.setAttribute('delay', String(this.delay))\n // The tooltip is portaled to document.body, so forward the offsets onto it\n // directly rather than relying on inheritance from this host.\n this.tooltipElement.setAttribute('side-offset', String(this.sideOffset))\n this.tooltipElement.setAttribute('align-offset', String(this.alignOffset))\n this.tooltipElement.textContent = this.text\n\n const trigger = this.findTriggerElement()\n if (trigger !== this.currentTrigger) {\n if (this.currentTrigger) {\n this.removeDescribedById(this.currentTrigger, this.tooltipId)\n }\n this.currentTrigger = trigger\n }\n this.addDescribedById(trigger, this.tooltipId)\n }\n\n protected willUpdate(changed: PropertyValues) {\n if (changed.has('lineClamp')) {\n if (this.lineClamp > 1) {\n this.setAttribute('line-clamp', String(this.lineClamp))\n this.style.setProperty('--_n-truncate-line-clamp', String(this.lineClamp))\n }\n else {\n this.removeAttribute('line-clamp')\n this.style.removeProperty('--_n-truncate-line-clamp')\n }\n }\n }\n\n render() {\n // The visible truncated text lives in a dedicated shadow-DOM span whose\n // `textContent` is managed imperatively in `measure()` (binary-search\n // updates would otherwise destroy Lit's ChildPart markers). The slot,\n // hidden visually, keeps the full text in light DOM so that assistive\n // tech and copy/paste of the host element still see the complete content.\n return html`\n <span ${ref(this.spanRef)} class=\"n-truncate\"></span>\n <slot class=\"n-truncate-source\" aria-hidden=\"true\"></slot>\n `\n }\n}\n\nfunction trimEnd(input: string): string {\n return input.replace(/[\\s.,;:!?-]+$/u, '')\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'nord-truncate': Truncate\n }\n}\n"],"names":["counter","Truncate","LitElement","constructor","this","spanRef","createRef","tooltipId","currentTrigger","isTruncated","text","position","delay","sideOffset","alignOffset","lineClamp","noTooltip","connectedCallback","super","isServer","observer","ResizeObserver","requestAnimationFrame","measure","contentObserver","MutationObserver","captureText","observe","characterData","childList","subtree","firstUpdated","span","value","updated","syncTooltip","disconnectedCallback","_a","disconnect","undefined","_b","_c","tooltipElement","remove","clearTrigger","next","textContent","trim","fullText","fits","lo","hi","length","mid","Math","ceil","trimEnd","slice","scrollHeight","clientHeight","scrollWidth","clientWidth","findTriggerElement","element","document","body","documentElement","getComputedStyle","pointerEvents","parentElement","addDescribedById","id","existing","getAttribute","ids","split","filter","Boolean","includes","push","setAttribute","join","removeDescribedById","x","removeAttribute","createElement","appendChild","String","trigger","willUpdate","changed","has","style","setProperty","removeProperty","render","html","ref","styles","__decorate","state","prototype","property","reflect","type","Number","attribute","customElement","input","replace"],"mappings":"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"}
|