@qijenchen/design-system 0.1.0-beta.74 → 0.1.0-beta.76
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/CLAUDE.md +1 -1
- package/dist/components/AppShell/app-shell.d.ts +2 -2
- package/dist/components/AppShell/app-shell.js.map +1 -1
- package/dist/components/Avatar/avatar.js.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js +1 -1
- package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
- package/dist/components/Button/button.d.ts.map +1 -1
- package/dist/components/Button/button.js.map +1 -1
- package/dist/components/Chart/chart.d.ts +1 -1
- package/dist/components/Chart/chart.js.map +1 -1
- package/dist/components/Checkbox/checkbox.d.ts +1 -1
- package/dist/components/Checkbox/checkbox.js +1 -1
- package/dist/components/Checkbox/checkbox.js.map +1 -1
- package/dist/components/Combobox/combobox.d.ts +1 -1
- package/dist/components/Combobox/combobox.d.ts.map +1 -1
- package/dist/components/Combobox/combobox.js +13 -10
- package/dist/components/Combobox/combobox.js.map +1 -1
- package/dist/components/Command/command.d.ts +1 -1
- package/dist/components/Command/command.js +3 -3
- package/dist/components/Command/command.js.map +1 -1
- package/dist/components/DataTable/cell-registry.d.ts.map +1 -1
- package/dist/components/DataTable/cell-registry.js +2 -2
- package/dist/components/DataTable/cell-registry.js.map +1 -1
- package/dist/components/DataTable/data-table.d.ts +27 -6
- package/dist/components/DataTable/data-table.d.ts.map +1 -1
- package/dist/components/DataTable/data-table.js +57 -34
- package/dist/components/DataTable/data-table.js.map +1 -1
- package/dist/components/DatePicker/date-picker.d.ts.map +1 -1
- package/dist/components/DatePicker/date-picker.js +2 -2
- package/dist/components/DatePicker/date-picker.js.map +1 -1
- package/dist/components/DescriptionList/description-list.d.ts +1 -1
- package/dist/components/DescriptionList/description-list.js +2 -2
- package/dist/components/DescriptionList/description-list.js.map +1 -1
- package/dist/components/Empty/empty.d.ts +2 -0
- package/dist/components/Empty/empty.d.ts.map +1 -1
- package/dist/components/Empty/empty.js.map +1 -1
- package/dist/components/Field/field-wrapper.js +4 -4
- package/dist/components/Field/field-wrapper.js.map +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.d.ts +1 -1
- package/dist/components/OverflowIndicator/overflow-indicator.js +2 -2
- package/dist/components/OverflowIndicator/overflow-indicator.js.map +1 -1
- package/dist/components/PeoplePicker/people-picker.js +2 -2
- package/dist/components/PeoplePicker/people-picker.js.map +1 -1
- package/dist/components/ProfileCard/profile-card.d.ts +1 -1
- package/dist/components/ProfileCard/profile-card.js +2 -1
- package/dist/components/ProfileCard/profile-card.js.map +1 -1
- package/dist/components/Rating/rating.js.map +1 -1
- package/dist/components/Select/select.js +4 -4
- package/dist/components/Select/select.js.map +1 -1
- package/dist/components/Textarea/textarea.d.ts +1 -1
- package/dist/components/Textarea/textarea.js +2 -2
- package/dist/components/Textarea/textarea.js.map +1 -1
- package/dist/components/TimePicker/time-picker.d.ts.map +1 -1
- package/dist/components/TimePicker/time-picker.js +14 -23
- package/dist/components/TimePicker/time-picker.js.map +1 -1
- package/dist/components/TreeView/tree-view.d.ts +1 -1
- package/dist/components/TreeView/tree-view.js +1 -1
- package/dist/components/TreeView/tree-view.js.map +1 -1
- package/ds-canonical/fork/governance.lock +1 -1
- package/ds-canonical/fork/preamble.md +2 -2
- package/ds-canonical/hooks/check_field_controls_contracts.sh +30 -3
- package/ds-canonical/hooks/check_story_invariants.sh +26 -0
- package/ds-canonical/hooks/tests/test_check_story_invariants.sh +30 -0
- package/ds-canonical/references/props-naming.md +15 -1
- package/ds-canonical/rules/ui-development.md +2 -2
- package/llms-full.txt +7 -2
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/components/Accordion/accordion.principles.stories.tsx +3 -3
- package/src/components/Alert/alert.anatomy.stories.tsx +4 -4
- package/src/components/Alert/alert.principles.stories.tsx +5 -5
- package/src/components/AppShell/app-shell.principles.stories.tsx +6 -6
- package/src/components/AppShell/app-shell.spec.md +4 -4
- package/src/components/AppShell/app-shell.tsx +2 -2
- package/src/components/AspectRatio/aspect-ratio.principles.stories.tsx +1 -1
- package/src/components/Avatar/avatar.principles.stories.tsx +3 -3
- package/src/components/Avatar/avatar.tsx +1 -1
- package/src/components/Badge/badge.principles.stories.tsx +3 -3
- package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +3 -3
- package/src/components/Breadcrumb/breadcrumb.spec.md +11 -1
- package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
- package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +3 -3
- package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
- package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +2 -2
- package/src/components/BulkActionBar/bulk-action-bar.tsx +3 -2
- package/src/components/Button/button.principles.stories.tsx +3 -3
- package/src/components/Button/button.tsx +0 -10
- package/src/components/Calendar/calendar.anatomy.stories.tsx +1 -1
- package/src/components/Calendar/calendar.principles.stories.tsx +3 -3
- package/src/components/Carousel/carousel.principles.stories.tsx +2 -2
- package/src/components/Chart/chart.principles.stories.tsx +4 -4
- package/src/components/Chart/chart.tsx +1 -1
- package/src/components/Checkbox/checkbox.principles.stories.tsx +2 -2
- package/src/components/Checkbox/checkbox.tsx +1 -1
- package/src/components/Chip/chip.principles.stories.tsx +3 -3
- package/src/components/Coachmark/coachmark.anatomy.stories.tsx +1 -1
- package/src/components/Coachmark/coachmark.principles.stories.tsx +3 -3
- package/src/components/Coachmark/coachmark.spec.md +2 -2
- package/src/components/Combobox/combobox.anatomy.stories.tsx +14 -14
- package/src/components/Combobox/combobox.principles.stories.tsx +6 -6
- package/src/components/Combobox/combobox.spec.md +1 -1
- package/src/components/Combobox/combobox.tsx +25 -14
- package/src/components/Command/command.anatomy.stories.tsx +2 -0
- package/src/components/Command/command.principles.stories.tsx +7 -7
- package/src/components/Command/command.tsx +2 -2
- package/src/components/DataTable/cell-registry.tsx +6 -2
- package/src/components/DataTable/data-table-filter-panel.tsx +3 -3
- package/src/components/DataTable/data-table.anatomy.stories.tsx +1 -1
- package/src/components/DataTable/data-table.css +1 -1
- package/src/components/DataTable/data-table.principles.stories.tsx +3 -3
- package/src/components/DataTable/data-table.spec.md +25 -17
- package/src/components/DataTable/data-table.stories.tsx +29 -25
- package/src/components/DataTable/data-table.tsx +92 -44
- package/src/components/DateGrid/date-grid.anatomy.stories.tsx +1 -1
- package/src/components/DateGrid/date-grid.principles.stories.tsx +4 -4
- package/src/components/DateGrid/date-grid.spec.md +1 -1
- package/src/components/DatePicker/date-picker.anatomy.stories.tsx +15 -11
- package/src/components/DatePicker/date-picker.principles.stories.tsx +5 -5
- package/src/components/DatePicker/date-picker.spec.md +1 -1
- package/src/components/DatePicker/date-picker.tsx +9 -6
- package/src/components/DescriptionList/description-list.principles.stories.tsx +5 -5
- package/src/components/DescriptionList/description-list.tsx +1 -1
- package/src/components/Dialog/dialog.anatomy.stories.tsx +1 -1
- package/src/components/Dialog/dialog.principles.stories.tsx +4 -4
- package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
- package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +5 -5
- package/src/components/DropdownMenu/dropdown-menu.spec.md +1 -1
- package/src/components/Empty/empty.principles.stories.tsx +2 -2
- package/src/components/Empty/empty.tsx +2 -0
- package/src/components/Field/field-controls.spec.md +9 -6
- package/src/components/Field/field-wrapper.tsx +4 -4
- package/src/components/Field/field.principles.stories.tsx +4 -4
- package/src/components/FileItem/file-item.principles.stories.tsx +6 -5
- package/src/components/FileUpload/file-upload.principles.stories.tsx +6 -6
- package/src/components/FileUpload/file-upload.spec.md +1 -1
- package/src/components/FileViewer/file-viewer.principles.stories.tsx +5 -5
- package/src/components/HoverCard/hover-card.principles.stories.tsx +6 -6
- package/src/components/Input/input.anatomy.stories.tsx +3 -3
- package/src/components/Input/input.principles.stories.tsx +4 -4
- package/src/components/LinkInput/link-input.anatomy.stories.tsx +3 -3
- package/src/components/LinkInput/link-input.principles.stories.tsx +5 -5
- package/src/components/Menu/menu-item.principles.stories.tsx +7 -7
- package/src/components/Notice/notice.anatomy.stories.tsx +1 -1
- package/src/components/Notice/notice.principles.stories.tsx +7 -7
- package/src/components/NumberInput/number-input.anatomy.stories.tsx +8 -7
- package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
- package/src/components/NumberInput/number-input.spec.md +1 -1
- package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +5 -5
- package/src/components/OverflowIndicator/overflow-indicator.tsx +1 -1
- package/src/components/PeoplePicker/people-picker.principles.stories.tsx +3 -3
- package/src/components/PeoplePicker/people-picker.spec.md +3 -3
- package/src/components/PeoplePicker/people-picker.tsx +6 -6
- package/src/components/Popover/popover.principles.stories.tsx +5 -5
- package/src/components/ProfileCard/profile-card.anatomy.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.principles.stories.tsx +1 -1
- package/src/components/ProfileCard/profile-card.tsx +1 -1
- package/src/components/ProgressBar/progress-bar.principles.stories.tsx +2 -2
- package/src/components/ProgressBar/progress-bar.spec.md +1 -1
- package/src/components/RadioGroup/radio-group.principles.stories.tsx +2 -2
- package/src/components/Rating/rating.anatomy.stories.tsx +2 -2
- package/src/components/Rating/rating.principles.stories.tsx +3 -3
- package/src/components/Rating/rating.spec.md +1 -1
- package/src/components/Rating/rating.tsx +1 -1
- package/src/components/ScrollArea/scroll-area.principles.stories.tsx +4 -4
- package/src/components/Select/select.anatomy.stories.tsx +18 -18
- package/src/components/Select/select.principles.stories.tsx +5 -5
- package/src/components/Select/select.spec.md +1 -1
- package/src/components/Select/select.tsx +7 -7
- package/src/components/SelectMenu/select-menu.anatomy.stories.tsx +1 -1
- package/src/components/SelectMenu/select-menu.principles.stories.tsx +8 -8
- package/src/components/SelectionControl/selection-item.principles.stories.tsx +7 -7
- package/src/components/Separator/separator.principles.stories.tsx +4 -4
- package/src/components/Sheet/sheet.principles.stories.tsx +2 -2
- package/src/components/Sidebar/sidebar.principles.stories.tsx +4 -4
- package/src/components/Sidebar/sidebar.spec.md +2 -2
- package/src/components/Skeleton/skeleton.principles.stories.tsx +5 -5
- package/src/components/Slider/slider.anatomy.stories.tsx +1 -1
- package/src/components/Slider/slider.principles.stories.tsx +3 -3
- package/src/components/Steps/steps.principles.stories.tsx +4 -4
- package/src/components/Steps/steps.spec.md +2 -2
- package/src/components/Switch/switch.principles.stories.tsx +1 -1
- package/src/components/Tabs/tabs.principles.stories.tsx +3 -3
- package/src/components/Tabs/tabs.spec.md +1 -1
- package/src/components/Tag/tag.principles.stories.tsx +3 -3
- package/src/components/Textarea/textarea.principles.stories.tsx +2 -2
- package/src/components/Textarea/textarea.tsx +3 -3
- package/src/components/TimePicker/time-picker.principles.stories.tsx +5 -5
- package/src/components/TimePicker/time-picker.spec.md +1 -1
- package/src/components/TimePicker/time-picker.tsx +11 -12
- package/src/components/Toast/toast.principles.stories.tsx +2 -2
- package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
- package/src/components/TreeView/tree-view.principles.stories.tsx +5 -5
- package/src/components/TreeView/tree-view.stories.tsx +1 -1
- package/src/components/TreeView/tree-view.tsx +1 -1
- package/src/patterns/element-anatomy/item-anatomy.spec.md +1 -1
- package/src/patterns/element-anatomy/item-anatomy.stories.tsx +1 -1
- package/src/patterns/overlay-surface/overlay-surface.spec.md +1 -0
- package/src/patterns/resize-handle/resize-handle.spec.md +1 -1
- package/src/tokens/color/color.spec.md +2 -0
- package/src/tokens/color/semantic.css +1 -1
- package/src/tokens/uiSize/uiSize.css +5 -0
- package/src/tokens/uiSize/uiSize.spec.md +17 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cell-registry.js","sources":["../../../src/components/DataTable/cell-registry.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — Cell Registry 含 10 cell-type components(string/number/date/time/select/multiSelect/person/multiPerson/boolean/url)+ shared helpers,split-into-files 會破壞 type-keyed registry SSOT canonical\n// DataTable Cell Registry — type-keyed SSOT for cell rendering(Phase C 2026-05-05)\n//\n// 對齊 M17 SSOT consolidation + audit recommendation:\n// 原 `renderTypedValue` switch + `EditableCellContent` switch 兩條平行 type-switch 已 collapse\n// 為**一張 type → cell component** registry。每個 cell component 同時處理 display / edit mode,\n// 靠底層 Field control 的 `mode` prop 切換。\n//\n// 設計原則:\n// - 每個 cell component 接同一組 props(`CellComponentProps`)\n// - 用 `variant=\"naked\"` — DataTable cell-as-input substrate(對齊 Field B1 chrome=bare)\n// - 消費 full Field 家族 primitive(無 stub)\n// - 不再用 `meta._editable` 私有 flag — `isEditable` 直接顯式入參(消除 M1 hack)\n//\n// World-class 對照(@benchmark-unverified):AG Grid cellRendererSelector / Material X-Grid\n// `valueGetter + renderCell` / Notion property type registry。\n\nimport * as React from 'react'\nimport type { ComponentType } from 'react'\nimport { Pencil } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { ColumnType } from './column-types'\nimport { Input } from '@/design-system/components/Input/input'\nimport { Textarea } from '@/design-system/components/Textarea/textarea'\nimport { NumberInput } from '@/design-system/components/NumberInput/number-input'\nimport { Select } from '@/design-system/components/Select/select'\nimport { Combobox } from '@/design-system/components/Combobox/combobox'\nimport { DatePicker } from '@/design-system/components/DatePicker/date-picker'\nimport { TimePicker } from '@/design-system/components/TimePicker/time-picker'\nimport { PeoplePicker } from '@/design-system/components/PeoplePicker/people-picker'\nimport { LinkInput } from '@/design-system/components/LinkInput/link-input'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\nimport { Button } from '@/design-system/components/Button/button'\nimport type { PersonValue } from '@/design-system/components/PeoplePicker/person-display'\nimport { FieldSurfaceProvider, FieldSurfaceSizeProvider } from '@/design-system/components/Field/field-context'\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type CellMode = 'display' | 'edit'\nexport type CellSize = 'sm' | 'md' | 'lg'\n\nexport interface CellComponentProps {\n // any-allow: free-form column value(consumer-defined,跨 type 共用 signature)\n value: any\n // any-allow: free-form consumer meta bag(prefix / options / formatOptions / locale / linkLabel 等)\n meta: Record<string, any>\n mode: CellMode\n size: CellSize\n autoRowHeight: boolean\n /** 該 cell 是否可編。replaces 舊 `meta._editable` 私有 flag(Phase C M1 hack 移除)。 */\n isEditable?: boolean\n /** 2026-05-13:cell 是否 disabled(state overlay,orthogonal to display/edit lifecycle)。\n * per codex Q3 verdict:不擴 CellMode='disabled',加 prop。各 Cell function 收 true 時\n * 傳 `mode='disabled'` 給 inner Field control,各 Field 內部走具體 disabled token(非 wrapper opacity)。 */\n isDisabled?: boolean\n /** Cell 進 edit mode → 提交新值(blur / Enter / option select 都觸發)— 提交後**自動 exit edit**。\n * 適用 single-shot commit:string / number / select(single)/ person(single)/ date / time / boolean / url。 */\n onCommit?: (next: unknown) => void\n /** Live commit — 提交新值但 **不 exit edit**(popover 持續開)。\n * 適用 multi-select 類:multiSelect / multiPerson — user 連續勾選,直到點外面才關。\n * 對齊 Notion / Linear / Airtable canonical:multi-pick popover 不在每次 toggle 後關閉。 */\n onCommitLive?: (next: unknown) => void\n /** Esc 取消編輯,不 commit。 */\n onCancel?: () => void\n /** URL cell 專用:hover 顯示的 Pencil 鈕 → 進 edit mode(read mode 保留 link click 語意)。 */\n onRequestEdit?: () => void\n /** Per-keystroke draft propagation(2026-05-10 Phase 7 D.3 portal Field virtualizer unmount preserve draft):\n * Edit mode 內部 input onChange/onValueChange 每 keystroke 呼叫 onDraft,讓 lifted draft state(in\n * data-table.tsx)持有 user 編輯中字。Cell DOM unmount(virtualizer scroll out)時 draft 在\n * parent state 不丟;mount-back 時 portal Field value 從 draft 取,user 字保留。\n * 非 portal mode(inline edit)不傳此 prop,各 Cell 走原 uncontrolled defaultValue 路徑。 */\n onDraft?: (next: unknown) => void\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** 鍵盤 commit / cancel — string / number cell edit mode 共用 */\nfunction makeKeyHandler(\n onCommit?: (v: unknown) => void,\n onCancel?: () => void,\n parseValue?: (raw: string) => unknown,\n) {\n return (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Escape') { e.preventDefault(); onCancel?.() }\n if (e.key === 'Enter') {\n e.preventDefault()\n const raw = (e.target as HTMLInputElement).value\n onCommit?.(parseValue ? parseValue(raw) : raw)\n }\n }\n}\n\nconst sizeForInput = (size: CellSize): CellSize => size\n\n/** 2026-05-13 Q3 helper(per codex Q3 verdict):cell display + isDisabled → Field mode='disabled'。\n * Cell display lifecycle 不擴 CellMode='disabled',而是各 Cell 在 display branch 翻譯 isDisabled\n * → Field mode='disabled' prop。inner Field 內部走具體 disabled token(text-fg-disabled / bg-disabled 等),\n * 非 wrapper blanket opacity-disabled 逃生艙(per color.spec.md:729)。 */\nconst displayOrDisabled = (isDisabled?: boolean): 'display' | 'disabled' =>\n isDisabled ? 'disabled' : 'display'\n\n// ── Cell Components ──────────────────────────────────────────────────────────\n\nfunction StringCell({ value, meta, mode, size, isDisabled, autoRowHeight, onCommit, onCancel, onDraft }: CellComponentProps) {\n // 2026-05-14 I9 fix(per codex+Layer A 共識):meta.maxLines opt-in line-clamp。\n // display autoRow 用 Tailwind arbitrary line-clamp 支援 N rows;edit textarea field-sizing\n // 已 auto-grow to content,natural match clamp。\n // 2026-05-16 Round 5 audit Dim 27 fix:narrow type 取代 `as any` cast。\n const maxLines: number | undefined = (meta as { maxLines?: number } | undefined)?.maxLines\n const clampClass = maxLines && autoRowHeight ? `line-clamp-[${maxLines}]` : undefined\n // string type canonical(2026-05-05 v2 user 校正:input space ≥ display space):\n // - autoRowHeight: Textarea(display + edit)— display wrap text 撐高 row,edit textarea\n // 多行輸入、`!h-full` 填 cell。對齊 Notion long-text cell canonical。\n // - fixed: Input(display + edit)— 單行 truncate display,單行 input edit;Field naked intrinsic\n // 高 = cell 高 = h-field-md,文字位置 display↔edit 完全一致。對齊 AG Grid / Material X-Grid。\n // - autoRowHeight 是 table 框架決定(consumer 不需 per-column 設 meta.wrap)。\n // - 互動(Textarea):Esc cancel / Cmd|Ctrl+Enter commit / blur commit;Enter 保留換行\n // - 互動(Input):Esc cancel / Enter commit / blur commit\n const v = value != null ? String(value) : ''\n if (mode === 'display') {\n // size 必傳:DataTable cell 字級隨 size 變(sm/md text-body / lg text-body-lg),\n // 對齊 Field family size→font SSOT(field-wrapper.tsx:60-64)。漏傳 → fallback md → lg 表格\n // 字卡 14px 跟 Select/Date 等有傳 size 的 cell 不一致(2026-06-08 user 抓 frozen string 欄字偏小)。\n return autoRowHeight\n ? <Textarea variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={v} size={size} className={clampClass} />\n : <Input variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={v} size={size} />\n }\n if (autoRowHeight) {\n // 2026-05-14 I8 fix(per codex verdict + user 抓「edit cell shrink」):\n // 原 `wrapRows = value.length / 40` 字元估算不準(對應實際 column width 不同\n // → cell 進 edit shrink)。改 CSS `field-sizing: content`(Chrome 123+ / FF 122+ /\n // Safari 17+)讓 textarea 自動 grow to content,匹配 display wrap 真實高度。\n // Fallback rows 仍保留給舊 browser(rows attr 在 field-sizing 支援時被覆蓋)。\n const newlineRows = (v.match(/\\n/g) || []).length + 1\n const wrapRows = Math.ceil(v.length / 40)\n const estimateRows = Math.min(10, Math.max(1, newlineRows, wrapRows))\n return (\n <Textarea\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n rows={estimateRows}\n defaultValue={v}\n // any-allow: CSS `field-sizing` 屬性 Chrome 123+/FF 122+/Safari 17+ 支援但 TypeScript lib.dom\n // 尚未加 type;narrow 到 CSSProperties 仍需 cast,保留 single-site any 較 type aug 簡潔。\n style={{ fieldSizing: 'content' } as React.CSSProperties}\n onChange={(e) => onDraft?.((e.target as HTMLTextAreaElement).value)}\n onBlur={(e) => onCommit?.((e.target as HTMLTextAreaElement).value)}\n onKeyDown={(e) => {\n if (e.key === 'Escape') { e.preventDefault(); onCancel?.() }\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n onCommit?.((e.target as HTMLTextAreaElement).value)\n }\n }}\n />\n )\n }\n return (\n <Input\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n defaultValue={v}\n onChange={(e) => onDraft?.(e.target.value)}\n onBlur={(e) => onCommit?.(e.target.value)}\n onKeyDown={makeKeyHandler(onCommit, onCancel)}\n />\n )\n}\n\nfunction NumberCell({ value, meta, mode, size, isDisabled, onCommit, onCancel, onDraft }: CellComponentProps) {\n // currency 透過 columnType-aware prefix:type='currency' → 預設 '$'(可 override)\n const isCurrency = meta?.type === 'currency'\n const prefix = isCurrency ? (meta?.prefix ?? '$') : meta?.prefix\n if (mode === 'display') {\n return (\n <NumberInput\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as number | null}\n // size 必傳(同 StringCell)— currency/number cell 字級隨 DataTable size 變,對齊 Field SSOT。\n size={size}\n prefix={prefix}\n suffix={meta?.suffix}\n precision={meta?.precision}\n locale={meta?.locale}\n />\n )\n }\n // Edit mode value pre-fill canonical(2026-05-05):NumberInput edit 強制 controlled\n // (`value={value ?? ''}`)— 若 NumberCell 以 `defaultValue` 傳入,NumberInput value=undefined → ''\n // empty。對齊 cell-as-input「edit mode 自動帶入 display 值」(對齊 Notion / Airtable 共識),\n // 改用 local state controlled。User typing → setLocalValue;blur/Enter → onCommit(localValue)。\n const initial = typeof value === 'number' ? value : null\n const [localValue, setLocalValue] = React.useState<number | null>(initial)\n return (\n <NumberInput\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n value={localValue}\n onChange={(v) => { setLocalValue(v); onDraft?.(v) }}\n prefix={prefix}\n suffix={meta?.suffix}\n precision={meta?.precision}\n onBlur={() => onCommit?.(localValue)}\n onKeyDown={(e) => {\n if (e.key === 'Escape') { e.preventDefault(); onCancel?.() }\n if (e.key === 'Enter') { e.preventDefault(); onCommit?.(localValue) }\n }}\n />\n )\n}\n\n// Cell-as-input dismiss canonical(2026-05-05):defaultOpen=true 開始 → user click 外 popover 關\n// → 元件 fire onOpenChange(false) → cell call onCancel exit edit。否則 cell 卡 edit mode 不可 re-trigger\n// (對齊 Airtable / Notion canonical:click 外即關)。\nconst dismissOnClose = (onCancel?: () => void) => (open: boolean) => { if (!open) onCancel?.() }\n\n// Mode-keyed remount canonical(2026-05-05):display↔edit 切換時,因 React reconciliation 同 type 同\n// position 會重用 instance,導致 `useState(defaultOpen)` 只在首次 mount 跑(那時 mode='display'\n// defaultOpen 沒給→預設 false)。後續 mode='edit' 即使傳 defaultOpen=true 也無效。\n// Fix:`key={mode}` 強制 React unmount + remount,每次切 mode 都重跑 useState init。\n// 對齊 Notion / Airtable cell-as-input「display 跟 edit 是不同 mount cycle」語義。\n\nfunction DateCell({ value, meta, mode, size, isDisabled, isEditable, onCommit, onCancel }: CellComponentProps) {\n if (mode === 'display') {\n return (\n <DatePicker\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as string | null}\n size={size}\n formatOptions={meta?.formatOptions}\n locale={meta?.locale}\n // Indicator(calendar icon)= editable affordance(2026-05-10 user 糾正)。\n // Non-editable cell 不該顯 picker indicator(誤導 read-only 為 editable)。\n // 對齊 UrlCell L394 / BooleanCell L368 既有 isEditable conditional pattern。\n showDisplayEndIcon={isEditable === true}\n />\n )\n }\n return (\n <DatePicker\n key=\"edit\"\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n value={typeof value === 'string' ? value : null}\n showTime={meta?.includeTime === true}\n onChange={(v) => onCommit?.(v)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction TimeCell({ value, meta, mode, size, isDisabled, isEditable, onCommit, onCancel }: CellComponentProps) {\n if (mode === 'display') {\n return (\n <TimePicker\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as string | null}\n size={size}\n formatOptions={meta?.formatOptions}\n locale={meta?.locale}\n showDisplayEndIcon={isEditable === true} // 2026-05-10:non-editable 不顯 indicator\n />\n )\n }\n return (\n <TimePicker\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n value={typeof value === 'string' ? value : null}\n showSeconds={meta?.showSeconds === true}\n minuteStep={meta?.minuteStep}\n secondStep={meta?.secondStep}\n onChange={(v) => onCommit?.(v)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction SelectCell({ value, meta, mode, size, isDisabled, isEditable, onCommit, onCancel }: CellComponentProps) {\n // Display canonical(2026-05-05):cell IS variant,default plain text(no Tag pill 疊在 cell border 內)。\n // Consumer 可在 column meta.display='tag' opt-in 內容導向的 Tag 視覺(category 含色彩標籤等)。\n // 對齊 JTable / AG Grid「renderer/editor 視覺一致」canonical。\n const displayMode = (meta?.display as 'plain' | 'tag' | undefined) ?? 'plain'\n if (mode === 'display') {\n return (\n <Select\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as string | null}\n options={meta?.options ?? []}\n size={size}\n display={displayMode}\n showDisplayEndIcon={isEditable === true} // 2026-05-10:non-editable 不顯 chevron indicator\n />\n )\n }\n return (\n <Select\n key=\"edit\"\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n options={meta?.options ?? []}\n value={value as string | null | undefined}\n onChange={(v) => onCommit?.(v)}\n // B7(2026-05-05):cell 編輯時支援 inline search,沿用 Select.searchable 機制(對齊 cell-as-input\n // 「沿用既有輸入框互動」原則)。Default false,consumer 在 meta.searchable 開啟。\n searchable={meta?.searchable === true}\n display={displayMode}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction MultiSelectCell({ value, meta, mode, size, isDisabled, autoRowHeight, isEditable, onCommitLive, onCancel }: CellComponentProps) {\n const wrap = autoRowHeight && meta?.wrap === true\n if (mode === 'display') {\n return (\n <Combobox\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={(value as string[] | null) ?? []}\n options={meta?.options ?? []}\n wrap={wrap}\n size={size}\n showDisplayEndIcon={isEditable === true} // 2026-05-10:non-editable 不顯 chevron indicator\n />\n )\n }\n // Multi 用 onCommitLive(commit 但不 exit edit)— 每勾一項即時生效,popover 持續開\n // 直到點外面;onOpenChange(false) → onCancel exit edit。對齊 Notion / Linear / Airtable canonical。\n return (\n <Combobox\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n options={meta?.options ?? []}\n value={Array.isArray(value) ? (value as string[]) : []}\n onChange={(v) => onCommitLive?.(v)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction PersonCell({ value, mode, size, isDisabled, isEditable, onCommit, onCancel, meta }: CellComponentProps) {\n if (mode === 'display') {\n // 2026-05-10:non-editable 不顯 chevron indicator(對齊 UrlCell isEditable conditional pattern)\n return <PeoplePicker key=\"display\" variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={value as PersonValue | null} size={size} showDisplayEndIcon={isEditable === true} />\n }\n return (\n <PeoplePicker\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n value={value as PersonValue | null}\n people={meta?.people ?? []}\n // PeoplePicker onChange 永遠 emit array(API contract);single mode commit 取首位\n onChange={(next) => onCommit?.(next[0] ?? null)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction MultiPersonCell({ value, mode, size, isDisabled, isEditable, onCommitLive, onCancel, meta }: CellComponentProps) {\n if (mode === 'display') {\n // 2026-05-10:non-editable 不顯 chevron indicator\n return <PeoplePicker key=\"display\" variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={(value as PersonValue[]) ?? []} size={size} showDisplayEndIcon={isEditable === true} />\n }\n // Multi 用 onCommitLive(commit 但不 exit edit)— 每勾一人即時生效,popover 持續開\n // 直到點外面;onOpenChange(false) → onCancel exit edit。對齊 multiSelect canonical。\n return (\n <PeoplePicker\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n value={Array.isArray(value) ? (value as PersonValue[]) : []}\n people={meta?.people ?? []}\n onChange={(next) => onCommitLive?.(next)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction BooleanCell({ value, mode, meta, size, isEditable, isDisabled, onCommit }: CellComponentProps) {\n // boolean 不分 read/edit mode — display 渲 mode='display' 純展示;editable 時直接 toggle Checkbox\n // 2026-05-13 codex V1 fix:editable=true + disabled=true 之前 fall through to live Checkbox,\n // onCheckedChange 仍 fire(violate disabled contract)。Fix:`!isEditable || isDisabled` →\n // 走 display branch,Checkbox 拿 disabled mode + 不接 onCheckedChange。\n if (mode === 'display' && (!isEditable || isDisabled)) {\n return <Checkbox variant=\"naked\" mode={displayOrDisabled(isDisabled)} checked={value === true} />\n }\n return (\n <Checkbox\n size={size === 'lg' ? 'lg' : 'md'}\n checked={value === true}\n onCheckedChange={(checked) => onCommit?.(checked === true)}\n aria-label={meta?.ariaLabel ?? '切換'}\n />\n )\n}\n\n/**\n * UrlCell — Phase C drift fix:\n * 舊 EditableCellContent edit mode 對 url 走 plain `<Input>`(失去 URL 驗證 + auto-link)。\n * 現改用 `<LinkInput>` edit mode → 保留 URL parse / hostname 顯示一致性 + 鍵盤 commit / cancel。\n * read mode 仍 `<LinkInput mode={displayOrDisabled(isDisabled)}>` = 一致 SSOT。\n * editable 互動:hover 時右側出 Pencil 鈕 → 進 edit(保留 link click 語意,對齊原 spec)。\n */\nfunction UrlCell({ value, meta, mode, size, isDisabled, isEditable, onRequestEdit, onCommit, onCancel }: CellComponentProps) {\n if (mode === 'display') {\n // showDisplayEndIcon ← D path Phase 2(2026-05-08):Field naked wrapper 包 anchor,與 Input edit 同 chrome\n const display = (\n <LinkInput variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={value as string | null} label={meta?.linkLabel} size={size} showDisplayEndIcon />\n )\n // 2026-05-13 codex V1 fix:disabled URL 不顯 Pencil affordance(parent onRequestEdit 已被攔但 UI 仍誤導)\n if (!isEditable || isDisabled) return display\n // editable read mode:hover Pencil 鈕(對齊 spec 第十二段「url:read = 連結 + Pencil」)\n return (\n <span className=\"group/cell relative flex items-center w-full\"> {/* @naked-row-mode-allow: URL hover-Pencil 是 inline action 不是 value content,items-center 鎖 Pencil 對齊行高第一行(autoRow 跟 fixed 皆同視覺正確) */}\n <span className=\"flex-1 min-w-0\">{display}</span>\n <Button\n variant=\"tertiary\"\n size=\"xs\"\n iconOnly\n startIcon={Pencil}\n aria-label=\"編輯連結\"\n className={cn('ml-1 opacity-0 group-hover/cell:opacity-100 transition-opacity')}\n onClick={(e) => {\n e.stopPropagation()\n onRequestEdit?.()\n }}\n />\n </span>\n )\n }\n // edit mode value pre-fill canonical(2026-05-05):LinkInput edit `value` prop 強制 controlled\n // (line 113 `useState(value ?? '')`)+ `showLink = !editing && hasValidValue` 預設顯 link 不顯 input\n // → cell-as-input editing 場景需要 input 直接 focus 編輯。改用 plain `<Input>`(uncontrolled\n // `defaultValue` 正確 pre-fill,Input.tsx `value={value}` 是 undefined → uncontrolled 走 defaultValue)。\n // URL 驗證等 deferred 到 commit phase(consumer 可在 onCommit 時 validate)。\n return (\n <Input\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n defaultValue={value != null ? String(value) : ''}\n onBlur={(e) => onCommit?.(e.target.value)}\n onKeyDown={makeKeyHandler(onCommit, onCancel)}\n />\n )\n}\n\n// ── Registry ────────────────────────────────────────────────────────────────\n//\n// type → cell component。新增 columnType 必同步註冊一條(否則 fallback 到 string)。\n\nexport const cellRegistry: Record<ColumnType, ComponentType<CellComponentProps>> = {\n string: StringCell,\n number: NumberCell,\n currency: NumberCell, // 共用 NumberCell — currency-ness 走 meta.type 判 prefix='$'\n date: DateCell,\n time: TimeCell,\n select: SelectCell,\n multiSelect: MultiSelectCell,\n person: PersonCell,\n multiPerson: MultiPersonCell,\n boolean: BooleanCell,\n url: UrlCell,\n}\n\n/** Resolve cell component by type;default = StringCell(consumer 沒設 type 的 fallback)。\n * 2026-05-12 Stream C Cluster B fix:wrap with FieldSurfaceProvider `surface='table-cell'`\n * 讓所有 Cell 內的 Field family controls 透過 `useFieldSurface()` 取得「我在 cell 裡」context,\n * 取代散落的 `variant === 'naked'` cell-detection heuristic + per-prop hardcoded padding。\n *\n * **2026-05-13 (a) perf fix(user 拍板 + codex V1 verdict + Layer A grep root cause)**:\n * 原 factory pattern 每次 call 在 function body 內宣告新 `CellWithSurface` FC closure → 每 scroll\n * × 每 visible cell 都 return 新 FC reference → React 認 component type 變,**整 subtree mount/\n * unmount cascade**(Field + ItemPrefix/Suffix + Avatar / Tag / PersonDisplay)。\n * Fix:每 ColumnType **module-level 預建** wrapped FC + `React.memo`,resolve 走 cached map,\n * identity stable across scroll → memo 真生效 + subtree 不 mount/unmount。\n * Cite world-class:AG Grid「cell renderer per-type stable reference」/ MUI X DataGrid「memoized\n * subcomponents」/ Glide Data Grid「DOM virtualization 加解掛 = bottleneck」。 */\nconst cellWithSurfaceCache = new Map<ColumnType | '_default_', ComponentType<CellComponentProps>>()\n\nfunction buildCellWithSurface(Inner: ComponentType<CellComponentProps>, key: string): ComponentType<CellComponentProps> {\n const CellWithSurface = React.memo(function CellWithSurface(props: CellComponentProps) {\n return (\n <FieldSurfaceProvider surface=\"table-cell\">\n {/* 2026-06-08:把 table-density size 經獨立 surface-size context 注給 child Field controls,\n 漏傳 size 的 cell 也自動繼承(根治「新 cell 漏傳 size」class);size primitive 不破壞 memo identity。*/}\n <FieldSurfaceSizeProvider size={props.size}>\n <Inner {...props} />\n </FieldSurfaceSizeProvider>\n </FieldSurfaceProvider>\n )\n })\n ;(CellWithSurface as { displayName?: string }).displayName = `CellWithSurface(${key})`\n return CellWithSurface as ComponentType<CellComponentProps>\n}\n\n// Pre-build per-type cached wrapped components(module-level,one-time init)\nfor (const type of Object.keys(cellRegistry) as ColumnType[]) {\n cellWithSurfaceCache.set(type, buildCellWithSurface(cellRegistry[type], type))\n}\ncellWithSurfaceCache.set('_default_', buildCellWithSurface(StringCell, 'StringCell-fallback'))\n\nexport function resolveCellComponent(type?: ColumnType): ComponentType<CellComponentProps> {\n return cellWithSurfaceCache.get(type ?? '_default_') ?? cellWithSurfaceCache.get('_default_')!\n}\n"],"names":["DatePicker","CellWithSurface"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cell-registry.js","sources":["../../../src/components/DataTable/cell-registry.tsx"],"sourcesContent":["// @benchmark-unverified-blanket: file-level retraction per M22 (d) — claims herein not individually URL-cited; treat as unverified visual/usage rumor unless retrofit per-claim. Hook escape preserved.\n// code-quality-allow: file-size — Cell Registry 含 10 cell-type components(string/number/date/time/select/multiSelect/person/multiPerson/boolean/url)+ shared helpers,split-into-files 會破壞 type-keyed registry SSOT canonical\n// DataTable Cell Registry — type-keyed SSOT for cell rendering(Phase C 2026-05-05)\n//\n// 對齊 M17 SSOT consolidation + audit recommendation:\n// 原 `renderTypedValue` switch + `EditableCellContent` switch 兩條平行 type-switch 已 collapse\n// 為**一張 type → cell component** registry。每個 cell component 同時處理 display / edit mode,\n// 靠底層 Field control 的 `mode` prop 切換。\n//\n// 設計原則:\n// - 每個 cell component 接同一組 props(`CellComponentProps`)\n// - 用 `variant=\"naked\"` — DataTable cell-as-input substrate(對齊 Field B1 chrome=bare)\n// - 消費 full Field 家族 primitive(無 stub)\n// - 不再用 `meta._editable` 私有 flag — `isEditable` 直接顯式入參(消除 M1 hack)\n//\n// World-class 對照(@benchmark-unverified):AG Grid cellRendererSelector / Material X-Grid\n// `valueGetter + renderCell` / Notion property type registry。\n\nimport * as React from 'react'\nimport type { ComponentType } from 'react'\nimport { Pencil } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport type { ColumnType } from './column-types'\nimport { Input } from '@/design-system/components/Input/input'\nimport { Textarea } from '@/design-system/components/Textarea/textarea'\nimport { NumberInput } from '@/design-system/components/NumberInput/number-input'\nimport { Select } from '@/design-system/components/Select/select'\nimport { Combobox } from '@/design-system/components/Combobox/combobox'\nimport { DatePicker } from '@/design-system/components/DatePicker/date-picker'\nimport { TimePicker } from '@/design-system/components/TimePicker/time-picker'\nimport { PeoplePicker } from '@/design-system/components/PeoplePicker/people-picker'\nimport { LinkInput } from '@/design-system/components/LinkInput/link-input'\nimport { Checkbox } from '@/design-system/components/Checkbox/checkbox'\nimport { Button } from '@/design-system/components/Button/button'\nimport type { PersonValue } from '@/design-system/components/PeoplePicker/person-display'\nimport { FieldSurfaceProvider, FieldSurfaceSizeProvider } from '@/design-system/components/Field/field-context'\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type CellMode = 'display' | 'edit'\nexport type CellSize = 'sm' | 'md' | 'lg'\n\nexport interface CellComponentProps {\n // any-allow: free-form column value(consumer-defined,跨 type 共用 signature)\n value: any\n // any-allow: free-form consumer meta bag(prefix / options / formatOptions / locale / linkLabel 等)\n meta: Record<string, any>\n mode: CellMode\n size: CellSize\n autoRowHeight: boolean\n /** 該 cell 是否可編。replaces 舊 `meta._editable` 私有 flag(Phase C M1 hack 移除)。 */\n isEditable?: boolean\n /** 2026-05-13:cell 是否 disabled(state overlay,orthogonal to display/edit lifecycle)。\n * per codex Q3 verdict:不擴 CellMode='disabled',加 prop。各 Cell function 收 true 時\n * 傳 `mode='disabled'` 給 inner Field control,各 Field 內部走具體 disabled token(非 wrapper opacity)。 */\n isDisabled?: boolean\n /** Cell 進 edit mode → 提交新值(blur / Enter / option select 都觸發)— 提交後**自動 exit edit**。\n * 適用 single-shot commit:string / number / select(single)/ person(single)/ date / time / boolean / url。 */\n onCommit?: (next: unknown) => void\n /** Live commit — 提交新值但 **不 exit edit**(popover 持續開)。\n * 適用 multi-select 類:multiSelect / multiPerson — user 連續勾選,直到點外面才關。\n * 對齊 Notion / Linear / Airtable canonical:multi-pick popover 不在每次 toggle 後關閉。 */\n onCommitLive?: (next: unknown) => void\n /** Esc 取消編輯,不 commit。 */\n onCancel?: () => void\n /** URL cell 專用:hover 顯示的 Pencil 鈕 → 進 edit mode(read mode 保留 link click 語意)。 */\n onRequestEdit?: () => void\n /** Per-keystroke draft propagation(2026-05-10 Phase 7 D.3 portal Field virtualizer unmount preserve draft):\n * Edit mode 內部 input onChange/onValueChange 每 keystroke 呼叫 onDraft,讓 lifted draft state(in\n * data-table.tsx)持有 user 編輯中字。Cell DOM unmount(virtualizer scroll out)時 draft 在\n * parent state 不丟;mount-back 時 portal Field value 從 draft 取,user 字保留。\n * 非 portal mode(inline edit)不傳此 prop,各 Cell 走原 uncontrolled defaultValue 路徑。 */\n onDraft?: (next: unknown) => void\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n/** 鍵盤 commit / cancel — string / number cell edit mode 共用 */\nfunction makeKeyHandler(\n onCommit?: (v: unknown) => void,\n onCancel?: () => void,\n parseValue?: (raw: string) => unknown,\n) {\n return (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Escape') { e.preventDefault(); onCancel?.() }\n if (e.key === 'Enter') {\n e.preventDefault()\n const raw = (e.target as HTMLInputElement).value\n onCommit?.(parseValue ? parseValue(raw) : raw)\n }\n }\n}\n\nconst sizeForInput = (size: CellSize): CellSize => size\n\n/** 2026-05-13 Q3 helper(per codex Q3 verdict):cell display + isDisabled → Field mode='disabled'。\n * Cell display lifecycle 不擴 CellMode='disabled',而是各 Cell 在 display branch 翻譯 isDisabled\n * → Field mode='disabled' prop。inner Field 內部走具體 disabled token(text-fg-disabled / bg-disabled 等),\n * 非 wrapper blanket opacity-disabled 逃生艙(per color.spec.md:729)。 */\nconst displayOrDisabled = (isDisabled?: boolean): 'display' | 'disabled' =>\n isDisabled ? 'disabled' : 'display'\n\n// ── Cell Components ──────────────────────────────────────────────────────────\n\nfunction StringCell({ value, meta, mode, size, isDisabled, autoRowHeight, onCommit, onCancel, onDraft }: CellComponentProps) {\n // 2026-05-14 I9 fix(per codex+Layer A 共識):meta.maxLines opt-in line-clamp。\n // display autoRow 用 Tailwind arbitrary line-clamp 支援 N rows;edit textarea field-sizing\n // 已 auto-grow to content,natural match clamp。\n // 2026-05-16 Round 5 audit Dim 27 fix:narrow type 取代 `as any` cast。\n const maxLines: number | undefined = (meta as { maxLines?: number } | undefined)?.maxLines\n const clampClass = maxLines && autoRowHeight ? `line-clamp-[${maxLines}]` : undefined\n // string type canonical(2026-05-05 v2 user 校正:input space ≥ display space):\n // - autoRowHeight: Textarea(display + edit)— display wrap text 撐高 row,edit textarea\n // 多行輸入、`!h-full` 填 cell。對齊 Notion long-text cell canonical。\n // - fixed: Input(display + edit)— 單行 truncate display,單行 input edit;Field naked intrinsic\n // 高 = cell 高 = h-field-md,文字位置 display↔edit 完全一致。對齊 AG Grid / Material X-Grid。\n // - autoRowHeight 是 table 框架決定(consumer 不需 per-column 設 meta.wrap)。\n // - 互動(Textarea):Esc cancel / Cmd|Ctrl+Enter commit / blur commit;Enter 保留換行\n // - 互動(Input):Esc cancel / Enter commit / blur commit\n const v = value != null ? String(value) : ''\n if (mode === 'display') {\n // size 必傳:DataTable cell 字級隨 size 變(sm/md text-body / lg text-body-lg),\n // 對齊 Field family size→font SSOT(field-wrapper.tsx:60-64)。漏傳 → fallback md → lg 表格\n // 字卡 14px 跟 Select/Date 等有傳 size 的 cell 不一致(2026-06-08 user 抓 frozen string 欄字偏小)。\n return autoRowHeight\n ? <Textarea variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={v} size={size} className={clampClass} />\n : <Input variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={v} size={size} />\n }\n if (autoRowHeight) {\n // 2026-05-14 I8 fix(per codex verdict + user 抓「edit cell shrink」):\n // 原 `wrapRows = value.length / 40` 字元估算不準(對應實際 column width 不同\n // → cell 進 edit shrink)。改 CSS `field-sizing: content`(Chrome 123+ / FF 122+ /\n // Safari 17+)讓 textarea 自動 grow to content,匹配 display wrap 真實高度。\n // Fallback rows 仍保留給舊 browser(rows attr 在 field-sizing 支援時被覆蓋)。\n const newlineRows = (v.match(/\\n/g) || []).length + 1\n const wrapRows = Math.ceil(v.length / 40)\n const estimateRows = Math.min(10, Math.max(1, newlineRows, wrapRows))\n return (\n <Textarea\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n rows={estimateRows}\n defaultValue={v}\n // any-allow: CSS `field-sizing` 屬性 Chrome 123+/FF 122+/Safari 17+ 支援但 TypeScript lib.dom\n // 尚未加 type;narrow 到 CSSProperties 仍需 cast,保留 single-site any 較 type aug 簡潔。\n style={{ fieldSizing: 'content' } as React.CSSProperties}\n onChange={(e) => onDraft?.((e.target as HTMLTextAreaElement).value)}\n onBlur={(e) => onCommit?.((e.target as HTMLTextAreaElement).value)}\n onKeyDown={(e) => {\n if (e.key === 'Escape') { e.preventDefault(); onCancel?.() }\n if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {\n e.preventDefault()\n onCommit?.((e.target as HTMLTextAreaElement).value)\n }\n }}\n />\n )\n }\n return (\n <Input\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n defaultValue={v}\n onChange={(e) => onDraft?.(e.target.value)}\n onBlur={(e) => onCommit?.(e.target.value)}\n onKeyDown={makeKeyHandler(onCommit, onCancel)}\n />\n )\n}\n\nfunction NumberCell({ value, meta, mode, size, isDisabled, onCommit, onCancel, onDraft }: CellComponentProps) {\n // currency 透過 columnType-aware prefix:type='currency' → 預設 '$'(可 override)\n const isCurrency = meta?.type === 'currency'\n const prefix = isCurrency ? (meta?.prefix ?? '$') : meta?.prefix\n // React #310 fix:useState 必在 display early-return 前無條件呼叫。同一 memo'd cell instance\n // 在 display↔edit 切換時被重用(render site 無 key={mode},data-table.tsx:1352),hook 數量不可變,\n // 否則 Rules of Hooks violation → React #310 crash。對齊 combobox/select hoist pattern。\n const initial = typeof value === 'number' ? value : null\n const [localValue, setLocalValue] = React.useState<number | null>(initial)\n if (mode === 'display') {\n return (\n <NumberInput\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as number | null}\n // size 必傳(同 StringCell)— currency/number cell 字級隨 DataTable size 變,對齊 Field SSOT。\n size={size}\n prefix={prefix}\n suffix={meta?.suffix}\n precision={meta?.precision}\n locale={meta?.locale}\n />\n )\n }\n // Edit mode value pre-fill canonical(2026-05-05):NumberInput edit 強制 controlled\n // (`value={value ?? ''}`)— 若 NumberCell 以 `defaultValue` 傳入,NumberInput value=undefined → ''\n // empty。對齊 cell-as-input「edit mode 自動帶入 display 值」(對齊 Notion / Airtable 共識),\n // 改用 local state controlled。User typing → setLocalValue;blur/Enter → onCommit(localValue)。\n // (initial + useState 已 hoist 到 display-return 前 — 見上方 React #310 fix。)\n return (\n <NumberInput\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n value={localValue}\n onChange={(v) => { setLocalValue(v); onDraft?.(v) }}\n prefix={prefix}\n suffix={meta?.suffix}\n precision={meta?.precision}\n onBlur={() => onCommit?.(localValue)}\n onKeyDown={(e) => {\n if (e.key === 'Escape') { e.preventDefault(); onCancel?.() }\n if (e.key === 'Enter') { e.preventDefault(); onCommit?.(localValue) }\n }}\n />\n )\n}\n\n// Cell-as-input dismiss canonical(2026-05-05):defaultOpen=true 開始 → user click 外 popover 關\n// → 元件 fire onOpenChange(false) → cell call onCancel exit edit。否則 cell 卡 edit mode 不可 re-trigger\n// (對齊 Airtable / Notion canonical:click 外即關)。\nconst dismissOnClose = (onCancel?: () => void) => (open: boolean) => { if (!open) onCancel?.() }\n\n// Mode-keyed remount canonical(2026-05-05):display↔edit 切換時,因 React reconciliation 同 type 同\n// position 會重用 instance,導致 `useState(defaultOpen)` 只在首次 mount 跑(那時 mode='display'\n// defaultOpen 沒給→預設 false)。後續 mode='edit' 即使傳 defaultOpen=true 也無效。\n// Fix:`key={mode}` 強制 React unmount + remount,每次切 mode 都重跑 useState init。\n// 對齊 Notion / Airtable cell-as-input「display 跟 edit 是不同 mount cycle」語義。\n\nfunction DateCell({ value, meta, mode, size, isDisabled, isEditable, onCommit, onCancel }: CellComponentProps) {\n if (mode === 'display') {\n return (\n <DatePicker\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as string | null}\n size={size}\n formatOptions={meta?.formatOptions}\n locale={meta?.locale}\n // Indicator(calendar icon)= editable affordance(2026-05-10 user 糾正)。\n // Non-editable cell 不該顯 picker indicator(誤導 read-only 為 editable)。\n // 對齊 UrlCell L394 / BooleanCell L368 既有 isEditable conditional pattern。\n showDisplayEndIcon={isEditable === true}\n />\n )\n }\n return (\n <DatePicker\n key=\"edit\"\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n value={typeof value === 'string' ? value : null}\n showTime={meta?.includeTime === true}\n onChange={(v) => onCommit?.(v)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction TimeCell({ value, meta, mode, size, isDisabled, isEditable, onCommit, onCancel }: CellComponentProps) {\n if (mode === 'display') {\n return (\n <TimePicker\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as string | null}\n size={size}\n formatOptions={meta?.formatOptions}\n locale={meta?.locale}\n showDisplayEndIcon={isEditable === true} // 2026-05-10:non-editable 不顯 indicator\n />\n )\n }\n return (\n <TimePicker\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n value={typeof value === 'string' ? value : null}\n showSeconds={meta?.showSeconds === true}\n minuteStep={meta?.minuteStep}\n secondStep={meta?.secondStep}\n onChange={(v) => onCommit?.(v)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction SelectCell({ value, meta, mode, size, isDisabled, isEditable, onCommit, onCancel }: CellComponentProps) {\n // Display canonical(2026-05-05):cell IS variant,default plain text(no Tag pill 疊在 cell border 內)。\n // Consumer 可在 column meta.display='tag' opt-in 內容導向的 Tag 視覺(category 含色彩標籤等)。\n // 對齊 JTable / AG Grid「renderer/editor 視覺一致」canonical。\n const displayMode = (meta?.display as 'plain' | 'tag' | undefined) ?? 'plain'\n if (mode === 'display') {\n return (\n <Select\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={value as string | null}\n options={meta?.options ?? []}\n size={size}\n display={displayMode}\n showDisplayEndIcon={isEditable === true} // 2026-05-10:non-editable 不顯 chevron indicator\n />\n )\n }\n return (\n <Select\n key=\"edit\"\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n options={meta?.options ?? []}\n value={value as string | null | undefined}\n onChange={(v) => onCommit?.(v)}\n // B7(2026-05-05):cell 編輯時支援 inline search,沿用 Select.searchable 機制(對齊 cell-as-input\n // 「沿用既有輸入框互動」原則)。Default false,consumer 在 meta.searchable 開啟。\n searchable={meta?.searchable === true}\n display={displayMode}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction MultiSelectCell({ value, meta, mode, size, isDisabled, autoRowHeight, isEditable, onCommitLive, onCancel }: CellComponentProps) {\n const wrap = autoRowHeight && meta?.wrap === true\n if (mode === 'display') {\n return (\n <Combobox\n key=\"display\"\n variant=\"naked\"\n mode={displayOrDisabled(isDisabled)}\n value={(value as string[] | null) ?? []}\n options={meta?.options ?? []}\n wrap={wrap}\n size={size}\n showDisplayEndIcon={isEditable === true} // 2026-05-10:non-editable 不顯 chevron indicator\n />\n )\n }\n // Multi 用 onCommitLive(commit 但不 exit edit)— 每勾一項即時生效,popover 持續開\n // 直到點外面;onOpenChange(false) → onCancel exit edit。對齊 Notion / Linear / Airtable canonical。\n return (\n <Combobox\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n options={meta?.options ?? []}\n value={Array.isArray(value) ? (value as string[]) : []}\n onChange={(v) => onCommitLive?.(v)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction PersonCell({ value, mode, size, isDisabled, isEditable, onCommit, onCancel, meta }: CellComponentProps) {\n if (mode === 'display') {\n // 2026-05-10:non-editable 不顯 chevron indicator(對齊 UrlCell isEditable conditional pattern)\n return <PeoplePicker key=\"display\" variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={value as PersonValue | null} size={size} showDisplayEndIcon={isEditable === true} />\n }\n return (\n <PeoplePicker\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n value={value as PersonValue | null}\n people={meta?.people ?? []}\n // PeoplePicker onChange 永遠 emit array(API contract);single mode commit 取首位\n onChange={(next) => onCommit?.(next[0] ?? null)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction MultiPersonCell({ value, mode, size, isDisabled, isEditable, onCommitLive, onCancel, meta }: CellComponentProps) {\n if (mode === 'display') {\n // 2026-05-10:non-editable 不顯 chevron indicator\n return <PeoplePicker key=\"display\" variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={(value as PersonValue[]) ?? []} size={size} showDisplayEndIcon={isEditable === true} />\n }\n // Multi 用 onCommitLive(commit 但不 exit edit)— 每勾一人即時生效,popover 持續開\n // 直到點外面;onOpenChange(false) → onCancel exit edit。對齊 multiSelect canonical。\n return (\n <PeoplePicker\n key=\"edit\"\n variant=\"naked\"\n size={sizeForInput(size)}\n value={Array.isArray(value) ? (value as PersonValue[]) : []}\n people={meta?.people ?? []}\n onChange={(next) => onCommitLive?.(next)}\n defaultOpen\n onOpenChange={dismissOnClose(onCancel)}\n />\n )\n}\n\nfunction BooleanCell({ value, mode, meta, size, isEditable, isDisabled, onCommit }: CellComponentProps) {\n // boolean 不分 read/edit mode — display 渲 mode='display' 純展示;editable 時直接 toggle Checkbox\n // 2026-05-13 codex V1 fix:editable=true + disabled=true 之前 fall through to live Checkbox,\n // onCheckedChange 仍 fire(violate disabled contract)。Fix:`!isEditable || isDisabled` →\n // 走 display branch,Checkbox 拿 disabled mode + 不接 onCheckedChange。\n if (mode === 'display' && (!isEditable || isDisabled)) {\n return <Checkbox variant=\"naked\" mode={displayOrDisabled(isDisabled)} checked={value === true} />\n }\n return (\n <Checkbox\n size={size === 'lg' ? 'lg' : 'md'}\n checked={value === true}\n onCheckedChange={(checked) => onCommit?.(checked === true)}\n aria-label={meta?.ariaLabel ?? '切換'}\n />\n )\n}\n\n/**\n * UrlCell — Phase C drift fix:\n * 舊 EditableCellContent edit mode 對 url 走 plain `<Input>`(失去 URL 驗證 + auto-link)。\n * 現改用 `<LinkInput>` edit mode → 保留 URL parse / hostname 顯示一致性 + 鍵盤 commit / cancel。\n * read mode 仍 `<LinkInput mode={displayOrDisabled(isDisabled)}>` = 一致 SSOT。\n * editable 互動:hover 時右側出 Pencil 鈕 → 進 edit(保留 link click 語意,對齊原 spec)。\n */\nfunction UrlCell({ value, meta, mode, size, isDisabled, isEditable, onRequestEdit, onCommit, onCancel }: CellComponentProps) {\n if (mode === 'display') {\n // showDisplayEndIcon ← D path Phase 2(2026-05-08):Field naked wrapper 包 anchor,與 Input edit 同 chrome\n const display = (\n <LinkInput variant=\"naked\" mode={displayOrDisabled(isDisabled)} value={value as string | null} label={meta?.linkLabel} size={size} showDisplayEndIcon />\n )\n // 2026-05-13 codex V1 fix:disabled URL 不顯 Pencil affordance(parent onRequestEdit 已被攔但 UI 仍誤導)\n if (!isEditable || isDisabled) return display\n // editable read mode:hover Pencil 鈕(對齊 spec 第十二段「url:read = 連結 + Pencil」)\n return (\n <span className=\"group/cell relative flex items-center w-full\"> {/* @naked-row-mode-allow: URL hover-Pencil 是 inline action 不是 value content,items-center 鎖 Pencil 對齊行高第一行(autoRow 跟 fixed 皆同視覺正確) */}\n <span className=\"flex-1 min-w-0\">{display}</span>\n <Button\n variant=\"tertiary\"\n size=\"xs\"\n iconOnly\n startIcon={Pencil}\n aria-label=\"編輯連結\"\n className={cn('ml-1 opacity-0 group-hover/cell:opacity-100 transition-opacity')}\n onClick={(e) => {\n e.stopPropagation()\n onRequestEdit?.()\n }}\n />\n </span>\n )\n }\n // edit mode value pre-fill canonical(2026-05-05):LinkInput edit `value` prop 強制 controlled\n // (line 113 `useState(value ?? '')`)+ `showLink = !editing && hasValidValue` 預設顯 link 不顯 input\n // → cell-as-input editing 場景需要 input 直接 focus 編輯。改用 plain `<Input>`(uncontrolled\n // `defaultValue` 正確 pre-fill,Input.tsx `value={value}` 是 undefined → uncontrolled 走 defaultValue)。\n // URL 驗證等 deferred 到 commit phase(consumer 可在 onCommit 時 validate)。\n return (\n <Input\n autoFocus\n variant=\"naked\"\n size={sizeForInput(size)}\n defaultValue={value != null ? String(value) : ''}\n onBlur={(e) => onCommit?.(e.target.value)}\n onKeyDown={makeKeyHandler(onCommit, onCancel)}\n />\n )\n}\n\n// ── Registry ────────────────────────────────────────────────────────────────\n//\n// type → cell component。新增 columnType 必同步註冊一條(否則 fallback 到 string)。\n\nexport const cellRegistry: Record<ColumnType, ComponentType<CellComponentProps>> = {\n string: StringCell,\n number: NumberCell,\n currency: NumberCell, // 共用 NumberCell — currency-ness 走 meta.type 判 prefix='$'\n date: DateCell,\n time: TimeCell,\n select: SelectCell,\n multiSelect: MultiSelectCell,\n person: PersonCell,\n multiPerson: MultiPersonCell,\n boolean: BooleanCell,\n url: UrlCell,\n}\n\n/** Resolve cell component by type;default = StringCell(consumer 沒設 type 的 fallback)。\n * 2026-05-12 Stream C Cluster B fix:wrap with FieldSurfaceProvider `surface='table-cell'`\n * 讓所有 Cell 內的 Field family controls 透過 `useFieldSurface()` 取得「我在 cell 裡」context,\n * 取代散落的 `variant === 'naked'` cell-detection heuristic + per-prop hardcoded padding。\n *\n * **2026-05-13 (a) perf fix(user 拍板 + codex V1 verdict + Layer A grep root cause)**:\n * 原 factory pattern 每次 call 在 function body 內宣告新 `CellWithSurface` FC closure → 每 scroll\n * × 每 visible cell 都 return 新 FC reference → React 認 component type 變,**整 subtree mount/\n * unmount cascade**(Field + ItemPrefix/Suffix + Avatar / Tag / PersonDisplay)。\n * Fix:每 ColumnType **module-level 預建** wrapped FC + `React.memo`,resolve 走 cached map,\n * identity stable across scroll → memo 真生效 + subtree 不 mount/unmount。\n * Cite world-class:AG Grid「cell renderer per-type stable reference」/ MUI X DataGrid「memoized\n * subcomponents」/ Glide Data Grid「DOM virtualization 加解掛 = bottleneck」。 */\nconst cellWithSurfaceCache = new Map<ColumnType | '_default_', ComponentType<CellComponentProps>>()\n\nfunction buildCellWithSurface(Inner: ComponentType<CellComponentProps>, key: string): ComponentType<CellComponentProps> {\n const CellWithSurface = React.memo(function CellWithSurface(props: CellComponentProps) {\n return (\n <FieldSurfaceProvider surface=\"table-cell\">\n {/* 2026-06-08:把 table-density size 經獨立 surface-size context 注給 child Field controls,\n 漏傳 size 的 cell 也自動繼承(根治「新 cell 漏傳 size」class);size primitive 不破壞 memo identity。*/}\n <FieldSurfaceSizeProvider size={props.size}>\n <Inner {...props} />\n </FieldSurfaceSizeProvider>\n </FieldSurfaceProvider>\n )\n })\n ;(CellWithSurface as { displayName?: string }).displayName = `CellWithSurface(${key})`\n return CellWithSurface as ComponentType<CellComponentProps>\n}\n\n// Pre-build per-type cached wrapped components(module-level,one-time init)\nfor (const type of Object.keys(cellRegistry) as ColumnType[]) {\n cellWithSurfaceCache.set(type, buildCellWithSurface(cellRegistry[type], type))\n}\ncellWithSurfaceCache.set('_default_', buildCellWithSurface(StringCell, 'StringCell-fallback'))\n\nexport function resolveCellComponent(type?: ColumnType): ComponentType<CellComponentProps> {\n return cellWithSurfaceCache.get(type ?? '_default_') ?? cellWithSurfaceCache.get('_default_')!\n}\n"],"names":["DatePicker","CellWithSurface"],"mappings":";;;;;;;;;;;;;;;;AA8EA,SAAS,eACP,UACA,UACA,YACA;AACA,SAAO,CAAC,MAA6C;AACnD,QAAI,EAAE,QAAQ,UAAU;AAAE,QAAE,eAAA;AAAkB;AAAA,IAAa;AAC3D,QAAI,EAAE,QAAQ,SAAS;AACrB,QAAE,eAAA;AACF,YAAM,MAAO,EAAE,OAA4B;AAC3C,2CAA0C;AAAA,IAC5C;AAAA,EACF;AACF;AAEA,MAAM,eAAe,CAAC,SAA6B;AAMnD,MAAM,oBAAoB,CAAC,eACzB,aAAa,aAAa;AAI5B,SAAS,WAAW,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,eAAe,UAAU,UAAU,QAAA,GAA+B;AAK3H,QAAM,WAAgC,6BAA4C;AAClF,QAAM,aAAa,YAAY,gBAAgB,eAAe,QAAQ,MAAM;AAS5E,QAAM,IAAI,SAAS,OAAO,OAAO,KAAK,IAAI;AAC1C,MAAI,SAAS,WAAW;AAItB,WAAO,gBACH,oBAAC,UAAA,EAAS,SAAQ,SAAQ,MAAM,kBAAkB,UAAU,GAAG,OAAO,GAAG,MAAY,WAAW,WAAA,CAAY,IAC5G,oBAAC,OAAA,EAAM,SAAQ,SAAQ,MAAM,kBAAkB,UAAU,GAAG,OAAO,GAAG,KAAA,CAAY;AAAA,EACxF;AACA,MAAI,eAAe;AAMjB,UAAM,eAAe,EAAE,MAAM,KAAK,KAAK,CAAA,GAAI,SAAS;AACpD,UAAM,WAAW,KAAK,KAAK,EAAE,SAAS,EAAE;AACxC,UAAM,eAAe,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,aAAa,QAAQ,CAAC;AACpE,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,WAAS;AAAA,QACT,SAAQ;AAAA,QACR,MAAM,aAAa,IAAI;AAAA,QACvB,MAAM;AAAA,QACN,cAAc;AAAA,QAGd,OAAO,EAAE,aAAa,UAAA;AAAA,QACtB,UAAU,CAAC,MAAM,mCAAW,EAAE,OAA+B;AAAA,QAC7D,QAAQ,CAAC,MAAM,qCAAY,EAAE,OAA+B;AAAA,QAC5D,WAAW,CAAC,MAAM;AAChB,cAAI,EAAE,QAAQ,UAAU;AAAE,cAAE,eAAA;AAAkB;AAAA,UAAa;AAC3D,cAAI,EAAE,QAAQ,YAAY,EAAE,WAAW,EAAE,UAAU;AACjD,cAAE,eAAA;AACF,iDAAY,EAAE,OAA+B;AAAA,UAC/C;AAAA,QACF;AAAA,MAAA;AAAA,IAAA;AAAA,EAGN;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAS;AAAA,MACT,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,cAAc;AAAA,MACd,UAAU,CAAC,MAAM,mCAAU,EAAE,OAAO;AAAA,MACpC,QAAQ,CAAC,MAAM,qCAAW,EAAE,OAAO;AAAA,MACnC,WAAW,eAAe,UAAU,QAAQ;AAAA,IAAA;AAAA,EAAA;AAGlD;AAEA,SAAS,WAAW,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,UAAU,UAAU,WAA+B;AAE5G,QAAM,cAAa,6BAAM,UAAS;AAClC,QAAM,SAAS,cAAc,6BAAM,WAAU,MAAO,6BAAM;AAI1D,QAAM,UAAU,OAAO,UAAU,WAAW,QAAQ;AACpD,QAAM,CAAC,YAAY,aAAa,IAAI,MAAM,SAAwB,OAAO;AACzE,MAAI,SAAS,WAAW;AACtB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,SAAQ;AAAA,QACR,MAAM,kBAAkB,UAAU;AAAA,QAClC;AAAA,QAEA;AAAA,QACA;AAAA,QACA,QAAQ,6BAAM;AAAA,QACd,WAAW,6BAAM;AAAA,QACjB,QAAQ,6BAAM;AAAA,MAAA;AAAA,IAAA;AAAA,EAGpB;AAMA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAS;AAAA,MACT,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,OAAO;AAAA,MACP,UAAU,CAAC,MAAM;AAAE,sBAAc,CAAC;AAAG,2CAAU;AAAA,MAAG;AAAA,MAClD;AAAA,MACA,QAAQ,6BAAM;AAAA,MACd,WAAW,6BAAM;AAAA,MACjB,QAAQ,MAAM,qCAAW;AAAA,MACzB,WAAW,CAAC,MAAM;AAChB,YAAI,EAAE,QAAQ,UAAU;AAAE,YAAE,eAAA;AAAkB;AAAA,QAAa;AAC3D,YAAI,EAAE,QAAQ,SAAS;AAAE,YAAE,eAAA;AAAkB,+CAAW;AAAA,QAAY;AAAA,MACtE;AAAA,IAAA;AAAA,EAAA;AAGN;AAKA,MAAM,iBAAiB,CAAC,aAA0B,CAAC,SAAkB;AAAE,MAAI,CAAC,KAAM;AAAa;AAQ/F,SAAS,SAAS,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,YAAY,UAAU,YAAgC;AAC7G,MAAI,SAAS,WAAW;AACtB,WACE;AAAA,MAACA;AAAAA,MAAA;AAAA,QAEC,SAAQ;AAAA,QACR,MAAM,kBAAkB,UAAU;AAAA,QAClC;AAAA,QACA;AAAA,QACA,eAAe,6BAAM;AAAA,QACrB,QAAQ,6BAAM;AAAA,QAId,oBAAoB,eAAe;AAAA,MAAA;AAAA,MAV/B;AAAA,IAAA;AAAA,EAaV;AACA,SACE;AAAA,IAACA;AAAAA,IAAA;AAAA,MAEC,WAAS;AAAA,MACT,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,MAC3C,WAAU,6BAAM,iBAAgB;AAAA,MAChC,UAAU,CAAC,MAAM,qCAAW;AAAA,MAC5B,aAAW;AAAA,MACX,cAAc,eAAe,QAAQ;AAAA,IAAA;AAAA,IARjC;AAAA,EAAA;AAWV;AAEA,SAAS,SAAS,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,YAAY,UAAU,YAAgC;AAC7G,MAAI,SAAS,WAAW;AACtB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,SAAQ;AAAA,QACR,MAAM,kBAAkB,UAAU;AAAA,QAClC;AAAA,QACA;AAAA,QACA,eAAe,6BAAM;AAAA,QACrB,QAAQ,6BAAM;AAAA,QACd,oBAAoB,eAAe;AAAA,MAAA;AAAA,MAP/B;AAAA,IAAA;AAAA,EAUV;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,OAAO,OAAO,UAAU,WAAW,QAAQ;AAAA,MAC3C,cAAa,6BAAM,iBAAgB;AAAA,MACnC,YAAY,6BAAM;AAAA,MAClB,YAAY,6BAAM;AAAA,MAClB,UAAU,CAAC,MAAM,qCAAW;AAAA,MAC5B,aAAW;AAAA,MACX,cAAc,eAAe,QAAQ;AAAA,IAAA;AAAA,IATjC;AAAA,EAAA;AAYV;AAEA,SAAS,WAAW,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,YAAY,UAAU,YAAgC;AAI/G,QAAM,eAAe,6BAAM,YAA2C;AACtE,MAAI,SAAS,WAAW;AACtB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,SAAQ;AAAA,QACR,MAAM,kBAAkB,UAAU;AAAA,QAClC;AAAA,QACA,UAAS,6BAAM,YAAW,CAAA;AAAA,QAC1B;AAAA,QACA,SAAS;AAAA,QACT,oBAAoB,eAAe;AAAA,MAAA;AAAA,MAP/B;AAAA,IAAA;AAAA,EAUV;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,WAAS;AAAA,MACT,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,UAAS,6BAAM,YAAW,CAAA;AAAA,MAC1B;AAAA,MACA,UAAU,CAAC,MAAM,qCAAW;AAAA,MAG5B,aAAY,6BAAM,gBAAe;AAAA,MACjC,SAAS;AAAA,MACT,aAAW;AAAA,MACX,cAAc,eAAe,QAAQ;AAAA,IAAA;AAAA,IAZjC;AAAA,EAAA;AAeV;AAEA,SAAS,gBAAgB,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,eAAe,YAAY,cAAc,SAAA,GAAgC;AACvI,QAAM,OAAO,kBAAiB,6BAAM,UAAS;AAC7C,MAAI,SAAS,WAAW;AACtB,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QAEC,SAAQ;AAAA,QACR,MAAM,kBAAkB,UAAU;AAAA,QAClC,OAAQ,SAA6B,CAAA;AAAA,QACrC,UAAS,6BAAM,YAAW,CAAA;AAAA,QAC1B;AAAA,QACA;AAAA,QACA,oBAAoB,eAAe;AAAA,MAAA;AAAA,MAP/B;AAAA,IAAA;AAAA,EAUV;AAGA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,UAAS,6BAAM,YAAW,CAAA;AAAA,MAC1B,OAAO,MAAM,QAAQ,KAAK,IAAK,QAAqB,CAAA;AAAA,MACpD,UAAU,CAAC,MAAM,6CAAe;AAAA,MAChC,aAAW;AAAA,MACX,cAAc,eAAe,QAAQ;AAAA,IAAA;AAAA,IAPjC;AAAA,EAAA;AAUV;AAEA,SAAS,WAAW,EAAE,OAAO,MAAM,MAAM,YAAY,YAAY,UAAU,UAAU,QAA4B;AAC/G,MAAI,SAAS,WAAW;AAEtB,WAAO,oBAAC,cAAA,EAA2B,SAAQ,SAAQ,MAAM,kBAAkB,UAAU,GAAG,OAAoC,MAAY,oBAAoB,eAAe,QAAlJ,SAAwJ;AAAA,EACnL;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB;AAAA,MACA,SAAQ,6BAAM,WAAU,CAAA;AAAA,MAExB,UAAU,CAAC,SAAS,qCAAW,KAAK,CAAC,KAAK;AAAA,MAC1C,aAAW;AAAA,MACX,cAAc,eAAe,QAAQ;AAAA,IAAA;AAAA,IARjC;AAAA,EAAA;AAWV;AAEA,SAAS,gBAAgB,EAAE,OAAO,MAAM,MAAM,YAAY,YAAY,cAAc,UAAU,QAA4B;AACxH,MAAI,SAAS,WAAW;AAEtB,+BAAQ,cAAA,EAA2B,SAAQ,SAAQ,MAAM,kBAAkB,UAAU,GAAG,OAAQ,SAA2B,CAAA,GAAI,MAAY,oBAAoB,eAAe,QAArJ,SAA2J;AAAA,EACtL;AAGA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MAEC,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,OAAO,MAAM,QAAQ,KAAK,IAAK,QAA0B,CAAA;AAAA,MACzD,SAAQ,6BAAM,WAAU,CAAA;AAAA,MACxB,UAAU,CAAC,SAAS,6CAAe;AAAA,MACnC,aAAW;AAAA,MACX,cAAc,eAAe,QAAQ;AAAA,IAAA;AAAA,IAPjC;AAAA,EAAA;AAUV;AAEA,SAAS,YAAY,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,YAAY,YAAgC;AAKtG,MAAI,SAAS,cAAc,CAAC,cAAc,aAAa;AACrD,WAAO,oBAAC,UAAA,EAAS,SAAQ,SAAQ,MAAM,kBAAkB,UAAU,GAAG,SAAS,UAAU,KAAA,CAAM;AAAA,EACjG;AACA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAM,SAAS,OAAO,OAAO;AAAA,MAC7B,SAAS,UAAU;AAAA,MACnB,iBAAiB,CAAC,YAAY,qCAAW,YAAY;AAAA,MACrD,eAAY,6BAAM,cAAa;AAAA,IAAA;AAAA,EAAA;AAGrC;AASA,SAAS,QAAQ,EAAE,OAAO,MAAM,MAAM,MAAM,YAAY,YAAY,eAAe,UAAU,SAAA,GAAgC;AAC3H,MAAI,SAAS,WAAW;AAEtB,UAAM,UACJ,oBAAC,WAAA,EAAU,SAAQ,SAAQ,MAAM,kBAAkB,UAAU,GAAG,OAA+B,OAAO,6BAAM,WAAW,MAAY,oBAAkB,MAAC;AAGxJ,QAAI,CAAC,cAAc,WAAY,QAAO;AAEtC,WACE,qBAAC,QAAA,EAAK,WAAU,gDAA+C,UAAA;AAAA,MAAA;AAAA,MAC7D,oBAAC,QAAA,EAAK,WAAU,kBAAkB,UAAA,SAAQ;AAAA,MAC1C;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,SAAQ;AAAA,UACR,MAAK;AAAA,UACL,UAAQ;AAAA,UACR,WAAW;AAAA,UACX,cAAW;AAAA,UACX,WAAW,GAAG,gEAAgE;AAAA,UAC9E,SAAS,CAAC,MAAM;AACd,cAAE,gBAAA;AACF;AAAA,UACF;AAAA,QAAA;AAAA,MAAA;AAAA,IACF,GACF;AAAA,EAEJ;AAMA,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,WAAS;AAAA,MACT,SAAQ;AAAA,MACR,MAAM,aAAa,IAAI;AAAA,MACvB,cAAc,SAAS,OAAO,OAAO,KAAK,IAAI;AAAA,MAC9C,QAAQ,CAAC,MAAM,qCAAW,EAAE,OAAO;AAAA,MACnC,WAAW,eAAe,UAAU,QAAQ;AAAA,IAAA;AAAA,EAAA;AAGlD;AAMO,MAAM,eAAsE;AAAA,EACjF,QAAa;AAAA,EACb,QAAa;AAAA,EACb,UAAa;AAAA;AAAA,EACb,MAAa;AAAA,EACb,MAAa;AAAA,EACb,QAAa;AAAA,EACb,aAAa;AAAA,EACb,QAAa;AAAA,EACb,aAAa;AAAA,EACb,SAAa;AAAA,EACb,KAAa;AACf;AAeA,MAAM,2CAA2B,IAAA;AAEjC,SAAS,qBAAqB,OAA0C,KAAgD;AACtH,QAAM,kBAAkB,MAAM,KAAK,SAASC,iBAAgB,OAA2B;AACrF,WACE,oBAAC,sBAAA,EAAqB,SAAQ,cAG5B,8BAAC,0BAAA,EAAyB,MAAM,MAAM,MACpC,UAAA,oBAAC,OAAA,EAAO,GAAG,MAAA,CAAO,GACpB,GACF;AAAA,EAEJ,CAAC;AACC,kBAA6C,cAAc,mBAAmB,GAAG;AACnF,SAAO;AACT;AAGA,WAAW,QAAQ,OAAO,KAAK,YAAY,GAAmB;AAC5D,uBAAqB,IAAI,MAAM,qBAAqB,aAAa,IAAI,GAAG,IAAI,CAAC;AAC/E;AACA,qBAAqB,IAAI,aAAa,qBAAqB,YAAY,qBAAqB,CAAC;AAEtF,SAAS,qBAAqB,MAAsD;AACzF,SAAO,qBAAqB,IAAI,QAAQ,WAAW,KAAK,qBAAqB,IAAI,WAAW;AAC9F;"}
|
|
@@ -69,12 +69,14 @@ export interface DataTableProps<TData> extends Omit<React.HTMLAttributes<HTMLDiv
|
|
|
69
69
|
* (`--primary-subtle` bg fill)— per user「不要 dash 直接實的就好」+ codex Q2.2 token。
|
|
70
70
|
*/
|
|
71
71
|
spreadsheetMode?: boolean;
|
|
72
|
-
/**
|
|
73
|
-
selection?: string[];
|
|
74
|
-
/** 預設選取(uncontrolled) */
|
|
75
|
-
defaultSelection?: string[];
|
|
76
|
-
/** Selection 變更 callback */
|
|
77
|
-
onSelectionChange?: (next:
|
|
72
|
+
/** 已選列(controlled)。傳 string[] = include shorthand;傳 DataTableSelection 支援反向選取(all + excluded) */
|
|
73
|
+
selection?: string[] | DataTableSelection;
|
|
74
|
+
/** 預設選取(uncontrolled);同上接受 string[] 或 DataTableSelection */
|
|
75
|
+
defaultSelection?: string[] | DataTableSelection;
|
|
76
|
+
/** Selection 變更 callback(emit DataTableSelection union;include / all 兩模型) */
|
|
77
|
+
onSelectionChange?: (next: DataTableSelection) => void;
|
|
78
|
+
/** 全資料集筆數 M(server-side / filter 後);all 模式 count = totalCount − excluded.length(consumer 計算) */
|
|
79
|
+
totalCount?: number;
|
|
78
80
|
/** 是否啟用 selection / 模式;true 等同 'multi' */
|
|
79
81
|
selectable?: boolean | 'single' | 'multi';
|
|
80
82
|
/** Row 是否可選(disabled rows 只 disable checkbox,row 內容正常 render) */
|
|
@@ -161,6 +163,25 @@ export interface DataTableProps<TData> extends Omit<React.HTMLAttributes<HTMLDiv
|
|
|
161
163
|
*/
|
|
162
164
|
onColumnReorder?: (sourceId: string, targetId: string, position: 'before' | 'after') => void;
|
|
163
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* L2 選取模型(discriminated union,2026-06-22 對齊 MUI X DataGrid v8
|
|
168
|
+
* rowSelectionModel { type: include | exclude, ids } + AG Grid selectAll + toggledNodes)。
|
|
169
|
+
*
|
|
170
|
+
* - mode==='include' (ids):只選 ids 列(預設;= 載入/可見列逐一選取)。
|
|
171
|
+
* - mode==='all' (excluded):全資料集(filter 後)選取,扣掉 excluded —— 反向選取(inverted)。
|
|
172
|
+
* 解決「全選 10k 筆只載 50 筆 → 無法列舉其餘 ID」:all 模式「選取 = 全集 − excluded」,
|
|
173
|
+
* 任何 toggle 都只是 excluded 的 add/remove,對任意順序封閉、O(1)、不需列舉未載入 ID。
|
|
174
|
+
*
|
|
175
|
+
* 計數(consumer):mode==='all' ? totalCount − excluded.length : ids.length(需傳 totalCount)。
|
|
176
|
+
* 進入 all 模式:consumer 在「選取全部 M」hint 點擊時 setSelection({ mode: 'all', excluded: [] })。
|
|
177
|
+
*/
|
|
178
|
+
export type DataTableSelection = {
|
|
179
|
+
mode: 'include';
|
|
180
|
+
ids: string[];
|
|
181
|
+
} | {
|
|
182
|
+
mode: 'all';
|
|
183
|
+
excluded: string[];
|
|
184
|
+
};
|
|
164
185
|
export declare const MIN_COLUMN_WIDTH = 80;
|
|
165
186
|
export declare const DataTable: <TData>(props: DataTableProps<TData> & {
|
|
166
187
|
ref?: React.ForwardedRef<HTMLDivElement>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/data-table.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAG9B,OAAO,EAML,KAAK,SAAS,EAEd,KAAK,YAAY,EAElB,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EAAO,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAA;AA4BjE,QAAA,MAAM,iBAAiB;;8EAGrB,CAAA;AAIF,KAAK,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAA;AAEnC,MAAM,WAAW,cAAc,CAAC,KAAK,CACnC,SAAQ,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,UAAU,CAAC,EAC5D,YAAY,CAAC,OAAO,iBAAiB,CAAC;IAExC,OAAO,EAAE,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAA;IAChC,IAAI,EAAE,KAAK,EAAE,CAAA;IACb,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,iBAAiB,CAAC,CAAC,CAAA;IACzF,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,KAAK,CAAC,SAAS,CAAA;IAC5C;;;;;;;;;;;;;;;OAeG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAA;IAC9C,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC7B,yDAAyD;IACzD,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;OAKG;IACH,8BAA8B,CAAC,EAAE,OAAO,CAAA;IACxC;;;;;;;OAOG;IACH,kCAAkC,CAAC,EAAE,OAAO,CAAA;IAC5C;;;;;;;;;;;;;OAaG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAGzB,
|
|
1
|
+
{"version":3,"file":"data-table.d.ts","sourceRoot":"","sources":["../../../src/components/DataTable/data-table.tsx"],"names":[],"mappings":"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"}
|
|
@@ -28,6 +28,27 @@ const dataTableVariants = cva("bg-surface rounded-md overflow-hidden", {
|
|
|
28
28
|
});
|
|
29
29
|
const cellEditId = (rowId, colId) => `${rowId}__${colId}`;
|
|
30
30
|
const SELECT_COL_ID = "__select__";
|
|
31
|
+
function normalizeSelection(input) {
|
|
32
|
+
if (input === void 0) return void 0;
|
|
33
|
+
if (Array.isArray(input)) return { mode: "include", ids: input };
|
|
34
|
+
return input;
|
|
35
|
+
}
|
|
36
|
+
function applySelectIds(sel, ids, willSelect) {
|
|
37
|
+
if (sel.mode === "include") {
|
|
38
|
+
const set2 = new Set(sel.ids);
|
|
39
|
+
ids.forEach((id) => {
|
|
40
|
+
if (willSelect) set2.add(id);
|
|
41
|
+
else set2.delete(id);
|
|
42
|
+
});
|
|
43
|
+
return { mode: "include", ids: Array.from(set2) };
|
|
44
|
+
}
|
|
45
|
+
const set = new Set(sel.excluded);
|
|
46
|
+
ids.forEach((id) => {
|
|
47
|
+
if (willSelect) set.delete(id);
|
|
48
|
+
else set.add(id);
|
|
49
|
+
});
|
|
50
|
+
return { mode: "all", excluded: Array.from(set) };
|
|
51
|
+
}
|
|
31
52
|
const cellPadding = { paddingBlock: "var(--table-cell-py)", paddingInline: "var(--table-cell-px)" };
|
|
32
53
|
const HEADER_BG = "bg-muted";
|
|
33
54
|
const MIN_COLUMN_WIDTH = 80;
|
|
@@ -314,6 +335,7 @@ function DataTableInner({
|
|
|
314
335
|
selection: selectionProp,
|
|
315
336
|
defaultSelection,
|
|
316
337
|
onSelectionChange,
|
|
338
|
+
totalCount,
|
|
317
339
|
selectable = false,
|
|
318
340
|
isRowSelectable,
|
|
319
341
|
getRowId,
|
|
@@ -401,8 +423,8 @@ function DataTableInner({
|
|
|
401
423
|
const enabled = selectable !== false;
|
|
402
424
|
const mode = selectable === "single" ? "single" : "multi";
|
|
403
425
|
const [selection, setSelection] = useControllable({
|
|
404
|
-
value: selectionProp,
|
|
405
|
-
defaultValue: defaultSelection ?? [],
|
|
426
|
+
value: normalizeSelection(selectionProp),
|
|
427
|
+
defaultValue: normalizeSelection(defaultSelection) ?? { mode: "include", ids: [] },
|
|
406
428
|
onChange: onSelectionChange
|
|
407
429
|
});
|
|
408
430
|
const anchorRowIdRef = React.useRef(null);
|
|
@@ -733,7 +755,7 @@ function DataTableInner({
|
|
|
733
755
|
const checkboxSize = size === "lg" ? "lg" : "md";
|
|
734
756
|
const onCellClick = isDisabled ? void 0 : (e) => {
|
|
735
757
|
e.stopPropagation();
|
|
736
|
-
if (mode === "single") setSelection([rowId]);
|
|
758
|
+
if (mode === "single") setSelection({ mode: "include", ids: [rowId] });
|
|
737
759
|
else toggleRow(rowId, rowOriginal, { shiftKey: e.shiftKey });
|
|
738
760
|
};
|
|
739
761
|
return /* @__PURE__ */ jsx(
|
|
@@ -757,7 +779,7 @@ function DataTableInner({
|
|
|
757
779
|
Checkbox,
|
|
758
780
|
{
|
|
759
781
|
size: checkboxSize,
|
|
760
|
-
checked:
|
|
782
|
+
checked: isSelectedId(rowId),
|
|
761
783
|
disabled: isDisabled,
|
|
762
784
|
"aria-label": ariaLabel,
|
|
763
785
|
onClick: (e) => {
|
|
@@ -967,33 +989,42 @@ function DataTableInner({
|
|
|
967
989
|
React.useEffect(() => {
|
|
968
990
|
if (!enabled || preserveSelectionOnFilter) return;
|
|
969
991
|
setSelection((prev) => {
|
|
970
|
-
|
|
971
|
-
|
|
992
|
+
if (prev.mode === "all") return prev;
|
|
993
|
+
const filtered = prev.ids.filter((id) => visibleRowIdsSet.has(id));
|
|
994
|
+
return filtered.length === prev.ids.length ? prev : { mode: "include", ids: filtered };
|
|
972
995
|
});
|
|
973
996
|
}, [visibleRowIdsKey, enabled, preserveSelectionOnFilter, visibleRowIdsSet, setSelection]);
|
|
974
997
|
const selectableVisibleIds = React.useMemo(() => {
|
|
975
998
|
if (!enabled) return [];
|
|
976
999
|
return rows.filter((r) => !isRowSelectable || isRowSelectable(r.original)).map((r) => r.id);
|
|
977
1000
|
}, [rows, enabled, isRowSelectable]);
|
|
978
|
-
const
|
|
979
|
-
|
|
1001
|
+
const includeSet = React.useMemo(
|
|
1002
|
+
() => selection.mode === "include" ? new Set(selection.ids) : /* @__PURE__ */ new Set(),
|
|
1003
|
+
[selection]
|
|
1004
|
+
);
|
|
1005
|
+
const excludeSet = React.useMemo(
|
|
1006
|
+
() => selection.mode === "all" ? new Set(selection.excluded) : /* @__PURE__ */ new Set(),
|
|
1007
|
+
[selection]
|
|
1008
|
+
);
|
|
1009
|
+
const isSelectedId = React.useCallback(
|
|
1010
|
+
(id) => selection.mode === "include" ? includeSet.has(id) : !excludeSet.has(id),
|
|
1011
|
+
[selection.mode, includeSet, excludeSet]
|
|
1012
|
+
);
|
|
1013
|
+
const hasAnySelection = selection.mode === "all" || includeSet.size > 0;
|
|
1014
|
+
const visibleSelectedCount = selectableVisibleIds.filter((id) => isSelectedId(id)).length;
|
|
980
1015
|
const headerCheckedState = selectableVisibleIds.length === 0 ? false : visibleSelectedCount === 0 ? false : visibleSelectedCount === selectableVisibleIds.length ? true : "indeterminate";
|
|
981
1016
|
const visibleIdToRow = React.useMemo(
|
|
982
1017
|
() => new Map(rows.map((r) => [r.id, r])),
|
|
983
1018
|
[rows]
|
|
984
1019
|
);
|
|
985
1020
|
const toggleHeaderCheckbox = React.useCallback(() => {
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
setSelection((prev) => prev.filter((id) => !visibleSet.has(id)));
|
|
989
|
-
} else {
|
|
990
|
-
setSelection((prev) => Array.from(/* @__PURE__ */ new Set([...prev, ...selectableVisibleIds])));
|
|
991
|
-
}
|
|
1021
|
+
const willSelect = headerCheckedState !== true;
|
|
1022
|
+
setSelection((prev) => applySelectIds(prev, selectableVisibleIds, willSelect));
|
|
992
1023
|
}, [headerCheckedState, selectableVisibleIds, setSelection]);
|
|
993
1024
|
const toggleRow = React.useCallback((rowId, rowOriginal, opts) => {
|
|
994
1025
|
if (isRowSelectable && !isRowSelectable(rowOriginal)) return;
|
|
995
1026
|
if (mode === "single") {
|
|
996
|
-
setSelection(
|
|
1027
|
+
setSelection(isSelectedId(rowId) ? { mode: "include", ids: [] } : { mode: "include", ids: [rowId] });
|
|
997
1028
|
anchorRowIdRef.current = rowId;
|
|
998
1029
|
return;
|
|
999
1030
|
}
|
|
@@ -1008,23 +1039,15 @@ function DataTableInner({
|
|
|
1008
1039
|
const row = visibleIdToRow.get(id);
|
|
1009
1040
|
return row && (!isRowSelectable || isRowSelectable(row.original));
|
|
1010
1041
|
});
|
|
1011
|
-
const
|
|
1012
|
-
setSelection((prev) =>
|
|
1013
|
-
const set = new Set(prev);
|
|
1014
|
-
rangeIds.forEach((id) => willCheck ? set.add(id) : set.delete(id));
|
|
1015
|
-
return Array.from(set);
|
|
1016
|
-
});
|
|
1042
|
+
const willCheck2 = !isSelectedId(rowId);
|
|
1043
|
+
setSelection((prev) => applySelectIds(prev, rangeIds, willCheck2));
|
|
1017
1044
|
return;
|
|
1018
1045
|
}
|
|
1019
1046
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
if (set.has(rowId)) set.delete(rowId);
|
|
1023
|
-
else set.add(rowId);
|
|
1024
|
-
return Array.from(set);
|
|
1025
|
-
});
|
|
1047
|
+
const willCheck = !isSelectedId(rowId);
|
|
1048
|
+
setSelection((prev) => applySelectIds(prev, [rowId], willCheck));
|
|
1026
1049
|
anchorRowIdRef.current = rowId;
|
|
1027
|
-
}, [isRowSelectable, mode,
|
|
1050
|
+
}, [isRowSelectable, mode, isSelectedId, rows, visibleIdToRow, setSelection]);
|
|
1028
1051
|
const tableKeyboardHandler = React.useCallback(
|
|
1029
1052
|
(e) => {
|
|
1030
1053
|
var _a2;
|
|
@@ -1081,12 +1104,12 @@ function DataTableInner({
|
|
|
1081
1104
|
if (!enabled) return;
|
|
1082
1105
|
if ((e.metaKey || e.ctrlKey) && e.key === "a" && mode === "multi") {
|
|
1083
1106
|
e.preventDefault();
|
|
1084
|
-
setSelection((prev) =>
|
|
1107
|
+
setSelection((prev) => applySelectIds(prev, selectableVisibleIds, true));
|
|
1085
1108
|
return;
|
|
1086
1109
|
}
|
|
1087
|
-
if (e.key === "Escape" &&
|
|
1110
|
+
if (e.key === "Escape" && hasAnySelection) {
|
|
1088
1111
|
e.preventDefault();
|
|
1089
|
-
setSelection([]);
|
|
1112
|
+
setSelection({ mode: "include", ids: [] });
|
|
1090
1113
|
anchorRowIdRef.current = null;
|
|
1091
1114
|
return;
|
|
1092
1115
|
}
|
|
@@ -1094,7 +1117,7 @@ function DataTableInner({
|
|
|
1094
1117
|
[
|
|
1095
1118
|
enabled,
|
|
1096
1119
|
mode,
|
|
1097
|
-
|
|
1120
|
+
hasAnySelection,
|
|
1098
1121
|
selectableVisibleIds,
|
|
1099
1122
|
setSelection,
|
|
1100
1123
|
spreadsheetMode,
|
|
@@ -1826,8 +1849,8 @@ function DataTableInner({
|
|
|
1826
1849
|
return /* @__PURE__ */ jsx(
|
|
1827
1850
|
RadioGroupPrimitive.Root,
|
|
1828
1851
|
{
|
|
1829
|
-
value: selection[0] ?? "",
|
|
1830
|
-
onValueChange: (v) => v && setSelection([v]),
|
|
1852
|
+
value: selection.mode === "include" ? selection.ids[0] ?? "" : "",
|
|
1853
|
+
onValueChange: (v) => v && setSelection({ mode: "include", ids: [v] }),
|
|
1831
1854
|
children: wrapWithDnd(tableContent)
|
|
1832
1855
|
}
|
|
1833
1856
|
);
|