@godxjp/ui 22.0.0 → 23.0.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/README.md +4 -4
- package/dist/components/data-display/scroll-area.d.ts +24 -4
- package/dist/components/data-display/scroll-area.js +34 -72
- package/dist/components/data-entry/cascader.js +8 -6
- package/dist/components/data-entry/checkbox.js +2 -0
- package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
- package/dist/components/data-entry/choice-hit-target.js +12 -0
- package/dist/components/data-entry/control-surface.d.ts +8 -0
- package/dist/components/data-entry/control-surface.js +10 -1
- package/dist/components/data-entry/form-field.js +3 -2
- package/dist/components/data-entry/radio.js +3 -2
- package/dist/components/data-entry/search-select.js +225 -95
- package/dist/components/data-entry/select.d.ts +76 -14
- package/dist/components/data-entry/select.js +495 -163
- package/dist/components/data-entry/slider.d.ts +3 -11
- package/dist/components/data-entry/slider.js +582 -93
- package/dist/components/data-entry/switch.js +2 -0
- package/dist/components/navigation/dropdown-menu.d.ts +11 -3
- package/dist/components/navigation/dropdown-menu.js +190 -15
- package/dist/components/navigation/index.d.ts +0 -3
- package/dist/components/navigation/index.js +0 -78
- package/dist/components/ui/index.d.ts +0 -3
- package/dist/components/ui/index.js +0 -3
- package/dist/components/ui/input-otp.d.ts +1 -1
- package/dist/components/ui/segmented.js +3 -1
- package/dist/components/ui/tag-input.js +3 -5
- package/dist/lib/select-options.d.ts +21 -0
- package/dist/lib/select-options.js +46 -0
- package/dist/props/components/data-display.prop.d.ts +9 -6
- package/dist/props/components/data-entry.prop.d.ts +323 -30
- package/dist/props/components/index.d.ts +1 -1
- package/dist/props/components/navigation.prop.d.ts +17 -0
- package/dist/props/registry.d.ts +63 -0
- package/dist/props/registry.js +69 -0
- package/dist/styles/control.css +205 -23
- package/dist/styles/data-display-layout.css +18 -10
- package/dist/styles/focus-ring.css +2 -2
- package/dist/styles/fonts.css +88 -4
- package/dist/styles/navigation-layout.css +11 -236
- package/dist/styles/shell-layout.css +144 -139
- package/dist/styles/table-layout.css +7 -3
- package/dist/tokens/components/control.css +4 -0
- package/dist/tokens/components/data-display.css +0 -5
- package/dist/tokens/components/navigation.css +3 -5
- package/dist/tokens/components/scroll-area.css +7 -0
- package/docs/CUSTOMER-THEMING.md +7 -1
- package/docs/FRAME-COVERAGE-REPORT.md +4 -7
- package/docs/data-display/data-table/index.tsx +27 -1
- package/docs/data-display/popover.tsx +4 -6
- package/docs/data-display/scroll-area.tsx +51 -45
- package/docs/data-entry/date-picker.tsx +2 -3
- package/docs/data-entry/input-otp.tsx +5 -2
- package/docs/data-entry/segmented.tsx +40 -1
- package/docs/data-entry/select-matrix.tsx +1 -2
- package/docs/data-entry/select.tsx +108 -3
- package/docs/data-entry/slider.tsx +125 -47
- package/docs/feedback/dialog.tsx +1 -2
- package/docs/feedback/sheet.tsx +1 -2
- package/docs/layout/app-shell-states.tsx +143 -0
- package/docs/navigation/dropdown-menu.tsx +75 -4
- package/docs/roadmap/parity-audit-data-entry.md +44 -33
- package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
- package/docs/roadmap/parity-backlog.md +2 -3
- package/package.json +20 -44
- package/scripts/ui-audit.mjs +1 -1
- package/scripts/visual-audit-rules.mjs +0 -7
- package/scripts/visual-audit.mjs +6 -27
- package/dist/components/navigation/context-menu.d.ts +0 -21
- package/dist/components/navigation/context-menu.js +0 -149
- package/dist/components/navigation/menubar.d.ts +0 -28
- package/dist/components/navigation/menubar.js +0 -136
- package/dist/components/navigation/navigation-menu.d.ts +0 -10
- package/dist/components/navigation/navigation-menu.js +0 -91
- package/dist/components/ui/context-menu.d.ts +0 -1
- package/dist/components/ui/context-menu.js +0 -2
- package/dist/components/ui/menubar.d.ts +0 -1
- package/dist/components/ui/menubar.js +0 -2
- package/dist/components/ui/navigation-menu.d.ts +0 -1
- package/dist/components/ui/navigation-menu.js +0 -2
- package/docs/FRAME-A11Y-CI.md +0 -349
- package/docs/navigation/context-menu.tsx +0 -128
- package/docs/navigation/menubar.tsx +0 -141
- package/docs/navigation/navigation-menu.tsx +0 -158
|
@@ -1,33 +1,472 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import * as React from "react";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
Label as AriaLabel,
|
|
6
|
+
Slider as AriaSlider,
|
|
7
|
+
SliderStateContext,
|
|
8
|
+
SliderThumb as AriaSliderThumb,
|
|
9
|
+
SliderTrack as AriaSliderTrack,
|
|
10
|
+
useLocale
|
|
11
|
+
} from "react-aria-components";
|
|
5
12
|
import { cn } from "../../lib/utils.js";
|
|
13
|
+
const PAGE_STEPS = 10;
|
|
14
|
+
const GRID_PRECISION = 10;
|
|
15
|
+
const PERCENT_PRECISION = 4;
|
|
16
|
+
const OPPOSITE = {
|
|
17
|
+
top: "bottom",
|
|
18
|
+
bottom: "top",
|
|
19
|
+
left: "right",
|
|
20
|
+
right: "left"
|
|
21
|
+
};
|
|
22
|
+
function clamp(value, low, high) {
|
|
23
|
+
return Math.min(high, Math.max(low, value));
|
|
24
|
+
}
|
|
25
|
+
function decimalPlaces(value) {
|
|
26
|
+
if (!Number.isFinite(value)) return 0;
|
|
27
|
+
const text = String(value);
|
|
28
|
+
const exponent = /e-(\d+)$/.exec(text);
|
|
29
|
+
if (exponent) return Number(exponent[1]);
|
|
30
|
+
const dot = text.indexOf(".");
|
|
31
|
+
return dot === -1 ? 0 : text.length - dot - 1;
|
|
32
|
+
}
|
|
33
|
+
function snapToStep(value, min, max, step) {
|
|
34
|
+
const places = Math.max(decimalPlaces(step), decimalPlaces(min));
|
|
35
|
+
const lastOnGrid = min + Math.floor(Number(((max - min) / step).toFixed(GRID_PRECISION))) * step;
|
|
36
|
+
const snapped = Math.round((value - min) / step) * step + min;
|
|
37
|
+
return Number(clamp(snapped, min, Math.max(min, lastOnGrid)).toFixed(places));
|
|
38
|
+
}
|
|
6
39
|
function fraction(value, min, max, reverse) {
|
|
7
40
|
if (max === min) return 0;
|
|
8
41
|
const raw = (value - min) / (max - min);
|
|
9
42
|
return reverse ? 1 - raw : raw;
|
|
10
43
|
}
|
|
44
|
+
function isMarkObject(mark) {
|
|
45
|
+
return typeof mark === "object" && mark !== null && !React.isValidElement(mark) && !Array.isArray(mark) && !(Symbol.iterator in mark);
|
|
46
|
+
}
|
|
11
47
|
function markEntries(marks) {
|
|
12
48
|
if (!marks) return [];
|
|
13
|
-
return Object.entries(marks).map(([key,
|
|
49
|
+
return Object.entries(marks).map(([key, mark]) => [Number(key), mark]).filter(([at]) => Number.isFinite(at)).sort((a, b) => a[0] - b[0]);
|
|
14
50
|
}
|
|
15
51
|
function dotStops(min, max, step) {
|
|
16
52
|
if (!Number.isFinite(step) || step <= 0) return [];
|
|
17
53
|
const stops = [];
|
|
18
|
-
for (let at = min; at <= max; at += step) stops.push(at);
|
|
54
|
+
for (let at = min; at <= max; at += step) stops.push(snapToStep(at, min, max, step));
|
|
19
55
|
return stops;
|
|
20
56
|
}
|
|
57
|
+
function markStops(min, max, marks) {
|
|
58
|
+
const inside = marks.map(([at]) => at).filter((at) => at >= min && at <= max);
|
|
59
|
+
return [.../* @__PURE__ */ new Set([min, ...inside, max])].sort((a, b) => a - b);
|
|
60
|
+
}
|
|
61
|
+
function greatestCommonDivisor(a, b) {
|
|
62
|
+
return b === 0 ? a : greatestCommonDivisor(b, a % b);
|
|
63
|
+
}
|
|
64
|
+
function gridThrough(stops) {
|
|
65
|
+
const places = Math.max(...stops.map(decimalPlaces));
|
|
66
|
+
const scale = 10 ** places;
|
|
67
|
+
const offsets = stops.map((at) => Math.round((at - stops[0]) * scale));
|
|
68
|
+
const divisor = offsets.reduce((acc, offset) => greatestCommonDivisor(acc, Math.abs(offset)), 0);
|
|
69
|
+
return divisor > 0 ? divisor / scale : 1;
|
|
70
|
+
}
|
|
71
|
+
function nearestStop(stops, value) {
|
|
72
|
+
return stops.reduce((best, at) => Math.abs(at - value) < Math.abs(best - value) ? at : best);
|
|
73
|
+
}
|
|
74
|
+
function stepThroughStops(stops, from, sign, count) {
|
|
75
|
+
const ahead = sign > 0 ? stops.filter((at) => at > from) : stops.filter((at) => at < from).reverse();
|
|
76
|
+
if (ahead.length === 0) return from;
|
|
77
|
+
return ahead[Math.min(count, ahead.length) - 1];
|
|
78
|
+
}
|
|
79
|
+
function toList(value) {
|
|
80
|
+
if (value === void 0) return void 0;
|
|
81
|
+
return Array.isArray(value) ? [...value] : [value];
|
|
82
|
+
}
|
|
21
83
|
function tooltipContent(tooltip, value) {
|
|
22
84
|
if (!tooltip || tooltip === true) return value;
|
|
23
85
|
if (tooltip.formatter === null) return null;
|
|
24
86
|
return tooltip.formatter ? tooltip.formatter(value) : value;
|
|
25
87
|
}
|
|
26
|
-
|
|
27
|
-
(
|
|
88
|
+
function sameList(a, b) {
|
|
89
|
+
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
90
|
+
}
|
|
91
|
+
function placeThumb(model, list, index, proposed, mode, count = 1) {
|
|
92
|
+
const { min, max, stops, step, gap, places } = model;
|
|
93
|
+
let target;
|
|
94
|
+
if (stops) {
|
|
95
|
+
target = mode === "nearest" ? nearestStop(stops, clamp(proposed, min, max)) : stepThroughStops(stops, list[index], mode, count);
|
|
96
|
+
} else {
|
|
97
|
+
target = snapToStep(clamp(proposed, min, max), min, max, step);
|
|
98
|
+
}
|
|
99
|
+
const low = index > 0 ? list[index - 1] + gap : min;
|
|
100
|
+
const high = index < list.length - 1 ? list[index + 1] - gap : max;
|
|
101
|
+
if (low > high) return list;
|
|
102
|
+
const held = Number(clamp(target, low, high).toFixed(places));
|
|
103
|
+
return list.map((value, at) => at === index ? held : value);
|
|
104
|
+
}
|
|
105
|
+
function shiftAll(model, start, delta) {
|
|
106
|
+
const { min, max, step, places } = model;
|
|
107
|
+
const lastOnGrid = snapToStep(max, min, max, step);
|
|
108
|
+
const rounded = Math.round(delta / step) * step;
|
|
109
|
+
const moved = clamp(rounded, min - start[0], lastOnGrid - start[start.length - 1]);
|
|
110
|
+
return start.map((value) => Number((value + moved).toFixed(places)));
|
|
111
|
+
}
|
|
112
|
+
function closestThumb(list, point, disabledAt) {
|
|
113
|
+
let best = null;
|
|
114
|
+
let bestDistance = Infinity;
|
|
115
|
+
list.forEach((value, index) => {
|
|
116
|
+
if (disabledAt(index)) return;
|
|
117
|
+
const distance = Math.abs(value - point);
|
|
118
|
+
if (distance < bestDistance || distance === bestDistance && point > value) {
|
|
119
|
+
best = index;
|
|
120
|
+
bestDistance = distance;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
return best;
|
|
124
|
+
}
|
|
125
|
+
function arrowSign(key, axis, direction, reversed) {
|
|
126
|
+
if (axis === "vertical") {
|
|
127
|
+
if (key === "ArrowUp") return reversed ? -1 : 1;
|
|
128
|
+
if (key === "ArrowDown") return reversed ? 1 : -1;
|
|
129
|
+
return key === "ArrowRight" ? 1 : -1;
|
|
130
|
+
}
|
|
131
|
+
if (key === "ArrowUp") return 1;
|
|
132
|
+
if (key === "ArrowDown") return -1;
|
|
133
|
+
const rightIncreases = direction === "ltr" !== reversed;
|
|
134
|
+
return key === "ArrowRight" === rightIncreases ? 1 : -1;
|
|
135
|
+
}
|
|
136
|
+
const ARROWS = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown"]);
|
|
137
|
+
function percent(share) {
|
|
138
|
+
return `${Number((share * 100).toFixed(PERCENT_PRECISION))}%`;
|
|
139
|
+
}
|
|
140
|
+
function offsetStyle(model, value, name) {
|
|
141
|
+
return {
|
|
142
|
+
[name]: percent(fraction(value, model.min, model.max, model.reversed))
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function SliderTooltip({
|
|
146
|
+
placement,
|
|
147
|
+
adjust,
|
|
148
|
+
open,
|
|
149
|
+
direction,
|
|
150
|
+
children
|
|
151
|
+
}) {
|
|
152
|
+
const ref = React.useRef(null);
|
|
153
|
+
const [flipped, setFlipped] = React.useState(false);
|
|
154
|
+
React.useLayoutEffect(() => {
|
|
155
|
+
const anchor = ref.current;
|
|
156
|
+
const thumb = anchor?.parentElement;
|
|
157
|
+
if (!anchor || !thumb || !adjust) {
|
|
158
|
+
setFlipped(false);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const rect = thumb.getBoundingClientRect();
|
|
162
|
+
const vertical = placement === "top" || placement === "bottom";
|
|
163
|
+
const need = vertical ? anchor.offsetHeight : anchor.offsetWidth;
|
|
164
|
+
const inlineStartRoom = direction === "rtl" ? window.innerWidth - rect.right : rect.left;
|
|
165
|
+
const inlineEndRoom = direction === "rtl" ? rect.left : window.innerWidth - rect.right;
|
|
166
|
+
const room = {
|
|
167
|
+
top: rect.top,
|
|
168
|
+
bottom: window.innerHeight - rect.bottom,
|
|
169
|
+
left: inlineStartRoom,
|
|
170
|
+
right: inlineEndRoom
|
|
171
|
+
};
|
|
172
|
+
setFlipped(room[placement] < need && room[OPPOSITE[placement]] > room[placement]);
|
|
173
|
+
});
|
|
174
|
+
return /* @__PURE__ */ jsx(
|
|
175
|
+
"span",
|
|
176
|
+
{
|
|
177
|
+
ref,
|
|
178
|
+
"data-slot": "slider-tooltip",
|
|
179
|
+
"data-placement": flipped ? OPPOSITE[placement] : placement,
|
|
180
|
+
"data-open": open === void 0 ? void 0 : String(open),
|
|
181
|
+
className: "ui-slider-tooltip",
|
|
182
|
+
"aria-hidden": "true",
|
|
183
|
+
children: /* @__PURE__ */ jsx("span", { className: "ui-slider-tooltip-content", children })
|
|
184
|
+
}
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
function SliderThumbView({
|
|
188
|
+
model,
|
|
189
|
+
index,
|
|
190
|
+
value,
|
|
191
|
+
inputRef
|
|
192
|
+
}) {
|
|
193
|
+
const { tooltip, values } = model;
|
|
194
|
+
const multiple = values.length > 1;
|
|
195
|
+
const indexId = React.useId();
|
|
196
|
+
const content = tooltip === false ? null : tooltipContent(tooltip, value);
|
|
197
|
+
const settings = typeof tooltip === "object" ? tooltip : void 0;
|
|
198
|
+
const spoken = settings?.formatter && (typeof content === "string" || typeof content === "number") ? String(content) : void 0;
|
|
199
|
+
React.useLayoutEffect(() => {
|
|
200
|
+
if (spoken !== void 0) inputRef.current?.setAttribute("aria-valuetext", spoken);
|
|
201
|
+
}, [inputRef, spoken, value]);
|
|
202
|
+
React.useLayoutEffect(() => {
|
|
203
|
+
const input = inputRef.current;
|
|
204
|
+
if (!input) return;
|
|
205
|
+
if (model.label) input.setAttribute("aria-label", model.label);
|
|
206
|
+
else input.removeAttribute("aria-label");
|
|
207
|
+
}, [inputRef, model.label]);
|
|
208
|
+
const labelledBy = [model.labelledBy, multiple ? indexId : void 0].filter(Boolean).join(" ");
|
|
209
|
+
return /* @__PURE__ */ jsxs(
|
|
210
|
+
AriaSliderThumb,
|
|
211
|
+
{
|
|
212
|
+
index,
|
|
213
|
+
"data-slot": "slider-thumb",
|
|
214
|
+
"data-index": index,
|
|
215
|
+
className: "ui-slider-thumb",
|
|
216
|
+
style: {
|
|
217
|
+
...offsetStyle(model, value, "--slider-thumb-offset"),
|
|
218
|
+
left: void 0,
|
|
219
|
+
top: void 0,
|
|
220
|
+
transform: void 0
|
|
221
|
+
},
|
|
222
|
+
isDisabled: model.thumbDisabled(index),
|
|
223
|
+
name: model.name ? multiple ? `${model.name}[]` : model.name : void 0,
|
|
224
|
+
form: model.form,
|
|
225
|
+
inputRef,
|
|
226
|
+
"aria-labelledby": labelledBy || void 0,
|
|
227
|
+
isInvalid: model.isInvalid || void 0,
|
|
228
|
+
isRequired: model.isRequired || void 0,
|
|
229
|
+
"aria-errormessage": model.errorMessage,
|
|
230
|
+
children: [
|
|
231
|
+
multiple ? /* @__PURE__ */ jsx("span", { id: indexId, className: "sr-only", children: index + 1 }) : null,
|
|
232
|
+
content !== null && content !== void 0 ? /* @__PURE__ */ jsx(
|
|
233
|
+
SliderTooltip,
|
|
234
|
+
{
|
|
235
|
+
placement: settings?.placement ?? (model.axis === "vertical" ? "right" : "top"),
|
|
236
|
+
adjust: settings?.autoAdjustOverflow ?? true,
|
|
237
|
+
open: settings?.open,
|
|
238
|
+
direction: model.direction,
|
|
239
|
+
children: content
|
|
240
|
+
}
|
|
241
|
+
) : null
|
|
242
|
+
]
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
function SliderBody({ model }) {
|
|
247
|
+
const state = React.useContext(SliderStateContext);
|
|
248
|
+
const railRef = React.useRef(null);
|
|
249
|
+
const pendingFocus = React.useRef(null);
|
|
250
|
+
const stopDrag = React.useRef(null);
|
|
251
|
+
const [activeIndex, setActiveIndex] = React.useState(null);
|
|
252
|
+
const { values, min, max, axis, direction, reversed, stops, step } = model;
|
|
253
|
+
const thumbCount = values.length;
|
|
254
|
+
const inputRefs = React.useMemo(
|
|
255
|
+
() => Array.from({ length: thumbCount }, () => React.createRef()),
|
|
256
|
+
[thumbCount]
|
|
257
|
+
);
|
|
258
|
+
React.useLayoutEffect(() => {
|
|
259
|
+
if (pendingFocus.current !== null) {
|
|
260
|
+
inputRefs[pendingFocus.current]?.current?.focus();
|
|
261
|
+
pendingFocus.current = null;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
const stateRef = React.useRef(state);
|
|
265
|
+
React.useLayoutEffect(() => {
|
|
266
|
+
stateRef.current = state;
|
|
267
|
+
});
|
|
268
|
+
React.useLayoutEffect(() => {
|
|
269
|
+
if (activeIndex === null) return;
|
|
270
|
+
stateRef.current?.setThumbDragging(activeIndex, true);
|
|
271
|
+
return () => stateRef.current?.setThumbDragging(activeIndex, false);
|
|
272
|
+
}, [activeIndex]);
|
|
273
|
+
React.useEffect(() => () => stopDrag.current?.(), []);
|
|
274
|
+
function focusThumb(index) {
|
|
275
|
+
pendingFocus.current = index;
|
|
276
|
+
inputRefs[index]?.current?.focus();
|
|
277
|
+
}
|
|
278
|
+
function valueAtPoint(clientX, clientY) {
|
|
279
|
+
const rect = railRef.current?.getBoundingClientRect();
|
|
280
|
+
if (!rect) return min;
|
|
281
|
+
let share = axis === "vertical" ? rect.height > 0 ? (rect.bottom - clientY) / rect.height : 0 : rect.width > 0 ? (clientX - rect.left) / rect.width : 0;
|
|
282
|
+
if (axis === "horizontal" && direction === "rtl") share = 1 - share;
|
|
283
|
+
if (reversed) share = 1 - share;
|
|
284
|
+
return min + clamp(share, 0, 1) * (max - min);
|
|
285
|
+
}
|
|
286
|
+
function thumbIndexOf(target) {
|
|
287
|
+
const thumb = target?.closest?.('[data-slot="slider-thumb"]');
|
|
288
|
+
if (!thumb) return null;
|
|
289
|
+
return Number(thumb.getAttribute("data-index"));
|
|
290
|
+
}
|
|
291
|
+
function onKeyDownCapture(event) {
|
|
292
|
+
const index = thumbIndexOf(event.target);
|
|
293
|
+
if (index === null || model.rootDisabled || model.thumbDisabled(index)) return;
|
|
294
|
+
const list = model.latest.current;
|
|
295
|
+
const along = (sign, count) => stops ? placeThumb(model, list, index, list[index], sign, count) : placeThumb(model, list, index, list[index] + sign * step * count, "nearest");
|
|
296
|
+
let next;
|
|
297
|
+
if (ARROWS.has(event.key)) {
|
|
298
|
+
next = along(
|
|
299
|
+
arrowSign(event.key, axis, direction, reversed),
|
|
300
|
+
event.shiftKey ? PAGE_STEPS : 1
|
|
301
|
+
);
|
|
302
|
+
} else if (event.key === "PageUp" || event.key === "PageDown") {
|
|
303
|
+
next = along(event.key === "PageUp" ? 1 : -1, PAGE_STEPS);
|
|
304
|
+
} else if (event.key === "Home" || event.key === "End") {
|
|
305
|
+
next = placeThumb(model, list, index, event.key === "Home" ? min : max, "nearest");
|
|
306
|
+
} else if ((event.key === "Delete" || event.key === "Backspace") && model.editable && list.length > model.minCount) {
|
|
307
|
+
next = list.filter((_, at) => at !== index);
|
|
308
|
+
if (next.length > 0) pendingFocus.current = Math.min(index, next.length - 1);
|
|
309
|
+
} else {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
event.preventDefault();
|
|
313
|
+
event.stopPropagation();
|
|
314
|
+
model.update(next, true);
|
|
315
|
+
}
|
|
316
|
+
function onPointerDownCapture(event) {
|
|
317
|
+
event.stopPropagation();
|
|
318
|
+
if (model.rootDisabled) return;
|
|
319
|
+
if (event.button !== 0 || event.ctrlKey || event.metaKey || event.altKey) return;
|
|
320
|
+
const list = model.latest.current;
|
|
321
|
+
const target = event.target;
|
|
322
|
+
const at = valueAtPoint(event.clientX, event.clientY);
|
|
323
|
+
let index = null;
|
|
324
|
+
let grab = 0;
|
|
325
|
+
let track = null;
|
|
326
|
+
let next = list;
|
|
327
|
+
const pressedThumb = thumbIndexOf(target);
|
|
328
|
+
if (pressedThumb !== null) {
|
|
329
|
+
if (model.thumbDisabled(pressedThumb)) return;
|
|
330
|
+
index = pressedThumb;
|
|
331
|
+
grab = at - list[pressedThumb];
|
|
332
|
+
} else if (model.draggableTrack && list.length > 1 && target.closest?.('[data-slot="slider-range"]')) {
|
|
333
|
+
track = { at, values: list };
|
|
334
|
+
} else {
|
|
335
|
+
const mark = target.closest?.('[data-slot="slider-mark"]');
|
|
336
|
+
const point = mark ? Number(mark.getAttribute("data-value")) : at;
|
|
337
|
+
if (model.editable && list.length < model.maxCount) {
|
|
338
|
+
const settled = placeThumb(model, [point], 0, point, "nearest")[0];
|
|
339
|
+
const insertAt = list.findIndex((value) => value > settled);
|
|
340
|
+
index = insertAt === -1 ? list.length : insertAt;
|
|
341
|
+
next = [...list.slice(0, index), settled, ...list.slice(index)];
|
|
342
|
+
} else {
|
|
343
|
+
index = closestThumb(list, point, model.thumbDisabled);
|
|
344
|
+
if (index === null) return;
|
|
345
|
+
next = placeThumb(model, list, index, point, "nearest");
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
event.preventDefault();
|
|
349
|
+
const start = list;
|
|
350
|
+
const pointerId = event.pointerId;
|
|
351
|
+
if (next !== list) model.update(next, false);
|
|
352
|
+
if (index !== null) {
|
|
353
|
+
focusThumb(index);
|
|
354
|
+
setActiveIndex(index);
|
|
355
|
+
}
|
|
356
|
+
const onMove = (move) => {
|
|
357
|
+
if (move.pointerId !== pointerId) return;
|
|
358
|
+
const point = valueAtPoint(move.clientX, move.clientY);
|
|
359
|
+
if (track) {
|
|
360
|
+
model.update(shiftAll(model, track.values, point - track.at), false);
|
|
361
|
+
} else if (index !== null) {
|
|
362
|
+
model.update(
|
|
363
|
+
placeThumb(model, model.latest.current, index, point - grab, "nearest"),
|
|
364
|
+
false
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
};
|
|
368
|
+
const finish = () => {
|
|
369
|
+
window.removeEventListener("pointermove", onMove);
|
|
370
|
+
window.removeEventListener("pointerup", onUp);
|
|
371
|
+
window.removeEventListener("pointercancel", onUp);
|
|
372
|
+
stopDrag.current = null;
|
|
373
|
+
setActiveIndex(null);
|
|
374
|
+
};
|
|
375
|
+
const onUp = (up) => {
|
|
376
|
+
if (up.pointerId !== pointerId) return;
|
|
377
|
+
finish();
|
|
378
|
+
const final = model.latest.current;
|
|
379
|
+
if (!sameList(final, start)) model.update(final, true);
|
|
380
|
+
};
|
|
381
|
+
stopDrag.current?.();
|
|
382
|
+
stopDrag.current = finish;
|
|
383
|
+
window.addEventListener("pointermove", onMove);
|
|
384
|
+
window.addEventListener("pointerup", onUp);
|
|
385
|
+
window.addEventListener("pointercancel", onUp);
|
|
386
|
+
}
|
|
387
|
+
const first = values[0] ?? min;
|
|
388
|
+
const last = values[values.length - 1] ?? min;
|
|
389
|
+
const spanFrom = values.length > 1 ? first : min;
|
|
390
|
+
const spanTo = values.length > 1 ? last : first;
|
|
391
|
+
const startShare = fraction(spanFrom, min, max, reversed);
|
|
392
|
+
const endShare = fraction(spanTo, min, max, reversed);
|
|
393
|
+
const dots = !model.dots ? [] : stops ? stops : dotStops(min, max, step);
|
|
394
|
+
const markOffset = axis === "vertical" ? "--slider-mark-offset-block" : "--slider-mark-offset-inline";
|
|
395
|
+
return /* @__PURE__ */ jsxs(
|
|
396
|
+
AriaSliderTrack,
|
|
397
|
+
{
|
|
398
|
+
"data-slot": "slider-control",
|
|
399
|
+
className: "ui-slider-control",
|
|
400
|
+
onPointerDownCapture,
|
|
401
|
+
onMouseDownCapture: (event) => event.stopPropagation(),
|
|
402
|
+
onTouchStartCapture: (event) => event.stopPropagation(),
|
|
403
|
+
...{ onKeyDownCapture },
|
|
404
|
+
children: [
|
|
405
|
+
/* @__PURE__ */ jsxs("span", { ref: railRef, "data-slot": "slider-track", className: "ui-slider-track", children: [
|
|
406
|
+
model.included && values.length > 0 ? /* @__PURE__ */ jsx(
|
|
407
|
+
"span",
|
|
408
|
+
{
|
|
409
|
+
"data-slot": "slider-range",
|
|
410
|
+
"data-draggable": model.draggableTrack && values.length > 1 ? "true" : void 0,
|
|
411
|
+
className: "ui-slider-range",
|
|
412
|
+
style: {
|
|
413
|
+
"--slider-range-start": percent(Math.min(startShare, endShare)),
|
|
414
|
+
"--slider-range-size": percent(Math.abs(endShare - startShare))
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
) : null,
|
|
418
|
+
dots.map((at) => /* @__PURE__ */ jsx(
|
|
419
|
+
"span",
|
|
420
|
+
{
|
|
421
|
+
"data-slot": "slider-dot",
|
|
422
|
+
"data-active": model.included && at >= spanFrom && at <= spanTo && values.length > 0 ? "true" : void 0,
|
|
423
|
+
className: "ui-slider-dot",
|
|
424
|
+
style: offsetStyle(model, at, markOffset),
|
|
425
|
+
"aria-hidden": "true"
|
|
426
|
+
},
|
|
427
|
+
`dot-${at}`
|
|
428
|
+
))
|
|
429
|
+
] }),
|
|
430
|
+
values.map((value, index) => /* @__PURE__ */ jsx(
|
|
431
|
+
SliderThumbView,
|
|
432
|
+
{
|
|
433
|
+
model,
|
|
434
|
+
index,
|
|
435
|
+
value,
|
|
436
|
+
inputRef: inputRefs[index]
|
|
437
|
+
},
|
|
438
|
+
index
|
|
439
|
+
)),
|
|
440
|
+
model.marks.length > 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "slider-marks", className: "ui-slider-marks", "aria-hidden": "true", children: model.marks.map(([at, mark]) => {
|
|
441
|
+
const label = isMarkObject(mark) ? mark.label : mark;
|
|
442
|
+
return /* @__PURE__ */ jsx(
|
|
443
|
+
"span",
|
|
444
|
+
{
|
|
445
|
+
"data-slot": "slider-mark",
|
|
446
|
+
"data-value": at,
|
|
447
|
+
className: "ui-slider-mark",
|
|
448
|
+
style: offsetStyle(model, at, markOffset),
|
|
449
|
+
children: /* @__PURE__ */ jsx(
|
|
450
|
+
"span",
|
|
451
|
+
{
|
|
452
|
+
className: "ui-slider-mark-label",
|
|
453
|
+
style: isMarkObject(mark) ? mark.style : void 0,
|
|
454
|
+
children: label
|
|
455
|
+
}
|
|
456
|
+
)
|
|
457
|
+
},
|
|
458
|
+
`mark-${at}`
|
|
459
|
+
);
|
|
460
|
+
}) }) : null
|
|
461
|
+
]
|
|
462
|
+
}
|
|
463
|
+
);
|
|
464
|
+
}
|
|
465
|
+
const Slider = React.forwardRef((props, ref) => {
|
|
466
|
+
const {
|
|
28
467
|
className,
|
|
29
|
-
defaultValue,
|
|
30
468
|
value,
|
|
469
|
+
defaultValue,
|
|
31
470
|
min = 0,
|
|
32
471
|
max = 100,
|
|
33
472
|
step = 1,
|
|
@@ -36,96 +475,146 @@ const Slider = React.forwardRef(
|
|
|
36
475
|
dots = false,
|
|
37
476
|
included = true,
|
|
38
477
|
reverse = false,
|
|
39
|
-
tooltip = false,
|
|
40
478
|
inverted,
|
|
41
|
-
|
|
479
|
+
tooltip = false,
|
|
480
|
+
disabled,
|
|
481
|
+
orientation,
|
|
482
|
+
vertical = false,
|
|
483
|
+
dir,
|
|
484
|
+
minStepsBetweenThumbs = 0,
|
|
485
|
+
onValueChange,
|
|
486
|
+
onValueCommit,
|
|
487
|
+
onChange,
|
|
488
|
+
onChangeComplete,
|
|
489
|
+
name,
|
|
490
|
+
form,
|
|
491
|
+
id,
|
|
42
492
|
"aria-label": ariaLabel,
|
|
43
493
|
"aria-labelledby": ariaLabelledby,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
494
|
+
"aria-describedby": ariaDescribedby,
|
|
495
|
+
"aria-invalid": ariaInvalid,
|
|
496
|
+
"aria-required": ariaRequired,
|
|
497
|
+
"aria-errormessage": ariaErrormessage,
|
|
498
|
+
...rest
|
|
499
|
+
} = props;
|
|
500
|
+
const locale = useLocale();
|
|
501
|
+
const direction = dir ?? locale.direction;
|
|
502
|
+
const axis = orientation ?? (vertical ? "vertical" : "horizontal");
|
|
503
|
+
const reversed = inverted ?? reverse;
|
|
504
|
+
const rangeConfig = typeof range === "object" && range !== null ? range : void 0;
|
|
505
|
+
const rangeOn = range === true || rangeConfig !== void 0;
|
|
506
|
+
const perThumbDisabled = Array.isArray(disabled) ? disabled : void 0;
|
|
507
|
+
const rootDisabled = disabled === true;
|
|
508
|
+
const editable = Boolean(rangeConfig?.editable) && !perThumbDisabled?.some(Boolean);
|
|
509
|
+
const draggableTrack = Boolean(rangeConfig?.draggableTrack) && !editable && step !== null;
|
|
510
|
+
const ticks = markEntries(marks);
|
|
511
|
+
const stops = step === null ? markStops(min, max, ticks) : null;
|
|
512
|
+
const gridStep = stops ? gridThrough(stops) : step;
|
|
513
|
+
const places = Math.max(decimalPlaces(gridStep), decimalPlaces(min));
|
|
514
|
+
const settle = (list) => list.map(
|
|
515
|
+
(at) => stops ? nearestStop(stops, clamp(at, min, max)) : snapToStep(clamp(at, min, max), min, max, gridStep)
|
|
516
|
+
).sort((a, b) => a - b);
|
|
517
|
+
const resolve = (given) => {
|
|
518
|
+
if (rangeOn) {
|
|
519
|
+
if (!given) return [min, max];
|
|
520
|
+
if (editable || given.length >= 2) return settle(given);
|
|
521
|
+
return settle([given[0] ?? min, max]);
|
|
522
|
+
}
|
|
523
|
+
if (range === false) return settle([given?.[0] ?? min]);
|
|
524
|
+
return settle(given && given.length > 0 ? given : [min]);
|
|
525
|
+
};
|
|
526
|
+
const isControlled = value !== void 0;
|
|
527
|
+
const [internal, setInternal] = React.useState(() => resolve(toList(defaultValue)));
|
|
528
|
+
const values = isControlled ? resolve(toList(value)) : internal;
|
|
529
|
+
const latest = React.useRef(values);
|
|
530
|
+
React.useLayoutEffect(() => {
|
|
531
|
+
latest.current = values;
|
|
532
|
+
});
|
|
533
|
+
const emit = rangeOn ? (list) => list : (list) => list[0];
|
|
534
|
+
const update = (next, commit) => {
|
|
535
|
+
if (sameList(next, latest.current)) {
|
|
536
|
+
if (!commit) return;
|
|
537
|
+
} else {
|
|
538
|
+
latest.current = next;
|
|
539
|
+
if (!isControlled) setInternal(next);
|
|
540
|
+
onValueChange?.(next);
|
|
541
|
+
onChange?.(emit(next));
|
|
542
|
+
}
|
|
543
|
+
if (commit) {
|
|
544
|
+
onValueCommit?.(next);
|
|
545
|
+
onChangeComplete?.(emit(next));
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const onAriaChange = (next) => {
|
|
549
|
+
const list = latest.current;
|
|
550
|
+
const proposed = Array.isArray(next) ? next : [next];
|
|
551
|
+
const index = proposed.findIndex((at, position) => at !== list[position]);
|
|
552
|
+
if (index === -1) return;
|
|
553
|
+
const sign = proposed[index] > list[index] ? 1 : -1;
|
|
554
|
+
const placed = placeThumb(model, list, index, proposed[index], stops ? sign : "nearest");
|
|
555
|
+
if (!sameList(placed, list)) update(placed, true);
|
|
556
|
+
};
|
|
557
|
+
const isInvalid = ariaInvalid !== void 0 && ariaInvalid !== false && ariaInvalid !== "false";
|
|
558
|
+
const isRequired = ariaRequired === true || ariaRequired === "true";
|
|
559
|
+
const model = {
|
|
560
|
+
values,
|
|
561
|
+
min,
|
|
562
|
+
max,
|
|
563
|
+
axis,
|
|
564
|
+
direction,
|
|
565
|
+
reversed,
|
|
566
|
+
stops,
|
|
567
|
+
step: gridStep,
|
|
568
|
+
gap: stops ? 0 : minStepsBetweenThumbs * gridStep,
|
|
569
|
+
places,
|
|
570
|
+
included,
|
|
571
|
+
marks: ticks,
|
|
572
|
+
dots,
|
|
573
|
+
tooltip,
|
|
574
|
+
rootDisabled,
|
|
575
|
+
thumbDisabled: (index) => rootDisabled || Boolean(perThumbDisabled?.[index]),
|
|
576
|
+
editable,
|
|
577
|
+
draggableTrack,
|
|
578
|
+
minCount: rangeConfig?.minCount ?? 0,
|
|
579
|
+
maxCount: rangeConfig?.maxCount ?? Infinity,
|
|
580
|
+
name,
|
|
581
|
+
form,
|
|
582
|
+
labelledBy: ariaLabelledby,
|
|
583
|
+
label: ariaLabel,
|
|
584
|
+
isInvalid,
|
|
585
|
+
isRequired,
|
|
586
|
+
errorMessage: ariaErrormessage,
|
|
587
|
+
update,
|
|
588
|
+
latest
|
|
589
|
+
};
|
|
590
|
+
return /* @__PURE__ */ jsxs(
|
|
591
|
+
AriaSlider,
|
|
592
|
+
{
|
|
593
|
+
...rest,
|
|
594
|
+
ref,
|
|
595
|
+
id,
|
|
596
|
+
"data-slot": "slider",
|
|
597
|
+
"data-has-marks": ticks.length > 0 ? "true" : void 0,
|
|
598
|
+
"data-reverse": reversed ? "true" : void 0,
|
|
599
|
+
dir: direction,
|
|
600
|
+
className: cn("ui-slider", className),
|
|
601
|
+
value: values,
|
|
602
|
+
onChange: onAriaChange,
|
|
603
|
+
minValue: min,
|
|
604
|
+
maxValue: max,
|
|
605
|
+
step: gridStep,
|
|
606
|
+
orientation: axis,
|
|
607
|
+
isDisabled: rootDisabled,
|
|
608
|
+
"aria-labelledby": ariaLabelledby,
|
|
609
|
+
"aria-describedby": ariaDescribedby,
|
|
610
|
+
children: [
|
|
611
|
+
/* @__PURE__ */ jsx(AriaLabel, { className: "sr-only", children: ariaLabelledby ? null : ariaLabel }),
|
|
612
|
+
/* @__PURE__ */ jsx(SliderBody, { model })
|
|
613
|
+
]
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
});
|
|
617
|
+
Slider.displayName = "Slider";
|
|
129
618
|
export {
|
|
130
619
|
Slider
|
|
131
620
|
};
|