@shohojdhara/atomix 0.3.0 → 0.3.1
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 +309 -105
- package/dist/atomix.min.css +3 -5
- package/dist/index.d.ts +804 -53
- package/dist/index.esm.js +16367 -16413
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +16275 -16336
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/themes/applemix.css +309 -105
- package/dist/themes/applemix.min.css +5 -7
- package/dist/themes/boomdevs.css +202 -10
- package/dist/themes/boomdevs.min.css +3 -5
- package/dist/themes/esrar.css +309 -105
- package/dist/themes/esrar.min.css +4 -6
- package/dist/themes/flashtrade.css +310 -105
- package/dist/themes/flashtrade.min.css +5 -7
- package/dist/themes/mashroom.css +300 -96
- package/dist/themes/mashroom.min.css +4 -6
- package/dist/themes/shaj-default.css +300 -96
- package/dist/themes/shaj-default.min.css +4 -6
- package/package.json +1 -1
- package/src/components/AtomixGlass/AtomixGlass.test.tsx +21 -32
- package/src/components/AtomixGlass/AtomixGlass.tsx +55 -42
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +205 -57
- package/src/components/AtomixGlass/GlassFilter.tsx +22 -8
- package/src/components/AtomixGlass/__snapshots__/AtomixGlass.test.tsx.snap +221 -0
- package/src/components/AtomixGlass/atomixGLass.old.tsx +0 -3
- package/src/components/AtomixGlass/shader-utils.ts +8 -0
- package/src/components/AtomixGlass/stories/AtomixGlass.stories.tsx +319 -100
- package/src/components/AtomixGlass/stories/Examples.stories.tsx +601 -105
- package/src/components/AtomixGlass/stories/Modes.stories.tsx +30 -12
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +173 -38
- package/src/components/AtomixGlass/stories/ShaderVariants.stories.tsx +18 -18
- package/src/components/AtomixGlass/stories/shared-components.tsx +27 -5
- package/src/components/Button/Button.tsx +62 -17
- package/src/components/Callout/Callout.test.tsx +8 -14
- package/src/components/Card/Card.tsx +103 -1
- package/src/components/Card/index.ts +3 -2
- package/src/components/Icon/index.ts +1 -1
- package/src/components/Modal/Modal.stories.tsx +29 -38
- package/src/components/Modal/Modal.tsx +4 -4
- package/src/components/Navigation/SideMenu/SideMenu.tsx +49 -41
- package/src/components/Navigation/SideMenu/SideMenuItem.tsx +63 -24
- package/src/components/Popover/Popover.tsx +1 -1
- package/src/components/VideoPlayer/VideoPlayer.stories.tsx +977 -400
- package/src/components/VideoPlayer/VideoPlayer.tsx +1 -6
- package/src/lib/composables/shared-mouse-tracker.ts +133 -0
- package/src/lib/composables/useAtomixGlass.ts +303 -115
- package/src/lib/theme/ThemeManager.integration.test.ts +124 -0
- package/src/lib/theme/ThemeManager.stories.tsx +13 -13
- package/src/lib/theme/ThemeManager.test.ts +4 -0
- package/src/lib/theme/ThemeManager.ts +203 -59
- package/src/lib/theme/ThemeProvider.tsx +183 -33
- package/src/lib/theme/composeTheme.ts +375 -0
- package/src/lib/theme/createTheme.test.ts +475 -0
- package/src/lib/theme/createTheme.ts +510 -0
- package/src/lib/theme/generateCSSVariables.ts +713 -0
- package/src/lib/theme/index.ts +67 -0
- package/src/lib/theme/themeUtils.ts +333 -0
- package/src/lib/theme/types.ts +337 -8
- package/src/lib/theme/useTheme.test.tsx +2 -1
- package/src/lib/theme/useTheme.ts +6 -22
- package/src/lib/types/components.ts +148 -59
- package/src/styles/01-settings/_index.scss +2 -2
- package/src/styles/01-settings/_settings.badge.scss +2 -2
- package/src/styles/01-settings/_settings.border-radius.scss +1 -1
- package/src/styles/01-settings/{_settings.maps.scss → _settings.design-tokens.scss} +163 -49
- package/src/styles/01-settings/_settings.modal.scss +1 -1
- package/src/styles/01-settings/_settings.spacing.scss +14 -13
- package/src/styles/03-generic/_generic.root.scss +131 -50
- package/src/styles/05-objects/_objects.block.scss +1 -1
- package/src/styles/06-components/_components.atomix-glass.scss +20 -22
- package/src/styles/06-components/_components.badge.scss +2 -2
- package/src/styles/06-components/_components.button.scss +1 -1
- package/src/styles/06-components/_components.callout.scss +1 -1
- package/src/styles/06-components/_components.card.scss +74 -2
- package/src/styles/06-components/_components.chart.scss +1 -1
- package/src/styles/06-components/_components.dropdown.scss +6 -0
- package/src/styles/06-components/_components.footer.scss +1 -1
- package/src/styles/06-components/_components.list-group.scss +1 -1
- package/src/styles/06-components/_components.list.scss +1 -1
- package/src/styles/06-components/_components.menu.scss +1 -1
- package/src/styles/06-components/_components.messages.scss +1 -1
- package/src/styles/06-components/_components.modal.scss +7 -2
- package/src/styles/06-components/_components.navbar.scss +1 -1
- package/src/styles/06-components/_components.popover.scss +10 -0
- package/src/styles/06-components/_components.product-review.scss +1 -1
- package/src/styles/06-components/_components.progress.scss +1 -1
- package/src/styles/06-components/_components.rating.scss +1 -1
- package/src/styles/06-components/_components.spinner.scss +1 -1
- package/src/styles/99-utilities/_utilities.background.scss +1 -1
- package/src/styles/99-utilities/_utilities.border.scss +1 -1
- package/src/styles/99-utilities/_utilities.link.scss +1 -1
- package/src/styles/99-utilities/_utilities.text.scss +1 -1
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
OverLightObjectConfig,
|
|
15
15
|
} from '../types/components';
|
|
16
16
|
import { ATOMIX_GLASS } from '../constants/components';
|
|
17
|
+
import { globalMouseTracker } from './shared-mouse-tracker';
|
|
17
18
|
import {
|
|
18
19
|
calculateDistance,
|
|
19
20
|
calculateElementCenter,
|
|
@@ -25,6 +26,125 @@ import {
|
|
|
25
26
|
|
|
26
27
|
const { CONSTANTS } = ATOMIX_GLASS;
|
|
27
28
|
|
|
29
|
+
// Module-level shared background detection cache using WeakMap
|
|
30
|
+
// Automatically cleaned up when elements are removed from DOM
|
|
31
|
+
interface BackgroundDetectionCacheEntry {
|
|
32
|
+
result: boolean;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
config: OverLightConfig;
|
|
35
|
+
threshold: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const backgroundDetectionCache = new WeakMap<HTMLElement, BackgroundDetectionCacheEntry>();
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Compare two OverLightConfig values for equality
|
|
42
|
+
* Handles primitives (boolean, 'auto') and objects with deep comparison
|
|
43
|
+
*/
|
|
44
|
+
const compareOverLightConfig = (
|
|
45
|
+
config1: OverLightConfig,
|
|
46
|
+
config2: OverLightConfig
|
|
47
|
+
): boolean => {
|
|
48
|
+
// Primitive comparison for boolean and 'auto'
|
|
49
|
+
if (typeof config1 !== 'object' || config1 === null) {
|
|
50
|
+
return config1 === config2;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Both must be objects at this point
|
|
54
|
+
if (typeof config2 !== 'object' || config2 === null) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const obj1 = config1 as OverLightObjectConfig;
|
|
59
|
+
const obj2 = config2 as OverLightObjectConfig;
|
|
60
|
+
|
|
61
|
+
// Deep comparison of object properties
|
|
62
|
+
// Compare all defined properties (threshold, opacity, contrast, brightness, saturationBoost)
|
|
63
|
+
const props: (keyof OverLightObjectConfig)[] = [
|
|
64
|
+
'threshold',
|
|
65
|
+
'opacity',
|
|
66
|
+
'contrast',
|
|
67
|
+
'brightness',
|
|
68
|
+
'saturationBoost',
|
|
69
|
+
];
|
|
70
|
+
|
|
71
|
+
for (const prop of props) {
|
|
72
|
+
const val1 = obj1[prop];
|
|
73
|
+
const val2 = obj2[prop];
|
|
74
|
+
|
|
75
|
+
// If both are undefined, they're equal for this property
|
|
76
|
+
if (val1 === undefined && val2 === undefined) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// If one is undefined and the other isn't, they're different
|
|
81
|
+
if (val1 === undefined || val2 === undefined) {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Compare numeric values (handle NaN and floating point precision)
|
|
86
|
+
if (typeof val1 === 'number' && typeof val2 === 'number') {
|
|
87
|
+
// Use Number.isNaN for proper NaN comparison
|
|
88
|
+
if (Number.isNaN(val1) && Number.isNaN(val2)) {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (Number.isNaN(val1) || Number.isNaN(val2)) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
// Compare with small epsilon for floating point numbers
|
|
95
|
+
if (Math.abs(val1 - val2) > Number.EPSILON) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
} else if (val1 !== val2) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Get cached background detection result
|
|
108
|
+
*/
|
|
109
|
+
const getCachedBackgroundDetection = (
|
|
110
|
+
parentElement: HTMLElement | null,
|
|
111
|
+
overLightConfig: OverLightConfig
|
|
112
|
+
): boolean | null => {
|
|
113
|
+
if (!parentElement) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const cached = backgroundDetectionCache.get(parentElement);
|
|
118
|
+
if (cached && compareOverLightConfig(cached.config, overLightConfig)) {
|
|
119
|
+
// Update timestamp for LRU-like behavior (though WeakMap doesn't support LRU)
|
|
120
|
+
cached.timestamp = Date.now();
|
|
121
|
+
return cached.result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return null;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Set cached background detection result
|
|
129
|
+
*/
|
|
130
|
+
const setCachedBackgroundDetection = (
|
|
131
|
+
parentElement: HTMLElement | null,
|
|
132
|
+
overLightConfig: OverLightConfig,
|
|
133
|
+
result: boolean,
|
|
134
|
+
threshold: number
|
|
135
|
+
): void => {
|
|
136
|
+
if (!parentElement) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
backgroundDetectionCache.set(parentElement, {
|
|
141
|
+
result,
|
|
142
|
+
timestamp: Date.now(),
|
|
143
|
+
config: overLightConfig,
|
|
144
|
+
threshold,
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
|
|
28
148
|
interface UseAtomixGlassOptions extends Omit<AtomixGlassProps, 'children'> {
|
|
29
149
|
glassRef: React.RefObject<HTMLDivElement>;
|
|
30
150
|
contentRef: React.RefObject<HTMLDivElement>;
|
|
@@ -44,7 +164,7 @@ interface UseAtomixGlassReturn {
|
|
|
44
164
|
detectedOverLight: boolean;
|
|
45
165
|
globalMousePosition: MousePosition;
|
|
46
166
|
mouseOffset: MousePosition;
|
|
47
|
-
|
|
167
|
+
|
|
48
168
|
// OverLight config
|
|
49
169
|
overLightConfig: {
|
|
50
170
|
isOverLight: boolean;
|
|
@@ -56,12 +176,12 @@ interface UseAtomixGlassReturn {
|
|
|
56
176
|
shadowIntensity: number;
|
|
57
177
|
borderOpacity: number;
|
|
58
178
|
};
|
|
59
|
-
|
|
179
|
+
|
|
60
180
|
// Transform calculations
|
|
61
181
|
elasticTranslation: { x: number; y: number };
|
|
62
182
|
directionalScale: string;
|
|
63
183
|
transformStyle: string;
|
|
64
|
-
|
|
184
|
+
|
|
65
185
|
// Event handlers
|
|
66
186
|
handleMouseEnter: () => void;
|
|
67
187
|
handleMouseLeave: () => void;
|
|
@@ -108,19 +228,21 @@ export function useAtomixGlass({
|
|
|
108
228
|
const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(false);
|
|
109
229
|
const [userPrefersHighContrast, setUserPrefersHighContrast] = useState(false);
|
|
110
230
|
const [detectedOverLight, setDetectedOverLight] = useState(false);
|
|
231
|
+
|
|
232
|
+
// Use shared module-level cache (no per-instance cache needed)
|
|
111
233
|
|
|
112
234
|
// Memoized derived values
|
|
113
235
|
const effectiveCornerRadius = useMemo(() => {
|
|
114
236
|
if (cornerRadius !== undefined) {
|
|
115
237
|
const result = Math.max(0, cornerRadius);
|
|
116
|
-
if (debugCornerRadius) {
|
|
238
|
+
if (process.env.NODE_ENV !== 'production' && debugCornerRadius) {
|
|
117
239
|
console.log('[AtomixGlass] Using manual cornerRadius prop:', result);
|
|
118
240
|
}
|
|
119
241
|
return result;
|
|
120
242
|
}
|
|
121
243
|
|
|
122
244
|
const result = Math.max(0, dynamicCornerRadius);
|
|
123
|
-
if (debugCornerRadius) {
|
|
245
|
+
if (process.env.NODE_ENV !== 'production' && debugCornerRadius) {
|
|
124
246
|
console.log('[AtomixGlass] Using dynamic cornerRadius:', result);
|
|
125
247
|
}
|
|
126
248
|
return result;
|
|
@@ -180,21 +302,21 @@ export function useAtomixGlass({
|
|
|
180
302
|
if (extractedRadius !== null && extractedRadius > 0) {
|
|
181
303
|
setDynamicCornerRadius(extractedRadius);
|
|
182
304
|
|
|
183
|
-
if (debugCornerRadius) {
|
|
305
|
+
if (process.env.NODE_ENV !== 'production' && debugCornerRadius) {
|
|
184
306
|
console.log('[AtomixGlass] Corner radius extracted:', {
|
|
185
307
|
value: extractedRadius,
|
|
186
308
|
source: extractionSource,
|
|
187
309
|
timestamp: new Date().toISOString(),
|
|
188
310
|
});
|
|
189
311
|
}
|
|
190
|
-
} else if (debugCornerRadius) {
|
|
312
|
+
} else if (process.env.NODE_ENV !== 'production' && debugCornerRadius) {
|
|
191
313
|
console.log(
|
|
192
314
|
'[AtomixGlass] No corner radius found, using default:',
|
|
193
315
|
CONSTANTS.DEFAULT_CORNER_RADIUS
|
|
194
316
|
);
|
|
195
317
|
}
|
|
196
318
|
} catch (error) {
|
|
197
|
-
if (debugCornerRadius) {
|
|
319
|
+
if (process.env.NODE_ENV !== 'production' && debugCornerRadius) {
|
|
198
320
|
console.error('[AtomixGlass] Error extracting corner radius:', error);
|
|
199
321
|
}
|
|
200
322
|
}
|
|
@@ -209,22 +331,31 @@ export function useAtomixGlass({
|
|
|
209
331
|
useEffect(() => {
|
|
210
332
|
// Only run auto-detection for 'auto' mode or object config (which uses auto-detection)
|
|
211
333
|
const shouldDetect = (overLight === 'auto' || (typeof overLight === 'object' && overLight !== null));
|
|
212
|
-
|
|
334
|
+
|
|
213
335
|
if (shouldDetect && glassRef.current) {
|
|
336
|
+
const element = glassRef.current;
|
|
337
|
+
const parentElement = element.parentElement;
|
|
338
|
+
|
|
339
|
+
// Check shared cache: skip detection if parent unchanged and config unchanged
|
|
340
|
+
const cachedResult = getCachedBackgroundDetection(parentElement, overLight);
|
|
341
|
+
if (cachedResult !== null) {
|
|
342
|
+
setDetectedOverLight(cachedResult);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
214
346
|
const timeoutId = setTimeout(() => {
|
|
215
347
|
try {
|
|
216
|
-
const element = glassRef.current;
|
|
217
348
|
if (!element) {
|
|
218
349
|
setDetectedOverLight(false);
|
|
219
350
|
return;
|
|
220
351
|
}
|
|
221
|
-
|
|
352
|
+
|
|
222
353
|
// Validate window context
|
|
223
354
|
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
|
|
224
355
|
setDetectedOverLight(false);
|
|
225
356
|
return;
|
|
226
357
|
}
|
|
227
|
-
|
|
358
|
+
|
|
228
359
|
let totalLuminance = 0;
|
|
229
360
|
let validSamples = 0;
|
|
230
361
|
let hasValidBackground = false;
|
|
@@ -233,7 +364,7 @@ export function useAtomixGlass({
|
|
|
233
364
|
let depth = 0;
|
|
234
365
|
const maxDepth = 20;
|
|
235
366
|
const maxSamples = 10;
|
|
236
|
-
|
|
367
|
+
|
|
237
368
|
// Limit traversal depth to prevent infinite loops and performance issues
|
|
238
369
|
while (currentElement && validSamples < maxSamples && depth < maxDepth) {
|
|
239
370
|
try {
|
|
@@ -243,7 +374,7 @@ export function useAtomixGlass({
|
|
|
243
374
|
depth++;
|
|
244
375
|
continue;
|
|
245
376
|
}
|
|
246
|
-
|
|
377
|
+
|
|
247
378
|
const bgColor = computedStyle.backgroundColor;
|
|
248
379
|
const bgImage = computedStyle.backgroundImage;
|
|
249
380
|
|
|
@@ -254,11 +385,11 @@ export function useAtomixGlass({
|
|
|
254
385
|
const r = Number(rgb[0]);
|
|
255
386
|
const g = Number(rgb[1]);
|
|
256
387
|
const b = Number(rgb[2]);
|
|
257
|
-
|
|
388
|
+
|
|
258
389
|
// Validate RGB values are valid numbers
|
|
259
|
-
if (!isNaN(r) && !isNaN(g) && !isNaN(b) &&
|
|
260
|
-
|
|
261
|
-
|
|
390
|
+
if (!isNaN(r) && !isNaN(g) && !isNaN(b) &&
|
|
391
|
+
isFinite(r) && isFinite(g) && isFinite(b) &&
|
|
392
|
+
r >= 0 && r <= 255 && g >= 0 && g <= 255 && b >= 0 && b <= 255) {
|
|
262
393
|
// Only consider if it's not pure black or very dark
|
|
263
394
|
if (r > 10 || g > 10 || b > 10) {
|
|
264
395
|
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
@@ -271,7 +402,7 @@ export function useAtomixGlass({
|
|
|
271
402
|
}
|
|
272
403
|
}
|
|
273
404
|
}
|
|
274
|
-
|
|
405
|
+
|
|
275
406
|
// Check for image backgrounds
|
|
276
407
|
if (bgImage && bgImage !== 'none' && bgImage !== 'initial') {
|
|
277
408
|
// For image backgrounds, assume medium luminance
|
|
@@ -285,7 +416,7 @@ export function useAtomixGlass({
|
|
|
285
416
|
console.debug('AtomixGlass: Error getting computed style for element:', styleError);
|
|
286
417
|
}
|
|
287
418
|
}
|
|
288
|
-
|
|
419
|
+
|
|
289
420
|
// Move to parent element for next iteration
|
|
290
421
|
if (currentElement) {
|
|
291
422
|
currentElement = currentElement.parentElement;
|
|
@@ -300,25 +431,29 @@ export function useAtomixGlass({
|
|
|
300
431
|
const avgLuminance = totalLuminance / validSamples;
|
|
301
432
|
if (!isNaN(avgLuminance) && isFinite(avgLuminance)) {
|
|
302
433
|
let threshold = 0.7; // Conservative threshold for overlight
|
|
303
|
-
|
|
434
|
+
|
|
304
435
|
// If overLight is an object, use its threshold property with validation
|
|
305
436
|
if (typeof overLight === 'object' && overLight !== null) {
|
|
306
437
|
const objConfig = overLight as OverLightObjectConfig;
|
|
307
438
|
if (objConfig.threshold !== undefined) {
|
|
308
|
-
const configThreshold = typeof objConfig.threshold === 'number' &&
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
439
|
+
const configThreshold = typeof objConfig.threshold === 'number' &&
|
|
440
|
+
!isNaN(objConfig.threshold) &&
|
|
441
|
+
isFinite(objConfig.threshold)
|
|
442
|
+
? objConfig.threshold
|
|
443
|
+
: 0.7;
|
|
313
444
|
threshold = Math.min(0.9, Math.max(0.1, configThreshold));
|
|
314
445
|
}
|
|
315
446
|
}
|
|
316
|
-
|
|
447
|
+
|
|
317
448
|
const isOverLightDetected = avgLuminance > threshold;
|
|
318
|
-
setDetectedOverLight(isOverLightDetected);
|
|
319
449
|
|
|
450
|
+
// Cache the result in shared cache
|
|
451
|
+
setCachedBackgroundDetection(element.parentElement, overLight, isOverLightDetected, threshold);
|
|
452
|
+
|
|
453
|
+
setDetectedOverLight(isOverLightDetected);
|
|
454
|
+
|
|
320
455
|
// Debug logging
|
|
321
|
-
if (debugOverLight) {
|
|
456
|
+
if (process.env.NODE_ENV !== 'production' && debugOverLight) {
|
|
322
457
|
console.log('[AtomixGlass] OverLight Detection:', {
|
|
323
458
|
avgLuminance: avgLuminance.toFixed(3),
|
|
324
459
|
threshold: threshold.toFixed(3),
|
|
@@ -331,28 +466,46 @@ export function useAtomixGlass({
|
|
|
331
466
|
}
|
|
332
467
|
} else {
|
|
333
468
|
// Invalid luminance calculation, default to false
|
|
334
|
-
|
|
469
|
+
const result = false;
|
|
470
|
+
const threshold = typeof overLight === 'object' && overLight !== null
|
|
471
|
+
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
472
|
+
: 0.7;
|
|
473
|
+
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
474
|
+
setDetectedOverLight(result);
|
|
335
475
|
}
|
|
336
476
|
} else {
|
|
337
477
|
// Default to false if no valid background found
|
|
338
|
-
|
|
478
|
+
const result = false;
|
|
479
|
+
const threshold = typeof overLight === 'object' && overLight !== null
|
|
480
|
+
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
481
|
+
: 0.7;
|
|
482
|
+
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
483
|
+
setDetectedOverLight(result);
|
|
339
484
|
}
|
|
340
485
|
} catch (error) {
|
|
341
486
|
// Enhanced error logging with context
|
|
342
487
|
if (process.env.NODE_ENV === 'development') {
|
|
343
488
|
console.warn('AtomixGlass: Error detecting background brightness:', error);
|
|
344
489
|
}
|
|
345
|
-
|
|
490
|
+
const result = false;
|
|
491
|
+
if (element && element.parentElement) {
|
|
492
|
+
const threshold = typeof overLight === 'object' && overLight !== null
|
|
493
|
+
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
494
|
+
: 0.7;
|
|
495
|
+
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
496
|
+
}
|
|
497
|
+
setDetectedOverLight(result);
|
|
346
498
|
}
|
|
347
499
|
}, 150);
|
|
348
|
-
|
|
500
|
+
|
|
349
501
|
return () => clearTimeout(timeoutId);
|
|
350
502
|
} else if (typeof overLight === 'boolean') {
|
|
351
503
|
// For boolean values, disable auto-detection
|
|
504
|
+
// Cache is automatically managed by WeakMap (no manual clearing needed)
|
|
352
505
|
setDetectedOverLight(false);
|
|
353
|
-
|
|
506
|
+
|
|
354
507
|
// Debug logging for boolean mode
|
|
355
|
-
if (debugOverLight) {
|
|
508
|
+
if (process.env.NODE_ENV !== 'production' && debugOverLight) {
|
|
356
509
|
console.log('[AtomixGlass] OverLight Mode: boolean', {
|
|
357
510
|
value: overLight,
|
|
358
511
|
autoDetection: false,
|
|
@@ -407,88 +560,121 @@ export function useAtomixGlass({
|
|
|
407
560
|
}
|
|
408
561
|
}, [overLight, glassRef, debugOverLight]);
|
|
409
562
|
|
|
410
|
-
// Mouse tracking
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
563
|
+
// Mouse tracking using shared global tracker
|
|
564
|
+
// Cache bounding rect to avoid repeated getBoundingClientRect calls
|
|
565
|
+
const cachedRectRef = useRef<DOMRect | null>(null);
|
|
566
|
+
const updateRectRef = useRef<number | null>(null);
|
|
567
|
+
|
|
568
|
+
// Handle mouse position updates from shared tracker
|
|
569
|
+
const handleGlobalMousePosition = useCallback(
|
|
570
|
+
(globalPos: MousePosition) => {
|
|
571
|
+
if (externalGlobalMousePosition && externalMouseOffset) {
|
|
572
|
+
// External mouse position provided, skip internal tracking
|
|
573
|
+
return;
|
|
574
|
+
}
|
|
417
575
|
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (!event) {
|
|
422
|
-
mouseMoveThrottleRef.current = null;
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
576
|
+
if (effectiveDisableEffects) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
425
579
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
}
|
|
580
|
+
const container = mouseContainer?.current || glassRef.current;
|
|
581
|
+
if (!container) {
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
431
584
|
|
|
432
|
-
|
|
585
|
+
const startTime = enablePerformanceMonitoring ? performance.now() : 0;
|
|
433
586
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
587
|
+
// Use cached rect if available, otherwise get new one
|
|
588
|
+
let rect = cachedRectRef.current;
|
|
589
|
+
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
590
|
+
rect = container.getBoundingClientRect();
|
|
591
|
+
cachedRectRef.current = rect;
|
|
592
|
+
}
|
|
439
593
|
|
|
440
|
-
|
|
594
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
441
597
|
|
|
442
|
-
|
|
443
|
-
x: ((event.clientX - center.x) / rect.width) * 100,
|
|
444
|
-
y: ((event.clientY - center.y) / rect.height) * 100,
|
|
445
|
-
});
|
|
598
|
+
const center = calculateElementCenter(rect);
|
|
446
599
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
600
|
+
// Calculate offset relative to this container
|
|
601
|
+
const newOffset = {
|
|
602
|
+
x: ((globalPos.x - center.x) / rect.width) * 100,
|
|
603
|
+
y: ((globalPos.y - center.y) / rect.height) * 100,
|
|
604
|
+
};
|
|
451
605
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
if (duration > 5) {
|
|
456
|
-
console.warn(`AtomixGlass: Mouse tracking took ${duration.toFixed(2)}ms`);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
606
|
+
// React 18 automatically batches these updates
|
|
607
|
+
setInternalMouseOffset(newOffset);
|
|
608
|
+
setInternalGlobalMousePosition(globalPos);
|
|
459
609
|
|
|
460
|
-
|
|
461
|
-
|
|
610
|
+
if (process.env.NODE_ENV !== 'production' && enablePerformanceMonitoring) {
|
|
611
|
+
const endTime = performance.now();
|
|
612
|
+
const duration = endTime - startTime;
|
|
613
|
+
if (duration > 5) {
|
|
614
|
+
console.warn(`AtomixGlass: Mouse tracking took ${duration.toFixed(2)}ms`);
|
|
615
|
+
}
|
|
462
616
|
}
|
|
463
617
|
},
|
|
464
|
-
[
|
|
618
|
+
[
|
|
619
|
+
mouseContainer,
|
|
620
|
+
glassRef,
|
|
621
|
+
externalGlobalMousePosition,
|
|
622
|
+
externalMouseOffset,
|
|
623
|
+
effectiveDisableEffects,
|
|
624
|
+
enablePerformanceMonitoring,
|
|
625
|
+
]
|
|
465
626
|
);
|
|
466
627
|
|
|
628
|
+
// Subscribe to shared mouse tracker
|
|
467
629
|
useEffect(() => {
|
|
468
630
|
if (externalGlobalMousePosition && externalMouseOffset) {
|
|
631
|
+
// External mouse position provided, don't subscribe
|
|
469
632
|
return undefined;
|
|
470
633
|
}
|
|
471
634
|
|
|
472
635
|
if (effectiveDisableEffects) {
|
|
636
|
+
// Effects disabled, don't subscribe
|
|
473
637
|
return undefined;
|
|
474
638
|
}
|
|
475
639
|
|
|
640
|
+
// Subscribe to shared tracker
|
|
641
|
+
const unsubscribe = globalMouseTracker.subscribe(handleGlobalMousePosition);
|
|
642
|
+
|
|
643
|
+
// Update cached rect when container size changes
|
|
644
|
+
const updateRect = () => {
|
|
645
|
+
if (updateRectRef.current !== null) {
|
|
646
|
+
cancelAnimationFrame(updateRectRef.current);
|
|
647
|
+
}
|
|
648
|
+
updateRectRef.current = requestAnimationFrame(() => {
|
|
649
|
+
const container = mouseContainer?.current || glassRef.current;
|
|
650
|
+
if (container) {
|
|
651
|
+
cachedRectRef.current = container.getBoundingClientRect();
|
|
652
|
+
}
|
|
653
|
+
updateRectRef.current = null;
|
|
654
|
+
});
|
|
655
|
+
};
|
|
656
|
+
|
|
657
|
+
// Use ResizeObserver to update cached rect when container size changes
|
|
476
658
|
const container = mouseContainer?.current || glassRef.current;
|
|
477
|
-
|
|
478
|
-
|
|
659
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
660
|
+
|
|
661
|
+
if (container && typeof ResizeObserver !== 'undefined') {
|
|
662
|
+
resizeObserver = new ResizeObserver(updateRect);
|
|
663
|
+
resizeObserver.observe(container);
|
|
479
664
|
}
|
|
480
665
|
|
|
481
|
-
container.addEventListener('mousemove', handleMouseMove, { passive: true });
|
|
482
|
-
|
|
483
666
|
return () => {
|
|
484
|
-
|
|
485
|
-
if (
|
|
486
|
-
cancelAnimationFrame(
|
|
487
|
-
|
|
667
|
+
unsubscribe();
|
|
668
|
+
if (updateRectRef.current !== null) {
|
|
669
|
+
cancelAnimationFrame(updateRectRef.current);
|
|
670
|
+
updateRectRef.current = null;
|
|
671
|
+
}
|
|
672
|
+
if (resizeObserver) {
|
|
673
|
+
resizeObserver.disconnect();
|
|
488
674
|
}
|
|
489
675
|
};
|
|
490
676
|
}, [
|
|
491
|
-
|
|
677
|
+
handleGlobalMousePosition,
|
|
492
678
|
mouseContainer,
|
|
493
679
|
glassRef,
|
|
494
680
|
externalGlobalMousePosition,
|
|
@@ -632,10 +818,10 @@ export function useAtomixGlass({
|
|
|
632
818
|
return;
|
|
633
819
|
}
|
|
634
820
|
|
|
635
|
-
|
|
821
|
+
// Measure actual rendered size without artificial offsets to avoid feedback loops
|
|
636
822
|
const newSize: GlassSize = {
|
|
637
|
-
width: Math.round(rect.width
|
|
638
|
-
height: Math.round(rect.height
|
|
823
|
+
width: Math.round(rect.width),
|
|
824
|
+
height: Math.round(rect.height),
|
|
639
825
|
};
|
|
640
826
|
|
|
641
827
|
const cornerRadiusChanged = lastCornerRadius !== effectiveCornerRadius;
|
|
@@ -662,32 +848,29 @@ export function useAtomixGlass({
|
|
|
662
848
|
const initialTimeoutId = setTimeout(() => updateGlassSize(true), 0);
|
|
663
849
|
|
|
664
850
|
let resizeObserver: ResizeObserver | null = null;
|
|
665
|
-
let
|
|
666
|
-
|
|
667
|
-
const hasResizeObserver = typeof ResizeObserver !== 'undefined';
|
|
851
|
+
let resizeDebounceTimeout: NodeJS.Timeout | null = null;
|
|
668
852
|
|
|
669
|
-
|
|
853
|
+
// ResizeObserver has 98%+ browser support, no need for fallback
|
|
854
|
+
if (typeof ResizeObserver !== 'undefined' && isValidElement(glassRef.current)) {
|
|
670
855
|
try {
|
|
671
856
|
resizeObserver = new ResizeObserver(entries => {
|
|
672
857
|
for (const entry of entries) {
|
|
673
858
|
if (entry.target === glassRef.current) {
|
|
674
|
-
|
|
859
|
+
// Update cached rect when size changes
|
|
860
|
+
if (glassRef.current) {
|
|
861
|
+
cachedRectRef.current = glassRef.current.getBoundingClientRect();
|
|
862
|
+
}
|
|
863
|
+
// Debounce resize updates to match RAF timing (16ms)
|
|
864
|
+
if (resizeDebounceTimeout) clearTimeout(resizeDebounceTimeout);
|
|
865
|
+
resizeDebounceTimeout = setTimeout(() => updateGlassSize(false), 16);
|
|
675
866
|
break;
|
|
676
867
|
}
|
|
677
868
|
}
|
|
678
869
|
});
|
|
679
870
|
resizeObserver.observe(glassRef.current);
|
|
680
|
-
} catch {
|
|
681
|
-
|
|
682
|
-
() => isValidElement(glassRef.current) && updateGlassSize(false),
|
|
683
|
-
100
|
|
684
|
-
);
|
|
871
|
+
} catch (error) {
|
|
872
|
+
console.warn('AtomixGlass: ResizeObserver not available, using window resize only', error);
|
|
685
873
|
}
|
|
686
|
-
} else {
|
|
687
|
-
fallbackInterval = setInterval(
|
|
688
|
-
() => isValidElement(glassRef.current) && updateGlassSize(false),
|
|
689
|
-
100
|
|
690
|
-
);
|
|
691
874
|
}
|
|
692
875
|
|
|
693
876
|
window.addEventListener('resize', debouncedResizeHandler, { passive: true });
|
|
@@ -696,7 +879,7 @@ export function useAtomixGlass({
|
|
|
696
879
|
clearTimeout(initialTimeoutId);
|
|
697
880
|
if (rafId !== null) cancelAnimationFrame(rafId);
|
|
698
881
|
if (resizeTimeoutId) clearTimeout(resizeTimeoutId);
|
|
699
|
-
if (
|
|
882
|
+
if (resizeDebounceTimeout) clearTimeout(resizeDebounceTimeout);
|
|
700
883
|
window.removeEventListener('resize', debouncedResizeHandler);
|
|
701
884
|
resizeObserver?.disconnect();
|
|
702
885
|
};
|
|
@@ -749,7 +932,7 @@ export function useAtomixGlass({
|
|
|
749
932
|
|
|
750
933
|
// More robust overlight configuration with better defaults and clamping
|
|
751
934
|
const baseOpacity = isOverLight ? Math.min(0.6, Math.max(0.2, 0.5 * hoverIntensity * activeIntensity)) : 0;
|
|
752
|
-
|
|
935
|
+
|
|
753
936
|
const baseConfig = {
|
|
754
937
|
isOverLight,
|
|
755
938
|
threshold: 0.7,
|
|
@@ -763,14 +946,14 @@ export function useAtomixGlass({
|
|
|
763
946
|
|
|
764
947
|
if (typeof overLight === 'object' && overLight !== null) {
|
|
765
948
|
const objConfig = overLight as OverLightObjectConfig;
|
|
766
|
-
|
|
949
|
+
|
|
767
950
|
// Validate and apply object config values with proper clamping
|
|
768
951
|
const validatedThreshold = validateConfigValue(objConfig.threshold, 0.1, 1.0, baseConfig.threshold);
|
|
769
952
|
const validatedOpacity = validateConfigValue(objConfig.opacity, 0.1, 1.0, baseConfig.opacity);
|
|
770
953
|
const validatedContrast = validateConfigValue(objConfig.contrast, 0.5, 2.5, baseConfig.contrast);
|
|
771
954
|
const validatedBrightness = validateConfigValue(objConfig.brightness, 0.5, 2.0, baseConfig.brightness);
|
|
772
955
|
const validatedSaturationBoost = validateConfigValue(objConfig.saturationBoost, 0.5, 3.0, baseConfig.saturationBoost);
|
|
773
|
-
|
|
956
|
+
|
|
774
957
|
const finalConfig = {
|
|
775
958
|
...baseConfig,
|
|
776
959
|
threshold: validatedThreshold,
|
|
@@ -779,9 +962,9 @@ export function useAtomixGlass({
|
|
|
779
962
|
brightness: validatedBrightness + mouseInfluence * 0.15,
|
|
780
963
|
saturationBoost: validatedSaturationBoost + mouseInfluence * 0.4,
|
|
781
964
|
};
|
|
782
|
-
|
|
965
|
+
|
|
783
966
|
// Debug logging
|
|
784
|
-
if (debugOverLight) {
|
|
967
|
+
if (process.env.NODE_ENV !== 'production' && debugOverLight) {
|
|
785
968
|
console.log('[AtomixGlass] OverLight Config:', {
|
|
786
969
|
isOverLight,
|
|
787
970
|
config: {
|
|
@@ -808,12 +991,12 @@ export function useAtomixGlass({
|
|
|
808
991
|
timestamp: new Date().toISOString(),
|
|
809
992
|
});
|
|
810
993
|
}
|
|
811
|
-
|
|
994
|
+
|
|
812
995
|
return finalConfig;
|
|
813
996
|
}
|
|
814
997
|
|
|
815
998
|
// Debug logging for non-object configs
|
|
816
|
-
if (debugOverLight) {
|
|
999
|
+
if (process.env.NODE_ENV !== 'production' && debugOverLight) {
|
|
817
1000
|
console.log('[AtomixGlass] OverLight Config:', {
|
|
818
1001
|
isOverLight,
|
|
819
1002
|
configType: typeof overLight === 'boolean' ? (overLight ? 'true' : 'false') : overLight,
|
|
@@ -854,6 +1037,12 @@ export function useAtomixGlass({
|
|
|
854
1037
|
[onClick]
|
|
855
1038
|
);
|
|
856
1039
|
|
|
1040
|
+
// No-op handler for backward compatibility (mouse tracking now handled by shared tracker)
|
|
1041
|
+
const handleMouseMove = useCallback((_e: MouseEvent) => {
|
|
1042
|
+
// Mouse tracking is now handled by shared global tracker
|
|
1043
|
+
// This handler is kept for backward compatibility with existing code
|
|
1044
|
+
}, []);
|
|
1045
|
+
|
|
857
1046
|
return {
|
|
858
1047
|
// State
|
|
859
1048
|
isHovered,
|
|
@@ -885,4 +1074,3 @@ export function useAtomixGlass({
|
|
|
885
1074
|
handleKeyDown,
|
|
886
1075
|
};
|
|
887
1076
|
}
|
|
888
|
-
|