@stackframe/dashboard-ui-components 2.8.84 → 2.8.85

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 (229) hide show
  1. package/dist/components/analytics-chart/analytics-chart-pie.d.ts +67 -0
  2. package/dist/components/analytics-chart/analytics-chart-pie.d.ts.map +1 -0
  3. package/dist/components/analytics-chart/analytics-chart-pie.js +253 -0
  4. package/dist/components/analytics-chart/analytics-chart-pie.js.map +1 -0
  5. package/dist/components/analytics-chart/analytics-chart.d.ts +554 -0
  6. package/dist/components/analytics-chart/analytics-chart.d.ts.map +1 -0
  7. package/dist/components/analytics-chart/analytics-chart.js +1021 -0
  8. package/dist/components/analytics-chart/analytics-chart.js.map +1 -0
  9. package/dist/components/analytics-chart/default-analytics-chart-tooltip.d.ts +66 -0
  10. package/dist/components/analytics-chart/default-analytics-chart-tooltip.d.ts.map +1 -0
  11. package/dist/components/analytics-chart/default-analytics-chart-tooltip.js +179 -0
  12. package/dist/components/analytics-chart/default-analytics-chart-tooltip.js.map +1 -0
  13. package/dist/components/analytics-chart/format.d.ts +13 -0
  14. package/dist/components/analytics-chart/format.d.ts.map +1 -0
  15. package/dist/components/analytics-chart/format.js +138 -0
  16. package/dist/components/analytics-chart/format.js.map +1 -0
  17. package/dist/components/analytics-chart/index.d.ts +8 -0
  18. package/dist/components/analytics-chart/index.js +184 -0
  19. package/dist/components/analytics-chart/palette.d.ts +15 -0
  20. package/dist/components/analytics-chart/palette.d.ts.map +1 -0
  21. package/dist/components/analytics-chart/palette.js +60 -0
  22. package/dist/components/analytics-chart/palette.js.map +1 -0
  23. package/dist/components/analytics-chart/render-data-series.d.ts +28 -0
  24. package/dist/components/analytics-chart/render-data-series.d.ts.map +1 -0
  25. package/dist/components/analytics-chart/render-data-series.js +109 -0
  26. package/dist/components/analytics-chart/render-data-series.js.map +1 -0
  27. package/dist/components/analytics-chart/state.d.ts +54 -0
  28. package/dist/components/analytics-chart/state.d.ts.map +1 -0
  29. package/dist/components/analytics-chart/state.js +142 -0
  30. package/dist/components/analytics-chart/state.js.map +1 -0
  31. package/dist/components/analytics-chart/strings.d.ts +33 -0
  32. package/dist/components/analytics-chart/strings.d.ts.map +1 -0
  33. package/dist/components/analytics-chart/strings.js +37 -0
  34. package/dist/components/analytics-chart/strings.js.map +1 -0
  35. package/dist/components/analytics-chart/types.d.ts +157 -0
  36. package/dist/components/analytics-chart/types.d.ts.map +1 -0
  37. package/dist/components/analytics-chart/types.js +21 -0
  38. package/dist/components/analytics-chart/types.js.map +1 -0
  39. package/dist/components/badge.d.ts +16 -0
  40. package/dist/components/badge.d.ts.map +1 -1
  41. package/dist/components/badge.js +16 -0
  42. package/dist/components/badge.js.map +1 -1
  43. package/dist/components/button.d.ts +15 -1
  44. package/dist/components/button.d.ts.map +1 -1
  45. package/dist/components/button.js +14 -0
  46. package/dist/components/button.js.map +1 -1
  47. package/dist/components/card.d.ts +28 -0
  48. package/dist/components/card.d.ts.map +1 -1
  49. package/dist/components/card.js +28 -0
  50. package/dist/components/card.js.map +1 -1
  51. package/dist/components/chart-card.d.ts +29 -0
  52. package/dist/components/chart-card.d.ts.map +1 -1
  53. package/dist/components/chart-card.js +29 -0
  54. package/dist/components/chart-card.js.map +1 -1
  55. package/dist/components/chart-legend.d.ts +1 -2
  56. package/dist/components/chart-legend.d.ts.map +1 -1
  57. package/dist/components/chart-legend.js +0 -4
  58. package/dist/components/chart-legend.js.map +1 -1
  59. package/dist/components/data-grid/data-grid-sizing.d.ts +11 -0
  60. package/dist/components/data-grid/data-grid-sizing.d.ts.map +1 -0
  61. package/dist/components/data-grid/data-grid-sizing.js +34 -0
  62. package/dist/components/data-grid/data-grid-sizing.js.map +1 -0
  63. package/dist/components/data-grid/data-grid-toolbar.d.ts +31 -0
  64. package/dist/components/data-grid/data-grid-toolbar.d.ts.map +1 -0
  65. package/dist/components/data-grid/data-grid-toolbar.js +226 -0
  66. package/dist/components/data-grid/data-grid-toolbar.js.map +1 -0
  67. package/dist/components/data-grid/data-grid.d.ts +233 -0
  68. package/dist/components/data-grid/data-grid.d.ts.map +1 -0
  69. package/dist/components/data-grid/data-grid.js +871 -0
  70. package/dist/components/data-grid/data-grid.js.map +1 -0
  71. package/dist/components/data-grid/index.d.ts +7 -0
  72. package/dist/components/data-grid/index.js +176 -0
  73. package/dist/components/data-grid/state.d.ts +91 -0
  74. package/dist/components/data-grid/state.d.ts.map +1 -0
  75. package/dist/components/data-grid/state.js +329 -0
  76. package/dist/components/data-grid/state.js.map +1 -0
  77. package/dist/components/data-grid/strings.d.ts +8 -0
  78. package/dist/components/data-grid/strings.d.ts.map +1 -0
  79. package/dist/components/data-grid/strings.js +42 -0
  80. package/dist/components/data-grid/strings.js.map +1 -0
  81. package/dist/components/data-grid/types.d.ts +242 -0
  82. package/dist/components/data-grid/types.d.ts.map +1 -0
  83. package/dist/components/data-grid/types.js +0 -0
  84. package/dist/components/data-grid/use-data-source.d.ts +79 -0
  85. package/dist/components/data-grid/use-data-source.d.ts.map +1 -0
  86. package/dist/components/data-grid/use-data-source.js +236 -0
  87. package/dist/components/data-grid/use-data-source.js.map +1 -0
  88. package/dist/components/empty-state.d.ts +16 -0
  89. package/dist/components/empty-state.d.ts.map +1 -1
  90. package/dist/components/empty-state.js +16 -0
  91. package/dist/components/empty-state.js.map +1 -1
  92. package/dist/components/metric-card.d.ts +24 -0
  93. package/dist/components/metric-card.d.ts.map +1 -1
  94. package/dist/components/metric-card.js +24 -0
  95. package/dist/components/metric-card.js.map +1 -1
  96. package/dist/components/progress-bar.d.ts +10 -0
  97. package/dist/components/progress-bar.d.ts.map +1 -1
  98. package/dist/components/progress-bar.js +10 -0
  99. package/dist/components/progress-bar.js.map +1 -1
  100. package/dist/components/separator.d.ts +9 -0
  101. package/dist/components/separator.d.ts.map +1 -1
  102. package/dist/components/separator.js +9 -0
  103. package/dist/components/separator.js.map +1 -1
  104. package/dist/components/skeleton.d.ts +12 -0
  105. package/dist/components/skeleton.d.ts.map +1 -1
  106. package/dist/components/skeleton.js +12 -0
  107. package/dist/components/skeleton.js.map +1 -1
  108. package/dist/components/table.d.ts +25 -0
  109. package/dist/components/table.d.ts.map +1 -1
  110. package/dist/components/table.js +25 -0
  111. package/dist/components/table.js.map +1 -1
  112. package/dist/dashboard-ui-components.global.js +8607 -2902
  113. package/dist/dashboard-ui-components.global.js.map +4 -4
  114. package/dist/esm/components/analytics-chart/analytics-chart-pie.d.ts +67 -0
  115. package/dist/esm/components/analytics-chart/analytics-chart-pie.d.ts.map +1 -0
  116. package/dist/esm/components/analytics-chart/analytics-chart-pie.js +251 -0
  117. package/dist/esm/components/analytics-chart/analytics-chart-pie.js.map +1 -0
  118. package/dist/esm/components/analytics-chart/analytics-chart.d.ts +554 -0
  119. package/dist/esm/components/analytics-chart/analytics-chart.d.ts.map +1 -0
  120. package/dist/esm/components/analytics-chart/analytics-chart.js +1019 -0
  121. package/dist/esm/components/analytics-chart/analytics-chart.js.map +1 -0
  122. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.d.ts +66 -0
  123. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.d.ts.map +1 -0
  124. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.js +176 -0
  125. package/dist/esm/components/analytics-chart/default-analytics-chart-tooltip.js.map +1 -0
  126. package/dist/esm/components/analytics-chart/format.d.ts +13 -0
  127. package/dist/esm/components/analytics-chart/format.d.ts.map +1 -0
  128. package/dist/esm/components/analytics-chart/format.js +133 -0
  129. package/dist/esm/components/analytics-chart/format.js.map +1 -0
  130. package/dist/esm/components/analytics-chart/index.d.ts +8 -0
  131. package/dist/esm/components/analytics-chart/index.js +9 -0
  132. package/dist/esm/components/analytics-chart/palette.d.ts +15 -0
  133. package/dist/esm/components/analytics-chart/palette.d.ts.map +1 -0
  134. package/dist/esm/components/analytics-chart/palette.js +55 -0
  135. package/dist/esm/components/analytics-chart/palette.js.map +1 -0
  136. package/dist/esm/components/analytics-chart/render-data-series.d.ts +28 -0
  137. package/dist/esm/components/analytics-chart/render-data-series.d.ts.map +1 -0
  138. package/dist/esm/components/analytics-chart/render-data-series.js +107 -0
  139. package/dist/esm/components/analytics-chart/render-data-series.js.map +1 -0
  140. package/dist/esm/components/analytics-chart/state.d.ts +54 -0
  141. package/dist/esm/components/analytics-chart/state.d.ts.map +1 -0
  142. package/dist/esm/components/analytics-chart/state.js +126 -0
  143. package/dist/esm/components/analytics-chart/state.js.map +1 -0
  144. package/dist/esm/components/analytics-chart/strings.d.ts +33 -0
  145. package/dist/esm/components/analytics-chart/strings.d.ts.map +1 -0
  146. package/dist/esm/components/analytics-chart/strings.js +34 -0
  147. package/dist/esm/components/analytics-chart/strings.js.map +1 -0
  148. package/dist/esm/components/analytics-chart/types.d.ts +157 -0
  149. package/dist/esm/components/analytics-chart/types.d.ts.map +1 -0
  150. package/dist/esm/components/analytics-chart/types.js +18 -0
  151. package/dist/esm/components/analytics-chart/types.js.map +1 -0
  152. package/dist/esm/components/badge.d.ts +16 -0
  153. package/dist/esm/components/badge.d.ts.map +1 -1
  154. package/dist/esm/components/badge.js +16 -0
  155. package/dist/esm/components/badge.js.map +1 -1
  156. package/dist/esm/components/button.d.ts +14 -0
  157. package/dist/esm/components/button.d.ts.map +1 -1
  158. package/dist/esm/components/button.js +14 -0
  159. package/dist/esm/components/button.js.map +1 -1
  160. package/dist/esm/components/card.d.ts +28 -0
  161. package/dist/esm/components/card.d.ts.map +1 -1
  162. package/dist/esm/components/card.js +28 -0
  163. package/dist/esm/components/card.js.map +1 -1
  164. package/dist/esm/components/chart-card.d.ts +29 -0
  165. package/dist/esm/components/chart-card.d.ts.map +1 -1
  166. package/dist/esm/components/chart-card.js +29 -0
  167. package/dist/esm/components/chart-card.js.map +1 -1
  168. package/dist/esm/components/chart-legend.d.ts +1 -2
  169. package/dist/esm/components/chart-legend.d.ts.map +1 -1
  170. package/dist/esm/components/chart-legend.js +1 -3
  171. package/dist/esm/components/chart-legend.js.map +1 -1
  172. package/dist/esm/components/data-grid/data-grid-sizing.d.ts +11 -0
  173. package/dist/esm/components/data-grid/data-grid-sizing.d.ts.map +1 -0
  174. package/dist/esm/components/data-grid/data-grid-sizing.js +29 -0
  175. package/dist/esm/components/data-grid/data-grid-sizing.js.map +1 -0
  176. package/dist/esm/components/data-grid/data-grid-toolbar.d.ts +31 -0
  177. package/dist/esm/components/data-grid/data-grid-toolbar.d.ts.map +1 -0
  178. package/dist/esm/components/data-grid/data-grid-toolbar.js +223 -0
  179. package/dist/esm/components/data-grid/data-grid-toolbar.js.map +1 -0
  180. package/dist/esm/components/data-grid/data-grid.d.ts +233 -0
  181. package/dist/esm/components/data-grid/data-grid.d.ts.map +1 -0
  182. package/dist/esm/components/data-grid/data-grid.js +868 -0
  183. package/dist/esm/components/data-grid/data-grid.js.map +1 -0
  184. package/dist/esm/components/data-grid/index.d.ts +7 -0
  185. package/dist/esm/components/data-grid/index.js +7 -0
  186. package/dist/esm/components/data-grid/state.d.ts +91 -0
  187. package/dist/esm/components/data-grid/state.d.ts.map +1 -0
  188. package/dist/esm/components/data-grid/state.js +305 -0
  189. package/dist/esm/components/data-grid/state.js.map +1 -0
  190. package/dist/esm/components/data-grid/strings.d.ts +8 -0
  191. package/dist/esm/components/data-grid/strings.d.ts.map +1 -0
  192. package/dist/esm/components/data-grid/strings.js +39 -0
  193. package/dist/esm/components/data-grid/strings.js.map +1 -0
  194. package/dist/esm/components/data-grid/types.d.ts +242 -0
  195. package/dist/esm/components/data-grid/types.d.ts.map +1 -0
  196. package/dist/esm/components/data-grid/types.js +1 -0
  197. package/dist/esm/components/data-grid/use-data-source.d.ts +79 -0
  198. package/dist/esm/components/data-grid/use-data-source.d.ts.map +1 -0
  199. package/dist/esm/components/data-grid/use-data-source.js +234 -0
  200. package/dist/esm/components/data-grid/use-data-source.js.map +1 -0
  201. package/dist/esm/components/empty-state.d.ts +16 -0
  202. package/dist/esm/components/empty-state.d.ts.map +1 -1
  203. package/dist/esm/components/empty-state.js +16 -0
  204. package/dist/esm/components/empty-state.js.map +1 -1
  205. package/dist/esm/components/metric-card.d.ts +24 -0
  206. package/dist/esm/components/metric-card.d.ts.map +1 -1
  207. package/dist/esm/components/metric-card.js +24 -0
  208. package/dist/esm/components/metric-card.js.map +1 -1
  209. package/dist/esm/components/progress-bar.d.ts +10 -0
  210. package/dist/esm/components/progress-bar.d.ts.map +1 -1
  211. package/dist/esm/components/progress-bar.js +10 -0
  212. package/dist/esm/components/progress-bar.js.map +1 -1
  213. package/dist/esm/components/separator.d.ts +9 -0
  214. package/dist/esm/components/separator.d.ts.map +1 -1
  215. package/dist/esm/components/separator.js +9 -0
  216. package/dist/esm/components/separator.js.map +1 -1
  217. package/dist/esm/components/skeleton.d.ts +12 -0
  218. package/dist/esm/components/skeleton.d.ts.map +1 -1
  219. package/dist/esm/components/skeleton.js +12 -0
  220. package/dist/esm/components/skeleton.js.map +1 -1
  221. package/dist/esm/components/table.d.ts +25 -0
  222. package/dist/esm/components/table.d.ts.map +1 -1
  223. package/dist/esm/components/table.js +25 -0
  224. package/dist/esm/components/table.js.map +1 -1
  225. package/dist/esm/index.d.ts +4 -2
  226. package/dist/esm/index.js +6 -2
  227. package/dist/index.d.ts +15 -2
  228. package/dist/index.js +16 -7
  229. package/package.json +4 -3
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-grid.js","names":[],"sources":["../../../../src/components/data-grid/data-grid.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n ArrowDown,\n ArrowUp,\n CaretDown,\n CaretUp,\n CheckSquare,\n MinusSquare,\n Square,\n} from \"@phosphor-icons/react\";\nimport { cn } from \"@stackframe/stack-ui\";\nimport { useVirtualizer, type VirtualItem } from \"@tanstack/react-virtual\";\nimport React, {\n useCallback,\n useEffect,\n useLayoutEffect,\n useMemo,\n useRef,\n} from \"react\";\n\nimport { DesignSkeleton } from \"../skeleton\";\nimport { DataGridToolbar } from \"./data-grid-toolbar\";\nimport {\n applyDraggedColumnWidth,\n clampColumnWidth,\n createGridSizingStyle,\n getColumnSizingStyle,\n} from \"./data-grid-sizing\";\nimport {\n clearSelection,\n exportToCsv,\n formatGridDate,\n getSortDirection,\n getSortIndex,\n isColumnVisible,\n resolveColumnValue,\n resolveColumnWidth,\n selectAll,\n toggleRowSelection,\n toggleSort,\n} from \"./state\";\nimport { resolveDataGridStrings } from \"./strings\";\nimport type {\n DataGridCellContext,\n DataGridColumnDef,\n DataGridDateDisplay,\n DataGridFooterContext,\n DataGridHeaderContext,\n DataGridPaginationMode,\n DataGridProps,\n DataGridState,\n DataGridStrings,\n DataGridToolbarContext,\n RowId\n} from \"./types\";\n// ─── Resize handle ───────────────────────────────────────────────────\n\nfunction ResizeHandle({\n onResize,\n onResizeEnd,\n}: {\n onResize: (delta: number) => void;\n onResizeEnd: () => void;\n}) {\n const startXRef = useRef(0);\n const rafRef = useRef(0);\n const latestDeltaRef = useRef(0);\n const callbacksRef = useRef({ onResize, onResizeEnd });\n\n callbacksRef.current = { onResize, onResizeEnd };\n\n const onPointerDown = useCallback(\n (e: React.PointerEvent) => {\n e.preventDefault();\n e.stopPropagation();\n startXRef.current = e.clientX;\n latestDeltaRef.current = 0;\n const el = e.currentTarget as HTMLElement;\n el.setPointerCapture(e.pointerId);\n let finished = false;\n\n const onMove = (ev: PointerEvent) => {\n latestDeltaRef.current = ev.clientX - startXRef.current;\n if (rafRef.current !== 0) {\n return;\n }\n\n rafRef.current = requestAnimationFrame(() => {\n rafRef.current = 0;\n callbacksRef.current.onResize(latestDeltaRef.current);\n });\n };\n const finish = () => {\n if (finished) return;\n finished = true;\n if (rafRef.current !== 0) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = 0;\n callbacksRef.current.onResize(latestDeltaRef.current);\n }\n el.removeEventListener(\"pointermove\", onMove);\n el.removeEventListener(\"pointerup\", finish);\n el.removeEventListener(\"pointercancel\", finish);\n el.removeEventListener(\"lostpointercapture\", finish);\n if (el.hasPointerCapture(e.pointerId)) {\n el.releasePointerCapture(e.pointerId);\n }\n callbacksRef.current.onResizeEnd();\n };\n\n el.addEventListener(\"pointermove\", onMove);\n el.addEventListener(\"pointerup\", finish);\n el.addEventListener(\"pointercancel\", finish);\n el.addEventListener(\"lostpointercapture\", finish);\n },\n [],\n );\n\n return (\n <div\n className={cn(\n \"absolute right-0 top-0 bottom-0 z-10 w-[5px] cursor-col-resize touch-none\",\n \"group-hover/header:bg-foreground/[0.06] hover:!bg-blue-500/30\",\n \"transition-colors duration-100\",\n )}\n onClick={(e) => {\n e.preventDefault();\n e.stopPropagation();\n }}\n onPointerDown={onPointerDown}\n />\n );\n}\n\n// ─── Header cell ─────────────────────────────────────────────────────\n\nfunction HeaderCell<TRow>({\n col,\n isSorted,\n sortIndex,\n resizable,\n onSort,\n onResize,\n onResizeEnd,\n}: {\n col: DataGridColumnDef<TRow>;\n isSorted: false | \"asc\" | \"desc\";\n sortIndex: number | null;\n resizable: boolean;\n onSort: (columnId: string, multi: boolean) => void;\n onResize: (columnId: string, delta: number) => void;\n onResizeEnd: () => void;\n}) {\n const ctx: DataGridHeaderContext<TRow> = {\n columnId: col.id,\n columnDef: col,\n isSorted,\n sortIndex,\n };\n const label =\n typeof col.header === \"function\" ? col.header(ctx) : col.header;\n\n const sortable = col.sortable !== false;\n\n return (\n <div\n className={cn(\n \"group/header relative flex items-center gap-1.5 px-3 select-none bg-transparent\",\n \"border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0\",\n sortable && \"cursor-pointer\",\n )}\n style={getColumnSizingStyle(col)}\n data-col-id={col.id}\n onClick={(e) => sortable && onSort(col.id, e.metaKey || e.ctrlKey)}\n role=\"columnheader\"\n aria-sort={isSorted === \"asc\" ? \"ascending\" : isSorted === \"desc\" ? \"descending\" : \"none\"}\n >\n <span\n className={cn(\n \"flex-1 truncate text-xs font-semibold uppercase tracking-wider text-muted-foreground\",\n col.align === \"center\" && \"text-center\",\n col.align === \"right\" && \"text-right\",\n )}\n >\n {label}\n </span>\n\n {/* Sort indicator */}\n {isSorted && (\n <span className=\"flex items-center gap-0.5 text-foreground/60\">\n {isSorted === \"asc\" ? (\n <ArrowUp className=\"h-3 w-3\" weight=\"bold\" />\n ) : (\n <ArrowDown className=\"h-3 w-3\" weight=\"bold\" />\n )}\n {sortIndex != null && (\n <span className=\"text-[10px] font-medium tabular-nums\">{sortIndex}</span>\n )}\n </span>\n )}\n\n {/* Unsorted hint on hover */}\n {!isSorted && sortable && (\n <span className=\"hidden group-hover/header:flex items-center text-foreground/20\">\n <CaretUp className=\"h-2.5 w-2.5 -mb-[1px]\" weight=\"bold\" />\n <CaretDown className=\"h-2.5 w-2.5 -mt-[1px]\" weight=\"bold\" />\n </span>\n )}\n\n {/* Resize handle */}\n {resizable && col.resizable !== false && (\n <ResizeHandle\n onResize={(delta) => onResize(col.id, delta)}\n onResizeEnd={onResizeEnd}\n />\n )}\n </div>\n );\n}\n\n// ─── Data cell ───────────────────────────────────────────────────────\n\nfunction DataCell<TRow>({\n col,\n row,\n rowId,\n rowIndex,\n isSelected,\n dateDisplay,\n}: {\n col: DataGridColumnDef<TRow>;\n row: TRow;\n rowId: RowId;\n rowIndex: number;\n isSelected: boolean;\n dateDisplay: DataGridDateDisplay;\n}) {\n const value = resolveColumnValue(col, row);\n const ctx: DataGridCellContext<TRow> = {\n row,\n rowId,\n rowIndex,\n value,\n columnId: col.id,\n isSelected,\n dateDisplay,\n };\n\n const isDateCol = col.type === \"date\" || col.type === \"dateTime\";\n let content: React.ReactNode;\n if (col.renderCell) {\n content = col.renderCell(ctx);\n } else if (isDateCol) {\n content = renderDateCell(value, dateDisplay, col);\n } else {\n content = formatCellValue(value);\n }\n const hasCellClick = col.onCellClick || col.onCellDoubleClick;\n\n return (\n <div\n className={cn(\n \"flex items-center px-3 truncate bg-transparent\",\n \"border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0\",\n \"text-sm text-foreground\",\n col.align === \"center\" && \"justify-center\",\n col.align === \"right\" && \"justify-end\",\n hasCellClick && \"cursor-pointer\",\n )}\n style={getColumnSizingStyle(col)}\n data-col-id={col.id}\n role=\"gridcell\"\n onClick={col.onCellClick ? (e) => {\n e.stopPropagation();\n col.onCellClick!(ctx, e);\n } : undefined}\n onDoubleClick={col.onCellDoubleClick ? (e) => {\n e.stopPropagation();\n col.onCellDoubleClick!(ctx, e);\n } : undefined}\n >\n {content}\n </div>\n );\n}\n\nfunction formatCellValue(value: unknown): React.ReactNode {\n if (value == null) return <span className=\"text-muted-foreground/40\">-</span>;\n if (typeof value === \"boolean\") {\n return (\n <span\n className={cn(\n \"inline-flex items-center px-1.5 py-0.5 rounded-md text-xs font-medium\",\n value\n ? \"bg-emerald-500/10 text-emerald-600 dark:text-emerald-400\"\n : \"bg-foreground/[0.04] text-muted-foreground\",\n )}\n >\n {value ? \"Yes\" : \"No\"}\n </span>\n );\n }\n if (value instanceof Date) {\n return (\n <span className=\"tabular-nums text-muted-foreground\">\n {value.toLocaleDateString()}\n </span>\n );\n }\n return <span className=\"truncate\">{String(value)}</span>;\n}\n\n/** Built-in date cell — mirrors what `formatGridDate` returns but wraps\n * the display in a `<span>` with a `title` tooltip showing the absolute\n * datetime. Only used when the column has `type: \"date\" | \"dateTime\"`\n * and no custom `renderCell`. */\nfunction renderDateCell<TRow>(\n value: unknown,\n dateDisplay: DataGridDateDisplay,\n col: DataGridColumnDef<TRow>,\n): React.ReactNode {\n const { display, tooltip } = formatGridDate(value, dateDisplay, {\n parseValue: col.parseValue,\n dateFormat: col.dateFormat,\n });\n if (display == null) return <span className=\"text-muted-foreground/40\">-</span>;\n return (\n <span\n className=\"tabular-nums text-muted-foreground truncate cursor-help\"\n title={tooltip ?? undefined}\n >\n {display}\n </span>\n );\n}\n\n// ─── Skeleton row ────────────────────────────────────────────────────\n\nfunction SkeletonRow({\n columns,\n height,\n showCheckbox,\n}: {\n columns: readonly DataGridColumnDef<any>[];\n height: number;\n showCheckbox?: boolean;\n}) {\n return (\n <div className=\"flex\" style={{ height }} role=\"row\">\n {showCheckbox && (\n <div\n className=\"flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]\"\n style={{ width: 44 }}\n >\n <DesignSkeleton className=\"h-4 w-4 rounded\" />\n </div>\n )}\n {columns.map((col) => (\n <div\n key={col.id}\n className=\"flex items-center px-3 border-r border-black/[0.04] dark:border-white/[0.04] last:border-r-0\"\n style={getColumnSizingStyle(col)}\n >\n <DesignSkeleton\n className=\"h-3.5 rounded-md\"\n style={{ width: `${40 + Math.random() * 40}%` }}\n />\n </div>\n ))}\n </div>\n );\n}\n\n// ─── Checkbox cell ───────────────────────────────────────────────────\n\nfunction SelectionCheckbox({\n checked,\n indeterminate,\n onChange,\n ariaLabel,\n}: {\n checked: boolean;\n indeterminate?: boolean;\n onChange: (event: React.MouseEvent<HTMLButtonElement>) => void;\n ariaLabel: string;\n}) {\n const Icon = indeterminate ? MinusSquare : checked ? CheckSquare : Square;\n return (\n <button\n className={cn(\n \"flex items-center justify-center w-full h-full\",\n \"hover:bg-foreground/[0.04] transition-colors duration-75\",\n checked || indeterminate\n ? \"text-blue-600 dark:text-blue-400\"\n : \"text-muted-foreground/40 hover:text-muted-foreground/60\",\n )}\n onClick={(e) => {\n e.stopPropagation();\n onChange(e);\n }}\n aria-label={ariaLabel}\n role=\"checkbox\"\n aria-checked={indeterminate ? \"mixed\" : checked}\n >\n <Icon className=\"h-4 w-4\" weight={checked || indeterminate ? \"fill\" : \"regular\"} />\n </button>\n );\n}\n\n// ─── Infinite scroll sentinel ────────────────────────────────────────\n\nfunction InfiniteScrollSentinel({\n onIntersect,\n isLoading,\n strings,\n}: {\n onIntersect: () => void;\n isLoading: boolean;\n strings: DataGridStrings;\n}) {\n const ref = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const el = ref.current;\n if (!el) return;\n\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0]?.isIntersecting) {\n onIntersect();\n }\n },\n { rootMargin: \"200px\" },\n );\n observer.observe(el);\n return () => observer.disconnect();\n }, [onIntersect]);\n\n return (\n <div ref={ref} className=\"flex items-center justify-center py-4\">\n {isLoading && (\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <div className=\"h-3 w-3 rounded-full border-2 border-current border-t-transparent animate-spin\" />\n {strings.loadingMore}\n </div>\n )}\n </div>\n );\n}\n\n// ─── Footer ──────────────────────────────────────────────────────────\n\nfunction DefaultFooter<TRow>({\n ctx,\n pagination,\n onChange,\n}: {\n ctx: DataGridFooterContext<TRow>;\n pagination: DataGridPaginationMode;\n onChange: React.Dispatch<React.SetStateAction<DataGridState>>;\n}) {\n const { state, totalRowCount, visibleRowCount, selectedRowCount, strings } = ctx;\n const totalPages = totalRowCount != null\n ? Math.max(1, Math.ceil(totalRowCount / state.pagination.pageSize))\n : undefined;\n\n const setPage = (pageIndex: number) =>\n onChange((s) => ({\n ...s,\n pagination: { ...s.pagination, pageIndex },\n }));\n\n const setPageSize = (pageSize: number) =>\n onChange((s) => ({\n ...s,\n pagination: { ...s.pagination, pageSize, pageIndex: 0 },\n }));\n\n return (\n <div className=\"flex items-center justify-between px-4 py-2.5 border-t border-foreground/[0.06] text-xs text-muted-foreground\">\n <div className=\"flex items-center gap-3\">\n {selectedRowCount > 0 && (\n <span className=\"font-medium text-foreground\">\n {strings.rowsSelected(selectedRowCount)}\n </span>\n )}\n {totalRowCount != null && (\n <span>\n {visibleRowCount} of {totalRowCount} rows\n </span>\n )}\n </div>\n\n {pagination !== \"infinite\" && totalPages != null && (\n <div className=\"flex items-center gap-3\">\n {/* Page size selector */}\n <div className=\"flex items-center gap-1.5\">\n <span>{strings.rowsPerPage}</span>\n <select\n className={cn(\n \"h-7 rounded-lg border border-black/[0.08] dark:border-white/[0.08] bg-background px-1.5\",\n \"text-xs text-foreground focus:outline-none focus:ring-1 focus:ring-foreground/[0.1]\",\n \"cursor-pointer\",\n )}\n value={state.pagination.pageSize}\n onChange={(e) => setPageSize(Number(e.target.value))}\n >\n {[10, 25, 50, 100].map((size) => (\n <option key={size} value={size}>\n {size}\n </option>\n ))}\n </select>\n </div>\n\n {/* Page navigation */}\n <div className=\"flex items-center gap-1\">\n <button\n className={cn(\n \"h-7 w-7 flex items-center justify-center rounded-lg\",\n \"hover:bg-foreground/[0.04] disabled:opacity-30 disabled:cursor-not-allowed\",\n \"transition-colors duration-75\",\n )}\n onClick={() => setPage(state.pagination.pageIndex - 1)}\n disabled={state.pagination.pageIndex === 0}\n aria-label=\"Previous page\"\n >\n <CaretUp className=\"h-3.5 w-3.5 -rotate-90\" weight=\"bold\" />\n </button>\n <span className=\"px-2 tabular-nums font-medium\">\n {strings.pageOf(state.pagination.pageIndex + 1, totalPages)}\n </span>\n <button\n className={cn(\n \"h-7 w-7 flex items-center justify-center rounded-lg\",\n \"hover:bg-foreground/[0.04] disabled:opacity-30 disabled:cursor-not-allowed\",\n \"transition-colors duration-75\",\n )}\n onClick={() => setPage(state.pagination.pageIndex + 1)}\n disabled={state.pagination.pageIndex >= totalPages - 1}\n aria-label=\"Next page\"\n >\n <CaretDown className=\"h-3.5 w-3.5 -rotate-90\" weight=\"bold\" />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n\n// ─── Main DataGrid ───────────────────────────────────────────────────\n\n/**\n * Interactive table with sorting, quick search, pagination, selection,\n * and virtualization. Handles 10k+ rows smoothly. Pair with\n * `useDataSource` for client-side data; use an async `dataSource`\n * generator for server or infinite-scroll modes.\n *\n * ## Mental model (read this first — everything else depends on it)\n *\n * DataGrid is a **display** component. It does NOT sort, search, or\n * paginate your data directly — you own that, but `useDataSource` does\n * it for you. The `rows` prop is always the already-processed slice to\n * show. The grid tracks user intent in `state` (sort model, quick\n * search text, page index). You feed that state into `useDataSource`,\n * and its output goes back in as `rows`.\n *\n * `useDataSource` IS the processor. Given your full dataset and the\n * grid's state, it returns the searched + sorted + paginated rows\n * ready to pass to DataGrid. This is the ONLY correct pattern for\n * client-side data — do NOT pass a raw array to `rows`.\n *\n * ## Search (client vs async)\n *\n * - **Client mode** (`useDataSource` with `data`): a case-insensitive\n * substring match across every column is applied automatically.\n * Override the matcher with `matchRow` for fuzzy / weighted search,\n * or disable by passing `matchRow: () => true`.\n * - **Async mode** (`useDataSource` with `dataSource`): `state.quickSearch`\n * is forwarded to the generator as `params.quickSearch`. Same\n * mechanism as `params.sorting` — a change triggers a refetch, and\n * the generator is the \"matching logic\" (typically a WHERE / ILIKE\n * clause in the backend query). The grid does NO client-side\n * filtering in async mode.\n *\n * ## The canonical pattern\n *\n * ```tsx\n * // 1. Columns — define OUTSIDE the component or inside a useMemo. Must be stable.\n * const columns = React.useMemo(() => [\n * { id: \"name\", header: \"Name\", accessor: \"name\", width: 180, type: \"string\" },\n * { id: \"email\", header: \"Email\", accessor: \"email\", width: 240, type: \"string\" },\n * { id: \"role\", header: \"Role\", accessor: \"role\", width: 120, type: \"singleSelect\",\n * valueOptions: [{ value: \"admin\", label: \"Admin\" }, { value: \"member\", label: \"Member\" }] },\n * { id: \"signUps\", header: \"Sign-ups\", accessor: \"signUps\", width: 120, type: \"number\", align: \"right\",\n * renderCell: ({ value }) => <span className=\"tabular-nums\">{Number(value).toLocaleString()}</span> },\n * ], []);\n *\n * // 2. Grid state — one hook, initialized from the columns. NEVER build the state object by hand.\n * const [gridState, setGridState] = React.useState(() => createDefaultDataGridState(columns));\n *\n * // 3. Data source — wires your raw array through the grid state. ALWAYS call this\n * // hook unconditionally at the top level (no if/return before it).\n * const gridData = useDataSource({\n * data: users, // your raw array (can be [] while loading)\n * columns,\n * getRowId: (row) => row.id,\n * sorting: gridState.sorting,\n * quickSearch: gridState.quickSearch,\n * pagination: gridState.pagination,\n * paginationMode: \"client\", // \"client\" | \"server\" | \"infinite\"\n * });\n *\n * // 4. Render — `rows` comes from gridData.rows, NOT from your raw array.\n * <DataGrid\n * columns={columns}\n * rows={gridData.rows}\n * getRowId={(row) => row.id}\n * totalRowCount={gridData.totalRowCount}\n * isLoading={gridData.isLoading}\n * state={gridState}\n * onChange={setGridState}\n * selectionMode=\"none\" // \"none\" | \"single\" | \"multiple\"\n * maxHeight={480}\n * />\n * ```\n *\n * ## Iron rules (violating any of these breaks the grid)\n *\n * 1. The prop is `rows`, NOT `data`. There is no `data` prop on DataGrid.\n * `data` belongs on `useDataSource`.\n * 2. `rows` is ALWAYS `gridData.rows`. Never pass your raw array to\n * `rows` — the grid won't search, sort, or paginate it.\n * 3. Columns must be stable across renders. Define them outside the\n * component or wrap in `React.useMemo`. A fresh columns array every\n * render will reset sorting state.\n * 4. Initialize grid state with `createDefaultDataGridState(columns)`.\n * Do NOT spell out the state object manually — you will miss fields\n * and crash.\n * 5. `onChange` takes a `SetStateAction` (the setter you got from\n * `useState`). Pass `setGridState` directly. Do NOT wrap it unless\n * you know exactly what you're doing.\n * 6. Call `useDataSource` ONCE per grid, at the top level, before any\n * early return. It contains hooks.\n * 7. `renderCell` is a PURE function of its context. NEVER call React\n * hooks inside it (no `useState`, `useMemo`, `useEffect`, nothing).\n * If you need derived data per row, compute it BEFORE the render —\n * e.g. build a `Map<rowId, sparklineData>` in a `useMemo` and look\n * it up in `renderCell`.\n * 8. `toolbar` accepts `false` (hide it) or a render function\n * `(ctx) => ReactNode`. Anything else — `true`, `undefined`, a state\n * variable — will either show the default toolbar or crash. If you\n * just want the default toolbar, omit the prop entirely.\n * 9. The toolbar's search input writes to `state.quickSearch`. That\n * value is consumed by `useDataSource` — client mode filters\n * client-side, async mode forwards to the generator. Do NOT wire\n * a separate \"controlled\" search prop, everything flows through\n * grid state.\n *\n * ## renderCell — what you can and cannot do inside it\n *\n * ```tsx\n * // OK — pure rendering from ctx:\n * renderCell: ({ value }) => <span className=\"tabular-nums\">{Number(value).toLocaleString()}</span>\n * renderCell: ({ row }) => <Badge variant={row.active ? \"default\" : \"outline\"}>{row.status}</Badge>\n *\n * // OK — looking up pre-computed data by row id:\n * // BEFORE the return, in the component body:\n * const sparklinesById = React.useMemo(() => {\n * const m = new Map();\n * for (const u of users) {\n * m.set(u.id, u.recentActivity.map((n, i) => ({ ts: i, values: { primary: n } })));\n * }\n * return m;\n * }, [users]);\n * // Then inside the column def:\n * renderCell: ({ rowId }) => <MiniSparkline data={sparklinesById.get(rowId) ?? []} />\n *\n * // NOT OK — hooks inside renderCell:\n * renderCell: ({ row }) => {\n * const [hovered, setHovered] = React.useState(false); // ← crashes the grid\n * const data = React.useMemo(() => ..., []); // ← crashes the grid\n * return ...;\n * }\n *\n * // NOT OK — embedding AnalyticsChart (or any other controlled, stateful chart) per row:\n * // AnalyticsChart owns its own state, tooltips, zoom, and virtualized data\n * // pipeline. Instantiating one per row is expensive and fights the grid's\n * // virtualizer. Don't do it.\n * ```\n *\n * ## Sparklines and mini-charts in cells — use raw Recharts\n *\n * If you want a tiny chart (sparkline, micro bar chart, trend line) inside\n * a cell, drop down to raw `Recharts.*` components — they are lightweight\n * and stateless, so they render cleanly per row without owning any state.\n * Read pre-computed points off the row (or off a `Map<rowId, points>` you\n * built in a `useMemo` above) and pass them directly to the Recharts\n * primitive. Do NOT wrap them in `DesignChartContainer` or\n * `DesignChartCard` inside a cell — those add chrome meant for full-size\n * charts.\n *\n * ```tsx\n * // OK — raw Recharts sparkline per row:\n * renderCell: ({ rowId }) => {\n * const points = sparklinesById.get(rowId) ?? [];\n * return (\n * <Recharts.ResponsiveContainer width=\"100%\" height={28}>\n * <Recharts.LineChart data={points} margin={{ top: 2, right: 2, bottom: 2, left: 2 }}>\n * <Recharts.Line type=\"monotone\" dataKey=\"v\" stroke=\"currentColor\" strokeWidth={1.5} dot={false} isAnimationActive={false} />\n * </Recharts.LineChart>\n * </Recharts.ResponsiveContainer>\n * );\n * }\n * ```\n *\n * Keep in-cell Recharts configs minimal: no axes, no tooltips, no animation\n * (`isAnimationActive={false}`), tight margins, fixed height. The goal is a\n * visual summary, not an interactive chart.\n *\n * ## State shape (from `createDefaultDataGridState`)\n *\n * ```ts\n * {\n * sorting: [], // { columnId, direction: \"asc\" | \"desc\" }[]\n * quickSearch: \"\", // search input text\n * dateDisplay: \"relative\", // \"relative\" | \"absolute\"\n * columnVisibility: {}, columnWidths: {...},\n * columnPinning: { left: [], right: [] }, columnOrder: [...],\n * pagination: { pageIndex: 0, pageSize: 50 },\n * selection: { selectedIds: new Set(), anchorId: null },\n * }\n * ```\n *\n * Everything is updated through `setGridState` — the toolbar, header,\n * and footer all call it for you. You do not need to wire any of this\n * manually.\n *\n * ## Height and scrolling\n *\n * DataGrid is NOT a card. It has no border, rounded corners, or shadow of\n * its own. Wrap it in whatever chrome you want — a `DesignCard`, a section,\n * or just raw layout. The grid itself fills its parent's height via\n * `h-full`.\n *\n * How the grid gets its height (pick ONE):\n * 1. Bounded parent — put the grid inside a flex/grid container with a\n * definite height (e.g. `flex-1 min-h-0` inside a page-filling flex\n * column). The grid stretches to that height and scrolls its body.\n * 2. `maxHeight` prop — pass a number (pixels) or CSS string\n * (`\"480px\"`, `\"60vh\"`, `\"100%\"`). The grid caps at that size and\n * scrolls its body.\n * 3. Unbounded — omit `maxHeight` and let the parent grow freely. The\n * grid renders at its full content height and the page scrolls. Fine\n * for small lists; bad UX for thousands of rows.\n *\n * The toolbar, header, and footer are always `shrink-0`; only the body\n * scrolls. You do NOT need to subtract toolbar/footer heights from\n * `maxHeight` — the grid's internal flex layout handles that.\n *\n * ## When to use what\n *\n * - Simple static list, < 20 rows, no interaction → use a plain table component instead.\n * - Interactive table, sortable + searchable, any size → `DataGrid` +\n * `useDataSource` with `paginationMode: \"client\"`.\n * - Infinite scroll over a huge dataset you fetch in pages → `dataSource` async\n * generator + `paginationMode: \"infinite\"`. Only reach for this if you actually\n * need pagination over a remote source. For anything that fits in memory,\n * `\"client\"` is simpler and faster.\n *\n * ## Features you get for free\n *\n * Quick search, sortable columns (shift-click for multi-sort), column\n * visibility toggle, column resize, CSV export, virtualized rendering\n * for 10k+ rows, keyboard navigation, and a relative/absolute date\n * toggle for `date` / `dateTime` columns.\n */\nexport function DataGrid<TRow>(props: DataGridProps<TRow>) {\n const {\n columns: allColumns,\n rows,\n getRowId,\n totalRowCount,\n isLoading = false,\n isRefetching = false,\n hasMore = false,\n isLoadingMore = false,\n onLoadMore,\n state,\n onChange,\n paginationMode = \"paginated\",\n selectionMode = \"none\",\n resizable = true,\n rowHeight = 44,\n headerHeight = 44,\n overscan = 5,\n maxHeight,\n toolbar,\n toolbarExtra,\n emptyState,\n loadingState,\n footer,\n footerExtra,\n exportFilename = \"export\",\n strings: stringsOverride,\n className,\n // Callbacks\n onRowClick,\n onRowDoubleClick,\n onSelectionChange,\n onSortChange,\n } = props;\n\n const strings = useMemo(\n () => resolveDataGridStrings(stringsOverride),\n [stringsOverride],\n );\n\n // ── Visible columns ──────────────────────────────────────────\n const visibleColumns = useMemo(\n () =>\n (state.columnOrder.length > 0\n ? state.columnOrder\n .map((id) => allColumns.find((c) => c.id === id))\n .filter(Boolean) as DataGridColumnDef<TRow>[]\n : allColumns\n ).filter((col) => isColumnVisible(col.id, state.columnVisibility)),\n [allColumns, state.columnOrder, state.columnVisibility],\n );\n\n // ── Row IDs (stable) ─────────────────────────────────────────\n const rowIds = useMemo(() => rows.map(getRowId), [rows, getRowId]);\n\n // ── Column widths ────────────────────────────────────────────\n const visibleColumnMetrics = useMemo(() => {\n const widths = new Map<string, number>();\n let totalWidth = selectionMode !== \"none\" ? 44 : 0;\n\n for (const col of visibleColumns) {\n const width = resolveColumnWidth(col, state.columnWidths[col.id]);\n widths.set(col.id, width);\n totalWidth += width;\n }\n\n return { widths, totalWidth };\n }, [selectionMode, state.columnWidths, visibleColumns]);\n\n const gridSizingStyle = useMemo(\n () => createGridSizingStyle(visibleColumnMetrics.widths, visibleColumnMetrics.totalWidth),\n [visibleColumnMetrics],\n );\n\n // Resize drag tracked via ref — zero React re-renders during drag.\n // CSS variables on gridRef are mutated directly; committed on pointer up.\n const resizeRef = useRef<{ columnId: string; baseWidth: number; baseTotalWidth: number; latestWidth: number } | null>(null);\n const gridRef = useRef<HTMLDivElement>(null);\n\n // ── Handlers ─────────────────────────────────────────────────\n const handleSort = useCallback(\n (columnId: string, multi: boolean) => {\n onChange((s) => {\n const next = toggleSort(s.sorting, columnId, multi);\n onSortChange?.(next);\n return { ...s, sorting: next };\n });\n },\n [onChange, onSortChange],\n );\n\n const handleResize = useCallback(\n (columnId: string, delta: number) => {\n const col = allColumns.find((c) => c.id === columnId);\n if (!col) return;\n if (!resizeRef.current || resizeRef.current.columnId !== columnId) {\n const baseWidth = visibleColumnMetrics.widths.get(columnId) ?? resolveColumnWidth(col, state.columnWidths[columnId]);\n resizeRef.current = { columnId, baseWidth, baseTotalWidth: visibleColumnMetrics.totalWidth, latestWidth: baseWidth };\n }\n const newWidth = clampColumnWidth(col, resizeRef.current.baseWidth + delta);\n resizeRef.current.latestWidth = newWidth;\n if (gridRef.current) {\n applyDraggedColumnWidth(gridRef.current, columnId, newWidth, resizeRef.current.baseTotalWidth + (newWidth - resizeRef.current.baseWidth));\n }\n },\n [allColumns, state.columnWidths, visibleColumnMetrics],\n );\n\n // Re-apply CSS vars after React re-renders (e.g. sort during drag)\n useLayoutEffect(() => {\n const r = resizeRef.current;\n if (r && gridRef.current) {\n applyDraggedColumnWidth(gridRef.current, r.columnId, r.latestWidth, r.baseTotalWidth + (r.latestWidth - r.baseWidth));\n }\n }, [gridSizingStyle]);\n\n const handleResizeEnd = useCallback(() => {\n const r = resizeRef.current;\n resizeRef.current = null;\n if (!r || r.latestWidth === r.baseWidth) return;\n onChange((s) => ({ ...s, columnWidths: { ...s.columnWidths, [r.columnId]: r.latestWidth } }));\n }, [onChange]);\n\n const handleRowClick = useCallback(\n (row: TRow, rowId: RowId, event: React.MouseEvent) => {\n // Selection\n if (selectionMode !== \"none\") {\n onChange((s) => {\n const next = toggleRowSelection(\n s.selection,\n rowId,\n selectionMode,\n event.shiftKey,\n event.metaKey || event.ctrlKey,\n rowIds,\n );\n // Fire callback after state update\n if (onSelectionChange) {\n const selectedRows = rows.filter((r) =>\n next.selectedIds.has(getRowId(r)),\n );\n setTimeout(() => onSelectionChange(next.selectedIds, selectedRows), 0);\n }\n return { ...s, selection: next };\n });\n }\n\n onRowClick?.(row, rowId, event);\n },\n [selectionMode, onChange, onRowClick, onSelectionChange, rowIds, rows, getRowId],\n );\n\n const handleRowSelectionCheckboxClick = useCallback(\n (\n row: TRow,\n rowId: RowId,\n event: React.MouseEvent<HTMLButtonElement>,\n ) => {\n handleRowClick(row, rowId, event);\n },\n [handleRowClick],\n );\n\n const handleSelectAll = useCallback(() => {\n onChange((s) => {\n const allSelected = rowIds.every((id) => s.selection.selectedIds.has(id));\n const next = allSelected ? clearSelection() : selectAll(rowIds);\n if (onSelectionChange) {\n const selectedRows = allSelected\n ? []\n : rows;\n setTimeout(() => onSelectionChange(next.selectedIds, [...selectedRows]), 0);\n }\n return { ...s, selection: next };\n });\n }, [onChange, rowIds, rows, onSelectionChange]);\n\n const handleExportCsv = useCallback(() => {\n exportToCsv(rows, visibleColumns, exportFilename);\n }, [rows, visibleColumns, exportFilename]);\n\n // ── Virtualizer ──────────────────────────────────────────────\n const scrollContainerRef = useRef<HTMLDivElement>(null);\n const headerScrollRef = useRef<HTMLDivElement>(null);\n const rowVirtualizer = useVirtualizer({\n count: rows.length,\n getScrollElement: () => scrollContainerRef.current,\n estimateSize: () => rowHeight,\n overscan,\n });\n\n // Sync horizontal scroll from body to header\n const handleBodyScroll = useCallback(() => {\n const body = scrollContainerRef.current;\n const header = headerScrollRef.current;\n if (body && header) {\n header.scrollLeft = body.scrollLeft;\n }\n }, []);\n\n // ── Toolbar context ──────────────────────────────────────────\n const toolbarCtx: DataGridToolbarContext<TRow> = useMemo(\n () => ({\n state,\n onChange,\n columns: allColumns,\n visibleColumns,\n totalRowCount,\n selectedRowCount: state.selection.selectedIds.size,\n strings,\n exportCsv: handleExportCsv,\n }),\n [state, onChange, allColumns, visibleColumns, totalRowCount, strings, handleExportCsv],\n );\n\n // ── Footer context ───────────────────────────────────────────\n const footerCtx: DataGridFooterContext<TRow> = useMemo(\n () => ({\n state,\n totalRowCount,\n visibleRowCount: rows.length,\n selectedRowCount: state.selection.selectedIds.size,\n paginationMode,\n strings,\n }),\n [state, totalRowCount, rows.length, paginationMode, strings],\n );\n\n // ── Selection state for header checkbox ──────────────────────\n const allSelected = rowIds.length > 0 && rowIds.every((id) => state.selection.selectedIds.has(id));\n const someSelected = !allSelected && rowIds.some((id) => state.selection.selectedIds.has(id));\n\n // ── Render ───────────────────────────────────────────────────\n //\n // Height model:\n // - Root is `flex flex-col h-full min-h-0 bg-transparent`. `h-full`\n // makes the grid fill a bounded parent; in an unbounded parent it\n // resolves to `auto` and the grid takes the content's intrinsic size.\n // - Toolbar/header/footer are `shrink-0`; the scroll body is\n // `flex-1 min-h-0 overflow-auto`, so the scroll area naturally takes\n // whatever space remains after the fixed-size chrome — regardless of\n // toolbar/footer size.\n // - `maxHeight` is applied directly to the root; the scroll body never\n // subtracts chrome sizes manually (that math breaks when the toolbar\n // wraps, the footer grows, etc.).\n return (\n <div\n ref={gridRef}\n className={cn(\n \"flex flex-col h-full min-h-0 bg-transparent\",\n className,\n )}\n style={maxHeight != null ? { ...gridSizingStyle, maxHeight } : gridSizingStyle}\n role=\"grid\"\n aria-rowcount={totalRowCount ?? rows.length}\n aria-colcount={visibleColumns.length}\n >\n {/* Toolbar. When a custom `toolbar` render function is supplied,\n it owns the whole row — `toolbarExtra` is ignored because the\n custom render can consume `ctx` and include whatever it wants.\n When the default toolbar is used, `toolbarExtra` is injected\n into its row to the left of the columns / export actions. */}\n {toolbar !== false && (\n <div className=\"relative shrink-0 bg-transparent\">\n {toolbar ? (\n toolbar(toolbarCtx)\n ) : (\n <DataGridToolbar\n ctx={toolbarCtx}\n extra={\n typeof toolbarExtra === \"function\"\n ? toolbarExtra(toolbarCtx)\n : toolbarExtra\n }\n />\n )}\n </div>\n )}\n\n {/* Grid content */}\n <div className=\"relative flex min-h-0 flex-1 flex-col\">\n {/* Refetch indicator — thin progress bar, no layout shift */}\n {isRefetching && (\n <div className=\"absolute top-0 left-0 right-0 h-0.5 z-30 bg-foreground/[0.04] overflow-hidden\">\n <div className=\"h-full w-1/3 bg-blue-500/60 rounded-full animate-pulse\" />\n </div>\n )}\n\n {/* Header row — separate from scroll body, syncs horizontal scroll */}\n <div\n ref={headerScrollRef}\n className=\"overflow-hidden shrink-0 border-b border-foreground/[0.06]\"\n >\n <div\n className=\"flex\"\n style={{ height: headerHeight, minWidth: visibleColumnMetrics.totalWidth }}\n role=\"row\"\n >\n {selectionMode !== \"none\" && (\n <div\n className=\"flex items-center justify-center border-r border-foreground/[0.04]\"\n style={{ width: 44 }}\n >\n {selectionMode === \"multiple\" && (\n <SelectionCheckbox\n checked={allSelected}\n indeterminate={someSelected}\n onChange={handleSelectAll}\n ariaLabel=\"Select all rows\"\n />\n )}\n </div>\n )}\n {visibleColumns.map((col) => (\n <HeaderCell\n key={col.id}\n col={col}\n isSorted={getSortDirection(state.sorting, col.id)}\n sortIndex={getSortIndex(state.sorting, col.id)}\n resizable={resizable}\n onSort={handleSort}\n onResize={handleResize}\n onResizeEnd={handleResizeEnd}\n />\n ))}\n </div>\n </div>\n\n {/* Scrollable body — flex-1 + min-h-0 makes it take the remaining\n space inside the `flex flex-col` grid wrapper, no manual math. */}\n <div\n ref={scrollContainerRef}\n className={cn(\n \"min-h-0 overflow-auto flex-1 bg-transparent\",\n // Custom scrollbar\n \"[&::-webkit-scrollbar]:w-1.5 [&::-webkit-scrollbar]:h-1.5\",\n \"[&::-webkit-scrollbar-track]:bg-transparent\",\n \"[&::-webkit-scrollbar-thumb]:bg-foreground/[0.08] [&::-webkit-scrollbar-thumb]:rounded-full\",\n \"[&::-webkit-scrollbar-thumb]:hover:bg-foreground/[0.15]\",\n )}\n onScroll={handleBodyScroll}\n >\n {/* Loading initial */}\n {isLoading && (\n <div style={{ minWidth: visibleColumnMetrics.totalWidth }}>\n {loadingState ??\n Array.from({ length: 8 }).map((_, i) => (\n <SkeletonRow\n key={i}\n columns={visibleColumns}\n height={rowHeight}\n showCheckbox={selectionMode !== \"none\"}\n />\n ))}\n </div>\n )}\n\n {/* Empty state */}\n {!isLoading && rows.length === 0 && (\n <div\n className=\"flex items-center justify-center py-16 text-sm text-muted-foreground\"\n style={{ minWidth: visibleColumnMetrics.totalWidth }}\n >\n {emptyState ?? strings.noData}\n </div>\n )}\n\n {/* Virtualized rows */}\n {!isLoading && rows.length > 0 && (\n <div\n style={{\n height: rowVirtualizer.getTotalSize(),\n width: \"100%\",\n minWidth: visibleColumnMetrics.totalWidth,\n position: \"relative\",\n }}\n >\n {rowVirtualizer.getVirtualItems().map((virtualRow: VirtualItem) => {\n const row = rows[virtualRow.index]!;\n const rowId = getRowId(row);\n const isSelected = state.selection.selectedIds.has(rowId);\n\n const isOddRow = virtualRow.index % 2 === 1;\n return (\n <div\n key={rowId}\n className={cn(\n \"absolute left-0 w-full flex\",\n \"border-b border-black/[0.03] dark:border-white/[0.03]\",\n \"transition-colors duration-75\",\n isSelected\n ? \"bg-blue-500/[0.06] dark:bg-blue-400/[0.08] hover:bg-blue-500/[0.08] dark:hover:bg-blue-400/[0.1]\"\n : isOddRow\n ? \"bg-foreground/[0.02] dark:bg-foreground/[0.03] hover:bg-foreground/[0.04] dark:hover:bg-foreground/[0.06]\"\n : \"hover:bg-foreground/[0.025] dark:hover:bg-foreground/[0.04]\",\n selectionMode !== \"none\" && \"cursor-pointer\",\n )}\n style={{\n height: rowHeight,\n transform: `translateY(${virtualRow.start}px)`,\n }}\n onClick={(e) => handleRowClick(row, rowId, e)}\n onDoubleClick={(e) => onRowDoubleClick?.(row, rowId, e)}\n role=\"row\"\n aria-rowindex={virtualRow.index + 2}\n aria-selected={isSelected}\n data-row-id={rowId}\n data-state={isSelected ? \"selected\" : undefined}\n >\n {/* Selection checkbox */}\n {selectionMode !== \"none\" && (\n <div\n className=\"flex items-center justify-center border-r border-black/[0.04] dark:border-white/[0.04]\"\n style={{ width: 44 }}\n >\n <SelectionCheckbox\n checked={isSelected}\n onChange={(event) => handleRowSelectionCheckboxClick(row, rowId, event)}\n ariaLabel={`Select row ${rowId}`}\n />\n </div>\n )}\n\n {/* Data cells */}\n {visibleColumns.map((col) => (\n <DataCell\n key={col.id}\n col={col}\n row={row}\n rowId={rowId}\n rowIndex={virtualRow.index}\n isSelected={isSelected}\n dateDisplay={state.dateDisplay}\n />\n ))}\n </div>\n );\n })}\n </div>\n )}\n\n {/* Infinite scroll sentinel */}\n {paginationMode === \"infinite\" && hasMore && !isLoading && (\n <InfiniteScrollSentinel\n onIntersect={onLoadMore ?? (() => {})}\n isLoading={isLoadingMore}\n strings={strings}\n />\n )}\n </div>\n </div>\n\n {/* Footer */}\n {footer !== false && (\n <div className=\"relative z-10 shrink-0 bg-transparent\">\n {footer ? (\n footer(footerCtx)\n ) : (\n <DefaultFooter\n ctx={footerCtx}\n pagination={paginationMode}\n onChange={onChange}\n />\n )}\n {footerExtra && (\n typeof footerExtra === \"function\" ? footerExtra(footerCtx) : footerExtra\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { DATA_GRID_DEFAULT_STRINGS, resolveDataGridStrings } from "./strings";
2
+ import { DataGridCallbacks, DataGridCellContext, DataGridColumnAlign, DataGridColumnDef, DataGridColumnPin, DataGridColumnPinning, DataGridColumnType, DataGridColumnVisibility, DataGridDataPaginationMode, DataGridDataSource, DataGridDateDisplay, DataGridDateFormat, DataGridFetchParams, DataGridFetchResult, DataGridFooterContext, DataGridHeaderContext, DataGridPaginationMode, DataGridPaginationModel, DataGridProps, DataGridSelectOption, DataGridSelectionMode, DataGridSelectionModel, DataGridSortItem, DataGridSortModel, DataGridState, DataGridStrings, DataGridToolbarContext, RowId } from "./types";
3
+ import { DEFAULT_PAGINATION, EMPTY_SELECTION, EMPTY_SORT_MODEL, applyQuickSearch, buildRowComparator, clearSelection, createDefaultDataGridState, defaultFormatAbsolute, defaultFormatRelative, defaultMatchRow, defaultParseDate, exportToCsv, formatGridDate, getSortDirection, getSortIndex, getTotalPages, isColumnVisible, paginateRows, resolveColumnValue, resolveColumnWidth, selectAll, toggleRowSelection, toggleSort } from "./state";
4
+ import { DataGrid } from "./data-grid";
5
+ import { DataGridToolbar } from "./data-grid-toolbar";
6
+ import { UseDataSourceResult, useDataSource } from "./use-data-source";
7
+ export { DATA_GRID_DEFAULT_STRINGS, DEFAULT_PAGINATION, DataGrid, type DataGridCallbacks, type DataGridCellContext, type DataGridColumnAlign, type DataGridColumnDef, type DataGridColumnPin, type DataGridColumnPinning, type DataGridColumnType, type DataGridColumnVisibility, type DataGridDataPaginationMode, type DataGridDataSource, type DataGridDateDisplay, type DataGridDateFormat, type DataGridFetchParams, type DataGridFetchResult, type DataGridFooterContext, type DataGridHeaderContext, type DataGridPaginationMode, type DataGridPaginationModel, type DataGridProps, type DataGridSelectOption, type DataGridSelectionMode, type DataGridSelectionModel, type DataGridSortItem, type DataGridSortModel, type DataGridState, type DataGridStrings, DataGridToolbar, type DataGridToolbarContext, EMPTY_SELECTION, EMPTY_SORT_MODEL, type RowId, type UseDataSourceResult, applyQuickSearch, buildRowComparator, clearSelection, createDefaultDataGridState, defaultFormatAbsolute, defaultFormatRelative, defaultMatchRow, defaultParseDate, exportToCsv, formatGridDate, getSortDirection, getSortIndex, getTotalPages, isColumnVisible, paginateRows, resolveColumnValue, resolveColumnWidth, resolveDataGridStrings, selectAll, toggleRowSelection, toggleSort, useDataSource };
@@ -0,0 +1,7 @@
1
+ import { DEFAULT_PAGINATION, EMPTY_SELECTION, EMPTY_SORT_MODEL, applyQuickSearch, buildRowComparator, clearSelection, createDefaultDataGridState, defaultFormatAbsolute, defaultFormatRelative, defaultMatchRow, defaultParseDate, exportToCsv, formatGridDate, getSortDirection, getSortIndex, getTotalPages, isColumnVisible, paginateRows, resolveColumnValue, resolveColumnWidth, selectAll, toggleRowSelection, toggleSort } from "./state.js";
2
+ import { DATA_GRID_DEFAULT_STRINGS, resolveDataGridStrings } from "./strings.js";
3
+ import { DataGridToolbar } from "./data-grid-toolbar.js";
4
+ import { DataGrid } from "./data-grid.js";
5
+ import { useDataSource } from "./use-data-source.js";
6
+
7
+ export { DATA_GRID_DEFAULT_STRINGS, DEFAULT_PAGINATION, DataGrid, DataGridToolbar, EMPTY_SELECTION, EMPTY_SORT_MODEL, applyQuickSearch, buildRowComparator, clearSelection, createDefaultDataGridState, defaultFormatAbsolute, defaultFormatRelative, defaultMatchRow, defaultParseDate, exportToCsv, formatGridDate, getSortDirection, getSortIndex, getTotalPages, isColumnVisible, paginateRows, resolveColumnValue, resolveColumnWidth, resolveDataGridStrings, selectAll, toggleRowSelection, toggleSort, useDataSource };
@@ -0,0 +1,91 @@
1
+ import { DataGridColumnDef, DataGridDateDisplay, DataGridDateFormat, DataGridPaginationModel, DataGridSelectionModel, DataGridSortModel, DataGridState } from "./types";
2
+
3
+ //#region src/components/data-grid/state.d.ts
4
+ declare const EMPTY_SORT_MODEL: DataGridSortModel;
5
+ declare const EMPTY_SELECTION: DataGridSelectionModel;
6
+ declare const DEFAULT_PAGINATION: DataGridPaginationModel;
7
+ /**
8
+ * Build the initial `DataGridState` for a set of columns. Pass this as the
9
+ * lazy initializer to `useState` — NEVER hand-assemble the state object.
10
+ *
11
+ * ```tsx
12
+ * const [gridState, setGridState] = React.useState(() =>
13
+ * createDefaultDataGridState(columns)
14
+ * );
15
+ * ```
16
+ *
17
+ * `columns` must be defined BEFORE this call (obvious, but a common TDZ
18
+ * mistake: if you declare columns after the `useState`, you'll crash on
19
+ * the first render). Keep the columns reference stable across renders
20
+ * (define them outside the component or wrap in `React.useMemo`).
21
+ */
22
+ declare function createDefaultDataGridState(columns: readonly DataGridColumnDef<any>[]): DataGridState;
23
+ declare function resolveColumnValue<TRow>(col: DataGridColumnDef<TRow>, row: TRow): unknown;
24
+ declare function resolveColumnWidth(col: DataGridColumnDef<any>, storedWidth: number | undefined): number;
25
+ declare function isColumnVisible(columnId: string, visibility: Record<string, boolean>): boolean;
26
+ declare function toggleSort(model: DataGridSortModel, columnId: string, multiSort: boolean): DataGridSortModel;
27
+ declare function getSortDirection(model: DataGridSortModel, columnId: string): false | "asc" | "desc";
28
+ declare function getSortIndex(model: DataGridSortModel, columnId: string): number | null;
29
+ declare function buildRowComparator<TRow>(sortModel: DataGridSortModel, columns: readonly DataGridColumnDef<TRow>[]): ((a: TRow, b: TRow) => number) | null;
30
+ declare function paginateRows<TRow>(rows: readonly TRow[], pagination: DataGridPaginationModel): TRow[];
31
+ declare function getTotalPages(totalRows: number, pageSize: number): number;
32
+ declare function toggleRowSelection(selection: DataGridSelectionModel, rowId: string, mode: "single" | "multiple", shiftKey: boolean, ctrlKey: boolean, allRowIds: readonly string[]): DataGridSelectionModel;
33
+ declare function selectAll(allRowIds: readonly string[]): DataGridSelectionModel;
34
+ declare function clearSelection(): DataGridSelectionModel;
35
+ /** Default row matcher used by `applyQuickSearch`. Case-insensitive
36
+ * substring match across every column's resolved cell value. Columns
37
+ * with `null` / `undefined` values are skipped. The query is expected
38
+ * to be pre-trimmed and lowercased by `applyQuickSearch` — this helper
39
+ * does NOT trim or lowercase it again, so if you wire it up yourself,
40
+ * do that first. */
41
+ declare function defaultMatchRow<TRow>(row: TRow, query: string, columns: readonly DataGridColumnDef<TRow>[]): boolean;
42
+ /** Client-side quick-search filter. Returns the original array
43
+ * reference when `query` is empty, so calling this in a hot `useMemo`
44
+ * is cheap in the common "no search" case.
45
+ *
46
+ * Used by `useDataSource` in client mode. Exported so consumers driving
47
+ * the grid manually (or doing their own pre-filtering before feeding
48
+ * rows to an async data source) can stay consistent with the built-in
49
+ * search behaviour.
50
+ *
51
+ * Override `matchRow` for custom matching logic — e.g. fuzzy matching,
52
+ * field-specific weighting, or skipping some columns. */
53
+ declare function applyQuickSearch<TRow>(rows: readonly TRow[], query: string, columns: readonly DataGridColumnDef<TRow>[], matchRow?: (row: TRow, query: string, columns: readonly DataGridColumnDef<TRow>[]) => boolean): readonly TRow[];
54
+ /** Parse a raw cell value into a `Date`. Returns `null` for nullish,
55
+ * unparseable, or invalid dates. Accepts strings (including ISO and
56
+ * "YYYY-MM-DD HH:MM:SS"-style ClickHouse output), numbers (ms since
57
+ * epoch), and `Date` instances. For truly weird formats, override via
58
+ * `col.parseValue`. */
59
+ declare function defaultParseDate(value: unknown): Date | null;
60
+ /** Default relative formatter — "1 day ago" / "in 2 hours" via
61
+ * `Intl.RelativeTimeFormat`. Pure function of the date; does NOT
62
+ * re-render as real time passes. */
63
+ declare function defaultFormatRelative(date: Date): string;
64
+ /** Default absolute formatter — full locale date + time. */
65
+ declare function defaultFormatAbsolute(date: Date): string;
66
+ /** Format a raw cell value for display in a `date` / `dateTime` column.
67
+ * Returns both the inline display string and the tooltip string (which
68
+ * is always the absolute form so users can read the exact datetime).
69
+ *
70
+ * Used internally by the grid's default date cell renderer, and exported
71
+ * so consumers writing a custom `renderCell` for a date column can stay
72
+ * visually consistent with the built-in behaviour.
73
+ *
74
+ * ```tsx
75
+ * renderCell: ({ value, dateDisplay }) => {
76
+ * const { display, tooltip } = formatGridDate(value, dateDisplay);
77
+ * if (!display) return <span className="text-muted-foreground/40">—</span>;
78
+ * return <span title={tooltip ?? undefined}>{display}</span>;
79
+ * }
80
+ * ``` */
81
+ declare function formatGridDate(value: unknown, mode: DataGridDateDisplay, opts?: {
82
+ parseValue?: (value: unknown) => Date | null;
83
+ dateFormat?: DataGridDateFormat;
84
+ }): {
85
+ display: string | null;
86
+ tooltip: string | null;
87
+ };
88
+ declare function exportToCsv<TRow>(rows: readonly TRow[], columns: readonly DataGridColumnDef<TRow>[], filename: string): void;
89
+ //#endregion
90
+ export { DEFAULT_PAGINATION, EMPTY_SELECTION, EMPTY_SORT_MODEL, applyQuickSearch, buildRowComparator, clearSelection, createDefaultDataGridState, defaultFormatAbsolute, defaultFormatRelative, defaultMatchRow, defaultParseDate, exportToCsv, formatGridDate, getSortDirection, getSortIndex, getTotalPages, isColumnVisible, paginateRows, resolveColumnValue, resolveColumnWidth, selectAll, toggleRowSelection, toggleSort };
91
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","names":[],"sources":["../../../../src/components/data-grid/state.ts"],"mappings":";;;cAaa,gBAAA,EAAkB,iBAAA;AAAA,cAClB,eAAA,EAAiB,sBAAA;AAAA,cAIjB,kBAAA,EAAoB,uBAAA;;;;;AAJjC;;;;;AAIA;;;;;AAoBA;iBAAgB,0BAAA,CACd,OAAA,WAAkB,iBAAA,UACjB,aAAA;AAAA,iBAwBa,kBAAA,MAAA,CACd,GAAA,EAAK,iBAAA,CAAkB,IAAA,GACvB,GAAA,EAAK,IAAA;AAAA,iBAOS,kBAAA,CACd,GAAA,EAAK,iBAAA,OACL,WAAA;AAAA,iBAKc,eAAA,CACd,QAAA,UACA,UAAA,EAAY,MAAA;AAAA,iBAOE,UAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA,UACA,SAAA,YACC,iBAAA;AAAA,iBAiBa,gBAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA;AAAA,iBAMc,YAAA,CACd,KAAA,EAAO,iBAAA,EACP,QAAA;AAAA,iBAmBc,kBAAA,MAAA,CACd,SAAA,EAAW,iBAAA,EACX,OAAA,WAAkB,iBAAA,CAAkB,IAAA,QACjC,CAAA,EAAG,IAAA,EAAM,CAAA,EAAG,IAAA;AAAA,iBAuBD,YAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,UAAA,EAAY,uBAAA,GACX,IAAA;AAAA,iBAKa,aAAA,CACd,SAAA,UACA,QAAA;AAAA,iBAOc,kBAAA,CACd,SAAA,EAAW,sBAAA,EACX,KAAA,UACA,IAAA,yBACA,QAAA,WACA,OAAA,WACA,SAAA,sBACC,sBAAA;AAAA,iBA2Ca,SAAA,CACd,SAAA,sBACC,sBAAA;AAAA,iBAOa,cAAA,CAAA,GAAkB,sBAAA;;;;;;;iBAYlB,eAAA,MAAA,CACd,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA;;;;;AAvLtC;;;;;;;iBA4MgB,gBAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA,KACpC,QAAA,IACE,GAAA,EAAK,IAAA,EACL,KAAA,UACA,OAAA,WAAkB,iBAAA,CAAkB,IAAA,2BAE5B,IAAA;;AA9MZ;;;;iBA2NgB,gBAAA,CAAiB,KAAA,YAAiB,IAAA;;;;iBA2BlC,qBAAA,CAAsB,IAAA,EAAM,IAAA;AA7O5C;AAAA,iBA0PgB,qBAAA,CAAsB,IAAA,EAAM,IAAA;;;;;;;;;;AArO5C;;;;;;iBAwPgB,cAAA,CACd,KAAA,WACA,IAAA,EAAM,mBAAA,EACN,IAAA;EACE,UAAA,IAAc,KAAA,cAAmB,IAAA;EACjC,UAAA,GAAa,kBAAA;AAAA;EAEZ,OAAA;EAAwB,OAAA;AAAA;AAAA,iBAeb,WAAA,MAAA,CACd,IAAA,WAAe,IAAA,IACf,OAAA,WAAkB,iBAAA,CAAkB,IAAA,KACpC,QAAA"}
@@ -0,0 +1,305 @@
1
+ import { stringCompare } from "@stackframe/stack-shared/dist/utils/strings";
2
+
3
+ //#region src/components/data-grid/state.ts
4
+ const EMPTY_SORT_MODEL = [];
5
+ const EMPTY_SELECTION = {
6
+ selectedIds: /* @__PURE__ */ new Set(),
7
+ anchorId: null
8
+ };
9
+ const DEFAULT_PAGINATION = {
10
+ pageIndex: 0,
11
+ pageSize: 50
12
+ };
13
+ /**
14
+ * Build the initial `DataGridState` for a set of columns. Pass this as the
15
+ * lazy initializer to `useState` — NEVER hand-assemble the state object.
16
+ *
17
+ * ```tsx
18
+ * const [gridState, setGridState] = React.useState(() =>
19
+ * createDefaultDataGridState(columns)
20
+ * );
21
+ * ```
22
+ *
23
+ * `columns` must be defined BEFORE this call (obvious, but a common TDZ
24
+ * mistake: if you declare columns after the `useState`, you'll crash on
25
+ * the first render). Keep the columns reference stable across renders
26
+ * (define them outside the component or wrap in `React.useMemo`).
27
+ */
28
+ function createDefaultDataGridState(columns) {
29
+ const columnWidths = {};
30
+ const columnOrder = [];
31
+ for (const col of columns) {
32
+ columnWidths[col.id] = col.width ?? 150;
33
+ columnOrder.push(col.id);
34
+ }
35
+ return {
36
+ sorting: EMPTY_SORT_MODEL,
37
+ columnVisibility: {},
38
+ columnWidths,
39
+ columnPinning: {
40
+ left: [],
41
+ right: []
42
+ },
43
+ columnOrder,
44
+ pagination: DEFAULT_PAGINATION,
45
+ selection: EMPTY_SELECTION,
46
+ dateDisplay: "relative",
47
+ quickSearch: ""
48
+ };
49
+ }
50
+ function resolveColumnValue(col, row) {
51
+ if (typeof col.accessor === "function") return col.accessor(row);
52
+ return row[col.accessor ?? col.id];
53
+ }
54
+ function resolveColumnWidth(col, storedWidth) {
55
+ return storedWidth ?? col.width ?? 150;
56
+ }
57
+ function isColumnVisible(columnId, visibility) {
58
+ return visibility[columnId] !== false;
59
+ }
60
+ function toggleSort(model, columnId, multiSort) {
61
+ const existing = model.find((s) => s.columnId === columnId);
62
+ if (!existing) {
63
+ const item = {
64
+ columnId,
65
+ direction: "asc"
66
+ };
67
+ return multiSort ? [...model, item] : [item];
68
+ }
69
+ if (existing.direction === "asc") {
70
+ const updated = {
71
+ columnId,
72
+ direction: "desc"
73
+ };
74
+ return model.map((s) => s.columnId === columnId ? updated : s);
75
+ }
76
+ return model.filter((s) => s.columnId !== columnId);
77
+ }
78
+ function getSortDirection(model, columnId) {
79
+ const item = model.find((s) => s.columnId === columnId);
80
+ return item ? item.direction : false;
81
+ }
82
+ function getSortIndex(model, columnId) {
83
+ if (model.length <= 1) return null;
84
+ const idx = model.findIndex((s) => s.columnId === columnId);
85
+ return idx >= 0 ? idx + 1 : null;
86
+ }
87
+ function defaultComparator(a, b) {
88
+ if (a == null && b == null) return 0;
89
+ if (a == null) return -1;
90
+ if (b == null) return 1;
91
+ if (typeof a === "number" && typeof b === "number") return a - b;
92
+ if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();
93
+ return stringCompare(String(a), String(b));
94
+ }
95
+ function buildRowComparator(sortModel, columns) {
96
+ if (sortModel.length === 0) return null;
97
+ const colMap = new Map(columns.map((c) => [c.id, c]));
98
+ return (a, b) => {
99
+ for (const { columnId, direction } of sortModel) {
100
+ const col = colMap.get(columnId);
101
+ if (!col) continue;
102
+ const va = resolveColumnValue(col, a);
103
+ const vb = resolveColumnValue(col, b);
104
+ const cmp = col.sortComparator ? col.sortComparator(va, vb) : defaultComparator(va, vb);
105
+ if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
106
+ }
107
+ return 0;
108
+ };
109
+ }
110
+ function paginateRows(rows, pagination) {
111
+ const start = pagination.pageIndex * pagination.pageSize;
112
+ return rows.slice(start, start + pagination.pageSize);
113
+ }
114
+ function getTotalPages(totalRows, pageSize) {
115
+ return Math.max(1, Math.ceil(totalRows / pageSize));
116
+ }
117
+ function toggleRowSelection(selection, rowId, mode, shiftKey, ctrlKey, allRowIds) {
118
+ if (mode === "single") {
119
+ const isSelected = selection.selectedIds.has(rowId);
120
+ return {
121
+ selectedIds: isSelected ? /* @__PURE__ */ new Set() : new Set([rowId]),
122
+ anchorId: isSelected ? null : rowId
123
+ };
124
+ }
125
+ if (shiftKey && selection.anchorId != null) {
126
+ const anchorIdx = allRowIds.indexOf(selection.anchorId);
127
+ const currentIdx = allRowIds.indexOf(rowId);
128
+ if (anchorIdx >= 0 && currentIdx >= 0) {
129
+ const start = Math.min(anchorIdx, currentIdx);
130
+ const end = Math.max(anchorIdx, currentIdx);
131
+ const rangeIds = allRowIds.slice(start, end + 1);
132
+ const next = ctrlKey ? new Set(selection.selectedIds) : /* @__PURE__ */ new Set();
133
+ for (const id of rangeIds) next.add(id);
134
+ return {
135
+ selectedIds: next,
136
+ anchorId: selection.anchorId
137
+ };
138
+ }
139
+ }
140
+ if (ctrlKey) {
141
+ const next = new Set(selection.selectedIds);
142
+ if (next.has(rowId)) next.delete(rowId);
143
+ else next.add(rowId);
144
+ return {
145
+ selectedIds: next,
146
+ anchorId: rowId
147
+ };
148
+ }
149
+ return {
150
+ selectedIds: new Set([rowId]),
151
+ anchorId: rowId
152
+ };
153
+ }
154
+ function selectAll(allRowIds) {
155
+ return {
156
+ selectedIds: new Set(allRowIds),
157
+ anchorId: null
158
+ };
159
+ }
160
+ function clearSelection() {
161
+ return EMPTY_SELECTION;
162
+ }
163
+ /** Default row matcher used by `applyQuickSearch`. Case-insensitive
164
+ * substring match across every column's resolved cell value. Columns
165
+ * with `null` / `undefined` values are skipped. The query is expected
166
+ * to be pre-trimmed and lowercased by `applyQuickSearch` — this helper
167
+ * does NOT trim or lowercase it again, so if you wire it up yourself,
168
+ * do that first. */
169
+ function defaultMatchRow(row, query, columns) {
170
+ for (const col of columns) {
171
+ const v = resolveColumnValue(col, row);
172
+ if (v == null) continue;
173
+ if (String(v).toLowerCase().includes(query)) return true;
174
+ }
175
+ return false;
176
+ }
177
+ /** Client-side quick-search filter. Returns the original array
178
+ * reference when `query` is empty, so calling this in a hot `useMemo`
179
+ * is cheap in the common "no search" case.
180
+ *
181
+ * Used by `useDataSource` in client mode. Exported so consumers driving
182
+ * the grid manually (or doing their own pre-filtering before feeding
183
+ * rows to an async data source) can stay consistent with the built-in
184
+ * search behaviour.
185
+ *
186
+ * Override `matchRow` for custom matching logic — e.g. fuzzy matching,
187
+ * field-specific weighting, or skipping some columns. */
188
+ function applyQuickSearch(rows, query, columns, matchRow = defaultMatchRow) {
189
+ const trimmed = query.trim().toLowerCase();
190
+ if (!trimmed) return rows;
191
+ return rows.filter((r) => matchRow(r, trimmed, columns));
192
+ }
193
+ /** Parse a raw cell value into a `Date`. Returns `null` for nullish,
194
+ * unparseable, or invalid dates. Accepts strings (including ISO and
195
+ * "YYYY-MM-DD HH:MM:SS"-style ClickHouse output), numbers (ms since
196
+ * epoch), and `Date` instances. For truly weird formats, override via
197
+ * `col.parseValue`. */
198
+ function defaultParseDate(value) {
199
+ if (value == null) return null;
200
+ if (value instanceof Date) return isNaN(value.getTime()) ? null : value;
201
+ if (typeof value === "number") {
202
+ const d = new Date(value);
203
+ return isNaN(d.getTime()) ? null : d;
204
+ }
205
+ if (typeof value === "string") {
206
+ const d = new Date(value);
207
+ return isNaN(d.getTime()) ? null : d;
208
+ }
209
+ return null;
210
+ }
211
+ const DIVISIONS = [
212
+ {
213
+ amount: 60,
214
+ unit: "second"
215
+ },
216
+ {
217
+ amount: 60,
218
+ unit: "minute"
219
+ },
220
+ {
221
+ amount: 24,
222
+ unit: "hour"
223
+ },
224
+ {
225
+ amount: 7,
226
+ unit: "day"
227
+ },
228
+ {
229
+ amount: 4.34524,
230
+ unit: "week"
231
+ },
232
+ {
233
+ amount: 12,
234
+ unit: "month"
235
+ },
236
+ {
237
+ amount: Number.POSITIVE_INFINITY,
238
+ unit: "year"
239
+ }
240
+ ];
241
+ /** Default relative formatter — "1 day ago" / "in 2 hours" via
242
+ * `Intl.RelativeTimeFormat`. Pure function of the date; does NOT
243
+ * re-render as real time passes. */
244
+ function defaultFormatRelative(date) {
245
+ const rtf = new Intl.RelativeTimeFormat(void 0, { numeric: "auto" });
246
+ let duration = (date.getTime() - Date.now()) / 1e3;
247
+ for (const div of DIVISIONS) {
248
+ if (Math.abs(duration) < div.amount) return rtf.format(Math.round(duration), div.unit);
249
+ duration /= div.amount;
250
+ }
251
+ return rtf.format(Math.round(duration), "year");
252
+ }
253
+ /** Default absolute formatter — full locale date + time. */
254
+ function defaultFormatAbsolute(date) {
255
+ return date.toLocaleString();
256
+ }
257
+ /** Format a raw cell value for display in a `date` / `dateTime` column.
258
+ * Returns both the inline display string and the tooltip string (which
259
+ * is always the absolute form so users can read the exact datetime).
260
+ *
261
+ * Used internally by the grid's default date cell renderer, and exported
262
+ * so consumers writing a custom `renderCell` for a date column can stay
263
+ * visually consistent with the built-in behaviour.
264
+ *
265
+ * ```tsx
266
+ * renderCell: ({ value, dateDisplay }) => {
267
+ * const { display, tooltip } = formatGridDate(value, dateDisplay);
268
+ * if (!display) return <span className="text-muted-foreground/40">—</span>;
269
+ * return <span title={tooltip ?? undefined}>{display}</span>;
270
+ * }
271
+ * ``` */
272
+ function formatGridDate(value, mode, opts) {
273
+ const date = (opts?.parseValue ?? defaultParseDate)(value);
274
+ if (!date) return {
275
+ display: null,
276
+ tooltip: null
277
+ };
278
+ const relative = opts?.dateFormat?.relative ?? defaultFormatRelative;
279
+ const tooltip = (opts?.dateFormat?.absolute ?? defaultFormatAbsolute)(date);
280
+ return {
281
+ display: mode === "relative" ? relative(date) : tooltip,
282
+ tooltip
283
+ };
284
+ }
285
+ function exportToCsv(rows, columns, filename) {
286
+ const header = columns.map((col) => typeof col.header === "string" ? col.header : col.id);
287
+ const csvRows = rows.map((row) => columns.map((col) => {
288
+ const val = resolveColumnValue(col, row);
289
+ const formatted = col.formatValue ? col.formatValue(val, row) : String(val ?? "");
290
+ if (formatted.includes(",") || formatted.includes("\"") || formatted.includes("\n")) return `"${formatted.replace(/"/g, "\"\"")}"`;
291
+ return formatted;
292
+ }));
293
+ const csvContent = [header.join(","), ...csvRows.map((row) => row.join(","))].join("\n");
294
+ const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
295
+ const url = URL.createObjectURL(blob);
296
+ const link = document.createElement("a");
297
+ link.href = url;
298
+ link.download = `${filename}.csv`;
299
+ link.click();
300
+ URL.revokeObjectURL(url);
301
+ }
302
+
303
+ //#endregion
304
+ export { DEFAULT_PAGINATION, EMPTY_SELECTION, EMPTY_SORT_MODEL, applyQuickSearch, buildRowComparator, clearSelection, createDefaultDataGridState, defaultFormatAbsolute, defaultFormatRelative, defaultMatchRow, defaultParseDate, exportToCsv, formatGridDate, getSortDirection, getSortIndex, getTotalPages, isColumnVisible, paginateRows, resolveColumnValue, resolveColumnWidth, selectAll, toggleRowSelection, toggleSort };
305
+ //# sourceMappingURL=state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.js","names":[],"sources":["../../../../src/components/data-grid/state.ts"],"sourcesContent":["import { stringCompare } from \"@stackframe/stack-shared/dist/utils/strings\";\nimport type {\n DataGridColumnDef,\n DataGridDateDisplay,\n DataGridDateFormat,\n DataGridPaginationModel,\n DataGridSelectionModel,\n DataGridSortModel,\n DataGridState,\n} from \"./types\";\n\n// ─── Default state ───────────────────────────────────────────────────\n\nexport const EMPTY_SORT_MODEL: DataGridSortModel = [];\nexport const EMPTY_SELECTION: DataGridSelectionModel = {\n selectedIds: new Set(),\n anchorId: null,\n};\nexport const DEFAULT_PAGINATION: DataGridPaginationModel = {\n pageIndex: 0,\n pageSize: 50,\n};\n\n/**\n * Build the initial `DataGridState` for a set of columns. Pass this as the\n * lazy initializer to `useState` — NEVER hand-assemble the state object.\n *\n * ```tsx\n * const [gridState, setGridState] = React.useState(() =>\n * createDefaultDataGridState(columns)\n * );\n * ```\n *\n * `columns` must be defined BEFORE this call (obvious, but a common TDZ\n * mistake: if you declare columns after the `useState`, you'll crash on\n * the first render). Keep the columns reference stable across renders\n * (define them outside the component or wrap in `React.useMemo`).\n */\nexport function createDefaultDataGridState(\n columns: readonly DataGridColumnDef<any>[],\n): DataGridState {\n const columnWidths: Record<string, number> = {};\n const columnOrder: string[] = [];\n\n for (const col of columns) {\n columnWidths[col.id] = col.width ?? 150;\n columnOrder.push(col.id);\n }\n\n return {\n sorting: EMPTY_SORT_MODEL,\n columnVisibility: {},\n columnWidths,\n columnPinning: { left: [], right: [] },\n columnOrder,\n pagination: DEFAULT_PAGINATION,\n selection: EMPTY_SELECTION,\n dateDisplay: \"relative\",\n quickSearch: \"\",\n };\n}\n\n// ─── Column helpers ──────────────────────────────────────────────────\n\nexport function resolveColumnValue<TRow>(\n col: DataGridColumnDef<TRow>,\n row: TRow,\n): unknown {\n if (typeof col.accessor === \"function\") return col.accessor(row);\n const key = (col.accessor ?? col.id) as keyof TRow;\n return row[key];\n}\n\nexport function resolveColumnWidth(\n col: DataGridColumnDef<any>,\n storedWidth: number | undefined,\n): number {\n return storedWidth ?? col.width ?? 150;\n}\n\nexport function isColumnVisible(\n columnId: string,\n visibility: Record<string, boolean>,\n): boolean {\n return visibility[columnId] !== false;\n}\n\n// ─── Sort helpers ────────────────────────────────────────────────────\n\nexport function toggleSort(\n model: DataGridSortModel,\n columnId: string,\n multiSort: boolean,\n): DataGridSortModel {\n const existing = model.find((s) => s.columnId === columnId);\n\n if (!existing) {\n const item = { columnId, direction: \"asc\" as const };\n return multiSort ? [...model, item] : [item];\n }\n\n if (existing.direction === \"asc\") {\n const updated = { columnId, direction: \"desc\" as const };\n return model.map((s) => (s.columnId === columnId ? updated : s));\n }\n\n // desc → remove\n return model.filter((s) => s.columnId !== columnId);\n}\n\nexport function getSortDirection(\n model: DataGridSortModel,\n columnId: string,\n): false | \"asc\" | \"desc\" {\n const item = model.find((s) => s.columnId === columnId);\n return item ? item.direction : false;\n}\n\nexport function getSortIndex(\n model: DataGridSortModel,\n columnId: string,\n): number | null {\n if (model.length <= 1) return null;\n const idx = model.findIndex((s) => s.columnId === columnId);\n return idx >= 0 ? idx + 1 : null;\n}\n\n// ─── Default sort comparator ─────────────────────────────────────────\n\nfunction defaultComparator(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (a instanceof Date && b instanceof Date) return a.getTime() - b.getTime();\n return stringCompare(String(a), String(b));\n}\n\nexport function buildRowComparator<TRow>(\n sortModel: DataGridSortModel,\n columns: readonly DataGridColumnDef<TRow>[],\n): ((a: TRow, b: TRow) => number) | null {\n if (sortModel.length === 0) return null;\n\n const colMap = new Map(columns.map((c) => [c.id, c]));\n\n return (a, b) => {\n for (const { columnId, direction } of sortModel) {\n const col = colMap.get(columnId);\n if (!col) continue;\n\n const va = resolveColumnValue(col, a);\n const vb = resolveColumnValue(col, b);\n const cmp = col.sortComparator\n ? col.sortComparator(va, vb)\n : defaultComparator(va, vb);\n if (cmp !== 0) return direction === \"asc\" ? cmp : -cmp;\n }\n return 0;\n };\n}\n\n// ─── Pagination helpers ──────────────────────────────────────────────\n\nexport function paginateRows<TRow>(\n rows: readonly TRow[],\n pagination: DataGridPaginationModel,\n): TRow[] {\n const start = pagination.pageIndex * pagination.pageSize;\n return rows.slice(start, start + pagination.pageSize) as TRow[];\n}\n\nexport function getTotalPages(\n totalRows: number,\n pageSize: number,\n): number {\n return Math.max(1, Math.ceil(totalRows / pageSize));\n}\n\n// ─── Selection helpers ───────────────────────────────────────────────\n\nexport function toggleRowSelection(\n selection: DataGridSelectionModel,\n rowId: string,\n mode: \"single\" | \"multiple\",\n shiftKey: boolean,\n ctrlKey: boolean,\n allRowIds: readonly string[],\n): DataGridSelectionModel {\n if (mode === \"single\") {\n const isSelected = selection.selectedIds.has(rowId);\n return {\n selectedIds: isSelected ? new Set() : new Set([rowId]),\n anchorId: isSelected ? null : rowId,\n };\n }\n\n // Multiple mode\n if (shiftKey && selection.anchorId != null) {\n const anchorIdx = allRowIds.indexOf(selection.anchorId);\n const currentIdx = allRowIds.indexOf(rowId);\n if (anchorIdx >= 0 && currentIdx >= 0) {\n const start = Math.min(anchorIdx, currentIdx);\n const end = Math.max(anchorIdx, currentIdx);\n const rangeIds = allRowIds.slice(start, end + 1);\n\n const next = ctrlKey ? new Set(selection.selectedIds) : new Set<string>();\n for (const id of rangeIds) next.add(id);\n\n return { selectedIds: next, anchorId: selection.anchorId };\n }\n }\n\n if (ctrlKey) {\n // Toggle single in multi mode\n const next = new Set(selection.selectedIds);\n if (next.has(rowId)) {\n next.delete(rowId);\n } else {\n next.add(rowId);\n }\n return { selectedIds: next, anchorId: rowId };\n }\n\n // Plain click in multi mode — select only this row\n return {\n selectedIds: new Set([rowId]),\n anchorId: rowId,\n };\n}\n\nexport function selectAll(\n allRowIds: readonly string[],\n): DataGridSelectionModel {\n return {\n selectedIds: new Set(allRowIds),\n anchorId: null,\n };\n}\n\nexport function clearSelection(): DataGridSelectionModel {\n return EMPTY_SELECTION;\n}\n\n// ─── Quick search ────────────────────────────────────────────────────\n\n/** Default row matcher used by `applyQuickSearch`. Case-insensitive\n * substring match across every column's resolved cell value. Columns\n * with `null` / `undefined` values are skipped. The query is expected\n * to be pre-trimmed and lowercased by `applyQuickSearch` — this helper\n * does NOT trim or lowercase it again, so if you wire it up yourself,\n * do that first. */\nexport function defaultMatchRow<TRow>(\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n): boolean {\n for (const col of columns) {\n const v = resolveColumnValue(col, row);\n if (v == null) continue;\n if (String(v).toLowerCase().includes(query)) return true;\n }\n return false;\n}\n\n/** Client-side quick-search filter. Returns the original array\n * reference when `query` is empty, so calling this in a hot `useMemo`\n * is cheap in the common \"no search\" case.\n *\n * Used by `useDataSource` in client mode. Exported so consumers driving\n * the grid manually (or doing their own pre-filtering before feeding\n * rows to an async data source) can stay consistent with the built-in\n * search behaviour.\n *\n * Override `matchRow` for custom matching logic — e.g. fuzzy matching,\n * field-specific weighting, or skipping some columns. */\nexport function applyQuickSearch<TRow>(\n rows: readonly TRow[],\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n matchRow: (\n row: TRow,\n query: string,\n columns: readonly DataGridColumnDef<TRow>[],\n ) => boolean = defaultMatchRow,\n): readonly TRow[] {\n const trimmed = query.trim().toLowerCase();\n if (!trimmed) return rows;\n return rows.filter((r) => matchRow(r, trimmed, columns));\n}\n\n// ─── Date helpers ────────────────────────────────────────────────────\n\n/** Parse a raw cell value into a `Date`. Returns `null` for nullish,\n * unparseable, or invalid dates. Accepts strings (including ISO and\n * \"YYYY-MM-DD HH:MM:SS\"-style ClickHouse output), numbers (ms since\n * epoch), and `Date` instances. For truly weird formats, override via\n * `col.parseValue`. */\nexport function defaultParseDate(value: unknown): Date | null {\n if (value == null) return null;\n if (value instanceof Date) return isNaN(value.getTime()) ? null : value;\n if (typeof value === \"number\") {\n const d = new Date(value);\n return isNaN(d.getTime()) ? null : d;\n }\n if (typeof value === \"string\") {\n const d = new Date(value);\n return isNaN(d.getTime()) ? null : d;\n }\n return null;\n}\n\nconst DIVISIONS: Array<{ amount: number; unit: Intl.RelativeTimeFormatUnit }> = [\n { amount: 60, unit: \"second\" },\n { amount: 60, unit: \"minute\" },\n { amount: 24, unit: \"hour\" },\n { amount: 7, unit: \"day\" },\n { amount: 4.34524, unit: \"week\" },\n { amount: 12, unit: \"month\" },\n { amount: Number.POSITIVE_INFINITY, unit: \"year\" },\n];\n\n/** Default relative formatter — \"1 day ago\" / \"in 2 hours\" via\n * `Intl.RelativeTimeFormat`. Pure function of the date; does NOT\n * re-render as real time passes. */\nexport function defaultFormatRelative(date: Date): string {\n const rtf = new Intl.RelativeTimeFormat(undefined, { numeric: \"auto\" });\n let duration = (date.getTime() - Date.now()) / 1000;\n for (const div of DIVISIONS) {\n if (Math.abs(duration) < div.amount) {\n return rtf.format(Math.round(duration), div.unit);\n }\n duration /= div.amount;\n }\n return rtf.format(Math.round(duration), \"year\");\n}\n\n/** Default absolute formatter — full locale date + time. */\nexport function defaultFormatAbsolute(date: Date): string {\n return date.toLocaleString();\n}\n\n/** Format a raw cell value for display in a `date` / `dateTime` column.\n * Returns both the inline display string and the tooltip string (which\n * is always the absolute form so users can read the exact datetime).\n *\n * Used internally by the grid's default date cell renderer, and exported\n * so consumers writing a custom `renderCell` for a date column can stay\n * visually consistent with the built-in behaviour.\n *\n * ```tsx\n * renderCell: ({ value, dateDisplay }) => {\n * const { display, tooltip } = formatGridDate(value, dateDisplay);\n * if (!display) return <span className=\"text-muted-foreground/40\">—</span>;\n * return <span title={tooltip ?? undefined}>{display}</span>;\n * }\n * ``` */\nexport function formatGridDate(\n value: unknown,\n mode: DataGridDateDisplay,\n opts?: {\n parseValue?: (value: unknown) => Date | null;\n dateFormat?: DataGridDateFormat;\n },\n): { display: string | null; tooltip: string | null } {\n const parse = opts?.parseValue ?? defaultParseDate;\n const date = parse(value);\n if (!date) return { display: null, tooltip: null };\n\n const relative = opts?.dateFormat?.relative ?? defaultFormatRelative;\n const absolute = opts?.dateFormat?.absolute ?? defaultFormatAbsolute;\n\n const tooltip = absolute(date);\n const display = mode === \"relative\" ? relative(date) : tooltip;\n return { display, tooltip };\n}\n\n// ─── CSV Export ──────────────────────────────────────────────────────\n\nexport function exportToCsv<TRow>(\n rows: readonly TRow[],\n columns: readonly DataGridColumnDef<TRow>[],\n filename: string,\n): void {\n const header = columns.map((col) =>\n typeof col.header === \"string\" ? col.header : col.id,\n );\n\n const csvRows = rows.map((row) =>\n columns.map((col) => {\n const val = resolveColumnValue(col, row);\n const formatted = col.formatValue ? col.formatValue(val, row) : String(val ?? \"\");\n // Escape CSV special characters\n if (formatted.includes(\",\") || formatted.includes('\"') || formatted.includes(\"\\n\")) {\n return `\"${formatted.replace(/\"/g, '\"\"')}\"`;\n }\n return formatted;\n }),\n );\n\n const csvContent = [\n header.join(\",\"),\n ...csvRows.map((row) => row.join(\",\")),\n ].join(\"\\n\");\n\n const blob = new Blob([csvContent], { type: \"text/csv;charset=utf-8;\" });\n const url = URL.createObjectURL(blob);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = `${filename}.csv`;\n link.click();\n URL.revokeObjectURL(url);\n}\n"],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { DataGridStrings } from "./types";
2
+
3
+ //#region src/components/data-grid/strings.d.ts
4
+ declare const DATA_GRID_DEFAULT_STRINGS: DataGridStrings;
5
+ declare function resolveDataGridStrings(override: Partial<DataGridStrings> | undefined): DataGridStrings;
6
+ //#endregion
7
+ export { DATA_GRID_DEFAULT_STRINGS, resolveDataGridStrings };
8
+ //# sourceMappingURL=strings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"strings.d.ts","names":[],"sources":["../../../../src/components/data-grid/strings.ts"],"mappings":";;;cAEa,yBAAA,EAA2B,eAAA;AAAA,iBAqCxB,sBAAA,CACd,QAAA,EAAU,OAAA,CAAQ,eAAA,gBACjB,eAAA"}