@shohojdhara/atomix 0.5.0 → 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 (168) hide show
  1. package/atomix.config.ts +12 -0
  2. package/build-tools/webpack-loader.js +5 -4
  3. package/dist/atomix.css +230 -83
  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 +24 -23
  9. package/dist/charts.js +271 -369
  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 +3 -2
  15. package/dist/core.js +342 -382
  16. package/dist/core.js.map +1 -1
  17. package/dist/forms.d.ts +4 -6
  18. package/dist/forms.js +233 -334
  19. package/dist/forms.js.map +1 -1
  20. package/dist/heavy.d.ts +11 -2
  21. package/dist/heavy.js +406 -445
  22. package/dist/heavy.js.map +1 -1
  23. package/dist/index.d.ts +109 -65
  24. package/dist/index.esm.js +654 -748
  25. package/dist/index.esm.js.map +1 -1
  26. package/dist/index.js +621 -717
  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 +24 -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 +1 -133
  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/AtomixGlass.tsx +188 -128
  55. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +63 -91
  56. package/src/components/AtomixGlass/PerformanceDashboard.tsx +153 -201
  57. package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +9 -6
  58. package/src/components/AtomixGlass/glass-utils.ts +51 -1
  59. package/src/components/AtomixGlass/stories/AnimationFeatures.stories.tsx +52 -46
  60. package/src/components/AtomixGlass/stories/Examples.stories.tsx +573 -236
  61. package/src/components/AtomixGlass/stories/Playground.stories.tsx +88 -41
  62. package/src/components/AtomixGlass/stories/argTypes.ts +19 -19
  63. package/src/components/AtomixGlass/stories/shared-components.tsx +7 -12
  64. package/src/components/AtomixGlass/stories/types.ts +3 -3
  65. package/src/components/Button/Button.tsx +114 -57
  66. package/src/components/Callout/Callout.tsx +4 -4
  67. package/src/components/Chart/ChartRenderer.tsx +1 -1
  68. package/src/components/Chart/DonutChart.tsx +11 -8
  69. package/src/components/EdgePanel/EdgePanel.tsx +119 -115
  70. package/src/components/Form/Select.tsx +4 -4
  71. package/src/components/List/List.tsx +4 -4
  72. package/src/components/Navigation/SideMenu/SideMenu.tsx +6 -6
  73. package/src/components/PhotoViewer/PhotoViewerImage.tsx +1 -1
  74. package/src/components/ProductReview/ProductReview.tsx +4 -2
  75. package/src/components/Rating/Rating.tsx +4 -2
  76. package/src/components/SectionIntro/SectionIntro.tsx +4 -2
  77. package/src/components/Steps/Steps.tsx +1 -1
  78. package/src/components/Tabs/Tabs.tsx +5 -5
  79. package/src/components/Testimonial/Testimonial.tsx +4 -2
  80. package/src/components/VideoPlayer/VideoPlayer.tsx +4 -2
  81. package/src/layouts/CssGrid/CssGrid.stories.tsx +464 -0
  82. package/src/layouts/CssGrid/CssGrid.tsx +215 -0
  83. package/src/layouts/CssGrid/index.ts +8 -0
  84. package/src/layouts/CssGrid/scripts/CssGrid.js +284 -0
  85. package/src/layouts/CssGrid/scripts/index.js +43 -0
  86. package/src/layouts/Grid/scripts/Container.js +139 -0
  87. package/src/layouts/Grid/scripts/Grid.js +184 -0
  88. package/src/layouts/Grid/scripts/GridCol.js +273 -0
  89. package/src/layouts/Grid/scripts/Row.js +154 -0
  90. package/src/layouts/Grid/scripts/index.js +48 -0
  91. package/src/layouts/MasonryGrid/MasonryGrid.tsx +71 -59
  92. package/src/lib/composables/atomix-glass/useGlassSize.ts +1 -1
  93. package/src/lib/composables/useAccordion.ts +5 -5
  94. package/src/lib/composables/useAtomixGlass.ts +111 -74
  95. package/src/lib/composables/useAtomixGlassStyles.ts +0 -2
  96. package/src/lib/composables/useBarChart.ts +2 -2
  97. package/src/lib/composables/useChart.ts +3 -2
  98. package/src/lib/composables/useChartToolbar.ts +48 -66
  99. package/src/lib/composables/useDataTable.ts +1 -1
  100. package/src/lib/composables/useDatePicker.ts +2 -2
  101. package/src/lib/composables/useEdgePanel.ts +45 -54
  102. package/src/lib/composables/useHeroBackgroundSlider.ts +5 -5
  103. package/src/lib/composables/usePhotoViewer.ts +2 -3
  104. package/src/lib/composables/usePieChart.ts +1 -1
  105. package/src/lib/composables/usePopover.ts +151 -139
  106. package/src/lib/composables/useSideMenu.ts +28 -41
  107. package/src/lib/composables/useSlider.ts +2 -6
  108. package/src/lib/composables/useTooltip.ts +2 -2
  109. package/src/lib/config/index.ts +39 -0
  110. package/src/lib/constants/components.ts +1 -0
  111. package/src/lib/theme/devtools/Comparator.tsx +1 -1
  112. package/src/lib/theme/devtools/Inspector.tsx +1 -1
  113. package/src/lib/theme/devtools/LiveEditor.tsx +1 -1
  114. package/src/lib/theme/runtime/ThemeProvider.tsx +1 -1
  115. package/src/lib/types/components.ts +1 -0
  116. package/src/styles/01-settings/_index.scss +1 -0
  117. package/src/styles/01-settings/_settings.atomix-glass.scss +174 -0
  118. package/src/styles/01-settings/_settings.masonry-grid.scss +42 -6
  119. package/src/styles/02-tools/_tools.glass.scss +6 -0
  120. package/src/styles/05-objects/_objects.masonry-grid.scss +162 -24
  121. package/src/styles/06-components/_components.atomix-glass.scss +160 -99
  122. package/scripts/cli/__tests__/README.md +0 -81
  123. package/scripts/cli/__tests__/basic.test.js +0 -116
  124. package/scripts/cli/__tests__/clean.test.js +0 -278
  125. package/scripts/cli/__tests__/component-generator.test.js +0 -332
  126. package/scripts/cli/__tests__/component-validator.test.js +0 -433
  127. package/scripts/cli/__tests__/generator.test.js +0 -613
  128. package/scripts/cli/__tests__/glass-motion.test.js +0 -256
  129. package/scripts/cli/__tests__/integration.test.js +0 -938
  130. package/scripts/cli/__tests__/migrate.test.js +0 -74
  131. package/scripts/cli/__tests__/security.test.js +0 -206
  132. package/scripts/cli/__tests__/theme-bridge.test.js +0 -507
  133. package/scripts/cli/__tests__/token-manager.test.js +0 -251
  134. package/scripts/cli/__tests__/token-provider.test.js +0 -361
  135. package/scripts/cli/__tests__/utils.test.js +0 -165
  136. package/src/components/AtomixGlass/stories/AnimationTests.stories.tsx +0 -95
  137. package/src/components/AtomixGlass/stories/CardExamples.stories.tsx +0 -212
  138. package/src/components/AtomixGlass/stories/Customization.stories.tsx +0 -131
  139. package/src/components/AtomixGlass/stories/DashboardExamples.stories.tsx +0 -348
  140. package/src/components/AtomixGlass/stories/EcommerceExamples.stories.tsx +0 -410
  141. package/src/components/AtomixGlass/stories/FormExamples.stories.tsx +0 -436
  142. package/src/components/AtomixGlass/stories/HeroExamples.stories.tsx +0 -264
  143. package/src/components/AtomixGlass/stories/InteractivePlayground.stories.tsx +0 -247
  144. package/src/components/AtomixGlass/stories/MobileUIExamples.stories.tsx +0 -418
  145. package/src/components/AtomixGlass/stories/ModalExamples.stories.tsx +0 -402
  146. package/src/components/AtomixGlass/stories/Modes.stories.tsx +0 -1082
  147. package/src/components/AtomixGlass/stories/Overview.stories.tsx +0 -497
  148. package/src/components/AtomixGlass/stories/Performance.stories.tsx +0 -103
  149. package/src/components/AtomixGlass/stories/PresetGallery.stories.tsx +0 -335
  150. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +0 -395
  151. package/src/components/AtomixGlass/stories/WidgetExamples.stories.tsx +0 -441
  152. package/src/components/TypedButton/TypedButton.stories.tsx +0 -59
  153. package/src/components/TypedButton/TypedButton.tsx +0 -39
  154. package/src/components/TypedButton/index.ts +0 -2
  155. package/src/lib/composables/useBreadcrumb.ts +0 -81
  156. package/src/lib/composables/useChartInteractions.ts +0 -123
  157. package/src/lib/composables/useChartPerformance.ts +0 -347
  158. package/src/lib/composables/useDropdown.ts +0 -338
  159. package/src/lib/composables/useModal.ts +0 -110
  160. package/src/lib/composables/useTypedButton.ts +0 -66
  161. package/src/lib/hooks/usePerformanceMonitor.ts +0 -148
  162. package/src/lib/utils/displacement-generator.ts +0 -92
  163. package/src/lib/utils/memoryMonitor.ts +0 -191
  164. package/src/styles/01-settings/_settings.testtypecheck.scss +0 -53
  165. package/src/styles/01-settings/_settings.typedbutton.scss +0 -53
  166. package/src/styles/06-components/_components.testbutton.scss +0 -212
  167. package/src/styles/06-components/_components.testtypecheck.scss +0 -212
  168. package/src/styles/06-components/_components.typedbutton.scss +0 -212
@@ -7,9 +7,7 @@ import React, {
7
7
  useRef,
8
8
  useState,
9
9
  useCallback,
10
- useMemo,
11
10
  Children,
12
- cloneElement,
13
11
  isValidElement,
14
12
  } from 'react';
15
13
  // Import styles for scoped CSS modules
@@ -122,7 +120,7 @@ export const MasonryGrid = forwardRef<HTMLDivElement, MasonryGridProps>(
122
120
  // === REFS & STATE ===
123
121
  const [columns, setColumns] = useState(xs);
124
122
  const [positions, setPositions] = useState<ItemPosition[]>([]);
125
- const [layoutComplete, setLayoutComplete] = useState(false);
123
+ const [, setLayoutComplete] = useState(false);
126
124
  const [loadingImages, setLoadingImages] = useState(false);
127
125
  const containerRef = useRef<HTMLDivElement>(null);
128
126
  const columnHeights = useRef<number[]>([]);
@@ -178,6 +176,32 @@ export const MasonryGrid = forwardRef<HTMLDivElement, MasonryGridProps>(
178
176
  setItems(newItems);
179
177
  }, [children]);
180
178
 
179
+ // === MANAGE ITEM LAYOUT ===
180
+ const calculateLayout = useCallback(() => {
181
+ if (!containerRef.current || items.length === 0) return;
182
+ const containerWidth = containerRef.current.offsetWidth;
183
+ const colWidth = (containerWidth - gap * (columns - 1)) / columns;
184
+ columnHeights.current = Array(columns).fill(0);
185
+ const newPositions: ItemPosition[] = [];
186
+ items.forEach((item, index) => {
187
+ if (item.ref.current) {
188
+ // Find the shortest column
189
+ const shortestCol = columnHeights.current.indexOf(Math.min(...columnHeights.current));
190
+ const left = shortestCol * (colWidth + gap);
191
+ const top = columnHeights.current[shortestCol] ?? 0;
192
+ const height = item.ref.current.offsetHeight;
193
+ columnHeights.current[shortestCol] = top + height + gap;
194
+ newPositions[index] = {
195
+ left,
196
+ top,
197
+ width: colWidth,
198
+ height,
199
+ };
200
+ }
201
+ });
202
+ setPositions(newPositions);
203
+ }, [items, columns, gap]);
204
+
181
205
  // === TRACK & MANAGE IMAGES ===
182
206
 
183
207
  const handleImageLoad = useCallback(
@@ -195,30 +219,28 @@ export const MasonryGrid = forwardRef<HTMLDivElement, MasonryGridProps>(
195
219
  itemElement.classList.remove('o-masonry-grid__item-loading');
196
220
  }
197
221
  }
198
- // Ensure layout is recalculated after DOM paints the item image (prevents overlap on slow/late image loads)
199
- requestAnimationFrame(() => {
200
- requestAnimationFrame(() => {
222
+ // Schedule layout recalculation after next paint to prevent overlap
223
+ const scheduleLayoutUpdate = () => {
224
+ const frameId = requestAnimationFrame(() => {
225
+ onImageLoad?.(imagesLoadedCount.current, totalImagesCount.current);
201
226
  calculateLayout();
202
227
  });
203
- });
204
- onImageLoad?.(imagesLoadedCount.current, totalImagesCount.current);
228
+ return () => cancelAnimationFrame(frameId);
229
+ };
230
+
231
+ // Clean up previous scheduled updates
232
+ const cleanup = scheduleLayoutUpdate();
205
233
 
206
234
  // If all images have loaded, update loading state and complete layout
207
235
  if (imagesLoadedCount.current >= totalImagesCount.current && totalImagesCount.current > 0) {
208
236
  setLayoutComplete(true);
209
- setLoadingImages(false); // This ensures the loading class is removed *immediately* after images load
210
- // Force a double requestAnimationFrame for final layout calculation after all images are loaded (guarantees DOM paint)
211
- requestAnimationFrame(() => {
212
- requestAnimationFrame(() => {
213
- calculateLayout();
214
- // As a failsafe, if still present for some render lag, force another setLoadingImages(false)
215
- setLoadingImages(false);
216
- });
217
- });
237
+ setLoadingImages(false);
238
+ setTimeout(() => cleanup(), 0); // Clean up after current execution
239
+ scheduleLayoutUpdate();
218
240
  onLayoutComplete?.();
219
241
  }
220
242
  },
221
- [onImageLoad, onLayoutComplete, imagesLoaded]
243
+ [onImageLoad, onLayoutComplete, imagesLoaded, calculateLayout]
222
244
  );
223
245
 
224
246
  const trackImages = useCallback(() => {
@@ -262,40 +284,28 @@ export const MasonryGrid = forwardRef<HTMLDivElement, MasonryGridProps>(
262
284
  };
263
285
  }, [imagesLoaded, handleImageLoad, onLayoutComplete]);
264
286
 
265
- // === MANAGE ITEM LAYOUT ===
266
- const calculateLayout = useCallback(() => {
267
- if (!containerRef.current || items.length === 0) return;
268
- const containerWidth = containerRef.current.offsetWidth;
269
- const colWidth = (containerWidth - gap * (columns - 1)) / columns;
270
- columnHeights.current = Array(columns).fill(0);
271
- const newPositions: ItemPosition[] = [];
272
- items.forEach((item, index) => {
273
- if (item.ref.current) {
274
- // Find the shortest column
275
- const shortestCol = columnHeights.current.indexOf(Math.min(...columnHeights.current));
276
- const left = shortestCol * (colWidth + gap);
277
- const top = columnHeights.current[shortestCol] ?? 0;
278
- const height = item.ref.current.offsetHeight;
279
- columnHeights.current[shortestCol] = top + height + gap;
280
- newPositions[index] = {
281
- left,
282
- top,
283
- width: colWidth,
284
- height,
285
- };
286
- }
287
- });
288
- setPositions(newPositions);
289
- }, [items, columns, gap]);
290
-
291
287
  // === OBSERVE CONTAINER RESIZE ===
292
288
  useEffect(() => {
293
289
  if (!containerRef.current) return undefined;
290
+
294
291
  let animationFrame: ReturnType<typeof requestAnimationFrame> | null = null;
295
- const observer = new ResizeObserver(() => {
296
- if (animationFrame) cancelAnimationFrame(animationFrame);
297
- animationFrame = requestAnimationFrame(() => calculateLayout());
292
+ let lastWidth = 0;
293
+
294
+ const observer = new ResizeObserver((entries) => {
295
+ const entry = entries[0];
296
+ if (!entry) return;
297
+ const currentWidth = entry.contentRect.width;
298
+
299
+ // Only recalculate if width actually changed (prevents excessive calculations)
300
+ if (Math.abs(currentWidth - lastWidth) > 1) {
301
+ if (animationFrame) cancelAnimationFrame(animationFrame);
302
+ animationFrame = requestAnimationFrame(() => {
303
+ calculateLayout();
304
+ lastWidth = currentWidth;
305
+ });
306
+ }
298
307
  });
308
+
299
309
  observer.observe(containerRef.current);
300
310
  return () => {
301
311
  observer.disconnect();
@@ -317,26 +327,28 @@ export const MasonryGrid = forwardRef<HTMLDivElement, MasonryGridProps>(
317
327
  // Only reset layoutComplete when items or columns change
318
328
  }, [items, columns, calculateLayout, imagesLoaded, trackImages]);
319
329
 
320
- // === NEW: Add ResizeObservers to all grid items for bulletproof image+content measurement ===
330
+ // === ADD RESIZEOBSERVERS TO GRID ITEMS FOR DYNAMIC CONTENT MEASUREMENT ===
321
331
  React.useEffect(() => {
322
- // Clean up old observers if items ever change
323
332
  const observers: ResizeObserver[] = [];
333
+ let animationFrame: ReturnType<typeof requestAnimationFrame> | null = null;
334
+
335
+ // Debounced layout calculation for item resize events
336
+ const debouncedCalculateLayout = () => {
337
+ if (animationFrame) cancelAnimationFrame(animationFrame);
338
+ animationFrame = requestAnimationFrame(calculateLayout);
339
+ };
340
+
324
341
  items.forEach(item => {
325
342
  if (item.ref.current) {
326
- const obs = new ResizeObserver(() => {
327
- // Double rAF: ensures layout only runs after DOM/paint/async renders
328
- requestAnimationFrame(() => {
329
- requestAnimationFrame(() => {
330
- calculateLayout();
331
- });
332
- });
333
- });
343
+ const obs = new ResizeObserver(debouncedCalculateLayout);
334
344
  obs.observe(item.ref.current);
335
345
  observers.push(obs);
336
346
  }
337
347
  });
348
+
338
349
  return () => {
339
350
  observers.forEach(obs => obs.disconnect());
351
+ if (animationFrame) cancelAnimationFrame(animationFrame);
340
352
  };
341
353
  }, [items, calculateLayout]);
342
354
 
@@ -376,7 +388,7 @@ export const MasonryGrid = forwardRef<HTMLDivElement, MasonryGridProps>(
376
388
  return (
377
389
  <div
378
390
  key={item.id}
379
- ref={item.ref as React.LegacyRef<HTMLDivElement>}
391
+ ref={item.ref as React.RefObject<HTMLDivElement>}
380
392
  style={{ opacity: 0, position: 'absolute' }}
381
393
  >
382
394
  {item.element}
@@ -386,7 +398,7 @@ export const MasonryGrid = forwardRef<HTMLDivElement, MasonryGridProps>(
386
398
  return (
387
399
  <div
388
400
  key={item.id}
389
- ref={item.ref as React.LegacyRef<HTMLDivElement>}
401
+ ref={item.ref as React.RefObject<HTMLDivElement>}
390
402
  className="o-masonry-grid__item"
391
403
  style={{
392
404
  position: 'absolute',
@@ -6,7 +6,7 @@ import type { GlassSize } from '../../types/components';
6
6
  const { CONSTANTS } = ATOMIX_GLASS;
7
7
 
8
8
  interface UseGlassSizeProps {
9
- glassRef: React.RefObject<HTMLDivElement>;
9
+ glassRef: React.RefObject<HTMLDivElement | null>;
10
10
  effectiveBorderRadius: number;
11
11
  cachedRectRef?: React.MutableRefObject<DOMRect | null>;
12
12
  }
@@ -1,5 +1,5 @@
1
1
  import { AccordionProps, AccordionState, IconPosition, ElementRefs } from '../types/components';
2
- import { useState, useEffect, useRef } from 'react';
2
+ import { useState, useEffect, useRef, useCallback } from 'react';
3
3
  import { ACCORDION } from '../constants/components';
4
4
 
5
5
  /**
@@ -72,20 +72,20 @@ export function useAccordion(
72
72
  /**
73
73
  * Update panel height based on content
74
74
  */
75
- const updatePanelHeight = (): void => {
75
+ const updatePanelHeight = useCallback((): void => {
76
76
  if (contentRef.current && panelRef.current) {
77
77
  const height = isOpen ? `${contentRef.current.clientHeight}px` : '0px';
78
78
  panelRef.current.style.setProperty(ACCORDION.CSS_VARS.PANEL_HEIGHT, height);
79
79
  setPanelHeight(height);
80
80
  }
81
- };
81
+ }, [isOpen]);
82
82
 
83
83
  /**
84
84
  * Effect to update panel height when open state changes
85
85
  */
86
86
  useEffect(() => {
87
87
  updatePanelHeight();
88
- }, [isOpen]);
88
+ }, [isOpen, updatePanelHeight]);
89
89
 
90
90
  /**
91
91
  * Effect to handle window resize and update panel height
@@ -99,7 +99,7 @@ export function useAccordion(
99
99
 
100
100
  window.addEventListener('resize', handleResize);
101
101
  return () => window.removeEventListener('resize', handleResize);
102
- }, [isOpen]);
102
+ }, [isOpen, updatePanelHeight]);
103
103
 
104
104
  /**
105
105
  * Generate accordion class names based on state
@@ -153,9 +153,9 @@ const setCachedBackgroundDetection = (
153
153
  };
154
154
 
155
155
  interface UseAtomixGlassOptions extends Omit<AtomixGlassProps, 'children'> {
156
- glassRef: React.RefObject<HTMLDivElement>;
157
- contentRef: React.RefObject<HTMLDivElement>;
158
- wrapperRef?: React.RefObject<HTMLDivElement>;
156
+ glassRef: React.RefObject<HTMLDivElement | null>;
157
+ contentRef: React.RefObject<HTMLDivElement | null>;
158
+ wrapperRef?: React.RefObject<HTMLDivElement | null>;
159
159
  children?: React.ReactNode;
160
160
  isFixedOrSticky?: boolean;
161
161
  // Phase 1: Time-Based Animation System
@@ -775,49 +775,14 @@ export function useAtomixGlass({
775
775
  // ── Raw mouse handler — writes to TARGET refs only ──────────────────
776
776
  // The lerp loop (below) reads the targets and incrementally
777
777
  // moves the "current" refs toward them for liquid smoothing.
778
- const handleGlobalMousePosition = useCallback(
779
- (globalPos: MousePosition) => {
780
- if (externalGlobalMousePosition && externalMouseOffset) {
781
- return;
782
- }
783
-
784
- if (effectiveWithoutEffects) {
785
- return;
786
- }
787
-
788
- const container = mouseContainer?.current || glassRef.current;
789
- if (!container) {
790
- return;
791
- }
792
-
793
- // Use cached rect if available, otherwise get new one
794
- let rect = cachedRectRef.current;
795
- if (!rect || rect.width === 0 || rect.height === 0) {
796
- rect = container.getBoundingClientRect();
797
- cachedRectRef.current = rect;
798
- }
799
778
 
800
- if (rect.width === 0 || rect.height === 0) {
801
- return;
802
- }
803
-
804
- const center = calculateElementCenter(rect);
805
-
806
- // Write raw target — the lerp loop will smoothly pursue it
807
- targetMouseOffsetRef.current = {
808
- x: ((globalPos.x - center.x) / rect.width) * 100,
809
- y: ((globalPos.y - center.y) / rect.height) * 100,
810
- };
811
- targetGlobalMousePositionRef.current = globalPos;
812
- },
813
- [
814
- mouseContainer,
815
- glassRef,
816
- externalGlobalMousePosition,
817
- externalMouseOffset,
818
- effectiveWithoutEffects,
819
- ]
820
- );
779
+ const stopLerpLoop = useCallback(() => {
780
+ lerpActiveRef.current = false;
781
+ if (lerpRafRef.current !== null) {
782
+ cancelAnimationFrame(lerpRafRef.current);
783
+ lerpRafRef.current = null;
784
+ }
785
+ }, []);
821
786
 
822
787
  // ── Lerp animation loop ─────────────────────────────────────────────
823
788
  // Continuously interpolates the current offset toward the target.
@@ -827,20 +792,18 @@ export function useAtomixGlass({
827
792
  lerpActiveRef.current = true;
828
793
 
829
794
  const LERP_T = CONSTANTS.LERP_FACTOR; // 0.08 – lower = more viscous
830
- const EPSILON = 0.05; // Stop iterating when close enough
795
+ const EPSILON = 0.01; // Snap when close enough
831
796
 
832
797
  const tick = () => {
833
798
  if (!lerpActiveRef.current) return;
834
799
 
835
- // Add ref validity check to prevent memory leaks
836
- if (!glassRef.current || !wrapperRef?.current) {
800
+ if (!glassRef.current) {
837
801
  lerpActiveRef.current = false;
838
802
  return;
839
803
  }
840
804
 
841
805
  const cur = internalMouseOffsetRef.current;
842
806
  const tgt = targetMouseOffsetRef.current;
843
-
844
807
  const dx = tgt.x - cur.x;
845
808
  const dy = tgt.y - cur.y;
846
809
 
@@ -848,22 +811,53 @@ export function useAtomixGlass({
848
811
  if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
849
812
  internalMouseOffsetRef.current = { ...tgt };
850
813
  internalGlobalMousePositionRef.current = { ...targetGlobalMousePositionRef.current };
851
- } else {
852
- internalMouseOffsetRef.current = {
853
- x: lerp(cur.x, tgt.x, LERP_T),
854
- y: lerp(cur.y, tgt.y, LERP_T),
855
- };
856
- const curG = internalGlobalMousePositionRef.current;
857
- const tgtG = targetGlobalMousePositionRef.current;
858
- internalGlobalMousePositionRef.current = {
859
- x: lerp(curG.x, tgtG.x, LERP_T),
860
- y: lerp(curG.y, tgtG.y, LERP_T),
861
- };
814
+
815
+ // Final update and stop
816
+ updateAtomixGlassStyles(
817
+ wrapperRef?.current || null,
818
+ glassRef.current,
819
+ {
820
+ mouseOffset: internalMouseOffsetRef.current,
821
+ globalMousePosition: internalGlobalMousePositionRef.current,
822
+ glassSize,
823
+ isHovered,
824
+ isActive,
825
+ isOverLight: overLightConfig.isOverLight,
826
+ baseOverLightConfig: overLightConfig,
827
+ effectiveBorderRadius,
828
+ effectiveWithoutEffects,
829
+ effectiveReducedMotion,
830
+ elasticity,
831
+ directionalScale: isActive && Boolean(onClick) ? 'scale(0.96)' : 'scale(1)',
832
+ onClick,
833
+ withLiquidBlur,
834
+ blurAmount,
835
+ saturation,
836
+ padding,
837
+ isFixedOrSticky,
838
+ }
839
+ );
840
+
841
+ stopLerpLoop();
842
+ return;
862
843
  }
863
844
 
864
- // Imperative style update with the smoothed values
845
+ // Smooth step
846
+ internalMouseOffsetRef.current = {
847
+ x: lerp(cur.x, tgt.x, LERP_T),
848
+ y: lerp(cur.y, tgt.y, LERP_T),
849
+ };
850
+
851
+ const curG = internalGlobalMousePositionRef.current;
852
+ const tgtG = targetGlobalMousePositionRef.current;
853
+ internalGlobalMousePositionRef.current = {
854
+ x: lerp(curG.x, tgtG.x, LERP_T),
855
+ y: lerp(curG.y, tgtG.y, LERP_T),
856
+ };
857
+
858
+ // Imperative style update
865
859
  updateAtomixGlassStyles(
866
- wrapperRef.current,
860
+ wrapperRef?.current || null,
867
861
  glassRef.current,
868
862
  {
869
863
  mouseOffset: internalMouseOffsetRef.current,
@@ -908,15 +902,58 @@ export function useAtomixGlass({
908
902
  saturation,
909
903
  padding,
910
904
  isFixedOrSticky,
905
+ stopLerpLoop,
911
906
  ]);
912
907
 
913
- const stopLerpLoop = useCallback(() => {
914
- lerpActiveRef.current = false;
915
- if (lerpRafRef.current !== null) {
916
- cancelAnimationFrame(lerpRafRef.current);
917
- lerpRafRef.current = null;
918
- }
919
- }, []);
908
+ const handleGlobalMousePosition = useCallback(
909
+ (globalPos: MousePosition) => {
910
+ if (externalGlobalMousePosition && externalMouseOffset) {
911
+ return;
912
+ }
913
+
914
+ if (effectiveWithoutEffects) {
915
+ return;
916
+ }
917
+
918
+ const container = mouseContainer?.current || glassRef.current;
919
+ if (!container) {
920
+ return;
921
+ }
922
+
923
+ // Use cached rect if available, otherwise get new one
924
+ let rect = cachedRectRef.current;
925
+ if (!rect || rect.width === 0 || rect.height === 0) {
926
+ rect = container.getBoundingClientRect();
927
+ cachedRectRef.current = rect;
928
+ }
929
+
930
+ if (rect.width === 0 || rect.height === 0) {
931
+ return;
932
+ }
933
+
934
+ const center = calculateElementCenter(rect);
935
+
936
+ // Write raw target — the lerp loop will smoothly pursue it
937
+ targetMouseOffsetRef.current = {
938
+ x: ((globalPos.x - center.x) / rect.width) * 100,
939
+ y: ((globalPos.y - center.y) / rect.height) * 100,
940
+ };
941
+ targetGlobalMousePositionRef.current = globalPos;
942
+
943
+ // Ensure the lerp loop is running to smoothly chase the new target
944
+ if (!lerpActiveRef.current) {
945
+ startLerpLoop();
946
+ }
947
+ },
948
+ [
949
+ mouseContainer,
950
+ glassRef,
951
+ externalGlobalMousePosition,
952
+ externalMouseOffset,
953
+ effectiveWithoutEffects,
954
+ startLerpLoop,
955
+ ]
956
+ );
920
957
 
921
958
  // Subscribe to shared mouse tracker
922
959
  useEffect(() => {
@@ -929,8 +966,8 @@ export function useAtomixGlass({
929
966
  }
930
967
 
931
968
  const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
932
-
933
- // Start the lerp loop — it will smoothly chase the target
969
+
970
+ // Initial start
934
971
  startLerpLoop();
935
972
 
936
973
  const updateRect = () => {
@@ -966,14 +1003,14 @@ export function useAtomixGlass({
966
1003
  }
967
1004
  };
968
1005
  }, [
1006
+ externalGlobalMousePosition,
1007
+ externalMouseOffset,
1008
+ effectiveWithoutEffects,
969
1009
  handleGlobalMousePosition,
970
1010
  startLerpLoop,
971
1011
  stopLerpLoop,
972
1012
  mouseContainer,
973
1013
  glassRef,
974
- externalGlobalMousePosition,
975
- externalMouseOffset,
976
- effectiveWithoutEffects,
977
1014
  ]);
978
1015
 
979
1016
  // Also call updateStyles on other state changes (hover, active, etc)
@@ -298,8 +298,6 @@ export const updateAtomixGlassStyles = (
298
298
  // Container variables
299
299
  const style = containerElement.style;
300
300
 
301
- style.setProperty('--atomix-glass-container-width', !isFixedOrSticky ? '100%' : `${glassSize.width}`);
302
- style.setProperty('--atomix-glass-container-height', !isFixedOrSticky ? '100%' : `${glassSize.height}`);
303
301
  style.setProperty('--atomix-glass-container-padding', padding);
304
302
  style.setProperty('--atomix-glass-container-radius', `${effectiveBorderRadius}px`);
305
303
 
@@ -325,7 +325,7 @@ export function useBarChart(datasets: ChartDataset[], options: BarChartOptions =
325
325
  }
326
326
  return value.toString();
327
327
  },
328
- [options.valueFormatter]
328
+ [options]
329
329
  );
330
330
 
331
331
  // Calculate data label position
@@ -352,7 +352,7 @@ export function useBarChart(datasets: ChartDataset[], options: BarChartOptions =
352
352
  };
353
353
  }
354
354
  },
355
- [options.dataLabelPosition]
355
+ [options]
356
356
  );
357
357
 
358
358
  return {
@@ -104,9 +104,10 @@ export function useChart(initialProps?: Partial<ChartProps>) {
104
104
 
105
105
  // Cleanup animation frame on unmount
106
106
  useEffect(() => {
107
+ const currentRef = animationFrameRef.current;
107
108
  return () => {
108
- if (animationFrameRef.current) {
109
- cancelAnimationFrame(animationFrameRef.current);
109
+ if (currentRef) {
110
+ cancelAnimationFrame(currentRef);
110
111
  }
111
112
  };
112
113
  }, []);