entasis 0.10.1 → 0.11.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.
- package/dist/components/Avatar/avatar.theme.js +3 -1
- package/dist/components/Avatar/avatarGroup.theme.js +4 -1
- package/dist/components/Form/Ask/ask.mcp.d.ts +1 -1
- package/dist/components/Form/Ask/ask.mcp.js +1 -1
- package/dist/components/Form/Field/Field.svelte +2 -2
- package/dist/components/Form/Field/Field.svelte.d.ts +6 -6
- package/dist/components/Form/Field/field.d.ts +12 -3
- package/dist/components/Form/Field/field.state.svelte.d.ts +8 -37
- package/dist/components/Form/Field/field.state.svelte.js +21 -4
- package/dist/components/Form/Form/Form.svelte +2 -1
- package/dist/components/Form/Form/FormFieldRenderer.svelte +57 -11
- package/dist/components/Form/Form/FormFieldRenderer.svelte.d.ts +3 -2
- package/dist/components/Form/Form/form.d.ts +52 -8
- package/dist/components/Form/Form/form.mcp.d.ts +1 -1
- package/dist/components/Form/Form/form.mcp.js +66 -0
- package/dist/components/Form/Form/form.registry.d.ts +28 -0
- package/dist/components/Form/Form/form.registry.js +62 -0
- package/dist/components/Form/Form/form.state.svelte.d.ts +49 -8
- package/dist/components/Form/Form/form.state.svelte.js +3 -2
- package/dist/components/Form/Form/index.d.ts +2 -1
- package/dist/components/Form/Form/index.js +1 -0
- package/dist/components/Form/Form/visibility.d.ts +32 -30
- package/dist/components/Form/MultiStepForm/multiStepForm.state.svelte.d.ts +28 -0
- package/dist/components/Marquee/Marquee.svelte +8 -6
- package/dist/components/Marquee/marquee.mcp.d.ts +1 -1
- package/dist/components/Marquee/marquee.mcp.js +11 -11
- package/dist/components/Marquee/marquee.theme.js +17 -8
- package/dist/components/Sidebar/SidebarMenuItem.svelte +9 -1
- package/dist/components/Sidebar/SidebarMenuSubItem.svelte +71 -6
- package/dist/components/Sidebar/SidebarMenuSubItem.svelte.d.ts +4 -1
- package/dist/components/Sidebar/sidebar.mcp.d.ts +1 -1
- package/dist/components/Sidebar/sidebar.mcp.js +62 -0
- package/dist/components/Sidebar/sidebar.props.d.ts +18 -0
- package/dist/components/Sidebar/sidebar.theme.d.ts +60 -0
- package/dist/components/Sidebar/sidebar.theme.js +40 -2
- package/dist/generated/componentContract.d.ts +1 -1
- package/dist/generated/componentContract.js +6 -0
- package/dist/generated/componentMcpRegistry.d.ts +4 -4
- package/package.json +1 -1
|
@@ -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. 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`. Every gutter between the activity bar, the panel and the page, and between them and the frame edge, is `--space-md`, so it scales with the theme's spacing. With `inset` and `split`, hiding the panel normally lets the page drop its frame and fill the edge; with an activity bar the rail is still beside the page, so the page keeps its framed form (padding, rounding, AppShell's border and shadow), and a `split` page takes the gutter the panel used to supply. The wrapper carries `data-page-flush` and the `main` part a `flush` variant only when the page does fill the edge. 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. `minWidth` and `maxWidth` (a CSS length or a number of px; defaults `12rem` and `32rem`) bound both the drag and the keyboard resize; a `maxWidth` below `minWidth` throws. 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";
|
|
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### SidebarMenuSubEntry\nA row of an inline submenu (`items` on a menu entry). It carries the same row features as a\ntop-level entry, so nested navigation (pages filed in folders) keeps its menus, pins and tints\nwithout flattening the tree. Every field below `size` is optional, and a sub entry that sets none\nof them renders exactly as a plain one.\n- **label**: string - Visible row label.\n- **href** / **onclick** - Render as an anchor, or a button with a native click handler.\n- **icon**: SidebarIcon - Leading icon.\n- **isActive** / **disabled** / **size** - As on a top-level entry; `size` defaults to the Sidebar size.\n- **iconColor**: Colors - Role tint for the leading icon, applied through `data-color`.\n- **iconVariant**: 'bare' | 'tile' - `tile` paints the same tinted rounded square as a top-level row.\n- **badge**: string | number - Trailing count or status; the label truncates before it.\n- **tooltip**: string - Tooltip text. Submenus never show in icon-collapsed mode, so a sub row shows its tooltip whenever one is set (never on mobile).\n- **class**: string - Classes applied to the row button.\n- **action**: SidebarMenuActionDescriptor | Snippet<[SidebarApi]> - Trailing action pinned to the row's end (`data-sidebar=\"menu-sub-action\"`), drawn by the `subAction` theme part: hidden until the row is hovered or focused from `md` up, kept visible while its menu is open, always visible below `md`. The label truncates before it.\n\n```svelte\n<script lang=\"ts\">\n\timport { Sidebar, type SidebarGroup } from 'entasis/sidebar';\n\timport { folderIcon } from 'entasis/icons/folder';\n\timport { fileTextIcon } from 'entasis/icons/fileText';\n\timport { pencilSimpleIcon } from 'entasis/icons/pencilSimple';\n\timport { pushPinIcon } from 'entasis/icons/pushPin';\n\timport { trashIcon } from 'entasis/icons/trash';\n\n\tconst items: SidebarGroup[] = [\n\t\t{\n\t\t\tlabel: 'Pages',\n\t\t\titems: [\n\t\t\t\t{\n\t\t\t\t\tlabel: 'Planning',\n\t\t\t\t\ticon: folderIcon,\n\t\t\t\t\tdefaultOpen: true,\n\t\t\t\t\titems: [\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tlabel: 'Q3 roadmap',\n\t\t\t\t\t\t\thref: '/pages/q3-roadmap',\n\t\t\t\t\t\t\ticon: fileTextIcon,\n\t\t\t\t\t\t\ticonColor: 'success',\n\t\t\t\t\t\t\ticonVariant: 'tile',\n\t\t\t\t\t\t\tbadge: 2,\n\t\t\t\t\t\t\taction: {\n\t\t\t\t\t\t\t\tlabel: 'More options for Q3 roadmap',\n\t\t\t\t\t\t\t\tmenu: [\n\t\t\t\t\t\t\t\t\t{ type: 'option', title: 'Rename', prefix: pencilSimpleIcon },\n\t\t\t\t\t\t\t\t\t{ type: 'option', title: 'Pin', prefix: pushPinIcon },\n\t\t\t\t\t\t\t\t\t{ type: 'option', title: 'Delete', prefix: trashIcon, color: 'danger' }\n\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{ label: 'Retro notes', href: '/pages/retro-notes', icon: fileTextIcon }\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t];\n</script>\n\n<Sidebar {items} />\n```\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`. Every gutter between the activity bar, the panel and the page, and between them and the frame edge, is `--space-md`, so it scales with the theme's spacing. With `inset` and `split`, hiding the panel normally lets the page drop its frame and fill the edge; with an activity bar the rail is still beside the page, so the page keeps its framed form (padding, rounding, AppShell's border and shadow), and a `split` page takes the gutter the panel used to supply. The wrapper carries `data-page-flush` and the `main` part a `flush` variant only when the page does fill the edge. 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. `minWidth` and `maxWidth` (a CSS length or a number of px; defaults `12rem` and `32rem`) bound both the drag and the keyboard resize; a `maxWidth` below `minWidth` throws. 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- A submenu row's `action` is a sibling of its link, so Tab reaches the action button right after the row; its trigger takes `label` as its accessible name, and Escape closes its menu and returns focus to the trigger. It is `inert` and `aria-hidden` whenever the Sidebar is collapsed to icons.\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\nPass either `items` or `children`.\n- **items**: Array<ButtonProps> - Array of button configurations\n - Each button can have all standard Button component props\n- **children**: Snippet - Buttons composed directly, for content an item object cannot describe\n (a Tooltip or Popover trigger, a Button with a custom body). Each direct child is joined to its\n neighbours; set size, color and variant on each Button, since the shared props below apply to\n `items` only.\n- **label**: string - Accessible name of the group (the root has `role=\"group\"`)\n\n### Shared Button Props (items)\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, even one whose item sets `disabled: false`\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### Composed Children\n```svelte\n<script lang=\"ts\">\n\timport { Button } from 'entasis/button';\n\timport { ButtonGroup } from 'entasis/button-group';\n\timport { Tooltip } from 'entasis/tooltip';\n</script>\n\n<ButtonGroup label=\"History\">\n\t<Button variant=\"outline\">Undo</Button>\n\t<Tooltip content=\"Redo the last change\" trigger={{ content: 'Redo', variant: 'outline' }} />\n</ButtonGroup>\n```\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";
|
|
@@ -33,7 +33,7 @@ export declare const componentMcpRegistry: {
|
|
|
33
33
|
readonly 'toggle-button-group': "\n# ToggleButtonGroup Component\n\nToggleButtonGroup renders an ordered list of ToggleButton items and exposes the pressed values as one bindable value.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\tlet formatting = $state(['bold']);\n</script>\n\n<ToggleButtonGroup\n\tbind:value={formatting}\n\tlabel=\"Text formatting\"\n\tcolor=\"neutral\"\n\titems={[\n\t\t{ value: 'bold', children: 'Bold' },\n\t\t{ value: 'italic', children: 'Italic' },\n\t\t{ value: 'underline', children: 'Underline' }\n\t]}\n/>\n```\n\n## Props\n\n- **items**: ToggleButtonGroupItem[] (required) - Ordered button configurations. Each item carries a unique `value` plus ToggleButton props (no `variant`, `color`, or `size`; pressed state lives on the group value).\n- **label**: string (required) - Accessible name for the group.\n- **value**: string[] | string | undefined (bindable) - The only pressed-state source. `type=\"multiple\"` holds every pressed value as an array; `type=\"single\"` holds the checked value.\n- **defaultValue**: same shape as value - Initial pressed state when value is omitted.\n- **onValueChange**: (value) => void - Called once with the updated group value after a toggle.\n- **size**: 'small' | 'normal' | 'large' - Applied to every item.\n- **color**: Colors - Applied to every item.\n- **variant**: 'outline' | 'ghost' - Applied to every item. Defaults to 'ghost'.\n- **disabled**: boolean - Disables every item.\n- **joined**: boolean (default: false) - Renders the buttons as contiguous segments.\n- **type**: 'single' | 'multiple' (default: 'multiple') - `'multiple'` lets any number of buttons be pressed (`role=\"group\"`, `aria-pressed`) and its value is a `string[]`. `'single'` makes the selection exclusive: the value is a single string, the root becomes `role=\"radiogroup\"`, each button `role=\"radio\"` with `aria-checked`, pressing one clears the others, and the checked radio cannot be unpressed.\n- **class**: string - Additional CSS classes for the root.\n- **theme**: ToggleButtonGroupThemeProps - Theme overrides.\n\n## Examples\n\n### Icon Toolbar\n\n```svelte\n<ToggleButtonGroup\n\tbind:value={formatting}\n\tlabel=\"Text formatting\"\n\titems={[\n\t\t{ value: 'bold', prefix: textBIcon, label: 'Bold' },\n\t\t{ value: 'italic', prefix: textItalicIcon, label: 'Italic' },\n\t\t{ value: 'underline', prefix: textUnderlineIcon, label: 'Underline' }\n\t]}\n/>\n```\n\n### Joined Segments\n\n```svelte\n<ToggleButtonGroup\n\tjoined\n\tvariant=\"outline\"\n\tcolor=\"neutral\"\n\tlabel=\"Text formatting\"\n\tvalue={['bold']}\n\titems={[\n\t\t{ value: 'bold', prefix: textBIcon, label: 'Bold' },\n\t\t{ value: 'italic', prefix: textItalicIcon, label: 'Italic' },\n\t\t{ value: 'underline', prefix: textUnderlineIcon, label: 'Underline' }\n\t]}\n/>\n```\n\n### Single Selection (radio group)\n\n```svelte\n<script lang=\"ts\">\n\tlet alignment = $state('left');\n</script>\n\n<ToggleButtonGroup\n\ttype=\"single\"\n\tlabel=\"Alignment\"\n\tbind:value={alignment}\n\titems={[\n\t\t{ value: 'left', children: 'Left' },\n\t\t{ value: 'center', children: 'Center' },\n\t\t{ value: 'right', children: 'Right' }\n\t]}\n/>\n```\n\n### Change Handler\n\n```svelte\n<ToggleButtonGroup\n\tlabel=\"Density options\"\n\titems={[\n\t\t{ value: 'compact', children: 'Compact' },\n\t\t{ value: 'comfortable', children: 'Comfortable' }\n\t]}\n\tonValueChange={(value) => {\n\t\tconsole.log(value);\n\t}}\n/>\n```\n\n## Theme\n\n- **root**: Main button group container styles.\n\n## Accessibility\n\n- `type=\"multiple\"`: the root is `role=\"group\"` named by `label`; each ToggleButton exposes its independent state through `aria-pressed`.\n- `type=\"single\"`: the root is `role=\"radiogroup\"`; buttons are `role=\"radio\"` with `aria-checked`.\n- The group is a single tab stop (roving tabindex): Tab enters it, ArrowLeft / ArrowRight move between buttons (looping, mirrored in RTL), Home / End jump to the first / last, Space or Enter toggles the focused button.\n- Prefer SegmentedControl for a visually segmented exclusive choice; use `type=\"single\"` when you want toggle-button styling with exclusive semantics.\n\n## Theme example\n\n```svelte\n<ToggleButtonGroup\n\tlabel=\"Options\"\n\titems={items}\n\ttheme={{\n\t\troot: {\n\t\t\tbase: 'flex items-center gap-1'\n\t\t}\n\t}}\n/>\n```\n";
|
|
34
34
|
readonly 'toggle-menu': "\n# ToggleMenu Component\n\nToggleMenu renders standalone toggles, independent toggle groups, exclusive radio groups, menu buttons, and custom snippet controls as one accessible, responsive toolbar.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { ToggleMenu, type ToggleMenuItem } from 'entasis/toggle-menu';\n\timport type { MenuItem } from 'entasis/menu';\n\timport { eyeIcon } from 'entasis/icons/eye';\n\timport { textBIcon } from 'entasis/icons/textB';\n\timport { textItalicIcon } from 'entasis/icons/textItalic';\n\timport { paletteIcon } from 'entasis/icons/palette';\n\n\tconst getTextColorOptions = (): MenuItem[] => [\n\t\t{ type: 'option', title: 'Red', color: 'danger' },\n\t\t{ type: 'option', title: 'Blue', color: 'info' }\n\t];\n\n\tlet items = $state<ToggleMenuItem[]>([\n\t\t{ type: 'toggle', prefix: eyeIcon, label: 'Preview', value: true },\n\t\t{\n\t\t\ttype: 'group',\n\t\t\tlabel: 'Text formatting',\n\t\t\tvalue: ['bold'],\n\t\t\titems: [\n\t\t\t\t{ value: 'bold', prefix: textBIcon, label: 'Bold' },\n\t\t\t\t{ value: 'italic', prefix: textItalicIcon, label: 'Italic' }\n\t\t\t]\n\t\t},\n\t\t{\n\t\t\ttype: 'menu',\n\t\t\tlabel: 'Text color',\n\t\t\tprefix: paletteIcon,\n\t\t\tmenu: getTextColorOptions\n\t\t}\n\t]);\n</script>\n\n<ToggleMenu bind:items label=\"Editor tools\" />\n```\n\n## Item Types\n\n- **toggle**: A standalone ToggleButton configuration with `type: 'toggle'`. Its boolean `value` stores the pressed state.\n- **group**: A labeled collection of independent toggles. Its `items` is an array of button configurations, each with its own `value` string, and the group `value` is the `string[]` of pressed values.\n- **radio-group**: A labeled collection of mutually exclusive toolbar choices. Its `items` is an array of radio configurations, each with its own `value` string, and the group `value` is the checked string.\n- **menu**: A toolbar menu button. Its `menu` is a `MenuItem[]` or reactive factory and automatically becomes a submenu inside More.\n- **custom**: A snippet control with `type: 'custom'`. Its `children` snippet receives the resolved toolbar state and a `reference` attachment for the primary focusable element. `overflowItems` is required so the control has an explicit representation inside More.\n\nGroups are joined by default. Set `joined: false` when their buttons should remain visually separate.\n\n## Props\n\n- **items**: ToggleMenuItem[] (required, bindable) - Ordered toggles, groups, menu buttons, and custom controls. Pressed state lives on the items, so bind them to keep it.\n- **label**: string (required) - Accessible name for the toolbar.\n- **size**: 'small' | 'normal' | 'large' - Default size inherited by items.\n- **color**: Colors - Default color inherited by items.\n- **variant**: 'outline' | 'ghost' - Default variant inherited by items.\n- **disabled**: boolean - Disables every item.\n- **onItemsChange**: (items) => void - Called once with the complete updated item list.\n- **class**: string - Additional classes for the toolbar root.\n- **theme**: ToggleMenuThemeProps - Theme overrides for root, rail, units, and overflow trigger.\n\nItem-level size, color, variant, and disabled values override toolbar defaults. Toggle, group, and radio-group callbacks receive their native checked boolean, pressed-value array, or selected value.\n\n## Menu Buttons\n\nA `menu` item renders a normal button with menu-button ARIA state and inherited toolbar styling. Pass a function when checked rows or other menu state must be recalculated reactively. `closeOnItemClick` defaults to `true`. ToggleMenu owns the PopupMenu attachments, tooltip, and overflow submenu representation.\n\n## Custom Controls\n\n`ToggleMenuCustomPayload` contains:\n\n- **reference**: Attachment for the custom control's primary focusable element. Applying it includes the control in roving keyboard navigation.\n- **size**, **color**, **variant**, **disabled**: Resolved toolbar or item values.\n- **overflowed**: Whether the logical unit currently lives in More.\n\n`overflowItems` accepts either a `MenuItem[]` or a function returning one. Use the function form when the overflow representation depends on reactive state. Popup triggers should attach both the toolbar `reference` and their own popover reference to the same button.\n\n## Overflow\n\nToggleMenu stays on one row. When complete logical units no longer fit, it moves them into a More popup rather than wrapping or splitting a group. Toggle overflow rows use `menuitemcheckbox`; radio rows use `menuitemradio`. Both expose `aria-checked` and remain open while changing state. Menu buttons become submenus from the same menu data. Custom controls contribute their declared `overflowItems` as one logical unit.\n\n## Keyboard And Tooltips\n\n- The toolbar uses one roving tab stop.\n- ArrowLeft and ArrowRight move between visible controls; Home and End move to the edges.\n- Arrow navigation through a radio group does not change its value. Space, Enter, or click selects the focused radio.\n- Disabled and overflowed controls are excluded from navigation.\n- Icon-only toggle and menu controls automatically use their `label` as tooltip content. Custom snippets own their tooltip and accessible label.\n- Use `radio-group` for exclusive choices inside ToggleMenu and SegmentedControl for standalone exclusive controls.\n";
|
|
35
35
|
readonly 'selection-menu': "\n# SelectionMenu Component\n\nSelectionMenu renders ToggleMenu controls anchored to a non-collapsed document selection. It accepts the same toolbar item props directly, filters the selection to a target container, preserves the range while controls are pressed, and positions the toolbar through Popover.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\timport { SelectionMenu } from 'entasis/selection-menu';\n\timport type { ToggleMenuItem } from 'entasis/toggle-menu';\n\n\tlet items = $state<ToggleMenuItem[]>([\n\t\t{ type: 'toggle', label: 'Bold', prefix: boldIcon },\n\t\t{ type: 'toggle', label: 'Comment', prefix: commentIcon }\n\t]);\n</script>\n\n<div>\n\t<article>Select text in this article.</article>\n\t<SelectionMenu bind:items label=\"Selection tools\" />\n</div>\n```\n\nWith no target prop, SelectionMenu watches its parent. Pass a selector or an HTMLElement when the selection container is elsewhere:\n\n```svelte\n<SelectionMenu target=\"#editor\" bind:items label=\"Editor tools\" />\n<SelectionMenu target={editorElement} bind:items label=\"Editor tools\" />\n```\n\n## Props\n\n- **target**: HTMLElement | string | null - Parent by default; selectors resolve in the same Document or ShadowRoot. Null disables tracking.\n- **items**: ToggleMenuItem[] (bindable) - Canonical toolbar item list passed directly to ToggleMenu. Pressed state lives on the items.\n- **label**: string - Accessible name passed directly to ToggleMenu.\n- **color / variant / disabled**: ToggleMenu defaults inherited by every item.\n- **onItemsChange**: (items) => void - Called with the complete updated item list after any control changes.\n- **class / theme**: ToggleMenu root class and theme overrides.\n- **children**: Optional temporary replacement for the ToggleMenu body while retaining the same selection tracker. Toolbar props remain required.\n- **enabled**: boolean = true - Temporarily suppresses the menu without changing the target.\n- **position**: Popover placement = 'top' - Preferred placement around the selected range.\n- **offset**: number = 8 - Gap from the selected range.\n- **size**: size token = 'normal' - Shared ToggleMenu item and Popover size.\n- **transition**: Popover transition options - Enter and exit transition overrides.\n- **directedTransition**: boolean = true - Enters from the resolved placement.\n- **closeOnEscape**: boolean = true - Escape dismisses the current selection.\n- **closeOnClickOutside**: boolean = true - Outside clicks dismiss the current selection.\n- **onSelect**: (payload: SelectionMenuSelection | null) => void - The pick event: receives cloned valid ranges and clear\n events. Callback-naming decision: the document owns the text selection, so SelectionMenu has no\n controlled `selection` prop to change and cannot use the `onSelectionChange` state family.\n- **onAfterOpen / onAfterClose**: post-transition lifecycle callbacks receiving SelectionMenuPayload.\n- **popover**: Props forwarded to the floating Popover panel as one object - `{ class, theme }`.\n- **contentClass**: string - Additional classes for the advanced custom-content wrapper.\n- **selectionTheme**: SelectionMenuThemeProps - Popover panel and custom-content theme overrides.\n\n## Target Resolution\n\nBoth selection endpoints must be inside the resolved target. Whitespace-only, collapsed, or non-rendered ranges do not open the menu. String selectors use standard querySelector semantics, so use a selector that uniquely identifies the intended container.\n\n## Accessibility\n\nDirect mode inherits ToggleMenu's toolbar semantics, roving keyboard focus, disabled-item handling, tooltips, and responsive More menu. Escape and click-outside dismissal are provided by Popover. Custom-content mode is responsible for its own accessible role and keyboard model.\n";
|
|
36
|
-
readonly ask: "\n# Ask\n\nImport from `entasis/ask`.\n\nMount Ask to host programmatic requests created by ask. Requests use the existing Form inputs and validation boundary inside Dialog. AskResult reports submitted and value; cancellation preserves the current form value. Submission failures reject the request. Mount the host before requesting a form.\n";
|
|
36
|
+
readonly ask: "\n# Ask\n\nImport from `entasis/ask`.\n\nMount Ask to host programmatic requests created by ask. Requests use the existing Form inputs and validation boundary inside Dialog, including input types the app registered with registerFormInputs (see Form), so a request can arrive as plain data. AskResult reports submitted and value; cancellation preserves the current form value. Submission failures reject the request. Mount the host before requesting a form.\n";
|
|
37
37
|
readonly field: "\n# Field\n\nImport Field, createFieldState, and the FieldState type from entasis/field. Field is the shared\nvalidation and accessibility owner used by Entasis form controls. It renders field structure\naround a custom control and does not create another form or validation system.\n\n## Controller\n\ncreateFieldState receives id, type, value, errors, and focused. Use getter/setter properties\nfor values bound to component props; plain initial values also work for standalone controls.\nOptional name, required, disabled, visible, size, density, onValidate, and onValueChange\nconfigure the same owner used by built-in controls.\n\nThe controller exposes value, setValue(value), validate(), checkSchema(value), isValid,\nerrors, errorMessages, hasError, focused, disabled, required, size, and density.\nUse bind:value on field.value or call field.setValue for user edits. Accepted changes publish\none onValueChange callback each. Unchanged values and parent state synchronization do not\npublish callbacks. Disabled controllers reject user edits.\n\nSpread field.controlAttrs on the native control and attach field.control to register its node.\nThe attributes provide id, name, disabled, required, aria-invalid, and aria-describedby.\nThe controller exposes id, labelId, and errorId for more specialized controls. Field connects\nits label and error messages to this control, preserving unrelated accessible descriptions.\n\nfieldSchemas exports the built-in optional and required schemas. onValidate supplements that\nschema: a message, message array, or true means invalid; false, null, or undefined means valid.\nOptional empty values skip custom validation. validate returns a tuple containing the error\nflag and schema-normalized value.\n\n## Wrapper props\n\n- field: the shared FieldState controller.\n- children: custom control content.\n- as: native wrapper element; defaults to div. Use fieldset for grouped controls.\n- labelFor: associated control ID, or false for aria-labelledby; defaults to field.id.\n- size: small, normal, or large control geometry and label typography.\n- density: compact, normal, or comfortable internal spacing, independent of size.\n- labelPosition: top or left; left switches to side labels once the field itself is at least 32rem wide (a container query on the field, not the viewport).\n- fieldAttrs: native attributes for the wrapper. Put native control attributes on the control.\n- class and theme: wrapper classes and Field part overrides.\n\nSlots include header, label, actions, prefix, suffix, footer, description, helper,\nerrorsContainer, and error. Field renders required indicators and validation errors.\n\n## Form participation\n\nA Form input with type: 'field' supplies fieldType and a snippet accepting FieldState<fieldType>.\nThe entry inherits InputProps, including defaultValue, required, disabled, size, density,\nonValidate, and onValueChange. The controller receives the resolved Form size and density.\nIts value participates in inferred values, validation, submission, and Form bind:value.\nThe existing type: 'custom' entry remains display-only and contributes no value key.\n";
|
|
38
38
|
readonly calendar: "\n# Calendar\n\nDate-based calendar primitives for single, range, and multiple selection. CalendarPrimitive is the visual primitive; CalendarInput wraps single and range modes in the Form Field contract.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n import { CalendarPrimitive, CalendarInput } from '../components/Form/Calendar/index.ts';\n</script>\n```\n\n## Single date\n\n```svelte\n<script lang=\"ts\">\n let value = $state<Date | null>(null);\n</script>\n\n<CalendarPrimitive type=\"calendar\" bind:value />\n```\n\n## Range\n\n```svelte\n<script lang=\"ts\">\n let value = $state<[Date | null, Date | null] | null>(null);\n</script>\n\n<CalendarPrimitive type=\"calendar-range\" bind:value />\n```\n\n## Multiple dates\n\n```svelte\n<script lang=\"ts\">\n let value = $state<Date[]>([]);\n</script>\n\n<CalendarPrimitive type=\"calendar-multiple\" bind:value />\n```\n\n## Main props\n\n- `type`: `'calendar' | 'calendar-range' | 'calendar-multiple'`.\n- `value`: bindable value matching the selected type.\n- `view`: `'single' | 'double'`. Double view falls back to one month when the calendar container is narrower than 576px.\n- `disabledDates`: individual dates or inclusive `[start, end]` ranges.\n- `minDate` / `maxDate`: selection bounds.\n- `events`: values with `start` and `end`; matching events are exposed to the custom cell snippet.\n- `cell`: custom day renderer receiving the complete Calendar cell payload.\n- `onViewChange`: receives the first and last visible month after pointer, keyboard, or controlled-value navigation.\n\n## Keyboard\n\nThe visible month title opens an in-place month and year chooser composed from two scroll areas. Each list uses roving focus with ArrowUp/ArrowDown and Home/End; Escape returns to the day grid.\n\nDay focus is roving. Arrow keys move by day or week, Home/End move to week boundaries, PageUp/PageDown move by month, and Shift+PageUp/PageDown move by year. Enter or Space uses the native day button activation.\n\nMonth navigation slides in chronological direction while the viewport transitions to its new intrinsic height. Double view responds to the calendar container rather than the browser viewport, so it safely falls back to one month in narrow layouts. Both animations respect reduced-motion preferences.\n\n## State contract\n\n- **value**: current bindable editable value.\n- **defaultValue**: initial value used only when `value` is omitted.\n- **onValueChange**: called with the new value when the component changes it.\n\n";
|
|
39
39
|
readonly checkbox: "\n# Checkbox\n\nImport from `entasis/checkbox`.\n\nCheckbox is a boolean Form control. value is bindable, defaultValue initializes an uncontrolled value, and onValueChange reports changes. required, disabled, errors, label, and fieldAttrs use the shared Field contract. indeterminate controls the mixed presentation. Checkbox shares the CheckboxesInput theme owner.\n";
|
|
@@ -44,7 +44,7 @@ export declare const componentMcpRegistry: {
|
|
|
44
44
|
readonly 'date-input': "\n# DateInput\n\nMasked native text entry composed with Field and DateSelector. The bindable value is a native `Date | null`.\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n import { DateInput } from '../components/Form/DateInput/index.ts';\n</script>\n```\n\n## Usage\n\n```svelte\n<script lang=\"ts\">\n let value = $state<Date | null>(null);\n</script>\n\n<DateInput label=\"Start date\" bind:value />\n```\n\n## Date formatting\n\nUse `format` with `dd`, `mm`, `yy`, and `yyyy` segments. Supported values include `dd/mm/yyyy`, `mm/dd/yyyy`, `mm/yyyy`, and `yyyy/mm/dd`. The `separator` prop replaces the slash in the rendered mask.\n\n## Selector options\n\n- `presets`: date shortcuts shaped as `{ label, value }`.\n- `disabledDates`: individual dates or inclusive date ranges.\n- `minDate` / `maxDate`: calendar selection bounds.\n- `calendarView`: `'single' | 'double'`.\n- `mobileSheet`: renders the DateSelector as a bottom sheet below 768px.\n- `closeOnSelect`: closes after choosing a date; defaults to `false`.\n- `onSelect`: the pick event -- fires only when a date is chosen from the selector or its\n presets; `onValueChange` is the value state change and reports both typed and picked dates.\n\n```svelte\n<DateInput\n label=\"Delivery date\"\n bind:value\n minDate={new Date()}\n presets={[\n { label: 'Tomorrow', value: new Date(2026, 6, 16) },\n { label: 'Next week', value: new Date(2026, 6, 22) }\n ]}\n/>\n```\n\nThe selector never opens on focus, so tabbing through the field does not pop it. Open it with ArrowDown while the input has focus, or from the full-height calendar button (which carries `aria-haspopup=\"dialog\"`, `aria-expanded`, and `aria-controls`). It stays open after selection unless `closeOnSelect` is enabled. Calendar days support arrow, Home/End, and PageUp/PageDown navigation.\n\n## State contract\n\n- **value**: current bindable editable value.\n- **defaultValue**: initial value used only when `value` is omitted.\n- **onValueChange**: called with the new value when the component changes it.\n\n";
|
|
45
45
|
readonly 'date-selector': "\n# DateSelector\n\nPopover calendar composition for selecting one date, a range, or multiple dates. It uses Entasis's Popover, CalendarPrimitive, Button, and transitionSize attachment.\n`DateSelector` is the raw popover primitive; `DateSelectorInput` wraps it in the standard field\nchrome (label, description, errors, form registration) and extends InputProps — field type 'date'\nfor `mode=\"date\"`, 'calendar-range' for `mode=\"range\"` (`mode=\"multiple\"` has no field type and\nstays primitive-only).\n\n## Import\n\n```svelte\n<script lang=\"ts\">\n import { DateSelector, DateSelectorInput } from '../components/Form/DateSelector/index.ts';\n</script>\n```\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n let value = $state<Date | null>(null);\n</script>\n\n<!-- Field variant: label/description/errors like every other form input -->\n<DateSelectorInput\n\tlabel=\"Due date\"\n\tdescription=\"We'll remind you the day before\"\n\tbind:value\n/>\n\n<!-- Raw popover primitive -->\n<DateSelector bind:value />\n```\n\n## DateSelectorInput (field variant)\n\nAccepts every field prop from InputProps — `label`, `description`, `name`, `required`, `errors`,\n`onValidate`, `helper`, snippets, … — plus the selector props (`mode`, `presets`, `trigger`,\n`closeOnSelect`, `position`, `minDate`/`maxDate`, …). `theme` is split: `theme.selector` for the\npopover parts, `theme.field` for the field wrapper.\n\n## Selection modes\n\n- `mode=\"date\"` binds `Date | null`.\n- `mode=\"range\"` binds `[Date | null, Date | null] | null`.\n- `mode=\"multiple\"` binds `Date[]`.\n\nThe selector stays open after selection by default. Set `closeOnSelect` to close after a date, a complete range, or a multiple-date change.\n\n## Presets\n\n```svelte\n<DateSelector\n bind:value\n presets={[\n { label: 'Today', value: new Date() },\n { label: 'Tomorrow', value: new Date(2026, 6, 16) }\n ]}\n/>\n```\n\nEach preset value must match the active mode. Presets follow the same `closeOnSelect` policy as calendar selection.\n\n## Custom trigger\n\nThe `trigger` prop follows Popover's trigger contract. A trigger snippet receives `PopoverState`; attach `popover.reference` to the trigger element and call `popover.toggle()`.\n\n## Calendar configuration\n\nUse `view`, `weekStartsOnMonday`, `weekdayLength`, `locale`, `minDate`, `maxDate`, and `disabledDates` to configure the embedded calendar. Set `mobileSheet` to render the selector as a bottom sheet below 768px.\n\nThe content container animates intrinsic height changes. Month navigation uses a direction-aware slide and respects reduced-motion preferences.\n\n## State contract\n\n- **value**: current bindable editable value.\n- **defaultValue**: initial value used only when `value` is omitted.\n- **onValueChange**: called with the new value when the component changes it.\n- **open**: bindable popover visibility.\n- **defaultOpen**: initializes an uncontrolled selector once.\n- **onOpenChange**: receives the new boolean once for each opening or closing interaction.\n\nSelecting the current date or preset does not emit another value callback. With closeOnSelect,\nthat selection can still close the popover and emit one onOpenChange callback.\n\n";
|
|
46
46
|
readonly 'file-input': "\n# FileInput Component\n\nThe FileInput component provides file upload functionality with drag-and-drop support and an inline list of the selected files (image files get a thumbnail preview). It supports a single-file mode and a multiple-file mode.\n\n## Basic Usage\n\n```svelte\n<script lang=\"ts\">\n\tlet file = $state<File | null>(null);\n</script>\n\n<FileInput label=\"Upload File\" bind:value={file} />\n```\n\n## Props\n\nExtends all Field component props plus:\n\n### Core Props\n- **mode**: 'single' | 'multiple' (default: 'single') - Single-file or multiple-file mode\n- **value**: File | null in single mode, File[] | null in multiple mode (bindable) - Selected file(s)\n- **defaultValue**: same type as `value` - Initial selection when `value` is omitted\n- **types**: string[] (default: ['image/*']) - Accepted MIME types or extensions (e.g. ['image/*'], ['.pdf', '.docx']); used for the native `accept` attribute and drag validation\n- **maxSize**: number (default: 50 MB) - Maximum file size in bytes; larger files are rejected\n- **maxFiles**: number (default: 1) - Maximum number of files (multiple mode caps the combined selection)\n- **clickable**: boolean (default: true) - Whether clicking the dropzone opens the native file picker\n\n### Display Props\n- **placeholder**: string (default: 'Click or drag files here') - Text shown in the empty dropzone\n- **fileList**: Snippet - Custom rendering of the selected files list\n- **file**: Snippet - Custom rendering of each selected file row\n- **fileListClass** / **fileClass** / **placeholderClass**: string - Extra classes for those parts\n\n### Field Props (inherited)\n- **label**: string | Snippet - Field label\n- **description**: string | Snippet - Helper text\n- **required**: boolean - Mark as required\n- **disabled**: boolean - Disable input\n- **size**: 'small' | 'normal' | 'large'\n\n### Event Props\n- **onValueChange**: (value: File | null) => void in single mode, (value: File[] | null) => void in multiple mode - Called when the selection changes\n- **onReject**: (rejections: { file: File; reason: 'type' | 'size' | 'duplicate' | 'limit' }[]) => void - Called with the files rejected by type, size, duplicate or count validation\n\n### Styling Props\n- **class**: string - Additional CSS classes\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\n```\n<Field>\n\t<Label />\n\t<Description />\n\t<Dropzone> <!-- inputContainer -->\n\t\t<Placeholder /> <!-- shown while no file is selected -->\n\t\t<FileList> <!-- shown once files are selected -->\n\t\t\t<File /> <!-- thumbnail, name, size, remove button -->\n\t\t\t<AddMoreButton /> <!-- multiple mode, while below maxFiles -->\n\t\t</FileList>\n\t</Dropzone>\n\t<Error />\n</Field>\n```\n\n## Examples\n\n### Basic File Input\n```svelte\n<script lang=\"ts\">\n\tlet file = $state<File | null>(null);\n</script>\n\n<FileInput\n\tlabel=\"Upload Document\"\n\ttypes={['.pdf', '.doc', '.docx']}\n\tbind:value={file}\n/>\n```\n\n### Multiple Files\n```svelte\n<script lang=\"ts\">\n\tlet files = $state<File[]>([]);\n</script>\n\n<FileInput\n\tmode=\"multiple\"\n\tmaxFiles={5}\n\tlabel=\"Upload Documents\"\n\ttypes={['.pdf', '.doc', '.docx']}\n\tbind:value={files}\n/>\n```\n\n### Image Upload with Preview\n```svelte\n<script lang=\"ts\">\n\tlet avatar = $state<File | null>(null);\n</script>\n\n<FileInput\n\tlabel=\"Profile Picture\"\n\tbind:value={avatar}\n\ttypes={['image/*']}\n/>\n```\n\n### Restrict File Types\n```svelte\n<script lang=\"ts\">\n\tlet document = $state<File | null>(null);\n</script>\n\n<FileInput\n\tlabel=\"Upload PDF\"\n\tbind:value={document}\n\ttypes={['.pdf']}\n\tdescription=\"PDF files only\"\n/>\n```\n\n### With File Size Limit\n```svelte\n<script lang=\"ts\">\n\tlet image = $state<File | null>(null);\n</script>\n\n<FileInput\n\tlabel=\"Upload Image\"\n\tbind:value={image}\n\ttypes={['image/*']}\n\tmaxSize={5 * 1024 * 1024}\n\tdescription=\"Maximum 5MB\"\n/>\n```\n\n### Required File\n```svelte\n<script lang=\"ts\">\n\tlet resume = $state<File | null>(null);\n</script>\n\n<FileInput\n\tlabel=\"Resume\"\n\tbind:value={resume}\n\ttypes={['.pdf', '.doc', '.docx']}\n\trequired\n/>\n```\n\n### Disabled State\n```svelte\n<FileInput\n\tlabel=\"Uploaded File\"\n\tvalue={existingFile}\n\tdisabled\n/>\n```\n\n### Avatar Upload\n```svelte\n<script lang=\"ts\">\n\tlet avatar = $state<File | null>(null);\n\n\tfunction handleUpload(file: File | null) {\n\t\tif (!file) return;\n\t\tconsole.log('Uploading:', file.name);\n\t\t// Upload to server\n\t}\n</script>\n\n<FileInput\n\tlabel=\"Profile Picture\"\n\tbind:value={avatar}\n\ttypes={['image/png', 'image/jpeg']}\n\tmaxSize={2 * 1024 * 1024}\n\tdescription=\"PNG or JPEG, max 2MB\"\n\tonValueChange={handleUpload}\n/>\n```\n\n### Handling Rejected Files\n```svelte\n<script lang=\"ts\">\n\tlet image = $state<File | null>(null);\n\tlet rejectionMessage = $state('');\n</script>\n\n<FileInput\n\tlabel=\"Upload Image\"\n\tbind:value={image}\n\ttypes={['image/*']}\n\tmaxSize={1024 * 1024}\n\tonReject={(rejections) => {\n\t\trejectionMessage = rejections\n\t\t\t.map((rejection) => `${rejection.file.name}: ${rejection.reason}`)\n\t\t\t.join(', ');\n\t}}\n/>\n\n{#if rejectionMessage}\n\t<p class=\"text-danger-readable text-sm\">{rejectionMessage}</p>\n{/if}\n```\n\n### Document Upload Form\n```svelte\n<script lang=\"ts\">\n\tlet documents = $state<File[]>([]);\n\n\tasync function handleSubmit(event: SubmitEvent) {\n\t\tevent.preventDefault();\n\t\tconst formData = new FormData();\n\t\tdocuments.forEach((file, i) => {\n\t\t\tformData.append(`document${i}`, file);\n\t\t});\n\n\t\tawait fetch('/api/upload', {\n\t\t\tmethod: 'POST',\n\t\t\tbody: formData\n\t\t});\n\t}\n</script>\n\n<form onsubmit={handleSubmit}>\n\t<FileInput\n\t\tmode=\"multiple\"\n\t\tmaxFiles={10}\n\t\tlabel=\"Supporting Documents\"\n\t\tbind:value={documents}\n\t\ttypes={['.pdf', '.doc', '.docx']}\n\t\tdescription=\"Upload relevant documents\"\n\t\trequired\n\t/>\n\n\t<Button type=\"submit\" disabled={documents.length === 0}>\n\t\tSubmit Application\n\t</Button>\n</form>\n```\n\n### Image Gallery Upload\n```svelte\n<script lang=\"ts\">\n\tlet images = $state<File[]>([]);\n</script>\n\n<FileInput\n\tmode=\"multiple\"\n\tmaxFiles={20}\n\tlabel=\"Upload Photos\"\n\tbind:value={images}\n\ttypes={['image/*']}\n\tmaxSize={10 * 1024 * 1024}\n\tdescription=\"Select multiple images (max 10MB each)\"\n/>\n\n<p>Selected: {images.length} images</p>\n```\n\n### With Upload Progress\n```svelte\n<script lang=\"ts\">\n\tlet file = $state<File | null>(null);\n\tlet progress = $state(0);\n\tlet uploading = $state(false);\n\n\tasync function handleUpload(selectedFile: File | null) {\n\t\tif (!selectedFile) return;\n\t\tuploading = true;\n\t\tprogress = 0;\n\n\t\tconst formData = new FormData();\n\t\tformData.append('file', selectedFile);\n\n\t\t// Simulate upload progress\n\t\tconst interval = setInterval(() => {\n\t\t\tprogress += 10;\n\t\t\tif (progress >= 100) {\n\t\t\t\tclearInterval(interval);\n\t\t\t\tuploading = false;\n\t\t\t}\n\t\t}, 200);\n\t}\n</script>\n\n<FileInput\n\tlabel=\"Upload File\"\n\tbind:value={file}\n\tonValueChange={handleUpload}\n\tdisabled={uploading}\n/>\n\n{#if uploading}\n\t<Meter value={progress} />\n{/if}\n```\n\n### CV Upload\n```svelte\n<script lang=\"ts\">\n\tlet cv = $state<File | null>(null);\n</script>\n\n<FileInput\n\tlabel=\"Upload CV/Resume\"\n\tbind:value={cv}\n\ttypes={['.pdf', '.doc', '.docx']}\n\tmaxSize={5 * 1024 * 1024}\n\tdescription=\"PDF or Word document, max 5MB\"\n\trequired\n/>\n```\n\n### Attachment Field\n```svelte\n<script lang=\"ts\">\n\tlet attachments = $state<File[]>([]);\n\n\tfunction removeFile(index: number) {\n\t\tattachments = attachments.filter((_, i) => i !== index);\n\t}\n</script>\n\n<FileInput\n\tmode=\"multiple\"\n\tmaxFiles={Infinity}\n\tlabel=\"Attachments\"\n\ttypes={['*/*']}\n\tbind:value={attachments}\n\tdescription=\"Add any supporting files\"\n/>\n\n{#if attachments.length > 0}\n\t<div class=\"file-list\">\n\t\t{#each attachments as file, i}\n\t\t\t<div class=\"file-item\">\n\t\t\t\t<span>{file.name}</span>\n\t\t\t\t<Button\n\t\t\t\t\tsize=\"small\"\n\t\t\t\t\tvariant=\"ghost\"\n\t\t\t\t\tonclick={() => removeFile(i)}\n\t\t\t\t>\n\t\t\t\t\tRemove\n\t\t\t\t</Button>\n\t\t\t</div>\n\t\t{/each}\n\t</div>\n{/if}\n```\n\n## Drag and Drop\n\nFileInput supports drag-and-drop:\n- Drag files over the drop zone\n- Visual feedback while dragging (the dropzone reflects an idle / potential / valid / invalid state)\n- Drop to select files\n- Works alongside click-to-browse (disable with `clickable={false}`)\n\n## File List\n\nOnce files are selected the dropzone shows them inline:\n- Images show a thumbnail preview\n- Other files show their name\n- File size displayed\n- Remove button for each file\n- In multiple mode, an \"Add more files\" button appears while below `maxFiles`\n\n## Validation\n\nFileInput validates:\n- **required**: At least one file selected\n- **types**: File type matches the accepted types (rejected with reason 'type')\n- **maxSize**: File size within limit (rejected with reason 'size')\n- **maxFiles**: Number of files in multiple mode (rejected with reason 'limit')\n- Duplicate files (same name and size) are rejected with reason 'duplicate'\n\nRejected files never enter `value`; listen to `onReject` to surface them.\n\n## File Type Patterns\n\nEntries accepted in `types`:\n- `'image/*'` - All images\n- `'image/png'`, `'image/jpeg'` - Specific image types\n- `'.pdf'` - PDF files\n- `'.pdf'`, `'.doc'`, `'.docx'` - Documents\n- `'video/*'` - Videos\n- `'audio/*'` - Audio files\n- `'*/*'` - Any file\n\n## Accessibility\n\n- Proper label association\n- Keyboard accessible (Enter/Space to open file picker)\n- ARIA attributes for drop zone\n- File selection announced to screen readers\n- Error states communicated\n\n## Notes\n\n- Supports drag-and-drop file upload\n- Preview images before upload\n- Validates file types and sizes\n- Works with FormData for upload\n- Can handle single or multiple files\n- File objects are returned for processing\n\n## Theme Customization\n\nThe FileInput 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- **inputContainer**: The dropzone container\n- **placeholder**: The empty-state placeholder (icon and text)\n- **fileList**: The list wrapping the selected files\n- **file**: Each selected file row\n\nField theme parts (label, description, error, ...) are also accepted on the same `theme` prop.\n\n### Available Variants\n\n**inputContainer**:\n- base: Base classes for the dropzone\n- Variants:\n - size: 'small' | 'normal' | 'large' - Minimum height\n - state: 'idle' | 'potential' | 'valid' | 'invalid' - Drag state styling\n - disabled: boolean - Disabled state styling\n\n**placeholder**:\n- base: Base classes for the placeholder\n- Variants:\n - size: 'small' | 'normal' | 'large' - Text size and gap\n\n**fileList**:\n- base: Base classes for the file list\n- Variants:\n - size: 'small' | 'normal' | 'large' - Gap between rows\n\n**file**:\n- base: Base classes for each file row\n- Variants:\n - size: 'small' | 'normal' | 'large' - Padding, text size and gap\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<FileInput\n label=\"Upload Files\"\n bind:value={files}\n theme={{\n inputContainer: {\n base: 'border-2 border-dashed rounded-lg',\n state: {\n valid: 'border-primary bg-primary/10'\n }\n }\n }}\n/>\n```\n\n**Custom Dropzone Styling**:\n```svelte\n<FileInput\n label=\"Custom Upload\"\n bind:value={files}\n theme={{\n inputContainer: {\n base: 'border-2 border-dashed border-neutral-muted rounded-xl p-8 hover:border-primary transition-colors',\n state: {\n potential: 'border-primary',\n valid: 'border-primary bg-primary/5',\n invalid: 'border-danger bg-danger/5'\n }\n },\n fileList: {\n base: 'mt-4 grid grid-cols-2 gap-4'\n },\n file: {\n base: 'rounded-md border border-neutral-muted'\n }\n }}\n/>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setFileInputTheme } from '../components/Form/File/index.ts';\n\n setFileInputTheme({\n inputContainer: {\n base: 'border-2 border-dashed rounded-lg transition-all',\n state: {\n valid: 'border-primary bg-primary/5'\n }\n },\n fileList: {\n base: 'mt-4'\n }\n });\n</script>\n```\n\n## State contract\n\n- **value**: current bindable editable value.\n- **defaultValue**: initial value used only when `value` is omitted.\n- **onValueChange**: called with the new value when the component changes it.\n\n";
|
|
47
|
-
readonly form: "\n# Form\n\nForm renders configured entasis fields inside a div, owns their state and validation, and submits programmatically. It provides managed Enter-key navigation and submission, but not native form events, browser constraint validation, or FormData behavior.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n\timport {\n\t\tForm,\n\t\ttype FormState,\n\t\ttype InferFormValue,\n\t\ttype LiveFormValue\n\t} from 'entasis/form';\n\n\tconst inputs = {\n\t\tidentity: {\n\t\t\ttype: 'group',\n\t\t\tlabel: 'Identity',\n\t\t\tdescription: 'Core account details',\n\t\t\tcolumns: 2,\n\t\t\tinputs: {\n\t\t\t\tname: { type: 'text', label: 'Name', required: true },\n\t\t\t\temail: { type: 'email', label: 'Email', required: true }\n\t\t\t}\n\t\t}\n\t} as const;\n\n\tlet value = $state<LiveFormValue<typeof inputs>>({ name: 'Ada' });\n\tlet form = $state<FormState<typeof inputs>>();\n\tlet submitted = $state<InferFormValue<typeof inputs> | null>(null);\n</script>\n\n<Form\n\t{inputs}\n\tbind:value\n\tbind:form\n\tvariant=\"card\"\n\tlayout=\"horizontal\"\n\tsize=\"small\"\n\tdensity=\"normal\"\n\tactions={[\n\t\t{\n\t\t\tchildren: 'Save',\n\t\t\tonAction: (payload) => payload.submit()\n\t\t}\n\t]}\n\tonSubmit={(validatedValue) => {\n\t\tsubmitted = validatedValue;\n\t}}\n/>\n```\n\n## Props\n\n- **inputs** (required): ordered object keyed by entry name. Entries may be value-bearing fields, visual groups, action-button rows, or custom snippets. Only fields contribute keys to the live and validated values.\n- **value** (bindable): controlled partial live value. Visible mounted fields are published; hidden fields are omitted. A supplied top-level value takes precedence over an input's configured value.\n- **defaultValue**: initial partial live value when value is omitted.\n- **onValueChange**: called once when a user changes the visible live value. Parent value updates do not emit it.\n- **form** (bindable): the FormState instance.\n- **onSubmit**: called with the validated visible payload. Its return value is ignored; rejected promises propagate.\n- **size**: small, normal (default), or large. Controls Form typography and becomes the fallback size for fields and actions. An explicit field or action size takes precedence.\n- **density**: compact, normal (default), or comfortable. Controls gaps between the Form header, fields, footer, and actions independently from size.\n- **variant**: plain (default), sectioned, or card. All variants consume the shared Card typography. Plain remains transparent without separators. Sectioned stays transparent and adds edge-to-edge separators between the header, visible top-level fields or groups, and footer. Card adds the same separators inside the Card surface and inset; Card header/footer padding applies only to that variant. Separators do not appear between fields inside a group.\n- **layout**: vertical (default) or horizontal. Horizontal keeps one field per row and places labels to the left once each field is at least 32rem wide; fields remain stacked in narrower forms.\n- **actions**: optional array of Button props. Each action's onAction receives the live FormState, so a submit action calls form.submit(). Actions are disabled while the form is submitting; their own loading and disabled props are composed with that protection.\n- **class**: additional classes on the root div.\n- **theme**: Form theme overrides for root, header, title, description, group, group label/description/fields, top-level items, custom entries, footer, and actions. Global Card typography and section-rhythm changes flow into all Form variants before Form-specific overrides are composed; Card surface changes apply only to the card variant.\n\nThe **header**, **title**, **description**, **children**, and **footer** snippets each receive the FormState instance directly. No-argument snippets remain valid.\n\nField wrapper attributes and native control attributes have separate owners. Use fieldAttrs for the shared Field wrapper. Controls expose a typed bag for their actual native element, such as inputAttrs on TextInput, textareaAttrs on TextArea, and triggerAttrs on the custom Select's combobox button.\n\n## FormState\n\n- **value**: current visible partial value.\n- **loading**: true while the active submission handler is pending.\n- **hasError**: true after validation fails; reset after successful validation.\n- **validate()**: returns the validated visible payload, or false.\n- **submit()**: returns a promise of the validated visible payload, or false. Concurrent calls share the active promise.\n\n## Enter-key behavior\n\nOn an enabled, editable single-line input, Enter validates the current field. A valid field moves focus to the next visible enabled field, while an invalid field remains focused and displays its errors. Enter on a valid final navigable field calls form.submit(). Multiline editors and controls that already own Enter, such as selects and combobox menus, keep their native component behavior. Modified, repeated, and IME-composition Enter events are ignored.\n\n## Programmatic Dialog forms\n\nMount one Ask host near the application root, then call ask() from any client-side event. Both exports come from entasis/form:\n\n```svelte\n<script lang=\"ts\">\n\timport { Ask, ask } from 'entasis/form';\n\n\tconst inputs = {\n\t\tname: { type: 'text', label: 'Name', required: true },\n\t\temail: { type: 'email', label: 'Email', required: true }\n\t} as const;\n\n\tasync function editProfile() {\n\t\tconst outcome = await ask({\n\t\t\ttitle: 'Edit profile',\n\t\t\tdescription: 'Review the account details.',\n\t\t\tinputs,\n\t\t\tvalue: { name: 'Ada' },\n\t\t\tconfirm: 'Save',\n\t\t\tcancel: 'Cancel'\n\t\t});\n\n\t\tif (outcome.submitted) {\n\t\t\t// outcome.value is InferFormValue<typeof inputs>\n\t\t} else {\n\t\t\t// outcome.value is the partial, visible live value at cancellation\n\t\t}\n\t}\n</script>\n\n<Ask />\n```\n\nAskOptions accepts inputs, value, onSubmit, class, size, density, variant, layout, and theme with the same contracts as Form. title is required for the Dialog's accessible name. confirm and cancel accept a string or Button presentation props plus text. Their click, loading, link, and native type behavior is owned by Ask. Set type on the mounted Ask host for its default Dialog type; a request's optional dialog object may override type and also accepts responsive, size, and scroll.\n\nValidation failures keep the Dialog open and focus the first invalid field. A successful submission resolves with the validated visible payload. Cancel, the close button, and Escape resolve with submitted: false and the current partial visible value. Outside-click and swipe dismissal are disabled. While submission is active, all close paths and actions are protected. Rejected onSubmit promises reject ask() after the Dialog closes. Concurrent ask() calls render as independent stacked Dialogs. Calling ask() during SSR or without one mounted host rejects clearly.\n\n## Visibility and values\n\nThe visible prop may be a boolean or a predicate receiving the complete private value cache:\n\n```svelte\n<Form\n\tinputs={{\n\t\tcontactMethod: {\n\t\t\ttype: 'select',\n\t\t\tlabel: 'Contact method',\n\t\t\titems: [\n\t\t\t\t{ label: 'Email', value: 'email' },\n\t\t\t\t{ label: 'Phone', value: 'phone' }\n\t\t\t]\n\t\t},\n\t\tphone: {\n\t\t\ttype: 'phone',\n\t\t\tlabel: 'Phone',\n\t\t\tvisible: (value) => value.contactMethod === 'phone'\n\t\t}\n\t}}\n/>\n```\n\nHidden field values are preserved privately and restored when shown again. Hidden fields are excluded from value, validation, and submission. Removing an input definition removes its cached value.\n\n## Field label position\n\nAll configured inputs inherit the Form layout through the shared Field wrapper. Set labelPosition: 'top' or 'left' on an individual input to override the Form layout without affecting value inference:\n\n```ts\nexport const inputs = {\n\tname: { type: 'text', label: 'Name' },\n\tnotes: { type: 'textarea', label: 'Notes', labelPosition: 'top' }\n} as const;\n```\n\nThe left position becomes a two-column label/control layout once the field is at least 32rem wide and remains stacked in narrower forms. The breakpoint is a container query against the field, so a form in a narrow drawer stacks on any screen. Form uses layout rather than orientation so controls such as Slider retain their own orientation prop.\n\n## Visual groups\n\nA group renders a transparent semantic fieldset with a required legend label and an optional description. Its label and description use the same size scale as ordinary fields; grouping does not add another card or padded container:\n\n```ts\nimport type { InferFormValue } from 'entasis/form';\n\nexport const inputs = {\n\tcontact: {\n\t\ttype: 'group',\n\t\tlabel: 'Contact details',\n\t\tdescription: 'How we can reach you',\n\t\tcolumns: 1,\n\t\tinputs: {\n\t\t\temail: { type: 'email', label: 'Email', required: true },\n\t\t\tphone: { type: 'phone', label: 'Phone' }\n\t\t}\n\t}\n} as const;\n\ntype Value = InferFormValue<typeof inputs>;\n// { email: string; phone: string | null }\n```\n\nThe group key is visual only and never appears in value or submission output. Descendant field names remain top-level, so duplicate names across groups are rejected. Group visibility hides all descendants, preserves field values in the private cache, and makes inferred field keys optional. In a vertical form layout, set columns to 1, 2, 3, or 4 to control the equal-width grid; the count is a container query against the form width (2 columns from 42rem, 3 from 56rem, 4 from 72rem), and groups collapse to one column in narrower forms. Horizontal form layouts ignore columns and always render group entries in one column. A child class may span tracks when an entry needs more room. Groups may contain fields, action rows, and custom snippets; nested groups are intentionally unsupported.\n\n## Custom value fields\n\nUse type: 'field' when Form must own a value but a custom control must render it. fieldType selects the existing FieldState schema and inferred value type. The snippet receives that registered FieldState controller:\n\n```svelte\n{#snippet handleControl(field)}\n\t<input\n\t\t{...field.controlAttrs}\n\t\t{@attach field.control}\n\t\tbind:value={field.value}\n\t/>\n{/snippet}\n\n<Form\n\tinputs={{\n\t\thandle: {\n\t\t\ttype: 'field',\n\t\t\tfieldType: 'text',\n\t\t\tlabel: 'Handle',\n\t\t\trequired: true,\n\t\t\tsnippet: handleControl\n\t\t}\n\t}}\n/>\n```\n\nThe controller exposes value, disabled, required, validation state, accessible ids, controlAttrs, and the control attachment. The entry participates in bind:value, visibility, validation, submission, and InferFormValue through the same FieldState owner as built-in controls.\n\n## Non-value entries\n\nUse type: 'action' to place a labelled row of buttons in the ordered inputs flow. It accepts optional label and description content plus the same Form-aware action objects as the top-level actions prop. Its label position follows the Form layout unless labelPosition overrides it. Form size is the default button size, action density follows the Form, and loading or disabled protection cannot be overridden while the Form is submitting.\n\nUse type: 'custom' to place a snippet in the flow. The snippet receives the live public FormState and owns its own surface and padding; Form adds neither. Both entry types support class and visible, may appear at the top level or inside a group, and are omitted from bind:value, validation, and inferred submission output:\n\n```svelte\n{#snippet accountStatus(form)}\n\t<p>{Object.keys(form.value).length} visible values</p>\n{/snippet}\n\n<Form\n\tinputs={{\n\t\tname: { type: 'text', label: 'Name', required: true },\n\t\tstatus: { type: 'custom', snippet: accountStatus },\n\t\tcontrols: {\n\t\t\ttype: 'action',\n\t\t\tlabel: 'Account actions',\n\t\t\tdescription: 'Validate or save this account.',\n\t\t\tactions: [\n\t\t\t\t{ children: 'Validate', variant: 'soft', onAction: (payload) => payload.validate() },\n\t\t\t\t{ children: 'Save', onAction: (payload) => payload.submit() }\n\t\t\t]\n\t\t}\n\t}}\n/>\n```\n\nFor this configuration, InferFormValue contains name only; status and controls are presentation keys.\n\n## Validation\n\nBuilt-in validation uses the field schema and preserves its issue messages. onValidate may return:\n\n- false, null, undefined, an empty string, or an empty array for valid input;\n- true for a generic error;\n- a string for one error;\n- a string array for multiple errors.\n\nValidation runs on submission. After a field has an error, it revalidates while edited. Failed submission scrolls to and focuses the first invalid control.\n\n## Supported input types\n\ntext, email, url, password, number, rating, voice, slider, slider-range, textarea, rich-text, select, combobox, radio, checkboxes, checkbox, switch, phone, calendar, calendar-range, date, datetime, color, file, files, tag-group, tag, keyvalue, pin, and time.\n\nAlternative displays are display: 'selector' for date and calendar-range, and display: 'picker' for color.\n\nfield is the custom value-bearing entry. group, action, and custom are structural visual nodes rather than value-bearing input types.\n";
|
|
47
|
+
readonly form: "\n# Form\n\nForm renders configured entasis fields inside a div, owns their state and validation, and submits programmatically. It provides managed Enter-key navigation and submission, but not native form events, browser constraint validation, or FormData behavior.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n\timport {\n\t\tForm,\n\t\ttype FormState,\n\t\ttype InferFormValue,\n\t\ttype LiveFormValue\n\t} from 'entasis/form';\n\n\tconst inputs = {\n\t\tidentity: {\n\t\t\ttype: 'group',\n\t\t\tlabel: 'Identity',\n\t\t\tdescription: 'Core account details',\n\t\t\tcolumns: 2,\n\t\t\tinputs: {\n\t\t\t\tname: { type: 'text', label: 'Name', required: true },\n\t\t\t\temail: { type: 'email', label: 'Email', required: true }\n\t\t\t}\n\t\t}\n\t} as const;\n\n\tlet value = $state<LiveFormValue<typeof inputs>>({ name: 'Ada' });\n\tlet form = $state<FormState<typeof inputs>>();\n\tlet submitted = $state<InferFormValue<typeof inputs> | null>(null);\n</script>\n\n<Form\n\t{inputs}\n\tbind:value\n\tbind:form\n\tvariant=\"card\"\n\tlayout=\"horizontal\"\n\tsize=\"small\"\n\tdensity=\"normal\"\n\tactions={[\n\t\t{\n\t\t\tchildren: 'Save',\n\t\t\tonAction: (payload) => payload.submit()\n\t\t}\n\t]}\n\tonSubmit={(validatedValue) => {\n\t\tsubmitted = validatedValue;\n\t}}\n/>\n```\n\n## Props\n\n- **inputs** (required): ordered object keyed by entry name. Entries may be value-bearing fields, visual groups, action-button rows, or custom snippets. Only fields contribute keys to the live and validated values.\n- **value** (bindable): controlled partial live value. Visible mounted fields are published; hidden fields are omitted. A supplied top-level value takes precedence over an input's configured value.\n- **defaultValue**: initial partial live value when value is omitted.\n- **onValueChange**: called once when a user changes the visible live value. Parent value updates do not emit it.\n- **form** (bindable): the FormState instance.\n- **onSubmit**: called with the validated visible payload. Its return value is ignored; rejected promises propagate.\n- **size**: small, normal (default), or large. Controls Form typography and becomes the fallback size for fields and actions. An explicit field or action size takes precedence.\n- **density**: compact, normal (default), or comfortable. Controls gaps between the Form header, fields, footer, and actions independently from size.\n- **variant**: plain (default), sectioned, or card. All variants consume the shared Card typography. Plain remains transparent without separators. Sectioned stays transparent and adds edge-to-edge separators between the header, visible top-level fields or groups, and footer. Card adds the same separators inside the Card surface and inset; Card header/footer padding applies only to that variant. Separators do not appear between fields inside a group.\n- **layout**: vertical (default) or horizontal. Horizontal keeps one field per row and places labels to the left once each field is at least 32rem wide; fields remain stacked in narrower forms.\n- **actions**: optional array of Button props. Each action's onAction receives the live FormState, so a submit action calls form.submit(). Actions are disabled while the form is submitting; their own loading and disabled props are composed with that protection.\n- **class**: additional classes on the root div.\n- **theme**: Form theme overrides for root, header, title, description, group, group label/description/fields, top-level items, custom entries, footer, and actions. Global Card typography and section-rhythm changes flow into all Form variants before Form-specific overrides are composed; Card surface changes apply only to the card variant.\n\nThe **header**, **title**, **description**, **children**, and **footer** snippets each receive the FormState instance directly. No-argument snippets remain valid.\n\nField wrapper attributes and native control attributes have separate owners. Use fieldAttrs for the shared Field wrapper. Controls expose a typed bag for their actual native element, such as inputAttrs on TextInput, textareaAttrs on TextArea, and triggerAttrs on the custom Select's combobox button.\n\n## FormState\n\n- **value**: current visible partial value.\n- **loading**: true while the active submission handler is pending.\n- **hasError**: true after validation fails; reset after successful validation.\n- **validate()**: returns the validated visible payload, or false.\n- **submit()**: returns a promise of the validated visible payload, or false. Concurrent calls share the active promise.\n\n## Enter-key behavior\n\nOn an enabled, editable single-line input, Enter validates the current field. A valid field moves focus to the next visible enabled field, while an invalid field remains focused and displays its errors. Enter on a valid final navigable field calls form.submit(). Multiline editors and controls that already own Enter, such as selects and combobox menus, keep their native component behavior. Modified, repeated, and IME-composition Enter events are ignored.\n\n## Programmatic Dialog forms\n\nMount one Ask host near the application root, then call ask() from any client-side event. Both exports come from entasis/form:\n\n```svelte\n<script lang=\"ts\">\n\timport { Ask, ask } from 'entasis/form';\n\n\tconst inputs = {\n\t\tname: { type: 'text', label: 'Name', required: true },\n\t\temail: { type: 'email', label: 'Email', required: true }\n\t} as const;\n\n\tasync function editProfile() {\n\t\tconst outcome = await ask({\n\t\t\ttitle: 'Edit profile',\n\t\t\tdescription: 'Review the account details.',\n\t\t\tinputs,\n\t\t\tvalue: { name: 'Ada' },\n\t\t\tconfirm: 'Save',\n\t\t\tcancel: 'Cancel'\n\t\t});\n\n\t\tif (outcome.submitted) {\n\t\t\t// outcome.value is InferFormValue<typeof inputs>\n\t\t} else {\n\t\t\t// outcome.value is the partial, visible live value at cancellation\n\t\t}\n\t}\n</script>\n\n<Ask />\n```\n\nAskOptions accepts inputs, value, onSubmit, class, size, density, variant, layout, and theme with the same contracts as Form. title is required for the Dialog's accessible name. confirm and cancel accept a string or Button presentation props plus text. Their click, loading, link, and native type behavior is owned by Ask. Set type on the mounted Ask host for its default Dialog type; a request's optional dialog object may override type and also accepts responsive, size, and scroll.\n\nValidation failures keep the Dialog open and focus the first invalid field. A successful submission resolves with the validated visible payload. Cancel, the close button, and Escape resolve with submitted: false and the current partial visible value. Outside-click and swipe dismissal are disabled. While submission is active, all close paths and actions are protected. Rejected onSubmit promises reject ask() after the Dialog closes. Concurrent ask() calls render as independent stacked Dialogs. Calling ask() during SSR or without one mounted host rejects clearly.\n\n## Visibility and values\n\nThe visible prop may be a boolean or a predicate receiving the complete private value cache:\n\n```svelte\n<Form\n\tinputs={{\n\t\tcontactMethod: {\n\t\t\ttype: 'select',\n\t\t\tlabel: 'Contact method',\n\t\t\titems: [\n\t\t\t\t{ label: 'Email', value: 'email' },\n\t\t\t\t{ label: 'Phone', value: 'phone' }\n\t\t\t]\n\t\t},\n\t\tphone: {\n\t\t\ttype: 'phone',\n\t\t\tlabel: 'Phone',\n\t\t\tvisible: (value) => value.contactMethod === 'phone'\n\t\t}\n\t}}\n/>\n```\n\nHidden field values are preserved privately and restored when shown again. Hidden fields are excluded from value, validation, and submission. Removing an input definition removes its cached value.\n\n## Field label position\n\nAll configured inputs inherit the Form layout through the shared Field wrapper. Set labelPosition: 'top' or 'left' on an individual input to override the Form layout without affecting value inference:\n\n```ts\nexport const inputs = {\n\tname: { type: 'text', label: 'Name' },\n\tnotes: { type: 'textarea', label: 'Notes', labelPosition: 'top' }\n} as const;\n```\n\nThe left position becomes a two-column label/control layout once the field is at least 32rem wide and remains stacked in narrower forms. The breakpoint is a container query against the field, so a form in a narrow drawer stacks on any screen. Form uses layout rather than orientation so controls such as Slider retain their own orientation prop.\n\n## Visual groups\n\nA group renders a transparent semantic fieldset with a required legend label and an optional description. Its label and description use the same size scale as ordinary fields; grouping does not add another card or padded container:\n\n```ts\nimport type { InferFormValue } from 'entasis/form';\n\nexport const inputs = {\n\tcontact: {\n\t\ttype: 'group',\n\t\tlabel: 'Contact details',\n\t\tdescription: 'How we can reach you',\n\t\tcolumns: 1,\n\t\tinputs: {\n\t\t\temail: { type: 'email', label: 'Email', required: true },\n\t\t\tphone: { type: 'phone', label: 'Phone' }\n\t\t}\n\t}\n} as const;\n\ntype Value = InferFormValue<typeof inputs>;\n// { email: string; phone: string | null }\n```\n\nThe group key is visual only and never appears in value or submission output. Descendant field names remain top-level, so duplicate names across groups are rejected. Group visibility hides all descendants, preserves field values in the private cache, and makes inferred field keys optional. In a vertical form layout, set columns to 1, 2, 3, or 4 to control the equal-width grid; the count is a container query against the form width (2 columns from 42rem, 3 from 56rem, 4 from 72rem), and groups collapse to one column in narrower forms. Horizontal form layouts ignore columns and always render group entries in one column. A child class may span tracks when an entry needs more room. Groups may contain fields, action rows, and custom snippets; nested groups are intentionally unsupported.\n\n## Custom value fields\n\nUse type: 'field' when Form must own a value but a custom control must render it. fieldType selects the existing FieldState schema and inferred value type. The snippet receives that registered FieldState controller:\n\n```svelte\n{#snippet handleControl(field)}\n\t<input\n\t\t{...field.controlAttrs}\n\t\t{@attach field.control}\n\t\tbind:value={field.value}\n\t/>\n{/snippet}\n\n<Form\n\tinputs={{\n\t\thandle: {\n\t\t\ttype: 'field',\n\t\t\tfieldType: 'text',\n\t\t\tlabel: 'Handle',\n\t\t\trequired: true,\n\t\t\tsnippet: handleControl\n\t\t}\n\t}}\n/>\n```\n\nThe controller exposes value, disabled, required, validation state, accessible ids, controlAttrs, and the control attachment. The entry participates in bind:value, visibility, validation, submission, and InferFormValue through the same FieldState owner as built-in controls.\n\n## App-defined input types\n\nAn app can add its own input types to every Form and ask() dialog, by name, so entries stay plain data that serializes (JSON from a server, a tool call, a database). Two steps, once per app.\n\nType it: augment `FormInputRegistry` with the value the input holds and the props its entries take.\n\n```ts\n// doc-fence: skip (imports a sibling component; tooling/component-contract/consumer-form-input.ts checks this exact setup against the package)\nimport { registerFormInputs } from 'entasis/form';\nimport MoneyInput from './MoneyInput.svelte';\n\nexport type Money = { amount: number; currency: string };\n\ndeclare module 'entasis/form' {\n\tinterface FormInputRegistry {\n\t\tmoney: { value: Money; props: { currencies?: string[] } };\n\t}\n}\n\nregisterFormInputs({\n\tmoney: {\n\t\tcomponent: MoneyInput,\n\t\tisEmpty: (value) => value?.amount === undefined,\n\t\tvalidate: (value) => (value.amount < 0 ? 'Must not be negative' : null)\n\t}\n});\n```\n\nRegister it: call registerFormInputs at the top level of a module the server and the browser both load before a form renders, such as one the root layout imports. Its keys are checked against the registry; built-in type names are refused, and registering a type again replaces it.\n\nThe component renders the control alone. Form wraps it in Field (label, description, errors) and hands it the field it registered, typed from the registry, plus the entry's own props:\n\n```svelte\n<!-- doc-fence: skip -->\n<!-- Needs the money registration above; tooling/component-contract/consumer-money-input.svelte is the checked copy. -->\n<script lang=\"ts\">\n\timport type { FormInputComponentProps } from 'entasis/form';\n\n\tlet { field, currencies = ['EUR'] }: FormInputComponentProps<'money'> = $props();\n\tconst currency = $derived(field.value?.currency ?? currencies[0] ?? 'EUR');\n</script>\n\n<input\n\ttype=\"number\"\n\t{...field.controlAttrs}\n\t{@attach field.control}\n\tvalue={field.value?.amount ?? ''}\n\toninput={(event) => field.setValue({ amount: Number(event.currentTarget.value), currency })}\n/>\n```\n\nEntries then use the type like a built-in one, in Form inputs, groups, and ask():\n\n```ts\n// doc-fence: skip (needs the money registration above)\nconst result = await ask({\n\ttitle: 'Set a price',\n\tconfirm: 'Save',\n\tcancel: 'Cancel',\n\tinputs: { price: { type: 'money', label: 'Price', required: true, currencies: ['EUR', 'USD'] } }\n});\nif (result.submitted) result.value.price; // Money\n```\n\nEvery Field entry option applies (label, description, required, disabled, size, density, visible, defaultValue, onValidate, onValueChange, slots); any other key goes to the component. required rejects what isEmpty reports empty (by default null, undefined, an empty string, or an empty array); validate runs for every entry of the type, before the entry's own onValidate. The value takes part in bind:value, visibility, validation, submission, and InferFormValue like a built-in input's. An entry whose type is neither built-in nor registered throws when the form reads it.\n\n## Non-value entries\n\nUse type: 'action' to place a labelled row of buttons in the ordered inputs flow. It accepts optional label and description content plus the same Form-aware action objects as the top-level actions prop. Its label position follows the Form layout unless labelPosition overrides it. Form size is the default button size, action density follows the Form, and loading or disabled protection cannot be overridden while the Form is submitting.\n\nUse type: 'custom' to place a snippet in the flow. The snippet receives the live public FormState and owns its own surface and padding; Form adds neither. Both entry types support class and visible, may appear at the top level or inside a group, and are omitted from bind:value, validation, and inferred submission output:\n\n```svelte\n{#snippet accountStatus(form)}\n\t<p>{Object.keys(form.value).length} visible values</p>\n{/snippet}\n\n<Form\n\tinputs={{\n\t\tname: { type: 'text', label: 'Name', required: true },\n\t\tstatus: { type: 'custom', snippet: accountStatus },\n\t\tcontrols: {\n\t\t\ttype: 'action',\n\t\t\tlabel: 'Account actions',\n\t\t\tdescription: 'Validate or save this account.',\n\t\t\tactions: [\n\t\t\t\t{ children: 'Validate', variant: 'soft', onAction: (payload) => payload.validate() },\n\t\t\t\t{ children: 'Save', onAction: (payload) => payload.submit() }\n\t\t\t]\n\t\t}\n\t}}\n/>\n```\n\nFor this configuration, InferFormValue contains name only; status and controls are presentation keys.\n\n## Validation\n\nBuilt-in validation uses the field schema and preserves its issue messages. onValidate may return:\n\n- false, null, undefined, an empty string, or an empty array for valid input;\n- true for a generic error;\n- a string for one error;\n- a string array for multiple errors.\n\nValidation runs on submission. After a field has an error, it revalidates while edited. Failed submission scrolls to and focuses the first invalid control.\n\n## Supported input types\n\ntext, email, url, password, number, rating, voice, slider, slider-range, textarea, rich-text, select, combobox, radio, checkboxes, checkbox, switch, phone, calendar, calendar-range, date, datetime, color, file, files, tag-group, tag, keyvalue, pin, and time.\n\nAlternative displays are display: 'selector' for date and calendar-range, and display: 'picker' for color.\n\nfield is the custom value-bearing entry. group, action, and custom are structural visual nodes rather than value-bearing input types.\n";
|
|
48
48
|
readonly 'key-value-input': "\n# KeyValueInput Component\n\nAn editable list of key/value string pairs. Each row renders a key text input, a value text input, and a remove button; a full-width Add button below the rows appends a new empty pair. There is no dropdown, no async, and no option list — just plain text entry.\n\n## Basic Usage\n\n```svelte\n<script>\n\timport { KeyValueInput } from 'entasis/key-value-input';\n\tlet value = $state(null);\n</script>\n\n<KeyValueInput bind:value={value} />\n```\n\n## Props\n\n### Core Props\n\n- **value**: `KeyValuePair[] | null` (default: `null`, bindable)\n - The current list of `{ key, value }` pairs. `null` is treated as an empty list.\n - The array is never mutated in place — every add/remove assigns a fresh array.\n - Replacing `value` updates the editor rows without emitting `onValueChange`. Use `defaultValue` to initialize an uncontrolled editor once.\n\n### Content Props\n\n- **keyPlaceholder**: `string` (optional)\n - Placeholder for the key input. Defaults to the localized \"Key\" label.\n\n- **valuePlaceholder**: `string` (optional)\n - Placeholder for the value input. Defaults to the localized \"Value\" label.\n\n- **addLabel**: `string` (optional)\n - Label for the Add button. Defaults to the localized \"Add\" label.\n\n### Behavior Props\n\n- **maxRows**: `number` (optional)\n - Maximum number of rows. Once reached, the Add button is disabled. There is no minimum.\n\n### Field Props\n\n- **required**: `boolean` (default: `false`)\n- **disabled**: `boolean`\n- **size**: `'small' | 'normal' | 'large'` (default: `'normal'`)\n- **name**: `string`\n- **errors**: `string[] | boolean` (bindable)\n- **focused**: `boolean` (bindable)\n- **description**: `string`\n\n### Event Props\n\n- **onValueChange**: `(value: KeyValuePair[]) => void`\n - Called when the pairs change.\n\n- **onValidate**: `(value: KeyValuePair[]) => string[] | boolean`\n - Custom validation function.\n\n### i18n / Theme Props\n\n- **i18n**: `Partial<Messages>`\n - Per-instance message overrides merged over the global catalog (`add`, `keyLabel`, `valueLabel`, `remove`).\n\n- **theme**: `KeyValueInputThemeProps`\n - Customize styling for: inputContainer, row, input, removeButton, addButton.\n\n## Examples\n\n### Empty (Start With Add)\n\n```svelte\n<script>\n\tlet value = $state(null);\n</script>\n\n<KeyValueInput bind:value={value} />\n```\n\n### Pre-filled Value\n\n```svelte\n<script>\n\tlet value = $state([\n\t\t{ key: 'Content-Type', value: 'application/json' },\n\t\t{ key: 'Accept', value: '*/*' }\n\t]);\n</script>\n\n<KeyValueInput bind:value={value} />\n```\n\n### Limited Number of Rows\n\n```svelte\n<KeyValueInput maxRows={3} bind:value={value} />\n```\n\n### Custom Placeholders\n\n```svelte\n<KeyValueInput keyPlaceholder=\"Header\" valuePlaceholder=\"Value\" addLabel=\"Add header\" bind:value={value} />\n```\n\n### Object Output\n\nThe value is an array of pairs; convert it to a plain object with `Object.fromEntries`:\n\n```svelte\n<script>\n\tlet value = $state([{ key: 'a', value: '1' }]);\n\tconst object = $derived(Object.fromEntries((value ?? []).map((p) => [p.key, p.value])));\n</script>\n\n<KeyValueInput bind:value={value} />\n<pre>{JSON.stringify(object)}</pre>\n```\n\n## Keyboard Interactions\n\n- **Enter** (in a non-empty value input): Prevents the enclosing form from submitting, appends a new empty row, and focuses its key input.\n- **Enter** (in a key or empty value input): Prevents the enclosing form from submitting without adding a row.\n- **Arrow Right** (at the end of an input): Moves to the next input and places the caret at its start.\n- **Arrow Left** (at the start of an input): Moves to the previous input and places the caret at its end.\n- **Arrow Up / Arrow Down**: Moves to the same field in the previous or next row while preserving the caret offset when possible.\n- **Backspace** (in an empty input): Moves to the previous input. When the entire row is empty, removes the row and focuses the previous row's value input when available.\n\n## Accessibility\n\n- The rows collection is a real list (`role=\"list\"` / `role=\"listitem\"`), not a listbox.\n- Each row's remove control is a real `<button type=\"button\">` with an `aria-label` of \"Remove <key>\" (falling back to the \"Key\" label when the key is empty).\n- The Add button is a real `<button type=\"button\">`.\n- Inputs use `autocomplete=\"off\"` and `data-1p-ignore`.\n\n## Notes\n\n- **Stable-id keying**: Rows are keyed by a stable per-instance uid, not by the key string. Keys can be empty or duplicated while typing, and duplicate/empty keys in a keyed `{#each}` would crash the renderer.\n- **Controlled values**: Parent value replacements update the editor. User edits publish one change callback; unchanged pairs do not publish another callback.\n- **Fresh arrays**: Add and remove always assign a new array so binding, reactivity, and form updates fire correctly.\n- **Animations**: Rows animate on reorder (`animate:flip`) and on enter/leave (`transition:scale`).\n- **maxRows**: Disables the Add button once the row count reaches `maxRows`.\n- **Object output**: Use `Object.fromEntries((value ?? []).map((p) => [p.key, p.value]))` to get a plain object.\n\n## Theme Customization\n\nThe KeyValueInput uses a theme object customizable via the `theme` prop or a global theme.\n\n### Theme Structure\n\n- **inputContainer**: The vertical stack holding the rows and Add button (no border/background of its own).\n- **row**: The animated flex wrapper around each key/value/remove trio.\n- **input**: The bordered key and value text inputs.\n- **removeButton**: The square remove (X) button.\n- **addButton**: The full-width Add button.\n\n### Global Theme Setting\n\n```svelte\n<script>\n\timport { setKeyValueInputTheme } from 'entasis/key-value-input';\n\n\tsetKeyValueInputTheme({\n\t\taddButton: {\n\t\t\tbase: 'rounded-lg border-2'\n\t\t}\n\t});\n</script>\n```\n\n## State contract\n\n- **value**: current bindable editable value.\n- **defaultValue**: initial value used only when `value` is omitted.\n- **onValueChange**: called with the new value when the component changes it.\n\n";
|
|
49
49
|
readonly 'multi-step-form': "\n# MultiStepForm\n\nMultiStepForm owns one Form per step, displays them through Stepper, validates visited steps before navigation, and validates every step before final submission. Field names must be unique across steps.\n\n## Basic usage\n\n```svelte\n<script lang=\"ts\">\n\timport {\n\t\tMultiStepForm,\n\t\ttype FormStep,\n\t\ttype MergedMultiStepFormInputs\n\t} from 'entasis/multi-step-form';\n\timport type { FormInputs, InferFormValue, LiveFormValue } from 'entasis/form';\n\n\tconst accountInputs = {\n\t\tname: { type: 'text', label: 'Name', required: true },\n\t\temail: { type: 'email', label: 'Email', required: true }\n\t} satisfies FormInputs;\n\tconst preferenceInputs = {\n\t\tnewsletter: { type: 'switch', label: 'Newsletter' }\n\t} satisfies FormInputs;\n\n\tconst items: [\n\t\tFormStep<typeof accountInputs>,\n\t\tFormStep<typeof preferenceInputs>\n\t] = [\n\t\t{\n\t\t\ttitle: 'Account',\n\t\t\tinputs: accountInputs\n\t\t},\n\t\t{\n\t\t\ttitle: 'Preferences',\n\t\t\tinputs: preferenceInputs\n\t\t}\n\t];\n\n\ttype Inputs = MergedMultiStepFormInputs<typeof items>;\n\tlet value = $state<LiveFormValue<Inputs>>({});\n\tlet submitted = $state<InferFormValue<Inputs> | null>(null);\n</script>\n\n<MultiStepForm\n\t{items}\n\tbind:value\n\tonSubmitForm={(validatedValue) => {\n\t\tsubmitted = validatedValue;\n\t}}\n/>\n```\n\n## Props\n\n- **items** (required): ordered FormStep array. Duplicate field names across steps throw.\n- **value** (bindable): controlled partial values merged across all steps, synchronized in both directions.\n- **defaultValue**: initial merged values when value is omitted.\n- **onValueChange**: called when the merged live value changes.\n- **onSubmitForm**: called with the validated merged payload on the final step only.\n- **onSubmitStep**: receives one `{ value, step, index }` payload after the step's onBeforeChange. `value` contains validated values from visited steps; unvisited keys remain optional. Return false to block advancing.\n- **showMeter**: whether to render the progress meter. Defaults to true.\n- **meterColor**: meter color token. Defaults to neutral.\n- **previousText**, **nextText**, **submitText**: navigation labels.\n- **variant**: visual presentation of the multi-step shell. Card renders one shared Card surface around the fixed meter, transitioning step content, and navigation footer. Header, meter, step fields, and footer share one horizontal inset in every variant. Each step reuses Form's card spacing and edge-to-edge separator rules without creating another Card surface. Supports plain, sectioned, and card; defaults to plain.\n- **previousButtonProps**, **nextButtonProps**, **submitButtonProps**: Button props. Custom click handlers are composed with internal behavior; disabled and loading protection cannot be overridden.\n- **class**: additional classes on the root.\n- **theme**: MultiStepForm theme overrides plus optional nested form theme overrides.\n\nThe **header**, **children**, and **footer** snippets receive the MultiStepFormState instance directly. Supplying a header or footer replaces that region's default meter or controls. In the card variant, header and footer content remains inside the shared card while only the active step content transitions.\n\n## FormStep\n\n```ts\ntype FormStep<I extends FormInputs> = {\n\ttitle?: string;\n\tdescription?: string;\n\tinputs: I;\n\tonBeforeChange?: (context: {\n\t\tvalue: InferFormValue<I>;\n\t\tform: FormState<I>;\n\t\tnext: () => void;\n\t}) => void | Promise<void>;\n};\n```\n\nWhen onBeforeChange is present, navigation remains blocked unless the hook calls next(). After permission is granted, onSubmitStep may still block by returning false.\n\n## Submission behavior\n\n- Next validates every visited step. Hooks do not run and the active step does not change when validation fails.\n- Final submission validates every step before calling onSubmitForm; partial invalid data is never submitted.\n- Enter follows Form's field-navigation behavior. A terminal Enter validates the current and visited steps, advances when permitted, and submits from the final step.\n- Rejected hooks and submit handlers propagate their errors, while loading always resets.\n- Concurrent calls to submit() share one active promise.\n\n## MultiStepFormState\n\n- **steps**: current step definitions.\n- **stepper**: bound Stepper state, including value, next(), and previous().\n- **value**: merged live partial value.\n- **loading**: true while a navigation or final-submit hook is pending.\n- **isLastStep**: whether the current step is final.\n- **progress** and **meterSteps**: values used by the default meter.\n- **submit()**: validates and advances or submits, returning the validated visited-step payload or false. The final-step result is the complete validated payload.\n";
|
|
50
50
|
readonly 'number-input': "\n# NumberInput Component\n\nThe NumberInput component is a numeric input field with +/- controls, min/max validation, and formatting options.\n\n## Basic Usage\n\n```svelte\n<script>\n\tlet quantity = $state(1);\n</script>\n\n<NumberInput label=\"Quantity\" bind:value={quantity} />\n```\n\n## Props\n\nExtends all Field component props plus:\n\n### Core Props\n- **value**: number (bindable) - Input value\n- **min**: number - Minimum allowed value\n- **max**: number - Maximum allowed value\n- **step**: number - Native number input step for keyboard and browser interactions\n- **increment**: number (default: step ?? 1) - Amount changed by the +/- buttons\n- **showControls**: boolean (default: true) - Whether to render the +/- buttons\n\n### Display Props\n- **placeholder**: string - Placeholder text\n\n### Field Props (inherited)\n- **label**: string | Snippet - Field label\n- **description**: string | Snippet - Helper text\n- **error**: string - Error message\n- **required**: boolean - Mark as required\n- **disabled**: boolean - Disable input\n- **size**: 'small' | 'normal' | 'large' - Input size\n\n### Content Slots\n- **prefix**: Snippet - Content before input (e.g., currency symbol)\n- **suffix**: Snippet - Content after input (e.g., unit)\n\n### Styling Props\n- **class**: string - Additional CSS classes\n- **theme**: ComponentTheme - Custom theme overrides\n\n## Structure\n\n```\n<Field>\n\t<Label />\n\t<Description />\n\t<InputContainer>\n\t\t<Prefix />\n\t\t<Input type=\"number\" />\n\t\t<Controls>\n\t\t\t<Button label=\"Decrease value\" />\n\t\t\t<Button label=\"Increase value\" />\n\t\t</Controls>\n\t\t<Suffix />\n\t</InputContainer>\n\t<Error />\n</Field>\n```\n\n## Examples\n\n### Basic Number Input\n```svelte\n<script>\n\tlet age = $state(25);\n</script>\n\n<NumberInput label=\"Age\" bind:value={age} />\n```\n\n### With Min and Max\n```svelte\n<NumberInput \n\tlabel=\"Quantity\"\n\tbind:value={quantity}\n\tmin={1}\n\tmax={100}\n/>\n```\n\n### With Step and Button Increment\n```svelte\n<NumberInput \n\tlabel=\"Price\"\n\tbind:value={price}\n\tstep={0.01}\n\tincrement={1}\n\tmin={0}\n/>\n```\n\n### Custom Button Increment\n```svelte\n<NumberInput \n\tlabel=\"Quantity\"\n\tbind:value={quantity}\n\tincrement={5}\n/>\n```\n\n### With Prefix (Currency)\n```svelte\n<NumberInput \n\tlabel=\"Amount\"\n\tbind:value={amount}\n\tmin={0}\n\tstep={0.01}\n>\n\t{#snippet prefix()}\n\t\t<span>$</span>\n\t{/snippet}\n</NumberInput>\n```\n\n### With Suffix (Unit)\n```svelte\n<NumberInput \n\tlabel=\"Weight\"\n\tbind:value={weight}\n\tmin={0}\n>\n\t{#snippet suffix()}\n\t\t<span>kg</span>\n\t{/snippet}\n</NumberInput>\n```\n\n### Required Field\n```svelte\n<NumberInput \n\tlabel=\"Number of Guests\"\n\tbind:value={guests}\n\tmin={1}\n\trequired\n/>\n```\n\n### Disabled State\n```svelte\n<NumberInput \n\tlabel=\"Score\"\n\tvalue={100}\n\tdisabled\n/>\n```\n\n### Product Quantity Selector\n```svelte\n<script>\n\tlet quantity = $state(1);\n\tlet price = 29.99;\n\t\n\tconst total = $derived(quantity * price);\n</script>\n\n<div>\n\t<NumberInput \n\t\tlabel=\"Quantity\"\n\t\tbind:value={quantity}\n\t\tmin={1}\n\t\tmax={10}\n\t\tdescription={`Total: $${total.toFixed(2)}`}\n\t/>\n</div>\n```\n\n### Age Input\n```svelte\n<NumberInput \n\tlabel=\"Age\"\n\tbind:value={age}\n\tmin={18}\n\tmax={120}\n\tdescription=\"You must be 18 or older\"\n\trequired\n/>\n```\n\n### Temperature Control\n```svelte\n<NumberInput \n\tlabel=\"Temperature\"\n\tbind:value={temperature}\n\tmin={-10}\n\tmax={40}\n\tstep={0.5}\n>\n\t{#snippet suffix()}\n\t\t<span>°C</span>\n\t{/snippet}\n</NumberInput>\n```\n\n### Percentage Input\n```svelte\n<NumberInput \n\tlabel=\"Discount\"\n\tbind:value={discount}\n\tmin={0}\n\tmax={100}\n\tstep={5}\n>\n\t{#snippet suffix()}\n\t\t<span>%</span>\n\t{/snippet}\n</NumberInput>\n```\n\n### Rating Input\n```svelte\n<NumberInput \n\tlabel=\"Rating\"\n\tbind:value={rating}\n\tmin={1}\n\tmax={5}\n\tstep={0.5}\n\tdescription=\"Rate from 1 to 5 stars\"\n/>\n```\n\n## Validation\n\nNumberInput automatically validates:\n- **min**: Value must be >= min\n- **max**: Value must be <= max\n- **step**: Value must align with step increments\n- **required**: Non-null value\n\n## Keyboard Controls\n\n- **Arrow Up**: Increment by step\n- **Arrow Down**: Decrement by step\n- **Page Up**: Increment by step * 10\n- **Page Down**: Decrement by step * 10\n\n## Accessibility\n\n- Proper label association\n- ARIA attributes for min, max, and current value\n- Keyboard navigation support\n- Screen reader announcements for value changes\n- Control buttons are focusable and keyboard accessible\n\n## Notes\n\n- Native HTML number input for best compatibility\n- +/- controls provide visual increment/decrement\n- Automatically respects min/max bounds\n- Supports decimal values with step\n- Can be styled with prefix/suffix for units\n\n## Theme Customization\n\nThe NumberInput 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- **input**: Input element styles\n- **inputContainer**: Input container wrapper styles\n\n### Available Variants\n\n**input**:\n- base: Base classes applied to the input element\n- Variants:\n - size: 'small' | 'normal' | 'large' - Text size\n - disabled: boolean - Disabled state styling\n\n**inputContainer**:\n- base: Base classes for the input container (handles focus states, borders, padding)\n- Variants:\n - size: 'small' | 'normal' | 'large' - Container height and size-based styling\n - disabled: boolean - Disabled state styling\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<NumberInput \n label=\"Quantity\"\n bind:value={quantity}\n theme={{\n inputContainer: {\n base: 'border-2 rounded-lg',\n size: {\n normal: 'h-8 px-4'\n }\n },\n input: {\n size: {\n normal: 'text-base'\n }\n }\n }}\n/>\n```\n\n**Focus State Customization**:\n\nTo recolor every focus ring in the app at once, set `designTokens.focusColor` on `Theme`\ninstead of overriding per component. `ring-focus` is the focus state role and falls back to the\ncurrent role, so it never hard-pins a color.\n\n```svelte\n<NumberInput \n label=\"Custom Number\"\n bind:value={value}\n theme={{\n inputContainer: {\n base: 'focus-within:ring-2 focus-within:ring-focus/50 focus-within:border-focus'\n }\n }}\n/>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setNumberInputTheme } from '../components/Form/NumberInput/index.ts';\n \n setNumberInputTheme({\n inputContainer: {\n base: 'rounded-lg border-2 transition-all',\n size: {\n normal: 'h-8 px-4'\n }\n },\n input: {\n base: 'text-right'\n }\n });\n</script>\n```\n\n## State contract\n\n- **value**: current bindable editable value.\n- **defaultValue**: initial value used only when `value` is omitted.\n- **onValueChange**: called with the new value when the component changes it.\n\n";
|
|
@@ -123,7 +123,7 @@ export declare const componentMcpRegistry: {
|
|
|
123
123
|
readonly globe: "\n# Globe Component\n\nAn interactive, auto-rotating WebGL globe (powered by cobe). Draggable, theme-aware, and animated\nin via a fade. Colors accept theme tokens and recolor automatically when the app theme changes.\n\n## Requires\n\nGlobe renders through cobe (WebGL). It is an optional peer dependency of entasis, so install it alongside entasis:\n\n`pnpm add cobe`\n\n## Basic Usage\n\n```svelte\n<script>\n import { Globe } from '../components/Globe/index.ts';\n</script>\n\n<Globe />\n<Globe baseColor=\"neutral\" glowColor=\"primary\" markerColor=\"primary\" />\n```\n\n## Props\n\n### Appearance\n- **baseColor**: Colors | [number, number, number] (default: soft white) - Landmass dot color. Token or normalized RGB (0–1). A light base gives bright continents that pop on the shaded sphere.\n- **markerColor**: Colors | [number, number, number] (default: 'primary') - Marker dot color.\n- **glowColor**: Colors | [number, number, number] (default: soft white) - Atmospheric glow color.\n- **dark**: number (default: 1) - Shading darkness 0–1. Pass a negative value to follow the app theme (dark -> 1, light -> 0).\n- **opacity**: number (default: 1) - Globe texture opacity.\n- **scale**: number (default: 1) - Globe scale within the canvas.\n- **diffuse**: number (default: 1.2) - Diffuse lighting intensity.\n- **mapSamples**: number (default: 16000) - Number of map dots (0–100000).\n- **mapBrightness**: number (default: 6) - Brightness of the map dots.\n- **offset**: [number, number] | ['x%', 'y%'] - Globe offset in pixels or viewport-relative percentages.\n\n### Rotation & interaction\n- **autoRotate**: number (default: 0.3) - Idle rotation speed. 0 disables; negative reverses.\n- **draggable**: boolean (default: true) - Drag to rotate.\n- **dragAxis**: 'x' | 'y' | 'xy' (default: 'xy') - Axis dragging affects.\n- **dragSpeed**: number (default: 2) - Drag rotation speed.\n- **dragStiffness / dragDamping**: number - Spring momentum tuning.\n- **phi / theta**: number - Initial angles.\n\n### Bindable / callbacks\n- **scrollTo**: bind:scrollTo -> (lat, lng) => void - Imperatively rotate to a coordinate. Pins there until the next drag.\n- **onReady**: () => void - Fires once the first frame renders.\n\n### Styling\n- **class**: string - Applied to the <canvas>. Default theme sizes it 400px square; override for other sizes.\n- **theme**: GlobeThemeProps - Theme overrides (globe part).\n\n## Examples\n\n### Rotate to a city on demand\n```svelte\n<script>\n let goTo;\n</script>\n\n<Globe bind:scrollTo={goTo} />\n<button onclick={() => goTo(48.85, 2.35)}>Paris</button>\n```\n\n### Custom markers and size\n```svelte\n<Globe\n class=\"size-[600px]\"\n markers={[{ location: [48.85, 2.35], size: 0.1 }]}\n/>\n```\n\n## Accessibility\n\nThe globe is a decorative <canvas> with no text content. When it conveys meaning, provide an\naccessible label on a wrapping element or nearby text; keyboard users cannot rotate it.\n\n## Notes\n\n- Renders nothing meaningful without WebGL support.\n- Colors resolve at runtime from CSS variables, so theme tokens recolor the globe on theme toggle.\n- The render loop runs while mounted and stops (and disposes the WebGL context) on unmount.\n";
|
|
124
124
|
readonly map: "\n# Map Component\n\nAn interactive vector map (pan, zoom, markers, clustering, shapes, geolocation) built on [MapLibre GL](https://maplibre.org/). MapLibre is loaded from a CDN at runtime and never bundled. The base map style is a tokenized Positron style whose colours are derived from your entasis design tokens, so the map adapts to light and dark automatically. Ported from svelte-pro's Map component.\n\n## Basic Usage\n\n```svelte\n<script>\n import { Map } from '../components/Map/index.ts';\n const markers = [\n { id: 'paris', lng: 2.3522, lat: 48.8566, label: 'Paris' },\n { id: 'lyon', lng: 4.8357, lat: 45.7640, label: 'Lyon' }\n ];\n</script>\n\n<Map {markers} controls cluster popup tooltip />\n```\n\n## Props\n\n### Core Props\n- **markers**: MapMarker[] (required) - The markers to place. `MapMarker = { id, lng, lat, label?, description?, popup?, tooltip?, color?, data? }`. Ids must be unique; lng/lat are validated.\n- **center**: [number, number] (default: [2.3522, 48.8566]) - Initial map center (lng, lat).\n- **zoom**: number (default: 5) - Initial zoom level.\n- **bounds**: [w,s,e,n] | [[lng,lat],[lng,lat]] - Fit the view to these bounds (overrides center/zoom on mount).\n- **minZoom** / **maxZoom**: number - Zoom constraints.\n- **size**: 'small' | 'normal' | 'large' (default: 'normal') - Viewport height variant.\n\n### Style Props\n- **styleUrl**: string - Use a specific MapLibre style URL instead of the tokenized default (mutually exclusive with `styles`).\n- **styles**: { positron?: string; dark?: string } - Per-scheme style URL overrides (mutually exclusive with `styleUrl`).\n\n### Interaction Props\n- **interactive**: boolean - Enable/disable pan/zoom interactions.\n- **showAttribution**: boolean (default: true) - Show the attribution control.\n- **fitMarkersOnMount**: boolean - Fit the view to all markers on mount.\n- **fitMarkersPadding**: number - Padding (px) used when fitting markers.\n- **controls**: boolean | ('zoom-in'|'zoom-out'|'fit-markers'|'geolocate'|'reset')[] - Show the control cluster; pass an array to pick which buttons.\n- **controlPosition**: 'top-left'|'top-right'|'bottom-left'|'bottom-right' - Where to dock the controls.\n- **cluster**: boolean | MapClusterConfig - Cluster nearby markers.\n- **geolocation**: boolean | { enabled?, watch?, zoom?, showMarker? } - Locate the user.\n- **popup**: boolean | Snippet - Show a popover on marker click. `true` uses the marker's `popup`/`label`; a snippet receives `{ marker, data, map, lngLat, open, close }`.\n- **tooltip**: boolean | Snippet - Show a hover tooltip. Snippet receives `{ marker, data, map, lngLat, open }`.\n- **shapes**: MapShape[] - Draw circles, polylines, polygons or rectangles.\n\n### Content Props (Snippets)\n- **marker**: Snippet - Custom marker rendering; receives `{ marker, data, map, lngLat }`.\n- **clusterMarker**: Snippet - Custom cluster rendering; receives `{ id, count, coordinates, map, expand, markers, data, feature }`.\n- **userLocationMarker**: Snippet - Custom user-location marker.\n- **controlButton**: Snippet - Custom control button; receives `{ action, label, disabled, active, onclick }`.\n\n### Event Props\n- **onMarkerClick**: (payload) => void\n- **onClusterClick**: (payload) => void\n- **onReady**: (payload) => void - The MapLibre map instance is ready.\n- **onViewChange** / **onMoveEnd** / **onZoomEnd**: (payload) => void\n- **onError**: (error: Error) => void - Loading, style, or runtime errors (otherwise logged to console).\n\n### Advanced Props\n- **class**: string - Additional classes on the map root.\n- **theme**: MapTheme - Theme overrides for the root container.\n\n## Theming\n\nThe base style is generated at runtime from the current entasis `--color-*` tokens and re-generated whenever they change (theme flip, palette swap, runtime token edits): `primary` tints land, buildings and water like a monochrome basemap, `success` paints parks and points of interest, `danger` draws boundaries and `neutral` carries roads and labels on `surface`. To fully override the cartography, pass `styleUrl` or `styles`.\n\n## Accessibility\n\n- Markers are focusable buttons with descriptive labels; popups are keyboard-toggleable and close on Escape/outside-click.\n- The control cluster is a labelled button group.\n- A loading skeleton with `role=\"status\"` is shown until the map's first render; if the map fails to load (e.g. the CDN is unreachable) a `role=\"alert\"` message replaces it instead of hanging on the skeleton.\n\n## Notes\n\n- MapLibre GL JS + its stylesheet load from a CDN on first mount; nothing is bundled. A network connection is required for the library and the default Positron tiles.\n- `styleUrl` and `styles` cannot both be set.\n";
|
|
125
125
|
readonly markdown: "\n# Markdown Component\n\nRenders Markdown as themed HTML, built for streaming LLM output. Wraps the user's own [svelte-streamdown](https://github.com/) renderer, restyled with entasis design tokens so it matches the rest of the app and adapts to light and dark automatically. Incomplete/streaming Markdown (an unterminated code fence, a half-written table) is handled gracefully — nothing crashes or flashes as chunks arrive.\n\nFenced code blocks render through entasis's own Code component (syntax highlighting + copy button), and \\`mermaid\\` fences render through entasis's Mermaid component (pan/zoom, brand theming, and errorForgiving so a diagram being streamed in doesn't flash errors between chunks).\n\nCard, Stat, Stack, Grid, and GridSpan are also available as built-in MDX components. They can be used directly inside the Markdown source without passing an MDX component map.\n\n## Basic Usage\n\n\\`\\`\\`svelte\n<Markdown content={\"# Hello\\n\\nSome **bold** and _italic_ text.\"} />\n<Markdown content={message} size=\"small\" />\n<Markdown content={doc} size=\"large\" />\n\\`\\`\\`\n\n## Props\n\n### Core Props\n- **content**: string (required) - The Markdown source. Re-renders reactively as it changes; incomplete/streaming Markdown is handled gracefully (safe to bind to a growing string from a token stream).\n- **size**: 'small' | 'normal' | 'large' (default: 'normal') - Type and spacing scale.\n - **small**: compact — smaller headings, tighter block margins, \\`text-sm\\` body. Best for chat messages and dense UI.\n - **normal**: the default reading scale (\\`text-base\\` body, shadcn-equivalent heading sizes).\n - **large**: long-form — larger headings, roomier margins, \\`text-lg\\` body. Best for documents and articles.\n\n### Advanced Props\n- **class**: string - Additional CSS classes on the root wrapper.\n- **theme**: MarkdownThemeProps - Overrides for the root wrapper part (size variants live here).\n- **mdxComponents**: StreamdownProps['mdxComponents'] - Custom MDX components. Matching names override the built-in entasis components.\n- **...streamdown passthroughs**: every other svelte-streamdown prop is forwarded except the ones entasis controls (\\`content\\`, \\`theme\\`, \\`baseTheme\\`, \\`mergeTheme\\`, \\`class\\`, \\`code\\`, \\`mermaid\\`, \\`children\\`, \\`streamdown\\`, \\`element\\`). Use these for streamdown features such as controlling which block types are parsed, link/image handling, and other renderer options — see the svelte-streamdown docs.\n\n## Rendering\n\n- **Block elements**: headings (h1–h6), paragraphs, lists (ordered, unordered, task lists), blockquotes, horizontal rules, tables, images and links are all styled from entasis tokens.\n- **GitHub alerts**: \\`> [!NOTE]\\`, \\`> [!TIP]\\`, \\`> [!IMPORTANT]\\`, \\`> [!WARNING]\\` and \\`> [!CAUTION]\\` render as colored callouts mapped to entasis info/success/primary/warning/danger muted pairings.\n- **Inline code** (\\`codespan\\`): styled as a subtle chip; its text size scales with \\`size\\`.\n- **Code fences**: rendered by entasis's Code component — syntax highlighting and a copy button. The code block sizing follows \\`size\\`.\n- **Mermaid fences**: a fence tagged \\`mermaid\\` renders through entasis's Mermaid component with \\`errorForgiving\\` enabled, so a diagram that is still streaming in holds its last valid frame instead of flashing parse errors. Diagram size maps from the Markdown \\`size\\`.\n\n## Built-in MDX components\n\nThe built-in tags are **Card**, **Stat**, **Stack**, **Grid**, and **GridSpan**:\n\n~~~svelte\nconst content = [\n '<Grid columns={2} gap=\"lg\">',\n '<GridSpan columns=\"full\">',\n '<Card title=\"Release readiness\" variant=\"outline\">',\n '<Stack gap=\"md\">Everything is **ready**.</Stack>',\n '</Card>',\n '</GridSpan>',\n '<Stat label=\"Coverage\" value=\"94%\" trend=\"+3.2%\" trendDirection=\"up\" />',\n '<Stat label=\"Issues\" value=\"3\" trend=\"-8\" trendDirection=\"down\" />',\n '</Grid>'\n].join('\\n');\n\n<Markdown {content} />\n~~~\n\nMDX attributes support string, number, and boolean values. Imperative event handlers and root styling or link props are not forwarded from Markdown content. Caller-provided mdxComponents take priority, and the custom mdx snippet remains the fallback for unknown tags.\n\n## Theming\n\nThe full theme is derived from entasis design tokens (translated from the shadcn preset): borders use \\`border-neutral-muted\\`, muted surfaces use \\`bg-neutral-muted\\`, secondary text uses \\`text-neutral/70\\`, and so on. Because it is token-based, the rendered Markdown adapts to light and dark automatically and stays visually consistent with the rest of the app. Per-instance tweaks to the root wrapper go through the \\`theme\\` prop; deeper element overrides fall back safely to the built-in theme.\n\n## Examples\n\n### Chat message (small)\n\\`\\`\\`svelte\n<Markdown content={assistantMessage} size=\"small\" />\n\\`\\`\\`\n\n### Long-form document (large)\n\\`\\`\\`svelte\n<Markdown content={articleBody} size=\"large\" />\n\\`\\`\\`\n\n### Streaming from an LLM\n\\`\\`\\`svelte\n<script>\n\tlet content = $state('');\n\t// append tokens as they arrive\n\tfor await (const chunk of stream) content += chunk;\n</script>\n<Markdown {content} size=\"small\" />\n\\`\\`\\`\n\n## Accessibility\n\n- Output is semantic HTML (real headings, lists, tables, \\`<a>\\`, \\`<code>\\`), so the document outline and screen-reader navigation work as expected.\n- Code blocks inherit the Code component's accessible copy button (aria-labelled, keyboard operable) and Mermaid inherits its \\`role=\"img\"\\` + aria-label and keyboard-accessible controls.\n- Color is never the only signal for GitHub alerts — each keeps its label/heading text.\n- Task-list checkboxes are disabled and have no text of their own, so each carries an accessible name from the catalog (`taskComplete` / `taskIncomplete`).\n\n## Notes\n\n- Renders reactively: it is safe to bind \\`content\\` to a string that grows over time (token streaming). Partial Markdown between chunks won't throw.\n- The theme deep-merges over svelte-streamdown's built-in base theme, so any element entasis doesn't explicitly restyle still renders sensibly.\n- Code and Mermaid rendering delegate to entasis's own components rather than streamdown's, keeping copy/pan/zoom behavior consistent across the app.\n";
|
|
126
|
-
readonly marquee: "\n# Marquee Component\n\nThe Marquee component creates an infinite scrolling animation for displaying content. It automatically duplicates the slot content to create a seamless infinite loop effect and supports customizable speed, direction, fade effects, and pause-on-hover functionality.\n\n## Basic Usage\n\n```svelte\n<script>\n\timport { Marquee } from 'entasis/marquee';\n\t\n\tconst items = [\n\t\t{ id: 1, text: 'Item 1' },\n\t\t{ id: 2, text: 'Item 2' },\n\t\t{ id: 3, text: 'Item 3' }\n\t];\n</script>\n\n<Marquee>\n\t{#each items as item}\n\t\t<div class=\"px-4 py-2 bg-surface rounded\">\n\t\t\t{item.text}\n\t\t</div>\n\t{/each}\n</Marquee>\n```\n\n## Props\n\n### Core Props\n- **children**: Snippet (required)\n - Content to display in the marquee\n - Parent component handles iteration over items\n - Content is automatically duplicated based on `numberOfCopies`\n\n- **direction**: 'left' | 'up' (default: 'left')\n - `'left'`: Horizontal scrolling from right to left\n - `'up'`: Vertical scrolling from bottom to top\n\n- **numberOfCopies**: number (default: 2)\n - Minimum number of times to duplicate the content for a seamless loop\n - The component measures the viewport and first copy, then adds copies when the content is too short to cover the viewport continuously\n - The effective count is bounded to 100 copies\n\n### Animation Props\n- **speed**: 'fast' | 'normal' | 'slow' | number (default: 'fast')\n - Animation speed control\n - `'fast'`: 20 seconds per cycle\n - `'normal'`: 40 seconds per cycle\n - `'slow'`: 80 seconds per cycle\n - `number`: Custom duration in seconds (e.g., `30` for 30 seconds)\n\n- **reverse**: boolean (default: false)\n - If true, animation runs in reverse direction\n - For 'left': scrolls left to right\n - For 'up': scrolls top to bottom\n\n- **pauseOnHover**: boolean (default: true)\n - If true, animation pauses when mouse hovers over the marquee\n - If false, animation continues regardless of hover state\n\n### Visual Props\n- **fade**: boolean (default: true)\n - If true, applies the shared scroll-fade utility at the marquee edges\n - Uses a static scroll-fade mode because the marquee is clipped animation, not a native scroll container\n\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n -
|
|
126
|
+
readonly marquee: "\n# Marquee Component\n\nThe Marquee component creates an infinite scrolling animation for displaying content. It automatically duplicates the slot content to create a seamless infinite loop effect and supports customizable speed, direction, fade effects, and pause-on-hover functionality.\n\n## Basic Usage\n\n```svelte\n<script>\n\timport { Marquee } from 'entasis/marquee';\n\t\n\tconst items = [\n\t\t{ id: 1, text: 'Item 1' },\n\t\t{ id: 2, text: 'Item 2' },\n\t\t{ id: 3, text: 'Item 3' }\n\t];\n</script>\n\n<Marquee>\n\t{#each items as item}\n\t\t<div class=\"px-4 py-2 bg-surface rounded\">\n\t\t\t{item.text}\n\t\t</div>\n\t{/each}\n</Marquee>\n```\n\n## Props\n\n### Core Props\n- **children**: Snippet (required)\n - Content to display in the marquee\n - Parent component handles iteration over items\n - Content is automatically duplicated based on `numberOfCopies`\n\n- **direction**: 'left' | 'up' (default: 'left')\n - `'left'`: Horizontal scrolling from right to left\n - `'up'`: Vertical scrolling from bottom to top\n\n- **numberOfCopies**: number (default: 2)\n - Minimum number of times to duplicate the content for a seamless loop\n - The component measures the viewport and first copy, then adds copies when the content is too short to cover the viewport continuously\n - The effective count is bounded to 100 copies\n\n### Animation Props\n- **speed**: 'fast' | 'normal' | 'slow' | number (default: 'fast')\n - Animation speed control\n - `'fast'`: 20 seconds per cycle\n - `'normal'`: 40 seconds per cycle\n - `'slow'`: 80 seconds per cycle\n - `number`: Custom duration in seconds (e.g., `30` for 30 seconds)\n\n- **reverse**: boolean (default: false)\n - If true, animation runs in reverse direction\n - For 'left': scrolls left to right\n - For 'up': scrolls top to bottom\n\n- **pauseOnHover**: boolean (default: true)\n - If true, animation pauses when mouse hovers over the marquee\n - If false, animation continues regardless of hover state\n\n### Visual Props\n- **fade**: boolean (default: true)\n - If true, applies the shared scroll-fade utility at the marquee edges\n - Uses a static scroll-fade mode because the marquee is clipped animation, not a native scroll container\n\n- **size**: 'small' | 'normal' | 'large' (default: 'normal')\n - Sets the marquee's one spacing value, `--gap`, from the theme spacing scale\n - `small`: `--space-md`\n - `normal`: `--space-xl`\n - `large`: `--layout-space-md`\n - The copies, the items inside each copy and the loop step all use `--gap`, so the loop stays\n seamless at every size and theme spacing. To respace a marquee, set `--gap` once on the root\n (`class=\"[--gap:var(--space-lg)]\"`); never redeclare it on the copies.\n\n### Styling Props\n- **ref**: HTMLDivElement | null (bindable)\n - Reference to the marquee container\n\n- **class**: string\n - Additional CSS classes for the marquee container\n\n- **innerClass**: string\n - Additional CSS classes for each duplicated copy of the content\n\n- **theme**: MarqueeThemeProps\n - Custom theme overrides for advanced styling\n\n## Examples\n\n### Basic Horizontal Marquee\n\n```svelte\n<script>\n\timport { Marquee } from 'entasis/marquee';\n\t\n\tconst testimonials = [\n\t\t{ author: 'John Doe', quote: 'Amazing product!' },\n\t\t{ author: 'Jane Smith', quote: 'Highly recommended!' },\n\t\t{ author: 'Bob Johnson', quote: 'Best purchase ever!' }\n\t];\n</script>\n\n<Marquee>\n\t{#each testimonials as item}\n\t\t<div class=\"px-8 py-4 bg-surface rounded-lg\">\n\t\t\t<p class=\"text-lg font-semibold\">\"{item.quote}\"</p>\n\t\t\t<p class=\"text-sm text-muted\">— {item.author}</p>\n\t\t</div>\n\t{/each}\n</Marquee>\n```\n\n### Logo Carousel\n\n```svelte\n<script>\n\timport { Marquee } from 'entasis/marquee';\n\t\n\tconst logos = [\n\t\t{ name: 'Company 1', logo: '/logo1.png' },\n\t\t{ name: 'Company 2', logo: '/logo2.png' },\n\t\t{ name: 'Company 3', logo: '/logo3.png' }\n\t];\n</script>\n\n<Marquee speed=\"normal\" size=\"large\">\n\t{#each logos as logo}\n\t\t<img src={logo.logo} alt={logo.name} class=\"h-12 w-auto\" />\n\t{/each}\n</Marquee>\n```\n\n### Vertical Marquee\n\n```svelte\n<Marquee direction=\"up\" class=\"h-[400px]\">\n\t{#each items as item}\n\t\t<div class=\"px-6 py-4 bg-surface rounded\">\n\t\t\t{item.content}\n\t\t</div>\n\t{/each}\n</Marquee>\n```\n\n### With Fade Effect\n\n```svelte\n<Marquee fade={true}>\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n```\n\n### Reverse Direction\n\n```svelte\n<Marquee reverse={true}>\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n```\n\n### Custom Speed\n\n```svelte\n<Marquee speed={30}>\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n```\n\n### Different Sizes\n\n```svelte\n<!-- Small -->\n<Marquee size=\"small\">\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n\n<!-- Normal (default) -->\n<Marquee size=\"normal\">\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n\n<!-- Large -->\n<Marquee size=\"large\">\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n```\n\n### Custom Number of Copies\n\n```svelte\n<!-- More copies for smoother appearance -->\n<Marquee numberOfCopies={4} speed={30}>\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n```\n\n### Disable Pause on Hover\n\n```svelte\n<Marquee pauseOnHover={false}>\n\t{#each items as item}\n\t\t<div>{item.content}</div>\n\t{/each}\n</Marquee>\n```\n\n### Combined Effects\n\n```svelte\n<Marquee \n\tfade={true}\n\treverse={true}\n\tspeed=\"slow\"\n\tsize=\"large\"\n>\n\t{#each items as item}\n\t\t<div class=\"px-8 py-6 bg-surface rounded-lg\">\n\t\t\t{item.content}\n\t\t</div>\n\t{/each}\n</Marquee>\n```\n\n## Structure\n\nThe Marquee component renders:\n- A container `<div>` with overflow hidden and optional scroll-fade utility classes\n- Multiple copies of the content (default 2) based on `numberOfCopies`\n- Each copy has the animation applied independently\n- Parent component iterates over items in the slot\n\n## Animation Details\n\n- Uses CSS keyframe animations for smooth GPU-accelerated performance\n- Content is duplicated `numberOfCopies` times to create seamless infinite scroll\n- Animation translates each copy by -100% of its width/height plus `--gap`, the same value the copies are spaced with\n- Animation duration is controlled via CSS custom property `--animation-duration`\n- Pause on hover uses CSS `animation-play-state` for instant response\n- Reverse mode uses CSS `direction: reverse` for the animation\n\n## Accessibility\n\n- Uses `role=\"presentation\"` on the container as the marquee is decorative\n- Consider providing alternative static content for screen readers\n- Ensure sufficient color foreground for text content\n- Pause on hover helps users interact with and read content\n- Animation is disabled when the user requests reduced motion; the still row then fades only its trailing edge\n\n## Notes\n\n- Parent component is responsible for iterating over items (similar to Carousel pattern)\n- Content is automatically duplicated internally based on `numberOfCopies`\n- The component uses CSS animations for optimal performance\n- Custom speed values are specified in seconds (e.g., `30` = 30 seconds per cycle)\n- For vertical marquees, ensure the container has a defined height\n- Increasing `numberOfCopies` creates smoother appearance but uses more DOM elements\n- The fade effect uses the shared scroll-fade utility classes\n\n## Theme Customization\n\nThe Marquee 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 marquee container styles\n- **inner**: Inner scrolling content container styles\n\n### Available Variants\n\n**root**:\n- base: Base classes for main container\n- Variants:\n - direction: 'left' | 'up' - Scroll direction\n - size: 'small' | 'normal' | 'large' - Gap between duplicated content\n - fade: boolean - Applies shared scroll-fade utility classes\n\n**inner**:\n- base: Base classes for scrolling content\n- Variants:\n - direction: 'left' | 'up' - Animation direction\n - size: 'small' | 'normal' | 'large' - Gap size\n - pauseOnHover: boolean - Pause animation on hover\n - reverse: boolean - Reverse animation direction\n\n### Usage Examples\n\n**Basic Theme Override**:\n```svelte\n<Marquee \n theme={{\n root: {\n base: 'group flex overflow-hidden relative',\n direction: {\n left: 'flex-row'\n },\n size: {\n normal: '[--gap:var(--space-lg)]'\n }\n }\n }}\n>\n {#each items as item}\n <div>{item.content}</div>\n {/each}\n</Marquee>\n```\n\n**Custom Animation Styling**:\n```svelte\n<Marquee \n theme={{\n inner: {\n base: 'flex shrink-0 justify-around whitespace-nowrap',\n direction: {\n left: 'flex-row animate-marquee-left'\n },\n pauseOnHover: {\n true: 'group-hover:[animation-play-state:paused]'\n }\n }\n }}\n>\n {#each items as item}\n <div>{item.content}</div>\n {/each}\n</Marquee>\n```\n\n**Global Theme Setting**:\n```svelte\n<script>\n import { setMarqueeTheme } from '../components/Marquee/index.ts';\n \n setMarqueeTheme({\n root: {\n base: 'group flex overflow-hidden relative',\n direction: {\n left: 'flex-row'\n },\n size: {\n normal: '[--gap:var(--space-lg)]'\n }\n },\n inner: {\n base: 'flex shrink-0 justify-around whitespace-nowrap',\n direction: {\n left: 'flex-row animate-marquee-left'\n },\n pauseOnHover: {\n true: 'group-hover:[animation-play-state:paused]'\n }\n }\n });\n</script>\n```\n";
|
|
127
127
|
readonly mermaid: "\n# Mermaid Component\n\nRenders a [Mermaid](https://mermaid.js.org/) diagram (flowchart, sequence, gantt, pie, class, state, ER, etc.) from a code string, with pan, zoom, fullscreen and download controls. The diagram is themed with our design tokens so it adapts to light and dark automatically. Ported from svelte-streamdown's Mermaid element, rebuilt on the entasis state/theme system.\n\n## Basic Usage\n\n```svelte\n<Mermaid chart={`graph TD; A[Start] --> B{Choice}; B -->|Yes| C[OK]; B -->|No| D[Stop];`} />\n<Mermaid chart={sequenceCode} size=\"large\" />\n```\n\n## Props\n\n### Core Props\n- **chart**: string (required) - The mermaid diagram source. Sanitized before rendering (BOM, zero-width/control chars, smart quotes, CRLF, HTML entities, flowchart arrow spacing, trailing semicolons). Re-renders reactively when it changes.\n- **config**: MermaidConfig - Extra mermaid config merged over the defaults (`theme: 'base'`, `securityLevel: 'strict'`, brand `themeVariables`, flowchart `curve: 'basis'`). Pass `themeVariables` to override individual mapped colors, or `fontFamily` to change the diagram font.\n- **size**: 'small' | 'normal' | 'large' (default: 'normal') - The height of the diagram viewport.\n\n### Interaction Props\n- **controls**: boolean | { fit?, zoomIn?, zoomOut?, expand?, download? } (default: true) - The floating controls to show. `true` shows all, `false` hides the bar, an object toggles individual controls.\n- **mouseWheelZoom**: boolean (default: true) - Enable mouse-wheel zoom over the diagram (activated after a short hover so page scroll isn't hijacked). Pan-drag and the zoom buttons work regardless.\n- **touchPan**: boolean (default: false) - Capture single-finger touch as a pan. Off inline so the diagram never traps page scroll; the fullscreen dialog turns it on. Pinch-zoom always works.\n- **errorForgiving**: boolean (default: false) - Swallow transient parse/render errors and keep the last valid diagram on screen instead of showing the error overlay. For token-streaming (LLM output), where the source is incomplete/invalid between chunks. Errors are still reported via `onError`.\n\n### Event Props\n- **onRender**: (state: MermaidState) => void - Called after each successful render.\n- **onError**: (error: Error) => void - Called when loading mermaid, parsing, or rendering fails.\n\n### Advanced Props\n- **class**: string - Additional CSS classes on the root element (the panzoom viewport).\n- **theme**: MermaidTheme - Theme overrides (root, container, svg, buttons, error, skeleton).\n\n## Interactions\n\n- **Pan**: drag the diagram.\n- **Zoom**: the +/− buttons, mouse-wheel (after a short hover), pinch, or double-click.\n- **Fit**: the fit-to-view button re-centers and scales the diagram to the viewport.\n- **Fullscreen**: the expand button opens the same diagram in a `fullScreen` Dialog (focus trap, Escape and the close button exit, single-finger touch pans inside it).\n- **Download**: serializes the rendered diagram to a standalone .svg file.\n\n## Brand theming\n\nMermaid is initialized with `theme: 'base'` and a `themeVariables` object whose values are read from our `--color-*` design tokens at render time (mermaid reads them as concrete colors, not live CSS vars). The diagram re-renders when the active theme changes, so it stays in sync with light/dark. Mapping: `primaryColor -> --color-primary-muted`, `primaryTextColor/textColor -> --color-neutral`, `primaryBorderColor/nodeBorder -> --color-primary`, `lineColor -> --color-neutral-muted`, `background -> --color-surface`, `mainBkg -> --color-surface-raised`, `secondaryColor -> --color-secondary`, `tertiaryColor -> --color-neutral-muted`, plus note/cluster/title colors and `fontFamily`.\n\n## Examples\n\n### Live editable diagram\n```svelte\n<script>\n\tlet chart = $state('graph LR; A-->B; B-->C;');\n</script>\n<textarea bind:value={chart}></textarea>\n<Mermaid {chart} />\n```\n\n### Only some controls\n```svelte\n<Mermaid {chart} controls={{ zoomIn: true, zoomOut: true, fit: true, download: false, expand: false }} />\n```\n\n## Accessibility\n\n- The controls are Buttons with aria-labels and are keyboard accessible; the download uses safe blob/data URLs.\n- The render host has `role=\"img\"` with an aria-label; the error surface uses `role=\"alert\"`.\n\n## Notes\n\n- **mermaid is not bundled**: it is loaded from cdnjs at runtime, on the client only (mermaid **11.12.0**, the self-contained UMD build). The component is SSR-safe and shows a skeleton while loading. A strict Content-Security-Policy must allow `cdnjs.cloudflare.com` in `script-src`.\n- On a load failure or a mermaid parse/render error, an error surface is shown (danger tokens) with the message — `securityLevel: 'strict'` + `suppressErrorRendering` keep mermaid from injecting its own error DOM.\n- mermaid leaves temporary measuring containers in the body; they are removed after each render and on destroy.\n";
|
|
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";
|