@godxjp/ui 22.0.0 → 23.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.
Files changed (115) hide show
  1. package/README.md +4 -4
  2. package/dist/app/date-format-labels.d.ts +14 -2
  3. package/dist/app/date-format-labels.js +1 -1
  4. package/dist/app/date-formats.d.ts +14 -2
  5. package/dist/app/date-formats.js +9 -2
  6. package/dist/components/data-display/data-table.d.ts +8 -1
  7. package/dist/components/data-display/data-table.js +9 -2
  8. package/dist/components/data-display/scroll-area.d.ts +24 -4
  9. package/dist/components/data-display/scroll-area.js +34 -72
  10. package/dist/components/data-entry/cascader.js +8 -6
  11. package/dist/components/data-entry/checkbox.js +3 -1
  12. package/dist/components/data-entry/choice-hit-target.d.ts +24 -0
  13. package/dist/components/data-entry/choice-hit-target.js +12 -0
  14. package/dist/components/data-entry/control-surface.d.ts +8 -0
  15. package/dist/components/data-entry/control-surface.js +10 -1
  16. package/dist/components/data-entry/form-field.js +5 -4
  17. package/dist/components/data-entry/input.js +1 -1
  18. package/dist/components/data-entry/number-input.js +27 -6
  19. package/dist/components/data-entry/radio.js +4 -3
  20. package/dist/components/data-entry/search-select.js +225 -95
  21. package/dist/components/data-entry/select.d.ts +76 -14
  22. package/dist/components/data-entry/select.js +495 -163
  23. package/dist/components/data-entry/slider.d.ts +3 -11
  24. package/dist/components/data-entry/slider.js +582 -93
  25. package/dist/components/data-entry/switch.js +3 -1
  26. package/dist/components/feedback/dialog.js +2 -2
  27. package/dist/components/feedback/sheet.js +2 -22
  28. package/dist/components/navigation/dropdown-menu.d.ts +11 -3
  29. package/dist/components/navigation/dropdown-menu.js +190 -15
  30. package/dist/components/navigation/filter-bar.js +2 -1
  31. package/dist/components/navigation/index.d.ts +0 -3
  32. package/dist/components/navigation/index.js +0 -78
  33. package/dist/components/navigation/tabs.js +16 -4
  34. package/dist/components/ui/index.d.ts +0 -3
  35. package/dist/components/ui/index.js +0 -3
  36. package/dist/components/ui/input-otp.d.ts +1 -1
  37. package/dist/components/ui/segmented.js +3 -1
  38. package/dist/components/ui/tag-input.js +3 -5
  39. package/dist/i18n/messages/en.json +5 -1
  40. package/dist/i18n/messages/ja.json +5 -1
  41. package/dist/i18n/messages/vi.json +5 -1
  42. package/dist/lib/breakpoint-token.d.ts +8 -0
  43. package/dist/lib/breakpoint-token.js +29 -0
  44. package/dist/lib/select-options.d.ts +21 -0
  45. package/dist/lib/select-options.js +46 -0
  46. package/dist/props/components/data-display.prop.d.ts +17 -7
  47. package/dist/props/components/data-entry.prop.d.ts +323 -30
  48. package/dist/props/components/index.d.ts +1 -1
  49. package/dist/props/components/navigation.prop.d.ts +25 -1
  50. package/dist/props/registry.d.ts +69 -1
  51. package/dist/props/registry.js +75 -0
  52. package/dist/props/vocabulary/data.prop.d.ts +5 -0
  53. package/dist/props/vocabulary/index.d.ts +1 -1
  54. package/dist/styles/control.css +246 -28
  55. package/dist/styles/data-display-layout.css +18 -10
  56. package/dist/styles/focus-ring.css +28 -0
  57. package/dist/styles/fonts.css +88 -4
  58. package/dist/styles/form-layout.css +4 -0
  59. package/dist/styles/layout.css +2 -1
  60. package/dist/styles/navigation-layout.css +24 -238
  61. package/dist/styles/shell-layout.css +148 -139
  62. package/dist/styles/table-layout.css +12 -5
  63. package/dist/tokens/components/control.css +12 -3
  64. package/dist/tokens/components/data-display.css +0 -5
  65. package/dist/tokens/components/navigation.css +5 -5
  66. package/dist/tokens/components/scroll-area.css +7 -0
  67. package/docs/CONSUMER-RULES.md +7 -2
  68. package/docs/CUSTOMER-THEMING.md +25 -9
  69. package/docs/DATETIME.md +11 -2
  70. package/docs/FRAME-COVERAGE-REPORT.md +4 -7
  71. package/docs/SPACING.md +35 -5
  72. package/docs/TOKENS.md +1 -1
  73. package/docs/data-display/data-table/index.tsx +44 -3
  74. package/docs/data-display/popover.tsx +4 -6
  75. package/docs/data-display/scroll-area.tsx +51 -45
  76. package/docs/data-display/table.tsx +1 -1
  77. package/docs/data-entry/date-picker.tsx +2 -3
  78. package/docs/data-entry/input-otp.tsx +5 -2
  79. package/docs/data-entry/segmented-in-filter-row.tsx +77 -0
  80. package/docs/data-entry/segmented.tsx +40 -1
  81. package/docs/data-entry/select-matrix.tsx +1 -2
  82. package/docs/data-entry/select.tsx +108 -3
  83. package/docs/data-entry/slider.tsx +125 -47
  84. package/docs/feedback/dialog.tsx +1 -2
  85. package/docs/feedback/sheet.tsx +1 -2
  86. package/docs/layout/app-shell-states.tsx +143 -0
  87. package/docs/navigation/dropdown-menu.tsx +77 -6
  88. package/docs/navigation/tabs.tsx +72 -0
  89. package/docs/roadmap/ai-chat-components.md +1 -2
  90. package/docs/roadmap/parity-audit-data-entry.md +44 -33
  91. package/docs/roadmap/parity-audit-layout-navigation-general.md +304 -310
  92. package/docs/roadmap/parity-backlog.md +2 -3
  93. package/docs/roadmap/tree-components.md +0 -1
  94. package/package.json +25 -55
  95. package/scripts/_agent-setup.mjs +18 -0
  96. package/scripts/guinea-pig-skill.md +12 -6
  97. package/scripts/ui-audit.mjs +185 -7
  98. package/scripts/visual-audit-rules.mjs +0 -7
  99. package/scripts/visual-audit.mjs +6 -27
  100. package/dist/components/navigation/context-menu.d.ts +0 -21
  101. package/dist/components/navigation/context-menu.js +0 -149
  102. package/dist/components/navigation/menubar.d.ts +0 -28
  103. package/dist/components/navigation/menubar.js +0 -136
  104. package/dist/components/navigation/navigation-menu.d.ts +0 -10
  105. package/dist/components/navigation/navigation-menu.js +0 -91
  106. package/dist/components/ui/context-menu.d.ts +0 -1
  107. package/dist/components/ui/context-menu.js +0 -2
  108. package/dist/components/ui/menubar.d.ts +0 -1
  109. package/dist/components/ui/menubar.js +0 -2
  110. package/dist/components/ui/navigation-menu.d.ts +0 -1
  111. package/dist/components/ui/navigation-menu.js +0 -2
  112. package/docs/FRAME-A11Y-CI.md +0 -349
  113. package/docs/navigation/context-menu.tsx +0 -128
  114. package/docs/navigation/menubar.tsx +0 -141
  115. 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 * as SliderPrimitive from "@radix-ui/react-slider";
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, label]) => [Number(key), label]).filter(([at]) => Number.isFinite(at)).sort((a, b) => a[0] - b[0]);
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
- const Slider = React.forwardRef(
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
- orientation = "horizontal",
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
- ...props
45
- }, ref) => {
46
- const values = React.useMemo(() => {
47
- const given = Array.isArray(value) ? value : Array.isArray(defaultValue) ? defaultValue : null;
48
- if (range === true) {
49
- if (given) return given.length >= 2 ? given.slice(0, 2) : [given[0] ?? min, max];
50
- return [min, max];
51
- }
52
- if (range === false) return given ? given.slice(0, 1) : [min];
53
- return given ?? [min, max];
54
- }, [defaultValue, max, min, range, value]);
55
- const thumbLabel = (index) => ariaLabel != null && values.length > 1 ? `${ariaLabel} ${index + 1}` : ariaLabel;
56
- const ticks = markEntries(marks);
57
- const stops = dots ? dotStops(min, max, typeof step === "number" ? step : 1) : [];
58
- const vertical = orientation === "vertical";
59
- const showTooltip = tooltip !== false && tooltip != null;
60
- const offsetStyle = (at) => ({
61
- [vertical ? "--slider-mark-offset-block" : "--slider-mark-offset-inline"]: `${fraction(at, min, max, reverse) * 100}%`
62
- });
63
- return /* @__PURE__ */ jsxs(
64
- SliderPrimitive.Root,
65
- {
66
- ref,
67
- "data-slot": "slider",
68
- "data-has-marks": ticks.length > 0 ? "true" : void 0,
69
- className: cn("ui-slider", className),
70
- defaultValue: defaultValue ?? (range === true ? [min, max] : void 0),
71
- min,
72
- max,
73
- step,
74
- value,
75
- orientation,
76
- inverted: inverted ?? reverse,
77
- ...props,
78
- children: [
79
- /* @__PURE__ */ jsxs(SliderPrimitive.Track, { "data-slot": "slider-track", className: "ui-slider-track", children: [
80
- included ? /* @__PURE__ */ jsx(SliderPrimitive.Range, { "data-slot": "slider-range", className: "ui-slider-range" }) : null,
81
- stops.map((at) => /* @__PURE__ */ jsx(
82
- "span",
83
- {
84
- "data-slot": "slider-dot",
85
- "data-active": included && at <= (values[values.length - 1] ?? min) ? "true" : void 0,
86
- className: "ui-slider-dot",
87
- style: offsetStyle(at),
88
- "aria-hidden": "true"
89
- },
90
- `dot-${at}`
91
- ))
92
- ] }),
93
- values.map((thumbValue, index) => /* @__PURE__ */ jsx(
94
- SliderPrimitive.Thumb,
95
- {
96
- "data-slot": "slider-thumb",
97
- className: "ui-slider-thumb",
98
- "aria-label": thumbLabel(index),
99
- "aria-labelledby": ariaLabelledby,
100
- children: showTooltip ? /* @__PURE__ */ jsx(
101
- "span",
102
- {
103
- "data-slot": "slider-tooltip",
104
- "data-open": tooltip !== true && tooltip.open ? "true" : void 0,
105
- className: "ui-slider-tooltip",
106
- "aria-hidden": "true",
107
- children: tooltipContent(tooltip, thumbValue)
108
- }
109
- ) : null
110
- },
111
- index
112
- )),
113
- ticks.length > 0 ? /* @__PURE__ */ jsx("span", { "data-slot": "slider-marks", className: "ui-slider-marks", "aria-hidden": "true", children: ticks.map(([at, label]) => /* @__PURE__ */ jsx(
114
- "span",
115
- {
116
- "data-slot": "slider-mark",
117
- className: "ui-slider-mark",
118
- style: offsetStyle(at),
119
- children: label
120
- },
121
- `mark-${at}`
122
- )) }) : null
123
- ]
124
- }
125
- );
126
- }
127
- );
128
- Slider.displayName = SliderPrimitive.Root.displayName;
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
  };