@qijenchen/design-system 0.1.0-beta.74 → 0.1.0-beta.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/components/BulkActionBar/bulk-action-bar.d.ts.map +1 -1
  2. package/dist/components/BulkActionBar/bulk-action-bar.js +1 -1
  3. package/dist/components/BulkActionBar/bulk-action-bar.js.map +1 -1
  4. package/dist/components/DataTable/data-table.d.ts +27 -6
  5. package/dist/components/DataTable/data-table.d.ts.map +1 -1
  6. package/dist/components/DataTable/data-table.js +57 -34
  7. package/dist/components/DataTable/data-table.js.map +1 -1
  8. package/ds-canonical/hooks/check_story_invariants.sh +26 -0
  9. package/ds-canonical/hooks/tests/test_check_story_invariants.sh +30 -0
  10. package/llms-full.txt +1 -1
  11. package/llms.txt +1 -1
  12. package/package.json +1 -1
  13. package/src/components/Accordion/accordion.principles.stories.tsx +3 -3
  14. package/src/components/Alert/alert.principles.stories.tsx +5 -5
  15. package/src/components/AppShell/app-shell.principles.stories.tsx +6 -6
  16. package/src/components/AspectRatio/aspect-ratio.principles.stories.tsx +1 -1
  17. package/src/components/Avatar/avatar.principles.stories.tsx +3 -3
  18. package/src/components/Badge/badge.principles.stories.tsx +3 -3
  19. package/src/components/Breadcrumb/breadcrumb.principles.stories.tsx +3 -3
  20. package/src/components/BulkActionBar/bulk-action-bar.anatomy.stories.tsx +1 -1
  21. package/src/components/BulkActionBar/bulk-action-bar.principles.stories.tsx +3 -3
  22. package/src/components/BulkActionBar/bulk-action-bar.spec.md +4 -2
  23. package/src/components/BulkActionBar/bulk-action-bar.stories.tsx +2 -2
  24. package/src/components/BulkActionBar/bulk-action-bar.tsx +3 -2
  25. package/src/components/Button/button.principles.stories.tsx +3 -3
  26. package/src/components/Calendar/calendar.principles.stories.tsx +3 -3
  27. package/src/components/Carousel/carousel.principles.stories.tsx +2 -2
  28. package/src/components/Chart/chart.principles.stories.tsx +4 -4
  29. package/src/components/Checkbox/checkbox.principles.stories.tsx +2 -2
  30. package/src/components/Chip/chip.principles.stories.tsx +3 -3
  31. package/src/components/Coachmark/coachmark.principles.stories.tsx +3 -3
  32. package/src/components/Combobox/combobox.anatomy.stories.tsx +2 -2
  33. package/src/components/Combobox/combobox.principles.stories.tsx +5 -5
  34. package/src/components/Command/command.principles.stories.tsx +7 -7
  35. package/src/components/DataTable/data-table.principles.stories.tsx +3 -3
  36. package/src/components/DataTable/data-table.spec.md +23 -15
  37. package/src/components/DataTable/data-table.stories.tsx +29 -25
  38. package/src/components/DataTable/data-table.tsx +92 -44
  39. package/src/components/DateGrid/date-grid.principles.stories.tsx +4 -4
  40. package/src/components/DatePicker/date-picker.principles.stories.tsx +5 -5
  41. package/src/components/DescriptionList/description-list.principles.stories.tsx +5 -5
  42. package/src/components/Dialog/dialog.principles.stories.tsx +4 -4
  43. package/src/components/DropdownMenu/dropdown-menu.anatomy.stories.tsx +1 -1
  44. package/src/components/DropdownMenu/dropdown-menu.principles.stories.tsx +5 -5
  45. package/src/components/Empty/empty.principles.stories.tsx +2 -2
  46. package/src/components/Field/field.principles.stories.tsx +4 -4
  47. package/src/components/FileItem/file-item.principles.stories.tsx +5 -5
  48. package/src/components/FileUpload/file-upload.principles.stories.tsx +4 -4
  49. package/src/components/FileViewer/file-viewer.principles.stories.tsx +5 -5
  50. package/src/components/HoverCard/hover-card.principles.stories.tsx +5 -5
  51. package/src/components/Input/input.principles.stories.tsx +4 -4
  52. package/src/components/LinkInput/link-input.principles.stories.tsx +5 -5
  53. package/src/components/Menu/menu-item.principles.stories.tsx +7 -7
  54. package/src/components/Notice/notice.principles.stories.tsx +7 -7
  55. package/src/components/NumberInput/number-input.principles.stories.tsx +4 -4
  56. package/src/components/OverflowIndicator/overflow-indicator.principles.stories.tsx +5 -5
  57. package/src/components/PeoplePicker/people-picker.principles.stories.tsx +3 -3
  58. package/src/components/Popover/popover.principles.stories.tsx +1 -1
  59. package/src/components/ProfileCard/profile-card.principles.stories.tsx +1 -1
  60. package/src/components/ProgressBar/progress-bar.principles.stories.tsx +2 -2
  61. package/src/components/RadioGroup/radio-group.principles.stories.tsx +2 -2
  62. package/src/components/Rating/rating.principles.stories.tsx +3 -3
  63. package/src/components/ScrollArea/scroll-area.principles.stories.tsx +4 -4
  64. package/src/components/Select/select.principles.stories.tsx +5 -5
  65. package/src/components/SelectMenu/select-menu.principles.stories.tsx +8 -8
  66. package/src/components/SelectionControl/selection-item.principles.stories.tsx +7 -7
  67. package/src/components/Separator/separator.principles.stories.tsx +4 -4
  68. package/src/components/Sheet/sheet.principles.stories.tsx +2 -2
  69. package/src/components/Sidebar/sidebar.principles.stories.tsx +4 -4
  70. package/src/components/Skeleton/skeleton.principles.stories.tsx +5 -5
  71. package/src/components/Slider/slider.principles.stories.tsx +3 -3
  72. package/src/components/Steps/steps.principles.stories.tsx +4 -4
  73. package/src/components/Switch/switch.principles.stories.tsx +1 -1
  74. package/src/components/Tabs/tabs.principles.stories.tsx +3 -3
  75. package/src/components/Tag/tag.principles.stories.tsx +3 -3
  76. package/src/components/Textarea/textarea.principles.stories.tsx +2 -2
  77. package/src/components/TimePicker/time-picker.principles.stories.tsx +5 -5
  78. package/src/components/Toast/toast.principles.stories.tsx +2 -2
  79. package/src/components/Tooltip/tooltip.principles.stories.tsx +3 -3
  80. package/src/components/TreeView/tree-view.principles.stories.tsx +5 -5
  81. package/src/components/TreeView/tree-view.stories.tsx +1 -1
  82. package/src/tokens/color/color.spec.md +2 -0
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-action-bar.d.ts","sourceRoot":"","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAmB9B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAA;IACxC,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAGD,eAAO,MAAM,8BAA8B,EAAE,mBAK5C,CAAA;AAID,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E,wCAAwC;IACxC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,sFAAsF;IACtF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;CACtC;AAeD,QAAA,MAAM,aAAa,2FAkElB,CAAA;AAID,eAAO,MAAM,iBAAiB;;;;;;;;;;CAUpB,CAAA;AAEV,OAAO,EAAE,aAAa,EAAE,CAAA"}
1
+ {"version":3,"file":"bulk-action-bar.d.ts","sourceRoot":"","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAmB9B,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,CAAA;IACxC,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAGD,eAAO,MAAM,8BAA8B,EAAE,mBAK5C,CAAA;AAID,MAAM,WAAW,kBAAmB,SAAQ,KAAK,CAAC,cAAc,CAAC,cAAc,CAAC;IAC9E,wCAAwC;IACxC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,6DAA6D;IAC7D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,sFAAsF;IACtF,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,wEAAwE;IACxE,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,8CAA8C;IAC9C,MAAM,CAAC,EAAE,OAAO,CAAC,mBAAmB,CAAC,CAAA;CACtC;AAeD,QAAA,MAAM,aAAa,2FAmElB,CAAA;AAID,eAAO,MAAM,iBAAiB;;;;;;;;;;CAUpB,CAAA;AAEV,OAAO,EAAE,aAAa,EAAE,CAAA"}
@@ -16,7 +16,7 @@ const BulkActionBar = React.forwardRef(
16
16
  () => ({ ...BULK_ACTION_BAR_DEFAULT_LABELS, ...labelsOverride }),
17
17
  [labelsOverride]
18
18
  );
19
- if (selection.length === 0) return null;
19
+ if (selection.length === 0 && (totalSelected ?? 0) === 0) return null;
20
20
  return /* @__PURE__ */ jsxs(
21
21
  "div",
22
22
  {
@@ -1 +1 @@
1
- {"version":3,"file":"bulk-action-bar.js","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"sourcesContent":["import * as React from 'react'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ButtonDivider } from '@/design-system/components/Button/button-group'\n\n// ── 消費的 SSOT ───────────────────────────────────────────────────────────────\n// - bulk-action-bar.spec.md(本元件 SSOT)\n// - DataTable/data-table.spec.md「L2 選取」(整合方式)\n// - button.spec.md + button-group.tsx(action variant=tertiary,size=md,\n// gap-2 + ButtonDivider 自帶 mx-1 = 12px 視覺距離)\n// - inline-action.spec.md「same-row consistency rule」(close X 同尺寸 md)\n// - tokens/layoutSpace/layoutSpace.spec.md(footer 用 px-loose py-tight)\n// - patterns/overlay-surface/overlay-surface.spec.md SurfaceFooter canonical\n// - Alert(banner)用 title ReactNode 帶 inline link CTA\n\n// ── i18n labels ─────────────────────────────────────────────────────────────\n\n// code-quality-allow: dead-export — public API per spec.md(consumer i18n override hook)\nexport interface BulkActionBarLabels {\n count: (n: number) => string\n clear: string\n hiddenSuffix: (hidden: number) => string\n toolbarAriaLabel: string\n}\n\n// code-quality-allow: dead-export — public API per spec.md(consumer spread + override)\nexport const BULK_ACTION_BAR_DEFAULT_LABELS: BulkActionBarLabels = {\n count: (n) => `已選 ${n} 項`,\n clear: '清除選取',\n hiddenSuffix: (hidden) => `· ${hidden} 個被 filter 隱藏`,\n toolbarAriaLabel: '批次操作',\n}\n\n// ── Props ───────────────────────────────────────────────────────────────────\n\nexport interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 已選 ID,length === 0 時自動隱藏(回傳 null) */\n selection: readonly string[]\n /** Clear 觸發,user 點 X icon 或 Esc(consumer 在 page-level 監聽) */\n onClear?: () => void\n /** 批次 actions(consumer 提供 md Button,variant=tertiary 或 tertiary+danger;不用 primary) */\n actions?: React.ReactNode\n /** Filter 模式:hidden 數量,顯示在 count 區 inline「{N} 已選 · {M} 個被 filter 隱藏」 */\n hiddenByFilter?: number\n /**\n * 「擴選整個 dataset」狀態(2026-05-13 ship,per user 抓 Alert「已選 5370」但 BulkActionBar\n * 仍顯「已選 50 項」regression):\n * - undefined / null(default):count 走 `selection.length`(page-level 視覺選取)\n * - number:count 走此數值(整個 dataset 擴選後 user 已選的真總數)\n *\n * Canonical pattern:consumer 把 BulkActionBar 跟「Alert info banner(提示擴選 dataset)」\n * 一起 mount,Alert 點「點此選取全部 N 個」→ setTotalSelected(N) → BulkActionBar count 同步。\n * 對齊 Gmail / Linear / Notion 全選 dataset hint pattern。\n * 詳 `bulk-action-bar.spec.md`「Extend dataset pattern」段。\n */\n totalSelected?: number | null\n /** i18n labels(Partial,merge with default) */\n labels?: Partial<BulkActionBarLabels>\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n// 視覺結構(同 SurfaceFooter / DataTable toolbar canonical):\n// px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]\n// gap-2 between elements\n// 全 md Buttons(same-row consistency)\n// <ButtonDivider /> 自帶 mx-1 = 12px 視覺距離\n//\n// Hint banner(擴 dataset 提示)完全外包給 Alert 元件 — consumer 視 ref 圖\n// 黏在 BulkActionBar 上方/下方,用 Alert variant=\"info\" placement=\"fixed\"\n// title={inline link JSX}。BulkActionBar 自己不再有 hint banner slot。\n//\n// 浮起 / fixed positioning 由 consumer wrap 決定(BulkActionBar 不限定 placement)。\n\nconst BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(\n function BulkActionBar(\n { selection, onClear, actions, hiddenByFilter, totalSelected, labels: labelsOverride, className, ...props },\n ref\n ) {\n const labels: BulkActionBarLabels = React.useMemo(\n () => ({ ...BULK_ACTION_BAR_DEFAULT_LABELS, ...labelsOverride }),\n [labelsOverride]\n )\n\n // selection.length === 0 自動藏(對齊 spec 禁止事項 #3)\n if (selection.length === 0) return null\n\n return (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label={labels.toolbarAriaLabel}\n className={cn(\n 'flex items-center gap-2',\n 'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',\n // 上分隔線:bottom toolbar canonical(Linear / Apple Mail / Notion 共識)— 視覺從上方內容收尾\n 'border-t border-divider',\n className\n )}\n {...props}\n >\n {/* X close — md dismiss(2026-05-04 spec update:default placement = footer variant,\n visual weight 對齊 Dialog footer commitment buttons md;same-row consistency 維持)\n 未來若有 top-toolbar variant(覆蓋 sm-density toolbar)→ 該 variant override sm */}\n {onClear && (\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n dismiss\n startIcon={X}\n aria-label={labels.clear}\n onClick={onClear}\n />\n )}\n\n {/* count + filter hidden inline\n color canonical(2026-05-04):count = primary foreground + medium weight\n 理由:count 是 state-bearing 主資訊(「你在 selection mode + N items」),非裝飾\n World-class 共識:Linear/Notion/Carbon/Polaris 均用 primary foreground;muted 化弱化 state signal\n hiddenByFilter suffix 維持 muted(這是次資訊,視覺層次正確) */}\n {/* 2026-05-31 #3:aria-live 通知 SR 選取數變更;#20:補 font-medium 對齊 spec L90 + 上方 comment(state-bearing 主資訊) */}\n <span className=\"text-body text-foreground font-medium tabular-nums\" aria-live=\"polite\" aria-atomic=\"true\">\n {/* 2026-05-13:totalSelected override 走 dataset 擴選後真總數,否則 fallback page-level selection.length */}\n {labels.count(typeof totalSelected === 'number' ? totalSelected : selection.length)}\n {hiddenByFilter !== undefined && hiddenByFilter > 0 && (\n <span className=\"text-fg-muted font-normal\"> {labels.hiddenSuffix(hiddenByFilter)}</span>\n )}\n </span>\n\n {/* divider */}\n {actions && <ButtonDivider />}\n\n {/* batch actions slot(consumer 提供 md Buttons) */}\n {actions && (\n <div className=\"flex items-center gap-2 flex-1 min-w-0\">{actions}</div>\n )}\n </div>\n )\n }\n)\nBulkActionBar.displayName = 'BulkActionBar'\n\n// Story auto-compile metadata\nexport const bulkActionBarMeta = {\n component: 'BulkActionBar',\n family: null,\n variants: {},\n sizes: {},\n states: ['default'],\n tokens: {\n fg: ['text-foreground', 'text-fg-muted'],\n border: ['border-divider'],\n },\n} as const\n\nexport { BulkActionBar }\n"],"names":["BulkActionBar"],"mappings":";;;;;;AA2BO,MAAM,iCAAsD;AAAA,EACjE,OAAO,CAAC,MAAM,MAAM,CAAC;AAAA,EACrB,OAAO;AAAA,EACP,cAAc,CAAC,WAAW,KAAK,MAAM;AAAA,EACrC,kBAAkB;AACpB;AA0CA,MAAM,gBAAgB,MAAM;AAAA,EAC1B,SAASA,eACP,EAAE,WAAW,SAAS,SAAS,gBAAgB,eAAe,QAAQ,gBAAgB,WAAW,GAAG,MAAA,GACpG,KACA;AACA,UAAM,SAA8B,MAAM;AAAA,MACxC,OAAO,EAAE,GAAG,gCAAgC,GAAG;MAC/C,CAAC,cAAc;AAAA,IAAA;AAIjB,QAAI,UAAU,WAAW,EAAG,QAAO;AAEnC,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAY,OAAO;AAAA,QACnB,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA,UAEA;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAKH,UAAA;AAAA,UAAA,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,UAAQ;AAAA,cACR,SAAO;AAAA,cACP,WAAW;AAAA,cACX,cAAY,OAAO;AAAA,cACnB,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,+BAUZ,QAAA,EAAK,WAAU,sDAAqD,aAAU,UAAS,eAAY,QAEjG,UAAA;AAAA,YAAA,OAAO,MAAM,OAAO,kBAAkB,WAAW,gBAAgB,UAAU,MAAM;AAAA,YACjF,mBAAmB,UAAa,iBAAiB,KAChD,qBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA;AAAA,cAAA;AAAA,cAAE,OAAO,aAAa,cAAc;AAAA,YAAA,EAAA,CAAE;AAAA,UAAA,GAEtF;AAAA,UAGC,+BAAY,eAAA,EAAc;AAAA,UAG1B,WACC,oBAAC,OAAA,EAAI,WAAU,0CAA0C,UAAA,QAAA,CAAQ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIzE;AACF;AACA,cAAc,cAAc;AAGrB,MAAM,oBAAoB;AAAA,EAC/B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EACV,OAAO,CAAA;AAAA,EACP,QAAQ,CAAC,SAAS;AAAA,EAClB,QAAQ;AAAA,IACN,IAAI,CAAC,mBAAmB,eAAe;AAAA,IACvC,QAAQ,CAAC,gBAAgB;AAAA,EAAA;AAE7B;"}
1
+ {"version":3,"file":"bulk-action-bar.js","sources":["../../../src/components/BulkActionBar/bulk-action-bar.tsx"],"sourcesContent":["import * as React from 'react'\nimport { X } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button } from '@/design-system/components/Button/button'\nimport { ButtonDivider } from '@/design-system/components/Button/button-group'\n\n// ── 消費的 SSOT ───────────────────────────────────────────────────────────────\n// - bulk-action-bar.spec.md(本元件 SSOT)\n// - DataTable/data-table.spec.md「L2 選取」(整合方式)\n// - button.spec.md + button-group.tsx(action variant=tertiary,size=md,\n// gap-2 + ButtonDivider 自帶 mx-1 = 12px 視覺距離)\n// - inline-action.spec.md「same-row consistency rule」(close X 同尺寸 md)\n// - tokens/layoutSpace/layoutSpace.spec.md(footer 用 px-loose py-tight)\n// - patterns/overlay-surface/overlay-surface.spec.md SurfaceFooter canonical\n// - Alert(banner)用 title ReactNode 帶 inline link CTA\n\n// ── i18n labels ─────────────────────────────────────────────────────────────\n\n// code-quality-allow: dead-export — public API per spec.md(consumer i18n override hook)\nexport interface BulkActionBarLabels {\n count: (n: number) => string\n clear: string\n hiddenSuffix: (hidden: number) => string\n toolbarAriaLabel: string\n}\n\n// code-quality-allow: dead-export — public API per spec.md(consumer spread + override)\nexport const BULK_ACTION_BAR_DEFAULT_LABELS: BulkActionBarLabels = {\n count: (n) => `已選 ${n} 項`,\n clear: '清除選取',\n hiddenSuffix: (hidden) => `· ${hidden} 個被 filter 隱藏`,\n toolbarAriaLabel: '批次操作',\n}\n\n// ── Props ───────────────────────────────────────────────────────────────────\n\nexport interface BulkActionBarProps extends React.HTMLAttributes<HTMLDivElement> {\n /** 已選 ID,length === 0 時自動隱藏(回傳 null) */\n selection: readonly string[]\n /** Clear 觸發,user 點 X icon 或 Esc(consumer 在 page-level 監聽) */\n onClear?: () => void\n /** 批次 actions(consumer 提供 md Button,variant=tertiary 或 tertiary+danger;不用 primary) */\n actions?: React.ReactNode\n /** Filter 模式:hidden 數量,顯示在 count 區 inline「{N} 已選 · {M} 個被 filter 隱藏」 */\n hiddenByFilter?: number\n /**\n * 「擴選整個 dataset」狀態(2026-05-13 ship,per user 抓 Alert「已選 5370」但 BulkActionBar\n * 仍顯「已選 50 項」regression):\n * - undefined / null(default):count 走 `selection.length`(page-level 視覺選取)\n * - number:count 走此數值(整個 dataset 擴選後 user 已選的真總數)\n *\n * Canonical pattern:consumer 把 BulkActionBar 跟「Alert info banner(提示擴選 dataset)」\n * 一起 mount,Alert 點「點此選取全部 N 個」→ setTotalSelected(N) → BulkActionBar count 同步。\n * 對齊 Gmail / Linear / Notion 全選 dataset hint pattern。\n * 詳 `bulk-action-bar.spec.md`「Extend dataset pattern」段。\n */\n totalSelected?: number | null\n /** i18n labels(Partial,merge with default) */\n labels?: Partial<BulkActionBarLabels>\n}\n\n// ── Component ───────────────────────────────────────────────────────────────\n// 視覺結構(同 SurfaceFooter / DataTable toolbar canonical):\n// px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]\n// gap-2 between elements\n// 全 md Buttons(same-row consistency)\n// <ButtonDivider /> 自帶 mx-1 = 12px 視覺距離\n//\n// Hint banner(擴 dataset 提示)完全外包給 Alert 元件 — consumer 視 ref 圖\n// 黏在 BulkActionBar 上方/下方,用 Alert variant=\"info\" placement=\"fixed\"\n// title={inline link JSX}。BulkActionBar 自己不再有 hint banner slot。\n//\n// 浮起 / fixed positioning 由 consumer wrap 決定(BulkActionBar 不限定 placement)。\n\nconst BulkActionBar = React.forwardRef<HTMLDivElement, BulkActionBarProps>(\n function BulkActionBar(\n { selection, onClear, actions, hiddenByFilter, totalSelected, labels: labelsOverride, className, ...props },\n ref\n ) {\n const labels: BulkActionBarLabels = React.useMemo(\n () => ({ ...BULK_ACTION_BAR_DEFAULT_LABELS, ...labelsOverride }),\n [labelsOverride]\n )\n\n // selection.length === 0 自動藏(對齊 spec 禁止事項 #3)。\n // 反向選取(DataTable all 模式)例外:visible 全被 excluded 但 totalSelected>0 仍有選取 → 顯示(2026-06-22)\n if (selection.length === 0 && (totalSelected ?? 0) === 0) return null\n\n return (\n <div\n ref={ref}\n role=\"toolbar\"\n aria-label={labels.toolbarAriaLabel}\n className={cn(\n 'flex items-center gap-2',\n 'px-[var(--layout-space-loose)] py-[var(--layout-space-tight)]',\n // 上分隔線:bottom toolbar canonical(Linear / Apple Mail / Notion 共識)— 視覺從上方內容收尾\n 'border-t border-divider',\n className\n )}\n {...props}\n >\n {/* X close — md dismiss(2026-05-04 spec update:default placement = footer variant,\n visual weight 對齊 Dialog footer commitment buttons md;same-row consistency 維持)\n 未來若有 top-toolbar variant(覆蓋 sm-density toolbar)→ 該 variant override sm */}\n {onClear && (\n <Button\n variant=\"text\"\n size=\"md\"\n iconOnly\n dismiss\n startIcon={X}\n aria-label={labels.clear}\n onClick={onClear}\n />\n )}\n\n {/* count + filter hidden inline\n color canonical(2026-05-04):count = primary foreground + medium weight\n 理由:count 是 state-bearing 主資訊(「你在 selection mode + N items」),非裝飾\n World-class 共識:Linear/Notion/Carbon/Polaris 均用 primary foreground;muted 化弱化 state signal\n hiddenByFilter suffix 維持 muted(這是次資訊,視覺層次正確) */}\n {/* 2026-05-31 #3:aria-live 通知 SR 選取數變更;#20:補 font-medium 對齊 spec L90 + 上方 comment(state-bearing 主資訊) */}\n <span className=\"text-body text-foreground font-medium tabular-nums\" aria-live=\"polite\" aria-atomic=\"true\">\n {/* 2026-05-13:totalSelected override 走 dataset 擴選後真總數,否則 fallback page-level selection.length */}\n {labels.count(typeof totalSelected === 'number' ? totalSelected : selection.length)}\n {hiddenByFilter !== undefined && hiddenByFilter > 0 && (\n <span className=\"text-fg-muted font-normal\"> {labels.hiddenSuffix(hiddenByFilter)}</span>\n )}\n </span>\n\n {/* divider */}\n {actions && <ButtonDivider />}\n\n {/* batch actions slot(consumer 提供 md Buttons) */}\n {actions && (\n <div className=\"flex items-center gap-2 flex-1 min-w-0\">{actions}</div>\n )}\n </div>\n )\n }\n)\nBulkActionBar.displayName = 'BulkActionBar'\n\n// Story auto-compile metadata\nexport const bulkActionBarMeta = {\n component: 'BulkActionBar',\n family: null,\n variants: {},\n sizes: {},\n states: ['default'],\n tokens: {\n fg: ['text-foreground', 'text-fg-muted'],\n border: ['border-divider'],\n },\n} as const\n\nexport { BulkActionBar }\n"],"names":["BulkActionBar"],"mappings":";;;;;;AA2BO,MAAM,iCAAsD;AAAA,EACjE,OAAO,CAAC,MAAM,MAAM,CAAC;AAAA,EACrB,OAAO;AAAA,EACP,cAAc,CAAC,WAAW,KAAK,MAAM;AAAA,EACrC,kBAAkB;AACpB;AA0CA,MAAM,gBAAgB,MAAM;AAAA,EAC1B,SAASA,eACP,EAAE,WAAW,SAAS,SAAS,gBAAgB,eAAe,QAAQ,gBAAgB,WAAW,GAAG,MAAA,GACpG,KACA;AACA,UAAM,SAA8B,MAAM;AAAA,MACxC,OAAO,EAAE,GAAG,gCAAgC,GAAG;MAC/C,CAAC,cAAc;AAAA,IAAA;AAKjB,QAAI,UAAU,WAAW,MAAM,iBAAiB,OAAO,EAAG,QAAO;AAEjE,WACE;AAAA,MAAC;AAAA,MAAA;AAAA,QACC;AAAA,QACA,MAAK;AAAA,QACL,cAAY,OAAO;AAAA,QACnB,WAAW;AAAA,UACT;AAAA,UACA;AAAA;AAAA,UAEA;AAAA,UACA;AAAA,QAAA;AAAA,QAED,GAAG;AAAA,QAKH,UAAA;AAAA,UAAA,WACC;AAAA,YAAC;AAAA,YAAA;AAAA,cACC,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,UAAQ;AAAA,cACR,SAAO;AAAA,cACP,WAAW;AAAA,cACX,cAAY,OAAO;AAAA,cACnB,SAAS;AAAA,YAAA;AAAA,UAAA;AAAA,+BAUZ,QAAA,EAAK,WAAU,sDAAqD,aAAU,UAAS,eAAY,QAEjG,UAAA;AAAA,YAAA,OAAO,MAAM,OAAO,kBAAkB,WAAW,gBAAgB,UAAU,MAAM;AAAA,YACjF,mBAAmB,UAAa,iBAAiB,KAChD,qBAAC,QAAA,EAAK,WAAU,6BAA4B,UAAA;AAAA,cAAA;AAAA,cAAE,OAAO,aAAa,cAAc;AAAA,YAAA,EAAA,CAAE;AAAA,UAAA,GAEtF;AAAA,UAGC,+BAAY,eAAA,EAAc;AAAA,UAG1B,WACC,oBAAC,OAAA,EAAI,WAAU,0CAA0C,UAAA,QAAA,CAAQ;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,EAIzE;AACF;AACA,cAAc,cAAc;AAGrB,MAAM,oBAAoB;AAAA,EAC/B,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,UAAU,CAAA;AAAA,EACV,OAAO,CAAA;AAAA,EACP,QAAQ,CAAC,SAAS;AAAA,EAClB,QAAQ;AAAA,IACN,IAAI,CAAC,mBAAmB,eAAe;AAAA,IACvC,QAAQ,CAAC,gBAAgB;AAAA,EAAA;AAE7B;"}
@@ -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
- /** 已選 row IDs(controlled) */
73
- selection?: string[];
74
- /** 預設選取(uncontrolled) */
75
- defaultSelection?: string[];
76
- /** Selection 變更 callback */
77
- onSelectionChange?: (next: string[]) => void;
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,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;IACpB,yBAAyB;IACzB,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,4BAA4B;IAC5B,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,CAAA;IAC5C,0CAA0C;IAC1C,UAAU,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAA;IACzC,iEAAiE;IACjE,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,OAAO,CAAA;IACzC,uEAAuE;IACvE,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAA;IAChD,0DAA0D;IAC1D,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,MAAM,CAAA;IACxC,+EAA+E;IAC/E,yBAAyB,CAAC,EAAE,OAAO,CAAA;IAGnC,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC1C,yBAAyB;IACzB,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjD,oBAAoB;IACpB,wBAAwB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAGlE,qFAAqF;IACrF,eAAe,CAAC,EAAE,OAAO,CAAA;IAGzB;iFAC6E;IAC7E,qBAAqB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IAGlD;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAGxE;;;;;;;;;;;;;;;OAeG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,KAAK,IAAI,CAAA;IACzF;;;;;;;;;OASG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IAC1D;;;;;;;OAOG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,OAAO,KAAK,IAAI,CAAA;CAC7F;AA0CD,eAAO,MAAM,gBAAgB,KAAK,CAAA;AA+kFlC,eAAO,MAAM,SAAS,EAAuC,CAAC,KAAK,EACjE,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,cAAc,CAAC,CAAA;CAAE,KACxE,KAAK,CAAC,YAAY,CAGtB;AAGD,eAAO,MAAM,aAAa;;;;;;;;;;;CAehB,CAAA;AAEV,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
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: selectionSet.has(rowId),
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
- const filtered = prev.filter((id) => visibleRowIdsSet.has(id));
971
- return filtered.length === prev.length ? prev : filtered;
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 selectionSet = React.useMemo(() => new Set(selection), [selection]);
979
- const visibleSelectedCount = selectableVisibleIds.filter((id) => selectionSet.has(id)).length;
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
- if (headerCheckedState === true) {
987
- const visibleSet = new Set(selectableVisibleIds);
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(selectionSet.has(rowId) ? [] : [rowId]);
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 willCheck = !selectionSet.has(rowId);
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
- setSelection((prev) => {
1021
- const set = new Set(prev);
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, selectionSet, rows, visibleIdToRow, setSelection]);
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) => Array.from(/* @__PURE__ */ new Set([...prev, ...selectableVisibleIds])));
1107
+ setSelection((prev) => applySelectIds(prev, selectableVisibleIds, true));
1085
1108
  return;
1086
1109
  }
1087
- if (e.key === "Escape" && selection.length > 0) {
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
- selection.length,
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
  );