import { defineWebComponent } from './define'; import { Screen, type ScreenController } from '../components/screen'; import { wireDisclosure } from './disclosure'; interface Props extends Record { /** Drive/observe open state (Shoelace-style: settable + reflected to the `open` * attribute; the element still self-manages). Set `el.open = true`, or * ``; listen for `kai-open-change`. */ open?: boolean; /** Initial open state on mount (uncontrolled seed). */ defaultOpen?: boolean; /** Header title text. A projected `title` slot overrides it. (Named `headline` * because `title` collides with the global `HTMLElement.title` attribute.) */ headline?: string; /** Show the back button (default true). */ back?: boolean; /** Opt out of marking sibling elements inert/aria-hidden while open (for unusual layouts). */ noInert?: boolean; } /** Events fired by ``. */ interface Events { /** Back navigation intent: the back button or Escape. The consumer flips their * own routing in response (the screen knows nothing about the trigger). */ 'kai-back': Record; /** The screen opened or closed (a method, `Escape` close, or driven `open`). */ 'kai-open-change': { open: boolean }; } /** * `` is a developer-swapped, full-bleed overlay destination: the * push/drill-in surface that takes over its mount point under a back-header. The * developer owns the swap (their own routing flips `open` in response to their * trigger button and the screen's `kai-back`); the screen owns being the takeover. * * It fills whatever it is mounted in (mount at the app root for a full takeover, * in a positioned region for a scoped one), marks sibling elements `inert` while * open (the standard modal pattern; opt out with `no-inert`), moves focus in on * open and restores it on close, and runs an enter/exit transition that honors * `prefers-reduced-motion`. `Escape` fires `kai-back`. * * ```html * * *
…your surface…
*
* ``` * * Open state is the standard disclosure surface: settable+reflecting `open`, * `kai-open-change`, and `show()`/`hide()`/`toggle()`; seed with `default-open`. * Parts: `header` · `back` · `title` · `body`. */ defineWebComponent('kai-screen', { open: undefined, defaultOpen: undefined, headline: undefined, back: undefined, noInert: undefined, }, (props, ctx) => { const { flag, element, dispatch, expose } = ctx; let api: ScreenController | undefined; let surface: HTMLElement | undefined; // The standard disclosure surface: settable+reflecting `open`, kai-open-change, // show/hide/toggle. See ./disclosure. This is the SOLE emitter of kai-open-change. wireDisclosure(ctx, () => api, () => props.open); // focus() shadows the host's native focus so it targets the screen surface // inside the shadow root (the WebAwesome/Shoelace convention). expose({ /** Move focus to the screen surface (no-op while closed). */ focus: (options?: FocusOptions) => surface?.focus(options), }); return ( element} titleSlot={{props.headline as string | undefined}} actions={} onBack={() => { dispatch('kai-back', {}); }} controllerRef={(a) => (api = a)} surfaceRef={(el) => (surface = el)} > ); });