@cratis/components 0.1.9 → 0.1.12

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/dist/cjs/CommandForm/CommandFormFields.js +9 -3
  2. package/dist/cjs/CommandForm/CommandFormFields.js.map +1 -1
  3. package/dist/cjs/CommandForm/ValidationMessage.js +24 -0
  4. package/dist/cjs/CommandForm/ValidationMessage.js.map +1 -0
  5. package/dist/cjs/CommandForm/asCommandFormField.js +47 -0
  6. package/dist/cjs/CommandForm/asCommandFormField.js.map +1 -0
  7. package/dist/cjs/CommandForm/fields/CheckboxField.js +13 -0
  8. package/dist/cjs/CommandForm/fields/CheckboxField.js.map +1 -0
  9. package/dist/cjs/CommandForm/fields/DropdownField.js +13 -0
  10. package/dist/cjs/CommandForm/fields/DropdownField.js.map +1 -0
  11. package/dist/cjs/CommandForm/fields/InputTextField.js +13 -0
  12. package/dist/cjs/CommandForm/fields/InputTextField.js.map +1 -0
  13. package/dist/cjs/CommandForm/fields/NumberField.js +13 -0
  14. package/dist/cjs/CommandForm/fields/NumberField.js.map +1 -0
  15. package/dist/cjs/CommandForm/fields/SliderField.js +17 -0
  16. package/dist/cjs/CommandForm/fields/SliderField.js.map +1 -0
  17. package/dist/cjs/CommandForm/fields/TextAreaField.js +13 -0
  18. package/dist/cjs/CommandForm/fields/TextAreaField.js.map +1 -0
  19. package/dist/cjs/CommandForm/index.js +15 -7
  20. package/dist/cjs/CommandForm/index.js.map +1 -1
  21. package/dist/cjs/PivotViewer/PivotViewer.css +1258 -0
  22. package/dist/cjs/PivotViewer/PivotViewer.js +14 -0
  23. package/dist/cjs/PivotViewer/PivotViewer.js.map +1 -1
  24. package/dist/cjs/PivotViewer/components/PivotCanvas.js +33 -10
  25. package/dist/cjs/PivotViewer/components/PivotCanvas.js.map +1 -1
  26. package/dist/cjs/PivotViewer/components/PivotViewerMain.js +1 -1
  27. package/dist/cjs/PivotViewer/components/PivotViewerMain.js.map +1 -1
  28. package/dist/cjs/PivotViewer/components/Spinner.css +77 -0
  29. package/dist/cjs/PivotViewer/components/pivot/sprites.js +79 -15
  30. package/dist/cjs/PivotViewer/components/pivot/sprites.js.map +1 -1
  31. package/dist/cjs/PivotViewer/components/pivot/visibility.js +36 -10
  32. package/dist/cjs/PivotViewer/components/pivot/visibility.js.map +1 -1
  33. package/dist/cjs/PivotViewer/engine/layout.js +2 -1
  34. package/dist/cjs/PivotViewer/engine/layout.js.map +1 -1
  35. package/dist/cjs/PivotViewer/hooks/usePivotEngine.js +37 -2
  36. package/dist/cjs/PivotViewer/hooks/usePivotEngine.js.map +1 -1
  37. package/dist/cjs/PivotViewer/index.js +3 -0
  38. package/dist/cjs/PivotViewer/index.js.map +1 -1
  39. package/dist/cjs/PivotViewer/types.js +22 -0
  40. package/dist/cjs/PivotViewer/types.js.map +1 -0
  41. package/dist/cjs/TimeMachine/EventsView.css +213 -0
  42. package/dist/cjs/TimeMachine/TimeMachine.css +567 -0
  43. package/dist/cjs/TimeMachine/TimeMachine.js +8 -3
  44. package/dist/cjs/TimeMachine/TimeMachine.js.map +1 -1
  45. package/dist/esm/CommandForm/CommandForm.stories.d.ts +1 -0
  46. package/dist/esm/CommandForm/CommandForm.stories.d.ts.map +1 -1
  47. package/dist/esm/CommandForm/CommandForm.stories.js +34 -1
  48. package/dist/esm/CommandForm/CommandForm.stories.js.map +1 -1
  49. package/dist/esm/CommandForm/CommandFormFields.d.ts.map +1 -1
  50. package/dist/esm/CommandForm/CommandFormFields.js +9 -3
  51. package/dist/esm/CommandForm/CommandFormFields.js.map +1 -1
  52. package/dist/esm/CommandForm/UserRegistrationCommand.d.ts +63 -0
  53. package/dist/esm/CommandForm/UserRegistrationCommand.d.ts.map +1 -0
  54. package/dist/esm/CommandForm/UserRegistrationCommand.js +143 -0
  55. package/dist/esm/CommandForm/UserRegistrationCommand.js.map +1 -0
  56. package/dist/esm/CommandForm/ValidationMessage.d.ts +8 -0
  57. package/dist/esm/CommandForm/ValidationMessage.d.ts.map +1 -0
  58. package/dist/esm/CommandForm/ValidationMessage.js +22 -0
  59. package/dist/esm/CommandForm/ValidationMessage.js.map +1 -0
  60. package/dist/esm/CommandForm/asCommandFormField.d.ts +32 -0
  61. package/dist/esm/CommandForm/asCommandFormField.d.ts.map +1 -0
  62. package/dist/esm/CommandForm/asCommandFormField.js +45 -0
  63. package/dist/esm/CommandForm/asCommandFormField.js.map +1 -0
  64. package/dist/esm/CommandForm/fields/CheckboxField.d.ts +10 -0
  65. package/dist/esm/CommandForm/fields/CheckboxField.d.ts.map +1 -0
  66. package/dist/esm/CommandForm/fields/CheckboxField.js +11 -0
  67. package/dist/esm/CommandForm/fields/CheckboxField.js.map +1 -0
  68. package/dist/esm/CommandForm/fields/DropdownField.d.ts +15 -0
  69. package/dist/esm/CommandForm/fields/DropdownField.d.ts.map +1 -0
  70. package/dist/esm/CommandForm/fields/DropdownField.js +11 -0
  71. package/dist/esm/CommandForm/fields/DropdownField.js.map +1 -0
  72. package/dist/esm/CommandForm/fields/InputTextField.d.ts +11 -0
  73. package/dist/esm/CommandForm/fields/InputTextField.d.ts.map +1 -0
  74. package/dist/esm/CommandForm/fields/InputTextField.js +11 -0
  75. package/dist/esm/CommandForm/fields/InputTextField.js.map +1 -0
  76. package/dist/esm/CommandForm/fields/NumberField.d.ts +13 -0
  77. package/dist/esm/CommandForm/fields/NumberField.d.ts.map +1 -0
  78. package/dist/esm/CommandForm/fields/NumberField.js +11 -0
  79. package/dist/esm/CommandForm/fields/NumberField.js.map +1 -0
  80. package/dist/esm/CommandForm/fields/SliderField.d.ts +12 -0
  81. package/dist/esm/CommandForm/fields/SliderField.d.ts.map +1 -0
  82. package/dist/esm/CommandForm/fields/SliderField.js +15 -0
  83. package/dist/esm/CommandForm/fields/SliderField.js.map +1 -0
  84. package/dist/esm/CommandForm/fields/TextAreaField.d.ts +12 -0
  85. package/dist/esm/CommandForm/fields/TextAreaField.d.ts.map +1 -0
  86. package/dist/esm/CommandForm/fields/TextAreaField.js +11 -0
  87. package/dist/esm/CommandForm/fields/TextAreaField.js.map +1 -0
  88. package/dist/esm/CommandForm/fields/index.d.ts +7 -0
  89. package/dist/esm/CommandForm/fields/index.d.ts.map +1 -0
  90. package/dist/esm/CommandForm/fields/index.js +7 -0
  91. package/dist/esm/CommandForm/fields/index.js.map +1 -0
  92. package/dist/esm/CommandForm/index.d.ts +3 -4
  93. package/dist/esm/CommandForm/index.d.ts.map +1 -1
  94. package/dist/esm/CommandForm/index.js +8 -4
  95. package/dist/esm/CommandForm/index.js.map +1 -1
  96. package/dist/esm/PivotViewer/PivotViewer.css +1258 -0
  97. package/dist/esm/PivotViewer/PivotViewer.d.ts.map +1 -1
  98. package/dist/esm/PivotViewer/PivotViewer.js +14 -0
  99. package/dist/esm/PivotViewer/PivotViewer.js.map +1 -1
  100. package/dist/esm/PivotViewer/PivotViewer.stories.d.ts +1 -0
  101. package/dist/esm/PivotViewer/PivotViewer.stories.d.ts.map +1 -1
  102. package/dist/esm/PivotViewer/PivotViewer.stories.js +43 -3
  103. package/dist/esm/PivotViewer/PivotViewer.stories.js.map +1 -1
  104. package/dist/esm/PivotViewer/components/PivotCanvas.d.ts.map +1 -1
  105. package/dist/esm/PivotViewer/components/PivotCanvas.js +33 -10
  106. package/dist/esm/PivotViewer/components/PivotCanvas.js.map +1 -1
  107. package/dist/esm/PivotViewer/components/PivotViewerMain.js +1 -1
  108. package/dist/esm/PivotViewer/components/PivotViewerMain.js.map +1 -1
  109. package/dist/esm/PivotViewer/components/Spinner.css +77 -0
  110. package/dist/esm/PivotViewer/components/pivot/sprites.d.ts.map +1 -1
  111. package/dist/esm/PivotViewer/components/pivot/sprites.js +79 -15
  112. package/dist/esm/PivotViewer/components/pivot/sprites.js.map +1 -1
  113. package/dist/esm/PivotViewer/components/pivot/visibility.d.ts.map +1 -1
  114. package/dist/esm/PivotViewer/components/pivot/visibility.js +36 -10
  115. package/dist/esm/PivotViewer/components/pivot/visibility.js.map +1 -1
  116. package/dist/esm/PivotViewer/engine/layout.js +2 -1
  117. package/dist/esm/PivotViewer/engine/layout.js.map +1 -1
  118. package/dist/esm/PivotViewer/engine/pivot.worker.d.ts.map +1 -1
  119. package/dist/esm/PivotViewer/engine/pivot.worker.js +22 -7
  120. package/dist/esm/PivotViewer/engine/pivot.worker.js.map +1 -1
  121. package/dist/esm/PivotViewer/hooks/useFilteredData.d.ts +2 -2
  122. package/dist/esm/PivotViewer/hooks/useFilteredData.d.ts.map +1 -1
  123. package/dist/esm/PivotViewer/hooks/useFilteredData.js +4 -2
  124. package/dist/esm/PivotViewer/hooks/useFilteredData.js.map +1 -1
  125. package/dist/esm/PivotViewer/hooks/usePivotEngine.d.ts.map +1 -1
  126. package/dist/esm/PivotViewer/hooks/usePivotEngine.js +37 -2
  127. package/dist/esm/PivotViewer/hooks/usePivotEngine.js.map +1 -1
  128. package/dist/esm/PivotViewer/index.d.ts +2 -1
  129. package/dist/esm/PivotViewer/index.d.ts.map +1 -1
  130. package/dist/esm/PivotViewer/index.js +1 -0
  131. package/dist/esm/PivotViewer/index.js.map +1 -1
  132. package/dist/esm/PivotViewer/types.d.ts +4 -1
  133. package/dist/esm/PivotViewer/types.d.ts.map +1 -1
  134. package/dist/esm/PivotViewer/types.js +19 -2
  135. package/dist/esm/PivotViewer/types.js.map +1 -1
  136. package/dist/esm/TimeMachine/EventsView.css +213 -0
  137. package/dist/esm/TimeMachine/TimeMachine.css +567 -0
  138. package/dist/esm/TimeMachine/TimeMachine.d.ts.map +1 -1
  139. package/dist/esm/TimeMachine/TimeMachine.js +8 -3
  140. package/dist/esm/TimeMachine/TimeMachine.js.map +1 -1
  141. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  142. package/package.json +31 -32
  143. package/.storybook/main.ts +0 -24
  144. package/CommandDialog/CommandDialog.stories.tsx +0 -25
  145. package/CommandDialog/CommandDialog.tsx +0 -161
  146. package/CommandDialog/index.ts +0 -4
  147. package/CommandForm/CommandForm.stories.tsx +0 -24
  148. package/CommandForm/CommandForm.tsx +0 -266
  149. package/CommandForm/CommandFormField.tsx +0 -27
  150. package/CommandForm/CommandFormFields.tsx +0 -142
  151. package/CommandForm/DatePickerField.tsx +0 -57
  152. package/CommandForm/DropdownField.tsx +0 -65
  153. package/CommandForm/InputTextField.tsx +0 -62
  154. package/CommandForm/SliderField.tsx +0 -68
  155. package/CommandForm/index.ts +0 -10
  156. package/Common/ErrorBoundary.stories.tsx +0 -10
  157. package/Common/ErrorBoundary.tsx +0 -41
  158. package/Common/FormElement.stories.tsx +0 -10
  159. package/Common/FormElement.tsx +0 -20
  160. package/Common/Page.stories.tsx +0 -10
  161. package/Common/Page.tsx +0 -21
  162. package/Common/index.ts +0 -6
  163. package/DataPage/DataPage.stories.tsx +0 -10
  164. package/DataPage/DataPage.tsx +0 -191
  165. package/DataPage/index.ts +0 -4
  166. package/DataTables/DataTableForObservableQuery.stories.tsx +0 -10
  167. package/DataTables/DataTableForObservableQuery.tsx +0 -97
  168. package/DataTables/DataTableForQuery.stories.tsx +0 -10
  169. package/DataTables/DataTableForQuery.tsx +0 -97
  170. package/DataTables/index.ts +0 -5
  171. package/Dialogs/BusyIndicatorDialog.stories.tsx +0 -26
  172. package/Dialogs/BusyIndicatorDialog.tsx +0 -26
  173. package/Dialogs/ConfirmationDialog.stories.tsx +0 -36
  174. package/Dialogs/ConfirmationDialog.tsx +0 -75
  175. package/Dialogs/index.ts +0 -5
  176. package/Dropdown/Dropdown.tsx +0 -23
  177. package/Dropdown/index.ts +0 -4
  178. package/PivotViewer/PivotViewer.stories.tsx +0 -24
  179. package/PivotViewer/PivotViewer.tsx +0 -791
  180. package/PivotViewer/components/AxisLabels.tsx +0 -69
  181. package/PivotViewer/components/DetailPanel.tsx +0 -108
  182. package/PivotViewer/components/FilterPanel.tsx +0 -189
  183. package/PivotViewer/components/FilterPanelContainer.tsx +0 -10
  184. package/PivotViewer/components/PivotCanvas.tsx +0 -660
  185. package/PivotViewer/components/PivotViewerMain.tsx +0 -229
  186. package/PivotViewer/components/RangeHistogramFilter.tsx +0 -220
  187. package/PivotViewer/components/Spinner.tsx +0 -21
  188. package/PivotViewer/components/Toolbar.tsx +0 -130
  189. package/PivotViewer/components/ToolbarContainer.tsx +0 -10
  190. package/PivotViewer/components/index.ts +0 -12
  191. package/PivotViewer/components/pivot/animation.ts +0 -108
  192. package/PivotViewer/components/pivot/buckets.ts +0 -152
  193. package/PivotViewer/components/pivot/colorResolver.ts +0 -67
  194. package/PivotViewer/components/pivot/constants.ts +0 -46
  195. package/PivotViewer/components/pivot/sprites.ts +0 -265
  196. package/PivotViewer/components/pivot/visibility.ts +0 -319
  197. package/PivotViewer/constants.ts +0 -9
  198. package/PivotViewer/engine/layout.ts +0 -149
  199. package/PivotViewer/engine/pivot.worker.ts +0 -86
  200. package/PivotViewer/engine/store.ts +0 -437
  201. package/PivotViewer/engine/types.ts +0 -255
  202. package/PivotViewer/hooks/index.ts +0 -13
  203. package/PivotViewer/hooks/useContainerDimensions.ts +0 -45
  204. package/PivotViewer/hooks/useDimensionState.ts +0 -53
  205. package/PivotViewer/hooks/useFilterOptions.ts +0 -36
  206. package/PivotViewer/hooks/useFilterPanelDrag.ts +0 -49
  207. package/PivotViewer/hooks/useFilterState.ts +0 -106
  208. package/PivotViewer/hooks/useFilteredData.ts +0 -119
  209. package/PivotViewer/hooks/usePanning.ts +0 -163
  210. package/PivotViewer/hooks/usePivotEngine.ts +0 -252
  211. package/PivotViewer/hooks/useSelectedItem.ts +0 -402
  212. package/PivotViewer/hooks/useWheelZoom.ts +0 -114
  213. package/PivotViewer/hooks/useZoomState.ts +0 -34
  214. package/PivotViewer/index.ts +0 -7
  215. package/PivotViewer/types.ts +0 -59
  216. package/PivotViewer/utils/animations.ts +0 -249
  217. package/PivotViewer/utils/constants.ts +0 -20
  218. package/PivotViewer/utils/index.ts +0 -6
  219. package/PivotViewer/utils/selection.ts +0 -292
  220. package/PivotViewer/utils/utils.ts +0 -259
  221. package/TimeMachine/EventsView.stories.tsx +0 -10
  222. package/TimeMachine/EventsView.tsx +0 -119
  223. package/TimeMachine/Properties.stories.tsx +0 -10
  224. package/TimeMachine/Properties.tsx +0 -98
  225. package/TimeMachine/ReadModelView.stories.tsx +0 -10
  226. package/TimeMachine/ReadModelView.tsx +0 -143
  227. package/TimeMachine/TimeMachine.stories.tsx +0 -10
  228. package/TimeMachine/TimeMachine.tsx +0 -244
  229. package/TimeMachine/index.ts +0 -8
  230. package/TimeMachine/types.ts +0 -23
  231. package/dist/cjs/CommandForm/DatePickerField.js +0 -31
  232. package/dist/cjs/CommandForm/DatePickerField.js.map +0 -1
  233. package/dist/cjs/CommandForm/DropdownField.js +0 -31
  234. package/dist/cjs/CommandForm/DropdownField.js.map +0 -1
  235. package/dist/cjs/CommandForm/InputTextField.js +0 -32
  236. package/dist/cjs/CommandForm/InputTextField.js.map +0 -1
  237. package/dist/cjs/CommandForm/SliderField.js +0 -34
  238. package/dist/cjs/CommandForm/SliderField.js.map +0 -1
  239. package/dist/esm/CommandForm/DatePickerField.d.ts +0 -20
  240. package/dist/esm/CommandForm/DatePickerField.d.ts.map +0 -1
  241. package/dist/esm/CommandForm/DatePickerField.js +0 -29
  242. package/dist/esm/CommandForm/DatePickerField.js.map +0 -1
  243. package/dist/esm/CommandForm/DropdownField.d.ts +0 -24
  244. package/dist/esm/CommandForm/DropdownField.d.ts.map +0 -1
  245. package/dist/esm/CommandForm/DropdownField.js +0 -29
  246. package/dist/esm/CommandForm/DropdownField.js.map +0 -1
  247. package/dist/esm/CommandForm/InputTextField.d.ts +0 -20
  248. package/dist/esm/CommandForm/InputTextField.d.ts.map +0 -1
  249. package/dist/esm/CommandForm/InputTextField.js +0 -30
  250. package/dist/esm/CommandForm/InputTextField.js.map +0 -1
  251. package/dist/esm/CommandForm/SliderField.d.ts +0 -23
  252. package/dist/esm/CommandForm/SliderField.d.ts.map +0 -1
  253. package/dist/esm/CommandForm/SliderField.js +0 -32
  254. package/dist/esm/CommandForm/SliderField.js.map +0 -1
  255. package/global.d.ts +0 -11
  256. package/index.ts +0 -22
  257. package/useOverlayZIndex.ts +0 -32
  258. package/vite.config.ts +0 -80
@@ -1,791 +0,0 @@
1
- // Copyright (c) Cratis. All rights reserved.
2
- // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
-
4
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
- import type { PivotViewerProps } from './types';
6
- import type { FilterSpec, GroupSpec, FieldValue, GroupingResult, ItemId } from './engine/types';
7
- import { usePivotEngine } from './hooks/usePivotEngine';
8
- import { computeLayout } from './engine/layout';
9
- import { useFilterState } from './hooks/useFilterState';
10
- import { useDimensionState } from './hooks/useDimensionState';
11
- import { useZoomState } from './hooks/useZoomState';
12
- import { handleCardSelection } from './utils/selection';
13
- import { animateZoomAndScroll, smoothScrollTo } from './utils/animations';
14
- import {
15
- BASE_CARD_WIDTH,
16
- BASE_CARD_HEIGHT,
17
- CARDS_PER_COLUMN,
18
- GROUP_SPACING,
19
- } from './constants';
20
- import { ZOOM_MAX, MIN_ZOOM_ON_SELECT, ZOOM_MULTIPLIER, DETAIL_PANEL_WIDTH } from './utils/constants';
21
- import { calculateCenterScrollPosition } from './utils/animations';
22
- import './PivotViewer.css';
23
- import { PivotViewerMain } from './components/PivotViewerMain';
24
- import { FilterPanelContainer } from './components/FilterPanelContainer';
25
- import { ToolbarContainer } from './components/ToolbarContainer';
26
- import { usePanning, useWheelZoom, useFilterOptions } from './hooks';
27
- import { useContainerDimensions } from './hooks/useContainerDimensions';
28
- import type { ViewMode } from './components/Toolbar';
29
-
30
- export function PivotViewer<TItem extends object>({
31
- data,
32
- dimensions,
33
- filters,
34
- defaultDimensionKey,
35
- cardRenderer,
36
- getItemId,
37
- searchFields,
38
- className,
39
- emptyContent,
40
- isLoading = false,
41
- }: PivotViewerProps<TItem>) {
42
- // Refs
43
- const containerRef = useRef<HTMLDivElement>(null!);
44
- const filterButtonRef = useRef<HTMLButtonElement>(null!);
45
- const axisLabelsRef = useRef<HTMLDivElement>(null!);
46
- const spacerRef = useRef<HTMLDivElement>(null!);
47
-
48
- // State
49
- const [search, setSearch] = useState('');
50
- const [viewMode, setViewMode] = useState<ViewMode>('collection');
51
-
52
- const [filtersOpen, setFiltersOpen] = useState(false);
53
- const [selectedItem, setSelectedItem] = useState<TItem | null>(null);
54
- const [isZooming, setIsZooming] = useState(false);
55
- const [visibleIds, setVisibleIds] = useState<Uint32Array>(new Uint32Array(0));
56
- const [grouping, setGrouping] = useState<GroupingResult>({ groups: [] });
57
- const [hoveredGroupIndex, setHoveredGroupIndex] = useState<number | null>(null);
58
- const [preSelectionState, setPreSelectionState] = useState<{ zoom: number; scrollLeft: number; scrollTop: number } | null>(null);
59
- const [, setAnimationMode] = useState<'layout' | 'filter'>('layout');
60
- const [scrollPosition, setScrollPosition] = useState({ x: 0, y: 0 });
61
-
62
- // Filter hooks
63
- const {
64
- filterState,
65
- rangeFilterState,
66
- expandedFilterKey,
67
- setExpandedFilterKey,
68
- handleToggleFilter,
69
- handleClearFilter,
70
- handleRangeChange,
71
- } = useFilterState(filters);
72
-
73
- // Dimension hooks
74
- const {
75
- activeDimensionKey,
76
- setActiveDimensionKey,
77
- activeDimension,
78
- dimensionFilter,
79
- handleAxisLabelClick,
80
- } = useDimensionState(dimensions, defaultDimensionKey);
81
-
82
- // Track what type of change triggered the update (for animation mode)
83
- const prevFilterStateRef = useRef(filterState);
84
- const prevRangeFilterStateRef = useRef(rangeFilterState);
85
- const prevSearchRef = useRef(search);
86
- const prevDimensionRef = useRef(activeDimensionKey);
87
- const prevViewModeRef = useRef(viewMode);
88
- const isFirstRenderRef = useRef(true);
89
-
90
- // Zoom and pan hooks
91
- const {
92
- zoomLevel,
93
- setZoomLevel,
94
- handleZoomIn,
95
- handleZoomOut,
96
- handleZoomSlider,
97
- } = useZoomState(1);
98
-
99
- const {
100
- isPanning,
101
- handlePanStart,
102
- handlePanMove,
103
- handlePanEnd,
104
- } = usePanning(containerRef, undefined, setScrollPosition);
105
-
106
- useWheelZoom(containerRef, zoomLevel, setZoomLevel);
107
-
108
- // Track container dimensions for responsive layout
109
- const containerDimensions = useContainerDimensions(containerRef, isLoading);
110
-
111
- useEffect(() => {
112
- const container = containerRef.current;
113
- if (!container) return;
114
-
115
- const handleScroll = () => {
116
- setScrollPosition({
117
- x: container.scrollLeft,
118
- y: container.scrollTop,
119
- });
120
- };
121
-
122
- container.addEventListener('scroll', handleScroll);
123
- return () => container.removeEventListener('scroll', handleScroll);
124
- }, []);
125
-
126
- // Zoom reset removed to persist zoom level across view changes
127
-
128
-
129
- // Track what type of change triggered the update for animation mode
130
- useEffect(() => {
131
- // Skip the first render
132
- if (isFirstRenderRef.current) {
133
- isFirstRenderRef.current = false;
134
- return;
135
- }
136
-
137
- const filterChanged = prevFilterStateRef.current !== filterState;
138
- const rangeChanged = prevRangeFilterStateRef.current !== rangeFilterState;
139
- const searchChanged = prevSearchRef.current !== search;
140
- const dimensionChanged = prevDimensionRef.current !== activeDimensionKey;
141
- const viewModeChanged = prevViewModeRef.current !== viewMode;
142
-
143
- // If filters or search changed, use filter animation (fade/scale)
144
- // If dimension or view mode changed, use layout animation (fly)
145
- if (filterChanged || rangeChanged || searchChanged) {
146
- setAnimationMode('filter');
147
- } else if (dimensionChanged || viewModeChanged) {
148
- setAnimationMode('layout');
149
- }
150
-
151
- prevFilterStateRef.current = filterState;
152
- prevRangeFilterStateRef.current = rangeFilterState;
153
- prevSearchRef.current = search;
154
- prevDimensionRef.current = activeDimensionKey;
155
- prevViewModeRef.current = viewMode;
156
- }, [filterState, rangeFilterState, search, activeDimensionKey, viewMode]);
157
-
158
- // Sync axis labels scroll with container scroll
159
- useEffect(() => {
160
- const container = containerRef.current;
161
- const axisLabels = axisLabelsRef.current;
162
-
163
- if (!container || !axisLabels || viewMode !== 'grouped') return;
164
-
165
- const handleScroll = () => {
166
- axisLabels.scrollLeft = container.scrollLeft;
167
- };
168
-
169
- // Sync immediately
170
- handleScroll();
171
-
172
- container.addEventListener('scroll', handleScroll);
173
- return () => container.removeEventListener('scroll', handleScroll);
174
- }, [viewMode]);
175
-
176
- // Build field extractors for the columnar store
177
- const fieldExtractors = useMemo(() => {
178
- const extractors = new Map<string, (item: TItem) => FieldValue>();
179
-
180
- for (const dim of dimensions) {
181
- extractors.set(dim.key, (item) => {
182
- const val = dim.getValue(item);
183
- if (val instanceof Date) return val.getTime();
184
- if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val === null) {
185
- return val;
186
- }
187
- return String(val);
188
- });
189
- }
190
-
191
- if (filters) {
192
- for (const filter of filters) {
193
- extractors.set(filter.key, (item) => {
194
- const val = filter.getValue(item);
195
- if (val instanceof Date) return val.getTime();
196
- if (typeof val === 'string' || typeof val === 'number' || typeof val === 'boolean' || val === null) {
197
- return val;
198
- }
199
- return String(val);
200
- });
201
- }
202
- }
203
-
204
- return extractors;
205
- }, [dimensions, filters]);
206
-
207
- const indexFields = useMemo(() => {
208
- const fields = new Set<string>();
209
-
210
- for (const dim of dimensions) {
211
- fields.add(dim.key);
212
- }
213
-
214
- if (filters) {
215
- for (const filter of filters) {
216
- fields.add(filter.key);
217
- }
218
- }
219
-
220
- return Array.from(fields);
221
- }, [dimensions, filters]);
222
-
223
- // Initialize the Web Worker engine
224
- const { ready, applyFilters: engineApplyFilters, computeGrouping, sortIds } = usePivotEngine({
225
- data,
226
- fieldExtractors,
227
- indexFields,
228
- });
229
-
230
- // Build filter specs from UI state
231
- const currentFilters = useMemo((): FilterSpec[] => {
232
- const specs: FilterSpec[] = [];
233
-
234
- // Search filter
235
- const searchTerm = search.trim().toLowerCase();
236
- if (searchTerm && searchFields && searchFields.length > 0) {
237
- // TODO: Implement search in worker
238
- // For now, search will be handled client-side after worker filtering
239
- }
240
-
241
- // Categorical filters
242
- for (const [key, values] of Object.entries(filterState)) {
243
- const valueSet = values as Set<string>;
244
- if (valueSet.size > 0) {
245
- specs.push({
246
- field: key,
247
- type: 'categorical',
248
- values: valueSet,
249
- });
250
- }
251
- }
252
-
253
- // Range filters
254
- for (const [key, range] of Object.entries(rangeFilterState)) {
255
- if (range && (range[0] !== null || range[1] !== null)) {
256
- const min = range[0] ?? -Infinity;
257
- const max = range[1] ?? Infinity;
258
- specs.push({
259
- field: key,
260
- type: 'numeric',
261
- range: { min, max },
262
- });
263
- }
264
- }
265
-
266
- // Dimension filter (bucket filter)
267
- if (dimensionFilter && activeDimension) {
268
- specs.push({
269
- field: activeDimension.key,
270
- type: 'categorical',
271
- values: new Set([dimensionFilter]),
272
- });
273
- }
274
-
275
- return specs;
276
- }, [filterState, rangeFilterState, search, searchFields, dimensionFilter, activeDimension]);
277
-
278
- const currentGroupBy = useMemo((): GroupSpec => {
279
- return {
280
- field: activeDimensionKey || dimensions[0]?.key || '',
281
- buckets: 10,
282
- };
283
- }, [activeDimensionKey, dimensions]);
284
-
285
- // Apply filters
286
- useEffect(() => {
287
- if (!ready) return;
288
-
289
- engineApplyFilters(currentFilters).then((result) => {
290
- setVisibleIds(result.visibleIds);
291
- });
292
- }, [ready, currentFilters, engineApplyFilters]);
293
-
294
- // Compute grouping
295
- useEffect(() => {
296
- if (!ready || visibleIds.length === 0) {
297
- setGrouping({ groups: [] });
298
- return;
299
- }
300
-
301
- if (viewMode === 'collection') {
302
- // In collection mode, create a single group with all items
303
- // Sort items if activeDimensionKey is set
304
- if (activeDimensionKey) {
305
- sortIds(visibleIds, activeDimensionKey).then((sortedIds) => {
306
- setGrouping({
307
- groups: [{
308
- key: 'all',
309
- label: 'All Items',
310
- value: 'all',
311
- ids: sortedIds,
312
- count: sortedIds.length
313
- }]
314
- });
315
- });
316
- } else {
317
- setGrouping({
318
- groups: [{
319
- key: 'all',
320
- label: 'All Items',
321
- value: 'all',
322
- ids: visibleIds,
323
- count: visibleIds.length
324
- }]
325
- });
326
- }
327
- return;
328
- }
329
-
330
- computeGrouping(visibleIds, currentGroupBy).then((result) => {
331
- setGrouping(result);
332
- });
333
- }, [ready, visibleIds, currentGroupBy, viewMode, computeGrouping, sortIds, activeDimensionKey]);
334
-
335
- // Compute layout
336
- const layout = useMemo(() => {
337
- // Calculate layout at base dimensions (zoom is applied as transform)
338
- const cardWidth = BASE_CARD_WIDTH;
339
- const cardHeight = BASE_CARD_HEIGHT;
340
- const containerWidth = containerDimensions.width / zoomLevel;
341
- // For grouped mode, use fixed container height to ensure stable layout during zoom
342
- const containerHeight = viewMode === 'collection'
343
- ? containerDimensions.height / zoomLevel
344
- : containerDimensions.height;
345
-
346
- const result = computeLayout(grouping, {
347
- viewMode,
348
- cardWidth,
349
- cardHeight,
350
- cardsPerColumn: CARDS_PER_COLUMN,
351
- groupSpacing: GROUP_SPACING,
352
- containerWidth,
353
- containerHeight,
354
- });
355
-
356
- return result;
357
- }, [grouping, viewMode, zoomLevel, containerDimensions.width, containerDimensions.height]);
358
-
359
- const resolveId = useCallback((item: TItem, index: number): ItemId => {
360
- if (getItemId) {
361
- const id = getItemId(item, index);
362
- return typeof id === 'number' ? id : index;
363
- }
364
- const id = (item as Record<string, unknown>)['id'];
365
- return typeof id === 'number' ? id : index;
366
- }, [getItemId]);
367
-
368
- // Scroll positioning when switching view modes or grouping changes
369
- const lastProcessedViewMode = useRef(viewMode);
370
- const lastProcessedGrouping = useRef(grouping);
371
-
372
- useEffect(() => {
373
- const viewModeChanged = lastProcessedViewMode.current !== viewMode;
374
- const groupingChanged = lastProcessedGrouping.current !== grouping;
375
-
376
- if (!viewModeChanged && !groupingChanged) return;
377
-
378
- lastProcessedViewMode.current = viewMode;
379
- lastProcessedGrouping.current = grouping;
380
-
381
- const container = containerRef.current;
382
- if (!container) return;
383
-
384
- // If we have a selected item, we want to keep it centered in the new layout
385
- if (selectedItem) {
386
- // Resolve ID
387
- let itemId = resolveId(selectedItem, 0);
388
-
389
- // Ensure ID type matches layout
390
- if (typeof itemId === 'string' && !layout.positions.has(itemId)) {
391
- const numId = Number(itemId);
392
- if (!isNaN(numId) && layout.positions.has(numId)) itemId = numId;
393
- } else if (typeof itemId === 'number' && !layout.positions.has(itemId)) {
394
- const strId = String(itemId);
395
- if (layout.positions.has(strId)) itemId = strId;
396
- }
397
-
398
- const position = layout.positions.get(itemId);
399
- if (position) {
400
- const cardPosition = {
401
- x: position.x,
402
- y: position.y,
403
- width: BASE_CARD_WIDTH,
404
- height: BASE_CARD_HEIGHT
405
- };
406
-
407
- const detailWidth = viewMode === 'collection' ? 0 : DETAIL_PANEL_WIDTH;
408
-
409
- const { scrollLeft, scrollTop } = calculateCenterScrollPosition(
410
- container,
411
- cardPosition,
412
- zoomLevel,
413
- detailWidth,
414
- layout.totalHeight
415
- );
416
-
417
- container.scrollTo({ left: scrollLeft, top: scrollTop });
418
-
419
- // Clear pre-selection state as we've moved to a new context
420
- setPreSelectionState(null);
421
- }
422
- } else if (viewMode === 'grouped') {
423
- // Default behavior for grouped view: scroll to bottom
424
- // Use a small timeout to ensure the spacer has been resized
425
- setTimeout(() => {
426
- container.scrollTop = container.scrollHeight;
427
- // Sync scroll position state immediately to avoid stale values on first click
428
- setScrollPosition({ x: container.scrollLeft, y: container.scrollTop });
429
- }, 0);
430
- }
431
- }, [viewMode, grouping, layout, selectedItem, resolveId, zoomLevel]);
432
-
433
- const handleCardClick = useCallback((item: TItem, e: MouseEvent, id?: number | string) => {
434
- if (isPanning) return;
435
-
436
- const container = containerRef.current;
437
- if (!container) return;
438
-
439
- // Use the passed ID (index) if available, otherwise fallback to resolveId
440
- // Note: resolveId might be unreliable for looking up layout positions if IDs are strings
441
- let itemId = (id !== undefined && id !== null) ? id : resolveId(item, 0);
442
-
443
- // Ensure itemId matches layout keys type (number vs string)
444
- // If layout has number keys and itemId is string, try converting
445
- if (typeof itemId === 'string' && !layout.positions.has(itemId)) {
446
- const numId = Number(itemId);
447
- if (!isNaN(numId) && layout.positions.has(numId)) {
448
- itemId = numId;
449
- }
450
- } else if (typeof itemId === 'number' && !layout.positions.has(itemId)) {
451
- const strId = String(itemId);
452
- if (layout.positions.has(strId)) {
453
- itemId = strId;
454
- }
455
- }
456
-
457
- const selectedId = selectedItem ? (data.indexOf(selectedItem) !== -1 ? data.indexOf(selectedItem) : resolveId(selectedItem, 0)) : null;
458
-
459
- // Get card position from layout
460
- const position = layout.positions.get(itemId);
461
-
462
- const cardPosition = position ? {
463
- x: position.x,
464
- y: position.y,
465
- width: BASE_CARD_WIDTH,
466
- height: BASE_CARD_HEIGHT
467
- } : null;
468
-
469
- // Calculate target position for animation
470
- let targetCardPosition: { x: number; y: number; width: number; height: number } | null = null;
471
- let getCardPositionAtZoom: ((zoom: number) => { x: number; y: number; width: number; height: number } | null) | undefined = undefined;
472
- let targetTotalHeight = layout.totalHeight;
473
-
474
- if (viewMode === 'grouped' && cardPosition) {
475
- // Calculate target zoom (logic duplicated from zoomAndCenterCard)
476
- const targetZoom = Math.min(ZOOM_MAX, Math.max(MIN_ZOOM_ON_SELECT, zoomLevel * ZOOM_MULTIPLIER));
477
-
478
- // Calculate target layout
479
- const targetContainerWidth = containerDimensions.width / targetZoom;
480
- // For grouped mode, use fixed container height to ensure stable layout during zoom
481
- const targetContainerHeight = containerDimensions.height;
482
-
483
- const targetLayout = computeLayout(grouping, {
484
- viewMode,
485
- cardWidth: BASE_CARD_WIDTH,
486
- cardHeight: BASE_CARD_HEIGHT,
487
- cardsPerColumn: CARDS_PER_COLUMN,
488
- groupSpacing: GROUP_SPACING,
489
- containerWidth: targetContainerWidth,
490
- containerHeight: targetContainerHeight,
491
- });
492
-
493
- targetTotalHeight = targetLayout.totalHeight;
494
-
495
- const targetPos = targetLayout.positions.get(itemId);
496
- if (targetPos) {
497
- targetCardPosition = {
498
- x: targetPos.x,
499
- y: targetPos.y,
500
- width: BASE_CARD_WIDTH,
501
- height: BASE_CARD_HEIGHT
502
- };
503
- }
504
-
505
- // Provide callback for accurate position during animation
506
- getCardPositionAtZoom = (zoom: number) => {
507
- const currentContainerWidth = containerDimensions.width / zoom;
508
- // For grouped mode, use fixed container height to ensure stable layout during zoom
509
- const currentContainerHeight = containerDimensions.height;
510
-
511
- const currentLayout = computeLayout(grouping, {
512
- viewMode,
513
- cardWidth: BASE_CARD_WIDTH,
514
- cardHeight: BASE_CARD_HEIGHT,
515
- cardsPerColumn: CARDS_PER_COLUMN,
516
- groupSpacing: GROUP_SPACING,
517
- containerWidth: currentContainerWidth,
518
- containerHeight: currentContainerHeight,
519
- });
520
-
521
- const pos = currentLayout.positions.get(itemId);
522
- return pos ? { x: pos.x, y: pos.y, width: BASE_CARD_WIDTH, height: BASE_CARD_HEIGHT } : null;
523
- };
524
- }
525
-
526
- // Callback to get layout size at a specific zoom level (for spacer updates)
527
- const getLayoutSizeAtZoom = (zoom: number) => {
528
- if (viewMode === 'collection') {
529
- return { width: layout.totalWidth, height: layout.totalHeight };
530
- }
531
-
532
- const currentContainerWidth = containerDimensions.width / zoom;
533
- // For grouped mode, use fixed container height to ensure stable layout during zoom
534
- const currentContainerHeight = containerDimensions.height;
535
-
536
- const currentLayout = computeLayout(grouping, {
537
- viewMode,
538
- cardWidth: BASE_CARD_WIDTH,
539
- cardHeight: BASE_CARD_HEIGHT,
540
- cardsPerColumn: CARDS_PER_COLUMN,
541
- groupSpacing: GROUP_SPACING,
542
- containerWidth: currentContainerWidth,
543
- containerHeight: currentContainerHeight,
544
- });
545
-
546
- return { width: currentLayout.totalWidth, height: currentLayout.totalHeight };
547
- };
548
-
549
- handleCardSelection({
550
- item,
551
- itemId,
552
- selectedItemId: selectedId,
553
- container,
554
- cardPosition,
555
- targetCardPosition,
556
- getCardPositionAtZoom,
557
- getLayoutSizeAtZoom,
558
- spacer: spacerRef.current,
559
- preSelectionState,
560
- startScrollPosition: { x: scrollPosition.x, y: scrollPosition.y },
561
- setZoomLevel,
562
- setIsZooming,
563
- setSelectedItem,
564
- setPreSelectionState,
565
- viewMode,
566
- zoomLevel,
567
- totalHeight: targetTotalHeight,
568
- });
569
- }, [isPanning, selectedItem, zoomLevel, preSelectionState, viewMode, resolveId, setZoomLevel, layout, grouping, containerDimensions, scrollPosition]);
570
-
571
- const closeDetail = useCallback(() => {
572
- const container = containerRef.current;
573
- if (!container || !selectedItem) {
574
- setSelectedItem(null);
575
- return;
576
- }
577
-
578
- // Try to find the index of the selected item in the data array
579
- // This is more reliable than resolveId for layout lookup
580
- const index = data.indexOf(selectedItem);
581
- let itemId: string | number = index !== -1 ? index : resolveId(selectedItem, 0);
582
-
583
- // Ensure itemId matches layout keys type (number vs string)
584
- if (typeof itemId === 'string' && !layout.positions.has(itemId)) {
585
- const numId = Number(itemId);
586
- if (!isNaN(numId) && layout.positions.has(numId)) {
587
- itemId = numId;
588
- }
589
- } else if (typeof itemId === 'number' && !layout.positions.has(itemId)) {
590
- const strId = String(itemId);
591
- if (layout.positions.has(strId)) {
592
- itemId = strId;
593
- }
594
- }
595
-
596
- // Get card position from layout
597
- const position = layout.positions.get(itemId);
598
- const cardPosition = position ? {
599
- x: position.x,
600
- y: position.y,
601
- width: BASE_CARD_WIDTH,
602
- height: BASE_CARD_HEIGHT
603
- } : null;
604
-
605
- if (!preSelectionState) {
606
- setSelectedItem(null);
607
- return;
608
- }
609
-
610
- // Collection mode: just scroll back
611
- if (viewMode === 'collection') {
612
- setSelectedItem(null);
613
- smoothScrollTo(container, preSelectionState.scrollLeft, preSelectionState.scrollTop, true);
614
- setPreSelectionState(null);
615
- return;
616
- }
617
-
618
- // Grouped mode: animate zoom out if zoom changed
619
- const zoomChanged = Math.abs(preSelectionState.zoom - zoomLevel) > 0.001;
620
-
621
- if (!zoomChanged || !cardPosition) {
622
- setSelectedItem(null);
623
- smoothScrollTo(container, preSelectionState.scrollLeft, preSelectionState.scrollTop, true);
624
- setPreSelectionState(null);
625
- return;
626
- }
627
-
628
- // Calculate target position for animation (zooming out)
629
- let targetCardPosition: { x: number; y: number; width: number; height: number } | null = null;
630
- let getCardPositionAtZoom: ((zoom: number) => { x: number; y: number; width: number; height: number } | null) | undefined = undefined;
631
-
632
- if (viewMode === 'grouped') {
633
- const targetZoom = preSelectionState.zoom;
634
-
635
- const targetContainerWidth = containerDimensions.width / targetZoom;
636
- // For grouped mode, use fixed container height to ensure stable layout during zoom
637
- const targetContainerHeight = containerDimensions.height;
638
-
639
- const targetLayout = computeLayout(grouping, {
640
- viewMode,
641
- cardWidth: BASE_CARD_WIDTH,
642
- cardHeight: BASE_CARD_HEIGHT,
643
- cardsPerColumn: CARDS_PER_COLUMN,
644
- groupSpacing: GROUP_SPACING,
645
- containerWidth: targetContainerWidth,
646
- containerHeight: targetContainerHeight,
647
- });
648
-
649
- const targetPos = targetLayout.positions.get(itemId);
650
- if (targetPos) {
651
- targetCardPosition = {
652
- x: targetPos.x,
653
- y: targetPos.y,
654
- width: BASE_CARD_WIDTH,
655
- height: BASE_CARD_HEIGHT
656
- };
657
- }
658
-
659
- // Provide callback for accurate position during animation
660
- getCardPositionAtZoom = (zoom: number) => {
661
- const currentContainerWidth = containerDimensions.width / zoom;
662
- // For grouped mode, use fixed container height to ensure stable layout during zoom
663
- const currentContainerHeight = containerDimensions.height;
664
-
665
- const currentLayout = computeLayout(grouping, {
666
- viewMode,
667
- cardWidth: BASE_CARD_WIDTH,
668
- cardHeight: BASE_CARD_HEIGHT,
669
- cardsPerColumn: CARDS_PER_COLUMN,
670
- groupSpacing: GROUP_SPACING,
671
- containerWidth: currentContainerWidth,
672
- containerHeight: currentContainerHeight,
673
- });
674
-
675
- const pos = currentLayout.positions.get(itemId);
676
- return pos ? { x: pos.x, y: pos.y, width: BASE_CARD_WIDTH, height: BASE_CARD_HEIGHT } : null;
677
- };
678
- }
679
-
680
- setIsZooming(true);
681
-
682
- animateZoomAndScroll({
683
- container,
684
- cardPosition,
685
- targetCardPosition,
686
- getCardPositionAtZoom,
687
- startZoom: zoomLevel,
688
- targetZoom: preSelectionState.zoom,
689
- targetScrollLeft: preSelectionState.scrollLeft,
690
- targetScrollTop: preSelectionState.scrollTop,
691
- onUpdate: setZoomLevel,
692
- onComplete: () => {
693
- setIsZooming(false);
694
- setSelectedItem(null);
695
- setPreSelectionState(null);
696
- },
697
- });
698
- }, [preSelectionState, selectedItem, zoomLevel, viewMode, resolveId, setZoomLevel, layout, grouping, containerDimensions]);
699
-
700
- // Use base card dimensions - zoom is applied as transform in canvas
701
- const cardWidth = BASE_CARD_WIDTH;
702
- const cardHeight = BASE_CARD_HEIGHT;
703
-
704
- // Calculate filter options
705
- const filterOptions = useFilterOptions(data, filters, filterState, rangeFilterState);
706
-
707
- const hasFilters = Boolean(filters && filters.length > 0);
708
- const activeFilterCount = Object.values(filterState).reduce((sum: number, vals) => sum + (vals as Set<string>).size, 0) +
709
- Object.values(rangeFilterState).filter(r => r !== null).length;
710
-
711
- const viewerClassName = [
712
- 'pivot-viewer',
713
- className,
714
- hasFilters ? (filtersOpen ? 'filters-open' : 'filters-closed') : 'no-filters',
715
- viewMode === 'grouped' ? 'bucket-mode' : 'collection-mode',
716
- ]
717
- .filter(Boolean)
718
- .join(' ');
719
-
720
- return (
721
- <div className={viewerClassName}>
722
- <FilterPanelContainer
723
- isOpen={filtersOpen && hasFilters}
724
- search={search}
725
- filterState={filterState}
726
- rangeFilterState={rangeFilterState}
727
- expandedFilterKey={expandedFilterKey}
728
- filterOptions={filterOptions}
729
- anchorRef={filterButtonRef}
730
- onClose={() => setFiltersOpen(false)}
731
- onSearchChange={setSearch}
732
- onFilterToggle={handleToggleFilter}
733
- onFilterClear={handleClearFilter}
734
- onRangeChange={handleRangeChange}
735
- onExpandedFilterChange={setExpandedFilterKey}
736
- />
737
-
738
- <main className="pv-main">
739
- <ToolbarContainer
740
- hasFilters={hasFilters}
741
- filtersOpen={filtersOpen}
742
- filteredCount={visibleIds.length}
743
- viewMode={viewMode}
744
- zoomLevel={zoomLevel}
745
- activeDimensionKey={activeDimensionKey}
746
- dimensions={dimensions}
747
- activeFilterCount={activeFilterCount}
748
- onFiltersToggle={() => setFiltersOpen((prev) => !prev)}
749
- onViewModeChange={setViewMode}
750
- onZoomIn={handleZoomIn}
751
- onZoomOut={handleZoomOut}
752
- onZoomSlider={handleZoomSlider}
753
- onDimensionChange={setActiveDimensionKey}
754
- filterButtonRef={filterButtonRef}
755
- />
756
-
757
- <PivotViewerMain
758
- data={data}
759
- ready={ready}
760
- isLoading={isLoading}
761
- visibleIds={visibleIds}
762
- grouping={grouping}
763
- layout={layout}
764
- cardWidth={cardWidth}
765
- cardHeight={cardHeight}
766
- zoomLevel={zoomLevel}
767
- scrollPosition={scrollPosition}
768
- containerDimensions={containerDimensions}
769
- selectedItem={selectedItem}
770
- hoveredGroupIndex={hoveredGroupIndex}
771
- isZooming={isZooming}
772
- viewMode={viewMode}
773
- cardRenderer={cardRenderer}
774
- resolveId={resolveId}
775
- emptyContent={emptyContent}
776
- dimensionFilter={dimensionFilter}
777
- onCardClick={handleCardClick}
778
- onPanStart={handlePanStart as (e: React.MouseEvent) => void}
779
- onPanMove={handlePanMove as (e: React.MouseEvent) => void}
780
- onPanEnd={handlePanEnd}
781
- onGroupHover={setHoveredGroupIndex}
782
- onAxisLabelClick={handleAxisLabelClick}
783
- onCloseDetail={closeDetail}
784
- containerRef={containerRef}
785
- axisLabelsRef={axisLabelsRef}
786
- spacerRef={spacerRef}
787
- />
788
- </main>
789
- </div>
790
- );
791
- }