@alfakit/react 0.1.2 → 0.1.6
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 +8 -0
- package/dist/components/actions/action-modals.js +4 -0
- package/dist/components/actions/action-modals.js.map +1 -1
- package/dist/components/ai/copilot.d.ts +3 -1
- package/dist/components/ai/copilot.d.ts.map +1 -1
- package/dist/components/ai/copilot.js +4 -1
- package/dist/components/ai/copilot.js.map +1 -1
- package/dist/components/cms/live-preview.js +3 -2
- package/dist/components/cms/live-preview.js.map +1 -1
- package/dist/components/cms/page.js +1 -1
- package/dist/components/fields/builder.js +15 -6
- package/dist/components/fields/builder.js.map +1 -1
- package/dist/components/fields/image-editor-dialog.js +4 -2
- package/dist/components/fields/image-editor-dialog.js.map +1 -1
- package/dist/components/fields/item-actions.js +10 -10
- package/dist/components/fields/item-actions.js.map +1 -1
- package/dist/components/fields/key-value.d.ts.map +1 -1
- package/dist/components/fields/key-value.js +3 -3
- package/dist/components/fields/key-value.js.map +1 -1
- package/dist/components/fields/modal-table-select.js +6 -5
- package/dist/components/fields/modal-table-select.js.map +1 -1
- package/dist/components/fields/repeater.js +2 -2
- package/dist/components/fields/repeater.js.map +1 -1
- package/dist/components/fields/rich-editor.js +2 -1
- package/dist/components/fields/rich-editor.js.map +1 -1
- package/dist/components/form/form-block.js +1 -1
- package/dist/components/form/form-block.js.map +1 -1
- package/dist/components/form/form-context.js +2 -2
- package/dist/components/form/form-context.js.map +1 -1
- package/dist/components/kanban/kanban.js +4 -3
- package/dist/components/kanban/kanban.js.map +1 -1
- package/dist/components/layout/section.js +2 -2
- package/dist/components/layout/section.js.map +1 -1
- package/dist/components/panel/app-sidebar.d.ts +2 -3
- package/dist/components/panel/app-sidebar.d.ts.map +1 -1
- package/dist/components/panel/app-sidebar.js +1 -24
- package/dist/components/panel/app-sidebar.js.map +1 -1
- package/dist/components/panel/brand.d.ts +10 -0
- package/dist/components/panel/brand.d.ts.map +1 -0
- package/dist/components/panel/brand.js +37 -0
- package/dist/components/panel/brand.js.map +1 -0
- package/dist/components/panel/index.d.ts +2 -1
- package/dist/components/panel/topbar.js +1 -1
- package/dist/components/realtime/live-records-pill.d.ts.map +1 -1
- package/dist/components/realtime/live-records-pill.js +6 -3
- package/dist/components/realtime/live-records-pill.js.map +1 -1
- package/dist/components/table/cells.d.ts +6 -0
- package/dist/components/table/cells.d.ts.map +1 -1
- package/dist/components/table/cells.js +6 -2
- package/dist/components/table/cells.js.map +1 -1
- package/dist/components/table/column-manager.js +8 -7
- package/dist/components/table/column-manager.js.map +1 -1
- package/dist/components/table/content-layout.js +2 -1
- package/dist/components/table/content-layout.js.map +1 -1
- package/dist/components/table/pagination.js +16 -13
- package/dist/components/table/pagination.js.map +1 -1
- package/dist/components/table/table-block.js +14 -4
- package/dist/components/table/table-block.js.map +1 -1
- package/dist/components/ui/dialog.d.ts.map +1 -1
- package/dist/components/ui/dialog.js +3 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/popover.d.ts.map +1 -1
- package/dist/components/ui/popover.js +3 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/sheet.d.ts.map +1 -1
- package/dist/components/ui/sheet.js +3 -1
- package/dist/components/ui/sheet.js.map +1 -1
- package/dist/components/ui/tooltip.js +3 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/context.d.ts +5 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +5 -1
- package/dist/context.js.map +1 -1
- package/dist/create-alfakit.d.ts.map +1 -1
- package/dist/create-alfakit.js +8 -5
- package/dist/create-alfakit.js.map +1 -1
- package/dist/eject-manifest.json +16 -3
- package/dist/index.d.ts +6 -3
- package/dist/index.js +24 -21
- package/dist/layouts/app-layout.d.ts.map +1 -1
- package/dist/layouts/app-layout.js +2 -29
- package/dist/layouts/app-layout.js.map +1 -1
- package/dist/layouts/auth-layout.js +2 -2
- package/dist/layouts/auth-layout.js.map +1 -1
- package/dist/layouts/fullscreen-layout.js +1 -1
- package/dist/layouts/fullscreen-layout.js.map +1 -1
- package/dist/layouts/lazy-layouts.js +15 -0
- package/dist/layouts/lazy-layouts.js.map +1 -0
- package/dist/layouts/max-widths.js +34 -0
- package/dist/layouts/max-widths.js.map +1 -0
- package/dist/layouts/panel-layouts.d.ts +10 -0
- package/dist/layouts/panel-layouts.d.ts.map +1 -0
- package/dist/layouts/panel-layouts.js +20 -0
- package/dist/layouts/panel-layouts.js.map +1 -0
- package/dist/layouts/top-nav-layout.js +2 -2
- package/dist/layouts/top-nav-layout.js.map +1 -1
- package/dist/pages/page.d.ts.map +1 -1
- package/dist/pages/page.js +12 -7
- package/dist/pages/page.js.map +1 -1
- package/dist/standalone/alfakit-component.js +11 -6
- package/dist/standalone/alfakit-component.js.map +1 -1
- package/dist/standalone.css +7 -0
- package/dist/starter-kit/dashboard-widgets.d.ts +33 -0
- package/dist/starter-kit/dashboard-widgets.d.ts.map +1 -0
- package/dist/starter-kit/dashboard-widgets.js +53 -0
- package/dist/starter-kit/dashboard-widgets.js.map +1 -0
- package/dist/starter-kit/impersonation-banner.js +34 -0
- package/dist/starter-kit/impersonation-banner.js.map +1 -0
- package/dist/starter-kit/index.d.ts +3 -2
- package/dist/starter-kit/index.js +3 -2
- package/dist/starter-kit/starter-kit.d.ts +6 -0
- package/dist/starter-kit/starter-kit.d.ts.map +1 -1
- package/dist/starter-kit/starter-kit.js +18 -7
- package/dist/starter-kit/starter-kit.js.map +1 -1
- package/dist/starter-kit/widgets-loaded.js +20 -4
- package/dist/starter-kit/widgets-loaded.js.map +1 -1
- package/dist/starter-kit/widgets-triggers.js +34 -3
- package/dist/starter-kit/widgets-triggers.js.map +1 -1
- package/dist/starter-kit/widgets.d.ts +13 -5
- package/dist/starter-kit/widgets.d.ts.map +1 -1
- package/dist/starter-kit/widgets.js +56 -18
- package/dist/starter-kit/widgets.js.map +1 -1
- package/dist/styles.css +15 -2
- package/dist/themes.css +6 -1
- package/package.json +2 -2
- package/src/components/actions/action-modals.tsx +10 -0
- package/src/components/ai/copilot.tsx +14 -1
- package/src/components/cms/live-preview.tsx +5 -2
- package/src/components/fields/builder.tsx +14 -5
- package/src/components/fields/image-editor-dialog.tsx +7 -2
- package/src/components/fields/item-actions.tsx +32 -9
- package/src/components/fields/key-value.tsx +3 -2
- package/src/components/fields/modal-table-select.tsx +8 -3
- package/src/components/fields/repeater.tsx +2 -3
- package/src/components/fields/rich-editor.tsx +3 -0
- package/src/components/form/form-block.tsx +2 -1
- package/src/components/form/form-context.tsx +2 -2
- package/src/components/kanban/kanban.tsx +16 -3
- package/src/components/layout/section.tsx +2 -2
- package/src/components/panel/app-sidebar.tsx +2 -38
- package/src/components/panel/brand.tsx +46 -0
- package/src/components/realtime/live-records-pill.tsx +4 -3
- package/src/components/table/cells.tsx +9 -2
- package/src/components/table/column-manager.tsx +12 -10
- package/src/components/table/content-layout.tsx +5 -1
- package/src/components/table/pagination.tsx +16 -9
- package/src/components/table/table-block.tsx +25 -12
- package/src/components/ui/dialog.tsx +5 -1
- package/src/components/ui/popover.tsx +5 -1
- package/src/components/ui/sheet.tsx +5 -1
- package/src/components/ui/tooltip.tsx +5 -1
- package/src/context.tsx +10 -0
- package/src/create-alfakit.tsx +11 -2
- package/src/index.ts +2 -0
- package/src/layouts/app-layout.tsx +4 -28
- package/src/layouts/auth-layout.tsx +2 -2
- package/src/layouts/fullscreen-layout.tsx +1 -1
- package/src/layouts/lazy-layouts.ts +14 -0
- package/src/layouts/max-widths.ts +29 -0
- package/src/layouts/panel-layouts.ts +17 -0
- package/src/layouts/top-nav-layout.tsx +2 -2
- package/src/pages/page.tsx +21 -12
- package/src/standalone/alfakit-component.tsx +18 -7
- package/src/starter-kit/dashboard-widgets.tsx +78 -0
- package/src/starter-kit/impersonation-banner.tsx +48 -0
- package/src/starter-kit/index.ts +2 -0
- package/src/starter-kit/starter-kit.tsx +31 -4
- package/src/starter-kit/widgets-loaded.tsx +22 -0
- package/src/starter-kit/widgets-triggers.tsx +36 -1
- package/src/starter-kit/widgets.tsx +95 -20
- package/src/styles.css +15 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"repeater.js","names":[],"sources":["../../../src/components/fields/repeater.tsx"],"sourcesContent":["import type { RepeaterSchema, SchemaComponent } from '@alfakit/core'\nimport {\n VIRTUAL_ESTIMATES,\n appendRepeaterItem,\n cloneIndexedState,\n cloneRepeaterItem,\n columnSpanClasses,\n fieldIds,\n gridColumnsClasses,\n isDeferredPlaceholder,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n replacePlaceholder,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { useState } from 'react'\nimport { useFormatter } from '../../context'\nimport { useScrollToErroredItem, useVirtualList } from '../../hooks/use-virtual-list'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { DeferredSchema } from '../layout/deferred'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Collapsible, CollapsibleContent } from '../ui/collapsible'\nimport {\n AddActionRow,\n CollapseAllActions,\n ItemActionButton,\n ItemHeaderActions,\n useItemActionFallbacks,\n useItemActions,\n useItemMoves,\n useRunItemAction,\n} from './item-actions'\nimport { RepeaterTable } from './repeater-table'\n\n/**\n * Items use the per-index schema from the server when available (so closures see item\n * state) and fall back to the template for freshly added items.\n */\n/**\n * `deferLoading()`: a skeleton until the field scrolls into view, then the field with its\n * items (loaded from the server).\n */\nexport function RepeaterField({ component }: { component: RepeaterSchema }) {\n if (!isDeferredPlaceholder(component)) return <RepeaterFieldContent component={component} />\n\n return (\n <DeferredSchema component={component}>\n {(loaded) => <RepeaterFieldContent component={loaded} />}\n </DeferredSchema>\n )\n}\n\nfunction RepeaterFieldContent({ component }: { component: RepeaterSchema }) {\n const formatter = useFormatter()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<unknown[] | null>(\n component,\n )\n const context = useFieldActionContext(component)\n const runItemAction = useRunItemAction(context)\n const fallbacks = useItemActionFallbacks(component.addActionLabel)\n const resolve = useItemActions(component, fallbacks)\n // Read-only shows the items but hides add, delete, clone and reorder controls.\n const locked = disabled || readOnly\n const items = value ?? []\n const [collapsed, setCollapsed] = useState<Record<number, boolean>>({})\n const [dragIndex, setDragIndex] = useState<number | null>(null)\n // Long lists render only the visible items (plus a few around them); a multi-column\n // `grid()` is windowed by whole rows, counting its columns from the rendered grid.\n const virtual = useVirtualList(\n items.length,\n virtualizeOptions(\n component.virtualize,\n VIRTUAL_ESTIMATES.item,\n 12,\n component.grid ? 'auto' : 1,\n ),\n )\n const { range, indexes, style, attach, item } = virtual\n useScrollToErroredItem(virtual.list, range.active, component.statePath)\n\n // Collapsed flags are keyed by index, so they move with their item.\n const moveItem = (from: number, to: number) => {\n setValue(moveRepeaterItem(items, from, to))\n setCollapsed((state) => moveIndexedState(state, from, to))\n }\n const removeItem = (index: number) => {\n setValue(removeRepeaterItem(items, index))\n setCollapsed((state) => removeIndexedState(state, index))\n }\n const cloneItem = (index: number) => {\n setValue(cloneRepeaterItem(items, index))\n setCollapsed((state) => cloneIndexedState(state, index))\n }\n // Move buttons: announced, one at a time, focus kept on the moved item.\n const {\n moveWithButton,\n region: moveRegion,\n containerRef: moveContainerRef,\n } = useItemMoves(value, moveItem, (index) => component.items[index]?.label, items.length)\n // A drop runs the reorder action: in the browser, or on the server when customised.\n const dropItem = (from: number, to: number) =>\n runItemAction(resolve('reorder'), { items: reorderedItemKeys(items.length, from, to) }, () =>\n moveItem(from, to),\n )\n const setAllCollapsed = (value: boolean) =>\n setCollapsed(Object.fromEntries(items.map((_, index) => [index, value])))\n const addItem = () =>\n setValue(\n appendRepeaterItem(\n items,\n component.simple\n ? ({ [Object.keys(component.templateState)[0] ?? 'value']: null } as Record<\n string,\n unknown\n >)\n : component.templateState,\n ),\n )\n\n const schemaFor = (index: number): SchemaComponent[] =>\n component.items[index]?.schema ??\n replacePlaceholder(component.template, '__INDEX__', String(index))\n\n const canAdd =\n component.addable &&\n !locked &&\n (component.maxItems == null || items.length < component.maxItems)\n const canDelete =\n component.deletable &&\n !locked &&\n (component.minItems == null || items.length > component.minItems)\n const draggable =\n component.reorderable &&\n component.reorderableWithDragAndDrop !== false &&\n !locked &&\n resolve('reorder') !== null\n const buttons = component.reorderable && component.reorderableWithButtons !== false && !locked\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div ref={moveContainerRef} className=\"flex flex-col gap-3\">\n {moveRegion}\n {component.table?.length && !component.simple ? (\n <RepeaterTable\n id={id}\n columns={component.table}\n compact={component.compact}\n count={items.length}\n prefixFor={(index) => `${component.statePath}.${index}.`}\n schemaFor={schemaFor}\n labelledBy={component.label ? fieldIds(id).label : undefined}\n describedBy={aria['aria-describedby']}\n reorderable={buttons}\n cloneable={Boolean(component.cloneable) && canAdd}\n deletable={canDelete}\n onMove={moveItem}\n onClone={cloneItem}\n onRemove={removeItem}\n virtual={virtual}\n context={context}\n resolve={(name, index) => resolve(name, component.items[index])}\n extraActionsFor={(index) => component.items[index]?.actions}\n />\n ) : (\n <>\n {component.collapsible && !component.simple && items.length > 0 && (\n <CollapseAllActions\n context={context}\n resolve={(name) => resolve(name)}\n onCollapseAll={() => setAllCollapsed(true)}\n onExpandAll={() => setAllCollapsed(false)}\n />\n )}\n <ul\n id={id}\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n ref={attach}\n style={style}\n className={cn('grid gap-3', component.grid && gridColumnsClasses(component.grid))}\n >\n {indexes.map((index) => {\n const isCollapsed = collapsed[index] ?? component.collapsed ?? false\n const label = component.items[index]?.label ?? null\n const serverItem = component.items[index]\n\n return (\n <li\n key={index}\n ref={item}\n data-index={index}\n aria-setsize={range.active ? items.length : undefined}\n aria-posinset={range.active ? index + 1 : undefined}\n draggable={draggable}\n onDragStart={() => setDragIndex(index)}\n onDragOver={(event) => draggable && event.preventDefault()}\n onDrop={() => {\n if (dragIndex !== null && dragIndex !== index) dropItem(dragIndex, index)\n setDragIndex(null)\n }}\n onDragEnd={() => setDragIndex(null)}\n className={cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n component.simple && 'border-0 shadow-none',\n )}\n >\n <Collapsible open={!isCollapsed}>\n {!component.simple && (\n <div\n className=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n {draggable && (\n <span\n className=\"text-muted-foreground cursor-grab\"\n title={resolve('reorder', serverItem)?.label ?? undefined}\n aria-hidden=\"true\"\n >\n <Icon\n name={resolve('reorder', serverItem)?.icon ?? 'grip-vertical'}\n />\n </span>\n )}\n {component.itemNumbers && (\n <span className=\"text-muted-foreground text-xs tabular-nums\">\n {formatter.number(index + 1)}\n </span>\n )}\n {label && (\n <span dir=\"auto\" className=\"min-w-0 truncate text-sm font-medium\">\n {label}\n </span>\n )}\n <span className=\"flex-1\" />\n <ItemHeaderActions\n context={context}\n itemKey={String(index)}\n index={index}\n count={items.length}\n resolve={(name) => resolve(name, serverItem)}\n extraActions={locked ? undefined : serverItem?.actions}\n buttons={buttons}\n canClone={Boolean(component.cloneable) && canAdd}\n canDelete={canDelete}\n collapsible={Boolean(component.collapsible)}\n collapsed={isCollapsed}\n onMove={moveWithButton}\n onClone={() => cloneItem(index)}\n onDelete={() => removeItem(index)}\n onToggle={() =>\n setCollapsed((state) => ({ ...state, [index]: !isCollapsed }))\n }\n />\n </div>\n )}\n <CollapsibleContent>\n <div className={cn('flex gap-2', component.simple ? 'items-start' : 'p-4')}>\n <div className=\"flex-1\">\n <SchemaRenderer\n components={schemaFor(index)}\n columns={\n component.simple\n ? { default: 1 }\n : (component.columns ?? { default: 1, lg: 2 })\n }\n className={cn(\n component.simple && 'gap-0',\n '[&_[data-slot=field]_[data-slot=label]]:hidden',\n !component.simple && '[&_[data-slot=field]_[data-slot=label]]:flex',\n )}\n />\n </div>\n {component.simple && canDelete && (\n <ItemActionButton\n action={resolve('delete', serverItem)}\n context={context}\n args={{ item: String(index) }}\n size=\"default\"\n className=\"text-muted-foreground\"\n onClient={() => removeItem(index)}\n />\n )}\n </div>\n </CollapsibleContent>\n </Collapsible>\n </li>\n )\n })}\n </ul>\n </>\n )}\n {canAdd && (\n <AddActionRow alignment={component.addActionAlignment}>\n <ItemActionButton\n action={resolve('add')}\n context={context}\n size=\"sm\"\n onClient={addItem}\n />\n </AddActionRow>\n )}\n </div>\n </FieldWrapper>\n )\n}\n\nexport function columnSpanOf(component: SchemaComponent): string {\n return columnSpanClasses(component.columnSpan)\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"repeater.js","names":[],"sources":["../../../src/components/fields/repeater.tsx"],"sourcesContent":["import type { RepeaterSchema, SchemaComponent } from '@alfakit/core'\nimport {\n VIRTUAL_ESTIMATES,\n appendRepeaterItem,\n cloneIndexedState,\n cloneRepeaterItem,\n columnSpanClasses,\n fieldIds,\n gridColumnsClasses,\n isDeferredPlaceholder,\n moveIndexedState,\n moveRepeaterItem,\n removeIndexedState,\n removeRepeaterItem,\n reorderedItemKeys,\n templateForIndex,\n virtualizeOptions,\n} from '@alfakit/core'\nimport { useState } from 'react'\nimport { useFormatter } from '../../context'\nimport { useScrollToErroredItem, useVirtualList } from '../../hooks/use-virtual-list'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { DeferredSchema } from '../layout/deferred'\nimport { useFieldActionContext } from '../schema/field-actions'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { Collapsible, CollapsibleContent } from '../ui/collapsible'\nimport {\n AddActionRow,\n CollapseAllActions,\n ItemActionButton,\n ItemHeaderActions,\n useItemActionFallbacks,\n useItemActions,\n useItemMoves,\n useRunItemAction,\n} from './item-actions'\nimport { RepeaterTable } from './repeater-table'\n\n/**\n * Items use the per-index schema from the server when available (so closures see item\n * state) and fall back to the template for freshly added items.\n */\n/**\n * `deferLoading()`: a skeleton until the field scrolls into view, then the field with its\n * items (loaded from the server).\n */\nexport function RepeaterField({ component }: { component: RepeaterSchema }) {\n if (!isDeferredPlaceholder(component)) return <RepeaterFieldContent component={component} />\n\n return (\n <DeferredSchema component={component}>\n {(loaded) => <RepeaterFieldContent component={loaded} />}\n </DeferredSchema>\n )\n}\n\nfunction RepeaterFieldContent({ component }: { component: RepeaterSchema }) {\n const formatter = useFormatter()\n const { value, setValue, error, id, disabled, readOnly, aria } = useField<unknown[] | null>(\n component,\n )\n const context = useFieldActionContext(component)\n const runItemAction = useRunItemAction(context)\n const fallbacks = useItemActionFallbacks(component.addActionLabel)\n const resolve = useItemActions(component, fallbacks)\n // Read-only shows the items but hides add, delete, clone and reorder controls.\n const locked = disabled || readOnly\n const items = value ?? []\n const [collapsed, setCollapsed] = useState<Record<number, boolean>>({})\n const [dragIndex, setDragIndex] = useState<number | null>(null)\n // Long lists render only the visible items (plus a few around them); a multi-column\n // `grid()` is windowed by whole rows, counting its columns from the rendered grid.\n const virtual = useVirtualList(\n items.length,\n virtualizeOptions(\n component.virtualize,\n VIRTUAL_ESTIMATES.item,\n 12,\n component.grid ? 'auto' : 1,\n ),\n )\n const { range, indexes, style, attach, item } = virtual\n useScrollToErroredItem(virtual.list, range.active, component.statePath)\n\n // Collapsed flags are keyed by index, so they move with their item.\n const moveItem = (from: number, to: number) => {\n setValue(moveRepeaterItem(items, from, to))\n setCollapsed((state) => moveIndexedState(state, from, to))\n }\n const removeItem = (index: number) => {\n setValue(removeRepeaterItem(items, index))\n setCollapsed((state) => removeIndexedState(state, index))\n }\n const cloneItem = (index: number) => {\n setValue(cloneRepeaterItem(items, index))\n setCollapsed((state) => cloneIndexedState(state, index))\n }\n // Move buttons: announced, one at a time, focus kept on the moved item.\n const {\n moveWithButton,\n region: moveRegion,\n containerRef: moveContainerRef,\n } = useItemMoves(value, moveItem, (index) => component.items[index]?.label, items.length)\n // A drop runs the reorder action: in the browser, or on the server when customised.\n const dropItem = (from: number, to: number) =>\n runItemAction(resolve('reorder'), { items: reorderedItemKeys(items.length, from, to) }, () =>\n moveItem(from, to),\n )\n const setAllCollapsed = (value: boolean) =>\n setCollapsed(Object.fromEntries(items.map((_, index) => [index, value])))\n const addItem = () =>\n setValue(\n appendRepeaterItem(\n items,\n component.simple\n ? ({ [Object.keys(component.templateState)[0] ?? 'value']: null } as Record<\n string,\n unknown\n >)\n : component.templateState,\n ),\n )\n\n const schemaFor = (index: number): SchemaComponent[] =>\n component.items[index]?.schema ?? templateForIndex(component.template, index)\n\n const canAdd =\n component.addable &&\n !locked &&\n (component.maxItems == null || items.length < component.maxItems)\n const canDelete =\n component.deletable &&\n !locked &&\n (component.minItems == null || items.length > component.minItems)\n const draggable =\n component.reorderable &&\n component.reorderableWithDragAndDrop !== false &&\n !locked &&\n resolve('reorder') !== null\n const buttons = component.reorderable && component.reorderableWithButtons !== false && !locked\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div ref={moveContainerRef} className=\"flex flex-col gap-3\">\n {moveRegion}\n {component.table?.length && !component.simple ? (\n <RepeaterTable\n id={id}\n columns={component.table}\n compact={component.compact}\n count={items.length}\n prefixFor={(index) => `${component.statePath}.${index}.`}\n schemaFor={schemaFor}\n labelledBy={component.label ? fieldIds(id).label : undefined}\n describedBy={aria['aria-describedby']}\n reorderable={buttons}\n cloneable={Boolean(component.cloneable) && canAdd}\n deletable={canDelete}\n onMove={moveItem}\n onClone={cloneItem}\n onRemove={removeItem}\n virtual={virtual}\n context={context}\n resolve={(name, index) => resolve(name, component.items[index])}\n extraActionsFor={(index) => component.items[index]?.actions}\n />\n ) : (\n <>\n {component.collapsible && !component.simple && items.length > 0 && (\n <CollapseAllActions\n context={context}\n resolve={(name) => resolve(name)}\n onCollapseAll={() => setAllCollapsed(true)}\n onExpandAll={() => setAllCollapsed(false)}\n />\n )}\n <ul\n id={id}\n aria-labelledby={component.label ? fieldIds(id).label : undefined}\n aria-describedby={aria['aria-describedby']}\n ref={attach}\n style={style}\n className={cn('grid gap-3', component.grid && gridColumnsClasses(component.grid))}\n >\n {indexes.map((index) => {\n const isCollapsed = collapsed[index] ?? component.collapsed ?? false\n const label = component.items[index]?.label ?? null\n const serverItem = component.items[index]\n\n return (\n <li\n key={index}\n ref={item}\n data-index={index}\n aria-setsize={range.active ? items.length : undefined}\n aria-posinset={range.active ? index + 1 : undefined}\n draggable={draggable}\n onDragStart={() => setDragIndex(index)}\n onDragOver={(event) => draggable && event.preventDefault()}\n onDrop={() => {\n if (dragIndex !== null && dragIndex !== index) dropItem(dragIndex, index)\n setDragIndex(null)\n }}\n onDragEnd={() => setDragIndex(null)}\n className={cn(\n 'bg-card min-w-0 rounded-lg border shadow-xs',\n dragIndex === index && 'opacity-50',\n component.simple && 'border-0 shadow-none',\n )}\n >\n <Collapsible open={!isCollapsed}>\n {!component.simple && (\n <div\n className=\"flex flex-wrap items-center gap-1 border-b px-2 py-1.5\"\n data-slot=\"item-header\"\n >\n {draggable && (\n <span\n className=\"text-muted-foreground cursor-grab\"\n title={resolve('reorder', serverItem)?.label ?? undefined}\n aria-hidden=\"true\"\n >\n <Icon\n name={resolve('reorder', serverItem)?.icon ?? 'grip-vertical'}\n />\n </span>\n )}\n {component.itemNumbers && (\n <span className=\"text-muted-foreground text-xs tabular-nums\">\n {formatter.number(index + 1)}\n </span>\n )}\n {label && (\n <span dir=\"auto\" className=\"min-w-0 truncate text-sm font-medium\">\n {label}\n </span>\n )}\n <span className=\"flex-1\" />\n <ItemHeaderActions\n context={context}\n itemKey={String(index)}\n index={index}\n count={items.length}\n resolve={(name) => resolve(name, serverItem)}\n extraActions={locked ? undefined : serverItem?.actions}\n buttons={buttons}\n canClone={Boolean(component.cloneable) && canAdd}\n canDelete={canDelete}\n collapsible={Boolean(component.collapsible)}\n collapsed={isCollapsed}\n onMove={moveWithButton}\n onClone={() => cloneItem(index)}\n onDelete={() => removeItem(index)}\n onToggle={() =>\n setCollapsed((state) => ({ ...state, [index]: !isCollapsed }))\n }\n />\n </div>\n )}\n <CollapsibleContent>\n <div className={cn('flex gap-2', component.simple ? 'items-start' : 'p-4')}>\n <div className=\"flex-1\">\n <SchemaRenderer\n components={schemaFor(index)}\n columns={\n component.simple\n ? { default: 1 }\n : (component.columns ?? { default: 1, lg: 2 })\n }\n className={cn(\n component.simple && 'gap-0',\n '[&_[data-slot=field]_[data-slot=label]]:hidden',\n !component.simple && '[&_[data-slot=field]_[data-slot=label]]:flex',\n )}\n />\n </div>\n {component.simple && canDelete && (\n <ItemActionButton\n action={resolve('delete', serverItem)}\n context={context}\n args={{ item: String(index) }}\n size=\"default\"\n className=\"text-muted-foreground\"\n onClient={() => removeItem(index)}\n />\n )}\n </div>\n </CollapsibleContent>\n </Collapsible>\n </li>\n )\n })}\n </ul>\n </>\n )}\n {canAdd && (\n <AddActionRow alignment={component.addActionAlignment}>\n <ItemActionButton\n action={resolve('add')}\n context={context}\n size=\"sm\"\n onClient={addItem}\n />\n </AddActionRow>\n )}\n </div>\n </FieldWrapper>\n )\n}\n\nexport function columnSpanOf(component: SchemaComponent): string {\n return columnSpanClasses(component.columnSpan)\n}\n"],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { Skeleton } from "../ui/skeleton.js";
|
|
|
7
7
|
import { FieldWrapper } from "../schema/field-wrapper.js";
|
|
8
8
|
import { Separator } from "../ui/separator.js";
|
|
9
9
|
import { ExtensionToolbarButton, RichEditorSidePanel, SuggestionPopup, ToolbarGroupButton, stickyStyle, useRichEditorExtensions } from "./rich-editor-extensions.js";
|
|
10
|
-
import { EditorAttachments, TABLE_TOOLBAR_BUTTONS, allToolbarButtons, attachToolbarKeyboard, commandLabel, detailsNodeConfigs, fieldIds, fileAttachmentError, floatingToolbarStyle, focusToolbar, gridNodeConfigs, highlightMarkConfig, htmlToMarkdown, isToolbarGroup, isToolbarShortcut, leadExtensionConfig, linkExtensionOptions, loadRichEditorExtensions, panelUploadOptions, resizableImageAttributes, resizableImageOptions, resolveFloatingToolbar, resolveStickyOffset, setTextColor, showsTableToolbar, smallMarkConfig, subscriptMarkConfig, superscriptMarkConfig, textAlignExtensionConfig, textColorMarkConfig, toEditorDocument, toolbarButtonLabel, toolbarCommands, toolbarPanel, uploadTemporaryFile } from "@alfakit/core";
|
|
10
|
+
import { EditorAttachments, TABLE_TOOLBAR_BUTTONS, allToolbarButtons, attachToolbarKeyboard, commandLabel, detailsNodeConfigs, documentNonce, fieldIds, fileAttachmentError, floatingToolbarStyle, focusToolbar, gridNodeConfigs, highlightMarkConfig, htmlToMarkdown, isToolbarGroup, isToolbarShortcut, leadExtensionConfig, linkExtensionOptions, loadRichEditorExtensions, panelUploadOptions, resizableImageAttributes, resizableImageOptions, resolveFloatingToolbar, resolveStickyOffset, setTextColor, showsTableToolbar, smallMarkConfig, subscriptMarkConfig, superscriptMarkConfig, textAlignExtensionConfig, textColorMarkConfig, toEditorDocument, toolbarButtonLabel, toolbarCommands, toolbarPanel, uploadTemporaryFile } from "@alfakit/core";
|
|
11
11
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
12
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
13
13
|
import { ALargeSmallIcon, BaselineIcon, BetweenHorizontalEndIcon, BetweenHorizontalStartIcon, BetweenVerticalEndIcon, BetweenVerticalStartIcon, BoldIcon, CodeIcon, Columns2Icon, Columns3Icon, EraserIcon, Grid2x2XIcon, Heading1Icon, Heading2Icon, Heading3Icon, Heading4Icon, Heading5Icon, Heading6Icon, HighlighterIcon, ImageIcon, ItalicIcon, LinkIcon, ListCollapseIcon, ListIcon, ListOrderedIcon, Minus, PanelTopIcon, PilcrowIcon, QuoteIcon, RedoIcon, RemoveFormattingIcon, Rows3Icon, SquareCodeIcon, SquareXIcon, StrikethroughIcon, SubscriptIcon, SuperscriptIcon, TableCellsMergeIcon, TableCellsSplitIcon, TableIcon, TablePropertiesIcon, TextAlignCenterIcon, TextAlignEndIcon, TextAlignJustifyIcon, TextAlignStartIcon, TypeIcon, UnderlineIcon, UndoIcon } from "lucide-react";
|
|
@@ -227,6 +227,7 @@ function RichEditorInput({ component, plugins }) {
|
|
|
227
227
|
};
|
|
228
228
|
useEffect(() => () => attachments.destroy(), [attachments]);
|
|
229
229
|
const editor = useEditor({
|
|
230
|
+
injectNonce: documentNonce(),
|
|
230
231
|
extensions: [
|
|
231
232
|
StarterKit.configure({ link: false }),
|
|
232
233
|
Link.configure(linkExtensionOptions(component.linkProtocols)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rich-editor.js","names":[],"sources":["../../../src/components/fields/rich-editor.tsx"],"sourcesContent":["import type {\n LoadedRichEditorExtensions,\n RichEditorPanel,\n RichEditorSchema,\n ToolCommandContext,\n} from '@alfakit/core'\nimport {\n allToolbarButtons,\n attachToolbarKeyboard,\n commandLabel,\n detailsNodeConfigs,\n EditorAttachments,\n fieldIds,\n fileAttachmentError,\n floatingToolbarStyle,\n focusToolbar,\n gridNodeConfigs,\n highlightMarkConfig,\n htmlToMarkdown,\n isToolbarGroup,\n isToolbarShortcut,\n leadExtensionConfig,\n linkExtensionOptions,\n loadRichEditorExtensions,\n panelUploadOptions,\n resizableImageAttributes,\n resizableImageOptions,\n resolveFloatingToolbar,\n resolveStickyOffset,\n setTextColor,\n showsTableToolbar,\n smallMarkConfig,\n subscriptMarkConfig,\n superscriptMarkConfig,\n TABLE_TOOLBAR_BUTTONS,\n textAlignExtensionConfig,\n textColorMarkConfig,\n toEditorDocument,\n toolbarButtonLabel,\n toolbarCommands,\n toolbarPanel,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport Image from '@tiptap/extension-image'\nimport Link from '@tiptap/extension-link'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport { TableKit } from '@tiptap/extension-table'\nimport {\n EditorContent,\n Extension,\n Mark,\n Node,\n useEditor,\n type Editor,\n type Extensions,\n} from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n ALargeSmallIcon,\n BaselineIcon,\n BetweenHorizontalEndIcon,\n BetweenHorizontalStartIcon,\n BetweenVerticalEndIcon,\n BetweenVerticalStartIcon,\n BoldIcon,\n CodeIcon,\n Columns2Icon,\n Columns3Icon,\n EraserIcon,\n Grid2x2XIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n Heading4Icon,\n Heading5Icon,\n Heading6Icon,\n HighlighterIcon,\n ImageIcon,\n ItalicIcon,\n LinkIcon,\n ListCollapseIcon,\n ListIcon,\n ListOrderedIcon,\n Minus,\n PanelTopIcon,\n PilcrowIcon,\n QuoteIcon,\n RedoIcon,\n RemoveFormattingIcon,\n Rows3Icon,\n SquareCodeIcon,\n SquareXIcon,\n StrikethroughIcon,\n SubscriptIcon,\n SuperscriptIcon,\n TableCellsMergeIcon,\n TableCellsSplitIcon,\n TableIcon,\n TablePropertiesIcon,\n TextAlignCenterIcon,\n TextAlignEndIcon,\n TextAlignJustifyIcon,\n TextAlignStartIcon,\n TypeIcon,\n UnderlineIcon,\n UndoIcon,\n} from 'lucide-react'\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { useActionRunner, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Separator } from '../ui/separator'\nimport { Skeleton } from '../ui/skeleton'\nimport {\n ExtensionToolbarButton,\n RichEditorSidePanel,\n stickyStyle,\n SuggestionPopup,\n ToolbarGroupButton,\n useRichEditorExtensions,\n} from './rich-editor-extensions'\n\ntype MarkConfig = Parameters<typeof Mark.create>[0]\ntype NodeConfig = Parameters<typeof Node.create>[0]\ntype ExtensionConfig = Parameters<typeof Extension.create>[0]\n\n/** Toolbar button icons; what each button does lives in `@alfakit/core` (`TOOLBAR_COMMANDS`). */\nconst ICONS: Record<string, ReactNode> = {\n bold: <BoldIcon />,\n italic: <ItalicIcon />,\n underline: <UnderlineIcon />,\n strike: <StrikethroughIcon />,\n subscript: <SubscriptIcon />,\n superscript: <SuperscriptIcon />,\n code: <CodeIcon />,\n highlight: <HighlighterIcon />,\n textColor: <BaselineIcon />,\n link: <LinkIcon />,\n h1: <Heading1Icon />,\n h2: <Heading2Icon />,\n h3: <Heading3Icon />,\n h4: <Heading4Icon />,\n h5: <Heading5Icon />,\n h6: <Heading6Icon />,\n paragraph: <TypeIcon />,\n heading: <Heading2Icon />,\n // Alignment and column icons point at a side: mirror them right to left.\n alignStart: <TextAlignStartIcon className=\"rtl:-scale-x-100\" />,\n alignCenter: <TextAlignCenterIcon />,\n alignEnd: <TextAlignEndIcon className=\"rtl:-scale-x-100\" />,\n alignJustify: <TextAlignJustifyIcon />,\n blockquote: <QuoteIcon />,\n codeBlock: <SquareCodeIcon />,\n bulletList: <ListIcon />,\n orderedList: <ListOrderedIcon />,\n horizontalRule: <Minus />,\n details: <ListCollapseIcon />,\n lead: <PilcrowIcon />,\n small: <ALargeSmallIcon />,\n grid: <Columns2Icon />,\n gridAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n gridAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n gridDeleteColumn: <Columns3Icon />,\n gridDelete: <SquareXIcon />,\n table: <TableIcon />,\n tableAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n tableAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n tableDeleteColumn: <Columns3Icon />,\n tableAddRowBefore: <BetweenHorizontalStartIcon />,\n tableAddRowAfter: <BetweenHorizontalEndIcon />,\n tableDeleteRow: <Rows3Icon />,\n tableMergeCells: <TableCellsMergeIcon />,\n tableSplitCell: <TableCellsSplitIcon />,\n tableToggleHeaderRow: <PanelTopIcon />,\n tableToggleHeaderCell: <TablePropertiesIcon />,\n tableDelete: <Grid2x2XIcon />,\n clearFormatting: <RemoveFormattingIcon />,\n attachFiles: <ImageIcon />,\n undo: <UndoIcon />,\n redo: <RedoIcon />,\n}\n\n/**\n * Resize handles of `resizableImages()` (Tiptap's node view adds bare `data-resize-handle`\n * elements): round dots on the corners, shown on hover and while the image is selected.\n */\nconst RESIZE_HANDLE_CLASSES =\n '[&_[data-resize-handle]]:z-10 [&_[data-resize-handle]]:size-3 [&_[data-resize-handle]]:rounded-full [&_[data-resize-handle]]:border-2 [&_[data-resize-handle]]:border-background [&_[data-resize-handle]]:bg-primary [&_[data-resize-handle]]:opacity-0 [&_[data-resize-container]:hover_[data-resize-handle]]:opacity-100 [&_.ProseMirror-selectednode_[data-resize-handle]]:opacity-100 [&_[data-resize-handle=bottom-right]]:cursor-nwse-resize [&_[data-resize-handle=top-left]]:cursor-nwse-resize [&_[data-resize-handle=bottom-left]]:cursor-nesw-resize [&_[data-resize-handle=top-right]]:cursor-nesw-resize'\n\n/** The image extension, with drag handles for `resizableImages()`. */\nexport function imageExtension(resizable: boolean) {\n if (!resizable) return Image\n\n return Image.extend({\n addAttributes() {\n return resizableImageAttributes(this.parent?.() ?? {})\n },\n }).configure(resizableImageOptions())\n}\n\n/** Extensions for the extra toolbar buttons (only those the toolbar uses are loaded). */\nexport function toolbarExtensions(buttons: string[], markdown: boolean): Extensions {\n const has = (...names: string[]) => names.some((name) => buttons.includes(name))\n const extensions: Extensions = []\n\n if (has('table')) extensions.push(TableKit.configure({ table: { resizable: false } }))\n // Markdown has no syntax for these.\n if (markdown) return extensions\n\n if (has('highlight')) extensions.push(Mark.create(highlightMarkConfig() as MarkConfig))\n if (has('subscript')) extensions.push(Mark.create(subscriptMarkConfig() as MarkConfig))\n if (has('superscript')) extensions.push(Mark.create(superscriptMarkConfig() as MarkConfig))\n if (has('textColor')) extensions.push(Mark.create(textColorMarkConfig() as MarkConfig))\n if (has('alignStart', 'alignCenter', 'alignEnd', 'alignJustify')) {\n extensions.push(Extension.create(textAlignExtensionConfig() as ExtensionConfig))\n }\n if (has('details')) {\n extensions.push(...detailsNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n if (has('small')) extensions.push(Mark.create(smallMarkConfig() as MarkConfig))\n if (has('lead')) extensions.push(Extension.create(leadExtensionConfig() as ExtensionConfig))\n if (has('grid', 'gridDelete', 'gridAddColumnBefore', 'gridAddColumnAfter', 'gridDeleteColumn')) {\n extensions.push(...gridNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n\n return extensions\n}\n\nconst NO_EXTENSIONS: LoadedRichEditorExtensions = { extensions: [], commands: {} }\n\n/**\n * The rich and Markdown editors. Plugin extensions (`RichEditor::plugins()`) load before\n * the editor is created, since Tiptap extensions can't be added afterwards.\n */\nexport function RichEditorField({ component }: { component: RichEditorSchema }) {\n const keys = component.type === 'MarkdownEditor' ? [] : (component.extensions ?? [])\n const signature = keys.join('|')\n const [loaded, setLoaded] = useState<{\n signature: string\n value: LoadedRichEditorExtensions\n } | null>(() => (keys.length === 0 ? { signature, value: NO_EXTENSIONS } : null))\n\n useEffect(() => {\n if (loaded?.signature === signature) return\n\n let cancelled = false\n\n void loadRichEditorExtensions(signature === '' ? [] : signature.split('|')).then((value) => {\n if (!cancelled) setLoaded({ signature, value })\n })\n\n return () => {\n cancelled = true\n }\n }, [signature, loaded?.signature])\n\n if (loaded?.signature !== signature) {\n return <RichEditorLoading component={component} />\n }\n\n return <RichEditorInput key={signature} component={component} plugins={loaded.value} />\n}\n\nfunction RichEditorLoading({ component }: { component: RichEditorSchema }) {\n const { id, error } = useField<unknown>(component)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <Skeleton className=\"h-40 w-full\" aria-hidden=\"true\" />\n </FieldWrapper>\n )\n}\n\nfunction RichEditorInput({\n component,\n plugins,\n}: {\n component: RichEditorSchema\n plugins: LoadedRichEditorExtensions\n}) {\n const t = useT()\n const panel = usePanel()\n const runner = useActionRunner()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } =\n useField<unknown>(component)\n const fileInput = useRef<HTMLInputElement>(null)\n // ProseMirror reads its DOM attributes on every view update; the ref keeps them current.\n const ariaRef = useRef(aria)\n const isMarkdown = component.type === 'MarkdownEditor'\n // Uploaded images preview from a local URL but are stored by their `tmp:` key.\n const [attachments] = useState(() => new EditorAttachments())\n const editable = !disabled && !readOnly\n const isJson = Boolean(component.json) && !isMarkdown\n // The editor's live instance, for the custom block node views and suggestions.\n const editorRef = useRef<Editor | null>(null)\n const richExtensions = useRichEditorExtensions(component, editorRef)\n // The open toolbar panel (the text colour palette).\n const [openPanel, setOpenPanel] = useState<'textColor' | null>(null)\n // The open side panel (custom blocks or merge tags).\n const [sidePanel, setSidePanel] = useState<RichEditorPanel | null>(() => {\n const initial = component.activePanel ?? null\n\n if (initial === 'customBlocks') return component.customBlocks?.length ? initial : null\n if (initial === 'mergeTags') return component.mergeTags?.length ? initial : null\n\n return null\n })\n const [attachmentError, setAttachmentError] = useState<string | null>(null)\n // Re-render on selection changes so active states and the table row follow the cursor.\n const [, setSelectionTick] = useState(0)\n const sticky = Boolean(component.stickyToolbar || component.stickyPanels)\n const contextRef = useRef(richExtensions.context)\n // Keyboard access to the toolbars (roving tabindex, arrows, Escape, Alt+F10).\n const toolbarElements = useRef<Record<'main' | 'table' | 'floating', HTMLElement | null>>({\n main: null,\n table: null,\n floating: null,\n })\n const toolbars = useCallback(\n () => [\n toolbarElements.current.floating,\n toolbarElements.current.main,\n toolbarElements.current.table,\n ],\n [],\n )\n const attachKeyboard = useCallback(\n (slot: 'main' | 'table' | 'floating', element: HTMLElement | null) => {\n toolbarElements.current[slot] = element\n\n if (!element) return\n\n const detach = attachToolbarKeyboard(element, {\n onEscape: () => editorRef.current?.commands.focus(),\n toolbars,\n })\n\n return () => {\n detach()\n if (toolbarElements.current[slot] === element) toolbarElements.current[slot] = null\n }\n },\n [toolbars],\n )\n const mainToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('main', element),\n [attachKeyboard],\n )\n const tableToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('table', element),\n [attachKeyboard],\n )\n const floatingToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('floating', element),\n [attachKeyboard],\n )\n\n useEffect(() => {\n contextRef.current = richExtensions.context\n })\n\n // How plugin tools open their editor action. The refs are read when a tool runs (on\n // click), never during render.\n const [toolContext] = useState<ToolCommandContext>(() => ({\n runAction: (tool, args) =>\n tool.action\n ? runner.runField(tool.action, { ...contextRef.current, arguments: args })\n : Promise.resolve(undefined),\n editor: () => editorRef.current,\n }))\n\n // Built-in buttons, plugin tools and commands of plugin JavaScript extensions.\n const commands = useMemo(\n () => toolbarCommands(component.editorTools, toolContext, plugins.commands),\n [component.editorTools, plugins.commands, toolContext],\n )\n\n // Field state ↔ editor content: HTML, Markdown (loaded from the server's HTML) or a\n // Tiptap JSON document. `tmp:` upload keys become local preview URLs and back.\n const stateToContent = (): string | Record<string, unknown> => {\n if (isJson) {\n const document = toEditorDocument(value)\n\n return document ? JSON.parse(attachments.toEditor(JSON.stringify(document))) : ''\n }\n\n const markdownHtml = typeof component.html === 'string' ? component.html : null\n\n return attachments.toEditor(((isMarkdown ? markdownHtml : value) as string | null) ?? '')\n }\n const editorToState = (instance: Editor): unknown => {\n if (instance.isEmpty) return null\n if (isJson) return JSON.parse(attachments.toState(JSON.stringify(instance.getJSON())))\n\n const html = instance.getHTML()\n\n return attachments.toState(isMarkdown ? htmlToMarkdown(html) : html)\n }\n\n useEffect(() => () => attachments.destroy(), [attachments])\n\n const editor = useEditor({\n extensions: [\n StarterKit.configure({ link: false }),\n Link.configure(linkExtensionOptions(component.linkProtocols)),\n imageExtension(Boolean(component.resizableImages) && !isMarkdown),\n Placeholder.configure({ placeholder: component.placeholder ?? '' }),\n ...toolbarExtensions(allToolbarButtons(component), isMarkdown),\n ...(isMarkdown ? [] : richExtensions.extensions),\n ...(isMarkdown ? [] : (plugins.extensions as Extensions)),\n ],\n // Markdown is loaded from the server's HTML rendering of it, never parsed as HTML.\n content: stateToContent(),\n editable,\n editorProps: {\n // The mention / merge tag popup takes the arrow, Enter, Tab and Escape keys.\n handleKeyDown: (_view, event) => {\n // Alt+F10: into the floating toolbar, else the main one.\n if (isToolbarShortcut(event)) {\n return focusToolbar(toolbars())\n }\n\n return editorRef.current\n ? richExtensions.suggestions.handleKeyDown(event, editorRef.current)\n : false\n },\n attributes: () => ({\n class: cn(\n 'alfakit-prose min-h-32 px-3 py-2 text-sm outline-none',\n component.resizableImages && RESIZE_HANDLE_CLASSES,\n ),\n // Runtime sizes cannot be Tailwind classes (they would never be generated).\n ...(component.minHeight ? { style: `min-height: ${component.minHeight}px` } : {}),\n id,\n // Each paragraph follows its own script, so Arabic and English mix correctly.\n dir: 'auto',\n role: 'textbox',\n 'aria-multiline': 'true',\n ...(component.label ? { 'aria-labelledby': fieldIds(id).label } : {}),\n ...Object.fromEntries(\n Object.entries(ariaRef.current).map(([name, attribute]) => [name, String(attribute)]),\n ),\n }),\n },\n onUpdate: ({ editor: instance }) => {\n setValue(editorToState(instance))\n },\n onTransaction: ({ editor: instance }) => {\n richExtensions.suggestions.update(instance)\n setSelectionTick((tick) => tick + 1)\n },\n onBlur: () => {\n richExtensions.suggestions.close()\n blur()\n },\n })\n\n useEffect(() => {\n editorRef.current = editor\n }, [editor])\n\n // Keep editor in sync when the server refreshes the state.\n useEffect(() => {\n // Tiptap may replace the instance on mount (e.g. when the editor loads lazily); the\n // replaced one has no schema left, and the new one starts from the current value.\n if (!editor?.schema) return\n\n const current = editorToState(editor)\n const same = isJson\n ? JSON.stringify(toEditorDocument(value)) === JSON.stringify(current)\n : (value ?? '') === (current ?? '')\n\n if (!same) {\n editor.commands.setContent(stateToContent(), { emitUpdate: false })\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value])\n\n useEffect(() => {\n editor?.setEditable(editable)\n }, [editor, editable])\n\n // Re-apply the editable element's attributes when the error or description changes.\n useEffect(() => {\n ariaRef.current = aria\n\n if (editor && !editor.isDestroyed) {\n editor.view.setProps({})\n }\n }, [editor, aria])\n\n // Measured in the browser once the editor exists (the toolbar only renders then).\n const stickyTop = useMemo(\n () => (sticky && editor ? resolveStickyOffset(component.stickyOffset) : null),\n [sticky, editor, component.stickyOffset],\n )\n\n // A floating toolbar follows its selection while the page scrolls.\n const hasFloatingToolbars = Boolean(component.floatingToolbars)\n\n useEffect(() => {\n if (!hasFloatingToolbars) return\n\n const onScroll = () => setSelectionTick((tick) => tick + 1)\n\n window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n\n return () => window.removeEventListener('scroll', onScroll, { capture: true })\n }, [hasFloatingToolbars])\n\n const pickImage = () => fileInput.current?.click()\n\n const uploadImage = async (file: File) => {\n if (!panel.endpoints.upload || !editor) return\n\n // The server checks again when the form is saved.\n const problem = fileAttachmentError(file, component.fileAttachments)\n\n if (problem) {\n setAttachmentError(\n problem === 'too_large'\n ? t('form.file_too_large', {}, 'This file is too large.')\n : t('form.file_invalid_type', {}, 'This file type is not allowed.'),\n )\n\n return\n }\n\n setAttachmentError(null)\n\n let response: { key: string }\n\n try {\n // Large attachments go in resumable chunks.\n response = await uploadTemporaryFile(\n panel.endpoints.upload,\n file,\n panelUploadOptions(panel, component.fileAttachments.uploadPolicy),\n )\n } catch (error) {\n setAttachmentError(\n (error as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n t('form.upload_failed', {}, 'The file could not be uploaded.'),\n )\n\n return\n }\n\n const src = attachments.add(response.key, file)\n\n editor.chain().focus().setImage({ src, alt: file.name }).run()\n }\n\n const translate = (key: string, fallback: string) => t(key, {}, fallback)\n\n const iconFor = (name: string): ReactNode => {\n if (ICONS[name]) return ICONS[name]\n\n const tool = component.editorTools?.find((candidate) => candidate.name === name)\n\n return tool?.icon ? (\n <Icon name={tool.icon} />\n ) : (\n <span className=\"text-xs\">{commandLabel(name, commands, translate)}</span>\n )\n }\n\n const runCommand = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return\n\n button.run(editor, {\n // Messages arrive translated (`form.link_prompt`, `editor.grid_columns`).\n prompt: (message) => window.prompt(message),\n pickImage,\n togglePanel: (target) => setOpenPanel((current) => (current === target ? null : target)),\n t: translate,\n })\n }\n\n const renderButton = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return null\n\n const label = commandLabel(name, commands, translate)\n const active = button.active?.(editor) || (name === 'textColor' && openPanel === name)\n\n return (\n <Button\n key={name}\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={label}\n title={label}\n aria-pressed={button.active || name === 'textColor' ? Boolean(active) : undefined}\n disabled={!editable}\n className={cn(active && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => runCommand(name)}\n >\n {iconFor(name)}\n </Button>\n )\n }\n\n /** A toolbar entry: separator, group dropdown, panel toggle or button. */\n const renderEntry = (name: string, index: number) => {\n if (!editor) return null\n\n if (name === '|') {\n return <Separator key={`sep-${index}`} orientation=\"vertical\" className=\"mx-1 h-5\" />\n }\n\n if (isToolbarGroup(name)) {\n const group = component.toolbarButtonGroups?.[name]\n\n return group ? (\n <ToolbarGroupButton\n key={name}\n group={group}\n editor={editor}\n editable={editable}\n commands={commands}\n icons={iconFor}\n translate={translate}\n onRun={runCommand}\n />\n ) : null\n }\n\n const target = toolbarPanel(name)\n\n if (target) {\n return (\n <ExtensionToolbarButton\n key={name}\n name={target}\n component={component}\n editable={editable}\n open={sidePanel === target}\n onToggle={() => setSidePanel((current) => (current === target ? null : target))}\n />\n )\n }\n\n return renderButton(name)\n }\n\n const floating = editable ? resolveFloatingToolbar(editor, component.floatingToolbars) : null\n // A `table` floating toolbar replaces the table row of buttons.\n const tableRow =\n editor &&\n editable &&\n !component.floatingToolbars?.table &&\n showsTableToolbar(allToolbarButtons(component), editor)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n className={cn(\n 'border-input dark:bg-input/30 focus-within:border-ring focus-within:ring-ring/50 rounded-md border shadow-xs focus-within:ring-[3px]',\n error && 'border-destructive',\n disabled && 'opacity-60',\n )}\n >\n {component.toolbarButtons.length > 0 && editor && (\n <div\n ref={mainToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.toolbar', {}, 'Formatting')}\n aria-keyshortcuts=\"Alt+F10\"\n data-slot=\"rich-editor-toolbar\"\n className={cn(\n 'flex flex-wrap items-center gap-0.5 border-b p-1',\n component.stickyToolbar && 'bg-background sticky z-10 rounded-t-md',\n )}\n // The offset is a runtime value: no Tailwind class can hold it.\n style={component.stickyToolbar ? stickyStyle(stickyTop ?? '0px') : undefined}\n >\n {component.toolbarButtons.map(renderEntry)}\n </div>\n )}\n {editor && openPanel === 'textColor' && (\n <div\n role=\"group\"\n aria-label={toolbarButtonLabel('textColor', translate)}\n className=\"flex flex-wrap items-center gap-1 border-b p-1\"\n data-slot=\"rich-editor-text-colors\"\n >\n {(component.textColors ?? []).map((color) => (\n <button\n key={color.value}\n type=\"button\"\n title={color.label}\n aria-label={color.label}\n aria-pressed={editor.getAttributes('textColor').color === color.value}\n className=\"focus-visible:ring-ring/50 size-6 rounded-full border shadow-xs outline-none focus-visible:ring-[3px] aria-pressed:ring-2\"\n // Palette colours are runtime values: no Tailwind class can hold them.\n style={{ backgroundColor: color.value }}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, color.value)\n setOpenPanel(null)\n }}\n />\n ))}\n {component.customTextColors && (\n <input\n type=\"color\"\n aria-label={t('editor.custom_color', {}, 'Custom color')}\n title={t('editor.custom_color', {}, 'Custom color')}\n className=\"size-6 cursor-pointer rounded border bg-transparent p-0\"\n onChange={(event) => setTextColor(editor, event.target.value)}\n />\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('editor.remove_color', {}, 'Remove color')}\n title={t('editor.remove_color', {}, 'Remove color')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, null)\n setOpenPanel(null)\n }}\n >\n <EraserIcon />\n </Button>\n </div>\n )}\n {tableRow && (\n <div\n ref={tableToolbarRef}\n role=\"toolbar\"\n aria-label={toolbarButtonLabel('table', translate)}\n className=\"flex flex-wrap items-center gap-0.5 border-b p-1\"\n data-slot=\"rich-editor-table-toolbar\"\n >\n {TABLE_TOOLBAR_BUTTONS.map((name) => renderButton(name))}\n </div>\n )}\n <div className=\"@container\">\n <div className=\"flex flex-col-reverse @2xl:flex-row\">\n <EditorContent\n editor={editor}\n className=\"min-w-0 flex-1\"\n style={\n component.maxHeight\n ? { maxHeight: component.maxHeight, overflowY: 'auto' }\n : undefined\n }\n />\n {editor && sidePanel && (\n <RichEditorSidePanel\n panel={sidePanel}\n component={component}\n editable={editable}\n sticky={component.stickyPanels ? (stickyTop ?? '0px') : null}\n onInsertBlock={(blockId) => void richExtensions.insertBlock(blockId)}\n onInsertMergeTag={(tagId) =>\n editor\n .chain()\n .focus()\n .insertContent({ type: 'mergeTag', attrs: { id: tagId } })\n .run()\n }\n onClose={() => setSidePanel(null)}\n />\n )}\n </div>\n </div>\n {editor && floating && floating.buttons.length > 0 && (\n <div\n ref={floatingToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.floating_toolbar', {}, 'Selection formatting')}\n data-slot=\"rich-editor-floating-toolbar\"\n data-node={floating.node}\n className=\"bg-popover text-popover-foreground fixed z-50 flex items-center gap-0.5 rounded-md border p-1 shadow-md\"\n // Runtime coordinates cannot be Tailwind classes.\n style={floatingToolbarStyle(floating.coords)}\n >\n {floating.buttons.map(renderEntry)}\n </div>\n )}\n <SuggestionPopup\n suggestions={richExtensions.suggestions}\n editor={editor}\n id={`${id}-suggestions`}\n />\n {attachmentError && (\n <p role=\"alert\" className=\"text-destructive px-3 pb-2 text-xs\">\n {attachmentError}\n </p>\n )}\n <input\n ref={fileInput}\n type=\"file\"\n accept={(component.fileAttachments.acceptedFileTypes ?? ['image/*']).join(',')}\n className=\"sr-only\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => event.target.files?.[0] && void uploadImage(event.target.files[0])}\n />\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"rich-editor.js","names":[],"sources":["../../../src/components/fields/rich-editor.tsx"],"sourcesContent":["import type {\n LoadedRichEditorExtensions,\n RichEditorPanel,\n RichEditorSchema,\n ToolCommandContext,\n} from '@alfakit/core'\nimport {\n allToolbarButtons,\n attachToolbarKeyboard,\n commandLabel,\n detailsNodeConfigs,\n documentNonce,\n EditorAttachments,\n fieldIds,\n fileAttachmentError,\n floatingToolbarStyle,\n focusToolbar,\n gridNodeConfigs,\n highlightMarkConfig,\n htmlToMarkdown,\n isToolbarGroup,\n isToolbarShortcut,\n leadExtensionConfig,\n linkExtensionOptions,\n loadRichEditorExtensions,\n panelUploadOptions,\n resizableImageAttributes,\n resizableImageOptions,\n resolveFloatingToolbar,\n resolveStickyOffset,\n setTextColor,\n showsTableToolbar,\n smallMarkConfig,\n subscriptMarkConfig,\n superscriptMarkConfig,\n TABLE_TOOLBAR_BUTTONS,\n textAlignExtensionConfig,\n textColorMarkConfig,\n toEditorDocument,\n toolbarButtonLabel,\n toolbarCommands,\n toolbarPanel,\n uploadTemporaryFile,\n} from '@alfakit/core'\nimport Image from '@tiptap/extension-image'\nimport Link from '@tiptap/extension-link'\nimport Placeholder from '@tiptap/extension-placeholder'\nimport { TableKit } from '@tiptap/extension-table'\nimport {\n EditorContent,\n Extension,\n Mark,\n Node,\n useEditor,\n type Editor,\n type Extensions,\n} from '@tiptap/react'\nimport StarterKit from '@tiptap/starter-kit'\nimport {\n ALargeSmallIcon,\n BaselineIcon,\n BetweenHorizontalEndIcon,\n BetweenHorizontalStartIcon,\n BetweenVerticalEndIcon,\n BetweenVerticalStartIcon,\n BoldIcon,\n CodeIcon,\n Columns2Icon,\n Columns3Icon,\n EraserIcon,\n Grid2x2XIcon,\n Heading1Icon,\n Heading2Icon,\n Heading3Icon,\n Heading4Icon,\n Heading5Icon,\n Heading6Icon,\n HighlighterIcon,\n ImageIcon,\n ItalicIcon,\n LinkIcon,\n ListCollapseIcon,\n ListIcon,\n ListOrderedIcon,\n Minus,\n PanelTopIcon,\n PilcrowIcon,\n QuoteIcon,\n RedoIcon,\n RemoveFormattingIcon,\n Rows3Icon,\n SquareCodeIcon,\n SquareXIcon,\n StrikethroughIcon,\n SubscriptIcon,\n SuperscriptIcon,\n TableCellsMergeIcon,\n TableCellsSplitIcon,\n TableIcon,\n TablePropertiesIcon,\n TextAlignCenterIcon,\n TextAlignEndIcon,\n TextAlignJustifyIcon,\n TextAlignStartIcon,\n TypeIcon,\n UnderlineIcon,\n UndoIcon,\n} from 'lucide-react'\nimport { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from 'react'\nimport { useActionRunner, usePanel, useT } from '../../context'\nimport { Icon } from '../../lib/icons'\nimport { cn } from '../../lib/utils'\nimport { useField } from '../form/form-context'\nimport { FieldWrapper } from '../schema/field-wrapper'\nimport { Button } from '../ui/button'\nimport { Separator } from '../ui/separator'\nimport { Skeleton } from '../ui/skeleton'\nimport {\n ExtensionToolbarButton,\n RichEditorSidePanel,\n stickyStyle,\n SuggestionPopup,\n ToolbarGroupButton,\n useRichEditorExtensions,\n} from './rich-editor-extensions'\n\ntype MarkConfig = Parameters<typeof Mark.create>[0]\ntype NodeConfig = Parameters<typeof Node.create>[0]\ntype ExtensionConfig = Parameters<typeof Extension.create>[0]\n\n/** Toolbar button icons; what each button does lives in `@alfakit/core` (`TOOLBAR_COMMANDS`). */\nconst ICONS: Record<string, ReactNode> = {\n bold: <BoldIcon />,\n italic: <ItalicIcon />,\n underline: <UnderlineIcon />,\n strike: <StrikethroughIcon />,\n subscript: <SubscriptIcon />,\n superscript: <SuperscriptIcon />,\n code: <CodeIcon />,\n highlight: <HighlighterIcon />,\n textColor: <BaselineIcon />,\n link: <LinkIcon />,\n h1: <Heading1Icon />,\n h2: <Heading2Icon />,\n h3: <Heading3Icon />,\n h4: <Heading4Icon />,\n h5: <Heading5Icon />,\n h6: <Heading6Icon />,\n paragraph: <TypeIcon />,\n heading: <Heading2Icon />,\n // Alignment and column icons point at a side: mirror them right to left.\n alignStart: <TextAlignStartIcon className=\"rtl:-scale-x-100\" />,\n alignCenter: <TextAlignCenterIcon />,\n alignEnd: <TextAlignEndIcon className=\"rtl:-scale-x-100\" />,\n alignJustify: <TextAlignJustifyIcon />,\n blockquote: <QuoteIcon />,\n codeBlock: <SquareCodeIcon />,\n bulletList: <ListIcon />,\n orderedList: <ListOrderedIcon />,\n horizontalRule: <Minus />,\n details: <ListCollapseIcon />,\n lead: <PilcrowIcon />,\n small: <ALargeSmallIcon />,\n grid: <Columns2Icon />,\n gridAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n gridAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n gridDeleteColumn: <Columns3Icon />,\n gridDelete: <SquareXIcon />,\n table: <TableIcon />,\n tableAddColumnBefore: <BetweenVerticalStartIcon className=\"rtl:-scale-x-100\" />,\n tableAddColumnAfter: <BetweenVerticalEndIcon className=\"rtl:-scale-x-100\" />,\n tableDeleteColumn: <Columns3Icon />,\n tableAddRowBefore: <BetweenHorizontalStartIcon />,\n tableAddRowAfter: <BetweenHorizontalEndIcon />,\n tableDeleteRow: <Rows3Icon />,\n tableMergeCells: <TableCellsMergeIcon />,\n tableSplitCell: <TableCellsSplitIcon />,\n tableToggleHeaderRow: <PanelTopIcon />,\n tableToggleHeaderCell: <TablePropertiesIcon />,\n tableDelete: <Grid2x2XIcon />,\n clearFormatting: <RemoveFormattingIcon />,\n attachFiles: <ImageIcon />,\n undo: <UndoIcon />,\n redo: <RedoIcon />,\n}\n\n/**\n * Resize handles of `resizableImages()` (Tiptap's node view adds bare `data-resize-handle`\n * elements): round dots on the corners, shown on hover and while the image is selected.\n */\nconst RESIZE_HANDLE_CLASSES =\n '[&_[data-resize-handle]]:z-10 [&_[data-resize-handle]]:size-3 [&_[data-resize-handle]]:rounded-full [&_[data-resize-handle]]:border-2 [&_[data-resize-handle]]:border-background [&_[data-resize-handle]]:bg-primary [&_[data-resize-handle]]:opacity-0 [&_[data-resize-container]:hover_[data-resize-handle]]:opacity-100 [&_.ProseMirror-selectednode_[data-resize-handle]]:opacity-100 [&_[data-resize-handle=bottom-right]]:cursor-nwse-resize [&_[data-resize-handle=top-left]]:cursor-nwse-resize [&_[data-resize-handle=bottom-left]]:cursor-nesw-resize [&_[data-resize-handle=top-right]]:cursor-nesw-resize'\n\n/** The image extension, with drag handles for `resizableImages()`. */\nexport function imageExtension(resizable: boolean) {\n if (!resizable) return Image\n\n return Image.extend({\n addAttributes() {\n return resizableImageAttributes(this.parent?.() ?? {})\n },\n }).configure(resizableImageOptions())\n}\n\n/** Extensions for the extra toolbar buttons (only those the toolbar uses are loaded). */\nexport function toolbarExtensions(buttons: string[], markdown: boolean): Extensions {\n const has = (...names: string[]) => names.some((name) => buttons.includes(name))\n const extensions: Extensions = []\n\n if (has('table')) extensions.push(TableKit.configure({ table: { resizable: false } }))\n // Markdown has no syntax for these.\n if (markdown) return extensions\n\n if (has('highlight')) extensions.push(Mark.create(highlightMarkConfig() as MarkConfig))\n if (has('subscript')) extensions.push(Mark.create(subscriptMarkConfig() as MarkConfig))\n if (has('superscript')) extensions.push(Mark.create(superscriptMarkConfig() as MarkConfig))\n if (has('textColor')) extensions.push(Mark.create(textColorMarkConfig() as MarkConfig))\n if (has('alignStart', 'alignCenter', 'alignEnd', 'alignJustify')) {\n extensions.push(Extension.create(textAlignExtensionConfig() as ExtensionConfig))\n }\n if (has('details')) {\n extensions.push(...detailsNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n if (has('small')) extensions.push(Mark.create(smallMarkConfig() as MarkConfig))\n if (has('lead')) extensions.push(Extension.create(leadExtensionConfig() as ExtensionConfig))\n if (has('grid', 'gridDelete', 'gridAddColumnBefore', 'gridAddColumnAfter', 'gridDeleteColumn')) {\n extensions.push(...gridNodeConfigs().map((config) => Node.create(config as NodeConfig)))\n }\n\n return extensions\n}\n\nconst NO_EXTENSIONS: LoadedRichEditorExtensions = { extensions: [], commands: {} }\n\n/**\n * The rich and Markdown editors. Plugin extensions (`RichEditor::plugins()`) load before\n * the editor is created, since Tiptap extensions can't be added afterwards.\n */\nexport function RichEditorField({ component }: { component: RichEditorSchema }) {\n const keys = component.type === 'MarkdownEditor' ? [] : (component.extensions ?? [])\n const signature = keys.join('|')\n const [loaded, setLoaded] = useState<{\n signature: string\n value: LoadedRichEditorExtensions\n } | null>(() => (keys.length === 0 ? { signature, value: NO_EXTENSIONS } : null))\n\n useEffect(() => {\n if (loaded?.signature === signature) return\n\n let cancelled = false\n\n void loadRichEditorExtensions(signature === '' ? [] : signature.split('|')).then((value) => {\n if (!cancelled) setLoaded({ signature, value })\n })\n\n return () => {\n cancelled = true\n }\n }, [signature, loaded?.signature])\n\n if (loaded?.signature !== signature) {\n return <RichEditorLoading component={component} />\n }\n\n return <RichEditorInput key={signature} component={component} plugins={loaded.value} />\n}\n\nfunction RichEditorLoading({ component }: { component: RichEditorSchema }) {\n const { id, error } = useField<unknown>(component)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <Skeleton className=\"h-40 w-full\" aria-hidden=\"true\" />\n </FieldWrapper>\n )\n}\n\nfunction RichEditorInput({\n component,\n plugins,\n}: {\n component: RichEditorSchema\n plugins: LoadedRichEditorExtensions\n}) {\n const t = useT()\n const panel = usePanel()\n const runner = useActionRunner()\n const { value, setValue, blur, error, id, disabled, readOnly, aria } =\n useField<unknown>(component)\n const fileInput = useRef<HTMLInputElement>(null)\n // ProseMirror reads its DOM attributes on every view update; the ref keeps them current.\n const ariaRef = useRef(aria)\n const isMarkdown = component.type === 'MarkdownEditor'\n // Uploaded images preview from a local URL but are stored by their `tmp:` key.\n const [attachments] = useState(() => new EditorAttachments())\n const editable = !disabled && !readOnly\n const isJson = Boolean(component.json) && !isMarkdown\n // The editor's live instance, for the custom block node views and suggestions.\n const editorRef = useRef<Editor | null>(null)\n const richExtensions = useRichEditorExtensions(component, editorRef)\n // The open toolbar panel (the text colour palette).\n const [openPanel, setOpenPanel] = useState<'textColor' | null>(null)\n // The open side panel (custom blocks or merge tags).\n const [sidePanel, setSidePanel] = useState<RichEditorPanel | null>(() => {\n const initial = component.activePanel ?? null\n\n if (initial === 'customBlocks') return component.customBlocks?.length ? initial : null\n if (initial === 'mergeTags') return component.mergeTags?.length ? initial : null\n\n return null\n })\n const [attachmentError, setAttachmentError] = useState<string | null>(null)\n // Re-render on selection changes so active states and the table row follow the cursor.\n const [, setSelectionTick] = useState(0)\n const sticky = Boolean(component.stickyToolbar || component.stickyPanels)\n const contextRef = useRef(richExtensions.context)\n // Keyboard access to the toolbars (roving tabindex, arrows, Escape, Alt+F10).\n const toolbarElements = useRef<Record<'main' | 'table' | 'floating', HTMLElement | null>>({\n main: null,\n table: null,\n floating: null,\n })\n const toolbars = useCallback(\n () => [\n toolbarElements.current.floating,\n toolbarElements.current.main,\n toolbarElements.current.table,\n ],\n [],\n )\n const attachKeyboard = useCallback(\n (slot: 'main' | 'table' | 'floating', element: HTMLElement | null) => {\n toolbarElements.current[slot] = element\n\n if (!element) return\n\n const detach = attachToolbarKeyboard(element, {\n onEscape: () => editorRef.current?.commands.focus(),\n toolbars,\n })\n\n return () => {\n detach()\n if (toolbarElements.current[slot] === element) toolbarElements.current[slot] = null\n }\n },\n [toolbars],\n )\n const mainToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('main', element),\n [attachKeyboard],\n )\n const tableToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('table', element),\n [attachKeyboard],\n )\n const floatingToolbarRef = useCallback(\n (element: HTMLElement | null) => attachKeyboard('floating', element),\n [attachKeyboard],\n )\n\n useEffect(() => {\n contextRef.current = richExtensions.context\n })\n\n // How plugin tools open their editor action. The refs are read when a tool runs (on\n // click), never during render.\n const [toolContext] = useState<ToolCommandContext>(() => ({\n runAction: (tool, args) =>\n tool.action\n ? runner.runField(tool.action, { ...contextRef.current, arguments: args })\n : Promise.resolve(undefined),\n editor: () => editorRef.current,\n }))\n\n // Built-in buttons, plugin tools and commands of plugin JavaScript extensions.\n const commands = useMemo(\n () => toolbarCommands(component.editorTools, toolContext, plugins.commands),\n [component.editorTools, plugins.commands, toolContext],\n )\n\n // Field state ↔ editor content: HTML, Markdown (loaded from the server's HTML) or a\n // Tiptap JSON document. `tmp:` upload keys become local preview URLs and back.\n const stateToContent = (): string | Record<string, unknown> => {\n if (isJson) {\n const document = toEditorDocument(value)\n\n return document ? JSON.parse(attachments.toEditor(JSON.stringify(document))) : ''\n }\n\n const markdownHtml = typeof component.html === 'string' ? component.html : null\n\n return attachments.toEditor(((isMarkdown ? markdownHtml : value) as string | null) ?? '')\n }\n const editorToState = (instance: Editor): unknown => {\n if (instance.isEmpty) return null\n if (isJson) return JSON.parse(attachments.toState(JSON.stringify(instance.getJSON())))\n\n const html = instance.getHTML()\n\n return attachments.toState(isMarkdown ? htmlToMarkdown(html) : html)\n }\n\n useEffect(() => () => attachments.destroy(), [attachments])\n\n const editor = useEditor({\n // TipTap injects its base CSS; with the page's CSP nonce a nonce-based policy allows it.\n injectNonce: documentNonce(),\n extensions: [\n StarterKit.configure({ link: false }),\n Link.configure(linkExtensionOptions(component.linkProtocols)),\n imageExtension(Boolean(component.resizableImages) && !isMarkdown),\n Placeholder.configure({ placeholder: component.placeholder ?? '' }),\n ...toolbarExtensions(allToolbarButtons(component), isMarkdown),\n ...(isMarkdown ? [] : richExtensions.extensions),\n ...(isMarkdown ? [] : (plugins.extensions as Extensions)),\n ],\n // Markdown is loaded from the server's HTML rendering of it, never parsed as HTML.\n content: stateToContent(),\n editable,\n editorProps: {\n // The mention / merge tag popup takes the arrow, Enter, Tab and Escape keys.\n handleKeyDown: (_view, event) => {\n // Alt+F10: into the floating toolbar, else the main one.\n if (isToolbarShortcut(event)) {\n return focusToolbar(toolbars())\n }\n\n return editorRef.current\n ? richExtensions.suggestions.handleKeyDown(event, editorRef.current)\n : false\n },\n attributes: () => ({\n class: cn(\n 'alfakit-prose min-h-32 px-3 py-2 text-sm outline-none',\n component.resizableImages && RESIZE_HANDLE_CLASSES,\n ),\n // Runtime sizes cannot be Tailwind classes (they would never be generated).\n ...(component.minHeight ? { style: `min-height: ${component.minHeight}px` } : {}),\n id,\n // Each paragraph follows its own script, so Arabic and English mix correctly.\n dir: 'auto',\n role: 'textbox',\n 'aria-multiline': 'true',\n ...(component.label ? { 'aria-labelledby': fieldIds(id).label } : {}),\n ...Object.fromEntries(\n Object.entries(ariaRef.current).map(([name, attribute]) => [name, String(attribute)]),\n ),\n }),\n },\n onUpdate: ({ editor: instance }) => {\n setValue(editorToState(instance))\n },\n onTransaction: ({ editor: instance }) => {\n richExtensions.suggestions.update(instance)\n setSelectionTick((tick) => tick + 1)\n },\n onBlur: () => {\n richExtensions.suggestions.close()\n blur()\n },\n })\n\n useEffect(() => {\n editorRef.current = editor\n }, [editor])\n\n // Keep editor in sync when the server refreshes the state.\n useEffect(() => {\n // Tiptap may replace the instance on mount (e.g. when the editor loads lazily); the\n // replaced one has no schema left, and the new one starts from the current value.\n if (!editor?.schema) return\n\n const current = editorToState(editor)\n const same = isJson\n ? JSON.stringify(toEditorDocument(value)) === JSON.stringify(current)\n : (value ?? '') === (current ?? '')\n\n if (!same) {\n editor.commands.setContent(stateToContent(), { emitUpdate: false })\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value])\n\n useEffect(() => {\n editor?.setEditable(editable)\n }, [editor, editable])\n\n // Re-apply the editable element's attributes when the error or description changes.\n useEffect(() => {\n ariaRef.current = aria\n\n if (editor && !editor.isDestroyed) {\n editor.view.setProps({})\n }\n }, [editor, aria])\n\n // Measured in the browser once the editor exists (the toolbar only renders then).\n const stickyTop = useMemo(\n () => (sticky && editor ? resolveStickyOffset(component.stickyOffset) : null),\n [sticky, editor, component.stickyOffset],\n )\n\n // A floating toolbar follows its selection while the page scrolls.\n const hasFloatingToolbars = Boolean(component.floatingToolbars)\n\n useEffect(() => {\n if (!hasFloatingToolbars) return\n\n const onScroll = () => setSelectionTick((tick) => tick + 1)\n\n window.addEventListener('scroll', onScroll, { capture: true, passive: true })\n\n return () => window.removeEventListener('scroll', onScroll, { capture: true })\n }, [hasFloatingToolbars])\n\n const pickImage = () => fileInput.current?.click()\n\n const uploadImage = async (file: File) => {\n if (!panel.endpoints.upload || !editor) return\n\n // The server checks again when the form is saved.\n const problem = fileAttachmentError(file, component.fileAttachments)\n\n if (problem) {\n setAttachmentError(\n problem === 'too_large'\n ? t('form.file_too_large', {}, 'This file is too large.')\n : t('form.file_invalid_type', {}, 'This file type is not allowed.'),\n )\n\n return\n }\n\n setAttachmentError(null)\n\n let response: { key: string }\n\n try {\n // Large attachments go in resumable chunks.\n response = await uploadTemporaryFile(\n panel.endpoints.upload,\n file,\n panelUploadOptions(panel, component.fileAttachments.uploadPolicy),\n )\n } catch (error) {\n setAttachmentError(\n (error as { errors?: Record<string, string[]> }).errors?.file?.[0] ??\n t('form.upload_failed', {}, 'The file could not be uploaded.'),\n )\n\n return\n }\n\n const src = attachments.add(response.key, file)\n\n editor.chain().focus().setImage({ src, alt: file.name }).run()\n }\n\n const translate = (key: string, fallback: string) => t(key, {}, fallback)\n\n const iconFor = (name: string): ReactNode => {\n if (ICONS[name]) return ICONS[name]\n\n const tool = component.editorTools?.find((candidate) => candidate.name === name)\n\n return tool?.icon ? (\n <Icon name={tool.icon} />\n ) : (\n <span className=\"text-xs\">{commandLabel(name, commands, translate)}</span>\n )\n }\n\n const runCommand = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return\n\n button.run(editor, {\n // Messages arrive translated (`form.link_prompt`, `editor.grid_columns`).\n prompt: (message) => window.prompt(message),\n pickImage,\n togglePanel: (target) => setOpenPanel((current) => (current === target ? null : target)),\n t: translate,\n })\n }\n\n const renderButton = (name: string) => {\n const button = commands[name]\n\n if (!button || !editor) return null\n\n const label = commandLabel(name, commands, translate)\n const active = button.active?.(editor) || (name === 'textColor' && openPanel === name)\n\n return (\n <Button\n key={name}\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={label}\n title={label}\n aria-pressed={button.active || name === 'textColor' ? Boolean(active) : undefined}\n disabled={!editable}\n className={cn(active && 'bg-accent text-accent-foreground')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => runCommand(name)}\n >\n {iconFor(name)}\n </Button>\n )\n }\n\n /** A toolbar entry: separator, group dropdown, panel toggle or button. */\n const renderEntry = (name: string, index: number) => {\n if (!editor) return null\n\n if (name === '|') {\n return <Separator key={`sep-${index}`} orientation=\"vertical\" className=\"mx-1 h-5\" />\n }\n\n if (isToolbarGroup(name)) {\n const group = component.toolbarButtonGroups?.[name]\n\n return group ? (\n <ToolbarGroupButton\n key={name}\n group={group}\n editor={editor}\n editable={editable}\n commands={commands}\n icons={iconFor}\n translate={translate}\n onRun={runCommand}\n />\n ) : null\n }\n\n const target = toolbarPanel(name)\n\n if (target) {\n return (\n <ExtensionToolbarButton\n key={name}\n name={target}\n component={component}\n editable={editable}\n open={sidePanel === target}\n onToggle={() => setSidePanel((current) => (current === target ? null : target))}\n />\n )\n }\n\n return renderButton(name)\n }\n\n const floating = editable ? resolveFloatingToolbar(editor, component.floatingToolbars) : null\n // A `table` floating toolbar replaces the table row of buttons.\n const tableRow =\n editor &&\n editable &&\n !component.floatingToolbars?.table &&\n showsTableToolbar(allToolbarButtons(component), editor)\n\n return (\n <FieldWrapper field={component} htmlFor={id} error={error}>\n <div\n className={cn(\n 'border-input dark:bg-input/30 focus-within:border-ring focus-within:ring-ring/50 rounded-md border shadow-xs focus-within:ring-[3px]',\n error && 'border-destructive',\n disabled && 'opacity-60',\n )}\n >\n {component.toolbarButtons.length > 0 && editor && (\n <div\n ref={mainToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.toolbar', {}, 'Formatting')}\n aria-keyshortcuts=\"Alt+F10\"\n data-slot=\"rich-editor-toolbar\"\n className={cn(\n 'flex flex-wrap items-center gap-0.5 border-b p-1',\n component.stickyToolbar && 'bg-background sticky z-10 rounded-t-md',\n )}\n // The offset is a runtime value: no Tailwind class can hold it.\n style={component.stickyToolbar ? stickyStyle(stickyTop ?? '0px') : undefined}\n >\n {component.toolbarButtons.map(renderEntry)}\n </div>\n )}\n {editor && openPanel === 'textColor' && (\n <div\n role=\"group\"\n aria-label={toolbarButtonLabel('textColor', translate)}\n className=\"flex flex-wrap items-center gap-1 border-b p-1\"\n data-slot=\"rich-editor-text-colors\"\n >\n {(component.textColors ?? []).map((color) => (\n <button\n key={color.value}\n type=\"button\"\n title={color.label}\n aria-label={color.label}\n aria-pressed={editor.getAttributes('textColor').color === color.value}\n className=\"focus-visible:ring-ring/50 size-6 rounded-full border shadow-xs outline-none focus-visible:ring-[3px] aria-pressed:ring-2\"\n // Palette colours are runtime values: no Tailwind class can hold them.\n style={{ backgroundColor: color.value }}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, color.value)\n setOpenPanel(null)\n }}\n />\n ))}\n {component.customTextColors && (\n <input\n type=\"color\"\n aria-label={t('editor.custom_color', {}, 'Custom color')}\n title={t('editor.custom_color', {}, 'Custom color')}\n className=\"size-6 cursor-pointer rounded border bg-transparent p-0\"\n onChange={(event) => setTextColor(editor, event.target.value)}\n />\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label={t('editor.remove_color', {}, 'Remove color')}\n title={t('editor.remove_color', {}, 'Remove color')}\n onMouseDown={(event) => event.preventDefault()}\n onClick={() => {\n setTextColor(editor, null)\n setOpenPanel(null)\n }}\n >\n <EraserIcon />\n </Button>\n </div>\n )}\n {tableRow && (\n <div\n ref={tableToolbarRef}\n role=\"toolbar\"\n aria-label={toolbarButtonLabel('table', translate)}\n className=\"flex flex-wrap items-center gap-0.5 border-b p-1\"\n data-slot=\"rich-editor-table-toolbar\"\n >\n {TABLE_TOOLBAR_BUTTONS.map((name) => renderButton(name))}\n </div>\n )}\n <div className=\"@container\">\n <div className=\"flex flex-col-reverse @2xl:flex-row\">\n <EditorContent\n editor={editor}\n className=\"min-w-0 flex-1\"\n style={\n component.maxHeight\n ? { maxHeight: component.maxHeight, overflowY: 'auto' }\n : undefined\n }\n />\n {editor && sidePanel && (\n <RichEditorSidePanel\n panel={sidePanel}\n component={component}\n editable={editable}\n sticky={component.stickyPanels ? (stickyTop ?? '0px') : null}\n onInsertBlock={(blockId) => void richExtensions.insertBlock(blockId)}\n onInsertMergeTag={(tagId) =>\n editor\n .chain()\n .focus()\n .insertContent({ type: 'mergeTag', attrs: { id: tagId } })\n .run()\n }\n onClose={() => setSidePanel(null)}\n />\n )}\n </div>\n </div>\n {editor && floating && floating.buttons.length > 0 && (\n <div\n ref={floatingToolbarRef}\n role=\"toolbar\"\n aria-label={t('editor.floating_toolbar', {}, 'Selection formatting')}\n data-slot=\"rich-editor-floating-toolbar\"\n data-node={floating.node}\n className=\"bg-popover text-popover-foreground fixed z-50 flex items-center gap-0.5 rounded-md border p-1 shadow-md\"\n // Runtime coordinates cannot be Tailwind classes.\n style={floatingToolbarStyle(floating.coords)}\n >\n {floating.buttons.map(renderEntry)}\n </div>\n )}\n <SuggestionPopup\n suggestions={richExtensions.suggestions}\n editor={editor}\n id={`${id}-suggestions`}\n />\n {attachmentError && (\n <p role=\"alert\" className=\"text-destructive px-3 pb-2 text-xs\">\n {attachmentError}\n </p>\n )}\n <input\n ref={fileInput}\n type=\"file\"\n accept={(component.fileAttachments.acceptedFileTypes ?? ['image/*']).join(',')}\n className=\"sr-only\"\n tabIndex={-1}\n aria-hidden=\"true\"\n onChange={(event) => event.target.files?.[0] && void uploadImage(event.target.files[0])}\n />\n </div>\n </FieldWrapper>\n )\n}\n"],"mappings":"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"}
|
|
@@ -75,7 +75,7 @@ function FormBlock$1({ block }) {
|
|
|
75
75
|
submitting.current = true;
|
|
76
76
|
submittedBlock.current = latestBlock.current;
|
|
77
77
|
store.setProcessing(true);
|
|
78
|
-
runner.execute(action, ctx, store.state.values);
|
|
78
|
+
store.whenSettled(() => runner.execute(action, ctx, store.state.values));
|
|
79
79
|
}, [runner, store]);
|
|
80
80
|
useEffect(() => registerFormSubmitter(block.component, trigger), [block.component, trigger]);
|
|
81
81
|
useEffect(() => router.on("success", (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-block.js","names":["FormBlock"],"sources":["../../../src/components/form/form-block.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema, FormBlock as FormBlockSchema } from '@alfakit/core'\nimport {\n FormStore,\n applyFormRefresh,\n formActionsVisible,\n formIdentity,\n formRefreshFor,\n isImplicitSubmit,\n isLeavingVisit,\n pageFlash,\n pageFormName,\n registerConflictForm,\n registerFormSubmitter,\n registerPageForm,\n resolvePartialProps,\n shouldResyncForm,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useActionRunner, useAlfakitProps, usePageSchema, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { ActionList } from '../actions/action-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { FormProvider } from './form-context'\n\n/**\n * A page form: its state lives in a FormStore, and its buttons are actions that post the\n * current values through the action endpoint.\n */\nexport function FormBlock({ block }: { block: FormBlockSchema }) {\n const panel = usePanel()\n const page = usePageSchema()\n const props = useAlfakitProps()\n const runner = useActionRunner()\n const t = useT()\n const token = page?.token ?? ''\n\n // The store survives reloads of the same form (table sorting/paging, polling,\n // relation-manager actions all deliver a new `block` object); only a different form\n // identity starts from server state again. The token is re-issued per response, so it\n // is swapped in place instead of being part of the identity.\n const identity = formIdentity(page, block)\n\n const store = useMemo(\n () =>\n new FormStore(block, {\n token,\n component: block.component,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n // Live dashboard filters are applied by the refresh; the widgets reload to follow.\n onRefreshed: () => {\n if (latestBlock.current.liveFilters)\n router.reload({ only: resolvePartialProps(['page']) })\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [identity],\n )\n\n useEffect(() => () => store.destroy(), [store])\n\n // Named forms of custom pages (`<PageForm name>`) publish their store for page method calls.\n useEffect(() => {\n const name = pageFormName(block.component)\n\n return name ? registerPageForm(name, store) : undefined\n }, [block.component, store])\n\n // Edit pages: the conflict banner diffs and merges the main form (`detectConflicts()`).\n useEffect(\n () => (block.component === 'form' ? registerConflictForm(store) : undefined),\n [block.component, store],\n )\n\n useEffect(() => {\n store.setToken(token)\n }, [store, token])\n\n useEffect(() => {\n store.setErrors((props.errors as Record<string, string>) ?? {})\n }, [props.errors, store])\n\n const state = useStore(store.store)\n const context = useMemo<ActionContext>(() => ({ token, component: 'page' }), [token])\n const submitting = useRef(false)\n // After this form's own successful submit, refill from the server's new state. The new\n // block may arrive before or after Inertia's `success` event, so handle both orders.\n const latestBlock = useRef(block)\n const submittedBlock = useRef<FormBlockSchema | null>(null)\n const refillPending = useRef(false)\n // Another action succeeded and its new block has not arrived yet.\n const resyncPending = useRef(false)\n // The block when the latest non-GET visit started, to tell whether its response arrived.\n const visitBlock = useRef<FormBlockSchema | null>(null)\n\n useEffect(() => {\n latestBlock.current = block\n\n if (refillPending.current) {\n refillPending.current = false\n store.fill(block)\n }\n\n if (resyncPending.current) {\n resyncPending.current = false\n\n if (!store.isDirty) store.fill(block)\n }\n }, [block, store])\n\n const actions: ActionSchema[] = useMemo(\n () => block.actions.map((action) => ({ ...action, hasModal: false })),\n [block.actions],\n )\n\n const trigger = useCallback(\n async (action: ActionSchema, ctx: ActionContext) => {\n if (action.url && !action.hasAction) {\n return runner.trigger(action, ctx)\n }\n\n submitting.current = true\n submittedBlock.current = latestBlock.current\n store.setProcessing(true)\n runner.execute(action, ctx, store.state.values)\n },\n [runner, store],\n )\n\n // Buttons elsewhere with `formId('form')` (a header \"Save\") submit through this form.\n useEffect(() => registerFormSubmitter(block.component, trigger), [block.component, trigger])\n\n // `refreshFormData([...])`: an action changed the record; copy those fields in.\n useEffect(\n () =>\n router.on('success', (event) => {\n const refresh = formRefreshFor(pageFlash(event.detail.page), block.component)\n\n if (refresh) applyFormRefresh(store, refresh)\n }),\n [block.component, store],\n )\n\n // Wizard forms (`HasWizard` pages) show their buttons on the last step only.\n const progressStore = useMemo(() => wizardProgressStore(store), [store])\n const progress = useStore(progressStore)\n const showActions = formActionsVisible(block, progress)\n\n // Track this form's own submit visit so unrelated reloads finishing meanwhile (polling,\n // relation-manager paging) neither end its processing state nor count as its success.\n const submitVisit = useRef<string | null>(null)\n\n useEffect(() => {\n const offStart = router.on('start', (event) => {\n const visit = event.detail.visit\n\n if (submitting.current && !submitVisit.current && visit.method !== 'get') {\n submitVisit.current = visit.id\n }\n\n if (visit.method !== 'get') visitBlock.current = latestBlock.current\n })\n\n const offSuccess = router.on('success', (event) => {\n const id = submitVisit.current\n\n // Another action changed the record: show the server's new form unless it holds edits.\n if (!id && shouldResyncForm(pageFlash(event.detail.page), store)) {\n if (latestBlock.current !== visitBlock.current) {\n store.fill(latestBlock.current)\n } else {\n resyncPending.current = true\n }\n\n return\n }\n\n if (!id || (event.detail.visitId ?? id) !== id) return\n\n // A halted action (rate limit, `halt()`) saved nothing: keep what the user typed.\n if (pageFlash(event.detail.page).alfakitAction?.status === 'halted') return\n\n if (latestBlock.current !== submittedBlock.current) {\n store.fill(latestBlock.current)\n } else {\n refillPending.current = true\n }\n })\n\n const offFinish = router.on('finish', (event) => {\n if (submitVisit.current && event.detail.visit.id !== submitVisit.current) return\n\n store.setProcessing(false)\n submitting.current = false\n submitVisit.current = null\n })\n\n return () => {\n offStart()\n offSuccess()\n offFinish()\n }\n }, [store])\n\n useEffect(() => {\n if (!panel.unsavedChangesAlerts) {\n return\n }\n\n return router.on('before', (event) => {\n if (\n store.isDirty &&\n !submitting.current &&\n isLeavingVisit(event.detail.visit) &&\n !window.confirm(t('unsaved_changes', {}, 'You have unsaved changes. Leave anyway?'))\n ) {\n event.preventDefault()\n }\n })\n }, [panel.unsavedChangesAlerts, store, t])\n\n const primary = actions[0]\n\n return (\n <FormProvider store={store}>\n <form\n data-slot=\"form-block\"\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n\n if (primary && showActions) {\n void trigger(primary, context)\n }\n }}\n onKeyDown={(event) => {\n // Enter in a text field runs the primary action (actions are type=\"button\").\n if (primary && showActions && isImplicitSubmit(event.nativeEvent, event.currentTarget)) {\n event.preventDefault()\n void trigger(primary, context)\n }\n }}\n >\n <fieldset disabled={state.processing || block.operation === 'view'} className=\"contents\">\n <SchemaRenderer components={state.components} columns={state.columns} />\n </fieldset>\n {actions.length > 0 && showActions && (\n <div data-slot=\"form-actions\" className=\"border-t pt-6\">\n <ActionList actions={actions} context={context} onTrigger={trigger} />\n </div>\n )}\n </form>\n </FormProvider>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"form-block.js","names":["FormBlock"],"sources":["../../../src/components/form/form-block.tsx"],"sourcesContent":["import type { ActionContext, ActionSchema, FormBlock as FormBlockSchema } from '@alfakit/core'\nimport {\n FormStore,\n applyFormRefresh,\n formActionsVisible,\n formIdentity,\n formRefreshFor,\n isImplicitSubmit,\n isLeavingVisit,\n pageFlash,\n pageFormName,\n registerConflictForm,\n registerFormSubmitter,\n registerPageForm,\n resolvePartialProps,\n shouldResyncForm,\n wizardProgressStore,\n} from '@alfakit/core'\nimport { router } from '@inertiajs/react'\nimport { useCallback, useEffect, useMemo, useRef } from 'react'\nimport { useActionRunner, useAlfakitProps, usePageSchema, usePanel, useT } from '../../context'\nimport { useStore } from '../../hooks/use-store'\nimport { ActionList } from '../actions/action-list'\nimport { showToast } from '../notifications/flash-toasts'\nimport { SchemaRenderer } from '../schema/schema-renderer'\nimport { FormProvider } from './form-context'\n\n/**\n * A page form: its state lives in a FormStore, and its buttons are actions that post the\n * current values through the action endpoint.\n */\nexport function FormBlock({ block }: { block: FormBlockSchema }) {\n const panel = usePanel()\n const page = usePageSchema()\n const props = useAlfakitProps()\n const runner = useActionRunner()\n const t = useT()\n const token = page?.token ?? ''\n\n // The store survives reloads of the same form (table sorting/paging, polling,\n // relation-manager actions all deliver a new `block` object); only a different form\n // identity starts from server state again. The token is re-issued per response, so it\n // is swapped in place instead of being part of the identity.\n const identity = formIdentity(page, block)\n\n const store = useMemo(\n () =>\n new FormStore(block, {\n token,\n component: block.component,\n refreshUrl: panel.endpoints.schemaRefresh,\n optionsUrl: panel.endpoints.schemaOptions,\n validateUrl: panel.endpoints.schemaValidate,\n // A halted wizard step shows what its hook sent.\n onNotifications: (notifications) => notifications.forEach((n) => showToast(n)),\n // Live dashboard filters are applied by the refresh; the widgets reload to follow.\n onRefreshed: () => {\n if (latestBlock.current.liveFilters)\n router.reload({ only: resolvePartialProps(['page']) })\n },\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [identity],\n )\n\n useEffect(() => () => store.destroy(), [store])\n\n // Named forms of custom pages (`<PageForm name>`) publish their store for page method calls.\n useEffect(() => {\n const name = pageFormName(block.component)\n\n return name ? registerPageForm(name, store) : undefined\n }, [block.component, store])\n\n // Edit pages: the conflict banner diffs and merges the main form (`detectConflicts()`).\n useEffect(\n () => (block.component === 'form' ? registerConflictForm(store) : undefined),\n [block.component, store],\n )\n\n useEffect(() => {\n store.setToken(token)\n }, [store, token])\n\n useEffect(() => {\n store.setErrors((props.errors as Record<string, string>) ?? {})\n }, [props.errors, store])\n\n const state = useStore(store.store)\n const context = useMemo<ActionContext>(() => ({ token, component: 'page' }), [token])\n const submitting = useRef(false)\n // After this form's own successful submit, refill from the server's new state. The new\n // block may arrive before or after Inertia's `success` event, so handle both orders.\n const latestBlock = useRef(block)\n const submittedBlock = useRef<FormBlockSchema | null>(null)\n const refillPending = useRef(false)\n // Another action succeeded and its new block has not arrived yet.\n const resyncPending = useRef(false)\n // The block when the latest non-GET visit started, to tell whether its response arrived.\n const visitBlock = useRef<FormBlockSchema | null>(null)\n\n useEffect(() => {\n latestBlock.current = block\n\n if (refillPending.current) {\n refillPending.current = false\n store.fill(block)\n }\n\n if (resyncPending.current) {\n resyncPending.current = false\n\n if (!store.isDirty) store.fill(block)\n }\n }, [block, store])\n\n const actions: ActionSchema[] = useMemo(\n () => block.actions.map((action) => ({ ...action, hasModal: false })),\n [block.actions],\n )\n\n const trigger = useCallback(\n async (action: ActionSchema, ctx: ActionContext) => {\n if (action.url && !action.hasAction) {\n return runner.trigger(action, ctx)\n }\n\n submitting.current = true\n submittedBlock.current = latestBlock.current\n store.setProcessing(true)\n // A click that blurred a live field started a refresh: send the state it returns.\n store.whenSettled(() => runner.execute(action, ctx, store.state.values))\n },\n [runner, store],\n )\n\n // Buttons elsewhere with `formId('form')` (a header \"Save\") submit through this form.\n useEffect(() => registerFormSubmitter(block.component, trigger), [block.component, trigger])\n\n // `refreshFormData([...])`: an action changed the record; copy those fields in.\n useEffect(\n () =>\n router.on('success', (event) => {\n const refresh = formRefreshFor(pageFlash(event.detail.page), block.component)\n\n if (refresh) applyFormRefresh(store, refresh)\n }),\n [block.component, store],\n )\n\n // Wizard forms (`HasWizard` pages) show their buttons on the last step only.\n const progressStore = useMemo(() => wizardProgressStore(store), [store])\n const progress = useStore(progressStore)\n const showActions = formActionsVisible(block, progress)\n\n // Track this form's own submit visit so unrelated reloads finishing meanwhile (polling,\n // relation-manager paging) neither end its processing state nor count as its success.\n const submitVisit = useRef<string | null>(null)\n\n useEffect(() => {\n const offStart = router.on('start', (event) => {\n const visit = event.detail.visit\n\n if (submitting.current && !submitVisit.current && visit.method !== 'get') {\n submitVisit.current = visit.id\n }\n\n if (visit.method !== 'get') visitBlock.current = latestBlock.current\n })\n\n const offSuccess = router.on('success', (event) => {\n const id = submitVisit.current\n\n // Another action changed the record: show the server's new form unless it holds edits.\n if (!id && shouldResyncForm(pageFlash(event.detail.page), store)) {\n if (latestBlock.current !== visitBlock.current) {\n store.fill(latestBlock.current)\n } else {\n resyncPending.current = true\n }\n\n return\n }\n\n if (!id || (event.detail.visitId ?? id) !== id) return\n\n // A halted action (rate limit, `halt()`) saved nothing: keep what the user typed.\n if (pageFlash(event.detail.page).alfakitAction?.status === 'halted') return\n\n if (latestBlock.current !== submittedBlock.current) {\n store.fill(latestBlock.current)\n } else {\n refillPending.current = true\n }\n })\n\n const offFinish = router.on('finish', (event) => {\n if (submitVisit.current && event.detail.visit.id !== submitVisit.current) return\n\n store.setProcessing(false)\n submitting.current = false\n submitVisit.current = null\n })\n\n return () => {\n offStart()\n offSuccess()\n offFinish()\n }\n }, [store])\n\n useEffect(() => {\n if (!panel.unsavedChangesAlerts) {\n return\n }\n\n return router.on('before', (event) => {\n if (\n store.isDirty &&\n !submitting.current &&\n isLeavingVisit(event.detail.visit) &&\n !window.confirm(t('unsaved_changes', {}, 'You have unsaved changes. Leave anyway?'))\n ) {\n event.preventDefault()\n }\n })\n }, [panel.unsavedChangesAlerts, store, t])\n\n const primary = actions[0]\n\n return (\n <FormProvider store={store}>\n <form\n data-slot=\"form-block\"\n className=\"flex flex-col gap-6\"\n onSubmit={(event) => {\n event.preventDefault()\n\n if (primary && showActions) {\n void trigger(primary, context)\n }\n }}\n onKeyDown={(event) => {\n // Enter in a text field runs the primary action (actions are type=\"button\").\n if (primary && showActions && isImplicitSubmit(event.nativeEvent, event.currentTarget)) {\n event.preventDefault()\n void trigger(primary, context)\n }\n }}\n >\n <fieldset disabled={state.processing || block.operation === 'view'} className=\"contents\">\n <SchemaRenderer components={state.components} columns={state.columns} />\n </fieldset>\n {actions.length > 0 && showActions && (\n <div data-slot=\"form-actions\" className=\"border-t pt-6\">\n <ActionList actions={actions} context={context} onTrigger={trigger} />\n </div>\n )}\n </form>\n </FormProvider>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useStore, useStoreSelector } from "../../hooks/use-store.js";
|
|
2
|
-
import { fieldAria, fieldDomId } from "@alfakit/core";
|
|
2
|
+
import { fieldAria, fieldDomId, fieldError } from "@alfakit/core";
|
|
3
3
|
import { createContext, useCallback, useContext, useMemo } from "react";
|
|
4
4
|
import { jsx } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/form/form-context.tsx
|
|
@@ -33,7 +33,7 @@ function useField(field) {
|
|
|
33
33
|
}
|
|
34
34
|
return current;
|
|
35
35
|
});
|
|
36
|
-
const error = useStoreSelector(store.store, (state) => state.errors
|
|
36
|
+
const error = useStoreSelector(store.store, (state) => fieldError(state.errors, path));
|
|
37
37
|
const setValue = useCallback((next) => store.setValue(path, next), [store, path]);
|
|
38
38
|
const blur = useCallback(() => store.blur(path), [store, path]);
|
|
39
39
|
const id = useMemo(() => field.id ?? fieldDomId(store.idPrefix, path), [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"form-context.js","names":[],"sources":["../../../src/components/form/form-context.tsx"],"sourcesContent":["import type { BaseField, FieldAriaAttributes, FormState, FormStore } from '@alfakit/core'\nimport { fieldAria, fieldDomId } from '@alfakit/core'\nimport { createContext, useCallback, useContext, useMemo, type ReactNode } from 'react'\nimport { useStore, useStoreSelector } from '../../hooks/use-store'\n\nconst FormStoreContext = createContext<FormStore | null>(null)\n\nexport function FormProvider({ store, children }: { store: FormStore; children: ReactNode }) {\n return <FormStoreContext.Provider value={store}>{children}</FormStoreContext.Provider>\n}\n\nexport function useFormStore(): FormStore {\n const store = useContext(FormStoreContext)\n\n if (!store) {\n throw new Error('Form fields must be rendered inside a FormProvider.')\n }\n\n return store\n}\n\nexport function useOptionalFormStore(): FormStore | null {\n return useContext(FormStoreContext)\n}\n\nexport function useFormState(): FormState {\n return useStore(useFormStore().store)\n}\n\nexport interface FieldBinding<T = unknown> {\n value: T\n setValue: (value: T) => void\n blur: () => void\n error: string | undefined\n id: string\n /** The field cannot be interacted with at all (`disabled()`). */\n disabled: boolean\n /** The field shows its value but the user cannot change it (`readOnly()`). */\n readOnly: boolean\n /** `aria-describedby` (hint, helper text, error), `aria-invalid` and `aria-required` for the control. */\n aria: FieldAriaAttributes\n}\n\n/** Bind a field schema (a built-in or custom field) to the form store. */\nexport function useField<T = unknown>(field: BaseField): FieldBinding<T> {\n const store = useFormStore()\n const path = field.statePath\n\n const value = useStoreSelector(store.store, (state) => {\n let current: unknown = state.values\n\n for (const segment of path.split('.')) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined as T\n }\n\n current = (current as Record<string, unknown>)[segment]\n }\n\n return current as T\n })\n\n const error = useStoreSelector(store.store, (state) => state.errors
|
|
1
|
+
{"version":3,"file":"form-context.js","names":[],"sources":["../../../src/components/form/form-context.tsx"],"sourcesContent":["import type { BaseField, FieldAriaAttributes, FormState, FormStore } from '@alfakit/core'\nimport { fieldAria, fieldDomId, fieldError } from '@alfakit/core'\nimport { createContext, useCallback, useContext, useMemo, type ReactNode } from 'react'\nimport { useStore, useStoreSelector } from '../../hooks/use-store'\n\nconst FormStoreContext = createContext<FormStore | null>(null)\n\nexport function FormProvider({ store, children }: { store: FormStore; children: ReactNode }) {\n return <FormStoreContext.Provider value={store}>{children}</FormStoreContext.Provider>\n}\n\nexport function useFormStore(): FormStore {\n const store = useContext(FormStoreContext)\n\n if (!store) {\n throw new Error('Form fields must be rendered inside a FormProvider.')\n }\n\n return store\n}\n\nexport function useOptionalFormStore(): FormStore | null {\n return useContext(FormStoreContext)\n}\n\nexport function useFormState(): FormState {\n return useStore(useFormStore().store)\n}\n\nexport interface FieldBinding<T = unknown> {\n value: T\n setValue: (value: T) => void\n blur: () => void\n error: string | undefined\n id: string\n /** The field cannot be interacted with at all (`disabled()`). */\n disabled: boolean\n /** The field shows its value but the user cannot change it (`readOnly()`). */\n readOnly: boolean\n /** `aria-describedby` (hint, helper text, error), `aria-invalid` and `aria-required` for the control. */\n aria: FieldAriaAttributes\n}\n\n/** Bind a field schema (a built-in or custom field) to the form store. */\nexport function useField<T = unknown>(field: BaseField): FieldBinding<T> {\n const store = useFormStore()\n const path = field.statePath\n\n const value = useStoreSelector(store.store, (state) => {\n let current: unknown = state.values\n\n for (const segment of path.split('.')) {\n if (current === null || current === undefined || typeof current !== 'object') {\n return undefined as T\n }\n\n current = (current as Record<string, unknown>)[segment]\n }\n\n return current as T\n })\n\n const error = useStoreSelector(store.store, (state) => fieldError(state.errors, path))\n\n const setValue = useCallback((next: T) => store.setValue(path, next), [store, path])\n const blur = useCallback(() => store.blur(path), [store, path])\n\n const id = useMemo(() => field.id ?? fieldDomId(store.idPrefix, path), [field.id, store, path])\n\n const aria = useMemo(\n () =>\n fieldAria(\n id,\n { hint: field.hint, helperText: field.helperText, required: field.required },\n error,\n ),\n [id, field.hint, field.helperText, field.required, error],\n )\n\n // Fields stay enabled during a live refresh: the store keeps edits made while the request\n // is in flight, so locking inputs would only swallow keystrokes.\n return {\n value,\n setValue,\n blur,\n error,\n id,\n disabled: Boolean(field.disabled),\n readOnly: Boolean(field.readOnly),\n aria,\n }\n}\n"],"mappings":";;;;;AAKA,MAAM,mBAAmB,cAAgC,IAAI;AAE7D,SAAgB,aAAa,EAAE,OAAO,YAAuD;CAC3F,OAAO,oBAAC,iBAAiB,UAAlB;EAA2B,OAAO;EAAQ;CAAoC,CAAA;AACvF;AAEA,SAAgB,eAA0B;CACxC,MAAM,QAAQ,WAAW,gBAAgB;CAEzC,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,qDAAqD;CAGvE,OAAO;AACT;AAEA,SAAgB,uBAAyC;CACvD,OAAO,WAAW,gBAAgB;AACpC;AAEA,SAAgB,eAA0B;CACxC,OAAO,SAAS,aAAa,CAAC,CAAC,KAAK;AACtC;;AAiBA,SAAgB,SAAsB,OAAmC;CACvE,MAAM,QAAQ,aAAa;CAC3B,MAAM,OAAO,MAAM;CAEnB,MAAM,QAAQ,iBAAiB,MAAM,QAAQ,UAAU;EACrD,IAAI,UAAmB,MAAM;EAE7B,KAAK,MAAM,WAAW,KAAK,MAAM,GAAG,GAAG;GACrC,IAAI,YAAY,QAAQ,YAAY,KAAA,KAAa,OAAO,YAAY,UAClE;GAGF,UAAW,QAAoC;EACjD;EAEA,OAAO;CACT,CAAC;CAED,MAAM,QAAQ,iBAAiB,MAAM,QAAQ,UAAU,WAAW,MAAM,QAAQ,IAAI,CAAC;CAErF,MAAM,WAAW,aAAa,SAAY,MAAM,SAAS,MAAM,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC;CACnF,MAAM,OAAO,kBAAkB,MAAM,KAAK,IAAI,GAAG,CAAC,OAAO,IAAI,CAAC;CAE9D,MAAM,KAAK,cAAc,MAAM,MAAM,WAAW,MAAM,UAAU,IAAI,GAAG;EAAC,MAAM;EAAI;EAAO;CAAI,CAAC;CAE9F,MAAM,OAAO,cAET,UACE,IACA;EAAE,MAAM,MAAM;EAAM,YAAY,MAAM;EAAY,UAAU,MAAM;CAAS,GAC3E,KACF,GACF;EAAC;EAAI,MAAM;EAAM,MAAM;EAAY,MAAM;EAAU;CAAK,CAC1D;CAIA,OAAO;EACL;EACA;EACA;EACA;EACA;EACA,UAAU,QAAQ,MAAM,QAAQ;EAChC,UAAU,QAAQ,MAAM,QAAQ;EAChC;CACF;AACF"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useActionRunner, useDirection, useFormatter, usePanel, useT } from "../../context.js";
|
|
1
|
+
import { useActionRunner, useChoice, useDirection, useFormatter, usePanel, useT } from "../../context.js";
|
|
2
2
|
import { usePageActionContext } from "../actions/action-context.js";
|
|
3
3
|
import { cn } from "../../lib/utils.js";
|
|
4
4
|
import { badgeColorClasses, colorClass, cssColor } from "../../lib/colors.js";
|
|
@@ -303,6 +303,7 @@ function KanbanFilters({ controller, store, t }) {
|
|
|
303
303
|
})] });
|
|
304
304
|
}
|
|
305
305
|
function ColumnHeader({ column, id, collapsed, creatable, filtered, onToggle, onCreate, t }) {
|
|
306
|
+
const choice = useChoice();
|
|
306
307
|
const wip = kanbanWipState(column);
|
|
307
308
|
const shown = column.wipLimit !== void 0 ? column.count : filtered ? column.total : column.count;
|
|
308
309
|
const countLabel = column.wipLimit !== void 0 ? t("kanban.wip", {
|
|
@@ -311,7 +312,7 @@ function ColumnHeader({ column, id, collapsed, creatable, filtered, onToggle, on
|
|
|
311
312
|
}, ":count of :limit") : filtered && column.total !== column.count ? t("kanban.count_filtered", {
|
|
312
313
|
shown: column.total,
|
|
313
314
|
count: column.count
|
|
314
|
-
}, ":shown of :count cards") :
|
|
315
|
+
}, ":shown of :count cards") : choice("kanban.count", column.count, {}, "{1} :count card|[0,*] :count cards");
|
|
315
316
|
const style = column.color ? { borderTopColor: cssColor(column.color) } : void 0;
|
|
316
317
|
const toggleLabel = collapsed ? t("kanban.expand", { column: column.label }, "Expand :column") : t("kanban.collapse", { column: column.label }, "Collapse :column");
|
|
317
318
|
if (collapsed) return /* @__PURE__ */ jsxs("div", {
|
|
@@ -403,7 +404,7 @@ function ColumnHeader({ column, id, collapsed, creatable, filtered, onToggle, on
|
|
|
403
404
|
}),
|
|
404
405
|
wip === "over" && /* @__PURE__ */ jsx("p", {
|
|
405
406
|
className: "text-destructive text-xs",
|
|
406
|
-
children:
|
|
407
|
+
children: choice("kanban.wip_over", column.wipLimit ?? 0, { limit: column.wipLimit ?? 0 }, "{1} Over the limit of :limit card|[0,*] Over the limit of :limit cards")
|
|
407
408
|
}),
|
|
408
409
|
wip === "full" && column.wipStrict && /* @__PURE__ */ jsx("p", {
|
|
409
410
|
className: "text-muted-foreground text-xs",
|