@alfakit/core 0.1.10 → 0.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/components.css +19 -0
  3. package/dist/actions.d.ts +6 -0
  4. package/dist/actions.d.ts.map +1 -1
  5. package/dist/actions.js +21 -2
  6. package/dist/actions.js.map +1 -1
  7. package/dist/advanced-fields.d.ts +6 -0
  8. package/dist/advanced-fields.d.ts.map +1 -1
  9. package/dist/advanced-fields.js +20 -1
  10. package/dist/advanced-fields.js.map +1 -1
  11. package/dist/chart.d.ts.map +1 -1
  12. package/dist/chart.js +23 -15
  13. package/dist/chart.js.map +1 -1
  14. package/dist/conflicts.d.ts.map +1 -1
  15. package/dist/conflicts.js +3 -5
  16. package/dist/conflicts.js.map +1 -1
  17. package/dist/index.d.ts +6 -6
  18. package/dist/index.js +6 -6
  19. package/dist/item-actions.d.ts +10 -0
  20. package/dist/item-actions.d.ts.map +1 -1
  21. package/dist/item-actions.js +8 -1
  22. package/dist/item-actions.js.map +1 -1
  23. package/dist/prefetch.d.ts.map +1 -1
  24. package/dist/prefetch.js +12 -3
  25. package/dist/prefetch.js.map +1 -1
  26. package/dist/presence.d.ts +0 -4
  27. package/dist/presence.d.ts.map +1 -1
  28. package/dist/presence.js +15 -2
  29. package/dist/presence.js.map +1 -1
  30. package/dist/realtime.d.ts +8 -0
  31. package/dist/realtime.d.ts.map +1 -1
  32. package/dist/realtime.js +13 -1
  33. package/dist/realtime.js.map +1 -1
  34. package/dist/schema/actions.d.ts +10 -0
  35. package/dist/schema/actions.d.ts.map +1 -1
  36. package/dist/schema/actions.js.map +1 -1
  37. package/dist/schema/components.d.ts +5 -0
  38. package/dist/schema/components.d.ts.map +1 -1
  39. package/dist/schema/components.js.map +1 -1
  40. package/dist/schema/form.d.ts +4 -0
  41. package/dist/schema/form.d.ts.map +1 -1
  42. package/dist/schema/form.js.map +1 -1
  43. package/dist/schema/panel.d.ts +2 -0
  44. package/dist/schema/panel.d.ts.map +1 -1
  45. package/dist/schema/panel.js.map +1 -1
  46. package/dist/schema/widgets.d.ts +5 -0
  47. package/dist/schema/widgets.d.ts.map +1 -1
  48. package/dist/standalone.d.ts +7 -0
  49. package/dist/standalone.d.ts.map +1 -1
  50. package/dist/standalone.js +42 -6
  51. package/dist/standalone.js.map +1 -1
  52. package/dist/starter-kit.d.ts.map +1 -1
  53. package/dist/starter-kit.js +17 -2
  54. package/dist/starter-kit.js.map +1 -1
  55. package/dist/theme.d.ts.map +1 -1
  56. package/dist/theme.js +2 -1
  57. package/dist/theme.js.map +1 -1
  58. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"panel.js","names":[],"sources":["../../src/schema/panel.ts"],"sourcesContent":["import type { ActionSchema, AnyActionSchema } from './actions'\nimport type { CommandPaletteSchema, KeyboardShortcutsSchema } from './command-palette'\nimport type { Color, IconName, RecordKey, Width } from './common'\nimport type { SchemaComponent } from './components'\nimport type { DebugSchema } from './debug'\nimport type { DevToolsSchema } from './dev-tools'\nimport type { FormBlock, InfolistBlock, LinksBlock, SchemaBlock } from './form'\nimport type { NotificationSchema } from './notifications'\nimport type { PwaSchema } from './pwa'\nimport type { ConflictsSchema, PresenceSchema, RealtimeSchema } from './realtime'\nimport type { DeferredTabBadges } from './resources'\nimport type { StandaloneComponentSchema } from './standalone'\nimport type { RelationManagerSchema, RelationManagersBlock, TableBlock } from './table'\nimport type { WidgetSchema, WidgetsBlock } from './widgets'\n\nexport interface NavigationItemSchema {\n /** Text, or sanitised HTML when `labelHtml` is true (an `Htmlable` label). */\n label: string\n /** `label` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n labelHtml?: boolean\n icon?: IconName\n /**\n * Split navigation (`alfakit.navigation` is an Inertia once prop): the icon of the item\n * while active, swapped in by `resolvePanelNavigation()`.\n */\n activeIcon?: IconName\n url?: string\n openInNewTab?: boolean\n active?: boolean\n badge?: string | number\n badgeColor?: Color\n badgeTooltip?: string\n /** `Panel::deferNavigationBadges()`: the item has a badge that loads after the page. */\n deferredBadge?: boolean\n /** Set by `resolvePanelNavigation()` while a deferred badge is loading (show a placeholder). */\n badgePending?: boolean\n /** `NavigationItem::extraAttributes()`: HTML attributes for the item's link (`class` merges). */\n extraAttributes?: Record<string, string | number | boolean | null>\n children?: NavigationItemSchema[]\n}\n\nexport interface NavigationGroupSchema {\n label?: string | null\n icon?: IconName\n collapsible: boolean\n collapsed?: boolean\n items: NavigationItemSchema[]\n /** `NavigationGroup::extraSidebarAttributes()`: HTML attributes for the group in the sidebar. */\n extraSidebarAttributes?: Record<string, string | number | boolean | null>\n /** `NavigationGroup::extraTopbarAttributes()`: HTML attributes for the group in the top navigation. */\n extraTopbarAttributes?: Record<string, string | number | boolean | null>\n}\n\n/** A navigation item's badge, from `navigationState` or the deferred `alfakitNavigationBadges` prop. */\nexport interface NavigationBadgeSchema {\n badge: string | number\n badgeColor?: Color\n badgeTooltip?: string\n}\n\n/**\n * The per-request part of the panel navigation, whose structure (`navigation`) is an\n * Inertia once prop. Items are addressed by position: `group.item[.child…]` (`0.2.1`).\n */\nexport interface NavigationStateSchema {\n /** The active items (an active child's ancestors included). */\n active?: string[]\n /** Badges, unless `Panel::deferNavigationBadges()` defers them. */\n badges?: Record<string, NavigationBadgeSchema>\n}\n\n/** One `starterKit()` panel's navigation, as shared with the host application's pages. */\n/** One `starterKit()` panel's dashboard widgets on a host page (`alfakitWidgets` prop). */\nexport interface StarterKitDashboardWidgetsSchema {\n /** The panel id. */\n id: string\n /** A standalone widgets payload bound to the panel (lazy loading and polling included). */\n component: StandaloneComponentSchema\n}\n\nexport interface StarterKitNavigationSchema {\n /** The panel id. */\n id: string\n /** The panel's home URL. */\n url: string\n brandName: string\n /** Empty for tenancy panels outside a tenant (their items link to tenant routes). */\n groups: NavigationGroupSchema[]\n /**\n * The panel's own user menu links (`userMenuItems()`, plugin entries such as Billing's),\n * for `<AlfakitUserMenuItems />` in the kit's user menu. Links only (entries with server\n * logic or a modal need an AlfaKit page); omitted when there are none, and for tenancy\n * panels outside a tenant.\n */\n userMenu?: MenuItemSchema[]\n /** What the drop-in header components render before they load. */\n widgets?: StarterKitWidgetsSchema\n}\n\n/**\n * The `widgets` of a `starterKit()` panel's shared navigation: what `<AlfakitSearch />`,\n * `<AlfakitNotifications />`, `<AlfakitTenantSwitcher />` and `<AlfakitLanguageSwitcher />`\n * show on any host page before their code and the panel data (`endpoint`) have loaded.\n */\nexport interface StarterKitWidgetsSchema {\n /** GET: the panel data the components render with (the `alfakit` prop of the panel's pages). */\n endpoint: string\n /** Global search or the command palette (tenancy panels: only inside a tenant). */\n search?: { keyBindings: string[]; placeholder: string }\n /** `databaseNotifications()`: the bell's label and the user's unread count. */\n notifications?: { label: string; unreadCount: number }\n /** Tenancy panels: the user's tenants; `current` is null on the host's own pages. */\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`). */\n registrationLabel?: string\n searchable?: true\n }\n /**\n * `<AlfakitLanguageSwitcher />`: the button's label (\"Change language\") and the current\n * language, when the panel has a language switcher with two or more languages\n * (`locales()`). The languages and the locale endpoint come with the panel data.\n */\n language?: { label: string; current: LocaleSchema }\n /** While impersonating: the banner the header components show on the host's own pages. */\n impersonation?: ImpersonationSchema\n /**\n * `AiPlugin::copilot()` (alfasix/alfakit-ai, `$panel->starterKitWidget('copilot')`): the\n * label and shortcuts of `<AlfakitCopilot />` before its code loads. Tenancy panels: only\n * inside a tenant.\n */\n copilot?: { label: string; keyBindings: string[] }\n}\n\n/** A page's secondary navigation: cluster pages or a record's pages. */\nexport interface SubNavigationSchema {\n position: 'start' | 'end' | 'top'\n groups: NavigationGroupSchema[]\n}\n\n/**\n * An entry of the user or tenant menu: a link (`url`, `method: 'post'` for POST links) or\n * an `Action` with server logic or a modal (`action`), run through the action endpoint\n * with the component `userMenu` / `tenantMenu` and the page's token.\n */\nexport interface MenuItemSchema {\n label?: string\n icon?: IconName\n url?: string\n openInNewTab?: boolean\n color?: Color\n method?: 'post'\n action?: ActionSchema\n /** Group index (nested arrays in `userMenuItems()`); a separator divides groups. */\n group?: number\n}\n\n/** Action endpoint components of the menus (`Panel::USER_MENU_COMPONENT`, …). */\nexport const USER_MENU_COMPONENT = 'userMenu'\nexport const TENANT_MENU_COMPONENT = 'tenantMenu'\n\nexport interface PanelUserSchema {\n id: RecordKey\n name: string\n email?: string | null\n avatar?: string | null\n}\n\nexport interface TenantSchema {\n id: RecordKey\n /** Text, or sanitised HTML when `nameHtml` is true (an `Htmlable` `getAlfakitName()`). */\n name: string\n /** `name` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n nameHtml?: boolean\n avatar?: string | null\n url: string\n /** `HasCurrentTenantLabel`: a caption shown with the current tenant (\"Active team\"). */\n label?: string | null\n}\n\n/** The panel's design, see `Panel::theme()`. */\nexport interface PanelThemeSchema {\n name: string\n /** Value of `data-alfakit-theme`, base theme first: `\"alfakit acme\"`. */\n attribute: string\n sidebar: 'inset' | 'floating' | 'flush'\n density: 'comfortable' | 'compact'\n radius?: string | null\n font?: string | null\n /** CSS value for `--alfakit-font`. */\n fontCss?: string | null\n /** CSS value for `--alfakit-heading-font`. */\n headingFontCss?: string | null\n /** Stylesheet that loads the font, if any. */\n fontUrl?: string | null\n /** Every font stylesheet to load (body and heading fonts). */\n fontUrls?: string[]\n /** Structure and component styles; mirrored as `data-*` attributes on `<html>`. */\n design: PanelDesignSchema\n /**\n * Icon aliases replaced with `Icons::register()` (`alfakit::panels.sidebar.collapse-button`\n * → a Lucide name, image URL or sanitised SVG). The chrome falls back to its own icon.\n */\n icons?: Record<string, string> | null\n}\n\nexport interface PanelDesignSchema {\n layout: 'sidebar' | 'topbar' | 'rail'\n nav: 'soft' | 'pill' | 'bar' | 'underline'\n header: 'plain' | 'banner' | 'band' | 'compact'\n surfaces: 'bordered' | 'elevated' | 'flat' | 'ring'\n inputs: 'outline' | 'filled' | 'underline'\n buttons: 'rounded' | 'pill' | 'square'\n tables: 'lined' | 'striped' | 'minimal'\n /**\n * Below the `md` breakpoint: `drawer` (default) or the `bottom` mobile preset (bottom\n * navigation bar, tables as stacked cards, slide-over modals, 44px touch targets and\n * sticky form actions). See `Panel::mobile()`.\n */\n mobile?: 'drawer' | 'bottom'\n}\n\n/**\n * One piece of slot content: server-rendered HTML or a schema component. `for` lists the\n * components (`relationManager:<class>`, `widget:<id>`) it is scoped to: such items only\n * render in a `<Slot scope=\"…\">` of a matching component. `when` (`Panel::slot(…, when:)`)\n * limits it to renders whose slot data matches: `{ column: 'title' }` only renders in the\n * `table.header-cell` slot of the `title` column; a list value matches any of its entries.\n */\nexport type SlotItemSchema = (\n { type: 'html'; html: string } | { type: 'component'; component: SchemaComponent }\n) & { for?: string[]; when?: Record<string, SlotDataValue | SlotDataValue[]> }\n\n/** A value in a slot's render data (`<Slot data={{ column: 'title' }}>`). */\nexport type SlotDataValue = string | number | boolean | null\n\n/** A language the panel can be displayed in. */\nexport interface LocaleSchema {\n /** Laravel-style code: `en`, `pt_BR`, `zh_Hant`. */\n code: string\n /** BCP 47 tag for `Intl` and the `lang` attribute: `pt-BR`. */\n tag: string\n /** The language's own name: `Deutsch`, `العربية`. */\n label: string\n direction: 'ltr' | 'rtl'\n}\n\n/** The current request's language, formats and the panel's language list. */\nexport interface LocalizationSchema extends LocaleSchema {\n fallbackLocale: string\n /** 0 = Sunday … 6 = Saturday. */\n firstDayOfWeek: number\n /** Default ISO 4217 currency. */\n currency: string\n /** IANA timezone dates are displayed in. */\n timezone: string\n /** Whether to show a language menu. */\n switcher: boolean\n /**\n * Where panel pages offer the menu (`Panel::languageSwitcher()`): the user menu (default\n * when absent), a top bar button, both, or neither. Auth pages keep their switcher.\n */\n switcherPosition?: LanguageSwitcherPosition\n /** Languages to offer in the menu (empty when the switcher is off). */\n locales: LocaleSchema[]\n}\n\nexport type LanguageSwitcherPosition = 'user-menu' | 'topbar' | 'both' | 'hidden'\n\nexport interface PanelEndpoints {\n action: string\n modal: string\n schemaRefresh: string\n schemaOptions: string\n /** Validates one wizard step's fields (`step` = the Step component's key). */\n schemaValidate?: string\n /** Loads `deferLoading()` children and `Tab::deferBadge()` badges (`{schema}::{key}`). */\n schemaDeferred?: string\n tableCell: string\n tableReorder: string\n /** Stores a user's column order (`reorderableColumns()`). */\n tableColumns?: string\n /** `Table::deferTotals()`: the record count and summaries, loaded after the rows. */\n tableTotals?: string\n /** `Table::savedViews()`: save, share, rename, delete and pick a default view. */\n tableViews?: string\n /** `Table::spreadsheet()`: batch save (or validate) grid edits. */\n tableCells?: string\n /** Fresh schemas of lazy and polled components by token (components outside panels). */\n components?: string\n locale: string\n /** Calls `#[Expose]` methods of custom pages (`usePageMethod()`). */\n pageCall?: string\n /** Edit pages: what changed since the form was loaded (`detectConflicts()`). */\n conflicts?: string\n upload: string\n uploadDestroy: string\n /** Starts a resumable chunked upload (`{uploadChunks}/{id}` takes chunks, `/complete` finishes). */\n uploadChunks?: string\n globalSearch: string\n /** Command palette: \"create\" links and recent records (GET). */\n commandPalette?: string\n /** Stores the user's customised keyboard shortcuts (POST `{shortcuts}`). */\n shortcuts?: string\n /** `Action::undoable()`: undoes an action from its toast (POST `{undo}`). */\n undo?: string\n /** `Dashboard::customizable()`: saves or resets the user's layout (POST `{token, layout}`). */\n dashboardLayout?: string\n notifications: string\n notificationsReadAll: string\n notificationsDestroyAll: string\n logout: string\n}\n\n/** Where toasts appear (`Panel::toastPosition()`); `start`/`end` follow the reading direction. */\nexport type ToastPosition =\n 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end'\n\n/** Shown while impersonating another user (`ImpersonateAction`). */\nexport interface ImpersonationSchema {\n /** \"You are signed in as …\", translated on the server. */\n label: string\n leaveLabel: string\n /** POST here to return to the real user. */\n leaveUrl: string\n}\n\n/** The `alfakit` prop shared with every page of a panel. */\n/**\n * One `errorNotifications` entry: the text to show, or `hidden` (caught, nothing shown,\n * `hiddenErrorNotification()`) or `disabled` (default handling, `disabledErrorNotification()`).\n */\nexport interface ErrorNotificationEntrySchema {\n title?: string\n body?: string | null\n icon?: IconName\n hidden?: boolean\n disabled?: boolean\n}\n\n/** `errorNotifications`, keyed by HTTP status plus `default`. */\nexport type ErrorNotificationsSchema = Record<string, ErrorNotificationEntrySchema>\n\nexport interface PanelSchema {\n id: string\n /**\n * The schema contract version the server serialised (`SchemaContract::VERSION`). Renderers\n * compare its major part with `SCHEMA_CONTRACT_VERSION`; absent on servers before 1.0.\n */\n schemaContract?: string\n path: string\n url?: string\n /** Where \"G then D\" goes when it isn't `url`: the app's `/dashboard` for a starter kit panel without a dashboard page. */\n dashboardUrl?: string\n brandName: string\n brandLogo?: string\n darkModeBrandLogo?: string\n brandLogoHeight?: string\n favicon?: string\n /**\n * Colour custom properties without the `--alfakit-` prefix: `primary`, its readable\n * `primary-foreground` and, for palettes (`Color::Indigo`), `primary-50` … `primary-950`.\n */\n colors?: Record<string, string>\n /**\n * CSS for custom colour names (`colors(['brand' => …])`): `.alfakit-color-{name}` rules\n * that point the generic colour utilities of themes.css at `--alfakit-{name}`.\n */\n colorCss?: string\n darkMode: {\n enabled: boolean\n forced: boolean\n /** `defaultThemeMode()`; absent for `system`. */\n default?: 'light' | 'dark'\n /** `false` when `themeSwitcher(false)`: no switcher, always the default mode. */\n switcher?: false\n }\n /** `false` when `topbar(false)`: sidebar layouts render no top bar (a slim bar on phones). */\n topbar?: false\n sidebarCollapsibleOnDesktop: boolean\n sidebarFullyCollapsibleOnDesktop?: boolean\n topNavigation?: boolean\n maxContentWidth?: Width | string\n breadcrumbs: boolean\n unsavedChangesAlerts?: boolean\n /** `false` when `collapsibleNavigationGroups(false)`: groups never collapse. */\n collapsibleNavigationGroups?: false\n /** CSS lengths for the open and icon-only sidebar (`--sidebar-width`, `--sidebar-width-icon`). */\n sidebarWidth?: string\n collapsedSidebarWidth?: string\n /** Max width of `auth` layout pages (login, registration…). */\n simplePageMaxContentWidth?: Width | string\n /** `false` when `spa(false)`: every link loads a full page. */\n spa?: false\n /** URL patterns (`*` wildcard) that load with a full page request. */\n spaUrlExceptions?: string[]\n /**\n * Notifications shown instead of Inertia's error modal, keyed by HTTP status plus\n * `default`. Absent when disabled or in debug mode. A page's own `errorNotifications`\n * replaces it while that page is open.\n */\n errorNotifications?: ErrorNotificationsSchema\n theme: PanelThemeSchema\n /** Content inserted with `Panel::slot()`, keyed by slot name. */\n slots?: Record<string, SlotItemSchema[]>\n /**\n * Empty for `starterKit()` panels: their navigation is the `alfakitNavigation` page prop,\n * which every page of the host application carries (not only the panel's).\n */\n navigation: NavigationGroupSchema[]\n /**\n * The active items and badges of this request; `navigation` holds only the structure\n * (sent once). Read the navigation through `usePanel()`, which merges the two.\n */\n navigationState?: NavigationStateSchema\n /**\n * `Panel::starterKit()`: the panel runs inside a Laravel starter kit app. Its pages render\n * in the host's own layout (sidebar, header, appearance) and the host's shadcn tokens;\n * the panel's own chrome, theme attributes and colour scheme are not applied.\n */\n starterKit?: true\n user?: PanelUserSchema\n userMenu?: {\n items: MenuItemSchema[]\n profileUrl?: string\n logoutUrl: string\n /** The `profile` entry as customised in `userMenuItems()` (replaces the built-in link). */\n profile?: MenuItemSchema\n /** The customised `logout` entry, or `false` when it is hidden. */\n logout?: MenuItemSchema | false\n /**\n * `topbar` when `userMenu(position: UserMenuPosition::Topbar)`: sidebar layouts show the\n * menu in the top bar instead of the sidebar footer. Omitted for the sidebar.\n */\n position?: 'topbar'\n }\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n profileUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`); else \"New\". */\n registrationLabel?: string\n /** The profile page's own label (`EditTenantProfile::getLabel()`); else \"Settings\". */\n profileLabel?: string\n /** The customised `register` entry of `tenantMenuItems()`. */\n registration?: MenuItemSchema\n /** The customised `profile` entry of `tenantMenuItems()`. */\n profile?: MenuItemSchema\n menu?: MenuItemSchema[]\n /** `false` when `tenantSwitcher(false)`: `tenants` is empty and the menu lists no others. */\n switcher?: false\n /** `searchableTenantMenu()`, or more than 10 tenants: show a search field in the menu. */\n searchable?: boolean\n }\n databaseNotifications?: {\n pollingInterval?: string\n unreadCount: number\n /**\n * `sidebar` when `databaseNotifications(position: 'sidebar')` or `topbar(false)`: sidebar\n * layouts show the bell in the sidebar instead of the top bar. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n globalSearch?: {\n keyBindings: string[]\n placeholder: string\n /** `globalSearchDebounce()` in milliseconds; renderers default to 250. */\n debounce?: number\n /** `globalSearchFieldSuffix()`: text shown at the end of the field instead of the key hint. */\n suffix?: string\n /** `false` when `globalSearchFieldKeyBindingSuffix(false)`: no key binding hint. */\n keyBindingSuffix?: false\n /**\n * `sidebar` when `globalSearch(position: GlobalSearchPosition::Sidebar)` or\n * `topbar(false)`: sidebar layouts show the field in the sidebar header. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n endpoints: Partial<PanelEndpoints> & { logout: string }\n /** `alfakit.uploads.chunk_size` in bytes: larger files are uploaded in chunks. */\n uploadChunkSize?: number\n auth: { login?: string; register?: string; passwordReset?: string }\n flashKey: string\n /** Present while impersonating: render a banner with a way back. */\n impersonation?: ImpersonationSchema\n /**\n * `Panel::realtimeNotifications()`: the private channel to listen on with Laravel Echo;\n * with `Panel::realtime()` also the connection (`live`, `authEndpoint`, `echo`).\n */\n realtime?: RealtimeSchema\n /** Omitted for the default (`top-end`). */\n toastPosition?: ToastPosition\n /** The command palette's extras: page actions, panel commands, navigation, create links, recent records. */\n commandPalette?: CommandPaletteSchema\n /** Keyboard shortcuts (`false` with `keyboardShortcuts(false)`); absent for guests. */\n keyboardShortcuts?: KeyboardShortcutsSchema | false\n /** `Panel::pwa()`: service worker, install prompt and Web Push; absent when off. */\n pwa?: PwaSchema\n localization: LocalizationSchema\n csrf: string\n translations: Record<string, unknown>\n}\n\nexport interface BreadcrumbSchema {\n label: string\n url?: string\n}\n\nexport type ContentBlock =\n | FormBlock\n | InfolistBlock\n | SchemaBlock\n | TableBlock\n | RelationManagersBlock\n | WidgetsBlock\n | LinksBlock\n | { type: string; key: string; [key: string]: unknown }\n\nexport type PageLayout = 'app' | 'auth' | 'blank' | 'fullscreen'\n\n/** The `page` prop rendered by `Alfakit/Page`. */\nexport interface PageSchema {\n class: string\n layout: PageLayout\n title: string\n heading?: string\n subheading?: string\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n /** `fullscreen` layout: whether to show the slim header, and where its exit link goes. */\n fullscreen?: { header: boolean; exitUrl?: string }\n /** Cluster or record sub-navigation. */\n subNavigation?: SubNavigationSchema\n headerActions?: AnyActionSchema[]\n /** `$headerActionsAlignment`; absent for `end` (the default). */\n headerActionsAlignment?: 'start' | 'center' | 'end' | 'between' | 'justify'\n /** `$extraBodyAttributes` / `getExtraBodyAttributes()`: set on `<body>` while the page is open. */\n extraBodyAttributes?: Record<string, string>\n content: ContentBlock[]\n /** Encrypted context token for internal endpoints. */\n token: string\n record?: RecordKey\n view?: { component: string; props: Record<string, unknown> }\n /**\n * An action whose modal the renderer opens once the page hydrated (`$defaultAction`,\n * `?defaultAction=`, `?tableAction=&tableActionRecord=`).\n */\n defaultAction?: DefaultActionSchema\n /**\n * Page override of the panel's `errorNotifications` (`$hasErrorNotifications`,\n * `setUpErrorNotifications()`): `false` turns them off on this page. Absent when the\n * page follows the panel.\n */\n errorNotifications?: ErrorNotificationsSchema | false\n /** Record pages with `Panel::realtime()`: who else is on the record. */\n presence?: PresenceSchema\n /** Edit pages with `detectConflicts()`: the version the form was loaded from. */\n conflicts?: ConflictsSchema\n}\n\n/** `PageSchema::defaultAction`: what to open and in which context. */\nexport interface DefaultActionSchema {\n action: ActionSchema\n /** Context component: `page`, `table`, `relationManager:…`. */\n component: string\n record?: RecordKey\n arguments?: Record<string, unknown>\n}\n\n/**\n * Fresh schemas returned by a poll, keyed by poll target: `table`, `relationManager:Class`\n * or `widget:<id or class>`.\n */\nexport type PolledComponents = Record<string, ContentBlock | WidgetSchema | RelationManagerSchema>\n\n/** Shape of `usePage().props` on an Alfakit page. */\nexport interface AlfakitPageProps {\n alfakit: PanelSchema\n page?: PageSchema\n /** Components refreshed by a poll (only present after one); merged into `page`. */\n alfakitPoll?: PolledComponents\n /**\n * Lazy widgets and relation managers (`Page::LAZY_PROP`), an Inertia deferred prop that\n * arrives right after the first response; `resolveLazyComponents()` puts them in `page`.\n */\n alfakitLazy?: PolledComponents\n /** Badges of `Tab::deferBadge()` list tabs, a deferred prop loaded after the page. */\n alfakitTabBadges?: DeferredTabBadges\n /**\n * `Panel::deferNavigationBadges()`: the navigation badges keyed by item id\n * (`navigationState`), a deferred prop loaded after the page.\n */\n alfakitNavigationBadges?: Record<string, NavigationBadgeSchema>\n alfakitAppearance?: 'light' | 'dark' | 'system'\n /** The stored sidebar state (`alfakit_sidebar_state` cookie); null when never toggled. */\n alfakitSidebarOpen?: boolean | null\n /**\n * The navigation of the application's `starterKit()` panels, shared with every Inertia\n * page (the host's own pages too) for the host sidebar. Null for guests.\n */\n alfakitNavigation?: StarterKitNavigationSchema[] | null\n /**\n * `starterKit()` panels: their dashboard widgets on the host's dashboard\n * (`starterKitDashboard()`), for `<AlfakitWidgets />`. Null on other pages, for guests\n * and users who may not open the panel.\n */\n alfakitWidgets?: StarterKitDashboardWidgetsSchema[] | null\n /**\n * The developer debug panel's data (`Page::DEBUG_PROP`): schema tree with visibility and\n * authorisation, evaluated closures and server timings. Only present while the panel's\n * debug panel is active; never in production.\n */\n alfakitDebug?: DebugSchema\n /**\n * The schema inspector's data (context identity, class links). Only present while the\n * panel's dev tools are on (`Panel::devTools()` with `app.debug`); never in production.\n */\n alfakitDevTools?: DevToolsSchema\n flash?: {\n alfakitNotifications?: NotificationSchema[]\n alfakitAction?: {\n name: string\n status: 'success' | 'halted' | 'cancelled'\n closeModal?: boolean\n deselectRecords?: boolean | null\n }\n [key: string]: unknown\n }\n errors?: Record<string, string>\n [key: string]: unknown\n}\n\nexport type { ActionSchema }\n"],"mappings":";;AAgKA,MAAa,sBAAsB;AACnC,MAAa,wBAAwB"}
1
+ {"version":3,"file":"panel.js","names":[],"sources":["../../src/schema/panel.ts"],"sourcesContent":["import type { ActionSchema, AnyActionSchema } from './actions'\nimport type { CommandPaletteSchema, KeyboardShortcutsSchema } from './command-palette'\nimport type { Color, IconName, RecordKey, Width } from './common'\nimport type { SchemaComponent } from './components'\nimport type { DebugSchema } from './debug'\nimport type { DevToolsSchema } from './dev-tools'\nimport type { FormBlock, InfolistBlock, LinksBlock, SchemaBlock } from './form'\nimport type { NotificationSchema } from './notifications'\nimport type { PwaSchema } from './pwa'\nimport type { ConflictsSchema, PresenceSchema, RealtimeSchema } from './realtime'\nimport type { DeferredTabBadges } from './resources'\nimport type { StandaloneComponentSchema } from './standalone'\nimport type { RelationManagerSchema, RelationManagersBlock, TableBlock } from './table'\nimport type { WidgetSchema, WidgetsBlock } from './widgets'\n\nexport interface NavigationItemSchema {\n /** Text, or sanitised HTML when `labelHtml` is true (an `Htmlable` label). */\n label: string\n /** `label` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n labelHtml?: boolean\n icon?: IconName\n /**\n * Split navigation (`alfakit.navigation` is an Inertia once prop): the icon of the item\n * while active, swapped in by `resolvePanelNavigation()`.\n */\n activeIcon?: IconName\n url?: string\n openInNewTab?: boolean\n active?: boolean\n badge?: string | number\n badgeColor?: Color\n badgeTooltip?: string\n /** `Panel::deferNavigationBadges()`: the item has a badge that loads after the page. */\n deferredBadge?: boolean\n /** Set by `resolvePanelNavigation()` while a deferred badge is loading (show a placeholder). */\n badgePending?: boolean\n /** `NavigationItem::extraAttributes()`: HTML attributes for the item's link (`class` merges). */\n extraAttributes?: Record<string, string | number | boolean | null>\n children?: NavigationItemSchema[]\n}\n\nexport interface NavigationGroupSchema {\n label?: string | null\n icon?: IconName\n collapsible: boolean\n collapsed?: boolean\n items: NavigationItemSchema[]\n /** `NavigationGroup::extraSidebarAttributes()`: HTML attributes for the group in the sidebar. */\n extraSidebarAttributes?: Record<string, string | number | boolean | null>\n /** `NavigationGroup::extraTopbarAttributes()`: HTML attributes for the group in the top navigation. */\n extraTopbarAttributes?: Record<string, string | number | boolean | null>\n}\n\n/** A navigation item's badge, from `navigationState` or the deferred `alfakitNavigationBadges` prop. */\nexport interface NavigationBadgeSchema {\n badge: string | number\n badgeColor?: Color\n badgeTooltip?: string\n}\n\n/**\n * The per-request part of the panel navigation, whose structure (`navigation`) is an\n * Inertia once prop. Items are addressed by position: `group.item[.child…]` (`0.2.1`).\n */\nexport interface NavigationStateSchema {\n /** The active items (an active child's ancestors included). */\n active?: string[]\n /** Badges, unless `Panel::deferNavigationBadges()` defers them. */\n badges?: Record<string, NavigationBadgeSchema>\n}\n\n/** One `starterKit()` panel's navigation, as shared with the host application's pages. */\n/** One `starterKit()` panel's dashboard widgets on a host page (`alfakitWidgets` prop). */\nexport interface StarterKitDashboardWidgetsSchema {\n /** The panel id. */\n id: string\n /** A standalone widgets payload bound to the panel (lazy loading and polling included). */\n component: StandaloneComponentSchema\n}\n\nexport interface StarterKitNavigationSchema {\n /** The panel id. */\n id: string\n /** The panel's home URL. */\n url: string\n brandName: string\n /** Empty for tenancy panels outside a tenant (their items link to tenant routes). */\n groups: NavigationGroupSchema[]\n /**\n * The panel's own user menu links (`userMenuItems()`, plugin entries such as Billing's),\n * for `<AlfakitUserMenuItems />` in the kit's user menu. Links only (entries with server\n * logic or a modal need an AlfaKit page); omitted when there are none, and for tenancy\n * panels outside a tenant.\n */\n userMenu?: MenuItemSchema[]\n /** What the drop-in header components render before they load. */\n widgets?: StarterKitWidgetsSchema\n}\n\n/**\n * The `widgets` of a `starterKit()` panel's shared navigation: what `<AlfakitSearch />`,\n * `<AlfakitNotifications />`, `<AlfakitTenantSwitcher />` and `<AlfakitLanguageSwitcher />`\n * show on any host page before their code and the panel data (`endpoint`) have loaded.\n */\nexport interface StarterKitWidgetsSchema {\n /** GET: the panel data the components render with (the `alfakit` prop of the panel's pages). */\n endpoint: string\n /** Global search or the command palette (tenancy panels: only inside a tenant). */\n search?: { keyBindings: string[]; placeholder: string }\n /** `databaseNotifications()`: the bell's label and the user's unread count. */\n notifications?: { label: string; unreadCount: number }\n /** Tenancy panels: the user's tenants; `current` is null on the host's own pages. */\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`). */\n registrationLabel?: string\n searchable?: true\n }\n /**\n * `<AlfakitLanguageSwitcher />`: the button's label (\"Change language\") and the current\n * language, when the panel has a language switcher with two or more languages\n * (`locales()`). The languages and the locale endpoint come with the panel data.\n */\n language?: { label: string; current: LocaleSchema }\n /** While impersonating: the banner the header components show on the host's own pages. */\n impersonation?: ImpersonationSchema\n /**\n * `AiPlugin::copilot()` (alfasix/alfakit-ai, `$panel->starterKitWidget('copilot')`): the\n * label and shortcuts of `<AlfakitCopilot />` before its code loads. Tenancy panels: only\n * inside a tenant.\n */\n copilot?: { label: string; keyBindings: string[] }\n}\n\n/** A page's secondary navigation: cluster pages or a record's pages. */\nexport interface SubNavigationSchema {\n position: 'start' | 'end' | 'top'\n groups: NavigationGroupSchema[]\n}\n\n/**\n * An entry of the user or tenant menu: a link (`url`, `method: 'post'` for POST links) or\n * an `Action` with server logic or a modal (`action`), run through the action endpoint\n * with the component `userMenu` / `tenantMenu` and the page's token.\n */\nexport interface MenuItemSchema {\n label?: string\n icon?: IconName\n url?: string\n openInNewTab?: boolean\n color?: Color\n method?: 'post'\n action?: ActionSchema\n /** Group index (nested arrays in `userMenuItems()`); a separator divides groups. */\n group?: number\n}\n\n/** Action endpoint components of the menus (`Panel::USER_MENU_COMPONENT`, …). */\nexport const USER_MENU_COMPONENT = 'userMenu'\nexport const TENANT_MENU_COMPONENT = 'tenantMenu'\n\nexport interface PanelUserSchema {\n id: RecordKey\n name: string\n email?: string | null\n avatar?: string | null\n}\n\nexport interface TenantSchema {\n id: RecordKey\n /** Text, or sanitised HTML when `nameHtml` is true (an `Htmlable` `getAlfakitName()`). */\n name: string\n /** `name` is sanitised HTML: render it as markup (use `htmlLabelToText()` for text). */\n nameHtml?: boolean\n avatar?: string | null\n url: string\n /** `HasCurrentTenantLabel`: a caption shown with the current tenant (\"Active team\"). */\n label?: string | null\n}\n\n/** The panel's design, see `Panel::theme()`. */\nexport interface PanelThemeSchema {\n name: string\n /** Value of `data-alfakit-theme`, base theme first: `\"alfakit acme\"`. */\n attribute: string\n sidebar: 'inset' | 'floating' | 'flush'\n density: 'comfortable' | 'compact'\n radius?: string | null\n font?: string | null\n /** CSS value for `--alfakit-font`. */\n fontCss?: string | null\n /** CSS value for `--alfakit-heading-font`. */\n headingFontCss?: string | null\n /** Stylesheet that loads the font, if any. */\n fontUrl?: string | null\n /** Every font stylesheet to load (body and heading fonts). */\n fontUrls?: string[]\n /** Structure and component styles; mirrored as `data-*` attributes on `<html>`. */\n design: PanelDesignSchema\n /**\n * Icon aliases replaced with `Icons::register()` (`alfakit::panels.sidebar.collapse-button`\n * → a Lucide name, image URL or sanitised SVG). The chrome falls back to its own icon.\n */\n icons?: Record<string, string> | null\n}\n\nexport interface PanelDesignSchema {\n layout: 'sidebar' | 'topbar' | 'rail'\n nav: 'soft' | 'pill' | 'bar' | 'underline'\n header: 'plain' | 'banner' | 'band' | 'compact'\n surfaces: 'bordered' | 'elevated' | 'flat' | 'ring'\n inputs: 'outline' | 'filled' | 'underline'\n buttons: 'rounded' | 'pill' | 'square'\n tables: 'lined' | 'striped' | 'minimal'\n /**\n * Below the `md` breakpoint: `drawer` (default) or the `bottom` mobile preset (bottom\n * navigation bar, tables as stacked cards, slide-over modals, 44px touch targets and\n * sticky form actions). See `Panel::mobile()`.\n */\n mobile?: 'drawer' | 'bottom'\n}\n\n/**\n * One piece of slot content: server-rendered HTML or a schema component. `for` lists the\n * components (`relationManager:<class>`, `widget:<id>`) it is scoped to: such items only\n * render in a `<Slot scope=\"…\">` of a matching component. `when` (`Panel::slot(…, when:)`)\n * limits it to renders whose slot data matches: `{ column: 'title' }` only renders in the\n * `table.header-cell` slot of the `title` column; a list value matches any of its entries.\n */\nexport type SlotItemSchema = (\n { type: 'html'; html: string } | { type: 'component'; component: SchemaComponent }\n) & { for?: string[]; when?: Record<string, SlotDataValue | SlotDataValue[]> }\n\n/** A value in a slot's render data (`<Slot data={{ column: 'title' }}>`). */\nexport type SlotDataValue = string | number | boolean | null\n\n/** A language the panel can be displayed in. */\nexport interface LocaleSchema {\n /** Laravel-style code: `en`, `pt_BR`, `zh_Hant`. */\n code: string\n /** BCP 47 tag for `Intl` and the `lang` attribute: `pt-BR`. */\n tag: string\n /** The language's own name: `Deutsch`, `العربية`. */\n label: string\n direction: 'ltr' | 'rtl'\n}\n\n/** The current request's language, formats and the panel's language list. */\nexport interface LocalizationSchema extends LocaleSchema {\n fallbackLocale: string\n /** 0 = Sunday … 6 = Saturday. */\n firstDayOfWeek: number\n /** Default ISO 4217 currency. */\n currency: string\n /** IANA timezone dates are displayed in. */\n timezone: string\n /** Whether to show a language menu. */\n switcher: boolean\n /**\n * Where panel pages offer the menu (`Panel::languageSwitcher()`): the user menu (default\n * when absent), a top bar button, both, or neither. Auth pages keep their switcher.\n */\n switcherPosition?: LanguageSwitcherPosition\n /** Languages to offer in the menu (empty when the switcher is off). */\n locales: LocaleSchema[]\n}\n\nexport type LanguageSwitcherPosition = 'user-menu' | 'topbar' | 'both' | 'hidden'\n\nexport interface PanelEndpoints {\n action: string\n modal: string\n schemaRefresh: string\n schemaOptions: string\n /** Validates one wizard step's fields (`step` = the Step component's key). */\n schemaValidate?: string\n /** Loads `deferLoading()` children and `Tab::deferBadge()` badges (`{schema}::{key}`). */\n schemaDeferred?: string\n tableCell: string\n tableReorder: string\n /** Stores a user's column order (`reorderableColumns()`). */\n tableColumns?: string\n /** `Table::deferTotals()`: the record count and summaries, loaded after the rows. */\n tableTotals?: string\n /** `Table::savedViews()`: save, share, rename, delete and pick a default view. */\n tableViews?: string\n /** `Table::spreadsheet()`: batch save (or validate) grid edits. */\n tableCells?: string\n /** Fresh schemas of lazy and polled components by token (components outside panels). */\n components?: string\n locale: string\n /** Calls `#[Expose]` methods of custom pages (`usePageMethod()`). */\n pageCall?: string\n /** Edit pages: what changed since the form was loaded (`detectConflicts()`). */\n conflicts?: string\n upload: string\n uploadDestroy: string\n /** Starts a resumable chunked upload (`{uploadChunks}/{id}` takes chunks, `/complete` finishes). */\n uploadChunks?: string\n globalSearch: string\n /** Command palette: \"create\" links and recent records (GET). */\n commandPalette?: string\n /** Stores the user's customised keyboard shortcuts (POST `{shortcuts}`). */\n shortcuts?: string\n /** `Action::undoable()`: undoes an action from its toast (POST `{undo}`). */\n undo?: string\n /** `Dashboard::customizable()`: saves or resets the user's layout (POST `{token, layout}`). */\n dashboardLayout?: string\n notifications: string\n notificationsReadAll: string\n notificationsDestroyAll: string\n logout: string\n}\n\n/** Where toasts appear (`Panel::toastPosition()`); `start`/`end` follow the reading direction. */\nexport type ToastPosition =\n 'top-start' | 'top-center' | 'top-end' | 'bottom-start' | 'bottom-center' | 'bottom-end'\n\n/** Shown while impersonating another user (`ImpersonateAction`). */\nexport interface ImpersonationSchema {\n /** \"You are signed in as …\", translated on the server. */\n label: string\n leaveLabel: string\n /** POST here to return to the real user. */\n leaveUrl: string\n}\n\n/** The `alfakit` prop shared with every page of a panel. */\n/**\n * One `errorNotifications` entry: the text to show, or `hidden` (caught, nothing shown,\n * `hiddenErrorNotification()`) or `disabled` (default handling, `disabledErrorNotification()`).\n */\nexport interface ErrorNotificationEntrySchema {\n title?: string\n body?: string | null\n icon?: IconName\n hidden?: boolean\n disabled?: boolean\n}\n\n/** `errorNotifications`, keyed by HTTP status plus `default`. */\nexport type ErrorNotificationsSchema = Record<string, ErrorNotificationEntrySchema>\n\nexport interface PanelSchema {\n id: string\n /**\n * The schema contract version the server serialised (`SchemaContract::VERSION`). Renderers\n * compare its major part with `SCHEMA_CONTRACT_VERSION`; absent on servers before 1.0.\n */\n schemaContract?: string\n path: string\n url?: string\n /** Where \"G then D\" goes when it isn't `url`: the app's `/dashboard` for a starter kit panel without a dashboard page. */\n dashboardUrl?: string\n brandName: string\n brandLogo?: string\n darkModeBrandLogo?: string\n brandLogoHeight?: string\n favicon?: string\n /**\n * Colour custom properties without the `--alfakit-` prefix: `primary`, its readable\n * `primary-foreground` and, for palettes (`Color::Indigo`), `primary-50` … `primary-950`.\n */\n colors?: Record<string, string>\n /**\n * CSS for custom colour names (`colors(['brand' => …])`): `.alfakit-color-{name}` rules\n * that point the generic colour utilities of themes.css at `--alfakit-{name}`.\n */\n colorCss?: string\n darkMode: {\n enabled: boolean\n forced: boolean\n /** `defaultThemeMode()`; absent for `system`. */\n default?: 'light' | 'dark'\n /** `false` when `themeSwitcher(false)`: no switcher, always the default mode. */\n switcher?: false\n }\n /** `false` when `topbar(false)`: sidebar layouts render no top bar (a slim bar on phones). */\n topbar?: false\n sidebarCollapsibleOnDesktop: boolean\n sidebarFullyCollapsibleOnDesktop?: boolean\n topNavigation?: boolean\n maxContentWidth?: Width | string\n breadcrumbs: boolean\n unsavedChangesAlerts?: boolean\n /** `false` when `collapsibleNavigationGroups(false)`: groups never collapse. */\n collapsibleNavigationGroups?: false\n /** CSS lengths for the open and icon-only sidebar (`--sidebar-width`, `--sidebar-width-icon`). */\n sidebarWidth?: string\n collapsedSidebarWidth?: string\n /** Max width of `auth` layout pages (login, registration…). */\n simplePageMaxContentWidth?: Width | string\n /** `false` when `spa(false)`: every link loads a full page. */\n spa?: false\n /** URL patterns (`*` wildcard) that load with a full page request. */\n spaUrlExceptions?: string[]\n /**\n * Notifications shown instead of Inertia's error modal, keyed by HTTP status plus\n * `default`. Absent when disabled or in debug mode. A page's own `errorNotifications`\n * replaces it while that page is open.\n */\n errorNotifications?: ErrorNotificationsSchema\n theme: PanelThemeSchema\n /** Content inserted with `Panel::slot()`, keyed by slot name. */\n slots?: Record<string, SlotItemSchema[]>\n /**\n * Empty for `starterKit()` panels: their navigation is the `alfakitNavigation` page prop,\n * which every page of the host application carries (not only the panel's).\n */\n navigation: NavigationGroupSchema[]\n /**\n * The active items and badges of this request; `navigation` holds only the structure\n * (sent once). Read the navigation through `usePanel()`, which merges the two.\n */\n navigationState?: NavigationStateSchema\n /**\n * `Panel::starterKit()`: the panel runs inside a Laravel starter kit app. Its pages render\n * in the host's own layout (sidebar, header, appearance) and the host's shadcn tokens;\n * the panel's own chrome, theme attributes and colour scheme are not applied.\n */\n starterKit?: true\n user?: PanelUserSchema\n userMenu?: {\n items: MenuItemSchema[]\n profileUrl?: string\n logoutUrl: string\n /** The `profile` entry as customised in `userMenuItems()` (replaces the built-in link). */\n profile?: MenuItemSchema\n /** The customised `logout` entry, or `false` when it is hidden. */\n logout?: MenuItemSchema | false\n /**\n * `topbar` when `userMenu(position: UserMenuPosition::Topbar)`: sidebar layouts show the\n * menu in the top bar instead of the sidebar footer. Omitted for the sidebar.\n */\n position?: 'topbar'\n }\n tenancy?: {\n current?: TenantSchema | null\n tenants: TenantSchema[]\n registrationUrl?: string\n profileUrl?: string\n /** The registration page's own label (`RegisterTenant::getLabel()`); else \"New\". */\n registrationLabel?: string\n /** The profile page's own label (`EditTenantProfile::getLabel()`); else \"Settings\". */\n profileLabel?: string\n /** The customised `register` entry of `tenantMenuItems()`. */\n registration?: MenuItemSchema\n /** The customised `profile` entry of `tenantMenuItems()`. */\n profile?: MenuItemSchema\n menu?: MenuItemSchema[]\n /** `false` when `tenantSwitcher(false)`: `tenants` is empty and the menu lists no others. */\n switcher?: false\n /** `searchableTenantMenu()`, or more than 10 tenants: show a search field in the menu. */\n searchable?: boolean\n }\n databaseNotifications?: {\n pollingInterval?: string\n unreadCount: number\n /**\n * `sidebar` when `databaseNotifications(position: 'sidebar')` or `topbar(false)`: sidebar\n * layouts show the bell in the sidebar instead of the top bar. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n globalSearch?: {\n keyBindings: string[]\n placeholder: string\n /** `globalSearchDebounce()` in milliseconds; renderers default to 250. */\n debounce?: number\n /** `globalSearchFieldSuffix()`: text shown at the end of the field instead of the key hint. */\n suffix?: string\n /** `false` when `globalSearchFieldKeyBindingSuffix(false)`: no key binding hint. */\n keyBindingSuffix?: false\n /**\n * `sidebar` when `globalSearch(position: GlobalSearchPosition::Sidebar)` or\n * `topbar(false)`: sidebar layouts show the field in the sidebar header. Omitted for the top bar.\n */\n position?: 'sidebar'\n }\n endpoints: Partial<PanelEndpoints> & { logout: string }\n /** `alfakit.uploads.chunk_size` in bytes: larger files are uploaded in chunks. */\n uploadChunkSize?: number\n auth: { login?: string; register?: string; passwordReset?: string }\n flashKey: string\n /** Present while impersonating: render a banner with a way back. */\n impersonation?: ImpersonationSchema\n /**\n * `Panel::realtimeNotifications()`: the private channel to listen on with Laravel Echo;\n * with `Panel::realtime()` also the connection (`live`, `authEndpoint`, `echo`).\n */\n realtime?: RealtimeSchema\n /** Omitted for the default (`top-end`). */\n toastPosition?: ToastPosition\n /** The command palette's extras: page actions, panel commands, navigation, create links, recent records. */\n commandPalette?: CommandPaletteSchema\n /** Keyboard shortcuts (`false` with `keyboardShortcuts(false)`); absent for guests. */\n keyboardShortcuts?: KeyboardShortcutsSchema | false\n /** `Panel::pwa()`: service worker, install prompt and Web Push; absent when off. */\n pwa?: PwaSchema\n localization: LocalizationSchema\n csrf: string\n translations: Record<string, unknown>\n}\n\nexport interface BreadcrumbSchema {\n label: string\n url?: string\n}\n\nexport type ContentBlock =\n | FormBlock\n | InfolistBlock\n | SchemaBlock\n | TableBlock\n | RelationManagersBlock\n | WidgetsBlock\n | LinksBlock\n | { type: string; key: string; [key: string]: unknown }\n\nexport type PageLayout = 'app' | 'auth' | 'blank' | 'fullscreen'\n\n/** The `page` prop rendered by `Alfakit/Page`. */\nexport interface PageSchema {\n class: string\n layout: PageLayout\n title: string\n heading?: string\n subheading?: string\n breadcrumbs?: BreadcrumbSchema[]\n maxContentWidth?: Width | string\n /** `fullscreen` layout: whether to show the slim header, and where its exit link goes. */\n fullscreen?: { header: boolean; exitUrl?: string }\n /** Cluster or record sub-navigation. */\n subNavigation?: SubNavigationSchema\n headerActions?: AnyActionSchema[]\n /** `$headerActionsAlignment`; absent for `end` (the default). */\n headerActionsAlignment?: 'start' | 'center' | 'end' | 'between' | 'justify'\n /** `$extraBodyAttributes` / `getExtraBodyAttributes()`: set on `<body>` while the page is open. */\n extraBodyAttributes?: Record<string, string>\n content: ContentBlock[]\n /** Encrypted context token for internal endpoints. */\n token: string\n record?: RecordKey\n view?: { component: string; props: Record<string, unknown> }\n /**\n * An action whose modal the renderer opens once the page hydrated (`$defaultAction`,\n * `?defaultAction=`, `?tableAction=&tableActionRecord=`).\n */\n defaultAction?: DefaultActionSchema\n /**\n * Page override of the panel's `errorNotifications` (`$hasErrorNotifications`,\n * `setUpErrorNotifications()`): `false` turns them off on this page. Absent when the\n * page follows the panel.\n */\n errorNotifications?: ErrorNotificationsSchema | false\n /** Record pages with `Panel::realtime()`: who else is on the record. */\n presence?: PresenceSchema\n /** Edit pages with `detectConflicts()`: the version the form was loaded from. */\n conflicts?: ConflictsSchema\n}\n\n/** `PageSchema::defaultAction`: what to open and in which context. */\nexport interface DefaultActionSchema {\n action: ActionSchema\n /** Context component: `page`, `table`, `relationManager:…`. */\n component: string\n record?: RecordKey\n arguments?: Record<string, unknown>\n}\n\n/**\n * Fresh schemas returned by a poll, keyed by poll target: `table`, `relationManager:Class`\n * or `widget:<id or class>`.\n */\nexport type PolledComponents = Record<string, ContentBlock | WidgetSchema | RelationManagerSchema>\n\n/** Shape of `usePage().props` on an Alfakit page. */\nexport interface AlfakitPageProps {\n alfakit: PanelSchema\n page?: PageSchema\n /** Components refreshed by a poll (only present after one); merged into `page`. */\n alfakitPoll?: PolledComponents\n /**\n * Lazy widgets and relation managers (`Page::LAZY_PROP`), an Inertia deferred prop that\n * arrives right after the first response; `resolveLazyComponents()` puts them in `page`.\n */\n alfakitLazy?: PolledComponents\n /** Badges of `Tab::deferBadge()` list tabs, a deferred prop loaded after the page. */\n alfakitTabBadges?: DeferredTabBadges\n /**\n * `Panel::deferNavigationBadges()`: the navigation badges keyed by item id\n * (`navigationState`), a deferred prop loaded after the page.\n */\n alfakitNavigationBadges?: Record<string, NavigationBadgeSchema>\n alfakitAppearance?: 'light' | 'dark' | 'system'\n /** The stored sidebar state (`alfakit_sidebar_state` cookie); null when never toggled. */\n alfakitSidebarOpen?: boolean | null\n /**\n * The navigation of the application's `starterKit()` panels, shared with every Inertia\n * page (the host's own pages too) for the host sidebar. Null for guests.\n */\n alfakitNavigation?: StarterKitNavigationSchema[] | null\n /**\n * `starterKit()` panels: their dashboard widgets on the host's dashboard\n * (`starterKitDashboard()`), for `<AlfakitWidgets />`. Null on other pages, for guests\n * and users who may not open the panel.\n */\n alfakitWidgets?: StarterKitDashboardWidgetsSchema[] | null\n /**\n * The developer debug panel's data (`Page::DEBUG_PROP`): schema tree with visibility and\n * authorisation, evaluated closures and server timings. Only present while the panel's\n * debug panel is active; never in production.\n */\n alfakitDebug?: DebugSchema\n /**\n * The schema inspector's data (context identity, class links). Only present while the\n * panel's dev tools are on (`Panel::devTools()` with `app.debug`); never in production.\n */\n alfakitDevTools?: DevToolsSchema\n flash?: {\n alfakitNotifications?: NotificationSchema[]\n alfakitAction?: {\n name: string\n status: 'success' | 'halted' | 'cancelled'\n closeModal?: boolean\n deselectRecords?: boolean | null\n /** A file the handler returned: a signed, one-time link to save it from. */\n download?: string\n }\n [key: string]: unknown\n }\n errors?: Record<string, string>\n [key: string]: unknown\n}\n\nexport type { ActionSchema }\n"],"mappings":";;AAgKA,MAAa,sBAAsB;AACnC,MAAa,wBAAwB"}
@@ -115,6 +115,11 @@ export type WidgetSchema = StatsOverviewWidgetSchema | ChartWidgetSchema | Table
115
115
  */
116
116
  export interface LazyWidgetSchema extends BaseWidgetSchema {
117
117
  lazy: true;
118
+ /**
119
+ * Set in the browser, never by PHP: loading the widget's schema failed (a refused or
120
+ * failed request), so the renderer shows an error with a retry instead of a skeleton.
121
+ */
122
+ failed?: true;
118
123
  }
119
124
  export interface WidgetsBlock {
120
125
  type: 'Widgets';
@@ -1 +1 @@
1
- {"version":3,"file":"widgets.d.ts","names":[],"sources":["../../src/schema/widgets.ts"],"mappings":";;;;;;;;;;iBAUiB;EACf;EACA;EACA;EACA,YAAY,YAAY;;;;;EAKxB;;EAEA,OAAO;;iBAGQ;EACf;EACA;;EAEA;EACA;EACA,kBAAkB;;EAElB,0BAA0B;EAC1B,mBAAmB;EACnB,QAAQ;EACR,OAAO;EACP;EACA,aAAa;EACb;EACA;EACA,kBAAkB;;iBAGH,kCAAkC;EACjD;EACA;EACA;EACA;EACA,UAAU;EACV,OAAO;;iBAGQ,0BAA0B;EACzC;EACA;EAUA;EACA;;;;;EAKA;;EAEA,MAAM;EACN,UAAU;EACV,UAAU;IAAQ;IAAe;;EACjC;EACA;EACA;;EAEA,QAAQ;;EAER;;EAEA,cAAc;;EAEd;EACA,aAAa;;;iBAIE;EACf;EACA;EACA,OAAO;;EAEP,UAAU;;EAEV;;EAEA,WAAW;;;iBAII,+BAA+B;;EAE9C;;EAEA;;EAEA,iBAAiB,QAAQ,wCAAwC;;iBAGlD,0BAA0B;EACzC;EACA;EACA,OAAO;;iBAGQ,2BAA2B;EAC1C;EACA;EACA,QAAQ;;YAGE,eACR,4BACA,oBACA,oBACA,qBAGA,uBACA,mBACA,qBAEA;;;;;iBAMa,yBAAyB;EACxC;;iBAGe;EACf;EACA;EACA,SAAS;EACT,SAAS,MAAM,eAAe;;EAE9B,YAAY;;;iBAIG;EACf;;EAEA;EACA;;;iBAIe;EACf;EACA;;EAEA,YAAY,YAAY;;;iBAIT;EACf;EACA;EACA;;EAEA;;EAEA;EACA,OAAO;;iBAGQ;;EAEf;EACA;;EAEA;EACA,QAAQ;EACR,eAAe;EACf,SAAS"}
1
+ {"version":3,"file":"widgets.d.ts","names":[],"sources":["../../src/schema/widgets.ts"],"mappings":";;;;;;;;;;iBAUiB;EACf;EACA;EACA;EACA,YAAY,YAAY;;;;;EAKxB;;EAEA,OAAO;;iBAGQ;EACf;EACA;;EAEA;EACA;EACA,kBAAkB;;EAElB,0BAA0B;EAC1B,mBAAmB;EACnB,QAAQ;EACR,OAAO;EACP;EACA,aAAa;EACb;EACA;EACA,kBAAkB;;iBAGH,kCAAkC;EACjD;EACA;EACA;EACA;EACA,UAAU;EACV,OAAO;;iBAGQ,0BAA0B;EACzC;EACA;EAUA;EACA;;;;;EAKA;;EAEA,MAAM;EACN,UAAU;EACV,UAAU;IAAQ;IAAe;;EACjC;EACA;EACA;;EAEA,QAAQ;;EAER;;EAEA,cAAc;;EAEd;EACA,aAAa;;;iBAIE;EACf;EACA;EACA,OAAO;;EAEP,UAAU;;EAEV;;EAEA,WAAW;;;iBAII,+BAA+B;;EAE9C;;EAEA;;EAEA,iBAAiB,QAAQ,wCAAwC;;iBAGlD,0BAA0B;EACzC;EACA;EACA,OAAO;;iBAGQ,2BAA2B;EAC1C;EACA;EACA,QAAQ;;YAGE,eACR,4BACA,oBACA,oBACA,qBAGA,uBACA,mBACA,qBAEA;;;;;iBAMa,yBAAyB;EACxC;;;;;EAKA;;iBAGe;EACf;EACA;EACA,SAAS;EACT,SAAS,MAAM,eAAe;;EAE9B,YAAY;;;iBAIG;EACf;;EAEA;EACA;;;iBAIe;EACf;EACA;;EAEA,YAAY,YAAY;;;iBAIT;EACf;EACA;EACA;;EAEA;;EAEA;EACA,OAAO;;iBAGQ;;EAEf;EACA;;EAEA;EACA,QAAQ;EACR,eAAe;EACf,SAAS"}
@@ -33,6 +33,11 @@ export declare function findStandalonePropName(props: Record<string, unknown> |
33
33
  * with the host page's query string so tables keep their sort, search and filters.
34
34
  */
35
35
  export declare function fetchStandaloneComponents(endpoint: string, token: string, targets: string[]): Promise<PolledComponents>;
36
+ /**
37
+ * Mark the lazy widget placeholders whose request failed (`failed: true`); a page without
38
+ * failures (or whose failed widgets have loaded since) keeps its identity.
39
+ */
40
+ export declare function markFailedLazyWidgets(page: PageSchema, failed: ReadonlySet<string>): PageSchema;
36
41
  /**
37
42
  * Lazy and polled component state of one `<AlfakitComponent>`: a poll scheduler bound to
38
43
  * its token and the schemas fetched so far, merged into the page it renders.
@@ -42,6 +47,8 @@ export declare class StandaloneComponentState {
42
47
  private readonly onChange;
43
48
  readonly scheduler: PollScheduler;
44
49
  private fresh;
50
+ /** Targets whose last request failed (lazy widgets then show an error, not a skeleton). */
51
+ private failed;
45
52
  constructor(component: StandaloneComponentSchema, onChange: () => void);
46
53
  /** A new payload (page reload) replaces what was polled; lazy schemas stay cached. */
47
54
  setComponent(component: StandaloneComponentSchema): void;
@@ -1 +1 @@
1
- {"version":3,"file":"standalone.d.ts","names":[],"sources":["../src/standalone.ts"],"mappings":";;;;;;;;;;;iBAiBiB,mBAAmB;;EAElC,aAAa,eAAe;;;;;;wBAuBd,sBAAsB,WAAW,4BAA4B;;wBAqB7D,qBAAqB,WAAW,4BAA4B;;wBAe5D,oBACd,cAAc,KAAK;;;;;;wBAUL,uBACd,OAAO,qCACP,WAAW,KAAK;;;;;wBAmBF,0BACd,kBACA,eACA,oBACC,QAAQ;;;;;qBAYE;UAMD;mBACS;WANH,WAAW;UAEnB;EAGE,YAAA,WAAW,2BACF;;EAMnB,aAAa,WAAW;MAQpB,QAAQ;;;;;;;EAaZ,SAAS,YAAY,4BAA4B;;EAOjD,QAAQ,oBAAoB;;EAK5B,KAAK,gBAAgB;UAIP;;UAuBN"}
1
+ {"version":3,"file":"standalone.d.ts","names":[],"sources":["../src/standalone.ts"],"mappings":";;;;;;;;;;;iBAwBiB,mBAAmB;;EAElC,aAAa,eAAe;;;;;;wBAuBd,sBAAsB,WAAW,4BAA4B;;wBAqB7D,qBAAqB,WAAW,4BAA4B;;wBAe5D,oBACd,cAAc,KAAK;;;;;;wBAUL,uBACd,OAAO,qCACP,WAAW,KAAK;;;;;wBAmBF,0BACd,kBACA,eACA,oBACC,QAAQ;;;;;wBAYK,sBAAsB,MAAM,YAAY,QAAQ,sBAAsB;;;;;qBA4BzE;UASD;mBACS;WATH,WAAW;UAEnB;;UAGA;EAGE,YAAA,WAAW,2BACF;;EAMnB,aAAa,WAAW;MASpB,QAAQ;;;;;;;EAaZ,SAAS,YAAY,4BAA4B;;EAOjD,QAAQ,oBAAoB;;EAK5B,KAAK,gBAAgB;UAIP;;UA8BN"}
@@ -1,8 +1,8 @@
1
1
  import { registerStandaloneProp, resolvePartialProps } from "./partial-props.js";
2
2
  import { currentPageQuery } from "./page-query.js";
3
3
  import { requestJson } from "./http.js";
4
- import { PollScheduler, mergePolledComponents } from "./poll.js";
5
- import { lazyComponentCache, resolveLazyComponents } from "./lazy.js";
4
+ import { PollScheduler, mergePolledComponents, widgetPollTarget } from "./poll.js";
5
+ import { isLazyPlaceholder, lazyComponentCache, resolveLazyComponents } from "./lazy.js";
6
6
  import { isStandaloneComponent } from "./schema/standalone.js";
7
7
  //#region src/standalone.ts
8
8
  const DEFAULT_THEME = {
@@ -89,6 +89,32 @@ function fetchStandaloneComponents(endpoint, token, targets) {
89
89
  } });
90
90
  }
91
91
  /**
92
+ * Mark the lazy widget placeholders whose request failed (`failed: true`); a page without
93
+ * failures (or whose failed widgets have loaded since) keeps its identity.
94
+ */
95
+ function markFailedLazyWidgets(page, failed) {
96
+ if (failed.size === 0) return page;
97
+ let changed = false;
98
+ const content = page.content.map((block) => {
99
+ if (block.type !== "Widgets") return block;
100
+ const widgets = block.widgets;
101
+ const next = widgets.map((widget) => isLazyPlaceholder(widget) && !widget.failed && failed.has(widgetPollTarget(widget)) ? {
102
+ ...widget,
103
+ failed: true
104
+ } : widget);
105
+ if (!next.some((widget, index) => widget !== widgets[index])) return block;
106
+ changed = true;
107
+ return {
108
+ ...block,
109
+ widgets: next
110
+ };
111
+ });
112
+ return changed ? {
113
+ ...page,
114
+ content
115
+ } : page;
116
+ }
117
+ /**
92
118
  * Lazy and polled component state of one `<AlfakitComponent>`: a poll scheduler bound to
93
119
  * its token and the schemas fetched so far, merged into the page it renders.
94
120
  */
@@ -97,6 +123,8 @@ var StandaloneComponentState = class {
97
123
  onChange;
98
124
  scheduler;
99
125
  fresh = {};
126
+ /** Targets whose last request failed (lazy widgets then show an error, not a skeleton). */
127
+ failed = /* @__PURE__ */ new Set();
100
128
  constructor(component, onChange) {
101
129
  this.component = component;
102
130
  this.onChange = onChange;
@@ -104,13 +132,16 @@ var StandaloneComponentState = class {
104
132
  }
105
133
  /** A new payload (page reload) replaces what was polled; lazy schemas stay cached. */
106
134
  setComponent(component) {
107
- if (component.token !== this.component.token) this.fresh = {};
135
+ if (component.token !== this.component.token) {
136
+ this.fresh = {};
137
+ this.failed = /* @__PURE__ */ new Set();
138
+ }
108
139
  this.component = component;
109
140
  }
110
141
  get page() {
111
142
  const base = standalonePageSchema(this.component);
112
143
  const resolved = resolveLazyComponents(base, this.fresh, lazyComponentCache, this.scope());
113
- return mergePolledComponents(resolved, this.fresh);
144
+ return markFailedLazyWidgets(mergePolledComponents(resolved, this.fresh), this.failed);
114
145
  }
115
146
  /**
116
147
  * Load the lazy components named by the payload. Pass the new payload when it arrived
@@ -140,8 +171,13 @@ var StandaloneComponentState = class {
140
171
  ...this.fresh,
141
172
  ...polled
142
173
  };
174
+ targets.forEach((target) => this.failed.delete(target));
175
+ this.onChange();
176
+ } catch {
177
+ if (token !== this.component.token) return;
178
+ targets.forEach((target) => this.failed.add(target));
143
179
  this.onChange();
144
- } catch {}
180
+ }
145
181
  }
146
182
  /** Lazy schemas are cached per page path and component class, like panel pages. */
147
183
  scope() {
@@ -149,6 +185,6 @@ var StandaloneComponentState = class {
149
185
  }
150
186
  };
151
187
  //#endregion
152
- export { StandaloneComponentState, fetchStandaloneComponents, findStandalonePropName, registerStandaloneProp, resolvePartialProps, standaloneDirection, standalonePageSchema, standalonePanelSchema };
188
+ export { StandaloneComponentState, fetchStandaloneComponents, findStandalonePropName, markFailedLazyWidgets, registerStandaloneProp, resolvePartialProps, standaloneDirection, standalonePageSchema, standalonePanelSchema };
153
189
 
154
190
  //# sourceMappingURL=standalone.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"standalone.js","names":[],"sources":["../src/standalone.ts"],"sourcesContent":["import { requestJson } from './http'\nimport { lazyComponentCache, resolveLazyComponents } from './lazy'\nimport { currentPageQuery } from './page-query'\nimport { mergePolledComponents, PollScheduler } from './poll'\nimport type { LocalizationSchema, PageSchema, PanelSchema, PolledComponents } from './schema/panel'\nimport type { StandaloneComponentSchema } from './schema/standalone'\nimport { isStandaloneComponent } from './schema/standalone'\n\nexport { registerStandaloneProp, resolvePartialProps } from './partial-props'\n\n/**\n * Support for components rendered outside panels (`<AlfakitComponent>`): the panel and\n * page shapes the renderers' hooks expect, partial-reload prop resolution, and fetching\n * lazy or polled components through the standalone endpoint.\n */\n\n/** Defaults used for `AlfakitComponent`s in every renderer (`configureAlfakit()`). */\nexport interface StandaloneDefaults<TComponent> {\n /** Schema component renderers, keyed by type (`TextInput`) or View name (`map-picker`). */\n components?: Record<string, TComponent>\n}\n\nconst DEFAULT_THEME: PanelSchema['theme'] = {\n name: 'neutral',\n attribute: 'neutral',\n sidebar: 'inset',\n density: 'comfortable',\n design: {\n layout: 'sidebar',\n nav: 'soft',\n header: 'plain',\n surfaces: 'bordered',\n inputs: 'outline',\n buttons: 'rounded',\n tables: 'lined',\n },\n}\n\n/**\n * The `alfakit` panel prop a standalone payload stands for, so every renderer component\n * (`usePanel()`, `useLocalization()`, `useFormatter()`) works unchanged outside panels.\n */\nexport function standalonePanelSchema(component: StandaloneComponentSchema): PanelSchema {\n return {\n id: 'standalone',\n path: '',\n brandName: '',\n darkMode: { enabled: true, forced: false },\n sidebarCollapsibleOnDesktop: false,\n breadcrumbs: false,\n theme: DEFAULT_THEME,\n navigation: [],\n endpoints: { ...component.endpoints, logout: '' },\n uploadChunkSize: component.uploadChunkSize,\n auth: {},\n flashKey: component.flashKey,\n localization: component.localization,\n csrf: component.csrf,\n translations: component.translations,\n }\n}\n\n/** The `page` prop a standalone payload stands for (`usePageSchema()`). */\nexport function standalonePageSchema(component: StandaloneComponentSchema): PageSchema {\n return {\n class: component.class,\n layout: 'blank',\n title: component.heading ?? '',\n heading: component.heading ?? undefined,\n subheading: component.subheading ?? undefined,\n headerActions: component.headerActions ?? undefined,\n content: component.content,\n token: component.token,\n record: component.record ?? undefined,\n }\n}\n\n/** Direction of a standalone component's language. */\nexport function standaloneDirection(\n localization: Pick<LocalizationSchema, 'direction'> | undefined,\n): 'ltr' | 'rtl' {\n return localization?.direction ?? 'ltr'\n}\n\n/**\n * The name of the Inertia prop holding `component` (matched by token), so partial reloads\n * can ask for it alone. Undefined when nested in another prop: the page is then reloaded\n * in full.\n */\nexport function findStandalonePropName(\n props: Record<string, unknown> | undefined,\n component: Pick<StandaloneComponentSchema, 'token'>,\n): string | undefined {\n if (!props) return undefined\n\n for (const [name, value] of Object.entries(props)) {\n if (isStandaloneComponent(value) && value.token === component.token) {\n return name\n }\n }\n\n return undefined\n}\n\n// ---------------------------------------------------------------- Lazy and polled components\n\n/**\n * Fetch fresh schemas of components (`table`, `widget:<id>`) of a standalone component,\n * with the host page's query string so tables keep their sort, search and filters.\n */\nexport function fetchStandaloneComponents(\n endpoint: string,\n token: string,\n targets: string[],\n): Promise<PolledComponents> {\n if (targets.length === 0) return Promise.resolve({})\n\n return requestJson<PolledComponents>(endpoint, {\n query: { token, targets: targets.join(','), tableQuery: currentPageQuery() },\n })\n}\n\n/**\n * Lazy and polled component state of one `<AlfakitComponent>`: a poll scheduler bound to\n * its token and the schemas fetched so far, merged into the page it renders.\n */\nexport class StandaloneComponentState {\n public readonly scheduler: PollScheduler\n\n private fresh: PolledComponents = {}\n\n constructor(\n private component: StandaloneComponentSchema,\n private readonly onChange: () => void,\n ) {\n this.scheduler = new PollScheduler((targets) => this.load(targets))\n }\n\n /** A new payload (page reload) replaces what was polled; lazy schemas stay cached. */\n setComponent(component: StandaloneComponentSchema): void {\n if (component.token !== this.component.token) {\n this.fresh = {}\n }\n\n this.component = component\n }\n\n get page(): PageSchema {\n const base = standalonePageSchema(this.component)\n const resolved = resolveLazyComponents(base, this.fresh, lazyComponentCache, this.scope())\n\n return mergePolledComponents(resolved, this.fresh)\n }\n\n /**\n * Load the lazy components named by the payload. Pass the new payload when it arrived\n * (a reload after an action): the load then belongs to it, while one started for the\n * previous payload would be dropped as stale once the page renders the new one (the\n * starter kit dashboard's calendar kept its old events after creating one).\n */\n loadLazy(component?: StandaloneComponentSchema): Promise<void> {\n if (component) this.setComponent(component)\n\n return this.load(this.component.lazy ?? [])\n }\n\n /** Fetch fresh schemas of these components now (`live()` refreshes on record changes). */\n refresh(targets: string[]): Promise<void> {\n return this.load(targets)\n }\n\n /** Poll a component (`table`, `widget:<id>`); returns an unsubscribe. */\n poll(target: string, interval: string | number | null | undefined): () => void {\n return this.scheduler.subscribe(target, interval)\n }\n\n private async load(targets: string[]): Promise<void> {\n if (targets.length === 0) return\n\n const token = this.component.token\n\n try {\n const polled = await fetchStandaloneComponents(\n this.component.endpoints.components,\n token,\n targets,\n )\n\n // The page re-rendered meanwhile: its new payload is fresher than this response.\n if (token !== this.component.token) return\n\n this.fresh = { ...this.fresh, ...polled }\n this.onChange()\n } catch {\n // A failed poll keeps what the page has; the next tick tries again.\n }\n }\n\n /** Lazy schemas are cached per page path and component class, like panel pages. */\n private scope(): string {\n const path = typeof window === 'undefined' ? '' : window.location.pathname\n\n return `${path}|${this.component.class}`\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"standalone.js","names":[],"sources":["../src/standalone.ts"],"sourcesContent":["import { requestJson } from './http'\nimport { isLazyPlaceholder, lazyComponentCache, resolveLazyComponents } from './lazy'\nimport { currentPageQuery } from './page-query'\nimport { mergePolledComponents, PollScheduler, widgetPollTarget } from './poll'\nimport type {\n ContentBlock,\n LocalizationSchema,\n PageSchema,\n PanelSchema,\n PolledComponents,\n} from './schema/panel'\nimport type { StandaloneComponentSchema } from './schema/standalone'\nimport { isStandaloneComponent } from './schema/standalone'\nimport type { WidgetsBlock } from './schema/widgets'\n\nexport { registerStandaloneProp, resolvePartialProps } from './partial-props'\n\n/**\n * Support for components rendered outside panels (`<AlfakitComponent>`): the panel and\n * page shapes the renderers' hooks expect, partial-reload prop resolution, and fetching\n * lazy or polled components through the standalone endpoint.\n */\n\n/** Defaults used for `AlfakitComponent`s in every renderer (`configureAlfakit()`). */\nexport interface StandaloneDefaults<TComponent> {\n /** Schema component renderers, keyed by type (`TextInput`) or View name (`map-picker`). */\n components?: Record<string, TComponent>\n}\n\nconst DEFAULT_THEME: PanelSchema['theme'] = {\n name: 'neutral',\n attribute: 'neutral',\n sidebar: 'inset',\n density: 'comfortable',\n design: {\n layout: 'sidebar',\n nav: 'soft',\n header: 'plain',\n surfaces: 'bordered',\n inputs: 'outline',\n buttons: 'rounded',\n tables: 'lined',\n },\n}\n\n/**\n * The `alfakit` panel prop a standalone payload stands for, so every renderer component\n * (`usePanel()`, `useLocalization()`, `useFormatter()`) works unchanged outside panels.\n */\nexport function standalonePanelSchema(component: StandaloneComponentSchema): PanelSchema {\n return {\n id: 'standalone',\n path: '',\n brandName: '',\n darkMode: { enabled: true, forced: false },\n sidebarCollapsibleOnDesktop: false,\n breadcrumbs: false,\n theme: DEFAULT_THEME,\n navigation: [],\n endpoints: { ...component.endpoints, logout: '' },\n uploadChunkSize: component.uploadChunkSize,\n auth: {},\n flashKey: component.flashKey,\n localization: component.localization,\n csrf: component.csrf,\n translations: component.translations,\n }\n}\n\n/** The `page` prop a standalone payload stands for (`usePageSchema()`). */\nexport function standalonePageSchema(component: StandaloneComponentSchema): PageSchema {\n return {\n class: component.class,\n layout: 'blank',\n title: component.heading ?? '',\n heading: component.heading ?? undefined,\n subheading: component.subheading ?? undefined,\n headerActions: component.headerActions ?? undefined,\n content: component.content,\n token: component.token,\n record: component.record ?? undefined,\n }\n}\n\n/** Direction of a standalone component's language. */\nexport function standaloneDirection(\n localization: Pick<LocalizationSchema, 'direction'> | undefined,\n): 'ltr' | 'rtl' {\n return localization?.direction ?? 'ltr'\n}\n\n/**\n * The name of the Inertia prop holding `component` (matched by token), so partial reloads\n * can ask for it alone. Undefined when nested in another prop: the page is then reloaded\n * in full.\n */\nexport function findStandalonePropName(\n props: Record<string, unknown> | undefined,\n component: Pick<StandaloneComponentSchema, 'token'>,\n): string | undefined {\n if (!props) return undefined\n\n for (const [name, value] of Object.entries(props)) {\n if (isStandaloneComponent(value) && value.token === component.token) {\n return name\n }\n }\n\n return undefined\n}\n\n// ---------------------------------------------------------------- Lazy and polled components\n\n/**\n * Fetch fresh schemas of components (`table`, `widget:<id>`) of a standalone component,\n * with the host page's query string so tables keep their sort, search and filters.\n */\nexport function fetchStandaloneComponents(\n endpoint: string,\n token: string,\n targets: string[],\n): Promise<PolledComponents> {\n if (targets.length === 0) return Promise.resolve({})\n\n return requestJson<PolledComponents>(endpoint, {\n query: { token, targets: targets.join(','), tableQuery: currentPageQuery() },\n })\n}\n\n/**\n * Mark the lazy widget placeholders whose request failed (`failed: true`); a page without\n * failures (or whose failed widgets have loaded since) keeps its identity.\n */\nexport function markFailedLazyWidgets(page: PageSchema, failed: ReadonlySet<string>): PageSchema {\n if (failed.size === 0) return page\n\n let changed = false\n const content = page.content.map((block) => {\n if (block.type !== 'Widgets') return block\n\n const widgets = (block as WidgetsBlock).widgets\n const next = widgets.map((widget) =>\n isLazyPlaceholder(widget) && !widget.failed && failed.has(widgetPollTarget(widget))\n ? { ...widget, failed: true as const }\n : widget,\n )\n\n if (!next.some((widget, index) => widget !== widgets[index])) return block\n\n changed = true\n\n return { ...block, widgets: next }\n }) as ContentBlock[]\n\n return changed ? { ...page, content } : page\n}\n\n/**\n * Lazy and polled component state of one `<AlfakitComponent>`: a poll scheduler bound to\n * its token and the schemas fetched so far, merged into the page it renders.\n */\nexport class StandaloneComponentState {\n public readonly scheduler: PollScheduler\n\n private fresh: PolledComponents = {}\n\n /** Targets whose last request failed (lazy widgets then show an error, not a skeleton). */\n private failed = new Set<string>()\n\n constructor(\n private component: StandaloneComponentSchema,\n private readonly onChange: () => void,\n ) {\n this.scheduler = new PollScheduler((targets) => this.load(targets))\n }\n\n /** A new payload (page reload) replaces what was polled; lazy schemas stay cached. */\n setComponent(component: StandaloneComponentSchema): void {\n if (component.token !== this.component.token) {\n this.fresh = {}\n this.failed = new Set()\n }\n\n this.component = component\n }\n\n get page(): PageSchema {\n const base = standalonePageSchema(this.component)\n const resolved = resolveLazyComponents(base, this.fresh, lazyComponentCache, this.scope())\n\n return markFailedLazyWidgets(mergePolledComponents(resolved, this.fresh), this.failed)\n }\n\n /**\n * Load the lazy components named by the payload. Pass the new payload when it arrived\n * (a reload after an action): the load then belongs to it, while one started for the\n * previous payload would be dropped as stale once the page renders the new one (the\n * starter kit dashboard's calendar kept its old events after creating one).\n */\n loadLazy(component?: StandaloneComponentSchema): Promise<void> {\n if (component) this.setComponent(component)\n\n return this.load(this.component.lazy ?? [])\n }\n\n /** Fetch fresh schemas of these components now (`live()` refreshes on record changes). */\n refresh(targets: string[]): Promise<void> {\n return this.load(targets)\n }\n\n /** Poll a component (`table`, `widget:<id>`); returns an unsubscribe. */\n poll(target: string, interval: string | number | null | undefined): () => void {\n return this.scheduler.subscribe(target, interval)\n }\n\n private async load(targets: string[]): Promise<void> {\n if (targets.length === 0) return\n\n const token = this.component.token\n\n try {\n const polled = await fetchStandaloneComponents(\n this.component.endpoints.components,\n token,\n targets,\n )\n\n // The page re-rendered meanwhile: its new payload is fresher than this response.\n if (token !== this.component.token) return\n\n this.fresh = { ...this.fresh, ...polled }\n targets.forEach((target) => this.failed.delete(target))\n this.onChange()\n } catch {\n // A failed poll keeps what the page has; the next tick tries again. Lazy widgets\n // still waiting for their schema show an error with a retry instead of a skeleton\n // that never ends (a refused request: W-F-12).\n if (token !== this.component.token) return\n\n targets.forEach((target) => this.failed.add(target))\n this.onChange()\n }\n }\n\n /** Lazy schemas are cached per page path and component class, like panel pages. */\n private scope(): string {\n const path = typeof window === 'undefined' ? '' : window.location.pathname\n\n return `${path}|${this.component.class}`\n }\n}\n"],"mappings":";;;;;;;AA6BA,MAAM,gBAAsC;CAC1C,MAAM;CACN,WAAW;CACX,SAAS;CACT,SAAS;CACT,QAAQ;EACN,QAAQ;EACR,KAAK;EACL,QAAQ;EACR,UAAU;EACV,QAAQ;EACR,SAAS;EACT,QAAQ;CACV;AACF;;;;;AAMA,SAAgB,sBAAsB,WAAmD;CACvF,OAAO;EACL,IAAI;EACJ,MAAM;EACN,WAAW;EACX,UAAU;GAAE,SAAS;GAAM,QAAQ;EAAM;EACzC,6BAA6B;EAC7B,aAAa;EACb,OAAO;EACP,YAAY,CAAC;EACb,WAAW;GAAE,GAAG,UAAU;GAAW,QAAQ;EAAG;EAChD,iBAAiB,UAAU;EAC3B,MAAM,CAAC;EACP,UAAU,UAAU;EACpB,cAAc,UAAU;EACxB,MAAM,UAAU;EAChB,cAAc,UAAU;CAC1B;AACF;;AAGA,SAAgB,qBAAqB,WAAkD;CACrF,OAAO;EACL,OAAO,UAAU;EACjB,QAAQ;EACR,OAAO,UAAU,WAAW;EAC5B,SAAS,UAAU,WAAW,KAAA;EAC9B,YAAY,UAAU,cAAc,KAAA;EACpC,eAAe,UAAU,iBAAiB,KAAA;EAC1C,SAAS,UAAU;EACnB,OAAO,UAAU;EACjB,QAAQ,UAAU,UAAU,KAAA;CAC9B;AACF;;AAGA,SAAgB,oBACd,cACe;CACf,OAAO,cAAc,aAAa;AACpC;;;;;;AAOA,SAAgB,uBACd,OACA,WACoB;CACpB,IAAI,CAAC,OAAO,OAAO,KAAA;CAEnB,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,KAAK,GAC9C,IAAI,sBAAsB,KAAK,KAAK,MAAM,UAAU,UAAU,OAC5D,OAAO;AAKb;;;;;AAQA,SAAgB,0BACd,UACA,OACA,SAC2B;CAC3B,IAAI,QAAQ,WAAW,GAAG,OAAO,QAAQ,QAAQ,CAAC,CAAC;CAEnD,OAAO,YAA8B,UAAU,EAC7C,OAAO;EAAE;EAAO,SAAS,QAAQ,KAAK,GAAG;EAAG,YAAY,iBAAiB;CAAE,EAC7E,CAAC;AACH;;;;;AAMA,SAAgB,sBAAsB,MAAkB,QAAyC;CAC/F,IAAI,OAAO,SAAS,GAAG,OAAO;CAE9B,IAAI,UAAU;CACd,MAAM,UAAU,KAAK,QAAQ,KAAK,UAAU;EAC1C,IAAI,MAAM,SAAS,WAAW,OAAO;EAErC,MAAM,UAAW,MAAuB;EACxC,MAAM,OAAO,QAAQ,KAAK,WACxB,kBAAkB,MAAM,KAAK,CAAC,OAAO,UAAU,OAAO,IAAI,iBAAiB,MAAM,CAAC,IAC9E;GAAE,GAAG;GAAQ,QAAQ;EAAc,IACnC,MACN;EAEA,IAAI,CAAC,KAAK,MAAM,QAAQ,UAAU,WAAW,QAAQ,MAAM,GAAG,OAAO;EAErE,UAAU;EAEV,OAAO;GAAE,GAAG;GAAO,SAAS;EAAK;CACnC,CAAC;CAED,OAAO,UAAU;EAAE,GAAG;EAAM;CAAQ,IAAI;AAC1C;;;;;AAMA,IAAa,2BAAb,MAAsC;CAS1B;CACS;CATnB;CAEA,QAAkC,CAAC;;CAGnC,yBAAiB,IAAI,IAAY;CAEjC,YACE,WACA,UACA;EAFQ,KAAA,YAAA;EACS,KAAA,WAAA;EAEjB,KAAK,YAAY,IAAI,eAAe,YAAY,KAAK,KAAK,OAAO,CAAC;CACpE;;CAGA,aAAa,WAA4C;EACvD,IAAI,UAAU,UAAU,KAAK,UAAU,OAAO;GAC5C,KAAK,QAAQ,CAAC;GACd,KAAK,yBAAS,IAAI,IAAI;EACxB;EAEA,KAAK,YAAY;CACnB;CAEA,IAAI,OAAmB;EACrB,MAAM,OAAO,qBAAqB,KAAK,SAAS;EAChD,MAAM,WAAW,sBAAsB,MAAM,KAAK,OAAO,oBAAoB,KAAK,MAAM,CAAC;EAEzF,OAAO,sBAAsB,sBAAsB,UAAU,KAAK,KAAK,GAAG,KAAK,MAAM;CACvF;;;;;;;CAQA,SAAS,WAAsD;EAC7D,IAAI,WAAW,KAAK,aAAa,SAAS;EAE1C,OAAO,KAAK,KAAK,KAAK,UAAU,QAAQ,CAAC,CAAC;CAC5C;;CAGA,QAAQ,SAAkC;EACxC,OAAO,KAAK,KAAK,OAAO;CAC1B;;CAGA,KAAK,QAAgB,UAA0D;EAC7E,OAAO,KAAK,UAAU,UAAU,QAAQ,QAAQ;CAClD;CAEA,MAAc,KAAK,SAAkC;EACnD,IAAI,QAAQ,WAAW,GAAG;EAE1B,MAAM,QAAQ,KAAK,UAAU;EAE7B,IAAI;GACF,MAAM,SAAS,MAAM,0BACnB,KAAK,UAAU,UAAU,YACzB,OACA,OACF;GAGA,IAAI,UAAU,KAAK,UAAU,OAAO;GAEpC,KAAK,QAAQ;IAAE,GAAG,KAAK;IAAO,GAAG;GAAO;GACxC,QAAQ,SAAS,WAAW,KAAK,OAAO,OAAO,MAAM,CAAC;GACtD,KAAK,SAAS;EAChB,QAAQ;GAIN,IAAI,UAAU,KAAK,UAAU,OAAO;GAEpC,QAAQ,SAAS,WAAW,KAAK,OAAO,IAAI,MAAM,CAAC;GACnD,KAAK,SAAS;EAChB;CACF;;CAGA,QAAwB;EAGtB,OAAO,GAFM,OAAO,WAAW,cAAc,KAAK,OAAO,SAAS,SAEnD,GAAG,KAAK,UAAU;CACnC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"starter-kit.d.ts","names":[],"sources":["../src/starter-kit.ts"],"mappings":";;;;;;;;;;;;;;qBAmCa;;wBAGG,kBACd,OAAO,KAAK;;iBAMG;EACf;EACA;;;;;;;;wBASc,wBACd,aAAa,wCACZ;;;;;;;;wBAsBa,yBAAyB;wBAMzB,+BAA+B,iBAAiB;;;;;wBAUhD,sBAAsB,iBAAiB;;;;;wBAUvC,yBAAyB;;;;;;wBAWzB,6BAA6B;EAAY;GACvD,SAAS,KACT,iBACC;;;;;wBAWa,qBACd,gBACA,mBACC;;;;;iBAcc;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;;iBAGK;EACf;EACA;EACA;EACA,OAAO;;;wBAmBO,sBACd,QAAQ,6CACP;;;;;;iBAkBc;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;;wBAIc,0BACd,OAAO,sCACN;;;;;;wBA2Ba,gBACd,WAAW,4CACX,WAAW,6CACV;;;;;wBA6Ba,mBAAmB,OAAO,QAAQ;;wBAalC,0BAA0B;;;;;;;;wBAW1B,yBACd,OAAO,cAAc,6BACrB,IAAI,aAAa,eAAe,iCAAiC"}
1
+ {"version":3,"file":"starter-kit.d.ts","names":[],"sources":["../src/starter-kit.ts"],"mappings":";;;;;;;;;;;;;;qBAmCa;;wBAGG,kBACd,OAAO,KAAK;;iBAMG;EACf;EACA;;;;;;;;wBASc,wBACd,aAAa,wCACZ;;;;;;;;wBAsBa,yBAAyB;wBAMzB,+BAA+B,iBAAiB;;;;;wBAUhD,sBAAsB,iBAAiB;;;;;wBAUvC,yBAAyB;;;;;;wBAWzB,6BAA6B;EAAY;GACvD,SAAS,KACT,iBACC;;;;;wBAWa,qBACd,gBACA,mBACC;;;;;iBAcc;EACf;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;;iBAGK;EACf;EACA;EACA;EACA,OAAO;;;wBAqCO,sBACd,QAAQ,6CACP;;;;;;iBAkBc;EACf;EACA;EACA;EACA;EACA;EACA;EACA;;EAEA;;;wBAIc,0BACd,OAAO,sCACN;;;;;;wBA2Ba,gBACd,WAAW,4CACX,WAAW,6CACV;;;;;wBA6Ba,mBAAmB,OAAO,QAAQ;;wBAalC,0BAA0B;;;;;;;;wBAW1B,yBACd,OAAO,cAAc,6BACrB,IAAI,aAAa,eAAe,iCAAiC"}
@@ -99,9 +99,24 @@ function toNavItem(item, key) {
99
99
  badge: item.badge ?? void 0,
100
100
  badgeColor: item.badgeColor ?? void 0,
101
101
  badgeTooltip: item.badgeTooltip ?? void 0,
102
- children: (item.children ?? []).map((child, index) => toNavItem(child, `${key}.${index}`))
102
+ children: toNavItems(item.children ?? [], key)
103
103
  };
104
104
  }
105
+ /**
106
+ * Items keyed by their URL (positional only without one, or for a repeated URL), so a keyed
107
+ * list keeps each sidebar link with its own entry when the labels and their order change,
108
+ * e.g. after a language switch; a reused Inertia `<Link>` kept navigating to the URL of the
109
+ * entry it was created for (W-S4-08).
110
+ */
111
+ function toNavItems(items, parentKey) {
112
+ const seen = /* @__PURE__ */ new Set();
113
+ return items.map((item, index) => {
114
+ const url = item.url ? `url:${item.url}` : null;
115
+ const key = url && !seen.has(url) ? url : `${parentKey}.${index}`;
116
+ seen.add(key);
117
+ return toNavItem(item, key);
118
+ });
119
+ }
105
120
  /** A panel's navigation groups as plain items for the host's sidebar primitives. */
106
121
  function toStarterKitNavGroups(groups) {
107
122
  return (groups ?? []).map((group, index) => {
@@ -110,7 +125,7 @@ function toStarterKitNavGroups(groups) {
110
125
  key,
111
126
  label: group.label ?? null,
112
127
  icon: group.icon ?? void 0,
113
- items: group.items.map((item, itemIndex) => toNavItem(item, `${key}.${itemIndex}`))
128
+ items: toNavItems(group.items, key)
114
129
  };
115
130
  });
116
131
  }
@@ -1 +1 @@
1
- {"version":3,"file":"starter-kit.js","names":[],"sources":["../src/starter-kit.ts"],"sourcesContent":["import { debugRecorder } from './debug'\nimport {\n effectiveErrorNotifications,\n installErrorNotifications,\n resolveRequestErrorNotification,\n setRequestErrorReporter,\n} from './error-notifications'\nimport { reloadOnExpiredPage } from './http'\nimport { navigationItemText } from './navigation'\nimport { installPrefetchInvalidation } from './prefetch'\nimport type { NotificationSchema } from './schema/notifications'\nimport type {\n AlfakitPageProps,\n BreadcrumbSchema,\n ErrorNotificationsSchema,\n MenuItemSchema,\n NavigationGroupSchema,\n NavigationItemSchema,\n PanelSchema,\n StarterKitNavigationSchema,\n} from './schema/panel'\nimport { installSpaUrlExceptions } from './spa'\n\n/**\n * Starter kit mode (`Panel::starterKit()`): Alfakit pages render inside an application made\n * from Laravel's React, Vue or Svelte starter kit, in the host's own layout. These helpers are\n * shared by the three renderers; the host's sidebar reads the navigation through each\n * renderer's `useAlfakitNavigation()`.\n */\n\n/**\n * The sonner toaster Alfakit renders in starter kit mode. The host layout mounts its own\n * id-less `<Toaster />`; giving Alfakit's an id keeps each toast in one toaster even when the\n * host and Alfakit resolve the same sonner instance.\n */\nexport const STARTER_KIT_TOASTER_ID = 'alfakit'\n\n/** Whether the panel runs inside a starter kit application (`Panel::starterKit()`). */\nexport function isStarterKitPanel(\n panel: Pick<PanelSchema, 'starterKit'> | null | undefined,\n): boolean {\n return panel?.starterKit === true\n}\n\n/** A breadcrumb as the starter kits' `AppLayout` takes it (`BreadcrumbItem`). */\nexport interface StarterKitBreadcrumb {\n title: string\n href: string\n}\n\n/**\n * The page's breadcrumbs in the starter kits' shape: `{ title, href }` instead of\n * `{ label, url }`. The kits render the last crumb (the current page) as plain text and key\n * the list by `href`, so every `href` is unique: the last crumb without a URL keeps an empty\n * one, other crumbs without one (or repeating one) get a `#index` fragment.\n */\nexport function toStarterKitBreadcrumbs(\n breadcrumbs: BreadcrumbSchema[] | null | undefined,\n): StarterKitBreadcrumb[] {\n const list = breadcrumbs ?? []\n const seen = new Set<string>()\n\n return list.map((crumb, index) => {\n let href = crumb.url ?? (index === list.length - 1 ? '' : `#${index}`)\n\n if (seen.has(href)) href = `${href}#${index}`\n\n seen.add(href)\n\n return { title: crumb.label, href }\n })\n}\n\n/** The breadcrumbs of an Alfakit page from its Inertia props (`props.page.breadcrumbs`). */\n/**\n * Whether an Alfakit page renders inside the host layout (`createStarterKitPage({ layout })`).\n * Pages with the `auth` layout (sign-in, registration, password reset, multi-factor\n * challenge, email verification, `->login()` brought back after `starterKit()`) stand on\n * their own, like the host's auth pages: the host layout expects a signed-in user.\n */\nexport function usesStarterKitHostLayout(props: unknown): boolean {\n const page = (props as { page?: { layout?: string } } | null | undefined)?.page\n\n return page?.layout !== 'auth'\n}\n\nexport function starterKitBreadcrumbsFromProps(props: unknown): StarterKitBreadcrumb[] {\n const page = (props as { page?: { breadcrumbs?: BreadcrumbSchema[] } } | null | undefined)?.page\n\n return toStarterKitBreadcrumbs(page?.breadcrumbs)\n}\n\n/**\n * The shared `alfakitNavigation` prop of a page: one entry per `starterKit()` panel, or none\n * for guests and pages of other panels.\n */\nexport function starterKitNavigations(props: unknown): StarterKitNavigationSchema[] {\n const value = (props as { alfakitNavigation?: unknown } | null | undefined)?.alfakitNavigation\n\n return Array.isArray(value) ? (value as StarterKitNavigationSchema[]) : []\n}\n\n/**\n * The panel of the open AlfaKit page (`props.alfakit.id`), or undefined on the host's own\n * pages. With several `starterKit()` panels, components without a `panel` follow it.\n */\nexport function currentStarterKitPanelId(props: unknown): string | undefined {\n const id = (props as { alfakit?: { id?: unknown } } | null | undefined)?.alfakit?.id\n\n return typeof id === 'string' ? id : undefined\n}\n\n/**\n * Order panel entries for components without a `panel` id: the open AlfaKit page's panel\n * first (an `/app` page of a second `starterKit()` panel shows that panel's navigation and\n * tenant menu), then the others in registration order.\n */\nexport function preferCurrentStarterKitPanel<T extends { id: string }>(\n entries: T[],\n props: unknown,\n): T[] {\n const current = currentStarterKitPanelId(props)\n const index = current === undefined ? -1 : entries.findIndex((entry) => entry.id === current)\n\n return index <= 0 ? entries : [entries[index]!, ...entries.filter((_, i) => i !== index)]\n}\n\n/**\n * One panel's navigation, or null. Without an id: the open AlfaKit page's panel, else the\n * first starter kit panel.\n */\nexport function starterKitNavigation(\n props: unknown,\n panelId?: string,\n): StarterKitNavigationSchema | null {\n const navigations = starterKitNavigations(props)\n\n return (\n (panelId === undefined\n ? preferCurrentStarterKitPanel(navigations, props)[0]\n : navigations.find((navigation) => navigation.id === panelId)) ?? null\n )\n}\n\n/**\n * A navigation item flattened for a starter kit sidebar: the label as text and the item's\n * nested children. `title`/`href` match the kits' `NavItem` so the host can reuse its types.\n */\nexport interface StarterKitNavItem {\n key: string\n title: string\n href: string\n icon?: string\n isActive: boolean\n openInNewTab: boolean\n badge?: string | number\n badgeColor?: string\n badgeTooltip?: string\n children: StarterKitNavItem[]\n}\n\nexport interface StarterKitNavGroup {\n key: string\n label: string | null\n icon?: string\n items: StarterKitNavItem[]\n}\n\nfunction toNavItem(item: NavigationItemSchema, key: string): StarterKitNavItem {\n return {\n key,\n title: navigationItemText(item),\n href: item.url ?? '',\n icon: item.icon ?? undefined,\n isActive: item.active === true,\n openInNewTab: item.openInNewTab === true,\n badge: item.badge ?? undefined,\n badgeColor: item.badgeColor ?? undefined,\n badgeTooltip: item.badgeTooltip ?? undefined,\n children: (item.children ?? []).map((child, index) => toNavItem(child, `${key}.${index}`)),\n }\n}\n\n/** A panel's navigation groups as plain items for the host's sidebar primitives. */\nexport function toStarterKitNavGroups(\n groups: NavigationGroupSchema[] | null | undefined,\n): StarterKitNavGroup[] {\n return (groups ?? []).map((group, index) => {\n const key = `${index}:${group.label ?? ''}`\n\n return {\n key,\n label: group.label ?? null,\n icon: group.icon ?? undefined,\n items: group.items.map((item, itemIndex) => toNavItem(item, `${key}.${itemIndex}`)),\n }\n })\n}\n\n/**\n * A user menu entry of a `starterKit()` panel for the kit's own dropdown items\n * (`<AlfakitUserMenuItems />`). `group` changes where a separator goes; `method: 'post'`\n * links are sent as POST (like the kit's Log out).\n */\nexport interface StarterKitUserMenuItem {\n key: string\n title: string\n href: string\n icon?: string\n openInNewTab: boolean\n method?: 'post'\n group: number\n /** Whether a separator goes before this entry (it starts a new group). */\n startsGroup: boolean\n}\n\n/** A panel's shared user menu links as plain items for the host's dropdown primitives. */\nexport function toStarterKitUserMenuItems(\n items: MenuItemSchema[] | null | undefined,\n): StarterKitUserMenuItem[] {\n const links = (items ?? []).filter(\n (item): item is MenuItemSchema & { url: string } =>\n typeof item.url === 'string' && item.url !== '' && item.action === undefined,\n )\n\n return links.map((item, index) => {\n const group = item.group ?? 0\n\n return {\n key: `${index}:${item.label ?? ''}`,\n title: item.label ?? item.url,\n href: item.url,\n icon: item.icon ?? undefined,\n openInNewTab: item.openInNewTab === true,\n method: item.method,\n group,\n startsGroup: index > 0 && group !== (links[index - 1]!.group ?? 0),\n }\n })\n}\n\n/**\n * The props of a custom page component (`Page::$component`) rendered by `Alfakit/Page` (a\n * starter kit resolves only that page): its view data, with the page's live top-level props\n * of the same names, so a partial reload (`reloadProps('items')`) reaches the component.\n */\nexport function customPageProps(\n viewProps: Record<string, unknown> | null | undefined,\n pageProps: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...(viewProps ?? {}) }\n\n for (const key of Object.keys(merged)) {\n if (pageProps && Object.prototype.hasOwnProperty.call(pageProps, key)) {\n merged[key] = pageProps[key]\n }\n }\n\n return merged\n}\n\n// ---------------------------------------------------------------- Browser runtime\n\n/**\n * What the browser-wide listeners of starter kit mode read: the open Alfakit page's panel\n * and error notifications, or nothing while a page of the host application is open (its\n * errors keep Inertia's default handling).\n */\nconst runtime: {\n panel: PanelSchema | null\n errorNotifications: ErrorNotificationsSchema | false | undefined\n installed: boolean\n} = { panel: null, errorNotifications: undefined, installed: false }\n\n/**\n * Record the Alfakit page being rendered (called by each renderer's starter kit root while\n * it renders); `null` once the root unmounts, when a host page opens.\n */\nexport function syncStarterKitPage(props: Partial<AlfakitPageProps> | null): void {\n const panel = props?.alfakit ?? null\n\n runtime.panel = panel\n runtime.errorNotifications = panel ? effectiveErrorNotifications(panel, props?.page) : undefined\n\n if (typeof window !== 'undefined') {\n // Internal requests and form stores are recorded only while the page carries the debug prop.\n debugRecorder.setEnabled(props?.alfakitDebug !== undefined)\n }\n}\n\n/** The panel of the open Alfakit page in starter kit mode, or null on host pages. */\nexport function currentStarterKitPanel(): PanelSchema | null {\n return runtime.panel\n}\n\n/**\n * The listeners `createAlfakit()` installs, once per page load, for starter kit mode: reload\n * on an expired CSRF token (419), the panel's error notifications instead of Inertia's error\n * modal, a notification for failed internal requests, the panel's SPA URL exceptions, and\n * dropping prefetched pages after a save (and reloading pages Back restores after one).\n * Returns false when they were already installed (or on the server).\n */\nexport function installStarterKitRuntime(\n show: (notification: NotificationSchema) => void,\n t: (key: string, replacements?: Record<string, string | number>, fallback?: string) => string,\n): boolean {\n if (typeof window === 'undefined' || runtime.installed) return false\n\n runtime.installed = true\n\n reloadOnExpiredPage()\n installPrefetchInvalidation()\n installSpaUrlExceptions(() => runtime.panel)\n installErrorNotifications(() => ({ errorNotifications: runtime.errorNotifications }), show)\n setRequestErrorReporter((error) => {\n const notification = resolveRequestErrorNotification(error, runtime.errorNotifications, t)\n\n if (notification) show(notification)\n })\n\n return true\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"starter-kit.js","names":[],"sources":["../src/starter-kit.ts"],"sourcesContent":["import { debugRecorder } from './debug'\nimport {\n effectiveErrorNotifications,\n installErrorNotifications,\n resolveRequestErrorNotification,\n setRequestErrorReporter,\n} from './error-notifications'\nimport { reloadOnExpiredPage } from './http'\nimport { navigationItemText } from './navigation'\nimport { installPrefetchInvalidation } from './prefetch'\nimport type { NotificationSchema } from './schema/notifications'\nimport type {\n AlfakitPageProps,\n BreadcrumbSchema,\n ErrorNotificationsSchema,\n MenuItemSchema,\n NavigationGroupSchema,\n NavigationItemSchema,\n PanelSchema,\n StarterKitNavigationSchema,\n} from './schema/panel'\nimport { installSpaUrlExceptions } from './spa'\n\n/**\n * Starter kit mode (`Panel::starterKit()`): Alfakit pages render inside an application made\n * from Laravel's React, Vue or Svelte starter kit, in the host's own layout. These helpers are\n * shared by the three renderers; the host's sidebar reads the navigation through each\n * renderer's `useAlfakitNavigation()`.\n */\n\n/**\n * The sonner toaster Alfakit renders in starter kit mode. The host layout mounts its own\n * id-less `<Toaster />`; giving Alfakit's an id keeps each toast in one toaster even when the\n * host and Alfakit resolve the same sonner instance.\n */\nexport const STARTER_KIT_TOASTER_ID = 'alfakit'\n\n/** Whether the panel runs inside a starter kit application (`Panel::starterKit()`). */\nexport function isStarterKitPanel(\n panel: Pick<PanelSchema, 'starterKit'> | null | undefined,\n): boolean {\n return panel?.starterKit === true\n}\n\n/** A breadcrumb as the starter kits' `AppLayout` takes it (`BreadcrumbItem`). */\nexport interface StarterKitBreadcrumb {\n title: string\n href: string\n}\n\n/**\n * The page's breadcrumbs in the starter kits' shape: `{ title, href }` instead of\n * `{ label, url }`. The kits render the last crumb (the current page) as plain text and key\n * the list by `href`, so every `href` is unique: the last crumb without a URL keeps an empty\n * one, other crumbs without one (or repeating one) get a `#index` fragment.\n */\nexport function toStarterKitBreadcrumbs(\n breadcrumbs: BreadcrumbSchema[] | null | undefined,\n): StarterKitBreadcrumb[] {\n const list = breadcrumbs ?? []\n const seen = new Set<string>()\n\n return list.map((crumb, index) => {\n let href = crumb.url ?? (index === list.length - 1 ? '' : `#${index}`)\n\n if (seen.has(href)) href = `${href}#${index}`\n\n seen.add(href)\n\n return { title: crumb.label, href }\n })\n}\n\n/** The breadcrumbs of an Alfakit page from its Inertia props (`props.page.breadcrumbs`). */\n/**\n * Whether an Alfakit page renders inside the host layout (`createStarterKitPage({ layout })`).\n * Pages with the `auth` layout (sign-in, registration, password reset, multi-factor\n * challenge, email verification, `->login()` brought back after `starterKit()`) stand on\n * their own, like the host's auth pages: the host layout expects a signed-in user.\n */\nexport function usesStarterKitHostLayout(props: unknown): boolean {\n const page = (props as { page?: { layout?: string } } | null | undefined)?.page\n\n return page?.layout !== 'auth'\n}\n\nexport function starterKitBreadcrumbsFromProps(props: unknown): StarterKitBreadcrumb[] {\n const page = (props as { page?: { breadcrumbs?: BreadcrumbSchema[] } } | null | undefined)?.page\n\n return toStarterKitBreadcrumbs(page?.breadcrumbs)\n}\n\n/**\n * The shared `alfakitNavigation` prop of a page: one entry per `starterKit()` panel, or none\n * for guests and pages of other panels.\n */\nexport function starterKitNavigations(props: unknown): StarterKitNavigationSchema[] {\n const value = (props as { alfakitNavigation?: unknown } | null | undefined)?.alfakitNavigation\n\n return Array.isArray(value) ? (value as StarterKitNavigationSchema[]) : []\n}\n\n/**\n * The panel of the open AlfaKit page (`props.alfakit.id`), or undefined on the host's own\n * pages. With several `starterKit()` panels, components without a `panel` follow it.\n */\nexport function currentStarterKitPanelId(props: unknown): string | undefined {\n const id = (props as { alfakit?: { id?: unknown } } | null | undefined)?.alfakit?.id\n\n return typeof id === 'string' ? id : undefined\n}\n\n/**\n * Order panel entries for components without a `panel` id: the open AlfaKit page's panel\n * first (an `/app` page of a second `starterKit()` panel shows that panel's navigation and\n * tenant menu), then the others in registration order.\n */\nexport function preferCurrentStarterKitPanel<T extends { id: string }>(\n entries: T[],\n props: unknown,\n): T[] {\n const current = currentStarterKitPanelId(props)\n const index = current === undefined ? -1 : entries.findIndex((entry) => entry.id === current)\n\n return index <= 0 ? entries : [entries[index]!, ...entries.filter((_, i) => i !== index)]\n}\n\n/**\n * One panel's navigation, or null. Without an id: the open AlfaKit page's panel, else the\n * first starter kit panel.\n */\nexport function starterKitNavigation(\n props: unknown,\n panelId?: string,\n): StarterKitNavigationSchema | null {\n const navigations = starterKitNavigations(props)\n\n return (\n (panelId === undefined\n ? preferCurrentStarterKitPanel(navigations, props)[0]\n : navigations.find((navigation) => navigation.id === panelId)) ?? null\n )\n}\n\n/**\n * A navigation item flattened for a starter kit sidebar: the label as text and the item's\n * nested children. `title`/`href` match the kits' `NavItem` so the host can reuse its types.\n */\nexport interface StarterKitNavItem {\n key: string\n title: string\n href: string\n icon?: string\n isActive: boolean\n openInNewTab: boolean\n badge?: string | number\n badgeColor?: string\n badgeTooltip?: string\n children: StarterKitNavItem[]\n}\n\nexport interface StarterKitNavGroup {\n key: string\n label: string | null\n icon?: string\n items: StarterKitNavItem[]\n}\n\nfunction toNavItem(item: NavigationItemSchema, key: string): StarterKitNavItem {\n return {\n key,\n title: navigationItemText(item),\n href: item.url ?? '',\n icon: item.icon ?? undefined,\n isActive: item.active === true,\n openInNewTab: item.openInNewTab === true,\n badge: item.badge ?? undefined,\n badgeColor: item.badgeColor ?? undefined,\n badgeTooltip: item.badgeTooltip ?? undefined,\n children: toNavItems(item.children ?? [], key),\n }\n}\n\n/**\n * Items keyed by their URL (positional only without one, or for a repeated URL), so a keyed\n * list keeps each sidebar link with its own entry when the labels and their order change,\n * e.g. after a language switch; a reused Inertia `<Link>` kept navigating to the URL of the\n * entry it was created for (W-S4-08).\n */\nfunction toNavItems(items: NavigationItemSchema[], parentKey: string): StarterKitNavItem[] {\n const seen = new Set<string>()\n\n return items.map((item, index) => {\n const url = item.url ? `url:${item.url}` : null\n const key = url && !seen.has(url) ? url : `${parentKey}.${index}`\n seen.add(key)\n\n return toNavItem(item, key)\n })\n}\n\n/** A panel's navigation groups as plain items for the host's sidebar primitives. */\nexport function toStarterKitNavGroups(\n groups: NavigationGroupSchema[] | null | undefined,\n): StarterKitNavGroup[] {\n return (groups ?? []).map((group, index) => {\n const key = `${index}:${group.label ?? ''}`\n\n return {\n key,\n label: group.label ?? null,\n icon: group.icon ?? undefined,\n items: toNavItems(group.items, key),\n }\n })\n}\n\n/**\n * A user menu entry of a `starterKit()` panel for the kit's own dropdown items\n * (`<AlfakitUserMenuItems />`). `group` changes where a separator goes; `method: 'post'`\n * links are sent as POST (like the kit's Log out).\n */\nexport interface StarterKitUserMenuItem {\n key: string\n title: string\n href: string\n icon?: string\n openInNewTab: boolean\n method?: 'post'\n group: number\n /** Whether a separator goes before this entry (it starts a new group). */\n startsGroup: boolean\n}\n\n/** A panel's shared user menu links as plain items for the host's dropdown primitives. */\nexport function toStarterKitUserMenuItems(\n items: MenuItemSchema[] | null | undefined,\n): StarterKitUserMenuItem[] {\n const links = (items ?? []).filter(\n (item): item is MenuItemSchema & { url: string } =>\n typeof item.url === 'string' && item.url !== '' && item.action === undefined,\n )\n\n return links.map((item, index) => {\n const group = item.group ?? 0\n\n return {\n key: `${index}:${item.label ?? ''}`,\n title: item.label ?? item.url,\n href: item.url,\n icon: item.icon ?? undefined,\n openInNewTab: item.openInNewTab === true,\n method: item.method,\n group,\n startsGroup: index > 0 && group !== (links[index - 1]!.group ?? 0),\n }\n })\n}\n\n/**\n * The props of a custom page component (`Page::$component`) rendered by `Alfakit/Page` (a\n * starter kit resolves only that page): its view data, with the page's live top-level props\n * of the same names, so a partial reload (`reloadProps('items')`) reaches the component.\n */\nexport function customPageProps(\n viewProps: Record<string, unknown> | null | undefined,\n pageProps: Record<string, unknown> | null | undefined,\n): Record<string, unknown> {\n const merged: Record<string, unknown> = { ...(viewProps ?? {}) }\n\n for (const key of Object.keys(merged)) {\n if (pageProps && Object.prototype.hasOwnProperty.call(pageProps, key)) {\n merged[key] = pageProps[key]\n }\n }\n\n return merged\n}\n\n// ---------------------------------------------------------------- Browser runtime\n\n/**\n * What the browser-wide listeners of starter kit mode read: the open Alfakit page's panel\n * and error notifications, or nothing while a page of the host application is open (its\n * errors keep Inertia's default handling).\n */\nconst runtime: {\n panel: PanelSchema | null\n errorNotifications: ErrorNotificationsSchema | false | undefined\n installed: boolean\n} = { panel: null, errorNotifications: undefined, installed: false }\n\n/**\n * Record the Alfakit page being rendered (called by each renderer's starter kit root while\n * it renders); `null` once the root unmounts, when a host page opens.\n */\nexport function syncStarterKitPage(props: Partial<AlfakitPageProps> | null): void {\n const panel = props?.alfakit ?? null\n\n runtime.panel = panel\n runtime.errorNotifications = panel ? effectiveErrorNotifications(panel, props?.page) : undefined\n\n if (typeof window !== 'undefined') {\n // Internal requests and form stores are recorded only while the page carries the debug prop.\n debugRecorder.setEnabled(props?.alfakitDebug !== undefined)\n }\n}\n\n/** The panel of the open Alfakit page in starter kit mode, or null on host pages. */\nexport function currentStarterKitPanel(): PanelSchema | null {\n return runtime.panel\n}\n\n/**\n * The listeners `createAlfakit()` installs, once per page load, for starter kit mode: reload\n * on an expired CSRF token (419), the panel's error notifications instead of Inertia's error\n * modal, a notification for failed internal requests, the panel's SPA URL exceptions, and\n * dropping prefetched pages after a save (and reloading pages Back restores after one).\n * Returns false when they were already installed (or on the server).\n */\nexport function installStarterKitRuntime(\n show: (notification: NotificationSchema) => void,\n t: (key: string, replacements?: Record<string, string | number>, fallback?: string) => string,\n): boolean {\n if (typeof window === 'undefined' || runtime.installed) return false\n\n runtime.installed = true\n\n reloadOnExpiredPage()\n installPrefetchInvalidation()\n installSpaUrlExceptions(() => runtime.panel)\n installErrorNotifications(() => ({ errorNotifications: runtime.errorNotifications }), show)\n setRequestErrorReporter((error) => {\n const notification = resolveRequestErrorNotification(error, runtime.errorNotifications, t)\n\n if (notification) show(notification)\n })\n\n return true\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"theme.d.ts","names":[],"sources":["../src/theme.ts"],"mappings":";;YAGY;;;;;wBAQI,oBAAoB,WAAU,aAAwB;;;;;wBAkBtD,gBAAgB,QAAQ,KAAK,kCAAkC;wBAM/D,kBAAkB,YAAY;wBAU9B,gBACd,YAAY,YACZ,QAAQ,KAAK;wBAgBC,cAAc,YAAY,YAAY,QAAQ,KAAK;;wBAenD,qBAAqB,QAAQ,KAAK;;;;;;;wBAyBlC,mBAAmB,oCAAoC;;qBAO1D;;;;;wBAcG,kBAAkB,mCAAmC;;;;;;wBAarD,mBAAmB,eAAe;;;;;;wBASlC,iBACd,QAAQ,oCACR;;;;;;wBAqDc,gBACd,QACK,KAAK,0CACJ,QAAQ,KAAK;;;;;wBA+DL,kBAAkB;qBA0BrB"}
1
+ {"version":3,"file":"theme.d.ts","names":[],"sources":["../src/theme.ts"],"mappings":";;YAGY;;;;;wBAQI,oBAAoB,WAAU,aAAwB;;;;;wBAkBtD,gBAAgB,QAAQ,KAAK,kCAAkC;wBAM/D,kBAAkB,YAAY;wBAU9B,gBACd,YAAY,YACZ,QAAQ,KAAK;wBAgBC,cAAc,YAAY,YAAY,QAAQ,KAAK;;wBAenD,qBAAqB,QAAQ,KAAK;;;;;;;wBAyBlC,mBAAmB,oCAAoC;;qBAO1D;;;;;wBAcG,kBAAkB,mCAAmC;;;;;;wBAarD,mBAAmB,eAAe;;;;;;wBASlC,iBACd,QAAQ,oCACR;;;;;;wBAqDc,gBACd,QACK,KAAK,0CACJ,QAAQ,KAAK;;;;;wBAqEL,kBAAkB;qBA0BrB"}
package/dist/theme.js CHANGED
@@ -124,8 +124,9 @@ function applyColorCss(css) {
124
124
  * panel id, radius, font and the font stylesheet.
125
125
  */
126
126
  function applyPanelTheme(panel) {
127
- if (typeof document === "undefined" || !panel?.theme) return;
127
+ if (!panel?.theme) return;
128
128
  setIconAliases(panel.theme.icons);
129
+ if (typeof document === "undefined") return;
129
130
  if (panel.starterKit === true) {
130
131
  applyPanelColors(panel.colors, panel.colorCss);
131
132
  return;
package/dist/theme.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"theme.js","names":[],"sources":["../src/theme.ts"],"sourcesContent":["import { setIconAliases } from './icons'\nimport type { PanelSchema } from './schema/panel'\n\nexport type Appearance = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'alfakit_appearance'\n\n/**\n * The colour scheme the user picked with the switcher, or `fallback` when they never\n * picked one (the panel's `defaultThemeMode()`).\n */\nexport function getStoredAppearance(fallback: Appearance = 'system'): Appearance {\n if (typeof localStorage === 'undefined') {\n return fallback\n }\n\n try {\n const value = localStorage.getItem(STORAGE_KEY)\n\n return value === 'light' || value === 'dark' || value === 'system' ? value : fallback\n } catch {\n return fallback\n }\n}\n\n/**\n * The colour scheme to use for a panel: the user's choice, else `defaultThemeMode()`.\n * Without the switcher (`themeSwitcher(false)`) always the default.\n */\nexport function panelAppearance(panel?: Pick<PanelSchema, 'darkMode'> | null): Appearance {\n const fallback: Appearance = panel?.darkMode.default ?? 'system'\n\n return panel?.darkMode.switcher === false ? fallback : getStoredAppearance(fallback)\n}\n\nexport function resolveAppearance(appearance: Appearance): 'light' | 'dark' {\n if (appearance !== 'system') {\n return appearance\n }\n\n return typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n}\n\nexport function applyAppearance(\n appearance: Appearance,\n panel?: Pick<PanelSchema, 'darkMode'>,\n): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const resolved = panel?.darkMode.forced\n ? 'dark'\n : panel?.darkMode.enabled === false\n ? 'light'\n : resolveAppearance(appearance)\n\n document.documentElement.classList.toggle('dark', resolved === 'dark')\n document.documentElement.style.colorScheme = resolved\n}\n\nexport function setAppearance(appearance: Appearance, panel?: Pick<PanelSchema, 'darkMode'>): void {\n try {\n localStorage.setItem(STORAGE_KEY, appearance)\n } catch {\n // Storage may be unavailable (private mode); the cookie still persists the choice.\n }\n\n if (typeof document !== 'undefined') {\n document.cookie = `${STORAGE_KEY}=${appearance};path=/;max-age=31536000;SameSite=Lax`\n }\n\n applyAppearance(appearance, panel)\n}\n\n/** Apply stored appearance on boot and follow OS changes while in `system` mode. */\nexport function initializeAppearance(panel?: Pick<PanelSchema, 'darkMode'>): () => void {\n applyAppearance(panelAppearance(panel), panel)\n\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const media = window.matchMedia('(prefers-color-scheme: dark)')\n const listener = () => {\n if (panelAppearance(panel) === 'system') {\n applyAppearance('system', panel)\n }\n }\n\n media.addEventListener('change', listener)\n\n return () => media.removeEventListener('change', listener)\n}\n\n/**\n * Whether the sidebar starts expanded, from the stored state the server shares\n * (`alfakitSidebarOpen`): the `rail` layout starts as icons until the user expands it,\n * other layouts start open until the user collapses them. Server and browser agree, so\n * server-rendered markup hydrates without a mismatch.\n */\nexport function sidebarDefaultOpen(stored: boolean | null | undefined, layout?: string): boolean {\n return layout === 'rail' ? stored === true : stored !== false\n}\n\nlet appliedColors: string[] = []\n\n/** Colour names every renderer maps to its own classes (`HasTheme::BUILT_IN_COLORS`). */\nexport const BUILT_IN_COLORS = [\n 'primary',\n 'secondary',\n 'success',\n 'warning',\n 'danger',\n 'info',\n 'gray',\n] as const\n\n/**\n * Whether a colour is a custom panel colour name (`colors(['brand' => …])`), as opposed to\n * a built-in name or a CSS value such as `#4f46e5`.\n */\nexport function isCustomColorName(color: string | null | undefined): color is string {\n return (\n typeof color === 'string' &&\n /^[a-z][a-z0-9-]*$/i.test(color) &&\n !(BUILT_IN_COLORS as readonly string[]).includes(color)\n )\n}\n\n/**\n * Classes for a custom colour name: the generic utility of themes.css (`alfakit-solid`,\n * `alfakit-soft`, …) plus the server's `alfakit-color-{name}` rule pointing it at\n * `--alfakit-{name}`.\n */\nexport function customColorClasses(color: string, utility: string): string {\n return `${utility} alfakit-color-${color}`\n}\n\n/**\n * Inject the panel's `colors([...])` as CSS variables consumed by the adapter stylesheet\n * (`--alfakit-primary`, …). Any CSS colour works; shadcn tokens are derived in CSS.\n * Colours of a previously visited panel are removed.\n */\nexport function applyPanelColors(\n colors: Record<string, string> | undefined,\n colorCss?: string | null,\n): void {\n if (typeof document === 'undefined') {\n return\n }\n\n // `colors(['gray' => …])` tints the neutral tokens (themes.css).\n if (colors?.gray) document.documentElement.setAttribute('data-alfakit-gray', 'true')\n else document.documentElement.removeAttribute('data-alfakit-gray')\n\n applyColorCss(colorCss)\n\n const style = document.documentElement.style\n const names = Object.keys(colors ?? {})\n\n for (const name of appliedColors) {\n if (!names.includes(name)) style.removeProperty(`--alfakit-${name}`)\n }\n\n for (const [name, value] of Object.entries(colors ?? {})) {\n style.setProperty(`--alfakit-${name}`, value)\n }\n\n appliedColors = names\n}\n\n/**\n * Keep the `<style data-alfakit-colors>` element (rendered by the root view) in sync with\n * the panel's custom colour classes (`PanelSchema.colorCss`, built by the server).\n */\nfunction applyColorCss(css: string | null | undefined): void {\n let element = document.head.querySelector<HTMLStyleElement>('style[data-alfakit-colors]')\n\n if (!css) {\n element?.remove()\n\n return\n }\n\n if (!element) {\n element = document.createElement('style')\n element.setAttribute('data-alfakit-colors', '')\n document.head.appendChild(element)\n }\n\n if (element.textContent !== css) element.textContent = css\n}\n\n/**\n * Keep `<html>` in sync with the current panel's theme after Inertia visits (the root\n * view sets the same attributes for the first paint): theme, sidebar style, density,\n * panel id, radius, font and the font stylesheet.\n */\nexport function applyPanelTheme(\n panel:\n | (Pick<PanelSchema, 'id' | 'theme' | 'colors'> &\n Partial<Pick<PanelSchema, 'favicon' | 'colorCss' | 'starterKit'>>)\n | undefined,\n): void {\n if (typeof document === 'undefined' || !panel?.theme) {\n return\n }\n\n setIconAliases(panel.theme.icons)\n\n // `starterKit()`: the host application owns the document's theme (its shadcn tokens,\n // fonts, favicon and colour scheme). Only the `--alfakit-*` colours are set; the starter kit\n // stylesheet (`standalone.css`) uses them for the status colours shadcn has no tokens for.\n if (panel.starterKit === true) {\n applyPanelColors(panel.colors, panel.colorCss)\n\n return\n }\n\n applyPanelFavicon(panel.favicon)\n\n const root = document.documentElement\n const { theme } = panel\n const attributes: Record<string, string> = {\n 'data-panel': panel.id,\n 'data-alfakit-theme': theme.attribute,\n 'data-sidebar-variant': theme.sidebar,\n 'data-density': theme.density,\n }\n\n for (const [dimension, value] of Object.entries(theme.design ?? {})) {\n attributes[`data-${dimension}`] = value\n }\n\n for (const [name, value] of Object.entries(attributes)) {\n if (root.getAttribute(name) !== value) root.setAttribute(name, value)\n }\n\n if (theme.radius) root.style.setProperty('--radius', theme.radius)\n else root.style.removeProperty('--radius')\n\n if (theme.fontCss) root.style.setProperty('--alfakit-font', theme.fontCss)\n else root.style.removeProperty('--alfakit-font')\n\n if (theme.headingFontCss) root.style.setProperty('--alfakit-heading-font', theme.headingFontCss)\n else root.style.removeProperty('--alfakit-heading-font')\n\n applyPanelColors(panel.colors, panel.colorCss)\n\n for (const url of theme.fontUrls ?? (theme.fontUrl ? [theme.fontUrl] : [])) {\n if (!document.querySelector(`link[rel=\"stylesheet\"][href=\"${CSS.escape(url)}\"]`)) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = url\n link.dataset.alfakitFont = ''\n document.head.appendChild(link)\n }\n }\n}\n\n/**\n * Point `<link rel=\"icon\">` at the panel's favicon after Inertia visits (switching\n * panels or tenants), adding the link when the first page had none.\n */\nexport function applyPanelFavicon(url: string | null | undefined): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const links = [...document.querySelectorAll<HTMLLinkElement>('link[rel~=\"icon\"]')]\n\n if (!url) {\n for (const link of links) if (link.dataset.alfakitFavicon !== undefined) link.remove()\n\n return\n }\n\n if (links.length === 0) {\n const link = document.createElement('link')\n link.rel = 'icon'\n link.dataset.alfakitFavicon = ''\n document.head.appendChild(link)\n links.push(link)\n }\n\n for (const link of links) {\n if (link.getAttribute('href') !== url) link.setAttribute('href', url)\n }\n}\n\nexport const SEMANTIC_COLORS = [\n 'primary',\n 'secondary',\n 'success',\n 'warning',\n 'danger',\n 'info',\n 'gray',\n] as const\n"],"mappings":"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"}
1
+ {"version":3,"file":"theme.js","names":[],"sources":["../src/theme.ts"],"sourcesContent":["import { setIconAliases } from './icons'\nimport type { PanelSchema } from './schema/panel'\n\nexport type Appearance = 'light' | 'dark' | 'system'\n\nconst STORAGE_KEY = 'alfakit_appearance'\n\n/**\n * The colour scheme the user picked with the switcher, or `fallback` when they never\n * picked one (the panel's `defaultThemeMode()`).\n */\nexport function getStoredAppearance(fallback: Appearance = 'system'): Appearance {\n if (typeof localStorage === 'undefined') {\n return fallback\n }\n\n try {\n const value = localStorage.getItem(STORAGE_KEY)\n\n return value === 'light' || value === 'dark' || value === 'system' ? value : fallback\n } catch {\n return fallback\n }\n}\n\n/**\n * The colour scheme to use for a panel: the user's choice, else `defaultThemeMode()`.\n * Without the switcher (`themeSwitcher(false)`) always the default.\n */\nexport function panelAppearance(panel?: Pick<PanelSchema, 'darkMode'> | null): Appearance {\n const fallback: Appearance = panel?.darkMode.default ?? 'system'\n\n return panel?.darkMode.switcher === false ? fallback : getStoredAppearance(fallback)\n}\n\nexport function resolveAppearance(appearance: Appearance): 'light' | 'dark' {\n if (appearance !== 'system') {\n return appearance\n }\n\n return typeof window !== 'undefined' && window.matchMedia('(prefers-color-scheme: dark)').matches\n ? 'dark'\n : 'light'\n}\n\nexport function applyAppearance(\n appearance: Appearance,\n panel?: Pick<PanelSchema, 'darkMode'>,\n): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const resolved = panel?.darkMode.forced\n ? 'dark'\n : panel?.darkMode.enabled === false\n ? 'light'\n : resolveAppearance(appearance)\n\n document.documentElement.classList.toggle('dark', resolved === 'dark')\n document.documentElement.style.colorScheme = resolved\n}\n\nexport function setAppearance(appearance: Appearance, panel?: Pick<PanelSchema, 'darkMode'>): void {\n try {\n localStorage.setItem(STORAGE_KEY, appearance)\n } catch {\n // Storage may be unavailable (private mode); the cookie still persists the choice.\n }\n\n if (typeof document !== 'undefined') {\n document.cookie = `${STORAGE_KEY}=${appearance};path=/;max-age=31536000;SameSite=Lax`\n }\n\n applyAppearance(appearance, panel)\n}\n\n/** Apply stored appearance on boot and follow OS changes while in `system` mode. */\nexport function initializeAppearance(panel?: Pick<PanelSchema, 'darkMode'>): () => void {\n applyAppearance(panelAppearance(panel), panel)\n\n if (typeof window === 'undefined') {\n return () => {}\n }\n\n const media = window.matchMedia('(prefers-color-scheme: dark)')\n const listener = () => {\n if (panelAppearance(panel) === 'system') {\n applyAppearance('system', panel)\n }\n }\n\n media.addEventListener('change', listener)\n\n return () => media.removeEventListener('change', listener)\n}\n\n/**\n * Whether the sidebar starts expanded, from the stored state the server shares\n * (`alfakitSidebarOpen`): the `rail` layout starts as icons until the user expands it,\n * other layouts start open until the user collapses them. Server and browser agree, so\n * server-rendered markup hydrates without a mismatch.\n */\nexport function sidebarDefaultOpen(stored: boolean | null | undefined, layout?: string): boolean {\n return layout === 'rail' ? stored === true : stored !== false\n}\n\nlet appliedColors: string[] = []\n\n/** Colour names every renderer maps to its own classes (`HasTheme::BUILT_IN_COLORS`). */\nexport const BUILT_IN_COLORS = [\n 'primary',\n 'secondary',\n 'success',\n 'warning',\n 'danger',\n 'info',\n 'gray',\n] as const\n\n/**\n * Whether a colour is a custom panel colour name (`colors(['brand' => …])`), as opposed to\n * a built-in name or a CSS value such as `#4f46e5`.\n */\nexport function isCustomColorName(color: string | null | undefined): color is string {\n return (\n typeof color === 'string' &&\n /^[a-z][a-z0-9-]*$/i.test(color) &&\n !(BUILT_IN_COLORS as readonly string[]).includes(color)\n )\n}\n\n/**\n * Classes for a custom colour name: the generic utility of themes.css (`alfakit-solid`,\n * `alfakit-soft`, …) plus the server's `alfakit-color-{name}` rule pointing it at\n * `--alfakit-{name}`.\n */\nexport function customColorClasses(color: string, utility: string): string {\n return `${utility} alfakit-color-${color}`\n}\n\n/**\n * Inject the panel's `colors([...])` as CSS variables consumed by the adapter stylesheet\n * (`--alfakit-primary`, …). Any CSS colour works; shadcn tokens are derived in CSS.\n * Colours of a previously visited panel are removed.\n */\nexport function applyPanelColors(\n colors: Record<string, string> | undefined,\n colorCss?: string | null,\n): void {\n if (typeof document === 'undefined') {\n return\n }\n\n // `colors(['gray' => …])` tints the neutral tokens (themes.css).\n if (colors?.gray) document.documentElement.setAttribute('data-alfakit-gray', 'true')\n else document.documentElement.removeAttribute('data-alfakit-gray')\n\n applyColorCss(colorCss)\n\n const style = document.documentElement.style\n const names = Object.keys(colors ?? {})\n\n for (const name of appliedColors) {\n if (!names.includes(name)) style.removeProperty(`--alfakit-${name}`)\n }\n\n for (const [name, value] of Object.entries(colors ?? {})) {\n style.setProperty(`--alfakit-${name}`, value)\n }\n\n appliedColors = names\n}\n\n/**\n * Keep the `<style data-alfakit-colors>` element (rendered by the root view) in sync with\n * the panel's custom colour classes (`PanelSchema.colorCss`, built by the server).\n */\nfunction applyColorCss(css: string | null | undefined): void {\n let element = document.head.querySelector<HTMLStyleElement>('style[data-alfakit-colors]')\n\n if (!css) {\n element?.remove()\n\n return\n }\n\n if (!element) {\n element = document.createElement('style')\n element.setAttribute('data-alfakit-colors', '')\n document.head.appendChild(element)\n }\n\n if (element.textContent !== css) element.textContent = css\n}\n\n/**\n * Keep `<html>` in sync with the current panel's theme after Inertia visits (the root\n * view sets the same attributes for the first paint): theme, sidebar style, density,\n * panel id, radius, font and the font stylesheet.\n */\nexport function applyPanelTheme(\n panel:\n | (Pick<PanelSchema, 'id' | 'theme' | 'colors'> &\n Partial<Pick<PanelSchema, 'favicon' | 'colorCss' | 'starterKit'>>)\n | undefined,\n): void {\n if (!panel?.theme) {\n return\n }\n\n // Also during a server render: chrome icons replaced with `Icons::register()` must be drawn\n // the same on the server as in the browser, or hydration mismatches (W-V3-28).\n setIconAliases(panel.theme.icons)\n\n if (typeof document === 'undefined') {\n return\n }\n\n // `starterKit()`: the host application owns the document's theme (its shadcn tokens,\n // fonts, favicon and colour scheme). Only the `--alfakit-*` colours are set; the starter kit\n // stylesheet (`standalone.css`) uses them for the status colours shadcn has no tokens for.\n if (panel.starterKit === true) {\n applyPanelColors(panel.colors, panel.colorCss)\n\n return\n }\n\n applyPanelFavicon(panel.favicon)\n\n const root = document.documentElement\n const { theme } = panel\n const attributes: Record<string, string> = {\n 'data-panel': panel.id,\n 'data-alfakit-theme': theme.attribute,\n 'data-sidebar-variant': theme.sidebar,\n 'data-density': theme.density,\n }\n\n for (const [dimension, value] of Object.entries(theme.design ?? {})) {\n attributes[`data-${dimension}`] = value\n }\n\n for (const [name, value] of Object.entries(attributes)) {\n if (root.getAttribute(name) !== value) root.setAttribute(name, value)\n }\n\n if (theme.radius) root.style.setProperty('--radius', theme.radius)\n else root.style.removeProperty('--radius')\n\n if (theme.fontCss) root.style.setProperty('--alfakit-font', theme.fontCss)\n else root.style.removeProperty('--alfakit-font')\n\n if (theme.headingFontCss) root.style.setProperty('--alfakit-heading-font', theme.headingFontCss)\n else root.style.removeProperty('--alfakit-heading-font')\n\n applyPanelColors(panel.colors, panel.colorCss)\n\n for (const url of theme.fontUrls ?? (theme.fontUrl ? [theme.fontUrl] : [])) {\n if (!document.querySelector(`link[rel=\"stylesheet\"][href=\"${CSS.escape(url)}\"]`)) {\n const link = document.createElement('link')\n link.rel = 'stylesheet'\n link.href = url\n link.dataset.alfakitFont = ''\n document.head.appendChild(link)\n }\n }\n}\n\n/**\n * Point `<link rel=\"icon\">` at the panel's favicon after Inertia visits (switching\n * panels or tenants), adding the link when the first page had none.\n */\nexport function applyPanelFavicon(url: string | null | undefined): void {\n if (typeof document === 'undefined') {\n return\n }\n\n const links = [...document.querySelectorAll<HTMLLinkElement>('link[rel~=\"icon\"]')]\n\n if (!url) {\n for (const link of links) if (link.dataset.alfakitFavicon !== undefined) link.remove()\n\n return\n }\n\n if (links.length === 0) {\n const link = document.createElement('link')\n link.rel = 'icon'\n link.dataset.alfakitFavicon = ''\n document.head.appendChild(link)\n links.push(link)\n }\n\n for (const link of links) {\n if (link.getAttribute('href') !== url) link.setAttribute('href', url)\n }\n}\n\nexport const SEMANTIC_COLORS = [\n 'primary',\n 'secondary',\n 'success',\n 'warning',\n 'danger',\n 'info',\n 'gray',\n] as const\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfakit/core",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "Framework-agnostic schema types and state management for Alfakit, the Laravel admin panel builder by Alfa Six.",
5
5
  "license": "MIT",
6
6
  "author": "Alfa Six (https://alfasixbh.com)",