@alfakit/react 0.1.9 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/components/dev-tools/dev-tools-inspector.js +1 -1
- package/dist/components/dev-tools/dev-tools-inspector.js.map +1 -1
- package/dist/components/fields/checkbox-list.js +2 -2
- package/dist/components/fields/checkbox-list.js.map +1 -1
- package/dist/components/fields/rich-editor-extensions.js +14 -4
- package/dist/components/fields/rich-editor-extensions.js.map +1 -1
- package/dist/components/file-manager/file-manager.js +2 -0
- package/dist/components/file-manager/file-manager.js.map +1 -1
- package/dist/components/layout/elements.d.ts.map +1 -1
- package/dist/components/layout/elements.js +16 -5
- package/dist/components/layout/elements.js.map +1 -1
- package/dist/components/layout/mixed-content.d.ts +6 -4
- package/dist/components/layout/mixed-content.d.ts.map +1 -1
- package/dist/components/layout/mixed-content.js +6 -4
- package/dist/components/layout/mixed-content.js.map +1 -1
- package/dist/components/layout/section.js +4 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/notifications/notifications-menu.d.ts.map +1 -1
- package/dist/components/notifications/notifications-menu.js +45 -17
- package/dist/components/notifications/notifications-menu.js.map +1 -1
- package/dist/components/panel/shortcuts-overlay.js +13 -10
- package/dist/components/panel/shortcuts-overlay.js.map +1 -1
- package/dist/components/table/column-manager.js +4 -4
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/spreadsheet.js +8 -1
- package/dist/components/table/spreadsheet.js.map +1 -1
- package/dist/components/translation-manager/translation-manager.js +1 -1
- package/dist/components/translation-manager/translation-manager.js.map +1 -1
- package/dist/components/widgets/stats-overview.js +3 -3
- package/dist/components/widgets/stats-overview.js.map +1 -1
- package/dist/eject-manifest.json +2 -2
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +4 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.d.ts +0 -10
- package/dist/standalone/alfakit-component.d.ts.map +1 -1
- package/dist/standalone/alfakit-component.js +3 -2
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone/runtime.js +2 -1
- package/dist/standalone/runtime.js.map +1 -1
- package/dist/standalone.css +2 -1
- package/dist/starter-kit/navigation.d.ts +6 -1
- package/dist/starter-kit/navigation.d.ts.map +1 -1
- package/dist/starter-kit/navigation.js +3 -2
- package/dist/starter-kit/navigation.js.map +1 -1
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +1 -1
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/styles.css +2 -1
- package/dist/themes.css +4 -0
- package/package.json +2 -2
- package/src/components/dev-tools/dev-tools-inspector.tsx +1 -1
- package/src/components/fields/checkbox-list.tsx +2 -2
- package/src/components/fields/rich-editor-extensions.tsx +24 -2
- package/src/components/file-manager/file-manager.tsx +3 -1
- package/src/components/layout/elements.tsx +27 -4
- package/src/components/layout/mixed-content.tsx +8 -4
- package/src/components/layout/section.tsx +2 -2
- package/src/components/notifications/notifications-menu.tsx +42 -1
- package/src/components/panel/shortcuts-overlay.tsx +29 -24
- package/src/components/table/column-manager.tsx +6 -4
- package/src/components/table/spreadsheet.tsx +18 -7
- package/src/components/translation-manager/translation-manager.tsx +1 -1
- package/src/components/widgets/stats-overview.tsx +6 -3
- package/src/pages/page.tsx +7 -3
- package/src/standalone/alfakit-component.tsx +7 -2
- package/src/standalone/runtime.ts +3 -0
- package/src/starter-kit/navigation.ts +12 -1
- package/src/starter-kit/starter-kit.tsx +3 -2
- package/src/styles.css +2 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"column-manager.js","names":[],"sources":["../../../src/components/table/column-manager.tsx"],"sourcesContent":["import type { TableSchema } from '@alfakit/core'\nimport {\n columnDraftChanged,\n columnManagerPosition,\n columnOrderChanged,\n defaultVisibleColumns,\n FILTERS_MODAL_WIDTH_CLASSES,\n gridColumnsClasses,\n keepMoveButtonFocus,\n moveColumnInManager,\n moveColumnTo,\n orderColumnsBy,\n saveColumnOrder,\n slideOverSide,\n toggleVisibleColumn,\n triggerButtonView,\n visitTable,\n} from '@alfakit/core'\nimport { ChevronDownIcon, ChevronUpIcon, Columns3Icon, GripVerticalIcon } from 'lucide-react'\nimport { useId, useState, type ReactNode } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '../ui/dialog'\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from '../ui/sheet'\nimport { TableTriggerButton } from './trigger-button'\n\n/**\n * The column manager: show / hide toggleable columns (kept in the query string) and,\n * with `reorderableColumns()`, move columns (stored per user on the server) by dragging a\n * row onto another or with the move up / down buttons (the keyboard path).\n *\n * `deferColumnManager()` keeps toggles in a draft until \"Apply\"; `columnManagerLayout()`\n * opens it in a dialog; `columnManagerColumns()` lays the list out in a grid.\n */\nexport function ColumnManager({\n table,\n token,\n component,\n}: {\n table: TableSchema\n token: string\n component: string\n}) {\n const t = useT()\n const panel = usePanel()\n const direction = useDirection()\n const [open, setOpen] = useState(false)\n const [saving, setSaving] = useState(false)\n const [dragging, setDragging] = useState<string | null>(null)\n const [dropTarget, setDropTarget] = useState<string | null>(null)\n // Toggles of a deferred column manager wait here until \"Apply\".\n const [draft, setDraft] = useState<string[] | null>(null)\n // A moved column order: the draft of a deferred column manager, or shown at once\n // while it is saved.\n // It belongs to the columns it was made from: the server's next order replaces the\n // one shown while saving.\n const [orderState, setOrderState] = useState<{\n order: string[]\n columns: TableSchema['columns']\n } | null>(null)\n // Screen-reader announcement of the last move.\n const [announcement, setAnnouncement] = useState('')\n const idBase = useId()\n const reorderable = Boolean(table.reorderableColumns && panel.endpoints.tableColumns)\n const toggleable = Boolean(table.toggleableColumns)\n const deferred = Boolean(table.deferColumnManager)\n const orderDraft =\n orderState && (deferred || orderState.columns === table.columns) ? orderState.order : null\n const setOrderDraft = (order: string[] | null) =>\n setOrderState(order ? { order, columns: table.columns } : null)\n const appliedNames = table.columns.map((column) => column.name)\n const names = orderDraft ?? appliedNames\n const columns = orderColumnsBy(\n reorderable ? table.columns : table.columns.filter((column) => column.toggleable),\n orderDraft,\n )\n const visible = draft ?? table.visibleColumns\n\n if (!toggleable && !reorderable) return null\n\n const trigger = triggerButtonView(table, 'columnManager', {\n label: t('table.columns', {}, 'Columns'),\n showLabel: false,\n })\n\n if (!trigger) return null\n\n const toggle = (name: string) => {\n const next = toggleVisibleColumn(visible, name)\n\n if (deferred) {\n setDraft(next)\n } else {\n visitTable(table.identifier, { columns: next }, ['page'])\n }\n }\n\n const save = async (order: string[]): Promise<boolean> => {\n if (!panel.endpoints.tableColumns) return false\n\n setSaving(true)\n\n try {\n await saveColumnOrder(panel.endpoints.tableColumns, token, component, order)\n\n return true\n } catch (error) {\n showToast({\n id: 'alfakit-column-order-failed',\n title: t('table.column_order_failed', {}, \"Couldn't save the column order\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n\n return false\n } finally {\n setSaving(false)\n }\n }\n\n const announce = (name: string, order: string[]) => {\n const { position, count, group } = columnManagerPosition(order, name, table.columnGroups)\n const label = table.columns.find((column) => column.name === name)?.label ?? name\n const replace = { column: label, position, count, group: group ?? '' }\n\n setAnnouncement(\n group\n ? deferred\n ? t(\n 'table.column_manager.reordered_in_group_deferred',\n replace,\n `${label} moved to position ${position} of ${count} in ${group}. Apply columns to save your changes.`,\n )\n : t(\n 'table.column_manager.reordered_in_group',\n replace,\n `${label} moved to position ${position} of ${count} in ${group}.`,\n )\n : deferred\n ? t(\n 'table.column_manager.reordered_deferred',\n replace,\n `${label} moved to position ${position} of ${count}. Apply columns to save your changes.`,\n )\n : t(\n 'table.column_manager.reordered',\n replace,\n `${label} moved to position ${position} of ${count}.`,\n ),\n )\n }\n\n // A new order: kept as a draft until \"Apply\" (`deferColumnManager()`), else saved now.\n const reorder = (name: string, order: string[]) => {\n if (saving || !columnOrderChanged(names, order)) return\n\n setOrderDraft(order)\n setAnnouncement('')\n\n if (deferred) {\n announce(name, order)\n\n return\n }\n\n void save(order).then((saved) => (saved ? announce(name, order) : setOrderDraft(null)))\n }\n\n const move = (name: string, offset: -1 | 1, button: HTMLElement) => {\n keepMoveButtonFocus(button)\n reorder(name, moveColumnInManager(names, name, offset, table.columnGroups))\n }\n\n const orderChanged = Boolean(orderDraft && columnOrderChanged(appliedNames, orderDraft))\n\n // The order is saved before the visibility visit: both requests share the session, and a\n // visit running alongside the save would write the old order back when it finishes.\n const apply = async () => {\n const columns = draft && columnDraftChanged(table.visibleColumns, draft) ? draft : null\n\n setDraft(null)\n setOrderDraft(null)\n setAnnouncement('')\n setOpen(false)\n\n if (orderDraft && orderChanged) await save(orderDraft)\n\n if (columns) visitTable(table.identifier, { columns }, ['page'])\n }\n\n // Reset: the defined column order and, for toggleable columns, the default visibility.\n const reset = async () => {\n if (reorderable) {\n setOrderDraft(null)\n await save([])\n }\n\n if (toggleable) {\n setDraft(null)\n visitTable(table.identifier, { columns: defaultVisibleColumns(table) }, ['page'])\n }\n }\n\n const resetView = triggerButtonView(table, 'columnManagerReset', {\n label: toggleable\n ? t('table.reset_columns', {}, 'Reset')\n : t('table.reset_column_order', {}, 'Reset column order'),\n variant: 'ghost',\n })\n const applyView = deferred\n ? triggerButtonView(table, 'columnManagerApply', {\n label: t('table_filters.apply', {}, 'Apply'),\n variant: 'default',\n })\n : null\n const resetButton = resetView ? (\n <TableTriggerButton\n view={resetView}\n disabled={saving}\n data-slot=\"column-manager-reset\"\n className={cn(!applyView && 'w-full')}\n onClick={() => void reset()}\n />\n ) : null\n const inHeader = table.columnManagerResetActionPosition === 'header'\n\n const list = (\n <ul\n className={cn(\n table.columnManagerColumns\n ? ['grid gap-0.5', gridColumnsClasses(table.columnManagerColumns)]\n : 'flex flex-col gap-0.5',\n )}\n aria-label={t('table.columns', {}, 'Columns')}\n >\n {columns.map((column) => {\n const { position, count, group } = columnManagerPosition(\n names,\n column.name,\n table.columnGroups,\n )\n const positionId = `${idBase}-${column.name}-position`\n\n return (\n <li\n key={column.name}\n data-column={column.name}\n draggable={reorderable && !saving}\n onDragStart={(event) => {\n if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'\n setDragging(column.name)\n }}\n onDragOver={(event) => {\n if (dragging === null) return\n\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'\n setDropTarget(column.name)\n }}\n onDragLeave={() => dropTarget === column.name && setDropTarget(null)}\n onDragEnd={() => {\n setDragging(null)\n setDropTarget(null)\n }}\n onDrop={(event) => {\n event.preventDefault()\n\n if (dragging !== null && dragging !== column.name) {\n reorder(dragging, moveColumnTo(names, dragging, column.name))\n }\n\n setDragging(null)\n setDropTarget(null)\n }}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1 text-sm',\n reorderable && 'cursor-grab',\n dragging === column.name && 'opacity-50',\n dropTarget === column.name && dragging !== column.name && 'bg-muted',\n )}\n >\n {reorderable && (\n <GripVerticalIcon\n className=\"text-muted-foreground size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n )}\n {column.toggleable ? (\n <label className=\"flex min-w-0 flex-1 items-center gap-2\">\n <Checkbox\n checked={visible.includes(column.name)}\n onCheckedChange={() => toggle(column.name)}\n />\n <span className=\"truncate\">{column.label}</span>\n </label>\n ) : (\n <span className=\"min-w-0 flex-1 truncate\">{column.label}</span>\n )}\n {reorderable && (\n <span className=\"flex shrink-0 items-center\">\n <span id={positionId} className=\"sr-only\" data-slot=\"column-manager-position\">\n {group\n ? t(\n 'table.column_manager.position_in_group',\n { position, count, group },\n `Position ${position} of ${count} in ${group}.`,\n )\n : t(\n 'table.column_manager.position',\n { position, count },\n `Position ${position} of ${count}.`,\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n disabled={position <= 1}\n aria-disabled={saving || undefined}\n aria-describedby={positionId}\n aria-label={t(\n 'table.move_column_up',\n { column: column.label },\n `Move ${column.label} up`,\n )}\n onClick={(event) => move(column.name, -1, event.currentTarget)}\n >\n <ChevronUpIcon />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n disabled={position >= count}\n aria-disabled={saving || undefined}\n aria-describedby={positionId}\n aria-label={t(\n 'table.move_column_down',\n { column: column.label },\n `Move ${column.label} down`,\n )}\n onClick={(event) => move(column.name, 1, event.currentTarget)}\n >\n <ChevronDownIcon />\n </Button>\n </span>\n )}\n </li>\n )\n })}\n </ul>\n )\n\n const body: ReactNode = (\n <div className=\"flex flex-col gap-1\">\n {inHeader && resetButton && <div className=\"flex justify-end\">{resetButton}</div>}\n {list}\n {reorderable && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n data-slot=\"column-manager-announcement\"\n >\n {announcement}\n </div>\n )}\n {((!inHeader && resetButton) || applyView) && (\n <div className=\"mt-1 flex items-center justify-end gap-2\">\n {!inHeader && resetButton}\n {applyView && (\n <TableTriggerButton\n view={applyView}\n data-slot=\"column-manager-apply\"\n disabled={\n !orderChanged && (!draft || !columnDraftChanged(table.visibleColumns, draft))\n }\n onClick={() => void apply()}\n />\n )}\n </div>\n )}\n </div>\n )\n\n const onOpenChange = (next: boolean) => {\n // Closing without \"Apply\" drops the draft.\n if (!next) {\n setDraft(null)\n if (deferred) setOrderDraft(null)\n setAnnouncement('')\n }\n setOpen(next)\n }\n\n const triggerButton = (\n <TableTriggerButton\n view={trigger}\n defaultIcon={<Icon name=\"alfakit::tables.actions.column-manager\" fallback={Columns3Icon} />}\n aria-label={trigger.showLabel ? undefined : trigger.label}\n />\n )\n\n if (table.columnManagerLayout === 'modal') {\n // `columnManagerTriggerAction()` may set `slideOver()`, `modalWidth()` and the heading.\n const modal = table.triggerActions?.columnManager?.modal\n const heading = modal?.heading ?? t('table.columns', {}, 'Columns')\n const description = modal?.description ?? table.pluralModelLabel\n const widthClass = modal?.width ? FILTERS_MODAL_WIDTH_CLASSES[modal.width] : undefined\n\n if (modal?.slideOver) {\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetTrigger asChild>{triggerButton}</SheetTrigger>\n <SheetContent\n side={slideOverSide(modal.slideOverPosition, direction)}\n className={cn('flex w-full flex-col gap-0 p-0', widthClass ?? 'sm:max-w-md')}\n data-slot=\"column-manager\"\n data-slide-over=\"\"\n >\n <SheetHeader className=\"border-b\">\n <SheetTitle>{heading}</SheetTitle>\n <SheetDescription className={modal.description ? undefined : 'sr-only'}>\n {description}\n </SheetDescription>\n </SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogTrigger asChild>{triggerButton}</DialogTrigger>\n <DialogContent className={widthClass ?? 'sm:max-w-lg'} data-slot=\"column-manager\">\n <DialogHeader>\n <DialogTitle>{heading}</DialogTitle>\n <DialogDescription className={modal?.description ? undefined : 'sr-only'}>\n {description}\n </DialogDescription>\n </DialogHeader>\n <div className=\"max-h-[60vh] overflow-y-auto\">{body}</div>\n </DialogContent>\n </Dialog>\n )\n }\n\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <PopoverTrigger asChild>{triggerButton}</PopoverTrigger>\n <PopoverContent\n align=\"end\"\n className={cn('p-2', table.columnManagerColumns ? 'w-auto min-w-64' : 'w-64')}\n data-slot=\"column-manager\"\n >\n {body}\n </PopoverContent>\n </Popover>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"column-manager.js","names":[],"sources":["../../../src/components/table/column-manager.tsx"],"sourcesContent":["import type { TableSchema } from '@alfakit/core'\nimport {\n columnDraftChanged,\n columnManagerPosition,\n columnOrderChanged,\n defaultVisibleColumns,\n FILTERS_MODAL_WIDTH_CLASSES,\n gridColumnsClasses,\n keepMoveButtonFocus,\n moveColumnInManager,\n moveColumnTo,\n orderColumnsBy,\n saveColumnOrder,\n slideOverSide,\n toggleVisibleColumn,\n triggerButtonView,\n visitTable,\n} from '@alfakit/core'\nimport { ChevronDownIcon, ChevronUpIcon, Columns3Icon, GripVerticalIcon } from 'lucide-react'\nimport { useId, useState, type ReactNode } from 'react'\nimport { useDirection, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Button } from '../ui/button'\nimport { Checkbox } from '../ui/checkbox'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogHeader,\n DialogTitle,\n DialogTrigger,\n} from '../ui/dialog'\nimport { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'\nimport {\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n SheetTrigger,\n} from '../ui/sheet'\nimport { TableTriggerButton } from './trigger-button'\n\n/**\n * The column manager: show / hide toggleable columns (kept in the query string) and,\n * with `reorderableColumns()`, move columns (stored per user on the server) by dragging a\n * row onto another or with the move up / down buttons (the keyboard path).\n *\n * `deferColumnManager()` keeps toggles in a draft until \"Apply\"; `columnManagerLayout()`\n * opens it in a dialog; `columnManagerColumns()` lays the list out in a grid.\n */\nexport function ColumnManager({\n table,\n token,\n component,\n}: {\n table: TableSchema\n token: string\n component: string\n}) {\n const t = useT()\n const panel = usePanel()\n const direction = useDirection()\n const [open, setOpen] = useState(false)\n const [saving, setSaving] = useState(false)\n const [dragging, setDragging] = useState<string | null>(null)\n const [dropTarget, setDropTarget] = useState<string | null>(null)\n // Toggles of a deferred column manager wait here until \"Apply\".\n const [draft, setDraft] = useState<string[] | null>(null)\n // A moved column order: the draft of a deferred column manager, or shown at once\n // while it is saved.\n // It belongs to the columns it was made from: the server's next order replaces the\n // one shown while saving.\n const [orderState, setOrderState] = useState<{\n order: string[]\n columns: TableSchema['columns']\n } | null>(null)\n // Screen-reader announcement of the last move.\n const [announcement, setAnnouncement] = useState('')\n const idBase = useId()\n const reorderable = Boolean(table.reorderableColumns && panel.endpoints.tableColumns)\n const toggleable = Boolean(table.toggleableColumns)\n const deferred = Boolean(table.deferColumnManager)\n const orderDraft =\n orderState && (deferred || orderState.columns === table.columns) ? orderState.order : null\n const setOrderDraft = (order: string[] | null) =>\n setOrderState(order ? { order, columns: table.columns } : null)\n const appliedNames = table.columns.map((column) => column.name)\n const names = orderDraft ?? appliedNames\n const columns = orderColumnsBy(\n reorderable ? table.columns : table.columns.filter((column) => column.toggleable),\n orderDraft,\n )\n const visible = draft ?? table.visibleColumns\n\n if (!toggleable && !reorderable) return null\n\n const trigger = triggerButtonView(table, 'columnManager', {\n label: t('table.columns', {}, 'Columns'),\n showLabel: false,\n })\n\n if (!trigger) return null\n\n const toggle = (name: string) => {\n const next = toggleVisibleColumn(visible, name)\n\n if (deferred) {\n setDraft(next)\n } else {\n visitTable(table.identifier, { columns: next }, ['page'])\n }\n }\n\n const save = async (order: string[], options: { reload?: boolean } = {}): Promise<boolean> => {\n if (!panel.endpoints.tableColumns) return false\n\n setSaving(true)\n\n try {\n await saveColumnOrder(panel.endpoints.tableColumns, token, component, order, options)\n\n return true\n } catch (error) {\n showToast({\n id: 'alfakit-column-order-failed',\n title: t('table.column_order_failed', {}, \"Couldn't save the column order\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n\n return false\n } finally {\n setSaving(false)\n }\n }\n\n const announce = (name: string, order: string[]) => {\n const { position, count, group } = columnManagerPosition(order, name, table.columnGroups)\n const label = table.columns.find((column) => column.name === name)?.label ?? name\n const replace = { column: label, position, count, group: group ?? '' }\n\n setAnnouncement(\n group\n ? deferred\n ? t(\n 'table.column_manager.reordered_in_group_deferred',\n replace,\n `${label} moved to position ${position} of ${count} in ${group}. Apply columns to save your changes.`,\n )\n : t(\n 'table.column_manager.reordered_in_group',\n replace,\n `${label} moved to position ${position} of ${count} in ${group}.`,\n )\n : deferred\n ? t(\n 'table.column_manager.reordered_deferred',\n replace,\n `${label} moved to position ${position} of ${count}. Apply columns to save your changes.`,\n )\n : t(\n 'table.column_manager.reordered',\n replace,\n `${label} moved to position ${position} of ${count}.`,\n ),\n )\n }\n\n // A new order: kept as a draft until \"Apply\" (`deferColumnManager()`), else saved now.\n const reorder = (name: string, order: string[]) => {\n if (saving || !columnOrderChanged(names, order)) return\n\n setOrderDraft(order)\n setAnnouncement('')\n\n if (deferred) {\n announce(name, order)\n\n return\n }\n\n void save(order).then((saved) => (saved ? announce(name, order) : setOrderDraft(null)))\n }\n\n const move = (name: string, offset: -1 | 1, button: HTMLElement) => {\n keepMoveButtonFocus(button)\n reorder(name, moveColumnInManager(names, name, offset, table.columnGroups))\n }\n\n const orderChanged = Boolean(orderDraft && columnOrderChanged(appliedNames, orderDraft))\n\n // The order is saved before the visibility visit: both requests share the session, and a\n // visit running alongside the save would write the old order back when it finishes.\n const apply = async () => {\n const columns = draft && columnDraftChanged(table.visibleColumns, draft) ? draft : null\n\n setDraft(null)\n setOrderDraft(null)\n setAnnouncement('')\n setOpen(false)\n\n // The visibility visit renders the new order too: no second reload racing it.\n if (orderDraft && orderChanged) await save(orderDraft, { reload: !columns })\n\n if (columns) visitTable(table.identifier, { columns }, ['page'])\n }\n\n // Reset: the defined column order and, for toggleable columns, the default visibility.\n const reset = async () => {\n if (reorderable) {\n setOrderDraft(null)\n // The visibility visit below renders the defined order too.\n await save([], { reload: !toggleable })\n }\n\n if (toggleable) {\n setDraft(null)\n visitTable(table.identifier, { columns: defaultVisibleColumns(table) }, ['page'])\n }\n }\n\n const resetView = triggerButtonView(table, 'columnManagerReset', {\n label: toggleable\n ? t('table.reset_columns', {}, 'Reset')\n : t('table.reset_column_order', {}, 'Reset column order'),\n variant: 'ghost',\n })\n const applyView = deferred\n ? triggerButtonView(table, 'columnManagerApply', {\n label: t('table_filters.apply', {}, 'Apply'),\n variant: 'default',\n })\n : null\n const resetButton = resetView ? (\n <TableTriggerButton\n view={resetView}\n disabled={saving}\n data-slot=\"column-manager-reset\"\n className={cn(!applyView && 'w-full')}\n onClick={() => void reset()}\n />\n ) : null\n const inHeader = table.columnManagerResetActionPosition === 'header'\n\n const list = (\n <ul\n className={cn(\n table.columnManagerColumns\n ? ['grid gap-0.5', gridColumnsClasses(table.columnManagerColumns)]\n : 'flex flex-col gap-0.5',\n )}\n aria-label={t('table.columns', {}, 'Columns')}\n >\n {columns.map((column) => {\n const { position, count, group } = columnManagerPosition(\n names,\n column.name,\n table.columnGroups,\n )\n const positionId = `${idBase}-${column.name}-position`\n\n return (\n <li\n key={column.name}\n data-column={column.name}\n draggable={reorderable && !saving}\n onDragStart={(event) => {\n if (event.dataTransfer) event.dataTransfer.effectAllowed = 'move'\n setDragging(column.name)\n }}\n onDragOver={(event) => {\n if (dragging === null) return\n\n event.preventDefault()\n if (event.dataTransfer) event.dataTransfer.dropEffect = 'move'\n setDropTarget(column.name)\n }}\n onDragLeave={() => dropTarget === column.name && setDropTarget(null)}\n onDragEnd={() => {\n setDragging(null)\n setDropTarget(null)\n }}\n onDrop={(event) => {\n event.preventDefault()\n\n if (dragging !== null && dragging !== column.name) {\n reorder(dragging, moveColumnTo(names, dragging, column.name))\n }\n\n setDragging(null)\n setDropTarget(null)\n }}\n className={cn(\n 'flex items-center gap-2 rounded-md px-2 py-1 text-sm',\n reorderable && 'cursor-grab',\n dragging === column.name && 'opacity-50',\n dropTarget === column.name && dragging !== column.name && 'bg-muted',\n )}\n >\n {reorderable && (\n <GripVerticalIcon\n className=\"text-muted-foreground size-4 shrink-0\"\n aria-hidden=\"true\"\n />\n )}\n {column.toggleable ? (\n <label className=\"flex min-w-0 flex-1 items-center gap-2\">\n <Checkbox\n checked={visible.includes(column.name)}\n onCheckedChange={() => toggle(column.name)}\n />\n <span className=\"truncate\">{column.label}</span>\n </label>\n ) : (\n <span className=\"min-w-0 flex-1 truncate\">{column.label}</span>\n )}\n {reorderable && (\n <span className=\"flex shrink-0 items-center\">\n <span id={positionId} className=\"sr-only\" data-slot=\"column-manager-position\">\n {group\n ? t(\n 'table.column_manager.position_in_group',\n { position, count, group },\n `Position ${position} of ${count} in ${group}.`,\n )\n : t(\n 'table.column_manager.position',\n { position, count },\n `Position ${position} of ${count}.`,\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n disabled={position <= 1}\n aria-disabled={saving || undefined}\n aria-describedby={positionId}\n aria-label={t(\n 'table.move_column_up',\n { column: column.label },\n `Move ${column.label} up`,\n )}\n onClick={(event) => move(column.name, -1, event.currentTarget)}\n >\n <ChevronUpIcon />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-xs\"\n disabled={position >= count}\n aria-disabled={saving || undefined}\n aria-describedby={positionId}\n aria-label={t(\n 'table.move_column_down',\n { column: column.label },\n `Move ${column.label} down`,\n )}\n onClick={(event) => move(column.name, 1, event.currentTarget)}\n >\n <ChevronDownIcon />\n </Button>\n </span>\n )}\n </li>\n )\n })}\n </ul>\n )\n\n const body: ReactNode = (\n <div className=\"flex flex-col gap-1\">\n {inHeader && resetButton && <div className=\"flex justify-end\">{resetButton}</div>}\n {list}\n {reorderable && (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n data-slot=\"column-manager-announcement\"\n >\n {announcement}\n </div>\n )}\n {((!inHeader && resetButton) || applyView) && (\n <div className=\"mt-1 flex items-center justify-end gap-2\">\n {!inHeader && resetButton}\n {applyView && (\n <TableTriggerButton\n view={applyView}\n data-slot=\"column-manager-apply\"\n disabled={\n !orderChanged && (!draft || !columnDraftChanged(table.visibleColumns, draft))\n }\n onClick={() => void apply()}\n />\n )}\n </div>\n )}\n </div>\n )\n\n const onOpenChange = (next: boolean) => {\n // Closing without \"Apply\" drops the draft.\n if (!next) {\n setDraft(null)\n if (deferred) setOrderDraft(null)\n setAnnouncement('')\n }\n setOpen(next)\n }\n\n const triggerButton = (\n <TableTriggerButton\n view={trigger}\n defaultIcon={<Icon name=\"alfakit::tables.actions.column-manager\" fallback={Columns3Icon} />}\n aria-label={trigger.showLabel ? undefined : trigger.label}\n />\n )\n\n if (table.columnManagerLayout === 'modal') {\n // `columnManagerTriggerAction()` may set `slideOver()`, `modalWidth()` and the heading.\n const modal = table.triggerActions?.columnManager?.modal\n const heading = modal?.heading ?? t('table.columns', {}, 'Columns')\n const description = modal?.description ?? table.pluralModelLabel\n const widthClass = modal?.width ? FILTERS_MODAL_WIDTH_CLASSES[modal.width] : undefined\n\n if (modal?.slideOver) {\n return (\n <Sheet open={open} onOpenChange={onOpenChange}>\n <SheetTrigger asChild>{triggerButton}</SheetTrigger>\n <SheetContent\n side={slideOverSide(modal.slideOverPosition, direction)}\n className={cn('flex w-full flex-col gap-0 p-0', widthClass ?? 'sm:max-w-md')}\n data-slot=\"column-manager\"\n data-slide-over=\"\"\n >\n <SheetHeader className=\"border-b\">\n <SheetTitle>{heading}</SheetTitle>\n <SheetDescription className={modal.description ? undefined : 'sr-only'}>\n {description}\n </SheetDescription>\n </SheetHeader>\n <div className=\"flex-1 overflow-y-auto p-4\">{body}</div>\n </SheetContent>\n </Sheet>\n )\n }\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogTrigger asChild>{triggerButton}</DialogTrigger>\n <DialogContent className={widthClass ?? 'sm:max-w-lg'} data-slot=\"column-manager\">\n <DialogHeader>\n <DialogTitle>{heading}</DialogTitle>\n <DialogDescription className={modal?.description ? undefined : 'sr-only'}>\n {description}\n </DialogDescription>\n </DialogHeader>\n <div className=\"max-h-[60vh] overflow-y-auto\">{body}</div>\n </DialogContent>\n </Dialog>\n )\n }\n\n return (\n <Popover open={open} onOpenChange={onOpenChange}>\n <PopoverTrigger asChild>{triggerButton}</PopoverTrigger>\n <PopoverContent\n align=\"end\"\n className={cn('p-2', table.columnManagerColumns ? 'w-auto min-w-64' : 'w-64')}\n data-slot=\"column-manager\"\n >\n {body}\n </PopoverContent>\n </Popover>\n )\n}\n"],"mappings":"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"}
|
|
@@ -376,7 +376,14 @@ function Spreadsheet({ table, token, component, onClose }) {
|
|
|
376
376
|
children: /* @__PURE__ */ jsxs(DialogContent, {
|
|
377
377
|
className: "sm:max-w-lg",
|
|
378
378
|
children: [
|
|
379
|
-
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: t("table.spreadsheet.paste_title", {}, "Paste cells") }), /* @__PURE__ */ jsxs(DialogDescription, { children: [
|
|
379
|
+
/* @__PURE__ */ jsxs(DialogHeader, { children: [/* @__PURE__ */ jsx(DialogTitle, { children: t("table.spreadsheet.paste_title", {}, "Paste cells") }), /* @__PURE__ */ jsxs(DialogDescription, { children: [/* @__PURE__ */ jsx("span", {
|
|
380
|
+
className: "block",
|
|
381
|
+
children: choice("table.spreadsheet.paste_summary", preview?.plan.changes.length ?? 0, {}, ":count cell will change|:count cells will change")
|
|
382
|
+
}), (preview?.plan.skipped ?? 0) > 0 && /* @__PURE__ */ jsx("span", {
|
|
383
|
+
className: "block",
|
|
384
|
+
"data-slot": "spreadsheet-paste-skipped",
|
|
385
|
+
children: choice("table.spreadsheet.paste_skipped", preview?.plan.skipped ?? 0, {}, ":count read-only cell is skipped|:count read-only cells are skipped")
|
|
386
|
+
})] })] }),
|
|
380
387
|
/* @__PURE__ */ jsxs("div", {
|
|
381
388
|
"data-slot": "spreadsheet-paste",
|
|
382
389
|
className: "flex flex-col gap-3",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spreadsheet.js","names":[],"sources":["../../../src/components/table/spreadsheet.tsx"],"sourcesContent":["import type {\n PastePlan,\n SpreadsheetAnnouncement,\n SpreadsheetSaveResult,\n TableSchema,\n} from '@alfakit/core'\nimport {\n SPREADSHEET_CLASSES,\n SPREADSHEET_ISSUE_MESSAGES,\n SpreadsheetGrid,\n VIRTUAL_ESTIMATES,\n inertiaReload,\n saveSpreadsheetChanges,\n spreadsheetAnnouncement,\n spreadsheetAria,\n spreadsheetCellClass,\n spreadsheetCellId,\n spreadsheetColumns,\n spreadsheetRows,\n spreadsheetSelectionSize,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { CheckIcon } from 'lucide-react'\nimport {\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ClipboardEvent,\n type KeyboardEvent,\n} from 'react'\nimport { useChoice, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { useVirtualList } from '../../hooks/use-virtual-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { VirtualSpacerRow } from '../schema/virtual-spacer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Spinner } from '../ui/spinner'\n\nexport interface SpreadsheetProps {\n table: TableSchema\n token: string\n component: string\n /** Leave spreadsheet mode. */\n onClose: () => void\n}\n\ninterface PastePreview {\n plan: PastePlan\n errors: SpreadsheetSaveResult['errors']\n validating: boolean\n}\n\n/**\n * Spreadsheet mode of a `Table::spreadsheet()` table (loaded on first use): a keyboard\n * grid over the page's records. Edits stay local (highlighted) until \"Save\" sends them in\n * one request; the server validates each cell with its column's rules and the record's\n * `update` policy, and its errors show on the cells.\n */\nexport function Spreadsheet({ table, token, component, onClose }: SpreadsheetProps) {\n const t = useT()\n const choice = useChoice()\n const direction = useDirection()\n const panel = usePanel()\n const baseId = useId()\n const columns = useMemo(() => spreadsheetColumns(table), [table])\n const rows = useMemo(\n () => spreadsheetRows(table.records ?? [], columns),\n [table.records, columns],\n )\n const [grid] = useState(() => new SpreadsheetGrid(rows, columns))\n const state = useStore(grid.store)\n const [announcement, setAnnouncement] = useState('')\n const [preview, setPreview] = useState<PastePreview | null>(null)\n const gridRef = useRef<HTMLTableElement | null>(null)\n const endpoint = panel.endpoints.tableCells\n\n // A reload (save, live update, polling) brings new records; edits of rows still on the\n // page are kept.\n useEffect(() => grid.setData(rows, columns), [grid, rows, columns])\n\n const {\n list,\n range,\n indexes,\n attach,\n item: attachRow,\n } = useVirtualList(rows.length, virtualizeOptions(table.virtualize, VIRTUAL_ESTIMATES.row))\n\n const announce = (message: SpreadsheetAnnouncement | null) => {\n if (message) setAnnouncement(choice(message.key, message.count, {}, message.fallback))\n }\n\n // Keep focus on the active cell (roving tabindex), scrolling a virtualised row into view.\n useEffect(() => {\n if (state.editing) return\n\n const root = gridRef.current\n\n if (!root || !root.contains(document.activeElement)) return\n\n const { row, col } = state.active\n const cell = document.getElementById(spreadsheetCellId(baseId, row, col))\n\n if (cell) {\n cell.focus({ preventScroll: false })\n cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n } else {\n list.scrollToIndex(row)\n }\n }, [state.active, state.editing, baseId, list])\n\n const save = async () => {\n const changes = grid.changes()\n\n if (!endpoint || changes.length === 0 || state.saving) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n\n if (result.saved.length > 0) {\n announce(spreadsheetAnnouncement('saved', result.saved.length))\n inertiaReload()\n }\n\n if (result.errors.length > 0) {\n announce(spreadsheetAnnouncement('failed', result.errors.length))\n }\n } catch (error) {\n showToast({\n id: 'alfakit-spreadsheet-failed',\n title: t('table.spreadsheet.request_failed', {}, \"Couldn't save the changes\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n } finally {\n grid.setSaving(false)\n }\n }\n\n const undoSave = async () => {\n const changes = grid.undoSaveChanges()\n\n if (!endpoint || changes.length === 0) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n announce(spreadsheetAnnouncement('undo', result.saved.length))\n\n if (result.saved.length > 0) inertiaReload()\n } finally {\n grid.setSaving(false)\n }\n }\n\n const close = () => {\n if (\n grid.dirtyCount > 0 &&\n !window.confirm(t('table.spreadsheet.discard_confirm', {}, 'Discard your unsaved changes?'))\n ) {\n return\n }\n\n grid.discard()\n onClose()\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLTableElement>) => {\n const result = grid.handleKey(event, direction)\n\n if (result === 'ignored') return\n\n event.preventDefault()\n\n if (result === 'save') {\n void save()\n } else if (result === 'undo') {\n announce(spreadsheetAnnouncement('undo', grid.undo()))\n } else if (result === 'fill' || result === 'clear') {\n announce(spreadsheetAnnouncement(result, grid.lastApplied))\n }\n\n if (result === 'commit' || result === 'cancel' || result === 'toggle') {\n // Back from the editor to the cell.\n requestAnimationFrame(() =>\n document\n .getElementById(spreadsheetCellId(baseId, grid.state.active.row, grid.state.active.col))\n ?.focus(),\n )\n }\n }\n\n const onCopy = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n event.clipboardData.setData('text/plain', grid.copyText())\n announce(spreadsheetAnnouncement('copy', spreadsheetSelectionSize(grid.bounds())))\n }\n\n const onPaste = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n\n const plan = grid.planPaste(event.clipboardData.getData('text/plain'))\n\n if (plan.changes.length === 0 && plan.issues.length === 0) return\n\n setPreview({ plan, errors: [], validating: Boolean(endpoint && plan.changes.length > 0) })\n\n if (!endpoint || plan.changes.length === 0) return\n\n // Validate the pasted values on the server before they are applied.\n void saveSpreadsheetChanges(\n endpoint,\n token,\n component,\n plan.changes.map((change) => {\n const record = grid.getRows()[change.row]\n const column = grid.getColumns()[change.col]\n\n return { record: record?.key ?? '', column: column?.name ?? '', state: change.value }\n }),\n { dryRun: true },\n )\n .then((result) =>\n setPreview(\n (current) => current && { ...current, errors: result.errors, validating: false },\n ),\n )\n .catch(() => setPreview((current) => current && { ...current, validating: false }))\n }\n\n const applyPaste = () => {\n if (!preview) return\n\n const count = grid.apply(preview.plan.changes)\n\n if (preview.errors.length > 0) grid.setErrors(preview.errors)\n\n announce(spreadsheetAnnouncement('paste', count))\n setPreview(null)\n }\n\n const issueText = (issue: PastePlan['issues'][number]) => {\n const message = SPREADSHEET_ISSUE_MESSAGES[issue.reason]\n\n return t(\n message.key,\n { text: issue.text, column: columns[issue.col]?.label ?? '', row: issue.row + 1 },\n message.fallback,\n )\n }\n\n const errorList = grid.errorCells()\n\n const helpId = `${baseId}-help`\n\n return (\n <div data-slot=\"spreadsheet\" className=\"flex flex-col gap-3\">\n <div data-slot=\"spreadsheet-toolbar\" className=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => void save()}\n >\n {state.saving && <Spinner />}\n {t('table.spreadsheet.save', {}, 'Save changes')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={!grid.canUndo || state.saving}\n onClick={() => announce(spreadsheetAnnouncement('undo', grid.undo()))}\n >\n {t('table.spreadsheet.undo', {}, 'Undo')}\n </Button>\n {grid.canUndoSave && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={state.saving}\n onClick={() => void undoSave()}\n >\n {t('table.spreadsheet.undo_save', {}, 'Undo last save')}\n </Button>\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => grid.discard()}\n >\n {t('table.spreadsheet.discard', {}, 'Discard')}\n </Button>\n <span className=\"text-muted-foreground text-sm\" aria-live=\"polite\">\n {grid.dirtyCount > 0 &&\n choice(\n 'table.spreadsheet.unsaved',\n grid.dirtyCount,\n {},\n ':count unsaved change|:count unsaved changes',\n )}\n {grid.errorCount > 0 && (\n <span className=\"text-destructive ms-2\">\n {choice(\n 'table.spreadsheet.errors',\n grid.errorCount,\n {},\n ':count error|:count errors',\n )}\n </span>\n )}\n </span>\n <span className=\"flex-1\" />\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={close}>\n {t('table.spreadsheet.close', {}, 'Done')}\n </Button>\n </div>\n <p id={helpId} className=\"sr-only\">\n {t(\n 'table.spreadsheet.help',\n {},\n 'Arrow keys move between cells. Enter or F2 edits a cell, Escape cancels, Tab moves to the next cell. Ctrl+C copies, Ctrl+V pastes, Ctrl+D fills down, Ctrl+Z undoes and Ctrl+S saves.',\n )}\n </p>\n <div data-surface=\"table\" className={SPREADSHEET_CLASSES.root} style={{ maxHeight: '70vh' }}>\n <table\n ref={gridRef}\n role=\"grid\"\n aria-label={table.heading ?? t('table.spreadsheet.label', {}, 'Spreadsheet')}\n aria-describedby={helpId}\n aria-rowcount={rows.length + 1}\n aria-colcount={columns.length}\n aria-multiselectable=\"true\"\n aria-busy={state.saving || undefined}\n data-slot=\"spreadsheet-grid\"\n className={SPREADSHEET_CLASSES.table}\n onKeyDown={onKeyDown}\n onCopy={onCopy}\n onPaste={onPaste}\n >\n <thead>\n <tr aria-rowindex={1}>\n {columns.map((column, col) => (\n <th\n key={column.name}\n role=\"columnheader\"\n scope=\"col\"\n aria-colindex={col + 1}\n className={SPREADSHEET_CLASSES.header}\n >\n {column.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody ref={attach}>\n <VirtualSpacerRow height={range.before} colSpan={columns.length} />\n {indexes.map((row) => {\n const record = rows[row]\n\n if (!record) return null\n\n return (\n <tr\n key={String(record.key)}\n ref={attachRow}\n data-index={row}\n aria-rowindex={row + 2}\n >\n {columns.map((column, col) => {\n const active = grid.isActive(row, col)\n const editing =\n state.editing && state.editing.row === row && state.editing.col === col\n const errors = grid.errorsAt(row, col)\n const value = grid.value(row, col)\n const editable = grid.isEditable(row, col)\n\n return (\n <td\n key={column.name}\n id={spreadsheetCellId(baseId, row, col)}\n role=\"gridcell\"\n {...spreadsheetAria(row, col)}\n tabIndex={active ? 0 : -1}\n aria-selected={grid.isSelected(row, col)}\n aria-readonly={!editable || undefined}\n aria-invalid={errors.length > 0 || undefined}\n title={errors.join(' ') || undefined}\n data-slot=\"spreadsheet-cell\"\n data-dirty={grid.isDirty(row, col) || undefined}\n data-active={active || undefined}\n className={spreadsheetCellClass(grid, row, col)}\n onMouseDown={(event) => {\n if (editing) return\n\n grid.moveTo(row, col, event.shiftKey)\n }}\n onDoubleClick={() => grid.beginEdit()}\n >\n {editing && column.kind === 'select' ? (\n <select\n autoFocus\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => {\n grid.updateDraft(event.target.value)\n grid.commitEdit()\n document.getElementById(spreadsheetCellId(baseId, row, col))?.focus()\n }}\n onBlur={() => grid.cancelEdit()}\n >\n <option value=\"\">{t('table.spreadsheet.no_value', {}, '—')}</option>\n {column.options?.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n >\n {option.label.replace(/<[^>]*>/g, '')}\n </option>\n ))}\n </select>\n ) : editing ? (\n <input\n autoFocus\n type={column.kind === 'number' ? 'text' : (column.inputType ?? 'text')}\n inputMode={column.kind === 'number' ? 'decimal' : undefined}\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => grid.updateDraft(event.target.value)}\n onBlur={() => grid.commitEdit()}\n />\n ) : column.kind === 'boolean' ? (\n <span className=\"inline-flex items-center gap-1\">\n <span\n aria-hidden=\"true\"\n className=\"border-input data-[checked=true]:bg-primary data-[checked=true]:text-primary-foreground inline-flex size-4 items-center justify-center rounded-[4px] border\"\n data-checked={Boolean(value)}\n >\n {Boolean(value) && <CheckIcon className=\"size-3\" />}\n </span>\n <span className=\"sr-only\">\n {value\n ? t('table.spreadsheet.yes', {}, 'Yes')\n : t('table.spreadsheet.no', {}, 'No')}\n </span>\n </span>\n ) : (\n grid.text(row, col)\n )}\n {errors.length > 0 && <span className=\"sr-only\">{errors.join(' ')}</span>}\n </td>\n )\n })}\n </tr>\n )\n })}\n <VirtualSpacerRow height={range.after} colSpan={columns.length} />\n </tbody>\n </table>\n {rows.length === 0 && (\n <p className=\"text-muted-foreground p-6 text-center text-sm\">\n {t('table.spreadsheet.empty', {}, 'No records to edit.')}\n </p>\n )}\n </div>\n {errorList.length > 0 && (\n <ul data-slot=\"spreadsheet-errors\" className=\"text-destructive flex flex-col gap-1 text-sm\">\n {errorList.map((error) => (\n <li key={error.key}>\n <button\n type=\"button\"\n className=\"text-start hover:underline\"\n onClick={() => {\n grid.moveTo(error.row, error.col)\n document.getElementById(spreadsheetCellId(baseId, error.row, error.col))?.focus()\n }}\n >\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: error.row + 1,\n column: columns[error.col]?.label ?? '',\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </button>\n </li>\n ))}\n </ul>\n )}\n <span\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n data-slot=\"spreadsheet-announcement\"\n >\n {announcement}\n </span>\n\n <Dialog open={preview !== null} onOpenChange={(open) => !open && setPreview(null)}>\n <DialogContent className=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{t('table.spreadsheet.paste_title', {}, 'Paste cells')}</DialogTitle>\n <DialogDescription>\n {choice(\n 'table.spreadsheet.paste_summary',\n preview?.plan.changes.length ?? 0,\n {},\n ':count cell will change|:count cells will change',\n )}\n {(preview?.plan.skipped ?? 0) > 0 &&\n ` ${choice('table.spreadsheet.paste_skipped', preview?.plan.skipped ?? 0, {}, ':count read-only cell is skipped|:count read-only cells are skipped')}`}\n </DialogDescription>\n </DialogHeader>\n <div data-slot=\"spreadsheet-paste\" className=\"flex flex-col gap-3\">\n {preview?.validating && (\n <p className=\"text-muted-foreground flex items-center gap-2 text-sm\">\n <Spinner />\n {t('table.spreadsheet.paste_validating', {}, 'Checking the values…')}\n </p>\n )}\n {preview && (preview.plan.issues.length > 0 || preview.errors.length > 0) && (\n <ul className=\"text-destructive flex max-h-48 flex-col gap-1 overflow-auto text-sm\">\n {preview.plan.issues.map((issue) => (\n <li key={`issue-${issue.row}-${issue.col}`}>{issueText(issue)}</li>\n ))}\n {preview.errors.map((error) => {\n const position = grid.position(error.record, error.column)\n const column = columns.find((item) => item.name === error.column)\n const row = position?.row ?? -1\n\n return (\n <li key={`error-${String(error.record)}-${error.column}`}>\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: row + 1,\n column: column?.label ?? error.column,\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </li>\n )\n })}\n </ul>\n )}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setPreview(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n disabled={!preview || preview.plan.changes.length === 0}\n onClick={applyPaste}\n >\n {t('table.spreadsheet.paste_apply', {}, 'Apply')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAoEA,SAAgB,YAAY,EAAE,OAAO,OAAO,WAAW,WAA6B;CAClF,MAAM,IAAI,KAAK;CACf,MAAM,SAAS,UAAU;CACzB,MAAM,YAAY,aAAa;CAC/B,MAAM,QAAQ,SAAS;CACvB,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,cAAc,mBAAmB,KAAK,GAAG,CAAC,KAAK,CAAC;CAChE,MAAM,OAAO,cACL,gBAAgB,MAAM,WAAW,CAAC,GAAG,OAAO,GAClD,CAAC,MAAM,SAAS,OAAO,CACzB;CACA,MAAM,CAAC,QAAQ,eAAe,IAAI,gBAAgB,MAAM,OAAO,CAAC;CAChE,MAAM,QAAQ,SAAS,KAAK,KAAK;CACjC,MAAM,CAAC,cAAc,mBAAmB,SAAS,EAAE;CACnD,MAAM,CAAC,SAAS,cAAc,SAA8B,IAAI;CAChE,MAAM,UAAU,OAAgC,IAAI;CACpD,MAAM,WAAW,MAAM,UAAU;CAIjC,gBAAgB,KAAK,QAAQ,MAAM,OAAO,GAAG;EAAC;EAAM;EAAM;CAAO,CAAC;CAElE,MAAM,EACJ,MACA,OACA,SACA,QACA,MAAM,cACJ,eAAe,KAAK,QAAQ,kBAAkB,MAAM,YAAY,kBAAkB,GAAG,CAAC;CAE1F,MAAM,YAAY,YAA4C;EAC5D,IAAI,SAAS,gBAAgB,OAAO,QAAQ,KAAK,QAAQ,OAAO,CAAC,GAAG,QAAQ,QAAQ,CAAC;CACvF;CAGA,gBAAgB;EACd,IAAI,MAAM,SAAS;EAEnB,MAAM,OAAO,QAAQ;EAErB,IAAI,CAAC,QAAQ,CAAC,KAAK,SAAS,SAAS,aAAa,GAAG;EAErD,MAAM,EAAE,KAAK,QAAQ,MAAM;EAC3B,MAAM,OAAO,SAAS,eAAe,kBAAkB,QAAQ,KAAK,GAAG,CAAC;EAExE,IAAI,MAAM;GACR,KAAK,MAAM,EAAE,eAAe,MAAM,CAAC;GACnC,KAAK,iBAAiB;IAAE,OAAO;IAAW,QAAQ;GAAU,CAAC;EAC/D,OACE,KAAK,cAAc,GAAG;CAE1B,GAAG;EAAC,MAAM;EAAQ,MAAM;EAAS;EAAQ;CAAI,CAAC;CAE9C,MAAM,OAAO,YAAY;EACvB,MAAM,UAAU,KAAK,QAAQ;EAE7B,IAAI,CAAC,YAAY,QAAQ,WAAW,KAAK,MAAM,QAAQ;EAEvD,KAAK,UAAU,IAAI;EAEnB,IAAI;GACF,MAAM,SAAS,MAAM,uBAAuB,UAAU,OAAO,WAAW,OAAO;GAE/E,KAAK,gBAAgB,MAAM;GAE3B,IAAI,OAAO,MAAM,SAAS,GAAG;IAC3B,SAAS,wBAAwB,SAAS,OAAO,MAAM,MAAM,CAAC;IAC9D,cAAc;GAChB;GAEA,IAAI,OAAO,OAAO,SAAS,GACzB,SAAS,wBAAwB,UAAU,OAAO,OAAO,MAAM,CAAC;EAEpE,SAAS,OAAO;GACd,UAAU;IACR,IAAI;IACJ,OAAO,EAAE,oCAAoC,CAAC,GAAG,2BAA2B;IAC5E,MAAM,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;IAC3D,QAAQ;IACR,QAAQ;GACV,CAAC;EACH,UAAU;GACR,KAAK,UAAU,KAAK;EACtB;CACF;CAEA,MAAM,WAAW,YAAY;EAC3B,MAAM,UAAU,KAAK,gBAAgB;EAErC,IAAI,CAAC,YAAY,QAAQ,WAAW,GAAG;EAEvC,KAAK,UAAU,IAAI;EAEnB,IAAI;GACF,MAAM,SAAS,MAAM,uBAAuB,UAAU,OAAO,WAAW,OAAO;GAE/E,KAAK,gBAAgB,MAAM;GAC3B,SAAS,wBAAwB,QAAQ,OAAO,MAAM,MAAM,CAAC;GAE7D,IAAI,OAAO,MAAM,SAAS,GAAG,cAAc;EAC7C,UAAU;GACR,KAAK,UAAU,KAAK;EACtB;CACF;CAEA,MAAM,cAAc;EAClB,IACE,KAAK,aAAa,KAClB,CAAC,OAAO,QAAQ,EAAE,qCAAqC,CAAC,GAAG,+BAA+B,CAAC,GAE3F;EAGF,KAAK,QAAQ;EACb,QAAQ;CACV;CAEA,MAAM,aAAa,UAA2C;EAC5D,MAAM,SAAS,KAAK,UAAU,OAAO,SAAS;EAE9C,IAAI,WAAW,WAAW;EAE1B,MAAM,eAAe;EAErB,IAAI,WAAW,QACb,KAAU;OACL,IAAI,WAAW,QACpB,SAAS,wBAAwB,QAAQ,KAAK,KAAK,CAAC,CAAC;OAChD,IAAI,WAAW,UAAU,WAAW,SACzC,SAAS,wBAAwB,QAAQ,KAAK,WAAW,CAAC;EAG5D,IAAI,WAAW,YAAY,WAAW,YAAY,WAAW,UAE3D,4BACE,SACG,eAAe,kBAAkB,QAAQ,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,OAAO,GAAG,CAAC,CAAC,EACtF,MAAM,CACZ;CAEJ;CAEA,MAAM,UAAU,UAA4C;EAC1D,IAAI,KAAK,MAAM,SAAS;EAExB,MAAM,eAAe;EACrB,MAAM,cAAc,QAAQ,cAAc,KAAK,SAAS,CAAC;EACzD,SAAS,wBAAwB,QAAQ,yBAAyB,KAAK,OAAO,CAAC,CAAC,CAAC;CACnF;CAEA,MAAM,WAAW,UAA4C;EAC3D,IAAI,KAAK,MAAM,SAAS;EAExB,MAAM,eAAe;EAErB,MAAM,OAAO,KAAK,UAAU,MAAM,cAAc,QAAQ,YAAY,CAAC;EAErE,IAAI,KAAK,QAAQ,WAAW,KAAK,KAAK,OAAO,WAAW,GAAG;EAE3D,WAAW;GAAE;GAAM,QAAQ,CAAC;GAAG,YAAY,QAAQ,YAAY,KAAK,QAAQ,SAAS,CAAC;EAAE,CAAC;EAEzF,IAAI,CAAC,YAAY,KAAK,QAAQ,WAAW,GAAG;EAG5C,uBACE,UACA,OACA,WACA,KAAK,QAAQ,KAAK,WAAW;GAC3B,MAAM,SAAS,KAAK,QAAQ,CAAC,CAAC,OAAO;GACrC,MAAM,SAAS,KAAK,WAAW,CAAC,CAAC,OAAO;GAExC,OAAO;IAAE,QAAQ,QAAQ,OAAO;IAAI,QAAQ,QAAQ,QAAQ;IAAI,OAAO,OAAO;GAAM;EACtF,CAAC,GACD,EAAE,QAAQ,KAAK,CACjB,CAAC,CACE,MAAM,WACL,YACG,YAAY,WAAW;GAAE,GAAG;GAAS,QAAQ,OAAO;GAAQ,YAAY;EAAM,CACjF,CACF,CAAC,CACA,YAAY,YAAY,YAAY,WAAW;GAAE,GAAG;GAAS,YAAY;EAAM,CAAC,CAAC;CACtF;CAEA,MAAM,mBAAmB;EACvB,IAAI,CAAC,SAAS;EAEd,MAAM,QAAQ,KAAK,MAAM,QAAQ,KAAK,OAAO;EAE7C,IAAI,QAAQ,OAAO,SAAS,GAAG,KAAK,UAAU,QAAQ,MAAM;EAE5D,SAAS,wBAAwB,SAAS,KAAK,CAAC;EAChD,WAAW,IAAI;CACjB;CAEA,MAAM,aAAa,UAAuC;EACxD,MAAM,UAAU,2BAA2B,MAAM;EAEjD,OAAO,EACL,QAAQ,KACR;GAAE,MAAM,MAAM;GAAM,QAAQ,QAAQ,MAAM,IAAI,EAAE,SAAS;GAAI,KAAK,MAAM,MAAM;EAAE,GAChF,QAAQ,QACV;CACF;CAEA,MAAM,YAAY,KAAK,WAAW;CAElC,MAAM,SAAS,GAAG,OAAO;CAEzB,OACE,qBAAC,OAAD;EAAK,aAAU;EAAc,WAAU;EAAvC,UAAA;GACE,qBAAC,OAAD;IAAK,aAAU;IAAsB,WAAU;IAA/C,UAAA;KACE,qBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,UAAU,KAAK,eAAe,KAAK,MAAM;MACzC,eAAe,KAAK,KAAK;MAJ3B,UAAA,CAMG,MAAM,UAAU,oBAAC,SAAD,CAAU,CAAA,GAC1B,EAAE,0BAA0B,CAAC,GAAG,cAAc,CACzC;;KACR,oBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,UAAU,CAAC,KAAK,WAAW,MAAM;MACjC,eAAe,SAAS,wBAAwB,QAAQ,KAAK,KAAK,CAAC,CAAC;MAEnE,UAAA,EAAE,0BAA0B,CAAC,GAAG,MAAM;KACjC,CAAA;KACP,KAAK,eACJ,oBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,UAAU,MAAM;MAChB,eAAe,KAAK,SAAS;MAE5B,UAAA,EAAE,+BAA+B,CAAC,GAAG,gBAAgB;KAChD,CAAA;KAEV,oBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,UAAU,KAAK,eAAe,KAAK,MAAM;MACzC,eAAe,KAAK,QAAQ;MAE3B,UAAA,EAAE,6BAA6B,CAAC,GAAG,SAAS;KACvC,CAAA;KACR,qBAAC,QAAD;MAAM,WAAU;MAAgC,aAAU;MAA1D,UAAA,CACG,KAAK,aAAa,KACjB,OACE,6BACA,KAAK,YACL,CAAC,GACD,8CACF,GACD,KAAK,aAAa,KACjB,oBAAC,QAAD;OAAM,WAAU;OACb,UAAA,OACC,4BACA,KAAK,YACL,CAAC,GACD,4BACF;MACI,CAAA,CAEJ;;KACN,oBAAC,QAAD,EAAM,WAAU,SAAU,CAAA;KAC1B,oBAAC,QAAD;MAAQ,MAAK;MAAS,MAAK;MAAK,SAAQ;MAAU,SAAS;MACxD,UAAA,EAAE,2BAA2B,CAAC,GAAG,MAAM;KAClC,CAAA;IACL;;GACL,oBAAC,KAAD;IAAG,IAAI;IAAQ,WAAU;IACtB,UAAA,EACC,0BACA,CAAC,GACD,uLACF;GACC,CAAA;GACH,qBAAC,OAAD;IAAK,gBAAa;IAAQ,WAAW,oBAAoB;IAAM,OAAO,EAAE,WAAW,OAAO;IAA1F,UAAA,CACE,qBAAC,SAAD;KACE,KAAK;KACL,MAAK;KACL,cAAY,MAAM,WAAW,EAAE,2BAA2B,CAAC,GAAG,aAAa;KAC3E,oBAAkB;KAClB,iBAAe,KAAK,SAAS;KAC7B,iBAAe,QAAQ;KACvB,wBAAqB;KACrB,aAAW,MAAM,UAAU,KAAA;KAC3B,aAAU;KACV,WAAW,oBAAoB;KACpB;KACH;KACC;KAbX,UAAA,CAeE,oBAAC,SAAD,EAAA,UACE,oBAAC,MAAD;MAAI,iBAAe;MAChB,UAAA,QAAQ,KAAK,QAAQ,QACpB,oBAAC,MAAD;OAEE,MAAK;OACL,OAAM;OACN,iBAAe,MAAM;OACrB,WAAW,oBAAoB;OAE9B,UAAA,OAAO;MACN,GAPG,OAAO,IAOV,CACL;KACC,CAAA,EACC,CAAA,GACP,qBAAC,SAAD;MAAO,KAAK;MAAZ,UAAA;OACE,oBAAC,kBAAD;QAAkB,QAAQ,MAAM;QAAQ,SAAS,QAAQ;OAAS,CAAA;OACjE,QAAQ,KAAK,QAAQ;QACpB,MAAM,SAAS,KAAK;QAEpB,IAAI,CAAC,QAAQ,OAAO;QAEpB,OACE,oBAAC,MAAD;SAEE,KAAK;SACL,cAAY;SACZ,iBAAe,MAAM;SAEpB,UAAA,QAAQ,KAAK,QAAQ,QAAQ;UAC5B,MAAM,SAAS,KAAK,SAAS,KAAK,GAAG;UACrC,MAAM,UACJ,MAAM,WAAW,MAAM,QAAQ,QAAQ,OAAO,MAAM,QAAQ,QAAQ;UACtE,MAAM,SAAS,KAAK,SAAS,KAAK,GAAG;UACrC,MAAM,QAAQ,KAAK,MAAM,KAAK,GAAG;UACjC,MAAM,WAAW,KAAK,WAAW,KAAK,GAAG;UAEzC,OACE,qBAAC,MAAD;WAEE,IAAI,kBAAkB,QAAQ,KAAK,GAAG;WACtC,MAAK;WACL,GAAI,gBAAgB,KAAK,GAAG;WAC5B,UAAU,SAAS,IAAI;WACvB,iBAAe,KAAK,WAAW,KAAK,GAAG;WACvC,iBAAe,CAAC,YAAY,KAAA;WAC5B,gBAAc,OAAO,SAAS,KAAK,KAAA;WACnC,OAAO,OAAO,KAAK,GAAG,KAAK,KAAA;WAC3B,aAAU;WACV,cAAY,KAAK,QAAQ,KAAK,GAAG,KAAK,KAAA;WACtC,eAAa,UAAU,KAAA;WACvB,WAAW,qBAAqB,MAAM,KAAK,GAAG;WAC9C,cAAc,UAAU;YACtB,IAAI,SAAS;YAEb,KAAK,OAAO,KAAK,KAAK,MAAM,QAAQ;WACtC;WACA,qBAAqB,KAAK,UAAU;WAnBtC,UAAA,CAqBG,WAAW,OAAO,SAAS,WAC1B,qBAAC,UAAD;YACE,WAAA;YACA,WAAW,oBAAoB;YAC/B,cAAY,OAAO;YACnB,OACE,MAAM,SAAS,UAAU,QAAQ,MAAM,SAAS,UAAU,KAAA,IACtD,KACA,OAAO,MAAM,QAAQ,KAAK;YAEhC,WAAW,UAAU;aACnB,KAAK,YAAY,MAAM,OAAO,KAAK;aACnC,KAAK,WAAW;aAChB,SAAS,eAAe,kBAAkB,QAAQ,KAAK,GAAG,CAAC,CAAC,EAAE,MAAM;YACtE;YACA,cAAc,KAAK,WAAW;YAdhC,UAAA,CAgBE,oBAAC,UAAD;aAAQ,OAAM;aAAI,UAAA,EAAE,8BAA8B,CAAC,GAAG,GAAG;YAAU,CAAA,GAClE,OAAO,SAAS,KAAK,WACpB,oBAAC,UAAD;aAEE,OAAO,OAAO;aACd,UAAU,OAAO;aAEhB,UAAA,OAAO,MAAM,QAAQ,YAAY,EAAE;YAC9B,GALD,OAAO,KAKN,CACT,CACK;WACN,CAAA,IAAA,UACF,oBAAC,SAAD;YACE,WAAA;YACA,MAAM,OAAO,SAAS,WAAW,SAAU,OAAO,aAAa;YAC/D,WAAW,OAAO,SAAS,WAAW,YAAY,KAAA;YAClD,WAAW,oBAAoB;YAC/B,cAAY,OAAO;YACnB,OACE,MAAM,SAAS,UAAU,QAAQ,MAAM,SAAS,UAAU,KAAA,IACtD,KACA,OAAO,MAAM,QAAQ,KAAK;YAEhC,WAAW,UAAU,KAAK,YAAY,MAAM,OAAO,KAAK;YACxD,cAAc,KAAK,WAAW;WAC/B,CAAA,IACC,OAAO,SAAS,YAClB,qBAAC,QAAD;YAAM,WAAU;YAAhB,UAAA,CACE,oBAAC,QAAD;aACE,eAAY;aACZ,WAAU;aACV,gBAAc,QAAQ,KAAK;aAE1B,UAAA,QAAQ,KAAK,KAAK,oBAAC,WAAD,EAAW,WAAU,SAAU,CAAA;YAC9C,CAAA,GACN,oBAAC,QAAD;aAAM,WAAU;aACb,UAAA,QACG,EAAE,yBAAyB,CAAC,GAAG,KAAK,IACpC,EAAE,wBAAwB,CAAC,GAAG,IAAI;YAClC,CAAA,CACF;WAEN,CAAA,IAAA,KAAK,KAAK,KAAK,GAAG,GAEnB,OAAO,SAAS,KAAK,oBAAC,QAAD;YAAM,WAAU;YAAW,UAAA,OAAO,KAAK,GAAG;WAAQ,CAAA,CACtE;UAlFG,GAAA,OAAO,IAkFV;SAER,CAAC;QACC,GApGG,OAAO,OAAO,GAAG,CAoGpB;OAER,CAAC;OACD,oBAAC,kBAAD;QAAkB,QAAQ,MAAM;QAAO,SAAS,QAAQ;OAAS,CAAA;MAC5D;KACF,CAAA,CAAA;IACN,CAAA,GAAA,KAAK,WAAW,KACf,oBAAC,KAAD;KAAG,WAAU;KACV,UAAA,EAAE,2BAA2B,CAAC,GAAG,qBAAqB;IACtD,CAAA,CAEF;;GACJ,UAAU,SAAS,KAClB,oBAAC,MAAD;IAAI,aAAU;IAAqB,WAAU;IAC1C,UAAA,UAAU,KAAK,UACd,oBAAC,MAAD,EAAA,UACE,oBAAC,UAAD;KACE,MAAK;KACL,WAAU;KACV,eAAe;MACb,KAAK,OAAO,MAAM,KAAK,MAAM,GAAG;MAChC,SAAS,eAAe,kBAAkB,QAAQ,MAAM,KAAK,MAAM,GAAG,CAAC,CAAC,EAAE,MAAM;KAClF;KAEC,UAAA,EACC,gCACA;MACE,KAAK,MAAM,MAAM;MACjB,QAAQ,QAAQ,MAAM,IAAI,EAAE,SAAS;MACrC,SAAS,MAAM,SAAS,KAAK,GAAG;KAClC,GACA,6BACF;IACM,CAAA,EACN,GAnBK,MAAM,GAmBX,CACL;GACC,CAAA;GAEN,oBAAC,QAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAU;IACV,aAAU;IAET,UAAA;GACG,CAAA;GAEN,oBAAC,QAAD;IAAQ,MAAM,YAAY;IAAM,eAAe,SAAS,CAAC,QAAQ,WAAW,IAAI;IAC9E,UAAA,qBAAC,eAAD;KAAe,WAAU;KAAzB,UAAA;MACE,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,aAAD,EAAA,UAAc,EAAE,iCAAiC,CAAC,GAAG,aAAa,EAAe,CAAA,GACjF,qBAAC,mBAAD,EAAA,UAAA,CACG,OACC,mCACA,SAAS,KAAK,QAAQ,UAAU,GAChC,CAAC,GACD,kDACF,IACE,SAAS,KAAK,WAAW,KAAK,KAC9B,IAAI,OAAO,mCAAmC,SAAS,KAAK,WAAW,GAAG,CAAC,GAAG,qEAAqE,GACpI,EAAA,CAAA,CACP,EAAA,CAAA;MACd,qBAAC,OAAD;OAAK,aAAU;OAAoB,WAAU;OAA7C,UAAA,CACG,SAAS,cACR,qBAAC,KAAD;QAAG,WAAU;QAAb,UAAA,CACE,oBAAC,SAAD,CAAU,CAAA,GACT,EAAE,sCAAsC,CAAC,GAAG,sBAAsB,CAClE;OAEJ,CAAA,GAAA,YAAY,QAAQ,KAAK,OAAO,SAAS,KAAK,QAAQ,OAAO,SAAS,MACrE,qBAAC,MAAD;QAAI,WAAU;QAAd,UAAA,CACG,QAAQ,KAAK,OAAO,KAAK,UACxB,oBAAC,MAAD,EAAA,UAA6C,UAAU,KAAK,EAAM,GAAzD,SAAS,MAAM,IAAI,GAAG,MAAM,KAA6B,CACnE,GACA,QAAQ,OAAO,KAAK,UAAU;SAC7B,MAAM,WAAW,KAAK,SAAS,MAAM,QAAQ,MAAM,MAAM;SACzD,MAAM,SAAS,QAAQ,MAAM,SAAS,KAAK,SAAS,MAAM,MAAM;SAChE,MAAM,MAAM,UAAU,OAAO;SAE7B,OACE,oBAAC,MAAD,EAAA,UACG,EACC,gCACA;UACE,KAAK,MAAM;UACX,QAAQ,QAAQ,SAAS,MAAM;UAC/B,SAAS,MAAM,SAAS,KAAK,GAAG;SAClC,GACA,6BACF,EACE,GAVK,SAAS,OAAO,MAAM,MAAM,EAAE,GAAG,MAAM,QAU5C;QAER,CAAC,CACC;OAEH,CAAA,CAAA;;MACL,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,QAAD;OAAQ,MAAK;OAAS,SAAQ;OAAU,eAAe,WAAW,IAAI;OACnE,UAAA,EAAE,UAAU,CAAC,GAAG,QAAQ;MACnB,CAAA,GACR,oBAAC,QAAD;OACE,MAAK;OACL,UAAU,CAAC,WAAW,QAAQ,KAAK,QAAQ,WAAW;OACtD,SAAS;OAER,UAAA,EAAE,iCAAiC,CAAC,GAAG,OAAO;MACzC,CAAA,CACI,EAAA,CAAA;KACD;;GACT,CAAA;EACL;;AAET"}
|
|
1
|
+
{"version":3,"file":"spreadsheet.js","names":[],"sources":["../../../src/components/table/spreadsheet.tsx"],"sourcesContent":["import type {\n PastePlan,\n SpreadsheetAnnouncement,\n SpreadsheetSaveResult,\n TableSchema,\n} from '@alfakit/core'\nimport {\n SPREADSHEET_CLASSES,\n SPREADSHEET_ISSUE_MESSAGES,\n SpreadsheetGrid,\n VIRTUAL_ESTIMATES,\n inertiaReload,\n saveSpreadsheetChanges,\n spreadsheetAnnouncement,\n spreadsheetAria,\n spreadsheetCellClass,\n spreadsheetCellId,\n spreadsheetColumns,\n spreadsheetRows,\n spreadsheetSelectionSize,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { CheckIcon } from 'lucide-react'\nimport {\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type ClipboardEvent,\n type KeyboardEvent,\n} from 'react'\nimport { useChoice, useDirection, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { useVirtualList } from '../../hooks/use-virtual-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { VirtualSpacerRow } from '../schema/virtual-spacer'\nimport { Button } from '../ui/button'\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '../ui/dialog'\nimport { Spinner } from '../ui/spinner'\n\nexport interface SpreadsheetProps {\n table: TableSchema\n token: string\n component: string\n /** Leave spreadsheet mode. */\n onClose: () => void\n}\n\ninterface PastePreview {\n plan: PastePlan\n errors: SpreadsheetSaveResult['errors']\n validating: boolean\n}\n\n/**\n * Spreadsheet mode of a `Table::spreadsheet()` table (loaded on first use): a keyboard\n * grid over the page's records. Edits stay local (highlighted) until \"Save\" sends them in\n * one request; the server validates each cell with its column's rules and the record's\n * `update` policy, and its errors show on the cells.\n */\nexport function Spreadsheet({ table, token, component, onClose }: SpreadsheetProps) {\n const t = useT()\n const choice = useChoice()\n const direction = useDirection()\n const panel = usePanel()\n const baseId = useId()\n const columns = useMemo(() => spreadsheetColumns(table), [table])\n const rows = useMemo(\n () => spreadsheetRows(table.records ?? [], columns),\n [table.records, columns],\n )\n const [grid] = useState(() => new SpreadsheetGrid(rows, columns))\n const state = useStore(grid.store)\n const [announcement, setAnnouncement] = useState('')\n const [preview, setPreview] = useState<PastePreview | null>(null)\n const gridRef = useRef<HTMLTableElement | null>(null)\n const endpoint = panel.endpoints.tableCells\n\n // A reload (save, live update, polling) brings new records; edits of rows still on the\n // page are kept.\n useEffect(() => grid.setData(rows, columns), [grid, rows, columns])\n\n const {\n list,\n range,\n indexes,\n attach,\n item: attachRow,\n } = useVirtualList(rows.length, virtualizeOptions(table.virtualize, VIRTUAL_ESTIMATES.row))\n\n const announce = (message: SpreadsheetAnnouncement | null) => {\n if (message) setAnnouncement(choice(message.key, message.count, {}, message.fallback))\n }\n\n // Keep focus on the active cell (roving tabindex), scrolling a virtualised row into view.\n useEffect(() => {\n if (state.editing) return\n\n const root = gridRef.current\n\n if (!root || !root.contains(document.activeElement)) return\n\n const { row, col } = state.active\n const cell = document.getElementById(spreadsheetCellId(baseId, row, col))\n\n if (cell) {\n cell.focus({ preventScroll: false })\n cell.scrollIntoView?.({ block: 'nearest', inline: 'nearest' })\n } else {\n list.scrollToIndex(row)\n }\n }, [state.active, state.editing, baseId, list])\n\n const save = async () => {\n const changes = grid.changes()\n\n if (!endpoint || changes.length === 0 || state.saving) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n\n if (result.saved.length > 0) {\n announce(spreadsheetAnnouncement('saved', result.saved.length))\n inertiaReload()\n }\n\n if (result.errors.length > 0) {\n announce(spreadsheetAnnouncement('failed', result.errors.length))\n }\n } catch (error) {\n showToast({\n id: 'alfakit-spreadsheet-failed',\n title: t('table.spreadsheet.request_failed', {}, \"Couldn't save the changes\"),\n body: error instanceof Error ? error.message : String(error),\n status: 'danger',\n format: 'alfakit',\n })\n } finally {\n grid.setSaving(false)\n }\n }\n\n const undoSave = async () => {\n const changes = grid.undoSaveChanges()\n\n if (!endpoint || changes.length === 0) return\n\n grid.setSaving(true)\n\n try {\n const result = await saveSpreadsheetChanges(endpoint, token, component, changes)\n\n grid.applySaveResult(result)\n announce(spreadsheetAnnouncement('undo', result.saved.length))\n\n if (result.saved.length > 0) inertiaReload()\n } finally {\n grid.setSaving(false)\n }\n }\n\n const close = () => {\n if (\n grid.dirtyCount > 0 &&\n !window.confirm(t('table.spreadsheet.discard_confirm', {}, 'Discard your unsaved changes?'))\n ) {\n return\n }\n\n grid.discard()\n onClose()\n }\n\n const onKeyDown = (event: KeyboardEvent<HTMLTableElement>) => {\n const result = grid.handleKey(event, direction)\n\n if (result === 'ignored') return\n\n event.preventDefault()\n\n if (result === 'save') {\n void save()\n } else if (result === 'undo') {\n announce(spreadsheetAnnouncement('undo', grid.undo()))\n } else if (result === 'fill' || result === 'clear') {\n announce(spreadsheetAnnouncement(result, grid.lastApplied))\n }\n\n if (result === 'commit' || result === 'cancel' || result === 'toggle') {\n // Back from the editor to the cell.\n requestAnimationFrame(() =>\n document\n .getElementById(spreadsheetCellId(baseId, grid.state.active.row, grid.state.active.col))\n ?.focus(),\n )\n }\n }\n\n const onCopy = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n event.clipboardData.setData('text/plain', grid.copyText())\n announce(spreadsheetAnnouncement('copy', spreadsheetSelectionSize(grid.bounds())))\n }\n\n const onPaste = (event: ClipboardEvent<HTMLTableElement>) => {\n if (grid.state.editing) return\n\n event.preventDefault()\n\n const plan = grid.planPaste(event.clipboardData.getData('text/plain'))\n\n if (plan.changes.length === 0 && plan.issues.length === 0) return\n\n setPreview({ plan, errors: [], validating: Boolean(endpoint && plan.changes.length > 0) })\n\n if (!endpoint || plan.changes.length === 0) return\n\n // Validate the pasted values on the server before they are applied.\n void saveSpreadsheetChanges(\n endpoint,\n token,\n component,\n plan.changes.map((change) => {\n const record = grid.getRows()[change.row]\n const column = grid.getColumns()[change.col]\n\n return { record: record?.key ?? '', column: column?.name ?? '', state: change.value }\n }),\n { dryRun: true },\n )\n .then((result) =>\n setPreview(\n (current) => current && { ...current, errors: result.errors, validating: false },\n ),\n )\n .catch(() => setPreview((current) => current && { ...current, validating: false }))\n }\n\n const applyPaste = () => {\n if (!preview) return\n\n const count = grid.apply(preview.plan.changes)\n\n if (preview.errors.length > 0) grid.setErrors(preview.errors)\n\n announce(spreadsheetAnnouncement('paste', count))\n setPreview(null)\n }\n\n const issueText = (issue: PastePlan['issues'][number]) => {\n const message = SPREADSHEET_ISSUE_MESSAGES[issue.reason]\n\n return t(\n message.key,\n { text: issue.text, column: columns[issue.col]?.label ?? '', row: issue.row + 1 },\n message.fallback,\n )\n }\n\n const errorList = grid.errorCells()\n\n const helpId = `${baseId}-help`\n\n return (\n <div data-slot=\"spreadsheet\" className=\"flex flex-col gap-3\">\n <div data-slot=\"spreadsheet-toolbar\" className=\"flex flex-wrap items-center gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => void save()}\n >\n {state.saving && <Spinner />}\n {t('table.spreadsheet.save', {}, 'Save changes')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={!grid.canUndo || state.saving}\n onClick={() => announce(spreadsheetAnnouncement('undo', grid.undo()))}\n >\n {t('table.spreadsheet.undo', {}, 'Undo')}\n </Button>\n {grid.canUndoSave && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n disabled={state.saving}\n onClick={() => void undoSave()}\n >\n {t('table.spreadsheet.undo_save', {}, 'Undo last save')}\n </Button>\n )}\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n disabled={grid.dirtyCount === 0 || state.saving}\n onClick={() => grid.discard()}\n >\n {t('table.spreadsheet.discard', {}, 'Discard')}\n </Button>\n <span className=\"text-muted-foreground text-sm\" aria-live=\"polite\">\n {grid.dirtyCount > 0 &&\n choice(\n 'table.spreadsheet.unsaved',\n grid.dirtyCount,\n {},\n ':count unsaved change|:count unsaved changes',\n )}\n {grid.errorCount > 0 && (\n <span className=\"text-destructive ms-2\">\n {choice(\n 'table.spreadsheet.errors',\n grid.errorCount,\n {},\n ':count error|:count errors',\n )}\n </span>\n )}\n </span>\n <span className=\"flex-1\" />\n <Button type=\"button\" size=\"sm\" variant=\"outline\" onClick={close}>\n {t('table.spreadsheet.close', {}, 'Done')}\n </Button>\n </div>\n <p id={helpId} className=\"sr-only\">\n {t(\n 'table.spreadsheet.help',\n {},\n 'Arrow keys move between cells. Enter or F2 edits a cell, Escape cancels, Tab moves to the next cell. Ctrl+C copies, Ctrl+V pastes, Ctrl+D fills down, Ctrl+Z undoes and Ctrl+S saves.',\n )}\n </p>\n <div data-surface=\"table\" className={SPREADSHEET_CLASSES.root} style={{ maxHeight: '70vh' }}>\n <table\n ref={gridRef}\n role=\"grid\"\n aria-label={table.heading ?? t('table.spreadsheet.label', {}, 'Spreadsheet')}\n aria-describedby={helpId}\n aria-rowcount={rows.length + 1}\n aria-colcount={columns.length}\n aria-multiselectable=\"true\"\n aria-busy={state.saving || undefined}\n data-slot=\"spreadsheet-grid\"\n className={SPREADSHEET_CLASSES.table}\n onKeyDown={onKeyDown}\n onCopy={onCopy}\n onPaste={onPaste}\n >\n <thead>\n <tr aria-rowindex={1}>\n {columns.map((column, col) => (\n <th\n key={column.name}\n role=\"columnheader\"\n scope=\"col\"\n aria-colindex={col + 1}\n className={SPREADSHEET_CLASSES.header}\n >\n {column.label}\n </th>\n ))}\n </tr>\n </thead>\n <tbody ref={attach}>\n <VirtualSpacerRow height={range.before} colSpan={columns.length} />\n {indexes.map((row) => {\n const record = rows[row]\n\n if (!record) return null\n\n return (\n <tr\n key={String(record.key)}\n ref={attachRow}\n data-index={row}\n aria-rowindex={row + 2}\n >\n {columns.map((column, col) => {\n const active = grid.isActive(row, col)\n const editing =\n state.editing && state.editing.row === row && state.editing.col === col\n const errors = grid.errorsAt(row, col)\n const value = grid.value(row, col)\n const editable = grid.isEditable(row, col)\n\n return (\n <td\n key={column.name}\n id={spreadsheetCellId(baseId, row, col)}\n role=\"gridcell\"\n {...spreadsheetAria(row, col)}\n tabIndex={active ? 0 : -1}\n aria-selected={grid.isSelected(row, col)}\n aria-readonly={!editable || undefined}\n aria-invalid={errors.length > 0 || undefined}\n title={errors.join(' ') || undefined}\n data-slot=\"spreadsheet-cell\"\n data-dirty={grid.isDirty(row, col) || undefined}\n data-active={active || undefined}\n className={spreadsheetCellClass(grid, row, col)}\n onMouseDown={(event) => {\n if (editing) return\n\n grid.moveTo(row, col, event.shiftKey)\n }}\n onDoubleClick={() => grid.beginEdit()}\n >\n {editing && column.kind === 'select' ? (\n <select\n autoFocus\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => {\n grid.updateDraft(event.target.value)\n grid.commitEdit()\n document.getElementById(spreadsheetCellId(baseId, row, col))?.focus()\n }}\n onBlur={() => grid.cancelEdit()}\n >\n <option value=\"\">{t('table.spreadsheet.no_value', {}, '—')}</option>\n {column.options?.map((option) => (\n <option\n key={option.value}\n value={option.value}\n disabled={option.disabled}\n >\n {option.label.replace(/<[^>]*>/g, '')}\n </option>\n ))}\n </select>\n ) : editing ? (\n <input\n autoFocus\n type={column.kind === 'number' ? 'text' : (column.inputType ?? 'text')}\n inputMode={column.kind === 'number' ? 'decimal' : undefined}\n className={SPREADSHEET_CLASSES.editor}\n aria-label={column.label}\n value={\n state.editing?.value === null || state.editing?.value === undefined\n ? ''\n : String(state.editing.value)\n }\n onChange={(event) => grid.updateDraft(event.target.value)}\n onBlur={() => grid.commitEdit()}\n />\n ) : column.kind === 'boolean' ? (\n <span className=\"inline-flex items-center gap-1\">\n <span\n aria-hidden=\"true\"\n className=\"border-input data-[checked=true]:bg-primary data-[checked=true]:text-primary-foreground inline-flex size-4 items-center justify-center rounded-[4px] border\"\n data-checked={Boolean(value)}\n >\n {Boolean(value) && <CheckIcon className=\"size-3\" />}\n </span>\n <span className=\"sr-only\">\n {value\n ? t('table.spreadsheet.yes', {}, 'Yes')\n : t('table.spreadsheet.no', {}, 'No')}\n </span>\n </span>\n ) : (\n grid.text(row, col)\n )}\n {errors.length > 0 && <span className=\"sr-only\">{errors.join(' ')}</span>}\n </td>\n )\n })}\n </tr>\n )\n })}\n <VirtualSpacerRow height={range.after} colSpan={columns.length} />\n </tbody>\n </table>\n {rows.length === 0 && (\n <p className=\"text-muted-foreground p-6 text-center text-sm\">\n {t('table.spreadsheet.empty', {}, 'No records to edit.')}\n </p>\n )}\n </div>\n {errorList.length > 0 && (\n <ul data-slot=\"spreadsheet-errors\" className=\"text-destructive flex flex-col gap-1 text-sm\">\n {errorList.map((error) => (\n <li key={error.key}>\n <button\n type=\"button\"\n className=\"text-start hover:underline\"\n onClick={() => {\n grid.moveTo(error.row, error.col)\n document.getElementById(spreadsheetCellId(baseId, error.row, error.col))?.focus()\n }}\n >\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: error.row + 1,\n column: columns[error.col]?.label ?? '',\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </button>\n </li>\n ))}\n </ul>\n )}\n <span\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"sr-only\"\n data-slot=\"spreadsheet-announcement\"\n >\n {announcement}\n </span>\n\n <Dialog open={preview !== null} onOpenChange={(open) => !open && setPreview(null)}>\n <DialogContent className=\"sm:max-w-lg\">\n <DialogHeader>\n <DialogTitle>{t('table.spreadsheet.paste_title', {}, 'Paste cells')}</DialogTitle>\n <DialogDescription>\n {/* One line per count: translations can't join two sentences. */}\n <span className=\"block\">\n {choice(\n 'table.spreadsheet.paste_summary',\n preview?.plan.changes.length ?? 0,\n {},\n ':count cell will change|:count cells will change',\n )}\n </span>\n {(preview?.plan.skipped ?? 0) > 0 && (\n <span className=\"block\" data-slot=\"spreadsheet-paste-skipped\">\n {choice(\n 'table.spreadsheet.paste_skipped',\n preview?.plan.skipped ?? 0,\n {},\n ':count read-only cell is skipped|:count read-only cells are skipped',\n )}\n </span>\n )}\n </DialogDescription>\n </DialogHeader>\n <div data-slot=\"spreadsheet-paste\" className=\"flex flex-col gap-3\">\n {preview?.validating && (\n <p className=\"text-muted-foreground flex items-center gap-2 text-sm\">\n <Spinner />\n {t('table.spreadsheet.paste_validating', {}, 'Checking the values…')}\n </p>\n )}\n {preview && (preview.plan.issues.length > 0 || preview.errors.length > 0) && (\n <ul className=\"text-destructive flex max-h-48 flex-col gap-1 overflow-auto text-sm\">\n {preview.plan.issues.map((issue) => (\n <li key={`issue-${issue.row}-${issue.col}`}>{issueText(issue)}</li>\n ))}\n {preview.errors.map((error) => {\n const position = grid.position(error.record, error.column)\n const column = columns.find((item) => item.name === error.column)\n const row = position?.row ?? -1\n\n return (\n <li key={`error-${String(error.record)}-${error.column}`}>\n {t(\n 'table.spreadsheet.cell_error',\n {\n row: row + 1,\n column: column?.label ?? error.column,\n message: error.messages.join(' '),\n },\n 'Row :row, :column: :message',\n )}\n </li>\n )\n })}\n </ul>\n )}\n </div>\n <DialogFooter>\n <Button type=\"button\" variant=\"outline\" onClick={() => setPreview(null)}>\n {t('cancel', {}, 'Cancel')}\n </Button>\n <Button\n type=\"button\"\n disabled={!preview || preview.plan.changes.length === 0}\n onClick={applyPaste}\n >\n {t('table.spreadsheet.paste_apply', {}, 'Apply')}\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
@@ -288,7 +288,7 @@ function TranslationManagerView({ schema }) {
|
|
|
288
288
|
className: "bg-muted/50",
|
|
289
289
|
children: /* @__PURE__ */ jsxs("tr", { children: [/* @__PURE__ */ jsx("th", {
|
|
290
290
|
scope: "col",
|
|
291
|
-
className: "bg-
|
|
291
|
+
className: "bg-card sticky start-0 z-10 min-w-56 border-b [background-image:linear-gradient(var(--muted),var(--muted))] px-3 py-2 text-start font-medium",
|
|
292
292
|
children: t("translation-manager.key", {}, "Key")
|
|
293
293
|
}), locales.map((locale) => /* @__PURE__ */ jsx("th", {
|
|
294
294
|
scope: "col",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"translation-manager.js","names":[],"sources":["../../../src/components/translation-manager/translation-manager.tsx"],"sourcesContent":["import type {\n TranslationCellRef,\n TranslationFilter,\n TranslationLocaleSchema,\n TranslationManagerEnvironment,\n TranslationManagerSchema,\n TranslationRowSchema,\n} from '@alfakit/core'\nimport { TranslationManagerController, clientErrorNotification } from '@alfakit/core'\nimport {\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n HistoryIcon,\n LanguagesIcon,\n SearchIcon,\n SparklesIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'\nimport { useActionRunner, useDirection, useFormatter, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Card, CardContent, CardHeader } from '../ui/card'\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\nimport { Textarea } from '../ui/textarea'\n\nconst SELECT =\n 'border-input focus-visible:border-ring focus-visible:ring-ring/50 h-9 min-w-0 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]'\n\nconst cellKey = (ref: TranslationCellRef) => `${ref.row}\\u0000${ref.locale}`\n\n/**\n * The translation editor of alfasix/alfakit-translation-manager: keys by locale with search,\n * filters, inline editing (plural forms, right-to-left inputs), keyboard navigation, AI\n * suggestion review and history. Everything is computed and saved by the server.\n */\nexport function TranslationManager({ component }: { component?: TranslationManagerSchema }) {\n return component ? (\n <TranslationManagerView key={component.actionComponent} schema={component} />\n ) : null\n}\n\nfunction TranslationManagerView({ schema }: { schema: TranslationManagerSchema }) {\n const runner = useActionRunner()\n const page = usePageActionContext()\n const t = useT()\n const direction = useDirection()\n const formatter = useFormatter()\n const labelId = useId()\n const hintId = useId()\n const environment = (): TranslationManagerEnvironment => ({\n runner,\n token: page.token,\n t,\n notify: (notifications) => notifications.forEach((notification) => showToast(notification)),\n error: (message) => showToast(clientErrorNotification('translation-manager', message)),\n })\n const [controller] = useState(() => new TranslationManagerController(schema, environment()))\n\n useEffect(() => {\n controller.setEnvironment(environment())\n })\n\n const first = useRef(true)\n\n useEffect(() => {\n if (first.current) {\n first.current = false\n\n return\n }\n\n controller.setSchema(schema)\n }, [controller, schema])\n\n useEffect(() => () => controller.dispose(), [controller])\n\n const state = useStore(controller.store)\n const cells = useRef(new Map<string, HTMLElement>())\n const editor = useRef<HTMLTextAreaElement | null>(null)\n const locales = controller.visibleLocales()\n const active =\n state.active && state.rows.some((row) => row.id === state.active?.row)\n ? state.active\n : state.rows[0] && locales[0]\n ? { row: state.rows[0].id, locale: locales[0].code }\n : null\n\n const focusCell = (ref: TranslationCellRef | undefined) => {\n if (!ref) return\n controller.focus(ref)\n requestAnimationFrame(() => cells.current.get(cellKey(ref))?.focus())\n }\n\n useEffect(() => {\n if (state.editing) {\n requestAnimationFrame(() => editor.current?.focus())\n }\n }, [state.editing])\n\n const onCellKeyDown = (ref: TranslationCellRef) => (event: KeyboardEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return\n\n const result = controller.handleCellKey(ref, event, direction)\n\n if (result.handled) {\n event.preventDefault()\n if (!controller.state.editing) focusCell(result.focus)\n }\n }\n\n const onEditorKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (\n event.key === 'Escape' ||\n (event.key === 'Enter' && !event.shiftKey) ||\n event.key === 'Tab'\n ) {\n event.preventDefault()\n void controller\n .handleEditorKey({\n key: event.key,\n shiftKey: event.shiftKey,\n isComposing: event.nativeEvent.isComposing,\n })\n .then((result) => focusCell(result.focus))\n }\n }\n\n const range = controller.range()\n const suggestionLocales = controller.suggestionLocales()\n const activeRow = active ? controller.row(active.row) : undefined\n const activeLocale = active ? controller.locale(active.locale) : undefined\n\n return (\n <Card className=\"gap-4 py-4\" data-slot=\"translation-manager\">\n <CardHeader className=\"flex flex-col gap-4 px-5\">\n <span id={labelId} className=\"sr-only\">\n {t('translation-manager.label', {}, 'Translations')}\n </span>\n <ul\n className=\"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6\"\n data-slot=\"translation-progress\"\n >\n {schema.locales.map((locale) => {\n const progress = state.progress[locale.code]\n const percent = progress ? progress.percent : 0\n\n return (\n <li\n key={locale.code}\n className=\"flex min-w-0 flex-col gap-1 rounded-md border px-3 py-2\"\n >\n <div className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"truncate font-medium\" lang={locale.code}>\n {locale.label}\n </span>\n <span className=\"text-muted-foreground font-mono text-xs\">{locale.code}</span>\n </div>\n <div\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={Math.round(percent * 100)}\n aria-label={t(\n 'translation-manager.progress_label',\n {\n locale: locale.label,\n translated: progress?.translated ?? 0,\n total: progress?.total ?? 0,\n },\n ':locale: :translated of :total translated',\n )}\n className=\"bg-muted h-1.5 overflow-hidden rounded-full\"\n >\n <div\n className=\"bg-primary h-full rounded-full transition-[width]\"\n style={{ width: `${percent * 100}%` }}\n />\n </div>\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.progress',\n { percent: formatter.percent(percent) },\n ':percent translated',\n )}\n </span>\n </li>\n )\n })}\n </ul>\n <div className=\"flex flex-wrap items-center gap-2\" data-slot=\"translation-toolbar\">\n <div className=\"relative min-w-48 flex-1\">\n <SearchIcon\n className=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={state.search}\n onChange={(event) => controller.setSearch(event.target.value)}\n placeholder={t('translation-manager.search', {}, 'Search keys and texts…')}\n aria-label={t('translation-manager.search_label', {}, 'Search translations')}\n className=\"ps-8\"\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.filter_label', {}, 'Show')}</span>\n <select\n className={SELECT}\n value={state.filter}\n onChange={(event) => controller.setFilter(event.target.value as TranslationFilter)}\n >\n {schema.filters.map((filter) => (\n <option key={filter} value={filter}>\n {t(`translation-manager.filters.${filter}`, {}, filter)}\n </option>\n ))}\n </select>\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.group_label', {}, 'Group')}</span>\n <select\n className={SELECT}\n value={state.group}\n onChange={(event) => controller.setGroup(event.target.value)}\n >\n <option value=\"\">{t('translation-manager.all_groups', {}, 'All groups')}</option>\n {schema.groups.map((group) => (\n <option key={group.value} value={group.value}>\n {group.label}\n </option>\n ))}\n </select>\n </label>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"h-9\">\n <LanguagesIcon aria-hidden=\"true\" />\n {t('translation-manager.locales_label', {}, 'Languages')}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {schema.locales.map((locale) => (\n <DropdownMenuCheckboxItem\n key={locale.code}\n checked={state.visibleLocales.includes(locale.code)}\n onCheckedChange={(checked) =>\n controller.toggleLocale(locale.code, checked === true)\n }\n onSelect={(event) => event.preventDefault()}\n >\n {locale.label}{' '}\n <span className=\"text-muted-foreground ms-auto font-mono text-xs\">\n {locale.code}\n </span>\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n {suggestionLocales.length > 0 && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md border px-3 py-2 text-sm\"\n data-slot=\"translation-suggestions\"\n >\n {suggestionLocales.map((code) => (\n <div key={code} className=\"flex flex-wrap items-center gap-2\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n <span className=\"flex-1 font-medium\">\n {t('translation-manager.suggestion', {}, 'AI suggestion')} ·{' '}\n {controller.locale(code)?.label}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void controller.decideAll(code, 'reject')}\n >\n {t('translation-manager.reject_all', {}, 'Reject all')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void controller.decideAll(code, 'accept')}\n >\n {t('translation-manager.accept_all', {}, 'Accept all without warnings')}\n </Button>\n </div>\n ))}\n </div>\n )}\n </CardHeader>\n <CardContent className=\"flex flex-col gap-4 px-5\">\n <p id={hintId} className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.keyboard',\n {},\n 'Arrow keys move between texts. Enter edits and saves, Shift + Enter adds a line break, Escape cancels.',\n )}\n </p>\n {state.rows.length === 0 ? (\n <p className=\"text-muted-foreground py-10 text-center text-sm\">\n {t('translation-manager.empty', {}, 'No translations match your search.')}\n </p>\n ) : (\n <div className=\"overflow-x-auto rounded-md border\" data-slot=\"translation-grid-scroll\">\n <table\n role=\"grid\"\n aria-labelledby={labelId}\n aria-describedby={hintId}\n aria-busy={state.loading || undefined}\n aria-rowcount={state.total + 1}\n className={cn('w-full border-collapse text-sm', state.loading && 'opacity-60')}\n data-slot=\"translation-grid\"\n >\n <thead className=\"bg-muted/50\">\n <tr>\n <th\n scope=\"col\"\n className=\"bg-muted sticky start-0 z-10 min-w-56 border-b px-3 py-2 text-start font-medium\"\n >\n {t('translation-manager.key', {}, 'Key')}\n </th>\n {locales.map((locale) => (\n <th\n key={locale.code}\n scope=\"col\"\n className=\"min-w-64 border-s border-b px-3 py-2 text-start font-medium\"\n >\n <span className=\"flex items-center gap-2\">\n <span lang={locale.code}>{locale.label}</span>\n <span className=\"text-muted-foreground font-mono text-xs\">\n {locale.code}\n </span>\n {locale.base && (\n <Badge variant=\"secondary\">\n {t('translation-manager.base', {}, 'Base')}\n </Badge>\n )}\n {!locale.editable && (\n <Badge variant=\"outline\">\n {t('translation-manager.read_only', {}, 'Read only')}\n </Badge>\n )}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {state.rows.map((row) => (\n <tr key={row.id} className=\"border-b last:border-b-0\" data-slot=\"translation-row\">\n <th\n scope=\"row\"\n className=\"bg-card sticky start-0 z-10 px-3 py-2 text-start align-top font-normal\"\n >\n <KeyCell row={row} />\n </th>\n {locales.map((locale) => {\n const ref = { row: row.id, locale: locale.code }\n const cell = row.cells[locale.code]\n const isActive = active?.row === row.id && active.locale === locale.code\n const isEditing =\n state.editing?.row === row.id && state.editing.locale === locale.code\n\n return (\n <td\n key={locale.code}\n ref={(element) => {\n if (element) cells.current.set(cellKey(ref), element)\n else cells.current.delete(cellKey(ref))\n }}\n role=\"gridcell\"\n tabIndex={isActive && !isEditing ? 0 : -1}\n aria-selected={isActive}\n aria-readonly={!controller.isEditable(ref) || undefined}\n aria-label={\n isEditing\n ? undefined\n : t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n }\n data-slot=\"translation-cell\"\n data-missing={cell?.value ? undefined : ''}\n data-changed={cell?.changed ? '' : undefined}\n data-active={isActive ? '' : undefined}\n onFocus={(event) =>\n event.target === event.currentTarget && controller.focus(ref)\n }\n onClick={() => {\n if (!isEditing) {\n controller.focus(ref)\n if (isActive) controller.startEdit(ref)\n }\n }}\n onDoubleClick={() => controller.startEdit(ref)}\n onKeyDown={onCellKeyDown(ref)}\n className={cn(\n 'hover:bg-accent/40 border-s px-3 py-2 align-top outline-none',\n isActive && 'ring-ring/50 ring-2 ring-inset',\n controller.isEditable(ref) && 'cursor-text',\n )}\n >\n {isEditing ? (\n <CellEditor\n row={row}\n locale={locale}\n draft={state.draft}\n forms={state.draftForms}\n labels={cell?.formLabels}\n saving={state.saving}\n error={state.error}\n editorRef={editor}\n onDraft={(value) => controller.setDraft(value)}\n onForm={(index, value) => controller.setDraftForm(index, value)}\n onKeyDown={onEditorKeyDown}\n onSave={() =>\n void controller.save().then((saved) => saved && focusCell(ref))\n }\n onCancel={() => {\n controller.cancelEdit()\n focusCell(ref)\n }}\n onCopyBase={() => controller.copyBase()}\n />\n ) : (\n <CellValue locale={locale} cell={cell} />\n )}\n </td>\n )\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n <div\n className=\"flex flex-wrap items-center justify-between gap-2 text-sm\"\n data-slot=\"translation-pagination\"\n >\n <span className=\"text-muted-foreground\">\n {t(\n 'translation-manager.pagination',\n { ...range, total: formatter.number(state.total) },\n ':from–:to of :total',\n )}\n </span>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.previous', {}, 'Previous page')}\n disabled={state.page <= 1 || state.loading}\n onClick={() => controller.goToPage(state.page - 1)}\n >\n <ChevronLeftIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n <span className=\"px-2 tabular-nums\">\n {t(\n 'translation-manager.page',\n { page: state.page, pages: state.lastPage },\n 'Page :page of :pages',\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.next', {}, 'Next page')}\n disabled={state.page >= state.lastPage || state.loading}\n onClick={() => controller.goToPage(state.page + 1)}\n >\n <ChevronRightIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n {activeRow && activeLocale ? (\n <section\n className=\"flex flex-col gap-3 rounded-md border p-4 text-sm\"\n data-slot=\"translation-details\"\n aria-label={t('translation-manager.details', {}, 'Details')}\n >\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <code className=\"font-mono text-xs break-all\">{activeRow.key}</code>\n <span className=\"text-muted-foreground\">· {activeLocale.label}</span>\n </div>\n {activeRow.placeholders && activeRow.placeholders.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1\">\n <span className=\"text-muted-foreground me-1\">\n {t('translation-manager.placeholders', {}, 'Placeholders')}\n </span>\n {activeRow.placeholders.map((placeholder) => (\n <Badge key={placeholder} variant=\"outline\" className=\"font-mono\">\n {placeholder}\n </Badge>\n ))}\n </div>\n )}\n {activeRow.files && activeRow.files.length > 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.used_in', {}, 'Used in')}:{' '}\n <span className=\"font-mono text-xs\">{activeRow.files.join(', ')}</span>\n </p>\n )}\n {activeRow.cells[activeLocale.code]?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive flex items-start gap-2\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n {warning}\n </p>\n ))}\n {activeRow.cells[activeLocale.code]?.suggestion && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md p-3\"\n data-slot=\"translation-suggestion\"\n >\n <span className=\"flex items-center gap-2 font-medium\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {activeRow.cells[activeLocale.code]?.suggestion?.value}\n </p>\n {activeRow.cells[activeLocale.code]?.suggestion?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive text-xs\">\n {warning}\n </p>\n ))}\n {activeLocale.editable && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'accept',\n )\n }\n >\n <CheckIcon aria-hidden=\"true\" />\n {t('translation-manager.accept', {}, 'Accept')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'reject',\n )\n }\n >\n <XIcon aria-hidden=\"true\" />\n {t('translation-manager.reject', {}, 'Reject')}\n </Button>\n </div>\n )}\n </div>\n )}\n {state.history?.row === activeRow.id && state.history.locale === activeLocale.code ? (\n <div className=\"flex flex-col gap-2\" data-slot=\"translation-history\">\n <span className=\"font-medium\">\n {t('translation-manager.history', {}, 'History')}\n </span>\n {state.history.entries?.length === 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.history_empty', {}, 'No changes yet.')}\n </p>\n )}\n <ol className=\"flex flex-col gap-2\">\n {state.history.entries?.map((entry) => (\n <li key={entry.id} className=\"border-s-2 ps-3\">\n <span className=\"text-muted-foreground text-xs\">\n {t(`translation-manager.sources.${entry.source}`, {}, entry.source)}\n {entry.user\n ? ` · ${t('translation-manager.by', { name: entry.user }, 'by :name')}`\n : ''}\n {entry.at ? ` · ${formatter.relative(entry.at)}` : ''}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {entry.new ?? (\n <em className=\"text-muted-foreground\">\n {t('translation-manager.empty_value', {}, 'Empty')}\n </em>\n )}\n </p>\n </li>\n ))}\n </ol>\n </div>\n ) : (\n <div>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={() =>\n void controller.loadHistory({ row: activeRow.id, locale: activeLocale.code })\n }\n >\n <HistoryIcon aria-hidden=\"true\" />\n {t('translation-manager.show_history', {}, 'Show history')}\n </Button>\n </div>\n )}\n </section>\n ) : null}\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state.announcement}\n </div>\n </CardContent>\n </Card>\n )\n}\n\nfunction KeyCell({ row }: { row: TranslationRowSchema }) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n <code className=\"font-mono text-xs break-all\">{row.key}</code>\n <div className=\"flex flex-wrap gap-1\">\n <span className=\"text-muted-foreground text-xs\">{row.groupLabel}</span>\n {row.undefined && (\n <Badge variant=\"destructive\">\n {t('translation-manager.undefined', {}, 'Not defined')}\n </Badge>\n )}\n {row.unused && (\n <Badge variant=\"outline\">{t('translation-manager.unused', {}, 'Unused')}</Badge>\n )}\n </div>\n </div>\n )\n}\n\nfunction CellValue({\n locale,\n cell,\n}: {\n locale: TranslationLocaleSchema\n cell: TranslationManagerSchema['rows'][number]['cells'][string] | undefined\n}) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n {cell?.value ? (\n <span\n className=\"line-clamp-4 whitespace-pre-wrap\"\n dir={locale.direction}\n lang={locale.code}\n >\n {cell.value}\n </span>\n ) : (\n <span className=\"text-muted-foreground italic\">\n {t('translation-manager.missing', {}, 'Missing')}\n </span>\n )}\n {(cell?.changed || cell?.warnings || cell?.suggestion) && (\n <span className=\"flex flex-wrap gap-1\">\n {cell.changed && (\n <Badge variant=\"secondary\">\n {t('translation-manager.changed', {}, 'Changed in the panel')}\n </Badge>\n )}\n {cell.warnings && (\n <Badge variant=\"destructive\" title={cell.warnings.join('\\n')}>\n <TriangleAlertIcon aria-hidden=\"true\" />\n {t('translation-manager.warnings', {}, 'Warnings')}\n </Badge>\n )}\n {cell.suggestion && (\n <Badge variant=\"outline\">\n <SparklesIcon aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </Badge>\n )}\n </span>\n )}\n </div>\n )\n}\n\nfunction CellEditor({\n row,\n locale,\n draft,\n forms,\n labels,\n saving,\n error,\n editorRef,\n onDraft,\n onForm,\n onKeyDown,\n onSave,\n onCancel,\n onCopyBase,\n}: {\n row: TranslationRowSchema\n locale: TranslationLocaleSchema\n draft: string\n forms: string[] | null\n labels: string[] | undefined\n saving: boolean\n error: string | null\n editorRef: { current: HTMLTextAreaElement | null }\n onDraft: (value: string) => void\n onForm: (index: number, value: string) => void\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => void\n onSave: () => void\n onCancel: () => void\n onCopyBase: () => void\n}) {\n const t = useT()\n const errorId = useId()\n const label = t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n\n return (\n <div\n className=\"flex flex-col gap-2\"\n data-slot=\"translation-editor\"\n onClick={(event) => event.stopPropagation()}\n >\n {forms ? (\n forms.map((form, index) => (\n <label key={index} className=\"flex flex-col gap-1\">\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.plural_form',\n { numbers: labels?.[index] ?? String(index + 1) },\n 'For :numbers',\n )}\n </span>\n <Textarea\n ref={index === 0 ? editorRef : undefined}\n value={form}\n rows={1}\n dir={locale.direction}\n lang={locale.code}\n aria-label={`${label} (${labels?.[index] ?? index + 1})`}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n className=\"min-h-9\"\n onChange={(event) => onForm(index, event.target.value)}\n onKeyDown={onKeyDown}\n />\n </label>\n ))\n ) : (\n <Textarea\n ref={editorRef}\n value={draft}\n rows={Math.min(8, Math.max(2, draft.split('\\n').length))}\n dir={locale.direction}\n lang={locale.code}\n aria-label={label}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n onChange={(event) => onDraft(event.target.value)}\n onKeyDown={onKeyDown}\n />\n )}\n {error && (\n <p id={errorId} className=\"text-destructive text-xs\">\n {error}\n </p>\n )}\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button type=\"button\" size=\"sm\" disabled={saving} onClick={onSave}>\n {saving\n ? t('translation-manager.saving', {}, 'Saving…')\n : t('translation-manager.save', {}, 'Save')}\n </Button>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={onCancel}>\n {t('translation-manager.cancel', {}, 'Cancel')}\n </Button>\n {!locale.base && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"ms-auto\"\n disabled={saving}\n onClick={onCopyBase}\n >\n <CopyIcon aria-hidden=\"true\" />\n {t('translation-manager.copy_base', {}, 'Copy the base text')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"translation-manager.js","names":[],"sources":["../../../src/components/translation-manager/translation-manager.tsx"],"sourcesContent":["import type {\n TranslationCellRef,\n TranslationFilter,\n TranslationLocaleSchema,\n TranslationManagerEnvironment,\n TranslationManagerSchema,\n TranslationRowSchema,\n} from '@alfakit/core'\nimport { TranslationManagerController, clientErrorNotification } from '@alfakit/core'\nimport {\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n HistoryIcon,\n LanguagesIcon,\n SearchIcon,\n SparklesIcon,\n TriangleAlertIcon,\n XIcon,\n} from 'lucide-react'\nimport { useEffect, useId, useRef, useState, type KeyboardEvent } from 'react'\nimport { useActionRunner, useDirection, useFormatter, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { cn } from '../../lib/utils'\nimport { usePageActionContext } from '../actions/action-context'\nimport { showToast } from '../notifications/flash-toasts'\nimport { Badge } from '../ui/badge'\nimport { Button } from '../ui/button'\nimport { Card, CardContent, CardHeader } from '../ui/card'\nimport {\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n} from '../ui/dropdown-menu'\nimport { Input } from '../ui/input'\nimport { Textarea } from '../ui/textarea'\n\nconst SELECT =\n 'border-input focus-visible:border-ring focus-visible:ring-ring/50 h-9 min-w-0 rounded-md border bg-transparent px-2 text-sm shadow-xs outline-none focus-visible:ring-[3px]'\n\nconst cellKey = (ref: TranslationCellRef) => `${ref.row}\\u0000${ref.locale}`\n\n/**\n * The translation editor of alfasix/alfakit-translation-manager: keys by locale with search,\n * filters, inline editing (plural forms, right-to-left inputs), keyboard navigation, AI\n * suggestion review and history. Everything is computed and saved by the server.\n */\nexport function TranslationManager({ component }: { component?: TranslationManagerSchema }) {\n return component ? (\n <TranslationManagerView key={component.actionComponent} schema={component} />\n ) : null\n}\n\nfunction TranslationManagerView({ schema }: { schema: TranslationManagerSchema }) {\n const runner = useActionRunner()\n const page = usePageActionContext()\n const t = useT()\n const direction = useDirection()\n const formatter = useFormatter()\n const labelId = useId()\n const hintId = useId()\n const environment = (): TranslationManagerEnvironment => ({\n runner,\n token: page.token,\n t,\n notify: (notifications) => notifications.forEach((notification) => showToast(notification)),\n error: (message) => showToast(clientErrorNotification('translation-manager', message)),\n })\n const [controller] = useState(() => new TranslationManagerController(schema, environment()))\n\n useEffect(() => {\n controller.setEnvironment(environment())\n })\n\n const first = useRef(true)\n\n useEffect(() => {\n if (first.current) {\n first.current = false\n\n return\n }\n\n controller.setSchema(schema)\n }, [controller, schema])\n\n useEffect(() => () => controller.dispose(), [controller])\n\n const state = useStore(controller.store)\n const cells = useRef(new Map<string, HTMLElement>())\n const editor = useRef<HTMLTextAreaElement | null>(null)\n const locales = controller.visibleLocales()\n const active =\n state.active && state.rows.some((row) => row.id === state.active?.row)\n ? state.active\n : state.rows[0] && locales[0]\n ? { row: state.rows[0].id, locale: locales[0].code }\n : null\n\n const focusCell = (ref: TranslationCellRef | undefined) => {\n if (!ref) return\n controller.focus(ref)\n requestAnimationFrame(() => cells.current.get(cellKey(ref))?.focus())\n }\n\n useEffect(() => {\n if (state.editing) {\n requestAnimationFrame(() => editor.current?.focus())\n }\n }, [state.editing])\n\n const onCellKeyDown = (ref: TranslationCellRef) => (event: KeyboardEvent<HTMLElement>) => {\n if (event.target !== event.currentTarget) return\n\n const result = controller.handleCellKey(ref, event, direction)\n\n if (result.handled) {\n event.preventDefault()\n if (!controller.state.editing) focusCell(result.focus)\n }\n }\n\n const onEditorKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n if (\n event.key === 'Escape' ||\n (event.key === 'Enter' && !event.shiftKey) ||\n event.key === 'Tab'\n ) {\n event.preventDefault()\n void controller\n .handleEditorKey({\n key: event.key,\n shiftKey: event.shiftKey,\n isComposing: event.nativeEvent.isComposing,\n })\n .then((result) => focusCell(result.focus))\n }\n }\n\n const range = controller.range()\n const suggestionLocales = controller.suggestionLocales()\n const activeRow = active ? controller.row(active.row) : undefined\n const activeLocale = active ? controller.locale(active.locale) : undefined\n\n return (\n <Card className=\"gap-4 py-4\" data-slot=\"translation-manager\">\n <CardHeader className=\"flex flex-col gap-4 px-5\">\n <span id={labelId} className=\"sr-only\">\n {t('translation-manager.label', {}, 'Translations')}\n </span>\n <ul\n className=\"grid grid-cols-2 gap-2 sm:grid-cols-3 lg:grid-cols-6\"\n data-slot=\"translation-progress\"\n >\n {schema.locales.map((locale) => {\n const progress = state.progress[locale.code]\n const percent = progress ? progress.percent : 0\n\n return (\n <li\n key={locale.code}\n className=\"flex min-w-0 flex-col gap-1 rounded-md border px-3 py-2\"\n >\n <div className=\"flex items-center justify-between gap-2 text-sm\">\n <span className=\"truncate font-medium\" lang={locale.code}>\n {locale.label}\n </span>\n <span className=\"text-muted-foreground font-mono text-xs\">{locale.code}</span>\n </div>\n <div\n role=\"progressbar\"\n aria-valuemin={0}\n aria-valuemax={100}\n aria-valuenow={Math.round(percent * 100)}\n aria-label={t(\n 'translation-manager.progress_label',\n {\n locale: locale.label,\n translated: progress?.translated ?? 0,\n total: progress?.total ?? 0,\n },\n ':locale: :translated of :total translated',\n )}\n className=\"bg-muted h-1.5 overflow-hidden rounded-full\"\n >\n <div\n className=\"bg-primary h-full rounded-full transition-[width]\"\n style={{ width: `${percent * 100}%` }}\n />\n </div>\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.progress',\n { percent: formatter.percent(percent) },\n ':percent translated',\n )}\n </span>\n </li>\n )\n })}\n </ul>\n <div className=\"flex flex-wrap items-center gap-2\" data-slot=\"translation-toolbar\">\n <div className=\"relative min-w-48 flex-1\">\n <SearchIcon\n className=\"text-muted-foreground pointer-events-none absolute start-2.5 top-1/2 size-4 -translate-y-1/2\"\n aria-hidden=\"true\"\n />\n <Input\n type=\"search\"\n value={state.search}\n onChange={(event) => controller.setSearch(event.target.value)}\n placeholder={t('translation-manager.search', {}, 'Search keys and texts…')}\n aria-label={t('translation-manager.search_label', {}, 'Search translations')}\n className=\"ps-8\"\n />\n </div>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.filter_label', {}, 'Show')}</span>\n <select\n className={SELECT}\n value={state.filter}\n onChange={(event) => controller.setFilter(event.target.value as TranslationFilter)}\n >\n {schema.filters.map((filter) => (\n <option key={filter} value={filter}>\n {t(`translation-manager.filters.${filter}`, {}, filter)}\n </option>\n ))}\n </select>\n </label>\n <label className=\"flex items-center gap-2 text-sm\">\n <span className=\"sr-only\">{t('translation-manager.group_label', {}, 'Group')}</span>\n <select\n className={SELECT}\n value={state.group}\n onChange={(event) => controller.setGroup(event.target.value)}\n >\n <option value=\"\">{t('translation-manager.all_groups', {}, 'All groups')}</option>\n {schema.groups.map((group) => (\n <option key={group.value} value={group.value}>\n {group.label}\n </option>\n ))}\n </select>\n </label>\n <DropdownMenu dir={direction}>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" className=\"h-9\">\n <LanguagesIcon aria-hidden=\"true\" />\n {t('translation-manager.locales_label', {}, 'Languages')}\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {schema.locales.map((locale) => (\n <DropdownMenuCheckboxItem\n key={locale.code}\n checked={state.visibleLocales.includes(locale.code)}\n onCheckedChange={(checked) =>\n controller.toggleLocale(locale.code, checked === true)\n }\n onSelect={(event) => event.preventDefault()}\n >\n {locale.label}{' '}\n <span className=\"text-muted-foreground ms-auto font-mono text-xs\">\n {locale.code}\n </span>\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n {suggestionLocales.length > 0 && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md border px-3 py-2 text-sm\"\n data-slot=\"translation-suggestions\"\n >\n {suggestionLocales.map((code) => (\n <div key={code} className=\"flex flex-wrap items-center gap-2\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n <span className=\"flex-1 font-medium\">\n {t('translation-manager.suggestion', {}, 'AI suggestion')} ·{' '}\n {controller.locale(code)?.label}\n </span>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() => void controller.decideAll(code, 'reject')}\n >\n {t('translation-manager.reject_all', {}, 'Reject all')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => void controller.decideAll(code, 'accept')}\n >\n {t('translation-manager.accept_all', {}, 'Accept all without warnings')}\n </Button>\n </div>\n ))}\n </div>\n )}\n </CardHeader>\n <CardContent className=\"flex flex-col gap-4 px-5\">\n <p id={hintId} className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.keyboard',\n {},\n 'Arrow keys move between texts. Enter edits and saves, Shift + Enter adds a line break, Escape cancels.',\n )}\n </p>\n {state.rows.length === 0 ? (\n <p className=\"text-muted-foreground py-10 text-center text-sm\">\n {t('translation-manager.empty', {}, 'No translations match your search.')}\n </p>\n ) : (\n <div className=\"overflow-x-auto rounded-md border\" data-slot=\"translation-grid-scroll\">\n <table\n role=\"grid\"\n aria-labelledby={labelId}\n aria-describedby={hintId}\n aria-busy={state.loading || undefined}\n aria-rowcount={state.total + 1}\n className={cn('w-full border-collapse text-sm', state.loading && 'opacity-60')}\n data-slot=\"translation-grid\"\n >\n <thead className=\"bg-muted/50\">\n <tr>\n <th\n scope=\"col\"\n className=\"bg-card sticky start-0 z-10 min-w-56 border-b [background-image:linear-gradient(var(--muted),var(--muted))] px-3 py-2 text-start font-medium\"\n >\n {t('translation-manager.key', {}, 'Key')}\n </th>\n {locales.map((locale) => (\n <th\n key={locale.code}\n scope=\"col\"\n className=\"min-w-64 border-s border-b px-3 py-2 text-start font-medium\"\n >\n <span className=\"flex items-center gap-2\">\n <span lang={locale.code}>{locale.label}</span>\n <span className=\"text-muted-foreground font-mono text-xs\">\n {locale.code}\n </span>\n {locale.base && (\n <Badge variant=\"secondary\">\n {t('translation-manager.base', {}, 'Base')}\n </Badge>\n )}\n {!locale.editable && (\n <Badge variant=\"outline\">\n {t('translation-manager.read_only', {}, 'Read only')}\n </Badge>\n )}\n </span>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {state.rows.map((row) => (\n <tr key={row.id} className=\"border-b last:border-b-0\" data-slot=\"translation-row\">\n <th\n scope=\"row\"\n className=\"bg-card sticky start-0 z-10 px-3 py-2 text-start align-top font-normal\"\n >\n <KeyCell row={row} />\n </th>\n {locales.map((locale) => {\n const ref = { row: row.id, locale: locale.code }\n const cell = row.cells[locale.code]\n const isActive = active?.row === row.id && active.locale === locale.code\n const isEditing =\n state.editing?.row === row.id && state.editing.locale === locale.code\n\n return (\n <td\n key={locale.code}\n ref={(element) => {\n if (element) cells.current.set(cellKey(ref), element)\n else cells.current.delete(cellKey(ref))\n }}\n role=\"gridcell\"\n tabIndex={isActive && !isEditing ? 0 : -1}\n aria-selected={isActive}\n aria-readonly={!controller.isEditable(ref) || undefined}\n aria-label={\n isEditing\n ? undefined\n : t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n }\n data-slot=\"translation-cell\"\n data-missing={cell?.value ? undefined : ''}\n data-changed={cell?.changed ? '' : undefined}\n data-active={isActive ? '' : undefined}\n onFocus={(event) =>\n event.target === event.currentTarget && controller.focus(ref)\n }\n onClick={() => {\n if (!isEditing) {\n controller.focus(ref)\n if (isActive) controller.startEdit(ref)\n }\n }}\n onDoubleClick={() => controller.startEdit(ref)}\n onKeyDown={onCellKeyDown(ref)}\n className={cn(\n 'hover:bg-accent/40 border-s px-3 py-2 align-top outline-none',\n isActive && 'ring-ring/50 ring-2 ring-inset',\n controller.isEditable(ref) && 'cursor-text',\n )}\n >\n {isEditing ? (\n <CellEditor\n row={row}\n locale={locale}\n draft={state.draft}\n forms={state.draftForms}\n labels={cell?.formLabels}\n saving={state.saving}\n error={state.error}\n editorRef={editor}\n onDraft={(value) => controller.setDraft(value)}\n onForm={(index, value) => controller.setDraftForm(index, value)}\n onKeyDown={onEditorKeyDown}\n onSave={() =>\n void controller.save().then((saved) => saved && focusCell(ref))\n }\n onCancel={() => {\n controller.cancelEdit()\n focusCell(ref)\n }}\n onCopyBase={() => controller.copyBase()}\n />\n ) : (\n <CellValue locale={locale} cell={cell} />\n )}\n </td>\n )\n })}\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n <div\n className=\"flex flex-wrap items-center justify-between gap-2 text-sm\"\n data-slot=\"translation-pagination\"\n >\n <span className=\"text-muted-foreground\">\n {t(\n 'translation-manager.pagination',\n { ...range, total: formatter.number(state.total) },\n ':from–:to of :total',\n )}\n </span>\n <div className=\"flex items-center gap-1\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.previous', {}, 'Previous page')}\n disabled={state.page <= 1 || state.loading}\n onClick={() => controller.goToPage(state.page - 1)}\n >\n <ChevronLeftIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n <span className=\"px-2 tabular-nums\">\n {t(\n 'translation-manager.page',\n { page: state.page, pages: state.lastPage },\n 'Page :page of :pages',\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon-sm\"\n aria-label={t('translation-manager.next', {}, 'Next page')}\n disabled={state.page >= state.lastPage || state.loading}\n onClick={() => controller.goToPage(state.page + 1)}\n >\n <ChevronRightIcon className=\"rtl:rotate-180\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n {activeRow && activeLocale ? (\n <section\n className=\"flex flex-col gap-3 rounded-md border p-4 text-sm\"\n data-slot=\"translation-details\"\n aria-label={t('translation-manager.details', {}, 'Details')}\n >\n <div className=\"flex flex-wrap items-baseline gap-2\">\n <code className=\"font-mono text-xs break-all\">{activeRow.key}</code>\n <span className=\"text-muted-foreground\">· {activeLocale.label}</span>\n </div>\n {activeRow.placeholders && activeRow.placeholders.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1\">\n <span className=\"text-muted-foreground me-1\">\n {t('translation-manager.placeholders', {}, 'Placeholders')}\n </span>\n {activeRow.placeholders.map((placeholder) => (\n <Badge key={placeholder} variant=\"outline\" className=\"font-mono\">\n {placeholder}\n </Badge>\n ))}\n </div>\n )}\n {activeRow.files && activeRow.files.length > 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.used_in', {}, 'Used in')}:{' '}\n <span className=\"font-mono text-xs\">{activeRow.files.join(', ')}</span>\n </p>\n )}\n {activeRow.cells[activeLocale.code]?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive flex items-start gap-2\">\n <TriangleAlertIcon className=\"mt-0.5 size-4 shrink-0\" aria-hidden=\"true\" />\n {warning}\n </p>\n ))}\n {activeRow.cells[activeLocale.code]?.suggestion && (\n <div\n className=\"bg-muted/50 flex flex-col gap-2 rounded-md p-3\"\n data-slot=\"translation-suggestion\"\n >\n <span className=\"flex items-center gap-2 font-medium\">\n <SparklesIcon className=\"text-primary size-4\" aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {activeRow.cells[activeLocale.code]?.suggestion?.value}\n </p>\n {activeRow.cells[activeLocale.code]?.suggestion?.warnings?.map((warning) => (\n <p key={warning} className=\"text-destructive text-xs\">\n {warning}\n </p>\n ))}\n {activeLocale.editable && (\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'accept',\n )\n }\n >\n <CheckIcon aria-hidden=\"true\" />\n {t('translation-manager.accept', {}, 'Accept')}\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"outline\"\n onClick={() =>\n void controller.decide(\n { row: activeRow.id, locale: activeLocale.code },\n 'reject',\n )\n }\n >\n <XIcon aria-hidden=\"true\" />\n {t('translation-manager.reject', {}, 'Reject')}\n </Button>\n </div>\n )}\n </div>\n )}\n {state.history?.row === activeRow.id && state.history.locale === activeLocale.code ? (\n <div className=\"flex flex-col gap-2\" data-slot=\"translation-history\">\n <span className=\"font-medium\">\n {t('translation-manager.history', {}, 'History')}\n </span>\n {state.history.entries?.length === 0 && (\n <p className=\"text-muted-foreground\">\n {t('translation-manager.history_empty', {}, 'No changes yet.')}\n </p>\n )}\n <ol className=\"flex flex-col gap-2\">\n {state.history.entries?.map((entry) => (\n <li key={entry.id} className=\"border-s-2 ps-3\">\n <span className=\"text-muted-foreground text-xs\">\n {t(`translation-manager.sources.${entry.source}`, {}, entry.source)}\n {entry.user\n ? ` · ${t('translation-manager.by', { name: entry.user }, 'by :name')}`\n : ''}\n {entry.at ? ` · ${formatter.relative(entry.at)}` : ''}\n </span>\n <p\n className=\"whitespace-pre-wrap\"\n dir={activeLocale.direction}\n lang={activeLocale.code}\n >\n {entry.new ?? (\n <em className=\"text-muted-foreground\">\n {t('translation-manager.empty_value', {}, 'Empty')}\n </em>\n )}\n </p>\n </li>\n ))}\n </ol>\n </div>\n ) : (\n <div>\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n onClick={() =>\n void controller.loadHistory({ row: activeRow.id, locale: activeLocale.code })\n }\n >\n <HistoryIcon aria-hidden=\"true\" />\n {t('translation-manager.show_history', {}, 'Show history')}\n </Button>\n </div>\n )}\n </section>\n ) : null}\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n {state.announcement}\n </div>\n </CardContent>\n </Card>\n )\n}\n\nfunction KeyCell({ row }: { row: TranslationRowSchema }) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n <code className=\"font-mono text-xs break-all\">{row.key}</code>\n <div className=\"flex flex-wrap gap-1\">\n <span className=\"text-muted-foreground text-xs\">{row.groupLabel}</span>\n {row.undefined && (\n <Badge variant=\"destructive\">\n {t('translation-manager.undefined', {}, 'Not defined')}\n </Badge>\n )}\n {row.unused && (\n <Badge variant=\"outline\">{t('translation-manager.unused', {}, 'Unused')}</Badge>\n )}\n </div>\n </div>\n )\n}\n\nfunction CellValue({\n locale,\n cell,\n}: {\n locale: TranslationLocaleSchema\n cell: TranslationManagerSchema['rows'][number]['cells'][string] | undefined\n}) {\n const t = useT()\n\n return (\n <div className=\"flex flex-col gap-1\">\n {cell?.value ? (\n <span\n className=\"line-clamp-4 whitespace-pre-wrap\"\n dir={locale.direction}\n lang={locale.code}\n >\n {cell.value}\n </span>\n ) : (\n <span className=\"text-muted-foreground italic\">\n {t('translation-manager.missing', {}, 'Missing')}\n </span>\n )}\n {(cell?.changed || cell?.warnings || cell?.suggestion) && (\n <span className=\"flex flex-wrap gap-1\">\n {cell.changed && (\n <Badge variant=\"secondary\">\n {t('translation-manager.changed', {}, 'Changed in the panel')}\n </Badge>\n )}\n {cell.warnings && (\n <Badge variant=\"destructive\" title={cell.warnings.join('\\n')}>\n <TriangleAlertIcon aria-hidden=\"true\" />\n {t('translation-manager.warnings', {}, 'Warnings')}\n </Badge>\n )}\n {cell.suggestion && (\n <Badge variant=\"outline\">\n <SparklesIcon aria-hidden=\"true\" />\n {t('translation-manager.suggestion', {}, 'AI suggestion')}\n </Badge>\n )}\n </span>\n )}\n </div>\n )\n}\n\nfunction CellEditor({\n row,\n locale,\n draft,\n forms,\n labels,\n saving,\n error,\n editorRef,\n onDraft,\n onForm,\n onKeyDown,\n onSave,\n onCancel,\n onCopyBase,\n}: {\n row: TranslationRowSchema\n locale: TranslationLocaleSchema\n draft: string\n forms: string[] | null\n labels: string[] | undefined\n saving: boolean\n error: string | null\n editorRef: { current: HTMLTextAreaElement | null }\n onDraft: (value: string) => void\n onForm: (index: number, value: string) => void\n onKeyDown: (event: KeyboardEvent<HTMLElement>) => void\n onSave: () => void\n onCancel: () => void\n onCopyBase: () => void\n}) {\n const t = useT()\n const errorId = useId()\n const label = t(\n 'translation-manager.edit',\n { key: row.key, locale: locale.label },\n 'Edit :key in :locale',\n )\n\n return (\n <div\n className=\"flex flex-col gap-2\"\n data-slot=\"translation-editor\"\n onClick={(event) => event.stopPropagation()}\n >\n {forms ? (\n forms.map((form, index) => (\n <label key={index} className=\"flex flex-col gap-1\">\n <span className=\"text-muted-foreground text-xs\">\n {t(\n 'translation-manager.plural_form',\n { numbers: labels?.[index] ?? String(index + 1) },\n 'For :numbers',\n )}\n </span>\n <Textarea\n ref={index === 0 ? editorRef : undefined}\n value={form}\n rows={1}\n dir={locale.direction}\n lang={locale.code}\n aria-label={`${label} (${labels?.[index] ?? index + 1})`}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n className=\"min-h-9\"\n onChange={(event) => onForm(index, event.target.value)}\n onKeyDown={onKeyDown}\n />\n </label>\n ))\n ) : (\n <Textarea\n ref={editorRef}\n value={draft}\n rows={Math.min(8, Math.max(2, draft.split('\\n').length))}\n dir={locale.direction}\n lang={locale.code}\n aria-label={label}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? errorId : undefined}\n onChange={(event) => onDraft(event.target.value)}\n onKeyDown={onKeyDown}\n />\n )}\n {error && (\n <p id={errorId} className=\"text-destructive text-xs\">\n {error}\n </p>\n )}\n <div className=\"flex flex-wrap items-center gap-1\">\n <Button type=\"button\" size=\"sm\" disabled={saving} onClick={onSave}>\n {saving\n ? t('translation-manager.saving', {}, 'Saving…')\n : t('translation-manager.save', {}, 'Save')}\n </Button>\n <Button type=\"button\" size=\"sm\" variant=\"ghost\" disabled={saving} onClick={onCancel}>\n {t('translation-manager.cancel', {}, 'Cancel')}\n </Button>\n {!locale.base && (\n <Button\n type=\"button\"\n size=\"sm\"\n variant=\"ghost\"\n className=\"ms-auto\"\n disabled={saving}\n onClick={onCopyBase}\n >\n <CopyIcon aria-hidden=\"true\" />\n {t('translation-manager.copy_base', {}, 'Copy the base text')}\n </Button>\n )}\n </div>\n </div>\n )\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuCA,MAAM,SACJ;AAEF,MAAM,WAAW,QAA4B,GAAG,IAAI,IAAI,QAAQ,IAAI;;;;;;AAOpE,SAAgB,mBAAmB,EAAE,aAAuD;CAC1F,OAAO,YACL,oBAAC,wBAAD,EAAwD,QAAQ,UAAY,GAA/C,UAAU,eAAqC,IAC1E;AACN;AAEA,SAAS,uBAAuB,EAAE,UAAgD;CAChF,MAAM,SAAS,gBAAgB;CAC/B,MAAM,OAAO,qBAAqB;CAClC,MAAM,IAAI,KAAK;CACf,MAAM,YAAY,aAAa;CAC/B,MAAM,YAAY,aAAa;CAC/B,MAAM,UAAU,MAAM;CACtB,MAAM,SAAS,MAAM;CACrB,MAAM,qBAAoD;EACxD;EACA,OAAO,KAAK;EACZ;EACA,SAAS,kBAAkB,cAAc,SAAS,iBAAiB,UAAU,YAAY,CAAC;EAC1F,QAAQ,YAAY,UAAU,wBAAwB,uBAAuB,OAAO,CAAC;CACvF;CACA,MAAM,CAAC,cAAc,eAAe,IAAI,6BAA6B,QAAQ,YAAY,CAAC,CAAC;CAE3F,gBAAgB;EACd,WAAW,eAAe,YAAY,CAAC;CACzC,CAAC;CAED,MAAM,QAAQ,OAAO,IAAI;CAEzB,gBAAgB;EACd,IAAI,MAAM,SAAS;GACjB,MAAM,UAAU;GAEhB;EACF;EAEA,WAAW,UAAU,MAAM;CAC7B,GAAG,CAAC,YAAY,MAAM,CAAC;CAEvB,sBAAsB,WAAW,QAAQ,GAAG,CAAC,UAAU,CAAC;CAExD,MAAM,QAAQ,SAAS,WAAW,KAAK;CACvC,MAAM,QAAQ,uBAAO,IAAI,IAAyB,CAAC;CACnD,MAAM,SAAS,OAAmC,IAAI;CACtD,MAAM,UAAU,WAAW,eAAe;CAC1C,MAAM,SACJ,MAAM,UAAU,MAAM,KAAK,MAAM,QAAQ,IAAI,OAAO,MAAM,QAAQ,GAAG,IACjE,MAAM,SACN,MAAM,KAAK,MAAM,QAAQ,KACvB;EAAE,KAAK,MAAM,KAAK,EAAE,CAAC;EAAI,QAAQ,QAAQ,EAAE,CAAC;CAAK,IACjD;CAER,MAAM,aAAa,QAAwC;EACzD,IAAI,CAAC,KAAK;EACV,WAAW,MAAM,GAAG;EACpB,4BAA4B,MAAM,QAAQ,IAAI,QAAQ,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC;CACtE;CAEA,gBAAgB;EACd,IAAI,MAAM,SACR,4BAA4B,OAAO,SAAS,MAAM,CAAC;CAEvD,GAAG,CAAC,MAAM,OAAO,CAAC;CAElB,MAAM,iBAAiB,SAA6B,UAAsC;EACxF,IAAI,MAAM,WAAW,MAAM,eAAe;EAE1C,MAAM,SAAS,WAAW,cAAc,KAAK,OAAO,SAAS;EAE7D,IAAI,OAAO,SAAS;GAClB,MAAM,eAAe;GACrB,IAAI,CAAC,WAAW,MAAM,SAAS,UAAU,OAAO,KAAK;EACvD;CACF;CAEA,MAAM,mBAAmB,UAAsC;EAC7D,IACE,MAAM,QAAQ,YACb,MAAM,QAAQ,WAAW,CAAC,MAAM,YACjC,MAAM,QAAQ,OACd;GACA,MAAM,eAAe;GACrB,WACG,gBAAgB;IACf,KAAK,MAAM;IACX,UAAU,MAAM;IAChB,aAAa,MAAM,YAAY;GACjC,CAAC,CAAC,CACD,MAAM,WAAW,UAAU,OAAO,KAAK,CAAC;EAC7C;CACF;CAEA,MAAM,QAAQ,WAAW,MAAM;CAC/B,MAAM,oBAAoB,WAAW,kBAAkB;CACvD,MAAM,YAAY,SAAS,WAAW,IAAI,OAAO,GAAG,IAAI,KAAA;CACxD,MAAM,eAAe,SAAS,WAAW,OAAO,OAAO,MAAM,IAAI,KAAA;CAEjE,OACE,qBAAC,MAAD;EAAM,WAAU;EAAa,aAAU;EAAvC,UAAA,CACE,qBAAC,YAAD;GAAY,WAAU;GAAtB,UAAA;IACE,oBAAC,QAAD;KAAM,IAAI;KAAS,WAAU;KAC1B,UAAA,EAAE,6BAA6B,CAAC,GAAG,cAAc;IAC9C,CAAA;IACN,oBAAC,MAAD;KACE,WAAU;KACV,aAAU;KAET,UAAA,OAAO,QAAQ,KAAK,WAAW;MAC9B,MAAM,WAAW,MAAM,SAAS,OAAO;MACvC,MAAM,UAAU,WAAW,SAAS,UAAU;MAE9C,OACE,qBAAC,MAAD;OAEE,WAAU;OAFZ,UAAA;QAIE,qBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,oBAAC,QAAD;UAAM,WAAU;UAAuB,MAAM,OAAO;UACjD,UAAA,OAAO;SACJ,CAAA,GACN,oBAAC,QAAD;UAAM,WAAU;UAA2C,UAAA,OAAO;SAAW,CAAA,CAC1E;;QACL,oBAAC,OAAD;SACE,MAAK;SACL,iBAAe;SACf,iBAAe;SACf,iBAAe,KAAK,MAAM,UAAU,GAAG;SACvC,cAAY,EACV,sCACA;UACE,QAAQ,OAAO;UACf,YAAY,UAAU,cAAc;UACpC,OAAO,UAAU,SAAS;SAC5B,GACA,2CACF;SACA,WAAU;SAEV,UAAA,oBAAC,OAAD;UACE,WAAU;UACV,OAAO,EAAE,OAAO,GAAG,UAAU,IAAI,GAAG;SACrC,CAAA;QACE,CAAA;QACL,oBAAC,QAAD;SAAM,WAAU;SACb,UAAA,EACC,gCACA,EAAE,SAAS,UAAU,QAAQ,OAAO,EAAE,GACtC,qBACF;QACI,CAAA;OACJ;MArCG,GAAA,OAAO,IAqCV;KAER,CAAC;IACC,CAAA;IACJ,qBAAC,OAAD;KAAK,WAAU;KAAoC,aAAU;KAA7D,UAAA;MACE,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,YAAD;QACE,WAAU;QACV,eAAY;OACb,CAAA,GACD,oBAAC,OAAD;QACE,MAAK;QACL,OAAO,MAAM;QACb,WAAW,UAAU,WAAW,UAAU,MAAM,OAAO,KAAK;QAC5D,aAAa,EAAE,8BAA8B,CAAC,GAAG,wBAAwB;QACzE,cAAY,EAAE,oCAAoC,CAAC,GAAG,qBAAqB;QAC3E,WAAU;OACX,CAAA,CACE;;MACL,qBAAC,SAAD;OAAO,WAAU;OAAjB,UAAA,CACE,oBAAC,QAAD;QAAM,WAAU;QAAW,UAAA,EAAE,oCAAoC,CAAC,GAAG,MAAM;OAAQ,CAAA,GACnF,oBAAC,UAAD;QACE,WAAW;QACX,OAAO,MAAM;QACb,WAAW,UAAU,WAAW,UAAU,MAAM,OAAO,KAA0B;QAEhF,UAAA,OAAO,QAAQ,KAAK,WACnB,oBAAC,UAAD;SAAqB,OAAO;SACzB,UAAA,EAAE,+BAA+B,UAAU,CAAC,GAAG,MAAM;QAChD,GAFK,MAEL,CACT;OACK,CAAA,CACH;;MACP,qBAAC,SAAD;OAAO,WAAU;OAAjB,UAAA,CACE,oBAAC,QAAD;QAAM,WAAU;QAAW,UAAA,EAAE,mCAAmC,CAAC,GAAG,OAAO;OAAQ,CAAA,GACnF,qBAAC,UAAD;QACE,WAAW;QACX,OAAO,MAAM;QACb,WAAW,UAAU,WAAW,SAAS,MAAM,OAAO,KAAK;QAH7D,UAAA,CAKE,oBAAC,UAAD;SAAQ,OAAM;SAAI,UAAA,EAAE,kCAAkC,CAAC,GAAG,YAAY;QAAU,CAAA,GAC/E,OAAO,OAAO,KAAK,UAClB,oBAAC,UAAD;SAA0B,OAAO,MAAM;SACpC,UAAA,MAAM;QACD,GAFK,MAAM,KAEX,CACT,CACK;OACH,CAAA,CAAA;;MACP,qBAAC,cAAD;OAAc,KAAK;OAAnB,UAAA,CACE,oBAAC,qBAAD;QAAqB,SAAA;QACnB,UAAA,qBAAC,QAAD;SAAQ,MAAK;SAAS,SAAQ;SAAU,MAAK;SAAK,WAAU;SAA5D,UAAA,CACE,oBAAC,eAAD,EAAe,eAAY,OAAQ,CAAA,GAClC,EAAE,qCAAqC,CAAC,GAAG,WAAW,CACjD;;OACW,CAAA,GACrB,oBAAC,qBAAD;QAAqB,OAAM;QACxB,UAAA,OAAO,QAAQ,KAAK,WACnB,qBAAC,0BAAD;SAEE,SAAS,MAAM,eAAe,SAAS,OAAO,IAAI;SAClD,kBAAkB,YAChB,WAAW,aAAa,OAAO,MAAM,YAAY,IAAI;SAEvD,WAAW,UAAU,MAAM,eAAe;SAN5C,UAAA;UAQG,OAAO;UAAO;UACf,oBAAC,QAAD;WAAM,WAAU;WACb,UAAA,OAAO;UACJ,CAAA;SACkB;QAXnB,GAAA,OAAO,IAWY,CAC3B;OACkB,CAAA,CACT;;KACX;;IACJ,kBAAkB,SAAS,KAC1B,oBAAC,OAAD;KACE,WAAU;KACV,aAAU;KAET,UAAA,kBAAkB,KAAK,SACtB,qBAAC,OAAD;MAAgB,WAAU;MAA1B,UAAA;OACE,oBAAC,cAAD;QAAc,WAAU;QAAsB,eAAY;OAAQ,CAAA;OAClE,qBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA;SACG,EAAE,kCAAkC,CAAC,GAAG,eAAe;SAAE;SAAG;SAC5D,WAAW,OAAO,IAAI,CAAC,EAAE;QACtB;;OACN,oBAAC,QAAD;QACE,MAAK;QACL,MAAK;QACL,SAAQ;QACR,eAAe,KAAK,WAAW,UAAU,MAAM,QAAQ;QAEtD,UAAA,EAAE,kCAAkC,CAAC,GAAG,YAAY;OAC/C,CAAA;OACR,oBAAC,QAAD;QACE,MAAK;QACL,MAAK;QACL,eAAe,KAAK,WAAW,UAAU,MAAM,QAAQ;QAEtD,UAAA,EAAE,kCAAkC,CAAC,GAAG,6BAA6B;OAChE,CAAA;MACL;KArBK,GAAA,IAqBL,CACN;IACE,CAAA;GAEG;EACZ,CAAA,GAAA,qBAAC,aAAD;GAAa,WAAU;GAAvB,UAAA;IACE,oBAAC,KAAD;KAAG,IAAI;KAAQ,WAAU;KACtB,UAAA,EACC,gCACA,CAAC,GACD,wGACF;IACC,CAAA;IACF,MAAM,KAAK,WAAW,IACrB,oBAAC,KAAD;KAAG,WAAU;KACV,UAAA,EAAE,6BAA6B,CAAC,GAAG,oCAAoC;IACvE,CAAA,IAEH,oBAAC,OAAD;KAAK,WAAU;KAAoC,aAAU;KAC3D,UAAA,qBAAC,SAAD;MACE,MAAK;MACL,mBAAiB;MACjB,oBAAkB;MAClB,aAAW,MAAM,WAAW,KAAA;MAC5B,iBAAe,MAAM,QAAQ;MAC7B,WAAW,GAAG,kCAAkC,MAAM,WAAW,YAAY;MAC7E,aAAU;MAPZ,UAAA,CASE,oBAAC,SAAD;OAAO,WAAU;OACf,UAAA,qBAAC,MAAD,EAAA,UAAA,CACE,oBAAC,MAAD;QACE,OAAM;QACN,WAAU;QAET,UAAA,EAAE,2BAA2B,CAAC,GAAG,KAAK;OACrC,CAAA,GACH,QAAQ,KAAK,WACZ,oBAAC,MAAD;QAEE,OAAM;QACN,WAAU;QAEV,UAAA,qBAAC,QAAD;SAAM,WAAU;SAAhB,UAAA;UACE,oBAAC,QAAD;WAAM,MAAM,OAAO;WAAO,UAAA,OAAO;UAAY,CAAA;UAC7C,oBAAC,QAAD;WAAM,WAAU;WACb,UAAA,OAAO;UACJ,CAAA;UACL,OAAO,QACN,oBAAC,OAAD;WAAO,SAAQ;WACZ,UAAA,EAAE,4BAA4B,CAAC,GAAG,MAAM;UACpC,CAAA;UAER,CAAC,OAAO,YACP,oBAAC,OAAD;WAAO,SAAQ;WACZ,UAAA,EAAE,iCAAiC,CAAC,GAAG,WAAW;UAC9C,CAAA;SAEL;;OACJ,GApBG,OAAO,IAoBV,CACL,CACC,EAAA,CAAA;MACC,CAAA,GACP,oBAAC,SAAD,EAAA,UACG,MAAM,KAAK,KAAK,QACf,qBAAC,MAAD;OAAiB,WAAU;OAA2B,aAAU;OAAhE,UAAA,CACE,oBAAC,MAAD;QACE,OAAM;QACN,WAAU;QAEV,UAAA,oBAAC,SAAD,EAAc,IAAM,CAAA;OAClB,CAAA,GACH,QAAQ,KAAK,WAAW;QACvB,MAAM,MAAM;SAAE,KAAK,IAAI;SAAI,QAAQ,OAAO;QAAK;QAC/C,MAAM,OAAO,IAAI,MAAM,OAAO;QAC9B,MAAM,WAAW,QAAQ,QAAQ,IAAI,MAAM,OAAO,WAAW,OAAO;QACpE,MAAM,YACJ,MAAM,SAAS,QAAQ,IAAI,MAAM,MAAM,QAAQ,WAAW,OAAO;QAEnE,OACE,oBAAC,MAAD;SAEE,MAAM,YAAY;UAChB,IAAI,SAAS,MAAM,QAAQ,IAAI,QAAQ,GAAG,GAAG,OAAO;eAC/C,MAAM,QAAQ,OAAO,QAAQ,GAAG,CAAC;SACxC;SACA,MAAK;SACL,UAAU,YAAY,CAAC,YAAY,IAAI;SACvC,iBAAe;SACf,iBAAe,CAAC,WAAW,WAAW,GAAG,KAAK,KAAA;SAC9C,cACE,YACI,KAAA,IACA,EACE,4BACA;UAAE,KAAK,IAAI;UAAK,QAAQ,OAAO;SAAM,GACrC,sBACF;SAEN,aAAU;SACV,gBAAc,MAAM,QAAQ,KAAA,IAAY;SACxC,gBAAc,MAAM,UAAU,KAAK,KAAA;SACnC,eAAa,WAAW,KAAK,KAAA;SAC7B,UAAU,UACR,MAAM,WAAW,MAAM,iBAAiB,WAAW,MAAM,GAAG;SAE9D,eAAe;UACb,IAAI,CAAC,WAAW;WACd,WAAW,MAAM,GAAG;WACpB,IAAI,UAAU,WAAW,UAAU,GAAG;UACxC;SACF;SACA,qBAAqB,WAAW,UAAU,GAAG;SAC7C,WAAW,cAAc,GAAG;SAC5B,WAAW,GACT,gEACA,YAAY,kCACZ,WAAW,WAAW,GAAG,KAAK,aAChC;SAEC,UAAA,YACC,oBAAC,YAAD;UACO;UACG;UACR,OAAO,MAAM;UACb,OAAO,MAAM;UACb,QAAQ,MAAM;UACd,QAAQ,MAAM;UACd,OAAO,MAAM;UACb,WAAW;UACX,UAAU,UAAU,WAAW,SAAS,KAAK;UAC7C,SAAS,OAAO,UAAU,WAAW,aAAa,OAAO,KAAK;UAC9D,WAAW;UACX,cACE,KAAK,WAAW,KAAK,CAAC,CAAC,MAAM,UAAU,SAAS,UAAU,GAAG,CAAC;UAEhE,gBAAgB;WACd,WAAW,WAAW;WACtB,UAAU,GAAG;UACf;UACA,kBAAkB,WAAW,SAAS;SACvC,CAAA,IAED,oBAAC,WAAD;UAAmB;UAAc;SAAO,CAAA;QAExC,GAhEG,OAAO,IAgEV;OAER,CAAC,CACC;MAnFK,GAAA,IAAI,EAmFT,CACL,EACI,CAAA,CACF;;IACJ,CAAA;IAEP,qBAAC,OAAD;KACE,WAAU;KACV,aAAU;KAFZ,UAAA,CAIE,oBAAC,QAAD;MAAM,WAAU;MACb,UAAA,EACC,kCACA;OAAE,GAAG;OAAO,OAAO,UAAU,OAAO,MAAM,KAAK;MAAE,GACjD,qBACF;KACI,CAAA,GACN,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA;OACE,oBAAC,QAAD;QACE,MAAK;QACL,SAAQ;QACR,MAAK;QACL,cAAY,EAAE,gCAAgC,CAAC,GAAG,eAAe;QACjE,UAAU,MAAM,QAAQ,KAAK,MAAM;QACnC,eAAe,WAAW,SAAS,MAAM,OAAO,CAAC;QAEjD,UAAA,oBAAC,iBAAD;SAAiB,WAAU;SAAiB,eAAY;QAAQ,CAAA;OAC1D,CAAA;OACR,oBAAC,QAAD;QAAM,WAAU;QACb,UAAA,EACC,4BACA;SAAE,MAAM,MAAM;SAAM,OAAO,MAAM;QAAS,GAC1C,sBACF;OACI,CAAA;OACN,oBAAC,QAAD;QACE,MAAK;QACL,SAAQ;QACR,MAAK;QACL,cAAY,EAAE,4BAA4B,CAAC,GAAG,WAAW;QACzD,UAAU,MAAM,QAAQ,MAAM,YAAY,MAAM;QAChD,eAAe,WAAW,SAAS,MAAM,OAAO,CAAC;QAEjD,UAAA,oBAAC,kBAAD;SAAkB,WAAU;SAAiB,eAAY;QAAQ,CAAA;OAC3D,CAAA;MACL;KACF,CAAA,CAAA;;IACJ,aAAa,eACZ,qBAAC,WAAD;KACE,WAAU;KACV,aAAU;KACV,cAAY,EAAE,+BAA+B,CAAC,GAAG,SAAS;KAH5D,UAAA;MAKE,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,QAAD;QAAM,WAAU;QAA+B,UAAA,UAAU;OAAU,CAAA,GACnE,qBAAC,QAAD;QAAM,WAAU;QAAhB,UAAA,CAAwC,MAAG,aAAa,KAAY;OACjE,CAAA,CAAA;;MACJ,UAAU,gBAAgB,UAAU,aAAa,SAAS,KACzD,qBAAC,OAAD;OAAK,WAAU;OAAf,UAAA,CACE,oBAAC,QAAD;QAAM,WAAU;QACb,UAAA,EAAE,oCAAoC,CAAC,GAAG,cAAc;OACrD,CAAA,GACL,UAAU,aAAa,KAAK,gBAC3B,oBAAC,OAAD;QAAyB,SAAQ;QAAU,WAAU;QAClD,UAAA;OACI,GAFK,WAEL,CACR,CACE;;MAEN,UAAU,SAAS,UAAU,MAAM,SAAS,KAC3C,qBAAC,KAAD;OAAG,WAAU;OAAb,UAAA;QACG,EAAE,+BAA+B,CAAC,GAAG,SAAS;QAAE;QAAE;QACnD,oBAAC,QAAD;SAAM,WAAU;SAAqB,UAAA,UAAU,MAAM,KAAK,IAAI;QAAQ,CAAA;OACrE;;MAEJ,UAAU,MAAM,aAAa,KAAK,EAAE,UAAU,KAAK,YAClD,qBAAC,KAAD;OAAiB,WAAU;OAA3B,UAAA,CACE,oBAAC,mBAAD;QAAmB,WAAU;QAAyB,eAAY;OAAQ,CAAA,GACzE,OACA;MAHK,GAAA,OAGL,CACJ;MACA,UAAU,MAAM,aAAa,KAAK,EAAE,cACnC,qBAAC,OAAD;OACE,WAAU;OACV,aAAU;OAFZ,UAAA;QAIE,qBAAC,QAAD;SAAM,WAAU;SAAhB,UAAA,CACE,oBAAC,cAAD;UAAc,WAAU;UAAsB,eAAY;SAAQ,CAAA,GACjE,EAAE,kCAAkC,CAAC,GAAG,eAAe,CACpD;;QACN,oBAAC,KAAD;SACE,WAAU;SACV,KAAK,aAAa;SAClB,MAAM,aAAa;SAElB,UAAA,UAAU,MAAM,aAAa,KAAK,EAAE,YAAY;QAChD,CAAA;QACF,UAAU,MAAM,aAAa,KAAK,EAAE,YAAY,UAAU,KAAK,YAC9D,oBAAC,KAAD;SAAiB,WAAU;SACxB,UAAA;QACA,GAFK,OAEL,CACJ;QACA,aAAa,YACZ,qBAAC,OAAD;SAAK,WAAU;SAAf,UAAA,CACE,qBAAC,QAAD;UACE,MAAK;UACL,MAAK;UACL,eACE,KAAK,WAAW,OACd;WAAE,KAAK,UAAU;WAAI,QAAQ,aAAa;UAAK,GAC/C,QACF;UAPJ,UAAA,CAUE,oBAAC,WAAD,EAAW,eAAY,OAAQ,CAAA,GAC9B,EAAE,8BAA8B,CAAC,GAAG,QAAQ,CACvC;SACR,CAAA,GAAA,qBAAC,QAAD;UACE,MAAK;UACL,MAAK;UACL,SAAQ;UACR,eACE,KAAK,WAAW,OACd;WAAE,KAAK,UAAU;WAAI,QAAQ,aAAa;UAAK,GAC/C,QACF;UARJ,UAAA,CAWE,oBAAC,OAAD,EAAO,eAAY,OAAQ,CAAA,GAC1B,EAAE,8BAA8B,CAAC,GAAG,QAAQ,CACvC;SACL,CAAA,CAAA;;OAEJ;;MAEN,MAAM,SAAS,QAAQ,UAAU,MAAM,MAAM,QAAQ,WAAW,aAAa,OAC5E,qBAAC,OAAD;OAAK,WAAU;OAAsB,aAAU;OAA/C,UAAA;QACE,oBAAC,QAAD;SAAM,WAAU;SACb,UAAA,EAAE,+BAA+B,CAAC,GAAG,SAAS;QAC3C,CAAA;QACL,MAAM,QAAQ,SAAS,WAAW,KACjC,oBAAC,KAAD;SAAG,WAAU;SACV,UAAA,EAAE,qCAAqC,CAAC,GAAG,iBAAiB;QAC5D,CAAA;QAEL,oBAAC,MAAD;SAAI,WAAU;SACX,UAAA,MAAM,QAAQ,SAAS,KAAK,UAC3B,qBAAC,MAAD;UAAmB,WAAU;UAA7B,UAAA,CACE,qBAAC,QAAD;WAAM,WAAU;WAAhB,UAAA;YACG,EAAE,+BAA+B,MAAM,UAAU,CAAC,GAAG,MAAM,MAAM;YACjE,MAAM,OACH,MAAM,EAAE,0BAA0B,EAAE,MAAM,MAAM,KAAK,GAAG,UAAU,MAClE;YACH,MAAM,KAAK,MAAM,UAAU,SAAS,MAAM,EAAE,MAAM;WAC/C;UACN,CAAA,GAAA,oBAAC,KAAD;WACE,WAAU;WACV,KAAK,aAAa;WAClB,MAAM,aAAa;WAElB,UAAA,MAAM,OACL,oBAAC,MAAD;YAAI,WAAU;YACX,UAAA,EAAE,mCAAmC,CAAC,GAAG,OAAO;WAC/C,CAAA;UAEL,CAAA,CACD;SAnBK,GAAA,MAAM,EAmBX,CACL;QACC,CAAA;OACD;MAEL,CAAA,IAAA,oBAAC,OAAD,EAAA,UACE,qBAAC,QAAD;OACE,MAAK;OACL,MAAK;OACL,SAAQ;OACR,eACE,KAAK,WAAW,YAAY;QAAE,KAAK,UAAU;QAAI,QAAQ,aAAa;OAAK,CAAC;OALhF,UAAA,CAQE,oBAAC,aAAD,EAAa,eAAY,OAAQ,CAAA,GAChC,EAAE,oCAAoC,CAAC,GAAG,cAAc,CACnD;MACL,CAAA,EAAA,CAAA;KAEA;IACP,CAAA,IAAA;IACJ,oBAAC,OAAD;KAAK,WAAU;KAAU,MAAK;KAAS,aAAU;KAC9C,UAAA,MAAM;IACJ,CAAA;GACM;EACT,CAAA,CAAA;;AAEV;AAEA,SAAS,QAAQ,EAAE,OAAsC;CACvD,MAAM,IAAI,KAAK;CAEf,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACE,oBAAC,QAAD;GAAM,WAAU;GAA+B,UAAA,IAAI;EAAU,CAAA,GAC7D,qBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,oBAAC,QAAD;KAAM,WAAU;KAAiC,UAAA,IAAI;IAAiB,CAAA;IACrE,IAAI,aACH,oBAAC,OAAD;KAAO,SAAQ;KACZ,UAAA,EAAE,iCAAiC,CAAC,GAAG,aAAa;IAChD,CAAA;IAER,IAAI,UACH,oBAAC,OAAD;KAAO,SAAQ;KAAW,UAAA,EAAE,8BAA8B,CAAC,GAAG,QAAQ;IAAS,CAAA;GAE9E;EACF,CAAA,CAAA;;AAET;AAEA,SAAS,UAAU,EACjB,QACA,QAIC;CACD,MAAM,IAAI,KAAK;CAEf,OACE,qBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CACG,MAAM,QACL,oBAAC,QAAD;GACE,WAAU;GACV,KAAK,OAAO;GACZ,MAAM,OAAO;GAEZ,UAAA,KAAK;EACF,CAAA,IAEN,oBAAC,QAAD;GAAM,WAAU;GACb,UAAA,EAAE,+BAA+B,CAAC,GAAG,SAAS;EAC3C,CAAA,IAEN,MAAM,WAAW,MAAM,YAAY,MAAM,eACzC,qBAAC,QAAD;GAAM,WAAU;GAAhB,UAAA;IACG,KAAK,WACJ,oBAAC,OAAD;KAAO,SAAQ;KACZ,UAAA,EAAE,+BAA+B,CAAC,GAAG,sBAAsB;IACvD,CAAA;IAER,KAAK,YACJ,qBAAC,OAAD;KAAO,SAAQ;KAAc,OAAO,KAAK,SAAS,KAAK,IAAI;KAA3D,UAAA,CACE,oBAAC,mBAAD,EAAmB,eAAY,OAAQ,CAAA,GACtC,EAAE,gCAAgC,CAAC,GAAG,UAAU,CAC5C;;IAER,KAAK,cACJ,qBAAC,OAAD;KAAO,SAAQ;KAAf,UAAA,CACE,oBAAC,cAAD,EAAc,eAAY,OAAQ,CAAA,GACjC,EAAE,kCAAkC,CAAC,GAAG,eAAe,CACnD;;GAEL;EAEL,CAAA,CAAA;;AAET;AAEA,SAAS,WAAW,EAClB,KACA,QACA,OACA,OACA,QACA,QACA,OACA,WACA,SACA,QACA,WACA,QACA,UACA,cAgBC;CACD,MAAM,IAAI,KAAK;CACf,MAAM,UAAU,MAAM;CACtB,MAAM,QAAQ,EACZ,4BACA;EAAE,KAAK,IAAI;EAAK,QAAQ,OAAO;CAAM,GACrC,sBACF;CAEA,OACE,qBAAC,OAAD;EACE,WAAU;EACV,aAAU;EACV,UAAU,UAAU,MAAM,gBAAgB;EAH5C,UAAA;GAKG,QACC,MAAM,KAAK,MAAM,UACf,qBAAC,SAAD;IAAmB,WAAU;IAA7B,UAAA,CACE,oBAAC,QAAD;KAAM,WAAU;KACb,UAAA,EACC,mCACA,EAAE,SAAS,SAAS,UAAU,OAAO,QAAQ,CAAC,EAAE,GAChD,cACF;IACI,CAAA,GACN,oBAAC,UAAD;KACE,KAAK,UAAU,IAAI,YAAY,KAAA;KAC/B,OAAO;KACP,MAAM;KACN,KAAK,OAAO;KACZ,MAAM,OAAO;KACb,cAAY,GAAG,MAAM,IAAI,SAAS,UAAU,QAAQ,EAAE;KACtD,gBAAc,QAAQ,OAAO,KAAA;KAC7B,oBAAkB,QAAQ,UAAU,KAAA;KACpC,WAAU;KACV,WAAW,UAAU,OAAO,OAAO,MAAM,OAAO,KAAK;KAC1C;IACZ,CAAA,CACI;GArBK,GAAA,KAqBL,CACR,IAED,oBAAC,UAAD;IACE,KAAK;IACL,OAAO;IACP,MAAM,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,MAAM,MAAM,IAAI,CAAC,CAAC,MAAM,CAAC;IACvD,KAAK,OAAO;IACZ,MAAM,OAAO;IACb,cAAY;IACZ,gBAAc,QAAQ,OAAO,KAAA;IAC7B,oBAAkB,QAAQ,UAAU,KAAA;IACpC,WAAW,UAAU,QAAQ,MAAM,OAAO,KAAK;IACpC;GACZ,CAAA;GAEF,SACC,oBAAC,KAAD;IAAG,IAAI;IAAS,WAAU;IACvB,UAAA;GACA,CAAA;GAEL,qBAAC,OAAD;IAAK,WAAU;IAAf,UAAA;KACE,oBAAC,QAAD;MAAQ,MAAK;MAAS,MAAK;MAAK,UAAU;MAAQ,SAAS;MACxD,UAAA,SACG,EAAE,8BAA8B,CAAC,GAAG,SAAS,IAC7C,EAAE,4BAA4B,CAAC,GAAG,MAAM;KACtC,CAAA;KACR,oBAAC,QAAD;MAAQ,MAAK;MAAS,MAAK;MAAK,SAAQ;MAAQ,UAAU;MAAQ,SAAS;MACxE,UAAA,EAAE,8BAA8B,CAAC,GAAG,QAAQ;KACvC,CAAA;KACP,CAAC,OAAO,QACP,qBAAC,QAAD;MACE,MAAK;MACL,MAAK;MACL,SAAQ;MACR,WAAU;MACV,UAAU;MACV,SAAS;MANX,UAAA,CAQE,oBAAC,UAAD,EAAU,eAAY,OAAQ,CAAA,GAC7B,EAAE,iCAAiC,CAAC,GAAG,oBAAoB,CACtD;;IAEP;;EACF;;AAET"}
|
|
@@ -50,7 +50,7 @@ function StatCard({ stat }) {
|
|
|
50
50
|
children: [
|
|
51
51
|
/* @__PURE__ */ jsx("span", {
|
|
52
52
|
className: "text-muted-foreground text-sm font-medium",
|
|
53
|
-
children: stat.label
|
|
53
|
+
children: /* @__PURE__ */ jsx("bdi", { children: stat.label })
|
|
54
54
|
}),
|
|
55
55
|
(stat.value === null || stat.value === "") && stat.placeholder ? /* @__PURE__ */ jsx("span", {
|
|
56
56
|
className: "text-muted-foreground text-2xl font-semibold tracking-tight",
|
|
@@ -58,7 +58,7 @@ function StatCard({ stat }) {
|
|
|
58
58
|
children: stat.placeholder
|
|
59
59
|
}) : /* @__PURE__ */ jsx("span", {
|
|
60
60
|
className: cn("text-2xl font-semibold tracking-tight", stat.color && textColorClasses[stat.color]),
|
|
61
|
-
children: stat.value
|
|
61
|
+
children: /* @__PURE__ */ jsx("bdi", { children: stat.value })
|
|
62
62
|
}),
|
|
63
63
|
stat.description && /* @__PURE__ */ jsxs("span", {
|
|
64
64
|
className: cn("inline-flex items-center gap-1 text-xs", stat.descriptionColor ? textColorClasses[stat.descriptionColor] : "text-muted-foreground"),
|
|
@@ -67,7 +67,7 @@ function StatCard({ stat }) {
|
|
|
67
67
|
name: stat.descriptionIcon,
|
|
68
68
|
className: "size-3.5"
|
|
69
69
|
}),
|
|
70
|
-
stat.description,
|
|
70
|
+
/* @__PURE__ */ jsx("bdi", { children: stat.description }),
|
|
71
71
|
stat.descriptionIcon && stat.descriptionIconPosition === "after" && /* @__PURE__ */ jsx(Icon, {
|
|
72
72
|
name: stat.descriptionIcon,
|
|
73
73
|
className: "size-3.5"
|