@shohojdhara/atomix 0.6.2 → 0.6.4
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/README.md +510 -106
- package/dist/atomix.css +26 -22
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +5 -5
- package/dist/atomix.min.css.map +1 -1
- package/dist/atomix.umd.js +1 -1
- package/dist/atomix.umd.js.map +1 -1
- package/dist/atomix.umd.min.js +1 -1
- package/dist/charts.d.ts +2 -2
- package/dist/charts.js +251 -131
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +5 -39
- package/dist/core.js +254 -137
- package/dist/core.js.map +1 -1
- package/dist/forms.d.ts +2 -1
- package/dist/forms.js +342 -177
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js +254 -135
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +141 -159
- package/dist/index.esm.js +348 -195
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +348 -195
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/theme.d.ts +14 -6
- package/dist/theme.js +2 -9
- package/dist/theme.js.map +1 -1
- package/package.json +26 -26
- package/src/components/AtomixGlass/AtomixGlass.tsx +1 -1
- package/src/components/AtomixGlass/AtomixGlassContainer.tsx +8 -1
- package/src/components/AtomixGlass/glass-utils.ts +29 -0
- package/src/components/AtomixGlass/stories/Playground.stories.tsx +32 -1
- package/src/components/Button/Button.stories.tsx +1 -1
- package/src/components/Button/Button.tsx +6 -5
- package/src/components/Card/Card.tsx +2 -2
- package/src/components/Dropdown/Dropdown.tsx +1 -0
- package/src/components/EdgePanel/EdgePanel.tsx +1 -3
- package/src/components/Form/Select.test.tsx +75 -0
- package/src/components/Form/Select.tsx +348 -252
- package/src/components/Form/SelectOption.tsx +16 -10
- package/src/components/index.ts +1 -1
- package/src/layouts/CssGrid/index.ts +1 -0
- package/src/lib/composables/useAtomixGlass.ts +238 -138
- package/src/lib/composables/useAtomixGlassStyles.ts +201 -149
- package/src/lib/constants/components.ts +50 -35
- package/src/lib/theme/config/configLoader.ts +1 -1
- package/src/lib/theme/test/testTheme.ts +2 -2
- package/src/lib/theme/utils/themeUtils.ts +98 -110
- package/src/lib/types/components.ts +21 -63
- package/src/styles/01-settings/_settings.atomix-glass.scss +5 -5
- package/src/styles/01-settings/_settings.spacing.scss +6 -1
- package/src/styles/03-generic/_generic.reset.scss +1 -1
- package/src/styles/06-components/_components.atomix-glass.scss +20 -29
- package/src/styles/06-components/_components.data-table.scss +5 -4
- package/src/styles/06-components/_components.dynamic-background.scss +9 -8
- package/src/styles/06-components/_components.footer.scss +8 -7
- package/src/styles/06-components/_components.hero.scss +2 -2
- package/src/styles/06-components/_components.messages.scss +16 -16
- package/src/styles/06-components/_components.select.scss +15 -2
- package/src/styles/06-components/_components.upload.scss +3 -3
- package/CHANGELOG.md +0 -165
|
@@ -8,12 +8,15 @@ export interface SelectContextType {
|
|
|
8
8
|
unregisterOption: (value: string) => void;
|
|
9
9
|
selectedValue?: string | string[];
|
|
10
10
|
onSelect: (value: string, label: string) => void;
|
|
11
|
+
focusedValue?: string;
|
|
12
|
+
id?: string;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export const SelectContext = createContext<SelectContextType | null>(null);
|
|
14
16
|
|
|
15
17
|
export interface SelectOptionProps {
|
|
16
18
|
value: string;
|
|
19
|
+
label?: string;
|
|
17
20
|
children?: ReactNode;
|
|
18
21
|
disabled?: boolean;
|
|
19
22
|
className?: string;
|
|
@@ -21,45 +24,48 @@ export interface SelectOptionProps {
|
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
export const SelectOption: React.FC<SelectOptionProps> = memo(
|
|
24
|
-
({ value, children, disabled = false, className = '', style }) => {
|
|
27
|
+
({ value, label, children, disabled = false, className = '', style }) => {
|
|
25
28
|
const context = useContext(SelectContext);
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
// For simplicity, we use children as label for registration if it's a string.
|
|
29
|
-
const label = typeof children === 'string' ? children : value;
|
|
30
|
+
const displayLabel = label || (typeof children === 'string' ? children : value);
|
|
30
31
|
|
|
31
32
|
useEffect(() => {
|
|
32
33
|
if (context) {
|
|
33
|
-
context.registerOption({ value, label, disabled });
|
|
34
|
+
context.registerOption({ value, label: displayLabel, disabled });
|
|
34
35
|
return () => {
|
|
35
36
|
context.unregisterOption(value);
|
|
36
37
|
};
|
|
37
38
|
}
|
|
38
39
|
return undefined;
|
|
39
|
-
}, [context, value,
|
|
40
|
+
}, [context, value, displayLabel, disabled]);
|
|
40
41
|
|
|
41
42
|
if (!context) {
|
|
42
43
|
console.warn('SelectOption must be used within a Select component');
|
|
43
44
|
return null;
|
|
44
45
|
}
|
|
45
46
|
|
|
46
|
-
const { selectedValue, onSelect } = context;
|
|
47
|
+
const { selectedValue, onSelect, focusedValue, id } = context;
|
|
47
48
|
|
|
48
49
|
const isSelected = Array.isArray(selectedValue)
|
|
49
50
|
? selectedValue.includes(value)
|
|
50
51
|
: selectedValue === value;
|
|
51
52
|
|
|
53
|
+
const isFocused = focusedValue === value;
|
|
54
|
+
|
|
52
55
|
const handleClick = (e: React.MouseEvent) => {
|
|
53
56
|
e.preventDefault();
|
|
54
57
|
e.stopPropagation();
|
|
55
58
|
if (!disabled) {
|
|
56
|
-
onSelect(value,
|
|
59
|
+
onSelect(value, displayLabel);
|
|
57
60
|
}
|
|
58
61
|
};
|
|
59
62
|
|
|
60
63
|
return (
|
|
61
64
|
<li
|
|
62
|
-
|
|
65
|
+
id={id ? `${id}-opt-${value}` : undefined}
|
|
66
|
+
className={`${SELECT.CLASSES.SELECT_ITEM} ${isFocused ? 'is-focused' : ''} ${
|
|
67
|
+
isSelected ? 'is-selected' : ''
|
|
68
|
+
} ${className}`.trim()}
|
|
63
69
|
data-value={value}
|
|
64
70
|
onClick={handleClick}
|
|
65
71
|
style={style}
|
|
@@ -76,7 +82,7 @@ export const SelectOption: React.FC<SelectOptionProps> = memo(
|
|
|
76
82
|
disabled={disabled}
|
|
77
83
|
tabIndex={-1}
|
|
78
84
|
/>
|
|
79
|
-
<div className="c-select__item-label">{children}</div>
|
|
85
|
+
<div className="c-select__item-label">{children || displayLabel}</div>
|
|
80
86
|
</label>
|
|
81
87
|
</li>
|
|
82
88
|
);
|
package/src/components/index.ts
CHANGED
|
@@ -89,7 +89,7 @@ export { default as Radio, type RadioProps } from './Form/Radio';
|
|
|
89
89
|
export { default as Select, type SelectProps } from './Form/Select';
|
|
90
90
|
export { default as Textarea, type TextareaProps } from './Form/Textarea';
|
|
91
91
|
export { default as Hero, type HeroProps } from './Hero/Hero';
|
|
92
|
-
export { default as Icon
|
|
92
|
+
export { default as Icon } from './Icon/Icon';
|
|
93
93
|
export { default as List, type ListProps } from './List/List';
|
|
94
94
|
// List sub-components
|
|
95
95
|
export { ListGroup } from './List/ListGroup';
|
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useCallback,
|
|
3
|
-
useEffect,
|
|
4
|
-
useMemo,
|
|
5
|
-
useRef,
|
|
6
|
-
useState,
|
|
7
|
-
} from 'react';
|
|
1
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
8
2
|
import type {
|
|
9
3
|
AtomixGlassProps,
|
|
10
4
|
GlassSize,
|
|
@@ -21,6 +15,9 @@ import {
|
|
|
21
15
|
extractBorderRadiusFromDOMElement,
|
|
22
16
|
validateGlassSize,
|
|
23
17
|
lerp,
|
|
18
|
+
calculateSpring,
|
|
19
|
+
calculateVelocity,
|
|
20
|
+
smoothstep,
|
|
24
21
|
} from '../../components/AtomixGlass/glass-utils';
|
|
25
22
|
import { updateAtomixGlassStyles } from './useAtomixGlassStyles';
|
|
26
23
|
// Phase 1: Time-Based Animation System
|
|
@@ -48,10 +45,7 @@ const backgroundDetectionCache = new WeakMap<HTMLElement, BackgroundDetectionCac
|
|
|
48
45
|
* Compare two OverLightConfig values for equality
|
|
49
46
|
* Handles primitives (boolean, 'auto') and objects with deep comparison
|
|
50
47
|
*/
|
|
51
|
-
const compareOverLightConfig = (
|
|
52
|
-
config1: OverLightConfig,
|
|
53
|
-
config2: OverLightConfig
|
|
54
|
-
): boolean => {
|
|
48
|
+
const compareOverLightConfig = (config1: OverLightConfig, config2: OverLightConfig): boolean => {
|
|
55
49
|
// Primitive comparison for boolean and 'auto'
|
|
56
50
|
if (typeof config1 !== 'object' || config1 === null) {
|
|
57
51
|
return config1 === config2;
|
|
@@ -267,6 +261,15 @@ export function useAtomixGlass({
|
|
|
267
261
|
const [dynamicBorderRadius, setDynamicCornerRadius] = useState<number>(
|
|
268
262
|
CONSTANTS.DEFAULT_CORNER_RADIUS
|
|
269
263
|
);
|
|
264
|
+
|
|
265
|
+
// ── Physics state refs ────────────────────────────────────────────────
|
|
266
|
+
const elasticTranslationRef = useRef<MousePosition>({ x: 0, y: 0 });
|
|
267
|
+
const elasticVelocityRef = useRef<MousePosition>({ x: 0, y: 0 });
|
|
268
|
+
const directionalScaleRef = useRef<{ x: number; y: number }>({ x: 1, y: 1 });
|
|
269
|
+
const scaleVelocityRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
|
270
|
+
const lastMouseTimeRef = useRef<number>(0);
|
|
271
|
+
const mouseVelocityRef = useRef<MousePosition>({ x: 0, y: 0 });
|
|
272
|
+
|
|
270
273
|
const [userPrefersReducedMotion, setUserPrefersReducedMotion] = useState(false);
|
|
271
274
|
const [userPrefersHighContrast, setUserPrefersHighContrast] = useState(false);
|
|
272
275
|
const [detectedOverLight, setDetectedOverLight] = useState(false);
|
|
@@ -287,7 +290,7 @@ export function useAtomixGlass({
|
|
|
287
290
|
const fbmConfig = useMemo(() => {
|
|
288
291
|
// If quality preset is provided, use it as base
|
|
289
292
|
const preset = getFBMConfigForQuality(distortionQuality);
|
|
290
|
-
|
|
293
|
+
|
|
291
294
|
// Override with custom values if provided
|
|
292
295
|
return {
|
|
293
296
|
octaves: distortionOctaves ?? preset.octaves,
|
|
@@ -373,7 +376,7 @@ export function useAtomixGlass({
|
|
|
373
376
|
}
|
|
374
377
|
|
|
375
378
|
const time = shaderTimeRef.current;
|
|
376
|
-
|
|
379
|
+
|
|
377
380
|
// Apply liquid glass distortion with time
|
|
378
381
|
return liquidGlassWithTime(uv, time, fbmConfig);
|
|
379
382
|
},
|
|
@@ -390,13 +393,12 @@ export function useAtomixGlass({
|
|
|
390
393
|
return result;
|
|
391
394
|
}, [borderRadius, dynamicBorderRadius]);
|
|
392
395
|
|
|
393
|
-
const { glassSize } = useGlassSize({
|
|
394
|
-
glassRef,
|
|
395
|
-
effectiveBorderRadius,
|
|
396
|
-
cachedRectRef
|
|
396
|
+
const { glassSize } = useGlassSize({
|
|
397
|
+
glassRef,
|
|
398
|
+
effectiveBorderRadius,
|
|
399
|
+
cachedRectRef,
|
|
397
400
|
});
|
|
398
401
|
|
|
399
|
-
|
|
400
402
|
const effectiveHighContrast = useMemo(
|
|
401
403
|
() => highContrast || userPrefersHighContrast,
|
|
402
404
|
[highContrast, userPrefersHighContrast]
|
|
@@ -438,7 +440,10 @@ export function useAtomixGlass({
|
|
|
438
440
|
setDynamicCornerRadius(extractedRadius);
|
|
439
441
|
}
|
|
440
442
|
} catch (error) {
|
|
441
|
-
if (
|
|
443
|
+
if (
|
|
444
|
+
(typeof process === 'undefined' || process.env?.NODE_ENV !== 'production') &&
|
|
445
|
+
debugBorderRadius
|
|
446
|
+
) {
|
|
442
447
|
console.error('[AtomixGlass] Error extracting corner radius:', error);
|
|
443
448
|
}
|
|
444
449
|
}
|
|
@@ -481,7 +486,8 @@ export function useAtomixGlass({
|
|
|
481
486
|
// Background detection for overLight auto-detect
|
|
482
487
|
useEffect(() => {
|
|
483
488
|
// Only run auto-detection for 'auto' mode or object config (which uses auto-detection)
|
|
484
|
-
const shouldDetect =
|
|
489
|
+
const shouldDetect =
|
|
490
|
+
overLight === 'auto' || (typeof overLight === 'object' && overLight !== null);
|
|
485
491
|
|
|
486
492
|
if (shouldDetect && glassRef.current) {
|
|
487
493
|
const element = glassRef.current;
|
|
@@ -530,7 +536,13 @@ export function useAtomixGlass({
|
|
|
530
536
|
const bgImage = computedStyle.backgroundImage;
|
|
531
537
|
|
|
532
538
|
// Check for solid color backgrounds
|
|
533
|
-
if (
|
|
539
|
+
if (
|
|
540
|
+
bgColor &&
|
|
541
|
+
bgColor !== 'rgba(0, 0, 0, 0)' &&
|
|
542
|
+
bgColor !== 'transparent' &&
|
|
543
|
+
bgColor !== 'initial' &&
|
|
544
|
+
bgColor !== 'none'
|
|
545
|
+
) {
|
|
534
546
|
const rgb = bgColor.match(/\d+/g);
|
|
535
547
|
if (rgb && rgb.length >= 3) {
|
|
536
548
|
const r = Number(rgb[0]);
|
|
@@ -556,7 +568,7 @@ export function useAtomixGlass({
|
|
|
556
568
|
hasValidBackground = true;
|
|
557
569
|
}
|
|
558
570
|
} catch (styleError) {
|
|
559
|
-
|
|
571
|
+
// Silently continue
|
|
560
572
|
}
|
|
561
573
|
|
|
562
574
|
if (currentElement) {
|
|
@@ -575,27 +587,37 @@ export function useAtomixGlass({
|
|
|
575
587
|
if (typeof overLight === 'object' && overLight !== null) {
|
|
576
588
|
const objConfig = overLight as OverLightObjectConfig;
|
|
577
589
|
if (objConfig.threshold !== undefined) {
|
|
578
|
-
|
|
590
|
+
const configThreshold =
|
|
591
|
+
typeof objConfig.threshold === 'number' && !isNaN(objConfig.threshold)
|
|
592
|
+
? objConfig.threshold
|
|
593
|
+
: 0.7;
|
|
579
594
|
threshold = Math.min(0.9, Math.max(0.1, configThreshold));
|
|
580
595
|
}
|
|
581
596
|
}
|
|
582
597
|
|
|
583
598
|
const isOverLightDetected = avgLuminance > threshold;
|
|
584
|
-
setCachedBackgroundDetection(
|
|
599
|
+
setCachedBackgroundDetection(
|
|
600
|
+
element.parentElement,
|
|
601
|
+
overLight,
|
|
602
|
+
isOverLightDetected,
|
|
603
|
+
threshold
|
|
604
|
+
);
|
|
585
605
|
setDetectedOverLight(isOverLightDetected);
|
|
586
606
|
} else {
|
|
587
607
|
const result = false;
|
|
588
|
-
const threshold =
|
|
589
|
-
|
|
590
|
-
|
|
608
|
+
const threshold =
|
|
609
|
+
typeof overLight === 'object' && overLight !== null
|
|
610
|
+
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
611
|
+
: 0.7;
|
|
591
612
|
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
592
613
|
setDetectedOverLight(result);
|
|
593
614
|
}
|
|
594
615
|
} else {
|
|
595
616
|
const result = false;
|
|
596
|
-
const threshold =
|
|
597
|
-
|
|
598
|
-
|
|
617
|
+
const threshold =
|
|
618
|
+
typeof overLight === 'object' && overLight !== null
|
|
619
|
+
? (overLight as OverLightObjectConfig).threshold || 0.7
|
|
620
|
+
: 0.7;
|
|
599
621
|
setCachedBackgroundDetection(element.parentElement, overLight, result, threshold);
|
|
600
622
|
setDetectedOverLight(result);
|
|
601
623
|
}
|
|
@@ -751,19 +773,12 @@ export function useAtomixGlass({
|
|
|
751
773
|
}
|
|
752
774
|
|
|
753
775
|
return baseConfig;
|
|
754
|
-
}, [
|
|
755
|
-
overLight,
|
|
756
|
-
getEffectiveOverLight,
|
|
757
|
-
isHovered,
|
|
758
|
-
isActive,
|
|
759
|
-
validateConfigValue,
|
|
760
|
-
debugOverLight,
|
|
761
|
-
]);
|
|
776
|
+
}, [overLight, getEffectiveOverLight, isHovered, isActive, validateConfigValue, debugOverLight]);
|
|
762
777
|
|
|
763
778
|
// Transform calculation (static base for React render)
|
|
764
779
|
// Mouse interactions are purely handled by imperative updates in the RAF lerp loop to prevent re-renders
|
|
765
780
|
const transformStyle = useMemo(() => {
|
|
766
|
-
return effectiveWithoutEffects || (isActive && Boolean(onClick)) ? 'scale(0.
|
|
781
|
+
return effectiveWithoutEffects || (isActive && Boolean(onClick)) ? 'scale(0.99)' : 'scale(1)';
|
|
767
782
|
}, [effectiveWithoutEffects, isActive, onClick]);
|
|
768
783
|
|
|
769
784
|
// Mouse tracking
|
|
@@ -772,7 +787,6 @@ export function useAtomixGlass({
|
|
|
772
787
|
// Derived values for imperative updates (we can use memoized ones or re-calculate)
|
|
773
788
|
// Since updateAtomixGlassStyles is called imperatively, we pass current refs and state
|
|
774
789
|
|
|
775
|
-
|
|
776
790
|
// Handle mouse position updates
|
|
777
791
|
// ── Raw mouse handler — writes to TARGET refs only ──────────────────
|
|
778
792
|
// The lerp loop (below) reads the targets and incrementally
|
|
@@ -806,82 +820,166 @@ export function useAtomixGlass({
|
|
|
806
820
|
|
|
807
821
|
const cur = internalMouseOffsetRef.current;
|
|
808
822
|
const tgt = targetMouseOffsetRef.current;
|
|
809
|
-
const dx = tgt.x - cur.x;
|
|
810
|
-
const dy = tgt.y - cur.y;
|
|
811
|
-
|
|
812
|
-
// If we're close enough, snap and park
|
|
813
|
-
if (Math.abs(dx) < EPSILON && Math.abs(dy) < EPSILON) {
|
|
814
|
-
internalMouseOffsetRef.current = { ...tgt };
|
|
815
|
-
internalGlobalMousePositionRef.current = { ...targetGlobalMousePositionRef.current };
|
|
816
|
-
|
|
817
|
-
// Final update and stop
|
|
818
|
-
updateAtomixGlassStyles(
|
|
819
|
-
wrapperRef?.current || null,
|
|
820
|
-
glassRef.current,
|
|
821
|
-
{
|
|
822
|
-
mouseOffset: internalMouseOffsetRef.current,
|
|
823
|
-
globalMousePosition: internalGlobalMousePositionRef.current,
|
|
824
|
-
glassSize,
|
|
825
|
-
isHovered,
|
|
826
|
-
isActive,
|
|
827
|
-
isOverLight: overLightConfig.isOverLight,
|
|
828
|
-
baseOverLightConfig: overLightConfig,
|
|
829
|
-
effectiveBorderRadius,
|
|
830
|
-
effectiveWithoutEffects,
|
|
831
|
-
effectiveReducedMotion,
|
|
832
|
-
elasticity,
|
|
833
|
-
directionalScale: isActive && Boolean(onClick) ? 'scale(0.96)' : 'scale(1)',
|
|
834
|
-
onClick,
|
|
835
|
-
withLiquidBlur,
|
|
836
|
-
blurAmount,
|
|
837
|
-
saturation,
|
|
838
|
-
padding,
|
|
839
|
-
isFixedOrSticky,
|
|
840
|
-
}
|
|
841
|
-
);
|
|
842
|
-
|
|
843
|
-
stopLerpLoop();
|
|
844
|
-
return;
|
|
845
|
-
}
|
|
846
823
|
|
|
847
|
-
//
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
824
|
+
// Calculate spring-based mouse offset (keeps the liquid tracking feel)
|
|
825
|
+
const springX = calculateSpring(
|
|
826
|
+
cur.x,
|
|
827
|
+
tgt.x,
|
|
828
|
+
mouseVelocityRef.current.x,
|
|
829
|
+
CONSTANTS.LERP_FACTOR,
|
|
830
|
+
CONSTANTS.ELASTICITY_DAMPING
|
|
831
|
+
);
|
|
832
|
+
const springY = calculateSpring(
|
|
833
|
+
cur.y,
|
|
834
|
+
tgt.y,
|
|
835
|
+
mouseVelocityRef.current.y,
|
|
836
|
+
CONSTANTS.LERP_FACTOR,
|
|
837
|
+
CONSTANTS.ELASTICITY_DAMPING
|
|
838
|
+
);
|
|
839
|
+
|
|
840
|
+
internalMouseOffsetRef.current = { x: springX.value, y: springY.value };
|
|
841
|
+
mouseVelocityRef.current = { x: springX.velocity, y: springY.velocity };
|
|
842
|
+
|
|
853
843
|
const curG = internalGlobalMousePositionRef.current;
|
|
854
844
|
const tgtG = targetGlobalMousePositionRef.current;
|
|
855
845
|
internalGlobalMousePositionRef.current = {
|
|
856
|
-
x: lerp(curG.x, tgtG.x,
|
|
857
|
-
y: lerp(curG.y, tgtG.y,
|
|
846
|
+
x: lerp(curG.x, tgtG.x, CONSTANTS.LERP_FACTOR),
|
|
847
|
+
y: lerp(curG.y, tgtG.y, CONSTANTS.LERP_FACTOR),
|
|
858
848
|
};
|
|
859
849
|
|
|
860
|
-
//
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
850
|
+
// ── Calculate Elastic Physics ─────────────────────────────────────
|
|
851
|
+
let targetElasticTranslation = { x: 0, y: 0 };
|
|
852
|
+
let targetScale = { x: 1, y: 1 };
|
|
853
|
+
|
|
854
|
+
if (!effectiveWithoutEffects && glassRef.current) {
|
|
855
|
+
const rect = cachedRectRef.current || glassRef.current.getBoundingClientRect();
|
|
856
|
+
const center = calculateElementCenter(rect);
|
|
857
|
+
const globalPos = internalGlobalMousePositionRef.current;
|
|
858
|
+
|
|
859
|
+
if (globalPos.x && globalPos.y) {
|
|
860
|
+
const deltaX = globalPos.x - center.x;
|
|
861
|
+
const deltaY = globalPos.y - center.y;
|
|
862
|
+
const edgeDistanceX = Math.max(0, Math.abs(deltaX) - rect.width / 2);
|
|
863
|
+
const edgeDistanceY = Math.max(0, Math.abs(deltaY) - rect.height / 2);
|
|
864
|
+
const edgeDistance = Math.sqrt(
|
|
865
|
+
edgeDistanceX * edgeDistanceX + edgeDistanceY * edgeDistanceY
|
|
866
|
+
);
|
|
867
|
+
|
|
868
|
+
const activationZone = CONSTANTS.ACTIVATION_ZONE;
|
|
869
|
+
const rawT = edgeDistance > activationZone ? 0 : 1 - edgeDistance / activationZone;
|
|
870
|
+
const fadeInFactor = smoothstep(rawT);
|
|
871
|
+
|
|
872
|
+
targetElasticTranslation = {
|
|
873
|
+
x: deltaX * elasticity * CONSTANTS.ELASTICITY_TRANSLATION_FACTOR * fadeInFactor,
|
|
874
|
+
y: deltaY * elasticity * CONSTANTS.ELASTICITY_TRANSLATION_FACTOR * fadeInFactor,
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
// Scale stretch logic (liquid surface tension)
|
|
878
|
+
if (edgeDistance <= activationZone) {
|
|
879
|
+
const centerDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
880
|
+
if (centerDistance > 0) {
|
|
881
|
+
const nx = deltaX / centerDistance;
|
|
882
|
+
const ny = deltaY / centerDistance;
|
|
883
|
+
const stretchIntensity = Math.min(centerDistance / 350, 1) * elasticity * rawT;
|
|
884
|
+
|
|
885
|
+
// Liquid magnification (lens effect)
|
|
886
|
+
const mag = 1 + stretchIntensity * 0.06;
|
|
887
|
+
|
|
888
|
+
targetScale = {
|
|
889
|
+
x: mag + Math.abs(nx) * stretchIntensity * CONSTANTS.ELASTICITY_STRETCH_RATIO,
|
|
890
|
+
y: mag + Math.abs(ny) * stretchIntensity * CONSTANTS.ELASTICITY_STRETCH_RATIO,
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
// Maintain liquid volume by compressing the perpendicular axis
|
|
894
|
+
targetScale.x -= Math.abs(ny) * stretchIntensity * 0.15;
|
|
895
|
+
targetScale.y -= Math.abs(nx) * stretchIntensity * 0.15;
|
|
896
|
+
}
|
|
897
|
+
}
|
|
883
898
|
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
// Integrate Elastic Translation Spring
|
|
902
|
+
const springTX = calculateSpring(
|
|
903
|
+
elasticTranslationRef.current.x,
|
|
904
|
+
targetElasticTranslation.x,
|
|
905
|
+
elasticVelocityRef.current.x,
|
|
906
|
+
CONSTANTS.ELASTICITY_STIFFNESS,
|
|
907
|
+
CONSTANTS.ELASTICITY_DAMPING
|
|
884
908
|
);
|
|
909
|
+
const springTY = calculateSpring(
|
|
910
|
+
elasticTranslationRef.current.y,
|
|
911
|
+
targetElasticTranslation.y,
|
|
912
|
+
elasticVelocityRef.current.y,
|
|
913
|
+
CONSTANTS.ELASTICITY_STIFFNESS,
|
|
914
|
+
CONSTANTS.ELASTICITY_DAMPING
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
elasticTranslationRef.current = { x: springTX.value, y: springTY.value };
|
|
918
|
+
elasticVelocityRef.current = { x: springTX.velocity, y: springTY.velocity };
|
|
919
|
+
|
|
920
|
+
// Integrate Scale Spring
|
|
921
|
+
const springSX = calculateSpring(
|
|
922
|
+
directionalScaleRef.current.x,
|
|
923
|
+
targetScale.x,
|
|
924
|
+
scaleVelocityRef.current.x,
|
|
925
|
+
CONSTANTS.ELASTICITY_STIFFNESS,
|
|
926
|
+
CONSTANTS.ELASTICITY_DAMPING
|
|
927
|
+
);
|
|
928
|
+
const springSY = calculateSpring(
|
|
929
|
+
directionalScaleRef.current.y,
|
|
930
|
+
targetScale.y,
|
|
931
|
+
scaleVelocityRef.current.y,
|
|
932
|
+
CONSTANTS.ELASTICITY_STIFFNESS,
|
|
933
|
+
CONSTANTS.ELASTICITY_DAMPING
|
|
934
|
+
);
|
|
935
|
+
|
|
936
|
+
directionalScaleRef.current = { x: springSX.value, y: springSY.value };
|
|
937
|
+
scaleVelocityRef.current = { x: springSX.velocity, y: springSY.velocity };
|
|
938
|
+
|
|
939
|
+
// Imperative style update
|
|
940
|
+
updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
|
|
941
|
+
mouseOffset: internalMouseOffsetRef.current,
|
|
942
|
+
globalMousePosition: internalGlobalMousePositionRef.current,
|
|
943
|
+
elasticTranslation: elasticTranslationRef.current,
|
|
944
|
+
elasticVelocity: elasticVelocityRef.current,
|
|
945
|
+
mouseVelocity: mouseVelocityRef.current,
|
|
946
|
+
directionalScale: directionalScaleRef.current,
|
|
947
|
+
glassSize,
|
|
948
|
+
isHovered,
|
|
949
|
+
isActive,
|
|
950
|
+
isOverLight: overLightConfig.isOverLight,
|
|
951
|
+
baseOverLightConfig: overLightConfig,
|
|
952
|
+
effectiveBorderRadius,
|
|
953
|
+
effectiveWithoutEffects,
|
|
954
|
+
effectiveReducedMotion,
|
|
955
|
+
elasticity,
|
|
956
|
+
scaleBase: isActive && Boolean(onClick) ? 0.99 : 1,
|
|
957
|
+
onClick,
|
|
958
|
+
withLiquidBlur,
|
|
959
|
+
blurAmount,
|
|
960
|
+
saturation,
|
|
961
|
+
padding,
|
|
962
|
+
isFixedOrSticky,
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
// ── Stop check ──────────────────────────────────────────────────
|
|
966
|
+
const VEL_EPS = 0.001;
|
|
967
|
+
const POS_EPS = 0.001;
|
|
968
|
+
|
|
969
|
+
const isAtRest =
|
|
970
|
+
Math.abs(mouseVelocityRef.current.x) < VEL_EPS &&
|
|
971
|
+
Math.abs(mouseVelocityRef.current.y) < VEL_EPS &&
|
|
972
|
+
Math.abs(elasticVelocityRef.current.x) < VEL_EPS &&
|
|
973
|
+
Math.abs(elasticVelocityRef.current.y) < VEL_EPS &&
|
|
974
|
+
Math.abs(scaleVelocityRef.current.x) < VEL_EPS &&
|
|
975
|
+
Math.abs(scaleVelocityRef.current.y) < VEL_EPS &&
|
|
976
|
+
Math.abs(internalMouseOffsetRef.current.x - targetMouseOffsetRef.current.x) < POS_EPS &&
|
|
977
|
+
Math.abs(internalMouseOffsetRef.current.y - targetMouseOffsetRef.current.y) < POS_EPS;
|
|
978
|
+
|
|
979
|
+
if (isAtRest) {
|
|
980
|
+
stopLerpLoop();
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
885
983
|
|
|
886
984
|
lerpRafRef.current = requestAnimationFrame(tick);
|
|
887
985
|
};
|
|
@@ -967,8 +1065,12 @@ export function useAtomixGlass({
|
|
|
967
1065
|
return undefined;
|
|
968
1066
|
}
|
|
969
1067
|
|
|
970
|
-
const unsubscribe = globalMouseTracker.subscribe(
|
|
971
|
-
|
|
1068
|
+
const unsubscribe = globalMouseTracker.subscribe(
|
|
1069
|
+
handleGlobalMousePosition,
|
|
1070
|
+
glassRef.current || undefined,
|
|
1071
|
+
300
|
|
1072
|
+
); // 300px max distance for full effect
|
|
1073
|
+
|
|
972
1074
|
// Initial start
|
|
973
1075
|
startLerpLoop();
|
|
974
1076
|
|
|
@@ -1017,29 +1119,29 @@ export function useAtomixGlass({
|
|
|
1017
1119
|
|
|
1018
1120
|
// Also call updateStyles on other state changes (hover, active, etc)
|
|
1019
1121
|
useEffect(() => {
|
|
1020
|
-
updateAtomixGlassStyles(
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1122
|
+
updateAtomixGlassStyles(wrapperRef?.current || null, glassRef.current, {
|
|
1123
|
+
mouseOffset: externalMouseOffset || internalMouseOffsetRef.current,
|
|
1124
|
+
globalMousePosition: externalGlobalMousePosition || internalGlobalMousePositionRef.current,
|
|
1125
|
+
elasticTranslation: elasticTranslationRef.current,
|
|
1126
|
+
elasticVelocity: elasticVelocityRef.current,
|
|
1127
|
+
mouseVelocity: mouseVelocityRef.current,
|
|
1128
|
+
directionalScale: directionalScaleRef.current,
|
|
1129
|
+
scaleBase: isActive && Boolean(onClick) ? 0.96 : 1,
|
|
1130
|
+
glassSize,
|
|
1131
|
+
isHovered,
|
|
1132
|
+
isActive,
|
|
1133
|
+
isOverLight: overLightConfig.isOverLight,
|
|
1134
|
+
baseOverLightConfig: overLightConfig,
|
|
1135
|
+
effectiveBorderRadius,
|
|
1136
|
+
effectiveWithoutEffects,
|
|
1137
|
+
effectiveReducedMotion,
|
|
1138
|
+
elasticity,
|
|
1139
|
+
onClick,
|
|
1140
|
+
withLiquidBlur,
|
|
1141
|
+
blurAmount,
|
|
1142
|
+
saturation,
|
|
1143
|
+
padding,
|
|
1144
|
+
});
|
|
1043
1145
|
}, [
|
|
1044
1146
|
isHovered,
|
|
1045
1147
|
isActive,
|
|
@@ -1057,7 +1159,7 @@ export function useAtomixGlass({
|
|
|
1057
1159
|
blurAmount,
|
|
1058
1160
|
saturation,
|
|
1059
1161
|
padding,
|
|
1060
|
-
onClick
|
|
1162
|
+
onClick,
|
|
1061
1163
|
]);
|
|
1062
1164
|
|
|
1063
1165
|
// Event handlers
|
|
@@ -1066,8 +1168,6 @@ export function useAtomixGlass({
|
|
|
1066
1168
|
const handleMouseDown = useCallback(() => setIsActive(true), []);
|
|
1067
1169
|
const handleMouseUp = useCallback(() => setIsActive(false), []);
|
|
1068
1170
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
1171
|
const handleKeyDown = useCallback(
|
|
1072
1172
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
1073
1173
|
if (onClick && (e.key === 'Enter' || e.key === ' ')) {
|
|
@@ -1089,7 +1189,7 @@ export function useAtomixGlass({
|
|
|
1089
1189
|
effectiveWithoutEffects,
|
|
1090
1190
|
detectedOverLight,
|
|
1091
1191
|
globalMousePosition, // This is now static (refs or props) unless prop changes
|
|
1092
|
-
mouseOffset,
|
|
1192
|
+
mouseOffset, // This is now static (refs or props) unless prop changes
|
|
1093
1193
|
overLightConfig,
|
|
1094
1194
|
transformStyle,
|
|
1095
1195
|
getShaderTime,
|
|
@@ -1100,4 +1200,4 @@ export function useAtomixGlass({
|
|
|
1100
1200
|
handleMouseUp,
|
|
1101
1201
|
handleKeyDown,
|
|
1102
1202
|
};
|
|
1103
|
-
}
|
|
1203
|
+
}
|