@weichenlin/swell-ui 0.3.0 → 0.4.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 +68 -2
- package/dist/index.d.ts +318 -39
- package/dist/index.js +2491 -420
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { ArrowDown, ArrowUp, ArrowUpDown, Check, ChevronDown, ChevronLeft, ChevronRight, CircleAlert, CircleCheck, CircleDashed, CircleX, Copy, Eye, EyeOff, Info, LoaderCircle, Menu, Moon, Search, Sun, TriangleAlert, X } from "lucide-react";
|
|
2
|
+
import { Children, Fragment, cloneElement, createContext, isValidElement, use, useCallback, useContext, useEffect, useEffectEvent, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
2
3
|
import { clsx } from "clsx";
|
|
3
4
|
import { extendTailwindMerge } from "tailwind-merge";
|
|
5
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
4
6
|
import { cva } from "class-variance-authority";
|
|
5
|
-
import { Children, cloneElement, createContext, isValidElement, useCallback, useContext, useEffect, useId, useInsertionEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
7
|
import { createPortal } from "react-dom";
|
|
7
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
8
8
|
//#region src/lib/cn.ts
|
|
9
9
|
const tone = (name) => [
|
|
10
10
|
name,
|
|
@@ -78,6 +78,595 @@ function cn(...inputs) {
|
|
|
78
78
|
return twMerge(clsx(inputs));
|
|
79
79
|
}
|
|
80
80
|
//#endregion
|
|
81
|
+
//#region src/components/input/field.tsx
|
|
82
|
+
const controlHeight = {
|
|
83
|
+
sm: "h-[34px]",
|
|
84
|
+
md: "h-[38px]",
|
|
85
|
+
lg: "h-[42px]"
|
|
86
|
+
};
|
|
87
|
+
const textSize = {
|
|
88
|
+
sm: "text-sm",
|
|
89
|
+
md: "text-sm",
|
|
90
|
+
lg: "text-base"
|
|
91
|
+
};
|
|
92
|
+
const iconSize$1 = {
|
|
93
|
+
sm: "[&_svg]:size-4",
|
|
94
|
+
md: "[&_svg]:size-4",
|
|
95
|
+
lg: "[&_svg]:size-[18px]"
|
|
96
|
+
};
|
|
97
|
+
const inputPadding = {
|
|
98
|
+
sm: "px-3",
|
|
99
|
+
md: "px-3",
|
|
100
|
+
lg: "px-4"
|
|
101
|
+
};
|
|
102
|
+
const startSlotPadding = {
|
|
103
|
+
sm: "ps-2",
|
|
104
|
+
md: "ps-2.5",
|
|
105
|
+
lg: "ps-3"
|
|
106
|
+
};
|
|
107
|
+
const endSlotPadding = {
|
|
108
|
+
sm: "pe-2",
|
|
109
|
+
md: "pe-2.5",
|
|
110
|
+
lg: "pe-3"
|
|
111
|
+
};
|
|
112
|
+
const actionsPadding = {
|
|
113
|
+
sm: "pe-[3px]",
|
|
114
|
+
md: "pe-1",
|
|
115
|
+
lg: "pe-1"
|
|
116
|
+
};
|
|
117
|
+
/** Compact Button square that fits inside the field's inner height with an even inset. */
|
|
118
|
+
const compactButton = {
|
|
119
|
+
sm: "size-[26px]",
|
|
120
|
+
md: "size-7",
|
|
121
|
+
lg: "size-8"
|
|
122
|
+
};
|
|
123
|
+
const labelStart = {
|
|
124
|
+
sm: "start-2",
|
|
125
|
+
md: "start-2",
|
|
126
|
+
lg: "start-3"
|
|
127
|
+
};
|
|
128
|
+
const floatShift = {
|
|
129
|
+
sm: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,17px))]",
|
|
130
|
+
md: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,19px))]",
|
|
131
|
+
lg: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+var(--field-label-shift,21px))]"
|
|
132
|
+
};
|
|
133
|
+
const isText = (node) => typeof node === "string" || typeof node === "number";
|
|
134
|
+
const isPresent = (node) => node != null && node !== false && node !== "";
|
|
135
|
+
function Adornment({ node, side, size }) {
|
|
136
|
+
const text = isText(node);
|
|
137
|
+
return /* @__PURE__ */ jsx("span", {
|
|
138
|
+
"aria-hidden": text ? void 0 : true,
|
|
139
|
+
className: cn("flex shrink-0 items-center self-stretch text-fg-muted [&_svg]:shrink-0", text ? ["whitespace-nowrap select-none pointer-coarse:text-base", textSize[size]] : iconSize$1[size], side === "start" ? startSlotPadding[size] : endSlotPadding[size]),
|
|
140
|
+
children: node
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const notchPadding = {
|
|
144
|
+
border: {
|
|
145
|
+
sm: "ps-2 pe-2",
|
|
146
|
+
md: "ps-2 pe-2",
|
|
147
|
+
lg: "ps-3 pe-1"
|
|
148
|
+
},
|
|
149
|
+
halo: {
|
|
150
|
+
sm: "ps-[9px] pe-[9px]",
|
|
151
|
+
md: "ps-[9px] pe-[9px]",
|
|
152
|
+
lg: "ps-[13px] pe-[5px]"
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* MUI-style notched outline: a fieldset draws the ring and an invisible legend holding the label
|
|
157
|
+
* text at its floated size cuts the gap. The legend collapses while the label rests in the field.
|
|
158
|
+
* A legend as tall as the border keeps the border at the fieldset's top edge.
|
|
159
|
+
*/
|
|
160
|
+
function Notch({ ring, size, disabled, children }) {
|
|
161
|
+
const halo = ring === "halo";
|
|
162
|
+
return /* @__PURE__ */ jsx("fieldset", {
|
|
163
|
+
"aria-hidden": true,
|
|
164
|
+
className: cn("pointer-events-none absolute m-0 min-w-0 p-0", "transition-[border-color] duration-200 ease-in-out motion-reduce:transition-none", notchPadding[ring][size], halo ? ["-inset-1 rounded-[calc(var(--radius-lg)+3px)] border-[3px] border-(--field-halo)", "forced-colors:invisible forced-colors:group-has-[:is(input,select,textarea):is(:focus,:open)]/control:visible forced-colors:border-[Highlight]"] : ["-inset-px rounded-[inherit] border border-(--field-border)", disabled && "forced-colors:border-[GrayText]"]),
|
|
165
|
+
children: /* @__PURE__ */ jsx("legend", {
|
|
166
|
+
className: cn("invisible float-none block w-auto max-w-[0.01px] overflow-hidden p-0 text-xs whitespace-nowrap", halo ? "h-0.75" : "h-px", "transition-[max-width] duration-50 ease-out motion-reduce:transition-none", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-full", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:delay-50", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:duration-100"),
|
|
167
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
168
|
+
className: "px-1",
|
|
169
|
+
children
|
|
170
|
+
})
|
|
171
|
+
})
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
/** Keeps an internal ref (focus forwarding, clear, reset) alongside the caller's ref. */
|
|
175
|
+
function useMergedRef(ownRef, ref) {
|
|
176
|
+
return useCallback((node) => {
|
|
177
|
+
ownRef.current = node;
|
|
178
|
+
if (typeof ref === "function") return ref(node);
|
|
179
|
+
if (ref) ref.current = node;
|
|
180
|
+
}, [ownRef, ref]);
|
|
181
|
+
}
|
|
182
|
+
const focusControl = (control) => control.focus();
|
|
183
|
+
/**
|
|
184
|
+
* The frame every field shares (input family, Select): label above or floating with a notched
|
|
185
|
+
* outline, the bordered control with its border/halo/hover states, description and live region.
|
|
186
|
+
* State selectors match `input`, `textarea` or `select`, never the inner buttons, so a focused clear or copy
|
|
187
|
+
* button shows only its own ring. `:open` too: an open base-select picker moves focus onto its
|
|
188
|
+
* options, and the select then matches neither `:focus` nor `:focus-within`. Inside `:is()` an
|
|
189
|
+
* unsupported `:open` is dropped alone instead of invalidating the rule.
|
|
190
|
+
*/
|
|
191
|
+
function FieldShell({ label, floating = false, helperText, error, size, id: idProp, required, disabled, readOnly, "aria-describedby": describedByProp, hasStart = false, multiline = false, footer, className, status = "", controlRef, activate = focusControl, children }) {
|
|
192
|
+
const autoId = useId();
|
|
193
|
+
const id = idProp ?? `${autoId}-control`;
|
|
194
|
+
const descriptionId = `${autoId}-description`;
|
|
195
|
+
const invalid = Boolean(error);
|
|
196
|
+
const description = invalid ? error === true ? null : error : helperText;
|
|
197
|
+
const hasDescription = isPresent(description);
|
|
198
|
+
const describedBy = [describedByProp, hasDescription ? descriptionId : null].filter(Boolean).join(" ") || void 0;
|
|
199
|
+
const hasLabel = Boolean(label);
|
|
200
|
+
const floatingLabel = floating && hasLabel;
|
|
201
|
+
const asterisk = required ? /* @__PURE__ */ jsx("span", {
|
|
202
|
+
"aria-hidden": true,
|
|
203
|
+
className: "text-danger",
|
|
204
|
+
children: " *"
|
|
205
|
+
}) : null;
|
|
206
|
+
const forwardClick = (e) => {
|
|
207
|
+
const control = controlRef.current;
|
|
208
|
+
const target = e.target;
|
|
209
|
+
if (!control || control.contains(target) || target.closest("button")) return;
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
activate(control);
|
|
212
|
+
};
|
|
213
|
+
const renderFloatingLabel = (restingWidth) => floatingLabel ? /* @__PURE__ */ jsxs("label", {
|
|
214
|
+
htmlFor: id,
|
|
215
|
+
className: cn("pointer-events-none absolute top-1/2 -translate-y-1/2 truncate px-1 leading-normal", "text-fg-muted pointer-coarse:text-base", "transition-[translate,font-size,color] duration-150 ease-out motion-reduce:transition-none", multiline && {
|
|
216
|
+
sm: "top-[18px]",
|
|
217
|
+
md: "top-5",
|
|
218
|
+
lg: "top-6"
|
|
219
|
+
}[size], labelStart[size], textSize[size], restingWidth, floatShift[size], "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:max-w-[calc(100%-1rem)]", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:text-xs", invalid ? "text-danger" : ["[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:group-has-[:is(input,select,textarea):not(:is(:focus,:open))]/control:text-fg-strong", "group-has-[:is(input,select,textarea):is(:focus,:open)]/control:text-input-focus"]),
|
|
220
|
+
children: [label, asterisk]
|
|
221
|
+
}) : null;
|
|
222
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
223
|
+
"data-slot": "input-field",
|
|
224
|
+
className: cn("flex w-full flex-col gap-1.5", disabled && "opacity-50", className),
|
|
225
|
+
children: [
|
|
226
|
+
hasLabel && !floating && /* @__PURE__ */ jsxs("label", {
|
|
227
|
+
htmlFor: id,
|
|
228
|
+
className: "self-start text-sm font-medium text-fg",
|
|
229
|
+
children: [label, asterisk]
|
|
230
|
+
}),
|
|
231
|
+
/* @__PURE__ */ jsxs("div", {
|
|
232
|
+
"data-slot": "input-control",
|
|
233
|
+
"data-start": hasStart ? "" : void 0,
|
|
234
|
+
onMouseDown: forwardClick,
|
|
235
|
+
className: cn("group/control relative isolate flex items-center rounded-lg border", floatingLabel ? "bg-transparent" : "[--field-bg:var(--color-input-bg)] bg-(--field-bg)", "transition-[border-color,box-shadow,background-color] duration-200 ease-in-out motion-reduce:transition-none", multiline ? {
|
|
236
|
+
sm: "[--field-label-shift:19px]",
|
|
237
|
+
md: "[--field-label-shift:21px]",
|
|
238
|
+
lg: "[--field-label-shift:25px]"
|
|
239
|
+
}[size] : controlHeight[size], invalid ? ["[--field-border:var(--color-danger)] [--field-halo:var(--color-input-halo-invalid)]", "has-[:is(input,select,textarea):is(:focus,:open)]:[--field-halo:var(--color-input-halo-invalid-focus)]"] : ["[--field-border:var(--color-input-border)] [--field-halo:transparent]", "has-[:is(input,select,textarea):is(:focus,:open)]:[--field-border:var(--color-input-focus)] has-[:is(input,select,textarea):is(:focus,:open)]:[--field-halo:var(--color-input-halo)]"], "[--field-lift:0_0_#0000]", !invalid && !disabled && !readOnly && [
|
|
240
|
+
"hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-border:var(--color-input-border-hover)]",
|
|
241
|
+
!floatingLabel && "hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-bg:var(--color-input-bg-hover)]",
|
|
242
|
+
"hover:not-has-[:is(input,select,textarea):is(:focus,:open)]:[--field-lift:var(--shadow-input-lift)]"
|
|
243
|
+
], floatingLabel ? ["border-transparent forced-colors:border-[Canvas]", !readOnly && "[box-shadow:var(--field-lift)]"] : [
|
|
244
|
+
"border-(--field-border)",
|
|
245
|
+
readOnly ? "[box-shadow:0_0_0_3px_var(--field-halo)]" : "[box-shadow:var(--shadow-input),var(--field-lift),0_0_0_3px_var(--field-halo)]",
|
|
246
|
+
"has-[:is(input,select,textarea):is(:focus,:open)]:outline-hidden",
|
|
247
|
+
disabled && "forced-colors:border-[GrayText]"
|
|
248
|
+
], readOnly && !floatingLabel && "[--field-bg:var(--color-bg)]", disabled && "cursor-not-allowed", "pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2"),
|
|
249
|
+
children: [floatingLabel && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Notch, {
|
|
250
|
+
size,
|
|
251
|
+
ring: "halo",
|
|
252
|
+
disabled,
|
|
253
|
+
children: [label, asterisk]
|
|
254
|
+
}), /* @__PURE__ */ jsxs(Notch, {
|
|
255
|
+
size,
|
|
256
|
+
ring: "border",
|
|
257
|
+
disabled,
|
|
258
|
+
children: [label, asterisk]
|
|
259
|
+
})] }), children({
|
|
260
|
+
id,
|
|
261
|
+
invalid,
|
|
262
|
+
describedBy,
|
|
263
|
+
floating: floatingLabel,
|
|
264
|
+
floatingLabel: renderFloatingLabel
|
|
265
|
+
})]
|
|
266
|
+
}),
|
|
267
|
+
(hasDescription || isPresent(footer)) && /* @__PURE__ */ jsxs("div", {
|
|
268
|
+
className: "flex items-start justify-between gap-3",
|
|
269
|
+
children: [hasDescription && /* @__PURE__ */ jsxs("p", {
|
|
270
|
+
id: descriptionId,
|
|
271
|
+
className: cn("m-0 flex min-w-0 flex-1 items-start gap-1 text-xs/snug", invalid ? "text-danger" : "text-fg-muted"),
|
|
272
|
+
children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
|
|
273
|
+
"aria-hidden": true,
|
|
274
|
+
className: "mt-px size-3.5 shrink-0"
|
|
275
|
+
}), /* @__PURE__ */ jsx("span", { children: description })]
|
|
276
|
+
}), footer]
|
|
277
|
+
}),
|
|
278
|
+
/* @__PURE__ */ jsx("span", {
|
|
279
|
+
"aria-live": "polite",
|
|
280
|
+
"aria-atomic": true,
|
|
281
|
+
className: "sr-only",
|
|
282
|
+
children: status
|
|
283
|
+
})
|
|
284
|
+
]
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
/** The input family's frame: adornments, actions and the input itself inside FieldShell. */
|
|
288
|
+
function FieldFrame({ label, floating = false, helperText, error, size = "md", start, end, mono = false, validating = false, validatingText = "Checking…", className, inputClassName, inputRef: inputRefProp, actions, status, ref, id, required, disabled, readOnly, placeholder, "aria-describedby": describedByProp, "aria-invalid": ariaInvalid, ...props }) {
|
|
289
|
+
const ownRef = useRef(null);
|
|
290
|
+
const inputRef = inputRefProp ?? ownRef;
|
|
291
|
+
const setRef = useMergedRef(inputRef, ref);
|
|
292
|
+
const hasStart = isPresent(start);
|
|
293
|
+
const hasEnd = isPresent(end);
|
|
294
|
+
return /* @__PURE__ */ jsx(FieldShell, {
|
|
295
|
+
label,
|
|
296
|
+
floating,
|
|
297
|
+
helperText,
|
|
298
|
+
error,
|
|
299
|
+
size,
|
|
300
|
+
id,
|
|
301
|
+
required,
|
|
302
|
+
disabled,
|
|
303
|
+
readOnly,
|
|
304
|
+
"aria-describedby": describedByProp,
|
|
305
|
+
hasStart,
|
|
306
|
+
className,
|
|
307
|
+
status: validating ? validatingText : status ?? "",
|
|
308
|
+
controlRef: inputRef,
|
|
309
|
+
children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
310
|
+
hasStart ? /* @__PURE__ */ jsx(Adornment, {
|
|
311
|
+
node: start,
|
|
312
|
+
side: "start",
|
|
313
|
+
size
|
|
314
|
+
}) : null,
|
|
315
|
+
/* @__PURE__ */ jsxs("span", {
|
|
316
|
+
className: "@container flex min-w-0 flex-1 self-stretch rounded-[inherit]",
|
|
317
|
+
children: [field.floatingLabel("max-w-[calc(100cqw-1rem)]"), /* @__PURE__ */ jsx("input", {
|
|
318
|
+
...props,
|
|
319
|
+
ref: setRef,
|
|
320
|
+
id: field.id,
|
|
321
|
+
required,
|
|
322
|
+
disabled,
|
|
323
|
+
readOnly,
|
|
324
|
+
placeholder: field.floating ? placeholder || " " : placeholder,
|
|
325
|
+
"aria-invalid": field.invalid ? true : ariaInvalid,
|
|
326
|
+
"aria-describedby": field.describedBy,
|
|
327
|
+
spellCheck: mono ? false : props.spellCheck,
|
|
328
|
+
autoCapitalize: mono ? "off" : props.autoCapitalize,
|
|
329
|
+
autoCorrect: mono ? "off" : props.autoCorrect,
|
|
330
|
+
className: cn("h-full min-w-0 flex-1 rounded-[inherit] bg-transparent text-fg outline-none", "placeholder:text-input-placeholder pointer-coarse:text-base", "disabled:cursor-not-allowed", "autofill:[transition:background-color_0s_600000s] autofill:[-webkit-text-fill-color:var(--color-fg)]", textSize[size], inputPadding[size], hasStart && (isText(start) ? "ps-1" : "ps-2"), !validating && hasEnd && isText(end) ? "pe-1" : (validating || hasEnd || actions) && "pe-2", mono && "font-mono", field.floating && ["placeholder:opacity-0", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(textarea:focus),:has(textarea:not(:placeholder-shown)),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:placeholder:opacity-100"], inputClassName)
|
|
331
|
+
})]
|
|
332
|
+
}),
|
|
333
|
+
validating ? /* @__PURE__ */ jsx("span", {
|
|
334
|
+
className: cn("flex shrink-0 items-center self-stretch text-fg-muted", iconSize$1[size], endSlotPadding[size]),
|
|
335
|
+
children: /* @__PURE__ */ jsx(LoaderCircle, {
|
|
336
|
+
"aria-hidden": true,
|
|
337
|
+
className: "animate-spin motion-reduce:[animation-duration:2.5s]"
|
|
338
|
+
})
|
|
339
|
+
}) : hasEnd ? /* @__PURE__ */ jsx(Adornment, {
|
|
340
|
+
node: end,
|
|
341
|
+
side: "end",
|
|
342
|
+
size
|
|
343
|
+
}) : null,
|
|
344
|
+
actions ? /* @__PURE__ */ jsx("span", {
|
|
345
|
+
className: cn("flex shrink-0 items-center gap-0.5", actionsPadding[size]),
|
|
346
|
+
children: actions
|
|
347
|
+
}) : null
|
|
348
|
+
] })
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
//#endregion
|
|
352
|
+
//#region src/components/accordion/use-accordion-motion.ts
|
|
353
|
+
/** Animate measured heights; never cap content with an arbitrary max-height. */
|
|
354
|
+
function useAccordionMotion(open, itemRef, restoreFocus) {
|
|
355
|
+
const panelRef = useRef(null);
|
|
356
|
+
const innerRef = useRef(null);
|
|
357
|
+
const previousOpen = useRef(open);
|
|
358
|
+
const [present, setPresent] = useState(open);
|
|
359
|
+
useLayoutEffect(() => {
|
|
360
|
+
const panel = panelRef.current;
|
|
361
|
+
const inner = innerRef.current;
|
|
362
|
+
if (!panel || !inner) return;
|
|
363
|
+
const changed = previousOpen.current !== open;
|
|
364
|
+
previousOpen.current = open;
|
|
365
|
+
let active = true;
|
|
366
|
+
let animation;
|
|
367
|
+
let observer;
|
|
368
|
+
let target = -1;
|
|
369
|
+
let settled = false;
|
|
370
|
+
const reduced = window.matchMedia?.("(prefers-reduced-motion: reduce)");
|
|
371
|
+
if (!open && panel.contains(panel.ownerDocument.activeElement) && itemRef.current) restoreFocus(itemRef.current);
|
|
372
|
+
panel.inert = !open;
|
|
373
|
+
panel.toggleAttribute("inert", !open);
|
|
374
|
+
if (open) panel.removeAttribute("aria-hidden");
|
|
375
|
+
else panel.setAttribute("aria-hidden", "true");
|
|
376
|
+
const finish = () => {
|
|
377
|
+
if (!active) return;
|
|
378
|
+
settled = true;
|
|
379
|
+
observer?.disconnect();
|
|
380
|
+
animation?.cancel();
|
|
381
|
+
animation = void 0;
|
|
382
|
+
panel.style.removeProperty("height");
|
|
383
|
+
panel.style.removeProperty("overflow");
|
|
384
|
+
panel.hidden = !open;
|
|
385
|
+
setPresent(open);
|
|
386
|
+
};
|
|
387
|
+
const run = () => {
|
|
388
|
+
if (!active || settled) return;
|
|
389
|
+
const nextTarget = open ? inner.getBoundingClientRect().height : 0;
|
|
390
|
+
if (nextTarget === target && animation) return;
|
|
391
|
+
target = nextTarget;
|
|
392
|
+
const from = panel.getBoundingClientRect().height;
|
|
393
|
+
animation?.cancel();
|
|
394
|
+
panel.style.height = `${from}px`;
|
|
395
|
+
const current = panel.animate([{ height: `${from}px` }, { height: `${target}px` }], {
|
|
396
|
+
duration: open ? 200 : 160,
|
|
397
|
+
easing: "cubic-bezier(.16,1,.3,1)",
|
|
398
|
+
fill: "both"
|
|
399
|
+
});
|
|
400
|
+
animation = current;
|
|
401
|
+
current.finished.then(() => {
|
|
402
|
+
if (active && animation === current) finish();
|
|
403
|
+
}, () => {});
|
|
404
|
+
};
|
|
405
|
+
if (!changed || reduced?.matches || typeof panel.animate !== "function") finish();
|
|
406
|
+
else {
|
|
407
|
+
if (open && !panel.style.height) panel.style.height = "0px";
|
|
408
|
+
panel.hidden = false;
|
|
409
|
+
panel.style.overflow = "hidden";
|
|
410
|
+
setPresent(true);
|
|
411
|
+
run();
|
|
412
|
+
if (open && typeof ResizeObserver !== "undefined") {
|
|
413
|
+
observer = new ResizeObserver(run);
|
|
414
|
+
observer.observe(inner);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
const onReducedMotion = () => {
|
|
418
|
+
if (reduced?.matches) finish();
|
|
419
|
+
};
|
|
420
|
+
reduced?.addEventListener("change", onReducedMotion);
|
|
421
|
+
return () => {
|
|
422
|
+
active = false;
|
|
423
|
+
observer?.disconnect();
|
|
424
|
+
reduced?.removeEventListener("change", onReducedMotion);
|
|
425
|
+
if (animation) {
|
|
426
|
+
panel.style.height = `${panel.getBoundingClientRect().height}px`;
|
|
427
|
+
animation.cancel();
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
}, [
|
|
431
|
+
open,
|
|
432
|
+
itemRef,
|
|
433
|
+
restoreFocus
|
|
434
|
+
]);
|
|
435
|
+
return {
|
|
436
|
+
panelRef,
|
|
437
|
+
innerRef,
|
|
438
|
+
present
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
//#endregion
|
|
442
|
+
//#region src/components/accordion/accordion.tsx
|
|
443
|
+
const AccordionContext = createContext(null);
|
|
444
|
+
const ItemContext = createContext(null);
|
|
445
|
+
function useAccordionContext(part) {
|
|
446
|
+
const context = useContext(AccordionContext);
|
|
447
|
+
if (!context) throw new Error(`${part} must be rendered inside <Accordion>`);
|
|
448
|
+
return context;
|
|
449
|
+
}
|
|
450
|
+
function useItemContext(part) {
|
|
451
|
+
const context = useContext(ItemContext);
|
|
452
|
+
if (!context) throw new Error(`${part} must be rendered inside <AccordionItem>`);
|
|
453
|
+
return context;
|
|
454
|
+
}
|
|
455
|
+
const valuesOf = (value) => typeof value === "string" ? [value] : [...new Set(value ?? [])];
|
|
456
|
+
const ownTriggers = (root) => [...root.querySelectorAll("[data-slot=\"accordion-trigger\"]")].filter((node) => node.closest("[data-slot=\"accordion\"]") === root);
|
|
457
|
+
/** Vertical disclosure group. Changing type requires a new React key. */
|
|
458
|
+
function Accordion({ type = "single", value, defaultValue, onValueChange, collapsible = true, variant = "line", headingLevel = 3, disabled = false, className, children, ref, onKeyDown, ...props }) {
|
|
459
|
+
const [initialType] = useState(type);
|
|
460
|
+
if (initialType !== type) throw new Error("Changing Accordion type requires a new key");
|
|
461
|
+
const controlled = value !== void 0;
|
|
462
|
+
const [internal, setInternal] = useState(() => valuesOf(defaultValue));
|
|
463
|
+
const selected = controlled ? valuesOf(value) : internal;
|
|
464
|
+
const rootRef = useRef(null);
|
|
465
|
+
const mergedRef = useMergedRef(rootRef, ref);
|
|
466
|
+
const items = useRef(/* @__PURE__ */ new Map());
|
|
467
|
+
const internalRef = useRef(internal);
|
|
468
|
+
useLayoutEffect(() => {
|
|
469
|
+
internalRef.current = internal;
|
|
470
|
+
}, [internal]);
|
|
471
|
+
const restoreFocus = useCallback((item, removing = false) => {
|
|
472
|
+
const root = rootRef.current;
|
|
473
|
+
if (!root) return;
|
|
474
|
+
const triggers = ownTriggers(root);
|
|
475
|
+
const own = triggers.find((node) => node.closest("[data-slot=\"accordion-item\"]") === item);
|
|
476
|
+
const index = own ? triggers.indexOf(own) : -1;
|
|
477
|
+
const next = (!removing && own && !own.disabled ? own : void 0) ?? triggers.slice(index + 1).find((node) => !node.disabled) ?? triggers.slice(0, Math.max(index, 0)).reverse().find((node) => !node.disabled);
|
|
478
|
+
if (next) next.focus({ preventScroll: true });
|
|
479
|
+
else {
|
|
480
|
+
const previous = root.getAttribute("tabindex");
|
|
481
|
+
root.tabIndex = -1;
|
|
482
|
+
root.focus({ preventScroll: true });
|
|
483
|
+
if (previous === null) root.removeAttribute("tabindex");
|
|
484
|
+
else root.setAttribute("tabindex", previous);
|
|
485
|
+
}
|
|
486
|
+
}, []);
|
|
487
|
+
const register = useCallback((itemValue, node) => {
|
|
488
|
+
const members = items.current;
|
|
489
|
+
if (members.has(itemValue)) throw new Error(`Accordion item values must be unique: "${itemValue}"`);
|
|
490
|
+
members.set(itemValue, node);
|
|
491
|
+
const reconcile = () => {
|
|
492
|
+
queueMicrotask(() => {
|
|
493
|
+
if (controlled || !rootRef.current) return;
|
|
494
|
+
if (internalRef.current.every((entry) => members.has(entry))) return;
|
|
495
|
+
setInternal((current) => {
|
|
496
|
+
const next = current.filter((entry) => members.has(entry));
|
|
497
|
+
return next.length === current.length ? current : next;
|
|
498
|
+
});
|
|
499
|
+
});
|
|
500
|
+
};
|
|
501
|
+
reconcile();
|
|
502
|
+
return () => {
|
|
503
|
+
members.delete(itemValue);
|
|
504
|
+
const focused = node.ownerDocument.activeElement;
|
|
505
|
+
if (focused && node.contains(focused)) {
|
|
506
|
+
const root = rootRef.current;
|
|
507
|
+
const triggers = root ? ownTriggers(root) : [];
|
|
508
|
+
const index = triggers.findIndex((trigger) => node.contains(trigger));
|
|
509
|
+
const candidates = [...triggers.slice(index + 1), ...triggers.slice(0, index).reverse()];
|
|
510
|
+
queueMicrotask(() => {
|
|
511
|
+
const active = node.ownerDocument.activeElement;
|
|
512
|
+
if (node.isConnected || !rootRef.current || active !== node.ownerDocument.body && active !== focused) return;
|
|
513
|
+
const next = candidates.find((trigger) => trigger.isConnected && !trigger.disabled);
|
|
514
|
+
if (next) next.focus({ preventScroll: true });
|
|
515
|
+
else restoreFocus(node, true);
|
|
516
|
+
});
|
|
517
|
+
}
|
|
518
|
+
reconcile();
|
|
519
|
+
};
|
|
520
|
+
}, [controlled, restoreFocus]);
|
|
521
|
+
const toggle = (itemValue) => {
|
|
522
|
+
const open = selected.includes(itemValue);
|
|
523
|
+
if (disabled || type === "single" && open && !collapsible) return;
|
|
524
|
+
const next = open ? selected.filter((entry) => entry !== itemValue) : type === "single" ? [itemValue] : [...selected, itemValue];
|
|
525
|
+
if (!controlled) setInternal(next);
|
|
526
|
+
if (type === "multiple") onValueChange?.(next);
|
|
527
|
+
else onValueChange?.(next[0] ?? null);
|
|
528
|
+
};
|
|
529
|
+
return /* @__PURE__ */ jsx(AccordionContext, {
|
|
530
|
+
value: {
|
|
531
|
+
selected,
|
|
532
|
+
variant,
|
|
533
|
+
headingLevel,
|
|
534
|
+
disabled,
|
|
535
|
+
collapsible: type === "multiple" || collapsible,
|
|
536
|
+
toggle,
|
|
537
|
+
register,
|
|
538
|
+
restoreFocus
|
|
539
|
+
},
|
|
540
|
+
children: /* @__PURE__ */ jsx(ItemContext, {
|
|
541
|
+
value: null,
|
|
542
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
543
|
+
...props,
|
|
544
|
+
ref: mergedRef,
|
|
545
|
+
"data-slot": "accordion",
|
|
546
|
+
"data-variant": variant,
|
|
547
|
+
"data-disabled": disabled || void 0,
|
|
548
|
+
className: cn("min-w-0", variant === "outline" && "flex flex-col gap-2", className),
|
|
549
|
+
onKeyDown: (event) => {
|
|
550
|
+
onKeyDown?.(event);
|
|
551
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
552
|
+
if (![
|
|
553
|
+
"ArrowDown",
|
|
554
|
+
"ArrowUp",
|
|
555
|
+
"Home",
|
|
556
|
+
"End"
|
|
557
|
+
].includes(event.key)) return;
|
|
558
|
+
const triggers = ownTriggers(event.currentTarget).filter((node) => !node.disabled);
|
|
559
|
+
const index = triggers.indexOf(event.target);
|
|
560
|
+
if (index === -1) return;
|
|
561
|
+
event.preventDefault();
|
|
562
|
+
triggers[event.key === "Home" ? 0 : event.key === "End" ? triggers.length - 1 : Math.max(0, Math.min(triggers.length - 1, index + (event.key === "ArrowDown" ? 1 : -1)))]?.focus();
|
|
563
|
+
},
|
|
564
|
+
children
|
|
565
|
+
})
|
|
566
|
+
})
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
function AccordionItem({ value, disabled = false, className, children, ref, ...props }) {
|
|
570
|
+
const group = useAccordionContext("AccordionItem");
|
|
571
|
+
if (!value.trim()) throw new Error("AccordionItem value must be a non-empty string");
|
|
572
|
+
const id = useId();
|
|
573
|
+
const itemRef = useRef(null);
|
|
574
|
+
const mergedRef = useMergedRef(itemRef, ref);
|
|
575
|
+
const register = group.register;
|
|
576
|
+
useLayoutEffect(() => {
|
|
577
|
+
if (itemRef.current) return register(value, itemRef.current);
|
|
578
|
+
}, [register, value]);
|
|
579
|
+
const open = group.selected.includes(value);
|
|
580
|
+
const isDisabled = group.disabled || disabled;
|
|
581
|
+
return /* @__PURE__ */ jsx(ItemContext, {
|
|
582
|
+
value: {
|
|
583
|
+
id,
|
|
584
|
+
value,
|
|
585
|
+
open,
|
|
586
|
+
disabled: isDisabled,
|
|
587
|
+
itemRef
|
|
588
|
+
},
|
|
589
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
590
|
+
...props,
|
|
591
|
+
ref: mergedRef,
|
|
592
|
+
"data-slot": "accordion-item",
|
|
593
|
+
"data-state": open ? "open" : "closed",
|
|
594
|
+
"data-disabled": isDisabled || void 0,
|
|
595
|
+
className: cn("min-w-0 border-input-border", group.variant === "outline" ? "rounded-xl border bg-surface" : "border-b", className),
|
|
596
|
+
children
|
|
597
|
+
})
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
function AccordionTrigger({ children, description, className, onClick, "aria-describedby": describedBy, ...props }) {
|
|
601
|
+
const group = useAccordionContext("AccordionTrigger");
|
|
602
|
+
const item = useItemContext("AccordionTrigger");
|
|
603
|
+
const Heading = `h${group.headingLevel}`;
|
|
604
|
+
const hasDescription = isPresent(description);
|
|
605
|
+
const locked = item.open && !group.collapsible;
|
|
606
|
+
return /* @__PURE__ */ jsx(Heading, {
|
|
607
|
+
"data-slot": "accordion-heading",
|
|
608
|
+
className: "m-0 text-inherit",
|
|
609
|
+
children: /* @__PURE__ */ jsxs("button", {
|
|
610
|
+
...props,
|
|
611
|
+
id: `${item.id}-trigger`,
|
|
612
|
+
type: "button",
|
|
613
|
+
disabled: item.disabled,
|
|
614
|
+
"aria-expanded": item.open,
|
|
615
|
+
"aria-controls": `${item.id}-content`,
|
|
616
|
+
"aria-disabled": locked || void 0,
|
|
617
|
+
"aria-labelledby": props["aria-label"] ? void 0 : `${item.id}-title`,
|
|
618
|
+
"aria-describedby": [describedBy, hasDescription && `${item.id}-description`].filter(Boolean).join(" ") || void 0,
|
|
619
|
+
"data-slot": "accordion-trigger",
|
|
620
|
+
"data-state": item.open ? "open" : "closed",
|
|
621
|
+
className: cn("group/accordion-trigger flex min-h-12 w-full cursor-pointer items-start gap-3 rounded-sm py-3 text-start text-sm/6 font-medium text-fg outline-none", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus", "disabled:cursor-not-allowed disabled:opacity-50 aria-disabled:cursor-default", "forced-colors:disabled:text-[GrayText]", group.variant === "outline" && "rounded-xl px-4", className),
|
|
622
|
+
onClick: (event) => {
|
|
623
|
+
onClick?.(event);
|
|
624
|
+
if (!event.defaultPrevented && !item.disabled && !locked) group.toggle(item.value);
|
|
625
|
+
},
|
|
626
|
+
children: [/* @__PURE__ */ jsxs("span", {
|
|
627
|
+
className: "min-w-0 flex-1 wrap-anywhere",
|
|
628
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
629
|
+
id: `${item.id}-title`,
|
|
630
|
+
className: "underline-offset-4 group-enabled/accordion-trigger:group-hover/accordion-trigger:underline",
|
|
631
|
+
children
|
|
632
|
+
}), hasDescription && /* @__PURE__ */ jsx("span", {
|
|
633
|
+
id: `${item.id}-description`,
|
|
634
|
+
className: "mt-0.5 block text-xs/4.5 font-normal text-fg-muted",
|
|
635
|
+
children: description
|
|
636
|
+
})]
|
|
637
|
+
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
638
|
+
"aria-hidden": true,
|
|
639
|
+
className: cn("mt-1 size-4 shrink-0 text-fg-muted transition-transform duration-200 ease-out motion-reduce:transition-none", item.open && "rotate-180")
|
|
640
|
+
})]
|
|
641
|
+
})
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
function AccordionContent({ keepMounted = true, region = false, className, contentClassName, children, ref, ...props }) {
|
|
645
|
+
const group = useAccordionContext("AccordionContent");
|
|
646
|
+
const item = useItemContext("AccordionContent");
|
|
647
|
+
const { panelRef, innerRef, present } = useAccordionMotion(item.open, item.itemRef, group.restoreFocus);
|
|
648
|
+
const mergedRef = useMergedRef(panelRef, ref);
|
|
649
|
+
return /* @__PURE__ */ jsx("div", {
|
|
650
|
+
...props,
|
|
651
|
+
ref: mergedRef,
|
|
652
|
+
id: `${item.id}-content`,
|
|
653
|
+
role: region ? "region" : void 0,
|
|
654
|
+
"aria-labelledby": region ? `${item.id}-title` : void 0,
|
|
655
|
+
hidden: !item.open && !present,
|
|
656
|
+
inert: !item.open && !present,
|
|
657
|
+
"aria-hidden": !item.open && !present ? true : void 0,
|
|
658
|
+
"data-slot": "accordion-content",
|
|
659
|
+
"data-state": item.open ? "open" : "closed",
|
|
660
|
+
className: cn("min-w-0", contentClassName),
|
|
661
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
662
|
+
ref: innerRef,
|
|
663
|
+
"data-slot": "accordion-content-inner",
|
|
664
|
+
className: cn("flow-root min-w-0 pb-4 text-sm/5.5 wrap-anywhere text-fg-muted", group.variant === "outline" ? "ps-4 pe-11" : "pe-7", className),
|
|
665
|
+
children: (keepMounted || item.open || present) && children
|
|
666
|
+
})
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
//#endregion
|
|
81
670
|
//#region src/hooks/use-press-feedback.ts
|
|
82
671
|
const GROW_MS = 450;
|
|
83
672
|
const FADE_MS = 300;
|
|
@@ -221,7 +810,7 @@ function Slot({ children, ref, ...props }) {
|
|
|
221
810
|
const OPEN_DELAY_MS = 300;
|
|
222
811
|
const GAP_PX = 6;
|
|
223
812
|
const VIEWPORT_PADDING_PX = 8;
|
|
224
|
-
const opposite = {
|
|
813
|
+
const opposite$1 = {
|
|
225
814
|
top: "bottom",
|
|
226
815
|
bottom: "top",
|
|
227
816
|
left: "right",
|
|
@@ -234,7 +823,7 @@ function isFocusVisible$1(el) {
|
|
|
234
823
|
return true;
|
|
235
824
|
}
|
|
236
825
|
}
|
|
237
|
-
function clamp(value, min, max) {
|
|
826
|
+
function clamp$2(value, min, max) {
|
|
238
827
|
return Math.max(min, Math.min(value, Math.max(min, max)));
|
|
239
828
|
}
|
|
240
829
|
/**
|
|
@@ -251,15 +840,15 @@ function place(anchor, width, height, preferred) {
|
|
|
251
840
|
right: vw - anchor.right
|
|
252
841
|
};
|
|
253
842
|
const need = (s) => (s === "top" || s === "bottom" ? height : width) + GAP_PX + VIEWPORT_PADDING_PX;
|
|
254
|
-
const side = room[preferred] < need(preferred) && room[opposite[preferred]] > room[preferred] ? opposite[preferred] : preferred;
|
|
843
|
+
const side = room[preferred] < need(preferred) && room[opposite$1[preferred]] > room[preferred] ? opposite$1[preferred] : preferred;
|
|
255
844
|
if (side === "top" || side === "bottom") return {
|
|
256
845
|
side,
|
|
257
|
-
left: clamp(anchor.left + anchor.width / 2 - width / 2, VIEWPORT_PADDING_PX, vw - width - VIEWPORT_PADDING_PX),
|
|
846
|
+
left: clamp$2(anchor.left + anchor.width / 2 - width / 2, VIEWPORT_PADDING_PX, vw - width - VIEWPORT_PADDING_PX),
|
|
258
847
|
top: side === "top" ? anchor.top - height - GAP_PX : anchor.bottom
|
|
259
848
|
};
|
|
260
849
|
return {
|
|
261
850
|
side,
|
|
262
|
-
top: clamp(anchor.top + anchor.height / 2 - height / 2, VIEWPORT_PADDING_PX, vh - height - VIEWPORT_PADDING_PX),
|
|
851
|
+
top: clamp$2(anchor.top + anchor.height / 2 - height / 2, VIEWPORT_PADDING_PX, vh - height - VIEWPORT_PADDING_PX),
|
|
263
852
|
left: side === "left" ? anchor.left - width - GAP_PX : anchor.right
|
|
264
853
|
};
|
|
265
854
|
}
|
|
@@ -452,7 +1041,7 @@ const spinnerSize = {
|
|
|
452
1041
|
md: 18,
|
|
453
1042
|
lg: 20
|
|
454
1043
|
};
|
|
455
|
-
const iconSize
|
|
1044
|
+
const iconSize = {
|
|
456
1045
|
sm: "[&_svg]:size-4",
|
|
457
1046
|
md: "[&_svg]:size-4",
|
|
458
1047
|
lg: "[&_svg]:size-[18px]"
|
|
@@ -470,7 +1059,7 @@ const iconEndPadding = {
|
|
|
470
1059
|
/** Press layer + content + spinner; shared by every button-styled host. */
|
|
471
1060
|
function ButtonContent({ size, ripple, loading = false, iconLeft, iconRight, children }) {
|
|
472
1061
|
const layerRef = usePressFeedback(ripple);
|
|
473
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1062
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
474
1063
|
/* @__PURE__ */ jsx("span", {
|
|
475
1064
|
ref: layerRef,
|
|
476
1065
|
"data-ripple": ripple ? "" : void 0,
|
|
@@ -478,7 +1067,7 @@ function ButtonContent({ size, ripple, loading = false, iconLeft, iconRight, chi
|
|
|
478
1067
|
className: "pointer-events-none absolute inset-0 overflow-hidden rounded-[inherit] forced-colors:hidden"
|
|
479
1068
|
}),
|
|
480
1069
|
/* @__PURE__ */ jsxs("span", {
|
|
481
|
-
className: cn("relative inline-flex items-center gap-2 [&_svg]:shrink-0", iconSize
|
|
1070
|
+
className: cn("relative inline-flex items-center gap-2 [&_svg]:shrink-0", iconSize[size], loading && "opacity-0"),
|
|
482
1071
|
children: [
|
|
483
1072
|
iconLeft && /* @__PURE__ */ jsx("span", {
|
|
484
1073
|
"aria-hidden": true,
|
|
@@ -569,7 +1158,7 @@ function ButtonImpl({ asChild = false, className, intent, size, tone, fullWidth,
|
|
|
569
1158
|
},
|
|
570
1159
|
children: content(children)
|
|
571
1160
|
});
|
|
572
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [tooltip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
1161
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [tooltip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
573
1162
|
...tooltip,
|
|
574
1163
|
className: fullWidth ? "flex w-full" : void 0,
|
|
575
1164
|
children: button
|
|
@@ -667,395 +1256,135 @@ function Alert({ tone = "neutral", title, action, onDismiss, dismissLabel = "Dis
|
|
|
667
1256
|
children: action
|
|
668
1257
|
})
|
|
669
1258
|
]
|
|
670
|
-
}), onDismiss && /* @__PURE__ */ jsx(Button, {
|
|
671
|
-
"aria-label": dismissLabel,
|
|
672
|
-
className: "-my-1.5 -me-1.5 shrink-0 text-fg-muted @md/alert:self-center",
|
|
673
|
-
icon: /* @__PURE__ */ jsx(X, {}),
|
|
674
|
-
intent: "ghost",
|
|
675
|
-
size: "sm",
|
|
676
|
-
onClick: onDismiss
|
|
677
|
-
})]
|
|
678
|
-
});
|
|
679
|
-
}
|
|
680
|
-
//#endregion
|
|
681
|
-
//#region src/components/badge/badge.tsx
|
|
682
|
-
const toneClasses$1 = {
|
|
683
|
-
neutral: {
|
|
684
|
-
subtle: ["border-neutral-border bg-neutral-subtle-bg text-neutral-subtle-fg", "text-neutral"],
|
|
685
|
-
outline: ["border-neutral-border bg-transparent text-neutral-subtle-fg", "text-neutral"],
|
|
686
|
-
solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
|
|
687
|
-
},
|
|
688
|
-
info: {
|
|
689
|
-
subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
|
|
690
|
-
outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
|
|
691
|
-
solid: ["border-info bg-info text-info-fg", "text-info-fg"]
|
|
692
|
-
},
|
|
693
|
-
success: {
|
|
694
|
-
subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
|
|
695
|
-
outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
|
|
696
|
-
solid: ["border-success bg-success text-success-fg", "text-success-fg"]
|
|
697
|
-
},
|
|
698
|
-
warning: {
|
|
699
|
-
subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
|
|
700
|
-
outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
|
|
701
|
-
solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
|
|
702
|
-
},
|
|
703
|
-
danger: {
|
|
704
|
-
subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
|
|
705
|
-
outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
|
|
706
|
-
solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
|
|
707
|
-
}
|
|
708
|
-
};
|
|
709
|
-
const sizeClasses$3 = {
|
|
710
|
-
sm: {
|
|
711
|
-
base: "h-5 px-1.5",
|
|
712
|
-
withIcon: "ps-1"
|
|
713
|
-
},
|
|
714
|
-
md: {
|
|
715
|
-
base: "h-6 px-2",
|
|
716
|
-
withIcon: "ps-1.5"
|
|
717
|
-
}
|
|
718
|
-
};
|
|
719
|
-
function Badge({ tone = "neutral", variant = "subtle", size = "md", icon, busy = false, context, className, children, ...props }) {
|
|
720
|
-
const [toneClass, iconClass] = toneClasses$1[tone][variant];
|
|
721
|
-
const hasIcon = busy || icon !== void 0 && icon !== null && icon !== false;
|
|
722
|
-
return /* @__PURE__ */ jsxs("span", {
|
|
723
|
-
...props,
|
|
724
|
-
"data-slot": "badge",
|
|
725
|
-
className: cn("inline-flex items-center gap-1 rounded-md border text-xs leading-none font-medium whitespace-nowrap", "forced-colors:border-[CanvasText]", sizeClasses$3[size].base, hasIcon && sizeClasses$3[size].withIcon, toneClass, className),
|
|
726
|
-
children: [hasIcon && /* @__PURE__ */ jsx("span", {
|
|
727
|
-
"aria-hidden": "true",
|
|
728
|
-
"data-slot": "badge-icon",
|
|
729
|
-
className: cn("inline-flex shrink-0 items-center [&_svg]:size-3 [&_svg]:shrink-0", iconClass),
|
|
730
|
-
children: busy ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] }) : icon
|
|
731
|
-
}), /* @__PURE__ */ jsxs("span", {
|
|
732
|
-
"data-slot": "badge-label",
|
|
733
|
-
className: "min-w-0 truncate leading-4",
|
|
734
|
-
children: [context && /* @__PURE__ */ jsxs("span", {
|
|
735
|
-
className: "sr-only",
|
|
736
|
-
children: [context, ": "]
|
|
737
|
-
}), children]
|
|
738
|
-
})]
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
//#endregion
|
|
742
|
-
//#region src/components/card/card.tsx
|
|
743
|
-
const CardContext = createContext({
|
|
744
|
-
titleId: "",
|
|
745
|
-
setTitled: () => {}
|
|
746
|
-
});
|
|
747
|
-
/**
|
|
748
|
-
* Bordered surface for a group of related content. No padding of its own: CardHeader and
|
|
749
|
-
* CardContent add it, so full-bleed rows (lists, tables) can sit edge to edge.
|
|
750
|
-
*/
|
|
751
|
-
function Card({ as: Comp = "div", className, ...props }) {
|
|
752
|
-
const titleId = useId();
|
|
753
|
-
const [titled, setTitled] = useState(false);
|
|
754
|
-
return /* @__PURE__ */ jsx(CardContext, {
|
|
755
|
-
value: {
|
|
756
|
-
titleId,
|
|
757
|
-
setTitled
|
|
758
|
-
},
|
|
759
|
-
children: /* @__PURE__ */ jsx(Comp, {
|
|
760
|
-
"aria-labelledby": (Comp === "section" || Comp === "article") && titled && !props["aria-label"] ? titleId : void 0,
|
|
761
|
-
...props,
|
|
762
|
-
"data-slot": "card",
|
|
763
|
-
className: cn("rounded-xl border border-border-strong bg-surface", className)
|
|
764
|
-
})
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
function CardHeader({ title, description, action, headingLevel = 2, className, ...props }) {
|
|
768
|
-
const { titleId, setTitled } = useContext(CardContext);
|
|
769
|
-
useLayoutEffect(() => {
|
|
770
|
-
setTitled(true);
|
|
771
|
-
return () => setTitled(false);
|
|
772
|
-
}, [setTitled]);
|
|
773
|
-
const Heading = headingLevel === 2 ? "h2" : "h3";
|
|
774
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
775
|
-
...props,
|
|
776
|
-
"data-slot": "card-header",
|
|
777
|
-
className: cn("flex items-start justify-between gap-4 px-5 pt-5", className),
|
|
778
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
779
|
-
className: "min-w-0",
|
|
780
|
-
children: [/* @__PURE__ */ jsx(Heading, {
|
|
781
|
-
id: titleId,
|
|
782
|
-
className: "font-sans text-sm font-medium",
|
|
783
|
-
children: title
|
|
784
|
-
}), description && /* @__PURE__ */ jsx("p", {
|
|
785
|
-
className: "text-xs text-fg-muted",
|
|
786
|
-
children: description
|
|
787
|
-
})]
|
|
788
|
-
}), action && /* @__PURE__ */ jsx("div", {
|
|
789
|
-
className: "shrink-0",
|
|
790
|
-
children: action
|
|
791
|
-
})]
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
function CardContent({ className, ...props }) {
|
|
795
|
-
return /* @__PURE__ */ jsx("div", {
|
|
796
|
-
...props,
|
|
797
|
-
"data-slot": "card-content",
|
|
798
|
-
className: cn("p-5 [[data-slot=card-header]+&]:pt-4", className)
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
//#endregion
|
|
802
|
-
//#region src/components/input/field.tsx
|
|
803
|
-
const controlHeight = {
|
|
804
|
-
sm: "h-[34px]",
|
|
805
|
-
md: "h-[38px]",
|
|
806
|
-
lg: "h-[42px]"
|
|
807
|
-
};
|
|
808
|
-
const textSize = {
|
|
809
|
-
sm: "text-sm",
|
|
810
|
-
md: "text-sm",
|
|
811
|
-
lg: "text-base"
|
|
812
|
-
};
|
|
813
|
-
const iconSize = {
|
|
814
|
-
sm: "[&_svg]:size-4",
|
|
815
|
-
md: "[&_svg]:size-4",
|
|
816
|
-
lg: "[&_svg]:size-[18px]"
|
|
817
|
-
};
|
|
818
|
-
const inputPadding = {
|
|
819
|
-
sm: "px-3",
|
|
820
|
-
md: "px-3",
|
|
821
|
-
lg: "px-4"
|
|
822
|
-
};
|
|
823
|
-
const startSlotPadding = {
|
|
824
|
-
sm: "ps-2",
|
|
825
|
-
md: "ps-2.5",
|
|
826
|
-
lg: "ps-3"
|
|
827
|
-
};
|
|
828
|
-
const endSlotPadding = {
|
|
829
|
-
sm: "pe-2",
|
|
830
|
-
md: "pe-2.5",
|
|
831
|
-
lg: "pe-3"
|
|
832
|
-
};
|
|
833
|
-
const actionsPadding = {
|
|
834
|
-
sm: "pe-[3px]",
|
|
835
|
-
md: "pe-1",
|
|
836
|
-
lg: "pe-1"
|
|
837
|
-
};
|
|
838
|
-
/** Compact Button square that fits inside the field's inner height with an even inset. */
|
|
839
|
-
const compactButton = {
|
|
840
|
-
sm: "size-[26px]",
|
|
841
|
-
md: "size-7",
|
|
842
|
-
lg: "size-8"
|
|
843
|
-
};
|
|
844
|
-
const labelStart = {
|
|
845
|
-
sm: "start-2",
|
|
846
|
-
md: "start-2",
|
|
847
|
-
lg: "start-3"
|
|
848
|
-
};
|
|
849
|
-
const floatShift = {
|
|
850
|
-
sm: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+17px)]",
|
|
851
|
-
md: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+19px)]",
|
|
852
|
-
lg: "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:-translate-y-[calc(50%+21px)]"
|
|
853
|
-
};
|
|
854
|
-
const isText = (node) => typeof node === "string" || typeof node === "number";
|
|
855
|
-
const isPresent = (node) => node != null && node !== false && node !== "";
|
|
856
|
-
function Adornment({ node, side, size }) {
|
|
857
|
-
const text = isText(node);
|
|
858
|
-
return /* @__PURE__ */ jsx("span", {
|
|
859
|
-
"aria-hidden": text ? void 0 : true,
|
|
860
|
-
className: cn("flex shrink-0 items-center self-stretch text-fg-muted [&_svg]:shrink-0", text ? ["whitespace-nowrap select-none pointer-coarse:text-base", textSize[size]] : iconSize[size], side === "start" ? startSlotPadding[size] : endSlotPadding[size]),
|
|
861
|
-
children: node
|
|
1259
|
+
}), onDismiss && /* @__PURE__ */ jsx(Button, {
|
|
1260
|
+
"aria-label": dismissLabel,
|
|
1261
|
+
className: "-my-1.5 -me-1.5 shrink-0 text-fg-muted @md/alert:self-center",
|
|
1262
|
+
icon: /* @__PURE__ */ jsx(X, {}),
|
|
1263
|
+
intent: "ghost",
|
|
1264
|
+
size: "sm",
|
|
1265
|
+
onClick: onDismiss
|
|
1266
|
+
})]
|
|
862
1267
|
});
|
|
863
1268
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1269
|
+
//#endregion
|
|
1270
|
+
//#region src/components/badge/badge.tsx
|
|
1271
|
+
const toneClasses$1 = {
|
|
1272
|
+
neutral: {
|
|
1273
|
+
subtle: ["border-neutral-border bg-neutral-subtle-bg text-neutral-subtle-fg", "text-neutral"],
|
|
1274
|
+
outline: ["border-neutral-border bg-transparent text-neutral-subtle-fg", "text-neutral"],
|
|
1275
|
+
solid: ["border-neutral bg-neutral text-neutral-fg", "text-neutral-fg"]
|
|
869
1276
|
},
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
1277
|
+
info: {
|
|
1278
|
+
subtle: ["border-info-border bg-info-subtle-bg text-info-subtle-fg", "text-info"],
|
|
1279
|
+
outline: ["border-info-border bg-transparent text-info-subtle-fg", "text-info"],
|
|
1280
|
+
solid: ["border-info bg-info text-info-fg", "text-info-fg"]
|
|
1281
|
+
},
|
|
1282
|
+
success: {
|
|
1283
|
+
subtle: ["border-success-border bg-success-subtle-bg text-success-subtle-fg", "text-success"],
|
|
1284
|
+
outline: ["border-success-border bg-transparent text-success-subtle-fg", "text-success"],
|
|
1285
|
+
solid: ["border-success bg-success text-success-fg", "text-success-fg"]
|
|
1286
|
+
},
|
|
1287
|
+
warning: {
|
|
1288
|
+
subtle: ["border-warning-border bg-warning-subtle-bg text-warning-subtle-fg", "text-warning"],
|
|
1289
|
+
outline: ["border-warning-border bg-transparent text-warning-subtle-fg", "text-warning"],
|
|
1290
|
+
solid: ["border-warning bg-warning text-warning-fg", "text-warning-fg"]
|
|
1291
|
+
},
|
|
1292
|
+
danger: {
|
|
1293
|
+
subtle: ["border-danger-border bg-danger-subtle-bg text-danger-subtle-fg", "text-danger"],
|
|
1294
|
+
outline: ["border-danger-border bg-transparent text-danger-subtle-fg", "text-danger"],
|
|
1295
|
+
solid: ["border-danger bg-danger text-danger-fg", "text-danger-fg"]
|
|
874
1296
|
}
|
|
875
1297
|
};
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1298
|
+
const sizeClasses$3 = {
|
|
1299
|
+
sm: {
|
|
1300
|
+
base: "h-5 px-1.5",
|
|
1301
|
+
withIcon: "ps-1"
|
|
1302
|
+
},
|
|
1303
|
+
md: {
|
|
1304
|
+
base: "h-6 px-2",
|
|
1305
|
+
withIcon: "ps-1.5"
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
function Badge({ tone = "neutral", variant = "subtle", size = "md", icon, busy = false, context, className, children, ...props }) {
|
|
1309
|
+
const [toneClass, iconClass] = toneClasses$1[tone][variant];
|
|
1310
|
+
const hasIcon = busy || icon !== void 0 && icon !== null && icon !== false;
|
|
1311
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
1312
|
+
...props,
|
|
1313
|
+
"data-slot": "badge",
|
|
1314
|
+
className: cn("inline-flex items-center gap-1 rounded-md border text-xs leading-none font-medium whitespace-nowrap", "forced-colors:border-[CanvasText]", sizeClasses$3[size].base, hasIcon && sizeClasses$3[size].withIcon, toneClass, className),
|
|
1315
|
+
children: [hasIcon && /* @__PURE__ */ jsx("span", {
|
|
1316
|
+
"aria-hidden": "true",
|
|
1317
|
+
"data-slot": "badge-icon",
|
|
1318
|
+
className: cn("inline-flex shrink-0 items-center [&_svg]:size-3 [&_svg]:shrink-0", iconClass),
|
|
1319
|
+
children: busy ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] }) : icon
|
|
1320
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
1321
|
+
"data-slot": "badge-label",
|
|
1322
|
+
className: "min-w-0 truncate leading-4",
|
|
1323
|
+
children: [context && /* @__PURE__ */ jsxs("span", {
|
|
1324
|
+
className: "sr-only",
|
|
1325
|
+
children: [context, ": "]
|
|
1326
|
+
}), children]
|
|
1327
|
+
})]
|
|
893
1328
|
});
|
|
894
1329
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
}, [ownRef, ref]);
|
|
902
|
-
}
|
|
903
|
-
const focusControl = (control) => control.focus();
|
|
1330
|
+
//#endregion
|
|
1331
|
+
//#region src/components/card/card.tsx
|
|
1332
|
+
const CardContext = createContext({
|
|
1333
|
+
titleId: "",
|
|
1334
|
+
setTitled: () => {}
|
|
1335
|
+
});
|
|
904
1336
|
/**
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
* State selectors match `input` or `select`, never the inner buttons, so a focused clear or copy
|
|
908
|
-
* button shows only its own ring. `:open` too: an open base-select picker moves focus onto its
|
|
909
|
-
* options, and the select then matches neither `:focus` nor `:focus-within`. Inside `:is()` an
|
|
910
|
-
* unsupported `:open` is dropped alone instead of invalidating the rule.
|
|
1337
|
+
* Bordered surface for a group of related content. No padding of its own: CardHeader and
|
|
1338
|
+
* CardContent add it, so full-bleed rows (lists, tables) can sit edge to edge.
|
|
911
1339
|
*/
|
|
912
|
-
function
|
|
913
|
-
const
|
|
914
|
-
const
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
})
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
};
|
|
934
|
-
const
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
disabled && "forced-colors:border-[GrayText]"
|
|
961
|
-
], readOnly && !floatingLabel && "[--field-bg:var(--color-bg)]", disabled && "cursor-not-allowed", "pointer-coarse:before:absolute pointer-coarse:before:inset-x-0 pointer-coarse:before:top-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:h-11 pointer-coarse:before:-translate-y-1/2"),
|
|
962
|
-
children: [floatingLabel && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Notch, {
|
|
963
|
-
size,
|
|
964
|
-
ring: "halo",
|
|
965
|
-
disabled,
|
|
966
|
-
children: [label, asterisk]
|
|
967
|
-
}), /* @__PURE__ */ jsxs(Notch, {
|
|
968
|
-
size,
|
|
969
|
-
ring: "border",
|
|
970
|
-
disabled,
|
|
971
|
-
children: [label, asterisk]
|
|
972
|
-
})] }), children({
|
|
973
|
-
id,
|
|
974
|
-
invalid,
|
|
975
|
-
describedBy,
|
|
976
|
-
floating: floatingLabel,
|
|
977
|
-
floatingLabel: renderFloatingLabel
|
|
978
|
-
})]
|
|
979
|
-
}),
|
|
980
|
-
hasDescription && /* @__PURE__ */ jsxs("p", {
|
|
981
|
-
id: descriptionId,
|
|
982
|
-
className: cn("m-0 flex items-start gap-1 text-xs/snug", invalid ? "text-danger" : "text-fg-muted"),
|
|
983
|
-
children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
|
|
984
|
-
"aria-hidden": true,
|
|
985
|
-
className: "mt-px size-3.5 shrink-0"
|
|
986
|
-
}), /* @__PURE__ */ jsx("span", { children: description })]
|
|
987
|
-
}),
|
|
988
|
-
/* @__PURE__ */ jsx("span", {
|
|
989
|
-
"aria-live": "polite",
|
|
990
|
-
"aria-atomic": true,
|
|
991
|
-
className: "sr-only",
|
|
992
|
-
children: status
|
|
993
|
-
})
|
|
994
|
-
]
|
|
995
|
-
});
|
|
996
|
-
}
|
|
997
|
-
/** The input family's frame: adornments, actions and the input itself inside FieldShell. */
|
|
998
|
-
function FieldFrame({ label, floating = false, helperText, error, size = "md", start, end, mono = false, validating = false, validatingText = "Checking…", className, inputClassName, inputRef: inputRefProp, actions, status, ref, id, required, disabled, readOnly, placeholder, "aria-describedby": describedByProp, "aria-invalid": ariaInvalid, ...props }) {
|
|
999
|
-
const ownRef = useRef(null);
|
|
1000
|
-
const inputRef = inputRefProp ?? ownRef;
|
|
1001
|
-
const setRef = useMergedRef(inputRef, ref);
|
|
1002
|
-
const hasStart = isPresent(start);
|
|
1003
|
-
const hasEnd = isPresent(end);
|
|
1004
|
-
return /* @__PURE__ */ jsx(FieldShell, {
|
|
1005
|
-
label,
|
|
1006
|
-
floating,
|
|
1007
|
-
helperText,
|
|
1008
|
-
error,
|
|
1009
|
-
size,
|
|
1010
|
-
id,
|
|
1011
|
-
required,
|
|
1012
|
-
disabled,
|
|
1013
|
-
readOnly,
|
|
1014
|
-
"aria-describedby": describedByProp,
|
|
1015
|
-
hasStart,
|
|
1016
|
-
className,
|
|
1017
|
-
status: validating ? validatingText : status ?? "",
|
|
1018
|
-
controlRef: inputRef,
|
|
1019
|
-
children: (field) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1020
|
-
hasStart ? /* @__PURE__ */ jsx(Adornment, {
|
|
1021
|
-
node: start,
|
|
1022
|
-
side: "start",
|
|
1023
|
-
size
|
|
1024
|
-
}) : null,
|
|
1025
|
-
/* @__PURE__ */ jsxs("span", {
|
|
1026
|
-
className: "@container flex min-w-0 flex-1 self-stretch rounded-[inherit]",
|
|
1027
|
-
children: [field.floatingLabel("max-w-[calc(100cqw-1rem)]"), /* @__PURE__ */ jsx("input", {
|
|
1028
|
-
...props,
|
|
1029
|
-
ref: setRef,
|
|
1030
|
-
id: field.id,
|
|
1031
|
-
required,
|
|
1032
|
-
disabled,
|
|
1033
|
-
readOnly,
|
|
1034
|
-
placeholder: field.floating ? placeholder || " " : placeholder,
|
|
1035
|
-
"aria-invalid": field.invalid ? true : ariaInvalid,
|
|
1036
|
-
"aria-describedby": field.describedBy,
|
|
1037
|
-
spellCheck: mono ? false : props.spellCheck,
|
|
1038
|
-
autoCapitalize: mono ? "off" : props.autoCapitalize,
|
|
1039
|
-
autoCorrect: mono ? "off" : props.autoCorrect,
|
|
1040
|
-
className: cn("h-full min-w-0 flex-1 rounded-[inherit] bg-transparent text-fg outline-none", "placeholder:text-input-placeholder pointer-coarse:text-base", "disabled:cursor-not-allowed", "autofill:[transition:background-color_0s_600000s] autofill:[-webkit-text-fill-color:var(--color-fg)]", textSize[size], inputPadding[size], hasStart && (isText(start) ? "ps-1" : "ps-2"), !validating && hasEnd && isText(end) ? "pe-1" : (validating || hasEnd || actions) && "pe-2", mono && "font-mono", field.floating && ["placeholder:opacity-0", "[[data-slot=input-control]:is([data-start],:has(input:focus),:has(input:not(:placeholder-shown)),:has(input:autofill),:has(input:-webkit-autofill),:has(select:focus),:has(select:open),:has(select):not(:has(option[value=\"\"]:checked)))_&]:placeholder:opacity-100"], inputClassName)
|
|
1041
|
-
})]
|
|
1042
|
-
}),
|
|
1043
|
-
validating ? /* @__PURE__ */ jsx("span", {
|
|
1044
|
-
className: cn("flex shrink-0 items-center self-stretch text-fg-muted", iconSize[size], endSlotPadding[size]),
|
|
1045
|
-
children: /* @__PURE__ */ jsx(LoaderCircle, {
|
|
1046
|
-
"aria-hidden": true,
|
|
1047
|
-
className: "animate-spin motion-reduce:[animation-duration:2.5s]"
|
|
1048
|
-
})
|
|
1049
|
-
}) : hasEnd ? /* @__PURE__ */ jsx(Adornment, {
|
|
1050
|
-
node: end,
|
|
1051
|
-
side: "end",
|
|
1052
|
-
size
|
|
1053
|
-
}) : null,
|
|
1054
|
-
actions ? /* @__PURE__ */ jsx("span", {
|
|
1055
|
-
className: cn("flex shrink-0 items-center gap-0.5", actionsPadding[size]),
|
|
1056
|
-
children: actions
|
|
1057
|
-
}) : null
|
|
1058
|
-
] })
|
|
1340
|
+
function Card({ as: Comp = "div", className, ...props }) {
|
|
1341
|
+
const titleId = useId();
|
|
1342
|
+
const [titled, setTitled] = useState(false);
|
|
1343
|
+
return /* @__PURE__ */ jsx(CardContext, {
|
|
1344
|
+
value: {
|
|
1345
|
+
titleId,
|
|
1346
|
+
setTitled
|
|
1347
|
+
},
|
|
1348
|
+
children: /* @__PURE__ */ jsx(Comp, {
|
|
1349
|
+
"aria-labelledby": (Comp === "section" || Comp === "article") && titled && !props["aria-label"] ? titleId : void 0,
|
|
1350
|
+
...props,
|
|
1351
|
+
"data-slot": "card",
|
|
1352
|
+
className: cn("rounded-xl border border-border-strong bg-surface", className)
|
|
1353
|
+
})
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
function CardHeader({ title, description, action, headingLevel = 2, className, ...props }) {
|
|
1357
|
+
const { titleId, setTitled } = useContext(CardContext);
|
|
1358
|
+
useLayoutEffect(() => {
|
|
1359
|
+
setTitled(true);
|
|
1360
|
+
return () => setTitled(false);
|
|
1361
|
+
}, [setTitled]);
|
|
1362
|
+
const Heading = headingLevel === 2 ? "h2" : "h3";
|
|
1363
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1364
|
+
...props,
|
|
1365
|
+
"data-slot": "card-header",
|
|
1366
|
+
className: cn("flex items-start justify-between gap-4 px-5 pt-5", className),
|
|
1367
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
1368
|
+
className: "min-w-0",
|
|
1369
|
+
children: [/* @__PURE__ */ jsx(Heading, {
|
|
1370
|
+
id: titleId,
|
|
1371
|
+
className: "font-sans text-sm font-medium",
|
|
1372
|
+
children: title
|
|
1373
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
1374
|
+
className: "text-xs text-fg-muted",
|
|
1375
|
+
children: description
|
|
1376
|
+
})]
|
|
1377
|
+
}), action && /* @__PURE__ */ jsx("div", {
|
|
1378
|
+
className: "shrink-0",
|
|
1379
|
+
children: action
|
|
1380
|
+
})]
|
|
1381
|
+
});
|
|
1382
|
+
}
|
|
1383
|
+
function CardContent({ className, ...props }) {
|
|
1384
|
+
return /* @__PURE__ */ jsx("div", {
|
|
1385
|
+
...props,
|
|
1386
|
+
"data-slot": "card-content",
|
|
1387
|
+
className: cn("p-5 [[data-slot=card-header]+&]:pt-4", className)
|
|
1059
1388
|
});
|
|
1060
1389
|
}
|
|
1061
1390
|
//#endregion
|
|
@@ -1113,7 +1442,7 @@ function ChoiceRow({ slot, label, description, error, labelPosition = "end", gap
|
|
|
1113
1442
|
return /* @__PURE__ */ jsx("label", {
|
|
1114
1443
|
"data-slot": slot,
|
|
1115
1444
|
className: cn("relative isolate flex min-h-6 min-w-6 items-start text-sm/6 text-fg", "cursor-pointer has-[input:disabled]:cursor-not-allowed has-[input[aria-busy=true]]:cursor-progress", "has-[input:disabled]:opacity-50", "forced-colors:has-[input:disabled]:text-[GrayText] forced-colors:has-[input:disabled]:opacity-100", "pointer-coarse:before:absolute pointer-coarse:before:top-1/2 pointer-coarse:before:left-1/2", "pointer-coarse:before:-z-10 pointer-coarse:before:size-full pointer-coarse:before:min-h-11", "pointer-coarse:before:min-w-11 pointer-coarse:before:-translate-1/2", gap, labelPosition === "start" ? "w-full justify-between" : "w-fit max-w-full", className),
|
|
1116
|
-
children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment, { children: [text, controlSlot] }) : /* @__PURE__ */ jsxs(Fragment, { children: [controlSlot, text] })
|
|
1445
|
+
children: labelPosition === "start" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [text, controlSlot] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [controlSlot, text] })
|
|
1117
1446
|
});
|
|
1118
1447
|
}
|
|
1119
1448
|
//#endregion
|
|
@@ -1236,6 +1565,515 @@ function CheckboxGroup({ legend, description, error, orientation = "vertical", c
|
|
|
1236
1565
|
});
|
|
1237
1566
|
}
|
|
1238
1567
|
//#endregion
|
|
1568
|
+
//#region src/components/combobox/types.ts
|
|
1569
|
+
const isGroup$1 = (item) => "options" in item;
|
|
1570
|
+
const normalize = (text) => text.trim().normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLocaleLowerCase();
|
|
1571
|
+
const defaultFilter = (option, query) => normalize([option.label, ...option.keywords ?? []].join(" ")).includes(normalize(query));
|
|
1572
|
+
//#endregion
|
|
1573
|
+
//#region src/components/combobox/use-combobox-position.ts
|
|
1574
|
+
const clamp$1 = (n, min, max) => Math.max(min, Math.min(n, Math.max(min, max)));
|
|
1575
|
+
/** Top-layer positioning only: unlike Menu, a combobox never transfers focus into the list. */
|
|
1576
|
+
function useComboboxPosition(open, inputRef, contentRef) {
|
|
1577
|
+
useLayoutEffect(() => {
|
|
1578
|
+
const content = contentRef.current;
|
|
1579
|
+
const anchor = inputRef.current?.closest("[data-slot=\"input-control\"]");
|
|
1580
|
+
if (!open || !content || !anchor) return;
|
|
1581
|
+
content.showPopover?.();
|
|
1582
|
+
const update = () => {
|
|
1583
|
+
const rect = anchor.getBoundingClientRect();
|
|
1584
|
+
const viewport = window.visualViewport;
|
|
1585
|
+
const left = viewport?.offsetLeft ?? 0;
|
|
1586
|
+
const top = viewport?.offsetTop ?? 0;
|
|
1587
|
+
const width = viewport?.width ?? document.documentElement.clientWidth;
|
|
1588
|
+
const height = viewport?.height ?? document.documentElement.clientHeight;
|
|
1589
|
+
content.style.width = `${Math.min(rect.width, Math.max(0, width - 16))}px`;
|
|
1590
|
+
const below = top + height - rect.bottom - 12;
|
|
1591
|
+
const above = rect.top - top - 12;
|
|
1592
|
+
const side = below < Math.min(content.scrollHeight, 280) && above > below ? "top" : "bottom";
|
|
1593
|
+
content.style.maxHeight = `${Math.max(0, Math.min(280, side === "top" ? above : below))}px`;
|
|
1594
|
+
const w = content.offsetWidth;
|
|
1595
|
+
const h = content.offsetHeight;
|
|
1596
|
+
const rtl = getComputedStyle(anchor).direction === "rtl";
|
|
1597
|
+
content.style.left = `${Math.round(clamp$1(rtl ? rect.right - w : rect.left, left + 8, left + width - w - 8))}px`;
|
|
1598
|
+
content.style.top = `${Math.round(clamp$1(side === "top" ? rect.top - h - 4 : rect.bottom + 4, top + 8, top + height - h - 8))}px`;
|
|
1599
|
+
content.dataset.side = side;
|
|
1600
|
+
};
|
|
1601
|
+
update();
|
|
1602
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(update);
|
|
1603
|
+
observer?.observe(anchor);
|
|
1604
|
+
observer?.observe(content);
|
|
1605
|
+
const mutation = new MutationObserver(update);
|
|
1606
|
+
mutation.observe(content, {
|
|
1607
|
+
childList: true,
|
|
1608
|
+
subtree: true,
|
|
1609
|
+
characterData: true
|
|
1610
|
+
});
|
|
1611
|
+
window.addEventListener("resize", update);
|
|
1612
|
+
window.addEventListener("scroll", update, true);
|
|
1613
|
+
window.visualViewport?.addEventListener("resize", update);
|
|
1614
|
+
window.visualViewport?.addEventListener("scroll", update);
|
|
1615
|
+
return () => {
|
|
1616
|
+
observer?.disconnect();
|
|
1617
|
+
mutation.disconnect();
|
|
1618
|
+
window.removeEventListener("resize", update);
|
|
1619
|
+
window.removeEventListener("scroll", update, true);
|
|
1620
|
+
window.visualViewport?.removeEventListener("resize", update);
|
|
1621
|
+
window.visualViewport?.removeEventListener("scroll", update);
|
|
1622
|
+
content.hidePopover?.();
|
|
1623
|
+
};
|
|
1624
|
+
}, [
|
|
1625
|
+
open,
|
|
1626
|
+
inputRef,
|
|
1627
|
+
contentRef
|
|
1628
|
+
]);
|
|
1629
|
+
}
|
|
1630
|
+
//#endregion
|
|
1631
|
+
//#region src/components/combobox/combobox.tsx
|
|
1632
|
+
const resultsText = (count) => `${count} ${count === 1 ? "option" : "options"} available.`;
|
|
1633
|
+
function Combobox({ options, value, defaultValue = null, onValueChange, inputValue, onInputValueChange, open: openProp, defaultOpen = false, onOpenChange, filter = defaultFilter, selectedOption, loading = false, loadingText = "Loading…", searchError, onRetry, retryText = "Retry", emptyText = "No matching options.", clearable = true, openOnFocus = false, clearLabel = "Clear selection", openLabel = "Open options", closeLabel = "Close options", requiredText = "Choose an option from the list.", unresolvedText = "The selected option is unavailable.", getResultsText = resultsText, label, floating, helperText, error, size = "md", start, mono, inputClassName, contentClassName, className, name, form, disabled, readOnly, required, ref, id: idProp, onClick, onFocus, onBlur, onKeyDown, onCompositionStart, onCompositionEnd, onInvalid, ...props }) {
|
|
1634
|
+
const autoId = useId();
|
|
1635
|
+
const id = idProp ?? `${autoId}-input`;
|
|
1636
|
+
const listId = `${autoId}-list`;
|
|
1637
|
+
const rootRef = useRef(null);
|
|
1638
|
+
const inputRef = useRef(null);
|
|
1639
|
+
const contentRef = useRef(null);
|
|
1640
|
+
const hiddenRef = useRef(null);
|
|
1641
|
+
const composing = useRef(false);
|
|
1642
|
+
const skipFocusOpen = useRef(false);
|
|
1643
|
+
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
1644
|
+
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
1645
|
+
const [draft, setDraft] = useState(null);
|
|
1646
|
+
const [dirty, setDirty] = useState(false);
|
|
1647
|
+
const [compositionText, setCompositionText] = useState(null);
|
|
1648
|
+
const [active, setActive] = useState(null);
|
|
1649
|
+
const [nativeInvalid, setNativeInvalid] = useState(false);
|
|
1650
|
+
const selectedValue = value === void 0 ? internalValue : value;
|
|
1651
|
+
const open = !disabled && !readOnly && (openProp ?? internalOpen);
|
|
1652
|
+
const flat = useMemo(() => {
|
|
1653
|
+
const items = options.flatMap((item) => isGroup$1(item) ? [...item.options] : [item]);
|
|
1654
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1655
|
+
for (const item of items) {
|
|
1656
|
+
if (!item.value || seen.has(item.value)) throw new Error("Combobox option values must be unique, non-empty strings");
|
|
1657
|
+
seen.add(item.value);
|
|
1658
|
+
}
|
|
1659
|
+
return items;
|
|
1660
|
+
}, [options]);
|
|
1661
|
+
const resolved = flat.find((item) => item.value === selectedValue) ?? (selectedOption?.value === selectedValue ? selectedOption : null);
|
|
1662
|
+
const [cached, setCached] = useState(resolved);
|
|
1663
|
+
const selected = resolved ?? (cached?.value === selectedValue ? cached : null);
|
|
1664
|
+
const [initialOption, setInitialOption] = useState(resolved?.value === defaultValue ? resolved : null);
|
|
1665
|
+
if (resolved?.value === defaultValue && resolved !== initialOption) setInitialOption(resolved);
|
|
1666
|
+
const text = compositionText !== null ? draft ?? inputValue ?? selected?.label ?? "" : inputValue ?? draft ?? selected?.label ?? "";
|
|
1667
|
+
const query = dirty ? compositionText ?? text : "";
|
|
1668
|
+
const blocked = loading || Boolean(searchError);
|
|
1669
|
+
const [previousResults, setPreviousResults] = useState({
|
|
1670
|
+
options,
|
|
1671
|
+
blocked,
|
|
1672
|
+
filter
|
|
1673
|
+
});
|
|
1674
|
+
if (previousResults.options !== options || previousResults.blocked !== blocked || previousResults.filter !== filter) {
|
|
1675
|
+
setPreviousResults({
|
|
1676
|
+
options,
|
|
1677
|
+
blocked,
|
|
1678
|
+
filter
|
|
1679
|
+
});
|
|
1680
|
+
setActive(null);
|
|
1681
|
+
}
|
|
1682
|
+
const visible = flat.filter((item) => filter === false || !query || filter(item, query));
|
|
1683
|
+
const activeItem = !blocked && active?.options === options && active.query === query ? visible.find((item) => item.value === active.value && !item.disabled) : void 0;
|
|
1684
|
+
const optionId = (item) => `${autoId}-option-${encodeURIComponent(item.value)}`;
|
|
1685
|
+
const activeId = open && activeItem ? optionId(activeItem) : void 0;
|
|
1686
|
+
const invalidMessage = selectedValue !== null && !selected ? unresolvedText : required && selectedValue === null ? requiredText : "";
|
|
1687
|
+
const fieldError = error || (nativeInvalid && invalidMessage ? invalidMessage : void 0);
|
|
1688
|
+
const count = visible.filter((item) => !item.disabled).length;
|
|
1689
|
+
const status = open ? searchError ? typeof searchError === "string" ? searchError : "Unable to load options." : loading ? loadingText : getResultsText(count) : "";
|
|
1690
|
+
const [previousSelection, setPreviousSelection] = useState({
|
|
1691
|
+
value: selectedValue,
|
|
1692
|
+
label: selected?.label
|
|
1693
|
+
});
|
|
1694
|
+
if (resolved && resolved !== cached) setCached(resolved);
|
|
1695
|
+
if (previousSelection.value !== selectedValue || previousSelection.label !== selected?.label) {
|
|
1696
|
+
setPreviousSelection({
|
|
1697
|
+
value: selectedValue,
|
|
1698
|
+
label: selected?.label
|
|
1699
|
+
});
|
|
1700
|
+
setDraft(null);
|
|
1701
|
+
setDirty(false);
|
|
1702
|
+
setActive(null);
|
|
1703
|
+
}
|
|
1704
|
+
if (nativeInvalid && !invalidMessage) setNativeInvalid(false);
|
|
1705
|
+
useLayoutEffect(() => {
|
|
1706
|
+
inputRef.current?.setCustomValidity(disabled || readOnly ? "" : invalidMessage);
|
|
1707
|
+
}, [
|
|
1708
|
+
invalidMessage,
|
|
1709
|
+
disabled,
|
|
1710
|
+
readOnly
|
|
1711
|
+
]);
|
|
1712
|
+
useComboboxPosition(open, inputRef, contentRef);
|
|
1713
|
+
useLayoutEffect(() => {
|
|
1714
|
+
if (activeId) document.getElementById(activeId)?.scrollIntoView?.({ block: "nearest" });
|
|
1715
|
+
}, [activeId]);
|
|
1716
|
+
const updateText = (next, reason, fallback = false) => {
|
|
1717
|
+
setDraft(fallback ? null : next);
|
|
1718
|
+
if (next !== text || reason === "input") onInputValueChange?.(next, { reason });
|
|
1719
|
+
};
|
|
1720
|
+
const changeOpen = (next, reason) => {
|
|
1721
|
+
if (openProp === void 0) setInternalOpen(next);
|
|
1722
|
+
if (next !== open) onOpenChange?.(next, { reason });
|
|
1723
|
+
};
|
|
1724
|
+
const close = (reason) => {
|
|
1725
|
+
changeOpen(false, reason);
|
|
1726
|
+
updateText(selected?.label ?? "", "revert", true);
|
|
1727
|
+
setDirty(false);
|
|
1728
|
+
setActive(null);
|
|
1729
|
+
};
|
|
1730
|
+
const show = (reason, direction) => {
|
|
1731
|
+
if (disabled || readOnly) return;
|
|
1732
|
+
changeOpen(true, reason);
|
|
1733
|
+
setDirty(false);
|
|
1734
|
+
const items = flat.filter((item) => !item.disabled);
|
|
1735
|
+
const initial = items.find((item) => item.value === selectedValue) ?? (direction === 1 ? items[0] : direction === -1 ? items.at(-1) : void 0);
|
|
1736
|
+
setActive(initial ? {
|
|
1737
|
+
value: initial.value,
|
|
1738
|
+
options,
|
|
1739
|
+
query: ""
|
|
1740
|
+
} : null);
|
|
1741
|
+
};
|
|
1742
|
+
const focusInput = () => {
|
|
1743
|
+
skipFocusOpen.current = true;
|
|
1744
|
+
inputRef.current?.focus({ preventScroll: true });
|
|
1745
|
+
skipFocusOpen.current = false;
|
|
1746
|
+
};
|
|
1747
|
+
const clear = (reason) => {
|
|
1748
|
+
if (value === void 0) setInternalValue(null);
|
|
1749
|
+
if (selectedValue !== null) onValueChange?.(null, {
|
|
1750
|
+
reason: "clear",
|
|
1751
|
+
option: null
|
|
1752
|
+
});
|
|
1753
|
+
updateText("", reason);
|
|
1754
|
+
setDirty(true);
|
|
1755
|
+
setActive(null);
|
|
1756
|
+
changeOpen(true, "clear");
|
|
1757
|
+
};
|
|
1758
|
+
const edit = (next) => {
|
|
1759
|
+
if (disabled || readOnly) return;
|
|
1760
|
+
if (next === "") clear("input");
|
|
1761
|
+
else {
|
|
1762
|
+
updateText(next, "input");
|
|
1763
|
+
setDirty(true);
|
|
1764
|
+
setActive(null);
|
|
1765
|
+
changeOpen(true, "input");
|
|
1766
|
+
}
|
|
1767
|
+
};
|
|
1768
|
+
const choose = (item) => {
|
|
1769
|
+
if (blocked || disabled || readOnly || item.disabled) return;
|
|
1770
|
+
setCached(item);
|
|
1771
|
+
if (value === void 0) setInternalValue(item.value);
|
|
1772
|
+
if (selectedValue !== item.value) onValueChange?.(item.value, {
|
|
1773
|
+
reason: "select",
|
|
1774
|
+
option: item
|
|
1775
|
+
});
|
|
1776
|
+
updateText(item.label, "selection", true);
|
|
1777
|
+
setDirty(false);
|
|
1778
|
+
setActive(null);
|
|
1779
|
+
changeOpen(false, "select");
|
|
1780
|
+
focusInput();
|
|
1781
|
+
};
|
|
1782
|
+
const dismiss = useEffectEvent(close);
|
|
1783
|
+
useLayoutEffect(() => {
|
|
1784
|
+
if (!open) return;
|
|
1785
|
+
const outside = (event) => {
|
|
1786
|
+
if (!event.composedPath().includes(rootRef.current)) dismiss("outside");
|
|
1787
|
+
};
|
|
1788
|
+
document.addEventListener("pointerdown", outside, true);
|
|
1789
|
+
return () => {
|
|
1790
|
+
document.removeEventListener("pointerdown", outside, true);
|
|
1791
|
+
};
|
|
1792
|
+
}, [open]);
|
|
1793
|
+
const wasOpen = useRef(open);
|
|
1794
|
+
const settle = useEffectEvent(() => {
|
|
1795
|
+
updateText(selected?.label ?? "", "revert", true);
|
|
1796
|
+
setDirty(false);
|
|
1797
|
+
setActive(null);
|
|
1798
|
+
});
|
|
1799
|
+
useLayoutEffect(() => {
|
|
1800
|
+
if (wasOpen.current && !open) settle();
|
|
1801
|
+
wasOpen.current = open;
|
|
1802
|
+
}, [open]);
|
|
1803
|
+
const reset = useEffectEvent(() => {
|
|
1804
|
+
const nextValue = value === void 0 ? defaultValue : value;
|
|
1805
|
+
const nextOption = flat.find((item) => item.value === nextValue) ?? (selectedOption?.value === nextValue ? selectedOption : null) ?? (cached?.value === nextValue ? cached : null) ?? (initialOption?.value === nextValue ? initialOption : null);
|
|
1806
|
+
if (nextOption) setCached(nextOption);
|
|
1807
|
+
const nextText = inputValue ?? nextOption?.label ?? "";
|
|
1808
|
+
if (value === void 0) setInternalValue(nextValue);
|
|
1809
|
+
if (openProp === void 0) setInternalOpen(false);
|
|
1810
|
+
updateText(nextText, "reset", true);
|
|
1811
|
+
setDirty(false);
|
|
1812
|
+
setCompositionText(null);
|
|
1813
|
+
composing.current = false;
|
|
1814
|
+
setActive(null);
|
|
1815
|
+
setNativeInvalid(false);
|
|
1816
|
+
if (inputRef.current) inputRef.current.value = nextText;
|
|
1817
|
+
if (hiddenRef.current) hiddenRef.current.value = nextValue ?? "";
|
|
1818
|
+
});
|
|
1819
|
+
useLayoutEffect(() => {
|
|
1820
|
+
const owner = inputRef.current?.form;
|
|
1821
|
+
if (!owner) return;
|
|
1822
|
+
let timer;
|
|
1823
|
+
const handleReset = (event) => {
|
|
1824
|
+
clearTimeout(timer);
|
|
1825
|
+
timer = setTimeout(() => {
|
|
1826
|
+
if (!event.defaultPrevented) reset();
|
|
1827
|
+
}, 0);
|
|
1828
|
+
};
|
|
1829
|
+
owner.addEventListener("reset", handleReset);
|
|
1830
|
+
return () => {
|
|
1831
|
+
clearTimeout(timer);
|
|
1832
|
+
owner.removeEventListener("reset", handleReset);
|
|
1833
|
+
};
|
|
1834
|
+
}, [form]);
|
|
1835
|
+
const renderOption = (item) => /* @__PURE__ */ jsxs("div", {
|
|
1836
|
+
id: optionId(item),
|
|
1837
|
+
role: "option",
|
|
1838
|
+
"aria-selected": item.value === selectedValue,
|
|
1839
|
+
"aria-disabled": item.disabled || void 0,
|
|
1840
|
+
"aria-labelledby": `${optionId(item)}-label`,
|
|
1841
|
+
"aria-describedby": item.description ? `${optionId(item)}-description` : void 0,
|
|
1842
|
+
"data-slot": "combobox-option",
|
|
1843
|
+
"data-active": activeItem?.value === item.value ? "" : void 0,
|
|
1844
|
+
className: cn("flex min-h-8.5 cursor-default items-center gap-2 rounded-lg px-2 py-1.5 text-sm/5 text-fg outline-none select-none pointer-coarse:min-h-11", "data-active:bg-popover-highlight data-active:outline-1 data-active:-outline-offset-1 data-active:outline-input-border", "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 forced-colors:data-active:outline-[Highlight]"),
|
|
1845
|
+
onPointerMove: (event) => {
|
|
1846
|
+
if (event.pointerType !== "touch" && !item.disabled) setActive({
|
|
1847
|
+
value: item.value,
|
|
1848
|
+
options,
|
|
1849
|
+
query
|
|
1850
|
+
});
|
|
1851
|
+
},
|
|
1852
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1853
|
+
onClick: () => choose(item),
|
|
1854
|
+
children: [
|
|
1855
|
+
item.icon && /* @__PURE__ */ jsx("span", {
|
|
1856
|
+
"aria-hidden": true,
|
|
1857
|
+
className: "shrink-0 text-fg-muted [&_svg]:size-4",
|
|
1858
|
+
children: item.icon
|
|
1859
|
+
}),
|
|
1860
|
+
/* @__PURE__ */ jsxs("span", {
|
|
1861
|
+
className: "min-w-0 flex-1 wrap-anywhere",
|
|
1862
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
1863
|
+
id: `${optionId(item)}-label`,
|
|
1864
|
+
children: item.label
|
|
1865
|
+
}), item.description && /* @__PURE__ */ jsx("span", {
|
|
1866
|
+
id: `${optionId(item)}-description`,
|
|
1867
|
+
className: "mt-0.5 block text-xs/snug text-fg-muted",
|
|
1868
|
+
children: item.description
|
|
1869
|
+
})]
|
|
1870
|
+
}),
|
|
1871
|
+
/* @__PURE__ */ jsx(Check, {
|
|
1872
|
+
"aria-hidden": true,
|
|
1873
|
+
className: cn("size-4 shrink-0", item.value !== selectedValue && "invisible")
|
|
1874
|
+
})
|
|
1875
|
+
]
|
|
1876
|
+
}, item.value);
|
|
1877
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
1878
|
+
ref: rootRef,
|
|
1879
|
+
onBlur: (event) => {
|
|
1880
|
+
if ((open || dirty) && !rootRef.current?.contains(event.relatedTarget)) close("blur");
|
|
1881
|
+
},
|
|
1882
|
+
"data-slot": "combobox",
|
|
1883
|
+
"data-state": open ? "open" : "closed",
|
|
1884
|
+
className: cn("w-full", className),
|
|
1885
|
+
children: [
|
|
1886
|
+
/* @__PURE__ */ jsx(FieldFrame, {
|
|
1887
|
+
...props,
|
|
1888
|
+
ref,
|
|
1889
|
+
inputRef,
|
|
1890
|
+
id,
|
|
1891
|
+
type: "text",
|
|
1892
|
+
form,
|
|
1893
|
+
label,
|
|
1894
|
+
floating,
|
|
1895
|
+
helperText,
|
|
1896
|
+
error: fieldError,
|
|
1897
|
+
required,
|
|
1898
|
+
disabled,
|
|
1899
|
+
readOnly,
|
|
1900
|
+
size,
|
|
1901
|
+
start,
|
|
1902
|
+
mono,
|
|
1903
|
+
value: text,
|
|
1904
|
+
role: "combobox",
|
|
1905
|
+
"aria-autocomplete": "list",
|
|
1906
|
+
"aria-expanded": open,
|
|
1907
|
+
"aria-controls": open ? listId : void 0,
|
|
1908
|
+
"aria-activedescendant": activeId,
|
|
1909
|
+
autoComplete: props.autoComplete ?? "off",
|
|
1910
|
+
status,
|
|
1911
|
+
inputClassName,
|
|
1912
|
+
className: "pointer-coarse:**:data-[slot=input-control]:min-h-11",
|
|
1913
|
+
onChange: (event) => {
|
|
1914
|
+
if (composing.current) setDraft(event.currentTarget.value);
|
|
1915
|
+
else edit(event.currentTarget.value);
|
|
1916
|
+
},
|
|
1917
|
+
onCompositionStart: (event) => {
|
|
1918
|
+
onCompositionStart?.(event);
|
|
1919
|
+
composing.current = true;
|
|
1920
|
+
setCompositionText(query);
|
|
1921
|
+
},
|
|
1922
|
+
onCompositionEnd: (event) => {
|
|
1923
|
+
onCompositionEnd?.(event);
|
|
1924
|
+
composing.current = false;
|
|
1925
|
+
setCompositionText(null);
|
|
1926
|
+
edit(event.currentTarget.value);
|
|
1927
|
+
},
|
|
1928
|
+
onClick: (event) => {
|
|
1929
|
+
onClick?.(event);
|
|
1930
|
+
if (!event.defaultPrevented && !open) show("trigger");
|
|
1931
|
+
},
|
|
1932
|
+
onFocus: (event) => {
|
|
1933
|
+
onFocus?.(event);
|
|
1934
|
+
if (!event.defaultPrevented && openOnFocus && !open && !skipFocusOpen.current) show("input");
|
|
1935
|
+
},
|
|
1936
|
+
onBlur,
|
|
1937
|
+
onInvalid: (event) => {
|
|
1938
|
+
setNativeInvalid(true);
|
|
1939
|
+
onInvalid?.(event);
|
|
1940
|
+
},
|
|
1941
|
+
onKeyDown: (event) => {
|
|
1942
|
+
onKeyDown?.(event);
|
|
1943
|
+
if (event.defaultPrevented || disabled || readOnly || composing.current || event.nativeEvent.isComposing || event.nativeEvent.keyCode === 229) return;
|
|
1944
|
+
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
1945
|
+
event.preventDefault();
|
|
1946
|
+
const direction = event.key === "ArrowDown" ? 1 : -1;
|
|
1947
|
+
if (!open) {
|
|
1948
|
+
show("keyboard", direction);
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
if (blocked) return;
|
|
1952
|
+
const enabled = visible.filter((item) => !item.disabled);
|
|
1953
|
+
const index = enabled.findIndex((item) => item.value === activeItem?.value);
|
|
1954
|
+
const next = index < 0 ? direction === 1 ? enabled[0] : enabled.at(-1) : enabled[index + direction];
|
|
1955
|
+
if (next) setActive({
|
|
1956
|
+
value: next.value,
|
|
1957
|
+
options,
|
|
1958
|
+
query
|
|
1959
|
+
});
|
|
1960
|
+
} else if (event.key === "Enter" && open) {
|
|
1961
|
+
event.preventDefault();
|
|
1962
|
+
if (activeItem) choose(activeItem);
|
|
1963
|
+
} else if (event.key === "Escape" && open) {
|
|
1964
|
+
event.preventDefault();
|
|
1965
|
+
event.stopPropagation();
|
|
1966
|
+
close("escape");
|
|
1967
|
+
} else if (event.key === "Tab" && !(searchError && onRetry)) close("blur");
|
|
1968
|
+
},
|
|
1969
|
+
actions: !readOnly && /* @__PURE__ */ jsxs(Fragment$1, { children: [clearable && (text !== "" || selectedValue !== null) && /* @__PURE__ */ jsx(Button, {
|
|
1970
|
+
type: "button",
|
|
1971
|
+
icon: /* @__PURE__ */ jsx(X, {}),
|
|
1972
|
+
"aria-label": clearLabel,
|
|
1973
|
+
intent: "ghost",
|
|
1974
|
+
size,
|
|
1975
|
+
disabled,
|
|
1976
|
+
className: cn(compactButton[size], "pointer-coarse:size-11 pointer-coarse:before:hidden"),
|
|
1977
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1978
|
+
onClick: () => {
|
|
1979
|
+
clear("clear");
|
|
1980
|
+
focusInput();
|
|
1981
|
+
}
|
|
1982
|
+
}), /* @__PURE__ */ jsx(Button, {
|
|
1983
|
+
type: "button",
|
|
1984
|
+
icon: /* @__PURE__ */ jsx(ChevronDown, {}),
|
|
1985
|
+
tabIndex: -1,
|
|
1986
|
+
"aria-label": open ? closeLabel : openLabel,
|
|
1987
|
+
"aria-expanded": open,
|
|
1988
|
+
"aria-controls": open ? listId : void 0,
|
|
1989
|
+
disabled,
|
|
1990
|
+
intent: "ghost",
|
|
1991
|
+
size,
|
|
1992
|
+
className: cn(compactButton[size], "pointer-coarse:size-11 pointer-coarse:before:hidden"),
|
|
1993
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1994
|
+
onClick: () => {
|
|
1995
|
+
focusInput();
|
|
1996
|
+
if (open) close("trigger");
|
|
1997
|
+
else show("trigger");
|
|
1998
|
+
}
|
|
1999
|
+
})] })
|
|
2000
|
+
}),
|
|
2001
|
+
name && /* @__PURE__ */ jsx("input", {
|
|
2002
|
+
ref: hiddenRef,
|
|
2003
|
+
type: "hidden",
|
|
2004
|
+
name,
|
|
2005
|
+
form,
|
|
2006
|
+
disabled,
|
|
2007
|
+
value: selectedValue ?? ""
|
|
2008
|
+
}),
|
|
2009
|
+
open && /* @__PURE__ */ jsxs("div", {
|
|
2010
|
+
ref: contentRef,
|
|
2011
|
+
popover: "manual",
|
|
2012
|
+
"data-slot": "combobox-content",
|
|
2013
|
+
className: cn("fixed inset-auto z-50 m-0 box-border max-h-70 overflow-y-auto rounded-xl border border-popover-border bg-popover-bg p-1 font-sans text-sm text-fg shadow-popover outline-none scrollbar-subtle", "transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none", contentClassName),
|
|
2014
|
+
onKeyDown: (event) => {
|
|
2015
|
+
if (event.key === "Escape") {
|
|
2016
|
+
event.preventDefault();
|
|
2017
|
+
event.stopPropagation();
|
|
2018
|
+
close("escape");
|
|
2019
|
+
focusInput();
|
|
2020
|
+
}
|
|
2021
|
+
},
|
|
2022
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2023
|
+
id: listId,
|
|
2024
|
+
role: "listbox",
|
|
2025
|
+
"aria-label": label ?? props["aria-label"],
|
|
2026
|
+
"aria-busy": loading || void 0,
|
|
2027
|
+
children: !blocked && options.map((item, index) => {
|
|
2028
|
+
if (!isGroup$1(item)) return visible.includes(item) ? renderOption(item) : null;
|
|
2029
|
+
const members = item.options.filter((option) => visible.includes(option));
|
|
2030
|
+
if (!members.length) return null;
|
|
2031
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2032
|
+
role: "group",
|
|
2033
|
+
"aria-labelledby": `${autoId}-group-${index}`,
|
|
2034
|
+
"data-slot": "combobox-group",
|
|
2035
|
+
className: "not-first:mt-1 not-first:border-t not-first:border-popover-border not-first:pt-1",
|
|
2036
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2037
|
+
id: `${autoId}-group-${index}`,
|
|
2038
|
+
className: "px-2 pt-1.5 pb-1 text-xs font-medium text-fg-muted",
|
|
2039
|
+
children: item.label
|
|
2040
|
+
}), members.map(renderOption)]
|
|
2041
|
+
}, `${item.label}-${index}`);
|
|
2042
|
+
})
|
|
2043
|
+
}), searchError ? /* @__PURE__ */ jsxs("div", {
|
|
2044
|
+
className: "flex items-start gap-2 p-3 text-xs text-danger",
|
|
2045
|
+
children: [
|
|
2046
|
+
/* @__PURE__ */ jsx(CircleAlert, {
|
|
2047
|
+
"aria-hidden": true,
|
|
2048
|
+
className: "mt-px size-3.5 shrink-0"
|
|
2049
|
+
}),
|
|
2050
|
+
/* @__PURE__ */ jsx("span", {
|
|
2051
|
+
className: "flex-1",
|
|
2052
|
+
children: searchError
|
|
2053
|
+
}),
|
|
2054
|
+
onRetry && /* @__PURE__ */ jsx(Button, {
|
|
2055
|
+
type: "button",
|
|
2056
|
+
size: "sm",
|
|
2057
|
+
intent: "secondary",
|
|
2058
|
+
onClick: onRetry,
|
|
2059
|
+
children: retryText
|
|
2060
|
+
})
|
|
2061
|
+
]
|
|
2062
|
+
}) : loading ? /* @__PURE__ */ jsxs("div", {
|
|
2063
|
+
className: "flex items-center justify-center gap-2 p-4 text-sm text-fg-muted",
|
|
2064
|
+
children: [/* @__PURE__ */ jsx(LoaderCircle, {
|
|
2065
|
+
"aria-hidden": true,
|
|
2066
|
+
className: "size-4 animate-spin motion-reduce:[animation-duration:2.5s]"
|
|
2067
|
+
}), loadingText]
|
|
2068
|
+
}) : visible.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
2069
|
+
className: "p-4 text-center text-sm text-fg-muted",
|
|
2070
|
+
children: emptyText
|
|
2071
|
+
}) : null]
|
|
2072
|
+
})
|
|
2073
|
+
]
|
|
2074
|
+
});
|
|
2075
|
+
}
|
|
2076
|
+
//#endregion
|
|
1239
2077
|
//#region src/components/modal/modal-shell.tsx
|
|
1240
2078
|
const ModalContext = createContext(null);
|
|
1241
2079
|
/** Parts of one wrapper must not work inside the other: a DialogHeader in a Drawer throws too. */
|
|
@@ -1410,7 +2248,7 @@ function ModalShell({ kind, open, onOpenChange, alert = false, dismissible = tru
|
|
|
1410
2248
|
onMouseDown: handleMouseDown,
|
|
1411
2249
|
onClick: handleClick,
|
|
1412
2250
|
className,
|
|
1413
|
-
children: rendered && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
2251
|
+
children: rendered && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
1414
2252
|
hidden: true,
|
|
1415
2253
|
ref: showOnAttach
|
|
1416
2254
|
}, closeCount), children] })
|
|
@@ -1617,32 +2455,352 @@ function DrawerHeader({ title, description, className, ...props }) {
|
|
|
1617
2455
|
})]
|
|
1618
2456
|
});
|
|
1619
2457
|
}
|
|
1620
|
-
/** Fills the space between header and footer and scrolls on its own; they stay put. */
|
|
1621
|
-
function DrawerBody({ className, ...props }) {
|
|
2458
|
+
/** Fills the space between header and footer and scrolls on its own; they stay put. */
|
|
2459
|
+
function DrawerBody({ className, ...props }) {
|
|
2460
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2461
|
+
...props,
|
|
2462
|
+
"data-slot": "drawer-body",
|
|
2463
|
+
className: cn("min-h-0 flex-1 overflow-y-auto overscroll-contain px-6 py-1 text-sm text-fg", className)
|
|
2464
|
+
});
|
|
2465
|
+
}
|
|
2466
|
+
/** Actions, right-aligned: Cancel first, the primary action last (rightmost). */
|
|
2467
|
+
function DrawerFooter({ className, ...props }) {
|
|
2468
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2469
|
+
...props,
|
|
2470
|
+
"data-slot": "drawer-footer",
|
|
2471
|
+
className: cn("flex shrink-0 flex-wrap items-center justify-end gap-2 px-6 pt-5 pb-6", className)
|
|
2472
|
+
});
|
|
2473
|
+
}
|
|
2474
|
+
/** A ghost icon button that closes the drawer, for custom headers. */
|
|
2475
|
+
function DrawerClose({ size = "sm", icon, ...props }) {
|
|
2476
|
+
const { requestClose } = useDrawerContext("DrawerClose");
|
|
2477
|
+
return /* @__PURE__ */ jsx(Button, {
|
|
2478
|
+
...props,
|
|
2479
|
+
intent: "ghost",
|
|
2480
|
+
size,
|
|
2481
|
+
icon: icon ?? /* @__PURE__ */ jsx(X, {}),
|
|
2482
|
+
tooltip: false,
|
|
2483
|
+
onClick: () => requestClose("close-button")
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
//#endregion
|
|
2487
|
+
//#region src/components/dropdown-menu/dropdown-menu.tsx
|
|
2488
|
+
const MenuContext = createContext(null);
|
|
2489
|
+
function useMenu() {
|
|
2490
|
+
const value = use(MenuContext);
|
|
2491
|
+
if (!value) throw new Error("DropdownMenu parts must be inside DropdownMenu");
|
|
2492
|
+
return value;
|
|
2493
|
+
}
|
|
2494
|
+
function DropdownMenu({ children, open: controlled, defaultOpen = false, onOpenChange }) {
|
|
2495
|
+
const [internal, setInternal] = useState(defaultOpen);
|
|
2496
|
+
const id = useId();
|
|
2497
|
+
const trigger = useRef(null);
|
|
2498
|
+
const content = useRef(null);
|
|
2499
|
+
const initialFocus = useRef("first");
|
|
2500
|
+
const reason = useRef("trigger");
|
|
2501
|
+
return /* @__PURE__ */ jsx(MenuContext, {
|
|
2502
|
+
value: {
|
|
2503
|
+
open: controlled ?? internal,
|
|
2504
|
+
triggerId: `${id}-trigger`,
|
|
2505
|
+
contentId: `${id}-menu`,
|
|
2506
|
+
trigger,
|
|
2507
|
+
content,
|
|
2508
|
+
initialFocus,
|
|
2509
|
+
reason,
|
|
2510
|
+
change(next, why, focus = "first") {
|
|
2511
|
+
if (next) initialFocus.current = focus;
|
|
2512
|
+
reason.current = why;
|
|
2513
|
+
if (controlled === void 0) setInternal(next);
|
|
2514
|
+
onOpenChange?.(next, why);
|
|
2515
|
+
}
|
|
2516
|
+
},
|
|
2517
|
+
children
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
function DropdownMenuTrigger({ asChild = false, ref, onClick, onClickCapture, onKeyDown, disabled, ...props }) {
|
|
2521
|
+
const menu = useMenu();
|
|
2522
|
+
const mergedRef = useMergedRef(menu.trigger, ref);
|
|
2523
|
+
return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
|
|
2524
|
+
...props,
|
|
2525
|
+
ref: mergedRef,
|
|
2526
|
+
id: menu.triggerId,
|
|
2527
|
+
type: "button",
|
|
2528
|
+
"data-slot": "dropdown-menu-trigger",
|
|
2529
|
+
"data-state": menu.open ? "open" : "closed",
|
|
2530
|
+
disabled: asChild ? disabled : void 0,
|
|
2531
|
+
"aria-haspopup": "menu",
|
|
2532
|
+
"aria-expanded": menu.open,
|
|
2533
|
+
"aria-controls": menu.open ? menu.contentId : void 0,
|
|
2534
|
+
"aria-disabled": disabled || props["aria-disabled"] || void 0,
|
|
2535
|
+
onClickCapture: (event) => {
|
|
2536
|
+
if (disabled) {
|
|
2537
|
+
event.preventDefault();
|
|
2538
|
+
event.stopPropagation();
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
onClickCapture?.(event);
|
|
2542
|
+
},
|
|
2543
|
+
onClick: (event) => {
|
|
2544
|
+
onClick?.(event);
|
|
2545
|
+
if (disabled || event.defaultPrevented || event.currentTarget.getAttribute("aria-disabled") === "true") return;
|
|
2546
|
+
menu.change(!menu.open, "trigger");
|
|
2547
|
+
},
|
|
2548
|
+
onKeyDown: (event) => {
|
|
2549
|
+
onKeyDown?.(event);
|
|
2550
|
+
if (disabled || event.defaultPrevented || event.currentTarget.getAttribute("aria-disabled") === "true") return;
|
|
2551
|
+
if (["ArrowDown", "ArrowUp"].includes(event.key)) {
|
|
2552
|
+
event.preventDefault();
|
|
2553
|
+
menu.change(true, "trigger", event.key === "ArrowUp" ? "last" : "first");
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
const opposite = {
|
|
2559
|
+
top: "bottom",
|
|
2560
|
+
bottom: "top",
|
|
2561
|
+
left: "right",
|
|
2562
|
+
right: "left"
|
|
2563
|
+
};
|
|
2564
|
+
const clamp = (value, low, high) => Math.max(low, Math.min(value, Math.max(low, high)));
|
|
2565
|
+
function items(content) {
|
|
2566
|
+
return Array.from(content.querySelectorAll("[role=\"menuitem\"]")).filter((item) => !item.hidden && item.getAttribute("aria-hidden") !== "true");
|
|
2567
|
+
}
|
|
2568
|
+
function DropdownMenuContent(props) {
|
|
2569
|
+
const { open } = useMenu();
|
|
2570
|
+
return open ? /* @__PURE__ */ jsx(OpenContent, { ...props }) : null;
|
|
2571
|
+
}
|
|
2572
|
+
function OpenContent({ side = "bottom", align = "end", sideOffset = 6, className, ref, onKeyDown, style, ...props }) {
|
|
2573
|
+
const menu = useMenu();
|
|
2574
|
+
const { content: contentRef, trigger: triggerRef, initialFocus, reason: closeReason } = menu;
|
|
2575
|
+
const mergedRef = useMergedRef(contentRef, ref);
|
|
2576
|
+
const query = useRef({
|
|
2577
|
+
text: "",
|
|
2578
|
+
time: 0
|
|
2579
|
+
});
|
|
2580
|
+
const dismiss = useEffectEvent((reason) => menu.change(false, reason));
|
|
2581
|
+
useLayoutEffect(() => {
|
|
2582
|
+
const content = contentRef.current;
|
|
2583
|
+
const trigger = triggerRef.current;
|
|
2584
|
+
if (!content || !trigger) return;
|
|
2585
|
+
content.showPopover?.();
|
|
2586
|
+
const entries = items(content);
|
|
2587
|
+
((initialFocus.current === "last" ? entries.at(-1) : entries[0]) ?? content).focus({ preventScroll: true });
|
|
2588
|
+
const outside = (event) => {
|
|
2589
|
+
const path = event.composedPath();
|
|
2590
|
+
if (!path.includes(content) && !path.includes(trigger)) dismiss("outside");
|
|
2591
|
+
};
|
|
2592
|
+
const focusOutside = (event) => {
|
|
2593
|
+
if (closeReason.current === "tab") return;
|
|
2594
|
+
if (!content.contains(event.target) && !trigger.contains(event.target)) dismiss("outside");
|
|
2595
|
+
};
|
|
2596
|
+
const reasonRef = closeReason;
|
|
2597
|
+
document.addEventListener("pointerdown", outside, true);
|
|
2598
|
+
document.addEventListener("focusin", focusOutside);
|
|
2599
|
+
return () => {
|
|
2600
|
+
document.removeEventListener("pointerdown", outside, true);
|
|
2601
|
+
document.removeEventListener("focusin", focusOutside);
|
|
2602
|
+
const restore = content.contains(document.activeElement) && !["outside", "tab"].includes(reasonRef.current);
|
|
2603
|
+
content.hidePopover?.();
|
|
2604
|
+
if (restore && trigger.isConnected) trigger.focus({ preventScroll: true });
|
|
2605
|
+
};
|
|
2606
|
+
}, [
|
|
2607
|
+
contentRef,
|
|
2608
|
+
triggerRef,
|
|
2609
|
+
initialFocus,
|
|
2610
|
+
closeReason
|
|
2611
|
+
]);
|
|
2612
|
+
useLayoutEffect(() => {
|
|
2613
|
+
const content = contentRef.current;
|
|
2614
|
+
const trigger = triggerRef.current;
|
|
2615
|
+
if (!content || !trigger) return;
|
|
2616
|
+
const update = () => {
|
|
2617
|
+
const rect = trigger.getBoundingClientRect();
|
|
2618
|
+
const viewport = window.visualViewport;
|
|
2619
|
+
const left = viewport?.offsetLeft ?? 0;
|
|
2620
|
+
const top = viewport?.offsetTop ?? 0;
|
|
2621
|
+
const width = viewport?.width ?? document.documentElement.clientWidth;
|
|
2622
|
+
const height = viewport?.height ?? document.documentElement.clientHeight;
|
|
2623
|
+
const gap = Math.max(0, sideOffset);
|
|
2624
|
+
content.style.maxWidth = `${Math.max(0, width - 16)}px`;
|
|
2625
|
+
content.style.maxHeight = `${Math.max(0, height - 16)}px`;
|
|
2626
|
+
const room = {
|
|
2627
|
+
top: rect.top - top,
|
|
2628
|
+
bottom: top + height - rect.bottom,
|
|
2629
|
+
left: rect.left - left,
|
|
2630
|
+
right: left + width - rect.right
|
|
2631
|
+
};
|
|
2632
|
+
const vertical = side === "top" || side === "bottom";
|
|
2633
|
+
const needed = (vertical ? content.scrollHeight : content.offsetWidth) + gap + 8;
|
|
2634
|
+
const actual = room[side] < needed && room[opposite[side]] > room[side] ? opposite[side] : side;
|
|
2635
|
+
if (vertical) content.style.maxHeight = `${Math.max(0, room[actual] - gap - 8)}px`;
|
|
2636
|
+
else content.style.maxWidth = `${Math.max(0, room[actual] - gap - 8)}px`;
|
|
2637
|
+
const w = content.offsetWidth;
|
|
2638
|
+
const h = content.offsetHeight;
|
|
2639
|
+
const rtl = getComputedStyle(trigger).direction === "rtl";
|
|
2640
|
+
const fraction = {
|
|
2641
|
+
start: 0,
|
|
2642
|
+
center: .5,
|
|
2643
|
+
end: 1
|
|
2644
|
+
}[align];
|
|
2645
|
+
const horizontalFraction = rtl ? 1 - fraction : fraction;
|
|
2646
|
+
const x = vertical ? rect.left + (rect.width - w) * horizontalFraction : actual === "left" ? rect.left - w - gap : rect.right + gap;
|
|
2647
|
+
const y = vertical ? actual === "top" ? rect.top - h - gap : rect.bottom + gap : rect.top + (rect.height - h) * fraction;
|
|
2648
|
+
content.style.left = `${Math.round(clamp(x, left + 8, left + width - w - 8))}px`;
|
|
2649
|
+
content.style.top = `${Math.round(clamp(y, top + 8, top + height - h - 8))}px`;
|
|
2650
|
+
content.dataset.side = actual;
|
|
2651
|
+
};
|
|
2652
|
+
update();
|
|
2653
|
+
const observer = new ResizeObserver(update);
|
|
2654
|
+
observer.observe(trigger);
|
|
2655
|
+
observer.observe(content);
|
|
2656
|
+
window.addEventListener("resize", update);
|
|
2657
|
+
window.addEventListener("scroll", update, true);
|
|
2658
|
+
window.visualViewport?.addEventListener("resize", update);
|
|
2659
|
+
window.visualViewport?.addEventListener("scroll", update);
|
|
2660
|
+
return () => {
|
|
2661
|
+
observer.disconnect();
|
|
2662
|
+
window.removeEventListener("resize", update);
|
|
2663
|
+
window.removeEventListener("scroll", update, true);
|
|
2664
|
+
window.visualViewport?.removeEventListener("resize", update);
|
|
2665
|
+
window.visualViewport?.removeEventListener("scroll", update);
|
|
2666
|
+
};
|
|
2667
|
+
}, [
|
|
2668
|
+
side,
|
|
2669
|
+
align,
|
|
2670
|
+
sideOffset,
|
|
2671
|
+
contentRef,
|
|
2672
|
+
triggerRef
|
|
2673
|
+
]);
|
|
2674
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2675
|
+
...props,
|
|
2676
|
+
ref: mergedRef,
|
|
2677
|
+
id: menu.contentId,
|
|
2678
|
+
role: "menu",
|
|
2679
|
+
tabIndex: -1,
|
|
2680
|
+
"aria-labelledby": props["aria-label"] ? void 0 : menu.triggerId,
|
|
2681
|
+
popover: "manual",
|
|
2682
|
+
"data-slot": "dropdown-menu-content",
|
|
2683
|
+
"data-state": "open",
|
|
2684
|
+
style,
|
|
2685
|
+
className: cn("fixed inset-auto z-50 m-0 box-border w-max min-w-50 overflow-y-auto rounded-lg border border-popover-border bg-popover-bg p-1 font-sans text-sm text-fg shadow-popover outline-none", "transition-opacity duration-150 starting:opacity-0 motion-reduce:transition-none", className),
|
|
2686
|
+
onKeyDown: (event) => {
|
|
2687
|
+
onKeyDown?.(event);
|
|
2688
|
+
if (event.defaultPrevented) return;
|
|
2689
|
+
const entries = items(event.currentTarget);
|
|
2690
|
+
const index = entries.indexOf(document.activeElement);
|
|
2691
|
+
if (event.key === "Escape") {
|
|
2692
|
+
event.preventDefault();
|
|
2693
|
+
event.stopPropagation();
|
|
2694
|
+
menu.change(false, "escape");
|
|
2695
|
+
} else if (event.key === "Tab") {
|
|
2696
|
+
event.preventDefault();
|
|
2697
|
+
const trigger = menu.trigger.current;
|
|
2698
|
+
if (!trigger) return;
|
|
2699
|
+
const scope = trigger.closest("dialog[open]") ?? document.body;
|
|
2700
|
+
const tabbables = Array.from(scope.querySelectorAll("a[href], button, input, select, textarea, [tabindex]")).filter((el) => el.tabIndex >= 0 && !el.matches(":disabled") && !el.closest("[inert]") && !event.currentTarget.contains(el) && el.getClientRects().length > 0);
|
|
2701
|
+
const target = tabbables[tabbables.indexOf(trigger) + (event.shiftKey ? -1 : 1)];
|
|
2702
|
+
menu.change(false, "tab");
|
|
2703
|
+
(target ?? trigger).focus();
|
|
2704
|
+
} else if ([
|
|
2705
|
+
"ArrowDown",
|
|
2706
|
+
"ArrowUp",
|
|
2707
|
+
"Home",
|
|
2708
|
+
"End"
|
|
2709
|
+
].includes(event.key)) {
|
|
2710
|
+
event.preventDefault();
|
|
2711
|
+
entries[event.key === "Home" ? 0 : event.key === "End" ? entries.length - 1 : (index + (event.key === "ArrowDown" ? 1 : -1) + entries.length) % entries.length]?.focus();
|
|
2712
|
+
} else if (event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey && event.key !== " ") {
|
|
2713
|
+
event.preventDefault();
|
|
2714
|
+
const now = Date.now();
|
|
2715
|
+
query.current.text = (now - query.current.time > 500 ? "" : query.current.text) + event.key.toLocaleLowerCase();
|
|
2716
|
+
query.current.time = now;
|
|
2717
|
+
const text = query.current.text;
|
|
2718
|
+
const term = Array.from(text).every((char) => char === text[0]) ? text[0] : text;
|
|
2719
|
+
[...entries.slice(index + 1), ...entries.slice(0, index + 1)].find((item) => (item.dataset.textValue ?? item.textContent ?? "").trim().toLocaleLowerCase().startsWith(term))?.focus();
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
});
|
|
2723
|
+
}
|
|
2724
|
+
function DropdownMenuItem({ asChild = false, iconLeft, tone = "default", textValue, disabled, onSelect, onClick, onClickCapture, onAuxClickCapture, onKeyDown, onPointerMove, children, className, ...props }) {
|
|
2725
|
+
const menu = useMenu();
|
|
2726
|
+
const Component = asChild ? Slot : "button";
|
|
2727
|
+
const renderContent = (label) => /* @__PURE__ */ jsxs(Fragment$1, { children: [iconLeft && /* @__PURE__ */ jsx("span", {
|
|
2728
|
+
"aria-hidden": true,
|
|
2729
|
+
children: iconLeft
|
|
2730
|
+
}), label] });
|
|
2731
|
+
const child = asChild ? Children.only(children) : null;
|
|
2732
|
+
const content = isValidElement(child) ? cloneElement(child, { children: wrapChildren(child.props.children, renderContent) }) : renderContent(children);
|
|
2733
|
+
return /* @__PURE__ */ jsx(Component, {
|
|
2734
|
+
...props,
|
|
2735
|
+
type: asChild ? void 0 : "button",
|
|
2736
|
+
role: "menuitem",
|
|
2737
|
+
tabIndex: -1,
|
|
2738
|
+
"aria-disabled": disabled || void 0,
|
|
2739
|
+
"data-slot": "dropdown-menu-item",
|
|
2740
|
+
"data-tone": tone,
|
|
2741
|
+
"data-text-value": textValue,
|
|
2742
|
+
className: cn("flex min-h-8 w-full cursor-default items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm/snug outline-none select-none pointer-coarse:min-h-10", "focus:bg-popover-highlight focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus", "aria-disabled:cursor-not-allowed aria-disabled:opacity-50 [&_svg]:size-4 [&_svg]:shrink-0", tone === "danger" ? "text-danger-subtle-fg focus:bg-danger-subtle-bg" : "text-fg [&_svg]:text-fg-muted", className),
|
|
2743
|
+
onClickCapture: (event) => {
|
|
2744
|
+
if (disabled) {
|
|
2745
|
+
event.preventDefault();
|
|
2746
|
+
event.stopPropagation();
|
|
2747
|
+
return;
|
|
2748
|
+
}
|
|
2749
|
+
onClickCapture?.(event);
|
|
2750
|
+
},
|
|
2751
|
+
onAuxClickCapture: (event) => {
|
|
2752
|
+
if (disabled) {
|
|
2753
|
+
event.preventDefault();
|
|
2754
|
+
event.stopPropagation();
|
|
2755
|
+
return;
|
|
2756
|
+
}
|
|
2757
|
+
onAuxClickCapture?.(event);
|
|
2758
|
+
},
|
|
2759
|
+
onPointerMove: (event) => {
|
|
2760
|
+
onPointerMove?.(event);
|
|
2761
|
+
if (!event.defaultPrevented && event.pointerType !== "touch") event.currentTarget.focus({ preventScroll: true });
|
|
2762
|
+
},
|
|
2763
|
+
onKeyDown: (event) => {
|
|
2764
|
+
onKeyDown?.(event);
|
|
2765
|
+
if (event.defaultPrevented) return;
|
|
2766
|
+
if (event.key === " " || event.key === "Enter") {
|
|
2767
|
+
event.preventDefault();
|
|
2768
|
+
event.currentTarget.click();
|
|
2769
|
+
}
|
|
2770
|
+
},
|
|
2771
|
+
onClick: (event) => {
|
|
2772
|
+
if (disabled) {
|
|
2773
|
+
event.preventDefault();
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
onClick?.(event);
|
|
2777
|
+
const selection = new Event("select", { cancelable: true });
|
|
2778
|
+
onSelect?.(selection);
|
|
2779
|
+
if (!selection.defaultPrevented) menu.change(false, "item-select");
|
|
2780
|
+
},
|
|
2781
|
+
children: content
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
function DropdownMenuGroup({ className, ...props }) {
|
|
1622
2785
|
return /* @__PURE__ */ jsx("div", {
|
|
1623
2786
|
...props,
|
|
1624
|
-
|
|
1625
|
-
|
|
2787
|
+
role: "group",
|
|
2788
|
+
"data-slot": "dropdown-menu-group",
|
|
2789
|
+
className
|
|
1626
2790
|
});
|
|
1627
2791
|
}
|
|
1628
|
-
|
|
1629
|
-
function DrawerFooter({ className, ...props }) {
|
|
2792
|
+
function DropdownMenuLabel({ className, ...props }) {
|
|
1630
2793
|
return /* @__PURE__ */ jsx("div", {
|
|
1631
2794
|
...props,
|
|
1632
|
-
"data-slot": "
|
|
1633
|
-
className: cn("
|
|
2795
|
+
"data-slot": "dropdown-menu-label",
|
|
2796
|
+
className: cn("px-2 py-1.5 text-xs text-fg-muted", className)
|
|
1634
2797
|
});
|
|
1635
2798
|
}
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
const { requestClose } = useDrawerContext("DrawerClose");
|
|
1639
|
-
return /* @__PURE__ */ jsx(Button, {
|
|
2799
|
+
function DropdownMenuSeparator({ className, ...props }) {
|
|
2800
|
+
return /* @__PURE__ */ jsx("hr", {
|
|
1640
2801
|
...props,
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
icon: icon ?? /* @__PURE__ */ jsx(X, {}),
|
|
1644
|
-
tooltip: false,
|
|
1645
|
-
onClick: () => requestClose("close-button")
|
|
2802
|
+
"data-slot": "dropdown-menu-separator",
|
|
2803
|
+
className: cn("my-1 h-px border-0 bg-popover-border", className)
|
|
1646
2804
|
});
|
|
1647
2805
|
}
|
|
1648
2806
|
//#endregion
|
|
@@ -1830,6 +2988,197 @@ function Meter({ label, summary, segments, className, ...props }) {
|
|
|
1830
2988
|
});
|
|
1831
2989
|
}
|
|
1832
2990
|
//#endregion
|
|
2991
|
+
//#region src/components/radio-group/radio-group.tsx
|
|
2992
|
+
const RadioContext = createContext(null);
|
|
2993
|
+
function RadioGroup({ name, legend, description, error, value, defaultValue, onValueChange, variant = "default", orientation = "vertical", itemsClassName, children, className, disabled, required, form, ref, "aria-invalid": ariaInvalid, "aria-describedby": describedByProp, ...props }) {
|
|
2994
|
+
const id = useId();
|
|
2995
|
+
const descriptionId = `${id}-description`;
|
|
2996
|
+
const errorId = `${id}-error`;
|
|
2997
|
+
const legendId = `${id}-legend`;
|
|
2998
|
+
const invalid = Boolean(error) || ariaInvalid === true || ariaInvalid === "true";
|
|
2999
|
+
const hasDescription = isPresent(description);
|
|
3000
|
+
const hasError = Boolean(error) && error !== true && isPresent(error);
|
|
3001
|
+
const describedBy = [
|
|
3002
|
+
describedByProp,
|
|
3003
|
+
hasDescription && descriptionId,
|
|
3004
|
+
hasError && errorId
|
|
3005
|
+
].filter(Boolean).join(" ") || void 0;
|
|
3006
|
+
const controlled = value !== void 0;
|
|
3007
|
+
const [internalValue, setInternalValue] = useState(defaultValue ?? null);
|
|
3008
|
+
const selected = controlled ? value : internalValue;
|
|
3009
|
+
const groupRef = useRef(null);
|
|
3010
|
+
const mergedRef = useMergedRef(groupRef, ref);
|
|
3011
|
+
const inputs = useRef(/* @__PURE__ */ new Set());
|
|
3012
|
+
const currentSelection = useRef(selected);
|
|
3013
|
+
useLayoutEffect(() => {
|
|
3014
|
+
currentSelection.current = selected;
|
|
3015
|
+
}, [selected]);
|
|
3016
|
+
const register = useCallback((node) => {
|
|
3017
|
+
const members = inputs.current;
|
|
3018
|
+
if ([...members].some((input) => input.value === node.value)) throw new Error(`Radio values must be unique within a RadioGroup: "${node.value}"`);
|
|
3019
|
+
members.add(node);
|
|
3020
|
+
const reconcile = () => {
|
|
3021
|
+
queueMicrotask(() => {
|
|
3022
|
+
if (controlled || !groupRef.current) return;
|
|
3023
|
+
setInternalValue((current) => [...members].some((input) => input.value === current) ? current : null);
|
|
3024
|
+
});
|
|
3025
|
+
};
|
|
3026
|
+
reconcile();
|
|
3027
|
+
return () => {
|
|
3028
|
+
members.delete(node);
|
|
3029
|
+
reconcile();
|
|
3030
|
+
};
|
|
3031
|
+
}, [controlled]);
|
|
3032
|
+
useLayoutEffect(() => {
|
|
3033
|
+
const owner = groupRef.current?.form;
|
|
3034
|
+
if (!owner) return;
|
|
3035
|
+
let timer;
|
|
3036
|
+
const reset = (event) => {
|
|
3037
|
+
clearTimeout(timer);
|
|
3038
|
+
timer = setTimeout(() => {
|
|
3039
|
+
if (event.defaultPrevented) return;
|
|
3040
|
+
const next = controlled ? currentSelection.current : defaultValue ?? null;
|
|
3041
|
+
const members = [...inputs.current];
|
|
3042
|
+
if (!controlled) setInternalValue(members.some((input) => input.value === next) ? next : null);
|
|
3043
|
+
for (const input of members) input.checked = input.value === next;
|
|
3044
|
+
}, 0);
|
|
3045
|
+
};
|
|
3046
|
+
owner.addEventListener("reset", reset);
|
|
3047
|
+
return () => {
|
|
3048
|
+
clearTimeout(timer);
|
|
3049
|
+
owner.removeEventListener("reset", reset);
|
|
3050
|
+
};
|
|
3051
|
+
}, [
|
|
3052
|
+
controlled,
|
|
3053
|
+
defaultValue,
|
|
3054
|
+
form
|
|
3055
|
+
]);
|
|
3056
|
+
return /* @__PURE__ */ jsx(RadioContext, {
|
|
3057
|
+
value: {
|
|
3058
|
+
name,
|
|
3059
|
+
form,
|
|
3060
|
+
selected,
|
|
3061
|
+
disabled,
|
|
3062
|
+
required,
|
|
3063
|
+
invalid,
|
|
3064
|
+
describedBy,
|
|
3065
|
+
variant,
|
|
3066
|
+
orientation,
|
|
3067
|
+
register,
|
|
3068
|
+
select: (event) => {
|
|
3069
|
+
if (!event.currentTarget.checked) return;
|
|
3070
|
+
const next = event.currentTarget.value;
|
|
3071
|
+
if (!controlled) setInternalValue(next);
|
|
3072
|
+
onValueChange?.(next, event);
|
|
3073
|
+
}
|
|
3074
|
+
},
|
|
3075
|
+
children: /* @__PURE__ */ jsxs("fieldset", {
|
|
3076
|
+
...props,
|
|
3077
|
+
ref: mergedRef,
|
|
3078
|
+
name,
|
|
3079
|
+
form,
|
|
3080
|
+
disabled,
|
|
3081
|
+
"data-slot": "radio-group",
|
|
3082
|
+
"data-variant": variant,
|
|
3083
|
+
role: "radiogroup",
|
|
3084
|
+
"aria-labelledby": props["aria-labelledby"] ?? legendId,
|
|
3085
|
+
"aria-describedby": describedBy,
|
|
3086
|
+
"aria-invalid": invalid ? true : ariaInvalid,
|
|
3087
|
+
"aria-required": required || void 0,
|
|
3088
|
+
"aria-orientation": orientation,
|
|
3089
|
+
className: cn("group/radio-group m-0 min-w-0 border-0 p-0", className),
|
|
3090
|
+
children: [/* @__PURE__ */ jsxs("legend", {
|
|
3091
|
+
id: legendId,
|
|
3092
|
+
className: "float-start mb-1.5 w-full p-0 text-sm font-medium text-fg group-disabled/radio-group:opacity-50",
|
|
3093
|
+
children: [legend, required && /* @__PURE__ */ jsx("span", {
|
|
3094
|
+
"aria-hidden": true,
|
|
3095
|
+
className: "text-danger",
|
|
3096
|
+
children: " *"
|
|
3097
|
+
})]
|
|
3098
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
3099
|
+
className: "clear-both flex flex-col",
|
|
3100
|
+
children: [
|
|
3101
|
+
hasDescription && /* @__PURE__ */ jsx("p", {
|
|
3102
|
+
id: descriptionId,
|
|
3103
|
+
className: "m-0 -mt-1 mb-2.5 text-xs/snug text-fg-muted group-disabled/radio-group:opacity-50",
|
|
3104
|
+
children: description
|
|
3105
|
+
}),
|
|
3106
|
+
/* @__PURE__ */ jsx("div", {
|
|
3107
|
+
"data-slot": "radio-group-items",
|
|
3108
|
+
"data-orientation": orientation,
|
|
3109
|
+
className: cn("flex gap-2", orientation === "vertical" ? "flex-col" : "flex-row flex-wrap gap-x-6", variant === "card" && orientation === "horizontal" && "gap-3", itemsClassName),
|
|
3110
|
+
children
|
|
3111
|
+
}),
|
|
3112
|
+
hasError && /* @__PURE__ */ jsxs("p", {
|
|
3113
|
+
id: errorId,
|
|
3114
|
+
className: "m-0 mt-2 flex items-start gap-1 text-xs/snug text-danger",
|
|
3115
|
+
children: [/* @__PURE__ */ jsx(CircleAlert, {
|
|
3116
|
+
"aria-hidden": true,
|
|
3117
|
+
className: "mt-px size-3.5 shrink-0"
|
|
3118
|
+
}), /* @__PURE__ */ jsx("span", { children: error })]
|
|
3119
|
+
})
|
|
3120
|
+
]
|
|
3121
|
+
})]
|
|
3122
|
+
})
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
function Radio({ value, label, description, disabled, className, inputClassName, ref, onChange, "aria-describedby": describedBy, ...props }) {
|
|
3126
|
+
const group = useContext(RadioContext);
|
|
3127
|
+
if (!group) throw new Error("Radio must be rendered inside <RadioGroup>");
|
|
3128
|
+
if (value === "") throw new Error("Radio value must be a non-empty string");
|
|
3129
|
+
const ownRef = useRef(null);
|
|
3130
|
+
const mergedRef = useMergedRef(ownRef, ref);
|
|
3131
|
+
const register = group.register;
|
|
3132
|
+
useLayoutEffect(() => {
|
|
3133
|
+
if (ownRef.current) return register(ownRef.current);
|
|
3134
|
+
}, [register, value]);
|
|
3135
|
+
const card = group.variant === "card";
|
|
3136
|
+
const invalid = group.invalid;
|
|
3137
|
+
return /* @__PURE__ */ jsx(ChoiceRow, {
|
|
3138
|
+
slot: "radio",
|
|
3139
|
+
label,
|
|
3140
|
+
description,
|
|
3141
|
+
gap: card ? "gap-3" : "gap-2",
|
|
3142
|
+
controlOffset: "mt-1",
|
|
3143
|
+
"aria-describedby": [describedBy, group.describedBy].filter(Boolean).join(" ") || void 0,
|
|
3144
|
+
className: cn("group/radio wrap-anywhere motion-reduce:**:transition-none", "pointer-coarse:min-h-11 pointer-coarse:py-2.5", card && [
|
|
3145
|
+
"w-full rounded-lg border bg-surface p-3 pointer-coarse:p-3",
|
|
3146
|
+
"transition-[border-color,background-color] duration-150 ease-out",
|
|
3147
|
+
"[&>span:last-child>span:first-child]:font-medium",
|
|
3148
|
+
"has-[input:checked]:bg-[color-mix(in_oklab,var(--color-control-checked)_4%,var(--color-surface))]",
|
|
3149
|
+
"has-[input:focus-visible]:outline-solid has-[input:focus-visible]:outline-2 has-[input:focus-visible]:outline-offset-2",
|
|
3150
|
+
invalid ? "border-danger has-[input:focus-visible]:outline-danger" : "border-input-border has-[input:checked]:border-control-checked has-[input:enabled]:hover:border-input-border-hover has-[input:focus-visible]:outline-control-checked",
|
|
3151
|
+
"forced-colors:has-[input:checked]:border-[Highlight] forced-colors:has-[input:focus-visible]:outline-[Highlight]",
|
|
3152
|
+
group.orientation === "horizontal" && "flex-[1_1_14rem]"
|
|
3153
|
+
], className),
|
|
3154
|
+
control: (row) => /* @__PURE__ */ jsxs("span", {
|
|
3155
|
+
className: "relative inline-grid size-4 place-items-center transition-[scale] duration-100 ease-out has-[input:enabled:active]:scale-[0.92]",
|
|
3156
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
3157
|
+
...props,
|
|
3158
|
+
ref: mergedRef,
|
|
3159
|
+
"data-slot": "radio-input",
|
|
3160
|
+
type: "radio",
|
|
3161
|
+
name: group.name,
|
|
3162
|
+
form: group.form,
|
|
3163
|
+
value,
|
|
3164
|
+
checked: group.selected === value,
|
|
3165
|
+
disabled: group.disabled || disabled,
|
|
3166
|
+
required: group.required,
|
|
3167
|
+
"aria-labelledby": props["aria-labelledby"] ?? row.labelledBy,
|
|
3168
|
+
"aria-describedby": row.describedBy,
|
|
3169
|
+
onChange: (event) => {
|
|
3170
|
+
onChange?.(event);
|
|
3171
|
+
group.select(event);
|
|
3172
|
+
},
|
|
3173
|
+
className: cn("peer col-start-1 row-start-1 m-0 size-4 cursor-pointer appearance-none rounded-full border outline-hidden disabled:cursor-not-allowed", "bg-input-bg checked:bg-control-checked", "transition-[border-color,background-color,box-shadow] duration-150 ease-out", invalid ? "border-danger [box-shadow:0_0_0_3px_var(--color-input-halo-invalid)]" : "border-input-border checked:border-control-checked group-hover/radio:enabled:not-checked:border-input-border-hover", !card && [invalid ? "focus-visible:[box-shadow:0_0_0_3px_var(--color-input-halo-invalid-focus)] not-forced-colors:focus-visible:outline-danger" : "focus-visible:border-input-focus focus-visible:[box-shadow:0_0_0_3px_var(--color-input-halo)] not-forced-colors:focus-visible:outline-control-checked", "checked:focus-visible:shadow-none checked:focus-visible:outline-solid checked:focus-visible:outline-2 checked:focus-visible:outline-offset-2"], card && "focus-visible:outline-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]", "forced-colors:checked:border-[Highlight] forced-colors:checked:bg-[Highlight]", "forced-colors:disabled:border-[GrayText] forced-colors:disabled:checked:bg-[GrayText]", !card && "forced-colors:focus-visible:outline-[Highlight]", inputClassName)
|
|
3174
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
3175
|
+
"aria-hidden": true,
|
|
3176
|
+
className: "pointer-events-none col-start-1 row-start-1 size-1.5 scale-0 rounded-full bg-control-checked-fg transition-[scale] duration-150 ease-out peer-checked:scale-100 forced-colors:forced-color-adjust-none forced-colors:bg-[HighlightText] forced-colors:peer-disabled:bg-[Canvas]"
|
|
3177
|
+
})]
|
|
3178
|
+
})
|
|
3179
|
+
});
|
|
3180
|
+
}
|
|
3181
|
+
//#endregion
|
|
1833
3182
|
//#region src/components/select/select.tsx
|
|
1834
3183
|
const selectPadding = {
|
|
1835
3184
|
sm: "px-3",
|
|
@@ -1914,7 +3263,7 @@ function Select({ options, placeholder, helperText, error, size = "md", start, m
|
|
|
1914
3263
|
className,
|
|
1915
3264
|
controlRef: selectRef,
|
|
1916
3265
|
activate: openPicker,
|
|
1917
|
-
children: (field) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3266
|
+
children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
1918
3267
|
field.floatingLabel(labelRestingWidth[size]),
|
|
1919
3268
|
/* @__PURE__ */ jsxs("select", {
|
|
1920
3269
|
...props,
|
|
@@ -1943,12 +3292,12 @@ function Select({ options, placeholder, helperText, error, size = "md", start, m
|
|
|
1943
3292
|
}),
|
|
1944
3293
|
hasLeading && /* @__PURE__ */ jsx("span", {
|
|
1945
3294
|
"aria-hidden": true,
|
|
1946
|
-
className: cn("pointer-events-none absolute inset-y-0 inset-s-0 flex items-center text-fg-muted [&_svg]:shrink-0", iconSize[size], startSlotPadding[size]),
|
|
3295
|
+
className: cn("pointer-events-none absolute inset-y-0 inset-s-0 flex items-center text-fg-muted [&_svg]:shrink-0", iconSize$1[size], startSlotPadding[size]),
|
|
1947
3296
|
children: leading
|
|
1948
3297
|
}),
|
|
1949
3298
|
/* @__PURE__ */ jsx("span", {
|
|
1950
3299
|
"aria-hidden": true,
|
|
1951
|
-
className: cn("pointer-events-none absolute inset-y-0 inset-e-0 flex items-center text-fg-muted", iconSize[size], endSlotPadding[size]),
|
|
3300
|
+
className: cn("pointer-events-none absolute inset-y-0 inset-e-0 flex items-center text-fg-muted", iconSize$1[size], endSlotPadding[size]),
|
|
1952
3301
|
children: /* @__PURE__ */ jsx(ChevronDown, { className: "transition-transform duration-150 ease-out group-has-[select:open]/control:rotate-180 motion-reduce:transition-none" })
|
|
1953
3302
|
})
|
|
1954
3303
|
] })
|
|
@@ -2063,7 +3412,7 @@ function TabContent({ icon, badge, children }) {
|
|
|
2063
3412
|
children
|
|
2064
3413
|
})]
|
|
2065
3414
|
}),
|
|
2066
|
-
badge != null && badge !== false && /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx("span", {
|
|
3415
|
+
badge != null && badge !== false && /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx("span", {
|
|
2067
3416
|
"data-slot": "tab-badge",
|
|
2068
3417
|
className: "inline-flex",
|
|
2069
3418
|
children: badge
|
|
@@ -2251,7 +3600,7 @@ function SidebarProvider({ children }) {
|
|
|
2251
3600
|
function Sidebar({ className, children, ...props }) {
|
|
2252
3601
|
const { open, setOpen, drawerId } = useSidebarContext("Sidebar");
|
|
2253
3602
|
const label = props["aria-label"] ? { "aria-label": props["aria-label"] } : { "aria-labelledby": props["aria-labelledby"] };
|
|
2254
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(PanelContext, {
|
|
3603
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(PanelContext, {
|
|
2255
3604
|
value: { drawer: false },
|
|
2256
3605
|
children: /* @__PURE__ */ jsx("nav", {
|
|
2257
3606
|
...props,
|
|
@@ -2471,13 +3820,586 @@ function SidebarIcon({ className, ...props }) {
|
|
|
2471
3820
|
}
|
|
2472
3821
|
/** Count at the end of a SidebarItem, as a small neutral Badge; part of the name ("Projects 8"). */
|
|
2473
3822
|
function SidebarBadge({ className, ...props }) {
|
|
2474
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
3823
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [" ", /* @__PURE__ */ jsx(Badge, {
|
|
2475
3824
|
size: "sm",
|
|
2476
3825
|
...props,
|
|
2477
3826
|
className: cn("shrink-0", className)
|
|
2478
3827
|
})] });
|
|
2479
3828
|
}
|
|
2480
3829
|
//#endregion
|
|
3830
|
+
//#region src/components/slider/scale.ts
|
|
3831
|
+
const fail = (message) => {
|
|
3832
|
+
throw new Error(`Slider: ${message}`);
|
|
3833
|
+
};
|
|
3834
|
+
const close = (value, integer) => Math.abs(value - integer) <= 1e-7;
|
|
3835
|
+
const decimals = (value) => {
|
|
3836
|
+
const [digits, exponent = "0"] = String(value).toLowerCase().split("e");
|
|
3837
|
+
return Math.max(0, (digits.split(".")[1]?.length ?? 0) - Number(exponent));
|
|
3838
|
+
};
|
|
3839
|
+
/** Work in integer ticks, not repeated floating point additions. */
|
|
3840
|
+
function createScale(min, max, step, gap) {
|
|
3841
|
+
if (![
|
|
3842
|
+
min,
|
|
3843
|
+
max,
|
|
3844
|
+
step,
|
|
3845
|
+
gap
|
|
3846
|
+
].every(Number.isFinite)) fail("bounds, step and gap must be finite");
|
|
3847
|
+
if (min >= max || step <= 0) fail("min must be less than max and step must be positive");
|
|
3848
|
+
if (!Number.isInteger(gap) || gap < 0) fail("minStepsBetweenThumbs must be a non-negative integer");
|
|
3849
|
+
const factor = 10 ** Math.max(decimals(min), decimals(max), decimals(step));
|
|
3850
|
+
const start = Math.round(min * factor), end = Math.round(max * factor), increment = Math.round(step * factor);
|
|
3851
|
+
if (![
|
|
3852
|
+
start,
|
|
3853
|
+
end,
|
|
3854
|
+
increment,
|
|
3855
|
+
end - start
|
|
3856
|
+
].every(Number.isSafeInteger) || increment < 1) fail("bounds and step exceed safe numeric precision");
|
|
3857
|
+
const count = (end - start) / increment;
|
|
3858
|
+
if (!Number.isSafeInteger(count)) fail("(max - min) must be evenly divisible by step");
|
|
3859
|
+
if (gap > count) fail("minimum thumb distance exceeds the available range");
|
|
3860
|
+
const fromTick = (tick) => (start + tick * increment) / factor;
|
|
3861
|
+
const toTick = (value) => (value * factor - start) / increment;
|
|
3862
|
+
const validate = (values, length) => {
|
|
3863
|
+
if (values.length !== length) fail(`expected ${length} value${length === 1 ? "" : "s"}`);
|
|
3864
|
+
const ticks = values.map((value) => {
|
|
3865
|
+
if (!Number.isFinite(value)) fail("values must be finite");
|
|
3866
|
+
const tick = toTick(value), rounded = Math.round(tick);
|
|
3867
|
+
if (!close(tick, rounded) || rounded < 0 || rounded > count) fail("values must be on a step within min and max");
|
|
3868
|
+
return rounded;
|
|
3869
|
+
});
|
|
3870
|
+
if (ticks.length === 2 && ticks[1] - ticks[0] < gap) fail("range values must be ordered and satisfy the minimum distance");
|
|
3871
|
+
return ticks.map(fromTick);
|
|
3872
|
+
};
|
|
3873
|
+
const normalize = (values) => {
|
|
3874
|
+
const ticks = values.map((value) => Math.max(0, Math.min(count, Math.round(toTick(value)))));
|
|
3875
|
+
if (ticks.length === 2) {
|
|
3876
|
+
ticks[0] = Math.min(ticks[0], count - gap);
|
|
3877
|
+
ticks[1] = Math.max(ticks[1], ticks[0] + gap);
|
|
3878
|
+
}
|
|
3879
|
+
return ticks.map(fromTick);
|
|
3880
|
+
};
|
|
3881
|
+
const limits = (values, index) => values.length === 1 ? [0, count] : index === 0 ? [0, Math.round(toTick(values[1])) - gap] : [Math.round(toTick(values[0])) + gap, count];
|
|
3882
|
+
const move = (values, index, tick) => {
|
|
3883
|
+
const [low, high] = limits(values, index);
|
|
3884
|
+
return values.map((value, i) => i === index ? fromTick(Math.max(low, Math.min(high, Math.round(tick)))) : value);
|
|
3885
|
+
};
|
|
3886
|
+
return {
|
|
3887
|
+
min,
|
|
3888
|
+
max,
|
|
3889
|
+
step,
|
|
3890
|
+
gap,
|
|
3891
|
+
count,
|
|
3892
|
+
fromTick,
|
|
3893
|
+
toTick,
|
|
3894
|
+
validate,
|
|
3895
|
+
normalize,
|
|
3896
|
+
limits,
|
|
3897
|
+
move,
|
|
3898
|
+
key: `${min}/${max}/${step}/${gap}`
|
|
3899
|
+
};
|
|
3900
|
+
}
|
|
3901
|
+
const sameValues = (left, right) => left.length === right.length && left.every((value, index) => value === right[index]);
|
|
3902
|
+
//#endregion
|
|
3903
|
+
//#region src/components/slider/use-slider.ts
|
|
3904
|
+
const keys = [
|
|
3905
|
+
"ArrowRight",
|
|
3906
|
+
"ArrowLeft",
|
|
3907
|
+
"ArrowUp",
|
|
3908
|
+
"ArrowDown",
|
|
3909
|
+
"Home",
|
|
3910
|
+
"End",
|
|
3911
|
+
"PageUp",
|
|
3912
|
+
"PageDown"
|
|
3913
|
+
];
|
|
3914
|
+
function useSlider(options) {
|
|
3915
|
+
const { scale, value, defaultValue, disabled, form } = options;
|
|
3916
|
+
const controlled = value !== void 0;
|
|
3917
|
+
const [internal, setInternal] = useState(() => [...defaultValue]);
|
|
3918
|
+
const values = controlled ? value : scale.normalize(internal);
|
|
3919
|
+
if (!controlled && !sameValues(internal, values)) setInternal([...values]);
|
|
3920
|
+
const [dragging, setDragging] = useState(false);
|
|
3921
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
3922
|
+
const inputs = useRef([]);
|
|
3923
|
+
const controlRef = useRef(null);
|
|
3924
|
+
const railRef = useRef(null);
|
|
3925
|
+
const session = useRef(null);
|
|
3926
|
+
const lastIndex = useRef(0);
|
|
3927
|
+
const mounted = useRef(false);
|
|
3928
|
+
const generation = useRef(0);
|
|
3929
|
+
const latest = useRef({
|
|
3930
|
+
...options,
|
|
3931
|
+
values,
|
|
3932
|
+
controlled
|
|
3933
|
+
});
|
|
3934
|
+
function release() {
|
|
3935
|
+
const current = session.current;
|
|
3936
|
+
session.current = null;
|
|
3937
|
+
if (!current) return null;
|
|
3938
|
+
current.removeListeners?.();
|
|
3939
|
+
if (current.pointerId !== void 0 && current.control?.hasPointerCapture?.(current.pointerId)) current.control.releasePointerCapture(current.pointerId);
|
|
3940
|
+
if (mounted.current) setDragging(false);
|
|
3941
|
+
return current;
|
|
3942
|
+
}
|
|
3943
|
+
function canUse(index) {
|
|
3944
|
+
return !latest.current.disabled && !!inputs.current[index] && !inputs.current[index].matches(":disabled");
|
|
3945
|
+
}
|
|
3946
|
+
function publish(next, details) {
|
|
3947
|
+
const state = latest.current;
|
|
3948
|
+
if (sameValues(state.values, next)) return;
|
|
3949
|
+
if (session.current) session.current.expected = next;
|
|
3950
|
+
if (!state.controlled) {
|
|
3951
|
+
latest.current = {
|
|
3952
|
+
...state,
|
|
3953
|
+
values: next
|
|
3954
|
+
};
|
|
3955
|
+
setInternal(next);
|
|
3956
|
+
}
|
|
3957
|
+
state.onValueChange?.(next, details);
|
|
3958
|
+
}
|
|
3959
|
+
function commit(start, expected, details, key, defer = false) {
|
|
3960
|
+
const token = generation.current;
|
|
3961
|
+
const notify = () => {
|
|
3962
|
+
const state = latest.current;
|
|
3963
|
+
if (!mounted.current || token !== generation.current || state.scale.key !== key || !canUse(details.thumbIndex)) return;
|
|
3964
|
+
if (!sameValues(state.values, expected) || sameValues(start, state.values)) return;
|
|
3965
|
+
state.onValueCommit?.([...state.values], details);
|
|
3966
|
+
};
|
|
3967
|
+
if (defer) queueMicrotask(notify);
|
|
3968
|
+
else notify();
|
|
3969
|
+
}
|
|
3970
|
+
function finish(canceled = false) {
|
|
3971
|
+
const current = release();
|
|
3972
|
+
if (!current) return;
|
|
3973
|
+
if (canceled) {
|
|
3974
|
+
generation.current++;
|
|
3975
|
+
publish(current.start, {
|
|
3976
|
+
thumbIndex: current.index,
|
|
3977
|
+
reason: "cancel"
|
|
3978
|
+
});
|
|
3979
|
+
} else commit(current.start, current.expected, {
|
|
3980
|
+
thumbIndex: current.index,
|
|
3981
|
+
reason: current.reason
|
|
3982
|
+
}, current.scaleKey);
|
|
3983
|
+
}
|
|
3984
|
+
function begin(index, reason) {
|
|
3985
|
+
const state = latest.current;
|
|
3986
|
+
const current = {
|
|
3987
|
+
index,
|
|
3988
|
+
reason,
|
|
3989
|
+
start: [...state.values],
|
|
3990
|
+
expected: [...state.values],
|
|
3991
|
+
scaleKey: state.scale.key,
|
|
3992
|
+
keys: /* @__PURE__ */ new Set(),
|
|
3993
|
+
offset: 0,
|
|
3994
|
+
startX: 0,
|
|
3995
|
+
pending: false
|
|
3996
|
+
};
|
|
3997
|
+
session.current = current;
|
|
3998
|
+
return current;
|
|
3999
|
+
}
|
|
4000
|
+
function move(index, tick, reason) {
|
|
4001
|
+
if (!canUse(index)) {
|
|
4002
|
+
release();
|
|
4003
|
+
return;
|
|
4004
|
+
}
|
|
4005
|
+
const state = latest.current;
|
|
4006
|
+
publish(state.scale.move(state.values, index, tick), {
|
|
4007
|
+
thumbIndex: index,
|
|
4008
|
+
reason
|
|
4009
|
+
});
|
|
4010
|
+
}
|
|
4011
|
+
function direction() {
|
|
4012
|
+
const node = controlRef.current;
|
|
4013
|
+
return node ? getComputedStyle(node).direction || node.closest("[dir]")?.getAttribute("dir") || "ltr" : "ltr";
|
|
4014
|
+
}
|
|
4015
|
+
function tickAt(clientX) {
|
|
4016
|
+
const rect = railRef.current?.getBoundingClientRect();
|
|
4017
|
+
if (!rect || rect.width <= 0) return null;
|
|
4018
|
+
let ratio = Math.max(0, Math.min(1, (clientX - rect.left) / rect.width));
|
|
4019
|
+
if (direction() === "rtl") ratio = 1 - ratio;
|
|
4020
|
+
return ratio * latest.current.scale.count;
|
|
4021
|
+
}
|
|
4022
|
+
function focus(index) {
|
|
4023
|
+
lastIndex.current = index;
|
|
4024
|
+
setActiveIndex(index);
|
|
4025
|
+
inputs.current[index]?.focus({ preventScroll: true });
|
|
4026
|
+
}
|
|
4027
|
+
function onPointerDown(event) {
|
|
4028
|
+
if (event.defaultPrevented || event.button !== 0 || event.isPrimary === false || session.current?.reason === "pointer") return;
|
|
4029
|
+
if (!canUse(0)) return;
|
|
4030
|
+
const tick = tickAt(event.clientX);
|
|
4031
|
+
if (tick === null) return;
|
|
4032
|
+
if (session.current) finish();
|
|
4033
|
+
const state = latest.current;
|
|
4034
|
+
const distances = state.values.map((v) => Math.abs(state.scale.toTick(v) - tick));
|
|
4035
|
+
const index = distances.length === 1 ? 0 : distances[0] === distances[1] ? lastIndex.current : distances[0] < distances[1] ? 0 : 1;
|
|
4036
|
+
event.preventDefault();
|
|
4037
|
+
focus(index);
|
|
4038
|
+
const current = begin(index, "pointer");
|
|
4039
|
+
current.pointerId = event.pointerId;
|
|
4040
|
+
current.control = event.currentTarget;
|
|
4041
|
+
current.startX = event.clientX;
|
|
4042
|
+
const rect = railRef.current.getBoundingClientRect();
|
|
4043
|
+
const ratio = state.scale.toTick(state.values[index]) / state.scale.count;
|
|
4044
|
+
const center = rect.left + (direction() === "rtl" ? 1 - ratio : ratio) * rect.width;
|
|
4045
|
+
const grabbed = Math.abs(event.clientX - center) <= 22;
|
|
4046
|
+
current.offset = grabbed ? event.clientX - center : 0;
|
|
4047
|
+
current.pending = state.values.length === 2 && state.values[0] === state.values[1] && grabbed;
|
|
4048
|
+
setDragging(true);
|
|
4049
|
+
const owner = event.currentTarget.ownerDocument.defaultView;
|
|
4050
|
+
const pointerMove = (next) => {
|
|
4051
|
+
if (session.current !== current || next.pointerId !== current.pointerId) return;
|
|
4052
|
+
const target = tickAt(next.clientX - current.offset);
|
|
4053
|
+
if (target === null) return;
|
|
4054
|
+
if (current.pending) {
|
|
4055
|
+
if (Math.abs(next.clientX - current.startX) < 2) return;
|
|
4056
|
+
current.index = target < latest.current.scale.toTick(current.start[0]) ? 0 : 1;
|
|
4057
|
+
current.pending = false;
|
|
4058
|
+
focus(current.index);
|
|
4059
|
+
}
|
|
4060
|
+
move(current.index, target, "pointer");
|
|
4061
|
+
};
|
|
4062
|
+
const pointerEnd = (next) => {
|
|
4063
|
+
if (session.current === current && next.pointerId === current.pointerId) finish(next.type !== "pointerup");
|
|
4064
|
+
};
|
|
4065
|
+
owner.addEventListener("pointermove", pointerMove);
|
|
4066
|
+
owner.addEventListener("pointerup", pointerEnd);
|
|
4067
|
+
owner.addEventListener("pointercancel", pointerEnd);
|
|
4068
|
+
current.control.addEventListener("lostpointercapture", pointerEnd);
|
|
4069
|
+
current.removeListeners = () => {
|
|
4070
|
+
owner.removeEventListener("pointermove", pointerMove);
|
|
4071
|
+
owner.removeEventListener("pointerup", pointerEnd);
|
|
4072
|
+
owner.removeEventListener("pointercancel", pointerEnd);
|
|
4073
|
+
current.control?.removeEventListener("lostpointercapture", pointerEnd);
|
|
4074
|
+
};
|
|
4075
|
+
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
4076
|
+
if (!grabbed) move(index, tick, "pointer");
|
|
4077
|
+
}
|
|
4078
|
+
function onKeyDown(event, index) {
|
|
4079
|
+
if (event.defaultPrevented || event.altKey || event.ctrlKey || event.metaKey || event.shiftKey || !keys.includes(event.key) || !canUse(index)) return;
|
|
4080
|
+
event.preventDefault();
|
|
4081
|
+
if (session.current?.reason === "pointer") return;
|
|
4082
|
+
(session.current ?? begin(index, "keyboard")).keys.add(event.key);
|
|
4083
|
+
const state = latest.current;
|
|
4084
|
+
const right = direction() === "rtl" ? -1 : 1;
|
|
4085
|
+
const deltas = {
|
|
4086
|
+
ArrowRight: right,
|
|
4087
|
+
ArrowLeft: -right,
|
|
4088
|
+
ArrowUp: 1,
|
|
4089
|
+
ArrowDown: -1,
|
|
4090
|
+
PageUp: 10,
|
|
4091
|
+
PageDown: -10
|
|
4092
|
+
};
|
|
4093
|
+
const [low, high] = state.scale.limits(state.values, index);
|
|
4094
|
+
move(index, event.key === "Home" ? low : event.key === "End" ? high : state.scale.toTick(state.values[index]) + deltas[event.key], "keyboard");
|
|
4095
|
+
}
|
|
4096
|
+
function onKeyUp(event) {
|
|
4097
|
+
const current = session.current;
|
|
4098
|
+
if (current?.reason !== "keyboard" || !current.keys.has(event.key)) return;
|
|
4099
|
+
current.keys.delete(event.key);
|
|
4100
|
+
if (!current.keys.size) finish();
|
|
4101
|
+
}
|
|
4102
|
+
function onInput(value, index) {
|
|
4103
|
+
if (session.current || !canUse(index) || !Number.isFinite(value)) return;
|
|
4104
|
+
const state = latest.current, start = [...state.values];
|
|
4105
|
+
const next = state.scale.move(start, index, state.scale.toTick(value));
|
|
4106
|
+
publish(next, {
|
|
4107
|
+
thumbIndex: index,
|
|
4108
|
+
reason: "input"
|
|
4109
|
+
});
|
|
4110
|
+
commit(start, next, {
|
|
4111
|
+
thumbIndex: index,
|
|
4112
|
+
reason: "input"
|
|
4113
|
+
}, state.scale.key, true);
|
|
4114
|
+
}
|
|
4115
|
+
useLayoutEffect(() => {
|
|
4116
|
+
latest.current = {
|
|
4117
|
+
...options,
|
|
4118
|
+
values,
|
|
4119
|
+
controlled
|
|
4120
|
+
};
|
|
4121
|
+
const current = session.current;
|
|
4122
|
+
if (current && (current.scaleKey !== scale.key || disabled || !sameValues(values, current.expected))) {
|
|
4123
|
+
generation.current++;
|
|
4124
|
+
release();
|
|
4125
|
+
}
|
|
4126
|
+
});
|
|
4127
|
+
const cancelRef = useRef(() => {});
|
|
4128
|
+
useLayoutEffect(() => {
|
|
4129
|
+
cancelRef.current = () => finish(true);
|
|
4130
|
+
});
|
|
4131
|
+
useLayoutEffect(() => {
|
|
4132
|
+
mounted.current = true;
|
|
4133
|
+
const owner = controlRef.current?.ownerDocument.defaultView;
|
|
4134
|
+
const blur = () => cancelRef.current();
|
|
4135
|
+
owner?.addEventListener("blur", blur);
|
|
4136
|
+
return () => {
|
|
4137
|
+
mounted.current = false;
|
|
4138
|
+
generation.current++;
|
|
4139
|
+
release();
|
|
4140
|
+
owner?.removeEventListener("blur", blur);
|
|
4141
|
+
};
|
|
4142
|
+
}, []);
|
|
4143
|
+
useLayoutEffect(() => {
|
|
4144
|
+
const owner = inputs.current[0]?.form;
|
|
4145
|
+
if (!owner) return;
|
|
4146
|
+
let timer;
|
|
4147
|
+
const reset = (event) => {
|
|
4148
|
+
clearTimeout(timer);
|
|
4149
|
+
timer = setTimeout(() => {
|
|
4150
|
+
if (event.defaultPrevented) return;
|
|
4151
|
+
generation.current++;
|
|
4152
|
+
release();
|
|
4153
|
+
const state = latest.current;
|
|
4154
|
+
const next = state.controlled ? state.values : state.defaultValue;
|
|
4155
|
+
if (!state.controlled) {
|
|
4156
|
+
latest.current = {
|
|
4157
|
+
...state,
|
|
4158
|
+
values: next
|
|
4159
|
+
};
|
|
4160
|
+
setInternal([...next]);
|
|
4161
|
+
}
|
|
4162
|
+
inputs.current.forEach((input, index) => {
|
|
4163
|
+
if (!input) return;
|
|
4164
|
+
const [low, high] = state.scale.limits(next, index);
|
|
4165
|
+
input.min = String(state.scale.fromTick(low));
|
|
4166
|
+
input.max = String(state.scale.fromTick(high));
|
|
4167
|
+
input.value = String(next[index]);
|
|
4168
|
+
});
|
|
4169
|
+
}, 0);
|
|
4170
|
+
};
|
|
4171
|
+
owner.addEventListener("reset", reset);
|
|
4172
|
+
return () => {
|
|
4173
|
+
clearTimeout(timer);
|
|
4174
|
+
owner.removeEventListener("reset", reset);
|
|
4175
|
+
};
|
|
4176
|
+
}, [form]);
|
|
4177
|
+
return {
|
|
4178
|
+
values,
|
|
4179
|
+
dragging,
|
|
4180
|
+
activeIndex,
|
|
4181
|
+
inputs,
|
|
4182
|
+
controlRef,
|
|
4183
|
+
railRef,
|
|
4184
|
+
onPointerDown,
|
|
4185
|
+
onKeyDown,
|
|
4186
|
+
onKeyUp,
|
|
4187
|
+
onInput,
|
|
4188
|
+
onFocus: (index) => {
|
|
4189
|
+
lastIndex.current = index;
|
|
4190
|
+
setActiveIndex(index);
|
|
4191
|
+
},
|
|
4192
|
+
onBlur: () => {
|
|
4193
|
+
if (session.current?.reason === "keyboard") finish(!controlRef.current?.ownerDocument.hasFocus());
|
|
4194
|
+
}
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
//#endregion
|
|
4198
|
+
//#region src/components/slider/slider.tsx
|
|
4199
|
+
function SliderControl({ values, defaults, names, thumbLabels, range = false, minStepsBetweenThumbs = 0, min = 0, max = 100, step = 1, disabled = false, form, label, helperText, error, showValue = true, formatValue = String, getValueText, marks, change, commit, className, ref, onPointerDown, onKeyDown, onKeyUp, onBlur, onFocus, "aria-label": ariaLabel, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }) {
|
|
4200
|
+
const id = useId();
|
|
4201
|
+
const scale = createScale(min, max, step, minStepsBetweenThumbs);
|
|
4202
|
+
const length = range ? 2 : 1;
|
|
4203
|
+
const initial = scale.validate(defaults ?? (range ? [min, max] : [min]), length);
|
|
4204
|
+
const selected = values === void 0 ? void 0 : scale.validate(values, length);
|
|
4205
|
+
const hasLabel = isPresent(label);
|
|
4206
|
+
if (!hasLabel && !ariaLabel?.trim()) throw new Error("Slider requires a label or aria-label");
|
|
4207
|
+
if (range && (!thumbLabels || thumbLabels.length !== 2 || thumbLabels.some((text) => !text.trim()))) throw new Error("RangeSlider requires two non-empty thumbLabels");
|
|
4208
|
+
if (names && (names.length !== length || names.some((name) => !name.trim()))) throw new Error("Slider requires one non-empty name per thumb");
|
|
4209
|
+
if (marks) {
|
|
4210
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4211
|
+
for (const mark of marks) {
|
|
4212
|
+
const normalized = scale.validate([mark.value], 1)[0];
|
|
4213
|
+
if (seen.has(normalized)) throw new Error("Slider mark values must be unique");
|
|
4214
|
+
seen.add(normalized);
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
const { inputs, controlRef, railRef, ...slider } = useSlider({
|
|
4218
|
+
scale,
|
|
4219
|
+
value: selected,
|
|
4220
|
+
defaultValue: initial,
|
|
4221
|
+
disabled,
|
|
4222
|
+
form,
|
|
4223
|
+
onValueChange: change,
|
|
4224
|
+
onValueCommit: commit
|
|
4225
|
+
});
|
|
4226
|
+
const mergedRef = useMergedRef(useRef(null), ref);
|
|
4227
|
+
const invalid = Boolean(error) || ariaInvalid === true || ariaInvalid === "true";
|
|
4228
|
+
const message = error && error !== true ? error : helperText;
|
|
4229
|
+
const description = [describedBy, isPresent(message) && `${id}-description`].filter(Boolean).join(" ") || void 0;
|
|
4230
|
+
const percentages = slider.values.map((value) => scale.toTick(value) / scale.count * 100);
|
|
4231
|
+
const start = range ? percentages[0] : 0;
|
|
4232
|
+
const end = percentages.at(-1);
|
|
4233
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
4234
|
+
...props,
|
|
4235
|
+
ref: mergedRef,
|
|
4236
|
+
role: "group",
|
|
4237
|
+
"aria-label": hasLabel ? void 0 : ariaLabel,
|
|
4238
|
+
"aria-labelledby": hasLabel ? `${id}-label` : void 0,
|
|
4239
|
+
"data-slot": "slider",
|
|
4240
|
+
"data-range": range || void 0,
|
|
4241
|
+
"data-disabled": disabled || void 0,
|
|
4242
|
+
"data-invalid": invalid || void 0,
|
|
4243
|
+
"data-dragging": slider.dragging || void 0,
|
|
4244
|
+
className: cn("min-w-0 has-[input:disabled]:opacity-50 forced-colors:has-[input:disabled]:opacity-100", className),
|
|
4245
|
+
onPointerDown: (event) => {
|
|
4246
|
+
onPointerDown?.(event);
|
|
4247
|
+
if (!event.defaultPrevented && controlRef.current?.contains(event.target)) slider.onPointerDown(event);
|
|
4248
|
+
},
|
|
4249
|
+
onKeyDown: (event) => {
|
|
4250
|
+
onKeyDown?.(event);
|
|
4251
|
+
const index = inputs.current.indexOf(event.target);
|
|
4252
|
+
if (!event.defaultPrevented && index !== -1) slider.onKeyDown(event, index);
|
|
4253
|
+
},
|
|
4254
|
+
onKeyUp: (event) => {
|
|
4255
|
+
onKeyUp?.(event);
|
|
4256
|
+
if (inputs.current.includes(event.target)) slider.onKeyUp(event);
|
|
4257
|
+
},
|
|
4258
|
+
onBlur: (event) => {
|
|
4259
|
+
onBlur?.(event);
|
|
4260
|
+
if (inputs.current.includes(event.target)) slider.onBlur();
|
|
4261
|
+
},
|
|
4262
|
+
onFocus: (event) => {
|
|
4263
|
+
onFocus?.(event);
|
|
4264
|
+
const index = inputs.current.indexOf(event.target);
|
|
4265
|
+
if (index !== -1) slider.onFocus(index);
|
|
4266
|
+
},
|
|
4267
|
+
children: [
|
|
4268
|
+
(hasLabel || showValue) && /* @__PURE__ */ jsxs("div", {
|
|
4269
|
+
className: "mb-0.5 flex flex-wrap items-baseline justify-between gap-x-4 gap-y-1",
|
|
4270
|
+
children: [hasLabel && /* @__PURE__ */ jsx("span", {
|
|
4271
|
+
id: `${id}-label`,
|
|
4272
|
+
className: "min-w-0 text-sm font-medium wrap-anywhere text-fg",
|
|
4273
|
+
children: label
|
|
4274
|
+
}), showValue && /* @__PURE__ */ jsx("span", {
|
|
4275
|
+
"data-slot": "slider-value",
|
|
4276
|
+
"aria-hidden": true,
|
|
4277
|
+
className: "ms-auto text-sm font-medium wrap-anywhere text-fg tabular-nums",
|
|
4278
|
+
children: /* @__PURE__ */ jsx("bdi", {
|
|
4279
|
+
dir: "ltr",
|
|
4280
|
+
children: slider.values.map(formatValue).join(" – ")
|
|
4281
|
+
})
|
|
4282
|
+
})]
|
|
4283
|
+
}),
|
|
4284
|
+
/* @__PURE__ */ jsx("div", {
|
|
4285
|
+
ref: controlRef,
|
|
4286
|
+
"data-slot": "slider-control",
|
|
4287
|
+
className: cn("group/slider-control relative h-11 touch-pan-y touch-pinch-zoom select-none", disabled ? "cursor-not-allowed" : "cursor-pointer"),
|
|
4288
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
4289
|
+
ref: railRef,
|
|
4290
|
+
className: "pointer-events-none absolute inset-x-5.5 top-0 h-full",
|
|
4291
|
+
children: [
|
|
4292
|
+
/* @__PURE__ */ jsx("div", {
|
|
4293
|
+
"aria-hidden": true,
|
|
4294
|
+
"data-slot": "slider-track",
|
|
4295
|
+
className: "absolute inset-x-0 top-5 h-1 rounded-full border border-input-border bg-surface-muted forced-colors:border-[CanvasText] forced-colors:bg-[Canvas]"
|
|
4296
|
+
}),
|
|
4297
|
+
/* @__PURE__ */ jsx("div", {
|
|
4298
|
+
"aria-hidden": true,
|
|
4299
|
+
"data-slot": "slider-range",
|
|
4300
|
+
className: cn("absolute top-5 h-1 rounded-full forced-colors:forced-color-adjust-none forced-colors:bg-[Highlight]", invalid ? "bg-danger" : "bg-control-checked", disabled && "forced-colors:bg-[GrayText]"),
|
|
4301
|
+
style: {
|
|
4302
|
+
insetInlineStart: `${start}%`,
|
|
4303
|
+
width: `${end - start}%`
|
|
4304
|
+
}
|
|
4305
|
+
}),
|
|
4306
|
+
slider.values.map((value, i) => {
|
|
4307
|
+
const index = i;
|
|
4308
|
+
const [low, high] = scale.limits(slider.values, index);
|
|
4309
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [range && /* @__PURE__ */ jsx("span", {
|
|
4310
|
+
id: `${id}-thumb-label-${index}`,
|
|
4311
|
+
className: "sr-only",
|
|
4312
|
+
children: thumbLabels[index]
|
|
4313
|
+
}), /* @__PURE__ */ jsxs("span", {
|
|
4314
|
+
className: "absolute top-0 size-11 -translate-x-1/2 rtl:translate-x-1/2",
|
|
4315
|
+
style: {
|
|
4316
|
+
insetInlineStart: `${percentages[index]}%`,
|
|
4317
|
+
zIndex: slider.activeIndex === index ? 2 : 1
|
|
4318
|
+
},
|
|
4319
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
4320
|
+
ref: (node) => {
|
|
4321
|
+
inputs.current[index] = node;
|
|
4322
|
+
},
|
|
4323
|
+
type: "range",
|
|
4324
|
+
"data-slot": "slider-input",
|
|
4325
|
+
name: names?.[index],
|
|
4326
|
+
form,
|
|
4327
|
+
min: scale.fromTick(low),
|
|
4328
|
+
max: scale.fromTick(high),
|
|
4329
|
+
step,
|
|
4330
|
+
value,
|
|
4331
|
+
disabled,
|
|
4332
|
+
"aria-label": hasLabel ? void 0 : [ariaLabel, thumbLabels?.[index]].filter(Boolean).join(" "),
|
|
4333
|
+
"aria-labelledby": hasLabel ? [`${id}-label`, range && `${id}-thumb-label-${index}`].filter(Boolean).join(" ") : void 0,
|
|
4334
|
+
"aria-describedby": description,
|
|
4335
|
+
"aria-invalid": invalid ? true : ariaInvalid,
|
|
4336
|
+
"aria-valuetext": getValueText?.(value, index) ?? formatValue(value),
|
|
4337
|
+
className: "peer pointer-events-none absolute inset-0 m-0 size-full opacity-0",
|
|
4338
|
+
onChange: (event) => slider.onInput(event.currentTarget.valueAsNumber, index)
|
|
4339
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
4340
|
+
"aria-hidden": true,
|
|
4341
|
+
"data-slot": "slider-thumb",
|
|
4342
|
+
"data-active": slider.dragging && slider.activeIndex === index || void 0,
|
|
4343
|
+
className: cn("pointer-events-none absolute inset-s-3.25 top-3.25 size-4.5 rounded-full border bg-surface-raised", "peer-focus-visible:outline-solid peer-focus-visible:outline-2 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-focus", "transition-[border-color,box-shadow] duration-100 ease-out motion-reduce:transition-none", "data-active:[box-shadow:0_0_0_4px_var(--color-input-halo)]", invalid ? "border-danger" : "border-control-checked peer-enabled:group-hover/slider-control:border-input-border-hover", "forced-colors:forced-color-adjust-none forced-colors:border-[ButtonText] forced-colors:bg-[ButtonFace] forced-colors:shadow-none", "forced-colors:peer-focus-visible:outline-[Highlight] forced-colors:peer-disabled:border-[GrayText]")
|
|
4344
|
+
})]
|
|
4345
|
+
})] }, index);
|
|
4346
|
+
})
|
|
4347
|
+
]
|
|
4348
|
+
})
|
|
4349
|
+
}),
|
|
4350
|
+
marks?.length ? /* @__PURE__ */ jsx("div", {
|
|
4351
|
+
"aria-hidden": true,
|
|
4352
|
+
"data-slot": "slider-marks",
|
|
4353
|
+
className: "relative mx-5.5 min-h-6 text-xs text-fg-muted tabular-nums",
|
|
4354
|
+
children: marks.map((mark) => {
|
|
4355
|
+
const fraction = scale.toTick(mark.value) / scale.count;
|
|
4356
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4357
|
+
className: "absolute top-0 w-0",
|
|
4358
|
+
style: { insetInlineStart: `${fraction * 100}%` },
|
|
4359
|
+
children: [/* @__PURE__ */ jsx("span", { className: "block h-1 w-px bg-input-border forced-colors:bg-[CanvasText]" }), /* @__PURE__ */ jsx("span", {
|
|
4360
|
+
className: cn("mt-0.5 inline-block whitespace-nowrap", fraction === 1 ? "-translate-x-full rtl:translate-x-full" : fraction !== 0 && "-translate-x-1/2 rtl:translate-x-1/2"),
|
|
4361
|
+
children: mark.label
|
|
4362
|
+
})]
|
|
4363
|
+
}, mark.value);
|
|
4364
|
+
})
|
|
4365
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
4366
|
+
"aria-hidden": true,
|
|
4367
|
+
className: "mx-5.5 flex justify-between gap-2 text-xs text-fg-muted tabular-nums",
|
|
4368
|
+
children: [/* @__PURE__ */ jsx("span", { children: formatValue(min) }), /* @__PURE__ */ jsx("span", { children: formatValue(max) })]
|
|
4369
|
+
}),
|
|
4370
|
+
isPresent(message) && /* @__PURE__ */ jsxs("p", {
|
|
4371
|
+
id: `${id}-description`,
|
|
4372
|
+
className: cn("mt-2 mb-0 flex items-start gap-1 text-xs/relaxed", invalid ? "text-danger" : "text-fg-muted"),
|
|
4373
|
+
children: [invalid && /* @__PURE__ */ jsx(CircleAlert, {
|
|
4374
|
+
"aria-hidden": true,
|
|
4375
|
+
className: "mt-0.5 size-3.5 shrink-0"
|
|
4376
|
+
}), /* @__PURE__ */ jsx("span", { children: message })]
|
|
4377
|
+
})
|
|
4378
|
+
]
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
function Slider({ value, defaultValue, name, onValueChange, onValueCommit, ...props }) {
|
|
4382
|
+
return /* @__PURE__ */ jsx(SliderControl, {
|
|
4383
|
+
...props,
|
|
4384
|
+
values: value === void 0 ? void 0 : [value],
|
|
4385
|
+
defaults: defaultValue === void 0 ? void 0 : [defaultValue],
|
|
4386
|
+
names: name === void 0 ? void 0 : [name],
|
|
4387
|
+
change: (next, details) => onValueChange?.(next[0], details),
|
|
4388
|
+
commit: (next, details) => onValueCommit?.(next[0], details)
|
|
4389
|
+
});
|
|
4390
|
+
}
|
|
4391
|
+
function RangeSlider({ value, defaultValue, name, onValueChange, onValueCommit, ...props }) {
|
|
4392
|
+
return /* @__PURE__ */ jsx(SliderControl, {
|
|
4393
|
+
...props,
|
|
4394
|
+
range: true,
|
|
4395
|
+
values: value,
|
|
4396
|
+
defaults: defaultValue,
|
|
4397
|
+
names: name,
|
|
4398
|
+
change: (next, details) => onValueChange?.([next[0], next[1]], details),
|
|
4399
|
+
commit: (next, details) => onValueCommit?.([next[0], next[1]], details)
|
|
4400
|
+
});
|
|
4401
|
+
}
|
|
4402
|
+
//#endregion
|
|
2481
4403
|
//#region src/components/switch/switch.tsx
|
|
2482
4404
|
const sizeClasses = {
|
|
2483
4405
|
sm: {
|
|
@@ -2531,7 +4453,7 @@ function Switch({ label, description, error, size = "md", loading = false, label
|
|
|
2531
4453
|
}), /* @__PURE__ */ jsx("span", {
|
|
2532
4454
|
"aria-hidden": true,
|
|
2533
4455
|
className: cn("pointer-events-none absolute inset-s-0.5 top-0.5 grid place-items-center rounded-full", "h-(--switch-thumb) w-(--switch-thumb-w) bg-switch-handle peer-checked:bg-switch-handle-checked text-control-checked shadow-switch-thumb", "[--switch-dir:1] rtl:[--switch-dir:-1] [translate:calc(var(--switch-x)*var(--switch-dir))_0]", "[--switch-x:0px] [--switch-thumb-w:var(--switch-thumb)] peer-checked:[--switch-x:var(--switch-travel)]", "transition-[translate,width] duration-150 ease-in-out", pressable && ["peer-active:[--switch-thumb-w:calc(var(--switch-thumb)+var(--switch-stretch))]", "peer-checked:peer-active:[--switch-x:calc(var(--switch-travel)-var(--switch-stretch))]"], "peer-disabled:shadow-none", "forced-colors:forced-color-adjust-none forced-colors:shadow-none", "forced-colors:bg-[CanvasText] forced-colors:text-[Canvas]", "forced-colors:peer-checked:bg-[HighlightText] forced-colors:peer-checked:text-[Highlight]", "forced-colors:peer-disabled:bg-[GrayText] forced-colors:peer-disabled:peer-checked:bg-[Canvas]", s.icon),
|
|
2534
|
-
children: loading && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] })
|
|
4456
|
+
children: loading && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(LoaderCircle, { className: "animate-spin motion-reduce:hidden" }), /* @__PURE__ */ jsx(CircleDashed, { className: "hidden motion-reduce:block" })] })
|
|
2535
4457
|
})]
|
|
2536
4458
|
})
|
|
2537
4459
|
});
|
|
@@ -2635,7 +4557,7 @@ function Table({ data, columns, getRowId, title, emptyState, noResults, filtered
|
|
|
2635
4557
|
tabIndex: -1,
|
|
2636
4558
|
className: "grid min-h-48 place-items-center px-4 py-8 text-center text-sm text-fg-muted outline-hidden focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-solid focus-visible:outline-focus",
|
|
2637
4559
|
children: emptyState === void 0 ? text.emptyState : emptyState
|
|
2638
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("section", {
|
|
4560
|
+
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("section", {
|
|
2639
4561
|
ref: scrollRef,
|
|
2640
4562
|
"data-slot": "table-scroll",
|
|
2641
4563
|
"aria-labelledby": tableName,
|
|
@@ -2970,6 +4892,155 @@ function TabPanel({ value, keepMounted = false, className, ...props }) {
|
|
|
2970
4892
|
});
|
|
2971
4893
|
}
|
|
2972
4894
|
//#endregion
|
|
4895
|
+
//#region src/components/textarea/use-auto-resize.ts
|
|
4896
|
+
/** Measure the native control so wrapping, fonts and IME text keep their browser behavior. */
|
|
4897
|
+
function useAutoResize(ref, enabled, minRows, maxRows) {
|
|
4898
|
+
const resize = useCallback(() => {
|
|
4899
|
+
const node = ref.current;
|
|
4900
|
+
if (!enabled || !node || node.getClientRects().length === 0) return;
|
|
4901
|
+
const style = getComputedStyle(node);
|
|
4902
|
+
const lineHeight = Number.parseFloat(style.lineHeight);
|
|
4903
|
+
if (!Number.isFinite(lineHeight)) return;
|
|
4904
|
+
const padding = Number.parseFloat(style.paddingTop) + Number.parseFloat(style.paddingBottom);
|
|
4905
|
+
const border = Number.parseFloat(style.borderTopWidth) + Number.parseFloat(style.borderBottomWidth);
|
|
4906
|
+
const min = minRows * lineHeight + padding;
|
|
4907
|
+
const max = maxRows == null ? Infinity : maxRows * lineHeight + padding;
|
|
4908
|
+
const scrollTop = node.scrollTop;
|
|
4909
|
+
node.style.overflowY = "hidden";
|
|
4910
|
+
node.style.height = "0px";
|
|
4911
|
+
const contentHeight = node.scrollHeight;
|
|
4912
|
+
const height = Math.max(min, Math.min(max, contentHeight));
|
|
4913
|
+
node.style.height = `${height + (style.boxSizing === "border-box" ? border : -padding)}px`;
|
|
4914
|
+
node.style.overflowY = contentHeight > max ? "auto" : "hidden";
|
|
4915
|
+
node.scrollTop = scrollTop;
|
|
4916
|
+
}, [
|
|
4917
|
+
enabled,
|
|
4918
|
+
ref,
|
|
4919
|
+
minRows,
|
|
4920
|
+
maxRows
|
|
4921
|
+
]);
|
|
4922
|
+
useLayoutEffect(resize);
|
|
4923
|
+
useLayoutEffect(() => {
|
|
4924
|
+
if (!enabled) return;
|
|
4925
|
+
const node = ref.current;
|
|
4926
|
+
if (!node) return;
|
|
4927
|
+
let width = node.getBoundingClientRect().width;
|
|
4928
|
+
const observer = typeof ResizeObserver === "undefined" ? void 0 : new ResizeObserver(() => {
|
|
4929
|
+
const nextWidth = node.getBoundingClientRect().width;
|
|
4930
|
+
if (nextWidth !== width) {
|
|
4931
|
+
width = nextWidth;
|
|
4932
|
+
resize();
|
|
4933
|
+
}
|
|
4934
|
+
});
|
|
4935
|
+
observer?.observe(node);
|
|
4936
|
+
window.addEventListener("resize", resize);
|
|
4937
|
+
document.fonts?.addEventListener("loadingdone", resize);
|
|
4938
|
+
let active = true;
|
|
4939
|
+
document.fonts?.ready.then(() => {
|
|
4940
|
+
if (active) resize();
|
|
4941
|
+
});
|
|
4942
|
+
return () => {
|
|
4943
|
+
active = false;
|
|
4944
|
+
observer?.disconnect();
|
|
4945
|
+
window.removeEventListener("resize", resize);
|
|
4946
|
+
document.fonts?.removeEventListener("loadingdone", resize);
|
|
4947
|
+
node.style.removeProperty("height");
|
|
4948
|
+
node.style.removeProperty("overflow-y");
|
|
4949
|
+
};
|
|
4950
|
+
}, [
|
|
4951
|
+
enabled,
|
|
4952
|
+
ref,
|
|
4953
|
+
resize
|
|
4954
|
+
]);
|
|
4955
|
+
return resize;
|
|
4956
|
+
}
|
|
4957
|
+
//#endregion
|
|
4958
|
+
//#region src/components/textarea/textarea.tsx
|
|
4959
|
+
const sizes = {
|
|
4960
|
+
sm: "px-3 py-2 text-sm/5",
|
|
4961
|
+
md: "px-3 py-2.5 text-sm/5",
|
|
4962
|
+
lg: "px-4 py-3 text-base/6"
|
|
4963
|
+
};
|
|
4964
|
+
function positiveRows(value, name) {
|
|
4965
|
+
if (!Number.isInteger(value) || value < 1) throw new RangeError(`Textarea ${name} must be a positive integer.`);
|
|
4966
|
+
return value;
|
|
4967
|
+
}
|
|
4968
|
+
const textLength = (value) => String(value ?? "").replace(/\r\n?/g, "\n").length;
|
|
4969
|
+
function Textarea({ label, floating = false, helperText, error, size = "md", autoResize = false, rows = 3, minRows = 3, maxRows, resize = "vertical", showCount = false, mono = false, className, textareaClassName, ref, id, required, disabled, readOnly, placeholder, value, defaultValue, maxLength, onChange, "aria-describedby": describedBy, "aria-invalid": ariaInvalid, ...props }) {
|
|
4970
|
+
const nativeRows = positiveRows(autoResize ? minRows : rows, autoResize ? "minRows" : "rows");
|
|
4971
|
+
if (autoResize && maxRows != null && positiveRows(maxRows, "maxRows") < nativeRows) throw new RangeError("Textarea maxRows must be greater than or equal to minRows.");
|
|
4972
|
+
const controlRef = useRef(null);
|
|
4973
|
+
const mergedRef = useMergedRef(controlRef, ref);
|
|
4974
|
+
const [uncontrolledLength, setUncontrolledLength] = useState(() => textLength(defaultValue));
|
|
4975
|
+
const length = value == null ? uncontrolledLength : textLength(value);
|
|
4976
|
+
const overLimit = maxLength != null && maxLength >= 0 && length > maxLength;
|
|
4977
|
+
const countId = `${useId()}-count`;
|
|
4978
|
+
const resizeToContent = useAutoResize(controlRef, autoResize, nativeRows, maxRows);
|
|
4979
|
+
useLayoutEffect(() => {
|
|
4980
|
+
const node = controlRef.current;
|
|
4981
|
+
const form = node?.form;
|
|
4982
|
+
if (!form || !node) return;
|
|
4983
|
+
let resetTimer;
|
|
4984
|
+
const reset = (event) => {
|
|
4985
|
+
clearTimeout(resetTimer);
|
|
4986
|
+
resetTimer = setTimeout(() => {
|
|
4987
|
+
if (event.defaultPrevented) return;
|
|
4988
|
+
setUncontrolledLength(node.value.length);
|
|
4989
|
+
resizeToContent();
|
|
4990
|
+
}, 0);
|
|
4991
|
+
};
|
|
4992
|
+
form.addEventListener("reset", reset);
|
|
4993
|
+
return () => {
|
|
4994
|
+
clearTimeout(resetTimer);
|
|
4995
|
+
form.removeEventListener("reset", reset);
|
|
4996
|
+
};
|
|
4997
|
+
}, [props.form, resizeToContent]);
|
|
4998
|
+
return /* @__PURE__ */ jsx(FieldShell, {
|
|
4999
|
+
multiline: true,
|
|
5000
|
+
label,
|
|
5001
|
+
floating,
|
|
5002
|
+
helperText,
|
|
5003
|
+
error: error || overLimit,
|
|
5004
|
+
size,
|
|
5005
|
+
id,
|
|
5006
|
+
required,
|
|
5007
|
+
disabled,
|
|
5008
|
+
readOnly,
|
|
5009
|
+
"aria-describedby": [describedBy, showCount ? countId : null].filter(Boolean).join(" ") || void 0,
|
|
5010
|
+
className,
|
|
5011
|
+
controlRef,
|
|
5012
|
+
footer: showCount ? /* @__PURE__ */ jsxs("span", {
|
|
5013
|
+
id: countId,
|
|
5014
|
+
className: cn("ms-auto shrink-0 text-xs/snug tabular-nums", overLimit ? "text-danger" : "text-fg-muted"),
|
|
5015
|
+
children: [length, maxLength != null && maxLength >= 0 ? ` / ${maxLength}` : ""]
|
|
5016
|
+
}) : void 0,
|
|
5017
|
+
children: (field) => /* @__PURE__ */ jsxs(Fragment$1, { children: [field.floatingLabel("max-w-[calc(100%-1rem)]"), /* @__PURE__ */ jsx("textarea", {
|
|
5018
|
+
...props,
|
|
5019
|
+
ref: mergedRef,
|
|
5020
|
+
id: field.id,
|
|
5021
|
+
rows: nativeRows,
|
|
5022
|
+
value,
|
|
5023
|
+
defaultValue,
|
|
5024
|
+
maxLength,
|
|
5025
|
+
required,
|
|
5026
|
+
disabled,
|
|
5027
|
+
readOnly,
|
|
5028
|
+
placeholder: field.floating ? placeholder || " " : placeholder,
|
|
5029
|
+
"aria-invalid": field.invalid ? true : ariaInvalid,
|
|
5030
|
+
"aria-describedby": field.describedBy,
|
|
5031
|
+
spellCheck: mono ? false : props.spellCheck,
|
|
5032
|
+
autoCapitalize: mono ? "off" : props.autoCapitalize,
|
|
5033
|
+
autoCorrect: mono ? "off" : props.autoCorrect,
|
|
5034
|
+
onChange: (event) => {
|
|
5035
|
+
setUncontrolledLength(event.currentTarget.value.length);
|
|
5036
|
+
resizeToContent();
|
|
5037
|
+
onChange?.(event);
|
|
5038
|
+
},
|
|
5039
|
+
className: cn("block w-full min-w-0 flex-1 rounded-[inherit] border-0 bg-transparent text-fg outline-none", "placeholder:text-input-placeholder disabled:cursor-not-allowed pointer-coarse:text-base", sizes[size], autoResize || resize === "none" ? "resize-none" : "resize-y", mono && "font-mono", field.floating && "placeholder:opacity-0 focus:placeholder:opacity-100", textareaClassName)
|
|
5040
|
+
})] })
|
|
5041
|
+
});
|
|
5042
|
+
}
|
|
5043
|
+
//#endregion
|
|
2973
5044
|
//#region src/components/theme-switcher/theme-switcher.tsx
|
|
2974
5045
|
const systemThemeQuery = "(prefers-color-scheme: dark)";
|
|
2975
5046
|
function subscribeSystemTheme(onChange) {
|
|
@@ -3438,7 +5509,7 @@ function Viewport({ id, position = "bottom-right", visibleToasts = 3, host }) {
|
|
|
3438
5509
|
};
|
|
3439
5510
|
const [y, x] = position.split("-");
|
|
3440
5511
|
const directions = x === "center" ? [y] : [y, x];
|
|
3441
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
5512
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
3442
5513
|
/* @__PURE__ */ jsx("section", {
|
|
3443
5514
|
ref: viewportRef,
|
|
3444
5515
|
popover: "manual",
|
|
@@ -3775,4 +5846,4 @@ function ToastCard({ record, layout, total, expanded, paused, frontHeight, ownHe
|
|
|
3775
5846
|
});
|
|
3776
5847
|
}
|
|
3777
5848
|
//#endregion
|
|
3778
|
-
export { Alert, Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, Input, Meter, PasswordInput, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Table, Tabs, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };
|
|
5849
|
+
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Alert, Badge, Button, Card, CardContent, CardHeader, Checkbox, CheckboxGroup, Combobox, CopyInput, Dialog, DialogBody, DialogFooter, DialogHeader, Drawer, DrawerBody, DrawerClose, DrawerFooter, DrawerHeader, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger, Input, Meter, PasswordInput, Radio, RadioGroup, RangeSlider, SearchInput, Select, Sidebar, SidebarBadge, SidebarBody, SidebarFooter, SidebarGroup, SidebarHeader, SidebarIcon, SidebarItem, SidebarProvider, SidebarTrigger, Slider, Switch, Tab, TabList, TabNav, TabNavLink, TabPanel, Table, Tabs, Textarea, ThemeSwitcher, Toaster, Tooltip, cn, toast, useTheme };
|