@kanda-libs/ks-component-ts 0.3.56 → 0.3.58
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/app/src/App.tsx +12 -49
- package/dist/index.d.ts +13796 -13794
- package/dist/index.esm.js +4 -4
- package/dist/index.esm.js.map +3 -3
- package/package.json +1 -1
- package/src/field/components/RangeInput/RangeInputUncontrolled.tsx +40 -14
- package/src/field/components/RangeInput/constants.ts +6 -0
- package/src/field/components/RangeInput/useRangeInputProps.ts +145 -31
- package/src/generated/widget/index.tsx +63142 -63142
package/package.json
CHANGED
|
@@ -4,6 +4,8 @@ import { DefaultFormFieldProps } from "~/field/types";
|
|
|
4
4
|
import useRangeInputProps from "./useRangeInputProps";
|
|
5
5
|
import { defaultFormatter } from "./helpers";
|
|
6
6
|
|
|
7
|
+
export type TieStepsTo = "min" | "max";
|
|
8
|
+
|
|
7
9
|
export interface RangeInputUncontrolledProps
|
|
8
10
|
extends InputHTMLAttributes<HTMLInputElement> {
|
|
9
11
|
min?: string;
|
|
@@ -14,6 +16,7 @@ export interface RangeInputUncontrolledProps
|
|
|
14
16
|
highlightLabel?: "min" | "max";
|
|
15
17
|
showCurrentValueLabel?: boolean;
|
|
16
18
|
suffix?: string;
|
|
19
|
+
tieStepsTo?: TieStepsTo;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
const RangeInputUncontrolled: FunctionComponent<
|
|
@@ -30,22 +33,34 @@ const RangeInputUncontrolled: FunctionComponent<
|
|
|
30
33
|
prefix = "",
|
|
31
34
|
suffix = "",
|
|
32
35
|
name = "",
|
|
36
|
+
tieStepsTo = "max",
|
|
33
37
|
highlightLabel,
|
|
34
38
|
showCurrentValueLabel = true,
|
|
35
39
|
...restProps
|
|
36
40
|
}) {
|
|
37
|
-
const {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
const {
|
|
42
|
+
ref,
|
|
43
|
+
inputProps,
|
|
44
|
+
minLabel,
|
|
45
|
+
maxLabel,
|
|
46
|
+
currentLabel,
|
|
47
|
+
classNames,
|
|
48
|
+
sliderName,
|
|
49
|
+
onSliderChange,
|
|
50
|
+
rawValue,
|
|
51
|
+
value,
|
|
52
|
+
} = useRangeInputProps(
|
|
53
|
+
name,
|
|
54
|
+
min,
|
|
55
|
+
max,
|
|
56
|
+
steps,
|
|
57
|
+
tieStepsTo,
|
|
58
|
+
formatter,
|
|
59
|
+
prefix,
|
|
60
|
+
suffix,
|
|
61
|
+
error,
|
|
62
|
+
highlightLabel
|
|
63
|
+
);
|
|
49
64
|
|
|
50
65
|
return (
|
|
51
66
|
<div className={classNames.container}>
|
|
@@ -66,11 +81,22 @@ const RangeInputUncontrolled: FunctionComponent<
|
|
|
66
81
|
type="range"
|
|
67
82
|
ref={forwardRef}
|
|
68
83
|
onInput={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
69
|
-
|
|
84
|
+
onSliderChange(e);
|
|
70
85
|
}}
|
|
71
|
-
name={
|
|
86
|
+
name={sliderName}
|
|
72
87
|
{...restProps}
|
|
73
88
|
{...inputProps}
|
|
89
|
+
value={rawValue}
|
|
90
|
+
/>
|
|
91
|
+
<input
|
|
92
|
+
name={name}
|
|
93
|
+
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
94
|
+
if (onChange) onChange(e);
|
|
95
|
+
}}
|
|
96
|
+
style={{
|
|
97
|
+
display: "none",
|
|
98
|
+
}}
|
|
99
|
+
value={value}
|
|
74
100
|
/>
|
|
75
101
|
<div className={classNames.cap} />
|
|
76
102
|
</div>
|
|
@@ -14,3 +14,9 @@ export const CLASS_NAMES = {
|
|
|
14
14
|
rangeWrapper: "flex flex-row relative",
|
|
15
15
|
cap: "w-2.5 h-2.5 bg-green-600 rounded-full border border-neutral-000 absolute z-0 top-[5px] first:left-0 last:right-0",
|
|
16
16
|
};
|
|
17
|
+
|
|
18
|
+
export const BASE_INPUT_PROPS = {
|
|
19
|
+
min: "0",
|
|
20
|
+
max: "100",
|
|
21
|
+
step: "1",
|
|
22
|
+
};
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef } from "react";
|
|
2
|
-
import { useWatch } from "react-hook-form";
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import { useFormContext, useWatch } from "react-hook-form";
|
|
3
3
|
import clsx from "clsx";
|
|
4
4
|
|
|
5
5
|
import { BG_COLOR, CLASS_NAMES } from "./constants";
|
|
6
6
|
|
|
7
7
|
import { ErrorMessage } from "~/field/types";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
RangeInputUncontrolledProps,
|
|
10
|
+
TieStepsTo,
|
|
11
|
+
} from "./RangeInputUncontrolled";
|
|
12
|
+
import { chainStateK } from "fp-ts/lib/FromState";
|
|
9
13
|
|
|
10
14
|
interface RangeClassNames {
|
|
11
15
|
container: string;
|
|
@@ -33,6 +37,10 @@ export interface Hook {
|
|
|
33
37
|
minLabel: string;
|
|
34
38
|
currentLabel?: string;
|
|
35
39
|
classNames: RangeClassNames;
|
|
40
|
+
onSliderChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
|
41
|
+
sliderName: string;
|
|
42
|
+
rawValue: string;
|
|
43
|
+
value: string;
|
|
36
44
|
}
|
|
37
45
|
|
|
38
46
|
export default function useRangeInputProps(
|
|
@@ -40,49 +48,135 @@ export default function useRangeInputProps(
|
|
|
40
48
|
min: string,
|
|
41
49
|
max: string,
|
|
42
50
|
steps: string,
|
|
51
|
+
tieStepsTo: TieStepsTo,
|
|
43
52
|
formatter: (input: string) => string,
|
|
44
53
|
prefix: string,
|
|
45
54
|
suffix: string,
|
|
46
55
|
error?: string | ErrorMessage,
|
|
47
56
|
highlightLabel?: RangeInputUncontrolledProps["highlightLabel"]
|
|
48
57
|
): Hook {
|
|
49
|
-
const
|
|
58
|
+
const sliderName = useMemo(() => `${name}__slider`, [name]);
|
|
59
|
+
const [value, rawValue] = useWatch({ name: [name, sliderName] });
|
|
60
|
+
const {
|
|
61
|
+
setValue,
|
|
62
|
+
formState: { defaultValues },
|
|
63
|
+
} = useFormContext();
|
|
64
|
+
|
|
65
|
+
const initialValue = useMemo(
|
|
66
|
+
() => defaultValues?.[name],
|
|
67
|
+
[defaultValues, name]
|
|
68
|
+
);
|
|
50
69
|
|
|
51
70
|
const ref = useRef<HTMLInputElement>(null);
|
|
52
71
|
|
|
53
|
-
const
|
|
54
|
-
|
|
72
|
+
const diff = useMemo(() => parseInt(max, 10) - parseInt(min, 10), [max, min]);
|
|
73
|
+
|
|
74
|
+
const numSteps = useMemo(
|
|
75
|
+
() => Math.min(diff, parseInt(steps, 10)),
|
|
76
|
+
[diff, steps]
|
|
55
77
|
);
|
|
56
78
|
|
|
57
|
-
const
|
|
58
|
-
min,
|
|
59
|
-
max,
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
const step = useMemo(
|
|
80
|
+
() => Math.ceil((parseInt(max, 10) - parseInt(min, 10)) / numSteps),
|
|
81
|
+
[max, min, numSteps]
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
const maxInput = useMemo(() => {
|
|
85
|
+
if (diff < 0) return "1";
|
|
86
|
+
if (diff > 100) return "100";
|
|
87
|
+
return String(diff);
|
|
88
|
+
}, [diff]);
|
|
89
|
+
|
|
90
|
+
const inputProps = useMemo(
|
|
91
|
+
() => ({
|
|
92
|
+
min: "0",
|
|
93
|
+
max: maxInput,
|
|
94
|
+
step: "1",
|
|
95
|
+
}),
|
|
96
|
+
[maxInput]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const tieMax = useCallback(
|
|
100
|
+
(rawInputValue: string) => {
|
|
101
|
+
const rawDiff = parseInt(maxInput, 10) - parseInt(rawInputValue, 10);
|
|
102
|
+
const increment = rawDiff * step;
|
|
103
|
+
return String(Math.max(parseInt(max, 10) - increment, parseInt(min, 10)));
|
|
104
|
+
},
|
|
105
|
+
[maxInput, step, max, min]
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const tieMin = useCallback(
|
|
109
|
+
(rawInputValue: string) => {
|
|
110
|
+
const rawDiff = parseInt(rawInputValue, 10);
|
|
111
|
+
const increment = rawDiff * step;
|
|
112
|
+
return String(Math.min(parseInt(min, 10) + increment, parseInt(max, 10)));
|
|
113
|
+
},
|
|
114
|
+
[step, max, min]
|
|
115
|
+
);
|
|
62
116
|
|
|
63
117
|
const currentLabel = useMemo(() => {
|
|
64
118
|
if (!value) return undefined;
|
|
65
119
|
return `${prefix}${formatter(value)}${suffix}`;
|
|
66
|
-
}, [value]);
|
|
67
|
-
|
|
68
|
-
const maxLabel =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
120
|
+
}, [formatter, value, prefix, suffix]);
|
|
121
|
+
|
|
122
|
+
const maxLabel = useMemo(
|
|
123
|
+
() => `${prefix}${formatter(String(max))}${suffix}`,
|
|
124
|
+
[prefix, formatter, max, suffix]
|
|
125
|
+
);
|
|
126
|
+
const minLabel = useMemo(
|
|
127
|
+
() => `${prefix}${formatter(String(min))}${suffix}`,
|
|
128
|
+
[prefix, formatter, min, suffix]
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
const classNames = useMemo(
|
|
132
|
+
() => ({
|
|
133
|
+
...CLASS_NAMES,
|
|
134
|
+
container: clsx(
|
|
135
|
+
CLASS_NAMES.container,
|
|
136
|
+
error ? "border-red-200" : "border-neutral-100"
|
|
137
|
+
),
|
|
138
|
+
minLowerLabel: clsx(
|
|
139
|
+
CLASS_NAMES.lowerLabel,
|
|
140
|
+
highlightLabel === "min" && CLASS_NAMES.highlightedLowerLabel
|
|
141
|
+
),
|
|
142
|
+
maxLowerLabel: clsx(
|
|
143
|
+
CLASS_NAMES.lowerLabel,
|
|
144
|
+
highlightLabel === "max" && CLASS_NAMES.highlightedLowerLabel
|
|
145
|
+
),
|
|
146
|
+
}),
|
|
147
|
+
[error, highlightLabel]
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const onSliderChange = useCallback(
|
|
151
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
152
|
+
const sliderValue = e.target.value;
|
|
153
|
+
setValue(sliderName, sliderValue);
|
|
154
|
+
},
|
|
155
|
+
[setValue, sliderName]
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
const getInitialSliderValue = useCallback(() => {
|
|
159
|
+
if (maxInput === "1") return maxInput;
|
|
160
|
+
if (!initialValue) return String(Math.ceil(parseInt(maxInput, 10) / 2));
|
|
161
|
+
const parsedMin = parseInt(min, 10);
|
|
162
|
+
const parsedMax = parseInt(max, 10);
|
|
163
|
+
const parsedVal = parseInt(initialValue, 10);
|
|
164
|
+
if (parsedVal >= parsedMax) {
|
|
165
|
+
setValue(name, String(parsedMax));
|
|
166
|
+
return "100";
|
|
167
|
+
}
|
|
168
|
+
if (parsedVal <= parsedMin) {
|
|
169
|
+
setValue(name, String(parsedMin));
|
|
170
|
+
return "0";
|
|
171
|
+
}
|
|
172
|
+
if (tieStepsTo === "max") {
|
|
173
|
+
const initDiff = (parseInt(max, 10) - parseInt(initialValue, 10)) / step;
|
|
174
|
+
return String(Math.round(parseInt(maxInput, 10) - initDiff));
|
|
175
|
+
}
|
|
176
|
+
return String(
|
|
177
|
+
Math.round((parseInt(initialValue, 10) - parseInt(min, 10)) / step)
|
|
178
|
+
);
|
|
179
|
+
}, [initialValue, maxInput, setValue, name, min, max, step]);
|
|
86
180
|
|
|
87
181
|
useEffect(() => {
|
|
88
182
|
if (!ref.current) return;
|
|
@@ -98,6 +192,22 @@ export default function useRangeInputProps(
|
|
|
98
192
|
input.style.background = BG_COLOR.replaceAll("$PCT", String(pct));
|
|
99
193
|
}, [value, min, max]);
|
|
100
194
|
|
|
195
|
+
const initRef = useRef<boolean>(false);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (initRef.current) return;
|
|
198
|
+
const initValue = getInitialSliderValue();
|
|
199
|
+
setValue(sliderName, initValue);
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
initRef.current = true;
|
|
202
|
+
}, 10);
|
|
203
|
+
}, [setValue, sliderName, getInitialSliderValue]);
|
|
204
|
+
|
|
205
|
+
useEffect(() => {
|
|
206
|
+
if (!initRef.current) return;
|
|
207
|
+
const newValue = tieStepsTo === "max" ? tieMax(rawValue) : tieMin(rawValue);
|
|
208
|
+
setValue(name, newValue);
|
|
209
|
+
}, [rawValue, tieStepsTo, setValue, tieMax, tieMin, name]);
|
|
210
|
+
|
|
101
211
|
return {
|
|
102
212
|
ref,
|
|
103
213
|
inputProps,
|
|
@@ -105,5 +215,9 @@ export default function useRangeInputProps(
|
|
|
105
215
|
minLabel,
|
|
106
216
|
currentLabel,
|
|
107
217
|
classNames,
|
|
218
|
+
onSliderChange,
|
|
219
|
+
sliderName,
|
|
220
|
+
value: value || "",
|
|
221
|
+
rawValue: rawValue || "0",
|
|
108
222
|
};
|
|
109
223
|
}
|