@shohojdhara/atomix 0.4.1 → 0.4.3

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 (131) hide show
  1. package/dist/atomix.css +9351 -9259
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +4 -4
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.d.ts +12 -19
  6. package/dist/charts.js +555 -358
  7. package/dist/charts.js.map +1 -1
  8. package/dist/core.d.ts +21 -24
  9. package/dist/core.js +435 -265
  10. package/dist/core.js.map +1 -1
  11. package/dist/forms.d.ts +11 -18
  12. package/dist/forms.js +411 -257
  13. package/dist/forms.js.map +1 -1
  14. package/dist/heavy.d.ts +14 -21
  15. package/dist/heavy.js +409 -254
  16. package/dist/heavy.js.map +1 -1
  17. package/dist/index.d.ts +38 -41
  18. package/dist/index.esm.js +731 -487
  19. package/dist/index.esm.js.map +1 -1
  20. package/dist/index.js +733 -492
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.min.js +1 -1
  23. package/dist/index.min.js.map +1 -1
  24. package/package.json +1 -1
  25. package/scripts/atomix-cli.js +34 -1
  26. package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
  27. package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
  28. package/src/components/AtomixGlass/README.md +5 -5
  29. package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
  30. package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
  31. package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
  32. package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
  33. package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
  34. package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
  35. package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
  36. package/src/components/Badge/Badge.stories.tsx +1 -1
  37. package/src/components/Badge/Badge.tsx +1 -1
  38. package/src/components/Breadcrumb/Breadcrumb.tsx +90 -77
  39. package/src/components/Breadcrumb/index.ts +2 -2
  40. package/src/components/Button/Button.stories.tsx +1 -1
  41. package/src/components/Button/Button.tsx +2 -1
  42. package/src/components/Button/README.md +2 -2
  43. package/src/components/Callout/Callout.test.tsx +3 -3
  44. package/src/components/Callout/Callout.tsx +2 -2
  45. package/src/components/Callout/README.md +2 -2
  46. package/src/components/Card/Card.tsx +31 -11
  47. package/src/components/Chart/Chart.stories.tsx +1 -1
  48. package/src/components/Chart/Chart.tsx +5 -5
  49. package/src/components/Chart/TreemapChart.tsx +37 -29
  50. package/src/components/DatePicker/readme.md +3 -3
  51. package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
  52. package/src/components/Dropdown/Dropdown.tsx +276 -273
  53. package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
  54. package/src/components/Footer/FooterLink.tsx +2 -2
  55. package/src/components/Form/Checkbox.stories.tsx +1 -1
  56. package/src/components/Form/Checkbox.tsx +1 -1
  57. package/src/components/Form/Input.stories.tsx +1 -1
  58. package/src/components/Form/Input.tsx +1 -1
  59. package/src/components/Form/Radio.stories.tsx +1 -1
  60. package/src/components/Form/Radio.tsx +1 -1
  61. package/src/components/Form/Select.stories.tsx +1 -1
  62. package/src/components/Form/Select.tsx +1 -1
  63. package/src/components/Form/Textarea.stories.tsx +1 -1
  64. package/src/components/Form/Textarea.tsx +1 -1
  65. package/src/components/Hero/Hero.stories.tsx +2 -2
  66. package/src/components/Hero/Hero.tsx +2 -2
  67. package/src/components/Messages/Messages.stories.tsx +1 -1
  68. package/src/components/Messages/Messages.tsx +2 -2
  69. package/src/components/Modal/Modal.stories.tsx +1 -1
  70. package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
  71. package/src/components/Navigation/Nav/Nav.tsx +1 -1
  72. package/src/components/Navigation/Nav/NavItem.tsx +6 -3
  73. package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
  74. package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
  75. package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
  76. package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
  77. package/src/components/Pagination/Pagination.stories.tsx +1 -1
  78. package/src/components/Pagination/Pagination.tsx +1 -1
  79. package/src/components/Popover/Popover.stories.tsx +1 -1
  80. package/src/components/Popover/Popover.tsx +1 -1
  81. package/src/components/Progress/Progress.tsx +1 -1
  82. package/src/components/Rating/Rating.stories.tsx +1 -1
  83. package/src/components/Rating/Rating.test.tsx +73 -0
  84. package/src/components/Rating/Rating.tsx +25 -37
  85. package/src/components/Spinner/Spinner.tsx +1 -1
  86. package/src/components/Steps/Steps.stories.tsx +1 -1
  87. package/src/components/Steps/Steps.tsx +2 -2
  88. package/src/components/Tabs/Tabs.stories.tsx +1 -1
  89. package/src/components/Tabs/Tabs.tsx +1 -1
  90. package/src/components/Todo/Todo.tsx +0 -1
  91. package/src/components/Toggle/Toggle.stories.tsx +1 -1
  92. package/src/components/Toggle/Toggle.tsx +1 -1
  93. package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
  94. package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
  95. package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
  96. package/src/lib/composables/__tests__/useChart.test.ts +50 -0
  97. package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
  98. package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
  99. package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
  100. package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
  101. package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
  102. package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
  103. package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
  104. package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
  105. package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
  106. package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
  107. package/src/lib/composables/glass-styles.ts +302 -0
  108. package/src/lib/composables/index.ts +0 -4
  109. package/src/lib/composables/useAtomixGlass.ts +331 -522
  110. package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
  111. package/src/lib/composables/useBarChart.ts +1 -1
  112. package/src/lib/composables/useBreadcrumb.ts +6 -6
  113. package/src/lib/composables/useChart.ts +104 -21
  114. package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
  115. package/src/lib/composables/useSlider.ts +66 -34
  116. package/src/lib/theme/devtools/CLI.ts +1 -1
  117. package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
  118. package/src/lib/types/components.ts +18 -21
  119. package/src/lib/utils/__tests__/dom.test.ts +100 -0
  120. package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
  121. package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
  122. package/src/styles/02-tools/_tools.utility-api.scss +6 -6
  123. package/src/styles/06-components/_components.accordion.scss +0 -2
  124. package/src/styles/06-components/_components.chart.scss +0 -1
  125. package/src/styles/06-components/_components.dropdown.scss +0 -1
  126. package/src/styles/06-components/_components.edge-panel.scss +0 -2
  127. package/src/styles/06-components/_components.photoviewer.scss +0 -1
  128. package/src/styles/06-components/_components.river.scss +0 -1
  129. package/src/styles/06-components/_components.slider.scss +0 -3
  130. package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
  131. package/src/styles/99-utilities/_utilities.text.scss +1 -0
@@ -1,7 +1,12 @@
1
- import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
1
+ import React, {
2
+ useCallback,
3
+ useEffect,
4
+ useMemo,
5
+ useRef,
6
+ useState,
7
+ } from 'react';
2
8
  import type {
3
9
  AtomixGlassProps,
4
- DisplacementMode,
5
10
  GlassSize,
6
11
  MousePosition,
7
12
  OverLightConfig,
@@ -17,6 +22,7 @@ import {
17
22
  extractBorderRadiusFromDOMElement,
18
23
  validateGlassSize,
19
24
  } from '../../components/AtomixGlass/glass-utils';
25
+ import { updateAtomixGlassStyles } from './useAtomixGlassStyles';
20
26
 
21
27
  const { CONSTANTS } = ATOMIX_GLASS;
22
28
 
@@ -35,7 +41,10 @@ const backgroundDetectionCache = new WeakMap<HTMLElement, BackgroundDetectionCac
35
41
  * Compare two OverLightConfig values for equality
36
42
  * Handles primitives (boolean, 'auto') and objects with deep comparison
37
43
  */
38
- const compareOverLightConfig = (config1: OverLightConfig, config2: OverLightConfig): boolean => {
44
+ const compareOverLightConfig = (
45
+ config1: OverLightConfig,
46
+ config2: OverLightConfig
47
+ ): boolean => {
39
48
  // Primitive comparison for boolean and 'auto'
40
49
  if (typeof config1 !== 'object' || config1 === null) {
41
50
  return config1 === config2;
@@ -139,6 +148,7 @@ const setCachedBackgroundDetection = (
139
148
  interface UseAtomixGlassOptions extends Omit<AtomixGlassProps, 'children'> {
140
149
  glassRef: React.RefObject<HTMLDivElement>;
141
150
  contentRef: React.RefObject<HTMLDivElement>;
151
+ wrapperRef?: React.RefObject<HTMLDivElement>;
142
152
  children?: React.ReactNode;
143
153
  }
144
154
 
@@ -147,11 +157,11 @@ interface UseAtomixGlassReturn {
147
157
  isHovered: boolean;
148
158
  isActive: boolean;
149
159
  glassSize: GlassSize;
150
- dynamicCornerRadius: number;
151
- effectiveCornerRadius: number;
160
+ dynamicBorderRadius: number;
161
+ effectiveBorderRadius: number;
152
162
  effectiveReducedMotion: boolean;
153
163
  effectiveHighContrast: boolean;
154
- effectiveDisableEffects: boolean;
164
+ effectiveWithoutEffects: boolean;
155
165
  detectedOverLight: boolean;
156
166
  globalMousePosition: MousePosition;
157
167
  mouseOffset: MousePosition;
@@ -182,6 +192,8 @@ interface UseAtomixGlassReturn {
182
192
  handleKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
183
193
  }
184
194
 
195
+ import { useGlassSize } from './atomix-glass/useGlassSize';
196
+
185
197
  /**
186
198
  * Composable hook for AtomixGlass component logic
187
199
  * Manages all state, calculations, and event handlers
@@ -189,48 +201,57 @@ interface UseAtomixGlassReturn {
189
201
  export function useAtomixGlass({
190
202
  glassRef,
191
203
  contentRef,
192
- cornerRadius,
204
+ wrapperRef,
205
+ borderRadius,
193
206
  globalMousePosition: externalGlobalMousePosition,
194
207
  mouseOffset: externalMouseOffset,
195
208
  mouseContainer,
196
209
  overLight = ATOMIX_GLASS.DEFAULTS.OVER_LIGHT,
197
210
  reducedMotion = false,
198
211
  highContrast = false,
199
- disableEffects = false,
212
+ withoutEffects = false,
200
213
  elasticity = 0.05,
201
214
  onClick,
202
- debugCornerRadius = false,
215
+ debugBorderRadius = false,
203
216
  debugOverLight = false,
217
+ debugPerformance = false,
204
218
  children,
219
+ blurAmount,
220
+ saturation,
221
+ padding,
222
+ withLiquidBlur,
205
223
  }: UseAtomixGlassOptions): UseAtomixGlassReturn {
206
224
  // State
207
225
  const [isHovered, setIsHovered] = useState(false);
208
226
  const [isActive, setIsActive] = useState(false);
209
- const [glassSize, setGlassSize] = useState<GlassSize>({ width: 270, height: 69 });
210
- const [internalGlobalMousePosition, setInternalGlobalMousePosition] = useState<MousePosition>({
211
- x: 0,
212
- y: 0,
213
- });
214
- const [internalMouseOffset, setInternalMouseOffset] = useState<MousePosition>({ x: 0, y: 0 });
215
- const [dynamicCornerRadius, setDynamicCornerRadius] = useState<number>(
227
+
228
+ // Mouse tracking refs
229
+ const cachedRectRef = useRef<DOMRect | null>(null);
230
+ const internalGlobalMousePositionRef = useRef<MousePosition>({ x: 0, y: 0 });
231
+ const internalMouseOffsetRef = useRef<MousePosition>({ x: 0, y: 0 });
232
+
233
+ const [dynamicBorderRadius, setDynamicCornerRadius] = useState<number>(
216
234
  CONSTANTS.DEFAULT_CORNER_RADIUS
217
235
  );
218
236
  const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(false);
219
237
  const [userPrefersHighContrast, setUserPrefersHighContrast] = useState(false);
220
238
  const [detectedOverLight, setDetectedOverLight] = useState(false);
221
239
 
222
- // Use shared module-level cache (no per-instance cache needed)
223
-
224
240
  // Memoized derived values
225
- const effectiveCornerRadius = useMemo(() => {
226
- if (cornerRadius !== undefined) {
227
- const result = Math.max(0, cornerRadius);
241
+ const effectiveBorderRadius = useMemo(() => {
242
+ if (borderRadius !== undefined) {
243
+ const result = Math.max(0, borderRadius);
228
244
  return result;
229
245
  }
230
-
231
- const result = Math.max(0, dynamicCornerRadius);
246
+ const result = Math.max(0, dynamicBorderRadius);
232
247
  return result;
233
- }, [cornerRadius, dynamicCornerRadius]);
248
+ }, [borderRadius, dynamicBorderRadius]);
249
+
250
+ const { glassSize } = useGlassSize({
251
+ glassRef,
252
+ effectiveBorderRadius,
253
+ cachedRectRef
254
+ });
234
255
 
235
256
  const effectiveReducedMotion = useMemo(
236
257
  () => reducedMotion || userPrefersReducedMotion,
@@ -242,20 +263,14 @@ export function useAtomixGlass({
242
263
  [highContrast, userPrefersHighContrast]
243
264
  );
244
265
 
245
- const effectiveDisableEffects = useMemo(
246
- () => disableEffects || effectiveReducedMotion,
247
- [disableEffects, effectiveReducedMotion]
248
- );
249
-
250
- const globalMousePosition = useMemo(
251
- () => externalGlobalMousePosition || internalGlobalMousePosition,
252
- [externalGlobalMousePosition, internalGlobalMousePosition]
266
+ const effectiveWithoutEffects = useMemo(
267
+ () => withoutEffects || effectiveReducedMotion,
268
+ [withoutEffects, effectiveReducedMotion]
253
269
  );
254
270
 
255
- const mouseOffset = useMemo(
256
- () => externalMouseOffset || internalMouseOffset,
257
- [externalMouseOffset, internalMouseOffset]
258
- );
271
+ // Return static/initial values for rendering, but internal updates use refs
272
+ const globalMousePosition = externalGlobalMousePosition || internalGlobalMousePositionRef.current;
273
+ const mouseOffset = externalMouseOffset || internalMouseOffsetRef.current;
259
274
 
260
275
  // Extract border-radius from children
261
276
  useEffect(() => {
@@ -284,10 +299,7 @@ export function useAtomixGlass({
284
299
  setDynamicCornerRadius(extractedRadius);
285
300
  }
286
301
  } catch (error) {
287
- if (
288
- (typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') &&
289
- debugCornerRadius
290
- ) {
302
+ if ((typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') && debugBorderRadius) {
291
303
  console.error('[AtomixGlass] Error extracting corner radius:', error);
292
304
  }
293
305
  }
@@ -296,13 +308,12 @@ export function useAtomixGlass({
296
308
  extractRadius();
297
309
  const timeoutId = setTimeout(extractRadius, 100);
298
310
  return () => clearTimeout(timeoutId);
299
- }, [children, debugCornerRadius, contentRef]);
311
+ }, [children, debugBorderRadius, contentRef]);
300
312
 
301
313
  // Media query handlers and background detection
302
314
  useEffect(() => {
303
315
  // Only run auto-detection for 'auto' mode or object config (which uses auto-detection)
304
- const shouldDetect =
305
- overLight === 'auto' || (typeof overLight === 'object' && overLight !== null);
316
+ const shouldDetect = (overLight === 'auto' || (typeof overLight === 'object' && overLight !== null));
306
317
 
307
318
  if (shouldDetect && glassRef.current) {
308
319
  const element = glassRef.current;
@@ -351,35 +362,14 @@ export function useAtomixGlass({
351
362
  const bgImage = computedStyle.backgroundImage;
352
363
 
353
364
  // Check for solid color backgrounds
354
- if (
355
- bgColor &&
356
- bgColor !== 'rgba(0, 0, 0, 0)' &&
357
- bgColor !== 'transparent' &&
358
- bgColor !== 'initial' &&
359
- bgColor !== 'none'
360
- ) {
365
+ if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent' && bgColor !== 'initial' && bgColor !== 'none') {
361
366
  const rgb = bgColor.match(/\d+/g);
362
367
  if (rgb && rgb.length >= 3) {
363
368
  const r = Number(rgb[0]);
364
369
  const g = Number(rgb[1]);
365
370
  const b = Number(rgb[2]);
366
371
 
367
- // Validate RGB values are valid numbers
368
- if (
369
- !isNaN(r) &&
370
- !isNaN(g) &&
371
- !isNaN(b) &&
372
- isFinite(r) &&
373
- isFinite(g) &&
374
- isFinite(b) &&
375
- r >= 0 &&
376
- r <= 255 &&
377
- g >= 0 &&
378
- g <= 255 &&
379
- b >= 0 &&
380
- b <= 255
381
- ) {
382
- // Only consider if it's not pure black or very dark
372
+ if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
383
373
  if (r > 10 || g > 10 || b > 10) {
384
374
  const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
385
375
  if (!isNaN(luminance) && isFinite(luminance)) {
@@ -392,100 +382,63 @@ export function useAtomixGlass({
392
382
  }
393
383
  }
394
384
 
395
- // Check for image backgrounds
396
385
  if (bgImage && bgImage !== 'none' && bgImage !== 'initial') {
397
- // For image backgrounds, assume medium luminance
398
386
  totalLuminance += 0.5;
399
387
  validSamples++;
400
388
  hasValidBackground = true;
401
389
  }
402
390
  } catch (styleError) {
403
- // Silently continue if getting computed style fails for this element
404
- if (typeof process === 'undefined' || process.env?.NODE_ENV === 'development') {
405
- console.debug('AtomixGlass: Error getting computed style for element:', styleError);
406
- }
391
+ // Silently continue
407
392
  }
408
393
 
409
- // Move to parent element for next iteration
410
394
  if (currentElement) {
411
395
  currentElement = currentElement.parentElement;
412
396
  depth++;
413
397
  } else {
414
- break; // Exit loop if currentElement becomes null
398
+ break;
415
399
  }
416
400
  }
417
401
 
418
- // More conservative detection with better error handling
419
402
  if (hasValidBackground && validSamples > 0) {
420
403
  const avgLuminance = totalLuminance / validSamples;
421
404
  if (!isNaN(avgLuminance) && isFinite(avgLuminance)) {
422
- let threshold = 0.7; // Conservative threshold for overlight
405
+ let threshold = 0.7;
423
406
 
424
- // If overLight is an object, use its threshold property with validation
425
407
  if (typeof overLight === 'object' && overLight !== null) {
426
408
  const objConfig = overLight as OverLightObjectConfig;
427
409
  if (objConfig.threshold !== undefined) {
428
- const configThreshold =
429
- typeof objConfig.threshold === 'number' &&
430
- !isNaN(objConfig.threshold) &&
431
- isFinite(objConfig.threshold)
432
- ? objConfig.threshold
433
- : 0.7;
410
+ const configThreshold = typeof objConfig.threshold === 'number' && !isNaN(objConfig.threshold) ? objConfig.threshold : 0.7;
434
411
  threshold = Math.min(0.9, Math.max(0.1, configThreshold));
435
412
  }
436
413
  }
437
414
 
438
415
  const isOverLightDetected = avgLuminance > threshold;
439
-
440
- // Cache the result in shared cache
441
- setCachedBackgroundDetection(
442
- element.parentElement,
443
- overLight,
444
- isOverLightDetected,
445
- threshold
446
- );
447
-
416
+ setCachedBackgroundDetection(element.parentElement, overLight, isOverLightDetected, threshold);
448
417
  setDetectedOverLight(isOverLightDetected);
449
418
  } else {
450
- // Invalid luminance calculation, default to false
451
419
  const result = false;
452
- const threshold =
453
- typeof overLight === 'object' && overLight !== null
454
- ? (overLight as OverLightObjectConfig).threshold || 0.7
455
- : 0.7;
420
+ const threshold = typeof overLight === 'object' && overLight !== null
421
+ ? (overLight as OverLightObjectConfig).threshold || 0.7
422
+ : 0.7;
456
423
  setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
457
424
  setDetectedOverLight(result);
458
425
  }
459
426
  } else {
460
- // Default to false if no valid background found
461
427
  const result = false;
462
- const threshold =
463
- typeof overLight === 'object' && overLight !== null
464
- ? (overLight as OverLightObjectConfig).threshold || 0.7
465
- : 0.7;
428
+ const threshold = typeof overLight === 'object' && overLight !== null
429
+ ? (overLight as OverLightObjectConfig).threshold || 0.7
430
+ : 0.7;
466
431
  setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
467
432
  setDetectedOverLight(result);
468
433
  }
469
434
  } catch (error) {
470
- // Enhanced error logging with context
471
- if (typeof process === 'undefined' || process.env?.NODE_ENV === 'development') {
472
- console.warn('AtomixGlass: Error detecting background brightness:', error);
473
- }
474
435
  const result = false;
475
- if (element && element.parentElement) {
476
- const threshold =
477
- typeof overLight === 'object' && overLight !== null
478
- ? (overLight as OverLightObjectConfig).threshold || 0.7
479
- : 0.7;
480
- setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
481
- }
482
436
  setDetectedOverLight(result);
483
437
  }
484
438
  }, 150);
485
439
 
486
440
  return () => clearTimeout(timeoutId);
487
441
  } else if (typeof overLight === 'boolean') {
488
- // For boolean values, disable auto-detection
489
442
  setDetectedOverLight(false);
490
443
  }
491
444
 
@@ -518,152 +471,119 @@ export function useAtomixGlass({
518
471
 
519
472
  return () => {
520
473
  try {
521
- if (mediaQueryReducedMotion.removeEventListener) {
522
- mediaQueryReducedMotion.removeEventListener('change', handleReducedMotionChange);
523
- mediaQueryHighContrast.removeEventListener('change', handleHighContrastChange);
524
- } else if (mediaQueryReducedMotion.removeListener) {
525
- mediaQueryReducedMotion.removeListener(handleReducedMotionChange);
526
- mediaQueryHighContrast.removeListener(handleHighContrastChange);
527
- }
474
+ // cleanup
528
475
  } catch (cleanupError) {
529
- console.error('AtomixGlass: Error cleaning up media query listeners:', cleanupError);
476
+ // ignore
530
477
  }
531
478
  };
532
479
  } catch (error) {
533
- console.error('AtomixGlass: Error setting up media queries:', error);
534
480
  return undefined;
535
481
  }
536
482
  }, [overLight, glassRef, debugOverLight]);
537
483
 
538
- // Mouse tracking using shared global tracker
539
- // Cache bounding rect to avoid repeated getBoundingClientRect calls
540
- const cachedRectRef = useRef<DOMRect | null>(null);
541
- const updateRectRef = useRef<number | null>(null);
484
+ /**
485
+ * Get effective overLight value based on configuration
486
+ */
487
+ const getEffectiveOverLight = useCallback(() => {
488
+ if (typeof overLight === 'boolean') {
489
+ return overLight;
490
+ }
491
+ if (overLight === 'auto') {
492
+ return detectedOverLight;
493
+ }
494
+ if (typeof overLight === 'object' && overLight !== null) {
495
+ return detectedOverLight;
496
+ }
497
+ return false;
498
+ }, [overLight, detectedOverLight]);
542
499
 
543
- // Handle mouse position updates from shared tracker
544
- const handleGlobalMousePosition = useCallback(
545
- (globalPos: MousePosition) => {
546
- if (externalGlobalMousePosition && externalMouseOffset) {
547
- // External mouse position provided, skip internal tracking
548
- return;
500
+ /**
501
+ * Validate and clamp a numeric config value
502
+ */
503
+ const validateConfigValue = useCallback(
504
+ (value: unknown, min: number, max: number, defaultValue: number): number => {
505
+ if (typeof value !== 'number' || isNaN(value) || !isFinite(value)) {
506
+ return defaultValue;
549
507
  }
508
+ return Math.min(max, Math.max(min, value));
509
+ },
510
+ []
511
+ );
550
512
 
551
- if (effectiveDisableEffects) {
552
- return;
553
- }
513
+ // Calculate Base OverLight Config (without mouse influence)
514
+ const baseOverLightConfig = useMemo(() => {
515
+ const isOverLight = getEffectiveOverLight();
516
+ // Use static mouse influence for base config
517
+ const mouseInfluence = 0;
554
518
 
555
- const container = mouseContainer?.current || glassRef.current;
556
- if (!container) {
557
- return;
558
- }
519
+ const baseOpacity = isOverLight ? Math.min(0.6, Math.max(0.2, 0.5)) : 0;
559
520
 
560
- // Use cached rect if available, otherwise get new one
561
- let rect = cachedRectRef.current;
562
- if (!rect || rect.width === 0 || rect.height === 0) {
563
- rect = container.getBoundingClientRect();
564
- cachedRectRef.current = rect;
565
- }
521
+ const baseConfig = {
522
+ isOverLight,
523
+ threshold: 0.7,
524
+ opacity: baseOpacity,
525
+ contrast: 1, // Base contrast
526
+ brightness: 1, // Base brightness
527
+ saturationBoost: 1.3,
528
+ shadowIntensity: 0.9,
529
+ borderOpacity: 0.7,
530
+ };
566
531
 
567
- if (rect.width === 0 || rect.height === 0) {
568
- return;
569
- }
532
+ if (typeof overLight === 'object' && overLight !== null) {
533
+ const objConfig = overLight as OverLightObjectConfig;
570
534
 
571
- const center = calculateElementCenter(rect);
535
+ const validatedThreshold = validateConfigValue(objConfig.threshold, 0.1, 1.0, baseConfig.threshold);
536
+ const validatedOpacity = validateConfigValue(objConfig.opacity, 0.1, 1.0, baseConfig.opacity);
537
+ const validatedContrast = validateConfigValue(objConfig.contrast, 0.5, 2.5, baseConfig.contrast);
538
+ const validatedBrightness = validateConfigValue(objConfig.brightness, 0.5, 2.0, baseConfig.brightness);
539
+ const validatedSaturationBoost = validateConfigValue(objConfig.saturationBoost, 0.5, 3.0, baseConfig.saturationBoost);
572
540
 
573
- // Calculate offset relative to this container
574
- const newOffset = {
575
- x: ((globalPos.x - center.x) / rect.width) * 100,
576
- y: ((globalPos.y - center.y) / rect.height) * 100,
541
+ return {
542
+ ...baseConfig,
543
+ threshold: validatedThreshold,
544
+ opacity: validatedOpacity,
545
+ contrast: validatedContrast,
546
+ brightness: validatedBrightness,
547
+ saturationBoost: validatedSaturationBoost,
577
548
  };
578
-
579
- // React 18 automatically batches these updates
580
- setInternalMouseOffset(newOffset);
581
- setInternalGlobalMousePosition(globalPos);
582
- },
583
- [
584
- mouseContainer,
585
- glassRef,
586
- externalGlobalMousePosition,
587
- externalMouseOffset,
588
- effectiveDisableEffects,
589
- ]
590
- );
591
-
592
- // Subscribe to shared mouse tracker
593
- useEffect(() => {
594
- if (externalGlobalMousePosition && externalMouseOffset) {
595
- // External mouse position provided, don't subscribe
596
- return undefined;
597
549
  }
598
550
 
599
- if (effectiveDisableEffects) {
600
- // Effects disabled, don't subscribe
601
- return undefined;
602
- }
551
+ return baseConfig;
552
+ }, [overLight, getEffectiveOverLight, validateConfigValue]);
603
553
 
604
- // Subscribe to shared tracker
605
- const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
554
+ // Calculate Effective OverLight Config (for component return value, static mouse influence for initial render)
555
+ const overLightConfig = useMemo(() => {
556
+ const mouseInfluence = calculateMouseInfluence(mouseOffset);
557
+ const hoverIntensity = isHovered ? 1.4 : 1;
558
+ const activeIntensity = isActive ? 1.6 : 1;
606
559
 
607
- // Update cached rect when container size changes
608
- const updateRect = () => {
609
- if (updateRectRef.current !== null) {
610
- cancelAnimationFrame(updateRectRef.current);
611
- }
612
- updateRectRef.current = requestAnimationFrame(() => {
613
- const container = mouseContainer?.current || glassRef.current;
614
- if (container) {
615
- cachedRectRef.current = container.getBoundingClientRect();
616
- }
617
- updateRectRef.current = null;
618
- });
560
+ return {
561
+ isOverLight: baseOverLightConfig.isOverLight,
562
+ threshold: baseOverLightConfig.threshold,
563
+ opacity: baseOverLightConfig.opacity * hoverIntensity * activeIntensity,
564
+ contrast: Math.min(1.6, baseOverLightConfig.contrast + mouseInfluence * 0.1),
565
+ brightness: Math.min(1.1, baseOverLightConfig.brightness + mouseInfluence * 0.05),
566
+ saturationBoost: baseOverLightConfig.saturationBoost,
567
+ shadowIntensity: Math.min(1.2, Math.max(0.5, baseOverLightConfig.shadowIntensity + mouseInfluence * 0.2)),
568
+ borderOpacity: Math.min(1.0, Math.max(0.3, baseOverLightConfig.borderOpacity + mouseInfluence * 0.1)),
619
569
  };
570
+ }, [baseOverLightConfig, mouseOffset, isHovered, isActive]);
620
571
 
621
- // Use ResizeObserver to update cached rect when container size changes
622
- const container = mouseContainer?.current || glassRef.current;
623
- let resizeObserver: ResizeObserver | null = null;
624
-
625
- if (container && typeof ResizeObserver !== 'undefined') {
626
- resizeObserver = new ResizeObserver(updateRect);
627
- resizeObserver.observe(container);
628
- }
572
+ // Mouse tracking
573
+ const updateRectRef = useRef<number | null>(null);
629
574
 
630
- return () => {
631
- unsubscribe();
632
- if (updateRectRef.current !== null) {
633
- cancelAnimationFrame(updateRectRef.current);
634
- updateRectRef.current = null;
635
- }
636
- if (resizeObserver) {
637
- resizeObserver.disconnect();
638
- }
639
- };
640
- }, [
641
- handleGlobalMousePosition,
642
- mouseContainer,
643
- glassRef,
644
- externalGlobalMousePosition,
645
- externalMouseOffset,
646
- effectiveDisableEffects,
647
- ]);
575
+ // Derived values for imperative updates (we can use memoized ones or re-calculate)
576
+ // Since updateAtomixGlassStyles is called imperatively, we pass current refs and state
648
577
 
649
- // Transform calculations
578
+ // Transform calculations for initial render
650
579
  const calculateDirectionalScale = useCallback(() => {
651
- // Disable directional scaling if overLight is active (to prevent zooming/distorting the premium glass effect)
652
- const isOverLightActive =
653
- overLight === true ||
654
- (overLight === 'auto' && detectedOverLight) ||
655
- (typeof overLight === 'object' && overLight !== null && detectedOverLight);
580
+ const isOverLightActive = baseOverLightConfig.isOverLight;
656
581
 
657
582
  if (isOverLightActive) {
658
583
  return 'scale(1)';
659
584
  }
660
585
 
661
- if (
662
- !globalMousePosition.x ||
663
- !globalMousePosition.y ||
664
- !glassRef.current ||
665
- !validateGlassSize(glassSize)
666
- ) {
586
+ if (!globalMousePosition.x || !globalMousePosition.y || !glassRef.current || !validateGlassSize(glassSize)) {
667
587
  return 'scale(1)';
668
588
  }
669
589
 
@@ -691,44 +611,25 @@ export function useAtomixGlass({
691
611
  const normalizedY = deltaY / centerDistance;
692
612
  const stretchIntensity = Math.min(centerDistance / 300, 1) * elasticity * fadeInFactor;
693
613
 
694
- const scaleX =
695
- 1 +
696
- Math.abs(normalizedX) * stretchIntensity * 0.3 -
697
- Math.abs(normalizedY) * stretchIntensity * 0.15;
698
- const scaleY =
699
- 1 +
700
- Math.abs(normalizedY) * stretchIntensity * 0.3 -
701
- Math.abs(normalizedX) * stretchIntensity * 0.15;
614
+ const scaleX = 1 + Math.abs(normalizedX) * stretchIntensity * 0.3 - Math.abs(normalizedY) * stretchIntensity * 0.15;
615
+ const scaleY = 1 + Math.abs(normalizedY) * stretchIntensity * 0.3 - Math.abs(normalizedX) * stretchIntensity * 0.15;
702
616
 
703
617
  return `scaleX(${Math.max(0.8, scaleX)}) scaleY(${Math.max(0.8, scaleY)})`;
704
- }, [globalMousePosition, elasticity, glassSize, glassRef, overLight, detectedOverLight]);
618
+ }, [globalMousePosition, elasticity, glassSize, glassRef, baseOverLightConfig]);
705
619
 
706
620
  const calculateFadeInFactor = useCallback(() => {
707
- if (
708
- !globalMousePosition.x ||
709
- !globalMousePosition.y ||
710
- !glassRef.current ||
711
- !validateGlassSize(glassSize)
712
- ) {
621
+ if (!globalMousePosition.x || !globalMousePosition.y || !glassRef.current || !validateGlassSize(glassSize)) {
713
622
  return 0;
714
623
  }
715
624
 
716
625
  const rect = glassRef.current.getBoundingClientRect();
717
626
  const center = calculateElementCenter(rect);
718
627
 
719
- const edgeDistanceX = Math.max(
720
- 0,
721
- Math.abs(globalMousePosition.x - center.x) - glassSize.width / 2
722
- );
723
- const edgeDistanceY = Math.max(
724
- 0,
725
- Math.abs(globalMousePosition.y - center.y) - glassSize.height / 2
726
- );
628
+ const edgeDistanceX = Math.max(0, Math.abs(globalMousePosition.x - center.x) - glassSize.width / 2);
629
+ const edgeDistanceY = Math.max(0, Math.abs(globalMousePosition.y - center.y) - glassSize.height / 2);
727
630
  const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
728
631
 
729
- return edgeDistance > CONSTANTS.ACTIVATION_ZONE
730
- ? 0
731
- : 1 - edgeDistance / CONSTANTS.ACTIVATION_ZONE;
632
+ return edgeDistance > CONSTANTS.ACTIVATION_ZONE ? 0 : 1 - edgeDistance / CONSTANTS.ACTIVATION_ZONE;
732
633
  }, [globalMousePosition, glassSize, glassRef]);
733
634
 
734
635
  const calculateElasticTranslation = useCallback(() => {
@@ -747,292 +648,208 @@ export function useAtomixGlass({
747
648
  }, [globalMousePosition, elasticity, calculateFadeInFactor, glassRef]);
748
649
 
749
650
  const elasticTranslation = useMemo(() => {
750
- if (effectiveDisableEffects) {
651
+ if (effectiveWithoutEffects) {
751
652
  return { x: 0, y: 0 };
752
653
  }
753
654
  return calculateElasticTranslation();
754
- }, [calculateElasticTranslation, effectiveDisableEffects]);
655
+ }, [calculateElasticTranslation, effectiveWithoutEffects]);
755
656
 
756
657
  const directionalScale = useMemo(() => {
757
- if (effectiveDisableEffects) {
658
+ if (effectiveWithoutEffects) {
758
659
  return 'scale(1)';
759
660
  }
760
661
  return calculateDirectionalScale();
761
- }, [calculateDirectionalScale, effectiveDisableEffects]);
662
+ }, [calculateDirectionalScale, effectiveWithoutEffects]);
762
663
 
763
664
  const transformStyle = useMemo(() => {
764
- if (effectiveDisableEffects) {
665
+ if (effectiveWithoutEffects) {
765
666
  return isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)';
766
667
  }
767
668
  return `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : directionalScale}`;
768
- }, [elasticTranslation, isActive, onClick, directionalScale, effectiveDisableEffects]);
769
-
770
- // Size management
771
- useEffect(() => {
772
- const isValidElement = (element: HTMLElement | null): element is HTMLElement =>
773
- element !== null && element instanceof HTMLElement && element.isConnected;
774
-
775
- const validateSize = (size: GlassSize): boolean =>
776
- validateGlassSize(size) &&
777
- size.width <= CONSTANTS.MAX_SIZE &&
778
- size.height <= CONSTANTS.MAX_SIZE;
669
+ }, [elasticTranslation, isActive, onClick, directionalScale, effectiveWithoutEffects]);
779
670
 
780
- let rafId: number | null = null;
781
- let lastSize = { width: 0, height: 0 };
782
- let lastCornerRadius = effectiveCornerRadius;
671
+ // Handle mouse position updates
672
+ const handleGlobalMousePosition = useCallback(
673
+ (globalPos: MousePosition) => {
674
+ if (externalGlobalMousePosition && externalMouseOffset) {
675
+ return;
676
+ }
783
677
 
784
- const updateGlassSize = (forceUpdate = false): void => {
785
- if (rafId !== null) cancelAnimationFrame(rafId);
678
+ if (effectiveWithoutEffects) {
679
+ return;
680
+ }
786
681
 
787
- rafId = requestAnimationFrame(() => {
788
- if (!isValidElement(glassRef.current)) {
789
- rafId = null;
790
- return;
791
- }
682
+ const container = mouseContainer?.current || glassRef.current;
683
+ if (!container) {
684
+ return;
685
+ }
792
686
 
793
- const rect = glassRef.current.getBoundingClientRect();
794
- if (rect.width <= 0 || rect.height <= 0) {
795
- rafId = null;
796
- return;
797
- }
687
+ // Use cached rect if available, otherwise get new one
688
+ let rect = cachedRectRef.current;
689
+ if (!rect || rect.width === 0 || rect.height === 0) {
690
+ rect = container.getBoundingClientRect();
691
+ cachedRectRef.current = rect;
692
+ }
798
693
 
799
- // Measure actual rendered size without artificial offsets to avoid feedback loops
800
- const newSize: GlassSize = {
801
- width: Math.round(rect.width),
802
- height: Math.round(rect.height),
803
- };
804
-
805
- const cornerRadiusChanged = lastCornerRadius !== effectiveCornerRadius;
806
- const dimensionsChanged =
807
- Math.abs(newSize.width - lastSize.width) > 1 ||
808
- Math.abs(newSize.height - lastSize.height) > 1;
809
-
810
- if ((forceUpdate || cornerRadiusChanged || dimensionsChanged) && validateSize(newSize)) {
811
- lastSize = newSize;
812
- lastCornerRadius = effectiveCornerRadius;
813
- setGlassSize(newSize);
814
- }
694
+ if (rect.width === 0 || rect.height === 0) {
695
+ return;
696
+ }
815
697
 
816
- rafId = null;
817
- });
818
- };
698
+ const center = calculateElementCenter(rect);
819
699
 
820
- let resizeTimeoutId: NodeJS.Timeout | null = null;
821
- const debouncedResizeHandler = (): void => {
822
- if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
823
- resizeTimeoutId = setTimeout(() => updateGlassSize(false), 16);
824
- };
700
+ // Calculate offset relative to this container
701
+ const newOffset = {
702
+ x: ((globalPos.x - center.x) / rect.width) * 100,
703
+ y: ((globalPos.y - center.y) / rect.height) * 100,
704
+ };
825
705
 
826
- const initialTimeoutId = setTimeout(() => updateGlassSize(true), 0);
706
+ // Store in refs instead of state
707
+ internalMouseOffsetRef.current = newOffset;
708
+ internalGlobalMousePositionRef.current = globalPos;
709
+
710
+ // Imperative style update
711
+ updateAtomixGlassStyles(
712
+ wrapperRef?.current || null,
713
+ glassRef.current,
714
+ {
715
+ mouseOffset: newOffset,
716
+ globalMousePosition: globalPos,
717
+ glassSize,
718
+ isHovered,
719
+ isActive,
720
+ isOverLight: baseOverLightConfig.isOverLight,
721
+ baseOverLightConfig,
722
+ effectiveBorderRadius,
723
+ effectiveWithoutEffects,
724
+ effectiveReducedMotion,
725
+ elasticity,
726
+ directionalScale: isActive && Boolean(onClick) ? 'scale(0.96)' : 'scale(1)', // Simplified directional scale for fast path
727
+ onClick,
728
+ withLiquidBlur,
729
+ blurAmount,
730
+ saturation,
731
+ padding,
732
+ }
733
+ );
734
+ },
735
+ [
736
+ mouseContainer,
737
+ glassRef,
738
+ wrapperRef,
739
+ externalGlobalMousePosition,
740
+ externalMouseOffset,
741
+ effectiveWithoutEffects,
742
+ glassSize,
743
+ isHovered,
744
+ isActive,
745
+ baseOverLightConfig,
746
+ effectiveBorderRadius,
747
+ effectiveReducedMotion,
748
+ elasticity,
749
+ onClick,
750
+ withLiquidBlur,
751
+ blurAmount,
752
+ saturation,
753
+ padding
754
+ ]
755
+ );
827
756
 
828
- let resizeObserver: ResizeObserver | null = null;
829
- let resizeDebounceTimeout: NodeJS.Timeout | null = null;
757
+ // Subscribe to shared mouse tracker
758
+ useEffect(() => {
759
+ if (externalGlobalMousePosition && externalMouseOffset) {
760
+ return undefined;
761
+ }
830
762
 
831
- // ResizeObserver has 98%+ browser support, no need for fallback
832
- if (typeof ResizeObserver !== 'undefined' && isValidElement(glassRef.current)) {
833
- try {
834
- resizeObserver = new ResizeObserver(entries => {
835
- for (const entry of entries) {
836
- if (entry.target === glassRef.current) {
837
- // Update cached rect when size changes
838
- if (glassRef.current) {
839
- cachedRectRef.current = glassRef.current.getBoundingClientRect();
840
- }
841
- // Debounce resize updates to match RAF timing (16ms)
842
- if (resizeDebounceTimeout) clearTimeout(resizeDebounceTimeout);
843
- resizeDebounceTimeout = setTimeout(() => updateGlassSize(false), 16);
844
- break;
845
- }
846
- }
847
- });
848
- resizeObserver.observe(glassRef.current);
849
- } catch (error) {
850
- console.warn('AtomixGlass: ResizeObserver not available, using window resize only', error);
851
- }
763
+ if (effectiveWithoutEffects) {
764
+ return undefined;
852
765
  }
853
766
 
854
- window.addEventListener('resize', debouncedResizeHandler, { passive: true });
767
+ const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
855
768
 
856
- return () => {
857
- clearTimeout(initialTimeoutId);
858
- if (rafId !== null) cancelAnimationFrame(rafId);
859
- if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
860
- if (resizeDebounceTimeout) clearTimeout(resizeDebounceTimeout);
861
- window.removeEventListener('resize', debouncedResizeHandler);
862
- resizeObserver?.disconnect();
769
+ const updateRect = () => {
770
+ if (updateRectRef.current !== null) {
771
+ cancelAnimationFrame(updateRectRef.current);
772
+ }
773
+ updateRectRef.current = requestAnimationFrame(() => {
774
+ const container = mouseContainer?.current || glassRef.current;
775
+ if (container) {
776
+ cachedRectRef.current = container.getBoundingClientRect();
777
+ }
778
+ updateRectRef.current = null;
779
+ });
863
780
  };
864
- }, [effectiveCornerRadius, glassRef]);
865
781
 
866
- // OverLight config
867
- /**
868
- * Get effective overLight value based on configuration
869
- * - boolean: returns the boolean value directly
870
- * - 'auto': returns detectedOverLight (auto-detected from background)
871
- * - object: returns detectedOverLight (auto-detected, but config object provides customization)
872
- */
873
- const getEffectiveOverLight = useCallback(() => {
874
- if (typeof overLight === 'boolean') {
875
- return overLight;
876
- }
877
- if (overLight === 'auto') {
878
- return detectedOverLight;
879
- }
880
- if (typeof overLight === 'object' && overLight !== null) {
881
- return detectedOverLight;
782
+ const container = mouseContainer?.current || glassRef.current;
783
+ let resizeObserver: ResizeObserver | null = null;
784
+
785
+ if (container && typeof ResizeObserver !== 'undefined') {
786
+ resizeObserver = new ResizeObserver(updateRect);
787
+ resizeObserver.observe(container);
882
788
  }
883
- // Default to false for safety when overLight is undefined or invalid
884
- return false;
885
- }, [overLight, detectedOverLight]);
886
789
 
887
- /**
888
- * Validate and clamp a numeric config value
889
- * @param value - The value to validate
890
- * @param min - Minimum allowed value
891
- * @param max - Maximum allowed value
892
- * @param defaultValue - Default value if validation fails
893
- * @returns Validated and clamped value
894
- */
895
- const validateConfigValue = useCallback(
896
- (value: unknown, min: number, max: number, defaultValue: number): number => {
897
- if (typeof value !== 'number' || isNaN(value) || !isFinite(value)) {
898
- return defaultValue;
790
+ return () => {
791
+ unsubscribe();
792
+ if (updateRectRef.current !== null) {
793
+ cancelAnimationFrame(updateRectRef.current);
794
+ updateRectRef.current = null;
795
+ }
796
+ if (resizeObserver) {
797
+ resizeObserver.disconnect();
899
798
  }
900
- return Math.min(max, Math.max(min, value));
901
- },
902
- []
903
- );
904
-
905
- const overLightConfig = useMemo(() => {
906
- const isOverLight = getEffectiveOverLight();
907
- const mouseInfluence = calculateMouseInfluence(mouseOffset);
908
- const hoverIntensity = isHovered ? 1.4 : 1;
909
- const activeIntensity = isActive ? 1.6 : 1;
910
-
911
- // More robust overlight configuration with better defaults and clamping
912
- const baseOpacity = isOverLight
913
- ? Math.min(0.6, Math.max(0.2, 0.5 * hoverIntensity * activeIntensity))
914
- : 0;
915
-
916
- const baseConfig = {
917
- isOverLight,
918
- threshold: 0.7,
919
- opacity: baseOpacity,
920
- contrast: Math.min(1.6, Math.max(1.0, 1.4 + mouseInfluence * 0.1)),
921
- brightness: Math.min(1.1, Math.max(0.8, 0.9 + mouseInfluence * 0.05)),
922
- saturationBoost: 1.3, // Fixed value — dynamic saturation amplifies perceived displacement
923
- shadowIntensity: Math.min(1.2, Math.max(0.5, 0.9 + mouseInfluence * 0.2)),
924
- borderOpacity: Math.min(1.0, Math.max(0.3, 0.7 + mouseInfluence * 0.1)),
925
799
  };
800
+ }, [
801
+ handleGlobalMousePosition,
802
+ mouseContainer,
803
+ glassRef,
804
+ externalGlobalMousePosition,
805
+ externalMouseOffset,
806
+ effectiveWithoutEffects,
807
+ ]);
926
808
 
927
- if (typeof overLight === 'object' && overLight !== null) {
928
- const objConfig = overLight as OverLightObjectConfig;
929
-
930
- // Validate and apply object config values with proper clamping
931
- const validatedThreshold = validateConfigValue(
932
- objConfig.threshold,
933
- 0.1,
934
- 1.0,
935
- baseConfig.threshold
936
- );
937
- const validatedOpacity = validateConfigValue(objConfig.opacity, 0.1, 1.0, baseConfig.opacity);
938
- const validatedContrast = validateConfigValue(
939
- objConfig.contrast,
940
- 0.5,
941
- 2.5,
942
- baseConfig.contrast
943
- );
944
- const validatedBrightness = validateConfigValue(
945
- objConfig.brightness,
946
- 0.5,
947
- 2.0,
948
- baseConfig.brightness
949
- );
950
- const validatedSaturationBoost = validateConfigValue(
951
- objConfig.saturationBoost,
952
- 0.5,
953
- 3.0,
954
- baseConfig.saturationBoost
809
+ // Also call updateStyles on other state changes (hover, active, etc)
810
+ useEffect(() => {
811
+ updateAtomixGlassStyles(
812
+ wrapperRef?.current || null,
813
+ glassRef.current,
814
+ {
815
+ mouseOffset: externalMouseOffset || internalMouseOffsetRef.current,
816
+ globalMousePosition: externalGlobalMousePosition || internalGlobalMousePositionRef.current,
817
+ glassSize,
818
+ isHovered,
819
+ isActive,
820
+ isOverLight: baseOverLightConfig.isOverLight,
821
+ baseOverLightConfig,
822
+ effectiveBorderRadius,
823
+ effectiveWithoutEffects,
824
+ effectiveReducedMotion,
825
+ elasticity,
826
+ directionalScale,
827
+ onClick,
828
+ withLiquidBlur,
829
+ blurAmount,
830
+ saturation,
831
+ padding,
832
+ }
955
833
  );
956
-
957
- const finalConfig = {
958
- ...baseConfig,
959
- threshold: validatedThreshold,
960
- opacity: validatedOpacity * hoverIntensity * activeIntensity,
961
- contrast: Math.min(1.6, validatedContrast + mouseInfluence * 0.1),
962
- brightness: Math.min(1.1, validatedBrightness + mouseInfluence * 0.05),
963
- saturationBoost: validatedSaturationBoost, // Use validated value directly, no mouse influence
964
- };
965
-
966
- // Debug logging
967
- if (
968
- (typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') &&
969
- debugOverLight
970
- ) {
971
- console.log('[AtomixGlass] OverLight Config:', {
972
- isOverLight,
973
- config: {
974
- threshold: finalConfig.threshold.toFixed(3),
975
- opacity: finalConfig.opacity.toFixed(3),
976
- contrast: finalConfig.contrast.toFixed(3),
977
- brightness: finalConfig.brightness.toFixed(3),
978
- saturationBoost: finalConfig.saturationBoost.toFixed(3),
979
- shadowIntensity: finalConfig.shadowIntensity.toFixed(3),
980
- borderOpacity: finalConfig.borderOpacity.toFixed(3),
981
- },
982
- input: {
983
- threshold: objConfig.threshold,
984
- opacity: objConfig.opacity,
985
- contrast: objConfig.contrast,
986
- brightness: objConfig.brightness,
987
- saturationBoost: objConfig.saturationBoost,
988
- },
989
- dynamic: {
990
- mouseInfluence: mouseInfluence.toFixed(3),
991
- hoverIntensity: hoverIntensity.toFixed(3),
992
- activeIntensity: activeIntensity.toFixed(3),
993
- },
994
- timestamp: new Date().toISOString(),
995
- });
996
- }
997
-
998
- return finalConfig;
999
- }
1000
-
1001
- // Debug logging for non-object configs
1002
- if (
1003
- (typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') &&
1004
- debugOverLight
1005
- ) {
1006
- console.log('[AtomixGlass] OverLight Config:', {
1007
- isOverLight,
1008
- configType: typeof overLight === 'boolean' ? (overLight ? 'true' : 'false') : overLight,
1009
- config: {
1010
- threshold: baseConfig.threshold.toFixed(3),
1011
- opacity: baseConfig.opacity.toFixed(3),
1012
- contrast: baseConfig.contrast.toFixed(3),
1013
- brightness: baseConfig.brightness.toFixed(3),
1014
- saturationBoost: baseConfig.saturationBoost.toFixed(3),
1015
- shadowIntensity: baseConfig.shadowIntensity.toFixed(3),
1016
- borderOpacity: baseConfig.borderOpacity.toFixed(3),
1017
- },
1018
- dynamic: {
1019
- mouseInfluence: mouseInfluence.toFixed(3),
1020
- hoverIntensity: hoverIntensity.toFixed(3),
1021
- activeIntensity: activeIntensity.toFixed(3),
1022
- },
1023
- timestamp: new Date().toISOString(),
1024
- });
1025
- }
1026
-
1027
- return baseConfig;
1028
834
  }, [
1029
- overLight,
1030
- getEffectiveOverLight,
1031
- mouseOffset,
1032
835
  isHovered,
1033
836
  isActive,
1034
- validateConfigValue,
1035
- debugOverLight,
837
+ glassSize,
838
+ baseOverLightConfig,
839
+ effectiveBorderRadius,
840
+ effectiveWithoutEffects,
841
+ effectiveReducedMotion,
842
+ elasticity,
843
+ directionalScale,
844
+ wrapperRef,
845
+ glassRef,
846
+ externalMouseOffset,
847
+ externalGlobalMousePosition,
848
+ withLiquidBlur,
849
+ blurAmount,
850
+ saturation,
851
+ padding,
852
+ onClick
1036
853
  ]);
1037
854
 
1038
855
  // Event handlers
@@ -1041,6 +858,10 @@ export function useAtomixGlass({
1041
858
  const handleMouseDown = useCallback(() => setIsActive(true), []);
1042
859
  const handleMouseUp = useCallback(() => setIsActive(false), []);
1043
860
 
861
+ const handleMouseMove = useCallback((_e: MouseEvent) => {
862
+ // Mouse tracking handled by shared global tracker
863
+ }, []);
864
+
1044
865
  const handleKeyDown = useCallback(
1045
866
  (e: React.KeyboardEvent<HTMLDivElement>) => {
1046
867
  if (onClick && (e.key === 'Enter' || e.key === ' ')) {
@@ -1051,34 +872,22 @@ export function useAtomixGlass({
1051
872
  [onClick]
1052
873
  );
1053
874
 
1054
- // Mouse tracking is now handled by shared global tracker
1055
- const handleMouseMove = useCallback((_e: MouseEvent) => {
1056
- // Mouse tracking handled by shared global tracker
1057
- }, []);
1058
-
1059
875
  return {
1060
- // State
1061
876
  isHovered,
1062
877
  isActive,
1063
878
  glassSize,
1064
- dynamicCornerRadius,
1065
- effectiveCornerRadius,
879
+ dynamicBorderRadius,
880
+ effectiveBorderRadius,
1066
881
  effectiveReducedMotion,
1067
882
  effectiveHighContrast,
1068
- effectiveDisableEffects,
883
+ effectiveWithoutEffects,
1069
884
  detectedOverLight,
1070
- globalMousePosition,
1071
- mouseOffset,
1072
-
1073
- // OverLight config
885
+ globalMousePosition, // This is now static (refs or props) unless prop changes
886
+ mouseOffset, // This is now static (refs or props) unless prop changes
1074
887
  overLightConfig,
1075
-
1076
- // Transform calculations
1077
888
  elasticTranslation,
1078
889
  directionalScale,
1079
890
  transformStyle,
1080
-
1081
- // Event handlers
1082
891
  handleMouseEnter,
1083
892
  handleMouseLeave,
1084
893
  handleMouseDown,
@@ -1086,4 +895,4 @@ export function useAtomixGlass({
1086
895
  handleMouseMove,
1087
896
  handleKeyDown,
1088
897
  };
1089
- }
898
+ }