@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.
- package/dist/atomix.css +9351 -9259
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +4 -4
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.d.ts +12 -19
- package/dist/charts.js +555 -358
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +21 -24
- package/dist/core.js +435 -265
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +11 -18
- package/dist/forms.js +411 -257
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -254
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +38 -41
- package/dist/index.esm.js +731 -487
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +733 -492
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/package.json +1 -1
- package/scripts/atomix-cli.js +34 -1
- package/src/components/AtomixGlass/AtomixGlass.tsx +82 -54
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +17 -18
- package/src/components/AtomixGlass/README.md +5 -5
- package/src/components/AtomixGlass/stories/Customization.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +42 -42
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +5 -5
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +3 -3
- package/src/components/AtomixGlass/stories/Performance.stories.tsx +2 -2
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +45 -45
- package/src/components/AtomixGlass/stories/Shaders.stories.tsx +3 -3
- package/src/components/Badge/Badge.stories.tsx +1 -1
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Breadcrumb/Breadcrumb.tsx +90 -77
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/Button.tsx +2 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +2 -2
- package/src/components/Callout/README.md +2 -2
- package/src/components/Card/Card.tsx +31 -11
- package/src/components/Chart/Chart.stories.tsx +1 -1
- package/src/components/Chart/Chart.tsx +5 -5
- package/src/components/Chart/TreemapChart.tsx +37 -29
- package/src/components/DatePicker/readme.md +3 -3
- package/src/components/Dropdown/Dropdown.stories.tsx +1 -1
- package/src/components/Dropdown/Dropdown.tsx +276 -273
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/Footer/FooterLink.tsx +2 -2
- package/src/components/Form/Checkbox.stories.tsx +1 -1
- package/src/components/Form/Checkbox.tsx +1 -1
- package/src/components/Form/Input.stories.tsx +1 -1
- package/src/components/Form/Input.tsx +1 -1
- package/src/components/Form/Radio.stories.tsx +1 -1
- package/src/components/Form/Radio.tsx +1 -1
- package/src/components/Form/Select.stories.tsx +1 -1
- package/src/components/Form/Select.tsx +1 -1
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +2 -2
- package/src/components/Hero/Hero.tsx +2 -2
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +1 -1
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- package/src/components/Navigation/Nav/NavItem.tsx +6 -3
- package/src/components/Navigation/Navbar/Navbar.stories.tsx +3 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +1 -1
- package/src/components/Navigation/SideMenu/SideMenu.stories.tsx +2 -2
- package/src/components/Navigation/SideMenu/SideMenu.tsx +1 -1
- package/src/components/Pagination/Pagination.stories.tsx +1 -1
- package/src/components/Pagination/Pagination.tsx +1 -1
- package/src/components/Popover/Popover.stories.tsx +1 -1
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/Rating/Rating.stories.tsx +1 -1
- package/src/components/Rating/Rating.test.tsx +73 -0
- package/src/components/Rating/Rating.tsx +25 -37
- package/src/components/Spinner/Spinner.tsx +1 -1
- package/src/components/Steps/Steps.stories.tsx +1 -1
- package/src/components/Steps/Steps.tsx +2 -2
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Todo/Todo.tsx +0 -1
- package/src/components/Toggle/Toggle.stories.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/Tooltip/Tooltip.stories.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +2 -2
- package/src/lib/composables/__tests__/useAtomixGlassPerf.test.tsx +88 -0
- package/src/lib/composables/__tests__/useChart.test.ts +50 -0
- package/src/lib/composables/__tests__/useChart.test.tsx +139 -0
- package/src/lib/composables/__tests__/useHeroBackgroundSlider.test.tsx +59 -0
- package/src/lib/composables/__tests__/useSliderAutoplay.test.tsx +68 -0
- package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +329 -0
- package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +82 -0
- package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +153 -0
- package/src/lib/composables/atomix-glass/useGlassOverLight.ts +198 -0
- package/src/lib/composables/atomix-glass/useGlassSize.ts +117 -0
- package/src/lib/composables/atomix-glass/useGlassState.ts +112 -0
- package/src/lib/composables/atomix-glass/useGlassTransforms.ts +160 -0
- package/src/lib/composables/glass-styles.ts +302 -0
- package/src/lib/composables/index.ts +0 -4
- package/src/lib/composables/useAtomixGlass.ts +331 -522
- package/src/lib/composables/useAtomixGlassStyles.ts +307 -0
- package/src/lib/composables/useBarChart.ts +1 -1
- package/src/lib/composables/useBreadcrumb.ts +6 -6
- package/src/lib/composables/useChart.ts +104 -21
- package/src/lib/composables/useHeroBackgroundSlider.ts +16 -7
- package/src/lib/composables/useSlider.ts +66 -34
- package/src/lib/theme/devtools/CLI.ts +1 -1
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +18 -21
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/styles/02-tools/_tools.breakpoints.scss +1 -1
- package/src/styles/02-tools/_tools.utility-api.scss +6 -6
- package/src/styles/06-components/_components.accordion.scss +0 -2
- package/src/styles/06-components/_components.chart.scss +0 -1
- package/src/styles/06-components/_components.dropdown.scss +0 -1
- package/src/styles/06-components/_components.edge-panel.scss +0 -2
- package/src/styles/06-components/_components.photoviewer.scss +0 -1
- package/src/styles/06-components/_components.river.scss +0 -1
- package/src/styles/06-components/_components.slider.scss +0 -3
- package/src/styles/99-utilities/_utilities.glass-fixes.scss +0 -1
- package/src/styles/99-utilities/_utilities.text.scss +1 -0
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
import 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 = (
|
|
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
|
-
|
|
151
|
-
|
|
160
|
+
dynamicBorderRadius: number;
|
|
161
|
+
effectiveBorderRadius: number;
|
|
152
162
|
effectiveReducedMotion: boolean;
|
|
153
163
|
effectiveHighContrast: boolean;
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
212
|
+
withoutEffects = false,
|
|
200
213
|
elasticity = 0.05,
|
|
201
214
|
onClick,
|
|
202
|
-
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
const [
|
|
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
|
|
226
|
-
if (
|
|
227
|
-
const result = Math.max(0,
|
|
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
|
-
}, [
|
|
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
|
|
246
|
-
() =>
|
|
247
|
-
[
|
|
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
|
-
|
|
256
|
-
|
|
257
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
454
|
-
|
|
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
|
-
|
|
464
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
const
|
|
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
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
552
|
-
|
|
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
|
-
|
|
556
|
-
if (!container) {
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
519
|
+
const baseOpacity = isOverLight ? Math.min(0.6, Math.max(0.2, 0.5)) : 0;
|
|
559
520
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
}
|
|
532
|
+
if (typeof overLight === 'object' && overLight !== null) {
|
|
533
|
+
const objConfig = overLight as OverLightObjectConfig;
|
|
570
534
|
|
|
571
|
-
const
|
|
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
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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
|
-
|
|
600
|
-
|
|
601
|
-
return undefined;
|
|
602
|
-
}
|
|
551
|
+
return baseConfig;
|
|
552
|
+
}, [overLight, getEffectiveOverLight, validateConfigValue]);
|
|
603
553
|
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
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
|
-
|
|
622
|
-
|
|
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
|
-
|
|
631
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
651
|
+
if (effectiveWithoutEffects) {
|
|
751
652
|
return { x: 0, y: 0 };
|
|
752
653
|
}
|
|
753
654
|
return calculateElasticTranslation();
|
|
754
|
-
}, [calculateElasticTranslation,
|
|
655
|
+
}, [calculateElasticTranslation, effectiveWithoutEffects]);
|
|
755
656
|
|
|
756
657
|
const directionalScale = useMemo(() => {
|
|
757
|
-
if (
|
|
658
|
+
if (effectiveWithoutEffects) {
|
|
758
659
|
return 'scale(1)';
|
|
759
660
|
}
|
|
760
661
|
return calculateDirectionalScale();
|
|
761
|
-
}, [calculateDirectionalScale,
|
|
662
|
+
}, [calculateDirectionalScale, effectiveWithoutEffects]);
|
|
762
663
|
|
|
763
664
|
const transformStyle = useMemo(() => {
|
|
764
|
-
if (
|
|
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,
|
|
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
|
-
|
|
781
|
-
|
|
782
|
-
|
|
671
|
+
// Handle mouse position updates
|
|
672
|
+
const handleGlobalMousePosition = useCallback(
|
|
673
|
+
(globalPos: MousePosition) => {
|
|
674
|
+
if (externalGlobalMousePosition && externalMouseOffset) {
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
783
677
|
|
|
784
|
-
|
|
785
|
-
|
|
678
|
+
if (effectiveWithoutEffects) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
786
681
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
682
|
+
const container = mouseContainer?.current || glassRef.current;
|
|
683
|
+
if (!container) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
792
686
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
817
|
-
});
|
|
818
|
-
};
|
|
698
|
+
const center = calculateElementCenter(rect);
|
|
819
699
|
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
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
|
-
|
|
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
|
-
|
|
829
|
-
|
|
757
|
+
// Subscribe to shared mouse tracker
|
|
758
|
+
useEffect(() => {
|
|
759
|
+
if (externalGlobalMousePosition && externalMouseOffset) {
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
830
762
|
|
|
831
|
-
|
|
832
|
-
|
|
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
|
-
|
|
767
|
+
const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
|
|
855
768
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
1035
|
-
|
|
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
|
-
|
|
1065
|
-
|
|
879
|
+
dynamicBorderRadius,
|
|
880
|
+
effectiveBorderRadius,
|
|
1066
881
|
effectiveReducedMotion,
|
|
1067
882
|
effectiveHighContrast,
|
|
1068
|
-
|
|
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
|
+
}
|