@shohojdhara/atomix 0.4.5 → 0.4.6
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 +33 -14
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +1 -1
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.js +183 -184
- package/dist/charts.js.map +1 -1
- package/dist/core.js +183 -184
- package/dist/core.js.map +1 -1
- package/dist/forms.js +183 -184
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +183 -184
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +4 -31
- package/dist/index.esm.js +192 -283
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +194 -285
- 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/src/components/AtomixGlass/AtomixGlass.tsx +60 -38
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +6 -35
- package/src/components/AtomixGlass/glass-utils.ts +27 -14
- package/src/components/AtomixGlass/stories/Overview.stories.tsx +19 -21
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +1162 -515
- package/src/components/AtomixGlass/stories/shared-components.tsx +11 -3
- package/src/components/Navigation/Navbar/Navbar.tsx +13 -5
- package/src/lib/composables/index.ts +1 -2
- package/src/lib/composables/useAtomixGlass.ts +246 -222
- package/src/lib/composables/useAtomixGlassStyles.ts +46 -23
- package/src/lib/constants/components.ts +3 -1
- package/src/styles/06-components/_components.atomix-glass.scss +45 -20
- package/src/lib/composables/atomix-glass/useGlassBackgroundDetection.ts +0 -329
- package/src/lib/composables/atomix-glass/useGlassCornerRadius.ts +0 -82
- package/src/lib/composables/atomix-glass/useGlassMouseTracking.ts +0 -153
- package/src/lib/composables/atomix-glass/useGlassOverLight.ts +0 -198
- package/src/lib/composables/atomix-glass/useGlassState.ts +0 -112
- package/src/lib/composables/atomix-glass/useGlassTransforms.ts +0 -160
- package/src/lib/composables/glass-styles.ts +0 -302
- package/src/lib/composables/useGlassContainer.ts +0 -177
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ATOMIX_GLASS } from '../constants/components';
|
|
2
|
-
import { calculateDistance, calculateElementCenter, calculateMouseInfluence, validateGlassSize, clampBlur } from '../../components/AtomixGlass/glass-utils';
|
|
2
|
+
import { calculateDistance, calculateElementCenter, calculateMouseInfluence, validateGlassSize, clampBlur, smoothstep, softClamp } from '../../components/AtomixGlass/glass-utils';
|
|
3
3
|
import type { GlassSize, MousePosition, OverLightObjectConfig } from '../types/components';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -73,30 +73,53 @@ export const updateAtomixGlassStyles = (
|
|
|
73
73
|
saturationBoost: baseOverLightConfig.saturationBoost
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
let computedDirectionalScale = directionalScale;
|
|
77
|
+
|
|
78
|
+
// Calculate elastic translation and directional scale
|
|
79
|
+
let elasticTranslation = { x: 0, y: 0 };
|
|
80
|
+
if (!effectiveWithoutEffects && wrapperElement) {
|
|
81
|
+
const rect = wrapperElement.getBoundingClientRect();
|
|
82
|
+
const center = calculateElementCenter(rect);
|
|
83
|
+
|
|
84
|
+
// Mouse presence and edge distance logic
|
|
85
|
+
if (globalMousePosition.x && globalMousePosition.y && validateGlassSize(glassSize)) {
|
|
86
|
+
const deltaX = globalMousePosition.x - center.x;
|
|
87
|
+
const deltaY = globalMousePosition.y - center.y;
|
|
88
|
+
const edgeDistanceX = Math.max(0, Math.abs(deltaX) - glassSize.width / 2);
|
|
89
|
+
const edgeDistanceY = Math.max(0, Math.abs(deltaY) - glassSize.height / 2);
|
|
90
|
+
const edgeDistance = calculateDistance({ x: edgeDistanceX, y: edgeDistanceY }, { x: 0, y: 0 });
|
|
91
|
+
|
|
92
|
+
// Elastic translation
|
|
93
|
+
const rawT = edgeDistance > ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE ? 0 : 1 - edgeDistance / ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE;
|
|
94
|
+
const fadeInFactor = smoothstep(rawT);
|
|
95
|
+
elasticTranslation = {
|
|
96
|
+
x: deltaX * elasticity * 0.1 * fadeInFactor,
|
|
97
|
+
y: deltaY * elasticity * 0.1 * fadeInFactor,
|
|
98
|
+
};
|
|
90
99
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
100
|
+
// Directional scale
|
|
101
|
+
if (!isOverLight && edgeDistance <= ATOMIX_GLASS.CONSTANTS.ACTIVATION_ZONE) {
|
|
102
|
+
const centerDistance = calculateDistance(globalMousePosition, center);
|
|
103
|
+
if (centerDistance > 0) {
|
|
104
|
+
const normalizedX = deltaX / centerDistance;
|
|
105
|
+
const normalizedY = deltaY / centerDistance;
|
|
106
|
+
const stretchIntensity = Math.min(centerDistance / 300, 1) * elasticity * rawT;
|
|
107
|
+
|
|
108
|
+
const scaleX = 1 + Math.abs(normalizedX) * stretchIntensity * 0.3 - Math.abs(normalizedY) * stretchIntensity * 0.15;
|
|
109
|
+
const scaleY = 1 + Math.abs(normalizedY) * stretchIntensity * 0.3 - Math.abs(normalizedX) * stretchIntensity * 0.15;
|
|
110
|
+
|
|
111
|
+
const softScaleX = 1 - softClamp(Math.max(0, 1 - scaleX), 0.2);
|
|
112
|
+
const softScaleY = 1 - softClamp(Math.max(0, 1 - scaleY), 0.2);
|
|
113
|
+
|
|
114
|
+
computedDirectionalScale = `scaleX(${Math.max(0.85, softScaleX)}) scaleY(${Math.max(0.85, softScaleY)})`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
96
119
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
120
|
+
const transformStyle = effectiveWithoutEffects
|
|
121
|
+
? isActive && Boolean(onClick) ? 'scale(0.98)' : 'scale(1)'
|
|
122
|
+
: `translate(${elasticTranslation.x}px, ${elasticTranslation.y}px) ${isActive && Boolean(onClick) ? 'scale(0.96)' : computedDirectionalScale}`;
|
|
100
123
|
|
|
101
124
|
// Update Wrapper Styles (glassVars)
|
|
102
125
|
if (wrapperElement) {
|
|
@@ -1663,7 +1663,9 @@ export const ATOMIX_GLASS = {
|
|
|
1663
1663
|
ENABLE_OVER_LIGHT_LAYERS: true,
|
|
1664
1664
|
},
|
|
1665
1665
|
CONSTANTS: {
|
|
1666
|
-
ACTIVATION_ZONE:
|
|
1666
|
+
ACTIVATION_ZONE: 350,
|
|
1667
|
+
LERP_FACTOR: 0.08,
|
|
1668
|
+
SMOOTHSTEP_POWER: 2.5,
|
|
1667
1669
|
MIN_BLUR: 0.1,
|
|
1668
1670
|
MOUSE_INFLUENCE_DIVISOR: 100,
|
|
1669
1671
|
EDGE_FADE_PIXELS: 2,
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
// CSS custom property defaults
|
|
10
10
|
--atomix-glass-radius: var(--atomix-radius-md, 16px);
|
|
11
11
|
--atomix-glass-transform: none;
|
|
12
|
-
--atomix-glass-transition: transform var(--atomix-transition-duration, 0.
|
|
12
|
+
--atomix-glass-transition: transform var(--atomix-transition-duration, 0.45s)
|
|
13
|
+
cubic-bezier(0.22, 1, 0.36, 1);
|
|
13
14
|
--atomix-glass-position: absolute;
|
|
14
15
|
--atomix-glass-container-width: 100%;
|
|
15
16
|
--atomix-glass-container-height: 100%;
|
|
@@ -19,22 +20,28 @@
|
|
|
19
20
|
// Z-INDEX STACKING ORDER (local scale, scoped to this component)
|
|
20
21
|
// =========================================================================
|
|
21
22
|
// 0: background layers
|
|
22
|
-
// 1:
|
|
23
|
+
// 1: container (wraps filter + content)
|
|
23
24
|
// 2: base / overlay effect layers
|
|
24
25
|
// 3: hover effect layers
|
|
25
|
-
// 4:
|
|
26
|
+
// 4: overlay-highlight
|
|
26
27
|
// 5: border-1
|
|
27
28
|
// 6: border-2
|
|
28
|
-
// 7: overlay-highlight
|
|
29
29
|
// =========================================================================
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
--
|
|
35
|
-
|
|
36
|
-
--_glass-z-
|
|
37
|
-
--_glass-z-
|
|
30
|
+
// Internal layers within the container:
|
|
31
|
+
// filter = base + 1
|
|
32
|
+
// content = base + 7
|
|
33
|
+
// =========================================================================
|
|
34
|
+
--atomix-glass-base-z-index: 0;
|
|
35
|
+
|
|
36
|
+
--_glass-z-background: calc(var(--atomix-glass-base-z-index) + 0);
|
|
37
|
+
--_glass-z-container: calc(var(--atomix-glass-base-z-index) + 1);
|
|
38
|
+
--_glass-z-filter: calc(var(--atomix-glass-base-z-index) + 1);
|
|
39
|
+
--_glass-z-effect: calc(var(--atomix-glass-base-z-index) + 2);
|
|
40
|
+
--_glass-z-hover: calc(var(--atomix-glass-base-z-index) + 3);
|
|
41
|
+
--_glass-z-overlay-highlight: calc(var(--atomix-glass-base-z-index) + 4);
|
|
42
|
+
--_glass-z-border-1: calc(var(--atomix-glass-base-z-index) + 5);
|
|
43
|
+
--_glass-z-border-2: calc(var(--atomix-glass-base-z-index) + 6);
|
|
44
|
+
--_glass-z-content: calc(var(--atomix-glass-base-z-index) + 7);
|
|
38
45
|
|
|
39
46
|
// Base layer styles for all effect layers (hover, border, overlay)
|
|
40
47
|
&__hover-1,
|
|
@@ -71,19 +78,25 @@
|
|
|
71
78
|
}
|
|
72
79
|
|
|
73
80
|
&__hover-1 {
|
|
74
|
-
transition:
|
|
81
|
+
transition:
|
|
82
|
+
opacity 0.2s cubic-bezier(0.22, 1, 0.36, 1),
|
|
83
|
+
background 0.35s cubic-bezier(0.22, 1, 0.36, 1);
|
|
75
84
|
opacity: var(--atomix-glass-hover-1-opacity, 0);
|
|
76
85
|
background: var(--atomix-glass-hover-1-gradient, none);
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
&__hover-2 {
|
|
80
|
-
transition:
|
|
89
|
+
transition:
|
|
90
|
+
opacity 0.35s cubic-bezier(0.22, 1, 0.36, 1),
|
|
91
|
+
background 0.45s cubic-bezier(0.22, 1, 0.36, 1);
|
|
81
92
|
opacity: var(--atomix-glass-hover-2-opacity, 0);
|
|
82
93
|
background: var(--atomix-glass-hover-2-gradient, none);
|
|
83
94
|
}
|
|
84
95
|
|
|
85
96
|
&__hover-3 {
|
|
86
|
-
transition:
|
|
97
|
+
transition:
|
|
98
|
+
opacity 0.55s cubic-bezier(0.22, 1, 0.36, 1),
|
|
99
|
+
background 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
|
87
100
|
opacity: var(--atomix-glass-hover-3-opacity, 0);
|
|
88
101
|
background: var(--atomix-glass-hover-3-gradient, none);
|
|
89
102
|
}
|
|
@@ -112,9 +125,8 @@
|
|
|
112
125
|
transition: var(--atomix-glass-transition);
|
|
113
126
|
mix-blend-mode: screen;
|
|
114
127
|
z-index: var(--_glass-z-overlay-highlight);
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
// Background gradient uses constants for positioning
|
|
128
|
+
opacity: var(--atomix-glass-overlay-highlight-opacity, 0);
|
|
129
|
+
background: var(--atomix-glass-overlay-highlight-bg, none);
|
|
118
130
|
}
|
|
119
131
|
|
|
120
132
|
// Border effect layers - matching old version exactly
|
|
@@ -180,6 +192,7 @@
|
|
|
180
192
|
position: relative;
|
|
181
193
|
border-radius: var(--atomix-glass-radius);
|
|
182
194
|
transition: var(--atomix-glass-transition);
|
|
195
|
+
z-index: var(--_glass-z-container);
|
|
183
196
|
}
|
|
184
197
|
|
|
185
198
|
&__inner {
|
|
@@ -187,6 +200,8 @@
|
|
|
187
200
|
height: var(--atomix-glass-container-height);
|
|
188
201
|
position: relative;
|
|
189
202
|
border-radius: var(--atomix-glass-radius);
|
|
203
|
+
padding: var(--atomix-glass-container-padding);
|
|
204
|
+
box-shadow: var(--atomix-glass-container-box-shadow);
|
|
190
205
|
}
|
|
191
206
|
|
|
192
207
|
&__filter {
|
|
@@ -207,13 +222,18 @@
|
|
|
207
222
|
position: absolute;
|
|
208
223
|
inset: 0;
|
|
209
224
|
pointer-events: none;
|
|
210
|
-
border-radius: var(--atomix-glass-radius);
|
|
225
|
+
border-radius: var(--atomix-glass-container-radius);
|
|
226
|
+
backdrop-filter: var(--atomix-glass-container-backdrop);
|
|
211
227
|
}
|
|
212
228
|
|
|
213
229
|
&__filter-shadow {
|
|
214
230
|
position: absolute;
|
|
215
231
|
inset: var(--atomix-glass-border-width);
|
|
216
232
|
pointer-events: none;
|
|
233
|
+
border-radius: var(--atomix-glass-container-radius);
|
|
234
|
+
box-shadow: var(--atomix-glass-container-shadow);
|
|
235
|
+
opacity: var(--atomix-glass-container-shadow-opacity);
|
|
236
|
+
background: var(--atomix-glass-container-bg);
|
|
217
237
|
}
|
|
218
238
|
|
|
219
239
|
&__content {
|
|
@@ -222,11 +242,16 @@
|
|
|
222
242
|
height: var(--atomix-glass-container-height);
|
|
223
243
|
border-radius: var(--atomix-glass-radius);
|
|
224
244
|
z-index: var(--_glass-z-content);
|
|
245
|
+
text-shadow: var(--atomix-glass-container-text-shadow);
|
|
225
246
|
}
|
|
226
247
|
|
|
227
248
|
// Background layers for over-light mode
|
|
228
249
|
&__background-layer {
|
|
229
|
-
position:
|
|
250
|
+
position: var(--atomix-glass-position);
|
|
251
|
+
top: var(--atomix-glass-top);
|
|
252
|
+
left: var(--atomix-glass-left);
|
|
253
|
+
width: var(--atomix-glass-width);
|
|
254
|
+
height: var(--atomix-glass-height);
|
|
230
255
|
pointer-events: none;
|
|
231
256
|
border-radius: var(--atomix-glass-radius);
|
|
232
257
|
transform: var(--atomix-glass-transform);
|
|
@@ -1,329 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react';
|
|
2
|
-
import type { OverLightConfig, OverLightObjectConfig } from '../../types/components';
|
|
3
|
-
|
|
4
|
-
// Module-level shared background detection cache using WeakMap
|
|
5
|
-
// Automatically cleaned up when elements are removed from DOM
|
|
6
|
-
interface BackgroundDetectionCacheEntry {
|
|
7
|
-
result: boolean;
|
|
8
|
-
timestamp: number;
|
|
9
|
-
config: OverLightConfig;
|
|
10
|
-
threshold: number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const backgroundDetectionCache = new WeakMap<HTMLElement, BackgroundDetectionCacheEntry>();
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Compare two OverLightConfig values for equality
|
|
17
|
-
* Handles primitives (boolean, 'auto') and objects with deep comparison
|
|
18
|
-
*/
|
|
19
|
-
export const compareOverLightConfig = (
|
|
20
|
-
config1: OverLightConfig,
|
|
21
|
-
config2: OverLightConfig
|
|
22
|
-
): boolean => {
|
|
23
|
-
// Primitive comparison for boolean and 'auto'
|
|
24
|
-
if (typeof config1 !== 'object' || config1 === null) {
|
|
25
|
-
return config1 === config2;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Both must be objects at this point
|
|
29
|
-
if (typeof config2 !== 'object' || config2 === null) {
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const obj1 = config1 as OverLightObjectConfig;
|
|
34
|
-
const obj2 = config2 as OverLightObjectConfig;
|
|
35
|
-
|
|
36
|
-
// Deep comparison of object properties
|
|
37
|
-
// Compare all defined properties (threshold, opacity, contrast, brightness, saturationBoost)
|
|
38
|
-
const props: (keyof OverLightObjectConfig)[] = [
|
|
39
|
-
'threshold',
|
|
40
|
-
'opacity',
|
|
41
|
-
'contrast',
|
|
42
|
-
'brightness',
|
|
43
|
-
'saturationBoost',
|
|
44
|
-
];
|
|
45
|
-
|
|
46
|
-
for (const prop of props) {
|
|
47
|
-
const val1 = obj1[prop];
|
|
48
|
-
const val2 = obj2[prop];
|
|
49
|
-
|
|
50
|
-
// If both are undefined, they're equal for this property
|
|
51
|
-
if (val1 === undefined && val2 === undefined) {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// If one is undefined and the other isn't, they're different
|
|
56
|
-
if (val1 === undefined || val2 === undefined) {
|
|
57
|
-
return false;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Compare numeric values (handle NaN and floating point precision)
|
|
61
|
-
if (typeof val1 === 'number' && typeof val2 === 'number') {
|
|
62
|
-
// Use Number.isNaN for proper NaN comparison
|
|
63
|
-
if (Number.isNaN(val1) && Number.isNaN(val2)) {
|
|
64
|
-
continue;
|
|
65
|
-
}
|
|
66
|
-
if (Number.isNaN(val1) || Number.isNaN(val2)) {
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
69
|
-
// Compare with small epsilon for floating point numbers
|
|
70
|
-
if (Math.abs(val1 - val2) > Number.EPSILON) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
} else if (val1 !== val2) {
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return true;
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Get cached background detection result
|
|
83
|
-
*/
|
|
84
|
-
const getCachedBackgroundDetection = (
|
|
85
|
-
parentElement: HTMLElement | null,
|
|
86
|
-
overLightConfig: OverLightConfig
|
|
87
|
-
): boolean | null => {
|
|
88
|
-
if (!parentElement) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
const cached = backgroundDetectionCache.get(parentElement);
|
|
93
|
-
if (cached && compareOverLightConfig(cached.config, overLightConfig)) {
|
|
94
|
-
// Update timestamp for LRU-like behavior (though WeakMap doesn't support LRU)
|
|
95
|
-
cached.timestamp = Date.now();
|
|
96
|
-
return cached.result;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return null;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
/**
|
|
103
|
-
* Set cached background detection result
|
|
104
|
-
*/
|
|
105
|
-
const setCachedBackgroundDetection = (
|
|
106
|
-
parentElement: HTMLElement | null,
|
|
107
|
-
overLightConfig: OverLightConfig,
|
|
108
|
-
result: boolean,
|
|
109
|
-
threshold: number
|
|
110
|
-
): void => {
|
|
111
|
-
if (!parentElement) {
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
backgroundDetectionCache.set(parentElement, {
|
|
116
|
-
result,
|
|
117
|
-
timestamp: Date.now(),
|
|
118
|
-
config: overLightConfig,
|
|
119
|
-
threshold,
|
|
120
|
-
});
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
interface UseGlassBackgroundDetectionProps {
|
|
124
|
-
glassRef: React.RefObject<HTMLDivElement>;
|
|
125
|
-
overLight: OverLightConfig;
|
|
126
|
-
debugOverLight?: boolean;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function useGlassBackgroundDetection({
|
|
130
|
-
glassRef,
|
|
131
|
-
overLight,
|
|
132
|
-
debugOverLight = false,
|
|
133
|
-
}: UseGlassBackgroundDetectionProps) {
|
|
134
|
-
const [detectedOverLight, setDetectedOverLight] = useState(false);
|
|
135
|
-
|
|
136
|
-
useEffect(() => {
|
|
137
|
-
// Only run auto-detection for 'auto' mode or object config (which uses auto-detection)
|
|
138
|
-
const shouldDetect =
|
|
139
|
-
overLight === 'auto' || (typeof overLight === 'object' && overLight !== null);
|
|
140
|
-
|
|
141
|
-
if (shouldDetect && glassRef.current) {
|
|
142
|
-
const element = glassRef.current;
|
|
143
|
-
const parentElement = element.parentElement;
|
|
144
|
-
|
|
145
|
-
// Check shared cache: skip detection if parent unchanged and config unchanged
|
|
146
|
-
const cachedResult = getCachedBackgroundDetection(parentElement, overLight);
|
|
147
|
-
if (cachedResult !== null) {
|
|
148
|
-
setDetectedOverLight(cachedResult);
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const timeoutId = setTimeout(() => {
|
|
153
|
-
try {
|
|
154
|
-
if (!element) {
|
|
155
|
-
setDetectedOverLight(false);
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// Validate window context
|
|
160
|
-
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
|
|
161
|
-
setDetectedOverLight(false);
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
let totalLuminance = 0;
|
|
166
|
-
let validSamples = 0;
|
|
167
|
-
let hasValidBackground = false;
|
|
168
|
-
|
|
169
|
-
let currentElement = element.parentElement;
|
|
170
|
-
let depth = 0;
|
|
171
|
-
const maxDepth = 20;
|
|
172
|
-
const maxSamples = 10;
|
|
173
|
-
|
|
174
|
-
// Limit traversal depth to prevent infinite loops and performance issues
|
|
175
|
-
while (currentElement && validSamples < maxSamples && depth < maxDepth) {
|
|
176
|
-
try {
|
|
177
|
-
const computedStyle = window.getComputedStyle(currentElement);
|
|
178
|
-
if (!computedStyle) {
|
|
179
|
-
currentElement = currentElement.parentElement;
|
|
180
|
-
depth++;
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
const bgColor = computedStyle.backgroundColor;
|
|
185
|
-
const bgImage = computedStyle.backgroundImage;
|
|
186
|
-
|
|
187
|
-
// Check for solid color backgrounds
|
|
188
|
-
if (
|
|
189
|
-
bgColor &&
|
|
190
|
-
bgColor !== 'rgba(0, 0, 0, 0)' &&
|
|
191
|
-
bgColor !== 'transparent' &&
|
|
192
|
-
bgColor !== 'initial' &&
|
|
193
|
-
bgColor !== 'none'
|
|
194
|
-
) {
|
|
195
|
-
const rgb = bgColor.match(/\d+/g);
|
|
196
|
-
if (rgb && rgb.length >= 3) {
|
|
197
|
-
const r = Number(rgb[0]);
|
|
198
|
-
const g = Number(rgb[1]);
|
|
199
|
-
const b = Number(rgb[2]);
|
|
200
|
-
|
|
201
|
-
// Validate RGB values are valid numbers
|
|
202
|
-
if (
|
|
203
|
-
!isNaN(r) &&
|
|
204
|
-
!isNaN(g) &&
|
|
205
|
-
!isNaN(b) &&
|
|
206
|
-
isFinite(r) &&
|
|
207
|
-
isFinite(g) &&
|
|
208
|
-
isFinite(b) &&
|
|
209
|
-
r >= 0 &&
|
|
210
|
-
r <= 255 &&
|
|
211
|
-
g >= 0 &&
|
|
212
|
-
g <= 255 &&
|
|
213
|
-
b >= 0 &&
|
|
214
|
-
b <= 255
|
|
215
|
-
) {
|
|
216
|
-
// Only consider if it's not pure black or very dark
|
|
217
|
-
if (r > 10 || g > 10 || b > 10) {
|
|
218
|
-
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
219
|
-
if (!isNaN(luminance) && isFinite(luminance)) {
|
|
220
|
-
totalLuminance += luminance;
|
|
221
|
-
validSamples++;
|
|
222
|
-
hasValidBackground = true;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Check for image backgrounds
|
|
230
|
-
if (bgImage && bgImage !== 'none' && bgImage !== 'initial') {
|
|
231
|
-
// For image backgrounds, assume medium luminance
|
|
232
|
-
totalLuminance += 0.5;
|
|
233
|
-
validSamples++;
|
|
234
|
-
hasValidBackground = true;
|
|
235
|
-
}
|
|
236
|
-
} catch (styleError) {
|
|
237
|
-
// Silently continue if getting computed style fails for this element
|
|
238
|
-
if (typeof process === 'undefined' || process.env?.NODE_ENV === 'development') {
|
|
239
|
-
console.debug('AtomixGlass: Error getting computed style for element:', styleError);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
// Move to parent element for next iteration
|
|
244
|
-
if (currentElement) {
|
|
245
|
-
currentElement = currentElement.parentElement;
|
|
246
|
-
depth++;
|
|
247
|
-
} else {
|
|
248
|
-
break; // Exit loop if currentElement becomes null
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
// More conservative detection with better error handling
|
|
253
|
-
if (hasValidBackground && validSamples > 0) {
|
|
254
|
-
const avgLuminance = totalLuminance / validSamples;
|
|
255
|
-
if (!isNaN(avgLuminance) && isFinite(avgLuminance)) {
|
|
256
|
-
let threshold = 0.7; // Conservative threshold for overlight
|
|
257
|
-
|
|
258
|
-
// If overLight is an object, use its threshold property with validation
|
|
259
|
-
if (typeof overLight === 'object' && overLight !== null) {
|
|
260
|
-
const objConfig = overLight as OverLightObjectConfig;
|
|
261
|
-
if (objConfig.threshold !== undefined) {
|
|
262
|
-
const configThreshold =
|
|
263
|
-
typeof objConfig.threshold === 'number' &&
|
|
264
|
-
!isNaN(objConfig.threshold) &&
|
|
265
|
-
isFinite(objConfig.threshold)
|
|
266
|
-
? objConfig.threshold
|
|
267
|
-
: 0.7;
|
|
268
|
-
threshold = Math.min(0.9, Math.max(0.1, configThreshold));
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
const isOverLightDetected = avgLuminance > threshold;
|
|
273
|
-
|
|
274
|
-
// Cache the result in shared cache
|
|
275
|
-
setCachedBackgroundDetection(
|
|
276
|
-
element.parentElement,
|
|
277
|
-
overLight,
|
|
278
|
-
isOverLightDetected,
|
|
279
|
-
threshold
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
setDetectedOverLight(isOverLightDetected);
|
|
283
|
-
} else {
|
|
284
|
-
// Invalid luminance calculation, default to false
|
|
285
|
-
const result = false;
|
|
286
|
-
const threshold =
|
|
287
|
-
typeof overLight === 'object' && overLight !== null
|
|
288
|
-
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
289
|
-
: 0.7;
|
|
290
|
-
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
291
|
-
setDetectedOverLight(result);
|
|
292
|
-
}
|
|
293
|
-
} else {
|
|
294
|
-
// Default to false if no valid background found
|
|
295
|
-
const result = false;
|
|
296
|
-
const threshold =
|
|
297
|
-
typeof overLight === 'object' && overLight !== null
|
|
298
|
-
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
299
|
-
: 0.7;
|
|
300
|
-
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
301
|
-
setDetectedOverLight(result);
|
|
302
|
-
}
|
|
303
|
-
} catch (error) {
|
|
304
|
-
// Enhanced error logging with context
|
|
305
|
-
if (typeof process === 'undefined' || process.env?.NODE_ENV === 'development') {
|
|
306
|
-
console.warn('AtomixGlass: Error detecting background brightness:', error);
|
|
307
|
-
}
|
|
308
|
-
const result = false;
|
|
309
|
-
if (element && element.parentElement) {
|
|
310
|
-
const threshold =
|
|
311
|
-
typeof overLight === 'object' && overLight !== null
|
|
312
|
-
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
313
|
-
: 0.7;
|
|
314
|
-
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
315
|
-
}
|
|
316
|
-
setDetectedOverLight(result);
|
|
317
|
-
}
|
|
318
|
-
}, 150);
|
|
319
|
-
|
|
320
|
-
return () => clearTimeout(timeoutId);
|
|
321
|
-
} else if (typeof overLight === 'boolean') {
|
|
322
|
-
// For boolean values, disable auto-detection
|
|
323
|
-
setDetectedOverLight(false);
|
|
324
|
-
}
|
|
325
|
-
return undefined;
|
|
326
|
-
}, [overLight, glassRef, debugOverLight]);
|
|
327
|
-
|
|
328
|
-
return { detectedOverLight };
|
|
329
|
-
}
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import React, { useEffect, useMemo, useState } from 'react';
|
|
2
|
-
import { ATOMIX_GLASS } from '../../constants/components';
|
|
3
|
-
import {
|
|
4
|
-
extractBorderRadiusFromChildren,
|
|
5
|
-
extractBorderRadiusFromDOMElement,
|
|
6
|
-
} from '../../../components/AtomixGlass/glass-utils';
|
|
7
|
-
|
|
8
|
-
const { CONSTANTS } = ATOMIX_GLASS;
|
|
9
|
-
|
|
10
|
-
interface UseGlassCornerRadiusProps {
|
|
11
|
-
contentRef: React.RefObject<HTMLDivElement>;
|
|
12
|
-
borderRadius?: number;
|
|
13
|
-
children?: React.ReactNode;
|
|
14
|
-
debugBorderRadius?: boolean;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function useGlassCornerRadius({
|
|
18
|
-
contentRef,
|
|
19
|
-
borderRadius,
|
|
20
|
-
children,
|
|
21
|
-
debugBorderRadius = false,
|
|
22
|
-
}: UseGlassCornerRadiusProps) {
|
|
23
|
-
const [dynamicBorderRadius, setDynamicCornerRadius] = useState<number>(
|
|
24
|
-
CONSTANTS.DEFAULT_CORNER_RADIUS
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
const effectiveBorderRadius = useMemo(() => {
|
|
28
|
-
if (borderRadius !== undefined) {
|
|
29
|
-
const result = Math.max(0, borderRadius);
|
|
30
|
-
return result;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const result = Math.max(0, dynamicBorderRadius);
|
|
34
|
-
return result;
|
|
35
|
-
}, [borderRadius, dynamicBorderRadius]);
|
|
36
|
-
|
|
37
|
-
// Extract border-radius from children
|
|
38
|
-
useEffect(() => {
|
|
39
|
-
const extractRadius = () => {
|
|
40
|
-
try {
|
|
41
|
-
let extractedRadius: number | null = null;
|
|
42
|
-
|
|
43
|
-
if (contentRef.current) {
|
|
44
|
-
const firstChild = contentRef.current.firstElementChild as HTMLElement;
|
|
45
|
-
if (firstChild) {
|
|
46
|
-
const domRadius = extractBorderRadiusFromDOMElement(firstChild);
|
|
47
|
-
if (domRadius !== null && domRadius > 0) {
|
|
48
|
-
extractedRadius = domRadius;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
if (extractedRadius === null) {
|
|
54
|
-
const childRadius = extractBorderRadiusFromChildren(children);
|
|
55
|
-
if (childRadius > 0 && childRadius !== CONSTANTS.DEFAULT_CORNER_RADIUS) {
|
|
56
|
-
extractedRadius = childRadius;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (extractedRadius !== null && extractedRadius > 0) {
|
|
61
|
-
setDynamicCornerRadius(extractedRadius);
|
|
62
|
-
}
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (
|
|
65
|
-
(typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') &&
|
|
66
|
-
debugBorderRadius
|
|
67
|
-
) {
|
|
68
|
-
console.error('[AtomixGlass] Error extracting corner radius:', error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
extractRadius();
|
|
74
|
-
const timeoutId = setTimeout(extractRadius, 100);
|
|
75
|
-
return () => clearTimeout(timeoutId);
|
|
76
|
-
}, [children, debugBorderRadius, contentRef]);
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
dynamicBorderRadius,
|
|
80
|
-
effectiveBorderRadius,
|
|
81
|
-
};
|
|
82
|
-
}
|