@alfakit/react 0.1.2 → 0.1.7

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 (171) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/components/actions/action-modals.js +4 -0
  3. package/dist/components/actions/action-modals.js.map +1 -1
  4. package/dist/components/ai/copilot.d.ts +3 -1
  5. package/dist/components/ai/copilot.d.ts.map +1 -1
  6. package/dist/components/ai/copilot.js +4 -1
  7. package/dist/components/ai/copilot.js.map +1 -1
  8. package/dist/components/cms/live-preview.js +3 -2
  9. package/dist/components/cms/live-preview.js.map +1 -1
  10. package/dist/components/cms/page.js +1 -1
  11. package/dist/components/fields/builder.js +15 -6
  12. package/dist/components/fields/builder.js.map +1 -1
  13. package/dist/components/fields/image-editor-dialog.js +4 -2
  14. package/dist/components/fields/image-editor-dialog.js.map +1 -1
  15. package/dist/components/fields/item-actions.js +10 -10
  16. package/dist/components/fields/item-actions.js.map +1 -1
  17. package/dist/components/fields/key-value.d.ts.map +1 -1
  18. package/dist/components/fields/key-value.js +3 -3
  19. package/dist/components/fields/key-value.js.map +1 -1
  20. package/dist/components/fields/modal-table-select.js +6 -5
  21. package/dist/components/fields/modal-table-select.js.map +1 -1
  22. package/dist/components/fields/repeater.js +2 -2
  23. package/dist/components/fields/repeater.js.map +1 -1
  24. package/dist/components/fields/rich-editor.js +2 -1
  25. package/dist/components/fields/rich-editor.js.map +1 -1
  26. package/dist/components/form/form-block.js +1 -1
  27. package/dist/components/form/form-block.js.map +1 -1
  28. package/dist/components/form/form-context.js +2 -2
  29. package/dist/components/form/form-context.js.map +1 -1
  30. package/dist/components/kanban/kanban.js +4 -3
  31. package/dist/components/kanban/kanban.js.map +1 -1
  32. package/dist/components/layout/section.js +2 -2
  33. package/dist/components/layout/section.js.map +1 -1
  34. package/dist/components/panel/app-sidebar.d.ts +2 -3
  35. package/dist/components/panel/app-sidebar.d.ts.map +1 -1
  36. package/dist/components/panel/app-sidebar.js +1 -24
  37. package/dist/components/panel/app-sidebar.js.map +1 -1
  38. package/dist/components/panel/brand.d.ts +10 -0
  39. package/dist/components/panel/brand.d.ts.map +1 -0
  40. package/dist/components/panel/brand.js +37 -0
  41. package/dist/components/panel/brand.js.map +1 -0
  42. package/dist/components/panel/index.d.ts +2 -1
  43. package/dist/components/panel/topbar.js +1 -1
  44. package/dist/components/realtime/live-records-pill.d.ts.map +1 -1
  45. package/dist/components/realtime/live-records-pill.js +6 -3
  46. package/dist/components/realtime/live-records-pill.js.map +1 -1
  47. package/dist/components/table/cells.d.ts +6 -0
  48. package/dist/components/table/cells.d.ts.map +1 -1
  49. package/dist/components/table/cells.js +6 -2
  50. package/dist/components/table/cells.js.map +1 -1
  51. package/dist/components/table/column-manager.js +8 -7
  52. package/dist/components/table/column-manager.js.map +1 -1
  53. package/dist/components/table/content-layout.js +2 -1
  54. package/dist/components/table/content-layout.js.map +1 -1
  55. package/dist/components/table/pagination.js +16 -13
  56. package/dist/components/table/pagination.js.map +1 -1
  57. package/dist/components/table/table-block.js +14 -4
  58. package/dist/components/table/table-block.js.map +1 -1
  59. package/dist/components/ui/dialog.d.ts.map +1 -1
  60. package/dist/components/ui/dialog.js +3 -1
  61. package/dist/components/ui/dialog.js.map +1 -1
  62. package/dist/components/ui/popover.d.ts.map +1 -1
  63. package/dist/components/ui/popover.js +3 -1
  64. package/dist/components/ui/popover.js.map +1 -1
  65. package/dist/components/ui/sheet.d.ts.map +1 -1
  66. package/dist/components/ui/sheet.js +3 -1
  67. package/dist/components/ui/sheet.js.map +1 -1
  68. package/dist/components/ui/tooltip.js +3 -1
  69. package/dist/components/ui/tooltip.js.map +1 -1
  70. package/dist/context.d.ts +5 -0
  71. package/dist/context.d.ts.map +1 -1
  72. package/dist/context.js +5 -1
  73. package/dist/context.js.map +1 -1
  74. package/dist/create-alfakit.d.ts.map +1 -1
  75. package/dist/create-alfakit.js +8 -5
  76. package/dist/create-alfakit.js.map +1 -1
  77. package/dist/eject-manifest.json +16 -3
  78. package/dist/index.d.ts +6 -3
  79. package/dist/index.js +24 -21
  80. package/dist/layouts/app-layout.d.ts.map +1 -1
  81. package/dist/layouts/app-layout.js +2 -29
  82. package/dist/layouts/app-layout.js.map +1 -1
  83. package/dist/layouts/auth-layout.js +2 -2
  84. package/dist/layouts/auth-layout.js.map +1 -1
  85. package/dist/layouts/fullscreen-layout.js +1 -1
  86. package/dist/layouts/fullscreen-layout.js.map +1 -1
  87. package/dist/layouts/lazy-layouts.js +15 -0
  88. package/dist/layouts/lazy-layouts.js.map +1 -0
  89. package/dist/layouts/max-widths.js +34 -0
  90. package/dist/layouts/max-widths.js.map +1 -0
  91. package/dist/layouts/panel-layouts.d.ts +10 -0
  92. package/dist/layouts/panel-layouts.d.ts.map +1 -0
  93. package/dist/layouts/panel-layouts.js +20 -0
  94. package/dist/layouts/panel-layouts.js.map +1 -0
  95. package/dist/layouts/top-nav-layout.js +2 -2
  96. package/dist/layouts/top-nav-layout.js.map +1 -1
  97. package/dist/pages/page.d.ts.map +1 -1
  98. package/dist/pages/page.js +12 -7
  99. package/dist/pages/page.js.map +1 -1
  100. package/dist/standalone/alfakit-component.js +11 -6
  101. package/dist/standalone/alfakit-component.js.map +1 -1
  102. package/dist/standalone.css +7 -0
  103. package/dist/starter-kit/dashboard-widgets.d.ts +33 -0
  104. package/dist/starter-kit/dashboard-widgets.d.ts.map +1 -0
  105. package/dist/starter-kit/dashboard-widgets.js +53 -0
  106. package/dist/starter-kit/dashboard-widgets.js.map +1 -0
  107. package/dist/starter-kit/impersonation-banner.js +34 -0
  108. package/dist/starter-kit/impersonation-banner.js.map +1 -0
  109. package/dist/starter-kit/index.d.ts +3 -2
  110. package/dist/starter-kit/index.js +3 -2
  111. package/dist/starter-kit/starter-kit.d.ts +6 -0
  112. package/dist/starter-kit/starter-kit.d.ts.map +1 -1
  113. package/dist/starter-kit/starter-kit.js +18 -7
  114. package/dist/starter-kit/starter-kit.js.map +1 -1
  115. package/dist/starter-kit/widgets-loaded.js +20 -4
  116. package/dist/starter-kit/widgets-loaded.js.map +1 -1
  117. package/dist/starter-kit/widgets-triggers.js +34 -3
  118. package/dist/starter-kit/widgets-triggers.js.map +1 -1
  119. package/dist/starter-kit/widgets.d.ts +13 -5
  120. package/dist/starter-kit/widgets.d.ts.map +1 -1
  121. package/dist/starter-kit/widgets.js +56 -18
  122. package/dist/starter-kit/widgets.js.map +1 -1
  123. package/dist/styles.css +15 -2
  124. package/dist/themes.css +6 -1
  125. package/package.json +2 -2
  126. package/src/components/actions/action-modals.tsx +10 -0
  127. package/src/components/ai/copilot.tsx +14 -1
  128. package/src/components/cms/live-preview.tsx +5 -2
  129. package/src/components/fields/builder.tsx +14 -5
  130. package/src/components/fields/image-editor-dialog.tsx +7 -2
  131. package/src/components/fields/item-actions.tsx +32 -9
  132. package/src/components/fields/key-value.tsx +3 -2
  133. package/src/components/fields/modal-table-select.tsx +8 -3
  134. package/src/components/fields/repeater.tsx +2 -3
  135. package/src/components/fields/rich-editor.tsx +3 -0
  136. package/src/components/form/form-block.tsx +2 -1
  137. package/src/components/form/form-context.tsx +2 -2
  138. package/src/components/kanban/kanban.tsx +16 -3
  139. package/src/components/layout/section.tsx +2 -2
  140. package/src/components/panel/app-sidebar.tsx +2 -38
  141. package/src/components/panel/brand.tsx +46 -0
  142. package/src/components/realtime/live-records-pill.tsx +4 -3
  143. package/src/components/table/cells.tsx +9 -2
  144. package/src/components/table/column-manager.tsx +12 -10
  145. package/src/components/table/content-layout.tsx +5 -1
  146. package/src/components/table/pagination.tsx +16 -9
  147. package/src/components/table/table-block.tsx +25 -12
  148. package/src/components/ui/dialog.tsx +5 -1
  149. package/src/components/ui/popover.tsx +5 -1
  150. package/src/components/ui/sheet.tsx +5 -1
  151. package/src/components/ui/tooltip.tsx +5 -1
  152. package/src/context.tsx +10 -0
  153. package/src/create-alfakit.tsx +11 -2
  154. package/src/index.ts +2 -0
  155. package/src/layouts/app-layout.tsx +4 -28
  156. package/src/layouts/auth-layout.tsx +2 -2
  157. package/src/layouts/fullscreen-layout.tsx +1 -1
  158. package/src/layouts/lazy-layouts.ts +14 -0
  159. package/src/layouts/max-widths.ts +29 -0
  160. package/src/layouts/panel-layouts.ts +17 -0
  161. package/src/layouts/top-nav-layout.tsx +2 -2
  162. package/src/pages/page.tsx +21 -12
  163. package/src/standalone/alfakit-component.tsx +18 -7
  164. package/src/starter-kit/dashboard-widgets.tsx +78 -0
  165. package/src/starter-kit/impersonation-banner.tsx +48 -0
  166. package/src/starter-kit/index.ts +2 -0
  167. package/src/starter-kit/starter-kit.tsx +31 -4
  168. package/src/starter-kit/widgets-loaded.tsx +22 -0
  169. package/src/starter-kit/widgets-triggers.tsx +36 -1
  170. package/src/starter-kit/widgets.tsx +95 -20
  171. package/src/styles.css +15 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @alfakit/react
2
2
 
3
+ ## 0.1.7
4
+
5
+ ### Patch Changes
6
+
7
+ - Released with AlfaKit 0.1.7 to keep the npm and PHP versions the same (no frontend changes).
8
+ - Updated dependencies
9
+ - @alfakit/core@0.1.7
10
+
11
+ ## 0.1.6
12
+
13
+ ### Patch Changes
14
+
15
+ - Released with AlfaKit 0.1.6 (the npm and PHP versions now match). Fixes from the full verification in React, Vue and Svelte: starter-kit copilot, dashboard widgets and discovered components, per-item form errors, stable template schemas in new blocks, CSP nonces, WCAG AA badge contrast, plural kanban counts and more (see the AlfaKit changelog for 0.1.6).
16
+ - Updated dependencies
17
+ - @alfakit/core@0.1.6
18
+
3
19
  ## 0.1.2
4
20
 
5
21
  ### Patch Changes
@@ -154,6 +154,10 @@ function ActionModal({ modal, hidden = false }) {
154
154
  const passwordError = modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0];
155
155
  const submit = (createAnother = false) => {
156
156
  submittedRef.current = true;
157
+ if (form) form.whenSettled(() => run(createAnother));
158
+ else run(createAnother);
159
+ };
160
+ const run = (createAnother) => {
157
161
  const values = form?.state.values ?? {};
158
162
  const options = {
159
163
  ...schema.passwordConfirmation ? { password } : {},
@@ -1 +1 @@
1
- {"version":3,"file":"action-modals.js","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"sourcesContent":["import type { FormState, OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n actionFormParams,\n actionPath,\n confirmModalClose,\n formIdPrefix,\n FormStore,\n guardModalUnload,\n isModalHiddenByChild,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider } from '../form/form-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport { ActionList } from './action-list'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\n// Module-level so the snapshot stays stable (a new object per read re-renders forever).\nconst EMPTY_PROGRESS = { step: 0, count: 0 }\nconst NO_PROGRESS = { get: () => EMPTY_PROGRESS, subscribe: () => () => {} }\nconst NO_FORM = { get: () => null, subscribe: () => () => {} }\n\n/** Renders every open action modal from the runner store. Mount once per app. */\nexport function ActionModals() {\n const runner = useActionRunner()\n const { modals } = useStore(runner.store)\n\n // Notifications from JSON responses (a modal `mountUsing()` cancelled, field actions).\n useEffect(() => {\n runner.setNotificationHandler((notifications) => notifications.forEach((n) => showToast(n)))\n\n return () => runner.setNotificationHandler(undefined)\n }, [runner])\n\n return (\n <>\n {modals.map((modal, index) => (\n <ActionModal\n key={`${actionPath(modal.action, modal.context)}-${modal.context.record ?? ''}`}\n modal={modal}\n hidden={isModalHiddenByChild(modals, index)}\n />\n ))}\n </>\n )\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\n/**\n * Put PHP attributes (`extraModalOverlayAttributes()`) on an element the renderer doesn't\n * create itself (the dialog's backdrop). `class` is added to the element's classes.\n */\nfunction applyAttributes(\n element: Element | null | undefined,\n attributes: Record<string, unknown> | undefined,\n) {\n if (!element || !attributes) return\n\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class' || name === 'className') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nfunction ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boolean }) {\n const runner = useActionRunner()\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n const { action, context, modal: schema } = modal\n const submittedRef = useRef(false)\n\n const form = useMemo(() => {\n if (!schema.form) {\n return null\n }\n\n return new FormStore(schema.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n // Own field ids: the modal opens over a form whose fields may share names.\n idPrefix: formIdPrefix(actionFormComponent(action, context)),\n record: context.record ?? null,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n params: actionFormParams(context),\n })\n }, [schema.form, context, action, panel.endpoints])\n\n useEffect(() => {\n form?.setErrors(modal.errors)\n }, [form, modal.errors])\n\n useEffect(() => () => form?.destroy(), [form])\n\n // \"Create & create another\": back to the `autofocus()` field of the fresh form.\n const refocus = modal.refocus\n\n useEffect(() => {\n if (!refocus || !form) return\n\n return scheduleAutofocus(\n () => document.getElementById(`action-form-${action.name}`),\n form.state.components,\n )\n }, [refocus, form, action.name])\n\n // Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n // (`fillData()`).\n useEffect(\n () => (form ? runner.registerModalForm(action, context, form) : undefined),\n [runner, form, action, context],\n )\n\n // `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\n // A callback ref: the portal renders the content after the first commit.\n const [content, setContent] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (hidden) return\n\n const overlay = content?.previousElementSibling\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay')) {\n applyAttributes(overlay, schema.overlayAttributes)\n }\n }, [schema.overlayAttributes, hidden, content])\n\n // Live updates replace the form's components (fields shown, options changed).\n const formState = useStore<FormState | null>(form?.store ?? NO_FORM)\n\n // A wizard form reports its step, so the submit button waits for the last one.\n const progressStore = useMemo(() => (form ? wizardProgressStore(form) : null), [form])\n const progress = useStore(progressStore ?? NO_PROGRESS)\n const submitVisible = isModalSubmitVisible(schema, progressStore ? progress : null)\n\n const [password, setPassword] = useState('')\n const passwordId = useId()\n const passwordError = modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0]\n\n const submit = (createAnother = false) => {\n submittedRef.current = true\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.passwordConfirmation ? { password } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n }\n\n const footerContext = useMemo(() => nestedActionContext(context, action), [context, action])\n\n // Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\n const close = () => runner.cancelModal(action.name)\n const unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n // Cancel, Escape, clicking away and the close button ask first when the form changed.\n const requestClose = () => {\n if (confirmModalClose(schema, form, unsavedMessage)) close()\n }\n\n useEffect(() => guardModalUnload(schema, form, unsavedMessage), [schema, form, unsavedMessage])\n const title = schema.heading ?? action.label ?? t('actions', {}, 'Actions')\n\n const header = (\n <div\n className={cn(\n 'flex gap-4',\n schema.alignment === 'center' && 'flex-col items-center text-center',\n )}\n >\n {schema.icon && (\n <div\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )}\n >\n <Icon name={schema.icon} className=\"size-5\" />\n </div>\n )}\n <div className=\"flex flex-col gap-1.5\">\n {/* Every dialog needs an accessible name: without a heading it stays visually hidden. */}\n {schema.slideOver ? (\n <SheetTitle className={cn(!schema.heading && 'sr-only')}>{title}</SheetTitle>\n ) : (\n <DialogTitle className={cn(!schema.heading && 'sr-only')}>{title}</DialogTitle>\n )}\n {schema.description ? (\n schema.slideOver ? (\n <SheetDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n ) : (\n <DialogDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n )\n ) : schema.slideOver ? (\n <SheetDescription className=\"sr-only\">{title}</SheetDescription>\n ) : (\n <DialogDescription className=\"sr-only\">{title}</DialogDescription>\n )}\n </div>\n </div>\n )\n\n const body = (\n <div className=\"flex flex-col gap-6\">\n {schema.content && <SchemaRenderer components={schema.content} />}\n {form && <Slot name=\"modal.schema.before\" data={{ action: action.name }} />}\n {form && (\n <FormProvider store={form}>\n <form\n id={`action-form-${action.name}`}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n >\n <SchemaRenderer\n components={formState?.components ?? form.state.components}\n columns={formState?.columns ?? form.state.columns}\n />\n </form>\n </FormProvider>\n )}\n {form && <Slot name=\"modal.schema.after\" data={{ action: action.name }} />}\n {schema.contentFooter && <SchemaRenderer components={schema.contentFooter} />}\n {schema.passwordConfirmation && (\n <div data-slot=\"password-confirmation\" className=\"flex flex-col gap-2\">\n <Label htmlFor={passwordId}>{t('current_password', {}, 'Current password')}</Label>\n <Input\n id={passwordId}\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n aria-invalid={passwordError ? true : undefined}\n aria-describedby={passwordError ? `${passwordId}-error` : `${passwordId}-hint`}\n onChange={(event) => setPassword(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n submit()\n }\n }}\n />\n {passwordError ? (\n <p id={`${passwordId}-error`} className=\"text-destructive text-sm\">\n {passwordError}\n </p>\n ) : (\n <p id={`${passwordId}-hint`} className=\"text-muted-foreground text-sm\">\n {t('confirm_password', {}, 'Confirm your password to continue')}\n </p>\n )}\n </div>\n )}\n </div>\n )\n\n const footerAlignment = modalFooterAlignment(schema)\n const footer = (schema.submitLabel ||\n schema.cancelLabel ||\n schema.extraFooterActions?.length) && (\n <div\n className={cn(\n 'flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center',\n FOOTER_JUSTIFY[footerAlignment],\n )}\n >\n {schema.extraFooterActions && (\n <ActionList\n actions={schema.extraFooterActions}\n context={footerContext}\n className={footerAlignment === 'end' ? 'sm:me-auto' : undefined}\n />\n )}\n {schema.cancelLabel && (\n <Button\n type=\"button\"\n variant=\"outline\"\n className={\n schema.cancelColor\n ? colorClass(outlineButtonColorClasses, schema.cancelColor)\n : undefined\n }\n onClick={requestClose}\n disabled={modal.processing}\n >\n {schema.cancelLabel}\n </Button>\n )}\n {schema.createAnotherLabel && schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n className={\n schema.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.createAnotherColor)\n : undefined\n }\n onClick={() => submit(true)}\n disabled={modal.processing}\n >\n {schema.createAnotherIcon && <Icon name={schema.createAnotherIcon} />}\n {schema.createAnotherLabel}\n </Button>\n )}\n {schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n className={colorClass(solidButtonColorClasses, schema.submitColor, 'primary')}\n onClick={() => submit()}\n disabled={modal.processing}\n autoFocus={schema.autofocus && !form}\n >\n {modal.processing ? <Spinner /> : schema.submitIcon && <Icon name={schema.submitIcon} />}\n {schema.submitLabel}\n </Button>\n )}\n </div>\n )\n\n const onOpenChange = (open: boolean) => {\n if (!open && !modal.processing) {\n requestClose()\n }\n }\n\n // `modalClickThrough()`: a non-modal dialog has no backdrop and leaves the page usable.\n const isModal = !schema.clickThrough\n\n if (schema.slideOver) {\n return (\n <Sheet open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <SheetContent\n ref={setContent}\n side={slideOverSide(schema.slideOverPosition, direction)}\n {...extraProps(\n schema.windowAttributes,\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <SheetHeader className=\"border-b\">{header}</SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n {footer && (\n <SheetFooter\n className={cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')}\n >\n {footer}\n </SheetFooter>\n )}\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <DialogContent\n ref={setContent}\n {...extraProps(\n schema.windowAttributes,\n cn('max-h-[calc(100vh-2rem)] overflow-y-auto', WIDTHS[schema.width] ?? 'sm:max-w-lg'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <DialogHeader\n className={cn(\n // Pinned to the top of the scrolling dialog, edge to edge.\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n )}\n >\n {header}\n </DialogHeader>\n {(schema.content || form || schema.contentFooter || schema.passwordConfirmation) && body}\n {footer && (\n <DialogFooter\n className={cn(\n // Pinned to the bottom of the scrolling dialog, edge to edge.\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )}\n >\n {footer}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"action-modals.js","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"sourcesContent":["import type { FormState, OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n actionFormParams,\n actionPath,\n confirmModalClose,\n formIdPrefix,\n FormStore,\n guardModalUnload,\n isModalHiddenByChild,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider } from '../form/form-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport { ActionList } from './action-list'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\n// Module-level so the snapshot stays stable (a new object per read re-renders forever).\nconst EMPTY_PROGRESS = { step: 0, count: 0 }\nconst NO_PROGRESS = { get: () => EMPTY_PROGRESS, subscribe: () => () => {} }\nconst NO_FORM = { get: () => null, subscribe: () => () => {} }\n\n/** Renders every open action modal from the runner store. Mount once per app. */\nexport function ActionModals() {\n const runner = useActionRunner()\n const { modals } = useStore(runner.store)\n\n // Notifications from JSON responses (a modal `mountUsing()` cancelled, field actions).\n useEffect(() => {\n runner.setNotificationHandler((notifications) => notifications.forEach((n) => showToast(n)))\n\n return () => runner.setNotificationHandler(undefined)\n }, [runner])\n\n return (\n <>\n {modals.map((modal, index) => (\n <ActionModal\n key={`${actionPath(modal.action, modal.context)}-${modal.context.record ?? ''}`}\n modal={modal}\n hidden={isModalHiddenByChild(modals, index)}\n />\n ))}\n </>\n )\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\n/**\n * Put PHP attributes (`extraModalOverlayAttributes()`) on an element the renderer doesn't\n * create itself (the dialog's backdrop). `class` is added to the element's classes.\n */\nfunction applyAttributes(\n element: Element | null | undefined,\n attributes: Record<string, unknown> | undefined,\n) {\n if (!element || !attributes) return\n\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class' || name === 'className') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nfunction ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boolean }) {\n const runner = useActionRunner()\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n const { action, context, modal: schema } = modal\n const submittedRef = useRef(false)\n\n const form = useMemo(() => {\n if (!schema.form) {\n return null\n }\n\n return new FormStore(schema.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n // Own field ids: the modal opens over a form whose fields may share names.\n idPrefix: formIdPrefix(actionFormComponent(action, context)),\n record: context.record ?? null,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n params: actionFormParams(context),\n })\n }, [schema.form, context, action, panel.endpoints])\n\n useEffect(() => {\n form?.setErrors(modal.errors)\n }, [form, modal.errors])\n\n useEffect(() => () => form?.destroy(), [form])\n\n // \"Create & create another\": back to the `autofocus()` field of the fresh form.\n const refocus = modal.refocus\n\n useEffect(() => {\n if (!refocus || !form) return\n\n return scheduleAutofocus(\n () => document.getElementById(`action-form-${action.name}`),\n form.state.components,\n )\n }, [refocus, form, action.name])\n\n // Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n // (`fillData()`).\n useEffect(\n () => (form ? runner.registerModalForm(action, context, form) : undefined),\n [runner, form, action, context],\n )\n\n // `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\n // A callback ref: the portal renders the content after the first commit.\n const [content, setContent] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (hidden) return\n\n const overlay = content?.previousElementSibling\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay')) {\n applyAttributes(overlay, schema.overlayAttributes)\n }\n }, [schema.overlayAttributes, hidden, content])\n\n // Live updates replace the form's components (fields shown, options changed).\n const formState = useStore<FormState | null>(form?.store ?? NO_FORM)\n\n // A wizard form reports its step, so the submit button waits for the last one.\n const progressStore = useMemo(() => (form ? wizardProgressStore(form) : null), [form])\n const progress = useStore(progressStore ?? NO_PROGRESS)\n const submitVisible = isModalSubmitVisible(schema, progressStore ? progress : null)\n\n const [password, setPassword] = useState('')\n const passwordId = useId()\n const passwordError = modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0]\n\n const submit = (createAnother = false) => {\n submittedRef.current = true\n\n // A click that blurred a live field started a refresh: send the state it returns.\n if (form) {\n form.whenSettled(() => run(createAnother))\n } else {\n run(createAnother)\n }\n }\n\n const run = (createAnother: boolean) => {\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.passwordConfirmation ? { password } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n }\n\n const footerContext = useMemo(() => nestedActionContext(context, action), [context, action])\n\n // Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\n const close = () => runner.cancelModal(action.name)\n const unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n // Cancel, Escape, clicking away and the close button ask first when the form changed.\n const requestClose = () => {\n if (confirmModalClose(schema, form, unsavedMessage)) close()\n }\n\n useEffect(() => guardModalUnload(schema, form, unsavedMessage), [schema, form, unsavedMessage])\n const title = schema.heading ?? action.label ?? t('actions', {}, 'Actions')\n\n const header = (\n <div\n className={cn(\n 'flex gap-4',\n schema.alignment === 'center' && 'flex-col items-center text-center',\n )}\n >\n {schema.icon && (\n <div\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )}\n >\n <Icon name={schema.icon} className=\"size-5\" />\n </div>\n )}\n <div className=\"flex flex-col gap-1.5\">\n {/* Every dialog needs an accessible name: without a heading it stays visually hidden. */}\n {schema.slideOver ? (\n <SheetTitle className={cn(!schema.heading && 'sr-only')}>{title}</SheetTitle>\n ) : (\n <DialogTitle className={cn(!schema.heading && 'sr-only')}>{title}</DialogTitle>\n )}\n {schema.description ? (\n schema.slideOver ? (\n <SheetDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n ) : (\n <DialogDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n )\n ) : schema.slideOver ? (\n <SheetDescription className=\"sr-only\">{title}</SheetDescription>\n ) : (\n <DialogDescription className=\"sr-only\">{title}</DialogDescription>\n )}\n </div>\n </div>\n )\n\n const body = (\n <div className=\"flex flex-col gap-6\">\n {schema.content && <SchemaRenderer components={schema.content} />}\n {form && <Slot name=\"modal.schema.before\" data={{ action: action.name }} />}\n {form && (\n <FormProvider store={form}>\n <form\n id={`action-form-${action.name}`}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n >\n <SchemaRenderer\n components={formState?.components ?? form.state.components}\n columns={formState?.columns ?? form.state.columns}\n />\n </form>\n </FormProvider>\n )}\n {form && <Slot name=\"modal.schema.after\" data={{ action: action.name }} />}\n {schema.contentFooter && <SchemaRenderer components={schema.contentFooter} />}\n {schema.passwordConfirmation && (\n <div data-slot=\"password-confirmation\" className=\"flex flex-col gap-2\">\n <Label htmlFor={passwordId}>{t('current_password', {}, 'Current password')}</Label>\n <Input\n id={passwordId}\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n aria-invalid={passwordError ? true : undefined}\n aria-describedby={passwordError ? `${passwordId}-error` : `${passwordId}-hint`}\n onChange={(event) => setPassword(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n submit()\n }\n }}\n />\n {passwordError ? (\n <p id={`${passwordId}-error`} className=\"text-destructive text-sm\">\n {passwordError}\n </p>\n ) : (\n <p id={`${passwordId}-hint`} className=\"text-muted-foreground text-sm\">\n {t('confirm_password', {}, 'Confirm your password to continue')}\n </p>\n )}\n </div>\n )}\n </div>\n )\n\n const footerAlignment = modalFooterAlignment(schema)\n const footer = (schema.submitLabel ||\n schema.cancelLabel ||\n schema.extraFooterActions?.length) && (\n <div\n className={cn(\n 'flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center',\n FOOTER_JUSTIFY[footerAlignment],\n )}\n >\n {schema.extraFooterActions && (\n <ActionList\n actions={schema.extraFooterActions}\n context={footerContext}\n className={footerAlignment === 'end' ? 'sm:me-auto' : undefined}\n />\n )}\n {schema.cancelLabel && (\n <Button\n type=\"button\"\n variant=\"outline\"\n className={\n schema.cancelColor\n ? colorClass(outlineButtonColorClasses, schema.cancelColor)\n : undefined\n }\n onClick={requestClose}\n disabled={modal.processing}\n >\n {schema.cancelLabel}\n </Button>\n )}\n {schema.createAnotherLabel && schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n className={\n schema.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.createAnotherColor)\n : undefined\n }\n onClick={() => submit(true)}\n disabled={modal.processing}\n >\n {schema.createAnotherIcon && <Icon name={schema.createAnotherIcon} />}\n {schema.createAnotherLabel}\n </Button>\n )}\n {schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n className={colorClass(solidButtonColorClasses, schema.submitColor, 'primary')}\n onClick={() => submit()}\n disabled={modal.processing}\n autoFocus={schema.autofocus && !form}\n >\n {modal.processing ? <Spinner /> : schema.submitIcon && <Icon name={schema.submitIcon} />}\n {schema.submitLabel}\n </Button>\n )}\n </div>\n )\n\n const onOpenChange = (open: boolean) => {\n if (!open && !modal.processing) {\n requestClose()\n }\n }\n\n // `modalClickThrough()`: a non-modal dialog has no backdrop and leaves the page usable.\n const isModal = !schema.clickThrough\n\n if (schema.slideOver) {\n return (\n <Sheet open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <SheetContent\n ref={setContent}\n side={slideOverSide(schema.slideOverPosition, direction)}\n {...extraProps(\n schema.windowAttributes,\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <SheetHeader className=\"border-b\">{header}</SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n {footer && (\n <SheetFooter\n className={cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')}\n >\n {footer}\n </SheetFooter>\n )}\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <DialogContent\n ref={setContent}\n {...extraProps(\n schema.windowAttributes,\n cn('max-h-[calc(100vh-2rem)] overflow-y-auto', WIDTHS[schema.width] ?? 'sm:max-w-lg'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <DialogHeader\n className={cn(\n // Pinned to the top of the scrolling dialog, edge to edge.\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n )}\n >\n {header}\n </DialogHeader>\n {(schema.content || form || schema.contentFooter || schema.passwordConfirmation) && body}\n {footer && (\n <DialogFooter\n className={cn(\n // Pinned to the bottom of the scrolling dialog, edge to edge.\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )}\n >\n {footer}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
@@ -5,8 +5,10 @@ import { AiCopilotSchema, CopilotController } from "@alfakit/core";
5
5
  * a chat slide-over. Questions, tools and actions run on the server; proposed actions
6
6
  * run only when the user confirms them in the chat.
7
7
  */
8
- export declare function AiCopilot({ component }: {
8
+ export declare function AiCopilot({ component, autoOpen }: {
9
9
  component: AiCopilotSchema;
10
+ /** Open once mounted (starter kit header: clicked or `mod+j` before its code loaded). */
11
+ autoOpen?: boolean;
10
12
  }): import("react").JSX.Element | null;
11
13
  //#endregion
12
14
  //# sourceMappingURL=copilot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"copilot.d.ts","names":[],"sources":["../../../src/components/ai/copilot.tsx"],"mappings":";;;;;;;wBA+BgB,YAAY;EAAe,WAAW;oBAAiB,IAAA"}
1
+ {"version":3,"file":"copilot.d.ts","names":[],"sources":["../../../src/components/ai/copilot.tsx"],"mappings":";;;;;;;wBA+BgB,YACd,WACA;EAEA,WAAW;;EAEX;oBACD,IAAA"}
@@ -16,7 +16,7 @@ function useCopilotState(controller) {
16
16
  * a chat slide-over. Questions, tools and actions run on the server; proposed actions
17
17
  * run only when the user confirms them in the chat.
18
18
  */
19
- function AiCopilot({ component }) {
19
+ function AiCopilot({ component, autoOpen = false }) {
20
20
  const t = useT();
21
21
  const controller = useMemo(() => new CopilotController(component), [component.endpoints.send]);
22
22
  const state = useCopilotState(controller);
@@ -24,6 +24,9 @@ function AiCopilot({ component }) {
24
24
  const keys = JSON.stringify(component.keyBindings ?? []);
25
25
  const label = t("ai.open", {}, "Open AI copilot");
26
26
  useEffect(() => controller.subscribe((next) => next.open && setMounted(true)), [controller]);
27
+ useEffect(() => {
28
+ if (autoOpen && component.canUse !== false) controller.open();
29
+ }, [controller]);
27
30
  useEffect(() => {
28
31
  const bindings = JSON.parse(keys);
29
32
  if (bindings.length === 0) return;
@@ -1 +1 @@
1
- {"version":3,"file":"copilot.js","names":[],"sources":["../../../src/components/ai/copilot.tsx"],"sourcesContent":["import {\n type AiCopilotSchema,\n CopilotController,\n type CopilotState,\n shortcuts,\n} from '@alfakit/core'\nimport { SparklesIcon } from 'lucide-react'\nimport { useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport { useT } from '../../context'\nimport { lazyComponent } from '../../lib/lazy'\nimport { Button } from '../ui/button'\n\n/** The chat slide-over; its code loads the first time the copilot opens. */\nconst CopilotPanel = lazyComponent<{ controller: CopilotController }>(\n () => import('./copilot-panel').then((module) => module.CopilotPanel),\n () => null,\n)\n\nexport function useCopilotState(controller: CopilotController): CopilotState {\n return useSyncExternalStore(\n (listener) => controller.subscribe(listener),\n () => controller.state,\n () => controller.state,\n )\n}\n\n/**\n * The copilot of alfasix/alfakit-ai (`ai:copilot`): a topbar button (and `mod+j`) opening\n * a chat slide-over. Questions, tools and actions run on the server; proposed actions\n * run only when the user confirms them in the chat.\n */\nexport function AiCopilot({ component }: { component: AiCopilotSchema }) {\n const t = useT()\n // One controller per panel: Inertia visits pass new props objects.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const controller = useMemo(() => new CopilotController(component), [component.endpoints.send])\n const state = useCopilotState(controller)\n const [mounted, setMounted] = useState(false)\n const keys = JSON.stringify(component.keyBindings ?? [])\n const label = t('ai.open', {}, 'Open AI copilot')\n\n // The panel mounts the first time the copilot opens (button or shortcut), then stays.\n useEffect(() => controller.subscribe((next) => next.open && setMounted(true)), [controller])\n\n useEffect(() => {\n const bindings = JSON.parse(keys) as string[]\n\n if (bindings.length === 0) return\n\n return shortcuts.register({\n id: 'ai.copilot',\n keys: bindings,\n label,\n group: 'general',\n allowInInputs: true,\n handler: () => controller.toggle(),\n })\n }, [controller, keys, label])\n\n if (component.canUse === false) return null\n\n return (\n <div data-slot=\"ai-copilot\" className=\"contents\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n data-slot=\"ai-copilot-trigger\"\n aria-label={label}\n aria-expanded={state.open}\n aria-haspopup=\"dialog\"\n title={label}\n onClick={() => controller.open()}\n onPointerEnter={() => void CopilotPanel.preload()}\n onFocus={() => void CopilotPanel.preload()}\n >\n <SparklesIcon aria-hidden=\"true\" />\n </Button>\n {mounted && <CopilotPanel controller={controller} />}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;AAaA,MAAM,eAAe,oBACb,OAAO,qBAAkB,CAAC,MAAM,WAAW,OAAO,YAAY,SAC9D,IACR;AAEA,SAAgB,gBAAgB,YAA6C;CAC3E,OAAO,sBACJ,aAAa,WAAW,UAAU,QAAQ,SACrC,WAAW,aACX,WAAW,KACnB;AACF;;;;;;AAOA,SAAgB,UAAU,EAAE,aAA6C;CACvE,MAAM,IAAI,KAAK;CAGf,MAAM,aAAa,cAAc,IAAI,kBAAkB,SAAS,GAAG,CAAC,UAAU,UAAU,IAAI,CAAC;CAC7F,MAAM,QAAQ,gBAAgB,UAAU;CACxC,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,OAAO,KAAK,UAAU,UAAU,eAAe,CAAC,CAAC;CACvD,MAAM,QAAQ,EAAE,WAAW,CAAC,GAAG,iBAAiB;CAGhD,gBAAgB,WAAW,WAAW,SAAS,KAAK,QAAQ,WAAW,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;CAE3F,gBAAgB;EACd,MAAM,WAAW,KAAK,MAAM,IAAI;EAEhC,IAAI,SAAS,WAAW,GAAG;EAE3B,OAAO,UAAU,SAAS;GACxB,IAAI;GACJ,MAAM;GACN;GACA,OAAO;GACP,eAAe;GACf,eAAe,WAAW,OAAO;EACnC,CAAC;CACH,GAAG;EAAC;EAAY;EAAM;CAAK,CAAC;CAE5B,IAAI,UAAU,WAAW,OAAO,OAAO;CAEvC,OACE,qBAAC,OAAD;EAAK,aAAU;EAAa,WAAU;EAAtC,UAAA,CACE,oBAAC,QAAD;GACE,MAAK;GACL,SAAQ;GACR,MAAK;GACL,aAAU;GACV,cAAY;GACZ,iBAAe,MAAM;GACrB,iBAAc;GACd,OAAO;GACP,eAAe,WAAW,KAAK;GAC/B,sBAAsB,KAAK,aAAa,QAAQ;GAChD,eAAe,KAAK,aAAa,QAAQ;GAEzC,UAAA,oBAAC,cAAD,EAAc,eAAY,OAAQ,CAAA;EAC5B,CAAA,GACP,WAAW,oBAAC,cAAD,EAA0B,WAAa,CAAA,CAChD;;AAET"}
1
+ {"version":3,"file":"copilot.js","names":[],"sources":["../../../src/components/ai/copilot.tsx"],"sourcesContent":["import {\n type AiCopilotSchema,\n CopilotController,\n type CopilotState,\n shortcuts,\n} from '@alfakit/core'\nimport { SparklesIcon } from 'lucide-react'\nimport { useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport { useT } from '../../context'\nimport { lazyComponent } from '../../lib/lazy'\nimport { Button } from '../ui/button'\n\n/** The chat slide-over; its code loads the first time the copilot opens. */\nconst CopilotPanel = lazyComponent<{ controller: CopilotController }>(\n () => import('./copilot-panel').then((module) => module.CopilotPanel),\n () => null,\n)\n\nexport function useCopilotState(controller: CopilotController): CopilotState {\n return useSyncExternalStore(\n (listener) => controller.subscribe(listener),\n () => controller.state,\n () => controller.state,\n )\n}\n\n/**\n * The copilot of alfasix/alfakit-ai (`ai:copilot`): a topbar button (and `mod+j`) opening\n * a chat slide-over. Questions, tools and actions run on the server; proposed actions\n * run only when the user confirms them in the chat.\n */\nexport function AiCopilot({\n component,\n autoOpen = false,\n}: {\n component: AiCopilotSchema\n /** Open once mounted (starter kit header: clicked or `mod+j` before its code loaded). */\n autoOpen?: boolean\n}) {\n const t = useT()\n // One controller per panel: Inertia visits pass new props objects.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const controller = useMemo(() => new CopilotController(component), [component.endpoints.send])\n const state = useCopilotState(controller)\n const [mounted, setMounted] = useState(false)\n const keys = JSON.stringify(component.keyBindings ?? [])\n const label = t('ai.open', {}, 'Open AI copilot')\n\n // The panel mounts the first time the copilot opens (button or shortcut), then stays.\n useEffect(() => controller.subscribe((next) => next.open && setMounted(true)), [controller])\n\n useEffect(() => {\n if (autoOpen && component.canUse !== false) controller.open()\n // Only when it mounts.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [controller])\n\n useEffect(() => {\n const bindings = JSON.parse(keys) as string[]\n\n if (bindings.length === 0) return\n\n return shortcuts.register({\n id: 'ai.copilot',\n keys: bindings,\n label,\n group: 'general',\n allowInInputs: true,\n handler: () => controller.toggle(),\n })\n }, [controller, keys, label])\n\n if (component.canUse === false) return null\n\n return (\n <div data-slot=\"ai-copilot\" className=\"contents\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n data-slot=\"ai-copilot-trigger\"\n aria-label={label}\n aria-expanded={state.open}\n aria-haspopup=\"dialog\"\n title={label}\n onClick={() => controller.open()}\n onPointerEnter={() => void CopilotPanel.preload()}\n onFocus={() => void CopilotPanel.preload()}\n >\n <SparklesIcon aria-hidden=\"true\" />\n </Button>\n {mounted && <CopilotPanel controller={controller} />}\n </div>\n )\n}\n"],"mappings":";;;;;;;;;AAaA,MAAM,eAAe,oBACb,OAAO,qBAAkB,CAAC,MAAM,WAAW,OAAO,YAAY,SAC9D,IACR;AAEA,SAAgB,gBAAgB,YAA6C;CAC3E,OAAO,sBACJ,aAAa,WAAW,UAAU,QAAQ,SACrC,WAAW,aACX,WAAW,KACnB;AACF;;;;;;AAOA,SAAgB,UAAU,EACxB,WACA,WAAW,SAKV;CACD,MAAM,IAAI,KAAK;CAGf,MAAM,aAAa,cAAc,IAAI,kBAAkB,SAAS,GAAG,CAAC,UAAU,UAAU,IAAI,CAAC;CAC7F,MAAM,QAAQ,gBAAgB,UAAU;CACxC,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,OAAO,KAAK,UAAU,UAAU,eAAe,CAAC,CAAC;CACvD,MAAM,QAAQ,EAAE,WAAW,CAAC,GAAG,iBAAiB;CAGhD,gBAAgB,WAAW,WAAW,SAAS,KAAK,QAAQ,WAAW,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;CAE3F,gBAAgB;EACd,IAAI,YAAY,UAAU,WAAW,OAAO,WAAW,KAAK;CAG9D,GAAG,CAAC,UAAU,CAAC;CAEf,gBAAgB;EACd,MAAM,WAAW,KAAK,MAAM,IAAI;EAEhC,IAAI,SAAS,WAAW,GAAG;EAE3B,OAAO,UAAU,SAAS;GACxB,IAAI;GACJ,MAAM;GACN;GACA,OAAO;GACP,eAAe;GACf,eAAe,WAAW,OAAO;EACnC,CAAC;CACH,GAAG;EAAC;EAAY;EAAM;CAAK,CAAC;CAE5B,IAAI,UAAU,WAAW,OAAO,OAAO;CAEvC,OACE,qBAAC,OAAD;EAAK,aAAU;EAAa,WAAU;EAAtC,UAAA,CACE,oBAAC,QAAD;GACE,MAAK;GACL,SAAQ;GACR,MAAK;GACL,aAAU;GACV,cAAY;GACZ,iBAAe,MAAM;GACrB,iBAAc;GACd,OAAO;GACP,eAAe,WAAW,KAAK;GAC/B,sBAAsB,KAAK,aAAa,QAAQ;GAChD,eAAe,KAAK,aAAa,QAAQ;GAEzC,UAAA,oBAAC,cAAD,EAAc,eAAY,OAAQ,CAAA;EAC5B,CAAA,GACP,WAAW,oBAAC,cAAD,EAA0B,WAAa,CAAA,CAChD;;AAET"}
@@ -62,6 +62,7 @@ function CmsLivePreview({ component }) {
62
62
  const root = useRef(null);
63
63
  const autosaveRef = useRef(null);
64
64
  const status = snapshot.hasChanges && component.changedStatus ? component.changedStatus : component.status;
65
+ const actions = snapshot.hasChanges && component.changedActions ? component.changedActions : component.actions;
65
66
  useEffect(() => {
66
67
  const frame = requestAnimationFrame(() => {
67
68
  setTop(resolveStickyOffset(null));
@@ -177,10 +178,10 @@ function CmsLivePreview({ component }) {
177
178
  onClick: () => setOpen(!open),
178
179
  "data-slot": "cms-live-preview-toggle",
179
180
  children: [/* @__PURE__ */ jsx(EyeIcon, { "aria-hidden": "true" }), open ? t("cms.hide_preview", {}, "Hide preview") : t("cms.live_preview", {}, "Live preview")]
180
- }), component.actions && component.actions.length > 0 && /* @__PURE__ */ jsx("div", {
181
+ }), actions && actions.length > 0 && /* @__PURE__ */ jsx("div", {
181
182
  onPointerDownCapture: () => void autosaveRef.current?.flush(),
182
183
  children: /* @__PURE__ */ jsx(ActionList, {
183
- actions: component.actions,
184
+ actions,
184
185
  context: actionContext,
185
186
  size: "sm"
186
187
  })
@@ -1 +1 @@
1
- {"version":3,"file":"live-preview.js","names":[],"sources":["../../../src/components/cms/live-preview.tsx"],"sourcesContent":["import {\n CMS_PREVIEW_DEVICES,\n CmsAutosave,\n callCmsAutosave,\n cmsPreviewFitsBeside,\n cmsPreviewOpensOnLoad,\n rememberedCmsPreview,\n reserveCmsPreviewSpace,\n resolveStickyOffset,\n type CmsAutosaveSnapshot,\n type CmsLivePreviewSchema,\n type CmsPreviewDevice,\n} from '@alfakit/core'\nimport {\n CheckIcon,\n CircleAlertIcon,\n EyeIcon,\n LoaderCircleIcon,\n Maximize2Icon,\n Minimize2Icon,\n MonitorIcon,\n SmartphoneIcon,\n TabletIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { usePageSchema, usePanel, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\n\n/** Badge colours of the status (the server's `PageState` colours). */\nconst STATUS_COLORS: Record<string, string> = {\n gray: 'border-border bg-muted text-foreground',\n success: 'border-emerald-600/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300',\n warning: 'border-amber-600/30 bg-amber-500/10 text-amber-700 dark:text-amber-300',\n info: 'border-sky-600/30 bg-sky-500/10 text-sky-700 dark:text-sky-300',\n danger: 'border-red-600/30 bg-red-500/10 text-red-700 dark:text-red-300',\n}\n\nconst DEVICE_ICONS = { desktop: MonitorIcon, tablet: TabletIcon, phone: SmartphoneIcon }\nconst DEVICE_LABELS = { desktop: 'Desktop', tablet: 'Tablet', phone: 'Phone' }\n\n/**\n * The page editor's autosave status and live preview (schema type `CmsLivePreview`).\n * Changes are autosaved as a draft a moment after typing stops; the preview pane shows\n * the signed preview of that draft beside the form, full screen, or at tablet and phone\n * widths.\n */\nexport function CmsLivePreview({ component }: { component: CmsLivePreviewSchema }) {\n const t = useT()\n const form = useOptionalFormStore()\n const page = usePageSchema()\n const panel = usePanel()\n const latest = useRef({ token: page?.token ?? '', endpoint: panel?.endpoints?.pageCall ?? '' })\n const [snapshot, setSnapshot] = useState<CmsAutosaveSnapshot>({\n status: component.savedAt ? 'saved' : 'idle',\n savedAt: component.savedAt ?? null,\n previewUrl: component.previewUrl,\n version: 0,\n hasChanges: component.hasChanges ?? false,\n })\n const [top, setTop] = useState('0px')\n const actionContext = useMemo(\n () => ({ token: page?.token ?? '', component: 'page' }),\n [page?.token],\n )\n\n const [open, setOpenState] = useState(false)\n const [fullScreen, setFullScreen] = useState(false)\n // Too narrow for the form beside the pane (e.g. 1280px with the sidebar): full-screen overlay.\n const [overlay, setOverlay] = useState(false)\n const expanded = fullScreen || overlay\n const [device, setDevice] = useState<CmsPreviewDevice>('desktop')\n const root = useRef<HTMLDivElement>(null)\n const autosaveRef = useRef<CmsAutosave | null>(null)\n const status =\n snapshot.hasChanges && component.changedStatus ? component.changedStatus : component.status\n\n // Measured in the browser after mounting: stick right under the panel's topbar when it\n // sticks too, and open the pane beside the form on wide screens (the viewer's last\n // choice wins).\n useEffect(() => {\n const frame = requestAnimationFrame(() => {\n setTop(resolveStickyOffset(null))\n if (component.previewUrl && cmsPreviewOpensOnLoad(root.current)) setOpenState(true)\n })\n\n return () => cancelAnimationFrame(frame)\n }, [component.previewUrl])\n\n /** Open or collapse the pane, remembering the choice for the next visit. */\n const setOpen = (next: boolean) => {\n // Decided in the same render as opening, so the form never makes room for nothing.\n if (next) setOverlay(!cmsPreviewFitsBeside(root.current))\n setOpenState(next)\n rememberedCmsPreview(next ? 'open' : 'closed')\n }\n\n useEffect(() => {\n latest.current = { token: page?.token ?? '', endpoint: panel?.endpoints?.pageCall ?? '' }\n })\n\n useEffect(() => {\n if (!form) return\n\n const autosave = new CmsAutosave({\n debounce: component.debounce,\n previewUrl: component.previewUrl,\n savedAt: component.savedAt,\n hasChanges: component.hasChanges,\n save: (state) => callCmsAutosave(component.autosaveMethod, state, latest.current),\n onChange: setSnapshot,\n })\n\n autosaveRef.current = autosave\n let values = form.state.values\n const unsubscribe = form.subscribe((state) => {\n if (state.values !== values) {\n values = state.values\n autosave.schedule(state.values)\n }\n })\n\n return () => {\n unsubscribe()\n void autosave.flush().finally(() => autosave.dispose())\n }\n }, [form, component.autosaveMethod, component.debounce, component.previewUrl, component.savedAt])\n\n // Measure whether the pane fits beside the form while it is open, and again on resize.\n useEffect(() => {\n if (!open) return\n\n const measure = () => setOverlay(!cmsPreviewFitsBeside(root.current))\n measure()\n window.addEventListener('resize', measure)\n\n return () => window.removeEventListener('resize', measure)\n }, [open])\n\n // Side by side, the form makes room for the pane.\n useEffect(() => {\n if (!open || expanded) return\n\n return reserveCmsPreviewSpace(root.current, 'min(50vw, 56rem)')\n }, [open, expanded])\n\n useEffect(() => {\n if (!open) return\n\n const onKey = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n if (fullScreen && !overlay) setFullScreen(false)\n else {\n setOpenState(false)\n rememberedCmsPreview('closed')\n }\n }\n\n window.addEventListener('keydown', onKey)\n\n return () => window.removeEventListener('keydown', onKey)\n }, [open, fullScreen, overlay])\n\n const width = CMS_PREVIEW_DEVICES[device]\n\n return (\n <div\n ref={root}\n data-slot=\"cms-live-preview\"\n style={{ top }}\n className=\"bg-background sticky z-20 flex flex-wrap items-center gap-x-4 gap-y-2 rounded-xl border px-3 py-2.5 shadow-sm\"\n >\n <div className=\"flex min-w-0 flex-1 basis-64 flex-col gap-0.5\">\n {status && (\n <p\n className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-sm\"\n data-slot=\"cms-editor-status\"\n >\n <Badge\n variant=\"outline\"\n data-color={status.color}\n className={STATUS_COLORS[status.color] ?? ''}\n >\n {status.label}\n </Badge>\n <span className=\"text-muted-foreground\">{status.description}</span>\n </p>\n )}\n <p\n className=\"text-muted-foreground flex min-h-4 items-center gap-1.5 text-xs\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <StatusIcon status={snapshot.status} />\n {statusLabel(snapshot.status, t)}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n {snapshot.previewUrl && (\n <Button\n type=\"button\"\n variant={open ? 'secondary' : 'outline'}\n size=\"sm\"\n aria-pressed={open}\n title={t(\n 'cms.live_preview_help',\n {},\n 'See the page as visitors will, updated as you type.',\n )}\n onClick={() => setOpen(!open)}\n data-slot=\"cms-live-preview-toggle\"\n >\n <EyeIcon aria-hidden=\"true\" />\n {open\n ? t('cms.hide_preview', {}, 'Hide preview')\n : t('cms.live_preview', {}, 'Live preview')}\n </Button>\n )}\n {component.actions && component.actions.length > 0 && (\n <div onPointerDownCapture={() => void autosaveRef.current?.flush()}>\n <ActionList actions={component.actions} context={actionContext} size=\"sm\" />\n </div>\n )}\n </div>\n {open && snapshot.previewUrl && (\n <section\n aria-label={t('cms.live_preview', {}, 'Live preview')}\n data-slot=\"cms-live-preview-pane\"\n data-full-screen={expanded || undefined}\n // Beside the form it starts below the sticky topbar, keeping the topbar usable.\n style={{ top: expanded ? '0px' : top }}\n className={cn(\n 'bg-background fixed end-0 bottom-0 z-50 flex flex-col border-s shadow-xl',\n expanded ? 'inset-x-0 border-s-0' : 'w-full lg:w-[min(50vw,56rem)]',\n )}\n >\n <header className=\"flex items-center justify-between gap-2 border-b px-3 py-2\">\n <div\n className=\"flex items-center gap-1\"\n role=\"group\"\n aria-label={t('cms.preview_width', {}, 'Preview width')}\n >\n {(Object.keys(CMS_PREVIEW_DEVICES) as CmsPreviewDevice[]).map((name) => {\n const DeviceIcon = DEVICE_ICONS[name]\n\n return (\n <Button\n key={name}\n type=\"button\"\n variant={device === name ? 'secondary' : 'ghost'}\n size=\"icon-sm\"\n aria-pressed={device === name}\n onClick={() => setDevice(name)}\n >\n <DeviceIcon aria-hidden=\"true\" />\n <span className=\"sr-only\">\n {t(`cms.device_${name}`, {}, DEVICE_LABELS[name])}\n </span>\n </Button>\n )\n })}\n </div>\n <p className=\"text-muted-foreground hidden truncate text-xs sm:block\">\n {statusLabel(snapshot.status, t)}\n </p>\n <div className=\"flex items-center gap-1\">\n {!overlay && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-pressed={fullScreen}\n onClick={() => setFullScreen(!fullScreen)}\n >\n {fullScreen ? (\n <Minimize2Icon aria-hidden=\"true\" />\n ) : (\n <Maximize2Icon aria-hidden=\"true\" />\n )}\n <span className=\"sr-only\">\n {fullScreen\n ? t('cms.exit_full_screen', {}, 'Exit full screen')\n : t('cms.full_screen', {}, 'Full screen')}\n </span>\n </Button>\n )}\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" onClick={() => setOpen(false)}>\n <XIcon aria-hidden=\"true\" />\n <span className=\"sr-only\">{t('cms.close_preview', {}, 'Close preview')}</span>\n </Button>\n </div>\n </header>\n <div className=\"bg-muted/40 flex flex-1 justify-center overflow-auto p-3\">\n <iframe\n key={snapshot.version}\n src={snapshot.previewUrl}\n title={t('cms.preview_frame', {}, 'Page preview')}\n className=\"bg-background h-full rounded-md border shadow-sm\"\n style={{ width: width === null ? '100%' : `${width}px`, maxWidth: '100%' }}\n />\n </div>\n </section>\n )}\n </div>\n )\n}\n\nfunction StatusIcon({ status }: { status: CmsAutosaveSnapshot['status'] }) {\n if (status === 'saving')\n return <LoaderCircleIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n if (status === 'error')\n return <CircleAlertIcon className=\"text-destructive size-4\" aria-hidden=\"true\" />\n if (status === 'saved') return <CheckIcon className=\"size-4\" aria-hidden=\"true\" />\n\n return null\n}\n\nfunction statusLabel(status: CmsAutosaveSnapshot['status'], t: ReturnType<typeof useT>): string {\n switch (status) {\n case 'saving':\n return t('cms.saving', {}, 'Saving…')\n case 'saved':\n return t('cms.saved', {}, 'Draft saved automatically')\n case 'pending':\n return t('cms.unsaved', {}, 'Unsaved changes')\n case 'error':\n return t(\n 'cms.save_failed',\n {},\n \"Your latest changes couldn't be saved. They will be tried again.\",\n )\n default:\n return ''\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"live-preview.js","names":[],"sources":["../../../src/components/cms/live-preview.tsx"],"sourcesContent":["import {\n CMS_PREVIEW_DEVICES,\n CmsAutosave,\n callCmsAutosave,\n cmsPreviewFitsBeside,\n cmsPreviewOpensOnLoad,\n rememberedCmsPreview,\n reserveCmsPreviewSpace,\n resolveStickyOffset,\n type CmsAutosaveSnapshot,\n type CmsLivePreviewSchema,\n type CmsPreviewDevice,\n} from '@alfakit/core'\nimport {\n CheckIcon,\n CircleAlertIcon,\n EyeIcon,\n LoaderCircleIcon,\n Maximize2Icon,\n Minimize2Icon,\n MonitorIcon,\n SmartphoneIcon,\n TabletIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { usePageSchema, usePanel, useT } from '../../context'\nimport { cn } from '../../lib/utils'\nimport { ActionList } from '../actions/action-list'\nimport { useOptionalFormStore } from '../form/form-context'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\n\n/** Badge colours of the status (the server's `PageState` colours). */\nconst STATUS_COLORS: Record<string, string> = {\n gray: 'border-border bg-muted text-foreground',\n success: 'border-emerald-600/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300',\n warning: 'border-amber-600/30 bg-amber-500/10 text-amber-700 dark:text-amber-300',\n info: 'border-sky-600/30 bg-sky-500/10 text-sky-700 dark:text-sky-300',\n danger: 'border-red-600/30 bg-red-500/10 text-red-700 dark:text-red-300',\n}\n\nconst DEVICE_ICONS = { desktop: MonitorIcon, tablet: TabletIcon, phone: SmartphoneIcon }\nconst DEVICE_LABELS = { desktop: 'Desktop', tablet: 'Tablet', phone: 'Phone' }\n\n/**\n * The page editor's autosave status and live preview (schema type `CmsLivePreview`).\n * Changes are autosaved as a draft a moment after typing stops; the preview pane shows\n * the signed preview of that draft beside the form, full screen, or at tablet and phone\n * widths.\n */\nexport function CmsLivePreview({ component }: { component: CmsLivePreviewSchema }) {\n const t = useT()\n const form = useOptionalFormStore()\n const page = usePageSchema()\n const panel = usePanel()\n const latest = useRef({ token: page?.token ?? '', endpoint: panel?.endpoints?.pageCall ?? '' })\n const [snapshot, setSnapshot] = useState<CmsAutosaveSnapshot>({\n status: component.savedAt ? 'saved' : 'idle',\n savedAt: component.savedAt ?? null,\n previewUrl: component.previewUrl,\n version: 0,\n hasChanges: component.hasChanges ?? false,\n })\n const [top, setTop] = useState('0px')\n const actionContext = useMemo(\n () => ({ token: page?.token ?? '', component: 'page' }),\n [page?.token],\n )\n\n const [open, setOpenState] = useState(false)\n const [fullScreen, setFullScreen] = useState(false)\n // Too narrow for the form beside the pane (e.g. 1280px with the sidebar): full-screen overlay.\n const [overlay, setOverlay] = useState(false)\n const expanded = fullScreen || overlay\n const [device, setDevice] = useState<CmsPreviewDevice>('desktop')\n const root = useRef<HTMLDivElement>(null)\n const autosaveRef = useRef<CmsAutosave | null>(null)\n const status =\n snapshot.hasChanges && component.changedStatus ? component.changedStatus : component.status\n // \"Publish changes\" appears as soon as an autosave makes the draft differ.\n const actions =\n snapshot.hasChanges && component.changedActions ? component.changedActions : component.actions\n\n // Measured in the browser after mounting: stick right under the panel's topbar when it\n // sticks too, and open the pane beside the form on wide screens (the viewer's last\n // choice wins).\n useEffect(() => {\n const frame = requestAnimationFrame(() => {\n setTop(resolveStickyOffset(null))\n if (component.previewUrl && cmsPreviewOpensOnLoad(root.current)) setOpenState(true)\n })\n\n return () => cancelAnimationFrame(frame)\n }, [component.previewUrl])\n\n /** Open or collapse the pane, remembering the choice for the next visit. */\n const setOpen = (next: boolean) => {\n // Decided in the same render as opening, so the form never makes room for nothing.\n if (next) setOverlay(!cmsPreviewFitsBeside(root.current))\n setOpenState(next)\n rememberedCmsPreview(next ? 'open' : 'closed')\n }\n\n useEffect(() => {\n latest.current = { token: page?.token ?? '', endpoint: panel?.endpoints?.pageCall ?? '' }\n })\n\n useEffect(() => {\n if (!form) return\n\n const autosave = new CmsAutosave({\n debounce: component.debounce,\n previewUrl: component.previewUrl,\n savedAt: component.savedAt,\n hasChanges: component.hasChanges,\n save: (state) => callCmsAutosave(component.autosaveMethod, state, latest.current),\n onChange: setSnapshot,\n })\n\n autosaveRef.current = autosave\n let values = form.state.values\n const unsubscribe = form.subscribe((state) => {\n if (state.values !== values) {\n values = state.values\n autosave.schedule(state.values)\n }\n })\n\n return () => {\n unsubscribe()\n void autosave.flush().finally(() => autosave.dispose())\n }\n }, [form, component.autosaveMethod, component.debounce, component.previewUrl, component.savedAt])\n\n // Measure whether the pane fits beside the form while it is open, and again on resize.\n useEffect(() => {\n if (!open) return\n\n const measure = () => setOverlay(!cmsPreviewFitsBeside(root.current))\n measure()\n window.addEventListener('resize', measure)\n\n return () => window.removeEventListener('resize', measure)\n }, [open])\n\n // Side by side, the form makes room for the pane.\n useEffect(() => {\n if (!open || expanded) return\n\n return reserveCmsPreviewSpace(root.current, 'min(50vw, 56rem)')\n }, [open, expanded])\n\n useEffect(() => {\n if (!open) return\n\n const onKey = (event: KeyboardEvent) => {\n if (event.key !== 'Escape') return\n if (fullScreen && !overlay) setFullScreen(false)\n else {\n setOpenState(false)\n rememberedCmsPreview('closed')\n }\n }\n\n window.addEventListener('keydown', onKey)\n\n return () => window.removeEventListener('keydown', onKey)\n }, [open, fullScreen, overlay])\n\n const width = CMS_PREVIEW_DEVICES[device]\n\n return (\n <div\n ref={root}\n data-slot=\"cms-live-preview\"\n style={{ top }}\n className=\"bg-background sticky z-20 flex flex-wrap items-center gap-x-4 gap-y-2 rounded-xl border px-3 py-2.5 shadow-sm\"\n >\n <div className=\"flex min-w-0 flex-1 basis-64 flex-col gap-0.5\">\n {status && (\n <p\n className=\"flex flex-wrap items-center gap-x-2 gap-y-1 text-sm\"\n data-slot=\"cms-editor-status\"\n >\n <Badge\n variant=\"outline\"\n data-color={status.color}\n className={STATUS_COLORS[status.color] ?? ''}\n >\n {status.label}\n </Badge>\n <span className=\"text-muted-foreground\">{status.description}</span>\n </p>\n )}\n <p\n className=\"text-muted-foreground flex min-h-4 items-center gap-1.5 text-xs\"\n role=\"status\"\n aria-live=\"polite\"\n >\n <StatusIcon status={snapshot.status} />\n {statusLabel(snapshot.status, t)}\n </p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n {snapshot.previewUrl && (\n <Button\n type=\"button\"\n variant={open ? 'secondary' : 'outline'}\n size=\"sm\"\n aria-pressed={open}\n title={t(\n 'cms.live_preview_help',\n {},\n 'See the page as visitors will, updated as you type.',\n )}\n onClick={() => setOpen(!open)}\n data-slot=\"cms-live-preview-toggle\"\n >\n <EyeIcon aria-hidden=\"true\" />\n {open\n ? t('cms.hide_preview', {}, 'Hide preview')\n : t('cms.live_preview', {}, 'Live preview')}\n </Button>\n )}\n {actions && actions.length > 0 && (\n <div onPointerDownCapture={() => void autosaveRef.current?.flush()}>\n <ActionList actions={actions} context={actionContext} size=\"sm\" />\n </div>\n )}\n </div>\n {open && snapshot.previewUrl && (\n <section\n aria-label={t('cms.live_preview', {}, 'Live preview')}\n data-slot=\"cms-live-preview-pane\"\n data-full-screen={expanded || undefined}\n // Beside the form it starts below the sticky topbar, keeping the topbar usable.\n style={{ top: expanded ? '0px' : top }}\n className={cn(\n 'bg-background fixed end-0 bottom-0 z-50 flex flex-col border-s shadow-xl',\n expanded ? 'inset-x-0 border-s-0' : 'w-full lg:w-[min(50vw,56rem)]',\n )}\n >\n <header className=\"flex items-center justify-between gap-2 border-b px-3 py-2\">\n <div\n className=\"flex items-center gap-1\"\n role=\"group\"\n aria-label={t('cms.preview_width', {}, 'Preview width')}\n >\n {(Object.keys(CMS_PREVIEW_DEVICES) as CmsPreviewDevice[]).map((name) => {\n const DeviceIcon = DEVICE_ICONS[name]\n\n return (\n <Button\n key={name}\n type=\"button\"\n variant={device === name ? 'secondary' : 'ghost'}\n size=\"icon-sm\"\n aria-pressed={device === name}\n onClick={() => setDevice(name)}\n >\n <DeviceIcon aria-hidden=\"true\" />\n <span className=\"sr-only\">\n {t(`cms.device_${name}`, {}, DEVICE_LABELS[name])}\n </span>\n </Button>\n )\n })}\n </div>\n <p className=\"text-muted-foreground hidden truncate text-xs sm:block\">\n {statusLabel(snapshot.status, t)}\n </p>\n <div className=\"flex items-center gap-1\">\n {!overlay && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-pressed={fullScreen}\n onClick={() => setFullScreen(!fullScreen)}\n >\n {fullScreen ? (\n <Minimize2Icon aria-hidden=\"true\" />\n ) : (\n <Maximize2Icon aria-hidden=\"true\" />\n )}\n <span className=\"sr-only\">\n {fullScreen\n ? t('cms.exit_full_screen', {}, 'Exit full screen')\n : t('cms.full_screen', {}, 'Full screen')}\n </span>\n </Button>\n )}\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" onClick={() => setOpen(false)}>\n <XIcon aria-hidden=\"true\" />\n <span className=\"sr-only\">{t('cms.close_preview', {}, 'Close preview')}</span>\n </Button>\n </div>\n </header>\n <div className=\"bg-muted/40 flex flex-1 justify-center overflow-auto p-3\">\n <iframe\n key={snapshot.version}\n src={snapshot.previewUrl}\n title={t('cms.preview_frame', {}, 'Page preview')}\n className=\"bg-background h-full rounded-md border shadow-sm\"\n style={{ width: width === null ? '100%' : `${width}px`, maxWidth: '100%' }}\n />\n </div>\n </section>\n )}\n </div>\n )\n}\n\nfunction StatusIcon({ status }: { status: CmsAutosaveSnapshot['status'] }) {\n if (status === 'saving')\n return <LoaderCircleIcon className=\"size-4 animate-spin\" aria-hidden=\"true\" />\n if (status === 'error')\n return <CircleAlertIcon className=\"text-destructive size-4\" aria-hidden=\"true\" />\n if (status === 'saved') return <CheckIcon className=\"size-4\" aria-hidden=\"true\" />\n\n return null\n}\n\nfunction statusLabel(status: CmsAutosaveSnapshot['status'], t: ReturnType<typeof useT>): string {\n switch (status) {\n case 'saving':\n return t('cms.saving', {}, 'Saving…')\n case 'saved':\n return t('cms.saved', {}, 'Draft saved automatically')\n case 'pending':\n return t('cms.unsaved', {}, 'Unsaved changes')\n case 'error':\n return t(\n 'cms.save_failed',\n {},\n \"Your latest changes couldn't be saved. They will be tried again.\",\n )\n default:\n return ''\n }\n}\n"],"mappings":"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"}
@@ -1,11 +1,11 @@
1
1
  import { useOptionalRegistry } from "../../context.js";
2
2
  import { cn } from "../../lib/utils.js";
3
3
  import { lazyComponent } from "../../lib/lazy.js";
4
- import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../ui/breadcrumb.js";
5
4
  import { CmsProvider, useCms } from "./context.js";
6
5
  import { CmsBlocks } from "./blocks.js";
7
6
  import { CmsHead } from "./head.js";
8
7
  import { CmsLanguageSwitcher, CmsMenuList, CmsNav } from "./menu.js";
8
+ import { Breadcrumb, BreadcrumbItem, BreadcrumbLink, BreadcrumbList, BreadcrumbPage, BreadcrumbSeparator } from "../ui/breadcrumb.js";
9
9
  import { CMS_FOOTER_KEY, CMS_HEADER_KEY, cmsPayload } from "@alfakit/core";
10
10
  import { usePage } from "@inertiajs/react";
11
11
  import { Fragment, createElement, useEffect } from "react";
@@ -47,7 +47,7 @@ function BuilderFieldContent({ component }) {
47
47
  const context = useFieldActionContext(component);
48
48
  const runner = useActionRunner();
49
49
  const runItemAction = useRunItemAction(context);
50
- const fallbacks = useItemActionFallbacks(component.addActionLabel);
50
+ const fallbacks = useItemActionFallbacks(component.addActionLabel, "builder");
51
51
  const resolve = useItemActions(component, fallbacks);
52
52
  const virtual = useVirtualList(items.length, virtualizeOptions(component.virtualize, VIRTUAL_ESTIMATES.item, 12));
53
53
  const { range, indexes, style, attach, item: attachItem } = virtual;
@@ -80,6 +80,9 @@ function BuilderFieldContent({ component }) {
80
80
  if (!open) form?.refresh(component.statePath);
81
81
  };
82
82
  const blockOf = (type) => component.blocks.find((block) => block.name === type);
83
+ const refreshSummaries = (indexes) => {
84
+ if (indexes.some((index) => blockOf(items[index]?.type ?? "")?.summary)) form?.refresh(component.statePath);
85
+ };
83
86
  const underMax = component.maxItems == null || items.length < component.maxItems;
84
87
  const canAdd = component.addable && !locked && underMax;
85
88
  const canDelete = component.deletable && !locked && (component.minItems == null || items.length > component.minItems);
@@ -122,7 +125,10 @@ function BuilderFieldContent({ component }) {
122
125
  component.collapsible && items.length > 0 && /* @__PURE__ */ jsx(CollapseAllActions, {
123
126
  context,
124
127
  resolve: (name) => resolve(name),
125
- onCollapseAll: () => setCollapsed(Object.fromEntries(items.map((_, index) => [index, true]))),
128
+ onCollapseAll: () => {
129
+ setCollapsed(Object.fromEntries(items.map((_, index) => [index, true])));
130
+ refreshSummaries(items.map((_, index) => index));
131
+ },
126
132
  onExpandAll: () => setCollapsed(Object.fromEntries(items.map((_, index) => [index, false])))
127
133
  }),
128
134
  /* @__PURE__ */ jsx("ul", {
@@ -213,10 +219,13 @@ function BuilderFieldContent({ component }) {
213
219
  onMove: moveWithButton,
214
220
  onClone: () => cloneItem(index),
215
221
  onDelete: () => removeItem(index),
216
- onToggle: () => setCollapsed((state) => ({
217
- ...state,
218
- [index]: !isCollapsed
219
- })),
222
+ onToggle: () => {
223
+ setCollapsed((state) => ({
224
+ ...state,
225
+ [index]: !isCollapsed
226
+ }));
227
+ if (!isCollapsed) refreshSummaries([index]);
228
+ },
220
229
  children: [hasPreview && !locked && /* @__PURE__ */ jsx(Button, {
221
230
  type: "button",
222
231
  variant: "ghost",
@@ -1 +1 @@
1
- {"version":3,"file":"builder.js","names":[],"sources":["../../../src/components/fields/builder.tsx"],"sourcesContent":["import type {\n ActionSchema,\n BuilderBlockSchema,\n BuilderItemState,\n BuilderSchema,\n ItemActionName,\n ItemActionSchema,\n} from '@alfakit/core'\nimport {\n builderItemLabel,\n builderItems,\n builderItemSchema,\n canAddBuilderBlock,\n cloneIndexedState,\n cloneRepeaterItem,\n fieldIds,\n insertBuilderItem,\n isDeferredPlaceholder,\n itemActionContext,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n VIRTUAL_ESTIMATES,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { EllipsisIcon, PencilIcon } from 'lucide-react'\nimport { createElement, useState, type KeyboardEvent } from 'react'\nimport { useActionRunner, useFormatter, useOptionalRegistry, useT } from '../../context'\nimport { useScrollToErroredItem, useVirtualList } from '../../hooks/use-virtual-list'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField, useOptionalFormStore } from '../form/form-context'\nimport { DeferredSchema } from '../layout/deferred'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport { Collapsible, CollapsibleContent } from '../ui/collapsible'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { BuilderBlockPicker, BuilderBlockPickerPanel } from './builder-picker'\nimport {\n AddActionRow,\n CollapseAllActions,\n ItemHeaderActions,\n useItemActionFallbacks,\n useItemActions,\n useItemMoves,\n useRunItemAction,\n} from './item-actions'\n\n/**\n * A list of blocks, each with its own schema. Items use the server's per-item schema\n * when it describes the same block, else the block's template (fresh, moved or cloned\n * items the server has not seen yet).\n */\n/**\n * `deferLoading()`: a skeleton until the field scrolls into view, then the field with its\n * items (loaded from the server).\n */\nexport function BuilderField({ component }: { component: BuilderSchema }) {\n if (!isDeferredPlaceholder(component)) return <BuilderFieldContent component={component} />\n\n return (\n <DeferredSchema component={component}>\n {(loaded) => <BuilderFieldContent component={loaded} />}\n </DeferredSchema>\n )\n}\n\nfunction BuilderFieldContent({ component }: { component: BuilderSchema }) {\n const t = useT()\n const formatter = useFormatter()\n const registry = useOptionalRegistry()\n const form = useOptionalFormStore()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<unknown>(component)\n const locked = disabled || readOnly\n const items = builderItems(value)\n const [collapsed, setCollapsed] = useState<Record<number, boolean>>({})\n const [editing, setEditing] = useState<Record<number, boolean>>({})\n const [dragIndex, setDragIndex] = useState<number | null>(null)\n const context = useFieldActionContext(component)\n const runner = useActionRunner()\n const runItemAction = useRunItemAction(context)\n const fallbacks = useItemActionFallbacks(component.addActionLabel)\n const resolve = useItemActions(component, fallbacks)\n // Long lists render only the visible blocks (plus a few around them).\n const virtual = useVirtualList(\n items.length,\n virtualizeOptions(component.virtualize, VIRTUAL_ESTIMATES.item, 12),\n )\n const { range, indexes, style, attach, item: attachItem } = virtual\n useScrollToErroredItem(virtual.list, range.active, component.statePath)\n\n const update = (next: unknown[]) => setValue(next as BuilderItemState[])\n const moveItem = (from: number, to: number) => {\n update(moveRepeaterItem(items, from, to))\n setCollapsed((state) => moveIndexedState(state, from, to))\n setEditing((state) => moveIndexedState(state, from, to))\n }\n const removeItem = (index: number) => {\n update(removeRepeaterItem(items, index))\n setCollapsed((state) => removeIndexedState(state, index))\n setEditing((state) => removeIndexedState(state, index))\n }\n const cloneItem = (index: number) => {\n update(cloneRepeaterItem(items, index))\n setCollapsed((state) => cloneIndexedState(state, index))\n setEditing((state) => cloneIndexedState(state, index))\n }\n const addBlock = (block: BuilderBlockSchema) =>\n runItemAction(resolve('add'), { block: block.name }, () =>\n update(insertBuilderItem(items, block)),\n )\n // Move buttons: announced, one at a time, focus kept on the moved item.\n const {\n moveWithButton,\n region: moveRegion,\n containerRef: moveContainerRef,\n } = useItemMoves(\n value,\n moveItem,\n (index) => builderItemLabel(component, items, index),\n items.length,\n )\n const dropItem = (from: number, to: number) =>\n runItemAction(resolve('reorder'), { items: reorderedItemKeys(items.length, from, to) }, () =>\n moveItem(from, to),\n )\n // Closing the editor re-renders the previews from the server.\n const toggleEditing = (index: number) => {\n const open = !editing[index]\n setEditing((state) => ({ ...state, [index]: open }))\n if (!open) void form?.refresh(component.statePath)\n }\n\n const blockOf = (type: string) => component.blocks.find((block) => block.name === type)\n const underMax = component.maxItems == null || items.length < component.maxItems\n const canAdd = component.addable && !locked && underMax\n const canDelete =\n component.deletable &&\n !locked &&\n (component.minItems == null || items.length > component.minItems)\n const draggable =\n component.reorderable &&\n component.reorderableWithDragAndDrop !== false &&\n !locked &&\n resolve('reorder') !== null\n const buttons = component.reorderable && component.reorderableWithButtons !== false && !locked\n const addAction = resolve('add')\n const showEmptyPicker =\n canAdd && addAction !== null && !addAction.disabled && component.blocks.length > 0\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div ref={moveContainerRef} className=\"flex flex-col gap-3\" data-slot=\"builder\">\n {moveRegion}\n {items.length === 0 &&\n (showEmptyPicker ? (\n // No blocks yet: the picker is right there instead of behind a button.\n <div\n data-slot=\"builder-empty\"\n className=\"bg-muted/30 flex flex-col gap-3 rounded-lg border border-dashed p-3 sm:p-4\"\n >\n <div className=\"px-1\">\n <p className=\"text-sm font-medium\">\n {t('builder.empty_heading', {}, 'Add your first block')}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {t(\n 'builder.empty_description',\n {},\n 'Pick one to start. You can add more, reorder and remove them at any time.',\n )}\n </p>\n </div>\n <BuilderBlockPickerPanel\n component={component}\n items={items}\n onPick={addBlock}\n className=\"bg-card max-h-[28rem] rounded-md border\"\n />\n </div>\n ) : (\n <p className=\"text-muted-foreground rounded-lg border border-dashed p-4 text-center text-sm\">\n {t('builder.empty', {}, 'No blocks yet.')}\n </p>\n ))}\n {component.collapsible && items.length > 0 && (\n <CollapseAllActions\n context={context}\n resolve={(name) => resolve(name)}\n onCollapseAll={() =>\n setCollapsed(Object.fromEntries(items.map((_, index) => [index, true])))\n }\n onExpandAll={() =>\n setCollapsed(Object.fromEntries(items.map((_, index) => [index, false])))\n }\n />\n )}\n <ul\n id={id}\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n ref={attach}\n style={style}\n className={cn('grid gap-3', items.length === 0 && 'hidden')}\n >\n {indexes.map((index) => {\n const item = items[index] as BuilderItemState\n const block = blockOf(item.type)\n const isCollapsed = collapsed[index] ?? component.collapsed ?? false\n const label = component.blockLabels ? builderItemLabel(component, items, index) : null\n const icon = block?.icon ?? component.items[index]?.icon\n const server = component.items[index]\n const serverItem = server?.type === item.type ? server : undefined\n const previewName = serverItem?.previewComponent ?? block?.previewComponent\n const Preview = previewName\n ? registry?.resolve(`builder-preview:${previewName}`)\n : undefined\n const hasPreview =\n component.blockPreviews === true &&\n (Preview !== undefined || Boolean(serverItem?.preview))\n const showPreview = hasPreview && !editing[index]\n const summary = serverItem?.summary\n const menu = Boolean(component.blockActionsMenu) && !locked\n const canClone =\n Boolean(component.cloneable) &&\n canAdd &&\n block !== undefined &&\n canAddBuilderBlock(component, block, items)\n const openEditor = () => {\n if (!locked) toggleEditing(index)\n }\n\n return (\n <li\n key={index}\n ref={attachItem}\n data-index={index}\n aria-setsize={range.active ? items.length : undefined}\n aria-posinset={range.active ? index + 1 : undefined}\n data-block={item.type}\n draggable={draggable}\n onDragStart={() => setDragIndex(index)}\n onDragOver={(event) => draggable && event.preventDefault()}\n onDrop={() => {\n if (dragIndex !== null && dragIndex !== index) dropItem(dragIndex, index)\n setDragIndex(null)\n }}\n onDragEnd={() => setDragIndex(null)}\n className={cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n )}\n >\n <Collapsible open={!isCollapsed}>\n <div\n className=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n {draggable && (\n <span\n className=\"text-muted-foreground cursor-grab\"\n title={resolve('reorder')?.label ?? undefined}\n aria-hidden=\"true\"\n >\n <Icon name={resolve('reorder')?.icon ?? 'grip-vertical'} />\n </span>\n )}\n {component.blockIcons && icon && (\n <Icon name={icon} className=\"text-muted-foreground size-4\" />\n )}\n {component.blockNumbers && (\n <span className=\"text-muted-foreground text-xs tabular-nums\">\n {formatter.number(index + 1)}\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 items-baseline gap-2\">\n {label && (\n <span\n dir=\"auto\"\n className=\"min-w-0 shrink-0 truncate text-sm font-medium sm:max-w-[60%]\"\n >\n {label}\n </span>\n )}\n {summary && (\n <span\n dir=\"auto\"\n className=\"text-muted-foreground min-w-0 truncate text-sm\"\n data-slot=\"builder-item-summary\"\n >\n {summary}\n </span>\n )}\n </span>\n <ItemHeaderActions\n context={context}\n itemKey={String(index)}\n index={index}\n count={items.length}\n resolve={(name) => resolve(name, serverItem)}\n extraActions={menu || locked ? undefined : serverItem?.actions}\n buttons={buttons && !menu}\n canClone={canClone && !menu}\n canDelete={canDelete && !menu}\n collapsible={Boolean(component.collapsible)}\n collapsed={isCollapsed}\n onMove={moveWithButton}\n onClone={() => cloneItem(index)}\n onDelete={() => removeItem(index)}\n onToggle={() =>\n setCollapsed((state) => ({ ...state, [index]: !isCollapsed }))\n }\n >\n {hasPreview && !locked && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('builder.edit_block', {}, 'Edit block')}\n aria-pressed={Boolean(editing[index])}\n onClick={() => toggleEditing(index)}\n >\n <Icon\n name=\"alfakit::forms.components.builder.actions.edit\"\n fallback={PencilIcon}\n />\n </Button>\n )}\n {menu && (\n <BlockActionsMenu\n label={label ?? ''}\n resolve={(name) => resolve(name, serverItem)}\n canMoveUp={buttons && index > 0}\n canMoveDown={buttons && index < items.length - 1}\n canClone={canClone}\n canDelete={canDelete}\n extraActions={locked ? undefined : serverItem?.actions}\n onAction={(name, onClient) =>\n runItemAction(\n resolve(name, serverItem),\n { item: String(index) },\n onClient,\n )\n }\n onExtraAction={(action) =>\n void runner.trigger(\n action,\n itemActionContext(context, { item: String(index) }),\n )\n }\n onMove={(to) => moveWithButton(index, to)}\n index={index}\n onClone={() => cloneItem(index)}\n onDelete={() => removeItem(index)}\n />\n )}\n </ItemHeaderActions>\n </div>\n <CollapsibleContent>\n {showPreview ? (\n <div\n data-slot=\"builder-preview\"\n className={cn(\n 'p-4',\n !component.interactivePreviews && !locked && 'cursor-pointer',\n )}\n {...(component.interactivePreviews || locked\n ? {}\n : {\n role: 'button',\n tabIndex: 0,\n 'aria-label': t('builder.edit_block', {}, 'Edit block'),\n onClick: openEditor,\n onKeyDown: (event: KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n openEditor()\n }\n },\n })}\n >\n <div\n className={cn(!component.interactivePreviews && 'pointer-events-none')}\n >\n {Preview ? (\n createElement(Preview, { data: item.data, block: item.type, index })\n ) : (\n <div dangerouslySetInnerHTML={{ __html: serverItem?.preview ?? '' }} />\n )}\n </div>\n </div>\n ) : (\n <div className=\"p-4\">\n <SchemaRenderer\n components={builderItemSchema(component, items, index)}\n columns={\n block?.columns ?? component.items[index]?.columns ?? { default: 1 }\n }\n />\n </div>\n )}\n </CollapsibleContent>\n </Collapsible>\n </li>\n )\n })}\n </ul>\n {canAdd && addAction && component.blocks.length > 0 && items.length > 0 && (\n <AddActionRow alignment={component.addActionAlignment}>\n <BuilderBlockPicker\n component={component}\n items={items}\n onPick={addBlock}\n trigger={\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={addAction.disabled}\n data-slot=\"builder-add\"\n >\n {addAction.icon && <Icon name={addAction.icon} />}\n {addAction.label}\n </Button>\n }\n />\n </AddActionRow>\n )}\n </div>\n </FieldWrapper>\n )\n}\n\n/**\n * `blockActionsMenu()`: an item's actions in one compact menu (move up / down, duplicate,\n * extra item actions, delete). Built-in actions run in the browser unless customised.\n */\nfunction BlockActionsMenu({\n label,\n resolve,\n canMoveUp,\n canMoveDown,\n canClone,\n canDelete,\n extraActions,\n index,\n onAction,\n onExtraAction,\n onMove,\n onClone,\n onDelete,\n}: {\n label: string\n resolve: (name: ItemActionName) => ItemActionSchema | null\n canMoveUp: boolean\n canMoveDown: boolean\n canClone: boolean\n canDelete: boolean\n extraActions?: ActionSchema[]\n index: number\n onAction: (name: ItemActionName, onClient: () => void) => void\n onExtraAction: (action: ActionSchema) => void\n onMove: (to: number) => void\n onClone: () => void\n onDelete: () => void\n}) {\n const t = useT()\n const entry = (\n name: ItemActionName,\n enabled: boolean,\n onClient: () => void,\n destructive = false,\n ) => {\n const action = resolve(name)\n\n if (!action || !enabled) return null\n\n return (\n <DropdownMenuItem\n key={name}\n variant={destructive ? 'destructive' : 'default'}\n disabled={action.disabled}\n onSelect={() => onAction(name, onClient)}\n >\n {action.icon && <Icon name={action.icon} />}\n {name === 'clone' ? t('builder.duplicate_block', {}, 'Duplicate') : action.label}\n </DropdownMenuItem>\n )\n }\n const visibleExtras = extraActions ?? []\n const hasMain = canMoveUp || canMoveDown || canClone || visibleExtras.length > 0\n\n if (!hasMain && !canDelete) return null\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n data-slot=\"builder-item-menu\"\n aria-label={t('builder.block_actions', { block: label }, 'Actions for :block')}\n >\n <EllipsisIcon aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-44\">\n {entry('moveUp', canMoveUp, () => onMove(index - 1))}\n {entry('moveDown', canMoveDown, () => onMove(index + 1))}\n {entry('clone', canClone, onClone)}\n {visibleExtras.map((action) => (\n <DropdownMenuItem\n key={action.name}\n disabled={action.disabled}\n onSelect={() => onExtraAction(action)}\n >\n {action.icon && <Icon name={action.icon} />}\n {action.label}\n </DropdownMenuItem>\n ))}\n {canDelete && hasMain && <DropdownMenuSeparator />}\n {entry('delete', canDelete, onDelete, true)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"builder.js","names":[],"sources":["../../../src/components/fields/builder.tsx"],"sourcesContent":["import type {\n ActionSchema,\n BuilderBlockSchema,\n BuilderItemState,\n BuilderSchema,\n ItemActionName,\n ItemActionSchema,\n} from '@alfakit/core'\nimport {\n builderItemLabel,\n builderItems,\n builderItemSchema,\n canAddBuilderBlock,\n cloneIndexedState,\n cloneRepeaterItem,\n fieldIds,\n insertBuilderItem,\n isDeferredPlaceholder,\n itemActionContext,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n VIRTUAL_ESTIMATES,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { EllipsisIcon, PencilIcon } from 'lucide-react'\nimport { createElement, useState, type KeyboardEvent } from 'react'\nimport { useActionRunner, useFormatter, useOptionalRegistry, useT } from '../../context'\nimport { useScrollToErroredItem, useVirtualList } from '../../hooks/use-virtual-list'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField, useOptionalFormStore } from '../form/form-context'\nimport { DeferredSchema } from '../layout/deferred'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport { Collapsible, CollapsibleContent } from '../ui/collapsible'\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { BuilderBlockPicker, BuilderBlockPickerPanel } from './builder-picker'\nimport {\n AddActionRow,\n CollapseAllActions,\n ItemHeaderActions,\n useItemActionFallbacks,\n useItemActions,\n useItemMoves,\n useRunItemAction,\n} from './item-actions'\n\n/**\n * A list of blocks, each with its own schema. Items use the server's per-item schema\n * when it describes the same block, else the block's template (fresh, moved or cloned\n * items the server has not seen yet).\n */\n/**\n * `deferLoading()`: a skeleton until the field scrolls into view, then the field with its\n * items (loaded from the server).\n */\nexport function BuilderField({ component }: { component: BuilderSchema }) {\n if (!isDeferredPlaceholder(component)) return <BuilderFieldContent component={component} />\n\n return (\n <DeferredSchema component={component}>\n {(loaded) => <BuilderFieldContent component={loaded} />}\n </DeferredSchema>\n )\n}\n\nfunction BuilderFieldContent({ component }: { component: BuilderSchema }) {\n const t = useT()\n const formatter = useFormatter()\n const registry = useOptionalRegistry()\n const form = useOptionalFormStore()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<unknown>(component)\n const locked = disabled || readOnly\n const items = builderItems(value)\n const [collapsed, setCollapsed] = useState<Record<number, boolean>>({})\n const [editing, setEditing] = useState<Record<number, boolean>>({})\n const [dragIndex, setDragIndex] = useState<number | null>(null)\n const context = useFieldActionContext(component)\n const runner = useActionRunner()\n const runItemAction = useRunItemAction(context)\n const fallbacks = useItemActionFallbacks(component.addActionLabel, 'builder')\n const resolve = useItemActions(component, fallbacks)\n // Long lists render only the visible blocks (plus a few around them).\n const virtual = useVirtualList(\n items.length,\n virtualizeOptions(component.virtualize, VIRTUAL_ESTIMATES.item, 12),\n )\n const { range, indexes, style, attach, item: attachItem } = virtual\n useScrollToErroredItem(virtual.list, range.active, component.statePath)\n\n const update = (next: unknown[]) => setValue(next as BuilderItemState[])\n const moveItem = (from: number, to: number) => {\n update(moveRepeaterItem(items, from, to))\n setCollapsed((state) => moveIndexedState(state, from, to))\n setEditing((state) => moveIndexedState(state, from, to))\n }\n const removeItem = (index: number) => {\n update(removeRepeaterItem(items, index))\n setCollapsed((state) => removeIndexedState(state, index))\n setEditing((state) => removeIndexedState(state, index))\n }\n const cloneItem = (index: number) => {\n update(cloneRepeaterItem(items, index))\n setCollapsed((state) => cloneIndexedState(state, index))\n setEditing((state) => cloneIndexedState(state, index))\n }\n const addBlock = (block: BuilderBlockSchema) =>\n runItemAction(resolve('add'), { block: block.name }, () =>\n update(insertBuilderItem(items, block)),\n )\n // Move buttons: announced, one at a time, focus kept on the moved item.\n const {\n moveWithButton,\n region: moveRegion,\n containerRef: moveContainerRef,\n } = useItemMoves(\n value,\n moveItem,\n (index) => builderItemLabel(component, items, index),\n items.length,\n )\n const dropItem = (from: number, to: number) =>\n runItemAction(resolve('reorder'), { items: reorderedItemKeys(items.length, from, to) }, () =>\n moveItem(from, to),\n )\n // Closing the editor re-renders the previews from the server.\n const toggleEditing = (index: number) => {\n const open = !editing[index]\n setEditing((state) => ({ ...state, [index]: open }))\n if (!open) void form?.refresh(component.statePath)\n }\n\n const blockOf = (type: string) => component.blocks.find((block) => block.name === type)\n // Summaries are made on the server: collapsing items whose block has one brings them up\n // to date with the edits.\n const refreshSummaries = (indexes: number[]) => {\n if (indexes.some((index) => blockOf(items[index]?.type ?? '')?.summary)) {\n void form?.refresh(component.statePath)\n }\n }\n const underMax = component.maxItems == null || items.length < component.maxItems\n const canAdd = component.addable && !locked && underMax\n const canDelete =\n component.deletable &&\n !locked &&\n (component.minItems == null || items.length > component.minItems)\n const draggable =\n component.reorderable &&\n component.reorderableWithDragAndDrop !== false &&\n !locked &&\n resolve('reorder') !== null\n const buttons = component.reorderable && component.reorderableWithButtons !== false && !locked\n const addAction = resolve('add')\n const showEmptyPicker =\n canAdd && addAction !== null && !addAction.disabled && component.blocks.length > 0\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div ref={moveContainerRef} className=\"flex flex-col gap-3\" data-slot=\"builder\">\n {moveRegion}\n {items.length === 0 &&\n (showEmptyPicker ? (\n // No blocks yet: the picker is right there instead of behind a button.\n <div\n data-slot=\"builder-empty\"\n className=\"bg-muted/30 flex flex-col gap-3 rounded-lg border border-dashed p-3 sm:p-4\"\n >\n <div className=\"px-1\">\n <p className=\"text-sm font-medium\">\n {t('builder.empty_heading', {}, 'Add your first block')}\n </p>\n <p className=\"text-muted-foreground text-sm\">\n {t(\n 'builder.empty_description',\n {},\n 'Pick one to start. You can add more, reorder and remove them at any time.',\n )}\n </p>\n </div>\n <BuilderBlockPickerPanel\n component={component}\n items={items}\n onPick={addBlock}\n className=\"bg-card max-h-[28rem] rounded-md border\"\n />\n </div>\n ) : (\n <p className=\"text-muted-foreground rounded-lg border border-dashed p-4 text-center text-sm\">\n {t('builder.empty', {}, 'No blocks yet.')}\n </p>\n ))}\n {component.collapsible && items.length > 0 && (\n <CollapseAllActions\n context={context}\n resolve={(name) => resolve(name)}\n onCollapseAll={() => {\n setCollapsed(Object.fromEntries(items.map((_, index) => [index, true])))\n refreshSummaries(items.map((_, index) => index))\n }}\n onExpandAll={() =>\n setCollapsed(Object.fromEntries(items.map((_, index) => [index, false])))\n }\n />\n )}\n <ul\n id={id}\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n ref={attach}\n style={style}\n className={cn('grid gap-3', items.length === 0 && 'hidden')}\n >\n {indexes.map((index) => {\n const item = items[index] as BuilderItemState\n const block = blockOf(item.type)\n const isCollapsed = collapsed[index] ?? component.collapsed ?? false\n const label = component.blockLabels ? builderItemLabel(component, items, index) : null\n const icon = block?.icon ?? component.items[index]?.icon\n const server = component.items[index]\n const serverItem = server?.type === item.type ? server : undefined\n const previewName = serverItem?.previewComponent ?? block?.previewComponent\n const Preview = previewName\n ? registry?.resolve(`builder-preview:${previewName}`)\n : undefined\n const hasPreview =\n component.blockPreviews === true &&\n (Preview !== undefined || Boolean(serverItem?.preview))\n const showPreview = hasPreview && !editing[index]\n const summary = serverItem?.summary\n const menu = Boolean(component.blockActionsMenu) && !locked\n const canClone =\n Boolean(component.cloneable) &&\n canAdd &&\n block !== undefined &&\n canAddBuilderBlock(component, block, items)\n const openEditor = () => {\n if (!locked) toggleEditing(index)\n }\n\n return (\n <li\n key={index}\n ref={attachItem}\n data-index={index}\n aria-setsize={range.active ? items.length : undefined}\n aria-posinset={range.active ? index + 1 : undefined}\n data-block={item.type}\n draggable={draggable}\n onDragStart={() => setDragIndex(index)}\n onDragOver={(event) => draggable && event.preventDefault()}\n onDrop={() => {\n if (dragIndex !== null && dragIndex !== index) dropItem(dragIndex, index)\n setDragIndex(null)\n }}\n onDragEnd={() => setDragIndex(null)}\n className={cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n )}\n >\n <Collapsible open={!isCollapsed}>\n <div\n className=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n {draggable && (\n <span\n className=\"text-muted-foreground cursor-grab\"\n title={resolve('reorder')?.label ?? undefined}\n aria-hidden=\"true\"\n >\n <Icon name={resolve('reorder')?.icon ?? 'grip-vertical'} />\n </span>\n )}\n {component.blockIcons && icon && (\n <Icon name={icon} className=\"text-muted-foreground size-4\" />\n )}\n {component.blockNumbers && (\n <span className=\"text-muted-foreground text-xs tabular-nums\">\n {formatter.number(index + 1)}\n </span>\n )}\n <span className=\"flex min-w-0 flex-1 items-baseline gap-2\">\n {label && (\n <span\n dir=\"auto\"\n className=\"min-w-0 shrink-0 truncate text-sm font-medium sm:max-w-[60%]\"\n >\n {label}\n </span>\n )}\n {summary && (\n <span\n dir=\"auto\"\n className=\"text-muted-foreground min-w-0 truncate text-sm\"\n data-slot=\"builder-item-summary\"\n >\n {summary}\n </span>\n )}\n </span>\n <ItemHeaderActions\n context={context}\n itemKey={String(index)}\n index={index}\n count={items.length}\n resolve={(name) => resolve(name, serverItem)}\n extraActions={menu || locked ? undefined : serverItem?.actions}\n buttons={buttons && !menu}\n canClone={canClone && !menu}\n canDelete={canDelete && !menu}\n collapsible={Boolean(component.collapsible)}\n collapsed={isCollapsed}\n onMove={moveWithButton}\n onClone={() => cloneItem(index)}\n onDelete={() => removeItem(index)}\n onToggle={() => {\n setCollapsed((state) => ({ ...state, [index]: !isCollapsed }))\n if (!isCollapsed) refreshSummaries([index])\n }}\n >\n {hasPreview && !locked && (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('builder.edit_block', {}, 'Edit block')}\n aria-pressed={Boolean(editing[index])}\n onClick={() => toggleEditing(index)}\n >\n <Icon\n name=\"alfakit::forms.components.builder.actions.edit\"\n fallback={PencilIcon}\n />\n </Button>\n )}\n {menu && (\n <BlockActionsMenu\n label={label ?? ''}\n resolve={(name) => resolve(name, serverItem)}\n canMoveUp={buttons && index > 0}\n canMoveDown={buttons && index < items.length - 1}\n canClone={canClone}\n canDelete={canDelete}\n extraActions={locked ? undefined : serverItem?.actions}\n onAction={(name, onClient) =>\n runItemAction(\n resolve(name, serverItem),\n { item: String(index) },\n onClient,\n )\n }\n onExtraAction={(action) =>\n void runner.trigger(\n action,\n itemActionContext(context, { item: String(index) }),\n )\n }\n onMove={(to) => moveWithButton(index, to)}\n index={index}\n onClone={() => cloneItem(index)}\n onDelete={() => removeItem(index)}\n />\n )}\n </ItemHeaderActions>\n </div>\n <CollapsibleContent>\n {showPreview ? (\n <div\n data-slot=\"builder-preview\"\n className={cn(\n 'p-4',\n !component.interactivePreviews && !locked && 'cursor-pointer',\n )}\n {...(component.interactivePreviews || locked\n ? {}\n : {\n role: 'button',\n tabIndex: 0,\n 'aria-label': t('builder.edit_block', {}, 'Edit block'),\n onClick: openEditor,\n onKeyDown: (event: KeyboardEvent) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n openEditor()\n }\n },\n })}\n >\n <div\n className={cn(!component.interactivePreviews && 'pointer-events-none')}\n >\n {Preview ? (\n createElement(Preview, { data: item.data, block: item.type, index })\n ) : (\n <div dangerouslySetInnerHTML={{ __html: serverItem?.preview ?? '' }} />\n )}\n </div>\n </div>\n ) : (\n <div className=\"p-4\">\n <SchemaRenderer\n components={builderItemSchema(component, items, index)}\n columns={\n block?.columns ?? component.items[index]?.columns ?? { default: 1 }\n }\n />\n </div>\n )}\n </CollapsibleContent>\n </Collapsible>\n </li>\n )\n })}\n </ul>\n {canAdd && addAction && component.blocks.length > 0 && items.length > 0 && (\n <AddActionRow alignment={component.addActionAlignment}>\n <BuilderBlockPicker\n component={component}\n items={items}\n onPick={addBlock}\n trigger={\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n disabled={addAction.disabled}\n data-slot=\"builder-add\"\n >\n {addAction.icon && <Icon name={addAction.icon} />}\n {addAction.label}\n </Button>\n }\n />\n </AddActionRow>\n )}\n </div>\n </FieldWrapper>\n )\n}\n\n/**\n * `blockActionsMenu()`: an item's actions in one compact menu (move up / down, duplicate,\n * extra item actions, delete). Built-in actions run in the browser unless customised.\n */\nfunction BlockActionsMenu({\n label,\n resolve,\n canMoveUp,\n canMoveDown,\n canClone,\n canDelete,\n extraActions,\n index,\n onAction,\n onExtraAction,\n onMove,\n onClone,\n onDelete,\n}: {\n label: string\n resolve: (name: ItemActionName) => ItemActionSchema | null\n canMoveUp: boolean\n canMoveDown: boolean\n canClone: boolean\n canDelete: boolean\n extraActions?: ActionSchema[]\n index: number\n onAction: (name: ItemActionName, onClient: () => void) => void\n onExtraAction: (action: ActionSchema) => void\n onMove: (to: number) => void\n onClone: () => void\n onDelete: () => void\n}) {\n const t = useT()\n const entry = (\n name: ItemActionName,\n enabled: boolean,\n onClient: () => void,\n destructive = false,\n ) => {\n const action = resolve(name)\n\n if (!action || !enabled) return null\n\n return (\n <DropdownMenuItem\n key={name}\n variant={destructive ? 'destructive' : 'default'}\n disabled={action.disabled}\n onSelect={() => onAction(name, onClient)}\n >\n {action.icon && <Icon name={action.icon} />}\n {name === 'clone' ? t('builder.duplicate_block', {}, 'Duplicate') : action.label}\n </DropdownMenuItem>\n )\n }\n const visibleExtras = extraActions ?? []\n const hasMain = canMoveUp || canMoveDown || canClone || visibleExtras.length > 0\n\n if (!hasMain && !canDelete) return null\n\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n data-slot=\"builder-item-menu\"\n aria-label={t('builder.block_actions', { block: label }, 'Actions for :block')}\n >\n <EllipsisIcon aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"min-w-44\">\n {entry('moveUp', canMoveUp, () => onMove(index - 1))}\n {entry('moveDown', canMoveDown, () => onMove(index + 1))}\n {entry('clone', canClone, onClone)}\n {visibleExtras.map((action) => (\n <DropdownMenuItem\n key={action.name}\n disabled={action.disabled}\n onSelect={() => onExtraAction(action)}\n >\n {action.icon && <Icon name={action.icon} />}\n {action.label}\n </DropdownMenuItem>\n ))}\n {canDelete && hasMain && <DropdownMenuSeparator />}\n {entry('delete', canDelete, onDelete, true)}\n </DropdownMenuContent>\n </DropdownMenu>\n )\n}\n"],"mappings":"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"}
@@ -5,7 +5,7 @@ import { useStore } from "../../hooks/use-store.js";
5
5
  import { Spinner } from "../ui/spinner.js";
6
6
  import { Input } from "../ui/input.js";
7
7
  import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "../ui/dialog.js";
8
- import { ImageEditor, loadEditorImage } from "@alfakit/core";
8
+ import { ImageEditor, imageEditorOptionsKey, loadEditorImage } from "@alfakit/core";
9
9
  import { useEffect, useId, useRef, useState } from "react";
10
10
  import { jsx, jsxs } from "react/jsx-runtime";
11
11
  import { FlipHorizontal2Icon, FlipVertical2Icon, RotateCcwIcon, RotateCwIcon, Undo2Icon, ZoomInIcon, ZoomOutIcon } from "lucide-react";
@@ -26,8 +26,10 @@ function ImageEditorDialog({ request, options }) {
26
26
  const [loaded, setLoaded] = useState(null);
27
27
  const [saving, setSaving] = useState(false);
28
28
  const hintId = useId();
29
+ const optionsKey = imageEditorOptionsKey(options);
29
30
  useEffect(() => {
30
31
  if (!request) return;
32
+ const options = JSON.parse(optionsKey);
31
33
  let cancelled = false;
32
34
  loadEditorImage(request.file).then((bitmap) => {
33
35
  if (cancelled) return bitmap.close();
@@ -52,7 +54,7 @@ function ImageEditorDialog({ request, options }) {
52
54
  return null;
53
55
  });
54
56
  };
55
- }, [request, options]);
57
+ }, [request, optionsKey]);
56
58
  const finish = (file) => {
57
59
  request?.resolve(file);
58
60
  };