@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.
- package/dist/cjs/CommandForm/CommandFormFields.js +9 -3
- package/dist/cjs/CommandForm/CommandFormFields.js.map +1 -1
- package/dist/cjs/CommandForm/ValidationMessage.js +24 -0
- package/dist/cjs/CommandForm/ValidationMessage.js.map +1 -0
- package/dist/cjs/CommandForm/asCommandFormField.js +47 -0
- package/dist/cjs/CommandForm/asCommandFormField.js.map +1 -0
- package/dist/cjs/CommandForm/fields/CheckboxField.js +13 -0
- package/dist/cjs/CommandForm/fields/CheckboxField.js.map +1 -0
- package/dist/cjs/CommandForm/fields/DropdownField.js +13 -0
- package/dist/cjs/CommandForm/fields/DropdownField.js.map +1 -0
- package/dist/cjs/CommandForm/fields/InputTextField.js +13 -0
- package/dist/cjs/CommandForm/fields/InputTextField.js.map +1 -0
- package/dist/cjs/CommandForm/fields/NumberField.js +13 -0
- package/dist/cjs/CommandForm/fields/NumberField.js.map +1 -0
- package/dist/cjs/CommandForm/fields/SliderField.js +17 -0
- package/dist/cjs/CommandForm/fields/SliderField.js.map +1 -0
- package/dist/cjs/CommandForm/fields/TextAreaField.js +13 -0
- package/dist/cjs/CommandForm/fields/TextAreaField.js.map +1 -0
- package/dist/cjs/CommandForm/index.js +15 -7
- package/dist/cjs/CommandForm/index.js.map +1 -1
- package/dist/cjs/PivotViewer/PivotViewer.css +1258 -0
- package/dist/cjs/PivotViewer/PivotViewer.js +14 -0
- package/dist/cjs/PivotViewer/PivotViewer.js.map +1 -1
- package/dist/cjs/PivotViewer/components/PivotCanvas.js +33 -10
- package/dist/cjs/PivotViewer/components/PivotCanvas.js.map +1 -1
- package/dist/cjs/PivotViewer/components/PivotViewerMain.js +1 -1
- package/dist/cjs/PivotViewer/components/PivotViewerMain.js.map +1 -1
- package/dist/cjs/PivotViewer/components/Spinner.css +77 -0
- package/dist/cjs/PivotViewer/components/pivot/sprites.js +79 -15
- package/dist/cjs/PivotViewer/components/pivot/sprites.js.map +1 -1
- package/dist/cjs/PivotViewer/components/pivot/visibility.js +36 -10
- package/dist/cjs/PivotViewer/components/pivot/visibility.js.map +1 -1
- package/dist/cjs/PivotViewer/engine/layout.js +2 -1
- package/dist/cjs/PivotViewer/engine/layout.js.map +1 -1
- package/dist/cjs/PivotViewer/hooks/usePivotEngine.js +37 -2
- package/dist/cjs/PivotViewer/hooks/usePivotEngine.js.map +1 -1
- package/dist/cjs/PivotViewer/index.js +3 -0
- package/dist/cjs/PivotViewer/index.js.map +1 -1
- package/dist/cjs/PivotViewer/types.js +22 -0
- package/dist/cjs/PivotViewer/types.js.map +1 -0
- package/dist/cjs/TimeMachine/EventsView.css +213 -0
- package/dist/cjs/TimeMachine/TimeMachine.css +567 -0
- package/dist/cjs/TimeMachine/TimeMachine.js +8 -3
- package/dist/cjs/TimeMachine/TimeMachine.js.map +1 -1
- package/dist/esm/CommandForm/CommandForm.stories.d.ts +1 -0
- package/dist/esm/CommandForm/CommandForm.stories.d.ts.map +1 -1
- package/dist/esm/CommandForm/CommandForm.stories.js +34 -1
- package/dist/esm/CommandForm/CommandForm.stories.js.map +1 -1
- package/dist/esm/CommandForm/CommandFormFields.d.ts.map +1 -1
- package/dist/esm/CommandForm/CommandFormFields.js +9 -3
- package/dist/esm/CommandForm/CommandFormFields.js.map +1 -1
- package/dist/esm/CommandForm/UserRegistrationCommand.d.ts +63 -0
- package/dist/esm/CommandForm/UserRegistrationCommand.d.ts.map +1 -0
- package/dist/esm/CommandForm/UserRegistrationCommand.js +143 -0
- package/dist/esm/CommandForm/UserRegistrationCommand.js.map +1 -0
- package/dist/esm/CommandForm/ValidationMessage.d.ts +8 -0
- package/dist/esm/CommandForm/ValidationMessage.d.ts.map +1 -0
- package/dist/esm/CommandForm/ValidationMessage.js +22 -0
- package/dist/esm/CommandForm/ValidationMessage.js.map +1 -0
- package/dist/esm/CommandForm/asCommandFormField.d.ts +32 -0
- package/dist/esm/CommandForm/asCommandFormField.d.ts.map +1 -0
- package/dist/esm/CommandForm/asCommandFormField.js +45 -0
- package/dist/esm/CommandForm/asCommandFormField.js.map +1 -0
- package/dist/esm/CommandForm/fields/CheckboxField.d.ts +10 -0
- package/dist/esm/CommandForm/fields/CheckboxField.d.ts.map +1 -0
- package/dist/esm/CommandForm/fields/CheckboxField.js +11 -0
- package/dist/esm/CommandForm/fields/CheckboxField.js.map +1 -0
- package/dist/esm/CommandForm/fields/DropdownField.d.ts +15 -0
- package/dist/esm/CommandForm/fields/DropdownField.d.ts.map +1 -0
- package/dist/esm/CommandForm/fields/DropdownField.js +11 -0
- package/dist/esm/CommandForm/fields/DropdownField.js.map +1 -0
- package/dist/esm/CommandForm/fields/InputTextField.d.ts +11 -0
- package/dist/esm/CommandForm/fields/InputTextField.d.ts.map +1 -0
- package/dist/esm/CommandForm/fields/InputTextField.js +11 -0
- package/dist/esm/CommandForm/fields/InputTextField.js.map +1 -0
- package/dist/esm/CommandForm/fields/NumberField.d.ts +13 -0
- package/dist/esm/CommandForm/fields/NumberField.d.ts.map +1 -0
- package/dist/esm/CommandForm/fields/NumberField.js +11 -0
- package/dist/esm/CommandForm/fields/NumberField.js.map +1 -0
- package/dist/esm/CommandForm/fields/SliderField.d.ts +12 -0
- package/dist/esm/CommandForm/fields/SliderField.d.ts.map +1 -0
- package/dist/esm/CommandForm/fields/SliderField.js +15 -0
- package/dist/esm/CommandForm/fields/SliderField.js.map +1 -0
- package/dist/esm/CommandForm/fields/TextAreaField.d.ts +12 -0
- package/dist/esm/CommandForm/fields/TextAreaField.d.ts.map +1 -0
- package/dist/esm/CommandForm/fields/TextAreaField.js +11 -0
- package/dist/esm/CommandForm/fields/TextAreaField.js.map +1 -0
- package/dist/esm/CommandForm/fields/index.d.ts +7 -0
- package/dist/esm/CommandForm/fields/index.d.ts.map +1 -0
- package/dist/esm/CommandForm/fields/index.js +7 -0
- package/dist/esm/CommandForm/fields/index.js.map +1 -0
- package/dist/esm/CommandForm/index.d.ts +3 -4
- package/dist/esm/CommandForm/index.d.ts.map +1 -1
- package/dist/esm/CommandForm/index.js +8 -4
- package/dist/esm/CommandForm/index.js.map +1 -1
- package/dist/esm/PivotViewer/PivotViewer.css +1258 -0
- package/dist/esm/PivotViewer/PivotViewer.d.ts.map +1 -1
- package/dist/esm/PivotViewer/PivotViewer.js +14 -0
- package/dist/esm/PivotViewer/PivotViewer.js.map +1 -1
- package/dist/esm/PivotViewer/PivotViewer.stories.d.ts +1 -0
- package/dist/esm/PivotViewer/PivotViewer.stories.d.ts.map +1 -1
- package/dist/esm/PivotViewer/PivotViewer.stories.js +43 -3
- package/dist/esm/PivotViewer/PivotViewer.stories.js.map +1 -1
- package/dist/esm/PivotViewer/components/PivotCanvas.d.ts.map +1 -1
- package/dist/esm/PivotViewer/components/PivotCanvas.js +33 -10
- package/dist/esm/PivotViewer/components/PivotCanvas.js.map +1 -1
- package/dist/esm/PivotViewer/components/PivotViewerMain.js +1 -1
- package/dist/esm/PivotViewer/components/PivotViewerMain.js.map +1 -1
- package/dist/esm/PivotViewer/components/Spinner.css +77 -0
- package/dist/esm/PivotViewer/components/pivot/sprites.d.ts.map +1 -1
- package/dist/esm/PivotViewer/components/pivot/sprites.js +79 -15
- package/dist/esm/PivotViewer/components/pivot/sprites.js.map +1 -1
- package/dist/esm/PivotViewer/components/pivot/visibility.d.ts.map +1 -1
- package/dist/esm/PivotViewer/components/pivot/visibility.js +36 -10
- package/dist/esm/PivotViewer/components/pivot/visibility.js.map +1 -1
- package/dist/esm/PivotViewer/engine/layout.js +2 -1
- package/dist/esm/PivotViewer/engine/layout.js.map +1 -1
- package/dist/esm/PivotViewer/engine/pivot.worker.d.ts.map +1 -1
- package/dist/esm/PivotViewer/engine/pivot.worker.js +22 -7
- package/dist/esm/PivotViewer/engine/pivot.worker.js.map +1 -1
- package/dist/esm/PivotViewer/hooks/useFilteredData.d.ts +2 -2
- package/dist/esm/PivotViewer/hooks/useFilteredData.d.ts.map +1 -1
- package/dist/esm/PivotViewer/hooks/useFilteredData.js +4 -2
- package/dist/esm/PivotViewer/hooks/useFilteredData.js.map +1 -1
- package/dist/esm/PivotViewer/hooks/usePivotEngine.d.ts.map +1 -1
- package/dist/esm/PivotViewer/hooks/usePivotEngine.js +37 -2
- package/dist/esm/PivotViewer/hooks/usePivotEngine.js.map +1 -1
- package/dist/esm/PivotViewer/index.d.ts +2 -1
- package/dist/esm/PivotViewer/index.d.ts.map +1 -1
- package/dist/esm/PivotViewer/index.js +1 -0
- package/dist/esm/PivotViewer/index.js.map +1 -1
- package/dist/esm/PivotViewer/types.d.ts +4 -1
- package/dist/esm/PivotViewer/types.d.ts.map +1 -1
- package/dist/esm/PivotViewer/types.js +19 -2
- package/dist/esm/PivotViewer/types.js.map +1 -1
- package/dist/esm/TimeMachine/EventsView.css +213 -0
- package/dist/esm/TimeMachine/TimeMachine.css +567 -0
- package/dist/esm/TimeMachine/TimeMachine.d.ts.map +1 -1
- package/dist/esm/TimeMachine/TimeMachine.js +8 -3
- package/dist/esm/TimeMachine/TimeMachine.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/package.json +31 -32
- package/.storybook/main.ts +0 -24
- package/CommandDialog/CommandDialog.stories.tsx +0 -25
- package/CommandDialog/CommandDialog.tsx +0 -161
- package/CommandDialog/index.ts +0 -4
- package/CommandForm/CommandForm.stories.tsx +0 -24
- package/CommandForm/CommandForm.tsx +0 -266
- package/CommandForm/CommandFormField.tsx +0 -27
- package/CommandForm/CommandFormFields.tsx +0 -142
- package/CommandForm/DatePickerField.tsx +0 -57
- package/CommandForm/DropdownField.tsx +0 -65
- package/CommandForm/InputTextField.tsx +0 -62
- package/CommandForm/SliderField.tsx +0 -68
- package/CommandForm/index.ts +0 -10
- package/Common/ErrorBoundary.stories.tsx +0 -10
- package/Common/ErrorBoundary.tsx +0 -41
- package/Common/FormElement.stories.tsx +0 -10
- package/Common/FormElement.tsx +0 -20
- package/Common/Page.stories.tsx +0 -10
- package/Common/Page.tsx +0 -21
- package/Common/index.ts +0 -6
- package/DataPage/DataPage.stories.tsx +0 -10
- package/DataPage/DataPage.tsx +0 -191
- package/DataPage/index.ts +0 -4
- package/DataTables/DataTableForObservableQuery.stories.tsx +0 -10
- package/DataTables/DataTableForObservableQuery.tsx +0 -97
- package/DataTables/DataTableForQuery.stories.tsx +0 -10
- package/DataTables/DataTableForQuery.tsx +0 -97
- package/DataTables/index.ts +0 -5
- package/Dialogs/BusyIndicatorDialog.stories.tsx +0 -26
- package/Dialogs/BusyIndicatorDialog.tsx +0 -26
- package/Dialogs/ConfirmationDialog.stories.tsx +0 -36
- package/Dialogs/ConfirmationDialog.tsx +0 -75
- package/Dialogs/index.ts +0 -5
- package/Dropdown/Dropdown.tsx +0 -23
- package/Dropdown/index.ts +0 -4
- package/PivotViewer/PivotViewer.stories.tsx +0 -24
- package/PivotViewer/PivotViewer.tsx +0 -791
- package/PivotViewer/components/AxisLabels.tsx +0 -69
- package/PivotViewer/components/DetailPanel.tsx +0 -108
- package/PivotViewer/components/FilterPanel.tsx +0 -189
- package/PivotViewer/components/FilterPanelContainer.tsx +0 -10
- package/PivotViewer/components/PivotCanvas.tsx +0 -660
- package/PivotViewer/components/PivotViewerMain.tsx +0 -229
- package/PivotViewer/components/RangeHistogramFilter.tsx +0 -220
- package/PivotViewer/components/Spinner.tsx +0 -21
- package/PivotViewer/components/Toolbar.tsx +0 -130
- package/PivotViewer/components/ToolbarContainer.tsx +0 -10
- package/PivotViewer/components/index.ts +0 -12
- package/PivotViewer/components/pivot/animation.ts +0 -108
- package/PivotViewer/components/pivot/buckets.ts +0 -152
- package/PivotViewer/components/pivot/colorResolver.ts +0 -67
- package/PivotViewer/components/pivot/constants.ts +0 -46
- package/PivotViewer/components/pivot/sprites.ts +0 -265
- package/PivotViewer/components/pivot/visibility.ts +0 -319
- package/PivotViewer/constants.ts +0 -9
- package/PivotViewer/engine/layout.ts +0 -149
- package/PivotViewer/engine/pivot.worker.ts +0 -86
- package/PivotViewer/engine/store.ts +0 -437
- package/PivotViewer/engine/types.ts +0 -255
- package/PivotViewer/hooks/index.ts +0 -13
- package/PivotViewer/hooks/useContainerDimensions.ts +0 -45
- package/PivotViewer/hooks/useDimensionState.ts +0 -53
- package/PivotViewer/hooks/useFilterOptions.ts +0 -36
- package/PivotViewer/hooks/useFilterPanelDrag.ts +0 -49
- package/PivotViewer/hooks/useFilterState.ts +0 -106
- package/PivotViewer/hooks/useFilteredData.ts +0 -119
- package/PivotViewer/hooks/usePanning.ts +0 -163
- package/PivotViewer/hooks/usePivotEngine.ts +0 -252
- package/PivotViewer/hooks/useSelectedItem.ts +0 -402
- package/PivotViewer/hooks/useWheelZoom.ts +0 -114
- package/PivotViewer/hooks/useZoomState.ts +0 -34
- package/PivotViewer/index.ts +0 -7
- package/PivotViewer/types.ts +0 -59
- package/PivotViewer/utils/animations.ts +0 -249
- package/PivotViewer/utils/constants.ts +0 -20
- package/PivotViewer/utils/index.ts +0 -6
- package/PivotViewer/utils/selection.ts +0 -292
- package/PivotViewer/utils/utils.ts +0 -259
- package/TimeMachine/EventsView.stories.tsx +0 -10
- package/TimeMachine/EventsView.tsx +0 -119
- package/TimeMachine/Properties.stories.tsx +0 -10
- package/TimeMachine/Properties.tsx +0 -98
- package/TimeMachine/ReadModelView.stories.tsx +0 -10
- package/TimeMachine/ReadModelView.tsx +0 -143
- package/TimeMachine/TimeMachine.stories.tsx +0 -10
- package/TimeMachine/TimeMachine.tsx +0 -244
- package/TimeMachine/index.ts +0 -8
- package/TimeMachine/types.ts +0 -23
- package/dist/cjs/CommandForm/DatePickerField.js +0 -31
- package/dist/cjs/CommandForm/DatePickerField.js.map +0 -1
- package/dist/cjs/CommandForm/DropdownField.js +0 -31
- package/dist/cjs/CommandForm/DropdownField.js.map +0 -1
- package/dist/cjs/CommandForm/InputTextField.js +0 -32
- package/dist/cjs/CommandForm/InputTextField.js.map +0 -1
- package/dist/cjs/CommandForm/SliderField.js +0 -34
- package/dist/cjs/CommandForm/SliderField.js.map +0 -1
- package/dist/esm/CommandForm/DatePickerField.d.ts +0 -20
- package/dist/esm/CommandForm/DatePickerField.d.ts.map +0 -1
- package/dist/esm/CommandForm/DatePickerField.js +0 -29
- package/dist/esm/CommandForm/DatePickerField.js.map +0 -1
- package/dist/esm/CommandForm/DropdownField.d.ts +0 -24
- package/dist/esm/CommandForm/DropdownField.d.ts.map +0 -1
- package/dist/esm/CommandForm/DropdownField.js +0 -29
- package/dist/esm/CommandForm/DropdownField.js.map +0 -1
- package/dist/esm/CommandForm/InputTextField.d.ts +0 -20
- package/dist/esm/CommandForm/InputTextField.d.ts.map +0 -1
- package/dist/esm/CommandForm/InputTextField.js +0 -30
- package/dist/esm/CommandForm/InputTextField.js.map +0 -1
- package/dist/esm/CommandForm/SliderField.d.ts +0 -23
- package/dist/esm/CommandForm/SliderField.d.ts.map +0 -1
- package/dist/esm/CommandForm/SliderField.js +0 -32
- package/dist/esm/CommandForm/SliderField.js.map +0 -1
- package/global.d.ts +0 -11
- package/index.ts +0 -22
- package/useOverlayZIndex.ts +0 -32
- package/vite.config.ts +0 -80
|
@@ -1,252 +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 { useEffect, useRef, useState, useCallback } from 'react';
|
|
5
|
-
import type {
|
|
6
|
-
PivotStore,
|
|
7
|
-
PivotIndexes,
|
|
8
|
-
FilterSpec,
|
|
9
|
-
FilterResult,
|
|
10
|
-
GroupSpec,
|
|
11
|
-
GroupingResult,
|
|
12
|
-
WorkerInMessage,
|
|
13
|
-
WorkerOutMessage,
|
|
14
|
-
FieldValue,
|
|
15
|
-
} from '../engine/types';
|
|
16
|
-
import { buildStore, buildIndexes, applyFilters, computeGrouping, sortIds } from '../engine/store';
|
|
17
|
-
|
|
18
|
-
export interface UsePivotEngineOptions<TItem extends object> {
|
|
19
|
-
data: TItem[];
|
|
20
|
-
fieldExtractors: Map<string, (item: TItem) => FieldValue>;
|
|
21
|
-
indexFields: string[];
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export interface UsePivotEngineResult {
|
|
25
|
-
ready: boolean;
|
|
26
|
-
applyFilters: (filters: FilterSpec[]) => Promise<FilterResult>;
|
|
27
|
-
computeGrouping: (visibleIds: Uint32Array, groupBy: GroupSpec) => Promise<GroupingResult>;
|
|
28
|
-
sortIds: (visibleIds: Uint32Array, sortBy: string) => Promise<Uint32Array>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function usePivotEngine<TItem extends object>({
|
|
32
|
-
data,
|
|
33
|
-
fieldExtractors,
|
|
34
|
-
indexFields,
|
|
35
|
-
}: UsePivotEngineOptions<TItem>): UsePivotEngineResult {
|
|
36
|
-
const [ready, setReady] = useState(false);
|
|
37
|
-
const workerRef = useRef<Worker | null>(null);
|
|
38
|
-
const indexesRef = useRef<PivotIndexes | null>(null);
|
|
39
|
-
const fallbackRef = useRef(false);
|
|
40
|
-
const storeRef = useRef<PivotStore | null>(null);
|
|
41
|
-
const pendingCallbacksRef = useRef<Map<string, (result: unknown) => void>>(new Map());
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
const worker = new Worker(
|
|
45
|
-
new URL('../engine/pivot.worker.ts', import.meta.url),
|
|
46
|
-
{ type: 'module' }
|
|
47
|
-
);
|
|
48
|
-
|
|
49
|
-
workerRef.current = worker;
|
|
50
|
-
|
|
51
|
-
worker.onmessage = (e: MessageEvent<WorkerOutMessage>) => {
|
|
52
|
-
const message = e.data;
|
|
53
|
-
|
|
54
|
-
switch (message.type) {
|
|
55
|
-
case 'indexesReady':
|
|
56
|
-
console.log('[PivotEngine] Indexes ready');
|
|
57
|
-
setReady(true);
|
|
58
|
-
break;
|
|
59
|
-
|
|
60
|
-
case 'filterResult': {
|
|
61
|
-
const callback = pendingCallbacksRef.current.get('filter');
|
|
62
|
-
if (callback) {
|
|
63
|
-
callback(message.result);
|
|
64
|
-
pendingCallbacksRef.current.delete('filter');
|
|
65
|
-
}
|
|
66
|
-
break;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
case 'groupingResult': {
|
|
70
|
-
const callback = pendingCallbacksRef.current.get('grouping');
|
|
71
|
-
if (callback) {
|
|
72
|
-
callback(message.result);
|
|
73
|
-
pendingCallbacksRef.current.delete('grouping');
|
|
74
|
-
}
|
|
75
|
-
break;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
case 'sortResult': {
|
|
79
|
-
const callback = pendingCallbacksRef.current.get('sort');
|
|
80
|
-
if (callback) {
|
|
81
|
-
callback(message.result);
|
|
82
|
-
pendingCallbacksRef.current.delete('sort');
|
|
83
|
-
}
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
worker.onerror = (error) => {
|
|
90
|
-
console.error('[PivotEngine] Worker error:', error);
|
|
91
|
-
// enable synchronous fallback so UI can still function without worker
|
|
92
|
-
fallbackRef.current = true;
|
|
93
|
-
workerRef.current = null;
|
|
94
|
-
// if we already built store/indexes, mark ready so UI can use fallback
|
|
95
|
-
if (storeRef.current) {
|
|
96
|
-
try {
|
|
97
|
-
indexesRef.current = buildIndexes(storeRef.current, indexFields);
|
|
98
|
-
} catch (e) {
|
|
99
|
-
console.error('[PivotEngine] Failed to build indexes in fallback:', e);
|
|
100
|
-
indexesRef.current = null;
|
|
101
|
-
}
|
|
102
|
-
setReady(true);
|
|
103
|
-
}
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
return () => {
|
|
107
|
-
worker.terminate();
|
|
108
|
-
};
|
|
109
|
-
}, []);
|
|
110
|
-
|
|
111
|
-
useEffect(() => {
|
|
112
|
-
if (!workerRef.current) return;
|
|
113
|
-
|
|
114
|
-
console.log('[PivotEngine] Building indexes for', data.length, 'items');
|
|
115
|
-
setReady(false);
|
|
116
|
-
|
|
117
|
-
const store = buildStore(data, fieldExtractors);
|
|
118
|
-
storeRef.current = store;
|
|
119
|
-
|
|
120
|
-
// compute indexes locally for immediate fallback and cache
|
|
121
|
-
try {
|
|
122
|
-
indexesRef.current = buildIndexes(store, indexFields);
|
|
123
|
-
} catch (e) {
|
|
124
|
-
console.error('[PivotEngine] buildIndexes failed:', e);
|
|
125
|
-
indexesRef.current = null;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (workerRef.current) {
|
|
129
|
-
const message: WorkerInMessage = {
|
|
130
|
-
type: 'buildIndexes',
|
|
131
|
-
store,
|
|
132
|
-
fields: indexFields,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
workerRef.current.postMessage(message);
|
|
136
|
-
} else {
|
|
137
|
-
// no worker available, mark ready to allow fallback synchronous usage
|
|
138
|
-
setReady(true);
|
|
139
|
-
}
|
|
140
|
-
}, [data, fieldExtractors, indexFields]);
|
|
141
|
-
|
|
142
|
-
const applyFiltersCallback = useCallback(
|
|
143
|
-
(filters: FilterSpec[]): Promise<FilterResult> => {
|
|
144
|
-
return new Promise((resolve) => {
|
|
145
|
-
// If worker is not available, use synchronous fallback using local indexes
|
|
146
|
-
if (!workerRef.current || fallbackRef.current) {
|
|
147
|
-
try {
|
|
148
|
-
const store = storeRef.current;
|
|
149
|
-
const indexes = indexesRef.current;
|
|
150
|
-
if (store && indexes) {
|
|
151
|
-
const result = applyFilters(store, indexes, filters);
|
|
152
|
-
resolve(result);
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
} catch (e) {
|
|
156
|
-
console.error('[PivotEngine] fallback applyFilters error:', e);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// if fallback not possible, return empty result
|
|
160
|
-
resolve({ visibleIds: new Uint32Array(0), count: 0 });
|
|
161
|
-
return;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
pendingCallbacksRef.current.set('filter', resolve as (result: unknown) => void);
|
|
165
|
-
|
|
166
|
-
const message: WorkerInMessage = {
|
|
167
|
-
type: 'applyFilters',
|
|
168
|
-
filters,
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
workerRef.current.postMessage(message);
|
|
172
|
-
});
|
|
173
|
-
},
|
|
174
|
-
[ready]
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
const computeGroupingCallback = useCallback(
|
|
178
|
-
(visibleIds: Uint32Array, groupBy: GroupSpec): Promise<GroupingResult> => {
|
|
179
|
-
return new Promise((resolve) => {
|
|
180
|
-
// synchronous fallback if worker unavailable
|
|
181
|
-
if (!workerRef.current || fallbackRef.current) {
|
|
182
|
-
try {
|
|
183
|
-
const store = storeRef.current;
|
|
184
|
-
const indexes = indexesRef.current;
|
|
185
|
-
if (store && indexes) {
|
|
186
|
-
const result = computeGrouping(store, indexes, visibleIds, groupBy);
|
|
187
|
-
resolve(result);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
} catch (e) {
|
|
191
|
-
console.error('[PivotEngine] fallback computeGrouping error:', e);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
resolve({ groups: [] });
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
pendingCallbacksRef.current.set('grouping', resolve as (result: unknown) => void);
|
|
199
|
-
|
|
200
|
-
const message: WorkerInMessage = {
|
|
201
|
-
type: 'computeGrouping',
|
|
202
|
-
visibleIds,
|
|
203
|
-
groupBy,
|
|
204
|
-
};
|
|
205
|
-
|
|
206
|
-
workerRef.current.postMessage(message);
|
|
207
|
-
});
|
|
208
|
-
},
|
|
209
|
-
[ready]
|
|
210
|
-
);
|
|
211
|
-
|
|
212
|
-
const sortIdsCallback = useCallback(
|
|
213
|
-
(visibleIds: Uint32Array, sortBy: string): Promise<Uint32Array> => {
|
|
214
|
-
return new Promise((resolve) => {
|
|
215
|
-
// synchronous fallback if worker unavailable
|
|
216
|
-
if (!workerRef.current || fallbackRef.current) {
|
|
217
|
-
try {
|
|
218
|
-
const store = storeRef.current;
|
|
219
|
-
if (store) {
|
|
220
|
-
const result = sortIds(store, visibleIds, sortBy);
|
|
221
|
-
resolve(result);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
} catch (e) {
|
|
225
|
-
console.error('[PivotEngine] fallback sortIds error:', e);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
resolve(visibleIds);
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
pendingCallbacksRef.current.set('sort', resolve as (result: unknown) => void);
|
|
233
|
-
|
|
234
|
-
const message: WorkerInMessage = {
|
|
235
|
-
type: 'sort',
|
|
236
|
-
ids: visibleIds,
|
|
237
|
-
sortBy,
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
workerRef.current.postMessage(message);
|
|
241
|
-
});
|
|
242
|
-
},
|
|
243
|
-
[ready]
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
return {
|
|
247
|
-
ready,
|
|
248
|
-
applyFilters: applyFiltersCallback,
|
|
249
|
-
computeGrouping: computeGroupingCallback,
|
|
250
|
-
sortIds: sortIdsCallback,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
@@ -1,402 +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 { useState, useRef, useCallback, useEffect } from 'react';
|
|
5
|
-
import { flushSync } from 'react-dom';
|
|
6
|
-
import { ZOOM_MAX } from '../utils/utils';
|
|
7
|
-
|
|
8
|
-
function getOffsetWithin(element: HTMLElement, ancestor: HTMLElement) {
|
|
9
|
-
let current: HTMLElement | null = element;
|
|
10
|
-
let x = 0;
|
|
11
|
-
let y = 0;
|
|
12
|
-
|
|
13
|
-
while (current && current !== ancestor) {
|
|
14
|
-
x += current.offsetLeft;
|
|
15
|
-
y += current.offsetTop;
|
|
16
|
-
current = current.offsetParent as HTMLElement | null;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
if (current !== ancestor) {
|
|
20
|
-
const containerRect = ancestor.getBoundingClientRect();
|
|
21
|
-
const elementRect = element.getBoundingClientRect();
|
|
22
|
-
x = elementRect.left - containerRect.left + ancestor.scrollLeft;
|
|
23
|
-
y = elementRect.top - containerRect.top + ancestor.scrollTop;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return { x, y };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function scrollCardToCenter(
|
|
30
|
-
container: HTMLElement,
|
|
31
|
-
card: HTMLElement,
|
|
32
|
-
smooth: boolean = true,
|
|
33
|
-
detailPanelWidth: number = 0
|
|
34
|
-
) {
|
|
35
|
-
const { x, y } = getOffsetWithin(card, container);
|
|
36
|
-
const cardCenterX = x + card.offsetWidth / 2;
|
|
37
|
-
const cardCenterY = y + card.offsetHeight / 2;
|
|
38
|
-
|
|
39
|
-
const availableWidth = container.clientWidth - detailPanelWidth;
|
|
40
|
-
const targetX = availableWidth / 2;
|
|
41
|
-
const targetY = container.clientHeight / 2;
|
|
42
|
-
|
|
43
|
-
const maxScrollLeft = Math.max(0, container.scrollWidth - container.clientWidth);
|
|
44
|
-
const maxScrollTop = Math.max(0, container.scrollHeight - container.clientHeight);
|
|
45
|
-
const targetLeft = Math.min(maxScrollLeft, Math.max(0, cardCenterX - targetX));
|
|
46
|
-
const targetTop = Math.min(maxScrollTop, Math.max(0, cardCenterY - targetY));
|
|
47
|
-
|
|
48
|
-
container.scrollTo({
|
|
49
|
-
left: targetLeft,
|
|
50
|
-
top: targetTop,
|
|
51
|
-
behavior: smooth ? 'smooth' : 'auto'
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function escapeAttributeSelectorValue(value: string) {
|
|
56
|
-
return value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function useSelectedItem<TItem extends object>(
|
|
60
|
-
containerRef: React.RefObject<HTMLDivElement | null>,
|
|
61
|
-
zoomLevel: number,
|
|
62
|
-
setZoomLevel: (zoom: number) => void,
|
|
63
|
-
resolveId: (item: TItem, index: number) => string | number,
|
|
64
|
-
viewMode: 'collection' | 'grouped' = 'grouped'
|
|
65
|
-
) {
|
|
66
|
-
const [selectedItem, setSelectedItem] = useState<TItem | null>(null);
|
|
67
|
-
const [preSelectionState, setPreSelectionState] = useState<{ zoom: number; scrollLeft: number; scrollTop: number } | null>(null);
|
|
68
|
-
const [isZooming, setIsZooming] = useState(false);
|
|
69
|
-
const pendingCenterRaf = useRef<number | null>(null);
|
|
70
|
-
|
|
71
|
-
const focusCardById = useCallback((targetId: string | number, detailPanelWidth: number = 380) => {
|
|
72
|
-
const container = containerRef.current;
|
|
73
|
-
if (!container) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const selector = `[data-card-id="${escapeAttributeSelectorValue(String(targetId))}"]`;
|
|
77
|
-
const latestCard = container.querySelector(selector) as HTMLElement | null;
|
|
78
|
-
if (latestCard) {
|
|
79
|
-
scrollCardToCenter(container, latestCard, true, detailPanelWidth);
|
|
80
|
-
}
|
|
81
|
-
}, [containerRef]);
|
|
82
|
-
|
|
83
|
-
const handleCardClick = useCallback((item: TItem, e: React.MouseEvent) => {
|
|
84
|
-
const container = containerRef.current;
|
|
85
|
-
if (!container) return;
|
|
86
|
-
|
|
87
|
-
const card = (e.target as HTMLElement).closest('.pv-card') as HTMLElement;
|
|
88
|
-
if (!card) return;
|
|
89
|
-
|
|
90
|
-
const itemId = resolveId(item, 0);
|
|
91
|
-
const selectedId = selectedItem ? resolveId(selectedItem, 0) : null;
|
|
92
|
-
|
|
93
|
-
if (selectedId === itemId) {
|
|
94
|
-
if (pendingCenterRaf.current !== null && typeof window !== 'undefined') {
|
|
95
|
-
window.clearTimeout(pendingCenterRaf.current);
|
|
96
|
-
pendingCenterRaf.current = null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (viewMode === 'collection') {
|
|
100
|
-
setSelectedItem(null);
|
|
101
|
-
if (preSelectionState) {
|
|
102
|
-
container.scrollTo({ left: preSelectionState.scrollLeft, top: preSelectionState.scrollTop, behavior: 'smooth' });
|
|
103
|
-
setPreSelectionState(null);
|
|
104
|
-
}
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
if (!preSelectionState || Math.abs(preSelectionState.zoom - zoomLevel) <= 0.001) {
|
|
109
|
-
setSelectedItem(null);
|
|
110
|
-
if (preSelectionState) {
|
|
111
|
-
container.scrollTo({ left: preSelectionState.scrollLeft, top: preSelectionState.scrollTop, behavior: 'smooth' });
|
|
112
|
-
setPreSelectionState(null);
|
|
113
|
-
}
|
|
114
|
-
return;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
setIsZooming(true);
|
|
118
|
-
container.style.scrollBehavior = 'auto';
|
|
119
|
-
|
|
120
|
-
const startZoom = zoomLevel;
|
|
121
|
-
const targetZoom = preSelectionState.zoom;
|
|
122
|
-
const targetScrollLeft = preSelectionState.scrollLeft;
|
|
123
|
-
const targetScrollTop = preSelectionState.scrollTop;
|
|
124
|
-
|
|
125
|
-
const startOffset = getOffsetWithin(card, container);
|
|
126
|
-
const startCardScreenX = startOffset.x + card.offsetWidth / 2 - container.scrollLeft;
|
|
127
|
-
const startCardScreenY = startOffset.y + card.offsetHeight / 2 - container.scrollTop;
|
|
128
|
-
|
|
129
|
-
const duration = 300;
|
|
130
|
-
let startTime: number | null = null;
|
|
131
|
-
|
|
132
|
-
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
|
|
133
|
-
|
|
134
|
-
const animate = (timestamp: number) => {
|
|
135
|
-
if (startTime === null) {
|
|
136
|
-
startTime = timestamp;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const elapsed = timestamp - startTime;
|
|
140
|
-
const progress = Math.min(1, elapsed / duration);
|
|
141
|
-
const easedProgress = easeOutCubic(progress);
|
|
142
|
-
|
|
143
|
-
const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;
|
|
144
|
-
|
|
145
|
-
flushSync(() => {
|
|
146
|
-
setZoomLevel(currentZoom);
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
const currentOffset = getOffsetWithin(card, container);
|
|
150
|
-
const currentCardCenterX = currentOffset.x + card.offsetWidth / 2;
|
|
151
|
-
const currentCardCenterY = currentOffset.y + card.offsetHeight / 2;
|
|
152
|
-
|
|
153
|
-
const endCardScreenX = currentCardCenterX - targetScrollLeft;
|
|
154
|
-
const endCardScreenY = currentCardCenterY - targetScrollTop;
|
|
155
|
-
|
|
156
|
-
const desiredScreenX = startCardScreenX + (endCardScreenX - startCardScreenX) * easedProgress;
|
|
157
|
-
const desiredScreenY = startCardScreenY + (endCardScreenY - startCardScreenY) * easedProgress;
|
|
158
|
-
|
|
159
|
-
const neededScrollLeft = currentCardCenterX - desiredScreenX;
|
|
160
|
-
const neededScrollTop = currentCardCenterY - desiredScreenY;
|
|
161
|
-
|
|
162
|
-
const maxScrollLeft = container.scrollWidth - container.clientWidth;
|
|
163
|
-
const maxScrollTop = container.scrollHeight - container.clientHeight;
|
|
164
|
-
container.scrollLeft = Math.max(0, Math.min(neededScrollLeft, maxScrollLeft));
|
|
165
|
-
container.scrollTop = Math.max(0, Math.min(neededScrollTop, maxScrollTop));
|
|
166
|
-
|
|
167
|
-
if (progress < 1) {
|
|
168
|
-
requestAnimationFrame(animate);
|
|
169
|
-
} else {
|
|
170
|
-
container.style.scrollBehavior = '';
|
|
171
|
-
setIsZooming(false);
|
|
172
|
-
setSelectedItem(null);
|
|
173
|
-
setPreSelectionState(null);
|
|
174
|
-
container.scrollLeft = targetScrollLeft;
|
|
175
|
-
container.scrollTop = targetScrollTop;
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
requestAnimationFrame(animate);
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const isFirstSelection = selectedItem === null;
|
|
184
|
-
|
|
185
|
-
if (isFirstSelection) {
|
|
186
|
-
setPreSelectionState({
|
|
187
|
-
zoom: zoomLevel,
|
|
188
|
-
scrollLeft: container.scrollLeft,
|
|
189
|
-
scrollTop: container.scrollTop,
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
setSelectedItem(item);
|
|
194
|
-
|
|
195
|
-
if (isFirstSelection) {
|
|
196
|
-
if (viewMode === 'collection') {
|
|
197
|
-
focusCardById(itemId, 0);
|
|
198
|
-
} else {
|
|
199
|
-
const zoomMultiplier = 1.15;
|
|
200
|
-
const minZoom = 1.2;
|
|
201
|
-
const targetZoom = Math.min(ZOOM_MAX, Math.max(minZoom, zoomLevel * zoomMultiplier));
|
|
202
|
-
const shouldZoom = Math.abs(targetZoom - zoomLevel) > 0.001;
|
|
203
|
-
|
|
204
|
-
if (shouldZoom) {
|
|
205
|
-
const selector = `[data-card-id="${escapeAttributeSelectorValue(String(itemId))}"]`;
|
|
206
|
-
const cardEl = container.querySelector(selector) as HTMLElement | null;
|
|
207
|
-
|
|
208
|
-
if (cardEl) {
|
|
209
|
-
setIsZooming(true);
|
|
210
|
-
container.style.scrollBehavior = 'auto';
|
|
211
|
-
|
|
212
|
-
const detailPanelWidth = 380;
|
|
213
|
-
const availableWidth = container.clientWidth - detailPanelWidth;
|
|
214
|
-
const targetScreenX = availableWidth / 2;
|
|
215
|
-
const targetScreenY = container.clientHeight / 2;
|
|
216
|
-
|
|
217
|
-
const startZoom = zoomLevel;
|
|
218
|
-
|
|
219
|
-
const startOffset = getOffsetWithin(cardEl, container);
|
|
220
|
-
const startCardScreenX = startOffset.x + cardEl.offsetWidth / 2 - container.scrollLeft;
|
|
221
|
-
const startCardScreenY = startOffset.y + cardEl.offsetHeight / 2 - container.scrollTop;
|
|
222
|
-
|
|
223
|
-
const duration = 300;
|
|
224
|
-
let startTime: number | null = null;
|
|
225
|
-
|
|
226
|
-
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
|
|
227
|
-
|
|
228
|
-
const animate = (timestamp: number) => {
|
|
229
|
-
if (startTime === null) {
|
|
230
|
-
startTime = timestamp;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const elapsed = timestamp - startTime;
|
|
234
|
-
const progress = Math.min(1, elapsed / duration);
|
|
235
|
-
const easedProgress = easeOutCubic(progress);
|
|
236
|
-
|
|
237
|
-
const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;
|
|
238
|
-
|
|
239
|
-
flushSync(() => {
|
|
240
|
-
setZoomLevel(currentZoom);
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const currentOffset = getOffsetWithin(cardEl, container);
|
|
244
|
-
const currentCardCenterX = currentOffset.x + cardEl.offsetWidth / 2;
|
|
245
|
-
const currentCardCenterY = currentOffset.y + cardEl.offsetHeight / 2;
|
|
246
|
-
|
|
247
|
-
const desiredScreenX = startCardScreenX + (targetScreenX - startCardScreenX) * easedProgress;
|
|
248
|
-
const desiredScreenY = startCardScreenY + (targetScreenY - startCardScreenY) * easedProgress;
|
|
249
|
-
|
|
250
|
-
const neededScrollLeft = currentCardCenterX - desiredScreenX;
|
|
251
|
-
const neededScrollTop = currentCardCenterY - desiredScreenY;
|
|
252
|
-
|
|
253
|
-
const maxScrollLeft = container.scrollWidth - container.clientWidth;
|
|
254
|
-
const maxScrollTop = container.scrollHeight - container.clientHeight;
|
|
255
|
-
container.scrollLeft = Math.max(0, Math.min(neededScrollLeft, maxScrollLeft));
|
|
256
|
-
container.scrollTop = Math.max(0, Math.min(neededScrollTop, maxScrollTop));
|
|
257
|
-
|
|
258
|
-
if (progress < 1) {
|
|
259
|
-
requestAnimationFrame(animate);
|
|
260
|
-
} else {
|
|
261
|
-
container.style.scrollBehavior = '';
|
|
262
|
-
setIsZooming(false);
|
|
263
|
-
requestAnimationFrame(() => {
|
|
264
|
-
focusCardById(itemId, 380);
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
requestAnimationFrame(animate);
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
focusCardById(itemId, 380);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
} else {
|
|
276
|
-
focusCardById(itemId, viewMode === 'collection' ? 0 : 380);
|
|
277
|
-
}
|
|
278
|
-
}, [selectedItem, zoomLevel, preSelectionState, resolveId, setZoomLevel, containerRef, viewMode]);
|
|
279
|
-
|
|
280
|
-
const closeDetail = useCallback(() => {
|
|
281
|
-
const container = containerRef.current;
|
|
282
|
-
|
|
283
|
-
if (pendingCenterRaf.current !== null && typeof window !== 'undefined') {
|
|
284
|
-
window.clearTimeout(pendingCenterRaf.current);
|
|
285
|
-
pendingCenterRaf.current = null;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (preSelectionState && container && selectedItem) {
|
|
289
|
-
const itemId = resolveId(selectedItem, 0);
|
|
290
|
-
const selector = `[data-card-id="${escapeAttributeSelectorValue(String(itemId))}"]`;
|
|
291
|
-
const cardEl = container.querySelector(selector) as HTMLElement | null;
|
|
292
|
-
|
|
293
|
-
if (viewMode === 'collection') {
|
|
294
|
-
setSelectedItem(null);
|
|
295
|
-
container.scrollTo({ left: preSelectionState.scrollLeft, top: preSelectionState.scrollTop, behavior: 'smooth' });
|
|
296
|
-
setPreSelectionState(null);
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
if (cardEl && Math.abs(preSelectionState.zoom - zoomLevel) > 0.001) {
|
|
301
|
-
setIsZooming(true);
|
|
302
|
-
container.style.scrollBehavior = 'auto';
|
|
303
|
-
|
|
304
|
-
const startZoom = zoomLevel;
|
|
305
|
-
const targetZoom = preSelectionState.zoom;
|
|
306
|
-
const targetScrollLeft = preSelectionState.scrollLeft;
|
|
307
|
-
const targetScrollTop = preSelectionState.scrollTop;
|
|
308
|
-
|
|
309
|
-
const startOffset = getOffsetWithin(cardEl, container);
|
|
310
|
-
const startCardScreenX = startOffset.x + cardEl.offsetWidth / 2 - container.scrollLeft;
|
|
311
|
-
const startCardScreenY = startOffset.y + cardEl.offsetHeight / 2 - container.scrollTop;
|
|
312
|
-
|
|
313
|
-
const duration = 300;
|
|
314
|
-
let startTime: number | null = null;
|
|
315
|
-
|
|
316
|
-
const easeOutCubic = (t: number) => 1 - Math.pow(1 - t, 3);
|
|
317
|
-
|
|
318
|
-
const animate = (timestamp: number) => {
|
|
319
|
-
if (startTime === null) {
|
|
320
|
-
startTime = timestamp;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
const elapsed = timestamp - startTime;
|
|
324
|
-
const progress = Math.min(1, elapsed / duration);
|
|
325
|
-
const easedProgress = easeOutCubic(progress);
|
|
326
|
-
|
|
327
|
-
const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;
|
|
328
|
-
|
|
329
|
-
flushSync(() => {
|
|
330
|
-
setZoomLevel(currentZoom);
|
|
331
|
-
});
|
|
332
|
-
|
|
333
|
-
const currentOffset = getOffsetWithin(cardEl, container);
|
|
334
|
-
const currentCardCenterX = currentOffset.x + cardEl.offsetWidth / 2;
|
|
335
|
-
const currentCardCenterY = currentOffset.y + cardEl.offsetHeight / 2;
|
|
336
|
-
|
|
337
|
-
const endCardScreenX = currentCardCenterX - targetScrollLeft;
|
|
338
|
-
const endCardScreenY = currentCardCenterY - targetScrollTop;
|
|
339
|
-
|
|
340
|
-
const desiredScreenX = startCardScreenX + (endCardScreenX - startCardScreenX) * easedProgress;
|
|
341
|
-
const desiredScreenY = startCardScreenY + (endCardScreenY - startCardScreenY) * easedProgress;
|
|
342
|
-
|
|
343
|
-
const neededScrollLeft = currentCardCenterX - desiredScreenX;
|
|
344
|
-
const neededScrollTop = currentCardCenterY - desiredScreenY;
|
|
345
|
-
|
|
346
|
-
const maxScrollLeft = container.scrollWidth - container.clientWidth;
|
|
347
|
-
const maxScrollTop = container.scrollHeight - container.clientHeight;
|
|
348
|
-
container.scrollLeft = Math.max(0, Math.min(neededScrollLeft, maxScrollLeft));
|
|
349
|
-
container.scrollTop = Math.max(0, Math.min(neededScrollTop, maxScrollTop));
|
|
350
|
-
|
|
351
|
-
if (progress < 1) {
|
|
352
|
-
requestAnimationFrame(animate);
|
|
353
|
-
} else {
|
|
354
|
-
container.style.scrollBehavior = '';
|
|
355
|
-
setIsZooming(false);
|
|
356
|
-
setSelectedItem(null);
|
|
357
|
-
setPreSelectionState(null);
|
|
358
|
-
container.scrollLeft = targetScrollLeft;
|
|
359
|
-
container.scrollTop = targetScrollTop;
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
|
|
363
|
-
requestAnimationFrame(animate);
|
|
364
|
-
} else {
|
|
365
|
-
setSelectedItem(null);
|
|
366
|
-
if (preSelectionState) {
|
|
367
|
-
setZoomLevel(preSelectionState.zoom);
|
|
368
|
-
container.scrollTo({ left: preSelectionState.scrollLeft, top: preSelectionState.scrollTop, behavior: 'smooth' });
|
|
369
|
-
setPreSelectionState(null);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
} else {
|
|
373
|
-
setSelectedItem(null);
|
|
374
|
-
if (preSelectionState) {
|
|
375
|
-
setPreSelectionState(null);
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}, [preSelectionState, selectedItem, zoomLevel, setZoomLevel, containerRef, resolveId, viewMode]);
|
|
379
|
-
|
|
380
|
-
const clearSelection = useCallback(() => {
|
|
381
|
-
if (selectedItem) {
|
|
382
|
-
closeDetail();
|
|
383
|
-
}
|
|
384
|
-
}, [selectedItem, closeDetail]);
|
|
385
|
-
|
|
386
|
-
useEffect(() => {
|
|
387
|
-
return () => {
|
|
388
|
-
if (typeof window !== 'undefined' && pendingCenterRaf.current !== null) {
|
|
389
|
-
window.clearTimeout(pendingCenterRaf.current);
|
|
390
|
-
pendingCenterRaf.current = null;
|
|
391
|
-
}
|
|
392
|
-
};
|
|
393
|
-
}, []);
|
|
394
|
-
|
|
395
|
-
return {
|
|
396
|
-
selectedItem,
|
|
397
|
-
isZooming,
|
|
398
|
-
handleCardClick,
|
|
399
|
-
closeDetail,
|
|
400
|
-
clearSelection,
|
|
401
|
-
};
|
|
402
|
-
}
|