tekiyo-physics 1.0.0 → 1.1.0
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/index.cjs +2404 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +313 -0
- package/dist/index.js +2305 -2
- package/dist/index.js.map +1 -1
- package/package.json +5 -2
package/dist/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@ var __defProp = Object.defineProperty;
|
|
|
3
3
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
4
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
5
5
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
6
|
-
const
|
|
6
|
+
const React = require("react");
|
|
7
7
|
const jsxRuntime = require("react/jsx-runtime");
|
|
8
8
|
class Vector2 {
|
|
9
9
|
constructor(x = 0, y = 0) {
|
|
@@ -696,7 +696,7 @@ const defaultContext = {
|
|
|
696
696
|
shadowIntensity: 1
|
|
697
697
|
}
|
|
698
698
|
};
|
|
699
|
-
const PhysicsContext =
|
|
699
|
+
const PhysicsContext = React.createContext(defaultContext);
|
|
700
700
|
function PhysicsProvider({
|
|
701
701
|
children,
|
|
702
702
|
preset = "ios",
|
|
@@ -705,7 +705,7 @@ function PhysicsProvider({
|
|
|
705
705
|
stretch,
|
|
706
706
|
lift
|
|
707
707
|
}) {
|
|
708
|
-
const value =
|
|
708
|
+
const value = React.useMemo(() => {
|
|
709
709
|
const basePreset = typeof preset === "string" ? presets[preset] ?? ios : preset;
|
|
710
710
|
return {
|
|
711
711
|
spring: { ...basePreset.spring, ...spring },
|
|
@@ -717,45 +717,45 @@ function PhysicsProvider({
|
|
|
717
717
|
return /* @__PURE__ */ jsxRuntime.jsx(PhysicsContext.Provider, { value, children });
|
|
718
718
|
}
|
|
719
719
|
function usePhysicsContext() {
|
|
720
|
-
return
|
|
720
|
+
return React.useContext(PhysicsContext);
|
|
721
721
|
}
|
|
722
722
|
function usePhysicsConfig() {
|
|
723
|
-
const context =
|
|
723
|
+
const context = React.useContext(PhysicsContext);
|
|
724
724
|
return context.spring;
|
|
725
725
|
}
|
|
726
726
|
function useFrictionConfig() {
|
|
727
|
-
const context =
|
|
727
|
+
const context = React.useContext(PhysicsContext);
|
|
728
728
|
return context.friction;
|
|
729
729
|
}
|
|
730
730
|
function useStretchConfig() {
|
|
731
|
-
const context =
|
|
731
|
+
const context = React.useContext(PhysicsContext);
|
|
732
732
|
return context.stretch;
|
|
733
733
|
}
|
|
734
734
|
function useLiftConfig() {
|
|
735
|
-
const context =
|
|
735
|
+
const context = React.useContext(PhysicsContext);
|
|
736
736
|
return context.lift;
|
|
737
737
|
}
|
|
738
738
|
function useSpring(options) {
|
|
739
739
|
const { to, from, config, onRest, immediate = true } = options;
|
|
740
740
|
const globalConfig = usePhysicsConfig();
|
|
741
|
-
const resolvedConfig =
|
|
741
|
+
const resolvedConfig = React.useMemo(() => {
|
|
742
742
|
if (!config) return { ...DEFAULT_SPRING_CONFIG, ...globalConfig };
|
|
743
743
|
if (typeof config === "string") {
|
|
744
744
|
return { ...DEFAULT_SPRING_CONFIG, ...globalConfig };
|
|
745
745
|
}
|
|
746
746
|
return { ...DEFAULT_SPRING_CONFIG, ...globalConfig, ...config };
|
|
747
747
|
}, [config, globalConfig]);
|
|
748
|
-
const springRef =
|
|
749
|
-
const idRef =
|
|
750
|
-
const onRestRef =
|
|
748
|
+
const springRef = React.useRef(null);
|
|
749
|
+
const idRef = React.useRef(generatePhysicsId("spring"));
|
|
750
|
+
const onRestRef = React.useRef(onRest);
|
|
751
751
|
onRestRef.current = onRest;
|
|
752
|
-
const [state, setState] =
|
|
752
|
+
const [state, setState] = React.useState({
|
|
753
753
|
x: (from == null ? void 0 : from.x) ?? to.x,
|
|
754
754
|
y: (from == null ? void 0 : from.y) ?? to.y,
|
|
755
755
|
isAnimating: false,
|
|
756
756
|
isSettled: true
|
|
757
757
|
});
|
|
758
|
-
|
|
758
|
+
React.useEffect(() => {
|
|
759
759
|
const initialPos = from ? Vector2.from(from.x, from.y) : Vector2.from(to.x, to.y);
|
|
760
760
|
springRef.current = new Spring(initialPos, resolvedConfig);
|
|
761
761
|
if (immediate) {
|
|
@@ -765,7 +765,7 @@ function useSpring(options) {
|
|
|
765
765
|
PhysicsEngine.unregister(idRef.current);
|
|
766
766
|
};
|
|
767
767
|
}, []);
|
|
768
|
-
|
|
768
|
+
React.useEffect(() => {
|
|
769
769
|
if (springRef.current && immediate) {
|
|
770
770
|
springRef.current.setTarget(Vector2.from(to.x, to.y));
|
|
771
771
|
if (!PhysicsEngine.has(idRef.current)) {
|
|
@@ -787,11 +787,11 @@ function useSpring(options) {
|
|
|
787
787
|
}
|
|
788
788
|
}
|
|
789
789
|
}, [to.x, to.y, immediate]);
|
|
790
|
-
|
|
790
|
+
React.useEffect(() => {
|
|
791
791
|
var _a;
|
|
792
792
|
(_a = springRef.current) == null ? void 0 : _a.updateConfig(resolvedConfig);
|
|
793
793
|
}, [resolvedConfig]);
|
|
794
|
-
const setTarget =
|
|
794
|
+
const setTarget = React.useCallback((target) => {
|
|
795
795
|
if (!springRef.current) return;
|
|
796
796
|
springRef.current.setTarget(Vector2.from(target.x, target.y));
|
|
797
797
|
if (!PhysicsEngine.has(idRef.current)) {
|
|
@@ -812,7 +812,7 @@ function useSpring(options) {
|
|
|
812
812
|
});
|
|
813
813
|
}
|
|
814
814
|
}, []);
|
|
815
|
-
const snapTo =
|
|
815
|
+
const snapTo = React.useCallback((position) => {
|
|
816
816
|
if (!springRef.current) return;
|
|
817
817
|
springRef.current.setPosition(Vector2.from(position.x, position.y));
|
|
818
818
|
springRef.current.setTarget(Vector2.from(position.x, position.y));
|
|
@@ -825,7 +825,7 @@ function useSpring(options) {
|
|
|
825
825
|
});
|
|
826
826
|
PhysicsEngine.unregister(idRef.current);
|
|
827
827
|
}, []);
|
|
828
|
-
const impulse =
|
|
828
|
+
const impulse = React.useCallback((velocity) => {
|
|
829
829
|
if (!springRef.current) return;
|
|
830
830
|
const currentState = springRef.current.getState();
|
|
831
831
|
springRef.current.setVelocity(
|
|
@@ -849,7 +849,7 @@ function useSpring(options) {
|
|
|
849
849
|
});
|
|
850
850
|
}
|
|
851
851
|
}, []);
|
|
852
|
-
const style =
|
|
852
|
+
const style = React.useMemo(
|
|
853
853
|
() => ({
|
|
854
854
|
transform: `translate3d(${state.x}px, ${state.y}px, 0)`,
|
|
855
855
|
willChange: state.isAnimating ? "transform" : "auto"
|
|
@@ -870,20 +870,20 @@ function useSpring(options) {
|
|
|
870
870
|
function useSpring1D(options) {
|
|
871
871
|
const { to, from, config, onRest, immediate = true } = options;
|
|
872
872
|
const globalConfig = usePhysicsConfig();
|
|
873
|
-
const resolvedConfig =
|
|
873
|
+
const resolvedConfig = React.useMemo(
|
|
874
874
|
() => ({ ...DEFAULT_SPRING_CONFIG, ...globalConfig, ...config }),
|
|
875
875
|
[config, globalConfig]
|
|
876
876
|
);
|
|
877
|
-
const springRef =
|
|
878
|
-
const idRef =
|
|
879
|
-
const onRestRef =
|
|
877
|
+
const springRef = React.useRef(null);
|
|
878
|
+
const idRef = React.useRef(generatePhysicsId("spring1d"));
|
|
879
|
+
const onRestRef = React.useRef(onRest);
|
|
880
880
|
onRestRef.current = onRest;
|
|
881
|
-
const [state, setState] =
|
|
881
|
+
const [state, setState] = React.useState({
|
|
882
882
|
value: from ?? to,
|
|
883
883
|
isAnimating: false,
|
|
884
884
|
isSettled: true
|
|
885
885
|
});
|
|
886
|
-
|
|
886
|
+
React.useEffect(() => {
|
|
887
887
|
springRef.current = new Spring1D(from ?? to, resolvedConfig);
|
|
888
888
|
if (immediate) {
|
|
889
889
|
springRef.current.setTarget(to);
|
|
@@ -892,7 +892,7 @@ function useSpring1D(options) {
|
|
|
892
892
|
PhysicsEngine.unregister(idRef.current);
|
|
893
893
|
};
|
|
894
894
|
}, []);
|
|
895
|
-
|
|
895
|
+
React.useEffect(() => {
|
|
896
896
|
if (springRef.current && immediate) {
|
|
897
897
|
springRef.current.setTarget(to);
|
|
898
898
|
if (!PhysicsEngine.has(idRef.current)) {
|
|
@@ -913,7 +913,7 @@ function useSpring1D(options) {
|
|
|
913
913
|
}
|
|
914
914
|
}
|
|
915
915
|
}, [to, immediate]);
|
|
916
|
-
const setTarget =
|
|
916
|
+
const setTarget = React.useCallback((target) => {
|
|
917
917
|
if (!springRef.current) return;
|
|
918
918
|
springRef.current.setTarget(target);
|
|
919
919
|
if (!PhysicsEngine.has(idRef.current)) {
|
|
@@ -952,16 +952,16 @@ function useGesture(options) {
|
|
|
952
952
|
flickThreshold = 500,
|
|
953
953
|
preventTouch = true
|
|
954
954
|
} = options;
|
|
955
|
-
const velocityTracker =
|
|
956
|
-
const startPosition =
|
|
957
|
-
const lastPosition =
|
|
958
|
-
const isDragging =
|
|
959
|
-
const hasExceededThreshold =
|
|
960
|
-
const pointerId =
|
|
961
|
-
const elementRef =
|
|
962
|
-
const handlersRef =
|
|
955
|
+
const velocityTracker = React.useRef(new VelocityTracker());
|
|
956
|
+
const startPosition = React.useRef(Vector2.zero());
|
|
957
|
+
const lastPosition = React.useRef(Vector2.zero());
|
|
958
|
+
const isDragging = React.useRef(false);
|
|
959
|
+
const hasExceededThreshold = React.useRef(false);
|
|
960
|
+
const pointerId = React.useRef(null);
|
|
961
|
+
const elementRef = React.useRef(null);
|
|
962
|
+
const handlersRef = React.useRef({ onDragStart, onDrag, onDragEnd, onFlick });
|
|
963
963
|
handlersRef.current = { onDragStart, onDrag, onDragEnd, onFlick };
|
|
964
|
-
const createGestureState =
|
|
964
|
+
const createGestureState = React.useCallback(
|
|
965
965
|
(event, first, last) => {
|
|
966
966
|
const position = Vector2.from(event.clientX, event.clientY);
|
|
967
967
|
const delta = position.subtract(lastPosition.current);
|
|
@@ -982,7 +982,7 @@ function useGesture(options) {
|
|
|
982
982
|
},
|
|
983
983
|
[]
|
|
984
984
|
);
|
|
985
|
-
const handlePointerMove =
|
|
985
|
+
const handlePointerMove = React.useCallback(
|
|
986
986
|
(event) => {
|
|
987
987
|
var _a, _b, _c, _d;
|
|
988
988
|
if (!isDragging.current || pointerId.current !== event.pointerId) return;
|
|
@@ -1001,7 +1001,7 @@ function useGesture(options) {
|
|
|
1001
1001
|
},
|
|
1002
1002
|
[threshold, createGestureState]
|
|
1003
1003
|
);
|
|
1004
|
-
const handlePointerUp =
|
|
1004
|
+
const handlePointerUp = React.useCallback(
|
|
1005
1005
|
(event) => {
|
|
1006
1006
|
var _a, _b, _c, _d;
|
|
1007
1007
|
if (!isDragging.current || pointerId.current !== event.pointerId) return;
|
|
@@ -1023,7 +1023,7 @@ function useGesture(options) {
|
|
|
1023
1023
|
},
|
|
1024
1024
|
[handlePointerMove, createGestureState, flickThreshold]
|
|
1025
1025
|
);
|
|
1026
|
-
const onPointerDown =
|
|
1026
|
+
const onPointerDown = React.useCallback(
|
|
1027
1027
|
(e) => {
|
|
1028
1028
|
if (isDragging.current) return;
|
|
1029
1029
|
const element = e.currentTarget;
|
|
@@ -1043,19 +1043,19 @@ function useGesture(options) {
|
|
|
1043
1043
|
},
|
|
1044
1044
|
[handlePointerMove, handlePointerUp]
|
|
1045
1045
|
);
|
|
1046
|
-
const onPointerEnter =
|
|
1046
|
+
const onPointerEnter = React.useCallback(() => {
|
|
1047
1047
|
onHover == null ? void 0 : onHover(true);
|
|
1048
1048
|
}, [onHover]);
|
|
1049
|
-
const onPointerLeave =
|
|
1049
|
+
const onPointerLeave = React.useCallback(() => {
|
|
1050
1050
|
onHover == null ? void 0 : onHover(false);
|
|
1051
1051
|
}, [onHover]);
|
|
1052
|
-
const handleFocus =
|
|
1052
|
+
const handleFocus = React.useCallback(() => {
|
|
1053
1053
|
onFocus == null ? void 0 : onFocus(true);
|
|
1054
1054
|
}, [onFocus]);
|
|
1055
|
-
const handleBlur =
|
|
1055
|
+
const handleBlur = React.useCallback(() => {
|
|
1056
1056
|
onFocus == null ? void 0 : onFocus(false);
|
|
1057
1057
|
}, [onFocus]);
|
|
1058
|
-
|
|
1058
|
+
React.useEffect(() => {
|
|
1059
1059
|
return () => {
|
|
1060
1060
|
document.removeEventListener("pointermove", handlePointerMove);
|
|
1061
1061
|
document.removeEventListener("pointerup", handlePointerUp);
|
|
@@ -1089,38 +1089,38 @@ function useDrag(options = {}) {
|
|
|
1089
1089
|
onRest
|
|
1090
1090
|
} = options;
|
|
1091
1091
|
const globalConfig = usePhysicsConfig();
|
|
1092
|
-
const resolvedSpringConfig =
|
|
1092
|
+
const resolvedSpringConfig = React.useMemo(
|
|
1093
1093
|
() => ({ ...DEFAULT_SPRING_CONFIG, ...globalConfig, ...springConfig }),
|
|
1094
1094
|
[globalConfig, springConfig]
|
|
1095
1095
|
);
|
|
1096
|
-
const resolvedFrictionConfig =
|
|
1096
|
+
const resolvedFrictionConfig = React.useMemo(
|
|
1097
1097
|
() => ({ ...DEFAULT_FRICTION_CONFIG, ...frictionConfig }),
|
|
1098
1098
|
[frictionConfig]
|
|
1099
1099
|
);
|
|
1100
|
-
const [state, setState] =
|
|
1100
|
+
const [state, setState] = React.useState({
|
|
1101
1101
|
x: initial.x,
|
|
1102
1102
|
y: initial.y,
|
|
1103
1103
|
isDragging: false,
|
|
1104
1104
|
isMomentum: false
|
|
1105
1105
|
});
|
|
1106
|
-
const positionRef =
|
|
1107
|
-
const startPositionRef =
|
|
1108
|
-
const dragOffsetRef =
|
|
1109
|
-
const velocityTracker =
|
|
1110
|
-
const springRef =
|
|
1111
|
-
const momentumRef =
|
|
1112
|
-
const idRef =
|
|
1113
|
-
const pointerIdRef =
|
|
1114
|
-
const handlersRef =
|
|
1106
|
+
const positionRef = React.useRef(Vector2.from(initial.x, initial.y));
|
|
1107
|
+
const startPositionRef = React.useRef(Vector2.zero());
|
|
1108
|
+
const dragOffsetRef = React.useRef(Vector2.zero());
|
|
1109
|
+
const velocityTracker = React.useRef(new VelocityTracker());
|
|
1110
|
+
const springRef = React.useRef(null);
|
|
1111
|
+
const momentumRef = React.useRef(null);
|
|
1112
|
+
const idRef = React.useRef(generatePhysicsId("drag"));
|
|
1113
|
+
const pointerIdRef = React.useRef(null);
|
|
1114
|
+
const handlersRef = React.useRef({ onDragStart, onDrag, onDragEnd, onRest });
|
|
1115
1115
|
handlersRef.current = { onDragStart, onDrag, onDragEnd, onRest };
|
|
1116
|
-
const boundsVec =
|
|
1116
|
+
const boundsVec = React.useMemo(() => {
|
|
1117
1117
|
if (!bounds) return null;
|
|
1118
1118
|
return {
|
|
1119
1119
|
min: Vector2.from(bounds.left ?? -Infinity, bounds.top ?? -Infinity),
|
|
1120
1120
|
max: Vector2.from(bounds.right ?? Infinity, bounds.bottom ?? Infinity)
|
|
1121
1121
|
};
|
|
1122
1122
|
}, [bounds]);
|
|
1123
|
-
const applyRubberBand =
|
|
1123
|
+
const applyRubberBand = React.useCallback(
|
|
1124
1124
|
(pos) => {
|
|
1125
1125
|
if (!boundsVec || !rubberBand) return pos;
|
|
1126
1126
|
let x = pos.x;
|
|
@@ -1143,14 +1143,14 @@ function useDrag(options = {}) {
|
|
|
1143
1143
|
},
|
|
1144
1144
|
[boundsVec, rubberBand, rubberBandFactor]
|
|
1145
1145
|
);
|
|
1146
|
-
const clampToBounds =
|
|
1146
|
+
const clampToBounds = React.useCallback(
|
|
1147
1147
|
(pos) => {
|
|
1148
1148
|
if (!boundsVec) return pos;
|
|
1149
1149
|
return pos.clampComponents(boundsVec.min, boundsVec.max);
|
|
1150
1150
|
},
|
|
1151
1151
|
[boundsVec]
|
|
1152
1152
|
);
|
|
1153
|
-
const findSnapPoint =
|
|
1153
|
+
const findSnapPoint = React.useCallback(
|
|
1154
1154
|
(pos, velocity) => {
|
|
1155
1155
|
if (!snapTo || snapTo.length === 0) return null;
|
|
1156
1156
|
const projectedPos = pos.add(velocity.multiply(0.1));
|
|
@@ -1168,7 +1168,7 @@ function useDrag(options = {}) {
|
|
|
1168
1168
|
},
|
|
1169
1169
|
[snapTo, snapThreshold]
|
|
1170
1170
|
);
|
|
1171
|
-
const animateToTarget =
|
|
1171
|
+
const animateToTarget = React.useCallback(
|
|
1172
1172
|
(target) => {
|
|
1173
1173
|
springRef.current = new Spring(positionRef.current, resolvedSpringConfig);
|
|
1174
1174
|
springRef.current.setTarget(target);
|
|
@@ -1191,7 +1191,7 @@ function useDrag(options = {}) {
|
|
|
1191
1191
|
},
|
|
1192
1192
|
[resolvedSpringConfig]
|
|
1193
1193
|
);
|
|
1194
|
-
const handlePointerMove =
|
|
1194
|
+
const handlePointerMove = React.useCallback(
|
|
1195
1195
|
(e) => {
|
|
1196
1196
|
var _a, _b;
|
|
1197
1197
|
if (pointerIdRef.current !== e.pointerId) return;
|
|
@@ -1214,7 +1214,7 @@ function useDrag(options = {}) {
|
|
|
1214
1214
|
},
|
|
1215
1215
|
[axis, applyRubberBand]
|
|
1216
1216
|
);
|
|
1217
|
-
const handlePointerUp =
|
|
1217
|
+
const handlePointerUp = React.useCallback(
|
|
1218
1218
|
(e) => {
|
|
1219
1219
|
var _a, _b, _c, _d;
|
|
1220
1220
|
if (pointerIdRef.current !== e.pointerId) return;
|
|
@@ -1283,7 +1283,7 @@ function useDrag(options = {}) {
|
|
|
1283
1283
|
resolvedFrictionConfig
|
|
1284
1284
|
]
|
|
1285
1285
|
);
|
|
1286
|
-
const onPointerDown =
|
|
1286
|
+
const onPointerDown = React.useCallback(
|
|
1287
1287
|
(e) => {
|
|
1288
1288
|
var _a, _b;
|
|
1289
1289
|
if (pointerIdRef.current !== null) return;
|
|
@@ -1304,7 +1304,7 @@ function useDrag(options = {}) {
|
|
|
1304
1304
|
},
|
|
1305
1305
|
[handlePointerMove, handlePointerUp]
|
|
1306
1306
|
);
|
|
1307
|
-
const setPosition =
|
|
1307
|
+
const setPosition = React.useCallback((position) => {
|
|
1308
1308
|
PhysicsEngine.unregister(idRef.current);
|
|
1309
1309
|
positionRef.current = Vector2.from(position.x, position.y);
|
|
1310
1310
|
setState((prev) => ({
|
|
@@ -1315,13 +1315,13 @@ function useDrag(options = {}) {
|
|
|
1315
1315
|
isMomentum: false
|
|
1316
1316
|
}));
|
|
1317
1317
|
}, []);
|
|
1318
|
-
const snapToPosition =
|
|
1318
|
+
const snapToPosition = React.useCallback(
|
|
1319
1319
|
(position) => {
|
|
1320
1320
|
animateToTarget(Vector2.from(position.x, position.y));
|
|
1321
1321
|
},
|
|
1322
1322
|
[animateToTarget]
|
|
1323
1323
|
);
|
|
1324
|
-
|
|
1324
|
+
React.useEffect(() => {
|
|
1325
1325
|
return () => {
|
|
1326
1326
|
PhysicsEngine.unregister(idRef.current);
|
|
1327
1327
|
document.removeEventListener("pointermove", handlePointerMove);
|
|
@@ -1329,7 +1329,7 @@ function useDrag(options = {}) {
|
|
|
1329
1329
|
document.removeEventListener("pointercancel", handlePointerUp);
|
|
1330
1330
|
};
|
|
1331
1331
|
}, [handlePointerMove, handlePointerUp]);
|
|
1332
|
-
const style =
|
|
1332
|
+
const style = React.useMemo(
|
|
1333
1333
|
() => ({
|
|
1334
1334
|
transform: `translate3d(${state.x}px, ${state.y}px, 0)`,
|
|
1335
1335
|
willChange: state.isDragging || state.isMomentum ? "transform" : "auto",
|
|
@@ -1358,7 +1358,7 @@ function useStretch(options) {
|
|
|
1358
1358
|
intensity = 0.12,
|
|
1359
1359
|
maxVelocity = 2e3
|
|
1360
1360
|
} = options;
|
|
1361
|
-
return
|
|
1361
|
+
return React.useMemo(() => {
|
|
1362
1362
|
const vel = velocity instanceof Vector2 ? velocity : Vector2.from(velocity.x, velocity.y);
|
|
1363
1363
|
const normalizedX = Math.min(Math.abs(vel.x) / maxVelocity, 1);
|
|
1364
1364
|
const normalizedY = Math.min(Math.abs(vel.y) / maxVelocity, 1);
|
|
@@ -1428,7 +1428,7 @@ function useLift(options = {}) {
|
|
|
1428
1428
|
springConfig,
|
|
1429
1429
|
onLift
|
|
1430
1430
|
} = options;
|
|
1431
|
-
const [isLifted, setIsLifted] =
|
|
1431
|
+
const [isLifted, setIsLifted] = React.useState(false);
|
|
1432
1432
|
const { value: scale } = useSpring1D({
|
|
1433
1433
|
to: isLifted ? targetScale : 1,
|
|
1434
1434
|
config: {
|
|
@@ -1437,14 +1437,14 @@ function useLift(options = {}) {
|
|
|
1437
1437
|
...springConfig
|
|
1438
1438
|
}
|
|
1439
1439
|
});
|
|
1440
|
-
const handleLift =
|
|
1440
|
+
const handleLift = React.useCallback(
|
|
1441
1441
|
(lifted) => {
|
|
1442
1442
|
setIsLifted(lifted);
|
|
1443
1443
|
onLift == null ? void 0 : onLift(lifted);
|
|
1444
1444
|
},
|
|
1445
1445
|
[onLift]
|
|
1446
1446
|
);
|
|
1447
|
-
const bind =
|
|
1447
|
+
const bind = React.useMemo(() => {
|
|
1448
1448
|
switch (trigger) {
|
|
1449
1449
|
case "hover":
|
|
1450
1450
|
return {
|
|
@@ -1471,7 +1471,7 @@ function useLift(options = {}) {
|
|
|
1471
1471
|
return {};
|
|
1472
1472
|
}
|
|
1473
1473
|
}, [trigger, handleLift]);
|
|
1474
|
-
const shadowStyle =
|
|
1474
|
+
const shadowStyle = React.useMemo(() => {
|
|
1475
1475
|
if (!shadow) return {};
|
|
1476
1476
|
const liftAmount = (scale - 1) / (targetScale - 1);
|
|
1477
1477
|
const clampedLift = Math.max(0, Math.min(1, liftAmount));
|
|
@@ -1483,7 +1483,7 @@ function useLift(options = {}) {
|
|
|
1483
1483
|
boxShadow: `0 ${yOffset}px ${blur}px ${spread}px rgba(0, 0, 0, ${opacity})`
|
|
1484
1484
|
};
|
|
1485
1485
|
}, [scale, targetScale, shadow, shadowIntensity]);
|
|
1486
|
-
const style =
|
|
1486
|
+
const style = React.useMemo(
|
|
1487
1487
|
() => ({
|
|
1488
1488
|
transform: `scale3d(${scale}, ${scale}, 1)`,
|
|
1489
1489
|
transformOrigin: "center center",
|
|
@@ -1519,23 +1519,23 @@ function useFlick(options = {}) {
|
|
|
1519
1519
|
onFlick,
|
|
1520
1520
|
onFlickEnd
|
|
1521
1521
|
} = options;
|
|
1522
|
-
const resolvedFriction =
|
|
1522
|
+
const resolvedFriction = React.useMemo(
|
|
1523
1523
|
() => ({ ...DEFAULT_FRICTION_CONFIG, ...friction }),
|
|
1524
1524
|
[friction]
|
|
1525
1525
|
);
|
|
1526
|
-
const [state, setState] =
|
|
1526
|
+
const [state, setState] = React.useState({
|
|
1527
1527
|
x: initial.x,
|
|
1528
1528
|
y: initial.y,
|
|
1529
1529
|
isFlicking: false,
|
|
1530
1530
|
velocity: Vector2.zero()
|
|
1531
1531
|
});
|
|
1532
|
-
const positionRef =
|
|
1533
|
-
const momentumRef =
|
|
1534
|
-
const velocityTracker =
|
|
1535
|
-
const idRef =
|
|
1536
|
-
const handlersRef =
|
|
1532
|
+
const positionRef = React.useRef(Vector2.from(initial.x, initial.y));
|
|
1533
|
+
const momentumRef = React.useRef(null);
|
|
1534
|
+
const velocityTracker = React.useRef(new VelocityTracker());
|
|
1535
|
+
const idRef = React.useRef(generatePhysicsId("flick"));
|
|
1536
|
+
const handlersRef = React.useRef({ onFlickStart, onFlick, onFlickEnd });
|
|
1537
1537
|
handlersRef.current = { onFlickStart, onFlick, onFlickEnd };
|
|
1538
|
-
const startMomentum =
|
|
1538
|
+
const startMomentum = React.useCallback(
|
|
1539
1539
|
(velocity) => {
|
|
1540
1540
|
var _a, _b;
|
|
1541
1541
|
let constrainedVelocity = velocity;
|
|
@@ -1578,7 +1578,7 @@ function useFlick(options = {}) {
|
|
|
1578
1578
|
},
|
|
1579
1579
|
[axis, resolvedFriction]
|
|
1580
1580
|
);
|
|
1581
|
-
const flick =
|
|
1581
|
+
const flick = React.useCallback(
|
|
1582
1582
|
(velocity) => {
|
|
1583
1583
|
PhysicsEngine.unregister(idRef.current);
|
|
1584
1584
|
velocityTracker.current.reset();
|
|
@@ -1586,7 +1586,7 @@ function useFlick(options = {}) {
|
|
|
1586
1586
|
},
|
|
1587
1587
|
[startMomentum]
|
|
1588
1588
|
);
|
|
1589
|
-
const stop =
|
|
1589
|
+
const stop = React.useCallback(() => {
|
|
1590
1590
|
var _a;
|
|
1591
1591
|
PhysicsEngine.unregister(idRef.current);
|
|
1592
1592
|
(_a = momentumRef.current) == null ? void 0 : _a.stop();
|
|
@@ -1597,7 +1597,7 @@ function useFlick(options = {}) {
|
|
|
1597
1597
|
velocity: Vector2.zero()
|
|
1598
1598
|
}));
|
|
1599
1599
|
}, []);
|
|
1600
|
-
const setPosition =
|
|
1600
|
+
const setPosition = React.useCallback(
|
|
1601
1601
|
(position) => {
|
|
1602
1602
|
stop();
|
|
1603
1603
|
positionRef.current = Vector2.from(position.x, position.y);
|
|
@@ -1610,7 +1610,7 @@ function useFlick(options = {}) {
|
|
|
1610
1610
|
},
|
|
1611
1611
|
[stop]
|
|
1612
1612
|
);
|
|
1613
|
-
const recordSample =
|
|
1613
|
+
const recordSample = React.useCallback((position) => {
|
|
1614
1614
|
velocityTracker.current.addSample(Vector2.from(position.x, position.y));
|
|
1615
1615
|
positionRef.current = Vector2.from(position.x, position.y);
|
|
1616
1616
|
setState((prev) => ({
|
|
@@ -1619,10 +1619,10 @@ function useFlick(options = {}) {
|
|
|
1619
1619
|
y: position.y
|
|
1620
1620
|
}));
|
|
1621
1621
|
}, []);
|
|
1622
|
-
const getCurrentVelocity =
|
|
1622
|
+
const getCurrentVelocity = React.useCallback(() => {
|
|
1623
1623
|
return velocityTracker.current.getVelocity();
|
|
1624
1624
|
}, []);
|
|
1625
|
-
const release =
|
|
1625
|
+
const release = React.useCallback(
|
|
1626
1626
|
(position) => {
|
|
1627
1627
|
positionRef.current = Vector2.from(position.x, position.y);
|
|
1628
1628
|
const velocity = velocityTracker.current.getVelocity();
|
|
@@ -1642,12 +1642,12 @@ function useFlick(options = {}) {
|
|
|
1642
1642
|
},
|
|
1643
1643
|
[threshold, startMomentum]
|
|
1644
1644
|
);
|
|
1645
|
-
|
|
1645
|
+
React.useEffect(() => {
|
|
1646
1646
|
return () => {
|
|
1647
1647
|
PhysicsEngine.unregister(idRef.current);
|
|
1648
1648
|
};
|
|
1649
1649
|
}, []);
|
|
1650
|
-
const style =
|
|
1650
|
+
const style = React.useMemo(
|
|
1651
1651
|
() => ({
|
|
1652
1652
|
transform: `translate3d(${state.x}px, ${state.y}px, 0)`,
|
|
1653
1653
|
willChange: state.isFlicking ? "transform" : "auto"
|
|
@@ -1668,7 +1668,7 @@ function useFlick(options = {}) {
|
|
|
1668
1668
|
release
|
|
1669
1669
|
};
|
|
1670
1670
|
}
|
|
1671
|
-
const Draggable =
|
|
1671
|
+
const Draggable = React.forwardRef(
|
|
1672
1672
|
({
|
|
1673
1673
|
children,
|
|
1674
1674
|
initial = { x: 0, y: 0 },
|
|
@@ -1711,7 +1711,7 @@ const Draggable = react.forwardRef(
|
|
|
1711
1711
|
scale: physicsContext.lift.scale,
|
|
1712
1712
|
shadowIntensity: physicsContext.lift.shadowIntensity
|
|
1713
1713
|
});
|
|
1714
|
-
const velocity =
|
|
1714
|
+
const velocity = React.useMemo(() => {
|
|
1715
1715
|
if (!isDragging && !isMomentum) return Vector2.zero();
|
|
1716
1716
|
return Vector2.from(0, 0);
|
|
1717
1717
|
}, [isDragging, isMomentum]);
|
|
@@ -1720,7 +1720,7 @@ const Draggable = react.forwardRef(
|
|
|
1720
1720
|
intensity: physicsContext.stretch.intensity,
|
|
1721
1721
|
maxVelocity: physicsContext.stretch.maxVelocity
|
|
1722
1722
|
});
|
|
1723
|
-
const combinedStyle =
|
|
1723
|
+
const combinedStyle = React.useMemo(() => {
|
|
1724
1724
|
let result = { ...dragStyle };
|
|
1725
1725
|
if (enableLift && isDragging) {
|
|
1726
1726
|
result = combineLiftStyle(result, liftStyle);
|
|
@@ -1735,7 +1735,7 @@ const Draggable = react.forwardRef(
|
|
|
1735
1735
|
userSelect: "none"
|
|
1736
1736
|
};
|
|
1737
1737
|
}, [dragStyle, liftStyle, stretchStyle, enableLift, enableStretch, isDragging, customStyle]);
|
|
1738
|
-
const combinedBind =
|
|
1738
|
+
const combinedBind = React.useMemo(() => {
|
|
1739
1739
|
return {
|
|
1740
1740
|
...bind,
|
|
1741
1741
|
onPointerDown: (e) => {
|
|
@@ -1766,7 +1766,7 @@ const Draggable = react.forwardRef(
|
|
|
1766
1766
|
}
|
|
1767
1767
|
);
|
|
1768
1768
|
Draggable.displayName = "Draggable";
|
|
1769
|
-
const Card =
|
|
1769
|
+
const Card = React.forwardRef(
|
|
1770
1770
|
({
|
|
1771
1771
|
children,
|
|
1772
1772
|
lift: enableLift = true,
|
|
@@ -1783,7 +1783,7 @@ const Card = react.forwardRef(
|
|
|
1783
1783
|
onPress
|
|
1784
1784
|
}, ref) => {
|
|
1785
1785
|
const physicsContext = usePhysicsContext();
|
|
1786
|
-
const [isPressed, setIsPressed] =
|
|
1786
|
+
const [isPressed, setIsPressed] = React.useState(false);
|
|
1787
1787
|
const resolvedLiftScale = liftScale ?? physicsContext.lift.scale;
|
|
1788
1788
|
const { style: liftStyle, bind: liftBind, isLifted } = useLift({
|
|
1789
1789
|
trigger: "hover",
|
|
@@ -1800,19 +1800,19 @@ const Card = react.forwardRef(
|
|
|
1800
1800
|
...springConfig
|
|
1801
1801
|
}
|
|
1802
1802
|
});
|
|
1803
|
-
const handlePointerDown =
|
|
1803
|
+
const handlePointerDown = React.useCallback(() => {
|
|
1804
1804
|
if (pressable) {
|
|
1805
1805
|
setIsPressed(true);
|
|
1806
1806
|
onPress == null ? void 0 : onPress(true);
|
|
1807
1807
|
}
|
|
1808
1808
|
}, [pressable, onPress]);
|
|
1809
|
-
const handlePointerUp =
|
|
1809
|
+
const handlePointerUp = React.useCallback(() => {
|
|
1810
1810
|
if (pressable) {
|
|
1811
1811
|
setIsPressed(false);
|
|
1812
1812
|
onPress == null ? void 0 : onPress(false);
|
|
1813
1813
|
}
|
|
1814
1814
|
}, [pressable, onPress]);
|
|
1815
|
-
const handlePointerLeave =
|
|
1815
|
+
const handlePointerLeave = React.useCallback(() => {
|
|
1816
1816
|
if (isPressed) {
|
|
1817
1817
|
setIsPressed(false);
|
|
1818
1818
|
onPress == null ? void 0 : onPress(false);
|
|
@@ -1824,7 +1824,7 @@ const Card = react.forwardRef(
|
|
|
1824
1824
|
WebkitBackdropFilter: `blur(${glassBlur}px)`,
|
|
1825
1825
|
border: "1px solid rgba(255, 255, 255, 0.18)"
|
|
1826
1826
|
} : {};
|
|
1827
|
-
const combinedStyle =
|
|
1827
|
+
const combinedStyle = React.useMemo(() => {
|
|
1828
1828
|
const liftTransform = enableLift ? liftStyle.transform || "" : "";
|
|
1829
1829
|
const pressTransform = pressable ? `scale(${pressScaleValue})` : "";
|
|
1830
1830
|
const transform = `${liftTransform} ${pressTransform}`.trim() || void 0;
|
|
@@ -1875,38 +1875,2341 @@ const cardBaseStyles = {
|
|
|
1875
1875
|
background: "white",
|
|
1876
1876
|
boxShadow: "0 4px 12px rgba(0, 0, 0, 0.08)"
|
|
1877
1877
|
};
|
|
1878
|
+
function PhysicsSlider({
|
|
1879
|
+
label,
|
|
1880
|
+
description,
|
|
1881
|
+
value,
|
|
1882
|
+
min,
|
|
1883
|
+
max,
|
|
1884
|
+
step,
|
|
1885
|
+
config,
|
|
1886
|
+
onChange,
|
|
1887
|
+
decimals = 3,
|
|
1888
|
+
forceDrag = false,
|
|
1889
|
+
className = ""
|
|
1890
|
+
}) {
|
|
1891
|
+
const trackRef = React.useRef(null);
|
|
1892
|
+
const thumbRef = React.useRef(null);
|
|
1893
|
+
const state = React.useRef({
|
|
1894
|
+
isDragging: false,
|
|
1895
|
+
displayPercent: (value - min) / (max - min) * 100,
|
|
1896
|
+
thumbScale: 1,
|
|
1897
|
+
verticalOffset: 0,
|
|
1898
|
+
stretchX: 1,
|
|
1899
|
+
stretchY: 1,
|
|
1900
|
+
stretchOriginPercent: 50,
|
|
1901
|
+
targetOriginPercent: 50,
|
|
1902
|
+
shadowY: 2,
|
|
1903
|
+
shadowBlur: 6,
|
|
1904
|
+
shadowOpacity: 0.12,
|
|
1905
|
+
lastPercent: (value - min) / (max - min) * 100,
|
|
1906
|
+
currentVelocity: 0,
|
|
1907
|
+
pointerStart: null
|
|
1908
|
+
});
|
|
1909
|
+
const [, forceRender] = React.useState(0);
|
|
1910
|
+
const rerender = React.useCallback(() => forceRender((n) => n + 1), []);
|
|
1911
|
+
const springRef = React.useRef(null);
|
|
1912
|
+
const momentumRef = React.useRef(null);
|
|
1913
|
+
const scaleSpringRef = React.useRef(null);
|
|
1914
|
+
const verticalSpringRef = React.useRef(null);
|
|
1915
|
+
const stretchXSpringRef = React.useRef(null);
|
|
1916
|
+
const stretchYSpringRef = React.useRef(null);
|
|
1917
|
+
const stretchOriginSpringRef = React.useRef(null);
|
|
1918
|
+
const shadowSpringRef = React.useRef(null);
|
|
1919
|
+
const velocityTracker = React.useRef(new VelocityTracker1D());
|
|
1920
|
+
const physicsIdRef = React.useRef(generatePhysicsId("slider"));
|
|
1921
|
+
const scaleIdRef = React.useRef(generatePhysicsId("scale"));
|
|
1922
|
+
const verticalIdRef = React.useRef(generatePhysicsId("sliderVertical"));
|
|
1923
|
+
const stretchXIdRef = React.useRef(generatePhysicsId("stretchX"));
|
|
1924
|
+
const stretchYIdRef = React.useRef(generatePhysicsId("stretchY"));
|
|
1925
|
+
const stretchOriginIdRef = React.useRef(generatePhysicsId("stretchOrigin"));
|
|
1926
|
+
const shadowIdRef = React.useRef(generatePhysicsId("shadow"));
|
|
1927
|
+
const dragDecayIdRef = React.useRef(generatePhysicsId("dragDecay"));
|
|
1928
|
+
React.useEffect(() => {
|
|
1929
|
+
if (!state.current.isDragging && !forceDrag) {
|
|
1930
|
+
const targetPercent = (value - min) / (max - min) * 100;
|
|
1931
|
+
animateToPercent(targetPercent);
|
|
1932
|
+
}
|
|
1933
|
+
}, [value, min, max]);
|
|
1934
|
+
const lastValueRef = React.useRef(value);
|
|
1935
|
+
React.useEffect(() => {
|
|
1936
|
+
if (!forceDrag) {
|
|
1937
|
+
lastValueRef.current = value;
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const s2 = state.current;
|
|
1941
|
+
const targetPercent = (value - min) / (max - min) * 100;
|
|
1942
|
+
const lastPercent = (lastValueRef.current - min) / (max - min) * 100;
|
|
1943
|
+
const velocity = targetPercent - lastPercent;
|
|
1944
|
+
lastValueRef.current = value;
|
|
1945
|
+
s2.thumbScale = config.liftScale;
|
|
1946
|
+
const absVelocity = Math.abs(velocity);
|
|
1947
|
+
const velStretch = Math.min(absVelocity * config.velocityStretch * 15, 1.2);
|
|
1948
|
+
s2.stretchX = 1 + velStretch;
|
|
1949
|
+
s2.stretchY = 1 - velStretch * 0.4;
|
|
1950
|
+
if (Math.abs(velocity) > 0.1) {
|
|
1951
|
+
s2.stretchOriginPercent = velocity > 0 ? 100 : 0;
|
|
1952
|
+
}
|
|
1953
|
+
s2.shadowY = 2 + absVelocity * 0.4;
|
|
1954
|
+
s2.shadowBlur = 6 + absVelocity * 0.6;
|
|
1955
|
+
s2.shadowOpacity = 0.12 + absVelocity * 0.015;
|
|
1956
|
+
s2.displayPercent = targetPercent;
|
|
1957
|
+
s2.isDragging = true;
|
|
1958
|
+
rerender();
|
|
1959
|
+
}, [forceDrag, value, min, max, config.liftScale, config.velocityStretch, rerender]);
|
|
1960
|
+
const animateToPercent = React.useCallback((targetPercent) => {
|
|
1961
|
+
const s2 = state.current;
|
|
1962
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
1963
|
+
springRef.current = new Spring1D(s2.displayPercent, {
|
|
1964
|
+
tension: config.springTension * 400,
|
|
1965
|
+
friction: 26,
|
|
1966
|
+
mass: 1,
|
|
1967
|
+
precision: 0.01
|
|
1968
|
+
});
|
|
1969
|
+
springRef.current.setTarget(targetPercent);
|
|
1970
|
+
PhysicsEngine.register(physicsIdRef.current, (dt) => {
|
|
1971
|
+
if (!springRef.current) return;
|
|
1972
|
+
const result = springRef.current.step(dt);
|
|
1973
|
+
s2.displayPercent = result.value;
|
|
1974
|
+
rerender();
|
|
1975
|
+
if (result.isSettled) {
|
|
1976
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
}, [config.springTension, rerender]);
|
|
1980
|
+
const percentToValue = React.useCallback((percent) => {
|
|
1981
|
+
const raw = min + percent / 100 * (max - min);
|
|
1982
|
+
return Math.round(raw / step) * step;
|
|
1983
|
+
}, [min, max, step]);
|
|
1984
|
+
const animateScaleTo = React.useCallback((targetScale) => {
|
|
1985
|
+
const s2 = state.current;
|
|
1986
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
1987
|
+
scaleSpringRef.current = new Spring1D(s2.thumbScale, {
|
|
1988
|
+
tension: 400,
|
|
1989
|
+
friction: 20,
|
|
1990
|
+
mass: 0.6,
|
|
1991
|
+
precision: 1e-3
|
|
1992
|
+
});
|
|
1993
|
+
scaleSpringRef.current.setTarget(targetScale);
|
|
1994
|
+
PhysicsEngine.register(scaleIdRef.current, (dt) => {
|
|
1995
|
+
if (!scaleSpringRef.current) return;
|
|
1996
|
+
const result = scaleSpringRef.current.step(dt);
|
|
1997
|
+
s2.thumbScale = result.value;
|
|
1998
|
+
rerender();
|
|
1999
|
+
if (result.isSettled) {
|
|
2000
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
}, [rerender]);
|
|
2004
|
+
const getMomentumDecay = React.useCallback((flickMs) => {
|
|
2005
|
+
const clamped = Math.max(100, Math.min(2e3, flickMs));
|
|
2006
|
+
const seconds = clamped / 1e3;
|
|
2007
|
+
const targetRatio = 0.05;
|
|
2008
|
+
return Math.exp(Math.log(targetRatio) / (seconds * 60));
|
|
2009
|
+
}, []);
|
|
2010
|
+
const animateStretchRelax = React.useCallback(() => {
|
|
2011
|
+
const s2 = state.current;
|
|
2012
|
+
PhysicsEngine.unregister(stretchXIdRef.current);
|
|
2013
|
+
stretchXSpringRef.current = new Spring1D(s2.stretchX, {
|
|
2014
|
+
tension: 300,
|
|
2015
|
+
friction: 14,
|
|
2016
|
+
mass: 0.5,
|
|
2017
|
+
precision: 1e-3
|
|
2018
|
+
});
|
|
2019
|
+
stretchXSpringRef.current.setTarget(1);
|
|
2020
|
+
PhysicsEngine.register(stretchXIdRef.current, (dt) => {
|
|
2021
|
+
if (!stretchXSpringRef.current) return;
|
|
2022
|
+
const result = stretchXSpringRef.current.step(dt);
|
|
2023
|
+
s2.stretchX = result.value;
|
|
2024
|
+
rerender();
|
|
2025
|
+
if (result.isSettled) {
|
|
2026
|
+
s2.stretchX = 1;
|
|
2027
|
+
PhysicsEngine.unregister(stretchXIdRef.current);
|
|
2028
|
+
}
|
|
2029
|
+
});
|
|
2030
|
+
PhysicsEngine.unregister(stretchYIdRef.current);
|
|
2031
|
+
stretchYSpringRef.current = new Spring1D(s2.stretchY, {
|
|
2032
|
+
tension: 300,
|
|
2033
|
+
friction: 14,
|
|
2034
|
+
mass: 0.5,
|
|
2035
|
+
precision: 1e-3
|
|
2036
|
+
});
|
|
2037
|
+
stretchYSpringRef.current.setTarget(1);
|
|
2038
|
+
PhysicsEngine.register(stretchYIdRef.current, (dt) => {
|
|
2039
|
+
if (!stretchYSpringRef.current) return;
|
|
2040
|
+
const result = stretchYSpringRef.current.step(dt);
|
|
2041
|
+
s2.stretchY = result.value;
|
|
2042
|
+
rerender();
|
|
2043
|
+
if (result.isSettled) {
|
|
2044
|
+
s2.stretchY = 1;
|
|
2045
|
+
PhysicsEngine.unregister(stretchYIdRef.current);
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
PhysicsEngine.unregister(shadowIdRef.current);
|
|
2049
|
+
shadowSpringRef.current = new Spring1D(s2.shadowY, {
|
|
2050
|
+
tension: 250,
|
|
2051
|
+
friction: 20,
|
|
2052
|
+
mass: 0.5,
|
|
2053
|
+
precision: 0.01
|
|
2054
|
+
});
|
|
2055
|
+
shadowSpringRef.current.setTarget(2);
|
|
2056
|
+
PhysicsEngine.register(shadowIdRef.current, (dt) => {
|
|
2057
|
+
if (!shadowSpringRef.current) return;
|
|
2058
|
+
const result = shadowSpringRef.current.step(dt);
|
|
2059
|
+
const t = (result.value - 2) / 10;
|
|
2060
|
+
s2.shadowY = result.value;
|
|
2061
|
+
s2.shadowBlur = 6 + t * 4;
|
|
2062
|
+
s2.shadowOpacity = 0.12 + t * 0.03;
|
|
2063
|
+
rerender();
|
|
2064
|
+
if (result.isSettled) {
|
|
2065
|
+
s2.shadowY = 2;
|
|
2066
|
+
s2.shadowBlur = 6;
|
|
2067
|
+
s2.shadowOpacity = 0.12;
|
|
2068
|
+
PhysicsEngine.unregister(shadowIdRef.current);
|
|
2069
|
+
}
|
|
2070
|
+
});
|
|
2071
|
+
PhysicsEngine.unregister(stretchOriginIdRef.current);
|
|
2072
|
+
stretchOriginSpringRef.current = new Spring1D(s2.stretchOriginPercent, {
|
|
2073
|
+
tension: 300,
|
|
2074
|
+
friction: 18,
|
|
2075
|
+
mass: 0.5,
|
|
2076
|
+
precision: 0.1
|
|
2077
|
+
});
|
|
2078
|
+
stretchOriginSpringRef.current.setTarget(50);
|
|
2079
|
+
PhysicsEngine.register(stretchOriginIdRef.current, (dt) => {
|
|
2080
|
+
if (!stretchOriginSpringRef.current) return;
|
|
2081
|
+
const result = stretchOriginSpringRef.current.step(dt);
|
|
2082
|
+
s2.stretchOriginPercent = result.value;
|
|
2083
|
+
s2.targetOriginPercent = 50;
|
|
2084
|
+
rerender();
|
|
2085
|
+
if (result.isSettled) {
|
|
2086
|
+
s2.stretchOriginPercent = 50;
|
|
2087
|
+
s2.targetOriginPercent = 50;
|
|
2088
|
+
PhysicsEngine.unregister(stretchOriginIdRef.current);
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
}, [rerender]);
|
|
2092
|
+
const handlePointerDown = React.useCallback((e) => {
|
|
2093
|
+
if (!trackRef.current) return;
|
|
2094
|
+
const s2 = state.current;
|
|
2095
|
+
e.preventDefault();
|
|
2096
|
+
e.target.setPointerCapture(e.pointerId);
|
|
2097
|
+
s2.pointerStart = { x: e.clientX, y: e.clientY };
|
|
2098
|
+
s2.isDragging = true;
|
|
2099
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
2100
|
+
PhysicsEngine.unregister(verticalIdRef.current);
|
|
2101
|
+
PhysicsEngine.unregister(stretchXIdRef.current);
|
|
2102
|
+
PhysicsEngine.unregister(stretchYIdRef.current);
|
|
2103
|
+
PhysicsEngine.unregister(stretchOriginIdRef.current);
|
|
2104
|
+
PhysicsEngine.unregister(shadowIdRef.current);
|
|
2105
|
+
velocityTracker.current.reset();
|
|
2106
|
+
animateScaleTo(config.liftScale);
|
|
2107
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
2108
|
+
const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
|
|
2109
|
+
const percent = x / rect.width * 100;
|
|
2110
|
+
s2.displayPercent = percent;
|
|
2111
|
+
s2.lastPercent = percent;
|
|
2112
|
+
velocityTracker.current.addSample(percent);
|
|
2113
|
+
onChange(percentToValue(percent));
|
|
2114
|
+
PhysicsEngine.register(dragDecayIdRef.current, () => {
|
|
2115
|
+
if (!s2.isDragging) return;
|
|
2116
|
+
const decayLerp = 0.08;
|
|
2117
|
+
const threshold = 1e-3;
|
|
2118
|
+
if (Math.abs(s2.stretchX - 1) > threshold) {
|
|
2119
|
+
s2.stretchX += (1 - s2.stretchX) * decayLerp;
|
|
2120
|
+
}
|
|
2121
|
+
if (Math.abs(s2.stretchY - 1) > threshold) {
|
|
2122
|
+
s2.stretchY += (1 - s2.stretchY) * decayLerp;
|
|
2123
|
+
}
|
|
2124
|
+
if (Math.abs(s2.stretchOriginPercent - 50) > threshold) {
|
|
2125
|
+
s2.stretchOriginPercent += (50 - s2.stretchOriginPercent) * decayLerp;
|
|
2126
|
+
}
|
|
2127
|
+
s2.shadowY += (2 - s2.shadowY) * decayLerp;
|
|
2128
|
+
s2.shadowBlur += (6 - s2.shadowBlur) * decayLerp;
|
|
2129
|
+
s2.shadowOpacity += (0.12 - s2.shadowOpacity) * decayLerp;
|
|
2130
|
+
rerender();
|
|
2131
|
+
});
|
|
2132
|
+
rerender();
|
|
2133
|
+
}, [onChange, percentToValue, animateScaleTo, config.liftScale, rerender]);
|
|
2134
|
+
const handlePointerMove = React.useCallback((e) => {
|
|
2135
|
+
const s2 = state.current;
|
|
2136
|
+
if (!s2.isDragging || !trackRef.current || !s2.pointerStart) return;
|
|
2137
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
2138
|
+
const rawX = e.clientX - rect.left;
|
|
2139
|
+
const rawPercent = rawX / rect.width * 100;
|
|
2140
|
+
let percent = rawPercent;
|
|
2141
|
+
let edgeStretchH = 0;
|
|
2142
|
+
if (rawPercent < 0) {
|
|
2143
|
+
edgeStretchH = -rawPercent / 100;
|
|
2144
|
+
percent = rawPercent * 0.15;
|
|
2145
|
+
s2.targetOriginPercent = 100;
|
|
2146
|
+
} else if (rawPercent > 100) {
|
|
2147
|
+
edgeStretchH = (rawPercent - 100) / 100;
|
|
2148
|
+
percent = 100 + (rawPercent - 100) * 0.15;
|
|
2149
|
+
s2.targetOriginPercent = 0;
|
|
2150
|
+
}
|
|
2151
|
+
const velocity = percent - s2.lastPercent;
|
|
2152
|
+
s2.lastPercent = percent;
|
|
2153
|
+
s2.currentVelocity = velocity;
|
|
2154
|
+
if (Math.abs(velocity) > 0.3 && edgeStretchH === 0) {
|
|
2155
|
+
s2.targetOriginPercent = velocity > 0 ? 100 : 0;
|
|
2156
|
+
}
|
|
2157
|
+
const originLerpSpeed = 0.15;
|
|
2158
|
+
s2.stretchOriginPercent += (s2.targetOriginPercent - s2.stretchOriginPercent) * originLerpSpeed;
|
|
2159
|
+
const rawVerticalOffset = e.clientY - s2.pointerStart.y;
|
|
2160
|
+
const rubberBandedOffset = rawVerticalOffset * 0.06;
|
|
2161
|
+
s2.verticalOffset = Math.max(-5, Math.min(5, rubberBandedOffset));
|
|
2162
|
+
const absVelocity = Math.abs(velocity);
|
|
2163
|
+
const velStretch = Math.min(absVelocity * config.velocityStretch * 15, 1.2);
|
|
2164
|
+
const edgeStretchFactorH = Math.min(Math.abs(edgeStretchH) * 0.6, 0.12);
|
|
2165
|
+
const edgeStretchFactorV = Math.abs(s2.verticalOffset) / 5 * 0.06;
|
|
2166
|
+
const targetStretchX = 1 + velStretch + edgeStretchFactorH;
|
|
2167
|
+
const targetStretchY = 1 - velStretch * 0.4 + edgeStretchFactorV;
|
|
2168
|
+
const baseLerp = 0.35;
|
|
2169
|
+
const decayLerp = 0.15;
|
|
2170
|
+
const velocityThreshold = 0.5;
|
|
2171
|
+
if (absVelocity < velocityThreshold) {
|
|
2172
|
+
const decayFactor = 1 - absVelocity / velocityThreshold;
|
|
2173
|
+
s2.stretchX += (1 - s2.stretchX) * decayLerp * decayFactor;
|
|
2174
|
+
s2.stretchY += (1 - s2.stretchY) * decayLerp * decayFactor;
|
|
2175
|
+
s2.stretchOriginPercent += (50 - s2.stretchOriginPercent) * decayLerp * decayFactor;
|
|
2176
|
+
}
|
|
2177
|
+
s2.stretchX += (targetStretchX - s2.stretchX) * baseLerp;
|
|
2178
|
+
s2.stretchY += (targetStretchY - s2.stretchY) * baseLerp;
|
|
2179
|
+
const targetShadowY = 2 + absVelocity * 0.4;
|
|
2180
|
+
const targetShadowBlur = 6 + absVelocity * 0.6;
|
|
2181
|
+
const targetShadowOpacity = 0.12 + absVelocity * 0.015;
|
|
2182
|
+
const shadowLerp = 0.3;
|
|
2183
|
+
s2.shadowY += (targetShadowY - s2.shadowY) * shadowLerp;
|
|
2184
|
+
s2.shadowBlur += (targetShadowBlur - s2.shadowBlur) * shadowLerp;
|
|
2185
|
+
s2.shadowOpacity += (targetShadowOpacity - s2.shadowOpacity) * shadowLerp;
|
|
2186
|
+
s2.displayPercent = Math.max(0, Math.min(100, percent));
|
|
2187
|
+
velocityTracker.current.addSample(percent);
|
|
2188
|
+
onChange(percentToValue(Math.max(0, Math.min(100, percent))));
|
|
2189
|
+
rerender();
|
|
2190
|
+
}, [onChange, percentToValue, config.velocityStretch, rerender]);
|
|
2191
|
+
const handlePointerUp = React.useCallback(() => {
|
|
2192
|
+
const s2 = state.current;
|
|
2193
|
+
if (!s2.isDragging) return;
|
|
2194
|
+
s2.isDragging = false;
|
|
2195
|
+
s2.pointerStart = null;
|
|
2196
|
+
PhysicsEngine.unregister(dragDecayIdRef.current);
|
|
2197
|
+
animateScaleTo(1);
|
|
2198
|
+
animateStretchRelax();
|
|
2199
|
+
if (s2.verticalOffset !== 0) {
|
|
2200
|
+
PhysicsEngine.unregister(verticalIdRef.current);
|
|
2201
|
+
verticalSpringRef.current = new Spring1D(s2.verticalOffset, {
|
|
2202
|
+
tension: 400,
|
|
2203
|
+
friction: 18,
|
|
2204
|
+
mass: 0.5,
|
|
2205
|
+
precision: 0.1
|
|
2206
|
+
});
|
|
2207
|
+
verticalSpringRef.current.setTarget(0);
|
|
2208
|
+
PhysicsEngine.register(verticalIdRef.current, (dt) => {
|
|
2209
|
+
if (!verticalSpringRef.current) return;
|
|
2210
|
+
const result = verticalSpringRef.current.step(dt);
|
|
2211
|
+
s2.verticalOffset = result.value;
|
|
2212
|
+
rerender();
|
|
2213
|
+
if (result.isSettled) {
|
|
2214
|
+
s2.verticalOffset = 0;
|
|
2215
|
+
PhysicsEngine.unregister(verticalIdRef.current);
|
|
2216
|
+
}
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
const velocity = velocityTracker.current.getVelocity();
|
|
2220
|
+
velocityTracker.current.reset();
|
|
2221
|
+
if (Math.abs(velocity) > 30) {
|
|
2222
|
+
const decay = getMomentumDecay(config.flickMomentum);
|
|
2223
|
+
momentumRef.current = new Momentum1D(s2.displayPercent, {
|
|
2224
|
+
decay,
|
|
2225
|
+
stopThreshold: 0.3,
|
|
2226
|
+
maxVelocity: config.maxVelocity * 100
|
|
2227
|
+
});
|
|
2228
|
+
momentumRef.current.start(s2.displayPercent, velocity * 0.4);
|
|
2229
|
+
PhysicsEngine.register(physicsIdRef.current, (dt) => {
|
|
2230
|
+
if (!momentumRef.current) return;
|
|
2231
|
+
const result = momentumRef.current.step(dt);
|
|
2232
|
+
const momentumVel = Math.abs(result.velocity || 0) / 100;
|
|
2233
|
+
if (momentumVel > 0.1) {
|
|
2234
|
+
const stretch = Math.min(momentumVel * config.velocityStretch * 5, 0.8);
|
|
2235
|
+
s2.stretchX = 1 + stretch;
|
|
2236
|
+
s2.stretchY = 1 - stretch * 0.5;
|
|
2237
|
+
s2.targetOriginPercent = (result.velocity || 0) > 0 ? 100 : 0;
|
|
2238
|
+
s2.stretchOriginPercent += (s2.targetOriginPercent - s2.stretchOriginPercent) * 0.1;
|
|
2239
|
+
}
|
|
2240
|
+
let newPercent = Math.max(0, Math.min(100, result.position));
|
|
2241
|
+
if (result.position < 0 || result.position > 100) {
|
|
2242
|
+
momentumRef.current.stop();
|
|
2243
|
+
const targetPercent = result.position < 0 ? 0 : 100;
|
|
2244
|
+
animateStretchRelax();
|
|
2245
|
+
springRef.current = new Spring1D(newPercent, {
|
|
2246
|
+
tension: config.springTension * 400,
|
|
2247
|
+
friction: 26
|
|
2248
|
+
});
|
|
2249
|
+
springRef.current.setTarget(targetPercent);
|
|
2250
|
+
PhysicsEngine.register(physicsIdRef.current, (dt2) => {
|
|
2251
|
+
if (!springRef.current) return;
|
|
2252
|
+
const springResult = springRef.current.step(dt2);
|
|
2253
|
+
s2.displayPercent = springResult.value;
|
|
2254
|
+
onChange(percentToValue(springResult.value));
|
|
2255
|
+
rerender();
|
|
2256
|
+
if (springResult.isSettled) {
|
|
2257
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
2258
|
+
}
|
|
2259
|
+
});
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
s2.displayPercent = newPercent;
|
|
2263
|
+
onChange(percentToValue(newPercent));
|
|
2264
|
+
rerender();
|
|
2265
|
+
if (!result.isActive) {
|
|
2266
|
+
animateStretchRelax();
|
|
2267
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
2271
|
+
rerender();
|
|
2272
|
+
}, [config, onChange, percentToValue, animateScaleTo, animateStretchRelax, getMomentumDecay, rerender]);
|
|
2273
|
+
const handleTrackClick = React.useCallback((e) => {
|
|
2274
|
+
const s2 = state.current;
|
|
2275
|
+
if (!trackRef.current || s2.isDragging) return;
|
|
2276
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
2277
|
+
const x = e.clientX - rect.left;
|
|
2278
|
+
const percent = Math.max(0, Math.min(100, x / rect.width * 100));
|
|
2279
|
+
onChange(percentToValue(percent));
|
|
2280
|
+
animateToPercent(percent);
|
|
2281
|
+
}, [onChange, percentToValue, animateToPercent]);
|
|
2282
|
+
React.useEffect(() => {
|
|
2283
|
+
return () => {
|
|
2284
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
2285
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
2286
|
+
PhysicsEngine.unregister(verticalIdRef.current);
|
|
2287
|
+
PhysicsEngine.unregister(stretchXIdRef.current);
|
|
2288
|
+
PhysicsEngine.unregister(stretchYIdRef.current);
|
|
2289
|
+
PhysicsEngine.unregister(stretchOriginIdRef.current);
|
|
2290
|
+
PhysicsEngine.unregister(shadowIdRef.current);
|
|
2291
|
+
PhysicsEngine.unregister(dragDecayIdRef.current);
|
|
2292
|
+
};
|
|
2293
|
+
}, []);
|
|
2294
|
+
const s = state.current;
|
|
2295
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `tekiyo-slider-container ${className}`, children: [
|
|
2296
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tekiyo-slider-header", children: [
|
|
2297
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tekiyo-slider-label-group", children: [
|
|
2298
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-slider-label", children: label }),
|
|
2299
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-slider-description", children: description })
|
|
2300
|
+
] }),
|
|
2301
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-slider-value", children: value.toFixed(decimals) })
|
|
2302
|
+
] }),
|
|
2303
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2304
|
+
"div",
|
|
2305
|
+
{
|
|
2306
|
+
className: "tekiyo-slider-track",
|
|
2307
|
+
ref: trackRef,
|
|
2308
|
+
onClick: handleTrackClick,
|
|
2309
|
+
children: [
|
|
2310
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2311
|
+
"div",
|
|
2312
|
+
{
|
|
2313
|
+
className: "tekiyo-slider-fill",
|
|
2314
|
+
style: { width: `${s.displayPercent}%` }
|
|
2315
|
+
}
|
|
2316
|
+
),
|
|
2317
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2318
|
+
"div",
|
|
2319
|
+
{
|
|
2320
|
+
ref: thumbRef,
|
|
2321
|
+
className: `tekiyo-slider-thumb ${s.isDragging ? "active" : ""}`,
|
|
2322
|
+
style: {
|
|
2323
|
+
left: `${s.displayPercent}%`,
|
|
2324
|
+
transform: `translate(-50%, calc(-50% + ${s.verticalOffset}px)) scale(${s.thumbScale}) scaleX(${s.stretchX}) scaleY(${s.stretchY})`,
|
|
2325
|
+
transformOrigin: `${s.stretchOriginPercent}% center`,
|
|
2326
|
+
boxShadow: `0 ${s.shadowY}px ${s.shadowBlur}px rgba(0, 0, 0, ${s.shadowOpacity})`
|
|
2327
|
+
},
|
|
2328
|
+
onPointerDown: handlePointerDown,
|
|
2329
|
+
onPointerMove: handlePointerMove,
|
|
2330
|
+
onPointerUp: handlePointerUp,
|
|
2331
|
+
onPointerCancel: handlePointerUp
|
|
2332
|
+
}
|
|
2333
|
+
)
|
|
2334
|
+
]
|
|
2335
|
+
}
|
|
2336
|
+
)
|
|
2337
|
+
] });
|
|
2338
|
+
}
|
|
2339
|
+
function createThumbState(percent) {
|
|
2340
|
+
return {
|
|
2341
|
+
displayPercent: percent,
|
|
2342
|
+
thumbScale: 1,
|
|
2343
|
+
verticalOffset: 0,
|
|
2344
|
+
stretchX: 1,
|
|
2345
|
+
stretchY: 1,
|
|
2346
|
+
stretchOriginPercent: 50,
|
|
2347
|
+
targetOriginPercent: 50,
|
|
2348
|
+
shadowY: 2,
|
|
2349
|
+
shadowBlur: 6,
|
|
2350
|
+
shadowOpacity: 0.12,
|
|
2351
|
+
lastPercent: percent,
|
|
2352
|
+
isDragging: false
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
function PhysicsRangeSlider({
|
|
2356
|
+
label,
|
|
2357
|
+
description,
|
|
2358
|
+
minValue,
|
|
2359
|
+
maxValue,
|
|
2360
|
+
min,
|
|
2361
|
+
max,
|
|
2362
|
+
step,
|
|
2363
|
+
config,
|
|
2364
|
+
onChange,
|
|
2365
|
+
formatValue = (v) => v.toFixed(0),
|
|
2366
|
+
className = ""
|
|
2367
|
+
}) {
|
|
2368
|
+
const trackRef = React.useRef(null);
|
|
2369
|
+
const minPercent = (minValue - min) / (max - min) * 100;
|
|
2370
|
+
const maxPercent = (maxValue - min) / (max - min) * 100;
|
|
2371
|
+
const minThumb = React.useRef(createThumbState(minPercent));
|
|
2372
|
+
const maxThumb = React.useRef(createThumbState(maxPercent));
|
|
2373
|
+
const activeThumb = React.useRef(null);
|
|
2374
|
+
const pointerStart = React.useRef(null);
|
|
2375
|
+
const [, forceRender] = React.useState(0);
|
|
2376
|
+
const rerender = React.useCallback(() => forceRender((n) => n + 1), []);
|
|
2377
|
+
const minScaleId = React.useRef(generatePhysicsId("rangeMinScale"));
|
|
2378
|
+
const maxScaleId = React.useRef(generatePhysicsId("rangeMaxScale"));
|
|
2379
|
+
const minDecayId = React.useRef(generatePhysicsId("rangeMinDecay"));
|
|
2380
|
+
const maxDecayId = React.useRef(generatePhysicsId("rangeMaxDecay"));
|
|
2381
|
+
const minRelaxId = React.useRef(generatePhysicsId("rangeMinRelax"));
|
|
2382
|
+
const maxRelaxId = React.useRef(generatePhysicsId("rangeMaxRelax"));
|
|
2383
|
+
const minScaleSpring = React.useRef(null);
|
|
2384
|
+
const maxScaleSpring = React.useRef(null);
|
|
2385
|
+
React.useEffect(() => {
|
|
2386
|
+
if (!minThumb.current.isDragging) {
|
|
2387
|
+
minThumb.current.displayPercent = (minValue - min) / (max - min) * 100;
|
|
2388
|
+
}
|
|
2389
|
+
if (!maxThumb.current.isDragging) {
|
|
2390
|
+
maxThumb.current.displayPercent = (maxValue - min) / (max - min) * 100;
|
|
2391
|
+
}
|
|
2392
|
+
rerender();
|
|
2393
|
+
}, [minValue, maxValue, min, max, rerender]);
|
|
2394
|
+
const percentToValue = React.useCallback(
|
|
2395
|
+
(percent) => {
|
|
2396
|
+
const raw = min + percent / 100 * (max - min);
|
|
2397
|
+
return Math.round(raw / step) * step;
|
|
2398
|
+
},
|
|
2399
|
+
[min, max, step]
|
|
2400
|
+
);
|
|
2401
|
+
const animateScaleTo = React.useCallback(
|
|
2402
|
+
(thumb, targetScale) => {
|
|
2403
|
+
const state = thumb === "min" ? minThumb.current : maxThumb.current;
|
|
2404
|
+
const springRef = thumb === "min" ? minScaleSpring : maxScaleSpring;
|
|
2405
|
+
const scaleId = thumb === "min" ? minScaleId : maxScaleId;
|
|
2406
|
+
PhysicsEngine.unregister(scaleId.current);
|
|
2407
|
+
springRef.current = new Spring1D(state.thumbScale, {
|
|
2408
|
+
tension: 400,
|
|
2409
|
+
friction: 20,
|
|
2410
|
+
mass: 0.6,
|
|
2411
|
+
precision: 1e-3
|
|
2412
|
+
});
|
|
2413
|
+
springRef.current.setTarget(targetScale);
|
|
2414
|
+
PhysicsEngine.register(scaleId.current, (dt) => {
|
|
2415
|
+
if (!springRef.current) return;
|
|
2416
|
+
const result = springRef.current.step(dt);
|
|
2417
|
+
state.thumbScale = result.value;
|
|
2418
|
+
rerender();
|
|
2419
|
+
if (result.isSettled) {
|
|
2420
|
+
PhysicsEngine.unregister(scaleId.current);
|
|
2421
|
+
}
|
|
2422
|
+
});
|
|
2423
|
+
},
|
|
2424
|
+
[rerender]
|
|
2425
|
+
);
|
|
2426
|
+
const animateStretchRelax = React.useCallback(
|
|
2427
|
+
(thumb) => {
|
|
2428
|
+
const state = thumb === "min" ? minThumb.current : maxThumb.current;
|
|
2429
|
+
const relaxId = thumb === "min" ? minRelaxId : maxRelaxId;
|
|
2430
|
+
PhysicsEngine.unregister(relaxId.current);
|
|
2431
|
+
const spring = new Spring1D(state.stretchX, {
|
|
2432
|
+
tension: 300,
|
|
2433
|
+
friction: 14,
|
|
2434
|
+
mass: 0.5,
|
|
2435
|
+
precision: 1e-3
|
|
2436
|
+
});
|
|
2437
|
+
spring.setTarget(1);
|
|
2438
|
+
PhysicsEngine.register(relaxId.current, (dt) => {
|
|
2439
|
+
const result = spring.step(dt);
|
|
2440
|
+
state.stretchX = result.value;
|
|
2441
|
+
state.stretchY = 1 - (result.value - 1) * 0.4;
|
|
2442
|
+
state.stretchOriginPercent += (50 - state.stretchOriginPercent) * 0.15;
|
|
2443
|
+
state.shadowY += (2 - state.shadowY) * 0.15;
|
|
2444
|
+
state.shadowBlur += (6 - state.shadowBlur) * 0.15;
|
|
2445
|
+
state.shadowOpacity += (0.12 - state.shadowOpacity) * 0.15;
|
|
2446
|
+
rerender();
|
|
2447
|
+
if (result.isSettled) {
|
|
2448
|
+
state.stretchX = 1;
|
|
2449
|
+
state.stretchY = 1;
|
|
2450
|
+
state.stretchOriginPercent = 50;
|
|
2451
|
+
PhysicsEngine.unregister(relaxId.current);
|
|
2452
|
+
}
|
|
2453
|
+
});
|
|
2454
|
+
},
|
|
2455
|
+
[rerender]
|
|
2456
|
+
);
|
|
2457
|
+
const handlePointerDown = React.useCallback(
|
|
2458
|
+
(e, thumb) => {
|
|
2459
|
+
if (!trackRef.current) return;
|
|
2460
|
+
const state = thumb === "min" ? minThumb.current : maxThumb.current;
|
|
2461
|
+
const decayId = thumb === "min" ? minDecayId : maxDecayId;
|
|
2462
|
+
e.preventDefault();
|
|
2463
|
+
e.stopPropagation();
|
|
2464
|
+
e.target.setPointerCapture(e.pointerId);
|
|
2465
|
+
pointerStart.current = { x: e.clientX, y: e.clientY };
|
|
2466
|
+
activeThumb.current = thumb;
|
|
2467
|
+
state.isDragging = true;
|
|
2468
|
+
animateScaleTo(thumb, config.liftScale);
|
|
2469
|
+
PhysicsEngine.register(decayId.current, () => {
|
|
2470
|
+
if (!state.isDragging) return;
|
|
2471
|
+
const decayLerp = 0.08;
|
|
2472
|
+
const threshold = 1e-3;
|
|
2473
|
+
if (Math.abs(state.stretchX - 1) > threshold) {
|
|
2474
|
+
state.stretchX += (1 - state.stretchX) * decayLerp;
|
|
2475
|
+
}
|
|
2476
|
+
if (Math.abs(state.stretchY - 1) > threshold) {
|
|
2477
|
+
state.stretchY += (1 - state.stretchY) * decayLerp;
|
|
2478
|
+
}
|
|
2479
|
+
if (Math.abs(state.stretchOriginPercent - 50) > threshold) {
|
|
2480
|
+
state.stretchOriginPercent += (50 - state.stretchOriginPercent) * decayLerp;
|
|
2481
|
+
}
|
|
2482
|
+
state.shadowY += (2 - state.shadowY) * decayLerp;
|
|
2483
|
+
state.shadowBlur += (6 - state.shadowBlur) * decayLerp;
|
|
2484
|
+
state.shadowOpacity += (0.12 - state.shadowOpacity) * decayLerp;
|
|
2485
|
+
rerender();
|
|
2486
|
+
});
|
|
2487
|
+
rerender();
|
|
2488
|
+
},
|
|
2489
|
+
[config.liftScale, animateScaleTo, rerender]
|
|
2490
|
+
);
|
|
2491
|
+
const handlePointerMove = React.useCallback(
|
|
2492
|
+
(e) => {
|
|
2493
|
+
if (!activeThumb.current || !trackRef.current || !pointerStart.current) return;
|
|
2494
|
+
const thumb = activeThumb.current;
|
|
2495
|
+
const state = thumb === "min" ? minThumb.current : maxThumb.current;
|
|
2496
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
2497
|
+
const rawX = e.clientX - rect.left;
|
|
2498
|
+
let percent = rawX / rect.width * 100;
|
|
2499
|
+
if (thumb === "min") {
|
|
2500
|
+
percent = Math.max(0, Math.min(maxThumb.current.displayPercent - 5, percent));
|
|
2501
|
+
} else {
|
|
2502
|
+
percent = Math.min(100, Math.max(minThumb.current.displayPercent + 5, percent));
|
|
2503
|
+
}
|
|
2504
|
+
const velocity = percent - state.lastPercent;
|
|
2505
|
+
state.lastPercent = percent;
|
|
2506
|
+
if (Math.abs(velocity) > 0.3) {
|
|
2507
|
+
state.targetOriginPercent = velocity > 0 ? 100 : 0;
|
|
2508
|
+
}
|
|
2509
|
+
state.stretchOriginPercent += (state.targetOriginPercent - state.stretchOriginPercent) * 0.15;
|
|
2510
|
+
const absVelocity = Math.abs(velocity);
|
|
2511
|
+
const velStretch = Math.min(absVelocity * config.velocityStretch * 15, 1.2);
|
|
2512
|
+
const targetStretchX = 1 + velStretch;
|
|
2513
|
+
const targetStretchY = 1 - velStretch * 0.4;
|
|
2514
|
+
const baseLerp = 0.35;
|
|
2515
|
+
const decayLerp = 0.15;
|
|
2516
|
+
const velocityThreshold = 0.5;
|
|
2517
|
+
if (absVelocity < velocityThreshold) {
|
|
2518
|
+
const decayFactor = 1 - absVelocity / velocityThreshold;
|
|
2519
|
+
state.stretchX += (1 - state.stretchX) * decayLerp * decayFactor;
|
|
2520
|
+
state.stretchY += (1 - state.stretchY) * decayLerp * decayFactor;
|
|
2521
|
+
state.stretchOriginPercent += (50 - state.stretchOriginPercent) * decayLerp * decayFactor;
|
|
2522
|
+
}
|
|
2523
|
+
state.stretchX += (targetStretchX - state.stretchX) * baseLerp;
|
|
2524
|
+
state.stretchY += (targetStretchY - state.stretchY) * baseLerp;
|
|
2525
|
+
const targetShadowY = 2 + absVelocity * 0.4;
|
|
2526
|
+
const targetShadowBlur = 6 + absVelocity * 0.6;
|
|
2527
|
+
const targetShadowOpacity = 0.12 + absVelocity * 0.015;
|
|
2528
|
+
state.shadowY += (targetShadowY - state.shadowY) * 0.3;
|
|
2529
|
+
state.shadowBlur += (targetShadowBlur - state.shadowBlur) * 0.3;
|
|
2530
|
+
state.shadowOpacity += (targetShadowOpacity - state.shadowOpacity) * 0.3;
|
|
2531
|
+
state.displayPercent = percent;
|
|
2532
|
+
const newMin = thumb === "min" ? percentToValue(percent) : minValue;
|
|
2533
|
+
const newMax = thumb === "max" ? percentToValue(percent) : maxValue;
|
|
2534
|
+
onChange(newMin, newMax);
|
|
2535
|
+
rerender();
|
|
2536
|
+
},
|
|
2537
|
+
[config.velocityStretch, minValue, maxValue, percentToValue, onChange, rerender]
|
|
2538
|
+
);
|
|
2539
|
+
const handlePointerUp = React.useCallback(() => {
|
|
2540
|
+
if (!activeThumb.current) return;
|
|
2541
|
+
const thumb = activeThumb.current;
|
|
2542
|
+
const state = thumb === "min" ? minThumb.current : maxThumb.current;
|
|
2543
|
+
const decayId = thumb === "min" ? minDecayId : maxDecayId;
|
|
2544
|
+
state.isDragging = false;
|
|
2545
|
+
pointerStart.current = null;
|
|
2546
|
+
activeThumb.current = null;
|
|
2547
|
+
PhysicsEngine.unregister(decayId.current);
|
|
2548
|
+
animateScaleTo(thumb, 1);
|
|
2549
|
+
animateStretchRelax(thumb);
|
|
2550
|
+
rerender();
|
|
2551
|
+
}, [animateScaleTo, animateStretchRelax, rerender]);
|
|
2552
|
+
React.useEffect(() => {
|
|
2553
|
+
return () => {
|
|
2554
|
+
PhysicsEngine.unregister(minScaleId.current);
|
|
2555
|
+
PhysicsEngine.unregister(maxScaleId.current);
|
|
2556
|
+
PhysicsEngine.unregister(minDecayId.current);
|
|
2557
|
+
PhysicsEngine.unregister(maxDecayId.current);
|
|
2558
|
+
PhysicsEngine.unregister(minRelaxId.current);
|
|
2559
|
+
PhysicsEngine.unregister(maxRelaxId.current);
|
|
2560
|
+
};
|
|
2561
|
+
}, []);
|
|
2562
|
+
const minS = minThumb.current;
|
|
2563
|
+
const maxS = maxThumb.current;
|
|
2564
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `tekiyo-range-slider ${className}`, children: [
|
|
2565
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tekiyo-slider-header", children: [
|
|
2566
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tekiyo-slider-label-group", children: [
|
|
2567
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-slider-label", children: label }),
|
|
2568
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-slider-description", children: description })
|
|
2569
|
+
] }),
|
|
2570
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tekiyo-slider-value", children: [
|
|
2571
|
+
formatValue(minValue),
|
|
2572
|
+
" - ",
|
|
2573
|
+
formatValue(maxValue)
|
|
2574
|
+
] })
|
|
2575
|
+
] }),
|
|
2576
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2577
|
+
"div",
|
|
2578
|
+
{
|
|
2579
|
+
className: "tekiyo-slider-track",
|
|
2580
|
+
ref: trackRef,
|
|
2581
|
+
onPointerMove: handlePointerMove,
|
|
2582
|
+
onPointerUp: handlePointerUp,
|
|
2583
|
+
onPointerCancel: handlePointerUp,
|
|
2584
|
+
children: [
|
|
2585
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "tekiyo-slider-track-bg" }),
|
|
2586
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2587
|
+
"div",
|
|
2588
|
+
{
|
|
2589
|
+
className: "tekiyo-range-fill",
|
|
2590
|
+
style: {
|
|
2591
|
+
left: `${minS.displayPercent}%`,
|
|
2592
|
+
width: `${maxS.displayPercent - minS.displayPercent}%`
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
),
|
|
2596
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2597
|
+
"div",
|
|
2598
|
+
{
|
|
2599
|
+
className: `tekiyo-slider-thumb tekiyo-range-thumb-min ${minS.isDragging ? "active" : ""}`,
|
|
2600
|
+
style: {
|
|
2601
|
+
left: `${minS.displayPercent}%`,
|
|
2602
|
+
transform: `translate(-50%, calc(-50% + ${minS.verticalOffset}px)) scale(${minS.thumbScale}) scaleX(${minS.stretchX}) scaleY(${minS.stretchY})`,
|
|
2603
|
+
transformOrigin: `${minS.stretchOriginPercent}% center`,
|
|
2604
|
+
boxShadow: `0 ${minS.shadowY}px ${minS.shadowBlur}px rgba(0, 0, 0, ${minS.shadowOpacity})`
|
|
2605
|
+
},
|
|
2606
|
+
onPointerDown: (e) => handlePointerDown(e, "min")
|
|
2607
|
+
}
|
|
2608
|
+
),
|
|
2609
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2610
|
+
"div",
|
|
2611
|
+
{
|
|
2612
|
+
className: `tekiyo-slider-thumb tekiyo-range-thumb-max ${maxS.isDragging ? "active" : ""}`,
|
|
2613
|
+
style: {
|
|
2614
|
+
left: `${maxS.displayPercent}%`,
|
|
2615
|
+
transform: `translate(-50%, calc(-50% + ${maxS.verticalOffset}px)) scale(${maxS.thumbScale}) scaleX(${maxS.stretchX}) scaleY(${maxS.stretchY})`,
|
|
2616
|
+
transformOrigin: `${maxS.stretchOriginPercent}% center`,
|
|
2617
|
+
boxShadow: `0 ${maxS.shadowY}px ${maxS.shadowBlur}px rgba(0, 0, 0, ${maxS.shadowOpacity})`
|
|
2618
|
+
},
|
|
2619
|
+
onPointerDown: (e) => handlePointerDown(e, "max")
|
|
2620
|
+
}
|
|
2621
|
+
)
|
|
2622
|
+
]
|
|
2623
|
+
}
|
|
2624
|
+
)
|
|
2625
|
+
] });
|
|
2626
|
+
}
|
|
2627
|
+
function PhysicsStepSlider({
|
|
2628
|
+
label,
|
|
2629
|
+
description,
|
|
2630
|
+
options,
|
|
2631
|
+
value,
|
|
2632
|
+
config,
|
|
2633
|
+
onChange,
|
|
2634
|
+
className = ""
|
|
2635
|
+
}) {
|
|
2636
|
+
const trackRef = React.useRef(null);
|
|
2637
|
+
const currentIndex = options.findIndex((opt) => opt.value === value);
|
|
2638
|
+
const stepPercent = 100 / (options.length - 1);
|
|
2639
|
+
const targetPercent = currentIndex * stepPercent;
|
|
2640
|
+
const state = React.useRef({
|
|
2641
|
+
isDragging: false,
|
|
2642
|
+
displayPercent: targetPercent,
|
|
2643
|
+
thumbScale: 1,
|
|
2644
|
+
verticalOffset: 0,
|
|
2645
|
+
stretchX: 1,
|
|
2646
|
+
stretchY: 1,
|
|
2647
|
+
stretchOriginPercent: 50,
|
|
2648
|
+
targetOriginPercent: 50,
|
|
2649
|
+
shadowY: 2,
|
|
2650
|
+
shadowBlur: 6,
|
|
2651
|
+
shadowOpacity: 0.12,
|
|
2652
|
+
lastPercent: targetPercent,
|
|
2653
|
+
pointerStart: null
|
|
2654
|
+
});
|
|
2655
|
+
const [, forceRender] = React.useState(0);
|
|
2656
|
+
const rerender = React.useCallback(() => forceRender((n) => n + 1), []);
|
|
2657
|
+
const scaleIdRef = React.useRef(generatePhysicsId("stepScale"));
|
|
2658
|
+
const snapIdRef = React.useRef(generatePhysicsId("stepSnap"));
|
|
2659
|
+
const decayIdRef = React.useRef(generatePhysicsId("stepDecay"));
|
|
2660
|
+
const relaxIdRef = React.useRef(generatePhysicsId("stepRelax"));
|
|
2661
|
+
const scaleSpring = React.useRef(null);
|
|
2662
|
+
const snapSpring = React.useRef(null);
|
|
2663
|
+
React.useEffect(() => {
|
|
2664
|
+
if (!state.current.isDragging) {
|
|
2665
|
+
const idx = options.findIndex((opt) => opt.value === value);
|
|
2666
|
+
const percent = idx * stepPercent;
|
|
2667
|
+
animateToPercent(percent);
|
|
2668
|
+
}
|
|
2669
|
+
}, [value, options, stepPercent]);
|
|
2670
|
+
const animateToPercent = React.useCallback(
|
|
2671
|
+
(targetPercent2) => {
|
|
2672
|
+
const s2 = state.current;
|
|
2673
|
+
PhysicsEngine.unregister(snapIdRef.current);
|
|
2674
|
+
snapSpring.current = new Spring1D(s2.displayPercent, {
|
|
2675
|
+
tension: config.springTension * 500,
|
|
2676
|
+
friction: 22,
|
|
2677
|
+
mass: 0.8,
|
|
2678
|
+
precision: 0.01
|
|
2679
|
+
});
|
|
2680
|
+
snapSpring.current.setTarget(targetPercent2);
|
|
2681
|
+
PhysicsEngine.register(snapIdRef.current, (dt) => {
|
|
2682
|
+
if (!snapSpring.current) return;
|
|
2683
|
+
const result = snapSpring.current.step(dt);
|
|
2684
|
+
s2.displayPercent = result.value;
|
|
2685
|
+
rerender();
|
|
2686
|
+
if (result.isSettled) {
|
|
2687
|
+
PhysicsEngine.unregister(snapIdRef.current);
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
},
|
|
2691
|
+
[config.springTension, rerender]
|
|
2692
|
+
);
|
|
2693
|
+
const animateScaleTo = React.useCallback(
|
|
2694
|
+
(targetScale) => {
|
|
2695
|
+
const s2 = state.current;
|
|
2696
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
2697
|
+
scaleSpring.current = new Spring1D(s2.thumbScale, {
|
|
2698
|
+
tension: 400,
|
|
2699
|
+
friction: 20,
|
|
2700
|
+
mass: 0.6,
|
|
2701
|
+
precision: 1e-3
|
|
2702
|
+
});
|
|
2703
|
+
scaleSpring.current.setTarget(targetScale);
|
|
2704
|
+
PhysicsEngine.register(scaleIdRef.current, (dt) => {
|
|
2705
|
+
if (!scaleSpring.current) return;
|
|
2706
|
+
const result = scaleSpring.current.step(dt);
|
|
2707
|
+
s2.thumbScale = result.value;
|
|
2708
|
+
rerender();
|
|
2709
|
+
if (result.isSettled) {
|
|
2710
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
2711
|
+
}
|
|
2712
|
+
});
|
|
2713
|
+
},
|
|
2714
|
+
[rerender]
|
|
2715
|
+
);
|
|
2716
|
+
const animateStretchRelax = React.useCallback(() => {
|
|
2717
|
+
const s2 = state.current;
|
|
2718
|
+
PhysicsEngine.unregister(relaxIdRef.current);
|
|
2719
|
+
const spring = new Spring1D(s2.stretchX, {
|
|
2720
|
+
tension: 300,
|
|
2721
|
+
friction: 14,
|
|
2722
|
+
mass: 0.5,
|
|
2723
|
+
precision: 1e-3
|
|
2724
|
+
});
|
|
2725
|
+
spring.setTarget(1);
|
|
2726
|
+
PhysicsEngine.register(relaxIdRef.current, (dt) => {
|
|
2727
|
+
const result = spring.step(dt);
|
|
2728
|
+
s2.stretchX = result.value;
|
|
2729
|
+
s2.stretchY = 1 - (result.value - 1) * 0.4;
|
|
2730
|
+
s2.stretchOriginPercent += (50 - s2.stretchOriginPercent) * 0.15;
|
|
2731
|
+
s2.shadowY += (2 - s2.shadowY) * 0.15;
|
|
2732
|
+
s2.shadowBlur += (6 - s2.shadowBlur) * 0.15;
|
|
2733
|
+
s2.shadowOpacity += (0.12 - s2.shadowOpacity) * 0.15;
|
|
2734
|
+
rerender();
|
|
2735
|
+
if (result.isSettled) {
|
|
2736
|
+
s2.stretchX = 1;
|
|
2737
|
+
s2.stretchY = 1;
|
|
2738
|
+
s2.stretchOriginPercent = 50;
|
|
2739
|
+
PhysicsEngine.unregister(relaxIdRef.current);
|
|
2740
|
+
}
|
|
2741
|
+
});
|
|
2742
|
+
}, [rerender]);
|
|
2743
|
+
const getClosestStepIndex = React.useCallback(
|
|
2744
|
+
(percent) => {
|
|
2745
|
+
const stepSize = 100 / (options.length - 1);
|
|
2746
|
+
return Math.round(percent / stepSize);
|
|
2747
|
+
},
|
|
2748
|
+
[options.length]
|
|
2749
|
+
);
|
|
2750
|
+
const handlePointerDown = React.useCallback(
|
|
2751
|
+
(e) => {
|
|
2752
|
+
if (!trackRef.current) return;
|
|
2753
|
+
const s2 = state.current;
|
|
2754
|
+
e.preventDefault();
|
|
2755
|
+
e.target.setPointerCapture(e.pointerId);
|
|
2756
|
+
s2.pointerStart = { x: e.clientX, y: e.clientY };
|
|
2757
|
+
s2.isDragging = true;
|
|
2758
|
+
PhysicsEngine.unregister(snapIdRef.current);
|
|
2759
|
+
animateScaleTo(config.liftScale);
|
|
2760
|
+
PhysicsEngine.register(decayIdRef.current, () => {
|
|
2761
|
+
if (!s2.isDragging) return;
|
|
2762
|
+
const decayLerp = 0.08;
|
|
2763
|
+
const threshold = 1e-3;
|
|
2764
|
+
if (Math.abs(s2.stretchX - 1) > threshold) {
|
|
2765
|
+
s2.stretchX += (1 - s2.stretchX) * decayLerp;
|
|
2766
|
+
}
|
|
2767
|
+
if (Math.abs(s2.stretchY - 1) > threshold) {
|
|
2768
|
+
s2.stretchY += (1 - s2.stretchY) * decayLerp;
|
|
2769
|
+
}
|
|
2770
|
+
if (Math.abs(s2.stretchOriginPercent - 50) > threshold) {
|
|
2771
|
+
s2.stretchOriginPercent += (50 - s2.stretchOriginPercent) * decayLerp;
|
|
2772
|
+
}
|
|
2773
|
+
s2.shadowY += (2 - s2.shadowY) * decayLerp;
|
|
2774
|
+
s2.shadowBlur += (6 - s2.shadowBlur) * decayLerp;
|
|
2775
|
+
s2.shadowOpacity += (0.12 - s2.shadowOpacity) * decayLerp;
|
|
2776
|
+
rerender();
|
|
2777
|
+
});
|
|
2778
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
2779
|
+
const x = Math.max(0, Math.min(rect.width, e.clientX - rect.left));
|
|
2780
|
+
const percent = x / rect.width * 100;
|
|
2781
|
+
s2.displayPercent = percent;
|
|
2782
|
+
s2.lastPercent = percent;
|
|
2783
|
+
rerender();
|
|
2784
|
+
},
|
|
2785
|
+
[config.liftScale, animateScaleTo, rerender]
|
|
2786
|
+
);
|
|
2787
|
+
const handlePointerMove = React.useCallback(
|
|
2788
|
+
(e) => {
|
|
2789
|
+
const s2 = state.current;
|
|
2790
|
+
if (!s2.isDragging || !trackRef.current || !s2.pointerStart) return;
|
|
2791
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
2792
|
+
const rawX = e.clientX - rect.left;
|
|
2793
|
+
const percent = Math.max(0, Math.min(100, rawX / rect.width * 100));
|
|
2794
|
+
const velocity = percent - s2.lastPercent;
|
|
2795
|
+
s2.lastPercent = percent;
|
|
2796
|
+
if (Math.abs(velocity) > 0.3) {
|
|
2797
|
+
s2.targetOriginPercent = velocity > 0 ? 100 : 0;
|
|
2798
|
+
}
|
|
2799
|
+
s2.stretchOriginPercent += (s2.targetOriginPercent - s2.stretchOriginPercent) * 0.15;
|
|
2800
|
+
const absVelocity = Math.abs(velocity);
|
|
2801
|
+
const velStretch = Math.min(absVelocity * config.velocityStretch * 15, 1.2);
|
|
2802
|
+
const targetStretchX = 1 + velStretch;
|
|
2803
|
+
const targetStretchY = 1 - velStretch * 0.4;
|
|
2804
|
+
const baseLerp = 0.35;
|
|
2805
|
+
const decayLerp = 0.15;
|
|
2806
|
+
const velocityThreshold = 0.5;
|
|
2807
|
+
if (absVelocity < velocityThreshold) {
|
|
2808
|
+
const decayFactor = 1 - absVelocity / velocityThreshold;
|
|
2809
|
+
s2.stretchX += (1 - s2.stretchX) * decayLerp * decayFactor;
|
|
2810
|
+
s2.stretchY += (1 - s2.stretchY) * decayLerp * decayFactor;
|
|
2811
|
+
}
|
|
2812
|
+
s2.stretchX += (targetStretchX - s2.stretchX) * baseLerp;
|
|
2813
|
+
s2.stretchY += (targetStretchY - s2.stretchY) * baseLerp;
|
|
2814
|
+
const targetShadowY = 2 + absVelocity * 0.4;
|
|
2815
|
+
const targetShadowBlur = 6 + absVelocity * 0.6;
|
|
2816
|
+
const targetShadowOpacity = 0.12 + absVelocity * 0.015;
|
|
2817
|
+
s2.shadowY += (targetShadowY - s2.shadowY) * 0.3;
|
|
2818
|
+
s2.shadowBlur += (targetShadowBlur - s2.shadowBlur) * 0.3;
|
|
2819
|
+
s2.shadowOpacity += (targetShadowOpacity - s2.shadowOpacity) * 0.3;
|
|
2820
|
+
s2.displayPercent = percent;
|
|
2821
|
+
rerender();
|
|
2822
|
+
},
|
|
2823
|
+
[config.velocityStretch, rerender]
|
|
2824
|
+
);
|
|
2825
|
+
const handlePointerUp = React.useCallback(() => {
|
|
2826
|
+
var _a;
|
|
2827
|
+
const s2 = state.current;
|
|
2828
|
+
if (!s2.isDragging) return;
|
|
2829
|
+
s2.isDragging = false;
|
|
2830
|
+
s2.pointerStart = null;
|
|
2831
|
+
PhysicsEngine.unregister(decayIdRef.current);
|
|
2832
|
+
animateScaleTo(1);
|
|
2833
|
+
animateStretchRelax();
|
|
2834
|
+
const closestIndex = getClosestStepIndex(s2.displayPercent);
|
|
2835
|
+
const snapPercent = closestIndex * stepPercent;
|
|
2836
|
+
animateToPercent(snapPercent);
|
|
2837
|
+
const newValue = (_a = options[closestIndex]) == null ? void 0 : _a.value;
|
|
2838
|
+
if (newValue !== void 0 && newValue !== value) {
|
|
2839
|
+
onChange(newValue);
|
|
2840
|
+
}
|
|
2841
|
+
rerender();
|
|
2842
|
+
}, [animateScaleTo, animateStretchRelax, animateToPercent, getClosestStepIndex, stepPercent, options, value, onChange, rerender]);
|
|
2843
|
+
const handleStepClick = React.useCallback(
|
|
2844
|
+
(index) => {
|
|
2845
|
+
if (state.current.isDragging) return;
|
|
2846
|
+
const newValue = options[index].value;
|
|
2847
|
+
if (newValue !== value) {
|
|
2848
|
+
onChange(newValue);
|
|
2849
|
+
}
|
|
2850
|
+
},
|
|
2851
|
+
[options, value, onChange]
|
|
2852
|
+
);
|
|
2853
|
+
React.useEffect(() => {
|
|
2854
|
+
return () => {
|
|
2855
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
2856
|
+
PhysicsEngine.unregister(snapIdRef.current);
|
|
2857
|
+
PhysicsEngine.unregister(decayIdRef.current);
|
|
2858
|
+
PhysicsEngine.unregister(relaxIdRef.current);
|
|
2859
|
+
};
|
|
2860
|
+
}, []);
|
|
2861
|
+
const s = state.current;
|
|
2862
|
+
const currentOption = options[currentIndex];
|
|
2863
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `tekiyo-step-slider ${className}`, children: [
|
|
2864
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tekiyo-slider-header", children: [
|
|
2865
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tekiyo-slider-label-group", children: [
|
|
2866
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-slider-label", children: label }),
|
|
2867
|
+
description && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-slider-description", children: description })
|
|
2868
|
+
] }),
|
|
2869
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2870
|
+
"span",
|
|
2871
|
+
{
|
|
2872
|
+
className: "tekiyo-step-value",
|
|
2873
|
+
style: { color: currentOption == null ? void 0 : currentOption.color },
|
|
2874
|
+
children: currentOption == null ? void 0 : currentOption.label
|
|
2875
|
+
}
|
|
2876
|
+
)
|
|
2877
|
+
] }),
|
|
2878
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
2879
|
+
"div",
|
|
2880
|
+
{
|
|
2881
|
+
className: "tekiyo-step-track",
|
|
2882
|
+
ref: trackRef,
|
|
2883
|
+
onPointerMove: handlePointerMove,
|
|
2884
|
+
onPointerUp: handlePointerUp,
|
|
2885
|
+
onPointerCancel: handlePointerUp,
|
|
2886
|
+
children: [
|
|
2887
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "tekiyo-step-track-bg" }),
|
|
2888
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2889
|
+
"div",
|
|
2890
|
+
{
|
|
2891
|
+
className: "tekiyo-step-track-fill",
|
|
2892
|
+
style: {
|
|
2893
|
+
width: `${s.displayPercent}%`,
|
|
2894
|
+
background: (currentOption == null ? void 0 : currentOption.color) || "#007aff"
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
),
|
|
2898
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "tekiyo-step-dots", children: options.map((option, index) => {
|
|
2899
|
+
const dotPercent = index * stepPercent;
|
|
2900
|
+
const isActive = s.displayPercent >= dotPercent - 1;
|
|
2901
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2902
|
+
"div",
|
|
2903
|
+
{
|
|
2904
|
+
className: `tekiyo-step-dot ${isActive ? "active" : ""}`,
|
|
2905
|
+
style: {
|
|
2906
|
+
left: `${dotPercent}%`,
|
|
2907
|
+
background: isActive ? option.color || "#007aff" : void 0
|
|
2908
|
+
},
|
|
2909
|
+
onClick: () => handleStepClick(index)
|
|
2910
|
+
},
|
|
2911
|
+
option.value
|
|
2912
|
+
);
|
|
2913
|
+
}) }),
|
|
2914
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2915
|
+
"div",
|
|
2916
|
+
{
|
|
2917
|
+
className: `tekiyo-slider-thumb tekiyo-step-thumb ${s.isDragging ? "active" : ""}`,
|
|
2918
|
+
style: {
|
|
2919
|
+
left: `${s.displayPercent}%`,
|
|
2920
|
+
transform: `translate(-50%, calc(-50% + ${s.verticalOffset}px)) scale(${s.thumbScale}) scaleX(${s.stretchX}) scaleY(${s.stretchY})`,
|
|
2921
|
+
transformOrigin: `${s.stretchOriginPercent}% center`,
|
|
2922
|
+
boxShadow: `0 ${s.shadowY}px ${s.shadowBlur}px rgba(0, 0, 0, ${s.shadowOpacity})`
|
|
2923
|
+
},
|
|
2924
|
+
onPointerDown: handlePointerDown
|
|
2925
|
+
}
|
|
2926
|
+
)
|
|
2927
|
+
]
|
|
2928
|
+
}
|
|
2929
|
+
),
|
|
2930
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "tekiyo-step-labels", children: options.map((option, index) => {
|
|
2931
|
+
const labelPercent = index * stepPercent;
|
|
2932
|
+
const isSelected = currentIndex === index;
|
|
2933
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
2934
|
+
"span",
|
|
2935
|
+
{
|
|
2936
|
+
className: `tekiyo-step-label ${isSelected ? "selected" : ""}`,
|
|
2937
|
+
style: {
|
|
2938
|
+
left: `${labelPercent}%`,
|
|
2939
|
+
color: isSelected ? option.color || "#007aff" : void 0
|
|
2940
|
+
},
|
|
2941
|
+
onClick: () => handleStepClick(index),
|
|
2942
|
+
children: option.label
|
|
2943
|
+
},
|
|
2944
|
+
option.value
|
|
2945
|
+
);
|
|
2946
|
+
}) })
|
|
2947
|
+
] });
|
|
2948
|
+
}
|
|
2949
|
+
function PhysicsVerticalSlider({
|
|
2950
|
+
label,
|
|
2951
|
+
icon,
|
|
2952
|
+
value,
|
|
2953
|
+
min,
|
|
2954
|
+
max,
|
|
2955
|
+
step,
|
|
2956
|
+
config,
|
|
2957
|
+
onChange,
|
|
2958
|
+
height = 160,
|
|
2959
|
+
color = "#007aff",
|
|
2960
|
+
className = ""
|
|
2961
|
+
}) {
|
|
2962
|
+
const trackRef = React.useRef(null);
|
|
2963
|
+
const state = React.useRef({
|
|
2964
|
+
isDragging: false,
|
|
2965
|
+
displayPercent: (value - min) / (max - min) * 100,
|
|
2966
|
+
thumbScale: 1,
|
|
2967
|
+
horizontalOffset: 0,
|
|
2968
|
+
stretchX: 1,
|
|
2969
|
+
stretchY: 1,
|
|
2970
|
+
stretchOriginPercent: 50,
|
|
2971
|
+
targetOriginPercent: 50,
|
|
2972
|
+
shadowX: 0,
|
|
2973
|
+
shadowY: 2,
|
|
2974
|
+
shadowBlur: 6,
|
|
2975
|
+
shadowOpacity: 0.12,
|
|
2976
|
+
lastPercent: (value - min) / (max - min) * 100,
|
|
2977
|
+
pointerStart: null
|
|
2978
|
+
});
|
|
2979
|
+
const [, forceRender] = React.useState(0);
|
|
2980
|
+
const rerender = React.useCallback(() => forceRender((n) => n + 1), []);
|
|
2981
|
+
const physicsIdRef = React.useRef(generatePhysicsId("vslider"));
|
|
2982
|
+
const scaleIdRef = React.useRef(generatePhysicsId("vscale"));
|
|
2983
|
+
const decayIdRef = React.useRef(generatePhysicsId("vdecay"));
|
|
2984
|
+
const relaxIdRef = React.useRef(generatePhysicsId("vrelax"));
|
|
2985
|
+
const horizontalIdRef = React.useRef(generatePhysicsId("vhorizontal"));
|
|
2986
|
+
const springRef = React.useRef(null);
|
|
2987
|
+
const scaleSpringRef = React.useRef(null);
|
|
2988
|
+
const horizontalSpringRef = React.useRef(null);
|
|
2989
|
+
React.useEffect(() => {
|
|
2990
|
+
if (!state.current.isDragging) {
|
|
2991
|
+
const targetPercent = (value - min) / (max - min) * 100;
|
|
2992
|
+
animateToPercent(targetPercent);
|
|
2993
|
+
}
|
|
2994
|
+
}, [value, min, max]);
|
|
2995
|
+
const animateToPercent = React.useCallback(
|
|
2996
|
+
(targetPercent) => {
|
|
2997
|
+
const s2 = state.current;
|
|
2998
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
2999
|
+
springRef.current = new Spring1D(s2.displayPercent, {
|
|
3000
|
+
tension: config.springTension * 400,
|
|
3001
|
+
friction: 26,
|
|
3002
|
+
mass: 1,
|
|
3003
|
+
precision: 0.01
|
|
3004
|
+
});
|
|
3005
|
+
springRef.current.setTarget(targetPercent);
|
|
3006
|
+
PhysicsEngine.register(physicsIdRef.current, (dt) => {
|
|
3007
|
+
if (!springRef.current) return;
|
|
3008
|
+
const result = springRef.current.step(dt);
|
|
3009
|
+
s2.displayPercent = result.value;
|
|
3010
|
+
rerender();
|
|
3011
|
+
if (result.isSettled) {
|
|
3012
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
3013
|
+
}
|
|
3014
|
+
});
|
|
3015
|
+
},
|
|
3016
|
+
[config.springTension, rerender]
|
|
3017
|
+
);
|
|
3018
|
+
const percentToValue = React.useCallback(
|
|
3019
|
+
(percent) => {
|
|
3020
|
+
const raw = min + percent / 100 * (max - min);
|
|
3021
|
+
return Math.round(raw / step) * step;
|
|
3022
|
+
},
|
|
3023
|
+
[min, max, step]
|
|
3024
|
+
);
|
|
3025
|
+
const animateScaleTo = React.useCallback(
|
|
3026
|
+
(targetScale) => {
|
|
3027
|
+
const s2 = state.current;
|
|
3028
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
3029
|
+
scaleSpringRef.current = new Spring1D(s2.thumbScale, {
|
|
3030
|
+
tension: 400,
|
|
3031
|
+
friction: 20,
|
|
3032
|
+
mass: 0.6,
|
|
3033
|
+
precision: 1e-3
|
|
3034
|
+
});
|
|
3035
|
+
scaleSpringRef.current.setTarget(targetScale);
|
|
3036
|
+
PhysicsEngine.register(scaleIdRef.current, (dt) => {
|
|
3037
|
+
if (!scaleSpringRef.current) return;
|
|
3038
|
+
const result = scaleSpringRef.current.step(dt);
|
|
3039
|
+
s2.thumbScale = result.value;
|
|
3040
|
+
rerender();
|
|
3041
|
+
if (result.isSettled) {
|
|
3042
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
3043
|
+
}
|
|
3044
|
+
});
|
|
3045
|
+
},
|
|
3046
|
+
[rerender]
|
|
3047
|
+
);
|
|
3048
|
+
const animateStretchRelax = React.useCallback(() => {
|
|
3049
|
+
const s2 = state.current;
|
|
3050
|
+
PhysicsEngine.unregister(relaxIdRef.current);
|
|
3051
|
+
const spring = new Spring1D(s2.stretchY, {
|
|
3052
|
+
tension: 300,
|
|
3053
|
+
friction: 14,
|
|
3054
|
+
mass: 0.5,
|
|
3055
|
+
precision: 1e-3
|
|
3056
|
+
});
|
|
3057
|
+
spring.setTarget(1);
|
|
3058
|
+
PhysicsEngine.register(relaxIdRef.current, (dt) => {
|
|
3059
|
+
const result = spring.step(dt);
|
|
3060
|
+
s2.stretchY = result.value;
|
|
3061
|
+
s2.stretchX = 1 - (result.value - 1) * 0.4;
|
|
3062
|
+
s2.stretchOriginPercent += (50 - s2.stretchOriginPercent) * 0.15;
|
|
3063
|
+
s2.shadowY += (2 - s2.shadowY) * 0.15;
|
|
3064
|
+
s2.shadowBlur += (6 - s2.shadowBlur) * 0.15;
|
|
3065
|
+
s2.shadowOpacity += (0.12 - s2.shadowOpacity) * 0.15;
|
|
3066
|
+
rerender();
|
|
3067
|
+
if (result.isSettled) {
|
|
3068
|
+
s2.stretchX = 1;
|
|
3069
|
+
s2.stretchY = 1;
|
|
3070
|
+
s2.stretchOriginPercent = 50;
|
|
3071
|
+
PhysicsEngine.unregister(relaxIdRef.current);
|
|
3072
|
+
}
|
|
3073
|
+
});
|
|
3074
|
+
}, [rerender]);
|
|
3075
|
+
const handlePointerDown = React.useCallback(
|
|
3076
|
+
(e) => {
|
|
3077
|
+
if (!trackRef.current) return;
|
|
3078
|
+
const s2 = state.current;
|
|
3079
|
+
e.preventDefault();
|
|
3080
|
+
e.target.setPointerCapture(e.pointerId);
|
|
3081
|
+
s2.pointerStart = { x: e.clientX, y: e.clientY };
|
|
3082
|
+
s2.isDragging = true;
|
|
3083
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
3084
|
+
PhysicsEngine.unregister(horizontalIdRef.current);
|
|
3085
|
+
PhysicsEngine.unregister(relaxIdRef.current);
|
|
3086
|
+
animateScaleTo(config.liftScale);
|
|
3087
|
+
PhysicsEngine.register(decayIdRef.current, () => {
|
|
3088
|
+
if (!s2.isDragging) return;
|
|
3089
|
+
const decayLerp = 0.08;
|
|
3090
|
+
const threshold = 1e-3;
|
|
3091
|
+
if (Math.abs(s2.stretchY - 1) > threshold) {
|
|
3092
|
+
s2.stretchY += (1 - s2.stretchY) * decayLerp;
|
|
3093
|
+
}
|
|
3094
|
+
if (Math.abs(s2.stretchX - 1) > threshold) {
|
|
3095
|
+
s2.stretchX += (1 - s2.stretchX) * decayLerp;
|
|
3096
|
+
}
|
|
3097
|
+
if (Math.abs(s2.stretchOriginPercent - 50) > threshold) {
|
|
3098
|
+
s2.stretchOriginPercent += (50 - s2.stretchOriginPercent) * decayLerp;
|
|
3099
|
+
}
|
|
3100
|
+
s2.shadowY += (2 - s2.shadowY) * decayLerp;
|
|
3101
|
+
s2.shadowBlur += (6 - s2.shadowBlur) * decayLerp;
|
|
3102
|
+
s2.shadowOpacity += (0.12 - s2.shadowOpacity) * decayLerp;
|
|
3103
|
+
rerender();
|
|
3104
|
+
});
|
|
3105
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
3106
|
+
const y = Math.max(0, Math.min(rect.height, e.clientY - rect.top));
|
|
3107
|
+
const percent = 100 - y / rect.height * 100;
|
|
3108
|
+
s2.displayPercent = percent;
|
|
3109
|
+
s2.lastPercent = percent;
|
|
3110
|
+
onChange(percentToValue(percent));
|
|
3111
|
+
rerender();
|
|
3112
|
+
},
|
|
3113
|
+
[config.liftScale, animateScaleTo, percentToValue, onChange, rerender]
|
|
3114
|
+
);
|
|
3115
|
+
const handlePointerMove = React.useCallback(
|
|
3116
|
+
(e) => {
|
|
3117
|
+
const s2 = state.current;
|
|
3118
|
+
if (!s2.isDragging || !trackRef.current || !s2.pointerStart) return;
|
|
3119
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
3120
|
+
const rawY = e.clientY - rect.top;
|
|
3121
|
+
const rawPercent = 100 - rawY / rect.height * 100;
|
|
3122
|
+
let percent = rawPercent;
|
|
3123
|
+
let edgeStretchV = 0;
|
|
3124
|
+
if (rawPercent < 0) {
|
|
3125
|
+
edgeStretchV = -rawPercent / 100;
|
|
3126
|
+
percent = rawPercent * 0.15;
|
|
3127
|
+
s2.targetOriginPercent = 0;
|
|
3128
|
+
} else if (rawPercent > 100) {
|
|
3129
|
+
edgeStretchV = (rawPercent - 100) / 100;
|
|
3130
|
+
percent = 100 + (rawPercent - 100) * 0.15;
|
|
3131
|
+
s2.targetOriginPercent = 100;
|
|
3132
|
+
}
|
|
3133
|
+
const velocity = percent - s2.lastPercent;
|
|
3134
|
+
s2.lastPercent = percent;
|
|
3135
|
+
if (Math.abs(velocity) > 0.3 && edgeStretchV === 0) {
|
|
3136
|
+
s2.targetOriginPercent = velocity > 0 ? 0 : 100;
|
|
3137
|
+
}
|
|
3138
|
+
s2.stretchOriginPercent += (s2.targetOriginPercent - s2.stretchOriginPercent) * 0.15;
|
|
3139
|
+
const rawHorizontalOffset = e.clientX - s2.pointerStart.x;
|
|
3140
|
+
const rubberBandedOffset = rawHorizontalOffset * 0.06;
|
|
3141
|
+
s2.horizontalOffset = Math.max(-5, Math.min(5, rubberBandedOffset));
|
|
3142
|
+
const absVelocity = Math.abs(velocity);
|
|
3143
|
+
const velStretch = Math.min(absVelocity * config.velocityStretch * 15, 1.2);
|
|
3144
|
+
const edgeStretchFactorV = Math.min(Math.abs(edgeStretchV) * 0.6, 0.12);
|
|
3145
|
+
const edgeStretchFactorH = Math.abs(s2.horizontalOffset) / 5 * 0.06;
|
|
3146
|
+
const targetStretchY = 1 + velStretch + edgeStretchFactorV;
|
|
3147
|
+
const targetStretchX = 1 - velStretch * 0.4 + edgeStretchFactorH;
|
|
3148
|
+
const baseLerp = 0.35;
|
|
3149
|
+
const decayLerp = 0.15;
|
|
3150
|
+
const velocityThreshold = 0.5;
|
|
3151
|
+
if (absVelocity < velocityThreshold) {
|
|
3152
|
+
const decayFactor = 1 - absVelocity / velocityThreshold;
|
|
3153
|
+
s2.stretchY += (1 - s2.stretchY) * decayLerp * decayFactor;
|
|
3154
|
+
s2.stretchX += (1 - s2.stretchX) * decayLerp * decayFactor;
|
|
3155
|
+
s2.stretchOriginPercent += (50 - s2.stretchOriginPercent) * decayLerp * decayFactor;
|
|
3156
|
+
}
|
|
3157
|
+
s2.stretchY += (targetStretchY - s2.stretchY) * baseLerp;
|
|
3158
|
+
s2.stretchX += (targetStretchX - s2.stretchX) * baseLerp;
|
|
3159
|
+
const targetShadowY = 2 + absVelocity * 0.4;
|
|
3160
|
+
const targetShadowBlur = 6 + absVelocity * 0.6;
|
|
3161
|
+
const targetShadowOpacity = 0.12 + absVelocity * 0.015;
|
|
3162
|
+
s2.shadowY += (targetShadowY - s2.shadowY) * 0.3;
|
|
3163
|
+
s2.shadowBlur += (targetShadowBlur - s2.shadowBlur) * 0.3;
|
|
3164
|
+
s2.shadowOpacity += (targetShadowOpacity - s2.shadowOpacity) * 0.3;
|
|
3165
|
+
s2.displayPercent = Math.max(0, Math.min(100, percent));
|
|
3166
|
+
onChange(percentToValue(Math.max(0, Math.min(100, percent))));
|
|
3167
|
+
rerender();
|
|
3168
|
+
},
|
|
3169
|
+
[config.velocityStretch, percentToValue, onChange, rerender]
|
|
3170
|
+
);
|
|
3171
|
+
const handlePointerUp = React.useCallback(() => {
|
|
3172
|
+
const s2 = state.current;
|
|
3173
|
+
if (!s2.isDragging) return;
|
|
3174
|
+
s2.isDragging = false;
|
|
3175
|
+
s2.pointerStart = null;
|
|
3176
|
+
PhysicsEngine.unregister(decayIdRef.current);
|
|
3177
|
+
animateScaleTo(1);
|
|
3178
|
+
animateStretchRelax();
|
|
3179
|
+
if (s2.horizontalOffset !== 0) {
|
|
3180
|
+
PhysicsEngine.unregister(horizontalIdRef.current);
|
|
3181
|
+
horizontalSpringRef.current = new Spring1D(s2.horizontalOffset, {
|
|
3182
|
+
tension: 400,
|
|
3183
|
+
friction: 18,
|
|
3184
|
+
mass: 0.5,
|
|
3185
|
+
precision: 0.1
|
|
3186
|
+
});
|
|
3187
|
+
horizontalSpringRef.current.setTarget(0);
|
|
3188
|
+
PhysicsEngine.register(horizontalIdRef.current, (dt) => {
|
|
3189
|
+
if (!horizontalSpringRef.current) return;
|
|
3190
|
+
const result = horizontalSpringRef.current.step(dt);
|
|
3191
|
+
s2.horizontalOffset = result.value;
|
|
3192
|
+
rerender();
|
|
3193
|
+
if (result.isSettled) {
|
|
3194
|
+
s2.horizontalOffset = 0;
|
|
3195
|
+
PhysicsEngine.unregister(horizontalIdRef.current);
|
|
3196
|
+
}
|
|
3197
|
+
});
|
|
3198
|
+
}
|
|
3199
|
+
rerender();
|
|
3200
|
+
}, [animateScaleTo, animateStretchRelax, rerender]);
|
|
3201
|
+
const handleTrackClick = React.useCallback(
|
|
3202
|
+
(e) => {
|
|
3203
|
+
const s2 = state.current;
|
|
3204
|
+
if (!trackRef.current || s2.isDragging) return;
|
|
3205
|
+
const rect = trackRef.current.getBoundingClientRect();
|
|
3206
|
+
const y = e.clientY - rect.top;
|
|
3207
|
+
const percent = 100 - Math.max(0, Math.min(100, y / rect.height * 100));
|
|
3208
|
+
onChange(percentToValue(percent));
|
|
3209
|
+
animateToPercent(percent);
|
|
3210
|
+
},
|
|
3211
|
+
[percentToValue, onChange, animateToPercent]
|
|
3212
|
+
);
|
|
3213
|
+
React.useEffect(() => {
|
|
3214
|
+
return () => {
|
|
3215
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
3216
|
+
PhysicsEngine.unregister(scaleIdRef.current);
|
|
3217
|
+
PhysicsEngine.unregister(decayIdRef.current);
|
|
3218
|
+
PhysicsEngine.unregister(relaxIdRef.current);
|
|
3219
|
+
PhysicsEngine.unregister(horizontalIdRef.current);
|
|
3220
|
+
};
|
|
3221
|
+
}, []);
|
|
3222
|
+
const s = state.current;
|
|
3223
|
+
const displayValue = Math.round(value);
|
|
3224
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `tekiyo-vertical-slider ${className}`, style: { height }, children: [
|
|
3225
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
3226
|
+
"div",
|
|
3227
|
+
{
|
|
3228
|
+
className: "tekiyo-vertical-track",
|
|
3229
|
+
ref: trackRef,
|
|
3230
|
+
onClick: handleTrackClick,
|
|
3231
|
+
onPointerMove: handlePointerMove,
|
|
3232
|
+
onPointerUp: handlePointerUp,
|
|
3233
|
+
onPointerCancel: handlePointerUp,
|
|
3234
|
+
style: { height },
|
|
3235
|
+
children: [
|
|
3236
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "tekiyo-vertical-track-bg" }),
|
|
3237
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3238
|
+
"div",
|
|
3239
|
+
{
|
|
3240
|
+
className: "tekiyo-vertical-track-fill",
|
|
3241
|
+
style: {
|
|
3242
|
+
height: `${s.displayPercent}%`,
|
|
3243
|
+
background: color
|
|
3244
|
+
}
|
|
3245
|
+
}
|
|
3246
|
+
),
|
|
3247
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3248
|
+
"div",
|
|
3249
|
+
{
|
|
3250
|
+
className: `tekiyo-slider-thumb tekiyo-vertical-thumb ${s.isDragging ? "active" : ""}`,
|
|
3251
|
+
style: {
|
|
3252
|
+
bottom: `${s.displayPercent}%`,
|
|
3253
|
+
transform: `translate(calc(-50% + ${s.horizontalOffset}px), 50%) scale(${s.thumbScale}) scaleX(${s.stretchX}) scaleY(${s.stretchY})`,
|
|
3254
|
+
transformOrigin: `center ${s.stretchOriginPercent}%`,
|
|
3255
|
+
boxShadow: `0 ${s.shadowY}px ${s.shadowBlur}px rgba(0, 0, 0, ${s.shadowOpacity})`
|
|
3256
|
+
},
|
|
3257
|
+
onPointerDown: handlePointerDown
|
|
3258
|
+
}
|
|
3259
|
+
)
|
|
3260
|
+
]
|
|
3261
|
+
}
|
|
3262
|
+
),
|
|
3263
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "tekiyo-vertical-slider-info", children: [
|
|
3264
|
+
icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "tekiyo-vertical-slider-icon", style: { color }, children: icon }),
|
|
3265
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "tekiyo-vertical-slider-value", style: { color }, children: [
|
|
3266
|
+
displayValue,
|
|
3267
|
+
"%"
|
|
3268
|
+
] }),
|
|
3269
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "tekiyo-vertical-slider-label", children: label })
|
|
3270
|
+
] })
|
|
3271
|
+
] });
|
|
3272
|
+
}
|
|
3273
|
+
const sliderPresets = [
|
|
3274
|
+
{
|
|
3275
|
+
name: "Buttery Smooth",
|
|
3276
|
+
description: "iOS classic feel",
|
|
3277
|
+
config: {
|
|
3278
|
+
springTension: 0.8,
|
|
3279
|
+
velocityStretch: 0.02,
|
|
3280
|
+
liftScale: 1.5,
|
|
3281
|
+
flickMomentum: 800,
|
|
3282
|
+
maxVelocity: 3
|
|
3283
|
+
}
|
|
3284
|
+
},
|
|
3285
|
+
{
|
|
3286
|
+
name: "Snappy",
|
|
3287
|
+
description: "Responsive & precise",
|
|
3288
|
+
config: {
|
|
3289
|
+
springTension: 1.4,
|
|
3290
|
+
velocityStretch: 0.01,
|
|
3291
|
+
liftScale: 1.05,
|
|
3292
|
+
flickMomentum: 400,
|
|
3293
|
+
maxVelocity: 5
|
|
3294
|
+
}
|
|
3295
|
+
},
|
|
3296
|
+
{
|
|
3297
|
+
name: "Playful",
|
|
3298
|
+
description: "Fun & elastic",
|
|
3299
|
+
config: {
|
|
3300
|
+
springTension: 0.5,
|
|
3301
|
+
velocityStretch: 0.08,
|
|
3302
|
+
liftScale: 1.5,
|
|
3303
|
+
flickMomentum: 1500,
|
|
3304
|
+
maxVelocity: 6
|
|
3305
|
+
}
|
|
3306
|
+
}
|
|
3307
|
+
];
|
|
3308
|
+
const defaultSliderConfig = sliderPresets[0].config;
|
|
3309
|
+
function SegmentedControl({
|
|
3310
|
+
options,
|
|
3311
|
+
defaultIndex = 0,
|
|
3312
|
+
value,
|
|
3313
|
+
config,
|
|
3314
|
+
showIndicator,
|
|
3315
|
+
onChange,
|
|
3316
|
+
className = ""
|
|
3317
|
+
}) {
|
|
3318
|
+
const [, forceRender] = React.useState(0);
|
|
3319
|
+
const rerender = React.useCallback(() => forceRender((n) => n + 1), []);
|
|
3320
|
+
const initialIndex = value !== void 0 ? value : defaultIndex;
|
|
3321
|
+
const state = React.useRef({
|
|
3322
|
+
selected: initialIndex,
|
|
3323
|
+
indicatorPos: initialIndex,
|
|
3324
|
+
stretchX: 1,
|
|
3325
|
+
stretchY: 1,
|
|
3326
|
+
pressScale: 1,
|
|
3327
|
+
verticalOffset: 0,
|
|
3328
|
+
isDragging: false,
|
|
3329
|
+
isPressed: false,
|
|
3330
|
+
stretchOriginPercent: 50,
|
|
3331
|
+
targetOriginPercent: 50,
|
|
3332
|
+
velocity: 0,
|
|
3333
|
+
shadowY: 4,
|
|
3334
|
+
shadowBlur: 12,
|
|
3335
|
+
shadowOpacity: 0.15
|
|
3336
|
+
});
|
|
3337
|
+
const containerRef = React.useRef(null);
|
|
3338
|
+
const springRef = React.useRef(null);
|
|
3339
|
+
const pressSpringRef = React.useRef(null);
|
|
3340
|
+
const verticalSpringRef = React.useRef(null);
|
|
3341
|
+
const stretchOriginSpringRef = React.useRef(null);
|
|
3342
|
+
const physicsId = React.useRef(generatePhysicsId("seg"));
|
|
3343
|
+
const pressId = React.useRef(generatePhysicsId("press"));
|
|
3344
|
+
const verticalId = React.useRef(generatePhysicsId("vert"));
|
|
3345
|
+
const stretchOriginId = React.useRef(generatePhysicsId("segOrigin"));
|
|
3346
|
+
const pointerStart = React.useRef(null);
|
|
3347
|
+
const lastPos = React.useRef(initialIndex);
|
|
3348
|
+
const hasDragged = React.useRef(false);
|
|
3349
|
+
const velocitySamples = React.useRef([]);
|
|
3350
|
+
React.useEffect(() => {
|
|
3351
|
+
if (value !== void 0 && value !== state.current.selected && !state.current.isDragging) {
|
|
3352
|
+
animateTo(value);
|
|
3353
|
+
}
|
|
3354
|
+
}, [value]);
|
|
3355
|
+
const getIndexFromX = React.useCallback((clientX) => {
|
|
3356
|
+
if (!containerRef.current) return state.current.selected;
|
|
3357
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
3358
|
+
const x = Math.max(0, Math.min(rect.width, clientX - rect.left));
|
|
3359
|
+
return Math.min(options.length - 1, Math.floor(x / rect.width * options.length));
|
|
3360
|
+
}, [options.length]);
|
|
3361
|
+
const getPosFromX = React.useCallback((clientX, allowOverdrag = false) => {
|
|
3362
|
+
if (!containerRef.current) return state.current.indicatorPos;
|
|
3363
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
3364
|
+
const x = clientX - rect.left;
|
|
3365
|
+
const rel = allowOverdrag ? x / rect.width : Math.max(0, Math.min(1, x / rect.width));
|
|
3366
|
+
return rel * (options.length - 1);
|
|
3367
|
+
}, [options.length]);
|
|
3368
|
+
const animateTo = React.useCallback((target, initialVelocity = 0) => {
|
|
3369
|
+
const s2 = state.current;
|
|
3370
|
+
s2.targetOriginPercent = target > s2.indicatorPos ? 0 : 100;
|
|
3371
|
+
s2.selected = target;
|
|
3372
|
+
PhysicsEngine.unregister(physicsId.current);
|
|
3373
|
+
PhysicsEngine.unregister(stretchOriginId.current);
|
|
3374
|
+
if (s2.pressScale !== 1) {
|
|
3375
|
+
PhysicsEngine.unregister(pressId.current);
|
|
3376
|
+
const releaseSpring = new Spring1D(s2.pressScale, {
|
|
3377
|
+
tension: 200,
|
|
3378
|
+
friction: 18,
|
|
3379
|
+
mass: 0.8,
|
|
3380
|
+
precision: 0.01
|
|
3381
|
+
});
|
|
3382
|
+
releaseSpring.setTarget(1);
|
|
3383
|
+
PhysicsEngine.register(pressId.current, (dt2) => {
|
|
3384
|
+
const pr = releaseSpring.step(dt2);
|
|
3385
|
+
s2.pressScale = pr.value;
|
|
3386
|
+
rerender();
|
|
3387
|
+
if (pr.isSettled) {
|
|
3388
|
+
s2.pressScale = 1;
|
|
3389
|
+
PhysicsEngine.unregister(pressId.current);
|
|
3390
|
+
}
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
springRef.current = new Spring1D(s2.indicatorPos, {
|
|
3394
|
+
tension: config.springTension * 280,
|
|
3395
|
+
friction: 18,
|
|
3396
|
+
mass: 0.8,
|
|
3397
|
+
precision: 1e-3
|
|
3398
|
+
});
|
|
3399
|
+
springRef.current.setTarget(target);
|
|
3400
|
+
if (initialVelocity) springRef.current.setVelocity(initialVelocity);
|
|
3401
|
+
PhysicsEngine.register(physicsId.current, (dt) => {
|
|
3402
|
+
if (!springRef.current) return;
|
|
3403
|
+
const r = springRef.current.step(dt);
|
|
3404
|
+
const vel = Math.abs(r.velocity || 0);
|
|
3405
|
+
const stretch = Math.min(vel * config.velocityStretch * 6, 0.25);
|
|
3406
|
+
s2.indicatorPos = r.value;
|
|
3407
|
+
s2.velocity = vel;
|
|
3408
|
+
s2.stretchX = 1 + stretch;
|
|
3409
|
+
s2.stretchY = 1 - stretch * 0.5;
|
|
3410
|
+
s2.stretchOriginPercent += (s2.targetOriginPercent - s2.stretchOriginPercent) * 0.12;
|
|
3411
|
+
s2.shadowY = 4 + vel * 2;
|
|
3412
|
+
s2.shadowBlur = 12 + vel * 4;
|
|
3413
|
+
s2.shadowOpacity = 0.15 + vel * 0.05;
|
|
3414
|
+
rerender();
|
|
3415
|
+
if (r.isSettled) {
|
|
3416
|
+
s2.stretchX = 1;
|
|
3417
|
+
s2.stretchY = 1;
|
|
3418
|
+
s2.velocity = 0;
|
|
3419
|
+
s2.shadowY = 4;
|
|
3420
|
+
s2.shadowBlur = 12;
|
|
3421
|
+
s2.shadowOpacity = 0.15;
|
|
3422
|
+
PhysicsEngine.unregister(physicsId.current);
|
|
3423
|
+
stretchOriginSpringRef.current = new Spring1D(s2.stretchOriginPercent, {
|
|
3424
|
+
tension: 300,
|
|
3425
|
+
friction: 18,
|
|
3426
|
+
mass: 0.5,
|
|
3427
|
+
precision: 0.1
|
|
3428
|
+
});
|
|
3429
|
+
stretchOriginSpringRef.current.setTarget(50);
|
|
3430
|
+
PhysicsEngine.register(stretchOriginId.current, (dt2) => {
|
|
3431
|
+
if (!stretchOriginSpringRef.current) return;
|
|
3432
|
+
const or = stretchOriginSpringRef.current.step(dt2);
|
|
3433
|
+
s2.stretchOriginPercent = or.value;
|
|
3434
|
+
s2.targetOriginPercent = 50;
|
|
3435
|
+
rerender();
|
|
3436
|
+
if (or.isSettled) {
|
|
3437
|
+
s2.stretchOriginPercent = 50;
|
|
3438
|
+
s2.targetOriginPercent = 50;
|
|
3439
|
+
PhysicsEngine.unregister(stretchOriginId.current);
|
|
3440
|
+
}
|
|
3441
|
+
});
|
|
3442
|
+
rerender();
|
|
3443
|
+
}
|
|
3444
|
+
});
|
|
3445
|
+
onChange == null ? void 0 : onChange(target);
|
|
3446
|
+
}, [config.springTension, config.velocityStretch, onChange, rerender]);
|
|
3447
|
+
const animatePress = React.useCallback((pressed) => {
|
|
3448
|
+
const s2 = state.current;
|
|
3449
|
+
PhysicsEngine.unregister(pressId.current);
|
|
3450
|
+
pressSpringRef.current = new Spring1D(s2.pressScale, {
|
|
3451
|
+
tension: 400,
|
|
3452
|
+
friction: 22,
|
|
3453
|
+
mass: 0.6,
|
|
3454
|
+
precision: 0.01
|
|
3455
|
+
});
|
|
3456
|
+
pressSpringRef.current.setTarget(pressed ? 1.4 : 1);
|
|
3457
|
+
PhysicsEngine.register(pressId.current, (dt) => {
|
|
3458
|
+
if (!pressSpringRef.current) return;
|
|
3459
|
+
const r = pressSpringRef.current.step(dt);
|
|
3460
|
+
s2.pressScale = r.value;
|
|
3461
|
+
rerender();
|
|
3462
|
+
if (r.isSettled) PhysicsEngine.unregister(pressId.current);
|
|
3463
|
+
});
|
|
3464
|
+
}, [rerender]);
|
|
3465
|
+
const animateVerticalBack = React.useCallback(() => {
|
|
3466
|
+
const s2 = state.current;
|
|
3467
|
+
if (Math.abs(s2.verticalOffset) < 0.5) {
|
|
3468
|
+
s2.verticalOffset = 0;
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3471
|
+
PhysicsEngine.unregister(verticalId.current);
|
|
3472
|
+
verticalSpringRef.current = new Spring1D(s2.verticalOffset, {
|
|
3473
|
+
tension: 400,
|
|
3474
|
+
friction: 20,
|
|
3475
|
+
mass: 0.5,
|
|
3476
|
+
precision: 0.1
|
|
3477
|
+
});
|
|
3478
|
+
verticalSpringRef.current.setTarget(0);
|
|
3479
|
+
PhysicsEngine.register(verticalId.current, (dt) => {
|
|
3480
|
+
if (!verticalSpringRef.current) return;
|
|
3481
|
+
const r = verticalSpringRef.current.step(dt);
|
|
3482
|
+
s2.verticalOffset = r.value;
|
|
3483
|
+
rerender();
|
|
3484
|
+
if (r.isSettled) {
|
|
3485
|
+
s2.verticalOffset = 0;
|
|
3486
|
+
PhysicsEngine.unregister(verticalId.current);
|
|
3487
|
+
}
|
|
3488
|
+
});
|
|
3489
|
+
}, [rerender]);
|
|
3490
|
+
const animateOriginBack = React.useCallback(() => {
|
|
3491
|
+
const s2 = state.current;
|
|
3492
|
+
if (Math.abs(s2.stretchOriginPercent - 50) < 1) {
|
|
3493
|
+
s2.stretchOriginPercent = 50;
|
|
3494
|
+
s2.targetOriginPercent = 50;
|
|
3495
|
+
return;
|
|
3496
|
+
}
|
|
3497
|
+
PhysicsEngine.unregister(stretchOriginId.current);
|
|
3498
|
+
stretchOriginSpringRef.current = new Spring1D(s2.stretchOriginPercent, {
|
|
3499
|
+
tension: 300,
|
|
3500
|
+
friction: 18,
|
|
3501
|
+
mass: 0.5,
|
|
3502
|
+
precision: 0.1
|
|
3503
|
+
});
|
|
3504
|
+
stretchOriginSpringRef.current.setTarget(50);
|
|
3505
|
+
PhysicsEngine.register(stretchOriginId.current, (dt) => {
|
|
3506
|
+
if (!stretchOriginSpringRef.current) return;
|
|
3507
|
+
const r = stretchOriginSpringRef.current.step(dt);
|
|
3508
|
+
s2.stretchOriginPercent = r.value;
|
|
3509
|
+
s2.targetOriginPercent = 50;
|
|
3510
|
+
rerender();
|
|
3511
|
+
if (r.isSettled) {
|
|
3512
|
+
s2.stretchOriginPercent = 50;
|
|
3513
|
+
s2.targetOriginPercent = 50;
|
|
3514
|
+
PhysicsEngine.unregister(stretchOriginId.current);
|
|
3515
|
+
}
|
|
3516
|
+
});
|
|
3517
|
+
}, [rerender]);
|
|
3518
|
+
const onPointerDown = React.useCallback((e) => {
|
|
3519
|
+
const s2 = state.current;
|
|
3520
|
+
pointerStart.current = { x: e.clientX, y: e.clientY };
|
|
3521
|
+
hasDragged.current = false;
|
|
3522
|
+
velocitySamples.current = [];
|
|
3523
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
3524
|
+
PhysicsEngine.unregister(stretchOriginId.current);
|
|
3525
|
+
if (containerRef.current) {
|
|
3526
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
3527
|
+
const segW = rect.width / options.length;
|
|
3528
|
+
const indLeft = rect.left + s2.indicatorPos * segW;
|
|
3529
|
+
const grabX = e.clientX - indLeft;
|
|
3530
|
+
const pct = Math.max(0, Math.min(100, grabX / segW * 100));
|
|
3531
|
+
s2.targetOriginPercent = pct;
|
|
3532
|
+
s2.stretchOriginPercent = pct;
|
|
3533
|
+
}
|
|
3534
|
+
s2.isPressed = true;
|
|
3535
|
+
animatePress(true);
|
|
3536
|
+
lastPos.current = getPosFromX(e.clientX);
|
|
3537
|
+
rerender();
|
|
3538
|
+
}, [options.length, getPosFromX, animatePress, rerender]);
|
|
3539
|
+
const onPointerMove = React.useCallback((e) => {
|
|
3540
|
+
if (!pointerStart.current) return;
|
|
3541
|
+
const s2 = state.current;
|
|
3542
|
+
const dx = e.clientX - pointerStart.current.x;
|
|
3543
|
+
const dy = e.clientY - pointerStart.current.y;
|
|
3544
|
+
if (!hasDragged.current && (Math.abs(dx) > 5 || Math.abs(dy) > 5)) {
|
|
3545
|
+
hasDragged.current = true;
|
|
3546
|
+
s2.isDragging = true;
|
|
3547
|
+
PhysicsEngine.unregister(physicsId.current);
|
|
3548
|
+
}
|
|
3549
|
+
if (!hasDragged.current) return;
|
|
3550
|
+
let pos = getPosFromX(e.clientX, true);
|
|
3551
|
+
const minPos = 0;
|
|
3552
|
+
const maxPos = options.length - 1;
|
|
3553
|
+
let edgeStretch = 0;
|
|
3554
|
+
if (pos < minPos) {
|
|
3555
|
+
const over = minPos - pos;
|
|
3556
|
+
edgeStretch = -over;
|
|
3557
|
+
pos = minPos - over * 0.15;
|
|
3558
|
+
s2.targetOriginPercent = 100;
|
|
3559
|
+
} else if (pos > maxPos) {
|
|
3560
|
+
const over = pos - maxPos;
|
|
3561
|
+
edgeStretch = over;
|
|
3562
|
+
pos = maxPos + over * 0.15;
|
|
3563
|
+
s2.targetOriginPercent = 0;
|
|
3564
|
+
}
|
|
3565
|
+
const vel = pos - lastPos.current;
|
|
3566
|
+
if (Math.abs(vel) > 0.01 && edgeStretch === 0) {
|
|
3567
|
+
s2.targetOriginPercent = vel > 0 ? 100 : 0;
|
|
3568
|
+
}
|
|
3569
|
+
s2.stretchOriginPercent += (s2.targetOriginPercent - s2.stretchOriginPercent) * 0.15;
|
|
3570
|
+
velocitySamples.current.push(vel);
|
|
3571
|
+
if (velocitySamples.current.length > 5) velocitySamples.current.shift();
|
|
3572
|
+
lastPos.current = pos;
|
|
3573
|
+
const vOff = Math.max(-12, Math.min(12, dy * 0.12));
|
|
3574
|
+
s2.verticalOffset = vOff;
|
|
3575
|
+
const vFactor = Math.min(Math.abs(vel) * config.velocityStretch * 80, 0.6);
|
|
3576
|
+
const edgeFactor = Math.min(Math.abs(edgeStretch) * 0.2, 0.2);
|
|
3577
|
+
s2.stretchX = 1 + vFactor + edgeFactor;
|
|
3578
|
+
s2.stretchY = 1 - vFactor * 0.5 - edgeFactor * 0.4;
|
|
3579
|
+
s2.indicatorPos = pos;
|
|
3580
|
+
const idx = getIndexFromX(e.clientX);
|
|
3581
|
+
if (idx !== s2.selected) s2.selected = idx;
|
|
3582
|
+
rerender();
|
|
3583
|
+
}, [getPosFromX, getIndexFromX, options.length, config.velocityStretch, rerender]);
|
|
3584
|
+
const onPointerUp = React.useCallback((e) => {
|
|
3585
|
+
const s2 = state.current;
|
|
3586
|
+
pointerStart.current = null;
|
|
3587
|
+
s2.isPressed = false;
|
|
3588
|
+
if (!hasDragged.current) {
|
|
3589
|
+
const idx = getIndexFromX(e.clientX);
|
|
3590
|
+
if (idx !== s2.selected) {
|
|
3591
|
+
const dir = idx > s2.indicatorPos ? 1 : -1;
|
|
3592
|
+
animateTo(idx, dir * 3);
|
|
3593
|
+
} else {
|
|
3594
|
+
animatePress(false);
|
|
3595
|
+
animateOriginBack();
|
|
3596
|
+
}
|
|
3597
|
+
rerender();
|
|
3598
|
+
return;
|
|
3599
|
+
}
|
|
3600
|
+
s2.isDragging = false;
|
|
3601
|
+
hasDragged.current = false;
|
|
3602
|
+
animatePress(false);
|
|
3603
|
+
const avgVel = velocitySamples.current.length > 0 ? velocitySamples.current.reduce((a, b) => a + b, 0) / velocitySamples.current.length : 0;
|
|
3604
|
+
velocitySamples.current = [];
|
|
3605
|
+
animateVerticalBack();
|
|
3606
|
+
animateTo(s2.selected, avgVel * 0.5);
|
|
3607
|
+
}, [getIndexFromX, animateTo, animatePress, animateVerticalBack, animateOriginBack, rerender]);
|
|
3608
|
+
const onPointerLeave = React.useCallback(() => {
|
|
3609
|
+
if (pointerStart.current) {
|
|
3610
|
+
const s2 = state.current;
|
|
3611
|
+
pointerStart.current = null;
|
|
3612
|
+
s2.isPressed = false;
|
|
3613
|
+
s2.isDragging = false;
|
|
3614
|
+
hasDragged.current = false;
|
|
3615
|
+
velocitySamples.current = [];
|
|
3616
|
+
animatePress(false);
|
|
3617
|
+
animateVerticalBack();
|
|
3618
|
+
animateTo(s2.selected);
|
|
3619
|
+
}
|
|
3620
|
+
}, [animatePress, animateVerticalBack, animateTo]);
|
|
3621
|
+
React.useEffect(() => {
|
|
3622
|
+
return () => {
|
|
3623
|
+
PhysicsEngine.unregister(physicsId.current);
|
|
3624
|
+
PhysicsEngine.unregister(pressId.current);
|
|
3625
|
+
PhysicsEngine.unregister(verticalId.current);
|
|
3626
|
+
PhysicsEngine.unregister(stretchOriginId.current);
|
|
3627
|
+
};
|
|
3628
|
+
}, []);
|
|
3629
|
+
const s = state.current;
|
|
3630
|
+
const dragClass = s.isPressed || s.isDragging ? "dragging" : "";
|
|
3631
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3632
|
+
"div",
|
|
3633
|
+
{
|
|
3634
|
+
ref: containerRef,
|
|
3635
|
+
className: `tekiyo-segmented-control ${s.isDragging ? "dragging" : ""} ${className}`,
|
|
3636
|
+
onPointerDown,
|
|
3637
|
+
onPointerMove,
|
|
3638
|
+
onPointerUp,
|
|
3639
|
+
onPointerCancel: onPointerUp,
|
|
3640
|
+
onPointerLeave,
|
|
3641
|
+
children: [
|
|
3642
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3643
|
+
"div",
|
|
3644
|
+
{
|
|
3645
|
+
className: `tekiyo-segmented-indicator ${s.isPressed ? "active" : ""} ${dragClass}`,
|
|
3646
|
+
style: {
|
|
3647
|
+
width: `calc((100% - 6px) / ${options.length})`,
|
|
3648
|
+
left: `calc(${s.indicatorPos} * (100% - 6px) / ${options.length} + 3px)`,
|
|
3649
|
+
transform: `translateY(${s.verticalOffset}px) scaleX(${s.stretchX * s.pressScale}) scaleY(${s.stretchY * s.pressScale})`,
|
|
3650
|
+
transformOrigin: `${s.stretchOriginPercent}% center`
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
),
|
|
3654
|
+
options.map((option, index) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3655
|
+
"div",
|
|
3656
|
+
{
|
|
3657
|
+
className: `tekiyo-segmented-option ${s.selected === index ? "active" : ""} ${dragClass}`,
|
|
3658
|
+
children: [
|
|
3659
|
+
option,
|
|
3660
|
+
showIndicator && s.selected === index && /* @__PURE__ */ jsxRuntime.jsx("span", { className: `tekiyo-option-dot ${dragClass}` })
|
|
3661
|
+
]
|
|
3662
|
+
},
|
|
3663
|
+
option
|
|
3664
|
+
))
|
|
3665
|
+
]
|
|
3666
|
+
}
|
|
3667
|
+
);
|
|
3668
|
+
}
|
|
3669
|
+
const segmentedControlPresets = [
|
|
3670
|
+
{
|
|
3671
|
+
name: "Buttery Smooth",
|
|
3672
|
+
description: "iOS classic feel",
|
|
3673
|
+
config: {
|
|
3674
|
+
springTension: 0.8,
|
|
3675
|
+
velocityStretch: 0.02,
|
|
3676
|
+
liftScale: 1.5,
|
|
3677
|
+
flickMomentum: 800,
|
|
3678
|
+
maxVelocity: 3
|
|
3679
|
+
}
|
|
3680
|
+
},
|
|
3681
|
+
{
|
|
3682
|
+
name: "Snappy",
|
|
3683
|
+
description: "Responsive & precise",
|
|
3684
|
+
config: {
|
|
3685
|
+
springTension: 1.4,
|
|
3686
|
+
velocityStretch: 0.01,
|
|
3687
|
+
liftScale: 1.05,
|
|
3688
|
+
flickMomentum: 400,
|
|
3689
|
+
maxVelocity: 5
|
|
3690
|
+
}
|
|
3691
|
+
},
|
|
3692
|
+
{
|
|
3693
|
+
name: "Playful",
|
|
3694
|
+
description: "Fun & elastic",
|
|
3695
|
+
config: {
|
|
3696
|
+
springTension: 0.5,
|
|
3697
|
+
velocityStretch: 0.08,
|
|
3698
|
+
liftScale: 1.5,
|
|
3699
|
+
flickMomentum: 1500,
|
|
3700
|
+
maxVelocity: 6
|
|
3701
|
+
}
|
|
3702
|
+
}
|
|
3703
|
+
];
|
|
3704
|
+
const defaultSegmentedControlConfig = segmentedControlPresets[0].config;
|
|
3705
|
+
const DEFAULT_LIQUID_GLASS_CONFIG = {
|
|
3706
|
+
width: 200,
|
|
3707
|
+
height: 60,
|
|
3708
|
+
bezelWidth: 20,
|
|
3709
|
+
glassThickness: 0.5,
|
|
3710
|
+
refractiveIndex: 1.5,
|
|
3711
|
+
profile: "squircle",
|
|
3712
|
+
scale: 70
|
|
3713
|
+
};
|
|
3714
|
+
const mapCache = /* @__PURE__ */ new Map();
|
|
3715
|
+
function getCacheKey(config) {
|
|
3716
|
+
return `${config.width}-${config.height}-${config.bezelWidth}-${config.glassThickness}-${config.refractiveIndex}-${config.profile}`;
|
|
3717
|
+
}
|
|
3718
|
+
function preloadMaps(width, height, bezelWidth = 20, glassThickness = 0.5, refractiveIndex = 1.5) {
|
|
3719
|
+
const profiles = ["convex", "squircle", "concave", "lip"];
|
|
3720
|
+
profiles.forEach((profile) => {
|
|
3721
|
+
const config = {
|
|
3722
|
+
width,
|
|
3723
|
+
height,
|
|
3724
|
+
bezelWidth,
|
|
3725
|
+
glassThickness,
|
|
3726
|
+
refractiveIndex,
|
|
3727
|
+
profile
|
|
3728
|
+
};
|
|
3729
|
+
const key = getCacheKey(config);
|
|
3730
|
+
if (!mapCache.has(key)) {
|
|
3731
|
+
mapCache.set(key, {
|
|
3732
|
+
displacement: generateDisplacementMapUncached(config),
|
|
3733
|
+
specular: generateSpecularMapUncached(config)
|
|
3734
|
+
});
|
|
3735
|
+
}
|
|
3736
|
+
});
|
|
3737
|
+
}
|
|
3738
|
+
function getCachedMaps(config) {
|
|
3739
|
+
const key = getCacheKey(config);
|
|
3740
|
+
if (mapCache.has(key)) {
|
|
3741
|
+
return mapCache.get(key);
|
|
3742
|
+
}
|
|
3743
|
+
const maps = {
|
|
3744
|
+
displacement: generateDisplacementMapUncached(config),
|
|
3745
|
+
specular: generateSpecularMapUncached(config)
|
|
3746
|
+
};
|
|
3747
|
+
mapCache.set(key, maps);
|
|
3748
|
+
return maps;
|
|
3749
|
+
}
|
|
3750
|
+
function clearMapCache() {
|
|
3751
|
+
mapCache.clear();
|
|
3752
|
+
}
|
|
3753
|
+
function smootherstep(x) {
|
|
3754
|
+
const t = Math.max(0, Math.min(1, x));
|
|
3755
|
+
return t * t * t * (t * (t * 6 - 15) + 10);
|
|
3756
|
+
}
|
|
3757
|
+
function getProfileFunction(profile) {
|
|
3758
|
+
switch (profile) {
|
|
3759
|
+
case "convex":
|
|
3760
|
+
return (x) => Math.sqrt(Math.max(0, 1 - Math.pow(1 - x, 2)));
|
|
3761
|
+
case "squircle":
|
|
3762
|
+
return (x) => Math.pow(Math.max(0, 1 - Math.pow(1 - x, 4)), 0.25);
|
|
3763
|
+
case "concave":
|
|
3764
|
+
return (x) => 1 - Math.sqrt(Math.max(0, 1 - Math.pow(1 - x, 2)));
|
|
3765
|
+
case "lip":
|
|
3766
|
+
return (x) => {
|
|
3767
|
+
const convex = Math.sqrt(Math.max(0, 1 - Math.pow(1 - x, 2)));
|
|
3768
|
+
const concave = 1 - convex;
|
|
3769
|
+
const t = smootherstep(x);
|
|
3770
|
+
return convex * (1 - t) + concave * t;
|
|
3771
|
+
};
|
|
3772
|
+
default:
|
|
3773
|
+
return (x) => x;
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
function calculateNormal(f, x) {
|
|
3777
|
+
const delta = 1e-3;
|
|
3778
|
+
const x1 = Math.max(0, x - delta);
|
|
3779
|
+
const x2 = Math.min(1, x + delta);
|
|
3780
|
+
const y1 = f(x1);
|
|
3781
|
+
const y2 = f(x2);
|
|
3782
|
+
const derivative = (y2 - y1) / (x2 - x1);
|
|
3783
|
+
const length = Math.sqrt(derivative * derivative + 1);
|
|
3784
|
+
return {
|
|
3785
|
+
nx: -derivative / length,
|
|
3786
|
+
ny: 1 / length
|
|
3787
|
+
};
|
|
3788
|
+
}
|
|
3789
|
+
function applySnellLaw(incidentAngle, n1, n2) {
|
|
3790
|
+
const sinTheta1 = Math.sin(incidentAngle);
|
|
3791
|
+
const sinTheta2 = n1 / n2 * sinTheta1;
|
|
3792
|
+
if (Math.abs(sinTheta2) > 1) {
|
|
3793
|
+
return incidentAngle;
|
|
3794
|
+
}
|
|
3795
|
+
return Math.asin(sinTheta2);
|
|
3796
|
+
}
|
|
3797
|
+
function calculateDisplacement(normalizedDist, angle, profile, glassThickness, refractiveIndex) {
|
|
3798
|
+
if (normalizedDist >= 1) {
|
|
3799
|
+
return { dx: 0, dy: 0 };
|
|
3800
|
+
}
|
|
3801
|
+
const height = profile(normalizedDist) * glassThickness;
|
|
3802
|
+
const normal = calculateNormal(profile, normalizedDist);
|
|
3803
|
+
const incidentAngle = Math.atan2(normal.nx, normal.ny);
|
|
3804
|
+
const refractedAngle = applySnellLaw(incidentAngle, 1, refractiveIndex);
|
|
3805
|
+
const angleDiff = refractedAngle - incidentAngle;
|
|
3806
|
+
const magnitude = Math.sin(angleDiff) * height;
|
|
3807
|
+
const dx = magnitude * Math.cos(angle);
|
|
3808
|
+
const dy = magnitude * Math.sin(angle);
|
|
3809
|
+
return { dx, dy };
|
|
3810
|
+
}
|
|
3811
|
+
function vectorToRGB(dx, dy) {
|
|
3812
|
+
return {
|
|
3813
|
+
r: Math.round(128 + dx * 127),
|
|
3814
|
+
g: Math.round(128 + dy * 127),
|
|
3815
|
+
b: 128
|
|
3816
|
+
// Unused channel
|
|
3817
|
+
};
|
|
3818
|
+
}
|
|
3819
|
+
function distanceToRoundedRectEdge(x, y, width, height, cornerRadius) {
|
|
3820
|
+
const halfW = width / 2;
|
|
3821
|
+
const halfH = height / 2;
|
|
3822
|
+
const cx = x - halfW;
|
|
3823
|
+
const cy = y - halfH;
|
|
3824
|
+
const r = Math.min(cornerRadius, halfW, halfH);
|
|
3825
|
+
const absX = Math.abs(cx);
|
|
3826
|
+
const absY = Math.abs(cy);
|
|
3827
|
+
const cornerX = halfW - r;
|
|
3828
|
+
const cornerY = halfH - r;
|
|
3829
|
+
if (absX > cornerX && absY > cornerY) {
|
|
3830
|
+
const cornerCenterX = cornerX * Math.sign(cx);
|
|
3831
|
+
const cornerCenterY = cornerY * Math.sign(cy);
|
|
3832
|
+
const dx = cx - cornerCenterX;
|
|
3833
|
+
const dy = cy - cornerCenterY;
|
|
3834
|
+
const distFromCorner = Math.sqrt(dx * dx + dy * dy);
|
|
3835
|
+
const distToEdge = r - distFromCorner;
|
|
3836
|
+
const angle = Math.atan2(dy, dx);
|
|
3837
|
+
return { distance: Math.max(0, distToEdge), angle };
|
|
3838
|
+
}
|
|
3839
|
+
if (absX > absY * (halfW / halfH)) {
|
|
3840
|
+
const distToEdge = halfW - absX;
|
|
3841
|
+
const angle = cx > 0 ? 0 : Math.PI;
|
|
3842
|
+
return { distance: Math.max(0, distToEdge), angle };
|
|
3843
|
+
} else {
|
|
3844
|
+
const distToEdge = halfH - absY;
|
|
3845
|
+
const angle = cy > 0 ? Math.PI / 2 : -Math.PI / 2;
|
|
3846
|
+
return { distance: Math.max(0, distToEdge), angle };
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
function generateDisplacementMapUncached(config) {
|
|
3850
|
+
const { width, height, bezelWidth, glassThickness, refractiveIndex, profile } = config;
|
|
3851
|
+
const canvas = document.createElement("canvas");
|
|
3852
|
+
canvas.width = width;
|
|
3853
|
+
canvas.height = height;
|
|
3854
|
+
const ctx = canvas.getContext("2d");
|
|
3855
|
+
if (!ctx) {
|
|
3856
|
+
console.error("Failed to get canvas context");
|
|
3857
|
+
return "";
|
|
3858
|
+
}
|
|
3859
|
+
const profileFn = getProfileFunction(profile);
|
|
3860
|
+
const imageData = ctx.createImageData(width, height);
|
|
3861
|
+
const data = imageData.data;
|
|
3862
|
+
const cornerRadius = Math.min(width, height) / 2;
|
|
3863
|
+
for (let y = 0; y < height; y++) {
|
|
3864
|
+
for (let x = 0; x < width; x++) {
|
|
3865
|
+
const { distance, angle } = distanceToRoundedRectEdge(
|
|
3866
|
+
x,
|
|
3867
|
+
y,
|
|
3868
|
+
width,
|
|
3869
|
+
height,
|
|
3870
|
+
cornerRadius
|
|
3871
|
+
);
|
|
3872
|
+
const normalizedDist = Math.min(1, distance / bezelWidth);
|
|
3873
|
+
const { dx, dy } = calculateDisplacement(
|
|
3874
|
+
normalizedDist,
|
|
3875
|
+
angle,
|
|
3876
|
+
profileFn,
|
|
3877
|
+
glassThickness,
|
|
3878
|
+
refractiveIndex
|
|
3879
|
+
);
|
|
3880
|
+
const { r, g, b } = vectorToRGB(dx, dy);
|
|
3881
|
+
const idx = (y * width + x) * 4;
|
|
3882
|
+
data[idx] = r;
|
|
3883
|
+
data[idx + 1] = g;
|
|
3884
|
+
data[idx + 2] = b;
|
|
3885
|
+
data[idx + 3] = 255;
|
|
3886
|
+
}
|
|
3887
|
+
}
|
|
3888
|
+
ctx.putImageData(imageData, 0, 0);
|
|
3889
|
+
return canvas.toDataURL("image/png");
|
|
3890
|
+
}
|
|
3891
|
+
function generateSpecularMapUncached(config) {
|
|
3892
|
+
const { width, height, bezelWidth, glassThickness, profile } = config;
|
|
3893
|
+
const canvas = document.createElement("canvas");
|
|
3894
|
+
canvas.width = width;
|
|
3895
|
+
canvas.height = height;
|
|
3896
|
+
const ctx = canvas.getContext("2d");
|
|
3897
|
+
if (!ctx) {
|
|
3898
|
+
console.error("Failed to get canvas context");
|
|
3899
|
+
return "";
|
|
3900
|
+
}
|
|
3901
|
+
const profileFn = getProfileFunction(profile);
|
|
3902
|
+
const imageData = ctx.createImageData(width, height);
|
|
3903
|
+
const data = imageData.data;
|
|
3904
|
+
const cornerRadius = Math.min(width, height) / 2;
|
|
3905
|
+
for (let y = 0; y < height; y++) {
|
|
3906
|
+
for (let x = 0; x < width; x++) {
|
|
3907
|
+
const { distance } = distanceToRoundedRectEdge(
|
|
3908
|
+
x,
|
|
3909
|
+
y,
|
|
3910
|
+
width,
|
|
3911
|
+
height,
|
|
3912
|
+
cornerRadius
|
|
3913
|
+
);
|
|
3914
|
+
const normalizedDist = Math.min(1, distance / bezelWidth);
|
|
3915
|
+
let specularIntensity = 0;
|
|
3916
|
+
if (normalizedDist < 1) {
|
|
3917
|
+
const delta = 0.02;
|
|
3918
|
+
const h1 = profileFn(Math.max(0, normalizedDist - delta));
|
|
3919
|
+
const h2 = profileFn(Math.min(1, normalizedDist + delta));
|
|
3920
|
+
const gradient = Math.abs((h2 - h1) / (2 * delta));
|
|
3921
|
+
specularIntensity = Math.pow(gradient, 0.7) * glassThickness * 0.6;
|
|
3922
|
+
const rimStart = 0.25;
|
|
3923
|
+
if (normalizedDist < rimStart) {
|
|
3924
|
+
const rimT = 1 - normalizedDist / rimStart;
|
|
3925
|
+
const smoothRim = rimT * rimT * (3 - 2 * rimT);
|
|
3926
|
+
specularIntensity += smoothRim * 0.3;
|
|
3927
|
+
}
|
|
3928
|
+
specularIntensity = Math.min(1, specularIntensity);
|
|
3929
|
+
}
|
|
3930
|
+
const value = Math.round(specularIntensity * 255);
|
|
3931
|
+
const idx = (y * width + x) * 4;
|
|
3932
|
+
data[idx] = value;
|
|
3933
|
+
data[idx + 1] = value;
|
|
3934
|
+
data[idx + 2] = value;
|
|
3935
|
+
data[idx + 3] = 255;
|
|
3936
|
+
}
|
|
3937
|
+
}
|
|
3938
|
+
ctx.putImageData(imageData, 0, 0);
|
|
3939
|
+
return canvas.toDataURL("image/png");
|
|
3940
|
+
}
|
|
3941
|
+
function generateDisplacementMap(config) {
|
|
3942
|
+
return getCachedMaps(config).displacement;
|
|
3943
|
+
}
|
|
3944
|
+
function generateSpecularMap(config) {
|
|
3945
|
+
return getCachedMaps(config).specular;
|
|
3946
|
+
}
|
|
3947
|
+
function supportsBackdropSvgFilter() {
|
|
3948
|
+
if (typeof window === "undefined" || typeof navigator === "undefined") {
|
|
3949
|
+
return false;
|
|
3950
|
+
}
|
|
3951
|
+
const ua = navigator.userAgent;
|
|
3952
|
+
const isSafari = /Safari\//.test(ua) && !/Chrome\//.test(ua);
|
|
3953
|
+
const isFirefox = /Firefox\//.test(ua);
|
|
3954
|
+
const isChrome = /Chrome\//.test(ua) && !isSafari && !isFirefox;
|
|
3955
|
+
return isChrome;
|
|
3956
|
+
}
|
|
3957
|
+
const LiquidGlassFilter = ({
|
|
3958
|
+
filterId,
|
|
3959
|
+
displacementMapUrl,
|
|
3960
|
+
specularMapUrl,
|
|
3961
|
+
width,
|
|
3962
|
+
height,
|
|
3963
|
+
scale,
|
|
3964
|
+
saturation = 1.2,
|
|
3965
|
+
blurAmount = 1,
|
|
3966
|
+
specularIntensity = 0.25
|
|
3967
|
+
}) => {
|
|
3968
|
+
const filterSvg = React.useMemo(
|
|
3969
|
+
() => /* @__PURE__ */ jsxRuntime.jsx(
|
|
3970
|
+
"svg",
|
|
3971
|
+
{
|
|
3972
|
+
style: {
|
|
3973
|
+
position: "absolute",
|
|
3974
|
+
width: 0,
|
|
3975
|
+
height: 0,
|
|
3976
|
+
overflow: "hidden",
|
|
3977
|
+
pointerEvents: "none"
|
|
3978
|
+
},
|
|
3979
|
+
"aria-hidden": "true",
|
|
3980
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
3981
|
+
"filter",
|
|
3982
|
+
{
|
|
3983
|
+
id: filterId,
|
|
3984
|
+
x: "-50%",
|
|
3985
|
+
y: "-50%",
|
|
3986
|
+
width: "200%",
|
|
3987
|
+
height: "200%",
|
|
3988
|
+
colorInterpolationFilters: "sRGB",
|
|
3989
|
+
children: [
|
|
3990
|
+
/* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: blurAmount, result: "blurred" }),
|
|
3991
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
3992
|
+
"feImage",
|
|
3993
|
+
{
|
|
3994
|
+
href: displacementMapUrl,
|
|
3995
|
+
x: "0",
|
|
3996
|
+
y: "0",
|
|
3997
|
+
width,
|
|
3998
|
+
height,
|
|
3999
|
+
result: "displacement_map",
|
|
4000
|
+
preserveAspectRatio: "none"
|
|
4001
|
+
}
|
|
4002
|
+
),
|
|
4003
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4004
|
+
"feDisplacementMap",
|
|
4005
|
+
{
|
|
4006
|
+
in: "blurred",
|
|
4007
|
+
in2: "displacement_map",
|
|
4008
|
+
scale,
|
|
4009
|
+
xChannelSelector: "R",
|
|
4010
|
+
yChannelSelector: "G",
|
|
4011
|
+
result: "displaced"
|
|
4012
|
+
}
|
|
4013
|
+
),
|
|
4014
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4015
|
+
"feColorMatrix",
|
|
4016
|
+
{
|
|
4017
|
+
in: "displaced",
|
|
4018
|
+
type: "saturate",
|
|
4019
|
+
values: String(saturation),
|
|
4020
|
+
result: "saturated"
|
|
4021
|
+
}
|
|
4022
|
+
),
|
|
4023
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
4024
|
+
"feImage",
|
|
4025
|
+
{
|
|
4026
|
+
href: specularMapUrl,
|
|
4027
|
+
x: "0",
|
|
4028
|
+
y: "0",
|
|
4029
|
+
width,
|
|
4030
|
+
height,
|
|
4031
|
+
result: "specular_map_raw",
|
|
4032
|
+
preserveAspectRatio: "none"
|
|
4033
|
+
}
|
|
4034
|
+
),
|
|
4035
|
+
/* @__PURE__ */ jsxRuntime.jsx("feGaussianBlur", { in: "specular_map_raw", stdDeviation: "3", result: "specular_map" }),
|
|
4036
|
+
/* @__PURE__ */ jsxRuntime.jsx("feFlood", { floodColor: "white", floodOpacity: specularIntensity * 0.5, result: "white" }),
|
|
4037
|
+
/* @__PURE__ */ jsxRuntime.jsx("feComposite", { in: "white", in2: "specular_map", operator: "in", result: "highlight" }),
|
|
4038
|
+
/* @__PURE__ */ jsxRuntime.jsx("feBlend", { in: "highlight", in2: "saturated", mode: "screen", result: "final" })
|
|
4039
|
+
]
|
|
4040
|
+
}
|
|
4041
|
+
) })
|
|
4042
|
+
}
|
|
4043
|
+
),
|
|
4044
|
+
[filterId, displacementMapUrl, specularMapUrl, width, height, scale, saturation, blurAmount, specularIntensity]
|
|
4045
|
+
);
|
|
4046
|
+
return filterSvg;
|
|
4047
|
+
};
|
|
4048
|
+
function generateFilterId(prefix = "liquid-glass") {
|
|
4049
|
+
return `${prefix}-${Math.random().toString(36).substr(2, 9)}`;
|
|
4050
|
+
}
|
|
4051
|
+
function useLiquidGlass(options) {
|
|
4052
|
+
const {
|
|
4053
|
+
width,
|
|
4054
|
+
height,
|
|
4055
|
+
profile = DEFAULT_LIQUID_GLASS_CONFIG.profile,
|
|
4056
|
+
bezelWidth = DEFAULT_LIQUID_GLASS_CONFIG.bezelWidth,
|
|
4057
|
+
glassThickness = DEFAULT_LIQUID_GLASS_CONFIG.glassThickness,
|
|
4058
|
+
refractiveIndex = DEFAULT_LIQUID_GLASS_CONFIG.refractiveIndex,
|
|
4059
|
+
animated = true,
|
|
4060
|
+
saturation = 1.3,
|
|
4061
|
+
blurAmount = 1,
|
|
4062
|
+
specularIntensity = 0.3
|
|
4063
|
+
} = options;
|
|
4064
|
+
const [scale, setScaleState] = React.useState(DEFAULT_LIQUID_GLASS_CONFIG.scale);
|
|
4065
|
+
const [displacementMapUrl, setDisplacementMapUrl] = React.useState("");
|
|
4066
|
+
const [specularMapUrl, setSpecularMapUrl] = React.useState("");
|
|
4067
|
+
const scaleSpringRef = React.useRef(null);
|
|
4068
|
+
const physicsIdRef = React.useRef(generatePhysicsId("liquid-glass"));
|
|
4069
|
+
const filterIdRef = React.useRef(generateFilterId());
|
|
4070
|
+
const isSupported = React.useMemo(() => supportsBackdropSvgFilter(), []);
|
|
4071
|
+
React.useEffect(() => {
|
|
4072
|
+
const config = {
|
|
4073
|
+
width,
|
|
4074
|
+
height,
|
|
4075
|
+
bezelWidth,
|
|
4076
|
+
glassThickness,
|
|
4077
|
+
refractiveIndex,
|
|
4078
|
+
profile
|
|
4079
|
+
};
|
|
4080
|
+
const dispMapUrl = generateDisplacementMap(config);
|
|
4081
|
+
const specMapUrl = generateSpecularMap(config);
|
|
4082
|
+
setDisplacementMapUrl(dispMapUrl);
|
|
4083
|
+
setSpecularMapUrl(specMapUrl);
|
|
4084
|
+
}, [width, height, bezelWidth, glassThickness, refractiveIndex, profile]);
|
|
4085
|
+
React.useEffect(() => {
|
|
4086
|
+
scaleSpringRef.current = new Spring1D(scale, {
|
|
4087
|
+
tension: 200,
|
|
4088
|
+
friction: 20,
|
|
4089
|
+
mass: 1,
|
|
4090
|
+
precision: 0.5
|
|
4091
|
+
});
|
|
4092
|
+
return () => {
|
|
4093
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
4094
|
+
};
|
|
4095
|
+
}, []);
|
|
4096
|
+
const setScale = React.useCallback(
|
|
4097
|
+
(targetScale) => {
|
|
4098
|
+
if (!animated) {
|
|
4099
|
+
setScaleState(targetScale);
|
|
4100
|
+
return;
|
|
4101
|
+
}
|
|
4102
|
+
if (!scaleSpringRef.current) {
|
|
4103
|
+
scaleSpringRef.current = new Spring1D(scale, {
|
|
4104
|
+
tension: 200,
|
|
4105
|
+
friction: 20,
|
|
4106
|
+
mass: 1,
|
|
4107
|
+
precision: 0.5
|
|
4108
|
+
});
|
|
4109
|
+
}
|
|
4110
|
+
scaleSpringRef.current.setTarget(targetScale);
|
|
4111
|
+
if (!PhysicsEngine.has(physicsIdRef.current)) {
|
|
4112
|
+
PhysicsEngine.register(physicsIdRef.current, (dt) => {
|
|
4113
|
+
if (!scaleSpringRef.current) return;
|
|
4114
|
+
const result = scaleSpringRef.current.step(dt);
|
|
4115
|
+
setScaleState(result.value);
|
|
4116
|
+
if (result.isSettled) {
|
|
4117
|
+
PhysicsEngine.unregister(physicsIdRef.current);
|
|
4118
|
+
}
|
|
4119
|
+
});
|
|
4120
|
+
}
|
|
4121
|
+
},
|
|
4122
|
+
[animated, scale]
|
|
4123
|
+
);
|
|
4124
|
+
const filterComponent = React.useMemo(() => {
|
|
4125
|
+
if (!displacementMapUrl || !specularMapUrl || !isSupported) {
|
|
4126
|
+
return null;
|
|
4127
|
+
}
|
|
4128
|
+
return React.createElement(LiquidGlassFilter, {
|
|
4129
|
+
filterId: filterIdRef.current,
|
|
4130
|
+
displacementMapUrl,
|
|
4131
|
+
specularMapUrl,
|
|
4132
|
+
width,
|
|
4133
|
+
height,
|
|
4134
|
+
scale,
|
|
4135
|
+
saturation,
|
|
4136
|
+
blurAmount,
|
|
4137
|
+
specularIntensity
|
|
4138
|
+
});
|
|
4139
|
+
}, [displacementMapUrl, specularMapUrl, width, height, scale, isSupported, saturation, blurAmount, specularIntensity]);
|
|
4140
|
+
const style = React.useMemo(() => {
|
|
4141
|
+
if (!isSupported) {
|
|
4142
|
+
return {
|
|
4143
|
+
backdropFilter: "blur(12px) saturate(1.2)",
|
|
4144
|
+
WebkitBackdropFilter: "blur(12px) saturate(1.2)"
|
|
4145
|
+
};
|
|
4146
|
+
}
|
|
4147
|
+
return {
|
|
4148
|
+
backdropFilter: `url(#${filterIdRef.current})`,
|
|
4149
|
+
WebkitBackdropFilter: `url(#${filterIdRef.current})`
|
|
4150
|
+
};
|
|
4151
|
+
}, [isSupported]);
|
|
4152
|
+
return {
|
|
4153
|
+
filterComponent,
|
|
4154
|
+
style,
|
|
4155
|
+
setScale,
|
|
4156
|
+
scale,
|
|
4157
|
+
isSupported
|
|
4158
|
+
};
|
|
4159
|
+
}
|
|
1878
4160
|
exports.Card = Card;
|
|
1879
4161
|
exports.DEFAULT_FRICTION_CONFIG = DEFAULT_FRICTION_CONFIG;
|
|
4162
|
+
exports.DEFAULT_LIQUID_GLASS_CONFIG = DEFAULT_LIQUID_GLASS_CONFIG;
|
|
1880
4163
|
exports.DEFAULT_SPRING_CONFIG = DEFAULT_SPRING_CONFIG;
|
|
1881
4164
|
exports.Draggable = Draggable;
|
|
4165
|
+
exports.LiquidGlassFilter = LiquidGlassFilter;
|
|
1882
4166
|
exports.Momentum = Momentum;
|
|
1883
4167
|
exports.Momentum1D = Momentum1D;
|
|
1884
4168
|
exports.PhysicsEngine = PhysicsEngine;
|
|
1885
4169
|
exports.PhysicsProvider = PhysicsProvider;
|
|
4170
|
+
exports.PhysicsRangeSlider = PhysicsRangeSlider;
|
|
4171
|
+
exports.PhysicsSlider = PhysicsSlider;
|
|
4172
|
+
exports.PhysicsStepSlider = PhysicsStepSlider;
|
|
4173
|
+
exports.PhysicsVerticalSlider = PhysicsVerticalSlider;
|
|
4174
|
+
exports.SegmentedControl = SegmentedControl;
|
|
1886
4175
|
exports.Spring = Spring;
|
|
1887
4176
|
exports.Spring1D = Spring1D;
|
|
1888
4177
|
exports.Vector2 = Vector2;
|
|
1889
4178
|
exports.VelocityTracker = VelocityTracker;
|
|
1890
4179
|
exports.VelocityTracker1D = VelocityTracker1D;
|
|
1891
4180
|
exports.cardBaseStyles = cardBaseStyles;
|
|
4181
|
+
exports.clearMapCache = clearMapCache;
|
|
1892
4182
|
exports.combineLiftStyle = combineLiftStyle;
|
|
1893
4183
|
exports.combineStretchStyle = combineStretchStyle;
|
|
4184
|
+
exports.defaultSegmentedControlConfig = defaultSegmentedControlConfig;
|
|
4185
|
+
exports.defaultSliderConfig = defaultSliderConfig;
|
|
4186
|
+
exports.generateDisplacementMap = generateDisplacementMap;
|
|
4187
|
+
exports.generateFilterId = generateFilterId;
|
|
1894
4188
|
exports.generatePhysicsId = generatePhysicsId;
|
|
4189
|
+
exports.generateSpecularMap = generateSpecularMap;
|
|
1895
4190
|
exports.gentle = gentle;
|
|
4191
|
+
exports.getCachedMaps = getCachedMaps;
|
|
1896
4192
|
exports.getFrictionConfig = getFrictionConfig;
|
|
1897
4193
|
exports.getPreset = getPreset;
|
|
4194
|
+
exports.getProfileFunction = getProfileFunction;
|
|
1898
4195
|
exports.getSpringConfig = getSpringConfig;
|
|
1899
4196
|
exports.ios = ios;
|
|
4197
|
+
exports.preloadMaps = preloadMaps;
|
|
1900
4198
|
exports.presets = presets;
|
|
4199
|
+
exports.segmentedControlPresets = segmentedControlPresets;
|
|
4200
|
+
exports.sliderPresets = sliderPresets;
|
|
1901
4201
|
exports.smooth = smooth;
|
|
4202
|
+
exports.smootherstep = smootherstep;
|
|
1902
4203
|
exports.snappy = snappy;
|
|
1903
4204
|
exports.stiff = stiff;
|
|
4205
|
+
exports.supportsBackdropSvgFilter = supportsBackdropSvgFilter;
|
|
1904
4206
|
exports.useDrag = useDrag;
|
|
1905
4207
|
exports.useFlick = useFlick;
|
|
1906
4208
|
exports.useFrictionConfig = useFrictionConfig;
|
|
1907
4209
|
exports.useGesture = useGesture;
|
|
1908
4210
|
exports.useLift = useLift;
|
|
1909
4211
|
exports.useLiftConfig = useLiftConfig;
|
|
4212
|
+
exports.useLiquidGlass = useLiquidGlass;
|
|
1910
4213
|
exports.usePhysicsConfig = usePhysicsConfig;
|
|
1911
4214
|
exports.usePhysicsContext = usePhysicsContext;
|
|
1912
4215
|
exports.useSpring = useSpring;
|