@alfakit/react 0.1.11 → 0.1.13

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 (64) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/components/entries/other-entries.d.ts.map +1 -1
  3. package/dist/components/entries/other-entries.js +1 -1
  4. package/dist/components/entries/other-entries.js.map +1 -1
  5. package/dist/components/fields/builder.d.ts.map +1 -1
  6. package/dist/components/fields/builder.js +3 -3
  7. package/dist/components/fields/builder.js.map +1 -1
  8. package/dist/components/fields/file-upload.d.ts.map +1 -1
  9. package/dist/components/fields/file-upload.js +2 -5
  10. package/dist/components/fields/file-upload.js.map +1 -1
  11. package/dist/components/fields/repeater.d.ts.map +1 -1
  12. package/dist/components/fields/repeater.js +3 -3
  13. package/dist/components/fields/repeater.js.map +1 -1
  14. package/dist/components/fields/rich-editor-extensions.js +5 -2
  15. package/dist/components/fields/rich-editor-extensions.js.map +1 -1
  16. package/dist/components/layout/wizard.d.ts.map +1 -1
  17. package/dist/components/layout/wizard.js +4 -1
  18. package/dist/components/layout/wizard.js.map +1 -1
  19. package/dist/components/notifications/flash-toasts.d.ts.map +1 -1
  20. package/dist/components/notifications/flash-toasts.js +11 -1
  21. package/dist/components/notifications/flash-toasts.js.map +1 -1
  22. package/dist/components/notifications/notifications-menu.js +3 -3
  23. package/dist/components/notifications/notifications-menu.js.map +1 -1
  24. package/dist/components/panel/pwa.d.ts.map +1 -1
  25. package/dist/components/panel/pwa.js +8 -7
  26. package/dist/components/panel/pwa.js.map +1 -1
  27. package/dist/components/panel/shortcuts-overlay.js +1 -1
  28. package/dist/components/panel/shortcuts-overlay.js.map +1 -1
  29. package/dist/components/table/cells.js +1 -1
  30. package/dist/components/table/cells.js.map +1 -1
  31. package/dist/components.css +19 -0
  32. package/dist/create-alfakit.d.ts.map +1 -1
  33. package/dist/create-alfakit.js +3 -1
  34. package/dist/create-alfakit.js.map +1 -1
  35. package/dist/eject-manifest.json +2 -2
  36. package/dist/pages/page.js +1 -1
  37. package/dist/pages/page.js.map +1 -1
  38. package/dist/standalone/alfakit-component.js +2 -1
  39. package/dist/standalone/alfakit-component.js.map +1 -1
  40. package/dist/standalone/alfakit-toaster.js +2 -1
  41. package/dist/standalone/alfakit-toaster.js.map +1 -1
  42. package/dist/standalone/runtime.js +1 -11
  43. package/dist/standalone/runtime.js.map +1 -1
  44. package/dist/standalone/toaster-claims.js +16 -0
  45. package/dist/standalone/toaster-claims.js.map +1 -0
  46. package/dist/starter-kit/widgets-loaded.js +63 -6
  47. package/dist/starter-kit/widgets-loaded.js.map +1 -1
  48. package/package.json +2 -2
  49. package/src/components/entries/other-entries.tsx +2 -0
  50. package/src/components/fields/builder.tsx +7 -2
  51. package/src/components/fields/file-upload.tsx +2 -7
  52. package/src/components/fields/repeater.tsx +6 -3
  53. package/src/components/fields/rich-editor-extensions.tsx +9 -5
  54. package/src/components/layout/wizard.tsx +5 -1
  55. package/src/components/notifications/flash-toasts.tsx +8 -0
  56. package/src/components/notifications/notifications-menu.tsx +3 -3
  57. package/src/components/panel/pwa.tsx +23 -9
  58. package/src/components/panel/shortcuts-overlay.tsx +1 -1
  59. package/src/components/table/cells.tsx +3 -1
  60. package/src/create-alfakit.tsx +6 -0
  61. package/src/pages/page.tsx +1 -1
  62. package/src/standalone/runtime.ts +2 -14
  63. package/src/standalone/toaster-claims.ts +16 -0
  64. package/src/starter-kit/widgets-loaded.tsx +84 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @alfakit/react
2
2
 
3
+ ## 0.1.13
4
+
5
+ ### Patch Changes
6
+
7
+ - Starter kit header components bring a toaster to the app's own pages, so push and PWA messages show there; AlfaKit pages keep a single toaster.
8
+
9
+ ## 0.1.12
10
+
11
+ ### Patch Changes
12
+
13
+ - Wizard submit actions run with the form's state; a second custom rich-editor block no longer replaces the first; extra item actions on browser-added repeater items; "+N" no longer overlaps stacked images; PWA and notification toasts in starter kits; infolist blocks follow their `columns()`.
14
+
3
15
  ## 0.1.11
4
16
 
5
17
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"other-entries.d.ts","names":[],"sources":["../../../src/components/entries/other-entries.tsx"],"mappings":";;;wBAqDgB,qBAAqB;EAAe,WAAW;oBAAiB,IAAA;wBAoBhE,sBAAsB;EAAe,WAAW;oBAAkB,IAAA;wBAyDlE,sBAAsB;EAAe,WAAW;oBAAkB,IAAA;wBA4ClE,yBAAyB;EAAe,WAAW;oBAAqB,IAAA;wBAiCxE,2BAA2B;EAAe,WAAW;oBAAuB,IAAA"}
1
+ {"version":3,"file":"other-entries.d.ts","names":[],"sources":["../../../src/components/entries/other-entries.tsx"],"mappings":";;;wBAqDgB,qBAAqB;EAAe,WAAW;oBAAiB,IAAA;wBAoBhE,sBAAsB;EAAe,WAAW;oBAAkB,IAAA;wBA2DlE,sBAAsB;EAAe,WAAW;oBAAkB,IAAA;wBA4ClE,yBAAyB;EAAe,WAAW;oBAAqB,IAAA;wBAiCxE,2BAA2B;EAAe,WAAW;oBAAuB,IAAA"}
@@ -70,7 +70,7 @@ function ImageEntryComponent({ component }) {
70
70
  ...extraProps(component.extraImgAttributes, cn("object-cover", component.circular ? "rounded-full" : "rounded-md", component.stacked && usesDefaultImageStack(component) && "ring-background ring-2"))
71
71
  }, index)), component.limitedRemainingText !== false && urls.length > limit && /* @__PURE__ */ jsxs("span", {
72
72
  "data-slot": "image-remaining",
73
- className: cn("text-muted-foreground", remainingTextSizeClass(component.limitedRemainingTextSize)),
73
+ className: cn("text-muted-foreground", component.stacked && usesDefaultImageStack(component) && "ms-3", remainingTextSizeClass(component.limitedRemainingTextSize)),
74
74
  children: ["+", urls.length - limit]
75
75
  })]
76
76
  })
@@ -1 +1 @@
1
- {"version":3,"file":"other-entries.js","names":[],"sources":["../../../src/components/entries/other-entries.tsx"],"sourcesContent":["import type {\n BaseEntry,\n ColorEntrySchema,\n IconEntrySchema,\n ImageEntrySchema,\n KeyValueEntrySchema,\n RepeatableEntrySchema,\n} from '@alfakit/core'\nimport {\n gridColumnsClasses,\n remainingTextSizeClass,\n stackedImageStyle,\n usesDefaultImageStack,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport type { ReactNode } from 'react'\nimport { extraProps } from '../../lib/attributes'\nimport { textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { EntryPlaceholder, EntryWrapper } from './entry-wrapper'\nimport { RepeatableEntryTable } from './repeatable-entry-table'\nimport { FormattedText } from './text-entry'\n\n/** Wrap an entry's content in its `url()` link (icon, image and colour entries). */\nexport function EntryLink({ entry, children }: { entry: BaseEntry; children: ReactNode }) {\n if (!entry.url) {\n return <>{children}</>\n }\n\n return entry.openUrlInNewTab ? (\n <a href={entry.url} target=\"_blank\" rel=\"noopener noreferrer\" className=\"inline-flex w-fit\">\n {children}\n <NewTabHint />\n </a>\n ) : (\n <Link href={entry.url} className=\"inline-flex w-fit\">\n {children}\n </Link>\n )\n}\n\nconst ICON_SIZES: Record<string, string> = {\n xs: 'size-3',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-8',\n '2xl': 'size-10',\n}\n\nexport function IconEntryComponent({ component }: { component: IconEntrySchema }) {\n return (\n <EntryWrapper entry={component}>\n {component.icon ? (\n <EntryLink entry={component}>\n <Icon\n name={component.icon}\n className={cn(\n ICON_SIZES[component.size ?? 'md'],\n component.color && textColorClasses[component.color],\n )}\n />\n </EntryLink>\n ) : (\n <EntryPlaceholder entry={component} />\n )}\n </EntryWrapper>\n )\n}\n\nexport function ImageEntryComponent({ component }: { component: ImageEntrySchema }) {\n const urls = component.state ?? []\n const limit = component.limit ?? urls.length\n const size = component.size ?? 40\n const style = {\n height: component.height ?? size,\n width: component.width ?? (component.square || component.circular ? size : undefined),\n }\n\n return (\n <EntryWrapper entry={component}>\n {urls.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : (\n <EntryLink entry={component}>\n <div\n className={cn(\n 'flex items-center',\n component.stacked ? usesDefaultImageStack(component) && '-space-x-2' : 'gap-2',\n )}\n >\n {urls.slice(0, limit).map((url, index) => (\n <img\n key={index}\n src={url}\n alt={component.alts?.[index] ?? component.alt ?? ''}\n style={{ ...style, ...stackedImageStyle(index, component) }}\n {...extraProps(\n component.extraImgAttributes,\n cn(\n 'object-cover',\n component.circular ? 'rounded-full' : 'rounded-md',\n component.stacked &&\n usesDefaultImageStack(component) &&\n 'ring-background ring-2',\n ),\n )}\n />\n ))}\n {component.limitedRemainingText !== false && urls.length > limit && (\n <span\n data-slot=\"image-remaining\"\n className={cn(\n 'text-muted-foreground',\n remainingTextSizeClass(component.limitedRemainingTextSize),\n )}\n >\n +{urls.length - limit}\n </span>\n )}\n </div>\n </EntryLink>\n )}\n </EntryWrapper>\n )\n}\n\nexport function ColorEntryComponent({ component }: { component: ColorEntrySchema }) {\n const colors = Array.isArray(component.state)\n ? component.state\n : component.state\n ? [component.state]\n : []\n\n return (\n <EntryWrapper entry={component}>\n {colors.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : (\n <EntryLink entry={component}>\n <div className=\"flex flex-wrap items-center gap-2\">\n {colors.map((color, index) => (\n <span key={index} className=\"inline-flex items-center gap-1.5 text-sm\">\n <span\n className=\"size-5 rounded border shadow-xs\"\n style={{ backgroundColor: color }}\n />\n {/* The code honours the entry's text styles and copy settings. */}\n {component.copyable && (\n <FormattedText\n value={color}\n className=\"text-xs\"\n schema={{\n size: component.size,\n weight: component.weight,\n fontFamily: component.fontFamily ?? 'mono',\n copyable: true,\n copyMessage: component.copyMessage,\n copyMessageDuration: component.copyMessageDuration,\n }}\n />\n )}\n </span>\n ))}\n </div>\n </EntryLink>\n )}\n </EntryWrapper>\n )\n}\n\nexport function KeyValueEntryComponent({ component }: { component: KeyValueEntrySchema }) {\n const entries = Object.entries(component.state ?? {})\n\n return (\n <EntryWrapper entry={component}>\n {entries.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : (\n <div className=\"overflow-hidden rounded-md border\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted/50 text-muted-foreground text-xs\">\n <tr>\n <th className=\"px-3 py-1.5 text-start font-medium\">{component.keyLabel}</th>\n <th className=\"px-3 py-1.5 text-start font-medium\">{component.valueLabel}</th>\n </tr>\n </thead>\n <tbody>\n {entries.map(([key, value]) => (\n <tr key={key} className=\"border-t\">\n <td className=\"px-3 py-1.5 font-medium\">{key}</td>\n <td className=\"px-3 py-1.5\">\n {typeof value === 'object' ? JSON.stringify(value) : String(value)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </EntryWrapper>\n )\n}\n\nexport function RepeatableEntryComponent({ component }: { component: RepeatableEntrySchema }) {\n return (\n <EntryWrapper entry={component}>\n {component.items.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : component.table?.length ? (\n <RepeatableEntryTable columns={component.table} items={component.items} />\n ) : (\n <div className={cn('grid gap-3', component.grid && gridColumnsClasses(component.grid))}>\n {component.items.map((item, index) => (\n <div\n key={index}\n className={cn(component.contained && 'bg-card rounded-lg border p-4 shadow-xs')}\n >\n <div className=\"contents\">\n <SchemaRenderer\n components={item}\n columns={component.columns ?? { default: 1, lg: 2 }}\n className=\"gap-4\"\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </EntryWrapper>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"other-entries.js","names":[],"sources":["../../../src/components/entries/other-entries.tsx"],"sourcesContent":["import type {\n BaseEntry,\n ColorEntrySchema,\n IconEntrySchema,\n ImageEntrySchema,\n KeyValueEntrySchema,\n RepeatableEntrySchema,\n} from '@alfakit/core'\nimport {\n gridColumnsClasses,\n remainingTextSizeClass,\n stackedImageStyle,\n usesDefaultImageStack,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport type { ReactNode } from 'react'\nimport { extraProps } from '../../lib/attributes'\nimport { textColorClasses } from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { NewTabHint } from '../../lib/new-tab-hint'\nimport { cn } from '../../lib/utils'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { EntryPlaceholder, EntryWrapper } from './entry-wrapper'\nimport { RepeatableEntryTable } from './repeatable-entry-table'\nimport { FormattedText } from './text-entry'\n\n/** Wrap an entry's content in its `url()` link (icon, image and colour entries). */\nexport function EntryLink({ entry, children }: { entry: BaseEntry; children: ReactNode }) {\n if (!entry.url) {\n return <>{children}</>\n }\n\n return entry.openUrlInNewTab ? (\n <a href={entry.url} target=\"_blank\" rel=\"noopener noreferrer\" className=\"inline-flex w-fit\">\n {children}\n <NewTabHint />\n </a>\n ) : (\n <Link href={entry.url} className=\"inline-flex w-fit\">\n {children}\n </Link>\n )\n}\n\nconst ICON_SIZES: Record<string, string> = {\n xs: 'size-3',\n sm: 'size-4',\n md: 'size-5',\n lg: 'size-6',\n xl: 'size-8',\n '2xl': 'size-10',\n}\n\nexport function IconEntryComponent({ component }: { component: IconEntrySchema }) {\n return (\n <EntryWrapper entry={component}>\n {component.icon ? (\n <EntryLink entry={component}>\n <Icon\n name={component.icon}\n className={cn(\n ICON_SIZES[component.size ?? 'md'],\n component.color && textColorClasses[component.color],\n )}\n />\n </EntryLink>\n ) : (\n <EntryPlaceholder entry={component} />\n )}\n </EntryWrapper>\n )\n}\n\nexport function ImageEntryComponent({ component }: { component: ImageEntrySchema }) {\n const urls = component.state ?? []\n const limit = component.limit ?? urls.length\n const size = component.size ?? 40\n const style = {\n height: component.height ?? size,\n width: component.width ?? (component.square || component.circular ? size : undefined),\n }\n\n return (\n <EntryWrapper entry={component}>\n {urls.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : (\n <EntryLink entry={component}>\n <div\n className={cn(\n 'flex items-center',\n component.stacked ? usesDefaultImageStack(component) && '-space-x-2' : 'gap-2',\n )}\n >\n {urls.slice(0, limit).map((url, index) => (\n <img\n key={index}\n src={url}\n alt={component.alts?.[index] ?? component.alt ?? ''}\n style={{ ...style, ...stackedImageStyle(index, component) }}\n {...extraProps(\n component.extraImgAttributes,\n cn(\n 'object-cover',\n component.circular ? 'rounded-full' : 'rounded-md',\n component.stacked &&\n usesDefaultImageStack(component) &&\n 'ring-background ring-2',\n ),\n )}\n />\n ))}\n {component.limitedRemainingText !== false && urls.length > limit && (\n <span\n data-slot=\"image-remaining\"\n className={cn(\n 'text-muted-foreground',\n // The last image keeps the stack's negative end margin (W-S3-03).\n component.stacked && usesDefaultImageStack(component) && 'ms-3',\n remainingTextSizeClass(component.limitedRemainingTextSize),\n )}\n >\n +{urls.length - limit}\n </span>\n )}\n </div>\n </EntryLink>\n )}\n </EntryWrapper>\n )\n}\n\nexport function ColorEntryComponent({ component }: { component: ColorEntrySchema }) {\n const colors = Array.isArray(component.state)\n ? component.state\n : component.state\n ? [component.state]\n : []\n\n return (\n <EntryWrapper entry={component}>\n {colors.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : (\n <EntryLink entry={component}>\n <div className=\"flex flex-wrap items-center gap-2\">\n {colors.map((color, index) => (\n <span key={index} className=\"inline-flex items-center gap-1.5 text-sm\">\n <span\n className=\"size-5 rounded border shadow-xs\"\n style={{ backgroundColor: color }}\n />\n {/* The code honours the entry's text styles and copy settings. */}\n {component.copyable && (\n <FormattedText\n value={color}\n className=\"text-xs\"\n schema={{\n size: component.size,\n weight: component.weight,\n fontFamily: component.fontFamily ?? 'mono',\n copyable: true,\n copyMessage: component.copyMessage,\n copyMessageDuration: component.copyMessageDuration,\n }}\n />\n )}\n </span>\n ))}\n </div>\n </EntryLink>\n )}\n </EntryWrapper>\n )\n}\n\nexport function KeyValueEntryComponent({ component }: { component: KeyValueEntrySchema }) {\n const entries = Object.entries(component.state ?? {})\n\n return (\n <EntryWrapper entry={component}>\n {entries.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : (\n <div className=\"overflow-hidden rounded-md border\">\n <table className=\"w-full text-sm\">\n <thead className=\"bg-muted/50 text-muted-foreground text-xs\">\n <tr>\n <th className=\"px-3 py-1.5 text-start font-medium\">{component.keyLabel}</th>\n <th className=\"px-3 py-1.5 text-start font-medium\">{component.valueLabel}</th>\n </tr>\n </thead>\n <tbody>\n {entries.map(([key, value]) => (\n <tr key={key} className=\"border-t\">\n <td className=\"px-3 py-1.5 font-medium\">{key}</td>\n <td className=\"px-3 py-1.5\">\n {typeof value === 'object' ? JSON.stringify(value) : String(value)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n </EntryWrapper>\n )\n}\n\nexport function RepeatableEntryComponent({ component }: { component: RepeatableEntrySchema }) {\n return (\n <EntryWrapper entry={component}>\n {component.items.length === 0 ? (\n <EntryPlaceholder entry={component} />\n ) : component.table?.length ? (\n <RepeatableEntryTable columns={component.table} items={component.items} />\n ) : (\n <div className={cn('grid gap-3', component.grid && gridColumnsClasses(component.grid))}>\n {component.items.map((item, index) => (\n <div\n key={index}\n className={cn(component.contained && 'bg-card rounded-lg border p-4 shadow-xs')}\n >\n <div className=\"contents\">\n <SchemaRenderer\n components={item}\n columns={component.columns ?? { default: 1, lg: 2 }}\n className=\"gap-4\"\n />\n </div>\n </div>\n ))}\n </div>\n )}\n </EntryWrapper>\n )\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"builder.d.ts","names":[],"sources":["../../../src/components/fields/builder.tsx"],"mappings":";;;;;;;;;;;wBAmEgB,eAAe;EAAe,WAAW;oBAAe,IAAA"}
1
+ {"version":3,"file":"builder.d.ts","names":[],"sources":["../../../src/components/fields/builder.tsx"],"mappings":";;;;;;;;;;;wBAoEgB,eAAe;EAAe,WAAW;oBAAe,IAAA"}
@@ -12,7 +12,7 @@ import { FieldWrapper } from "../schema/field-wrapper.js";
12
12
  import { Collapsible, CollapsibleContent } from "../ui/collapsible.js";
13
13
  import { BuilderBlockPicker, BuilderBlockPickerPanel } from "./builder-picker.js";
14
14
  import { AddActionRow, CollapseAllActions, ItemHeaderActions, useItemActionFallbacks, useItemActions, useItemMoves, useRunItemAction } from "./item-actions.js";
15
- import { VIRTUAL_ESTIMATES, builderItemLabel, builderItemSchema, builderItems, canAddBuilderBlock, cloneIndexedState, cloneRepeaterItem, fieldIds, insertBuilderItem, isDeferredPlaceholder, itemActionContext, moveIndexedState, moveRepeaterItem, removeIndexedState, removeRepeaterItem, reorderedItemKeys, virtualizeOptions } from "@alfakit/core";
15
+ import { VIRTUAL_ESTIMATES, builderItemLabel, builderItemSchema, builderItems, canAddBuilderBlock, cloneIndexedState, cloneRepeaterItem, fieldIds, insertBuilderItem, isDeferredPlaceholder, itemActionContext, itemExtraActions, moveIndexedState, moveRepeaterItem, removeIndexedState, removeRepeaterItem, reorderedItemKeys, virtualizeOptions } from "@alfakit/core";
16
16
  import { createElement, useState } from "react";
17
17
  import { jsx, jsxs } from "react/jsx-runtime";
18
18
  import { EllipsisIcon, PencilIcon } from "lucide-react";
@@ -210,7 +210,7 @@ function BuilderFieldContent({ component }) {
210
210
  index,
211
211
  count: items.length,
212
212
  resolve: (name) => resolve(name, serverItem),
213
- extraActions: menu || locked ? void 0 : serverItem?.actions,
213
+ extraActions: menu || locked ? void 0 : itemExtraActions(component, serverItem),
214
214
  buttons: buttons && !menu,
215
215
  canClone: canClone && !menu,
216
216
  canDelete: canDelete && !menu,
@@ -244,7 +244,7 @@ function BuilderFieldContent({ component }) {
244
244
  canMoveDown: buttons && index < items.length - 1,
245
245
  canClone,
246
246
  canDelete,
247
- extraActions: locked ? void 0 : serverItem?.actions,
247
+ extraActions: locked ? void 0 : itemExtraActions(component, serverItem),
248
248
  onAction: (name, onClient) => runItemAction(resolve(name, serverItem), { item: String(index) }, onClient),
249
249
  onExtraAction: (action) => void runner.trigger(action, itemActionContext(context, { item: String(index) })),
250
250
  onMove: (to) => moveWithButton(index, to),
@@ -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, '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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"}
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 itemExtraActions,\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={\n menu || locked ? undefined : itemExtraActions(component, serverItem)\n }\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={\n locked ? undefined : itemExtraActions(component, serverItem)\n }\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 +1 @@
1
- {"version":3,"file":"file-upload.d.ts","names":[],"sources":["../../../src/components/fields/file-upload.tsx"],"mappings":";;wBA+CgB,kBAAkB;EAAe,WAAW;oBAAkB,IAAA"}
1
+ {"version":3,"file":"file-upload.d.ts","names":[],"sources":["../../../src/components/fields/file-upload.tsx"],"mappings":";;wBAgDgB,kBAAkB;EAAe,WAAW;oBAAkB,IAAA"}
@@ -6,7 +6,7 @@ import { Spinner } from "../ui/spinner.js";
6
6
  import { useField, useFormStore } from "../form/form-context.js";
7
7
  import { FieldWrapper } from "../schema/field-wrapper.js";
8
8
  import { ImageEditorDialog } from "./image-editor-dialog-lazy.js";
9
- import { canEditImage, concurrencyLimiter, cssAspectRatio, editableImageName, fetchEditableImage, fieldMemory, fileManagerSize, fileUploadAccept, fileUploadPaths, filesFromClipboard, insertUploadedFile, moveRepeaterItem, panelUploadOptions, requestJson, transformImage, uploadTemporaryFile, uploadedFileLinks } from "@alfakit/core";
9
+ import { canEditImage, concurrencyLimiter, cssAspectRatio, editableImageName, fetchEditableImage, fieldMemory, fileManagerSize, fileUploadAccept, fileUploadEntries, fileUploadPaths, filesFromClipboard, insertUploadedFile, moveRepeaterItem, panelUploadOptions, requestJson, transformImage, uploadTemporaryFile, uploadedFileLinks } from "@alfakit/core";
10
10
  import { useRef, useState } from "react";
11
11
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
12
12
  import { ChevronDownIcon, ChevronUpIcon, DownloadIcon, ExternalLinkIcon, FileIcon, PencilIcon, UploadCloudIcon, XIcon } from "lucide-react";
@@ -42,10 +42,7 @@ function FileUploadField({ component }) {
42
42
  if (url && objectUrls.delete(url)) URL.revokeObjectURL(url);
43
43
  };
44
44
  const paths = fileUploadPaths(value);
45
- const files = paths.map((path) => [...component.files, ...uploaded].find((file) => file.path === path) ?? {
46
- path,
47
- name: path.replace(/^tmp:/, "")
48
- });
45
+ const files = fileUploadEntries(paths, component.files, uploaded);
49
46
  const maxFiles = component.multiple ? component.maxFiles ?? Infinity : 1;
50
47
  const canAddMore = files.length + uploading.length < maxFiles;
51
48
  const commit = (next) => setValue(component.multiple ? next : next[0] ?? null);
@@ -1 +1 @@
1
- {"version":3,"file":"file-upload.js","names":[],"sources":["../../../src/components/fields/file-upload.tsx"],"sourcesContent":["import type { FileUploadSchema, UploadedFile } from '@alfakit/core'\nimport {\n canEditImage,\n concurrencyLimiter,\n cssAspectRatio,\n editableImageName,\n fetchEditableImage,\n fieldMemory,\n fileManagerSize,\n filesFromClipboard,\n fileUploadAccept,\n fileUploadPaths,\n insertUploadedFile,\n moveRepeaterItem,\n panelUploadOptions,\n requestJson,\n transformImage,\n uploadedFileLinks,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport {\n ChevronDownIcon,\n ChevronUpIcon,\n DownloadIcon,\n ExternalLinkIcon,\n FileIcon,\n PencilIcon,\n UploadCloudIcon,\n XIcon,\n} from 'lucide-react'\nimport { useRef, useState, type ClipboardEvent, type DragEvent } from 'react'\nimport { useChoice, useFormatter, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { useField, useFormStore } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Spinner } from '../ui/spinner'\nimport type { ImageEditRequest } from './image-editor-dialog'\nimport { ImageEditorDialog } from './image-editor-dialog-lazy'\n\ninterface LocalFile extends UploadedFile {\n progress?: number\n error?: string\n preview?: string\n}\n\nexport function FileUploadField({ component }: { component: FileUploadSchema }) {\n const t = useT()\n const formatter = useFormatter()\n const choice = useChoice()\n const panel = usePanel()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<\n string | string[] | null\n >(component)\n // Read-only keeps open/download links but hides upload and remove controls.\n const locked = disabled || readOnly\n const store = useFormStore()\n // In-flight uploads and the metadata of files uploaded in this session (the server schema\n // only knows about saved files) live in the form's field memory, not in component state:\n // a virtualised Repeater item unmounts when scrolled out of view and finds them again,\n // local image previews included, when it mounts back.\n const uploadingStore = fieldMemory<LocalFile[]>(\n store,\n `${component.statePath}:uploading`,\n () => [],\n )\n const uploadedStore = fieldMemory<FileUploadSchema['files']>(\n store,\n `${component.statePath}:uploaded`,\n () => [],\n )\n const uploading = useStore(uploadingStore)\n const uploaded = useStore(uploadedStore)\n const setUploading = uploadingStore.set\n const setUploaded = uploadedStore.set\n const [dragging, setDragging] = useState(false)\n // Previews whose image failed to load (missing or unreachable file) show the file icon.\n const [brokenPreviews, setBrokenPreviews] = useState<ReadonlySet<string>>(() => new Set())\n const inputRef = useRef<HTMLInputElement>(null)\n const [dragPath, setDragPath] = useState<string | null>(null)\n // The image open in the editor (`imageEditor()`), one at a time.\n const [editing, setEditing] = useState<ImageEditRequest | null>(null)\n const editImage = (file: File) =>\n new Promise<File | null>((resolve) =>\n setEditing({\n file,\n resolve: (edited) => {\n setEditing(null)\n resolve(edited)\n },\n }),\n )\n // Object URLs made for local image previews; released when the file is removed.\n const objectUrls = fieldMemory<Set<string>>(\n store,\n `${component.statePath}:previews`,\n () => new Set(),\n ).get()\n\n const releasePreview = (url: string | undefined) => {\n if (url && objectUrls.delete(url)) URL.revokeObjectURL(url)\n }\n\n const paths = fileUploadPaths(value)\n const files: LocalFile[] = paths.map(\n (path) =>\n [...component.files, ...uploaded].find((file) => file.path === path) ?? {\n path,\n name: path.replace(/^tmp:/, ''),\n },\n )\n const maxFiles = component.multiple ? (component.maxFiles ?? Infinity) : 1\n const canAddMore = files.length + uploading.length < maxFiles\n\n const commit = (next: string[]) => setValue(component.multiple ? next : (next[0] ?? null))\n\n const upload = async (list: FileList | File[]) => {\n const endpoint = panel.endpoints.upload\n\n if (!endpoint) {\n return\n }\n\n const accepted = Array.from(list).slice(\n 0,\n Math.max(0, maxFiles - files.length - uploading.length),\n )\n\n // `maxParallelUploads()`: files are prepared (edited, resized) one by one and then\n // queued, so at most that many upload at once.\n const limit = concurrencyLimiter(component.maxParallelUploads ?? 1)\n const queued: Promise<void>[] = []\n\n for (const [position, picked] of accepted.entries()) {\n let original = picked\n const edit = Boolean(component.imageEditor && canEditImage(picked))\n\n if (edit) {\n const edited = await editImage(picked)\n\n // Cancelled: the file is not uploaded.\n if (!edited) continue\n\n original = edited\n }\n\n const file = await prepare(original, edit)\n\n if (!file) continue\n\n const local = pendingFile(file, position)\n setUploading((state) => [...state, local])\n\n queued.push(limit(() => send(endpoint, file, local)))\n }\n\n await Promise.all(queued)\n }\n\n /** Resize and orient a picked (or edited) file, and check its size. Null when rejected. */\n const prepare = async (original: File, edited: boolean): Promise<File | null> => {\n const file =\n component.imageCropAspectRatio ||\n component.imageResizeTargetWidth ||\n component.imageResizeTargetHeight ||\n component.orientImagesFromExif\n ? await transformImage(original, {\n // The editor already cropped the image.\n aspectRatio: edited ? null : component.imageCropAspectRatio,\n mode: component.imageResizeMode,\n width: component.imageResizeTargetWidth,\n height: component.imageResizeTargetHeight,\n orient: component.orientImagesFromExif,\n })\n : original\n\n if (component.minSize && file.size < component.minSize * 1024) {\n setUploading((state) => [\n ...state,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_small', {}, 'This file is too small.'),\n },\n ])\n\n return null\n }\n\n if (component.maxSize && file.size > component.maxSize * 1024) {\n setUploading((state) => [\n ...state,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_large', {}, 'This file is too large.'),\n },\n ])\n\n return null\n }\n\n return file\n }\n\n /** The in-progress entry shown while `file` uploads. */\n const pendingFile = (file: File, position: number | string): LocalFile => {\n const local: LocalFile = {\n // Unique within the batch and across re-picks of a changed file.\n path: `pending:${position}:${file.lastModified}:${file.size}:${file.name}`,\n name: file.name,\n size: file.size,\n progress: 0,\n preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n if (local.preview) objectUrls.add(local.preview)\n\n return local\n }\n\n /**\n * The \"Edit\" button on an uploaded image: open it in the editor and upload the result\n * in its place (the old file goes like any removed one: a temporary upload is deleted\n * now, a stored file when the form is saved with `deleteRemovedFiles()`).\n */\n const editExisting = async (existing: LocalFile) => {\n const endpoint = panel.endpoints.upload\n const url = existing.url ?? existing.preview\n const name = editableImageName(existing)\n\n if (!endpoint || !url || !name) return\n\n const source = await fetchEditableImage(url, name)\n\n if (!source) {\n setUploading((state) => [\n ...state,\n {\n path: `failed:${existing.path}`,\n name: existing.name,\n error: t('image_editor.load_failed', {}, 'The image could not be opened.'),\n },\n ])\n\n return\n }\n\n const edited = await editImage(source)\n\n // Cancelled, or the editor couldn't decode it: keep the file as it is.\n if (!edited || edited === source) return\n\n const file = await prepare(edited, true)\n\n if (!file) return\n\n const local = pendingFile(file, `edit:${existing.path}`)\n setUploading((state) => [...state, local])\n await send(endpoint, file, local, existing.path)\n }\n\n const send = async (endpoint: string, file: File, local: LocalFile, replacing?: string) => {\n try {\n // Large files go in resumable chunks; progress covers the whole file.\n const response = await uploadTemporaryFile(endpoint, file, {\n ...panelUploadOptions(panel, component.uploadPolicy),\n onProgress: (progress) =>\n setUploading((state) =>\n state.map((item) => (item.path === local.path ? { ...item, progress } : item)),\n ),\n })\n\n setUploaded((state) => [\n ...state,\n {\n path: response.key,\n name: response.name,\n size: response.size,\n temporary: true,\n url: local.preview,\n },\n ])\n setUploading((state) => state.filter((item) => item.path !== local.path))\n // Read the store, not `value`: it's stale after the first file of a batch.\n const current = fileUploadPaths(\n store.getValue<string | string[] | null>(component.statePath, null),\n )\n\n if (replacing !== undefined && current.includes(replacing)) {\n // An edited image takes the old one's place.\n commit(current.map((path) => (path === replacing ? response.key : path)))\n discard(replacing)\n } else {\n commit(insertUploadedFile(current, response.key, component, maxFiles))\n }\n } catch (uploadError) {\n const message =\n (uploadError as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n (uploadError as Error).message\n setUploading((state) =>\n state.map((item) =>\n item.path === local.path ? { ...item, error: message, progress: undefined } : item,\n ),\n )\n }\n }\n\n const remove = (path: string) => {\n commit(paths.filter((item) => item !== path))\n discard(path)\n }\n\n /** Release a file that left the field: its preview, and a temporary upload's file. */\n const discard = (path: string) => {\n releasePreview(uploadedStore.get().find((file) => file.path === path)?.url)\n\n if (path.startsWith('tmp:') && panel.endpoints.uploadDestroy) {\n void requestJson(panel.endpoints.uploadDestroy, {\n method: 'DELETE',\n body: { key: path },\n }).catch(() => undefined)\n }\n }\n\n const onDrop = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n setDragging(false)\n\n if (!locked && event.dataTransfer.files.length > 0) {\n void upload(event.dataTransfer.files)\n }\n }\n\n // `pasteable()`: images and files pasted while the drop zone has focus.\n const onPaste = (event: ClipboardEvent<HTMLDivElement>) => {\n if (locked || component.pasteable === false) return\n\n const pasted = filesFromClipboard(event.clipboardData)\n\n if (pasted.length > 0) {\n event.preventDefault()\n void upload(pasted)\n }\n }\n\n const move = (from: number, to: number) => commit(moveRepeaterItem(paths, from, to) as string[])\n const reorderable = Boolean(component.reorderable && component.multiple && !locked)\n\n // `previewable(false)` shows every file as a name and icon, without the image.\n const isImage = (file: LocalFile) =>\n component.previewable !== false &&\n (component.image || /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(file.name))\n const circle = component.panelLayout === 'circle' || component.avatar\n const integrated = component.panelLayout === 'integrated' && !circle\n const removeAtStart = component.positions?.removeButton === 'start'\n const progressAtStart = component.positions?.progressIndicator === 'start'\n const uploadAlign = component.positions?.uploadButton ?? 'center'\n\n // `imageEditor()`: uploaded JPEG, PNG and WebP images can be edited again.\n const canEditExisting = (file: LocalFile, index: number) =>\n Boolean(component.imageEditor) &&\n !locked &&\n index < files.length &&\n !file.missing &&\n Boolean(file.url ?? file.preview) &&\n editableImageName(file) !== null\n\n const renderRemove = (file: LocalFile) =>\n component.deletable &&\n !locked && (\n <Button\n type=\"button\"\n variant={circle ? 'secondary' : 'ghost'}\n size=\"icon-xs\"\n className={cn(circle && 'rounded-full shadow')}\n aria-label={t('file_upload.remove', { name: file.name }, 'Remove :name')}\n onClick={() => {\n if (file.error || file.progress !== undefined) {\n releasePreview(file.preview)\n setUploading((state) => state.filter((item) => item.path !== file.path))\n } else {\n remove(file.path)\n }\n }}\n >\n <XIcon />\n </Button>\n )\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n data-slot=\"file-upload\"\n data-layout={component.panelLayout}\n className={cn('flex flex-col gap-3', integrated && 'rounded-md border p-2')}\n >\n {(files.length > 0 || uploading.length > 0) && (\n <ul\n className={cn(\n 'grid gap-2',\n component.panelLayout === 'grid' && 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4',\n circle && 'justify-items-center',\n )}\n >\n {[...files, ...uploading].map((file, index) => (\n <li\n key={file.path}\n draggable={reorderable && index < files.length}\n onDragStart={() => setDragPath(file.path)}\n onDragOver={(event) => reorderable && event.preventDefault()}\n onDrop={(event) => {\n if (!reorderable || dragPath === null) return\n event.preventDefault()\n event.stopPropagation()\n const from = paths.indexOf(dragPath)\n if (from !== -1 && index < files.length) move(from, index)\n setDragPath(null)\n }}\n onDragEnd={() => setDragPath(null)}\n className={cn(\n dragPath === file.path && 'opacity-50',\n 'bg-muted/30 relative flex min-w-0 items-center gap-3 overflow-hidden rounded-md border p-2 text-sm',\n circle && 'size-28 justify-center rounded-full border-0 p-0',\n file.error && 'border-destructive',\n )}\n >\n {!circle && removeAtStart && renderRemove(file)}\n {!circle && progressAtStart && file.progress !== undefined && !file.error && (\n <Spinner />\n )}\n {isImage(file) && (file.url || file.preview) && !brokenPreviews.has(file.path) ? (\n <img\n src={file.url ?? file.preview}\n alt=\"\"\n onError={() => setBrokenPreviews((current) => new Set(current).add(file.path))}\n className={cn(\n 'size-12 shrink-0 rounded object-cover',\n circle && 'size-28 rounded-full',\n )}\n style={\n !circle && component.imagePreviewHeight\n ? {\n height: cssLength(component.imagePreviewHeight),\n width: 'auto',\n maxWidth: '50%',\n }\n : undefined\n }\n />\n ) : (\n !circle && <FileIcon className=\"text-muted-foreground size-6 shrink-0\" />\n )}\n {!circle && (\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium\">{file.name}</p>\n <p\n className={cn(\n 'text-muted-foreground text-xs',\n file.missing && 'text-destructive',\n )}\n >\n {file.error ??\n (file.missing\n ? t('form.file_missing', {}, 'This file no longer exists.')\n : file.progress !== undefined && !file.error\n ? (component.uploadingMessage ?? t('form.uploading', {}, 'Uploading…'))\n : file.size\n ? fileManagerSize(file.size, formatter)\n : file.temporary\n ? t('form.uploading', {}, 'Pending')\n : '')}\n </p>\n </div>\n )}\n <div\n className={cn('flex items-center gap-1', circle && 'absolute -end-1 -bottom-1')}\n >\n {(circle || !progressAtStart) && file.progress !== undefined && !file.error && (\n <Spinner />\n )}\n {reorderable && index < files.length && (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('file_upload.move_up', { name: file.name }, 'Move :name up')}\n disabled={index === 0}\n onClick={() => move(index, index - 1)}\n >\n <ChevronUpIcon />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t(\n 'file_upload.move_down',\n { name: file.name },\n 'Move :name down',\n )}\n disabled={index === files.length - 1}\n onClick={() => move(index, index + 1)}\n >\n <ChevronDownIcon />\n </Button>\n </>\n )}\n {canEditExisting(file, index) && (\n <Button\n type=\"button\"\n variant={circle ? 'secondary' : 'ghost'}\n size=\"icon-xs\"\n className={cn(circle && 'rounded-full shadow')}\n aria-label={t('file_upload.edit', { name: file.name }, 'Edit :name')}\n title={t('image_editor.title', {}, 'Edit image')}\n data-action=\"edit-image\"\n onClick={() => void editExisting(file)}\n >\n <PencilIcon />\n </Button>\n )}\n {component.openable && uploadedFileLinks(file).open && (\n <Button asChild type=\"button\" variant=\"ghost\" size=\"icon-xs\">\n <a\n href={uploadedFileLinks(file).open}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`${t('file_upload.open', { name: file.name }, 'Open :name')} ${t('opens_in_new_tab', {}, '(opens in a new tab)')}`}\n >\n <ExternalLinkIcon />\n </a>\n </Button>\n )}\n {component.downloadable && uploadedFileLinks(file).download && (\n <Button asChild type=\"button\" variant=\"ghost\" size=\"icon-xs\">\n <a\n href={uploadedFileLinks(file).download}\n download\n aria-label={t(\n 'file_upload.download',\n { name: file.name },\n 'Download :name',\n )}\n >\n <DownloadIcon />\n </a>\n </Button>\n )}\n {(circle || !removeAtStart) && renderRemove(file)}\n </div>\n </li>\n ))}\n </ul>\n )}\n {canAddMore && !locked && (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => inputRef.current?.click()}\n aria-describedby={aria['aria-describedby']}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n inputRef.current?.click()\n }\n }}\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n onPaste={onPaste}\n style={\n !circle && component.panelAspectRatio\n ? { aspectRatio: cssAspectRatio(component.panelAspectRatio) }\n : undefined\n }\n data-align={uploadAlign}\n className={cn(\n 'text-muted-foreground hover:border-ring flex cursor-pointer flex-col justify-center gap-1 rounded-md border border-dashed px-4 py-6 text-sm transition-colors',\n uploadAlign === 'start'\n ? 'items-start text-start'\n : uploadAlign === 'end'\n ? 'items-end text-end'\n : 'items-center text-center',\n integrated && 'border-transparent',\n dragging && 'border-ring bg-accent',\n circle && 'size-28 items-center rounded-full py-0',\n )}\n >\n <UploadCloudIcon className=\"size-5\" />\n {!circle && (\n <span>\n {component.placeholder ??\n t('form.drop_files', {}, 'Drop files here or click to browse')}\n </span>\n )}\n {!circle &&\n (component.acceptedFileTypes || component.maxSize || component.minFiles) && (\n <span className=\"text-muted-foreground text-xs\">\n {[\n component.acceptedFileTypes?.join(', '),\n component.maxSize\n ? `≤ ${fileManagerSize(component.maxSize * 1024, formatter)}`\n : null,\n component.minFiles\n ? choice(\n 'form.min_files',\n component.minFiles,\n {},\n 'At least :count file|At least :count files',\n )\n : null,\n ]\n .filter(Boolean)\n .join(' · ')}\n </span>\n )}\n </div>\n )}\n <input\n ref={inputRef}\n id={id}\n type=\"file\"\n className=\"sr-only\"\n tabIndex={-1}\n {...aria}\n multiple={component.multiple}\n accept={fileUploadAccept(component)}\n disabled={locked}\n onChange={(event) => {\n // Copy the list: clearing the input empties it while the editor is open.\n const picked = event.target.files ? Array.from(event.target.files) : []\n event.target.value = ''\n void upload(picked)\n }}\n />\n {component.imageEditor && (\n <ImageEditorDialog request={editing} options={component.imageEditor} />\n )}\n </div>\n </FieldWrapper>\n )\n}\n\n/** `imagePreviewHeight()`: numbers are pixels, strings are CSS lengths (`'250px'`, `'10rem'`). */\nfunction cssLength(value: number | string): string {\n return typeof value === 'number' || /^\\d+(\\.\\d+)?$/.test(value) ? `${value}px` : value\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"file-upload.js","names":[],"sources":["../../../src/components/fields/file-upload.tsx"],"sourcesContent":["import type { FileUploadSchema, UploadedFile } from '@alfakit/core'\nimport {\n canEditImage,\n concurrencyLimiter,\n cssAspectRatio,\n editableImageName,\n fetchEditableImage,\n fieldMemory,\n fileManagerSize,\n filesFromClipboard,\n fileUploadAccept,\n fileUploadEntries,\n fileUploadPaths,\n insertUploadedFile,\n moveRepeaterItem,\n panelUploadOptions,\n requestJson,\n transformImage,\n uploadedFileLinks,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport {\n ChevronDownIcon,\n ChevronUpIcon,\n DownloadIcon,\n ExternalLinkIcon,\n FileIcon,\n PencilIcon,\n UploadCloudIcon,\n XIcon,\n} from 'lucide-react'\nimport { useRef, useState, type ClipboardEvent, type DragEvent } from 'react'\nimport { useChoice, useFormatter, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { useField, useFormStore } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Spinner } from '../ui/spinner'\nimport type { ImageEditRequest } from './image-editor-dialog'\nimport { ImageEditorDialog } from './image-editor-dialog-lazy'\n\ninterface LocalFile extends UploadedFile {\n progress?: number\n error?: string\n preview?: string\n}\n\nexport function FileUploadField({ component }: { component: FileUploadSchema }) {\n const t = useT()\n const formatter = useFormatter()\n const choice = useChoice()\n const panel = usePanel()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<\n string | string[] | null\n >(component)\n // Read-only keeps open/download links but hides upload and remove controls.\n const locked = disabled || readOnly\n const store = useFormStore()\n // In-flight uploads and the metadata of files uploaded in this session (the server schema\n // only knows about saved files) live in the form's field memory, not in component state:\n // a virtualised Repeater item unmounts when scrolled out of view and finds them again,\n // local image previews included, when it mounts back.\n const uploadingStore = fieldMemory<LocalFile[]>(\n store,\n `${component.statePath}:uploading`,\n () => [],\n )\n const uploadedStore = fieldMemory<FileUploadSchema['files']>(\n store,\n `${component.statePath}:uploaded`,\n () => [],\n )\n const uploading = useStore(uploadingStore)\n const uploaded = useStore(uploadedStore)\n const setUploading = uploadingStore.set\n const setUploaded = uploadedStore.set\n const [dragging, setDragging] = useState(false)\n // Previews whose image failed to load (missing or unreachable file) show the file icon.\n const [brokenPreviews, setBrokenPreviews] = useState<ReadonlySet<string>>(() => new Set())\n const inputRef = useRef<HTMLInputElement>(null)\n const [dragPath, setDragPath] = useState<string | null>(null)\n // The image open in the editor (`imageEditor()`), one at a time.\n const [editing, setEditing] = useState<ImageEditRequest | null>(null)\n const editImage = (file: File) =>\n new Promise<File | null>((resolve) =>\n setEditing({\n file,\n resolve: (edited) => {\n setEditing(null)\n resolve(edited)\n },\n }),\n )\n // Object URLs made for local image previews; released when the file is removed.\n const objectUrls = fieldMemory<Set<string>>(\n store,\n `${component.statePath}:previews`,\n () => new Set(),\n ).get()\n\n const releasePreview = (url: string | undefined) => {\n if (url && objectUrls.delete(url)) URL.revokeObjectURL(url)\n }\n\n const paths = fileUploadPaths(value)\n const files: LocalFile[] = fileUploadEntries(paths, component.files, uploaded)\n const maxFiles = component.multiple ? (component.maxFiles ?? Infinity) : 1\n const canAddMore = files.length + uploading.length < maxFiles\n\n const commit = (next: string[]) => setValue(component.multiple ? next : (next[0] ?? null))\n\n const upload = async (list: FileList | File[]) => {\n const endpoint = panel.endpoints.upload\n\n if (!endpoint) {\n return\n }\n\n const accepted = Array.from(list).slice(\n 0,\n Math.max(0, maxFiles - files.length - uploading.length),\n )\n\n // `maxParallelUploads()`: files are prepared (edited, resized) one by one and then\n // queued, so at most that many upload at once.\n const limit = concurrencyLimiter(component.maxParallelUploads ?? 1)\n const queued: Promise<void>[] = []\n\n for (const [position, picked] of accepted.entries()) {\n let original = picked\n const edit = Boolean(component.imageEditor && canEditImage(picked))\n\n if (edit) {\n const edited = await editImage(picked)\n\n // Cancelled: the file is not uploaded.\n if (!edited) continue\n\n original = edited\n }\n\n const file = await prepare(original, edit)\n\n if (!file) continue\n\n const local = pendingFile(file, position)\n setUploading((state) => [...state, local])\n\n queued.push(limit(() => send(endpoint, file, local)))\n }\n\n await Promise.all(queued)\n }\n\n /** Resize and orient a picked (or edited) file, and check its size. Null when rejected. */\n const prepare = async (original: File, edited: boolean): Promise<File | null> => {\n const file =\n component.imageCropAspectRatio ||\n component.imageResizeTargetWidth ||\n component.imageResizeTargetHeight ||\n component.orientImagesFromExif\n ? await transformImage(original, {\n // The editor already cropped the image.\n aspectRatio: edited ? null : component.imageCropAspectRatio,\n mode: component.imageResizeMode,\n width: component.imageResizeTargetWidth,\n height: component.imageResizeTargetHeight,\n orient: component.orientImagesFromExif,\n })\n : original\n\n if (component.minSize && file.size < component.minSize * 1024) {\n setUploading((state) => [\n ...state,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_small', {}, 'This file is too small.'),\n },\n ])\n\n return null\n }\n\n if (component.maxSize && file.size > component.maxSize * 1024) {\n setUploading((state) => [\n ...state,\n {\n path: file.name,\n name: file.name,\n size: file.size,\n error: t('form.file_too_large', {}, 'This file is too large.'),\n },\n ])\n\n return null\n }\n\n return file\n }\n\n /** The in-progress entry shown while `file` uploads. */\n const pendingFile = (file: File, position: number | string): LocalFile => {\n const local: LocalFile = {\n // Unique within the batch and across re-picks of a changed file.\n path: `pending:${position}:${file.lastModified}:${file.size}:${file.name}`,\n name: file.name,\n size: file.size,\n progress: 0,\n preview: file.type.startsWith('image/') ? URL.createObjectURL(file) : undefined,\n }\n if (local.preview) objectUrls.add(local.preview)\n\n return local\n }\n\n /**\n * The \"Edit\" button on an uploaded image: open it in the editor and upload the result\n * in its place (the old file goes like any removed one: a temporary upload is deleted\n * now, a stored file when the form is saved with `deleteRemovedFiles()`).\n */\n const editExisting = async (existing: LocalFile) => {\n const endpoint = panel.endpoints.upload\n const url = existing.url ?? existing.preview\n const name = editableImageName(existing)\n\n if (!endpoint || !url || !name) return\n\n const source = await fetchEditableImage(url, name)\n\n if (!source) {\n setUploading((state) => [\n ...state,\n {\n path: `failed:${existing.path}`,\n name: existing.name,\n error: t('image_editor.load_failed', {}, 'The image could not be opened.'),\n },\n ])\n\n return\n }\n\n const edited = await editImage(source)\n\n // Cancelled, or the editor couldn't decode it: keep the file as it is.\n if (!edited || edited === source) return\n\n const file = await prepare(edited, true)\n\n if (!file) return\n\n const local = pendingFile(file, `edit:${existing.path}`)\n setUploading((state) => [...state, local])\n await send(endpoint, file, local, existing.path)\n }\n\n const send = async (endpoint: string, file: File, local: LocalFile, replacing?: string) => {\n try {\n // Large files go in resumable chunks; progress covers the whole file.\n const response = await uploadTemporaryFile(endpoint, file, {\n ...panelUploadOptions(panel, component.uploadPolicy),\n onProgress: (progress) =>\n setUploading((state) =>\n state.map((item) => (item.path === local.path ? { ...item, progress } : item)),\n ),\n })\n\n setUploaded((state) => [\n ...state,\n {\n path: response.key,\n name: response.name,\n size: response.size,\n temporary: true,\n url: local.preview,\n },\n ])\n setUploading((state) => state.filter((item) => item.path !== local.path))\n // Read the store, not `value`: it's stale after the first file of a batch.\n const current = fileUploadPaths(\n store.getValue<string | string[] | null>(component.statePath, null),\n )\n\n if (replacing !== undefined && current.includes(replacing)) {\n // An edited image takes the old one's place.\n commit(current.map((path) => (path === replacing ? response.key : path)))\n discard(replacing)\n } else {\n commit(insertUploadedFile(current, response.key, component, maxFiles))\n }\n } catch (uploadError) {\n const message =\n (uploadError as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n (uploadError as Error).message\n setUploading((state) =>\n state.map((item) =>\n item.path === local.path ? { ...item, error: message, progress: undefined } : item,\n ),\n )\n }\n }\n\n const remove = (path: string) => {\n commit(paths.filter((item) => item !== path))\n discard(path)\n }\n\n /** Release a file that left the field: its preview, and a temporary upload's file. */\n const discard = (path: string) => {\n releasePreview(uploadedStore.get().find((file) => file.path === path)?.url)\n\n if (path.startsWith('tmp:') && panel.endpoints.uploadDestroy) {\n void requestJson(panel.endpoints.uploadDestroy, {\n method: 'DELETE',\n body: { key: path },\n }).catch(() => undefined)\n }\n }\n\n const onDrop = (event: DragEvent<HTMLDivElement>) => {\n event.preventDefault()\n setDragging(false)\n\n if (!locked && event.dataTransfer.files.length > 0) {\n void upload(event.dataTransfer.files)\n }\n }\n\n // `pasteable()`: images and files pasted while the drop zone has focus.\n const onPaste = (event: ClipboardEvent<HTMLDivElement>) => {\n if (locked || component.pasteable === false) return\n\n const pasted = filesFromClipboard(event.clipboardData)\n\n if (pasted.length > 0) {\n event.preventDefault()\n void upload(pasted)\n }\n }\n\n const move = (from: number, to: number) => commit(moveRepeaterItem(paths, from, to) as string[])\n const reorderable = Boolean(component.reorderable && component.multiple && !locked)\n\n // `previewable(false)` shows every file as a name and icon, without the image.\n const isImage = (file: LocalFile) =>\n component.previewable !== false &&\n (component.image || /\\.(png|jpe?g|gif|webp|svg|avif)$/i.test(file.name))\n const circle = component.panelLayout === 'circle' || component.avatar\n const integrated = component.panelLayout === 'integrated' && !circle\n const removeAtStart = component.positions?.removeButton === 'start'\n const progressAtStart = component.positions?.progressIndicator === 'start'\n const uploadAlign = component.positions?.uploadButton ?? 'center'\n\n // `imageEditor()`: uploaded JPEG, PNG and WebP images can be edited again.\n const canEditExisting = (file: LocalFile, index: number) =>\n Boolean(component.imageEditor) &&\n !locked &&\n index < files.length &&\n !file.missing &&\n Boolean(file.url ?? file.preview) &&\n editableImageName(file) !== null\n\n const renderRemove = (file: LocalFile) =>\n component.deletable &&\n !locked && (\n <Button\n type=\"button\"\n variant={circle ? 'secondary' : 'ghost'}\n size=\"icon-xs\"\n className={cn(circle && 'rounded-full shadow')}\n aria-label={t('file_upload.remove', { name: file.name }, 'Remove :name')}\n onClick={() => {\n if (file.error || file.progress !== undefined) {\n releasePreview(file.preview)\n setUploading((state) => state.filter((item) => item.path !== file.path))\n } else {\n remove(file.path)\n }\n }}\n >\n <XIcon />\n </Button>\n )\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n data-slot=\"file-upload\"\n data-layout={component.panelLayout}\n className={cn('flex flex-col gap-3', integrated && 'rounded-md border p-2')}\n >\n {(files.length > 0 || uploading.length > 0) && (\n <ul\n className={cn(\n 'grid gap-2',\n component.panelLayout === 'grid' && 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4',\n circle && 'justify-items-center',\n )}\n >\n {[...files, ...uploading].map((file, index) => (\n <li\n key={file.path}\n draggable={reorderable && index < files.length}\n onDragStart={() => setDragPath(file.path)}\n onDragOver={(event) => reorderable && event.preventDefault()}\n onDrop={(event) => {\n if (!reorderable || dragPath === null) return\n event.preventDefault()\n event.stopPropagation()\n const from = paths.indexOf(dragPath)\n if (from !== -1 && index < files.length) move(from, index)\n setDragPath(null)\n }}\n onDragEnd={() => setDragPath(null)}\n className={cn(\n dragPath === file.path && 'opacity-50',\n 'bg-muted/30 relative flex min-w-0 items-center gap-3 overflow-hidden rounded-md border p-2 text-sm',\n circle && 'size-28 justify-center rounded-full border-0 p-0',\n file.error && 'border-destructive',\n )}\n >\n {!circle && removeAtStart && renderRemove(file)}\n {!circle && progressAtStart && file.progress !== undefined && !file.error && (\n <Spinner />\n )}\n {isImage(file) && (file.url || file.preview) && !brokenPreviews.has(file.path) ? (\n <img\n src={file.url ?? file.preview}\n alt=\"\"\n onError={() => setBrokenPreviews((current) => new Set(current).add(file.path))}\n className={cn(\n 'size-12 shrink-0 rounded object-cover',\n circle && 'size-28 rounded-full',\n )}\n style={\n !circle && component.imagePreviewHeight\n ? {\n height: cssLength(component.imagePreviewHeight),\n width: 'auto',\n maxWidth: '50%',\n }\n : undefined\n }\n />\n ) : (\n !circle && <FileIcon className=\"text-muted-foreground size-6 shrink-0\" />\n )}\n {!circle && (\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate font-medium\">{file.name}</p>\n <p\n className={cn(\n 'text-muted-foreground text-xs',\n file.missing && 'text-destructive',\n )}\n >\n {file.error ??\n (file.missing\n ? t('form.file_missing', {}, 'This file no longer exists.')\n : file.progress !== undefined && !file.error\n ? (component.uploadingMessage ?? t('form.uploading', {}, 'Uploading…'))\n : file.size\n ? fileManagerSize(file.size, formatter)\n : file.temporary\n ? t('form.uploading', {}, 'Pending')\n : '')}\n </p>\n </div>\n )}\n <div\n className={cn('flex items-center gap-1', circle && 'absolute -end-1 -bottom-1')}\n >\n {(circle || !progressAtStart) && file.progress !== undefined && !file.error && (\n <Spinner />\n )}\n {reorderable && index < files.length && (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t('file_upload.move_up', { name: file.name }, 'Move :name up')}\n disabled={index === 0}\n onClick={() => move(index, index - 1)}\n >\n <ChevronUpIcon />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n aria-label={t(\n 'file_upload.move_down',\n { name: file.name },\n 'Move :name down',\n )}\n disabled={index === files.length - 1}\n onClick={() => move(index, index + 1)}\n >\n <ChevronDownIcon />\n </Button>\n </>\n )}\n {canEditExisting(file, index) && (\n <Button\n type=\"button\"\n variant={circle ? 'secondary' : 'ghost'}\n size=\"icon-xs\"\n className={cn(circle && 'rounded-full shadow')}\n aria-label={t('file_upload.edit', { name: file.name }, 'Edit :name')}\n title={t('image_editor.title', {}, 'Edit image')}\n data-action=\"edit-image\"\n onClick={() => void editExisting(file)}\n >\n <PencilIcon />\n </Button>\n )}\n {component.openable && uploadedFileLinks(file).open && (\n <Button asChild type=\"button\" variant=\"ghost\" size=\"icon-xs\">\n <a\n href={uploadedFileLinks(file).open}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`${t('file_upload.open', { name: file.name }, 'Open :name')} ${t('opens_in_new_tab', {}, '(opens in a new tab)')}`}\n >\n <ExternalLinkIcon />\n </a>\n </Button>\n )}\n {component.downloadable && uploadedFileLinks(file).download && (\n <Button asChild type=\"button\" variant=\"ghost\" size=\"icon-xs\">\n <a\n href={uploadedFileLinks(file).download}\n download\n aria-label={t(\n 'file_upload.download',\n { name: file.name },\n 'Download :name',\n )}\n >\n <DownloadIcon />\n </a>\n </Button>\n )}\n {(circle || !removeAtStart) && renderRemove(file)}\n </div>\n </li>\n ))}\n </ul>\n )}\n {canAddMore && !locked && (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => inputRef.current?.click()}\n aria-describedby={aria['aria-describedby']}\n onKeyDown={(event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault()\n inputRef.current?.click()\n }\n }}\n onDragOver={(event) => {\n event.preventDefault()\n setDragging(true)\n }}\n onDragLeave={() => setDragging(false)}\n onDrop={onDrop}\n onPaste={onPaste}\n style={\n !circle && component.panelAspectRatio\n ? { aspectRatio: cssAspectRatio(component.panelAspectRatio) }\n : undefined\n }\n data-align={uploadAlign}\n className={cn(\n 'text-muted-foreground hover:border-ring flex cursor-pointer flex-col justify-center gap-1 rounded-md border border-dashed px-4 py-6 text-sm transition-colors',\n uploadAlign === 'start'\n ? 'items-start text-start'\n : uploadAlign === 'end'\n ? 'items-end text-end'\n : 'items-center text-center',\n integrated && 'border-transparent',\n dragging && 'border-ring bg-accent',\n circle && 'size-28 items-center rounded-full py-0',\n )}\n >\n <UploadCloudIcon className=\"size-5\" />\n {!circle && (\n <span>\n {component.placeholder ??\n t('form.drop_files', {}, 'Drop files here or click to browse')}\n </span>\n )}\n {!circle &&\n (component.acceptedFileTypes || component.maxSize || component.minFiles) && (\n <span className=\"text-muted-foreground text-xs\">\n {[\n component.acceptedFileTypes?.join(', '),\n component.maxSize\n ? `≤ ${fileManagerSize(component.maxSize * 1024, formatter)}`\n : null,\n component.minFiles\n ? choice(\n 'form.min_files',\n component.minFiles,\n {},\n 'At least :count file|At least :count files',\n )\n : null,\n ]\n .filter(Boolean)\n .join(' · ')}\n </span>\n )}\n </div>\n )}\n <input\n ref={inputRef}\n id={id}\n type=\"file\"\n className=\"sr-only\"\n tabIndex={-1}\n {...aria}\n multiple={component.multiple}\n accept={fileUploadAccept(component)}\n disabled={locked}\n onChange={(event) => {\n // Copy the list: clearing the input empties it while the editor is open.\n const picked = event.target.files ? Array.from(event.target.files) : []\n event.target.value = ''\n void upload(picked)\n }}\n />\n {component.imageEditor && (\n <ImageEditorDialog request={editing} options={component.imageEditor} />\n )}\n </div>\n </FieldWrapper>\n )\n}\n\n/** `imagePreviewHeight()`: numbers are pixels, strings are CSS lengths (`'250px'`, `'10rem'`). */\nfunction cssLength(value: number | string): string {\n return typeof value === 'number' || /^\\d+(\\.\\d+)?$/.test(value) ? `${value}px` : value\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"repeater.d.ts","names":[],"sources":["../../../src/components/fields/repeater.tsx"],"mappings":";;;;;;;;;;wBAiDgB,gBAAgB;EAAe,WAAW;oBAAgB,IAAA"}
1
+ {"version":3,"file":"repeater.d.ts","names":[],"sources":["../../../src/components/fields/repeater.tsx"],"mappings":";;;;;;;;;;wBAkDgB,gBAAgB;EAAe,WAAW;oBAAgB,IAAA"}
@@ -10,7 +10,7 @@ import { FieldWrapper } from "../schema/field-wrapper.js";
10
10
  import { Collapsible, CollapsibleContent } from "../ui/collapsible.js";
11
11
  import { AddActionRow, CollapseAllActions, ItemActionButton, ItemHeaderActions, useItemActionFallbacks, useItemActions, useItemMoves, useRunItemAction } from "./item-actions.js";
12
12
  import { RepeaterTable } from "./repeater-table.js";
13
- import { VIRTUAL_ESTIMATES, appendRepeaterItem, cloneIndexedState, cloneRepeaterItem, fieldIds, gridColumnsClasses, isDeferredPlaceholder, moveIndexedState, moveRepeaterItem, removeIndexedState, removeRepeaterItem, reorderedItemKeys, templateForIndex, virtualizeOptions } from "@alfakit/core";
13
+ import { VIRTUAL_ESTIMATES, appendRepeaterItem, cloneIndexedState, cloneRepeaterItem, fieldIds, gridColumnsClasses, isDeferredPlaceholder, itemExtraActions, moveIndexedState, moveRepeaterItem, removeIndexedState, removeRepeaterItem, reorderedItemKeys, templateForIndex, virtualizeOptions } from "@alfakit/core";
14
14
  import { useState } from "react";
15
15
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
16
16
  //#region src/components/fields/repeater.tsx
@@ -91,7 +91,7 @@ function RepeaterFieldContent({ component }) {
91
91
  virtual,
92
92
  context,
93
93
  resolve: (name, index) => resolve(name, component.items[index]),
94
- extraActionsFor: (index) => component.items[index]?.actions
94
+ extraActionsFor: (index) => itemExtraActions(component, component.items[index])
95
95
  }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [component.collapsible && !component.simple && items.length > 0 && /* @__PURE__ */ jsx(CollapseAllActions, {
96
96
  context,
97
97
  resolve: (name) => resolve(name),
@@ -150,7 +150,7 @@ function RepeaterFieldContent({ component }) {
150
150
  index,
151
151
  count: items.length,
152
152
  resolve: (name) => resolve(name, serverItem),
153
- extraActions: locked ? void 0 : serverItem?.actions,
153
+ extraActions: locked ? void 0 : itemExtraActions(component, serverItem),
154
154
  buttons,
155
155
  canClone: Boolean(component.cloneable) && canAdd,
156
156
  canDelete,