@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.
- package/dist/atomix.css +0 -14
- 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 -359
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +98 -28
- package/dist/core.js +1082 -733
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +26 -21
- package/dist/forms.js +937 -350
- package/dist/forms.js.map +1 -1
- package/dist/heavy.d.ts +14 -21
- package/dist/heavy.js +409 -256
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +518 -284
- package/dist/index.esm.js +1993 -1237
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +1994 -1237
- 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 +2 -2
- package/scripts/atomix-cli.js +43 -1
- package/scripts/cli/__tests__/utils.test.js +6 -2
- package/scripts/cli/migration-tools.js +2 -2
- package/scripts/cli/theme-bridge.js +7 -9
- package/scripts/cli/utils.js +2 -1
- package/src/components/Accordion/Accordion.stories.tsx +40 -0
- package/src/components/Accordion/Accordion.tsx +174 -56
- package/src/components/Accordion/AccordionCompound.test.tsx +70 -0
- 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 +185 -65
- package/src/components/Breadcrumb/BreadcrumbCompound.test.tsx +84 -0
- package/src/components/Breadcrumb/index.ts +2 -2
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/README.md +2 -2
- package/src/components/Callout/Callout.stories.tsx +166 -1011
- package/src/components/Callout/Callout.test.tsx +3 -3
- package/src/components/Callout/Callout.tsx +196 -84
- package/src/components/Callout/CalloutCompound.test.tsx +72 -0
- package/src/components/Callout/README.md +2 -2
- 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 +133 -20
- package/src/components/Dropdown/DropdownCompound.test.tsx +64 -0
- package/src/components/EdgePanel/EdgePanel.stories.tsx +7 -7
- package/src/components/EdgePanel/EdgePanel.tsx +164 -112
- package/src/components/EdgePanel/EdgePanelCompound.test.tsx +53 -0
- 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 +24 -1
- package/src/components/Form/Select.test.tsx +99 -0
- package/src/components/Form/Select.tsx +145 -94
- package/src/components/Form/SelectOption.tsx +88 -0
- package/src/components/Form/Textarea.stories.tsx +1 -1
- package/src/components/Form/Textarea.tsx +1 -1
- package/src/components/Hero/Hero.stories.tsx +39 -2
- package/src/components/Hero/Hero.test.tsx +142 -0
- package/src/components/Hero/Hero.tsx +143 -4
- package/src/components/List/List.test.tsx +62 -0
- package/src/components/List/List.tsx +16 -5
- package/src/components/List/ListItem.tsx +20 -0
- package/src/components/Messages/Messages.stories.tsx +1 -1
- package/src/components/Messages/Messages.tsx +2 -2
- package/src/components/Modal/Modal.stories.tsx +66 -2
- package/src/components/Modal/Modal.tsx +115 -35
- package/src/components/Modal/ModalCompound.test.tsx +94 -0
- package/src/components/Navigation/Nav/Nav.stories.tsx +2 -2
- package/src/components/Navigation/Nav/Nav.tsx +1 -1
- 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 +125 -22
- package/src/components/Steps/StepsCompound.test.tsx +81 -0
- package/src/components/Tabs/Tabs.stories.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +198 -45
- package/src/components/Tabs/TabsCompound.test.tsx +64 -0
- 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 -8
- package/src/lib/composables/useAtomixGlass.ts +331 -537
- 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 +2 -10
- package/src/lib/theme/utils/__tests__/themeUtils.test.ts +213 -0
- package/src/lib/types/components.ts +21 -23
- package/src/lib/utils/__tests__/componentUtils.test.ts +57 -2
- package/src/lib/utils/__tests__/dom.test.ts +100 -0
- package/src/lib/utils/__tests__/fontPreloader.test.ts +102 -0
- package/src/lib/utils/__tests__/themeNaming.test.ts +117 -0
- package/src/lib/utils/themeNaming.ts +1 -1
- 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
|
@@ -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,49 +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,
|
|
204
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
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>(
|
|
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
|
|
227
|
-
if (
|
|
228
|
-
const result = Math.max(0,
|
|
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
|
-
}, [
|
|
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
|
|
247
|
-
() =>
|
|
248
|
-
[
|
|
266
|
+
const effectiveWithoutEffects = useMemo(
|
|
267
|
+
() => withoutEffects || effectiveReducedMotion,
|
|
268
|
+
[withoutEffects, effectiveReducedMotion]
|
|
249
269
|
);
|
|
250
270
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
455
|
-
|
|
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
|
-
|
|
465
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
(
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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
|
-
|
|
553
|
-
|
|
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
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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
|
-
|
|
519
|
+
const baseOpacity = isOverLight ? Math.min(0.6, Math.max(0.2, 0.5)) : 0;
|
|
562
520
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
532
|
+
if (typeof overLight === 'object' && overLight !== null) {
|
|
533
|
+
const objConfig = overLight as OverLightObjectConfig;
|
|
573
534
|
|
|
574
|
-
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);
|
|
575
540
|
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
return undefined;
|
|
617
|
-
}
|
|
551
|
+
return baseConfig;
|
|
552
|
+
}, [overLight, getEffectiveOverLight, validateConfigValue]);
|
|
618
553
|
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
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
|
-
|
|
637
|
-
|
|
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
|
-
|
|
646
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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 (
|
|
651
|
+
if (effectiveWithoutEffects) {
|
|
766
652
|
return { x: 0, y: 0 };
|
|
767
653
|
}
|
|
768
654
|
return calculateElasticTranslation();
|
|
769
|
-
}, [calculateElasticTranslation,
|
|
655
|
+
}, [calculateElasticTranslation, effectiveWithoutEffects]);
|
|
770
656
|
|
|
771
657
|
const directionalScale = useMemo(() => {
|
|
772
|
-
if (
|
|
658
|
+
if (effectiveWithoutEffects) {
|
|
773
659
|
return 'scale(1)';
|
|
774
660
|
}
|
|
775
661
|
return calculateDirectionalScale();
|
|
776
|
-
}, [calculateDirectionalScale,
|
|
662
|
+
}, [calculateDirectionalScale, effectiveWithoutEffects]);
|
|
777
663
|
|
|
778
664
|
const transformStyle = useMemo(() => {
|
|
779
|
-
if (
|
|
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,
|
|
669
|
+
}, [elasticTranslation, isActive, onClick, directionalScale, effectiveWithoutEffects]);
|
|
784
670
|
|
|
785
|
-
//
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
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
|
-
|
|
800
|
-
|
|
678
|
+
if (effectiveWithoutEffects) {
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
801
681
|
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
}
|
|
682
|
+
const container = mouseContainer?.current || glassRef.current;
|
|
683
|
+
if (!container) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
807
686
|
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
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
|
-
|
|
815
|
-
|
|
816
|
-
|
|
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
|
-
|
|
832
|
-
});
|
|
833
|
-
};
|
|
698
|
+
const center = calculateElementCenter(rect);
|
|
834
699
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
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
|
-
|
|
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
|
-
|
|
844
|
-
|
|
757
|
+
// Subscribe to shared mouse tracker
|
|
758
|
+
useEffect(() => {
|
|
759
|
+
if (externalGlobalMousePosition && externalMouseOffset) {
|
|
760
|
+
return undefined;
|
|
761
|
+
}
|
|
845
762
|
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
767
|
+
const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
|
|
870
768
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
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
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
1050
|
-
|
|
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
|
-
|
|
1080
|
-
|
|
879
|
+
dynamicBorderRadius,
|
|
880
|
+
effectiveBorderRadius,
|
|
1081
881
|
effectiveReducedMotion,
|
|
1082
882
|
effectiveHighContrast,
|
|
1083
|
-
|
|
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
|
+
}
|