@alfakit/react 0.1.1 → 0.1.2

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 (71) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/components/actions/action-button.d.ts.map +1 -1
  3. package/dist/components/actions/action-button.js +5 -4
  4. package/dist/components/actions/action-button.js.map +1 -1
  5. package/dist/components/actions/action-modals.d.ts.map +1 -1
  6. package/dist/components/actions/action-modals.js +2 -5
  7. package/dist/components/actions/action-modals.js.map +1 -1
  8. package/dist/components/fields/color-picker.js +1 -0
  9. package/dist/components/fields/color-picker.js.map +1 -1
  10. package/dist/components/fields/file-upload.js +11 -10
  11. package/dist/components/fields/file-upload.js.map +1 -1
  12. package/dist/components/fields/one-time-code-input.js +1 -1
  13. package/dist/components/fields/one-time-code-input.js.map +1 -1
  14. package/dist/components/fields/repeater-table.js +12 -1
  15. package/dist/components/fields/repeater-table.js.map +1 -1
  16. package/dist/components/fields/select.js +3 -3
  17. package/dist/components/fields/select.js.map +1 -1
  18. package/dist/components/file-manager/file-manager.js +1 -1
  19. package/dist/components/file-manager/file-manager.js.map +1 -1
  20. package/dist/components/form/form-block.d.ts.map +1 -1
  21. package/dist/components/form/form-block.js +13 -1
  22. package/dist/components/form/form-block.js.map +1 -1
  23. package/dist/components/form/form-context.js +6 -2
  24. package/dist/components/form/form-context.js.map +1 -1
  25. package/dist/components/layout/fused-group.js +1 -0
  26. package/dist/components/layout/fused-group.js.map +1 -1
  27. package/dist/components/layout/tabs.d.ts.map +1 -1
  28. package/dist/components/layout/tabs.js +54 -15
  29. package/dist/components/layout/tabs.js.map +1 -1
  30. package/dist/components/panel/global-search.js +1 -0
  31. package/dist/components/panel/global-search.js.map +1 -1
  32. package/dist/components/panel/shortcut-keys.js +1 -0
  33. package/dist/components/panel/shortcut-keys.js.map +1 -1
  34. package/dist/components/panel/tenant-menu.js +2 -2
  35. package/dist/components/panel/tenant-menu.js.map +1 -1
  36. package/dist/components/table/cells.js +9 -1
  37. package/dist/components/table/cells.js.map +1 -1
  38. package/dist/components/table/grouping.js +11 -1
  39. package/dist/components/table/grouping.js.map +1 -1
  40. package/dist/components.css +347 -0
  41. package/dist/eject-manifest.json +2 -2
  42. package/dist/standalone/alfakit-toaster.d.ts.map +1 -1
  43. package/dist/standalone/alfakit-toaster.js +8 -1
  44. package/dist/standalone/alfakit-toaster.js.map +1 -1
  45. package/dist/standalone.css +47 -0
  46. package/dist/starter-kit/widgets-loaded.js +1 -1
  47. package/dist/starter-kit/widgets-loaded.js.map +1 -1
  48. package/dist/starter-kit/widgets-triggers.js +1 -0
  49. package/dist/starter-kit/widgets-triggers.js.map +1 -1
  50. package/dist/themes.css +2 -334
  51. package/package.json +2 -2
  52. package/src/components/actions/action-button.tsx +7 -4
  53. package/src/components/actions/action-modals.tsx +3 -1
  54. package/src/components/fields/color-picker.tsx +1 -0
  55. package/src/components/fields/file-upload.tsx +21 -10
  56. package/src/components/fields/one-time-code-input.tsx +1 -1
  57. package/src/components/fields/repeater-table.tsx +12 -1
  58. package/src/components/fields/select.tsx +7 -3
  59. package/src/components/file-manager/file-manager.tsx +1 -1
  60. package/src/components/form/form-block.tsx +24 -0
  61. package/src/components/form/form-context.tsx +2 -5
  62. package/src/components/layout/fused-group.tsx +5 -1
  63. package/src/components/layout/tabs.tsx +72 -17
  64. package/src/components/panel/global-search.tsx +1 -1
  65. package/src/components/panel/shortcut-keys.tsx +4 -1
  66. package/src/components/panel/tenant-menu.tsx +2 -2
  67. package/src/components/table/cells.tsx +18 -3
  68. package/src/components/table/grouping.tsx +15 -1
  69. package/src/standalone/alfakit-toaster.tsx +21 -2
  70. package/src/starter-kit/widgets-loaded.tsx +1 -1
  71. package/src/starter-kit/widgets-triggers.tsx +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # @alfakit/react
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Fixes from end-to-end QA in React, Vue and Svelte: modal field ids, errors in hidden tabs, per-row action spinners, one-time-code paste, RTL fixes (colours, key bindings, calendar), localised file sizes and named upload buttons, select placeholders, single toasts in starter kits, readable tab query values, enum group titles, tenant menu labels, Svelte hydration gate, plugin styles in starter-kit mode, and more (see the AlfaKit changelog for 0.1.2).
8
+ - Updated dependencies
9
+ - @alfakit/core@0.1.2
10
+
3
11
  ## 0.1.1
4
12
 
5
13
  ### Patch Changes
@@ -1 +1 @@
1
- {"version":3,"file":"action-button.d.ts","names":[],"sources":["../../../src/components/actions/action-button.tsx"],"mappings":";;iBA8CiB;EACf,QAAQ;EACR,SAAS;EACT;EACA;;EAEA,UAAU;;EAEV;;EAEA,aAAa,QAAQ,cAAc,SAAS,yBAAyB;;wBA8BvD,eACd,QACA,SACA,MACA,WACA,SAAS,aACT,iBACA,aACC,oCAAiB,IAAA"}
1
+ {"version":3,"file":"action-button.d.ts","names":[],"sources":["../../../src/components/actions/action-button.tsx"],"mappings":";;iBAgDiB;EACf,QAAQ;EACR,SAAS;EACT;EACA;;EAEA,UAAU;;EAEV;;EAEA,aAAa,QAAQ,cAAc,SAAS,yBAAyB;;wBA8BvD,eACd,QACA,SACA,MACA,WACA,SAAS,aACT,iBACA,aACC,oCAAiB,IAAA"}
@@ -8,7 +8,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip.js";
8
8
  import { useStoreSelector } from "../../hooks/use-store.js";
9
9
  import { extraProps } from "../../lib/attributes.js";
10
10
  import { Spinner } from "../ui/spinner.js";
11
- import { bindsKeyBindings, formatKeyBinding, iconButtonSize, registerKeyBindings, resolveActionSize } from "@alfakit/core";
11
+ import { bindsKeyBindings, formatKeyBinding, iconButtonSize, isActionLoading, isActionProcessing, registerKeyBindings, resolveActionSize } from "@alfakit/core";
12
12
  import { Link } from "@inertiajs/react";
13
13
  import { useEffect } from "react";
14
14
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
@@ -36,7 +36,7 @@ function buttonClasses(action, variant) {
36
36
  switch (variant) {
37
37
  case "outline": return cn("bg-background border", colorClass(outlineButtonColorClasses, color, "gray"));
38
38
  case "ghost": return colorClass(ghostButtonColorClasses, color, "gray");
39
- case "link": return cn("h-auto p-0 underline-offset-4 hover:underline", colorClass(textColorClasses, color, "primary"));
39
+ case "link": return cn("h-auto p-0 underline-offset-4 hover:underline has-[>svg]:px-0", colorClass(textColorClasses, color, "primary"));
40
40
  case "icon-button": return colorClass(ghostButtonColorClasses, color, "gray");
41
41
  case "badge": return cn(BADGE_TRIGGER_CLASSES, colorClass(badgeColorClasses, color, "primary"));
42
42
  default: return colorClass(solidButtonColorClasses, color, "primary");
@@ -45,8 +45,8 @@ function buttonClasses(action, variant) {
45
45
  function ActionButton({ action, context, size = "default", className, variant: variantProp, withKeyBindings = true, onTrigger }) {
46
46
  const runner = useActionRunner();
47
47
  const t = useT();
48
- const loading = useStoreSelector(runner.store, (state) => state.loading === action.name);
49
- const processing = useStoreSelector(runner.store, (state) => state.modals.some((modal) => modal.action.name === action.name && modal.processing));
48
+ const loading = useStoreSelector(runner.store, (state) => isActionLoading(state, action, context));
49
+ const processing = useStoreSelector(runner.store, (state) => isActionProcessing(state, action, context));
50
50
  const variant = variantProp ?? (action.outlined ? "outline" : action.variant ?? (action.hasAction || action.hasModal || action.url ? "link" : "button"));
51
51
  const isIconOnly = variant === "icon-button" || !action.label;
52
52
  const labeledFrom = !isIconOnly && action.icon && action.labeledFrom ? action.labeledFrom : null;
@@ -140,6 +140,7 @@ function ActionButton({ action, context, size = "default", className, variant: v
140
140
  asChild: true,
141
141
  children: trigger
142
142
  }), /* @__PURE__ */ jsxs(TooltipContent, { children: [tooltip, shortcut && /* @__PURE__ */ jsx("kbd", {
143
+ dir: "ltr",
143
144
  className: "ms-2 rounded border border-white/20 px-1 font-mono text-[10px]",
144
145
  children: formatKeyBinding(shortcut)
145
146
  })] })] });
@@ -1 +1 @@
1
- {"version":3,"file":"action-button.js","names":[],"sources":["../../../src/components/actions/action-button.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema } from '@alfakit/core'\nimport {\n bindsKeyBindings,\n formatKeyBinding,\n iconButtonSize,\n registerKeyBindings,\n resolveActionSize,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { useEffect, type MouseEvent } from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { useStoreSelector } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n badgeColorClasses,\n colorClass,\n ghostButtonColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Spinner } from '../ui/spinner'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\n\n/** `labeledFrom()`: the label shows from the breakpoint up. */\nconst LABEL_FROM: Record<string, string> = {\n sm: 'hidden sm:inline',\n md: 'hidden md:inline',\n lg: 'hidden lg:inline',\n xl: 'hidden xl:inline',\n '2xl': 'hidden 2xl:inline',\n}\n\n/** `labeledFrom()`: square (icon-only) padding below the breakpoint. */\nconst ICON_ONLY_BELOW: Record<string, string> = {\n sm: 'max-sm:aspect-square max-sm:px-0',\n md: 'max-md:aspect-square max-md:px-0',\n lg: 'max-lg:aspect-square max-lg:px-0',\n xl: 'max-xl:aspect-square max-xl:px-0',\n '2xl': 'max-2xl:aspect-square max-2xl:px-0',\n}\n\nexport interface ActionButtonProps {\n action: ActionSchema\n context: ActionContext\n size?: 'xs' | 'sm' | 'default' | 'lg'\n className?: string\n /** Force a variant (e.g. menu items render as plain rows). */\n variant?: ActionSchema['variant']\n /** Bind the action's key bindings globally while mounted. */\n withKeyBindings?: boolean\n /** Override how the action runs (e.g. a form submits its values). */\n onTrigger?: (action: ActionSchema, context: ActionContext) => void | Promise<void>\n}\n\n/** The badge trigger style (`badge()` without an argument). */\nconst BADGE_TRIGGER_CLASSES =\n \"h-auto min-h-6 gap-1 rounded-md border px-2 py-0.5 text-xs font-medium [&_svg:not([class*='size-'])]:size-3.5\"\n\nfunction buttonClasses(action: ActionSchema, variant: ActionSchema['variant']): string {\n const color = action.color ?? 'primary'\n\n switch (variant) {\n case 'outline':\n return cn('bg-background border', colorClass(outlineButtonColorClasses, color, 'gray'))\n case 'ghost':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'link':\n return cn(\n 'h-auto p-0 underline-offset-4 hover:underline',\n colorClass(textColorClasses, color, 'primary'),\n )\n case 'icon-button':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'badge':\n // `badge()`: a tinted pill in the action's colour.\n return cn(BADGE_TRIGGER_CLASSES, colorClass(badgeColorClasses, color, 'primary'))\n default:\n return colorClass(solidButtonColorClasses, color, 'primary')\n }\n}\n\nexport function ActionButton({\n action,\n context,\n size = 'default',\n className,\n variant: variantProp,\n withKeyBindings = true,\n onTrigger,\n}: ActionButtonProps) {\n const runner = useActionRunner()\n const t = useT()\n const loading = useStoreSelector(runner.store, (state) => state.loading === action.name)\n const processing = useStoreSelector(runner.store, (state) =>\n state.modals.some((modal) => modal.action.name === action.name && modal.processing),\n )\n\n const variant =\n variantProp ??\n (action.outlined\n ? 'outline'\n : (action.variant ?? (action.hasAction || action.hasModal || action.url ? 'link' : 'button')))\n const isIconOnly = variant === 'icon-button' || !action.label\n // `labeledFrom()`: icon only on small screens, so the label must be the accessible name.\n const labeledFrom = !isIconOnly && action.icon && action.labeledFrom ? action.labeledFrom : null\n const busy = loading || processing\n // Record actions repeat per row: a global shortcut would run them for every record.\n const bindsKeys = withKeyBindings && bindsKeyBindings(action, context)\n\n useEffect(() => {\n if (!bindsKeys || !action.keyBindings) {\n return\n }\n\n return registerKeyBindings([\n {\n keys: action.keyBindings,\n handler: () =>\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context)),\n },\n ])\n }, [action, context, runner, bindsKeys, onTrigger])\n\n // A disabled link renders as a disabled button so it cannot navigate.\n const isPlainLink = Boolean(\n action.url && !action.hasAction && !action.hasModal && !action.disabled,\n )\n\n const onClick = (event: MouseEvent) => {\n if (isPlainLink) {\n return // Plain link: let Inertia Link / anchor handle it.\n }\n\n event.preventDefault()\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context))\n }\n\n const content = (\n <>\n {busy ? (\n <Spinner />\n ) : action.icon && action.iconPosition !== 'after' ? (\n <Icon name={action.icon} />\n ) : null}\n {!isIconOnly && (\n <span className={labeledFrom ? LABEL_FROM[labeledFrom] : undefined}>{action.label}</span>\n )}\n {action.icon && action.iconPosition === 'after' && !busy && <Icon name={action.icon} />}\n {action.badge !== undefined && action.badge !== null && (\n <Badge\n variant=\"outline\"\n className={cn(\n 'ms-1 px-1.5 py-0',\n colorClass(badgeColorClasses, action.badgeColor, 'gray'),\n )}\n >\n {action.badge}\n </Badge>\n )}\n </>\n )\n\n const accessibleName = action.label ?? action.tooltip ?? action.name\n const newTabHint = t('opens_in_new_tab', {}, '(opens in a new tab)')\n const buttonSize = variant === 'badge' ? 'xs' : resolveActionSize(action.size, size)\n const sizeProp = isIconOnly ? iconButtonSize(buttonSize) : buttonSize\n const classes = cn(\n buttonClasses(action, variant),\n labeledFrom && ICON_ONLY_BELOW[labeledFrom],\n className,\n )\n const ariaLabel = isIconOnly || labeledFrom ? accessibleName : undefined\n\n const button = isPlainLink ? (\n action.openUrlInNewTab ? (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel ? `${ariaLabel} ${newTabHint}` : undefined}\n {...extraProps(action.extraAttributes, classes)}\n >\n <a href={action.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {content}\n {!isIconOnly && <span className=\"sr-only\"> {newTabHint}</span>}\n </a>\n </Button>\n ) : (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n <Link href={action.url ?? '#'}>{content}</Link>\n </Button>\n )\n ) : (\n <Button\n type=\"button\"\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled || busy}\n onClick={onClick}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n {content}\n </Button>\n )\n\n const tooltip = action.tooltip ?? (isIconOnly ? action.label : undefined)\n const shortcut = bindsKeys ? action.keyBindings?.[0] : undefined\n\n if (!tooltip && !shortcut) {\n return button\n }\n\n // A disabled button gets no pointer or focus events, so a focusable wrapper carries the\n // tooltip (e.g. the reason from `authorizationTooltip()`).\n const trigger =\n action.disabled && tooltip ? (\n <span tabIndex={0} className=\"inline-flex\" data-slot=\"action-disabled-tooltip\">\n {button}\n </span>\n ) : (\n button\n )\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {tooltip}\n {shortcut && (\n <kbd className=\"ms-2 rounded border border-white/20 px-1 font-mono text-[10px]\">\n {formatKeyBinding(shortcut)}\n </kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"action-button.js","names":[],"sources":["../../../src/components/actions/action-button.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema } from '@alfakit/core'\nimport {\n bindsKeyBindings,\n formatKeyBinding,\n iconButtonSize,\n isActionLoading,\n isActionProcessing,\n registerKeyBindings,\n resolveActionSize,\n} from '@alfakit/core'\nimport { Link } from '@inertiajs/react'\nimport { useEffect, type MouseEvent } from 'react'\nimport { useActionRunner, useT } from '../../context'\nimport { useStoreSelector } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n badgeColorClasses,\n colorClass,\n ghostButtonColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n textColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Spinner } from '../ui/spinner'\nimport { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip'\n\n/** `labeledFrom()`: the label shows from the breakpoint up. */\nconst LABEL_FROM: Record<string, string> = {\n sm: 'hidden sm:inline',\n md: 'hidden md:inline',\n lg: 'hidden lg:inline',\n xl: 'hidden xl:inline',\n '2xl': 'hidden 2xl:inline',\n}\n\n/** `labeledFrom()`: square (icon-only) padding below the breakpoint. */\nconst ICON_ONLY_BELOW: Record<string, string> = {\n sm: 'max-sm:aspect-square max-sm:px-0',\n md: 'max-md:aspect-square max-md:px-0',\n lg: 'max-lg:aspect-square max-lg:px-0',\n xl: 'max-xl:aspect-square max-xl:px-0',\n '2xl': 'max-2xl:aspect-square max-2xl:px-0',\n}\n\nexport interface ActionButtonProps {\n action: ActionSchema\n context: ActionContext\n size?: 'xs' | 'sm' | 'default' | 'lg'\n className?: string\n /** Force a variant (e.g. menu items render as plain rows). */\n variant?: ActionSchema['variant']\n /** Bind the action's key bindings globally while mounted. */\n withKeyBindings?: boolean\n /** Override how the action runs (e.g. a form submits its values). */\n onTrigger?: (action: ActionSchema, context: ActionContext) => void | Promise<void>\n}\n\n/** The badge trigger style (`badge()` without an argument). */\nconst BADGE_TRIGGER_CLASSES =\n \"h-auto min-h-6 gap-1 rounded-md border px-2 py-0.5 text-xs font-medium [&_svg:not([class*='size-'])]:size-3.5\"\n\nfunction buttonClasses(action: ActionSchema, variant: ActionSchema['variant']): string {\n const color = action.color ?? 'primary'\n\n switch (variant) {\n case 'outline':\n return cn('bg-background border', colorClass(outlineButtonColorClasses, color, 'gray'))\n case 'ghost':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'link':\n return cn(\n 'h-auto p-0 underline-offset-4 hover:underline has-[>svg]:px-0',\n colorClass(textColorClasses, color, 'primary'),\n )\n case 'icon-button':\n return colorClass(ghostButtonColorClasses, color, 'gray')\n case 'badge':\n // `badge()`: a tinted pill in the action's colour.\n return cn(BADGE_TRIGGER_CLASSES, colorClass(badgeColorClasses, color, 'primary'))\n default:\n return colorClass(solidButtonColorClasses, color, 'primary')\n }\n}\n\nexport function ActionButton({\n action,\n context,\n size = 'default',\n className,\n variant: variantProp,\n withKeyBindings = true,\n onTrigger,\n}: ActionButtonProps) {\n const runner = useActionRunner()\n const t = useT()\n // Only this run (this record's button), not the same action in every table row.\n const loading = useStoreSelector(runner.store, (state) => isActionLoading(state, action, context))\n const processing = useStoreSelector(runner.store, (state) =>\n isActionProcessing(state, action, context),\n )\n\n const variant =\n variantProp ??\n (action.outlined\n ? 'outline'\n : (action.variant ?? (action.hasAction || action.hasModal || action.url ? 'link' : 'button')))\n const isIconOnly = variant === 'icon-button' || !action.label\n // `labeledFrom()`: icon only on small screens, so the label must be the accessible name.\n const labeledFrom = !isIconOnly && action.icon && action.labeledFrom ? action.labeledFrom : null\n const busy = loading || processing\n // Record actions repeat per row: a global shortcut would run them for every record.\n const bindsKeys = withKeyBindings && bindsKeyBindings(action, context)\n\n useEffect(() => {\n if (!bindsKeys || !action.keyBindings) {\n return\n }\n\n return registerKeyBindings([\n {\n keys: action.keyBindings,\n handler: () =>\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context)),\n },\n ])\n }, [action, context, runner, bindsKeys, onTrigger])\n\n // A disabled link renders as a disabled button so it cannot navigate.\n const isPlainLink = Boolean(\n action.url && !action.hasAction && !action.hasModal && !action.disabled,\n )\n\n const onClick = (event: MouseEvent) => {\n if (isPlainLink) {\n return // Plain link: let Inertia Link / anchor handle it.\n }\n\n event.preventDefault()\n void (onTrigger ? onTrigger(action, context) : runner.trigger(action, context))\n }\n\n const content = (\n <>\n {busy ? (\n <Spinner />\n ) : action.icon && action.iconPosition !== 'after' ? (\n <Icon name={action.icon} />\n ) : null}\n {!isIconOnly && (\n <span className={labeledFrom ? LABEL_FROM[labeledFrom] : undefined}>{action.label}</span>\n )}\n {action.icon && action.iconPosition === 'after' && !busy && <Icon name={action.icon} />}\n {action.badge !== undefined && action.badge !== null && (\n <Badge\n variant=\"outline\"\n className={cn(\n 'ms-1 px-1.5 py-0',\n colorClass(badgeColorClasses, action.badgeColor, 'gray'),\n )}\n >\n {action.badge}\n </Badge>\n )}\n </>\n )\n\n const accessibleName = action.label ?? action.tooltip ?? action.name\n const newTabHint = t('opens_in_new_tab', {}, '(opens in a new tab)')\n const buttonSize = variant === 'badge' ? 'xs' : resolveActionSize(action.size, size)\n const sizeProp = isIconOnly ? iconButtonSize(buttonSize) : buttonSize\n const classes = cn(\n buttonClasses(action, variant),\n labeledFrom && ICON_ONLY_BELOW[labeledFrom],\n className,\n )\n const ariaLabel = isIconOnly || labeledFrom ? accessibleName : undefined\n\n const button = isPlainLink ? (\n action.openUrlInNewTab ? (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel ? `${ariaLabel} ${newTabHint}` : undefined}\n {...extraProps(action.extraAttributes, classes)}\n >\n <a href={action.url} target=\"_blank\" rel=\"noopener noreferrer\">\n {content}\n {!isIconOnly && <span className=\"sr-only\"> {newTabHint}</span>}\n </a>\n </Button>\n ) : (\n <Button\n asChild\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n <Link href={action.url ?? '#'}>{content}</Link>\n </Button>\n )\n ) : (\n <Button\n type=\"button\"\n variant=\"plain\"\n size={sizeProp}\n\n disabled={action.disabled || busy}\n onClick={onClick}\n aria-label={ariaLabel}\n {...extraProps(action.extraAttributes, classes)}\n >\n {content}\n </Button>\n )\n\n const tooltip = action.tooltip ?? (isIconOnly ? action.label : undefined)\n const shortcut = bindsKeys ? action.keyBindings?.[0] : undefined\n\n if (!tooltip && !shortcut) {\n return button\n }\n\n // A disabled button gets no pointer or focus events, so a focusable wrapper carries the\n // tooltip (e.g. the reason from `authorizationTooltip()`).\n const trigger =\n action.disabled && tooltip ? (\n <span tabIndex={0} className=\"inline-flex\" data-slot=\"action-disabled-tooltip\">\n {button}\n </span>\n ) : (\n button\n )\n\n return (\n <Tooltip>\n <TooltipTrigger asChild>{trigger}</TooltipTrigger>\n <TooltipContent>\n {tooltip}\n {shortcut && (\n <kbd dir=\"ltr\" className=\"ms-2 rounded border border-white/20 px-1 font-mono text-[10px]\">\n {formatKeyBinding(shortcut)}\n </kbd>\n )}\n </TooltipContent>\n </Tooltip>\n )\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"action-modals.d.ts","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"mappings":";;wBA0FgB,gCAAY,IAAA"}
1
+ {"version":3,"file":"action-modals.d.ts","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"mappings":";;wBA2FgB,gCAAY,IAAA"}
@@ -15,7 +15,7 @@ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, D
15
15
  import { showToast } from "../notifications/flash-toasts.js";
16
16
  import { Slot } from "../panel/slot.js";
17
17
  import { Sheet, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle } from "../ui/sheet.js";
18
- import { FormStore, PASSWORD_CONFIRMATION_INPUT, actionFormComponent, actionFormParams, actionPath, confirmModalClose, guardModalUnload, isModalHiddenByChild, isModalSubmitVisible, modalFooterAlignment, nestedActionContext, scheduleAutofocus, slideOverSide, wizardProgressStore } from "@alfakit/core";
18
+ import { FormStore, PASSWORD_CONFIRMATION_INPUT, actionFormComponent, actionFormParams, actionPath, confirmModalClose, formIdPrefix, guardModalUnload, isModalHiddenByChild, isModalSubmitVisible, modalFooterAlignment, nestedActionContext, scheduleAutofocus, slideOverSide, wizardProgressStore } from "@alfakit/core";
19
19
  import { useEffect, useId, useMemo, useRef, useState } from "react";
20
20
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
21
21
  //#region src/components/actions/action-modals.tsx
@@ -102,6 +102,7 @@ function ActionModal({ modal, hidden = false }) {
102
102
  return new FormStore(schema.form, {
103
103
  token: context.token,
104
104
  component: actionFormComponent(action, context),
105
+ idPrefix: formIdPrefix(actionFormComponent(action, context)),
105
106
  record: context.record ?? null,
106
107
  refreshUrl: panel.endpoints.schemaRefresh,
107
108
  optionsUrl: panel.endpoints.schemaOptions,
@@ -347,10 +348,6 @@ function ActionModal({ modal, hidden = false }) {
347
348
  footer && /* @__PURE__ */ jsx(DialogFooter, {
348
349
  className: cn(schema.stickyFooter && "bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4"),
349
350
  children: footer
350
- }),
351
- /* @__PURE__ */ jsx("span", {
352
- className: "sr-only",
353
- children: t("close", {}, "Close")
354
351
  })
355
352
  ]
356
353
  })
@@ -1 +1 @@
1
- {"version":3,"file":"action-modals.js","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"sourcesContent":["import type { FormState, OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n actionFormParams,\n actionPath,\n confirmModalClose,\n FormStore,\n guardModalUnload,\n isModalHiddenByChild,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider } from '../form/form-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport { ActionList } from './action-list'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\n// Module-level so the snapshot stays stable (a new object per read re-renders forever).\nconst EMPTY_PROGRESS = { step: 0, count: 0 }\nconst NO_PROGRESS = { get: () => EMPTY_PROGRESS, subscribe: () => () => {} }\nconst NO_FORM = { get: () => null, subscribe: () => () => {} }\n\n/** Renders every open action modal from the runner store. Mount once per app. */\nexport function ActionModals() {\n const runner = useActionRunner()\n const { modals } = useStore(runner.store)\n\n // Notifications from JSON responses (a modal `mountUsing()` cancelled, field actions).\n useEffect(() => {\n runner.setNotificationHandler((notifications) => notifications.forEach((n) => showToast(n)))\n\n return () => runner.setNotificationHandler(undefined)\n }, [runner])\n\n return (\n <>\n {modals.map((modal, index) => (\n <ActionModal\n key={`${actionPath(modal.action, modal.context)}-${modal.context.record ?? ''}`}\n modal={modal}\n hidden={isModalHiddenByChild(modals, index)}\n />\n ))}\n </>\n )\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\n/**\n * Put PHP attributes (`extraModalOverlayAttributes()`) on an element the renderer doesn't\n * create itself (the dialog's backdrop). `class` is added to the element's classes.\n */\nfunction applyAttributes(\n element: Element | null | undefined,\n attributes: Record<string, unknown> | undefined,\n) {\n if (!element || !attributes) return\n\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class' || name === 'className') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nfunction ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boolean }) {\n const runner = useActionRunner()\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n const { action, context, modal: schema } = modal\n const submittedRef = useRef(false)\n\n const form = useMemo(() => {\n if (!schema.form) {\n return null\n }\n\n return new FormStore(schema.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n record: context.record ?? null,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n params: actionFormParams(context),\n })\n }, [schema.form, context, action, panel.endpoints])\n\n useEffect(() => {\n form?.setErrors(modal.errors)\n }, [form, modal.errors])\n\n useEffect(() => () => form?.destroy(), [form])\n\n // \"Create & create another\": back to the `autofocus()` field of the fresh form.\n const refocus = modal.refocus\n\n useEffect(() => {\n if (!refocus || !form) return\n\n return scheduleAutofocus(\n () => document.getElementById(`action-form-${action.name}`),\n form.state.components,\n )\n }, [refocus, form, action.name])\n\n // Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n // (`fillData()`).\n useEffect(\n () => (form ? runner.registerModalForm(action, context, form) : undefined),\n [runner, form, action, context],\n )\n\n // `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\n // A callback ref: the portal renders the content after the first commit.\n const [content, setContent] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (hidden) return\n\n const overlay = content?.previousElementSibling\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay')) {\n applyAttributes(overlay, schema.overlayAttributes)\n }\n }, [schema.overlayAttributes, hidden, content])\n\n // Live updates replace the form's components (fields shown, options changed).\n const formState = useStore<FormState | null>(form?.store ?? NO_FORM)\n\n // A wizard form reports its step, so the submit button waits for the last one.\n const progressStore = useMemo(() => (form ? wizardProgressStore(form) : null), [form])\n const progress = useStore(progressStore ?? NO_PROGRESS)\n const submitVisible = isModalSubmitVisible(schema, progressStore ? progress : null)\n\n const [password, setPassword] = useState('')\n const passwordId = useId()\n const passwordError = modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0]\n\n const submit = (createAnother = false) => {\n submittedRef.current = true\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.passwordConfirmation ? { password } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n }\n\n const footerContext = useMemo(() => nestedActionContext(context, action), [context, action])\n\n // Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\n const close = () => runner.cancelModal(action.name)\n const unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n // Cancel, Escape, clicking away and the close button ask first when the form changed.\n const requestClose = () => {\n if (confirmModalClose(schema, form, unsavedMessage)) close()\n }\n\n useEffect(() => guardModalUnload(schema, form, unsavedMessage), [schema, form, unsavedMessage])\n const title = schema.heading ?? action.label ?? t('actions', {}, 'Actions')\n\n const header = (\n <div\n className={cn(\n 'flex gap-4',\n schema.alignment === 'center' && 'flex-col items-center text-center',\n )}\n >\n {schema.icon && (\n <div\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )}\n >\n <Icon name={schema.icon} className=\"size-5\" />\n </div>\n )}\n <div className=\"flex flex-col gap-1.5\">\n {/* Every dialog needs an accessible name: without a heading it stays visually hidden. */}\n {schema.slideOver ? (\n <SheetTitle className={cn(!schema.heading && 'sr-only')}>{title}</SheetTitle>\n ) : (\n <DialogTitle className={cn(!schema.heading && 'sr-only')}>{title}</DialogTitle>\n )}\n {schema.description ? (\n schema.slideOver ? (\n <SheetDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n ) : (\n <DialogDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n )\n ) : schema.slideOver ? (\n <SheetDescription className=\"sr-only\">{title}</SheetDescription>\n ) : (\n <DialogDescription className=\"sr-only\">{title}</DialogDescription>\n )}\n </div>\n </div>\n )\n\n const body = (\n <div className=\"flex flex-col gap-6\">\n {schema.content && <SchemaRenderer components={schema.content} />}\n {form && <Slot name=\"modal.schema.before\" data={{ action: action.name }} />}\n {form && (\n <FormProvider store={form}>\n <form\n id={`action-form-${action.name}`}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n >\n <SchemaRenderer\n components={formState?.components ?? form.state.components}\n columns={formState?.columns ?? form.state.columns}\n />\n </form>\n </FormProvider>\n )}\n {form && <Slot name=\"modal.schema.after\" data={{ action: action.name }} />}\n {schema.contentFooter && <SchemaRenderer components={schema.contentFooter} />}\n {schema.passwordConfirmation && (\n <div data-slot=\"password-confirmation\" className=\"flex flex-col gap-2\">\n <Label htmlFor={passwordId}>{t('current_password', {}, 'Current password')}</Label>\n <Input\n id={passwordId}\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n aria-invalid={passwordError ? true : undefined}\n aria-describedby={passwordError ? `${passwordId}-error` : `${passwordId}-hint`}\n onChange={(event) => setPassword(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n submit()\n }\n }}\n />\n {passwordError ? (\n <p id={`${passwordId}-error`} className=\"text-destructive text-sm\">\n {passwordError}\n </p>\n ) : (\n <p id={`${passwordId}-hint`} className=\"text-muted-foreground text-sm\">\n {t('confirm_password', {}, 'Confirm your password to continue')}\n </p>\n )}\n </div>\n )}\n </div>\n )\n\n const footerAlignment = modalFooterAlignment(schema)\n const footer = (schema.submitLabel ||\n schema.cancelLabel ||\n schema.extraFooterActions?.length) && (\n <div\n className={cn(\n 'flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center',\n FOOTER_JUSTIFY[footerAlignment],\n )}\n >\n {schema.extraFooterActions && (\n <ActionList\n actions={schema.extraFooterActions}\n context={footerContext}\n className={footerAlignment === 'end' ? 'sm:me-auto' : undefined}\n />\n )}\n {schema.cancelLabel && (\n <Button\n type=\"button\"\n variant=\"outline\"\n className={\n schema.cancelColor\n ? colorClass(outlineButtonColorClasses, schema.cancelColor)\n : undefined\n }\n onClick={requestClose}\n disabled={modal.processing}\n >\n {schema.cancelLabel}\n </Button>\n )}\n {schema.createAnotherLabel && schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n className={\n schema.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.createAnotherColor)\n : undefined\n }\n onClick={() => submit(true)}\n disabled={modal.processing}\n >\n {schema.createAnotherIcon && <Icon name={schema.createAnotherIcon} />}\n {schema.createAnotherLabel}\n </Button>\n )}\n {schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n className={colorClass(solidButtonColorClasses, schema.submitColor, 'primary')}\n onClick={() => submit()}\n disabled={modal.processing}\n autoFocus={schema.autofocus && !form}\n >\n {modal.processing ? <Spinner /> : schema.submitIcon && <Icon name={schema.submitIcon} />}\n {schema.submitLabel}\n </Button>\n )}\n </div>\n )\n\n const onOpenChange = (open: boolean) => {\n if (!open && !modal.processing) {\n requestClose()\n }\n }\n\n // `modalClickThrough()`: a non-modal dialog has no backdrop and leaves the page usable.\n const isModal = !schema.clickThrough\n\n if (schema.slideOver) {\n return (\n <Sheet open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <SheetContent\n ref={setContent}\n side={slideOverSide(schema.slideOverPosition, direction)}\n {...extraProps(\n schema.windowAttributes,\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <SheetHeader className=\"border-b\">{header}</SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n {footer && (\n <SheetFooter\n className={cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')}\n >\n {footer}\n </SheetFooter>\n )}\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <DialogContent\n ref={setContent}\n {...extraProps(\n schema.windowAttributes,\n cn('max-h-[calc(100vh-2rem)] overflow-y-auto', WIDTHS[schema.width] ?? 'sm:max-w-lg'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <DialogHeader\n className={cn(\n // Pinned to the top of the scrolling dialog, edge to edge.\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n )}\n >\n {header}\n </DialogHeader>\n {(schema.content || form || schema.contentFooter || schema.passwordConfirmation) && body}\n {footer && (\n <DialogFooter\n className={cn(\n // Pinned to the bottom of the scrolling dialog, edge to edge.\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )}\n >\n {footer}\n </DialogFooter>\n )}\n <span className=\"sr-only\">{t('close', {}, 'Close')}</span>\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"action-modals.js","names":[],"sources":["../../../src/components/actions/action-modals.tsx"],"sourcesContent":["import type { FormState, OpenModal } from '@alfakit/core'\nimport {\n actionFormComponent,\n actionFormParams,\n actionPath,\n confirmModalClose,\n formIdPrefix,\n FormStore,\n guardModalUnload,\n isModalHiddenByChild,\n isModalSubmitVisible,\n modalFooterAlignment,\n nestedActionContext,\n PASSWORD_CONFIRMATION_INPUT,\n scheduleAutofocus,\n slideOverSide,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport { useActionRunner, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { extraProps } from '../../lib/attributes'\nimport {\n colorClass,\n iconBackgroundColorClasses,\n outlineButtonColorClasses,\n solidButtonColorClasses,\n} from '../../lib/colors'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { FormProvider } from '../form/form-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Slot } from '../panel/slot'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Input } from '../ui/input'\nimport { Label } from '../ui/label'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetFooter,\n SheetHeader,\n SheetTitle,\n} from '../ui/sheet'\nimport { Spinner } from '../ui/spinner'\nimport { ActionList } from './action-list'\n\nconst WIDTHS: Record<string, string> = {\n '3xs': 'sm:max-w-3xs',\n '2xs': 'sm:max-w-2xs',\n xs: 'sm:max-w-xs',\n sm: 'sm:max-w-sm',\n md: 'sm:max-w-md',\n lg: 'sm:max-w-lg',\n xl: 'sm:max-w-xl',\n '2xl': 'sm:max-w-2xl',\n '3xl': 'sm:max-w-3xl',\n '4xl': 'sm:max-w-4xl',\n '5xl': 'sm:max-w-5xl',\n '6xl': 'sm:max-w-6xl',\n '7xl': 'sm:max-w-7xl',\n none: 'sm:max-w-none',\n min: 'sm:max-w-min',\n max: 'sm:max-w-max',\n fit: 'sm:max-w-fit',\n prose: 'sm:max-w-prose',\n container: 'sm:container',\n 'screen-sm': 'sm:max-w-(--breakpoint-sm)',\n 'screen-md': 'sm:max-w-(--breakpoint-md)',\n 'screen-lg': 'sm:max-w-(--breakpoint-lg)',\n 'screen-xl': 'sm:max-w-(--breakpoint-xl)',\n 'screen-2xl': 'sm:max-w-(--breakpoint-2xl)',\n full: 'sm:max-w-[calc(100%-2rem)]',\n screen: 'sm:max-w-[calc(100%-2rem)] h-[calc(100%-2rem)]',\n}\n\n// Module-level so the snapshot stays stable (a new object per read re-renders forever).\nconst EMPTY_PROGRESS = { step: 0, count: 0 }\nconst NO_PROGRESS = { get: () => EMPTY_PROGRESS, subscribe: () => () => {} }\nconst NO_FORM = { get: () => null, subscribe: () => () => {} }\n\n/** Renders every open action modal from the runner store. Mount once per app. */\nexport function ActionModals() {\n const runner = useActionRunner()\n const { modals } = useStore(runner.store)\n\n // Notifications from JSON responses (a modal `mountUsing()` cancelled, field actions).\n useEffect(() => {\n runner.setNotificationHandler((notifications) => notifications.forEach((n) => showToast(n)))\n\n return () => runner.setNotificationHandler(undefined)\n }, [runner])\n\n return (\n <>\n {modals.map((modal, index) => (\n <ActionModal\n key={`${actionPath(modal.action, modal.context)}-${modal.context.record ?? ''}`}\n modal={modal}\n hidden={isModalHiddenByChild(modals, index)}\n />\n ))}\n </>\n )\n}\n\nconst FOOTER_JUSTIFY: Record<string, string> = {\n start: 'sm:justify-start',\n center: 'sm:justify-center',\n end: 'sm:justify-end',\n between: 'sm:justify-between',\n}\n\n/**\n * Put PHP attributes (`extraModalOverlayAttributes()`) on an element the renderer doesn't\n * create itself (the dialog's backdrop). `class` is added to the element's classes.\n */\nfunction applyAttributes(\n element: Element | null | undefined,\n attributes: Record<string, unknown> | undefined,\n) {\n if (!element || !attributes) return\n\n for (const [name, value] of Object.entries(attributes)) {\n if (value === null || value === undefined || value === false) continue\n\n if (name === 'class' || name === 'className') {\n element.classList.add(...String(value).split(/\\s+/).filter(Boolean))\n } else {\n element.setAttribute(name, value === true ? '' : String(value))\n }\n }\n}\n\nfunction ActionModal({ modal, hidden = false }: { modal: OpenModal; hidden?: boolean }) {\n const runner = useActionRunner()\n const panel = usePanel()\n const direction = useDirection()\n const t = useT()\n const { action, context, modal: schema } = modal\n const submittedRef = useRef(false)\n\n const form = useMemo(() => {\n if (!schema.form) {\n return null\n }\n\n return new FormStore(schema.form, {\n token: context.token,\n component: actionFormComponent(action, context),\n // Own field ids: the modal opens over a form whose fields may share names.\n idPrefix: formIdPrefix(actionFormComponent(action, context)),\n record: context.record ?? null,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n params: actionFormParams(context),\n })\n }, [schema.form, context, action, panel.endpoints])\n\n useEffect(() => {\n form?.setErrors(modal.errors)\n }, [form, modal.errors])\n\n useEffect(() => () => form?.destroy(), [form])\n\n // \"Create & create another\": back to the `autofocus()` field of the fresh form.\n const refocus = modal.refocus\n\n useEffect(() => {\n if (!refocus || !form) return\n\n return scheduleAutofocus(\n () => document.getElementById(`action-form-${action.name}`),\n form.state.components,\n )\n }, [refocus, form, action.name])\n\n // Nested actions read this form (`$mountedActions`, `getRawData()`) and write into it\n // (`fillData()`).\n useEffect(\n () => (form ? runner.registerModalForm(action, context, form) : undefined),\n [runner, form, action, context],\n )\n\n // `extraModalOverlayAttributes()`: the backdrop is the content's previous sibling.\n // A callback ref: the portal renders the content after the first commit.\n const [content, setContent] = useState<HTMLDivElement | null>(null)\n\n useEffect(() => {\n if (hidden) return\n\n const overlay = content?.previousElementSibling\n\n if (overlay?.getAttribute('data-slot')?.endsWith('-overlay')) {\n applyAttributes(overlay, schema.overlayAttributes)\n }\n }, [schema.overlayAttributes, hidden, content])\n\n // Live updates replace the form's components (fields shown, options changed).\n const formState = useStore<FormState | null>(form?.store ?? NO_FORM)\n\n // A wizard form reports its step, so the submit button waits for the last one.\n const progressStore = useMemo(() => (form ? wizardProgressStore(form) : null), [form])\n const progress = useStore(progressStore ?? NO_PROGRESS)\n const submitVisible = isModalSubmitVisible(schema, progressStore ? progress : null)\n\n const [password, setPassword] = useState('')\n const passwordId = useId()\n const passwordError = modal.errors[PASSWORD_CONFIRMATION_INPUT]?.[0]\n\n const submit = (createAnother = false) => {\n submittedRef.current = true\n const values = form?.state.values ?? {}\n const options = {\n ...(schema.passwordConfirmation ? { password } : {}),\n ...(createAnother ? { createAnother: true } : {}),\n }\n\n if (Object.keys(options).length > 0) {\n runner.execute(action, context, values, options)\n } else {\n runner.execute(action, context, values)\n }\n }\n\n const footerContext = useMemo(() => nestedActionContext(context, action), [context, action])\n\n // Dismissing the modal (not submitting it): `cancelParentActionsOnClose()` applies.\n const close = () => runner.cancelModal(action.name)\n const unsavedMessage = t('unsaved_changes_close', {}, 'You have unsaved changes. Close anyway?')\n // Cancel, Escape, clicking away and the close button ask first when the form changed.\n const requestClose = () => {\n if (confirmModalClose(schema, form, unsavedMessage)) close()\n }\n\n useEffect(() => guardModalUnload(schema, form, unsavedMessage), [schema, form, unsavedMessage])\n const title = schema.heading ?? action.label ?? t('actions', {}, 'Actions')\n\n const header = (\n <div\n className={cn(\n 'flex gap-4',\n schema.alignment === 'center' && 'flex-col items-center text-center',\n )}\n >\n {schema.icon && (\n <div\n className={cn(\n 'flex size-10 shrink-0 items-center justify-center rounded-full',\n colorClass(iconBackgroundColorClasses, schema.iconColor, 'primary'),\n )}\n >\n <Icon name={schema.icon} className=\"size-5\" />\n </div>\n )}\n <div className=\"flex flex-col gap-1.5\">\n {/* Every dialog needs an accessible name: without a heading it stays visually hidden. */}\n {schema.slideOver ? (\n <SheetTitle className={cn(!schema.heading && 'sr-only')}>{title}</SheetTitle>\n ) : (\n <DialogTitle className={cn(!schema.heading && 'sr-only')}>{title}</DialogTitle>\n )}\n {schema.description ? (\n schema.slideOver ? (\n <SheetDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n ) : (\n <DialogDescription dangerouslySetInnerHTML={{ __html: schema.description }} />\n )\n ) : schema.slideOver ? (\n <SheetDescription className=\"sr-only\">{title}</SheetDescription>\n ) : (\n <DialogDescription className=\"sr-only\">{title}</DialogDescription>\n )}\n </div>\n </div>\n )\n\n const body = (\n <div className=\"flex flex-col gap-6\">\n {schema.content && <SchemaRenderer components={schema.content} />}\n {form && <Slot name=\"modal.schema.before\" data={{ action: action.name }} />}\n {form && (\n <FormProvider store={form}>\n <form\n id={`action-form-${action.name}`}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n >\n <SchemaRenderer\n components={formState?.components ?? form.state.components}\n columns={formState?.columns ?? form.state.columns}\n />\n </form>\n </FormProvider>\n )}\n {form && <Slot name=\"modal.schema.after\" data={{ action: action.name }} />}\n {schema.contentFooter && <SchemaRenderer components={schema.contentFooter} />}\n {schema.passwordConfirmation && (\n <div data-slot=\"password-confirmation\" className=\"flex flex-col gap-2\">\n <Label htmlFor={passwordId}>{t('current_password', {}, 'Current password')}</Label>\n <Input\n id={passwordId}\n type=\"password\"\n autoComplete=\"current-password\"\n value={password}\n aria-invalid={passwordError ? true : undefined}\n aria-describedby={passwordError ? `${passwordId}-error` : `${passwordId}-hint`}\n onChange={(event) => setPassword(event.target.value)}\n onKeyDown={(event) => {\n if (event.key === 'Enter') {\n event.preventDefault()\n submit()\n }\n }}\n />\n {passwordError ? (\n <p id={`${passwordId}-error`} className=\"text-destructive text-sm\">\n {passwordError}\n </p>\n ) : (\n <p id={`${passwordId}-hint`} className=\"text-muted-foreground text-sm\">\n {t('confirm_password', {}, 'Confirm your password to continue')}\n </p>\n )}\n </div>\n )}\n </div>\n )\n\n const footerAlignment = modalFooterAlignment(schema)\n const footer = (schema.submitLabel ||\n schema.cancelLabel ||\n schema.extraFooterActions?.length) && (\n <div\n className={cn(\n 'flex w-full flex-col-reverse gap-2 sm:flex-row sm:items-center',\n FOOTER_JUSTIFY[footerAlignment],\n )}\n >\n {schema.extraFooterActions && (\n <ActionList\n actions={schema.extraFooterActions}\n context={footerContext}\n className={footerAlignment === 'end' ? 'sm:me-auto' : undefined}\n />\n )}\n {schema.cancelLabel && (\n <Button\n type=\"button\"\n variant=\"outline\"\n className={\n schema.cancelColor\n ? colorClass(outlineButtonColorClasses, schema.cancelColor)\n : undefined\n }\n onClick={requestClose}\n disabled={modal.processing}\n >\n {schema.cancelLabel}\n </Button>\n )}\n {schema.createAnotherLabel && schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n variant=\"outline\"\n data-slot=\"create-another\"\n className={\n schema.createAnotherColor\n ? colorClass(outlineButtonColorClasses, schema.createAnotherColor)\n : undefined\n }\n onClick={() => submit(true)}\n disabled={modal.processing}\n >\n {schema.createAnotherIcon && <Icon name={schema.createAnotherIcon} />}\n {schema.createAnotherLabel}\n </Button>\n )}\n {schema.submitLabel && submitVisible && (\n <Button\n type=\"button\"\n className={colorClass(solidButtonColorClasses, schema.submitColor, 'primary')}\n onClick={() => submit()}\n disabled={modal.processing}\n autoFocus={schema.autofocus && !form}\n >\n {modal.processing ? <Spinner /> : schema.submitIcon && <Icon name={schema.submitIcon} />}\n {schema.submitLabel}\n </Button>\n )}\n </div>\n )\n\n const onOpenChange = (open: boolean) => {\n if (!open && !modal.processing) {\n requestClose()\n }\n }\n\n // `modalClickThrough()`: a non-modal dialog has no backdrop and leaves the page usable.\n const isModal = !schema.clickThrough\n\n if (schema.slideOver) {\n return (\n <Sheet open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <SheetContent\n ref={setContent}\n side={slideOverSide(schema.slideOverPosition, direction)}\n {...extraProps(\n schema.windowAttributes,\n cn('flex w-full flex-col gap-0 p-0', WIDTHS[schema.width] ?? 'sm:max-w-md'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <SheetHeader className=\"border-b\">{header}</SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n {footer && (\n <SheetFooter\n className={cn('border-t', schema.stickyFooter && 'bg-background sticky bottom-0')}\n >\n {footer}\n </SheetFooter>\n )}\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={!hidden} modal={isModal} onOpenChange={onOpenChange}>\n <DialogContent\n ref={setContent}\n {...extraProps(\n schema.windowAttributes,\n cn('max-h-[calc(100vh-2rem)] overflow-y-auto', WIDTHS[schema.width] ?? 'sm:max-w-lg'),\n )}\n showCloseButton={schema.closeButton}\n onInteractOutside={(event) => !schema.closeByClickingAway && event.preventDefault()}\n onEscapeKeyDown={(event) => !schema.closeByEscaping && event.preventDefault()}\n >\n <DialogHeader\n className={cn(\n // Pinned to the top of the scrolling dialog, edge to edge.\n schema.stickyHeader &&\n 'bg-background sticky -top-6 z-10 -mx-6 -mt-6 rounded-t-lg border-b px-6 pt-6 pb-4',\n )}\n >\n {header}\n </DialogHeader>\n {(schema.content || form || schema.contentFooter || schema.passwordConfirmation) && body}\n {footer && (\n <DialogFooter\n className={cn(\n // Pinned to the bottom of the scrolling dialog, edge to edge.\n schema.stickyFooter &&\n 'bg-background sticky bottom-0 -mx-6 -mb-6 rounded-b-lg border-t px-6 py-4',\n )}\n >\n {footer}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n )\n}\n"],"mappings":"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"}
@@ -33,6 +33,7 @@ function ColorPickerField({ component }) {
33
33
  }), /* @__PURE__ */ jsx(Input, {
34
34
  id,
35
35
  name: component.name,
36
+ dir: "ltr",
36
37
  value: value ?? "",
37
38
  onChange: (event) => setValue(event.target.value),
38
39
  onBlur: blur,
@@ -1 +1 @@
1
- {"version":3,"file":"color-picker.js","names":[],"sources":["../../../src/components/fields/color-picker.tsx"],"sourcesContent":["import type { ColorPickerSchema } from '@alfakit/core'\nimport { colorAlpha, colorToHex, formatColor, withColorAlpha } from '@alfakit/core'\nimport { useFormatter, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Input } from '../ui/input'\n\nexport function ColorPickerField({ component }: { component: ColorPickerSchema }) {\n const t = useT()\n const format = useFormatter()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<string | null>(\n component,\n )\n const isHex = component.format === 'hex'\n // `rgba()`: the native swatch has no alpha, so a slider sets the opacity.\n const alpha = Math.round(colorAlpha(value) * 100)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div className=\"flex items-center gap-2\">\n <label\n className=\"border-input relative size-9 shrink-0 cursor-pointer overflow-hidden rounded-md border shadow-xs\"\n style={{ backgroundColor: value ?? 'transparent' }}\n aria-label={t('form.pick_color', {}, 'Pick a colour')}\n >\n <input\n type=\"color\"\n value={colorToHex(value) ?? '#000000'}\n onChange={(event) => setValue(formatColor(event.target.value, component.format, value))}\n onBlur={blur}\n disabled={disabled || readOnly}\n className=\"absolute inset-0 size-full cursor-pointer opacity-0\"\n />\n </label>\n <Input\n id={id}\n name={component.name}\n value={value ?? ''}\n onChange={(event) => setValue(event.target.value)}\n onBlur={blur}\n placeholder={component.placeholder ?? (isHex ? '#000000' : component.format)}\n disabled={disabled}\n readOnly={readOnly}\n {...aria}\n\n {...extraProps(component.extraInputAttributes, 'font-mono')}\n />\n </div>\n {component.format === 'rgba' && (\n <div data-slot=\"color-picker-alpha\" className=\"mt-2 flex items-center gap-2\">\n <input\n type=\"range\"\n min={0}\n max={100}\n step={1}\n value={alpha}\n onChange={(event) => setValue(withColorAlpha(value, Number(event.target.value) / 100))}\n onBlur={blur}\n disabled={disabled || readOnly}\n aria-label={t('form.color_opacity', {}, 'Opacity')}\n aria-valuetext={`${alpha}%`}\n className=\"accent-primary h-2 flex-1 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50\"\n />\n <span className=\"text-muted-foreground w-10 text-end text-xs tabular-nums\">\n {format.percent(alpha / 100)}\n </span>\n </div>\n )}\n </FieldWrapper>\n )\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,iBAAiB,EAAE,aAA+C;CAChF,MAAM,IAAI,KAAK;CACf,MAAM,SAAS,aAAa;CAC5B,MAAM,EAAE,OAAO,UAAU,MAAM,OAAO,IAAI,UAAU,UAAU,SAAS,SACrE,SACF;CACA,MAAM,QAAQ,UAAU,WAAW;CAEnC,MAAM,QAAQ,KAAK,MAAM,WAAW,KAAK,IAAI,GAAG;CAEhD,OACE,qBAAC,cAAD;EAAc,OAAO;EAAW,SAAS;EAAW;EAApD,UAAA,CACE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,oBAAC,SAAD;IACE,WAAU;IACV,OAAO,EAAE,iBAAiB,SAAS,cAAc;IACjD,cAAY,EAAE,mBAAmB,CAAC,GAAG,eAAe;IAEpD,UAAA,oBAAC,SAAD;KACE,MAAK;KACL,OAAO,WAAW,KAAK,KAAK;KAC5B,WAAW,UAAU,SAAS,YAAY,MAAM,OAAO,OAAO,UAAU,QAAQ,KAAK,CAAC;KACtF,QAAQ;KACR,UAAU,YAAY;KACtB,WAAU;IACX,CAAA;GACI,CAAA,GACP,oBAAC,OAAD;IACM;IACJ,MAAM,UAAU;IAChB,OAAO,SAAS;IAChB,WAAW,UAAU,SAAS,MAAM,OAAO,KAAK;IAChD,QAAQ;IACR,aAAa,UAAU,gBAAgB,QAAQ,YAAY,UAAU;IAC3D;IACA;IACV,GAAI;IAEJ,GAAI,WAAW,UAAU,sBAAsB,WAAW;GAC3D,CAAA,CACE;EACJ,CAAA,GAAA,UAAU,WAAW,UACpB,qBAAC,OAAD;GAAK,aAAU;GAAqB,WAAU;GAA9C,UAAA,CACE,oBAAC,SAAD;IACE,MAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,OAAO;IACP,WAAW,UAAU,SAAS,eAAe,OAAO,OAAO,MAAM,OAAO,KAAK,IAAI,GAAG,CAAC;IACrF,QAAQ;IACR,UAAU,YAAY;IACtB,cAAY,EAAE,sBAAsB,CAAC,GAAG,SAAS;IACjD,kBAAgB,GAAG,MAAM;IACzB,WAAU;GACX,CAAA,GACD,oBAAC,QAAD;IAAM,WAAU;IACb,UAAA,OAAO,QAAQ,QAAQ,GAAG;GACvB,CAAA,CACH;EAEK,CAAA,CAAA;;AAElB"}
1
+ {"version":3,"file":"color-picker.js","names":[],"sources":["../../../src/components/fields/color-picker.tsx"],"sourcesContent":["import type { ColorPickerSchema } from '@alfakit/core'\nimport { colorAlpha, colorToHex, formatColor, withColorAlpha } from '@alfakit/core'\nimport { useFormatter, useT } from '../../context'\nimport { extraProps } from '../../lib/attributes'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Input } from '../ui/input'\n\nexport function ColorPickerField({ component }: { component: ColorPickerSchema }) {\n const t = useT()\n const format = useFormatter()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } = useField<string | null>(\n component,\n )\n const isHex = component.format === 'hex'\n // `rgba()`: the native swatch has no alpha, so a slider sets the opacity.\n const alpha = Math.round(colorAlpha(value) * 100)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div className=\"flex items-center gap-2\">\n <label\n className=\"border-input relative size-9 shrink-0 cursor-pointer overflow-hidden rounded-md border shadow-xs\"\n style={{ backgroundColor: value ?? 'transparent' }}\n aria-label={t('form.pick_color', {}, 'Pick a colour')}\n >\n <input\n type=\"color\"\n value={colorToHex(value) ?? '#000000'}\n onChange={(event) => setValue(formatColor(event.target.value, component.format, value))}\n onBlur={blur}\n disabled={disabled || readOnly}\n className=\"absolute inset-0 size-full cursor-pointer opacity-0\"\n />\n </label>\n <Input\n id={id}\n name={component.name}\n dir=\"ltr\"\n value={value ?? ''}\n onChange={(event) => setValue(event.target.value)}\n onBlur={blur}\n placeholder={component.placeholder ?? (isHex ? '#000000' : component.format)}\n disabled={disabled}\n readOnly={readOnly}\n {...aria}\n\n {...extraProps(component.extraInputAttributes, 'font-mono')}\n />\n </div>\n {component.format === 'rgba' && (\n <div data-slot=\"color-picker-alpha\" className=\"mt-2 flex items-center gap-2\">\n <input\n type=\"range\"\n min={0}\n max={100}\n step={1}\n value={alpha}\n onChange={(event) => setValue(withColorAlpha(value, Number(event.target.value) / 100))}\n onBlur={blur}\n disabled={disabled || readOnly}\n aria-label={t('form.color_opacity', {}, 'Opacity')}\n aria-valuetext={`${alpha}%`}\n className=\"accent-primary h-2 flex-1 cursor-pointer disabled:cursor-not-allowed disabled:opacity-50\"\n />\n <span className=\"text-muted-foreground w-10 text-end text-xs tabular-nums\">\n {format.percent(alpha / 100)}\n </span>\n </div>\n )}\n </FieldWrapper>\n )\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,iBAAiB,EAAE,aAA+C;CAChF,MAAM,IAAI,KAAK;CACf,MAAM,SAAS,aAAa;CAC5B,MAAM,EAAE,OAAO,UAAU,MAAM,OAAO,IAAI,UAAU,UAAU,SAAS,SACrE,SACF;CACA,MAAM,QAAQ,UAAU,WAAW;CAEnC,MAAM,QAAQ,KAAK,MAAM,WAAW,KAAK,IAAI,GAAG;CAEhD,OACE,qBAAC,cAAD;EAAc,OAAO;EAAW,SAAS;EAAW;EAApD,UAAA,CACE,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA,CACE,oBAAC,SAAD;IACE,WAAU;IACV,OAAO,EAAE,iBAAiB,SAAS,cAAc;IACjD,cAAY,EAAE,mBAAmB,CAAC,GAAG,eAAe;IAEpD,UAAA,oBAAC,SAAD;KACE,MAAK;KACL,OAAO,WAAW,KAAK,KAAK;KAC5B,WAAW,UAAU,SAAS,YAAY,MAAM,OAAO,OAAO,UAAU,QAAQ,KAAK,CAAC;KACtF,QAAQ;KACR,UAAU,YAAY;KACtB,WAAU;IACX,CAAA;GACI,CAAA,GACP,oBAAC,OAAD;IACM;IACJ,MAAM,UAAU;IAChB,KAAI;IACJ,OAAO,SAAS;IAChB,WAAW,UAAU,SAAS,MAAM,OAAO,KAAK;IAChD,QAAQ;IACR,aAAa,UAAU,gBAAgB,QAAQ,YAAY,UAAU;IAC3D;IACA;IACV,GAAI;IAEJ,GAAI,WAAW,UAAU,sBAAsB,WAAW;GAC3D,CAAA,CACE;EACJ,CAAA,GAAA,UAAU,WAAW,UACpB,qBAAC,OAAD;GAAK,aAAU;GAAqB,WAAU;GAA9C,UAAA,CACE,oBAAC,SAAD;IACE,MAAK;IACL,KAAK;IACL,KAAK;IACL,MAAM;IACN,OAAO;IACP,WAAW,UAAU,SAAS,eAAe,OAAO,OAAO,MAAM,OAAO,KAAK,IAAI,GAAG,CAAC;IACrF,QAAQ;IACR,UAAU,YAAY;IACtB,cAAY,EAAE,sBAAsB,CAAC,GAAG,SAAS;IACjD,kBAAgB,GAAG,MAAM;IACzB,WAAU;GACX,CAAA,GACD,oBAAC,QAAD;IAAM,WAAU;IACb,UAAA,OAAO,QAAQ,QAAQ,GAAG;GACvB,CAAA,CACH;EAEK,CAAA,CAAA;;AAElB"}
@@ -1,4 +1,4 @@
1
- import { useChoice, usePanel, useT } from "../../context.js";
1
+ import { useChoice, useFormatter, usePanel, useT } from "../../context.js";
2
2
  import { cn } from "../../lib/utils.js";
3
3
  import { Button } from "../ui/button.js";
4
4
  import { useStore } from "../../hooks/use-store.js";
@@ -6,13 +6,14 @@ 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, fileUploadAccept, fileUploadPaths, filesFromClipboard, formatBytes, insertUploadedFile, moveRepeaterItem, panelUploadOptions, requestJson, transformImage, uploadTemporaryFile, uploadedFileLinks } from "@alfakit/core";
9
+ import { canEditImage, concurrencyLimiter, cssAspectRatio, editableImageName, fetchEditableImage, fieldMemory, fileManagerSize, fileUploadAccept, 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";
13
13
  //#region src/components/fields/file-upload.tsx
14
14
  function FileUploadField({ component }) {
15
15
  const t = useT();
16
+ const formatter = useFormatter();
16
17
  const choice = useChoice();
17
18
  const panel = usePanel();
18
19
  const { value, setValue, error, id, disabled, readOnly, aria } = useField(component);
@@ -208,7 +209,7 @@ function FileUploadField({ component }) {
208
209
  variant: circle ? "secondary" : "ghost",
209
210
  size: "icon-xs",
210
211
  className: cn(circle && "rounded-full shadow"),
211
- "aria-label": t("remove", {}, "Remove"),
212
+ "aria-label": t("file_upload.remove", { name: file.name }, "Remove :name"),
212
213
  onClick: () => {
213
214
  if (file.error || file.progress !== void 0) {
214
215
  releasePreview(file.preview);
@@ -263,7 +264,7 @@ function FileUploadField({ component }) {
263
264
  children: file.name
264
265
  }), /* @__PURE__ */ jsx("p", {
265
266
  className: cn("text-muted-foreground text-xs", file.missing && "text-destructive"),
266
- children: file.error ?? (file.missing ? t("form.file_missing", {}, "This file no longer exists.") : file.progress !== void 0 && !file.error ? component.uploadingMessage ?? t("form.uploading", {}, "Uploading…") : file.size ? formatBytes(file.size) : file.temporary ? t("form.uploading", {}, "Pending") : "")
267
+ children: file.error ?? (file.missing ? t("form.file_missing", {}, "This file no longer exists.") : file.progress !== void 0 && !file.error ? component.uploadingMessage ?? t("form.uploading", {}, "Uploading…") : file.size ? fileManagerSize(file.size, formatter) : file.temporary ? t("form.uploading", {}, "Pending") : "")
267
268
  })]
268
269
  }),
269
270
  /* @__PURE__ */ jsxs("div", {
@@ -274,7 +275,7 @@ function FileUploadField({ component }) {
274
275
  type: "button",
275
276
  variant: "ghost",
276
277
  size: "icon-xs",
277
- "aria-label": t("form.move_up", {}, "Move up"),
278
+ "aria-label": t("file_upload.move_up", { name: file.name }, "Move :name up"),
278
279
  disabled: index === 0,
279
280
  onClick: () => move(index, index - 1),
280
281
  children: /* @__PURE__ */ jsx(ChevronUpIcon, {})
@@ -282,7 +283,7 @@ function FileUploadField({ component }) {
282
283
  type: "button",
283
284
  variant: "ghost",
284
285
  size: "icon-xs",
285
- "aria-label": t("form.move_down", {}, "Move down"),
286
+ "aria-label": t("file_upload.move_down", { name: file.name }, "Move :name down"),
286
287
  disabled: index === files.length - 1,
287
288
  onClick: () => move(index, index + 1),
288
289
  children: /* @__PURE__ */ jsx(ChevronDownIcon, {})
@@ -292,7 +293,7 @@ function FileUploadField({ component }) {
292
293
  variant: circle ? "secondary" : "ghost",
293
294
  size: "icon-xs",
294
295
  className: cn(circle && "rounded-full shadow"),
295
- "aria-label": t("image_editor.title", {}, "Edit image"),
296
+ "aria-label": t("file_upload.edit", { name: file.name }, "Edit :name"),
296
297
  title: t("image_editor.title", {}, "Edit image"),
297
298
  "data-action": "edit-image",
298
299
  onClick: () => void editExisting(file),
@@ -307,7 +308,7 @@ function FileUploadField({ component }) {
307
308
  href: uploadedFileLinks(file).open,
308
309
  target: "_blank",
309
310
  rel: "noopener noreferrer",
310
- "aria-label": `${t("open", {}, "Open")} ${t("opens_in_new_tab", {}, "(opens in a new tab)")}`,
311
+ "aria-label": `${t("file_upload.open", { name: file.name }, "Open :name")} ${t("opens_in_new_tab", {}, "(opens in a new tab)")}`,
311
312
  children: /* @__PURE__ */ jsx(ExternalLinkIcon, {})
312
313
  })
313
314
  }),
@@ -319,7 +320,7 @@ function FileUploadField({ component }) {
319
320
  children: /* @__PURE__ */ jsx("a", {
320
321
  href: uploadedFileLinks(file).download,
321
322
  download: true,
322
- "aria-label": t("download", {}, "Download"),
323
+ "aria-label": t("file_upload.download", { name: file.name }, "Download :name"),
323
324
  children: /* @__PURE__ */ jsx(DownloadIcon, {})
324
325
  })
325
326
  }),
@@ -357,7 +358,7 @@ function FileUploadField({ component }) {
357
358
  className: "text-muted-foreground text-xs",
358
359
  children: [
359
360
  component.acceptedFileTypes?.join(", "),
360
- component.maxSize ? `≤ ${formatBytes(component.maxSize * 1024, 0)}` : null,
361
+ component.maxSize ? `≤ ${fileManagerSize(component.maxSize * 1024, formatter)}` : null,
361
362
  component.minFiles ? choice("form.min_files", component.minFiles, {}, "At least :count file|At least :count files") : null
362
363
  ].filter(Boolean).join(" · ")
363
364
  })
@@ -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 filesFromClipboard,\n fileUploadAccept,\n fileUploadPaths,\n formatBytes,\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, 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 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('remove', {}, 'Remove')}\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 ? formatBytes(file.size)\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('form.move_up', {}, 'Move 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('form.move_down', {}, 'Move down')}\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('image_editor.title', {}, 'Edit image')}\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('open', {}, 'Open')} ${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('download', {}, 'Download')}\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 ? `≤ ${formatBytes(component.maxSize * 1024, 0)}` : 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 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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"}