entasis 0.7.0 → 0.8.0

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.
Files changed (48) hide show
  1. package/dist/components/FloatingWindow/FloatingWindow.svelte +26 -2
  2. package/dist/components/FloatingWindow/floatingWindow.dock.svelte.js +11 -2
  3. package/dist/components/FloatingWindow/floatingWindow.mcp.d.ts +1 -1
  4. package/dist/components/FloatingWindow/floatingWindow.mcp.js +7 -4
  5. package/dist/components/FloatingWindow/floatingWindow.props.d.ts +6 -0
  6. package/dist/components/FloatingWindow/floatingWindow.state.svelte.d.ts +3 -0
  7. package/dist/components/FloatingWindow/floatingWindow.state.svelte.js +18 -4
  8. package/dist/components/FloatingWindow/floatingWindow.theme.d.ts +3 -0
  9. package/dist/components/FloatingWindow/floatingWindow.theme.js +20 -7
  10. package/dist/components/Form/File/FileInput.svelte +6 -42
  11. package/dist/components/Sidebar/Sidebar.svelte +72 -12
  12. package/dist/components/Sidebar/Sidebar.svelte.d.ts +1 -1
  13. package/dist/components/Sidebar/SidebarActivityBar.svelte +9 -2
  14. package/dist/components/Sidebar/SidebarActivityBar.svelte.d.ts +5 -1
  15. package/dist/components/Sidebar/SidebarDesktopShell.svelte +12 -4
  16. package/dist/components/Sidebar/SidebarMenuItem.svelte +19 -2
  17. package/dist/components/Sidebar/SidebarMenuItem.svelte.d.ts +4 -0
  18. package/dist/components/Sidebar/SidebarPanel.svelte +199 -88
  19. package/dist/components/Sidebar/SidebarPanel.svelte.d.ts +3 -0
  20. package/dist/components/Sidebar/SidebarViewStage.svelte +103 -0
  21. package/dist/components/Sidebar/SidebarViewStage.svelte.d.ts +18 -0
  22. package/dist/components/Sidebar/index.d.ts +1 -1
  23. package/dist/components/Sidebar/sidebar-layout.d.ts +7 -2
  24. package/dist/components/Sidebar/sidebar-layout.js +17 -6
  25. package/dist/components/Sidebar/sidebar.mcp.d.ts +1 -1
  26. package/dist/components/Sidebar/sidebar.mcp.js +19 -2
  27. package/dist/components/Sidebar/sidebar.props.d.ts +56 -1
  28. package/dist/components/Sidebar/sidebar.state.svelte.d.ts +4 -0
  29. package/dist/components/Sidebar/sidebar.state.svelte.js +9 -1
  30. package/dist/components/Sidebar/sidebar.theme.d.ts +149 -5
  31. package/dist/components/Sidebar/sidebar.theme.js +96 -3
  32. package/dist/components/Sidebar/sidebar.views.svelte.d.ts +138 -0
  33. package/dist/components/Sidebar/sidebar.views.svelte.js +303 -0
  34. package/dist/components/Theme/theme.floatingWindows.d.ts +6 -1
  35. package/dist/components/Theme/theme.floatingWindows.js +7 -2
  36. package/dist/components/Theme/theme.mcp.d.ts +1 -1
  37. package/dist/components/Theme/theme.mcp.js +1 -0
  38. package/dist/generated/componentContract.d.ts +1 -1
  39. package/dist/generated/componentContract.js +1 -0
  40. package/dist/generated/componentMcpRegistry.d.ts +4 -4
  41. package/dist/tailwind/index.mcp.d.ts +1 -1
  42. package/dist/tailwind/index.mcp.js +4 -0
  43. package/dist/tailwind/scales.js +11 -3
  44. package/dist/tailwind/spacing.js +17 -0
  45. package/dist/utils/cva/merge.d.ts +7 -0
  46. package/dist/utils/cva/merge.js +6 -1
  47. package/dist/utils/pointerDrag.js +7 -1
  48. package/package.json +1 -1
@@ -361,7 +361,7 @@ export declare const componentInventory: readonly [{
361
361
  readonly id: "sidebar";
362
362
  readonly subpath: "entasis/sidebar";
363
363
  readonly sourceIndex: "src/lib/components/Sidebar/index.ts";
364
- readonly exportedSymbols: readonly ["Sidebar", "SidebarActiveVariant", "SidebarActivityBar", "SidebarActivityBarItem", "SidebarActivityBarSelectPayload", "SidebarApi", "SidebarCollapsible", "SidebarDensity", "SidebarDisplayState", "SidebarFrame", "SidebarGroup", "SidebarIcon", "SidebarIconVariant", "SidebarMenuActionDescriptor", "SidebarMenuAlign", "SidebarMenuButton", "SidebarMenuButtonItem", "SidebarMenuButtonSize", "SidebarMenuButtonVariant", "SidebarMenuEntry", "SidebarMenuSide", "SidebarMenuSubEntry", "SidebarMode", "SidebarProps", "SidebarRail", "SidebarResizable", "SidebarResizableOptions", "SidebarSearch", "SidebarSide", "SidebarSize", "SidebarState", "SidebarTheme", "SidebarThemeProps", "SidebarTooltipMode", "SidebarTreeNode", "SidebarVariant", "SidebarWidthChangePayload", "setSidebarTheme", "sidebarDescription", "sidebarTheme", "useSidebarTheme"];
364
+ readonly exportedSymbols: readonly ["Sidebar", "SidebarActiveVariant", "SidebarActivityBar", "SidebarActivityBarItem", "SidebarActivityBarSelectPayload", "SidebarApi", "SidebarCollapsible", "SidebarDensity", "SidebarDisplayState", "SidebarFrame", "SidebarGroup", "SidebarIcon", "SidebarIconVariant", "SidebarMenuActionDescriptor", "SidebarMenuAlign", "SidebarMenuButton", "SidebarMenuButtonItem", "SidebarMenuButtonSize", "SidebarMenuButtonVariant", "SidebarMenuEntry", "SidebarMenuSide", "SidebarMenuSubEntry", "SidebarMode", "SidebarProps", "SidebarRail", "SidebarResizable", "SidebarResizableOptions", "SidebarSearch", "SidebarSide", "SidebarSize", "SidebarState", "SidebarTheme", "SidebarThemeProps", "SidebarTooltipMode", "SidebarTreeNode", "SidebarVariant", "SidebarView", "SidebarWidthChangePayload", "setSidebarTheme", "sidebarDescription", "sidebarTheme", "useSidebarTheme"];
365
365
  readonly docs: readonly [{
366
366
  readonly id: "sidebar";
367
367
  readonly route: "/components/sidebar";
@@ -932,6 +932,7 @@ export const componentInventory = [
932
932
  'SidebarTooltipMode',
933
933
  'SidebarTreeNode',
934
934
  'SidebarVariant',
935
+ 'SidebarView',
935
936
  'SidebarWidthChangePayload',
936
937
  'setSidebarTheme',
937
938
  'sidebarDescription',
@@ -25,7 +25,7 @@ export declare const componentMcpRegistry: {
25
25
  readonly stack: "\n# Stack\n\nStack arranges arbitrary content along one flex axis. Use the `orientation` prop to switch\nbetween horizontal and vertical layout, and `align` / `justify` for cross-axis and main-axis\nalignment.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n import { Stack } from '../components/Stack/index.ts';\n</script>\n```\n\n## Usage\n\n```svelte\n<Stack gap=\"xl\" padding=\"xl\">\n <h2>Account</h2>\n <Stack orientation=\"horizontal\" align=\"center\" gap=\"md\" wrap=\"wrap\">\n <span>Profile</span>\n <span>Security</span>\n </Stack>\n</Stack>\n```\n\n## Responsive props\n\n`orientation`, `gap`, `align`, `justify` and `wrap` each take a plain value or a\nper-breakpoint record:\n\n```svelte\n<Stack orientation={{ md: 'horizontal' }} gap={{ xs: 'sm', lg: 'xl' }} align=\"center\">\n <span>Filters</span>\n <span>Results</span>\n</Stack>\n```\n\nThe breakpoints measure the stack's OWN width — `xs` base, `sm` 36rem, `md` 42rem,\n`lg` 56rem, `xl` 72rem — not the viewport's, so the same stack is `xs` in a narrow sidebar\nand `lg` full-bleed on the same page. The nearest defined key at or below the stack's width\nwins, and below the narrowest key the prop's default applies: `{ md: 'horizontal' }` is\nvertical at `xs` and `sm`. A prop falls back to its default only when it is `undefined`\nor `null`. A function form must be deterministic in its argument — it is called once per\nbreakpoint. There is no measurement and no JS: the five values ship as custom\nproperties and container queries pick one, so server-rendered markup is already laid out.\n\n## Props\n\n- `orientation`: `'horizontal' | 'vertical'` — flex direction (default: `'vertical'`).\n- `align`: cross-axis alignment — `start | center | end | stretch` (default: `'stretch'`).\n- `justify`: main-axis alignment — `start | center | end | between | around | evenly` (default: `'start'`).\n- `gap`, `padding`, `paddingInline`, and `paddingBlock` accept\n `none | xs | sm | md | lg | xl`.\n- `paddingInline` and `paddingBlock` override `padding` on their axis.\n- `width`, `height`, `maxWidth`, and `minHeight` accept CSS strings or pixel numbers.\n- `wrap` accepts `nowrap | wrap | wrap-reverse`.\n- `scrollable` enables native `overflow: auto`.\n- `as` changes the semantic HTML element without changing layout behavior:\n `div | span | section | article | aside | main | nav | header | footer | form | fieldset`.\n Lists are not among them — the root always wraps its children in one layout `<div>`, which\n `<ul>` and `<ol>` do not admit. Write the list yourself and put a stack inside an `<li>`.\n\n## Structure\n\nStack renders two elements: the root (`data-slot=\"stack\"`) is the box the host sizes and the\ncontainer the breakpoints are measured against — it takes `as`, `class`, `style`, the size\nprops, the padding and `scrollable` — and a single layout child (`data-slot=\"stack-layout\"`)\ncarries the flex line. The `inner` theme slot styles that child.\n\nStack forwards common semantic HTML attributes and Svelte attachments to the root element.\nPrefer parent-owned `gap` over child margins. The internal `micro` and `layout-*` tokens are\nreserved for component recipes and must not be used in generated interfaces.\n";
26
26
  readonly 'app-shell': "\n# AppShell Component\n\nConvenience wrapper for the common application layout: Sidebar owns navigation,\nresponsive drawer behavior, the application wall, and variant surfaces, while PageShell\nowns the page header, document-flow content, footer, and route-level injection. AppShell\nforwards one shared variant to Sidebar and composes PageShell inside it.\nAppShell establishes a dynamic viewport-height minimum while letting the document own\nvertical scrolling. The desktop sidebar remains sticky independently of page content.\n\nUse AppShell when every route follows the same sidebar + page shell structure. Use\nSidebar and PageShell directly when the frame needs custom composition.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { AppShell, type AppShellSidebarProps } from 'entasis/app-shell';\n\timport { houseIcon } from 'entasis/icons/house';\n\n\tconst sidebar: AppShellSidebarProps = {\n\t\tcollapsible: 'icon',\n\t\trail: true,\n\t\titems: [\n\t\t\t{\n\t\t\t\tlabel: 'Workspace',\n\t\t\t\titems: [{ label: 'Home', href: '/', icon: houseIcon, isActive: true }]\n\t\t\t}\n\t\t]\n\t};\n</script>\n\n<AppShell variant=\"framed\" {sidebar} title=\"Dashboard\" subtitle=\"Operational overview\">\n\t{#snippet children({ sidebar })}\n\t\t<button type=\"button\" onclick={sidebar.toggle}>Toggle sidebar</button>\n\t{/snippet}\n</AppShell>\n```\n\n## Route-Level Injection\n\nAppShell renders PageShell internally, so child pages can use the PageShell context API:\n\n```svelte\n<script lang=\"ts\">\n\timport { setPageShell } from 'entasis/page-shell';\n\n\tsetPageShell({\n\t\ttitle: 'Insights',\n\t\tsubtitle: 'Revenue and retention',\n\t\tfooter: pageFooter\n\t});\n</script>\n\n{#snippet pageFooter()}\n\t<span>Synced just now</span>\n{/snippet}\n```\n\n## Props\n\n- **variant**: 'admin' | 'floating' | 'inset' | 'split' | 'framed' - Shared shell treatment forwarded to Sidebar and PageShell chrome. Admin chrome uses the Sidebar canvas surface; floating chrome uses detached raised, rounded surfaces. `framed` draws one rounded card (`rounded-xl` + `raised-1`, so the border and elevation come from the elevation engine) around both the sidebar and the page; the Sidebar's own `framed` variant paints its navigation well as `surface-recessed`, an inset of that card, and the page header sits on the page surface.\n- **sidebar**: AppShellSidebarProps - Sidebar props except `children`, `mode`, `frame`, and `variant`.\n Configure Sidebar `size` and `density` independently inside this object.\n- **eyebrow**: string | Snippet<[PageShellApi]> - Small metadata above the PageShell title.\n- **breadcrumbs**: BreadcrumbItem[] | Snippet<[AppShellApi]> - PageShell breadcrumbs.\n- **breadcrumbsMaxItems**: number - Maximum visible breadcrumb items before ellipsis. Defaults to 4.\n- **back**: PageShellAction | Snippet<[AppShellApi]> - Back affordance before breadcrumbs or eyebrow.\n- **title**: string | Snippet<[PageShellApi]> - Default PageShell title.\n- **subtitle**: string | Snippet<[PageShellApi]> - Default PageShell subtitle.\n- **header**: Snippet<[PageShellApi]> - Custom PageShell header.\n- **headerActions**: Snippet<[AppShellApi]> | PageShellAction[] - Actions in the default PageShell header. Use an array for standard Button props, or a snippet when the action needs sidebar/page-shell API access.\n- **footer**: Snippet<[PageShellApi]> - PageShell footer.\n- **footerActions**: Snippet<[AppShellApi]> | PageShellAction[] - PageShell footer actions.\n- **children**: Snippet<[AppShellApi]> - Main content, with `pageShell` and `sidebar` APIs.\n- **contentPadding**: 'none' | 'small' | 'normal' | 'large' - PageShell content padding preset.\n- **contentWidth**: 'full' | 'narrow' | 'normal' | 'wide' | 'prose' - PageShell content width preset.\n- **pageShellTheme**: PageShellThemeProps - PageShell theme overrides.\n- **theme**: AppShellThemeProps - AppShell `root`, `frame` and `page` surface-token overrides. Sidebar owns the wall and shell geometry.\n\n## Accessibility\n\nAppShell delegates navigation semantics to Sidebar and page landmarks to PageShell.\nUse string `title` for the default `h1`, or preserve heading semantics when replacing\nthe PageShell header with a custom snippet.\n";
27
27
  readonly 'page-shell': "\n# PageShell Component\n\nContent shell for pages rendered inside an application frame. PageShell provides a\nsticky header and footer, document-flow content, title/subtitle props, and a context\nAPI for child routes to inject shell content. Scrolling stays on the document by default,\nso browser navigation and scroll restoration keep their native behavior.\n\nUse PageShell inside `Sidebar.children` when Sidebar owns navigation and responsive\ndrawer behavior.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { PageShell, type PageShellAction } from 'entasis/page-shell';\n\timport { downloadSimpleIcon } from 'entasis/icons/downloadSimple';\n\n\tconst headerActions = [\n\t\t{\n\t\t\tcontent: 'Export',\n\t\t\tcolor: 'primary',\n\t\t\tprefix: downloadSimpleIcon\n\t\t}\n\t] satisfies PageShellAction[];\n</script>\n\n<PageShell title=\"Insights\" subtitle=\"Live account health\" {headerActions}>\n\t{#snippet footer()}\n\t\t<span>Updated just now</span>\n\t{/snippet}\n\n\t{#snippet children()}\n\t\t<section class=\"p-6\">Page content</section>\n\t{/snippet}\n</PageShell>\n```\n\n## Route-Level Injection\n\nChild pages can set header and footer content through context. Use `setPageShell`\nduring component initialization for automatic cleanup.\n\n```svelte\n<script lang=\"ts\">\n\timport { setPageShell } from 'entasis/page-shell';\n\n\tsetPageShell({\n\t\ttitle: 'Revenue',\n\t\tsubtitle: 'Segment breakdown',\n\t\theaderActions: revenueActions,\n\t\tfooter: revenueFooter\n\t});\n</script>\n\n{#snippet revenueActions()}\n\t<button type=\"button\">Refresh</button>\n{/snippet}\n\n{#snippet revenueFooter()}\n\t<span>Synced 2 minutes ago</span>\n{/snippet}\n```\n\n## Props\n\n- **eyebrow**: string | Snippet<[PageShellApi]> - Small metadata above the title. Ignored when breadcrumbs are set.\n- **breadcrumbs**: BreadcrumbItem[] | Snippet<[PageShellApi]> - Default-header breadcrumbs.\n- **breadcrumbsMaxItems**: number - Maximum visible breadcrumb items before ellipsis. Defaults to 4.\n- **back**: PageShellAction | Snippet<[PageShellApi]> - Back affordance before breadcrumbs or eyebrow.\n- **title**: string | Snippet<[PageShellApi]> - Default header title.\n- **subtitle**: string | Snippet<[PageShellApi]> - Default header subtitle.\n- **header**: Snippet<[PageShellApi]> - Custom sticky header content.\n- **headerActions**: Snippet<[PageShellApi]> | PageShellAction[] - Actions on the right side of the default header. Use an array for standard Button props, or a snippet when the action needs shell API access.\n- **footer**: Snippet<[PageShellApi]> - Custom sticky footer content.\n- **footerActions**: Snippet<[PageShellApi]> | PageShellAction[] - Actions on the right side of the sticky footer.\n\t- **children**: Snippet<[PageShellApi]> - Page content rendered in normal document flow.\n\t- **contentPadding**: 'none' | 'small' | 'normal' | 'large' - Padding applied to the content inner wrapper.\n\t- **contentWidth**: 'full' | 'narrow' | 'normal' | 'wide' | 'prose' - Max-width preset for the content inner wrapper.\n\t- **actionOverflow**: 'auto' | 'never' - Mobile overflow behavior for action arrays.\n\t- **mobileActionCount**: 0 | 1 | 2 - Number of action-array buttons kept inline on mobile.\n\t- **label**: string - Accessible name for the page's `main` landmark, applied as aria-label.\n\t- **theme**: PageShellThemeProps - Per-instance theme overrides.\n\n## API\n\n- **usePageShell()** returns the current PageShell API and throws when no PageShell exists.\n- **setPageShell(config)** registers a scoped config override and removes it on component destroy.\n- **api.set(config)** pushes a manual override and returns a cleanup function.\n- **api.setFooterActions(actions)** pushes scoped page footer actions.\n- **api.reset()** clears all scoped overrides.\n\n## Header Action Arrays\n\n```svelte\n<script lang=\"ts\">\n\timport { PageShell, type PageShellAction } from 'entasis/page-shell';\n\timport { arrowClockwiseIcon } from 'entasis/icons/arrowClockwise';\n\n\tconst headerActions = [\n\t\t{\n\t\t\tlabel: 'Refresh',\n\t\t\tsquared: true,\n\t\t\tvariant: 'outline',\n\t\t\tprefix: arrowClockwiseIcon\n\t\t},\n\t\t{\n\t\t\tcontent: 'Create report',\n\t\t\tcolor: 'primary'\n\t\t}\n\t] satisfies PageShellAction[];\n</script>\n\n<PageShell title=\"Reports\" {headerActions}>\n\t{#snippet children()}\n\t\tPage content\n\t{/snippet}\n</PageShell>\n```\n\n## Content Presets And Footer Actions\n\n```svelte\n<PageShell\n\teyebrow=\"Settings\"\n\ttitle=\"Billing profile\"\n\tcontentPadding=\"normal\"\n\tcontentWidth=\"narrow\"\n\tfooterActions={[\n\t\t{ content: 'Cancel', variant: 'outline' },\n\t\t{ content: 'Save changes', color: 'primary' }\n\t]}\n>\n\t{#snippet footer()}\n\t\t<span>2 unsaved changes</span>\n\t{/snippet}\n\n\t{#snippet children()}\n\t\t<form>...</form>\n\t{/snippet}\n</PageShell>\n```\n\n## Accessibility\n\nPageShell renders semantic `header`, `main`, and `footer` regions. The title is an\n`h1` when provided as a string. Custom snippets are responsible for preserving\nequivalent semantics when replacing the default header.\n";
28
- readonly sidebar: "\n# Sidebar Component\n\nSidebar navigation with data-driven groups, icon collapse, mobile drawer behavior,\nrecursive tree groups, header/footer rows, search, actions, and snippet escape hatches.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n</script>\n```\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n\timport { houseIcon } from 'entasis/icons/house';\n\timport { gearIcon } from 'entasis/icons/gear';\n\n\tconst items: SidebarGroup[] = [\n\t\t{\n\t\t\tlabel: 'Workspace',\n\t\t\titems: [\n\t\t\t\t{ label: 'Dashboard', href: '/', icon: houseIcon, isActive: true },\n\t\t\t\t{ label: 'Settings', href: '/settings', icon: gearIcon }\n\t\t\t]\n\t\t}\n\t];\n</script>\n\n<Sidebar items={items}>\n\t{#snippet children({ toggle })}\n\t\t<header>\n\t\t\t<button type=\"button\" onclick={toggle}>Toggle</button>\n\t\t</header>\n\t\t<main>Page content</main>\n\t{/snippet}\n</Sidebar>\n```\n\n## AI-Safe Usage Contract\n\n1. Use `items` for normal navigation. Use `content` only when data-driven rows cannot express the layout.\n2. Use local Entasis icon snippets such as `houseIcon`, not Lucide component constructors.\n3. Use `MenuItem[]` from `entasis/menu` for `menu` and action dropdowns.\n4. Do not combine `menu` with `href` or `onclick` on the same row; use `action` for a trailing row menu.\n5. Keep `children`, `header`, `content`, `footer`, `banner`, and action snippets pure; they receive `SidebarApi`.\n6. Use `collapsible=\"icon\"` for icon rail behavior, `collapsible=\"offcanvas\"` for hidden desktop panels, and `collapsible=\"none\"` for fixed sidebars. Icon collapse automatically falls back to offcanvas when any data-driven row lacks an icon.\n7. Offcanvas sidebars reveal over the content from the screen edge by default when hidden; set `edgeReveal={false}` to disable that. A revealed hidden sidebar keeps its resize handle and dismisses through a small rectangular pointer tolerance.\n8. Set `keyboardShortcut={false}` when embedding Sidebar inside another shortcut-heavy surface.\n9. Sidebar owns navigation, resize mechanics, the lower application wall, and variant surface geometry. AppShell forwards its variant and composes PageShell inside that surface.\n10. Use `size` for typography, icon scale, and item height. Use `density` independently for section padding, gaps, and submenu spacing.\n11. Use `activityBar` for a persistent icon rail outside the panel (section switching, workspaces). Every item needs an `icon` and a `label`; the label is the accessible name and the tooltip. It is layout mode only: `mode=\"panel\"` renders the navigation panel alone.\n12. Use `expandOnHover` only with `collapsible=\"icon\"`. It is a temporary peek, not a toggle: the persisted collapsed state never changes, while the peeked panel renders with expanded semantics.\n\n## Data Model\n\n### SidebarGroup\n- **label**: string - Group label, hidden in icon-collapsed mode.\n- **items**: SidebarMenuEntry[] - Menu rows.\n- **tree**: SidebarTreeNode[] - Recursive tree rows instead of menu items.\n- **action**: SidebarMenuActionDescriptor | SidebarMenuActionDescriptor[] | Snippet<[SidebarApi]> - Top-right group actions. Pass an array to pin several affordances (a `+` and a drag handle) to one group header; each renders as its own icon-only ghost button.\n- **collapsible**: boolean - Makes the group label a toggle.\n- **defaultOpen**: boolean - Initial collapsible group state.\n- **separator**: boolean - Divider before the group.\n\n### SidebarMenuEntry\n- **label**: string - Visible row label.\n- **icon**: SidebarIcon - Entasis icon snippet or string.\n- **iconColor**: Colors - Role tint for the leading icon, applied through `data-color`.\n- **iconVariant**: 'bare' | 'tile' - Leading icon treatment. `tile` paints a rounded square (`bg-color-muted text-color-muted-readable`) around the glyph, so per-project colour chips come from the role scale instead of hand-built markup.\n- **href**: string - Render as an anchor. Mutually exclusive with menu.\n- **onclick**: (event: MouseEvent) => void - Native click handler for button or anchor rows. Mutually exclusive with menu.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **disabled**: boolean - Disables button rows and marks anchor rows disabled.\n- **badge**: string | number - Trailing count/status, hidden in icon mode.\n- **tooltip**: string - Entasis Tooltip content in icon mode. Defaults to label.\n- **items**: SidebarMenuSubEntry[] - Inline nested menu.\n- **collapsible**: boolean - Set false for an always-open submenu.\n- **defaultOpen**: boolean - Initial nested menu state.\n- **menu**: MenuItem[] - Popup menu opened from the full row. Mutually exclusive with href/onclick.\n- **action**: SidebarMenuActionDescriptor | Snippet<[SidebarApi]> - Hover/focus trailing action.\n\n### SidebarActivityBar\nIcon rail pinned to the outer edge of the sidebar, visible in every display state.\n- **items**: SidebarActivityBarItem[] - Items rendered from the top.\n- **footerItems**: SidebarActivityBarItem[] - Items pinned to the end of the column.\n- **header** / **footer**: Snippet - Custom content before the first item and after the pinned ones.\n- **width**: string (default '3rem') - Column thickness, published as `--sidebar-width-activity`.\n- **label**: string - Accessible name for the column landmark. Set it whenever the panel also renders navigation.\n- **onSelect**: ({ item, index }) => void - Fires after an item is activated. `index` counts `items` then `footerItems`.\n\n### SidebarActivityBarItem\n- **icon**: SidebarIcon (required) - Icon rendered in the square.\n- **label**: string (required) - Accessible name and default tooltip; the square shows no text.\n- **id**: string - Stable render key.\n- **href** / **target** / **rel** - Render an anchor instead of a button.\n- **onclick**: (event: MouseEvent) => void - Native click handler.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **badge**: string | number | Snippet - Corner badge pinned to the outer top corner. An empty string renders a bare dot. A string or number badge joins the accessible name (`\"Alerts, 3\"`); a dot and a Snippet badge are decorative, so put their meaning in `label`.\n- **disabled**: boolean - Blocks activation and skips the item during keyboard navigation.\n- **tooltip**: string | false - Tooltip override; `false` suppresses it.\n\n### SidebarMenuButtonItem\nUse for `headerButton`, `footerButton`, or direct `<SidebarMenuButton />` rows.\n- **icon**: SidebarIcon - Leading logo/icon.\n- **avatar**: { src?: string; alt?: string; fallback?: string } - Leading avatar.\n- **variant**: 'default' | 'brand' | 'compact'.\n- **title**: string - Primary text.\n- **subtitle**: string - Secondary text.\n- **trailing**: SidebarIcon | false | SidebarMenuActionDescriptor - Trailing content. An icon is decorative; an action descriptor (`{ icon, label, onclick }`, the same shape as a group action) renders its own icon-only ghost button beside the row, so a workspace card can carry its own collapse control without a custom `header` snippet. Descriptor handlers are `onclick(event, api)`, so `api.toggle()` is reachable.\n- **href** / **onclick** / **menu** - Choose link, button, or popup behavior. `onclick(event, api)` receives the SidebarApi beside the event, so `api.toggle()` is reachable from the row itself.\n- **menuIconClass**: string - Class override for option icons inside the popup menu.\n\n## Props\n\n### State\n- **open**: boolean (bindable, default true) - Desktop expanded state.\n- **defaultOpen**: boolean (default true) - Initial desktop state when `open` is omitted.\n- **onOpenChange**: (open: boolean) => void - Called once for a library-originated desktop state change. Repeated requests and parent prop updates stay silent.\n- **onDisplayStateChange**: (state: SidebarDisplayState) => void - Called once for a library-originated semantic display-state change.\n- **api.displayState**: 'expanded' | 'collapsed' | 'hidden' - Semantic desktop state; hidden means closed offcanvas. A hover peek does not change it.\n- **api.isPeeking**: boolean - True while a hover peek renders the collapsed panel at full width. Read it alongside `displayState` when a snippet hides content in icon mode.\n- **keyboardShortcut**: string | false (default 'b') - Ctrl/Cmd shortcut key.\n\n### Layout\n- **side**: 'left' | 'right' - Desktop and mobile side.\n- **variant**: 'admin' | 'floating' | 'inset' | 'split' | 'framed' - Sidebar geometry. `framed` is the admin geometry for a sidebar hosted inside a raised card (AppShell variant framed), with a `surface-recessed` well. `admin` renders the conventional full-height navigation column; `inset` integrates navigation into the lower wall with an inset content surface; `split` renders detached sidebar and content surfaces.\n- **size**: 'small' | 'normal' | 'large' (default 'normal') - Typography, icon, avatar, badge, leading-media, item-height, and search-height scale.\n- **iconSize**: 'small' | 'normal' | 'large' - Icon and leading-media scale inside the panel on its own; defaults to `size`. Menu rows, sub rows, group labels and the header button follow it; the activity bar keeps `size`.\n- **activeVariant**: 'soft' | 'outline' | 'solid' (default 'soft') - How active rows are painted. `soft` is the shared selected recipe (`selectedSoft`: `bg-selected-muted text-selected-muted-readable`), `solid` its loud counterpart (`selectedSolid`: `bg-selected text-selected-contrast`), `outline` a bordered surface card (`bg-surface border border-neutral-muted text-neutral`) that reads as a raised card on a tinted well. Each row carries the choice as `data-active-variant`, so no descendant selector is needed to restyle selection.\n- **density**: 'compact' | 'normal' | 'comfortable' (default 'normal') - Section padding, group padding, gaps, horizontal inset, and submenu spacing.\n- **collapsible**: 'offcanvas' | 'icon' | 'none' - Collapse behavior. Icon mode requires icons on every data-driven row and otherwise resolves to offcanvas.\n- **collapseIcon**: DisclosureIndicator — 'chevron' | 'plus-minus' | 'none' (default 'chevron') - Disclosure indicator drawn on collapsible menu rows. 'none' renders no indicator.\n- **mode**: 'layout' | 'panel' - Full resizing layout or only the visible navigation panel.\n- **frame**: 'viewport' | 'contained' - Standalone Sidebar positioning. Viewport mode uses Theme's dynamic window-height token; contained mode fills a positioned parent.\n- **width**: string - Expanded width.\n- **widthIcon**: string - Icon-collapsed width.\n- **widthMobile**: string - Mobile drawer width.\n- **rail**: boolean | 'line' | 'thumb' - Edge toggle rail. `true` keeps the thin line style; `thumb` renders a short visible handle with the same full-height hitbox. The appearance is preserved when the rail shares the resize control.\n- **activityBar**: SidebarActivityBar - Icon rail pinned outside the panel, in layout mode only (`mode=\"panel\"` renders the panel alone and ignores it). It never slides off screen: only the panel takes the offcanvas offset, and the reserved layout column is the panel width plus the rail width. On mobile it renders as a horizontal row at the top of the drawer.\n- **expandOnHover**: boolean (default false) - With `collapsible=\"icon\"`, hovering or focusing into the collapsed panel expands it to `width` over the page (`data-peek=\"true\"`) while the reserved column stays at `widthIcon`, so page content does not reflow. The persisted collapsed state is untouched, and the peeked panel renders exactly like an expanded one: group headers, badges, search, inline submenus, and inline tree branches all come back, and the rail or resize handle travels to its inner edge.\n- **edgeReveal**: boolean (default true) - Pointer/focus edge preview for hidden offcanvas sidebars. Hover reveal overlays content, remains resizable when configured, and re-hides after the pointer leaves its small rectangular tolerance. Dragging the sidebar closed suppresses immediate hover reopening until the pointer leaves the edge trigger; toggle/click opens persistently.\n- **resizable**: boolean | SidebarResizableOptions - Enables pointer and keyboard resizing while expanded, icon-collapsed, or temporarily edge-revealed. By default, collapse requires dragging 75% of `minWidth` beyond the minimum; override `collapseThreshold` for a custom boundary. Use `storageKey` to restore and persist the expanded width across sessions.\n - `onWidthChange({ width, isUserInteraction })` reports every expanded-width change with one named payload: continuously while the user resizes (`isUserInteraction: true`) and once when a stored width is restored (`isUserInteraction: false`).\n\n### Content\n- **items**: SidebarGroup[] - Data-driven body navigation.\n- **headerButton** / **footerButton**: SidebarMenuButtonItem - Sticky large rows.\n- **search**: SidebarSearch - Header search input; use its native `oninput` handler.\n- **headerMenu** / **footerMenu**: SidebarMenuEntry[] - Sticky quick menus.\n- Menu entries and nested entries accept `size: 'small' | 'normal' | 'large'` for row geometry.\n- **header**, **content**, **footer**, **children**, **banner**: Snippet<[SidebarApi]> - Escape hatches. The `header` snippet renders **first** in the header region, above `headerButton`, `search` and `headerMenu`.\n\n### Styling\n- **class**: string - Classes applied to the Sidebar root.\n- **theme**: SidebarThemeProps - Semantic part overrides such as `panel`, `header`, `nav`, `footer`, menu, search, rail, and mobile drawer parts. Every part, with the element it lands on, its variants and their default classes, is listed in the entasis skill's `theme-parts/sidebar.md`; the registry key is `sidebar`.\n\n## Motion\n\n- **motion** theme slot: the y-axis slide shared by collapsible groups, inline submenus, and\n tree branches. Takes `in` / `out` slide params plus a `duration` / `easing` motion token.\n- Ladder: `<Theme components={{ sidebar: { motion } }}>` → `setSidebarTheme({ motion })` →\n `theme.motion`. Reduced motion collapses it to 0.\n\n## Restyle recipes\n\nThe five asks that come up first, as the override to copy. Each one is rendered and asserted by\n`sidebar-recipes.svelte.test.ts`, so it cannot drift from the component. One rule behind them: a\ndefault written under a variant prefix (`data-[active-variant=solid]:data-active:bg-selected`,\n`data-[side=left]:border-r`) is only replaced by an override carrying the same prefixes; an\nunprefixed class coexists with it and loses on specificity. `theme-parts/sidebar.md` shows every\ndefault verbatim, prefixes included.\n\n- **Dark panel.** `dark` scopes the dark theme's variables to the panel, so every neutral ink inside\n flips with it (every theme also emits its variables on `.<name>`, so the class scopes the dark theme to one subtree); needs a theme named `dark`, which the documented setup declares.\n `theme={{ panel: { base: 'dark bg-slate-900' }, mobilePanel: { base: 'dark bg-slate-900' } }}`\n- **Active row in your colour.** `activeVariant=\"solid\"` plus the same prefix chain as the default:\n `theme={{ menuButton: { base: 'rounded-full', activeVariant: { solid: 'data-[active-variant=solid]:data-active:bg-indigo-600 data-[active-variant=solid]:data-active:text-white' } } }}`\n- **No hover change.** The overlay is a `::before` and the default also brightens the ink on hover:\n `theme={{ menuButton: { base: 'state-layer-none hover:text-inherit' }, subButton: { base: 'state-layer-none hover:text-neutral/70' } }}`\n- **Flat panel.** The edge is a prefixed `border-r` / `border-l` on the admin variant, the shadow a\n `raised-*` on the floating ones:\n `theme={{ panel: { base: 'raised-none', variant: { admin: 'data-[side=left]:border-r-0 data-[side=right]:border-l-0' } } }}`\n- **Row height.** A plain height beats the compound `h-control-*`: `theme={{ menuButton: { base: 'h-11' } }}`\n- **Bigger icons.** A prop, not a theme: `iconSize=\"large\"`.\n\n## Accessibility\n\n- The body navigation renders inside a named `<nav>` landmark (`data-sidebar=\"nav\"`), so assistive tech can jump straight to it and tell it apart from the activity bar's own landmark.\n- Active links set `aria-current=\"page\"`.\n- Collapsible rows and groups set `aria-expanded`.\n- Disabled buttons use `disabled`; disabled links omit `href`, use `aria-disabled` and `tabindex=-1`, and block activation.\n- Mobile drawer includes a backdrop button labelled \"Close Sidebar\".\n- Icon-collapsed rows keep their labels mounted and visually fade them, preserving accessible names and stable icon geometry.\n- Search, group controls, actions, and nested rows become inert before collapse can remove or hide them; focus returns to the owning visible row.\n- Nested groups, tree branches, and inline submenus use reversible height transitions for open, close, and sidebar-collapse changes.\n- Tree roots use menu-row styling and nested tree nodes use submenu-row styling. In desktop icon mode, root folders open a PopupMenu and descendants remain navigable through recursive Menu submenu popovers; root leaves retain direct navigation and tooltips.\n- When `rail` and `resizable` are both enabled, one edge control owns click-to-toggle, drag resize, and keyboard resize without overlapping hitboxes.\n- Hidden offcanvas sidebars keep that combined edge control while temporarily revealed. Resizing does not pin the sidebar open; leaving the panel, trigger, and handle tolerance re-hides it without discarding the configured width.\n- Both peeks (edge reveal and `expandOnHover`) stay open while focus is inside the panel or while an overlay opened from inside it is open, including nested submenus. They release about 120ms after the pointer, focus, and every such overlay are gone.\n- The activity bar is its own `<nav>` landmark with a `<ul>` of items, a roving tabindex, and ArrowUp/ArrowDown/Home/End navigation that loops and skips disabled items. Tab lands on the `isActive` item. Each square takes its accessible name from `label`, with a string or number `badge` appended to it, and shows `label` as a tooltip on hover and focus.\n- A hidden offcanvas panel is `inert`, so Tab never lands in a panel parked off screen; a peek makes it interactive again.\n\n## Notes\n\n- Dropdown menus use Entasis `PopupMenu` and `MenuItem[]`.\n- The component uses semantic Entasis tokens. Do not add shadcn `sidebar-*` color tokens.\n- Snippet icons from `entasis/icons/*` are the preferred icon format.\n";
28
+ readonly sidebar: "\n# Sidebar Component\n\nSidebar navigation with data-driven groups, icon collapse, mobile drawer behavior,\nrecursive tree groups, header/footer rows, search, actions, and snippet escape hatches.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n</script>\n```\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n\timport { houseIcon } from 'entasis/icons/house';\n\timport { gearIcon } from 'entasis/icons/gear';\n\n\tconst items: SidebarGroup[] = [\n\t\t{\n\t\t\tlabel: 'Workspace',\n\t\t\titems: [\n\t\t\t\t{ label: 'Dashboard', href: '/', icon: houseIcon, isActive: true },\n\t\t\t\t{ label: 'Settings', href: '/settings', icon: gearIcon }\n\t\t\t]\n\t\t}\n\t];\n</script>\n\n<Sidebar items={items}>\n\t{#snippet children({ toggle })}\n\t\t<header>\n\t\t\t<button type=\"button\" onclick={toggle}>Toggle</button>\n\t\t</header>\n\t\t<main>Page content</main>\n\t{/snippet}\n</Sidebar>\n```\n\n## AI-Safe Usage Contract\n\n1. Use `items` for normal navigation. Use `content` only when data-driven rows cannot express the layout.\n2. Use local Entasis icon snippets such as `houseIcon`, not Lucide component constructors.\n3. Use `MenuItem[]` from `entasis/menu` for `menu` and action dropdowns.\n4. Do not combine `menu` with `href` or `onclick` on the same row; use `action` for a trailing row menu.\n5. Keep `children`, `header`, `content`, `footer`, `banner`, and action snippets pure; they receive `SidebarApi`.\n6. Use `collapsible=\"icon\"` for icon rail behavior, `collapsible=\"offcanvas\"` for hidden desktop panels, and `collapsible=\"none\"` for fixed sidebars. Icon collapse automatically falls back to offcanvas when any data-driven row lacks an icon.\n7. Offcanvas sidebars reveal over the content from the screen edge by default when hidden; set `edgeReveal={false}` to disable that. A revealed hidden sidebar keeps its resize handle and dismisses through a small rectangular pointer tolerance.\n8. Set `keyboardShortcut={false}` when embedding Sidebar inside another shortcut-heavy surface.\n9. Sidebar owns navigation, resize mechanics, the lower application wall, and variant surface geometry. AppShell forwards its variant and composes PageShell inside that surface.\n10. Use `size` for typography, icon scale, and item height. Use `density` independently for section padding, gaps, and submenu spacing.\n11. Use `activityBar` for a persistent icon rail outside the panel (section switching, workspaces). Every item needs an `icon` and a `label`; the label is the accessible name and the tooltip. It is layout mode only: `mode=\"panel\"` renders the navigation panel alone. The rail does not change the panel by itself: keep the selected rail item in state from `onSelect`, mark it `isActive`, and either derive the Sidebar's `items` from it or, for an animated switch, give each rail item a view in `views` and set `view` from it.\n12. Use `expandOnHover` only with `collapsible=\"icon\"`. It is a temporary peek, not a toggle: the persisted collapsed state never changes, while the peeked panel renders with expanded semantics.\n13. Use `views` when the panel's contents change as the user moves through the app: sections switched from the activity bar or the route, and nested menus that open inside the panel. Key each view, set `parent` on nested ones, open them with a row's `view`, and drive `view` from state or the URL. Do not hand-animate `items` swaps.\n\n## Data Model\n\n### SidebarGroup\n- **label**: string - Group label, hidden in icon-collapsed mode.\n- **items**: SidebarMenuEntry[] - Menu rows.\n- **tree**: SidebarTreeNode[] - Recursive tree rows instead of menu items.\n- **action**: SidebarMenuActionDescriptor | SidebarMenuActionDescriptor[] | Snippet<[SidebarApi]> - Top-right group actions. Pass an array to pin several affordances (a `+` and a drag handle) to one group header; each renders as its own icon-only ghost button.\n- **collapsible**: boolean - Makes the group label a toggle.\n- **defaultOpen**: boolean - Initial collapsible group state.\n- **separator**: boolean - Divider before the group.\n\n### SidebarMenuEntry\n- **label**: string - Visible row label.\n- **icon**: SidebarIcon - Entasis icon snippet or string.\n- **iconColor**: Colors - Role tint for the leading icon, applied through `data-color`.\n- **iconVariant**: 'bare' | 'tile' - Leading icon treatment. `tile` paints a rounded square (`bg-color-muted text-color-muted-readable`) around the glyph, so per-project colour chips come from the role scale instead of hand-built markup.\n- **href**: string - Render as an anchor. Mutually exclusive with menu.\n- **onclick**: (event: MouseEvent) => void - Native click handler for button or anchor rows. Mutually exclusive with menu.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **disabled**: boolean - Disables button rows and marks anchor rows disabled.\n- **badge**: string | number - Trailing count/status, hidden in icon mode.\n- **tooltip**: string - Entasis Tooltip content in icon mode. Defaults to label.\n- **items**: SidebarMenuSubEntry[] - Inline nested menu.\n- **collapsible**: boolean - Set false for an always-open submenu.\n- **defaultOpen**: boolean - Initial nested menu state.\n- **menu**: MenuItem[] - Popup menu opened from the full row. Mutually exclusive with href/onclick.\n- **view**: string - Key of the `views` entry the row opens, sliding it in; the row shows a trailing chevron. Mutually exclusive with href, menu and items.\n- **action**: SidebarMenuActionDescriptor | Snippet<[SidebarApi]> - Hover/focus trailing action.\n\n### SidebarView\nOne named panel content in `views`.\n- **label**: string - The view's name, shown on the back row of the views nested under it.\n- **parent**: string - Key of the view this one is nested under. A nested view opens with a back row to its parent (named \"Back, <parent label>\"), and on mobile a swipe toward the inline end goes back. A view without `parent` is a top-level section.\n- **items**: SidebarGroup[] / **content**: Snippet<[SidebarApi]> - The view's body.\n- **headerButton**, **search**, **headerMenu**, **header**, **footerButton**, **footerMenu**, **footer** - Header and footer props for this view. Each one left undefined comes from the parent view, then from the Sidebar's own prop; `null` removes an inherited one.\n\nChanging the view slides the two views side by side, like pages: a deeper view comes in from the inline end while the old one leaves to the start, a shallower one slides back the other way, and between views at the same depth (sections) the later one in `views` counts as forward. When both views get every header and footer prop from the same place, the header and footer stay still and only the menu slides; when a view changes any of them, the whole panel slides as one page. `api.view` reads the current view and `api.setView(key)` changes it from a snippet.\n\n### SidebarActivityBar\nIcon rail pinned to the outer edge of the sidebar, visible in every display state.\n- **items**: SidebarActivityBarItem[] - Items rendered from the top.\n- **footerItems**: SidebarActivityBarItem[] - Items pinned to the end of the column.\n- **header** / **footer**: Snippet - Custom content before the first item and after the pinned ones.\n- **width**: string (default '3rem') - Column thickness, published as `--sidebar-width-activity`.\n- **label**: string - Accessible name for the column landmark. Set it whenever the panel also renders navigation.\n- **onSelect**: ({ item, index }) => void - Fires after an item is activated. `index` counts `items` then `footerItems`.\n\n### SidebarActivityBarItem\n- **icon**: SidebarIcon (required) - Icon rendered in the square.\n- **label**: string (required) - Accessible name and default tooltip; the square shows no text.\n- **id**: string - Stable render key.\n- **href** / **target** / **rel** - Render an anchor instead of a button.\n- **onclick**: (event: MouseEvent) => void - Native click handler.\n- **isActive**: boolean - Adds active styling and `aria-current=\"page\"`.\n- **badge**: string | number | Snippet - Corner badge pinned to the outer top corner. An empty string renders a bare dot. A string or number badge joins the accessible name (`\"Alerts, 3\"`); a dot and a Snippet badge are decorative, so put their meaning in `label`.\n- **disabled**: boolean - Blocks activation and skips the item during keyboard navigation.\n- **tooltip**: string | false - Tooltip override; `false` suppresses it.\n\n### SidebarMenuButtonItem\nUse for `headerButton`, `footerButton`, or direct `<SidebarMenuButton />` rows.\n- **icon**: SidebarIcon - Leading logo/icon.\n- **avatar**: { src?: string; alt?: string; fallback?: string } - Leading avatar.\n- **variant**: 'default' | 'brand' | 'compact'.\n- **title**: string - Primary text.\n- **subtitle**: string - Secondary text.\n- **trailing**: SidebarIcon | false | SidebarMenuActionDescriptor - Trailing content. An icon is decorative; an action descriptor (`{ icon, label, onclick }`, the same shape as a group action) renders its own icon-only ghost button beside the row, so a workspace card can carry its own collapse control without a custom `header` snippet. Descriptor handlers are `onclick(event, api)`, so `api.toggle()` is reachable.\n- **href** / **onclick** / **menu** - Choose link, button, or popup behavior. `onclick(event, api)` receives the SidebarApi beside the event, so `api.toggle()` is reachable from the row itself.\n- **menuIconClass**: string - Class override for option icons inside the popup menu.\n\n## Props\n\n### State\n- **open**: boolean (bindable, default true) - Desktop expanded state.\n- **defaultOpen**: boolean (default true) - Initial desktop state when `open` is omitted.\n- **onOpenChange**: (open: boolean) => void - Called once for a library-originated desktop state change. Repeated requests and parent prop updates stay silent.\n- **onDisplayStateChange**: (state: SidebarDisplayState) => void - Called once for a library-originated semantic display-state change.\n- **view**: string (bindable) - Key of the view on screen when `views` is set. Defaults to `defaultView`, then the first view.\n- **defaultView**: string - Initial view when `view` is omitted.\n- **onViewChange**: (view: string) => void - Called once for a library-originated view change: a view row, a back row, a swipe. Parent prop updates stay silent; with a route-driven `view`, navigate here.\n- **api.displayState**: 'expanded' | 'collapsed' | 'hidden' - Semantic desktop state; hidden means closed offcanvas. A hover peek does not change it.\n- **api.isPeeking**: boolean - True while a hover peek renders the collapsed panel at full width. Read it alongside `displayState` when a snippet hides content in icon mode.\n- **keyboardShortcut**: string | false (default 'b') - Ctrl/Cmd shortcut key.\n\n### Layout\n- **side**: 'left' | 'right' - Desktop and mobile side.\n- **variant**: 'admin' | 'floating' | 'inset' | 'split' | 'framed' - Sidebar geometry. `framed` is the admin geometry for a sidebar hosted inside a raised card (AppShell variant framed), with a `surface-recessed` well. `admin` renders the conventional full-height navigation column; `inset` integrates navigation into the lower wall with an inset content surface; `split` renders detached sidebar and content surfaces.\n- **size**: 'small' | 'normal' | 'large' (default 'normal') - Typography, icon, avatar, badge, leading-media, item-height, and search-height scale.\n- **iconSize**: 'small' | 'normal' | 'large' - Icon and leading-media scale inside the panel on its own; defaults to `size`. Menu rows, sub rows, group labels and the header button follow it; the activity bar keeps `size`.\n- **activeVariant**: 'soft' | 'outline' | 'solid' (default 'soft') - How active rows are painted. `soft` is the shared selected recipe (`selectedSoft`: `bg-selected-muted text-selected-muted-readable`), `solid` its loud counterpart (`selectedSolid`: `bg-selected text-selected-contrast`), `outline` a bordered surface card (`bg-surface border border-neutral-muted text-neutral`) that reads as a raised card on a tinted well. Each row carries the choice as `data-active-variant`, so no descendant selector is needed to restyle selection.\n- **density**: 'compact' | 'normal' | 'comfortable' (default 'normal') - Section padding, group padding, gaps, horizontal inset, and submenu spacing.\n- **collapsible**: 'offcanvas' | 'icon' | 'none' - Collapse behavior. Icon mode requires icons on every data-driven row and otherwise resolves to offcanvas.\n- **collapseIcon**: DisclosureIndicator — 'chevron' | 'plus-minus' | 'none' (default 'chevron') - Disclosure indicator drawn on collapsible menu rows. 'none' renders no indicator.\n- **mode**: 'layout' | 'panel' - Full resizing layout or only the visible navigation panel.\n- **frame**: 'viewport' | 'contained' - Standalone Sidebar positioning. Viewport mode uses Theme's dynamic window-height token; contained mode fills a positioned parent.\n- **width**: string - Expanded width.\n- **widthIcon**: string - Icon-collapsed width.\n- **widthMobile**: string - Mobile drawer width.\n- **rail**: boolean | 'line' | 'thumb' - Edge toggle rail. `true` keeps the thin line style; `thumb` renders a short visible handle with the same full-height hitbox. The appearance is preserved when the rail shares the resize control.\n- **activityBar**: SidebarActivityBar - Icon rail pinned outside the panel, in layout mode only (`mode=\"panel\"` renders the panel alone and ignores it). It never slides off screen: only the panel takes the offcanvas offset, and the reserved layout column is the panel width plus the rail width. It wears the surface of the variant's panel: a hairline column on the canvas for `admin`, the recessed well for `framed`, borderless on the canvas for `inset`, and a card of its own (the panel's radius, edge and gutter) for `floating` and `split`. On mobile it renders as a horizontal row at the top of the drawer.\n- **expandOnHover**: boolean (default false) - With `collapsible=\"icon\"`, hovering or focusing into the collapsed panel expands it to `width` over the page (`data-peek=\"true\"`) while the reserved column stays at `widthIcon`, so page content does not reflow. The persisted collapsed state is untouched, and the peeked panel renders exactly like an expanded one: group headers, badges, search, inline submenus, and inline tree branches all come back, and the rail or resize handle travels to its inner edge.\n- **edgeReveal**: boolean (default true) - Pointer/focus edge preview for hidden offcanvas sidebars. Hover reveal overlays content, remains resizable when configured, and re-hides after the pointer leaves its small rectangular tolerance. Dragging the sidebar closed suppresses immediate hover reopening until the pointer leaves the edge trigger; toggle/click opens persistently.\n- **resizable**: boolean | SidebarResizableOptions - Enables pointer and keyboard resizing while expanded, icon-collapsed, or temporarily edge-revealed. By default, collapse requires dragging 75% of `minWidth` beyond the minimum; override `collapseThreshold` for a custom boundary. Use `storageKey` to restore and persist the expanded width across sessions.\n - `onWidthChange({ width, isUserInteraction })` reports every expanded-width change with one named payload: continuously while the user resizes (`isUserInteraction: true`) and once when a stored width is restored (`isUserInteraction: false`).\n\n### Content\n- **items**: SidebarGroup[] - Data-driven body navigation.\n- **views**: Record<string, SidebarView> - Named panel contents, one on screen at a time, replacing `items` and `content`. See SidebarView.\n- **headerButton** / **footerButton**: SidebarMenuButtonItem - Sticky large rows.\n- **search**: SidebarSearch - Header search input; use its native `oninput` handler.\n- **headerMenu** / **footerMenu**: SidebarMenuEntry[] - Sticky quick menus.\n- Menu entries and nested entries accept `size: 'small' | 'normal' | 'large'` for row geometry.\n- **header**, **content**, **footer**, **children**, **banner**: Snippet<[SidebarApi]> - Escape hatches. The `header` snippet renders **first** in the header region, above `headerButton`, `search` and `headerMenu`.\n\n### Styling\n- **class**: string - Classes applied to the Sidebar root.\n- **theme**: SidebarThemeProps - Semantic part overrides such as `panel`, `header`, `nav`, `footer`, menu, search, rail, and mobile drawer parts. Every part, with the element it lands on, its variants and their default classes, is listed in the entasis skill's `theme-parts/sidebar.md`; the registry key is `sidebar`.\n\n## Motion\n\n- **motion** theme slot: the y-axis slide shared by collapsible groups, inline submenus, and\n tree branches. Takes `in` / `out` slide params plus a `duration` / `easing` motion token.\n- Ladder: `<Theme components={{ sidebar: { motion } }}>` → `setSidebarTheme({ motion })` →\n `theme.motion`. Reduced motion collapses it to 0.\n- The `view` variant (`part: 'view'`) is the pager between views: `in.x` / `out.x` is how far a view travels (default `'100%'`: the two views slide side by side like pages) while it fades between `opacity` (default 0) and 1, at the `slow` duration on the `enter` easing. The back swipe moves the same layers the view change would, with the same travel and opacity. Reduced motion makes the switch instant while a swipe still follows the finger.\n\n## Restyle recipes\n\nThe five asks that come up first, as the override to copy. Each one is rendered and asserted by\n`sidebar-recipes.svelte.test.ts`, so it cannot drift from the component. One rule behind them: a\ndefault written under a variant prefix (`data-[active-variant=solid]:data-active:bg-selected`,\n`data-[side=left]:border-r`) is only replaced by an override carrying the same prefixes; an\nunprefixed class coexists with it and loses on specificity. `theme-parts/sidebar.md` shows every\ndefault verbatim, prefixes included.\n\n- **Dark panel.** `dark` scopes the dark theme's variables to the panel, so every neutral ink inside\n flips with it (every theme also emits its variables on `.<name>`, so the class scopes the dark theme to one subtree); needs a theme named `dark`, which the documented setup declares.\n `theme={{ panel: { base: 'dark bg-slate-900' }, mobilePanel: { base: 'dark bg-slate-900' } }}`\n- **Active row in your colour.** `activeVariant=\"solid\"` plus the same prefix chain as the default:\n `theme={{ menuButton: { base: 'rounded-full', activeVariant: { solid: 'data-[active-variant=solid]:data-active:bg-indigo-600 data-[active-variant=solid]:data-active:text-white' } } }}`\n- **No hover change.** The overlay is a `::before` and the default also brightens the ink on hover:\n `theme={{ menuButton: { base: 'state-layer-none hover:text-inherit' }, subButton: { base: 'state-layer-none hover:text-neutral/70' } }}`\n- **Flat panel.** The edge is a prefixed `border-r` / `border-l` on the admin variant, the shadow a\n `raised-*` on the floating ones:\n `theme={{ panel: { base: 'raised-none', variant: { admin: 'data-[side=left]:border-r-0 data-[side=right]:border-l-0' } } }}`\n- **Row height.** A plain height beats the compound `h-control-*`: `theme={{ menuButton: { base: 'h-11' } }}`\n- **Bigger icons.** A prop, not a theme: `iconSize=\"large\"`.\n\n## Accessibility\n\n- The body navigation renders inside a named `<nav>` landmark (`data-sidebar=\"nav\"`), so assistive tech can jump straight to it and tell it apart from the activity bar's own landmark.\n- Active links set `aria-current=\"page\"`.\n- Collapsible rows and groups set `aria-expanded`.\n- Disabled buttons use `disabled`; disabled links omit `href`, use `aria-disabled` and `tabindex=-1`, and block activation.\n- Mobile drawer includes a backdrop button labelled \"Close Sidebar\".\n- Icon-collapsed rows keep their labels mounted and visually fade them, preserving accessible names and stable icon geometry.\n- Search, group controls, actions, and nested rows become inert before collapse can remove or hide them; focus returns to the owning visible row.\n- Nested groups, tree branches, and inline submenus use reversible height transitions for open, close, and sidebar-collapse changes.\n- Tree roots use menu-row styling and nested tree nodes use submenu-row styling. In desktop icon mode, root folders open a PopupMenu and descendants remain navigable through recursive Menu submenu popovers; root leaves retain direct navigation and tooltips.\n- When `rail` and `resizable` are both enabled, one edge control owns click-to-toggle, drag resize, and keyboard resize without overlapping hitboxes.\n- Hidden offcanvas sidebars keep that combined edge control while temporarily revealed. Resizing does not pin the sidebar open; leaving the panel, trigger, and handle tolerance re-hides it without discarding the configured width.\n- Both peeks (edge reveal and `expandOnHover`) stay open while focus is inside the panel or while an overlay opened from inside it is open, including nested submenus. They release about 120ms after the pointer, focus, and every such overlay are gone.\n- The activity bar is its own `<nav>` landmark with a `<ul>` of items, a roving tabindex, and ArrowUp/ArrowDown/Home/End navigation that loops and skips disabled items. Tab lands on the `isActive` item. Each square takes its accessible name from `label`, with a string or number `badge` appended to it, and shows `label` as a tooltip on hover and focus.\n- A hidden offcanvas panel is `inert`, so Tab never lands in a panel parked off screen; a peek makes it interactive again.\n- Views: when focus sat in the view being replaced, it lands on the back row after going deeper and on the row that opened the view after coming back. A view change from outside the panel (a route) leaves focus where it is. The back swipe is a shortcut; the back row stays the accessible path.\n\n## Notes\n\n- Dropdown menus use Entasis `PopupMenu` and `MenuItem[]`.\n- The component uses semantic Entasis tokens. Do not add shadcn `sidebar-*` color tokens.\n- Snippet icons from `entasis/icons/*` are the preferred icon format.\n";
29
29
  readonly button: "\n# Button Component\n\nThe Button component is a flexible and customizable button element that supports various variants, sizes, colors, and interactive states.\n\n## Basic Usage\n\n```svelte\n<Button>Click me</Button>\n<Button variant=\"outline\">Outline Button</Button>\n<Button color=\"primary\" size=\"large\">Large Primary Button</Button>\n```\n\n## Props\n\n### Core Props\n- **variant**: 'solid' | 'outline' | 'soft' | 'ghost' | 'link' (default: 'solid')\n - solid: Filled background with color\n - outline: Transparent background with colored border\n - soft: Muted color background\n - ghost: Transparent background with a transient state layer on hover and press\n - link: Text-only styling with underline on hover\n\n- **color**: 'primary' | 'secondary' | 'neutral' | 'danger' | 'success' | 'warning' | 'info' (default: 'neutral')\n - Determines the color scheme of the button\n\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n - small: 28px height, smaller padding and text\n - normal: 32px height, standard padding\n - large: 36px height, larger padding and text\n\n### Layout Props\n- **fullWidth**: boolean (default: false) - Makes button take full width of container\n- **squared**: boolean - Makes button square (aspect-ratio 1:1), auto-determined if only prefix/suffix is provided\n- **disabled**: boolean (default: false) - Disables button interaction\n- **loading**: boolean (default: false) - Shows the Theme-configured loading spinner and disables interaction\n\n### State Props\nDescribe the meaning; the Button writes the ARIA. Never pass an aria-* attribute to a Button.\n- **pressed**: boolean - Toggle state of a button that stays on or off (a bold button in a toolbar, a \"show password\" eye). Rendered as aria-pressed\n- **selected**: boolean - Chosen state of a button acting as one option among several (a tab, a listbox option). Rendered as aria-selected\n- **expanded**: boolean - Whether the surface this button opens is showing. Rendered as aria-expanded. A entasis surface (Popover, PopupMenu, Select, Combobox) sets this on its own trigger, so pass it only for a surface you open yourself\n- **haspopup**: 'dialog' | 'menu' | 'listbox' | 'tree' | 'grid' | true - What the surface this button opens contains. Rendered as aria-haspopup, and likewise set by a entasis surface on its own trigger\n\n### Link Props\n- **href**: string - Makes button render as anchor tag\n- **target**: string - Link target (e.g., \"_blank\")\n- **rel**: string - Link relationship\n\n### Event Props\n- **onclick**: (event: MouseEvent) => void - Native click event handler\n- **onpointerenter**: (event: PointerEvent) => void - Native pointer enter event handler\n- **onpointerleave**: (event: PointerEvent) => void - Native pointer leave event handler\n\n### Content Props (Slots)\n- **children**: Snippet - Main button content\n- **prefix**: Snippet - Content before main text (typically icons)\n- **suffix**: Snippet - Content after main text (typically icons)\n\n### Advanced Props\n- **label**: string - Accessible label applied as aria-label on the root element (required for icon-only buttons)\n- **ref**: HTMLElement - Reference to the button element\n- **class**: string - Additional CSS classes\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\nThe button follows this DOM structure:\n```\n<Button>\n\t<Prefix /> <!-- Optional prefix content -->\n\t<Children /> <!-- Main button content -->\n\t<Suffix /> <!-- Optional suffix content -->\n</Button>\n```\n\n## Examples\n\n### Basic Buttons\n```svelte\n<Button>Default Button</Button>\n<Button variant=\"outline\" color=\"primary\">Primary Outline</Button>\n<Button variant=\"soft\" color=\"danger\">Soft Danger</Button>\n<Button variant=\"ghost\">Ghost Button</Button>\n<Button variant=\"link\">Link Button</Button>\n```\n\n### With Icons\n```svelte\n<Button>\n\t{#snippet prefix()}\n\t\t{@render icon()}\n\t{/snippet}\n\tAdd Item\n</Button>\n\n<Button squared>\n\t{#snippet prefix()}\n\t\t{@render icon()}\n\t{/snippet}\n</Button>\n```\n\n### Interactive States\n```svelte\n<Button loading>Loading...</Button>\n<Button disabled>Disabled</Button>\n<Button fullWidth>Full Width Button</Button>\n```\n\n### As Link\n```svelte\n<Button href=\"/dashboard\" target=\"_blank\">Go to Dashboard</Button>\n```\n\n### With Event Handlers\n```svelte\n<script lang=\"ts\">\n\tfunction handleClick(event: MouseEvent) {\n\t\tconsole.log('Clicked:', event.currentTarget);\n\t}\n</script>\n\n<Button onclick={handleClick}>\n\tClick\n</Button>\n```\n\n### Custom Styling\n```svelte\n<Button class=\"lift-4 border-2\" color=\"primary\" variant=\"outline\">\n\tCustom Styled\n</Button>\n```\n\n## Accessibility\n\n- Automatically sets appropriate ARIA roles (button/link)\n- Supports keyboard navigation\n- Disabled state prevents interaction\n- Loading state provides visual feedback\n\n## Notes\n\n- When `href` is provided, renders as `<a>` tag, otherwise `<button>`\n- `squared` is automatically determined when only prefix or suffix is provided without children\n- All event handlers respect disabled state\n- Icon sizing is automatically adjusted based on button size\n\n## Theme Customization\n\nThe Button component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **root**: Main button container styles\n- **prefix**: Styles for prefix content (icons before text)\n- **suffix**: Styles for suffix content (icons after text)\n\n### Theme Type Definition\n\n```typescript\nimport type { ButtonThemeProps } from 'entasis/button';\n\n// Example theme customization\nconst customTheme: ButtonThemeProps = {\n root: {\n base: 'custom-base-classes',\n size: {\n small: 'custom-small-classes',\n normal: 'custom-normal-classes',\n large: 'custom-large-classes'\n },\n color: {\n primary: 'bg-blue-500 text-white',\n danger: 'bg-red-500 text-white'\n },\n variant: {\n solid: 'bg-color text-color-contrast',\n outline: 'border-2 border-color'\n }\n },\n prefix: {\n size: {\n small: 'w-3 h-3',\n normal: 'w-4 h-4',\n large: 'w-5 h-5'\n }\n }\n};\n```\n\n### Available Variants\n\n**root**:\n- base: Base classes applied to all buttons\n- Variants:\n - size: 'small' | 'normal' | 'large' - Controls padding, text size, and height\n - color: 'primary' | 'secondary' | 'neutral' | 'danger' | 'success' | 'warning' | 'info' - Color scheme\n - variant: 'solid' | 'outline' | 'soft' | 'ghost' | 'link' - Visual style variant\n - loading: boolean - Loading state styling\n - disabled: boolean - Disabled state styling\n - squared: boolean - Square button styling\n - fullWidth: boolean - Full width styling\n\n**prefix**:\n- base: Base classes for prefix content\n- Variants:\n - size: 'small' | 'normal' | 'large' - Icon size based on button size\n\n**suffix**:\n- base: Base classes for suffix content\n- Variants:\n - size: 'small' | 'normal' | 'large' - Icon size based on button size\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<Button \n theme={{\n root: {\n base: 'rounded-full lift-4',\n size: {\n large: 'px-8 py-4 text-xl'\n }\n }\n }}\n>\n Custom Styled Button\n</Button>\n```\n\n**Color Variant Customization**:\n```svelte\n<Button \n color=\"primary\"\n theme={{\n root: {\n color: {\n primary: 'state-layer bg-linear-to-r from-blue-500 to-purple-500'\n }\n }\n }}\n>\n Gradient Button\n</Button>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setButtonTheme } from '../components/Button/index.ts';\n \n setButtonTheme({\n root: {\n variant: {\n solid: 'state-layer bg-color text-color-contrast lift-3 hover:lift-4 transition-shadow',\n outline: 'state-layer border-2 border-color'\n }\n },\n prefix: {\n size: {\n normal: 'w-5 h-5'\n }\n }\n });\n</script>\n```\n";
30
30
  readonly 'button-group': "\n# ButtonGroup Component\n\nThe ButtonGroup component displays a collection of related buttons as a cohesive group with shared styling properties.\n\n## Basic Usage\n\n```svelte\n<ButtonGroup \n\titems={[\n\t\t{ children: 'First' },\n\t\t{ children: 'Second' },\n\t\t{ children: 'Third' }\n\t]}\n/>\n```\n\n## Props\n\n### Core Props\n- **items**: Array<ButtonProps> (required) - Array of button configurations\n - Each button can have all standard Button component props\n\n### Shared Button Props\n- **size**: 'small' | 'normal' | 'large' - Applied to all buttons in the group\n- **color**: 'primary' | 'secondary' | 'neutral' | 'danger' | 'success' | 'warning' | 'info' - Shared color for all buttons\n- **variant**: 'solid' | 'outline' | 'soft' | 'ghost' | 'link' - Shared variant for all buttons\n- **disabled**: boolean - Disables all buttons in the group\n\n### Styling Props\n- **class**: string - Additional CSS classes for the group container\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\n```\n<ButtonGroup>\n\t<Button />\n\t<Button />\n\t<Button />\n</ButtonGroup>\n```\n\n## Examples\n\n### Basic Button Group\n```svelte\n<ButtonGroup \n\titems={[\n\t\t{ children: 'Left' },\n\t\t{ children: 'Center' },\n\t\t{ children: 'Right' }\n\t]}\n/>\n```\n\n### With Shared Styling\n```svelte\n<ButtonGroup \n\tsize=\"large\"\n\tcolor=\"primary\"\n\tvariant=\"outline\"\n\titems={[\n\t\t{ children: 'Option 1' },\n\t\t{ children: 'Option 2' },\n\t\t{ children: 'Option 3' }\n\t]}\n/>\n```\n\n### With Icons\n```svelte\n<script lang=\"ts\">\n\timport { ButtonGroup } from 'entasis/button-group';\n\timport { textAlignLeftIcon } from 'entasis/icons/textAlignLeft';\n\timport { textAlignCenterIcon } from 'entasis/icons/textAlignCenter';\n\timport { textAlignRightIcon } from 'entasis/icons/textAlignRight';\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tprefix: textAlignLeftIcon,\n\t\t\tchildren: 'Left' \n\t\t},\n\t\t{ \n\t\t\tprefix: textAlignCenterIcon,\n\t\t\tchildren: 'Center' \n\t\t},\n\t\t{ \n\t\t\tprefix: textAlignRightIcon,\n\t\t\tchildren: 'Right' \n\t\t}\n\t]}\n/>\n```\n\n### With Individual Click Handlers\n```svelte\n<script>\n\tfunction handleOption(option) {\n\t\tconsole.log(`Selected: ${option}`);\n\t}\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tchildren: 'Save',\n\t\t\tonclick: () => handleOption('save')\n\t\t},\n\t\t{ \n\t\t\tchildren: 'Cancel',\n\t\t\tonclick: () => handleOption('cancel')\n\t\t}\n\t]}\n/>\n```\n\n### Disabled Group\n```svelte\n<ButtonGroup \n\tdisabled\n\titems={[\n\t\t{ children: 'Option 1' },\n\t\t{ children: 'Option 2' }\n\t]}\n/>\n```\n\n### Icon Only Buttons\n```svelte\n<script lang=\"ts\">\n\timport { ButtonGroup } from 'entasis/button-group';\n\timport { textBIcon } from 'entasis/icons/textB';\n\timport { textItalicIcon } from 'entasis/icons/textItalic';\n\timport { textUnderlineIcon } from 'entasis/icons/textUnderline';\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tsquared: true,\n\t\t\tprefix: textBIcon\n\t\t},\n\t\t{ \n\t\t\tsquared: true,\n\t\t\tprefix: textItalicIcon\n\t\t},\n\t\t{ \n\t\t\tsquared: true,\n\t\t\tprefix: textUnderlineIcon\n\t\t}\n\t]}\n/>\n```\n\n### Mixed Button States\n```svelte\n<ButtonGroup \n\tvariant=\"outline\"\n\titems={[\n\t\t{ children: 'Active', color: 'primary' },\n\t\t{ children: 'Default', color: 'neutral' },\n\t\t{ children: 'Disabled', disabled: true }\n\t]}\n/>\n```\n\n### Segmented Control\n```svelte\n<script>\n\tlet selected = $state('week');\n</script>\n\n<ButtonGroup \n\titems={[\n\t\t{ \n\t\t\tchildren: 'Day',\n\t\t\tvariant: selected === 'day' ? 'solid' : 'ghost',\n\t\t\tonclick: () => selected = 'day'\n\t\t},\n\t\t{ \n\t\t\tchildren: 'Week',\n\t\t\tvariant: selected === 'week' ? 'solid' : 'ghost',\n\t\t\tonclick: () => selected = 'week'\n\t\t},\n\t\t{ \n\t\t\tchildren: 'Month',\n\t\t\tvariant: selected === 'month' ? 'solid' : 'ghost',\n\t\t\tonclick: () => selected = 'month'\n\t\t}\n\t]}\n/>\n```\n\n## Styling\n\nButtonGroup automatically:\n- Removes border-radius from middle buttons\n- Adjusts borders to prevent double borders\n- Creates a cohesive, connected appearance\n- Maintains consistent spacing\n\n## Accessibility\n\n- Each button maintains full keyboard accessibility\n- Focus styles are preserved\n- Disabled state cascades properly\n- Screen readers announce each button individually\n\n## Notes\n\n- Individual button props override shared props\n- Buttons are rendered in the order provided\n- The group container can be styled with the `class` prop\n- All Button component features are supported for individual buttons\n\n## Theme Customization\n\nThe ButtonGroup component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **root**: Main button group container styles\n\n### Available Variants\n\n**root**:\n- base: Base classes for button group container (handles border radius and border connections between buttons)\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<ButtonGroup \n items={buttons}\n theme={{\n root: {\n base: 'flex items-center rounded-lg overflow-hidden'\n }\n }}\n/>\n```\n\n**Custom Group Styling**:\n```svelte\n<ButtonGroup \n items={buttons}\n theme={{\n root: {\n base: 'flex items-center gap-0 border-2 border-primary rounded-lg overflow-hidden'\n }\n }}\n/>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setButtonGroupTheme } from '../components/ButtonGroup/index.ts';\n \n setButtonGroupTheme({\n root: {\n base: 'flex items-center first-child:rounded-r-none last-child:rounded-l-none'\n }\n });\n</script>\n```\n";
31
31
  readonly 'segmented-control': "\n# SegmentedControl\n\nA compact single-selection input for switching between a small set of mutually exclusive modes. It uses radiogroup/radio semantics and an animated indicator that slides and resizes to the selected item.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n import { SegmentedControl } from '../components/SegmentedControl/index.ts';\n import { gridFourIcon } from 'entasis/icons/gridFour';\n import { listIcon } from 'entasis/icons/list';\n\n const items = [\n { value: 'grid', label: 'Grid', icon: gridFourIcon },\n { value: 'list', label: 'List', icon: listIcon }\n ] as const;\n\n let value = $state<(typeof items)[number]['value']>('grid');\n</script>\n\n<SegmentedControl {items} bind:value />\n```\n\n## Props\n\n- **items**: `readonly SegmentedControlItem[]` — required options with unique `value` fields.\n- **value**: `string` — bindable selected value; defaults to the first enabled item.\n- **defaultValue**: `string` — initial selected value when value is omitted.\n- **onValueChange**: `(value: string) => void` — called once after user interaction changes the value.\n- **item**: `Snippet<[SegmentedControlItem]>` — replaces the default item renderer.\n- **size**: `'small' | 'normal' | 'large'` — defaults to `'normal'`.\n- **color**: semantic color — controls the selected pill and focus ring; defaults to `'neutral'`.\n- **variant**: `'normal' | 'pill'` — controls corner radius; defaults to the moderately rounded `'normal'` shape.\n- **disabled**: `boolean` — disables the full control.\n- **label**: `string` — accessible name for the radiogroup; defaults to the catalog's \"Segmented control\".\n- **class**: additional root classes.\n- **theme**: component theme overrides.\n\n## Item shape\n\n```ts\ntype SegmentedControlItem<Value extends string = string> = {\n value: Value;\n label?: string | Snippet;\n icon?: string | Snippet;\n disabled?: boolean;\n};\n```\n\nThe default renderer displays `icon`, then `label`. `label` is also the segment's accessible name: a string is painted and spoken, a snippet names the segment through its own content, and an icon-only segment (no `label`) falls back to its `value`.\n\n## Custom item renderer\n\n```svelte\n<SegmentedControl {items} bind:value>\n {#snippet item(option)}\n <span>{option.label}</span>\n <span>{option.count}</span>\n {/snippet}\n</SegmentedControl>\n```\n\nExtra fields on item objects remain available to the snippet through generic inference.\n\n## Shapes\n\n`variant=\"normal\"` uses a moderately rounded track and segments. Use `variant=\"pill\"` for the fully rounded stadium shape.\n\n```svelte\n<SegmentedControl {items} bind:value />\n<SegmentedControl {items} bind:value variant=\"pill\" />\n```\n\n## Keyboard behavior\n\n- Tab enters on the selected item, or the first enabled item when no value matches.\n- Arrow Left/Right moves and selects, wrapping at the ends.\n- Home/End selects the first/last enabled item.\n- Disabled items are skipped.\n\nEach item has an invisible, size-aware pointer target that extends beyond the visual track: 36px for small, 44px for normal, and 48px for large. Adjacent targets meet at the midpoint of the configured gap instead of overlapping.\n\nUse SegmentedControl for mode or value selection. Use Tabbar when changing navigational views with tab semantics.\n";
@@ -105,7 +105,7 @@ export declare const componentMcpRegistry: {
105
105
  readonly 'menu-option': "\n# MenuOption Component\n\nThe MenuOption component is a flexible menu item that can be used in dropdown menus, navigation menus, or context menus. It supports title/description layout, custom content, icons, colors, and various interaction handlers.\n\n## Basic Usage\n\n```svelte\n<MenuOption>\n\t{#snippet title()}\n\t\tMy Menu Item\n\t{/snippet}\n</MenuOption>\n```\n\n## Props\n\n### Core Props\n- **size**: 'small' | 'normal' | 'large' (default: 'normal') - Scales typography and icons only\n- **density**: 'compact' | 'normal' | 'comfortable' (default: 'normal') - Owns paddings, gaps and min-height; reflected as `data-density` on the row. Combine freely with size.\n- **color**: Colors (default: 'primary') - Sets the semantic text color and persistent active tint\n - Available: primary, secondary, success, warning, danger, info, neutral\n\n### Content Slots\nEither use **title/description** OR **children** (mutually exclusive):\n- **title**: Snippet - Main text of the menu item\n- **description**: Snippet - Secondary descriptive text below the title\n- **children**: Snippet - Custom content (replaces title+description)\n\n### Icon/Badge Slots\n- **prefix**: Snippet - Icon or badge at the start of the menu item\n- **suffix**: Snippet - Icon or badge at the end of the menu item\n\n### Interaction Props\n- **onclick**: (event: MouseEvent) => void - Native click event handler\n- **onpointerenter**: (event: PointerEvent) => void - Native pointer enter event handler\n- **onpointerleave**: (event: PointerEvent) => void - Native pointer leave event handler\n\n### Link Props\n- **href**: string - If provided, renders as an anchor element\n- **target**: string - Link target attribute (e.g., '_blank')\n- **rel**: string - Link rel attribute (e.g., 'noopener noreferrer')\n\n### Listbox / option props\nMenuOption is also the shared row primitive for the listbox family (Command, Select, Combobox).\n- **role**: string - ARIA role override. Defaults to button/link/menuitem; pass `option` inside a `listbox`. Menu passes `menuitemradio` for option items that set `selected`.\n- **highlighted**: boolean - Keyboard-active state (virtual focus). Applies the highlight background and reflects to `data-highlighted`. Menus omit this and rely on `useNavigation` setting `data-highlighted` imperatively.\n- **selected**: boolean - Sets `data-selected` plus the role-appropriate state: `aria-checked` for checkable roles (`menuitemradio`, `menuitemcheckbox`, `checkbox`, `radio`, `switch`) and `aria-selected` for listbox `option` rows (pass a check icon via `suffix`).\n- **disabled**: boolean - Dims the row, sets `aria-disabled`, blocks pointer/click.\n- **attrs**: Record<string, any> - Extra attributes/handlers spread onto the row (`id`, `data-value`, `tabindex`, `onpointermove`, `onmousedown`).\n\n### Styling Props\n- **class**: string - Additional CSS classes\n- **theme**: MenuOptionThemeProps - Custom theme overrides\n- **as**: string - Override the automatic element type detection\n\n## Menu Structure\n\n```\n<MenuOption>\n\t<Prefix /> <!-- Icon/badge at start -->\n\t<Content> <!-- Main content area -->\n\t\t<Title /> <!-- Primary text -->\n\t\t<Description /> <!-- Secondary text -->\n\t</Content>\n\t<Suffix /> <!-- Icon/badge at end -->\n</MenuOption>\n```\n\n## Examples\n\n### Basic Menu Item with Title\n```svelte\n<MenuOption>\n\t{#snippet title()}\n\t\tSettings\n\t{/snippet}\n</MenuOption>\n```\n\n### Menu Item with Title and Description\n```svelte\n<MenuOption>\n\t{#snippet title()}\n\t\tAccount Settings\n\t{/snippet}\n\t{#snippet description()}\n\t\tManage your account preferences and security\n\t{/snippet}\n</MenuOption>\n```\n\n### Menu Item with Prefix Icon\n```svelte\n<script lang=\"ts\">\n\timport { MenuOption } from 'entasis/menu-option';\n\timport { gearIcon } from 'entasis/icons/gear';\n</script>\n\n<MenuOption>\n\t{#snippet prefix()}\n\t\t{@render gearIcon()}\n\t{/snippet}\n\t{#snippet title()}\n\t\tSettings\n\t{/snippet}\n</MenuOption>\n```\n\n### Menu Item with Suffix Icon\n```svelte\n<script lang=\"ts\">\n\timport { MenuOption } from 'entasis/menu-option';\n\timport { caretRightIcon } from 'entasis/icons/caretRight';\n</script>\n\n<MenuOption>\n\t{#snippet title()}\n\t\tMore Options\n\t{/snippet}\n\t{#snippet suffix()}\n\t\t{@render caretRightIcon()}\n\t{/snippet}\n</MenuOption>\n```\n\n### Menu Item with Both Icons\n```svelte\n<script lang=\"ts\">\n\timport { MenuOption } from 'entasis/menu-option';\n\timport { userIcon } from 'entasis/icons/user';\n\timport { checkIcon } from 'entasis/icons/check';\n</script>\n\n<MenuOption>\n\t{#snippet prefix()}\n\t\t{@render userIcon()}\n\t{/snippet}\n\t{#snippet title()}\n\t\tJohn Doe\n\t{/snippet}\n\t{#snippet suffix()}\n\t\t{@render checkIcon({ class: 'text-success' })}\n\t{/snippet}\n</MenuOption>\n```\n\n### Different Sizes\n```svelte\n<MenuOption size=\"small\">\n\t{#snippet title()}Small Menu Item{/snippet}\n</MenuOption>\n\n<MenuOption size=\"normal\">\n\t{#snippet title()}Normal Menu Item{/snippet}\n</MenuOption>\n\n<MenuOption size=\"large\">\n\t{#snippet title()}Large Menu Item{/snippet}\n</MenuOption>\n```\n\n### Different Densities\n```svelte\n<!-- density scales paddings/gaps/min-height; size scales text/icons -->\n<MenuOption density=\"compact\">\n\t{#snippet title()}Small row{/snippet}\n</MenuOption>\n\n<MenuOption density=\"normal\">\n\t{#snippet title()}Normal row{/snippet}\n</MenuOption>\n\n<MenuOption density=\"comfortable\">\n\t{#snippet title()}Large row{/snippet}\n</MenuOption>\n```\n\n### Different Colors\n```svelte\n<MenuOption color=\"primary\">\n\t{#snippet title()}Primary{/snippet}\n</MenuOption>\n\n<MenuOption color=\"danger\">\n\t{#snippet title()}Delete{/snippet}\n</MenuOption>\n\n<MenuOption color=\"success\">\n\t{#snippet title()}Approve{/snippet}\n</MenuOption>\n```\n\n### Interactive Menu Item with Click Handler\n```svelte\n<script>\n\tlet count = $state(0);\n</script>\n\n<MenuOption onclick={() => count++}>\n\t{#snippet title()}\n\t\tClicked {count} times\n\t{/snippet}\n</MenuOption>\n```\n\n### Menu Item with Hover Handlers\n```svelte\n<script>\n\tlet isHovered = $state(false);\n</script>\n\n<MenuOption \n\tonpointerenter={() => isHovered = true}\n\tonpointerleave={() => isHovered = false}\n>\n\t{#snippet title()}\n\t\t{isHovered ? 'Hovering!' : 'Hover over me'}\n\t{/snippet}\n</MenuOption>\n```\n\n### Menu Item as Link\n```svelte\n<MenuOption href=\"/settings\">\n\t{#snippet title()}\n\t\tGo to Settings\n\t{/snippet}\n</MenuOption>\n```\n\n### External Link\n```svelte\n<script lang=\"ts\">\n\timport { MenuOption } from 'entasis/menu-option';\n\timport { arrowSquareOutIcon } from 'entasis/icons/arrowSquareOut';\n</script>\n\n<MenuOption \n\thref=\"https://example.com\" \n\ttarget=\"_blank\" \n\trel=\"noopener noreferrer\"\n>\n\t{#snippet title()}\n\t\tVisit External Site\n\t{/snippet}\n\t{#snippet suffix()}\n\t\t{@render arrowSquareOutIcon()}\n\t{/snippet}\n</MenuOption>\n```\n\n### Custom Content with Children\n```svelte\n<MenuOption>\n\t{#snippet children()}\n\t\t<div class=\"flex items-center gap-2\">\n\t\t\t<img src=\"/avatar.jpg\" alt=\"User\" class=\"w-8 h-8 rounded-full\" />\n\t\t\t<div>\n\t\t\t\t<div class=\"font-bold\">John Doe</div>\n\t\t\t\t<div class=\"text-xs text-neutral/70\">john@example.com</div>\n\t\t\t</div>\n\t\t</div>\n\t{/snippet}\n</MenuOption>\n```\n\n### Menu with Multiple Options\n```svelte\n<script lang=\"ts\">\n\timport { MenuOption } from 'entasis/menu-option';\n\timport { gearIcon } from 'entasis/icons/gear';\n\timport { userIcon } from 'entasis/icons/user';\n\timport { signOutIcon } from 'entasis/icons/signOut';\n\timport { questionIcon } from 'entasis/icons/question';\n</script>\n\n<div class=\"w-64 bg-surface rounded-xl border border-neutral-muted p-1\">\n\t<MenuOption>\n\t\t{#snippet prefix()}{@render userIcon()}{/snippet}\n\t\t{#snippet title()}Profile{/snippet}\n\t\t{#snippet description()}View and edit your profile{/snippet}\n\t</MenuOption>\n\t\n\t<MenuOption>\n\t\t{#snippet prefix()}{@render gearIcon()}{/snippet}\n\t\t{#snippet title()}Settings{/snippet}\n\t\t{#snippet description()}Manage your preferences{/snippet}\n\t</MenuOption>\n\t\n\t<MenuOption>\n\t\t{#snippet prefix()}{@render questionIcon()}{/snippet}\n\t\t{#snippet title()}Help & Support{/snippet}\n\t</MenuOption>\n\t\n\t<div class=\"border-t border-neutral-muted my-1\"></div>\n\t\n\t<MenuOption color=\"danger\">\n\t\t{#snippet prefix()}{@render signOutIcon()}{/snippet}\n\t\t{#snippet title()}Log Out{/snippet}\n\t</MenuOption>\n</div>\n```\n\n### With Custom Theme\n```svelte\n<MenuOption \n\ttheme={{\n\t\troot: { base: 'rounded-full' },\n\t\ttitle: { base: 'font-bold' }\n\t}}\n>\n\t{#snippet title()}\n\t\tCustom Styled Menu Item\n\t{/snippet}\n</MenuOption>\n```\n\n### With Attachments\n```svelte\n<script lang=\"ts\">\n\timport { MenuOption } from 'entasis/menu-option';\n\timport { spinnerOverlay } from 'entasis/spinner-overlay';\n\t\n\tlet loading = $state(false);\n\t\n\tasync function handleClick() {\n\t\tloading = true;\n\t\tawait fetch('/api/action');\n\t\tloading = false;\n\t}\n</script>\n\n<MenuOption \n\tonclick={handleClick}\n\t{@attach spinnerOverlay({ loading })}\n>\n\t{#snippet title()}\n\t\tPerform Action\n\t{/snippet}\n</MenuOption>\n```\n\n### Override Element Type\n```svelte\n<!-- Force render as div even with onclick -->\n<MenuOption as=\"div\" onclick={() => console.log('clicked')}>\n\t{#snippet title()}\n\t\tCustom Element Type\n\t{/snippet}\n</MenuOption>\n```\n\n## Accessibility\n\n- Automatically sets appropriate `role` attribute based on element type\n - `button` for interactive elements\n - `link` for anchor elements\n - `menuitem` for non-interactive elements\n- Supports keyboard navigation when used as button or link\n- Proper semantic HTML structure\n- Color foreground meets accessibility standards\n\n## Element Type Detection\n\nThe component automatically determines the HTML element to render:\n1. If `as` prop is provided → uses that element\n2. If `href` is provided → renders as `<a>`\n3. Otherwise → renders as `<button>`\n4. Otherwise → renders as `<div>`\n\n## Notes\n\n- Title and description snippets are mutually exclusive with children snippet\n- Hover states automatically apply background color based on the color prop\n- Prefix icons are positioned at the start, suffix icons at the end (with ml-auto)\n- Hover and virtual focus use the shared current-color state layer\n- Works well within Popover or Dialog components for dropdown menus\n\n## Theme Customization\n\nThe MenuOption component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **root**: Main menu option container styles\n- **title**: Menu option title text styles\n- **description**: Menu option description text styles\n- **prefix**: Prefix icon/content styles\n- **suffix**: Suffix icon/content styles\n- **content**: Content wrapper styles\n\n### Available Variants\n\n**root**:\n- base: Base classes applied to all menu options\n- Variants:\n - size: 'small' | 'normal' | 'large' - Text size\n - density: 'compact' | 'normal' | 'comfortable' - Padding, gap, and min-height\n - color: 'primary' | 'secondary' | 'neutral' | 'danger' | 'success' | 'warning' | 'info' - Color scheme and hover states\n\n**title**:\n- base: Base classes for title text\n- Variants:\n - size: 'small' | 'normal' | 'large' - Text size\n\n**description**:\n- base: Base classes for description text\n- Variants:\n - size: 'small' | 'normal' | 'large' - Text size\n\n**prefix**:\n- base: Base classes for prefix content\n- Variants:\n - size: 'small' | 'normal' | 'large' - Icon size\n - align: 'start' | 'center' - Vertical alignment\n\n**suffix**:\n- base: Base classes for suffix content\n- Variants:\n - size: 'small' | 'normal' | 'large' - Icon size\n\n**content**:\n- base: Base classes for content wrapper\n- Variants:\n - density: 'compact' | 'normal' | 'comfortable' - Gap spacing between title and description\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<MenuOption\n theme={{\n root: {\n base: 'rounded-lg',\n density: {\n comfortable: 'px-4 py-3 min-h-12'\n }\n },\n title: {\n size: {\n large: 'text-lg font-semibold'\n }\n }\n }}\n>\n {#snippet title()}\n Custom Menu Option\n {/snippet}\n</MenuOption>\n```\n\n**Color Customization**:\n```svelte\n<MenuOption \n color=\"danger\"\n theme={{\n root: {\n color: {\n danger: 'text-red-600 highlight:bg-red-50 highlight:text-red-700'\n }\n }\n }}\n>\n {#snippet title()}\n Delete Item\n {/snippet}\n</MenuOption>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setMenuOptionTheme } from '../components/MenuOption/index.ts';\n \n setMenuOptionTheme({\n root: {\n base: 'rounded-md transition-colors',\n density: {\n normal: 'px-3 py-2'\n }\n },\n prefix: {\n size: {\n normal: 'w-5 h-5'\n }\n }\n });\n</script>\n```\n";
106
106
  readonly 'popup-menu': "\n# PopupMenu Component\n\nThe PopupMenu component is a wrapper around Popover that renders a Menu inside. It provides all Popover functionality (positioning, transitions, triggers) with integrated Menu rendering for quick menu implementations.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\timport { PopupMenu } from 'entasis/popup-menu';\n\timport { userIcon } from 'entasis/icons/user';\n\timport { gearIcon } from 'entasis/icons/gear';\n\timport { signOutIcon } from 'entasis/icons/signOut';\n\t\n\tconst menuItems = [\n\t\t{ type: 'option', prefix: userIcon, title: 'Profile' },\n\t\t{ type: 'option', prefix: gearIcon, title: 'Settings' },\n\t\t{ type: 'separator' },\n\t\t{ type: 'option', prefix: signOutIcon, title: 'Logout', color: 'danger' }\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu\n\ttrigger={{ content: 'Open Menu' }}\n\tposition=\"bottom-start\"\n\tmenu={{ items: menuItems }}\n/>\n```\n\n## Props\n\n### Menu Props\n- **menu**: MenuProps (required)\n - items: MenuItem[] - Array of menu items (buttons, options, separators)\n - class: string - Custom class for the menu container\n - theme: MenuThemeProps - Theme overrides for menu and its items\n - submenuMode: 'auto' | 'popover' | 'stack' - defaults to auto; mobileSheet menus stack submenus automatically\n\n- **closeOnItemClick**: boolean (default: true)\n - Whether to close the menu when a menu item (button or link) is clicked\n - Set to false for menus that should stay open for multiple selections\n\n### Popover Props (All Available)\n\n#### Positioning & Layout\n- **position**: ResponsiveProps<Placement> - Popover position relative to trigger\n - Values: 'top', 'bottom', 'left', 'right', 'top-start', 'bottom-start', etc.\n \n- **offset**: number - Distance from trigger in pixels\n\n- **size**: ResponsiveProps<'small' | 'normal' | 'large'> - Popover size\n\n- **fitTrigger**: boolean - Make popover width match trigger width\n\n#### Trigger Configuration\n- **trigger**: Snippet | ButtonProps | false\n - Snippet: Custom trigger rendering with popover state\n - ButtonProps: Render a button with these props\n - false: No trigger (control externally via open)\n\n#### Interaction Behavior\n- **open**: boolean (bindable) - Control open state externally\n\n- **openOnClick**: boolean (default: true) - Open on trigger click\n\n- **openOnHover**: boolean (default: false) - Open on trigger hover\n\n- **delay**: number (default: 100) - Delay before opening on hover (ms)\n\n- **closeOnClickOutside**: boolean (default: true) - Close when clicking outside\n\n- **closeOnEscape**: boolean (default: true) - Close on Escape key\n\n- **closeOnMouseLeave**: boolean (default: false) - Close when the pointer leaves the hover safe area. The safe area is the trigger, the panel, and a prediction cone toward the submenu, so a diagonal move into the submenu keeps it open while sibling rows stay hoverable.\n\n- **debugSafeArea**: boolean (default: false) - Show hover safe-area overlays. Trigger/panel rectangles render in blue; the prediction cone toward the submenu renders in orange.\n\n#### Visual & Animation\n- **transition**: ResponsiveProps<FSOProps> - Custom transition configuration\n\n- **directedTransition**: boolean (default: true) - Transition direction based on position\n\n- **lockScroll**: boolean (default: true) - Lock body scroll when open\n\n- **class**: string - Custom class for popover dialog\n\n- **mobileSheet**: boolean (default: false) - Render as a bottom sheet on mobile viewports. With menu.submenuMode='auto', nested submenus become stacked views.\n\n#### Advanced\n- **id**: string - Custom ID for popover element\n\n- **ref**: HTMLElement | null - External reference element (instead of trigger)\n\n- **onOpenChange**: (open: boolean) => void - Called for component-owned state changes\n\n- **onAfterOpen**: (popover: PopoverState) => void - Called after the popover opens\n\n- **onAfterClose**: (popover: PopoverState) => void - Called after the popover closes\n\n- **theme**: PopoverThemeProps - Theme overrides for popover\n\n## Structure\n\nPopupMenu renders as:\n```\n<Popover {...popoverProps}>\n <Menu {...menuProps} />\n</Popover>\n```\n\nThe Menu inherits the Popover's dialog styling (background, border, shadow, etc.)\n\n## Examples\n\n### Basic Dropdown Menu\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tconst items = [\n\t\t{ type: 'option', title: 'New File' },\n\t\t{ type: 'option', title: 'Open...' },\n\t\t{ type: 'option', title: 'Save' },\n\t\t{ type: 'separator' },\n\t\t{ type: 'option', title: 'Exit' }\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu\n\ttrigger={{ content: 'File', variant: 'ghost' }}\n\tposition=\"bottom-start\"\n\tmenu={{ items }}\n/>\n```\n\n### User Profile Menu\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\timport { userIcon } from 'entasis/icons/user';\n\timport { gearIcon } from 'entasis/icons/gear';\n\timport { questionIcon } from 'entasis/icons/question';\n\timport { signOutIcon } from 'entasis/icons/signOut';\n\t\n\tconst items = [\n\t\t{ type: 'option', prefix: userIcon, title: 'Profile', href: '/profile' },\n\t\t{ type: 'option', prefix: gearIcon, title: 'Settings', href: '/settings' },\n\t\t{ type: 'option', prefix: questionIcon, title: 'Help' },\n\t\t{ type: 'separator' },\n\t\t{ type: 'option', prefix: signOutIcon, title: 'Log Out', color: 'danger' }\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu\n\ttrigger={{ content: 'John Doe', variant: 'outline' }}\n\tposition=\"bottom-end\"\n\tmenu={{ items }}\n/>\n```\n\n### Context Menu (Right Click)\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\timport { trashIcon } from 'entasis/icons/trash';\n\timport { copyIcon } from 'entasis/icons/copy';\n\timport { shareIcon } from 'entasis/icons/share';\n\t\n\tlet open = $state(false);\n\tlet contextMenuRef = $state<HTMLElement | null>(null);\n\t\n\tfunction handleContextMenu(e: MouseEvent) {\n\t\te.preventDefault();\n\t\tcontextMenuRef = e.currentTarget as HTMLElement;\n\t\topen = true;\n\t}\n\t\n\tconst items = [\n\t\t{ type: 'option', title: 'Open' },\n\t\t{ type: 'option', prefix: copyIcon, title: 'Copy' },\n\t\t{ type: 'option', prefix: shareIcon, title: 'Share' },\n\t\t{ type: 'separator' },\n\t\t{ type: 'option', prefix: trashIcon, title: 'Delete', color: 'danger' }\n\t] satisfies MenuItem[];\n</script>\n\n<div oncontextmenu={handleContextMenu}>\n\tRight-click me\n</div>\n\n<PopupMenu\n\ttrigger={false}\n\tbind:open\n\tref={contextMenuRef}\n\tposition=\"bottom-start\"\n\tmenu={{ items }}\n/>\n```\n\n### With Custom Trigger Snippet\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tlet open = $state(false);\n\n\tconst items = [\n\t\t{ type: 'option', title: 'Option 1' },\n\t\t{ type: 'option', title: 'Option 2' }\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu bind:open position=\"bottom\" menu={{ items }}>\n\t{#snippet trigger(popover)}\n\t\t<button onclick={() => popover.toggle()}>\n\t\t\tCustom Trigger {open ? '▲' : '▼'}\n\t\t</button>\n\t{/snippet}\n</PopupMenu>\n```\n\n### Hover Menu\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tconst items = [\n\t\t{ type: 'option', title: 'Quick Action 1' },\n\t\t{ type: 'option', title: 'Quick Action 2' }\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu\n\ttrigger={{ content: 'Hover Me', variant: 'ghost' }}\n\topenOnHover={true}\n\topenOnClick={false}\n\tdelay={200}\n\tcloseOnMouseLeave={true}\n\tmenu={{ items }}\n/>\n```\n\n### Actions Menu with Buttons\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tconst items = [\n\t\t{ type: 'button', children: 'Save Draft', variant: 'ghost', fullWidth: true },\n\t\t{ type: 'button', children: 'Publish', variant: 'solid', color: 'primary', fullWidth: true },\n\t\t{ type: 'separator' },\n\t\t{ type: 'button', children: 'Delete', variant: 'soft', color: 'danger', fullWidth: true }\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu\n\ttrigger={{ content: 'Actions' }}\n\tposition=\"bottom-end\"\n\tmenu={{ items }}\n/>\n```\n\n### External Control with Bindable State\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tlet menuOpen = $state(false);\n\t\n\tconst items = [\n\t\t{ type: 'option', title: 'Item 1' },\n\t\t{ type: 'option', title: 'Item 2' }\n\t] satisfies MenuItem[];\n\t\n\tfunction openMenu() {\n\t\tmenuOpen = true;\n\t}\n</script>\n\n<button onclick={openMenu}>Open Menu Externally</button>\n\n<PopupMenu\n\ttrigger={{ content: 'Menu' }}\n\tbind:open={menuOpen}\n\tmenu={{ items }}\n/>\n```\n\n### Positioned Menu\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tconst items = [\n\t\t{ type: 'option', title: 'Top Start' },\n\t\t{ type: 'option', title: 'Example' }\n\t] satisfies MenuItem[];\n</script>\n\n<div class=\"flex gap-2\">\n\t<PopupMenu trigger={{ content: 'Top Start' }} position=\"top-start\" menu={{ items }} />\n\t<PopupMenu trigger={{ content: 'Bottom' }} position=\"bottom\" menu={{ items }} />\n\t<PopupMenu trigger={{ content: 'Right' }} position=\"right\" menu={{ items }} />\n</div>\n```\n\n### With Custom Theme\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tconst items = [\n\t\t{ type: 'option', title: 'Themed Option 1' },\n\t\t{ type: 'option', title: 'Themed Option 2' }\n\t] satisfies MenuItem[];\n\t\n\tconst menuTheme = {\n\t\troot: { base: 'gap-3' },\n\t\toption: {\n\t\t\troot: { base: 'px-4 py-3' }\n\t\t}\n\t};\n</script>\n\n<PopupMenu\n\ttrigger={{ content: 'Themed Menu' }}\n\tmenu={{ items, theme: menuTheme }}\n/>\n```\n\n### Keep Menu Open for Multiple Interactions\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\tlet selections = $state<string[]>([]);\n\t\n\tconst items = [\n\t\t{ \n\t\t\ttype: 'option', \n\t\t\ttitle: 'Option 1',\n\t\t\tonclick: () => selections.push('Option 1')\n\t\t},\n\t\t{ \n\t\t\ttype: 'option', \n\t\t\ttitle: 'Option 2',\n\t\t\tonclick: () => selections.push('Option 2')\n\t\t},\n\t\t{ type: 'separator' },\n\t\t{ \n\t\t\ttype: 'button', \n\t\t\tchildren: 'Done',\n\t\t\tvariant: 'solid',\n\t\t\tfullWidth: true\n\t\t}\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu\n\ttrigger={{ content: 'Select Multiple' }}\n\tcloseOnItemClick={false}\n\tmenu={{ items }}\n/>\n```\n\n### Nested Submenus\n```svelte\n<script lang=\"ts\">\n\timport type { MenuItem } from 'entasis/menu';\n\n\tconst items = [\n\t\t{ type: 'option', title: 'New File' },\n\t\t{\n\t\t\ttype: 'submenu',\n\t\t\ttitle: 'More Options',\n\t\t\tmenu: [\n\t\t\t\t{ type: 'option', title: 'Sub Option 1' },\n\t\t\t\t{ type: 'option', title: 'Sub Option 2' }\n\t\t\t]\n\t\t}\n\t] satisfies MenuItem[];\n</script>\n\n<PopupMenu trigger={{ content: 'Main Menu' }} position=\"bottom-start\" menu={{ items }} />\n```\n\n## Accessibility\n\n- Inherits all Popover accessibility features: the trigger carries `aria-haspopup=\"menu\"`, `aria-expanded`, and `aria-controls`, and focus returns to it on close\n- Menu items have appropriate roles (`menuitem`, or `menuitemradio` with `aria-checked` for options that set `selected`) and keyboard navigation\n- Type-ahead: typing letters moves the highlight to the next matching item\n- Escape closes only the topmost open layer, so a submenu closes before its parent (configurable)\n- An outside press closes every layer above the one pressed (configurable)\n\n## Notes\n\n- PopupMenu is a lightweight wrapper - all Popover props work as expected\n- Menu styling inherits from Popover's dialog theme\n- Use `closeOnClickOutside={true}` (default) for typical dropdown menus\n- Use `closeOnMouseLeave={true}` for hover-triggered quick menus; a prediction cone toward the submenu keeps it open during the diagonal move while sibling rows stay hoverable.\n- The `menu` prop accepts full MenuProps including theme forwarding to child components\n";
107
107
  readonly dialog: "\n# Dialog Component\n\nThe Dialog component (also known as Modal) displays content in a layer above the page, blocking interaction with the rest of the application until dismissed.\n\n## Basic Usage\n\n```svelte\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\timport { Button } from 'entasis/button';\n\t\t\n</script>\n// By default Dialog comes with a button that trigger them, no need to define a callback and a $state\n<Dialog title=\"Dialog Title\">\n\tDialog content goes here\n</Dialog>\n```\n\n## Props\n\n### Core Props\n- **open**: boolean (bindable) - Controls dialog visibility\n- **defaultOpen**: boolean (default: false) - Initial state when open is not provided\n- **id**: string - Unique identifier for the dialog\n- **type**: 'fullScreen' | 'drawerRight' | 'drawerLeft' | 'drawerBottom' | 'drawerTop' | 'alert' | 'modal' (default: 'modal')\n - fullScreen: Full screen dialog overlay\n - drawerRight: Drawer sliding in from the right\n - drawerLeft: Drawer sliding in from the left\n - drawerBottom: Drawer sliding in from the bottom\n - drawerTop: Drawer sliding in from the top\n - alert: Alert-style dialog\n - modal: Standard modal dialog\n - Supports responsive values: pass a `Partial<Record<Breakpoint, DialogType>>` record such as `{ xs: 'drawerBottom', md: 'modal' }` to vary the type per breakpoint (breakpoint is one of 'xs' | 'sm' | 'md' | 'lg' | 'xl', tracked live from the viewport; the nearest defined key at or below the active one wins)\n- **responsive**: boolean (default: true) - When the resolved type is `modal`, collapse it into a `drawerBottom` bottom sheet on mobile (viewport < 768px). The sheet inherits swipe-to-dismiss and the drag thumb. Set false to keep a centered modal on every screen size\n\n### Layout Props\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n - small: Compact dialog size\n - normal: Standard dialog size\n - large: Larger dialog size\n\n### Event Props\n- **onOpenChange**: (open: boolean) => void - Called once for each library-requested state change\n- **onAfterOpen**: (payload: DialogState) => void - Called after the open transition finishes\n- **onAfterClose**: (payload: DialogState) => void - Called after the close transition finishes\n\n### Slot Props\n- **title**: string | Snippet<[DialogState]> - Dialog title\n- **description**: string | Snippet<[DialogState]> - Dialog description\n- **children**: Snippet<[DialogState]> - Main dialog content\n- **header**: Snippet - Custom header content\n- **footer**: Snippet - Custom footer content\n- **trigger**: Snippet | (ButtonProps & { content?: string }) - Custom trigger button\n- **closeButton**: Snippet - Custom close button\n\n### Behavior Props\n- **closeOnEscape**: boolean (default: true) - Close when Escape key is pressed\n- **closeOnClickOutside**: boolean (default: true) - Close when clicking outside dialog\n- **closable**: boolean (default: true) - Whether dialog can be closed\n- **swipeToDismiss**: boolean (default: true for drawer types) - Drag the drawer toward its edge to dismiss. Direction-aware (drawerRight drags right, drawerBottom drags down, etc.) and never hijacks inner scrolling; opt elements out with `data-no-swipe`\n- **thumb**: boolean (default: true) - Drag thumb bar shown on swipe-dismissable drawers (inner edge, orientation follows the drawer side, oversized hitbox); set false to hide. Themeable via the `thumb` theme part\n- **inset**: a spacing step or `'none'` — how far a drawer stands off the screen edge for this dialog. Defaults to the theme's `designTokens.drawerInset` (`'layout-md'`). At `'none'` the drawer is edge to edge, its edge corners square off and only the corners facing the page stay rounded\n- **swipeFrom**: 'panel' | 'handle' (default: 'panel') - Where a swipe can start: anywhere on the panel, or only the drag handles (thumb and header)\n\n### Visual Props\n- **transition**: TransitionConfig - Custom transition animation\n\n### Styling Props\n- **class**: string - Additional CSS classes\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\n```\n<DialogOverlay>\n\t<DialogContent>\n\t\t<DialogHeader>\n\t\t\t<Title />\n\t\t\t<Description />\n\t\t\t<CloseButton />\n\t\t</DialogHeader>\n\t\t<DialogBody>\n\t\t\t<Children />\n\t\t</DialogBody>\n\t\t<DialogFooter />\n\t</DialogContent>\n</DialogOverlay>\n```\n\n## Examples\n\n### More Examples\n\n### A with a custom trigger \n```svelte\n\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\timport { Button } from 'entasis/button';\n\t\n\tlet open = $state(false);\n</script>\n\n\n<Dialog bind:open title=\"Welcome\">\n\t<p>This is a basic dialog.</p>\n\t\n\t{#snippet trigger(dialog)}\n\t\t<Button onclick={() => dialog.open()}>Open</Button>\n\t{/snippet}\n</Dialog>\n```\n\n### A with a custom as button props \n```svelte\n\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\timport { Button } from 'entasis/button';\n\t\n\tlet open = $state(false);\n</script>\n\n\n<Dialog bind:open title=\"Welcome\"\ntrigger={{\ncontent:\"Click me\",\ncolor:\"secondary\",\nsize:\"small\"\n}}\n>\n\t<p>This is a basic dialog.</p>\t\n</Dialog>\n```\n\n### With Description\n\n```svelte\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\t\n</script>\n\n<Dialog \t\n\ttitle=\"Confirm Action\"\n\tdescription=\"Are you sure you want to continue?\"\n>\n\t<p>This action cannot be undone.</p>\n</Dialog>\n```\n\n### Different Sizes\n\n```svelte\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\t\n\tlet open = $state(false);\n</script>\n\n```\n\n### Advanced Example\n\n```svelte\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\timport { Button } from 'entasis/button';\n\t\n\tlet open = $state(false);\n\t\n\tfunction handleConfirm() {\n\t\tconsole.log('Confirmed!');\n\t\topen = false;\n\t}\n</script>\n\n<Dialog bind:open title=\"Confirm\">\n\tAre you sure?\n\t\n\t{#snippet footer()}\n\t\t<div class=\"flex gap-2 justify-end\">\n\t\t\t<Button variant=\"ghost\" onclick={() => open = false}>\n\t\t\t\tCancel\n\t\t\t</Button>\n\t\t\t<Button color=\"danger\" onclick={handleConfirm}>\n\t\t\t\tConfirm\n\t\t\t</Button>\n\t\t</div>\n\t{/snippet}\n</Dialog>\n```\n\n### Drawer Types\n\n```svelte\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\t\n\t\n</script>\n\n<!-- Right drawer -->\n<Dialog type=\"drawerRight\" title=\"Side Drawer\">\n\tThis slides in from the right\n</Dialog>\n\n<!-- Left drawer -->\n<Dialog type=\"drawerLeft\" title=\"Left Drawer\">\n\tThis slides in from the left\n</Dialog>\n\n<!-- Bottom drawer -->\n<Dialog type=\"drawerBottom\" title=\"Bottom Drawer\">\n\tThis slides in from the bottom\n</Dialog>\n\n<!-- Full screen -->\n<Dialog type=\"fullScreen\" title=\"Full Screen\">\n\tFull screen dialog content\n</Dialog>\n```\n\n### Custom Header\n\n```svelte\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\timport { warningIcon } from 'entasis/icons/warning';\n</script>\n\n<Dialog bind:open>\n\t{#snippet header()}\n\t\t<div class=\"flex items-center gap-2\">\n\t\t\t{@render warningIcon()}\n\t\t\t<h2>Warning</h2>\n\t\t</div>\n\t{/snippet}\n\t\n\tThis is important!\n</Dialog>\n```\n\n\n### With Lifecycle Hooks\n\n```svelte\n<script>\n\timport { Dialog } from 'entasis/dialog';\n\t\n\tlet open = $state(false);\n</script>\n\n<Dialog \n\tbind:open\n\ttitle=\"Lifecycle\"\n\tonAfterOpen={(payload) => console.log('Dialog opened', payload)}\n\tonAfterClose={(payload) => console.log('Dialog closed', payload)}\n>\n\tWatch the console\n</Dialog>\n```\n\n## State Management\n\nThe Dialog component uses a `DialogState` instance that is passed to all slot snippets. This state object provides:\n\n- **id**: string - Dialog identifier\n- **type**: DialogType - Current dialog type\n- **size**: Size - Current dialog size\n- **open()**: () => void - Method to open the dialog\n- **close()**: () => void - Method to close the dialog\n\n## Accessibility\n\n- Initial focus goes to an `[autofocus]` / `[data-autofocus]` element inside the dialog, else the first tabbable control (the close button is skipped), else the panel itself\n- Tab and Shift+Tab are contained inside the dialog, and the rest of the page is `inert` while a modal is open\n- Focus is restored to the opener after the close transition finishes, just before `onAfterClose`\n- `aria-labelledby` links the `title` and `aria-describedby` links the `description`\n- Escape closes only the topmost open layer and an outside press dismisses the layers above the one pressed; both honour `closeOnEscape` / `closable` / `closeOnClickOutside` through the shared layer stack\n- Body scroll is locked when dialog is open\n\n## Notes\n\n- Multiple dialogs can be stacked\n- Last opened dialog receives focus\n- Background overlay prevents interaction with page\n- Scroll locking prevents background scroll\n- Transitions are customizable\n\n## Theme Customization\n\nThe Dialog component uses a theme object that can be customized using the `theme` prop or by setting a global theme.\n\n### Theme Structure\n\nThe theme object contains the following parts:\n- **motion**: Open/close transition preset, keyed by `type` (drawers fly from their edge). Takes\n `in` / `out` FSO params plus a `duration` / `easing` motion token; the `transition` prop wins\n over it\n- **root**: Dialog overlay/backdrop styles\n- **content**: Dialog content container styles\n- **thumb**: Drag thumb bar styles (type variant controls per-side placement)\n- **header**: Dialog header section styles\n- **footer**: Dialog footer section styles\n- **closeButton**: Close button styles\n- **title**: Dialog title text styles\n- **description**: Dialog description text styles\n\n### Theme Type Definition\n\n```typescript\nimport type { DialogThemeProps } from 'entasis/dialog';\n\n// Example theme customization\nconst customTheme: DialogThemeProps = {\n root: {\n base: 'z-[+50] fixed inset-0 flex',\n scroll: {\n inner: 'overflow-hidden',\n outer: 'overflow-auto'\n }\n },\n align: {\n type: {\n fullScreen: 'justify-center items-center',\n drawerRight: 'justify-end',\n drawerLeft: 'justify-start',\n drawerBottom: 'justify-center items-end',\n drawerTop: 'justify-center items-start',\n modal: 'justify-center',\n alert: 'justify-center'\n }\n },\n content: {\n size: {\n small: 'max-w-md w-full',\n normal: 'max-w-xl w-full',\n large: 'max-w-3xl w-full'\n },\n type: {\n fullScreen: 'h-full w-full max-w-full',\n drawerRight: 'rounded-r-[var(--drawer-edge-radius)] h-full',\n modal: ''\n }\n },\n header: {\n size: {\n small: '',\n normal: '',\n large: ''\n }\n },\n title: {\n size: {\n small: '',\n normal: '',\n large: ''\n }\n },\n description: {\n size: {\n small: '',\n normal: '',\n large: ''\n }\n }\n};\n```\n\n### Available Variants\n\n**root**:\n- base: Base classes for dialog overlay/backdrop\n- Variants:\n - size: 'small' | 'normal' | 'large' - Size constraints\n - type: 'fullScreen' | 'drawerRight' | 'drawerLeft' | 'drawerBottom' | 'drawerTop' | 'modal' | 'alert' - Dialog type/layout\n\n**content**:\n- base: Base classes for dialog content container\n- Variants:\n - size: 'small' | 'normal' | 'large' - Content max-width\n - type: 'fullScreen' | 'drawerRight' | 'drawerLeft' | 'drawerBottom' | 'drawerTop' | 'modal' | 'alert' - Content styling based on type\n\n**header**:\n- base: Base classes for header section\n- Variants:\n - size: 'small' | 'normal' | 'large' - Size-based styling\n\n**footer**:\n- base: Base classes for footer section\n- Variants:\n - size: 'small' | 'normal' | 'large' - Size-based styling\n\n**closeButton**:\n- base: Base classes for close button\n- Variants:\n - size: 'small' | 'normal' | 'large' - Size-based styling\n\n**title**:\n- base: Base classes for title text\n- Variants:\n - size: 'small' | 'normal' | 'large' - Text size\n\n**description**:\n- base: Base classes for description text\n- Variants:\n - size: 'small' | 'normal' | 'large' - Text size\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<Dialog \n title=\"Custom Dialog\"\n theme={{\n content: {\n base: 'rounded-2xl raised-5',\n size: {\n normal: 'max-w-2xl'\n }\n },\n header: {\n base: 'border-b-2 border-primary'\n }\n }}\n>\n Custom styled dialog content\n</Dialog>\n```\n\n**Drawer Type Customization**:\n```svelte\n<Dialog \n type=\"drawerRight\"\n theme={{\n align: {\n type: {\n drawerRight: 'justify-end'\n }\n },\n backdrop: {\n base: 'bg-black/50'\n },\n content: {\n type: {\n drawerRight: 'rounded-l-xl raised-5'\n }\n }\n }}\n>\n Custom drawer styling\n</Dialog>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setDialogTheme } from '../components/Dialog/index.ts';\n \n setDialogTheme({\n root: {\n base: 'backdrop-blur-sm'\n },\n content: {\n base: 'lift-5 border-2',\n size: {\n normal: 'max-w-2xl'\n }\n },\n title: {\n base: 'text-2xl font-bold'\n }\n });\n</script>\n```\n";
108
- readonly 'floating-window': "\n# FloatingWindow Component\n\nFloatingWindow renders a non-modal, portaled utility window that can be moved, resized, minimized into a configurable viewport-edge dock, restored, and closed. Multiple windows inside the same Theme provider coordinate their z-order and stack independently by dock placement. The Theme keeps floating windows below modal Dialog surfaces, so an open window remains mounted behind a dialog and returns unchanged when the dialog closes.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n import { Button } from '../components/Button/index.ts';\n import { FloatingWindow } from '../components/FloatingWindow/index.ts';\n\n let open = $state(false);\n</script>\n\n<Button onclick={() => (open = true)}>Open notes</Button>\n\n<FloatingWindow bind:open title=\"Notes\">\n <p>Window content remains interactive alongside the page.</p>\n</FloatingWindow>\n```\n\n## Props\n\n- **id**: string - Stable DOM id. Generated when omitted.\n- **open**: boolean (default: true) - Bindable rendered state.\n- **defaultOpen**: boolean (default: true) - Initial state when open is not provided.\n- **minimized**: boolean (default: false) - Bindable docked state.\n- **dockPlacement**: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom' (default: 'bottom-left') - Edge and alignment used by the minimized dock. Top and bottom placements stack horizontally; left and right placements use a vertical title bar and stack vertically.\n- **title**: Slot<FloatingWindowPayload> (required) - Window title as text or a snippet.\n- **children**: Slot<FloatingWindowPayload> - Main content.\n- **dragFrom**: 'header' | 'window' (default: 'header') - Restricts dragging to the header or allows any non-interactive surface to start a drag.\n- **draggable**: boolean (default: true) - Enables pointer dragging.\n- **resizable**: boolean (default: true) - Enables four edge and four corner resize handles.\n- **minimizable**: boolean (default: true) - Shows the minimize control.\n- **closable**: boolean (default: true) - Shows the close control.\n- **closeOnEscape**: boolean (default: true) - Closes the topmost expanded floating window when Escape is pressed.\n- **position**: { x: number; y: number } - Bindable viewport-relative top-left position. The first render is centered when omitted.\n- **dimensions**: { width: number; height: number; min?: [width, height]; max?: [width, height] } (default: 480 x 320, minimum 280 x 160) - Bindable pixel dimensions and optional constraint tuples. Maximum dimensions remain additionally constrained to the viewport.\n- **class**: string - Additional classes on the visible window.\n- **theme**: FloatingWindowThemeProps - Per-instance theme overrides.\n- **ref**: HTMLDivElement - Bindable reference to the visible window or minimized dock item.\n- **onOpenChange**: (open: boolean) => void - Runs once for each library-requested state change.\n- **onAfterOpen**: (payload) => void - Runs after the open transition finishes.\n- **onAfterClose**: (payload) => void - Runs after the close transition finishes.\n- **onMinimize**: (payload) => void - Runs after minimize state updates.\n- **onRestore**: (payload) => void - Runs after restore state updates.\n- **onMove**: ({ position, window }) => void - Runs when a move commits.\n- **onResize**: ({ dimensions, window }) => void - Runs when a pointer or keyboard resize commits.\n\n## Dragging\n\nHeader dragging is the default because it preserves text selection and content interactions. With `dragFrom=\"window\"`, buttons, links, inputs, editable content, resize handles, and descendants marked `data-floating-window-no-drag` remain excluded from drag starts.\n\n## Accessibility\n\n- The expanded surface uses a non-modal `dialog` role and is labelled by its title.\n- Close, minimize, and restore controls are native Entasis buttons with accessible labels.\n- Edge resize handles use `separator` semantics and support arrow-key resizing; hold Shift for a larger step.\n- Opening focuses the non-modal window, closing restores focus to its previous owner, and only the topmost expanded floating window handles Escape.\n- Alt+Arrow moves the focused window; hold Shift for a larger step.\n- Corner handles are pointer-only because a diagonal separator has no valid ARIA orientation.\n- The component does not trap focus or hide page content because it is explicitly non-modal.\n\n## Theme Parts\n\n- **root**: Floating window surface and drag/resize states.\n- **header**: Default title bar and drag handle.\n- **title**: Header title.\n- **actions**: Header control group.\n- **control**: Header and dock icon buttons.\n- **scrollArea**: Flexible ScrollArea root that owns body scrolling.\n- **content**: Padded content inside the ScrollArea viewport.\n- **resizeHandle**: Edge and corner handles by direction.\n- **dockItem**: Minimized surface.\n- **dockTitle**: Full-width restore button in the dock item.\n- **dockTitleText**: Truncated title text and lateral writing direction.\n- **dockActions**: Dock restore and close controls.\n\n## Motion\n\n- **motion** theme slot, keyed by `phase`: `flight` times the crossfade between window and\n dock pill, `enter` / `exit` the scale fallback when there is no counterpart.\n- Only `duration` / `easing` (plus the fallback's `scale` / `opacity`) are read.\n- Ladder: `<Theme components={{ 'floating-window': { motion } }}>` →\n `setFloatingWindowTheme({ motion })` → `theme.motion`. Read once, at mount.\n";
108
+ readonly 'floating-window': "\n# FloatingWindow Component\n\nFloatingWindow renders a portaled utility window, non-modal unless `backdrop` is set, that can be moved, resized, minimized into a configurable viewport-edge dock, restored, and closed. Multiple windows inside the same Theme provider coordinate their z-order and stack independently by dock placement. The Theme keeps floating windows below modal Dialog surfaces, so an open window remains mounted behind a dialog and returns unchanged when the dialog closes.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n import { Button } from '../components/Button/index.ts';\n import { FloatingWindow } from '../components/FloatingWindow/index.ts';\n\n let open = $state(false);\n</script>\n\n<Button onclick={() => (open = true)}>Open notes</Button>\n\n<FloatingWindow bind:open title=\"Notes\">\n <p>Window content remains interactive alongside the page.</p>\n</FloatingWindow>\n```\n\n## Props\n\n- **id**: string - Stable DOM id. Generated when omitted.\n- **open**: boolean (default: true) - Bindable rendered state.\n- **defaultOpen**: boolean (default: true) - Initial state when open is not provided.\n- **minimized**: boolean (default: false) - Bindable docked state.\n- **dockPlacement**: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right' | 'left-top' | 'left-bottom' | 'right-top' | 'right-bottom' (default: 'bottom-left') - Edge and alignment used by the minimized dock. Top and bottom placements stack horizontally; left and right placements use a vertical title bar and stack vertically.\n- **title**: Slot<FloatingWindowPayload> (required) - Window title as text or a snippet.\n- **children**: Slot<FloatingWindowPayload> - Main content.\n- **dragFrom**: 'header' | 'window' (default: 'header') - Restricts dragging to the header or allows any non-interactive surface to start a drag.\n- **draggable**: boolean (default: true) - Enables pointer dragging.\n- **resizable**: boolean (default: true) - Enables four edge and four corner resize handles.\n- **minimizable**: boolean (default: true) - Shows the minimize control.\n- **closable**: boolean (default: true) - Shows the close control.\n- **closeOnEscape**: boolean (default: true) - Closes the topmost expanded floating window when Escape is pressed.\n- **backdrop**: boolean (default: false) - Dims the page behind the expanded window and makes it modal like a Dialog: Tab stays inside, the rest of the page is inert, and page scroll is locked. Minimizing into the dock lifts all of it; restoring brings it back. Clicking the backdrop does nothing: close with the close control or Escape.\n- **position**: { x: number; y: number } - Bindable viewport-relative top-left position. The first render is centered when omitted.\n- **dimensions**: { width: number; height: number; min?: [width, height]; max?: [width, height] } (default: 480 x 320, minimum 280 x 160) - Bindable pixel dimensions and optional constraint tuples. Maximum dimensions remain additionally constrained to the viewport.\n- **class**: string - Additional classes on the visible window.\n- **theme**: FloatingWindowThemeProps - Per-instance theme overrides.\n- **ref**: HTMLDivElement - Bindable reference to the visible window or minimized dock item.\n- **onOpenChange**: (open: boolean) => void - Runs once for each library-requested state change.\n- **onAfterOpen**: (payload) => void - Runs after the open transition finishes.\n- **onAfterClose**: (payload) => void - Runs after the close transition finishes.\n- **onMinimize**: (payload) => void - Runs after minimize state updates.\n- **onRestore**: (payload) => void - Runs after restore state updates.\n- **onMove**: ({ position, window }) => void - Runs when a move commits.\n- **onResize**: ({ dimensions, window }) => void - Runs when a pointer or keyboard resize commits.\n\n## Dragging\n\nHeader dragging is the default because it preserves text selection and content interactions. With `dragFrom=\"window\"`, buttons, links, inputs, editable content, resize handles, and descendants marked `data-floating-window-no-drag` remain excluded from drag starts.\n\n## Accessibility\n\n- The expanded surface uses a `dialog` role labelled by its title: `aria-modal=\"false\"`, or `\"true\"` with `backdrop`.\n- Close, minimize, and restore controls are native Entasis buttons with accessible labels.\n- Edge resize handles use `separator` semantics and support arrow-key resizing; hold Shift for a larger step.\n- Opening focuses the window, closing restores focus to its previous owner, and only the topmost expanded floating window handles Escape.\n- Alt+Arrow moves the focused window; hold Shift for a larger step.\n- Corner handles are pointer-only because a diagonal separator has no valid ARIA orientation.\n- Without `backdrop` it does not trap focus or hide page content. With it, Tab and Shift+Tab stay inside the window, every sibling subtree up to `<body>` is `inert` (other windows and dock items included), and page scroll is locked, until the window closes or is minimized.\n\n## Theme Parts\n\n- **backdrop**: Page dim behind a window opened with `backdrop`, one layer below the window.\n- **root**: Floating window surface and drag/resize states.\n- **header**: Default title bar and drag handle.\n- **title**: Header title.\n- **actions**: Header control group.\n- **control**: Header and dock icon buttons.\n- **scrollArea**: Flexible ScrollArea root that owns body scrolling.\n- **content**: Padded content inside the ScrollArea viewport.\n- **resizeHandle**: Edge and corner handles by direction.\n- **dockItem**: Minimized surface.\n- **dockTitle**: Full-width restore button in the dock item.\n- **dockTitleText**: Truncated title text and lateral writing direction.\n- **dockActions**: Dock restore and close controls.\n\n## Motion\n\n- **motion** theme slot, keyed by `phase`: `flight` times the crossfade between window and\n dock pill, `enter` / `exit` the scale fallback when there is no counterpart.\n- Only `duration` / `easing` (plus the fallback's `scale` / `opacity`) are read.\n- The backdrop fades on the `enter` / `exit` timings.\n- Ladder: `<Theme components={{ 'floating-window': { motion } }}>` →\n `setFloatingWindowTheme({ motion })` → `theme.motion`. Read once, at mount.\n";
109
109
  readonly 'hover-card': "\n# HoverCard Component\n\nHoverCard previews supplemental content when a trigger is hovered or focused. It composes Popover for positioning and dismissal with Card for the visible content surface.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { HoverCard } from 'entasis/hover-card';\n</script>\n\n<HoverCard\n\ttrigger={{ content: 'Hover @entasis', variant: 'link' }}\n\ttitle=\"@entasis\"\n\tdescription=\"Composable Svelte UI components.\"\n>\n\t<p>Preview content shown on hover or focus.</p>\n</HoverCard>\n```\n\n## Props\n\n### Core Props\n- **id**: string - Stable id for the underlying popover root.\n- **open**: boolean - Bindable open state.\n- **defaultOpen**: boolean (default: false) - Initial state when open is not provided.\n- **trigger**: string | Snippet<[HoverCardPayload]> | ButtonProps - Trigger content. ButtonProps render a Entasis Button.\n- **children**: string | Snippet<[HoverCardPayload]> - Main card content.\n- **title**: string | Snippet<[HoverCardPayload]> - Card title slot.\n- **description**: string | Snippet<[HoverCardPayload]> - Card description slot.\n- **footer**: string | Snippet<[HoverCardPayload]> - Card footer slot.\n\n### Behavior Props\n- **position**: Placement (default: 'top') - Preferred placement relative to the trigger.\n- **offset**: number (default: 8) - Gap between trigger and card.\n- **delay**: number (default: 150) - Delay before opening on hover or focus.\n- **closeDelay**: number (default: 100) - Delay before closing after pointer/focus leaves.\n- **openOnFocus**: boolean (default: true) - Opens when focus enters the trigger or card.\n- **openOnClick**: boolean (default: false) - Toggles on trigger click, useful for touch fallbacks.\n- **disabled**: boolean (default: false) - Prevents opening and disables Button triggers.\n\n### Dismissal and Transition Props\n- **closeOnEscape**: boolean (default: true) - Escape closes the hover card.\n- **closeOnClickOutside**: boolean (default: true) - Outside clicks close the hover card.\n- **directedTransition**: boolean (default: true) - Transition direction follows placement.\n- **transition**: ResponsiveProps<FSOProps> - Popover transition override.\n\n### Styling Props\n- **size**: 'small' | 'normal' | 'large' (default: 'normal') - Controls Popover panel and Card sizing.\n- **density**: 'compact' | 'normal' | 'comfortable' (default: 'normal') - Controls inner Card padding and spacing.\n- **class**: string - Extra classes on the inner Card.\n- **triggerClass**: string - Extra classes on the trigger wrapper.\n- **popover**: Props forwarded to the transparent Popover panel as one object - `{ class, theme }`.\n- **card**: Props forwarded to the inner Card as one object - `{ color, variant, theme }` (defaults: color 'neutral', variant 'solid').\n- **showBorders**: boolean (default: false) - Card section borders.\n- **theme**: HoverCardThemeProps - Theme overrides for HoverCard wrapper parts.\n\n### Callbacks\n- **onOpenChange**: (open: boolean) => void - Called once for each library-requested state change.\n- **onAfterOpen**: (payload: HoverCardPayload) => void - Called after the open transition finishes.\n- **onAfterClose**: (payload: HoverCardPayload) => void - Called after the close transition finishes.\n\n## Examples\n\n### Delays\n```svelte\n<HoverCard delay={300} closeDelay={200} trigger=\"Hover\">\n\tContent\n</HoverCard>\n```\n\n### Custom Trigger\n```svelte\n<HoverCard position=\"right\">\n\t{#snippet trigger(hoverCard)}\n\t\t<button aria-expanded={hoverCard.isOpen}>Preview</button>\n\t{/snippet}\n\n\tPreview content\n</HoverCard>\n```\n\n## Accessibility\n\n- Opens on pointer hover and keyboard focus by default.\n- Escape and outside click dismissal are delegated to Popover.\n- Button triggers receive aria-haspopup, aria-expanded, and aria-controls.\n- HoverCard is best for supplemental previews; primary content should remain reachable without hover.\n\n## Motion\n\n- **motion** theme slot: one preset (no variants) — a small lift plus scale on `fast` / `enter`.\n- Resolved by HoverCard and handed to the underlying Popover, replacing the popover preset.\n- Ladder: `<Theme components={{ 'hover-card': { motion } }}>` → `setHoverCardTheme({ motion })`\n → `theme.motion` → the `transition` prop. Reduced motion collapses it to 0.\n";
110
110
  readonly 'link-preview': "\n# LinkPreview Component\n\nLinkPreview renders an anchor trigger with a HoverCard preview that loads link metadata asynchronously. It shows Skeleton placeholders while loading and displays title, description, site name, Open Graph image, and favicon when available.\n\n## Import\n\n```svelte\n<script>\n\timport { LinkPreview } from 'entasis/link-preview';\n</script>\n```\n\n## Basic Usage\n\n```svelte\n<LinkPreview href=\"https://svelte.dev\">Svelte</LinkPreview>\n```\n\nBy default, LinkPreview requests `/api/link-metadata?url=<href>` when the card opens. Browser-only fetching of arbitrary links is not reliable because most sites block cross-origin HTML reads, so applications should provide a server endpoint or a custom `fetchMetadata` function.\n\n## With Preloaded Metadata\n\n```svelte\n<LinkPreview\n\thref=\"https://entasis.dev\"\n\tmetadata={{\n\t\ttitle: 'Entasis',\n\t\tdescription: 'Configuration-first Svelte components.',\n\t\tsiteName: 'Entasis',\n\t\tfavicon: '/favicon.png'\n\t}}\n>\n\tEntasis\n</LinkPreview>\n```\n\n## Custom Fetcher\n\n```svelte\n<script>\n\tconst fetchMetadata = async (href, signal) => {\n\t\tconst response = await fetch(`/api/preview?href=${encodeURIComponent(href)}`, { signal });\n\t\tif (!response.ok) throw new Error('Preview unavailable');\n\t\treturn response.json();\n\t};\n</script>\n\n<LinkPreview href=\"https://example.com\" {fetchMetadata}>Example</LinkPreview>\n```\n\n## Props\n\n- **href**: string - URL opened by the trigger link and requested by the metadata loader.\n- **id**: string - Stable DOM id for the underlying HoverCard; falls back to a generated id.\n- **open**: boolean - Bindable open state.\n- **defaultOpen**: boolean (default: false) - Initial state when open is not provided.\n- **children**: string | Snippet<[LinkPreviewPayload]> - Trigger anchor content.\n- **metadata**: LinkPreviewMetadata - Preloaded metadata; skips network loading.\n- **fetchMetadata**: (href, signal) => Promise<LinkPreviewMetadata> - Custom async loader.\n- **metadataEndpoint**: string | (href) => string - Endpoint used when fetchMetadata is not provided. String endpoints receive ?url=<href>.\n- **prefetch**: boolean - Load metadata on mount instead of waiting for open.\n- **target**: string - Trigger anchor target.\n- **rel**: string - Trigger anchor rel. Defaults to noopener noreferrer for target=\"_blank\".\n- **fallbackTitle**: string - Title shown when metadata has no title.\n- **imageAlt**: string - Alt text for the preview image.\n- **showUrl**: boolean - Whether to show the URL line.\n- **loadingLabel**: string - Accessible label for the loading region.\n- **errorLabel**: string - Heading shown when metadata loading fails.\n- **position**: Popover placement - Preferred card placement.\n- **offset**: number - Gap between trigger and card.\n- **delay**: number - Open delay in milliseconds.\n- **closeDelay**: number - Close delay in milliseconds.\n- **openOnFocus**: boolean - Open when focus enters trigger or card.\n- **openOnClick**: boolean - Toggle card on click before navigation.\n- **closeOnEscape**: boolean - Close on Escape.\n- **closeOnClickOutside**: boolean - Close when clicking outside.\n- **directedTransition**: boolean - Use placement-aware transitions.\n- **transition**: object - Popover transition overrides.\n- **size**: 'small' | 'normal' | 'large' - Preview card size.\n- **disabled**: boolean - Disable opening and link navigation.\n- **class**: string - Trigger anchor classes.\n- **card**: Props forwarded to the inner Card as one object - `{ class, color, variant, theme }` (defaults: color 'neutral', variant 'solid').\n- **popover**: Props forwarded to the Popover panel as one object - `{ class, theme }`.\n- **showBorders**: boolean - Show Card section borders.\n- **onOpenChange**: (open: boolean) => void - Called once for each library-requested state change.\n- **onAfterOpen**: (payload) => void - Called after open transition.\n- **onAfterClose**: (payload) => void - Called after close transition.\n- **onLoad**: (payload) => void - Called after metadata loads.\n- **onError**: (error) => void - Called after metadata loading fails.\n- **theme**: LinkPreviewThemeProps - LinkPreview theme overrides.\n- **hoverCardTheme**: HoverCardThemeProps - HoverCard wrapper theme overrides.\n\n## Metadata Shape\n\n```ts\ntype LinkPreviewMetadata = {\n\turl?: string;\n\ttitle?: string;\n\tdescription?: string;\n\tsiteName?: string;\n\timage?: string;\n\tfavicon?: string;\n};\n```\n\n## Endpoint Contract\n\nThe default endpoint should return JSON matching LinkPreviewMetadata. Non-2xx responses should return a JSON object with a `message` string when possible.\n\n## Accessibility\n\n- The trigger remains a real anchor, so the destination is reachable without hover.\n- Loading and error states use role=\"status\".\n- A disabled LinkPreview removes the anchor href and prevents hover opening.\n\n## Theme Parts\n\n- **trigger** - Anchor trigger.\n- **card** - HoverCard surface classes.\n- **content** - Preview content wrapper.\n- **media** - Image container.\n- **image** - Preview image.\n- **body** - Metadata text stack.\n- **header** - Favicon and site row.\n- **favicon** - Favicon image.\n- **site** - Site name text.\n- **title** - Preview title.\n- **description** - Preview description.\n- **url** - URL display line.\n- **loading** - Skeleton stack.\n- **error** - Error state container.\n\n## Motion\n\n- LinkPreview has no preset of its own: it forwards `transition` (now a plain `FSOProps`,\n responsive) to HoverCard, whose **motion** slot owns the preset.\n- Retune it with `<Theme components={{ 'hover-card': { motion } }}>` or\n `setHoverCardTheme({ motion })`; the `transition` prop still wins per instance.\n";
111
111
  readonly overlay: "\n# Overlay Component\n\nOverlay layers concise content and actions over bounded media or another visual surface. Place it as the direct first child of a container; the component automatically establishes that parent as its positioning context, so no attachment or wrapper component is required.\n\n## Basic Usage\n\n```svelte\n<script>\n\timport { Overlay } from 'entasis/overlay';\n</script>\n\n<div class=\"aspect-video overflow-hidden rounded-lg\">\n\t<Overlay\n\t\ttitle=\"Design system foundations\"\n\t\tdescription=\"A practical tour of tokens, primitives, and composition.\"\n\t\tactions={[{ content: 'Open gallery', color: 'neutral', variant: 'soft' }]}\n\t/>\n\t<img src=\"/cover.jpg\" alt=\"Coastal landscape\" class=\"size-full object-cover\" />\n</div>\n```\n\n## Props\n\n- **position**: 'fill' | 'top' | 'bottom' (default: 'fill') - Places the content vertically. Fill uses a surface-wide dark scrim; top and bottom use content-sized black-to-transparent gradients.\n- **align**: 'start' | 'center' | 'end' (default: 'center') - Horizontal content and text alignment.\n- **showOn**: 'always' | 'hover' | 'focus' (default: 'always') - Reveal condition. Hover also reveals for focus-within so actions remain keyboard accessible.\n- **open**: boolean (default: true) - Enables or hides the overlay while preserving its reveal transition.\n- **defaultOpen**: boolean (default: true) - Initial state when open is not provided.\n- **onOpenChange**: (open: boolean) => void - Reserved for library-requested state changes.\n- **onAfterOpen**: () => void - Called after the open transition finishes.\n- **onAfterClose**: () => void - Called after the close transition finishes.\n- **scrim**: boolean (default: true) - Toggles the dark fill or directional gradient.\n- **size**: 'small' | 'normal' | 'large' (default: 'normal') - Scales padding, gaps, and typography.\n- **actions**: OverlayAction[] - Button props plus a content string, rendered as a wrapping action row.\n- **ref**: HTMLDivElement | null - Bindable root reference.\n- **class**: string - Additional root classes.\n- **theme**: OverlayThemeProps - Theme overrides.\n\n## Slots\n\n- **title**: Main overlay heading.\n- **description**: Supporting text.\n- **content**: Additional body content between the description and actions.\n- **children**: Fully custom composition replacing title, description, content, and actions.\n\nAll named content slots accept a string or snippet.\n\n## Placement\n\nThe overlay must be the direct first child of the surface it covers:\n\n```svelte\n<div class=\"overflow-hidden rounded-lg\">\n\t<Overlay position=\"bottom\" title=\"Golden hour\" />\n\t<img src=\"/photo.jpg\" alt=\"Golden hour over a valley\" />\n</div>\n```\n\nThe parent receives a zero-specificity relative positioning context and isolated stacking context through CSS `:has()`. An explicit parent positioning utility or inline style still takes precedence.\n\nTop and bottom content enters from its corresponding edge while the scrim fades in place. Fill content only fades. Motion is disabled when the user requests reduced motion.\n\n## Reveal on Hover or Focus\n\n```svelte\n<div class=\"aspect-video overflow-hidden rounded-lg\">\n\t<Overlay\n\t\tshowOn=\"hover\"\n\t\tposition=\"bottom\"\n\t\ttitle=\"Mountain archive\"\n\t\tactions={[{ content: 'View collection', color: 'neutral', variant: 'soft' }]}\n\t/>\n\t<img src=\"/mountain.jpg\" alt=\"Snow-covered mountain\" />\n</div>\n```\n\n## Accessibility\n\n- The overlay is semantically neutral; supplied buttons and links keep their native semantics.\n- Hover-revealed content also appears when focus enters the surface or its actions.\n- Setting open to false makes the overlay inert and hides it from assistive technology.\n- Images and video beneath the overlay still require their own accessible labels or alternatives.\n\n## Theme Parts\n\n- **root**: Absolute overlay, reveal state, vertical placement, and clipping.\n- **scrim**: Fill or directional gradient.\n- **content**: Padding and horizontal alignment.\n- **header**: Title and description stack.\n- **title**: Heading typography.\n- **description**: Supporting text.\n- **body**: Additional content slot.\n- **actions**: Button row.\n";
@@ -128,9 +128,9 @@ export declare const componentMcpRegistry: {
128
128
  readonly 'qr-code': "\n# QRCode Component\n\nThe QRCode component renders a customizable QR code as an SVG. It supports theme sizes and colors, gradients, custom shapes for data modules and finder patterns, an embedded center image, and downloading as SVG, PNG or JPEG. Ported from react-qr-code (https://github.com/LGLabGreg/react-qr-code).\n\n## Basic Usage\n\n```svelte\n<QRCode value=\"https://example.com\" />\n<QRCode value=\"https://example.com\" size=\"large\" color=\"primary\" />\n```\n\n## Props\n\n### Core Props\n- **value**: string | string[] (required) - The value to encode. An array of strings represents multiple segments to further optimize the QR Code.\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n - small: 96px (size-24)\n - normal: 128px (size-32)\n - large: 192px (size-48)\n- **color**: Colors (default: 'neutral') - Theme color of the modules and finder patterns. Applied through `currentColor`, so it adapts to the active theme.\n- **level**: 'L' | 'M' | 'Q' | 'H' (default: 'M') - The Error Correction Level.\n- **minVersion**: number (default: 1) - Minimum QR version (1-40) used as the lower bound when encoding.\n- **boostLevel**: boolean (default: true) - Allow raising the Error Correction Level when it does not increase the version.\n- **marginSize**: number (default: 4) - Number of modules used as margin (quiet zone). The QR specification requires 4.\n\n### Styling Props\n- **background**: string | GradientSettings - Background color or gradient. Transparent when not provided.\n- **gradient**: GradientSettings - Gradient applied to data modules and finder patterns. Overrides `color` and the settings colors.\n- **dataModulesSettings**: { color?, style?, randomSize?, scale?, lineWidth? } - Data module rendering.\n - style: 'square' | 'square-sm' | 'pinched-square' | 'rounded' | 'leaf' | 'vertical-line' | 'horizontal-line' | 'circuit-board' | 'circle' | 'diamond' | 'star' | 'heart' | 'hashtag'\n- **finderPatternOuterSettings**: { color?, style? } - Outer finder pattern rendering.\n - style: 'square' | 'pinched-square' | 'rounded-sm' | 'rounded' | 'rounded-lg' | 'circle' | 'inpoint-sm' | 'inpoint' | 'inpoint-lg' | 'outpoint-sm' | 'outpoint' | 'outpoint-lg' | 'leaf-sm' | 'leaf' | 'leaf-lg'\n- **finderPatternInnerSettings**: { color?, style? } - Inner finder pattern rendering.\n - style: same as outer, plus 'diamond' | 'star' | 'heart' | 'hashtag' | 'microchip'\n- **imageSettings**: { src, width, height, excavate?, x?, y?, opacity?, crossOrigin? } - Embedded center image. `excavate` clears the modules behind the image. Pixel values are relative to the nominal size of the QR code.\n- **class**: string - Additional CSS classes on the SVG element.\n- **theme**: QRCodeTheme - Theme overrides.\n\n### Accessibility Props\n- **label**: string (default: 'QR Code') - Accessible label of the SVG.\n\n### Advanced Props\n- **ref**: SVGSVGElement | null (bindable) - The rendered SVG element.\n\n## Methods\n\nBind the component instance to access:\n\n- **download(options?)**: Downloads the QR code.\n - options.name: string (default: 'qr-code') - File name without extension.\n - options.format: 'svg' | 'png' | 'jpeg' (default: 'svg')\n - options.dimension: number (default: 500) - Exported file width and height in pixels.\n\n```svelte\n<script>\n\tlet qr;\n</script>\n\n<QRCode bind:this={qr} value=\"https://example.com\" />\n<Button onclick={() => qr.download({ format: 'png' })}>Download</Button>\n```\n\n## Examples\n\n### Gradient with custom shapes\n```svelte\n<QRCode\n\tvalue=\"https://example.com\"\n\tgradient={{\n\t\ttype: 'linear',\n\t\trotation: 45,\n\t\tstops: [\n\t\t\t{ offset: '0%', color: '#6d78d5' },\n\t\t\t{ offset: '100%', color: '#d56d6d' }\n\t\t]\n\t}}\n\tdataModulesSettings={{ style: 'circle' }}\n\tfinderPatternOuterSettings={{ style: 'rounded' }}\n\tfinderPatternInnerSettings={{ style: 'circle' }}\n/>\n```\n\n### Embedded image\n```svelte\n<QRCode\n\tvalue=\"https://example.com\"\n\tlevel=\"H\"\n\timageSettings={{ src: '/logo.png', width: 24, height: 24, excavate: true }}\n/>\n```\n\n## Accessibility\n\n- The SVG has `role=\"img\"` and an `aria-label` (customizable via the `label` prop).\n\n## Notes\n\n- Colors default to `currentColor`, driven by the `color` prop theme classes; downloads resolve the computed color so exports match the on-screen theme.\n- Keep enough contrast between the modules and the surface behind the QR code, and prefer `level=\"H\"` when embedding an image, otherwise the code may not scan.\n- `randomSize` and low `scale`/`lineWidth` values in `dataModulesSettings` may degrade scannability.\n";
129
129
  readonly hitbox: "\n# Hitbox Component\n\nHitbox enlarges the pointer target of an existing interactive element without changing its visible dimensions or semantics. It renders one transparent, aria-hidden span centered over its positioned parent; pointer events bubble to the parent button or link.\n\n## Usage\n\n```svelte\n<script>\n import { Hitbox } from '../components/Hitbox/index.ts';\n</script>\n\n<button type=\"button\" aria-label=\"Select page\" class=\"relative size-2 rounded-full bg-primary\">\n <Hitbox size=\"normal\" />\n</button>\n```\n\nThe interactive parent must establish a positioning context and allow overflow. Adjacent controls should reserve enough layout space for their hitboxes so targets do not overlap.\n\n## Props\n\n- **size**: 'small' | 'normal' | 'large' (default: 'normal') - Target dimensions: 20px, 24px, or 28px.\n- **ref**: HTMLSpanElement | null - Bindable reference to the transparent span.\n- **class**: string - Classes applied to the span.\n- **theme**: HitboxThemeProps - Theme overrides for the root part.\n\n## Accessibility\n\nHitbox is aria-hidden and does not create another focusable element. The parent remains responsible for its accessible name, keyboard behavior, disabled state, and focus indication.\n\n## Theme\n\n- **root**: Transparent centered target surface and size variants.\n";
130
130
  readonly slot: "\n# Slot Component\n\nThe Slot component is a utility for rendering dynamic content - it can render snippets, strings, numbers, or other components with proper handling and props passing.\n\n## Basic Usage\n\n```svelte\n<Slot render={content} />\n```\n\n## Props\n\n### Core Props\n- **render**: Slot - Content to render (can be string, number, Snippet, or component)\n- **class**: string - CSS class to apply to the wrapper\n\n## Slot Type\n\nThe Slot type accepts:\n- **string** - Rendered as text\n- **number** - Rendered as text\n- **Snippet** - Rendered as a Svelte snippet with props\n- **Component** - Rendered as a Svelte component\n\n## Examples\n\n### Render String\n```svelte\n<Slot render=\"Hello World\" />\n```\n\n### Render Number\n```svelte\n<Slot render={42} />\n```\n\n### Render Snippet\n```svelte\n{#snippet content()}\n\t<strong>Bold Text</strong>\n{/snippet}\n\n<Slot render={content} />\n```\n\n### Render Snippet\n```svelte\n{#snippet greeting()}\n\t<h1>Hello World!</h1>\n{/snippet}\n\n<Slot render={greeting} />\n```\n\n### With CSS Class\n```svelte\n<Slot \n\trender={content}\n\tclass=\"text-primary font-bold\"\n/>\n```\n\n### Conditional Rendering\n```svelte\n<script>\n\tlet content = condition ? 'Yes' : 'No';\n</script>\n\n<Slot render={content} />\n```\n\n### In Component Props\n```svelte\n<script lang=\"ts\">\n\timport { Slot, type SlotContent } from 'entasis/slot';\n\n\tlet { title, description }: { title: SlotContent; description: SlotContent } = $props();\n</script>\n\n<div class=\"card\">\n\t<Slot render={title} class=\"card-title\" />\n\t<Slot render={description} class=\"card-description\" />\n</div>\n```\n\n### Dynamic Icon\n```svelte\n<script>\n\tlet icon = condition ? checkIcon : xIcon;\n</script>\n\n<Slot render={icon} class=\"icon\" />\n```\n\n## Use Cases\n\n### 1. Flexible Component Props\nAllow component users to pass either static content or dynamic snippets:\n\n```svelte\n<Button>\n\t<Slot render={label} />\n</Button>\n```\n\n### 2. Conditional Content\nRender different content types based on runtime conditions:\n\n```svelte\n<Slot render={isLoading ? 'Loading...' : data} />\n```\n\n### 3. List Rendering\nRender items with flexible content:\n\n```svelte\n{#each items as item}\n\t<Slot render={item.label} />\n{/each}\n```\n\n## Notes\n\n- Automatically handles different content types\n- Safely renders null/undefined as empty\n- Class is applied to the wrapper element\n- Useful for building flexible, reusable components\n";
131
- readonly theme: "\n# Theme\n\n`Theme` owns global theme selection, runtime design tokens, shared overlay state, and theme\ntransitions. Wrap the application once and use the `ThemeState` received by the children snippet.\n\n## Runtime design tokens\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme, type ThemeDesignTokenMap } from 'entasis/theme';\n\n\tlet spacing = $state<'small' | 'normal' | 'large'>('normal');\n\tconst designTokens = $derived({\n\t\tlight: {\n\t\t\tspacing,\n\t\t\tspacingScale: { xs: 1, sm: 1.5, md: 2, lg: 3, xl: 4 },\n\t\t\tradius: 'normal',\n\t\t\ttypeScale: 'default',\n\t\t\traisedWithBorder: true,\n\t\t\tdefaultColor: 'neutral'\n\t\t},\n\t\tdark: {\n\t\t\tspacing,\n\t\t\tspacingScale: { xs: 1, sm: 1.5, md: 2, lg: 3, xl: 4 },\n\t\t\tradius: 'small',\n\t\t\ttypeScale: 'compact',\n\t\t\traisedWithBorder: false,\n\t\t\tdefaultColor: 'neutral'\n\t\t}\n\t} satisfies ThemeDesignTokenMap<readonly ['light', 'dark']>);\n</script>\n\n<Theme {designTokens} transition=\"radial-top-right\">\n\t{#snippet children(theme)}\n\t\t<button onclick={() => (spacing = spacing === 'small' ? 'large' : 'small')}>\n\t\t\tChange density\n\t\t</button>\n\t\t<button onclick={() => (theme.theme = theme.resolvedTheme === 'dark' ? 'light' : 'dark')}>\n\t\t\tToggle color scheme\n\t\t</button>\n\t{/snippet}\n</Theme>\n```\n\n`designTokens` is keyed by logical theme name and respects the `attribute` and `value` props.\nEleven presets ship as `themePresets` (`dense`, `compact`, `balanced`, `comfortable`, `spacious`,\n`sharp`, `rounded`, `display`, `editorial`, `glass`, `terminal`): `designTokens={{ light: themePresets.glass.tokens, dark: themePresets.glass.tokens }}`.\nChanging the controlled object updates already-rendered Tailwind utilities without rebuilding CSS.\n\n### ThemeDesignTokens\n\n- `spacing`: `'small' | 'normal' | 'large' | number`. Globally scales density.\n- `spacingScale`: partial overrides for the strictly increasing `xs`, `sm`, `md`, `lg`,\n and `xl` spacing multipliers. Defaults to 1/1.5/2/3/4.\n- `radius`: `'none' | 'subtile' | 'small' | 'normal' | 'large' | 'round' | number`.\n- `typeScale`: `'compact' | 'default' | 'comfortable' | 'large' | TypeScaleOptions`.\n- `raisedWithBorder`: toggles the border used by `raised-*` utilities.\n- `defaultColor`: `Colors` role kit chrome inherits when a control omits `color`.\n Defaults to `neutral`. Set `primary` to restore an accent-colored kit. Compiles the\n current-color family (`--color`, `--color-readable`, muted/contrast/light/dark variants)\n and `--default-color` onto the theme selector. Do not set `data-color` on `html`.\n- `focusColor`, `selectedColor`, `hoverColor`, `pressedColor`: the four `Colors` **state\n roles**. They pin, for the whole theme, what a focus ring, a persistent selection and the\n transient hover/pressed layer look like, independently of the role of the control the state\n lands on. They compile `--color-focus`, `--color-selected` (plus its `-contrast`,\n `-readable` and `-muted-readable` companions), `--color-hover` and\n `--color-pressed` onto the theme selector. There is no `--color-selected-muted`: the soft\n fill is a translucent tint of `--color-selected` at `--state-selected-opacity`, so it reads\n on any surface. None is declared at `:root`: every use site\n falls back to the matching current role (`ring-focus` is\n `var(--color-focus, var(--color))`, `bg-selected-muted` tints\n `var(--color-selected, var(--color))`, the state layer is\n `var(--color-hover, currentColor)` and on `:active`\n `var(--color-pressed, var(--color-hover, currentColor))`), so leaving them unset changes\n nothing and `data-color` keeps moving the states with `--color`. Theme-level only: there is\n no per-component override. An unknown role throws.\n\nComponent-level density remains a local variant. It selects utility classes whose values inherit\nthe active global spacing token.\n\nGenerated interfaces should use the public `xs | sm | md | lg | xl` vocabulary through component\nprops and named gap/padding utilities. `micro` and `layout-*` are internal recipe tokens. Prefer\nparent-owned gaps over child margins; do not emit arbitrary spacing or unsupported radius values.\n\n## Theme selection\n\nThe selection props wrap `svelte-themes`: `themes`, `defaultTheme`, `forcedTheme`,\n`systemTheme`, `syncColorScheme`, `transitionOnChange`, `storageKey`, `attribute`,\n`value`, and `colorScheme`. The default themes are light and dark, with system selection\nenabled. `systemTheme`, `syncColorScheme`, and `transitionOnChange` all default to\n`true`; they map onto the library's `enableSystem`, `enableColorScheme`, and\n`disableTransitionOnChange` options.\n\n`ThemeState` exposes `theme`, `resolvedTheme`, `themes`, and `systemTheme`. Assign\n`theme.theme` to switch themes. The optional `transition` prop applies a named view transition;\nunsupported browsers and reduced-motion users switch instantly.\n\n`spinnerVariant` sets the global default spinner animation. The children snippet is required.\n\n## Motion tokens\n\nMotion is a token scale like spacing and radius: five duration steps and four easing roles.\n`motion` retunes them app-wide; an omitted token keeps its default.\n\n| Duration | Default | | Easing role | Default |\n| ---------- | ------- | --- | ------------ | ------------- |\n| `instant` | 0ms | | `standard` | `cubicInOut` |\n| `fast` | 100ms | | `enter` | `cubicOut` |\n| `normal` | 200ms | | `exit` | `cubicIn` |\n| `slow` | 300ms | | `emphasized` | `backOut` |\n| `slower` | 500ms | | | |\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme motion={{ duration: { normal: 150, slow: 260 }, easing: { standard: 'quintOut' } }}>\n\t{@render children()}\n</Theme>\n```\n\nThe Tailwind plugin emits the same scale as CSS variables on `html` (`--duration-normal`,\n`--ease-standard`, ...) plus the matching `duration-*` / `ease-*` utilities, so CSS transitions\nand Svelte transitions read one set of numbers. The `motion` prop rewrites those variables on\n`html` at runtime and `designTokens.motion` rewrites them again per theme, layered over the prop;\n`ThemeState.motion` resolves through the same two rungs, so the utilities and the presets never\ndisagree. `ThemeState.transition` is a deprecated alias for its `normal` duration and `standard`\neasing. Reduced motion resolves every duration to 0 and collapses the `--duration-*` variables via\nthe `data-entasis-reduce-motion` attribute on `html`.\n\nComponents keep their own transition in a reserved `motion` slot on their theme, so the `theme`\nprop covers motion as well as classes:\n\n```svelte\n<script lang=\"ts\">\n\timport { Dialog } from 'entasis/dialog';\n</script>\n\n<Dialog theme={{ motion: { duration: 'fast', easing: 'emphasized' } }} title=\"Quick\">Body</Dialog>\n```\n\n## Component theme registry\n\n`components` sets app-wide component theme defaults without a wrapper component per component:\nit is keyed by theme name (`dialog`, `button`, ...) and each entry takes the same slots as that\ncomponent's `theme` prop, the `motion` slot included. A `set<Component>Theme` call in a subtree\nbeats the registry, and an instance `theme` prop beats both — per slot: each rung layers on the one\nbelow it, so a subtree that restyles one slot keeps the registry's others.\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme\n\tcomponents={{\n\t\tdialog: { motion: { duration: 'fast' }, content: { base: 'rounded-2xl' } },\n\t\tbutton: { root: { base: 'tracking-wide' } }\n\t}}\n>\n\t{@render children()}\n</Theme>\n```\n\n## Reduced motion\n\n`reduceMotion` forces reduced motion on (`true`) or off (`false`) for every entasis animation,\noverriding the OS `prefers-reduced-motion` setting; omit it to follow the OS. The live result is\nexposed as `ThemeState.preferReducesMotion` (reactive, so it updates when the OS setting changes)\nand mirrored as a `data-entasis-reduce-motion` attribute on `html` for CSS-only animations.\n\n```svelte\n<script>\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme reduceMotion>{@render children()}</Theme>\n```\n\n## Build-time boundary\n\nThe Tailwind plugin still generates color palettes and registers utility names, variants,\nkeyframes, and spinner CSS. Spacing, radius, typography scale, raised borders,\n`defaultColor` and the four state roles (`focusColor`, `selectedColor`, `hoverColor`,\n`pressedColor`) belong to `Theme.designTokens`; colors remain CSS variables and can be\noverridden directly. `ThemeState.defaultColor` exposes the active role. Kit chrome should\nresolve omitted `color` props with `useDefaultColor`.\n";
131
+ readonly theme: "\n# Theme\n\n`Theme` owns global theme selection, runtime design tokens, shared overlay state, and theme\ntransitions. Wrap the application once and use the `ThemeState` received by the children snippet.\n\n## Runtime design tokens\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme, type ThemeDesignTokenMap } from 'entasis/theme';\n\n\tlet spacing = $state<'small' | 'normal' | 'large'>('normal');\n\tconst designTokens = $derived({\n\t\tlight: {\n\t\t\tspacing,\n\t\t\tspacingScale: { xs: 1, sm: 1.5, md: 2, lg: 3, xl: 4 },\n\t\t\tradius: 'normal',\n\t\t\ttypeScale: 'default',\n\t\t\traisedWithBorder: true,\n\t\t\tdefaultColor: 'neutral'\n\t\t},\n\t\tdark: {\n\t\t\tspacing,\n\t\t\tspacingScale: { xs: 1, sm: 1.5, md: 2, lg: 3, xl: 4 },\n\t\t\tradius: 'small',\n\t\t\ttypeScale: 'compact',\n\t\t\traisedWithBorder: false,\n\t\t\tdefaultColor: 'neutral'\n\t\t}\n\t} satisfies ThemeDesignTokenMap<readonly ['light', 'dark']>);\n</script>\n\n<Theme {designTokens} transition=\"radial-top-right\">\n\t{#snippet children(theme)}\n\t\t<button onclick={() => (spacing = spacing === 'small' ? 'large' : 'small')}>\n\t\t\tChange density\n\t\t</button>\n\t\t<button onclick={() => (theme.theme = theme.resolvedTheme === 'dark' ? 'light' : 'dark')}>\n\t\t\tToggle color scheme\n\t\t</button>\n\t{/snippet}\n</Theme>\n```\n\n`designTokens` is keyed by logical theme name and respects the `attribute` and `value` props.\nEleven presets ship as `themePresets` (`dense`, `compact`, `balanced`, `comfortable`, `spacious`,\n`sharp`, `rounded`, `display`, `editorial`, `glass`, `terminal`): `designTokens={{ light: themePresets.glass.tokens, dark: themePresets.glass.tokens }}`.\nChanging the controlled object updates already-rendered Tailwind utilities without rebuilding CSS.\n\n### ThemeDesignTokens\n\n- `spacing`: `'small' | 'normal' | 'large' | number`. Globally scales density.\n- `spacingScale`: partial overrides for the strictly increasing `xs`, `sm`, `md`, `lg`,\n and `xl` spacing multipliers. Defaults to 1/1.5/2/3/4.\n- `radius`: `'none' | 'subtile' | 'small' | 'normal' | 'large' | 'round' | number`.\n Controls take the full multiplier; surface steps (`lg` and up) stop at `large` (1.5×).\n- `typeScale`: `'compact' | 'default' | 'comfortable' | 'large' | TypeScaleOptions`.\n- `raisedWithBorder`: toggles the border used by `raised-*` utilities.\n- `defaultColor`: `Colors` role kit chrome inherits when a control omits `color`.\n Defaults to `neutral`. Set `primary` to restore an accent-colored kit. Compiles the\n current-color family (`--color`, `--color-readable`, muted/contrast/light/dark variants)\n and `--default-color` onto the theme selector. Do not set `data-color` on `html`.\n- `focusColor`, `selectedColor`, `hoverColor`, `pressedColor`: the four `Colors` **state\n roles**. They pin, for the whole theme, what a focus ring, a persistent selection and the\n transient hover/pressed layer look like, independently of the role of the control the state\n lands on. They compile `--color-focus`, `--color-selected` (plus its `-contrast`,\n `-readable` and `-muted-readable` companions), `--color-hover` and\n `--color-pressed` onto the theme selector. There is no `--color-selected-muted`: the soft\n fill is a translucent tint of `--color-selected` at `--state-selected-opacity`, so it reads\n on any surface. None is declared at `:root`: every use site\n falls back to the matching current role (`ring-focus` is\n `var(--color-focus, var(--color))`, `bg-selected-muted` tints\n `var(--color-selected, var(--color))`, the state layer is\n `var(--color-hover, currentColor)` and on `:active`\n `var(--color-pressed, var(--color-hover, currentColor))`), so leaving them unset changes\n nothing and `data-color` keeps moving the states with `--color`. Theme-level only: there is\n no per-component override. An unknown role throws.\n\nComponent-level density remains a local variant. It selects utility classes whose values inherit\nthe active global spacing token.\n\nGenerated interfaces should use the public `xs | sm | md | lg | xl` vocabulary through component\nprops and named gap/padding utilities. `micro` and `layout-*` are internal recipe tokens. Prefer\nparent-owned gaps over child margins; do not emit arbitrary spacing or unsupported radius values.\n\n## Theme selection\n\nThe selection props wrap `svelte-themes`: `themes`, `defaultTheme`, `forcedTheme`,\n`systemTheme`, `syncColorScheme`, `transitionOnChange`, `storageKey`, `attribute`,\n`value`, and `colorScheme`. The default themes are light and dark, with system selection\nenabled. `systemTheme`, `syncColorScheme`, and `transitionOnChange` all default to\n`true`; they map onto the library's `enableSystem`, `enableColorScheme`, and\n`disableTransitionOnChange` options.\n\n`ThemeState` exposes `theme`, `resolvedTheme`, `themes`, and `systemTheme`. Assign\n`theme.theme` to switch themes. The optional `transition` prop applies a named view transition;\nunsupported browsers and reduced-motion users switch instantly.\n\n`spinnerVariant` sets the global default spinner animation. The children snippet is required.\n\n## Motion tokens\n\nMotion is a token scale like spacing and radius: five duration steps and four easing roles.\n`motion` retunes them app-wide; an omitted token keeps its default.\n\n| Duration | Default | | Easing role | Default |\n| ---------- | ------- | --- | ------------ | ------------- |\n| `instant` | 0ms | | `standard` | `cubicInOut` |\n| `fast` | 100ms | | `enter` | `cubicOut` |\n| `normal` | 200ms | | `exit` | `cubicIn` |\n| `slow` | 300ms | | `emphasized` | `backOut` |\n| `slower` | 500ms | | | |\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme motion={{ duration: { normal: 150, slow: 260 }, easing: { standard: 'quintOut' } }}>\n\t{@render children()}\n</Theme>\n```\n\nThe Tailwind plugin emits the same scale as CSS variables on `html` (`--duration-normal`,\n`--ease-standard`, ...) plus the matching `duration-*` / `ease-*` utilities, so CSS transitions\nand Svelte transitions read one set of numbers. The `motion` prop rewrites those variables on\n`html` at runtime and `designTokens.motion` rewrites them again per theme, layered over the prop;\n`ThemeState.motion` resolves through the same two rungs, so the utilities and the presets never\ndisagree. `ThemeState.transition` is a deprecated alias for its `normal` duration and `standard`\neasing. Reduced motion resolves every duration to 0 and collapses the `--duration-*` variables via\nthe `data-entasis-reduce-motion` attribute on `html`.\n\nComponents keep their own transition in a reserved `motion` slot on their theme, so the `theme`\nprop covers motion as well as classes:\n\n```svelte\n<script lang=\"ts\">\n\timport { Dialog } from 'entasis/dialog';\n</script>\n\n<Dialog theme={{ motion: { duration: 'fast', easing: 'emphasized' } }} title=\"Quick\">Body</Dialog>\n```\n\n## Component theme registry\n\n`components` sets app-wide component theme defaults without a wrapper component per component:\nit is keyed by theme name (`dialog`, `button`, ...) and each entry takes the same slots as that\ncomponent's `theme` prop, the `motion` slot included. A `set<Component>Theme` call in a subtree\nbeats the registry, and an instance `theme` prop beats both — per slot: each rung layers on the one\nbelow it, so a subtree that restyles one slot keeps the registry's others.\n\n```svelte\n<script lang=\"ts\">\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme\n\tcomponents={{\n\t\tdialog: { motion: { duration: 'fast' }, content: { base: 'rounded-2xl' } },\n\t\tbutton: { root: { base: 'tracking-wide' } }\n\t}}\n>\n\t{@render children()}\n</Theme>\n```\n\n## Reduced motion\n\n`reduceMotion` forces reduced motion on (`true`) or off (`false`) for every entasis animation,\noverriding the OS `prefers-reduced-motion` setting; omit it to follow the OS. The live result is\nexposed as `ThemeState.preferReducesMotion` (reactive, so it updates when the OS setting changes)\nand mirrored as a `data-entasis-reduce-motion` attribute on `html` for CSS-only animations.\n\n```svelte\n<script>\n\timport { Theme } from 'entasis/theme';\n\n\tlet { children } = $props();\n</script>\n\n<Theme reduceMotion>{@render children()}</Theme>\n```\n\n## Build-time boundary\n\nThe Tailwind plugin still generates color palettes and registers utility names, variants,\nkeyframes, and spinner CSS. Spacing, radius, typography scale, raised borders,\n`defaultColor` and the four state roles (`focusColor`, `selectedColor`, `hoverColor`,\n`pressedColor`) belong to `Theme.designTokens`; colors remain CSS variables and can be\noverridden directly. `ThemeState.defaultColor` exposes the active role. Kit chrome should\nresolve omitted `color` props with `useDefaultColor`.\n";
132
132
  readonly i18n: "\n# Internationalization\n\nImport from `entasis/i18n`.\n\nI18n provides locale messages to child components. setI18n and useI18n set and read the component context. en is the English message set; Messages and I18nInput describe translation inputs. locales and localeList expose the supported locale inventory with LocaleCode and LocaleMeta types.\n";
133
- readonly 'tailwind-plugin': "\n# Main Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin'`\n\nThis palette-agnostic plugin registers shared utilities, variants, keyframes, and spinner CSS.\nUse it when colors are defined separately instead of through the theme plugin.\n\n## Configuration\n\n`spinner`\n- **Type**: `Spinner` object\n- **Default**: Auto-generated\n- **Description**: Custom spinner configuration for the `.ui-spinner` class\n\n## Shared utilities\n\n### `.state-layer`\n- Composites `currentColor` at `--state-hover-opacity` on hover and `data-highlighted=\"true\"`\n- Composites `currentColor` at `--state-pressed-opacity` on `:active`\n- Does not activate for disabled, `data-disabled`, or `aria-disabled=\"true\"` elements\n- Theme plugin defaults the opacities to 5% and 10% in light themes, and 16% and 32% in dark themes\n\n### `bg-selected-muted` / `rounded-<step>-concentric`\n- `bg-selected-muted` composites `var(--color-selected, var(--color))` at\n `--state-selected-opacity` (0.07 light, 0.10 dark), so a persistent selection reads the same on\n `surface`, `surface-raised` and `surface-floating`. `bg-color-muted` stays opaque.\n- `rounded-<step>-concentric` (also `rounded-t-<step>-concentric` /\n `rounded-b-<step>-concentric`, with `<step>` one of `xs sm md lg xl 2xl 3xl 4xl`) keeps the\n child on its own design step but caps it at what concentricity allows inside a rounded padded\n container:\n `min(var(--radius-<step>), var(--radius-parent) - max(--pad-parent-x, --pad-parent-y))`. The\n container declares nothing: every `rounded-<step>` publishes `--radius-parent` to its\n children and `p` / `px` / `py` publish `--pad-parent-x/-y`, so the two boxes stay\n concentric at every `radius` preset. Outside any rounded container the parent radius is\n infinite, so the child is exactly its step; a negative difference clamps to 0, a square corner.\n Put it on a child that sits flush against the padding box; a floating child (an avatar, a\n Button, a Chip) keeps its own radius.\n\nConfigure spacing, radius, typography scale, and raised borders at runtime through\n`Theme.designTokens`.\n\nSemantic spacing utilities use the active theme's `xs`, `sm`, `md`, `lg`, and `xl`\nscale for gaps, padding, margins, and physical insets. For example, `gap-lg`, `p-lg`,\n`top-lg`, and `left-lg` use the same spacing value. Inset utilities support `top`,\n`right`, `bottom`, and `left`, including responsive variants.\n";
133
+ readonly 'tailwind-plugin': "\n# Main Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin'`\n\nThis palette-agnostic plugin registers shared utilities, variants, keyframes, and spinner CSS.\nUse it when colors are defined separately instead of through the theme plugin.\n\n## Configuration\n\n`spinner`\n- **Type**: `Spinner` object\n- **Default**: Auto-generated\n- **Description**: Custom spinner configuration for the `.ui-spinner` class\n\n## Shared utilities\n\n### `.state-layer`\n- Composites `currentColor` at `--state-hover-opacity` on hover and `data-highlighted=\"true\"`\n- Composites `currentColor` at `--state-pressed-opacity` on `:active`\n- Does not activate for disabled, `data-disabled`, or `aria-disabled=\"true\"` elements\n- Theme plugin defaults the opacities to 5% and 10% in light themes, and 16% and 32% in dark themes\n\n### `bg-selected-muted` / `rounded-<step>-concentric`\n- `bg-selected-muted` composites `var(--color-selected, var(--color))` at\n `--state-selected-opacity` (0.07 light, 0.10 dark), so a persistent selection reads the same on\n `surface`, `surface-raised` and `surface-floating`. `bg-color-muted` stays opaque.\n- `rounded-<step>-concentric` (also `rounded-t-<step>-concentric` /\n `rounded-b-<step>-concentric`, with `<step>` one of `xs sm md lg xl 2xl 3xl 4xl`) keeps the\n child on its own design step but caps it at what concentricity allows inside a rounded padded\n container:\n `min(var(--radius-<step>), var(--radius-parent) - max(--pad-parent-x, --pad-parent-y))`. The\n container declares nothing: every `rounded-<step>` publishes `--radius-parent` to its\n children and `p` / `px` / `py` publish `--pad-parent-x/-y`, so the two boxes stay\n concentric at every `radius` preset. Outside any rounded container the parent radius is\n infinite, so the child is exactly its step; a negative difference clamps to 0, a square corner.\n Put it on a child that sits flush against the padding box; a floating child (an avatar, a\n Button, a Chip) keeps its own radius.\n The padding half reads the other way: `px|py-<step>-concentric` is\n `max(space-<step>, min(radius-parent / 2, space-<step> * 3))`, so a flush bar's content clears a\n large container corner (a very round theme over a compact title bar) and stays the plain step\n otherwise.\n\nConfigure spacing, radius, typography scale, and raised borders at runtime through\n`Theme.designTokens`.\n\nSemantic spacing utilities use the active theme's `xs`, `sm`, `md`, `lg`, and `xl`\nscale for gaps, padding, margins, and physical insets. For example, `gap-lg`, `p-lg`,\n`top-lg`, and `left-lg` use the same spacing value. Inset utilities support `top`,\n`right`, `bottom`, and `left`, including responsive variants.\n";
134
134
  readonly 'theme-tailwind-plugin': "\n# Theme Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin/theme'` generates color variables for each named theme. The declaration\nmarked `default: true` also registers the shared utility vocabulary, variants, spinner styles,\nand keyframes.\n\n```css\n@import 'tailwindcss';\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: light;\n\tdefault: true;\n\tcolorscheme: light;\n\tprimary: #5f62ef;\n\tsecondary: #e4e4e7;\n\tsurface: #fafafa;\n\tneutral: #18181b;\n}\n\n@plugin 'entasis/tailwind-plugin/theme' {\n\tname: dark;\n\tcolorscheme: dark;\n\tprimary: #5f62ef;\n\tsecondary: #27272a;\n\tsurface: #09090b;\n\tneutral: #fafafa;\n}\n```\n\n## Identity\n\n- `name: string` scopes variables to `html[data-theme=\"<name>\"]` and `.<name>`.\n- `default: boolean` also applies the palette to bare `html` and installs the shared engine.\n- `colorscheme: 'light' | 'dark'` controls mode-aware color defaults.\n- `prefersDark: boolean` also emits the palette under the dark system media query.\n\n## Palette inputs\n\nBase semantic colors are `primary`, `secondary`, `danger`, `success`, `warning`, `info`,\nand `neutral`. `surface` seeds the elevation ladder: `surface-recessed`,\n`surface-canvas`, `surface`, `surface-raised`, and `surface-floating`.\n\nEach semantic color supports explicit `-light`, `-lighter`, `-dark`, `-muted`,\n`-contrast`, `-readable`, and `-muted-readable` overrides. Missing variants are generated.\n`luminance` and `saturation` adjust the generated palette.\n\n`state-hover-opacity` and `state-pressed-opacity` calibrate the CSS variables consumed by\n`.state-layer`. They default to 0.05/0.10 in light mode and 0.16/0.32 in dark mode. `state-layer-none` switches that overlay off on one element.\n`state-selected-opacity` (0.07 light, 0.10 dark) is the alpha `bg-selected-muted` composites the\nselected role at: the soft selection fill is a translucent tint, not an opaque colour, so it reads\nthe same on `surface`, `surface-raised` and `surface-floating`.\n\n## Runtime boundary\n\nSpacing, radius, typography scale, raised borders, `defaultColor` and the four state roles\n(`focusColor`, `selectedColor`, `hoverColor`, `pressedColor`) are not plugin options.\nConfigure them with the `designTokens` prop on `Theme`. Tailwind still discovers and compiles\nthe finite utility names; runtime theming changes the CSS variables those utilities consume.\n\nThe public spacing vocabulary is `xs | sm | md | lg | xl`, available through named gap, padding,\nand margin utilities such as `gap-md` and `px-lg`. The `micro` and `layout-*` values are\ninternal component-recipe tokens. Generated interfaces should prefer Stack/Grid gaps and must not\nemit arbitrary spacing or unsupported radius utilities.\n\nColor variables can also be overridden directly at runtime:\n\n```css\nhtml[data-theme='light'] {\n\t--color-primary: oklab(0.21 0.01 -0.03);\n}\n```\n";
135
135
  readonly types: "\n# Shared theme types\n\nImport from `entasis/types`.\n\nColors names semantic palette roles. Sizes selects small, normal, or large component geometry. Density uses a separate compact/normal/comfortable scale for internal whitespace, so a density value can never be passed where a size is expected. The module also exports theme color paths, typography paths, transition easing, style types, and deepMerge for composing nested theme values.\n";
136
136
  readonly cva: "\n# Component variants\n\nImport from `entasis/cva`.\n\ncva defines class variants and defaults. cx joins class values; compose composes variants. setComponentTheme and useComponentTheme connect component theme definitions to the Svelte theme context. A resolver takes an optional second argument, the shared variant values, and then returns every class slot already bound to them, so a template calls `slots.root()` instead of passing the same props to each slot. VariantProps and InferComponentTheme derive the corresponding public types. Keep component CVA definitions beside their owner in a .theme.ts file.\n";
@@ -1 +1 @@
1
- export declare const tailwindPluginDescription = "\n# Main Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin'`\n\nThis palette-agnostic plugin registers shared utilities, variants, keyframes, and spinner CSS.\nUse it when colors are defined separately instead of through the theme plugin.\n\n## Configuration\n\n`spinner`\n- **Type**: `Spinner` object\n- **Default**: Auto-generated\n- **Description**: Custom spinner configuration for the `.ui-spinner` class\n\n## Shared utilities\n\n### `.state-layer`\n- Composites `currentColor` at `--state-hover-opacity` on hover and `data-highlighted=\"true\"`\n- Composites `currentColor` at `--state-pressed-opacity` on `:active`\n- Does not activate for disabled, `data-disabled`, or `aria-disabled=\"true\"` elements\n- Theme plugin defaults the opacities to 5% and 10% in light themes, and 16% and 32% in dark themes\n\n### `bg-selected-muted` / `rounded-<step>-concentric`\n- `bg-selected-muted` composites `var(--color-selected, var(--color))` at\n `--state-selected-opacity` (0.07 light, 0.10 dark), so a persistent selection reads the same on\n `surface`, `surface-raised` and `surface-floating`. `bg-color-muted` stays opaque.\n- `rounded-<step>-concentric` (also `rounded-t-<step>-concentric` /\n `rounded-b-<step>-concentric`, with `<step>` one of `xs sm md lg xl 2xl 3xl 4xl`) keeps the\n child on its own design step but caps it at what concentricity allows inside a rounded padded\n container:\n `min(var(--radius-<step>), var(--radius-parent) - max(--pad-parent-x, --pad-parent-y))`. The\n container declares nothing: every `rounded-<step>` publishes `--radius-parent` to its\n children and `p` / `px` / `py` publish `--pad-parent-x/-y`, so the two boxes stay\n concentric at every `radius` preset. Outside any rounded container the parent radius is\n infinite, so the child is exactly its step; a negative difference clamps to 0, a square corner.\n Put it on a child that sits flush against the padding box; a floating child (an avatar, a\n Button, a Chip) keeps its own radius.\n\nConfigure spacing, radius, typography scale, and raised borders at runtime through\n`Theme.designTokens`.\n\nSemantic spacing utilities use the active theme's `xs`, `sm`, `md`, `lg`, and `xl`\nscale for gaps, padding, margins, and physical insets. For example, `gap-lg`, `p-lg`,\n`top-lg`, and `left-lg` use the same spacing value. Inset utilities support `top`,\n`right`, `bottom`, and `left`, including responsive variants.\n";
1
+ export declare const tailwindPluginDescription = "\n# Main Tailwind plugin\n\n`@plugin 'entasis/tailwind-plugin'`\n\nThis palette-agnostic plugin registers shared utilities, variants, keyframes, and spinner CSS.\nUse it when colors are defined separately instead of through the theme plugin.\n\n## Configuration\n\n`spinner`\n- **Type**: `Spinner` object\n- **Default**: Auto-generated\n- **Description**: Custom spinner configuration for the `.ui-spinner` class\n\n## Shared utilities\n\n### `.state-layer`\n- Composites `currentColor` at `--state-hover-opacity` on hover and `data-highlighted=\"true\"`\n- Composites `currentColor` at `--state-pressed-opacity` on `:active`\n- Does not activate for disabled, `data-disabled`, or `aria-disabled=\"true\"` elements\n- Theme plugin defaults the opacities to 5% and 10% in light themes, and 16% and 32% in dark themes\n\n### `bg-selected-muted` / `rounded-<step>-concentric`\n- `bg-selected-muted` composites `var(--color-selected, var(--color))` at\n `--state-selected-opacity` (0.07 light, 0.10 dark), so a persistent selection reads the same on\n `surface`, `surface-raised` and `surface-floating`. `bg-color-muted` stays opaque.\n- `rounded-<step>-concentric` (also `rounded-t-<step>-concentric` /\n `rounded-b-<step>-concentric`, with `<step>` one of `xs sm md lg xl 2xl 3xl 4xl`) keeps the\n child on its own design step but caps it at what concentricity allows inside a rounded padded\n container:\n `min(var(--radius-<step>), var(--radius-parent) - max(--pad-parent-x, --pad-parent-y))`. The\n container declares nothing: every `rounded-<step>` publishes `--radius-parent` to its\n children and `p` / `px` / `py` publish `--pad-parent-x/-y`, so the two boxes stay\n concentric at every `radius` preset. Outside any rounded container the parent radius is\n infinite, so the child is exactly its step; a negative difference clamps to 0, a square corner.\n Put it on a child that sits flush against the padding box; a floating child (an avatar, a\n Button, a Chip) keeps its own radius.\n The padding half reads the other way: `px|py-<step>-concentric` is\n `max(space-<step>, min(radius-parent / 2, space-<step> * 3))`, so a flush bar's content clears a\n large container corner (a very round theme over a compact title bar) and stays the plain step\n otherwise.\n\nConfigure spacing, radius, typography scale, and raised borders at runtime through\n`Theme.designTokens`.\n\nSemantic spacing utilities use the active theme's `xs`, `sm`, `md`, `lg`, and `xl`\nscale for gaps, padding, margins, and physical insets. For example, `gap-lg`, `p-lg`,\n`top-lg`, and `left-lg` use the same spacing value. Inset utilities support `top`,\n`right`, `bottom`, and `left`, including responsive variants.\n";
@@ -36,6 +36,10 @@ Use it when colors are defined separately instead of through the theme plugin.
36
36
  infinite, so the child is exactly its step; a negative difference clamps to 0, a square corner.
37
37
  Put it on a child that sits flush against the padding box; a floating child (an avatar, a
38
38
  Button, a Chip) keeps its own radius.
39
+ The padding half reads the other way: \`px|py-<step>-concentric\` is
40
+ \`max(space-<step>, min(radius-parent / 2, space-<step> * 3))\`, so a flush bar's content clears a
41
+ large container corner (a very round theme over a compact title bar) and stays the plain step
42
+ otherwise.
39
43
 
40
44
  Configure spacing, radius, typography scale, and raised borders at runtime through
41
45
  \`Theme.designTokens\`.
@@ -23,8 +23,8 @@ export const radiusSteps = {
23
23
  xs: 0.125,
24
24
  sm: 0.25,
25
25
  md: 0.5, // controls (buttons, inputs) keep the radius they always had
26
- lg: 0.75, // panels: cards, popovers, menus, alerts
27
- xl: 1, // dialogs, drawers, large surfaces
26
+ lg: 0.75, // panels: cards, popovers, menus, alerts (factor capped, see radiusVariables)
27
+ xl: 1, // dialogs, drawers, large surfaces (factor capped)
28
28
  '2xl': 1.25,
29
29
  '3xl': 1.5,
30
30
  '4xl': 2
@@ -111,9 +111,17 @@ export const spacingScaleVariables = (spacingScale) => {
111
111
  });
112
112
  return Object.fromEntries(entries.map(([step, multiplier]) => [`--space-${step}`, `calc(var(--spacing) * ${multiplier})`]));
113
113
  };
114
+ // Surface steps (`lg` and up: cards, popovers, dialogs, windows) follow the factor only up to
115
+ // `large`. A round theme turns controls into pills; a panel rounder than ×1.5 stops reading as
116
+ // a panel. Radix Themes draws the same line: its `full` radius pills controls, not panels.
117
+ const SURFACE_RADIUS_STEPS = new Set(['lg', 'xl', '2xl', '3xl', '4xl']);
114
118
  export const radiusVariables = (radius) => {
115
119
  const factor = presetFactor(radius, radiusFactors, 'radius');
116
- const variables = Object.fromEntries(Object.entries(radiusSteps).map(([step, rem]) => [`--radius-${step}`, formatRem(rem * factor)]));
120
+ const surfaceFactor = Math.min(factor, radiusFactors.large);
121
+ const variables = Object.fromEntries(Object.entries(radiusSteps).map(([step, rem]) => [
122
+ `--radius-${step}`,
123
+ formatRem(rem * (SURFACE_RADIUS_STEPS.has(step) ? surfaceFactor : factor))
124
+ ]));
117
125
  return { '--radius': variables['--radius-md'], ...variables };
118
126
  };
119
127
  export const typeScaleVariables = (typeScale) => {
@@ -48,6 +48,22 @@ const paddingUtilities = {
48
48
  ps: (value) => ({ 'padding-inline-start': value }),
49
49
  pe: (value) => ({ 'padding-inline-end': value })
50
50
  };
51
+ // `px-<step>-concentric` / `py-<step>-concentric`: the padding half of NESTED RADIUS (see
52
+ // `radius.ts`) read the other way. A flush bar's content sits in its container's corner, so its
53
+ // padding is the step, or half the container's corner radius when that is larger. A very round
54
+ // theme on a compact bar needs it: at `radius: 2.25` a 36px window corner sits over a 36px title
55
+ // bar, and `px-md` (8px) puts the title's first glyphs inside the curve. Capped at three steps,
56
+ // because a pill container publishes an infinite radius. It only departs from the plain step when
57
+ // the corner is more than twice the step. It publishes what it pads, like `px` / `py`.
58
+ const cornerClearance = (value) => `max(${value}, min(calc(var(--radius-parent, 0px) / 2), calc(${value} * 3)))`;
59
+ const concentricPaddingValues = Object.fromEntries(Object.entries(spacingValues).map(([step, value]) => [
60
+ `${step}-concentric`,
61
+ cornerClearance(value)
62
+ ]));
63
+ const concentricPaddingUtilities = {
64
+ px: (value) => ({ 'padding-inline': value, '& > *': { '--pad-parent-x': value } }),
65
+ py: (value) => ({ 'padding-block': value, '& > *': { '--pad-parent-y': value } })
66
+ };
51
67
  const marginUtilities = {
52
68
  m: (value) => ({ margin: value }),
53
69
  mx: (value) => ({ 'margin-inline': value }),
@@ -69,6 +85,7 @@ export const applySpacingEngine = ({ addBase, matchUtilities }) => {
69
85
  addBase({ html: spacingVariables });
70
86
  matchUtilities(gapUtilities, { values: spacingValues });
71
87
  matchUtilities(paddingUtilities, { values: spacingValues });
88
+ matchUtilities(concentricPaddingUtilities, { values: concentricPaddingValues });
72
89
  matchUtilities(marginUtilities, { values: spacingValues, supportsNegativeValues: true });
73
90
  matchUtilities(insetUtilities, { values: spacingValues });
74
91
  };
@@ -18,6 +18,7 @@
18
18
  * `reverse` are modifiers; `shimmer-color|duration|spread|angle-*` are scales)
19
19
  * - `state-layer` / `state-layer-none` exclude each other
20
20
  * - `rounded-<step>-concentric` / `rounded-t|b-<step>-concentric` (join the core corner groups), the container side
21
+ * - `px|py-<step>-concentric` (join the core `px` / `py` groups)
21
22
  * needing nothing here — a `rounded-<step>` publishes its radius to its children by itself
22
23
  * - `duration-*` / `ease-*` motion tokens
23
24
  * - the semantic spacing scale (`p-md`, `gap-layout-lg`, ...) registered on
@@ -115,6 +116,12 @@ export declare const mergeConfig: {
115
116
  'rounded-b': {
116
117
  'rounded-b': ((value: string) => boolean)[];
117
118
  }[];
119
+ px: {
120
+ px: ((value: string) => boolean)[];
121
+ }[];
122
+ py: {
123
+ py: ((value: string) => boolean)[];
124
+ }[];
118
125
  };
119
126
  conflictingClassGroups: {
120
127
  raised: string[];