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