@shohojdhara/atomix 0.5.1 → 0.5.2

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 (123) hide show
  1. package/atomix.config.ts +12 -0
  2. package/build-tools/webpack-loader.js +5 -4
  3. package/dist/atomix.css +138 -17
  4. package/dist/atomix.css.map +1 -1
  5. package/dist/atomix.min.css +1 -1
  6. package/dist/atomix.min.css.map +1 -1
  7. package/dist/build-tools/webpack-loader.js +5 -4
  8. package/dist/charts.d.ts +23 -23
  9. package/dist/charts.js +40 -37
  10. package/dist/charts.js.map +1 -1
  11. package/dist/config.d.ts +624 -0
  12. package/dist/config.js +59 -0
  13. package/dist/config.js.map +1 -0
  14. package/dist/core.d.ts +2 -2
  15. package/dist/core.js +111 -50
  16. package/dist/core.js.map +1 -1
  17. package/dist/forms.d.ts +3 -6
  18. package/dist/forms.js +2 -2
  19. package/dist/forms.js.map +1 -1
  20. package/dist/heavy.d.ts +1 -1
  21. package/dist/heavy.js +173 -111
  22. package/dist/heavy.js.map +1 -1
  23. package/dist/index.d.ts +98 -65
  24. package/dist/index.esm.js +427 -422
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/index.js +394 -391
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.min.js +1 -1
  29. package/dist/index.min.js.map +1 -1
  30. package/dist/layout.js +59 -60
  31. package/dist/layout.js.map +1 -1
  32. package/dist/theme.js +4 -4
  33. package/dist/theme.js.map +1 -1
  34. package/package.json +14 -9
  35. package/scripts/atomix-cli.js +15 -1
  36. package/scripts/cli/__tests__/complexity-utils.test.js +24 -0
  37. package/scripts/cli/__tests__/detector.test.js +50 -0
  38. package/scripts/cli/__tests__/template-engine.test.js +23 -0
  39. package/scripts/cli/__tests__/test-setup.js +3 -0
  40. package/scripts/cli/commands/doctor.js +15 -3
  41. package/scripts/cli/commands/generate.js +113 -51
  42. package/scripts/cli/internal/ai-engine.js +30 -10
  43. package/scripts/cli/internal/complexity-utils.js +60 -0
  44. package/scripts/cli/internal/component-validator.js +49 -16
  45. package/scripts/cli/internal/generator.js +89 -36
  46. package/scripts/cli/internal/hook-generator.js +5 -2
  47. package/scripts/cli/internal/itcss-generator.js +16 -12
  48. package/scripts/cli/templates/next-templates.js +81 -30
  49. package/scripts/cli/templates/storybook-templates.js +12 -2
  50. package/scripts/cli/utils/detector.js +45 -7
  51. package/scripts/cli/utils/diagnostics.js +78 -0
  52. package/scripts/cli/utils/telemetry.js +13 -0
  53. package/src/components/Accordion/Accordion.stories.tsx +4 -0
  54. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +1 -1
  55. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +219 -0
  56. package/src/components/AtomixGlass/glass-utils.ts +1 -1
  57. package/src/components/Button/Button.tsx +114 -57
  58. package/src/components/Callout/Callout.tsx +4 -4
  59. package/src/components/Chart/ChartRenderer.tsx +1 -1
  60. package/src/components/Chart/DonutChart.tsx +11 -8
  61. package/src/components/EdgePanel/EdgePanel.tsx +119 -115
  62. package/src/components/Form/Select.tsx +4 -4
  63. package/src/components/List/List.tsx +4 -4
  64. package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
  65. package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
  66. package/src/components/ProductReview/ProductReview.tsx +4 -2
  67. package/src/components/Rating/Rating.tsx +4 -2
  68. package/src/components/SectionIntro/SectionIntro.tsx +4 -2
  69. package/src/components/Steps/Steps.tsx +1 -1
  70. package/src/components/Tabs/Tabs.tsx +5 -5
  71. package/src/components/Testimonial/Testimonial.tsx +4 -2
  72. package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
  73. package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
  74. package/src/layouts/CssGrid/CssGrid.tsx +215 -0
  75. package/src/layouts/CssGrid/index.ts +8 -0
  76. package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
  77. package/src/layouts/CssGrid/scripts/index.js +43 -0
  78. package/src/layouts/Grid/scripts/Container.js +139 -0
  79. package/src/layouts/Grid/scripts/Grid.js +184 -0
  80. package/src/layouts/Grid/scripts/GridCol.js +273 -0
  81. package/src/layouts/Grid/scripts/Row.js +154 -0
  82. package/src/layouts/Grid/scripts/index.js +48 -0
  83. package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
  84. package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
  85. package/src/lib/composables/useAccordion.ts +5 -5
  86. package/src/lib/composables/useAtomixGlass.ts +3 -3
  87. package/src/lib/composables/useBarChart.ts +2 -2
  88. package/src/lib/composables/useChart.ts +3 -2
  89. package/src/lib/composables/useChartToolbar.ts +48 -66
  90. package/src/lib/composables/useDataTable.ts +1 -1
  91. package/src/lib/composables/useDatePicker.ts +2 -2
  92. package/src/lib/composables/useEdgePanel.ts +45 -54
  93. package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
  94. package/src/lib/composables/usePhotoViewer.ts +2 -3
  95. package/src/lib/composables/usePieChart.ts +1 -1
  96. package/src/lib/composables/usePopover.ts +151 -139
  97. package/src/lib/composables/useSideMenu.ts +28 -41
  98. package/src/lib/composables/useSlider.ts +2 -6
  99. package/src/lib/composables/useTooltip.ts +2 -2
  100. package/src/lib/config/index.ts +39 -0
  101. package/src/lib/theme/devtools/Comparator.tsx +1 -1
  102. package/src/lib/theme/devtools/Inspector.tsx +1 -1
  103. package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
  104. package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
  105. package/src/styles/01-settings/_index.scss +1 -0
  106. package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
  107. package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
  108. package/src/styles/02-tools/_tools.glass.scss +6 -0
  109. package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
  110. package/src/styles/06-components/_components.atomix-glass.scss +4 -4
  111. package/src/lib/composables/useBreadcrumb.ts +0 -81
  112. package/src/lib/composables/useChartInteractions.ts +0 -123
  113. package/src/lib/composables/useChartPerformance.ts +0 -347
  114. package/src/lib/composables/useDropdown.ts +0 -338
  115. package/src/lib/composables/useModal.ts +0 -110
  116. package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
  117. package/src/lib/utils/displacement-generator.ts +0 -92
  118. package/src/lib/utils/memoryMonitor.ts +0 -191
  119. package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
  120. package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
  121. package/src/styles/06-components/_components.testbutton.scss +0 -212
  122. package/src/styles/06-components/_components.testtypecheck.scss +0 -212
  123. package/src/styles/06-components/_components.typedbutton.scss +0 -212
@@ -1,41 +1,116 @@
1
1
  @use '../01-settings/settings.config' as config;
2
2
  @use '../01-settings/settings.masonry-grid' as masonry;
3
3
  @use '../01-settings/settings.breakpoints' as breakpoints;
4
+ @use '../01-settings/settings.design-tokens' as tokens;
4
5
  @use '../02-tools/tools.rem' as *;
6
+ @use '../02-tools/tools.media-queries' as mq;
5
7
  @use 'sass:math';
6
8
 
9
+ // === MODERN CSS GRID FALLBACK IMPLEMENTATION ===
10
+ // Provides graceful degradation when JavaScript is disabled
11
+ .o-masonry-grid--css-fallback {
12
+ display: grid;
13
+ gap: masonry.$masonry-grid-gap;
14
+ // Use auto-fit with minmax for responsive columns
15
+ grid-template-columns: repeat(auto-fit, minmax(var(--atomix-masonry-min-column-width, 250px), 1fr));
16
+
17
+ // Mobile-first responsive adjustments
18
+ @include mq.media-up('sm') {
19
+ gap: masonry.$masonry-grid-gap-sm;
20
+ }
21
+
22
+ @include mq.media-up('md') {
23
+ gap: masonry.$masonry-grid-gap;
24
+ }
25
+
26
+ @include mq.media-up('lg') {
27
+ gap: masonry.$masonry-grid-gap-lg;
28
+ }
29
+
30
+ .o-masonry-grid__item {
31
+ // CSS Grid items don't need position: absolute
32
+ position: static;
33
+ opacity: 1;
34
+ visibility: visible;
35
+ width: 100%;
36
+
37
+ // Enhanced accessibility for CSS Grid
38
+ &:focus {
39
+ outline: masonry.$masonry-grid-focus-ring-width solid masonry.$masonry-grid-focus-ring-color;
40
+ outline-offset: masonry.$masonry-grid-focus-ring-offset;
41
+ z-index: masonry.$masonry-grid-z-index-focus;
42
+ }
43
+ }
44
+ }
45
+
7
46
  .o-masonry-grid {
8
47
  $root: &;
9
48
 
10
- // Base styles
49
+ // Mobile-first base styles with design system tokens
11
50
  position: relative;
12
51
  width: 100%;
13
- min-height: 100px; // Minimum height to prevent layout shift
52
+ min-height: masonry.$masonry-grid-min-height-mobile;
14
53
 
15
- // Animation variant
54
+ // Enhanced focus management for accessibility
55
+ &:focus-within {
56
+ .o-masonry-grid__item:focus {
57
+ outline: masonry.$masonry-grid-focus-ring-width solid masonry.$masonry-grid-focus-ring-color;
58
+ outline-offset: masonry.$masonry-grid-focus-ring-offset;
59
+ z-index: masonry.$masonry-grid-z-index-focus;
60
+ }
61
+ }
62
+
63
+ // Animation variant using design system transitions
16
64
  &--animate {
17
65
  .o-masonry-grid__item {
18
- transition:
19
- opacity 0.3s ease,
20
- top 0.3s ease,
21
- left 0.3s ease;
66
+ transition: masonry.$masonry-grid-item-transition;
67
+ // Modern CSS custom property for fine-grained animation control
68
+ @supports (transition-behavior: allow-discrete) {
69
+ transition-behavior: allow-discrete;
70
+ }
22
71
  }
23
72
  }
24
73
 
25
- // Item styles
74
+ // Item styles with enhanced accessibility and design system integration
26
75
  &__item {
27
76
  box-sizing: border-box;
28
77
  width: 100%;
78
+ // Design system color tokens for background
79
+ background-color: var(--#{config.$prefix}body-bg);
80
+
81
+ // Enhanced focus states with design system tokens
82
+ &:focus {
83
+ outline: masonry.$masonry-grid-focus-ring-width solid masonry.$masonry-grid-focus-ring-color;
84
+ outline-offset: masonry.$masonry-grid-focus-ring-offset;
85
+ z-index: masonry.$masonry-grid-z-index-focus;
86
+
87
+ // Subtle scale effect for better visual feedback
88
+ transform: scale(1.02);
89
+ transition: transform var(--#{config.$prefix}transition-duration-fast) var(--#{config.$prefix}transition-timing-base);
90
+ }
91
+
92
+ // High contrast mode support
93
+ @media (prefers-contrast: high) {
94
+ &:focus {
95
+ outline-width: calc(#{masonry.$masonry-grid-focus-ring-width} * 2);
96
+ }
97
+ }
29
98
 
30
99
  // Prevent FOUC (Flash of Unstyled Content)
31
100
  &:not([style*='position: absolute']) {
32
101
  opacity: 0;
33
102
  visibility: hidden;
103
+ // Use CSS Custom Property for progressive enhancement
104
+ @supports (view-transition-name: masonry-item) {
105
+ view-transition-name: masonry-item;
106
+ }
34
107
  }
35
108
  }
36
109
 
37
- // Loading states for items
110
+ // Loading states with modern design system colors
38
111
  &__item-loading {
112
+ position: relative;
113
+
39
114
  &::before {
40
115
  content: '';
41
116
  position: absolute;
@@ -43,54 +118,117 @@
43
118
  left: 0;
44
119
  width: 100%;
45
120
  height: 100%;
46
- background-color: var(--#{config.$prefix}gray-10);
47
- z-index: 1;
48
- border-radius: 4px;
121
+ // Design system loading state background
122
+ background: var(--#{config.$prefix}gray-10);
123
+ z-index: masonry.$masonry-grid-z-index-loading;
124
+ border-radius: var(--#{config.$prefix}border-radius-sm);
125
+
126
+ // Smooth fade-in animation
127
+ animation: masonry-loading-fade-in 0.5s ease-out forwards;
49
128
  }
50
129
 
51
130
  img {
52
131
  opacity: 0;
132
+ // CSS @property for smoother opacity transitions
133
+ @supports (property: opacity) and (animation-timeline: view()) {
134
+ @property --masonry-img-opacity {
135
+ syntax: '<number>';
136
+ inherits: false;
137
+ initial-value: 0;
138
+ }
139
+ opacity: var(--masonry-img-opacity);
140
+ }
53
141
  }
54
142
  }
55
143
 
56
- // Loaded state with fade-in animation
57
- &__item-loaded img {
58
- animation: masonry-item-fade-in 0.3s ease forwards;
144
+ // Enhanced loaded state with modern CSS features
145
+ &__item-loaded {
146
+ img {
147
+ // Modern CSS animation with design system tokens
148
+ animation: masonry-item-fade-in masonry.$masonry-grid-item-animation-duration masonry.$masonry-grid-item-animation-timing forwards;
149
+
150
+ // CSS @property for precise opacity control
151
+ @supports (property: opacity) and (animation-timeline: view()) {
152
+ @property --masonry-img-opacity {
153
+ syntax: '<number>';
154
+ inherits: false;
155
+ initial-value: 0;
156
+ }
157
+ opacity: var(--masonry-img-opacity);
158
+ animation: masonry-item-fade-in-advanced masonry.$masonry-grid-item-animation-duration masonry.$masonry-grid-item-animation-timing forwards;
159
+ }
160
+ }
59
161
  }
60
162
 
61
- // Progressive loading indicator
163
+ // Modern progressive loading indicator
62
164
  &--loading-images {
63
165
  position: relative;
64
166
 
65
167
  &::after {
66
168
  content: '';
67
169
  position: absolute;
68
- bottom: 16px;
69
- right: 16px;
70
- width: 24px;
71
- height: 24px;
72
- border: 2px solid rgba(0, 0, 0, 0.1);
170
+ bottom: var(--#{config.$prefix}spacing-16);
171
+ right: var(--#{config.$prefix}spacing-16);
172
+ width: masonry.$masonry-grid-loading-indicator-size;
173
+ height: masonry.$masonry-grid-loading-indicator-size;
174
+ border: masonry.$masonry-grid-loading-indicator-border-width solid rgba(0, 0, 0, 0.1);
73
175
  border-radius: 50%;
74
- border-top-color: var(--#{config.$prefix}brand-border-subtle);
176
+ border-top-color: masonry.$masonry-grid-loading-indicator-color;
75
177
  animation: masonry-spinner 0.8s linear infinite;
76
- z-index: 1000;
178
+ z-index: masonry.$masonry-grid-z-index-loading;
77
179
  pointer-events: none;
180
+
181
+ // Dark mode support
182
+ @media (prefers-color-scheme: dark) {
183
+ border-color: rgba(255, 255, 255, 0.1);
184
+ border-top-color: var(--#{config.$prefix}brand-border-subtle-dark);
185
+ }
78
186
  }
79
187
  }
80
188
 
81
- // Animations
189
+ // Responsive adjustments for desktop
190
+ @include mq.media-up('md') {
191
+ min-height: masonry.$masonry-grid-min-height-desktop;
192
+ }
193
+
194
+ // Modern animations with CSS custom properties
82
195
  @keyframes masonry-spinner {
83
196
  to {
84
197
  transform: rotate(360deg);
85
198
  }
86
199
  }
87
200
 
201
+ @keyframes masonry-loading-fade-in {
202
+ from {
203
+ opacity: 0;
204
+ transform: scale(0.95);
205
+ }
206
+ to {
207
+ opacity: 1;
208
+ transform: scale(1);
209
+ }
210
+ }
211
+
88
212
  @keyframes masonry-item-fade-in {
89
213
  from {
90
214
  opacity: 0;
215
+ transform: translateY(20px);
91
216
  }
92
217
  to {
93
218
  opacity: 1;
219
+ transform: translateY(0);
220
+ }
221
+ }
222
+
223
+ // Advanced animation with CSS @property for smooth transitions
224
+ @keyframes masonry-item-fade-in-advanced {
225
+ from {
226
+ --masonry-img-opacity: 0;
227
+ transform: translateY(20px) scale(0.95);
228
+ }
229
+ to {
230
+ --masonry-img-opacity: 1;
231
+ transform: translateY(0) scale(1);
94
232
  }
95
233
  }
96
234
  }
@@ -1,6 +1,7 @@
1
1
  // Component: AtomixGlass
2
2
  // =============================================================================
3
3
 
4
+ @use '../01-settings/settings.atomix-glass' as *;
4
5
  @use '../02-tools/tools.component' as *;
5
6
 
6
7
  .c-atomix-glass {
@@ -9,7 +10,6 @@
9
10
  // Local, sane defaults (kept minimal)
10
11
  --_glass-radius: var(--atomix-glass-radius, var(--atomix-radius-md, 0.75rem));
11
12
  --_glass-transform: var(--atomix-glass-transform, translateZ(0));
12
- --_glass-backdrop: var(--atomix-glass-border-backdrop, blur(14px) saturate(140%));
13
13
 
14
14
  --atomix-glass-position: absolute;
15
15
  --atomix-glass-border-width: var(--atomix-spacing-0-5, 0.125rem);
@@ -221,7 +221,7 @@
221
221
  &__border-backdrop {
222
222
  mix-blend-mode: overlay;
223
223
  z-index: var(--_glass-z-border-1);
224
- backdrop-filter: var(--_glass-backdrop);
224
+ backdrop-filter: blur(30px) saturate(140%) brightness(110%);
225
225
  box-shadow: var(--atomix-glass-border-shadow);
226
226
  }
227
227
 
@@ -291,8 +291,8 @@
291
291
 
292
292
  &__content {
293
293
  position: relative;
294
- width: var(--atomix-glass-container-width);
295
- height: var(--atomix-glass-container-height);
294
+ width: var(--atomix-glass-width);
295
+ height: var(--atomix-glass-height);
296
296
  border-radius: var(--_glass-radius);
297
297
  z-index: var(--_glass-z-content);
298
298
  text-shadow: var(--atomix-glass-container-text-shadow);
@@ -1,81 +0,0 @@
1
- import { BreadcrumbItemType } from '../../components/Breadcrumb/Breadcrumb';
2
- import { BREADCRUMB } from '../constants/components';
3
-
4
- interface BreadcrumbOptions {
5
- items: BreadcrumbItemType[];
6
- divider?: React.ReactNode;
7
- className?: string;
8
- 'aria-label'?: string;
9
- }
10
-
11
- /**
12
- * Breadcrumb state and functionality
13
- * @param initialOptions - Initial breadcrumb options
14
- * @returns Breadcrumb state and methods
15
- */
16
- export function useBreadcrumb(initialOptions?: Partial<BreadcrumbOptions>) {
17
- // Default breadcrumb options
18
- const defaultOptions: BreadcrumbOptions = {
19
- items: [],
20
- divider: BREADCRUMB.DEFAULTS.DIVIDER,
21
- className: '',
22
- 'aria-label': 'Breadcrumb',
23
- ...initialOptions,
24
- };
25
-
26
- /**
27
- * Generate breadcrumb class based on options
28
- * @param options - Breadcrumb options
29
- * @returns Class string
30
- */
31
- const generateBreadcrumbClass = (options: Partial<BreadcrumbOptions>): string => {
32
- const { className = '' } = options;
33
-
34
- return [BREADCRUMB.CLASSES.BASE, className].filter(Boolean).join(' ').trim();
35
- };
36
-
37
- /**
38
- * Generate breadcrumb item class
39
- * @param item - Breadcrumb item
40
- * @param isLast - Whether this is the last item
41
- * @returns Class string
42
- */
43
- const generateItemClass = (item: BreadcrumbItemType, isLast: boolean): string => {
44
- return [BREADCRUMB.CLASSES.ITEM, item.active || isLast ? BREADCRUMB.CLASSES.ACTIVE : '']
45
- .filter(Boolean)
46
- .join(' ')
47
- .trim();
48
- };
49
-
50
- /**
51
- * Check if an item should be rendered as a link
52
- * @param item - Breadcrumb item
53
- * @param isLast - Whether this is the last item
54
- * @returns Whether item should be a link
55
- */
56
- const isItemLink = (item: BreadcrumbItemType, isLast: boolean): boolean => {
57
- return Boolean(item.href && !item.active && !isLast);
58
- };
59
-
60
- /**
61
- * Parse items from a JSON string
62
- * @param jsonString - JSON string of items
63
- * @returns Array of breadcrumb items
64
- */
65
- const parseItemsFromJson = (jsonString: string): BreadcrumbItemType[] => {
66
- try {
67
- return JSON.parse(jsonString) as BreadcrumbItemType[];
68
- } catch (error) {
69
- console.error('Error parsing breadcrumb items:', error);
70
- return [];
71
- }
72
- };
73
-
74
- return {
75
- defaultOptions,
76
- generateBreadcrumbClass,
77
- generateItemClass,
78
- isItemLink,
79
- parseItemsFromJson,
80
- };
81
- }
@@ -1,123 +0,0 @@
1
- import { useCallback, useRef, useState } from 'react';
2
-
3
- interface InteractionState {
4
- isDragging: boolean;
5
- isZooming: boolean;
6
- lastPointerPos: { x: number; y: number } | null;
7
- zoomLevel: number;
8
- panOffset: { x: number; y: number };
9
- }
10
-
11
- export function useChartInteractions() {
12
- const [state, setState] = useState<InteractionState>({
13
- isDragging: false,
14
- isZooming: false,
15
- lastPointerPos: null,
16
- zoomLevel: 1,
17
- panOffset: { x: 0, y: 0 },
18
- });
19
-
20
- const wheelTimeoutRef = useRef<NodeJS.Timeout | null>(null);
21
- const lastWheelTime = useRef(0);
22
-
23
- // Improved wheel handling for both mouse and trackpad
24
- const handleWheel = useCallback((event: WheelEvent) => {
25
- event.preventDefault();
26
-
27
- const now = Date.now();
28
- const timeDelta = now - lastWheelTime.current;
29
- lastWheelTime.current = now;
30
-
31
- // Detect trackpad vs mouse wheel
32
- const isTrackpad = Math.abs(event.deltaY) < 50 && timeDelta < 50;
33
- const sensitivity = isTrackpad ? 0.01 : 0.1;
34
-
35
- if (wheelTimeoutRef.current) {
36
- clearTimeout(wheelTimeoutRef.current);
37
- }
38
-
39
- setState(prev => {
40
- const rect = (event.target as Element).getBoundingClientRect();
41
- const centerX = event.clientX - rect.left;
42
- const centerY = event.clientY - rect.top;
43
-
44
- // Zoom calculation
45
- const zoomFactor = 1 - event.deltaY * sensitivity;
46
- const newZoomLevel = Math.max(0.1, Math.min(10, prev.zoomLevel * zoomFactor));
47
-
48
- // Pan to zoom center
49
- const zoomRatio = newZoomLevel / prev.zoomLevel;
50
- const newPanOffset = {
51
- x: centerX - (centerX - prev.panOffset.x) * zoomRatio,
52
- y: centerY - (centerY - prev.panOffset.y) * zoomRatio,
53
- };
54
-
55
- return {
56
- ...prev,
57
- zoomLevel: newZoomLevel,
58
- panOffset: newPanOffset,
59
- isZooming: true,
60
- };
61
- });
62
-
63
- wheelTimeoutRef.current = setTimeout(() => {
64
- setState(prev => ({ ...prev, isZooming: false }));
65
- }, 150);
66
- }, []);
67
-
68
- // Unified pointer events for mouse and touch
69
- const handlePointerDown = useCallback((event: PointerEvent) => {
70
- setState(prev => ({
71
- ...prev,
72
- isDragging: true,
73
- lastPointerPos: { x: event.clientX, y: event.clientY },
74
- }));
75
- (event.target as Element).setPointerCapture(event.pointerId);
76
- }, []);
77
-
78
- const handlePointerMove = useCallback((event: PointerEvent) => {
79
- setState(prev => {
80
- if (!prev.isDragging || !prev.lastPointerPos) return prev;
81
-
82
- const deltaX = event.clientX - prev.lastPointerPos.x;
83
- const deltaY = event.clientY - prev.lastPointerPos.y;
84
-
85
- return {
86
- ...prev,
87
- panOffset: {
88
- x: prev.panOffset.x + deltaX,
89
- y: prev.panOffset.y + deltaY,
90
- },
91
- lastPointerPos: { x: event.clientX, y: event.clientY },
92
- };
93
- });
94
- }, []);
95
-
96
- const handlePointerUp = useCallback((event: PointerEvent) => {
97
- setState(prev => ({
98
- ...prev,
99
- isDragging: false,
100
- lastPointerPos: null,
101
- }));
102
- (event.target as Element).releasePointerCapture(event.pointerId);
103
- }, []);
104
-
105
- const resetView = useCallback(() => {
106
- setState(prev => ({
107
- ...prev,
108
- zoomLevel: 1,
109
- panOffset: { x: 0, y: 0 },
110
- }));
111
- }, []);
112
-
113
- return {
114
- state,
115
- handlers: {
116
- onWheel: handleWheel,
117
- onPointerDown: handlePointerDown,
118
- onPointerMove: handlePointerMove,
119
- onPointerUp: handlePointerUp,
120
- },
121
- resetView,
122
- };
123
- }