UNPKG

@sandlada/mdc

Version:

@sandlada/mdc is an open source component library that follows the Material Design 3 design specifications.

52 lines 9.98 kB
import{customElement as e}from"../../node_modules/@lit/reactive-element/decorators/custom-element.js";import{property as t}from"../../node_modules/@lit/reactive-element/decorators/property.js";import{state as n}from"../../node_modules/@lit/reactive-element/decorators/state.js";import{query as r}from"../../node_modules/@lit/reactive-element/decorators/query.js";import"../../node_modules/lit/decorators.js";import{html as i}from"../../node_modules/lit-html/lit-html.js";import"../../node_modules/lit/index.js";import{classMap as a}from"../../node_modules/lit-html/directives/class-map.js";import"../../node_modules/lit/directives/class-map.js";import{composeMixin as o}from"../../utils/compose-mixin/compose-mixin.js";import s from"../../_virtual/_@oxc-project_runtime@0.144.0/helpers/esm/decorate.js";import{redispatchEvent as c}from"../../utils/event/redispatch-event.js";import{BaseNavigationContainer as l}from"../navigation/internal/base-navigation-container.js";import{mixinConnectedPromiseResolve as u}from"../../utils/behaviors/connected-promise-resolve.js";import{mixinXROptions as d}from"../../utils/xr/xr-options.mixin.js";import{NavigationRailCollapsedVariant as f}from"./navigation-rail.interface.js";import{NavigationRailStyles as p}from"./navigation-rail.style.js"; /** * @license * Copyright 2026 Kai-Orion & Sandlada * SPDX-License-Identifier: MIT * * @fileoverview * Navigation rail container. * * Supports two modes: * - **Standard** (`modal=false`): In-flow rail that toggles between narrow (collapsed) * and wide (expanded) via CSS transition on `width`. * - **Modal** (`modal=true`): Floating overlay that enters/exits via WAAPI animation. * Width toggle also applies between collapsed↔expanded modal widths. * * @example * ```html * <!-- Standard rail --> * <mdc-navigation-rail navigation-scope="main-nav"> * <mdc-navigation-tab name="rail-tabs" value="/home">Home</mdc-navigation-tab> * <mdc-navigation-tab name="rail-tabs" value="/settings">Settings</mdc-navigation-tab> * </mdc-navigation-rail> * * <!-- Expanded standard rail --> * <mdc-navigation-rail expanded navigation-scope="main-nav-2"> * <mdc-navigation-tab name="rail-tabs" value="/home" variant="rail-vertical"> * <mdc-icon slot="inactive-icon">send</mdc-icon> * </mdc-navigation-tab> * </mdc-navigation-rail> * * <!-- Modal rail --> * <mdc-navigation-rail expanded modal navigation-scope="modal-nav"> * <mdc-navigation-tab name="modal-tabs" value="/settings">Settings</mdc-navigation-tab> * </mdc-navigation-rail> * ``` * * @prop {boolean} expanded — Toggles between collapsed/expanded. In standard mode * this changes width via CSS transition; in modal mode it controls dialog show/hide. * @prop {boolean} modal — When true, renders as a floating modal overlay instead of in-flow. * @prop {boolean} quick — Skip all animations (both CSS transition and WAAPI). * * @emits expand — Cancelable. Fires before expansion starts (modal mode only). * @emits expanded — Expansion complete. * @emits collapse — Cancelable. Fires before collapse starts (modal mode only). * @emits collapsed — Collapse complete. * @emits cancel — Cancelable. Fires on backdrop click (modal mode only). * * @version * Material Design 3 - Expressive */ let m=class extends o(u,d)(l){constructor(...e){super(...e),this.quick=!1,this.expanded=!1,this.modal=!1,this.collapsedVariant=f.Vertical,this.isAtScrollTop=!1,this.isAtScrollBottom=!1,this.handleExpandedChange=!0,this.isExpanding=!1,this.returnValue=``,this.nextClickIsFromContent=!1,this.escapePressedWithoutCancel=!1,this.cancelAnimations=null}static{this.styles=p}firstUpdated(){super.firstUpdated(),this.syncTabVariants(),this.shadowRoot?.querySelector(`slot`)?.addEventListener(`slotchange`,()=>{this.syncTabVariants()}),this.intersectionObserver=new IntersectionObserver(e=>{for(let t of e)this.handleAnchorIntersection(t)},{root:this.scroller}),this.intersectionObserver.observe(this.topAnchor),this.intersectionObserver.observe(this.bottomAnchor)}updated(e){super.updated(e),e.has(`expanded`)&&this.handleExpandedChange&&(this.expanded?this.expand():this.collapse()),(e.has(`expanded`)||e.has(`collapsedVariant`)||e.has(`xr`))&&this.syncTabVariants()}handleAnchorIntersection(e){let{target:t,isIntersecting:n}=e;t===this.topAnchor&&(this.isAtScrollTop=n),t===this.bottomAnchor&&(this.isAtScrollBottom=n)}syncTabVariants(){let e=this.computeTargetVariant(),t=this.querySelectorAll(`mdc-navigation-tab`);for(let n of t)n.variant=e}computeTargetVariant(){return this.expanded?`rail-horizontal`:this.collapsedVariant===`round`?this.xr?`rail-xr-round`:`rail-round`:this.xr?`rail-xr-vertical`:`rail-vertical`}getRenderClasses(){let e=!(this.isAtScrollTop&&this.isAtScrollBottom);return{...super.getRenderClasses(),modal:this.modal,standard:!this.modal,expanded:this.expanded,"collapsed-xr":this.xr&&!this.expanded,collapsed:!this.expanded,scrollable:e,"show-top-divider":e&&!this.isAtScrollTop,"show-bottom-divider":e&&!this.isAtScrollBottom}}render(){return i`<span aria-hidden="true" class="scrim"></span><dialog open class="${a(this.getRenderClasses())}" .returnValue="${this.returnValue}" @cancel="${this.handleCancel}" @click="${this.handleDialogClick}" @close="${this.handleClose}" @keydown="${this.handleKeydown}"><div class="container" @click="${this.handleContentClick}"><div class="menu-and-fab"><div class="menu">${this.renderMenuSlot()}</div><div class="fab">${this.renderFabSlot()}</div></div><div class="scroller-section"><mdc-divider class="top"></mdc-divider><div class="scroller"><div class="destination"><div class="top anchor"></div>${this.renderSlot()}<div class="bottom anchor"></div></div></div><mdc-divider class="bottom"></mdc-divider></div><div class="end">${this.renderEndSlot()}</div>${this.renderBackground()}</div></dialog>`}renderBackground(){return i`<div aria-hidden="true" class="background"></div>`}renderMenuSlot(){return i`<slot name="menu"></slot>`}renderFabSlot(){return i`<slot name="fab"></slot>`}renderEndSlot(){return i`<slot name="end"></slot>`}getExpandingAnimation(){return{dialog:[[[{transform:`translateX(-50px)`,opacity:`0`},{transform:`translateX(0px)`,opacity:`1`}],{duration:500}]]}}getCollapsingAnimation(){return{dialog:[[[{transform:`translateX(0px)`,opacity:`1`},{transform:`translateX(-50px)`,opacity:`0`}],{duration:500}]]}}async expand(){if(this.isExpanding=!0,await this.isConnectedPromise,await this.updateComplete,!this.modal){this.handleExpandedChange=!1,this.expanded=!0,this.handleExpandedChange=!0,this.dispatchEvent(new Event(`expanded`)),this.isExpanding=!1;return}let e=this.dialog;if(e.open||!this.isExpanding){this.isExpanding=!1;return}if(!this.dispatchEvent(new Event(`expand`,{cancelable:!0}))){this.handleExpandedChange=!1,this.expanded=!1,this.handleExpandedChange=!0,this.isExpanding=!1;return}e.showModal(),this.handleExpandedChange=!1,this.expanded=!0,this.handleExpandedChange=!0,this.scroller&&(this.scroller.scrollTop=0),this.querySelector(`[autofocus]`)?.focus(),await this.animateHost(this.getExpandingAnimation()),this.dispatchEvent(new Event(`expanded`)),this.isExpanding=!1}async collapse(e=this.returnValue){if(this.isExpanding=!1,!this.isConnected){this.handleExpandedChange=!1,this.expanded=!1,this.handleExpandedChange=!0;return}if(await this.updateComplete,!this.modal){this.handleExpandedChange=!1,this.expanded=!1,this.handleExpandedChange=!0,this.dispatchEvent(new Event(`collapsed`));return}let t=this.dialog;if(!t.open||this.isExpanding){this.handleExpandedChange=!1,this.expanded=!1,this.handleExpandedChange=!0;return}let n=this.returnValue;if(this.returnValue=e,!this.dispatchEvent(new Event(`collapse`,{cancelable:!0}))){this.returnValue=n;return}await this.animateHost(this.getCollapsingAnimation()),t.close(e),this.handleExpandedChange=!1,this.expanded=!1,this.handleExpandedChange=!0,this.dispatchEvent(new Event(`collapsed`))}async animateHost(e){if(this.cancelAnimations?.abort(),this.cancelAnimations=new AbortController,this.quick)return;let{dialog:t,container:n,destination:r}=this;if(!t||!n||!r)return;let{dialog:i,container:a,destination:o}=e,s=[[t,i??[]],[n,a??[]],[r,o??[]]],c=[];for(let[e,t]of s)for(let n of t){let t=e.animate(...n);this.cancelAnimations.signal.addEventListener(`abort`,()=>{t.cancel()}),c.push(t)}await Promise.all(c.map(e=>{e.finished.catch(()=>{})}))}handleDialogClick(){if(this.modal){if(this.nextClickIsFromContent){this.nextClickIsFromContent=!1;return}this.dispatchEvent(new Event(`cancel`,{cancelable:!0}))&&this.collapse()}}handleContentClick(){this.nextClickIsFromContent=!0}handleSubmit(e){if(!this.modal)return;let t=e.target,{submitter:n}=e;t.getAttribute(`method`)!==`dialog`||!n||this.collapse(n.getAttribute(`value`)??this.returnValue)}handleCancel(e){if(!this.modal||e.target!==this.container)return;this.escapePressedWithoutCancel=!1;let t=c(this,e);e.preventDefault(),!t&&this.collapse()}handleClose(){this.escapePressedWithoutCancel&&(this.escapePressedWithoutCancel=!1,this.dialog?.dispatchEvent(new Event(`cancel`,{cancelable:!0})))}handleKeydown(e){this.modal&&e.key===`Escape`&&(this.escapePressedWithoutCancel=!0,setTimeout(()=>{this.escapePressedWithoutCancel=!1}))}};s([t({type:Boolean,reflect:!0})],m.prototype,`quick`,void 0),s([t({type:Boolean,reflect:!0})],m.prototype,`expanded`,void 0),s([t({type:Boolean,reflect:!0})],m.prototype,`modal`,void 0),s([t({type:String,reflect:!0,attribute:`collapsed-variant`})],m.prototype,`collapsedVariant`,void 0),s([n()],m.prototype,`isAtScrollTop`,void 0),s([n()],m.prototype,`isAtScrollBottom`,void 0),s([r(`.top.anchor`)],m.prototype,`topAnchor`,void 0),s([r(`.bottom.anchor`)],m.prototype,`bottomAnchor`,void 0),s([r(`.scroller`)],m.prototype,`scroller`,void 0),s([r(`.destination`)],m.prototype,`destination`,void 0),s([r(`dialog`)],m.prototype,`dialog`,void 0),s([r(`.container`)],m.prototype,`container`,void 0),m=s([e(`mdc-navigation-rail`)],m);export{m as NavigationRail}; //# sourceMappingURL=navigation-rail.js.map