import { RadiantElement } from '@ecopages/radiant'; import type { EventEmitter } from '@ecopages/radiant/tools/event-emitter'; export type RuiSidebarVariant = 'sidebar' | 'inset'; export type RuiSidebarSide = 'left' | 'right'; export type RuiSidebarCollapsible = 'off' | 'icon' | 'full'; export type RuiSidebarState = 'expanded' | 'collapsed'; export type RuiSidebarMatchMode = 'pathname' | 'prefix'; export type RuiSidebarProps = { /** Visual treatment. `sidebar` is the default bordered pane; `inset` floats inside a card. Default: `sidebar`. */ variant?: RuiSidebarVariant; /** Which edge the sidebar sits on. Default: `left`. */ side?: RuiSidebarSide; /** Collapse behavior. `off` keeps the pane open; `icon` collapses to an icon rail; `full` collapses fully. Default: `off`. */ collapsible?: RuiSidebarCollapsible; /** Initial open state when uncontrolled. Default: `true`. */ defaultOpen?: boolean; /** * Open state below `mobileBreakpoint` when uncontrolled. Applied on connect * and when the viewport crosses into mobile. Ignored when `open` is set. * Default: `false`. */ mobileDefaultOpen?: boolean; /** * Controlled open state. Viewport crossings do not override this; listen to * `rui-sidebar-mobile-change` if the parent needs to react. */ open?: boolean; /** Initial width in pixels when uncontrolled. Default: `256`. */ defaultWidth?: number; /** Controlled width in pixels. */ width?: number; /** Minimum width in pixels when resizing. Default: `200`. */ minWidth?: number; /** Maximum width in pixels when resizing. Default: `480`. */ maxWidth?: number; /** Show a drag/keyboard resize handle on desktop. Default: `false`. */ resizable?: boolean; /** Hide on viewports below this width. Default: `768`. */ mobileBreakpoint?: number; /** Accessible name announced by the pane landmark and triggers. Default: `Sidebar`. */ label?: string; /** * When `true`, syncs `rui-sidebar__menu-button--active` and `aria-current="page"` * on descendant menu links whose URL matches the current location. */ matchActive?: boolean; /** How link URLs are compared to `location.pathname`. Default: `pathname`. */ matchMode?: RuiSidebarMatchMode; /** Scroll the active link into view on first connect. */ scrollActiveOnMount?: boolean; /** * Comma-separated document event names that re-sync after SPA navigation, * e.g. `eco:page-load,eco:after-swap`. */ navigationEvents?: string; }; export type RuiSidebarToggleDetail = { open: boolean; state: RuiSidebarState; }; export type RuiSidebarResizeDetail = { width: number; }; type RuiSidebarBindings = { label: string; }; /** * `` — a resizable, collapsible side panel. * * The sidebar hosts pane content in the view-owned light-DOM shell. When the * parent supplies a `RuiSidebarRail` element (or the `collapsible` mode is not * `off`), the sidebar manages open/closed state, a focusable resize handle, * and keyboard navigation that follows the APG Window Splitter pattern. * * When `collapsible="off"` (the default), the pane stays open and exposes * a draggable resize handle. When `collapsible="icon"`, the pane collapses * to a narrow rail. When `collapsible="full"`, the pane is hidden until * toggled, and shown as an overlay drawer below the mobile breakpoint. * * The host reflects `data-state`, `data-collapsible`, `data-variant`, * `data-side`, and `data-mobile` so the stylesheet can drive every visual * mode without imperative JS. * * @see https://www.w3.org/WAI/ARIA/apg/patterns/windowsplitter/ * * @element rui-sidebar * @fires rui-sidebar-toggle - Emitted on every open/closed transition. * `detail` is `{ open, state }`. * @fires rui-sidebar-resize - Emitted on every width change. `detail` is `{ width }`. * @fires rui-sidebar-mobile-change - Emitted when the host flips between * mobile drawer mode and inline mode. `detail` is `{ mobile: boolean }`. */ export declare class RuiSidebar extends RadiantElement { variant: RuiSidebarVariant; side: RuiSidebarSide; collapsible: RuiSidebarCollapsible; defaultWidth: number; width: number | undefined; minWidth: number; maxWidth: number; resizable: boolean; defaultOpen: boolean; mobileDefaultOpen: boolean; open: boolean | undefined; mobileBreakpoint: number; label: string; matchActive: boolean; matchMode: RuiSidebarMatchMode; scrollActiveOnMount: boolean; navigationEvents: string; rootTarget: HTMLElement; paneTarget: HTMLElement; handleTarget: HTMLElement; scrimTarget: HTMLButtonElement; toggleEvent: EventEmitter; resizeEvent: EventEmitter; mobileChangeEvent: EventEmitter<{ mobile: boolean; }>; isMobile: boolean; private mediaQuery; private readonly mediaListener; private dragging; private dragStartCoord; private dragStartSize; private navigationCleanups; /** @remarks Remains `false` until a projected active link has been scrolled. */ private didScrollActiveOnMount; /** * @remarks Captured once before defaults are applied. `setOpen` later assigns * `open`, which must not be mistaken for a controlled binding. */ private openControlled; /** * @remarks Viewport policy must not run until connect has snapshotted * `open` and applied defaults. Deferred JSX props can update * `mobileBreakpoint` before that microtask. */ private mobileReady; connectedCallback(): void; protected onConnected(): void; /** * @remarks Light-DOM hydrate/update can recreate menu links after the connect * microtask sync. Re-apply active classes once the render commits. */ hydrate(): void; update(): void; requestUpdate(): void; disconnectedCallback(): void; private syncActiveLinksAfterRender; /** * Keep host and inner shell `data-*` in sync. Host attrs drive * `:has(> rui-sidebar[...])`; inner `.rui-sidebar` drives visual styles. */ private syncPresentation; private isOpen; /** * Optional `Number` props initialize to `0` when no attribute/default is set * (`defaultValueForType(Number)`). Treat that unset `0` as “use defaultWidth”. */ private ensureWidthInitialized; onStateUpdated(): void; onMobileBreakpointUpdated(): void; onMatchSettingsUpdated(): void; /** Re-applies active classes on descendant menu links from the current URL. */ syncActiveLinks(scrollActiveIntoView?: boolean): boolean; private isLinkActive; private attachNavigationListeners; private detachNavigationListeners; private unbindMobileMediaQuery; private bindMobileMediaQuery; /** * Flip between mobile drawer and inline layout. * * @remarks * After connect, entering mobile applies `mobileDefaultOpen` so a * desktop-open pane does not become an overlay drawer. Leaving mobile while * closed with `collapsible="off"` reopens — desktop hides reopen triggers for * that mode, so a closed drawer would otherwise stick at width 0. Controlled * `open` is left alone; other collapsible modes keep the consumer's open * state when leaving mobile. */ private setMobile; private clampWidth; /** * @remarks Mobile is always a full drawer — never leave an icon rail when closed. */ private paneWidth; /** Drive layout through the CSS variable only — never set inline pane width. */ private syncPaneWidthVar; private applyWidth; toggle(): void; setOpen(next: boolean): void; private beginDrag; private endDrag; private onPointerMove; private onPointerUp; onHandlePointerDown(event: Event): void; onHandleKeydown(event: Event): void; onScrimClick(): void; onMenuLinkClick(): void; onHostKeydown(event: Event): void; } export {};