beautiful-grid 1.0.0-rc.1

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 (258) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +6 -0
  3. package/README.md +977 -0
  4. package/TRADEMARK.md +17 -0
  5. package/cjs/BGrid.js +503 -0
  6. package/cjs/components/CellEditorIcon.js +209 -0
  7. package/cjs/components/CellNavigationDomSync.js +117 -0
  8. package/cjs/components/CellTextEditorGateway.js +393 -0
  9. package/cjs/components/ColResizer.js +144 -0
  10. package/cjs/components/EditorPortalRoot.js +96 -0
  11. package/cjs/components/GridOptionalSurfaces.js +47 -0
  12. package/cjs/components/GripVertical.js +48 -0
  13. package/cjs/components/Loading.js +76 -0
  14. package/cjs/components/Pagination.js +93 -0
  15. package/cjs/components/PluginCellEditor.js +258 -0
  16. package/cjs/components/RowSelector.js +74 -0
  17. package/cjs/components/Table.js +2572 -0
  18. package/cjs/components/TableBody.js +446 -0
  19. package/cjs/components/TableBodyCell.js +112 -0
  20. package/cjs/components/TableBodyFrozen.js +44 -0
  21. package/cjs/components/TableColGroup.js +48 -0
  22. package/cjs/components/TableColGroupFrozen.js +57 -0
  23. package/cjs/components/TableFooter.js +70 -0
  24. package/cjs/components/TableHead.js +195 -0
  25. package/cjs/components/TableHeadColumn.js +124 -0
  26. package/cjs/components/TableHeadFrozen.js +165 -0
  27. package/cjs/components/TableSummary.js +133 -0
  28. package/cjs/components/TableSummaryFronzen.js +105 -0
  29. package/cjs/components/columnSortableRuntime.js +46 -0
  30. package/cjs/components/context-menu/GridContextMenu.js +174 -0
  31. package/cjs/components/context-menu/index.js +17 -0
  32. package/cjs/components/scrollbar/CustomScrollbar.js +58 -0
  33. package/cjs/components/scrollbar/ScrollbarButton.js +69 -0
  34. package/cjs/components/scrollbar/ScrollbarTrack.js +106 -0
  35. package/cjs/components/scrollbar/index.js +20 -0
  36. package/cjs/components/scrollbar/useScrollbarMetrics.js +103 -0
  37. package/cjs/components/search/GridSearchController.js +170 -0
  38. package/cjs/components/search/GridSearchPopover.js +163 -0
  39. package/cjs/components/search/index.js +18 -0
  40. package/cjs/components/selection/CellSelectionOverlay.js +52 -0
  41. package/cjs/components/selection/CellSelectionOverlayLayer.js +65 -0
  42. package/cjs/components/selection/index.js +18 -0
  43. package/cjs/components/toolbox/TableHeadToolbox.js +308 -0
  44. package/cjs/components/toolbox/ToolboxCustomSection.js +55 -0
  45. package/cjs/components/toolbox/ToolboxNumberFilterSection.js +151 -0
  46. package/cjs/components/toolbox/ToolboxSortSection.js +68 -0
  47. package/cjs/components/toolbox/ToolboxTextFilterSection.js +114 -0
  48. package/cjs/components/toolbox/ToolboxValueFilterSection.js +250 -0
  49. package/cjs/components/toolbox/index.js +22 -0
  50. package/cjs/components/useColumnSortable.js +70 -0
  51. package/cjs/editors/createDateEditorPlugin.js +88 -0
  52. package/cjs/editors/createSelectEditorPlugin.js +98 -0
  53. package/cjs/editors/defineEditorPlugin.js +17 -0
  54. package/cjs/editors/index.js +19 -0
  55. package/cjs/index.js +19 -0
  56. package/cjs/store/createAppStore.js +1645 -0
  57. package/cjs/store/index.js +17 -0
  58. package/cjs/types.js +14 -0
  59. package/cjs/utils/buildHeaderMatrix.js +265 -0
  60. package/cjs/utils/cellEditState.js +71 -0
  61. package/cjs/utils/cellEditTransaction.js +138 -0
  62. package/cjs/utils/cellSelectionGeometry.js +179 -0
  63. package/cjs/utils/common.js +154 -0
  64. package/cjs/utils/coordinate.js +125 -0
  65. package/cjs/utils/createPivotData.js +271 -0
  66. package/cjs/utils/delay.js +7 -0
  67. package/cjs/utils/filterData.js +182 -0
  68. package/cjs/utils/getCellSelectionAxisState.js +39 -0
  69. package/cjs/utils/getCellValue.js +21 -0
  70. package/cjs/utils/getColumnId.js +60 -0
  71. package/cjs/utils/getFrozenColumnsWidth.js +21 -0
  72. package/cjs/utils/getLineNumber.js +7 -0
  73. package/cjs/utils/getVisibleScrollableRowRange.js +24 -0
  74. package/cjs/utils/gridSearch.js +135 -0
  75. package/cjs/utils/index.js +39 -0
  76. package/cjs/utils/mergedCells.js +76 -0
  77. package/cjs/utils/mouseEventSubscribe.js +119 -0
  78. package/cjs/utils/number/index.js +19 -0
  79. package/cjs/utils/number/toFixed.js +6 -0
  80. package/cjs/utils/number/toMoney.js +13 -0
  81. package/cjs/utils/number/toNumber.js +12 -0
  82. package/cjs/utils/processDataQuery.js +126 -0
  83. package/cjs/utils/rowReorder.js +131 -0
  84. package/cjs/utils/scrollbar.js +62 -0
  85. package/cjs/utils/updateDataQuery.js +147 -0
  86. package/cjs/utils/useBodyData.js +277 -0
  87. package/cjs/utils/useForceUpdate.js +57 -0
  88. package/cjs/utils/useRowReorderController.js +666 -0
  89. package/esm/BGrid.js +466 -0
  90. package/esm/components/CellEditorIcon.js +115 -0
  91. package/esm/components/CellNavigationDomSync.js +80 -0
  92. package/esm/components/CellTextEditorGateway.js +275 -0
  93. package/esm/components/ColResizer.js +60 -0
  94. package/esm/components/EditorPortalRoot.js +57 -0
  95. package/esm/components/GridOptionalSurfaces.js +10 -0
  96. package/esm/components/GripVertical.js +11 -0
  97. package/esm/components/Loading.js +24 -0
  98. package/esm/components/Pagination.js +44 -0
  99. package/esm/components/PluginCellEditor.js +110 -0
  100. package/esm/components/RowSelector.js +38 -0
  101. package/esm/components/Table.js +2347 -0
  102. package/esm/components/TableBody.js +315 -0
  103. package/esm/components/TableBodyCell.js +73 -0
  104. package/esm/components/TableBodyFrozen.js +6 -0
  105. package/esm/components/TableColGroup.js +10 -0
  106. package/esm/components/TableColGroupFrozen.js +19 -0
  107. package/esm/components/TableFooter.js +30 -0
  108. package/esm/components/TableHead.js +114 -0
  109. package/esm/components/TableHeadColumn.js +86 -0
  110. package/esm/components/TableHeadFrozen.js +102 -0
  111. package/esm/components/TableSummary.js +66 -0
  112. package/esm/components/TableSummaryFronzen.js +62 -0
  113. package/esm/components/columnSortableRuntime.js +38 -0
  114. package/esm/components/context-menu/GridContextMenu.js +115 -0
  115. package/esm/components/context-menu/index.js +1 -0
  116. package/esm/components/scrollbar/CustomScrollbar.js +21 -0
  117. package/esm/components/scrollbar/ScrollbarButton.js +32 -0
  118. package/esm/components/scrollbar/ScrollbarTrack.js +66 -0
  119. package/esm/components/scrollbar/index.js +4 -0
  120. package/esm/components/scrollbar/useScrollbarMetrics.js +74 -0
  121. package/esm/components/search/GridSearchController.js +98 -0
  122. package/esm/components/search/GridSearchPopover.js +109 -0
  123. package/esm/components/search/index.js +2 -0
  124. package/esm/components/selection/CellSelectionOverlay.js +5 -0
  125. package/esm/components/selection/CellSelectionOverlayLayer.js +28 -0
  126. package/esm/components/selection/index.js +2 -0
  127. package/esm/components/toolbox/TableHeadToolbox.js +223 -0
  128. package/esm/components/toolbox/ToolboxCustomSection.js +18 -0
  129. package/esm/components/toolbox/ToolboxNumberFilterSection.js +98 -0
  130. package/esm/components/toolbox/ToolboxSortSection.js +31 -0
  131. package/esm/components/toolbox/ToolboxTextFilterSection.js +61 -0
  132. package/esm/components/toolbox/ToolboxValueFilterSection.js +195 -0
  133. package/esm/components/toolbox/index.js +6 -0
  134. package/esm/components/useColumnSortable.js +33 -0
  135. package/esm/editors/createDateEditorPlugin.js +48 -0
  136. package/esm/editors/createSelectEditorPlugin.js +60 -0
  137. package/esm/editors/defineEditorPlugin.js +6 -0
  138. package/esm/editors/index.js +3 -0
  139. package/esm/index.js +3 -0
  140. package/esm/store/createAppStore.js +1483 -0
  141. package/esm/store/index.js +1 -0
  142. package/esm/types.js +11 -0
  143. package/esm/utils/buildHeaderMatrix.js +231 -0
  144. package/esm/utils/cellEditState.js +32 -0
  145. package/esm/utils/cellEditTransaction.js +96 -0
  146. package/esm/utils/cellSelectionGeometry.js +166 -0
  147. package/esm/utils/common.js +145 -0
  148. package/esm/utils/coordinate.js +112 -0
  149. package/esm/utils/createPivotData.js +226 -0
  150. package/esm/utils/delay.js +1 -0
  151. package/esm/utils/filterData.js +176 -0
  152. package/esm/utils/getCellSelectionAxisState.js +34 -0
  153. package/esm/utils/getCellValue.js +18 -0
  154. package/esm/utils/getColumnId.js +52 -0
  155. package/esm/utils/getFrozenColumnsWidth.js +16 -0
  156. package/esm/utils/getLineNumber.js +3 -0
  157. package/esm/utils/getVisibleScrollableRowRange.js +20 -0
  158. package/esm/utils/gridSearch.js +127 -0
  159. package/esm/utils/index.js +23 -0
  160. package/esm/utils/mergedCells.js +70 -0
  161. package/esm/utils/mouseEventSubscribe.js +113 -0
  162. package/esm/utils/number/index.js +3 -0
  163. package/esm/utils/number/toFixed.js +3 -0
  164. package/esm/utils/number/toMoney.js +10 -0
  165. package/esm/utils/number/toNumber.js +9 -0
  166. package/esm/utils/processDataQuery.js +119 -0
  167. package/esm/utils/rowReorder.js +85 -0
  168. package/esm/utils/scrollbar.js +51 -0
  169. package/esm/utils/updateDataQuery.js +130 -0
  170. package/esm/utils/useBodyData.js +153 -0
  171. package/esm/utils/useForceUpdate.js +5 -0
  172. package/esm/utils/useRowReorderController.js +603 -0
  173. package/package.json +54 -0
  174. package/style.css +2508 -0
  175. package/types/BGrid.d.ts +3 -0
  176. package/types/components/CellEditorIcon.d.ts +13 -0
  177. package/types/components/CellNavigationDomSync.d.ts +17 -0
  178. package/types/components/CellTextEditorGateway.d.ts +20 -0
  179. package/types/components/ColResizer.d.ts +12 -0
  180. package/types/components/EditorPortalRoot.d.ts +8 -0
  181. package/types/components/GridOptionalSurfaces.d.ts +10 -0
  182. package/types/components/GripVertical.d.ts +6 -0
  183. package/types/components/Loading.d.ts +7 -0
  184. package/types/components/Pagination.d.ts +5 -0
  185. package/types/components/PluginCellEditor.d.ts +13 -0
  186. package/types/components/RowSelector.d.ts +10 -0
  187. package/types/components/Table.d.ts +61 -0
  188. package/types/components/TableBody.d.ts +39 -0
  189. package/types/components/TableBodyCell.d.ts +20 -0
  190. package/types/components/TableBodyFrozen.d.ts +15 -0
  191. package/types/components/TableColGroup.d.ts +3 -0
  192. package/types/components/TableColGroupFrozen.d.ts +5 -0
  193. package/types/components/TableFooter.d.ts +9 -0
  194. package/types/components/TableHead.d.ts +21 -0
  195. package/types/components/TableHeadColumn.d.ts +8 -0
  196. package/types/components/TableHeadFrozen.d.ts +7 -0
  197. package/types/components/TableSummary.d.ts +13 -0
  198. package/types/components/TableSummaryFronzen.d.ts +7 -0
  199. package/types/components/columnSortableRuntime.d.ts +12 -0
  200. package/types/components/context-menu/GridContextMenu.d.ts +6 -0
  201. package/types/components/context-menu/index.d.ts +1 -0
  202. package/types/components/scrollbar/CustomScrollbar.d.ts +11 -0
  203. package/types/components/scrollbar/ScrollbarButton.d.ts +9 -0
  204. package/types/components/scrollbar/ScrollbarTrack.d.ts +11 -0
  205. package/types/components/scrollbar/index.d.ts +4 -0
  206. package/types/components/scrollbar/useScrollbarMetrics.d.ts +12 -0
  207. package/types/components/search/GridSearchController.d.ts +1 -0
  208. package/types/components/search/GridSearchPopover.d.ts +6 -0
  209. package/types/components/search/index.d.ts +2 -0
  210. package/types/components/selection/CellSelectionOverlay.d.ts +17 -0
  211. package/types/components/selection/CellSelectionOverlayLayer.d.ts +18 -0
  212. package/types/components/selection/index.d.ts +2 -0
  213. package/types/components/toolbox/TableHeadToolbox.d.ts +13 -0
  214. package/types/components/toolbox/ToolboxCustomSection.d.ts +11 -0
  215. package/types/components/toolbox/ToolboxNumberFilterSection.d.ts +8 -0
  216. package/types/components/toolbox/ToolboxSortSection.d.ts +8 -0
  217. package/types/components/toolbox/ToolboxTextFilterSection.d.ts +8 -0
  218. package/types/components/toolbox/ToolboxValueFilterSection.d.ts +8 -0
  219. package/types/components/toolbox/index.d.ts +6 -0
  220. package/types/components/useColumnSortable.d.ts +13 -0
  221. package/types/editors/createDateEditorPlugin.d.ts +8 -0
  222. package/types/editors/createSelectEditorPlugin.d.ts +15 -0
  223. package/types/editors/defineEditorPlugin.d.ts +2 -0
  224. package/types/editors/index.d.ts +3 -0
  225. package/types/index.d.ts +3 -0
  226. package/types/store/createAppStore.d.ts +13 -0
  227. package/types/store/index.d.ts +1 -0
  228. package/types/types.d.ts +864 -0
  229. package/types/utils/buildHeaderMatrix.d.ts +32 -0
  230. package/types/utils/cellEditState.d.ts +12 -0
  231. package/types/utils/cellEditTransaction.d.ts +14 -0
  232. package/types/utils/cellSelectionGeometry.d.ts +34 -0
  233. package/types/utils/common.d.ts +3 -0
  234. package/types/utils/coordinate.d.ts +29 -0
  235. package/types/utils/createPivotData.d.ts +12 -0
  236. package/types/utils/delay.d.ts +1 -0
  237. package/types/utils/filterData.d.ts +18 -0
  238. package/types/utils/getCellSelectionAxisState.d.ts +12 -0
  239. package/types/utils/getCellValue.d.ts +2 -0
  240. package/types/utils/getColumnId.d.ts +19 -0
  241. package/types/utils/getFrozenColumnsWidth.d.ts +12 -0
  242. package/types/utils/getLineNumber.d.ts +6 -0
  243. package/types/utils/getVisibleScrollableRowRange.d.ts +20 -0
  244. package/types/utils/gridSearch.d.ts +18 -0
  245. package/types/utils/index.d.ts +23 -0
  246. package/types/utils/mergedCells.d.ts +14 -0
  247. package/types/utils/mouseEventSubscribe.d.ts +18 -0
  248. package/types/utils/number/index.d.ts +3 -0
  249. package/types/utils/number/toFixed.d.ts +1 -0
  250. package/types/utils/number/toMoney.d.ts +1 -0
  251. package/types/utils/number/toNumber.d.ts +1 -0
  252. package/types/utils/processDataQuery.d.ts +19 -0
  253. package/types/utils/rowReorder.d.ts +24 -0
  254. package/types/utils/scrollbar.d.ts +13 -0
  255. package/types/utils/updateDataQuery.d.ts +28 -0
  256. package/types/utils/useBodyData.d.ts +9 -0
  257. package/types/utils/useForceUpdate.d.ts +1 -0
  258. package/types/utils/useRowReorderController.d.ts +19 -0
package/README.md ADDED
@@ -0,0 +1,977 @@
1
+ # BeautifulGrid
2
+
3
+ **Beautiful. Powerful. Naturally React.**
4
+
5
+ [![NPM version](https://img.shields.io/npm/v/beautiful-grid.svg?style=flat)](https://npmjs.org/package/beautiful-grid)
6
+ [![NPM downloads](https://img.shields.io/npm/dm/beautiful-grid.svg?style=flat)](https://npmjs.org/package/beautiful-grid)
7
+ [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
+
9
+ BeautifulGrid is a beautiful, powerful, open-source React Data Grid for data-heavy business applications. It combines polished defaults with editing, sorting, filtering, selection, merging, aggregation, pivoting, and virtual scrolling.
10
+
11
+ Explore the live examples and documentation at [bgrid.axisj.com](https://bgrid.axisj.com).
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm i beautiful-grid
17
+ ```
18
+
19
+ ## Development
20
+
21
+ ```bash
22
+ npm i
23
+ npm run dev
24
+ ```
25
+
26
+ Open [http://localhost:5173](http://localhost:5173) with your browser to see the demo app.
27
+
28
+ - [BGrid component structure](docs/component-structure.md)
29
+ - [Cell selection and clipboard](docs/cell-selection.md)
30
+
31
+ ## Testing
32
+
33
+ ```bash
34
+ # Unit tests (Vitest)
35
+ npm test
36
+
37
+ # Unit tests in watch mode
38
+ npm run test:watch
39
+
40
+ # Consumer compatibility test (install + cjs/esm/types check)
41
+ npm run test:library:consumers
42
+
43
+ # Published ESM initial bundle budget and generated homepage metrics
44
+ npm run test:library:bundle
45
+
46
+ # E2E tests (Playwright)
47
+ npm run test:e2e
48
+
49
+ # E2E tests with UI runner
50
+ npm run test:e2e:ui
51
+ ```
52
+
53
+ ## Build
54
+
55
+ ```bash
56
+ # Demo app bundle (Vite)
57
+ npm run build
58
+
59
+ # Publishable library bundle (CJS + ESM + types + style)
60
+ npm run build:library
61
+
62
+ # Recalculate the published-library bundle metrics used by the website
63
+ npm run update:library:bundle-metrics
64
+ ```
65
+
66
+ The published ESM build targets ES2020 and loads column reordering, the column toolbox, search, and context menus only when they are used. The CommonJS build remains downleveled to ES5. Bundle measurements model an external ESM consumer and exclude the `react` and `react-dom` peer dependencies.
67
+
68
+ ## Publish
69
+
70
+ ```bash
71
+ # Build dist/cjs, dist/esm, dist/types and dist/package.json
72
+ npm run build:library
73
+
74
+ # Dry-run package file list from dist
75
+ npm run pack:library
76
+
77
+ # Publish a stable package manually
78
+ npm run publish:library
79
+
80
+ # Publish a prerelease without moving the latest tag
81
+ npm publish ./dist --access public --tag next
82
+ ```
83
+
84
+ ### Release checks
85
+
86
+ - Pushes to `main` run the GitHub Actions test workflow.
87
+ - Build and publish `dist` manually after the pushed commit passes CI.
88
+ - Prerelease versions must use the npm `next` tag so `latest` remains on the stable release.
89
+ - The production website deploy runs manually from `.github/workflows/deploy-website.yml` on `main`.
90
+
91
+ ## License
92
+
93
+ BeautifulGrid is open-source software licensed under the [Apache License 2.0](LICENSE). See [NOTICE](NOTICE) for attribution and [TRADEMARK.md](TRADEMARK.md) for use of the BeautifulGrid name and branding.
94
+
95
+ ## Styling
96
+
97
+ Import the library stylesheet once in your app:
98
+
99
+ ```typescript jsx
100
+ import 'beautiful-grid/style.css';
101
+ ```
102
+
103
+ `beautiful-grid` ships with pure static CSS and requires no runtime CSS-in-JS engine. The grid uses CSS custom properties (`--bgrid-*`) scoped to `[role='grid']`, allowing you to easily customize the theme globally or for a specific wrapper element.
104
+
105
+ We recommend overriding the public `--bgrid-*` CSS variables rather than targeting internal `.bgrid-*` classes directly to maintain upgrade compatibility across releases.
106
+
107
+ Default variables:
108
+
109
+ ```css
110
+ [role='grid'] {
111
+ --bgrid-primary-color: #3b82f6;
112
+ --bgrid-header-bg: #f3f4f5;
113
+ --bgrid-header-color: #222;
114
+ --bgrid-header-font-weight: 500;
115
+ --bgrid-header-hover-bg: #e2e5e5;
116
+ --bgrid-header-group-bg: #e9e9e9;
117
+ --bgrid-footer-bg: #f3f4f5;
118
+ --bgrid-summary-bg: #eaeef6;
119
+ --bgrid-border-color-base: #d2d5d9;
120
+ --bgrid-border-color-light: #d2d5d9;
121
+ --bgrid-border-color-subtle: #e8ebef;
122
+ --bgrid-header-separator-color: #dde2e8;
123
+ --bgrid-frozen-boundary-color: #b8bec5;
124
+ --bgrid-frozen-boundary-width: 2px;
125
+ --bgrid-border-radius: 4px;
126
+ --bgrid-row-selector-color: #ffffff;
127
+ --bgrid-body-bg: #ffffff;
128
+ --bgrid-body-odd-bg: #f8f8f8;
129
+ --bgrid-body-hover-bg: #f3f4f5;
130
+ --bgrid-body-hover-odd-bg: #eeeeee;
131
+ --bgrid-body-active-bg: #e6f6ff;
132
+ --bgrid-cell-selected-bg: var(--bgrid-body-active-bg);
133
+ --bgrid-cell-selected-overlay-opacity: 0.72;
134
+ --bgrid-cell-selected-border-color: rgba(59, 130, 246, 0.78);
135
+ --bgrid-active-cell-bg: var(--bgrid-body-bg);
136
+ --bgrid-active-cell-ring-color: var(--bgrid-primary-color);
137
+ --bgrid-active-cell-ring-width: 2px;
138
+ --bgrid-cell-selected-border-width: var(--bgrid-active-cell-ring-width);
139
+ --bgrid-selection-axis-bg: #dbeafe;
140
+ --bgrid-selection-axis-color: var(--bgrid-primary-color);
141
+ --bgrid-selection-axis-border-color: var(--bgrid-primary-color);
142
+ --bgrid-cell-edited-bg: #fff7ed;
143
+ --bgrid-cell-edited-color: #c2410c;
144
+ --bgrid-cell-edited-border-color: #fdba74;
145
+ --bgrid-cell-value-changed-bg: var(--bgrid-cell-edited-bg);
146
+ --bgrid-cell-value-changed-color: var(--bgrid-cell-edited-color);
147
+ --bgrid-cell-value-changed-border-color: var(--bgrid-cell-edited-border-color);
148
+ --bgrid-body-color: #444;
149
+
150
+ --bgrid-toolbox-bg: #ffffff;
151
+ --bgrid-toolbox-color: #444444;
152
+ --bgrid-toolbox-muted-color: #64748b;
153
+ --bgrid-toolbox-control-bg: #ffffff;
154
+ --bgrid-toolbox-control-color: #444444;
155
+ --bgrid-toolbox-control-border-color: #d2d5d9;
156
+ --bgrid-toolbox-control-placeholder-color: #94a3b8;
157
+ --bgrid-toolbox-hover-bg: #f3f4f5;
158
+ --bgrid-toolbox-active-bg: #e6f6ff;
159
+ --bgrid-toolbox-danger-color: #dc2626;
160
+ --bgrid-toolbox-danger-bg: #fef2f2;
161
+ --bgrid-toolbox-button-bg: #f8fafc;
162
+ --bgrid-toolbox-primary-hover-color: #2563eb;
163
+ --bgrid-toolbox-primary-contrast-color: #ffffff;
164
+ --bgrid-toolbox-notice-bg: #f8fafc;
165
+ --bgrid-toolbox-scroll-thumb-bg: #c7ccda;
166
+ --bgrid-toolbox-scroll-track-bg: #f6f6f6;
167
+ --bgrid-toolbox-focus-ring-color: #bfdbfe;
168
+
169
+ --bgrid-search-bg: #ffffff;
170
+ --bgrid-search-color: #334155;
171
+ --bgrid-search-border-color: #d4dce8;
172
+ --bgrid-search-control-bg: #f8fafc;
173
+ --bgrid-search-control-color: #1f2937;
174
+ --bgrid-search-control-border-color: #cbd5e1;
175
+ --bgrid-search-muted-color: #64748b;
176
+ --bgrid-search-focus-ring-color: #3b82f6;
177
+ --bgrid-search-button-hover-bg: #f1f5f9;
178
+ --bgrid-search-match-bg: rgba(250, 204, 21, 0.28);
179
+ --bgrid-search-match-border-color: #ca8a04;
180
+ --bgrid-search-current-bg: rgba(249, 115, 22, 0.3);
181
+ --bgrid-search-current-border-color: #f97316;
182
+ --bgrid-context-menu-bg: var(--bgrid-toolbox-bg);
183
+ --bgrid-context-menu-color: var(--bgrid-toolbox-color);
184
+ --bgrid-context-menu-border-color: var(--bgrid-toolbox-control-border-color);
185
+ --bgrid-context-menu-hover-bg: var(--bgrid-toolbox-hover-bg);
186
+ --bgrid-context-menu-muted-color: var(--bgrid-toolbox-muted-color);
187
+ --bgrid-floating-z-editor: 9999;
188
+ --bgrid-floating-z-toolbox: 20;
189
+ --bgrid-floating-z-context-menu: 30;
190
+ --bgrid-search-z-index: 31;
191
+
192
+ --bgrid-scroll-size: 11px;
193
+ --bgrid-scroll-bg: #ffffff;
194
+ --bgrid-scroll-track-bg: #f6f6f6;
195
+ --bgrid-scroll-thumb-radius: 100px;
196
+ --bgrid-scroll-thumb-bg: #c7ccda;
197
+ --bgrid-scroll-thumb-hover-bg: #a1a3a6;
198
+ --bgrid-scroll-corner-bg: #c7ccda;
199
+ --bgrid-scroll-corner-radius: 5px;
200
+
201
+ --bgrid-loading-bg: rgba(163, 163, 163, 0.1);
202
+ --bgrid-loading-color: rgba(0, 0, 0, 0.1);
203
+ --bgrid-loading-second-color: #767676;
204
+
205
+ --bgrid-page-number-active-border-radius: 4px;
206
+ }
207
+ ```
208
+
209
+ Common customization targets:
210
+
211
+ | Area | Variables |
212
+ | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
213
+ | Primary accents | `--bgrid-primary-color` |
214
+ | Header | `--bgrid-header-bg`, `--bgrid-header-color`, `--bgrid-header-font-weight`, `--bgrid-header-hover-bg`, `--bgrid-header-group-bg`, `--bgrid-header-separator-color` |
215
+ | Borders | `--bgrid-border-color-base`, `--bgrid-border-color-light`, `--bgrid-border-color-subtle`, `--bgrid-frozen-boundary-color`, `--bgrid-frozen-boundary-width`, `--bgrid-border-radius` |
216
+ | Body rows | `--bgrid-body-bg`, `--bgrid-body-odd-bg`, `--bgrid-body-hover-bg`, `--bgrid-body-hover-odd-bg`, `--bgrid-body-active-bg`, `--bgrid-body-color` |
217
+ | Cell selection | `--bgrid-cell-selected-bg`, `--bgrid-cell-selected-overlay-opacity`, `--bgrid-cell-selected-border-color`, `--bgrid-cell-selected-border-width`, `--bgrid-active-cell-bg`, `--bgrid-active-cell-ring-color`, `--bgrid-active-cell-ring-width` |
218
+ | Selection axes | `--bgrid-selection-axis-bg`, `--bgrid-selection-axis-color`, `--bgrid-selection-axis-border-color` |
219
+ | Edited cells | `--bgrid-cell-edited-bg`, `--bgrid-cell-edited-color`, `--bgrid-cell-edited-border-color` |
220
+ | Changed values | `--bgrid-cell-value-changed-bg`, `--bgrid-cell-value-changed-color`, `--bgrid-cell-value-changed-border-color` |
221
+ | Summary/Footer | `--bgrid-summary-bg`, `--bgrid-footer-bg` |
222
+ | Filter toolbox | `--bgrid-toolbox-bg`, `--bgrid-toolbox-color`, `--bgrid-toolbox-muted-color`, `--bgrid-toolbox-control-*`, `--bgrid-toolbox-hover-bg`, `--bgrid-toolbox-active-bg`, `--bgrid-toolbox-danger-*`, `--bgrid-toolbox-button-bg`, `--bgrid-toolbox-primary-*`, `--bgrid-toolbox-notice-bg`, `--bgrid-toolbox-scroll-*`, `--bgrid-toolbox-focus-ring-color` |
223
+ | Grid search | `--bgrid-search-bg`, `--bgrid-search-color`, `--bgrid-search-border-color`, `--bgrid-search-control-*`, `--bgrid-search-muted-color`, `--bgrid-search-focus-ring-color`, `--bgrid-search-button-hover-bg`, `--bgrid-search-match-*`, `--bgrid-search-current-*`, `--bgrid-search-z-index` |
224
+ | Context menu | `--bgrid-context-menu-bg`, `--bgrid-context-menu-color`, `--bgrid-context-menu-border-color`, `--bgrid-context-menu-hover-bg`, `--bgrid-context-menu-muted-color`, `--bgrid-floating-z-*` |
225
+ | Scrollbars | `--bgrid-scroll-size`, `--bgrid-scroll-bg`, `--bgrid-scroll-track-bg`, `--bgrid-scroll-thumb-radius`, `--bgrid-scroll-thumb-bg`, `--bgrid-scroll-thumb-hover-bg`, `--bgrid-scroll-corner-bg`, `--bgrid-scroll-corner-radius` |
226
+ | Loading overlay | `--bgrid-loading-bg`, `--bgrid-loading-color`, `--bgrid-loading-second-color` |
227
+ | Pagination | `--bgrid-page-number-active-border-radius` |
228
+
229
+ `selectedRowKey` row highlighting uses `--bgrid-body-active-bg`. Cell selection is painted by pointer-transparent overlay rectangles rather than per-cell borders. The overlay uses `--bgrid-cell-selected-bg` for the fill, `--bgrid-cell-selected-overlay-opacity` for fill opacity, and `--bgrid-cell-selected-border-color` for the selection edge. By default, `--bgrid-cell-selected-bg` references `--bgrid-body-active-bg` so row selection and cell selection stay in the same color tone.
230
+
231
+ When a range crosses frozen rows or columns, the Grid splits the overlay into the corresponding panel fragments and draws only the outside perimeter. This keeps the selection rectangular across merged cells and synchronized with horizontal and vertical scrolling.
232
+
233
+ The focused cell uses `--bgrid-active-cell-bg`. A single-cell selection also uses the inset ring controlled by `--bgrid-active-cell-ring-color` and `--bgrid-active-cell-ring-width`. During multi-cell selection the focused cell has no individual ring; only the outer border of the complete selection range is rendered. Its width is controlled by `--bgrid-cell-selected-border-width`, which defaults to the single-cell ring width.
234
+
235
+ The column headers and line numbers covered by the active cell or a multi-cell range receive `bgrid-column-axis-active` and `bgrid-row-axis-active`. Their fill, text, and inset axis line use the three `--bgrid-selection-axis-*` variables.
236
+
237
+ Cells directly changed by a text editor, plugin editor, legacy inline editor, or clipboard paste receive `bgrid-cell-edited` and `data-bgrid-cell-edited="true"`. The row wrapper records those stable column ids in `editedColumnIds`. It separately records normalized data key tokens in `changedKeys`. Every column sharing a changed key receives `bgrid-cell-value-changed` and `data-bgrid-cell-value-changed="true"`, even when only one column instance was directly edited. Clear both arrays after a successful commit. The two states use the `--bgrid-cell-edited-*` and `--bgrid-cell-value-changed-*` palettes; the changed-value palette defaults to the edited-cell palette.
238
+
239
+ The filter toolbox, editor plugins, and cell context menu share a document-level floating portal per Grid instance. The Grid copies its public `--bgrid-*` variables to that portal, so instance-level themes continue to apply outside the Grid DOM subtree.
240
+
241
+ ### Grid Search and Cell Context Menu
242
+
243
+ Pass `searchOptions` to enable search over all currently loaded display rows. The Grid searches Store data rather than rendered DOM, highlights every matching cell, and scrolls virtual or frozen regions when moving between results.
244
+
245
+ Dedicated context menu example: [`examples/ContextMenuExample.tsx`](./examples/ContextMenuExample.tsx)
246
+
247
+ ```tsx
248
+ <BGrid
249
+ columns={columns}
250
+ data={data}
251
+ rowKey='id'
252
+ searchOptions={{}}
253
+ contextMenuOptions={{
254
+ items: target => [
255
+ {
256
+ id: 'inspect-row',
257
+ label: 'Inspect row',
258
+ onSelect: () => console.log(target.sourceIndex, target.values),
259
+ },
260
+ ],
261
+ }}
262
+ />
263
+ ```
264
+
265
+ - `Ctrl+F` / `Cmd+F`: open search in the focused Grid.
266
+ - `Enter` / `Shift+Enter`: next / previous match.
267
+ - `Escape`: close search and clear highlights.
268
+ - Right-click: activate and select the target cell before opening the body-cell context menu.
269
+ - Selecting another cell closes the open context menu.
270
+ - `Shift+F10`: open the context menu for the active cell.
271
+ - Search covers only rows currently supplied to the Grid. Server-wide search and result filtering are separate concerns.
272
+ - Searchable text defaults to the value read from `item.values`. Use column `getSearchText` when `itemRender` displays a formatted value, or `searchable: false` to exclude a column.
273
+
274
+ Controlled `searchOptions.open` and `searchOptions.query` require their matching callbacks:
275
+
276
+ ```tsx
277
+ searchOptions={{
278
+ open,
279
+ query,
280
+ onOpenChange: setOpen,
281
+ onQueryChange: setQuery,
282
+ }}
283
+ ```
284
+
285
+ Example: scope a custom blue selection theme to one grid wrapper.
286
+
287
+ ```css
288
+ .my-grid [role='grid'] {
289
+ --bgrid-primary-color: #2563eb;
290
+ --bgrid-body-active-bg: #e0f2fe;
291
+ --bgrid-cell-selected-bg: var(--bgrid-body-active-bg);
292
+ --bgrid-cell-selected-overlay-opacity: 0.72;
293
+ --bgrid-cell-selected-border-color: rgba(37, 99, 235, 0.72);
294
+ }
295
+ ```
296
+
297
+ ## Usage
298
+
299
+ - codesandbox DEMO : https://codesandbox.io/p/devbox/basic-example-5ch6kt?embed=1&file=%2Fsrc%2FApp.tsx
300
+
301
+ ### Basic Example
302
+
303
+ ```typescript jsx
304
+ import * as React from 'react';
305
+ import { BGrid, BGridColumn } from 'beautiful-grid';
306
+
307
+ interface IListItem {
308
+ id: string;
309
+ title: string;
310
+ writer: string;
311
+ createAt: string;
312
+ }
313
+
314
+ const list = Array.from({ length: 1000 }).map((_, i) => ({
315
+ values: {
316
+ id: `ID_${i}`,
317
+ title: `title_${i}`,
318
+ writer: `writer_${i}`,
319
+ createAt: `2022-09-08`,
320
+ },
321
+ }));
322
+
323
+ function BasicExample() {
324
+ const [columns, setColumns] = React.useState<BGridColumn<IListItem>[]>([
325
+ { key: 'id', label: 'ID', width: 120 },
326
+ {
327
+ key: 'title',
328
+ label: '제목',
329
+ width: 260,
330
+ itemRender: ({ values }) => (
331
+ <>
332
+ {values.writer} / {values.title}
333
+ </>
334
+ ),
335
+ },
336
+ { key: 'writer', label: '작성자', width: 120 },
337
+ { key: 'createAt', label: '작성일', width: 140 },
338
+ ]);
339
+
340
+ const [checkedRowKeys, setCheckedRowKeys] = React.useState<React.Key[]>([]);
341
+
342
+ return (
343
+ <div style={{ fontSize: 13 }}>
344
+ <BGrid<IListItem>
345
+ width={720}
346
+ height={420}
347
+ data={list}
348
+ columns={columns}
349
+ rowKey={'id'}
350
+ onChangeColumns={(columnIndex, { width, columns }) => {
351
+ console.log('onChangeColumns', columnIndex, width, columns);
352
+ setColumns(columns);
353
+ }}
354
+ rowChecked={{
355
+ checkedRowKeys,
356
+ onChange: (checkedIndexes, checkedRowKeys, checkedAll) => {
357
+ console.log('rowChecked changed', checkedIndexes, checkedRowKeys, checkedAll);
358
+ setCheckedRowKeys(checkedRowKeys);
359
+ },
360
+ }}
361
+ />
362
+ </div>
363
+ );
364
+ }
365
+
366
+ export default BasicExample;
367
+ ```
368
+
369
+ ### Important Data/Column Rules
370
+
371
+ - Row data type is `BGridDataItem<T>` and the real domain model is always inside `item.values`.
372
+ - `BGridColumn.key` supports both `string` and `string[]`.
373
+ - `key: 'writer'`
374
+ - `key: ['user', 'profile', 'name']` (nested value path)
375
+ - `itemRender` receives both:
376
+ - `value`: current cell value by `column.key`
377
+ - `values`: full row model (`T`)
378
+ - `columns[i].left` is internal layout value computed by `BGrid`; do not manage it manually.
379
+
380
+ ### Header Toolbox: Sorting and Filtering
381
+
382
+ Add `toolbox` to a column and control the applied sort/filter state through `dataControl.query`. Query state is controlled: `onChange` receives a new query, and the consumer must pass that query back to the Grid.
383
+
384
+ ```typescript jsx
385
+ import * as React from 'react';
386
+ import { BGrid, BGridColumn, BGridDataItem, BGridDataQuery } from 'beautiful-grid';
387
+
388
+ interface Product {
389
+ id: number;
390
+ name: string;
391
+ category: string;
392
+ price: number;
393
+ }
394
+
395
+ const columns: BGridColumn<Product>[] = [
396
+ {
397
+ id: 'name',
398
+ key: 'name',
399
+ label: 'Name',
400
+ width: 220,
401
+ toolbox: true,
402
+ filter: { type: 'text' },
403
+ },
404
+ {
405
+ id: 'category',
406
+ key: 'category',
407
+ label: 'Category',
408
+ width: 140,
409
+ toolbox: true,
410
+ filter: { type: 'values' },
411
+ },
412
+ {
413
+ id: 'price',
414
+ key: 'price',
415
+ label: 'Price',
416
+ width: 120,
417
+ toolbox: { sort: true, filter: true },
418
+ filter: { type: 'number' },
419
+ },
420
+ ];
421
+
422
+ function ProductGrid({ data }: { data: BGridDataItem<Product>[] }) {
423
+ const [query, setQuery] = React.useState<BGridDataQuery>({
424
+ sortParams: [],
425
+ filterParams: [],
426
+ });
427
+
428
+ return (
429
+ <BGrid<Product>
430
+ width={720}
431
+ height={420}
432
+ data={data}
433
+ columns={columns}
434
+ rowKey='id'
435
+ dataControl={{
436
+ mode: 'client',
437
+ multiSort: true,
438
+ query,
439
+ onChange: nextQuery => setQuery(nextQuery),
440
+ }}
441
+ />
442
+ );
443
+ }
444
+ ```
445
+
446
+ Toolbox configuration:
447
+
448
+ - `toolbox: true` enables the built-in sort and filter sections.
449
+ - `toolbox: { sort, filter, extraItems, render, icons }` configures sections and custom content. Object form enables only sections explicitly set to `true`.
450
+ - `filter.type` supports `values`, `text`, and `number`. Use `filter: false` to suppress the filter section.
451
+ - Give every Toolbox column a stable, unique `id`, especially when multiple columns read the same `key`.
452
+ - Grid-level `icons` supplies default Toolbox icons. A column-level `toolbox.icons` overrides them for that column.
453
+
454
+ Data modes:
455
+
456
+ - `mode: 'client'` filters and sorts the complete `data` array inside the Grid. Do not combine it with externally paged data as if the current page were the complete data set.
457
+ - `mode: 'manual'` only emits the next query. Fetch, filter, sort, and paginate on the server, then pass the resulting `data` back. For a manual `values` filter, provide `filter.values` because the Grid must not infer the full server-side value domain from one page.
458
+ - `mode` defaults to `manual`. Set it explicitly when shared configuration should make the processing location obvious.
459
+ - `dataControl` takes precedence over the legacy `sort` prop. Existing grids can continue using `sort` when Toolbox filtering is not needed.
460
+
461
+ Manual/server-controlled example:
462
+
463
+ ```typescript jsx
464
+ <BGrid
465
+ width={720}
466
+ height={420}
467
+ data={serverPage}
468
+ columns={columns}
469
+ page={page}
470
+ dataControl={{
471
+ mode: 'manual',
472
+ query,
473
+ onChange: (nextQuery, event) => {
474
+ setQuery(nextQuery);
475
+ requestPage({ page: 0, query: nextQuery, changedBy: event });
476
+ },
477
+ }}
478
+ />
479
+ ```
480
+
481
+ Pivot mode disables the default Toolbox. Row reordering is automatically disabled while a client-side filter or sort is active because displayed row order no longer represents the source array order.
482
+
483
+ ### Row Reorder
484
+
485
+ Enable line numbers and `reorder` to move rows. The dedicated handle supports pointer drag and keyboard operation: focus it, press `Space` or `Enter` to pick up, use `ArrowUp`/`ArrowDown`, then press `Enter` to drop or `Escape` to cancel.
486
+
487
+ ```typescript jsx
488
+ <BGrid
489
+ data={data}
490
+ columns={columns}
491
+ rowKey='id'
492
+ showLineNumber
493
+ reorder={{
494
+ enabled: true,
495
+ onReorder: nextData => {
496
+ setData(nextData);
497
+ return true;
498
+ },
499
+ }}
500
+ />
501
+ ```
502
+
503
+ The Grid previews the source, shifted rows, and insertion target before changing data. It commits once after the settle motion. Returning `false` from the synchronous `onReorder` callback rolls the operation back; `true` or `void` keeps it. Checked rows and the active cell follow the moved item, while an open editor blocks reordering. Virtual scrolling supports edge auto-scroll and an off-screen source preview. Merged-cell grids use a safe preview fallback instead of transforming `rowspan` geometry. Reduced-motion preferences remove both the transition and its commit delay.
504
+
505
+ Row reordering is disabled for pivot output, frozen rows, and active client-side sort/filter queries. `onReorder` is not a Promise-based save lifecycle; applications that persist remotely should own their optimistic update and failure policy.
506
+
507
+ ### Cell Selection and Clipboard
508
+
509
+ Cell selection is enabled by default. Cells can be selected by mouse drag, and `Ctrl+C` or `Cmd+C` copies selected cell text using tab (`\t`) between columns and carriage return (`\r`) between rows. In an editable grid, select a target cell and press `Ctrl+V` or `Cmd+V` to paste tab/newline-delimited data from that cell. Disable the feature when it is not needed:
510
+
511
+ ```typescript jsx
512
+ <BGrid width={700} height={400} columns={columns} data={data} cellSelectionOptions={{ enabled: false }} />
513
+ ```
514
+
515
+ Cell selection is kept when the user clicks inside the grid, including empty body space or the grid scrollbar. By default, selection is cleared when `Escape` is pressed or when the user clicks outside the grid. Use `cellSelectionOptions` to change those clear rules:
516
+
517
+ ```typescript jsx
518
+ <BGrid
519
+ width={700}
520
+ height={400}
521
+ columns={columns}
522
+ data={data}
523
+ cellSelectionOptions={{
524
+ enabled: true,
525
+ clearOnEscape: false,
526
+ clearOnOutsideClick: false,
527
+ maxClipboardCells: 100000,
528
+ maxClipboardTextLength: 8 * 1024 * 1024,
529
+ onCopyError: error => {
530
+ console.warn(error);
531
+ },
532
+ onPasteError: error => {
533
+ console.warn(error);
534
+ },
535
+ createRowOnPaste: () => ({
536
+ values: createEmptyRow(),
537
+ }),
538
+ }}
539
+ />
540
+ ```
541
+
542
+ Clipboard copy is skipped before building clipboard text when the selected range is too large. The default limits are
543
+ `maxClipboardCells: 100000` and `maxClipboardTextLength: 8 * 1024 * 1024`. The same limits apply to paste. Use `onCopyError` or `onPasteError` to notify users when clipboard work is skipped, parsing fails, or the browser rejects the clipboard write.
544
+
545
+ Pasted data beyond the last row is clipped unless `createRowOnPaste` is provided. The callback creates each missing row, which is registered with `BGridDataItemStatus.new` and reported through `onChangeData(rowIndex, null, values, null)`.
546
+
547
+ By default, copied text is read from `column.key`:
548
+
549
+ ```typescript jsx
550
+ { key: 'title', label: 'Title', width: 240 }
551
+ ```
552
+
553
+ If a column renders a custom component or needs a different clipboard value, define `getClipboardText` on the column. `getClipboardText` is used before falling back to the `column.key` value.
554
+
555
+ ```typescript jsx
556
+ const columns: BGridColumn<IListItem>[] = [
557
+ {
558
+ key: 'title',
559
+ label: 'Title',
560
+ width: 260,
561
+ itemRender: ({ values }) => (
562
+ <>
563
+ {values.writer} / {values.title}
564
+ </>
565
+ ),
566
+ getClipboardText: ({ values }) => `${values.writer}\t${values.title}`,
567
+ },
568
+ {
569
+ key: ['user', 'profile', 'name'],
570
+ label: 'User',
571
+ width: 160,
572
+ getClipboardText: ({ value, values }) => value ?? values.writer,
573
+ },
574
+ ];
575
+ ```
576
+
577
+ ### Cell Focus and Keyboard Navigation
578
+
579
+ Click a cell to make it active, then use Arrow keys, Home/End, PageUp/PageDown, or Tab to move. `Shift` + Arrow extends the cell selection. `Ctrl`/`Cmd` + Arrow moves to an edge, and `Ctrl`/`Cmd` + Home/End moves to the first or last grid cell.
580
+
581
+ Use `cellNavigationOptions` when the active cell must be initialized or controlled by the application:
582
+
583
+ ```typescript jsx
584
+ const [activeCell, setActiveCell] = React.useState({ rowIndex: 0, columnIndex: 1 });
585
+
586
+ <BGrid
587
+ width={800}
588
+ height={420}
589
+ columns={columns}
590
+ data={data}
591
+ cellNavigationOptions={{
592
+ activeCell,
593
+ onActiveCellChange: cell => {
594
+ if (cell) setActiveCell(cell);
595
+ },
596
+ wrap: false,
597
+ editOnEnter: true,
598
+ keyRepeat: { interval: 16 },
599
+ }}
600
+ />;
601
+ ```
602
+
603
+ `F2` starts editing the active editable cell. `Enter` also starts editing when `editable` and `editOnEnter` are enabled; otherwise `Enter` and `Space` invoke the active cell's `onClick` callback without moving focus. Holding an Arrow key switches from the operating system repeat event to a frame-synchronized repeat loop; use `keyRepeat.interval` to tune the interval in milliseconds or `keyRepeat.enabled: false` to retain native repeat timing. Interactive elements such as inputs and buttons keep their own keyboard behavior. See the runnable [cell navigation example](examples/CellNavigationExample.tsx) and the [keyboard guide](site/src/content/learn/cell-navigation.md).
604
+
605
+ ### Pivot
606
+
607
+ Pass `pivot` to render the grid as a derived pivot table. The API follows the Excel-style field areas:
608
+
609
+ - `rows`: row fields
610
+ - `columns`: column fields
611
+ - `values`: value fields and aggregate rules
612
+
613
+ When `pivot` is active, row-based grid UI is disabled internally because the rendered rows are derived summary rows. This includes frozen columns, line numbers, row check controls, sorting, column sorting, editing, pagination, summary, cell merge, reorder, and row/cell change callbacks.
614
+
615
+ ```typescript jsx
616
+ import * as React from 'react';
617
+ import { BGrid, BGridColumn, BGridProps } from 'beautiful-grid';
618
+
619
+ interface SalesItem {
620
+ region: string;
621
+ product: string;
622
+ quarter: string;
623
+ sales: number;
624
+ quantity: number;
625
+ }
626
+
627
+ const data = [
628
+ { values: { region: 'North', product: 'Desk', quarter: 'Q1', sales: 1200, quantity: 12 } },
629
+ { values: { region: 'North', product: 'Desk', quarter: 'Q2', sales: 1400, quantity: 10 } },
630
+ { values: { region: 'South', product: 'Chair', quarter: 'Q1', sales: 900, quantity: 8 } },
631
+ ];
632
+
633
+ const columns: BGridColumn<SalesItem>[] = [
634
+ { key: 'region', label: 'Region', width: 120 },
635
+ { key: 'product', label: 'Product', width: 140 },
636
+ { key: 'quarter', label: 'Quarter', width: 100 },
637
+ { key: 'sales', label: 'Sales', width: 120, align: 'right' },
638
+ { key: 'quantity', label: 'Quantity', width: 120, align: 'right' },
639
+ ];
640
+
641
+ const pivot: BGridProps<SalesItem>['pivot'] = {
642
+ rows: [{ key: 'product', label: 'Product', width: 140 }],
643
+ columns: [{ key: 'region', label: 'Region' }],
644
+ values: [
645
+ {
646
+ key: 'sales',
647
+ label: 'Sales',
648
+ aggregate: 'sum',
649
+ itemRender: ({ value, columnValues, sourceItems }) => {
650
+ const text = `$${Number(value).toLocaleString()}`;
651
+ if (columnValues[0] === 'South') {
652
+ return <strong title={`${sourceItems.length} source rows`}>{text}</strong>;
653
+ }
654
+ return <span title={`${sourceItems.length} source rows`}>{text}</span>;
655
+ },
656
+ getClipboardText: ({ value }) => Number(value).toLocaleString(),
657
+ },
658
+ {
659
+ key: 'quantity',
660
+ label: 'Quantity',
661
+ aggregate: 'sum',
662
+ itemRender: ({ value }) => <>{Number(value).toLocaleString()} ea</>,
663
+ },
664
+ ],
665
+ emptyValue: 0,
666
+ };
667
+
668
+ function PivotExample() {
669
+ return (
670
+ <BGrid<SalesItem>
671
+ width={900}
672
+ height={420}
673
+ columns={columns}
674
+ data={data}
675
+ pivot={pivot}
676
+ variant={'vertical-bordered'}
677
+ />
678
+ );
679
+ }
680
+ ```
681
+
682
+ Built-in aggregate values:
683
+
684
+ | Aggregate | Description |
685
+ | --------- | --------------------------------- |
686
+ | `sum` | Numeric sum. This is the default. |
687
+ | `count` | Number of source values. |
688
+ | `avg` | Numeric average. |
689
+ | `min` | Numeric minimum. |
690
+ | `max` | Numeric maximum. |
691
+ | `first` | First source value. |
692
+
693
+ You can also pass a custom aggregate function:
694
+
695
+ ```typescript jsx
696
+ const pivot: BGridProps<SalesItem>['pivot'] = {
697
+ rows: [{ key: 'product', label: 'Product' }],
698
+ columns: [{ key: 'quarter', label: 'Quarter' }],
699
+ values: [
700
+ {
701
+ key: 'sales',
702
+ label: 'Max online sales',
703
+ aggregate: ({ items }) => {
704
+ return Math.max(...items.filter(item => item.values.region === 'North').map(item => item.values.sales));
705
+ },
706
+ },
707
+ ],
708
+ };
709
+ ```
710
+
711
+ `pivot.values[].itemRender` receives the normal cell render props plus pivot context:
712
+
713
+ | Prop | Description |
714
+ | -------------- | ----------------------------------------------- |
715
+ | `value` | Aggregated cell value |
716
+ | `values` | Pivot result row values |
717
+ | `rowValues` | Values for the current pivot row fields |
718
+ | `columnValues` | Values for the current pivot column fields |
719
+ | `sourceItems` | Original `BGridDataItem<T>[]` used for this cell |
720
+ | `pivotValue` | The current value-field definition |
721
+ | `aggregate` | The current aggregate setting |
722
+
723
+ ### Common Scenarios
724
+
725
+ #### 1) Frozen Columns
726
+
727
+ ```typescript jsx
728
+ <BGrid width={900} height={500} frozenColumnIndex={2} columns={columns} data={data} />
729
+ ```
730
+
731
+ `frozenColumnIndex` 이전 컬럼(0, 1)은 고정 영역으로 렌더링됩니다.
732
+
733
+ 행과 컬럼을 함께 고정할 수 있습니다. 상단 Summary Row를 사용하면 고정 행은 Summary 바로 다음 줄부터 시작합니다.
734
+
735
+ ```typescript jsx
736
+ <BGrid
737
+ width={900}
738
+ height={500}
739
+ frozenColumnIndex={2}
740
+ frozenRowCount={3}
741
+ summary={{ position: 'top', columns: summaryColumns }}
742
+ columns={columns}
743
+ data={data}
744
+ />
745
+ ```
746
+
747
+ #### 2) Editable Cell
748
+
749
+ ```typescript jsx
750
+ const columns: BGridColumn<IListItem>[] = [
751
+ {
752
+ key: 'title',
753
+ label: '제목',
754
+ width: 240,
755
+ editable: true,
756
+ editor: {
757
+ type: 'text',
758
+ startOnInput: true,
759
+ inputProps: { maxLength: 100, autoComplete: 'off' },
760
+ },
761
+ },
762
+ ];
763
+
764
+ <BGrid editable editTrigger={'dblclick'} columns={columns} data={data} width={700} height={400} />;
765
+ ```
766
+
767
+ The built-in text editor supports direct typing, IME composition, Enter/F2 preserve mode, Escape cancel,
768
+ and focus return after editing. Prebuilt Select and Date plugins are available from the editor subpath:
769
+
770
+ ```typescript jsx
771
+ import { createDateEditorPlugin, createSelectEditorPlugin } from 'beautiful-grid/editors';
772
+
773
+ const statusEditor = createSelectEditorPlugin<IListItem>({
774
+ id: 'status',
775
+ options: [
776
+ { value: 'ready', label: 'Ready' },
777
+ { value: 'done', label: 'Done' },
778
+ ],
779
+ });
780
+
781
+ const dateEditor = createDateEditorPlugin<IListItem>({ id: 'delivery-date' });
782
+ ```
783
+
784
+ Use `defineEditorPlugin()` from the same subpath to connect application-specific editors. Portal-based UI
785
+ components should mount their popup into the `getPortalContainer()` supplied to the plugin component.
786
+
787
+ #### 3) Sort + Page
788
+
789
+ ```typescript jsx
790
+ <BGrid
791
+ width={900}
792
+ height={560}
793
+ columns={columns}
794
+ data={rows}
795
+ sort={{
796
+ sortParams,
797
+ onChange: next => setSortParams(next),
798
+ }}
799
+ page={{
800
+ currentPage,
801
+ pageSize,
802
+ totalPages,
803
+ totalElements,
804
+ onChange: (nextPage, nextSize) => {
805
+ setCurrentPage(nextPage);
806
+ if (nextSize) setPageSize(nextSize);
807
+ },
808
+ }}
809
+ />
810
+ ```
811
+
812
+ ### Props Reference (BGrid)
813
+
814
+ 아래는 자주 사용하는 Props 중심 정리입니다. 타입의 최종 기준은 `beautiful-grid/types.ts`의 `BGridProps<T>`입니다.
815
+
816
+ #### Required
817
+
818
+ | Prop | Type | Description |
819
+ | --------- | ------------------------------------ | ---------------- |
820
+ | `width` | `number` | 그리드 전체 너비 |
821
+ | `height` | `number` | 그리드 전체 높이 |
822
+ | `columns` | `BGridColumn<T>[]` (`width` optional) | 컬럼 정의 |
823
+
824
+ #### Data / Selection / Row Focus
825
+
826
+ | Prop | Type | Description |
827
+ | ----------------- | ----------------------------------- | ------------------------------------------- |
828
+ | `data` | `BGridDataItem<T>[]` | 행 데이터 (`values` 래핑 필수) |
829
+ | `rowKey` | `React.Key \| React.Key[]` | 행 고유 키 필드 |
830
+ | `selectedRowKey` | `React.Key \| React.Key[]` | 포커스된 행 키 |
831
+ | `rowChecked` | `BGridRowChecked<T>` | 체크박스/라디오 선택 제어 (`onChange` 필수) |
832
+ | `getRowClassName` | `(ri, item) => string \| undefined` | 행 단위 className 지정 |
833
+
834
+ > `rowSelection` / `selectedIds`는 현재 API가 아닙니다. `rowChecked`를 사용해야 합니다.
835
+
836
+ #### Layout / Rendering
837
+
838
+ | Prop | Type | Description |
839
+ | ------------------- | ---------------------------------- | -------------------------------- |
840
+ | `headerHeight` | `number` | 헤더 높이 (기본 30) |
841
+ | `footerHeight` | `number` | 페이지네이션 푸터 높이 (기본 30) |
842
+ | `summaryHeight` | `number` | summary 높이 (기본 30) |
843
+ | `itemHeight` | `number` | 본문 행 컨텐츠 높이 (기본 15) |
844
+ | `itemPadding` | `number` | 행 내부 패딩 (기본 7) |
845
+ | `frozenColumnIndex` | `number` | 고정 컬럼 경계 인덱스 |
846
+ | `frozenRowCount` | `number` | 상단에 고정할 선행 데이터 행 수 |
847
+ | `showLineNumber` | `boolean` | 좌측 라인 번호 표시 |
848
+ | `variant` | `'default' \| 'vertical-bordered'` | 스킨 변형 |
849
+ | `loading` | `boolean` | 전체 오버레이 로딩 |
850
+ | `spinning` | `boolean` | 바디 스피너 로딩 |
851
+
852
+ #### Column / Editing / Events
853
+
854
+ | Prop | Type | Description |
855
+ | ----------------- | -------------------------------------------- | ---------------------------------- |
856
+ | `columnGroups` | `BGridColumnGroupNode[]` | 컬럼 ID 기반 임의 깊이 헤더 그룹 |
857
+ | `columnsGroup` | `BGridColumnGroup[]` | 레거시 2단 헤더 그룹 (deprecated) |
858
+ | `onChangeColumns` | `(columnIndex, info) => void` | 컬럼 폭/순서/그룹 변경 콜백 |
859
+ | `onChangeData` | `(index, columnIndex, item, column) => void` | 셀 편집 데이터 변경 콜백 |
860
+ | `editable` | `boolean` | 편집 모드 활성화 |
861
+ | `editTrigger` | `'click' \| 'dblclick'` | 편집 진입 트리거 (기본 `dblclick`) |
862
+ | `onClick` | `(params) => void` | 셀 클릭 이벤트 |
863
+
864
+ Each `BGridColumn<T>` can specify `editable` and an `editor`. Supported editor configs are the built-in
865
+ `{ type: 'text' }` config and plugin objects created by `defineEditorPlugin()` or the prebuilt editor factories.
866
+
867
+ Nested headers use stable column IDs and can be composed to any depth:
868
+
869
+ ```tsx
870
+ <BGrid
871
+ columns={[
872
+ { id: 'customer', key: 'customer', label: 'Customer', width: 160 },
873
+ { id: 'region', key: 'region', label: 'Region', width: 120 },
874
+ ]}
875
+ columnGroups={[
876
+ {
877
+ id: 'sales',
878
+ label: 'Sales',
879
+ children: [{ id: 'customer-info', label: 'Customer info', children: ['customer', 'region'] }],
880
+ },
881
+ ]}
882
+ {...props}
883
+ />
884
+ ```
885
+
886
+ Leaf headers can be styled with `BGridColumn.headerClassName` or `BGridColumn.headerStyle`. Nested group
887
+ headers support `BGridColumnGroupNode.className` and `BGridColumnGroupNode.headerStyle`; the same styling is
888
+ preserved when a header is rendered in the frozen region.
889
+
890
+ #### Extra Features
891
+
892
+ | Prop | Type | Description |
893
+ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- |
894
+ | `sort` | `BGridSortInfo` | 정렬 상태/변경 콜백 |
895
+ | `columnSortable` | `boolean` | 컬럼 drag sort 제어 |
896
+ | `page` | `BGridPage` | 페이지네이션 상태/콜백 |
897
+ | `summary` | `{ columns, position }` | 상/하단 summary 행 |
898
+ | `cellMergeOptions` | `{ columnsMap }` | 셀 병합 옵션 |
899
+ | `cellSelectionOptions` | `{ enabled?: boolean; clearOnEscape?: boolean; clearOnOutsideClick?: boolean; maxClipboardCells?: number; maxClipboardTextLength?: number; onCopyError?: (error) => void; onPasteError?: (error) => void; createRowOnPaste?: (context) => BGridDataItem<T> }` | 셀 선택 활성화 및 클립보드 룰 (기본 활성화) |
900
+ | `cellNavigationOptions` | `BGridCellNavigationOptions` | 활성 셀과 키보드 이동·편집 진입 정책 |
901
+ | `reorder` | `BGridReorderInfo<T>` | 행 reorder 설정 |
902
+ | `pivot` | `BGridPivotOptions<T>` | 피벗 테이블 렌더링 설정 |
903
+ | `msg` | `{ emptyList?: string }` | 커스텀 메시지 |
904
+ | `searchOptions` | `BGridSearchOptions<T>` | 검색 UI, 단축키, 문자열 getter와 제어형 상태 |
905
+ | `contextMenuOptions` | `BGridContextMenuOptions<T>` | 본문 셀 우클릭/키보드 메뉴 항목 |
906
+
907
+ ## Update Note
908
+
909
+ ### v1.5
910
+
911
+ - rowChecked 속성 추가 (rowChecked > isRadio, rowChecked > disabled)
912
+
913
+ ```typescript
914
+ <BGrid<IListItem>
915
+ width={containerWidth}
916
+ height={containerHeight}
917
+ headerHeight={35}
918
+ data={sortedList}
919
+ columns={columns}
920
+ onChangeColumns={(columnIndex, { width, columns }) => {
921
+ console.log('onChangeColumnWidths', columnIndex, width, columns);
922
+ setColumns(columns);
923
+ }}
924
+ rowChecked={{
925
+ disabled: (ri, item) => ri === 0,
926
+ isRadio: true,
927
+ checkedRowKeys: checkedKeys,
928
+ onChange: (ids, keys, selectedAll) => {
929
+ console.log('onChange rowSelection', ids, keys, selectedAll);
930
+ setCheckedKeys(keys);
931
+ },
932
+ }}
933
+ sort={{
934
+ sortParams,
935
+ onChange: sortParams => {
936
+ console.log('onChange: sortParams', sortParams);
937
+ setSortParams(sortParams);
938
+ },
939
+ }}
940
+ showLineNumber
941
+ rowKey={'nation'}
942
+ />
943
+ ```
944
+
945
+ ### V1.4
946
+
947
+ - columnsGroup 타입변경
948
+ 기존 columnsIndex: []에서 start, end 지정 형태로 변경되었습니다.
949
+
950
+ ```typescript jsx
951
+ [{ label: '묶음', groupStartIndex: 2, groupEndIndex: 4, align: 'center' }];
952
+ ```
953
+
954
+ - onChangeColumns 속성 변경
955
+
956
+ ```typescript jsx
957
+ // onChangeColumns Type
958
+ onChangeColumns?: (
959
+ columnIndex: number | null,
960
+ info: {
961
+ width?: number;
962
+ columns: BGridColumn<T>[];
963
+ columnsGroup?: BGridColumnGroup[];
964
+ columnGroups?: BGridColumnGroupNode[];
965
+ },
966
+ ) => void;
967
+
968
+ // onChangeColumns에서 변경된 컬럼과 컬럼 그룹을 받을 수 있습니다
969
+ <BGrid
970
+ /*...*/
971
+ onChangeColumns={(columnIndex, { columns, columnsGroup }) => {
972
+ console.log('onChangeColumnWidths', columnIndex, columns, columnsGroup);
973
+ setColumns(columns);
974
+ setColumnsGroup(columnsGroup);
975
+ }}
976
+ />
977
+ ```