@seekalfred/primitives 0.7.1 → 0.7.4
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 +255 -5
- package/dist/alert/alert.d.ts +49 -4
- package/dist/alert/alert.js +66 -38
- package/dist/alert/alert.js.map +1 -1
- package/dist/alert/index.d.ts +2 -2
- package/dist/alert/index.js +2 -2
- package/dist/alert-dialog/alert-dialog.d.ts +31 -4
- package/dist/alert-dialog/alert-dialog.js +75 -26
- package/dist/alert-dialog/alert-dialog.js.map +1 -1
- package/dist/announcement-bar/announcement-bar.d.ts +15 -4
- package/dist/announcement-bar/announcement-bar.js +31 -6
- package/dist/announcement-bar/announcement-bar.js.map +1 -1
- package/dist/avatar/avatar.d.ts +7 -0
- package/dist/avatar/avatar.js +33 -12
- package/dist/avatar/avatar.js.map +1 -1
- package/dist/badge/badge.d.ts +1 -1
- package/dist/bar-chart/bar-chart-series.d.ts +75 -0
- package/dist/bar-chart/bar-chart-series.js +401 -0
- package/dist/bar-chart/bar-chart-series.js.map +1 -0
- package/dist/bar-chart/bar-chart.d.ts +25 -1
- package/dist/bar-chart/bar-chart.js +26 -1
- package/dist/bar-chart/bar-chart.js.map +1 -1
- package/dist/bar-chart/index.d.ts +2 -1
- package/dist/button/button.d.ts +1 -1
- package/dist/calendar/calendar.d.ts +1 -1
- package/dist/card/card.d.ts +22 -1
- package/dist/card/card.js +60 -7
- package/dist/card/card.js.map +1 -1
- package/dist/carousel/carousel.d.ts +21 -0
- package/dist/carousel/carousel.js +34 -8
- package/dist/carousel/carousel.js.map +1 -1
- package/dist/chip-group/chip-group.d.ts +11 -0
- package/dist/chip-group/chip-group.js +50 -34
- package/dist/chip-group/chip-group.js.map +1 -1
- package/dist/chip-group/index.d.ts +2 -2
- package/dist/copy-button/copy-button.d.ts +37 -5
- package/dist/copy-button/copy-button.js +35 -11
- package/dist/copy-button/copy-button.js.map +1 -1
- package/dist/donut-chart/donut-chart.d.ts +22 -0
- package/dist/donut-chart/donut-chart.js +35 -18
- package/dist/donut-chart/donut-chart.js.map +1 -1
- package/dist/donut-chart/index.d.ts +2 -2
- package/dist/empty-state/empty-state.d.ts +21 -0
- package/dist/empty-state/empty-state.js +16 -4
- package/dist/empty-state/empty-state.js.map +1 -1
- package/dist/empty-state/index.d.ts +2 -2
- package/dist/empty-state/index.js +2 -2
- package/dist/file-drop-zone/file-drop-zone.d.ts +29 -3
- package/dist/file-drop-zone/file-drop-zone.js +26 -8
- package/dist/file-drop-zone/file-drop-zone.js.map +1 -1
- package/dist/filter-bar/filter-bar.d.ts +4 -1
- package/dist/filter-bar/filter-bar.js +53 -9
- package/dist/filter-bar/filter-bar.js.map +1 -1
- package/dist/gauge/gauge.d.ts +18 -1
- package/dist/gauge/gauge.js +35 -21
- package/dist/gauge/gauge.js.map +1 -1
- package/dist/gauge/index.d.ts +2 -2
- package/dist/index.d.ts +15 -14
- package/dist/index.js +9 -9
- package/dist/input/input.d.ts +7 -0
- package/dist/input/input.js +7 -5
- package/dist/input/input.js.map +1 -1
- package/dist/lib/field-shell.js +6 -3
- package/dist/lib/field-shell.js.map +1 -1
- package/dist/lib/field-surface.js +22 -1
- package/dist/lib/field-surface.js.map +1 -1
- package/dist/lib/required-marker.js +20 -0
- package/dist/lib/required-marker.js.map +1 -0
- package/dist/lib/use-copy-feedback.js +17 -3
- package/dist/lib/use-copy-feedback.js.map +1 -1
- package/dist/line-chart/index.d.ts +2 -2
- package/dist/line-chart/line-chart.d.ts +144 -2
- package/dist/line-chart/line-chart.js +314 -46
- package/dist/line-chart/line-chart.js.map +1 -1
- package/dist/money-input/index.d.ts +2 -2
- package/dist/money-input/money-input.d.ts +36 -1
- package/dist/money-input/money-input.js +30 -14
- package/dist/money-input/money-input.js.map +1 -1
- package/dist/morph-select/morph-select.d.ts +26 -1
- package/dist/morph-select/morph-select.js +28 -6
- package/dist/morph-select/morph-select.js.map +1 -1
- package/dist/multi-select/index.d.ts +2 -2
- package/dist/multi-select/index.js +2 -2
- package/dist/multi-select/multi-select.d.ts +74 -2
- package/dist/multi-select/multi-select.js +213 -39
- package/dist/multi-select/multi-select.js.map +1 -1
- package/dist/password-field/password-field.d.ts +16 -0
- package/dist/password-field/password-field.js +37 -8
- package/dist/password-field/password-field.js.map +1 -1
- package/dist/popover/index.d.ts +2 -2
- package/dist/popover/index.js +2 -2
- package/dist/popover/popover.d.ts +39 -2
- package/dist/popover/popover.js +29 -2
- package/dist/popover/popover.js.map +1 -1
- package/dist/progress/index.d.ts +2 -2
- package/dist/progress/index.js +2 -2
- package/dist/progress/progress.d.ts +68 -3
- package/dist/progress/progress.js +156 -39
- package/dist/progress/progress.js.map +1 -1
- package/dist/radio-group/index.d.ts +2 -2
- package/dist/radio-group/index.js +2 -2
- package/dist/radio-group/radio-group.d.ts +33 -3
- package/dist/radio-group/radio-group.js +109 -9
- package/dist/radio-group/radio-group.js.map +1 -1
- package/dist/scroll-area/scroll-area.js +4 -3
- package/dist/scroll-area/scroll-area.js.map +1 -1
- package/dist/scrub-input/scrub-input.d.ts +11 -1
- package/dist/scrub-input/scrub-input.js +22 -10
- package/dist/scrub-input/scrub-input.js.map +1 -1
- package/dist/search-field/search-field.d.ts +22 -1
- package/dist/search-field/search-field.js +22 -7
- package/dist/search-field/search-field.js.map +1 -1
- package/dist/select/select.d.ts +10 -1
- package/dist/select/select.js +11 -8
- package/dist/select/select.js.map +1 -1
- package/dist/separator/separator.d.ts +21 -9
- package/dist/separator/separator.js +33 -11
- package/dist/separator/separator.js.map +1 -1
- package/dist/sparkline/sparkline.d.ts +27 -2
- package/dist/sparkline/sparkline.js +115 -18
- package/dist/sparkline/sparkline.js.map +1 -1
- package/dist/stat-card/index.d.ts +2 -2
- package/dist/stat-card/index.js +2 -2
- package/dist/stat-card/stat-card.d.ts +65 -1
- package/dist/stat-card/stat-card.js +98 -25
- package/dist/stat-card/stat-card.js.map +1 -1
- package/dist/styles.css +1 -1
- package/dist/table/index.d.ts +2 -2
- package/dist/table/index.js +2 -2
- package/dist/table/table.d.ts +115 -5
- package/dist/table/table.js +374 -94
- package/dist/table/table.js.map +1 -1
- package/dist/text-shimmer/text-shimmer.d.ts +18 -2
- package/dist/text-shimmer/text-shimmer.js +5 -2
- package/dist/text-shimmer/text-shimmer.js.map +1 -1
- package/dist/textarea/textarea.d.ts +7 -0
- package/dist/textarea/textarea.js +3 -1
- package/dist/textarea/textarea.js.map +1 -1
- package/dist/time-picker/time-picker.d.ts +36 -1
- package/dist/time-picker/time-picker.js +33 -5
- package/dist/time-picker/time-picker.js.map +1 -1
- package/dist/toast/toast.d.ts +2 -2
- package/dist/usage-meter/index.d.ts +2 -2
- package/dist/usage-meter/usage-meter.d.ts +30 -0
- package/dist/usage-meter/usage-meter.js +56 -38
- package/dist/usage-meter/usage-meter.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scrub-input.js","names":["AriaButton"],"sources":["../../src/scrub-input/scrub-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n type FocusEvent,\n type InputHTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { prefersReducedMotion } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { evaluateScrubExpression } from \"./evaluate.js\";\n\nexport type ScrubInputSource = \"scrub\" | \"type\" | \"key\" | \"reset\";\n\nexport interface ScrubInputChangeDetails {\n /** What produced the value: a label drag, a typed entry, an arrow key, or a double click reset. */\n source: ScrubInputSource;\n /** The unit in effect after the change. */\n unit: string;\n}\n\nexport interface ScrubInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n | \"value\"\n | \"defaultValue\"\n | \"onChange\"\n | \"type\"\n | \"size\"\n | \"min\"\n | \"max\"\n | \"step\"\n | \"prefix\"\n | \"className\"\n> {\n /** Text or a 16px icon. It is the scrub handle. An icon label needs `aria-label` so the field keeps an accessible name. */\n label: ReactNode;\n /** The value. Controlled when given. */\n value?: number;\n /** @default 0 */\n defaultValue?: number;\n /** Fires for every change, including each step of a scrub. */\n onValueChange?: (value: number, details: ScrubInputChangeDetails) => void;\n /** Fires once when a change settles: a scrub ends, Enter or blur commits typing, or a key step lands. Save on this. */\n onValueCommit?: (value: number, details: ScrubInputChangeDetails) => void;\n /** @default -Infinity */\n min?: number;\n /** @default Infinity */\n max?: number;\n /** One step for the arrow keys and for scrubbing. @default 1 */\n step?: number;\n /** The step with Alt held. @default step / 10 */\n fineStep?: number;\n /** The step with Shift held; coarse steps land on the coarse grid. @default step * 10 */\n coarseStep?: number;\n /** Decimal places kept. Defaults to the precision of `step` or `fineStep`. */\n precision?: number;\n /** Pointer travel in px for one step while scrubbing. @default 2 */\n pixelsPerStep?: number;\n /** Wraps from `max` back to `min`, as a rotation does. Needs both limits. @default false */\n wrap?: boolean;\n /** The unit shown after the number. Controlled when given. With more than one entry in `units` it is a button that cycles them. */\n unit?: string;\n defaultUnit?: string;\n /** The units a typed value may carry, such as `[\"px\", \"%\"]`. Defaults to the current unit. */\n units?: readonly string[];\n onUnitChange?: (unit: string) => void;\n /** Double click the label to return here. */\n resetValue?: number;\n /** Shows where the value sits between `min` and `max` as a quiet fill. On by default when both limits are set. */\n track?: boolean;\n /** `md` is the compact control height, `sm` is 30px. @default \"md\" */\n size?: \"sm\" | \"md\";\n /** Locks the mouse pointer while it scrubs, when the browser allows it, so a long drag never runs out of screen. @default true */\n pointerLock?: boolean;\n /** Extra classes for the field. Merged last, so `w-40` sizes it. */\n className?: string;\n}\n\n/** Movement before a press on the label becomes a scrub, and the most pixels the field strains past a limit. */\nconst SLOP = 3;\nconst STRAIN = 12;\n/** The most overshoot kept, so reversing starts moving the value again after a short relax. */\nconst OVERSHOOT_CAP = 90;\n/** How far a refused step or an invalid entry kicks the number (px). */\nconst KICK = 6;\n/** The longest the spring home takes (the smooth spring, 720ms) plus a margin. */\nconst RELEASE_MS = 900;\n/** How long an invalid entry is flagged. */\nconst INVALID_MS = 900;\n\nconst decimalsOf = (value: number) => {\n const text = String(value);\n if (text.includes(\"e-\")) return Number(text.split(\"e-\")[1]);\n return (text.split(\".\")[1] ?? \"\").length;\n};\nconst roundTo = (value: number, places: number) => {\n const factor = 10 ** places;\n return Math.round(value * factor) / factor;\n};\n/** Rubber-band resistance: each extra pixel gives less, and the strain never exceeds `limit`. */\nconst rubber = (distance: number, limit = STRAIN) =>\n Math.sign(distance) * (1 - 1 / ((Math.abs(distance) * 0.55) / limit + 1)) * limit;\nconst format = (value: number, places: number) =>\n String(roundTo(value, places)).replace(/^-0$/, \"0\");\n\ninterface Drag {\n id: number;\n lastX: number;\n travel: number;\n raw: number;\n start: number;\n moved: boolean;\n locked: boolean;\n mouse: boolean;\n}\n\nconst sizeClass = {\n md: \"h-(--density-control-h-sm) rounded-md text-sm\",\n sm: \"h-7.5 rounded-sm text-xs\",\n} as const;\n\n/**\n * A compact number field for inspectors and property panels. Drag the label sideways to\n * scrub the value (the mouse pointer is locked when the browser allows it, so a long drag\n * never runs out of screen), or click in and type an exact value. A typed entry takes\n * arithmetic such as `12*2` or `(100-24)/2`, and a unit from `units` such as `50%`.\n *\n * Keyboard: the arrow keys step (Shift ten times coarser, Alt ten times finer), Enter\n * commits typing, Escape restores the last value (or cancels a scrub back to where it\n * began). A double click on the label restores `resetValue`. The input is a spinbutton, and\n * its value text spells out percent and degrees.\n *\n * At a limit the field strains: dragged past `min` or `max` it stretches against\n * rubber-band resistance and springs back on release, and a refused step or an invalid\n * entry kicks the number. Reduced motion removes the strain, the kick and the fill's glide.\n *\n * @example\n * <ScrubInput label=\"W\" value={width} onValueChange={setWidth} min={0} units={[\"px\", \"%\"]} />\n */\nexport const ScrubInput = forwardRef<HTMLInputElement, ScrubInputProps>(function ScrubInput(\n {\n label,\n value,\n defaultValue = 0,\n onValueChange,\n onValueCommit,\n min = -Infinity,\n max = Infinity,\n step = 1,\n fineStep,\n coarseStep,\n precision,\n pixelsPerStep = 2,\n wrap = false,\n unit: unitProp,\n defaultUnit,\n units,\n onUnitChange,\n resetValue,\n track,\n size = \"md\",\n pointerLock = true,\n className,\n disabled,\n id: idProp,\n onFocus,\n onBlur,\n onKeyDown,\n \"aria-label\": ariaLabel,\n ...rest\n },\n forwarded,\n) {\n const uid = useId();\n const id = idProp ?? `scrub-${uid.replace(/[^a-zA-Z0-9-]/g, \"\")}`;\n const inputRef = useRef<HTMLInputElement>(null);\n useImperativeHandle(forwarded, () => inputRef.current as HTMLInputElement);\n const shellRef = useRef<HTMLDivElement>(null);\n const bodyRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<HTMLLabelElement>(null);\n const cursorRef = useRef<HTMLDivElement>(null);\n\n const fine = fineStep ?? step / 10;\n const coarse = coarseStep ?? step * 10;\n const places = precision ?? Math.max(decimalsOf(step), decimalsOf(fine));\n const canWrap = wrap && Number.isFinite(min) && Number.isFinite(max) && max > min;\n const showTrack = (track ?? true) && Number.isFinite(min) && Number.isFinite(max) && max > min;\n\n const [inner, setInner] = useState(defaultValue);\n const current = value ?? inner;\n const [innerUnit, setInnerUnit] = useState(defaultUnit ?? unitProp ?? units?.[0] ?? \"\");\n const unit = unitProp ?? innerUnit;\n const unitList = units ?? (unit ? [unit] : []);\n const [draft, setDraft] = useState<string | null>(null);\n const [scrubbing, setScrubbing] = useState<null | { locked: boolean }>(null);\n const [modifier, setModifier] = useState<\"fine\" | \"coarse\" | null>(null);\n const [invalid, setInvalid] = useState(false);\n const [releasing, setReleasing] = useState(false);\n\n // What the handlers read, so a scrub in flight never sees a stale render.\n const latest = useRef({ current, unit });\n useEffect(() => {\n latest.current = { current, unit };\n }, [current, unit]);\n const drag = useRef<Drag | null>(null);\n const suppressClick = useRef(false);\n const timers = useRef({ invalid: 0, release: 0 });\n const cursor = useRef({ x: 0, y: 0 });\n useEffect(() => {\n const pending = timers.current;\n return () => {\n window.clearTimeout(pending.invalid);\n window.clearTimeout(pending.release);\n document.documentElement.style.removeProperty(\"cursor\");\n };\n }, []);\n\n const fraction = showTrack ? Math.min(1, Math.max(0, (current - min) / (max - min))) : 0;\n\n /** The strain past a limit in px (positive past max): the shell stretches from its far edge and the number leans with it. */\n function paintStrain(amount: number) {\n const shell = shellRef.current;\n const body = bodyRef.current;\n if (shell) {\n const width = Math.max(1, shell.offsetWidth);\n shell.style.transform = amount === 0 ? \"\" : `scaleX(${1 + Math.abs(amount) / width})`;\n shell.style.transformOrigin = amount >= 0 ? \"left\" : \"right\";\n }\n if (body) body.style.transform = amount === 0 ? \"\" : `translateX(${amount * 0.5}px)`;\n }\n\n /** Lets the shell and the number spring home: a transition switched on for the moment the strain is cleared. */\n function springHome() {\n paintStrain(0);\n if (prefersReducedMotion()) return;\n setReleasing(true);\n window.clearTimeout(timers.current.release);\n timers.current.release = window.setTimeout(() => setReleasing(false), RELEASE_MS);\n }\n\n /** A short kick of the number toward `direction`: a step refused at a limit, or an entry that was not understood. */\n function kick(direction: number) {\n const node = bodyRef.current;\n if (!node || prefersReducedMotion()) return;\n node.style.setProperty(\"--strain-x\", `${direction * KICK}px`);\n // Restart the animation: the attribute has to leave and come back across a style recalculation.\n node.removeAttribute(\"data-strain\");\n void node.offsetWidth;\n node.setAttribute(\"data-strain\", \"\");\n }\n\n const emit = useCallback(\n (next: number, source: ScrubInputSource, commit: boolean, nextUnit = latest.current.unit) => {\n if (next !== latest.current.current) {\n latest.current = { current: next, unit: nextUnit };\n if (value === undefined) setInner(next);\n onValueChange?.(next, { source, unit: nextUnit });\n }\n if (commit) onValueCommit?.(next, { source, unit: nextUnit });\n },\n [onValueChange, onValueCommit, value],\n );\n\n const constrain = (next: number) => {\n const rounded = roundTo(next, places);\n if (canWrap) {\n const span = max - min;\n return roundTo(((((rounded - min) % span) + span) % span) + min, places);\n }\n return Math.min(max, Math.max(min, rounded));\n };\n\n function flagInvalid() {\n setInvalid(true);\n window.clearTimeout(timers.current.invalid);\n timers.current.invalid = window.setTimeout(() => setInvalid(false), INVALID_MS);\n kick(-1);\n }\n\n function commitDraft(text: string | null = draft) {\n setDraft(null);\n if (text === null) return true;\n if (text.trim() === format(current, places)) return true;\n const parsed = evaluateScrubExpression(text, unitList);\n if (!parsed) {\n flagInvalid();\n return false;\n }\n let nextUnit = latest.current.unit;\n if (parsed.unit && parsed.unit !== nextUnit) {\n nextUnit = parsed.unit;\n if (unitProp === undefined) setInnerUnit(nextUnit);\n onUnitChange?.(nextUnit);\n }\n if (!canWrap && (parsed.value > max || parsed.value < min)) kick(parsed.value > max ? 1 : -1);\n emit(constrain(parsed.value), \"type\", true, nextUnit);\n return true;\n }\n\n function stepBy(direction: 1 | -1, event: { shiftKey: boolean; altKey: boolean }) {\n const amount = event.shiftKey ? coarse : event.altKey ? fine : step;\n const base = latest.current.current;\n if (!canWrap && ((direction > 0 && base >= max) || (direction < 0 && base <= min))) {\n kick(direction);\n return;\n }\n // Coarse steps land on the coarse grid, so 13 with Shift goes to 20, not 23.\n const snapped = event.shiftKey\n ? (direction > 0\n ? Math.floor(base / amount + 1e-9) + 1\n : Math.ceil(base / amount - 1e-9) - 1) * amount\n : base + direction * amount;\n emit(constrain(snapped), \"key\", true);\n }\n\n // ---- Scrubbing ----\n\n const endScrub = useCallback(\n (cancel: boolean) => {\n const state = drag.current;\n drag.current = null;\n if (!state) return;\n document.documentElement.style.removeProperty(\"cursor\");\n if (document.pointerLockElement === handleRef.current) document.exitPointerLock();\n setScrubbing(null);\n setModifier(null);\n if (!state.moved) return;\n suppressClick.current = true;\n if (cancel) emit(state.start, \"scrub\", true);\n else onValueCommit?.(latest.current.current, { source: \"scrub\", unit: latest.current.unit });\n springHome();\n },\n // springHome only reads refs and sets state.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [emit, onValueCommit],\n );\n\n useEffect(() => {\n if (!scrubbing) return;\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n endScrub(true);\n return;\n }\n if (event.key === \"Shift\" || event.key === \"Alt\")\n setModifier(event.shiftKey ? \"coarse\" : event.altKey ? \"fine\" : null);\n };\n const onLock = () => {\n if (drag.current?.locked && document.pointerLockElement !== handleRef.current)\n endScrub(false);\n };\n window.addEventListener(\"keydown\", onKey);\n window.addEventListener(\"keyup\", onKey);\n document.addEventListener(\"pointerlockchange\", onLock);\n return () => {\n window.removeEventListener(\"keydown\", onKey);\n window.removeEventListener(\"keyup\", onKey);\n document.removeEventListener(\"pointerlockchange\", onLock);\n };\n }, [endScrub, scrubbing]);\n\n function placeCursor() {\n const node = cursorRef.current;\n if (node) node.style.transform = `translate3d(${cursor.current.x}px, ${cursor.current.y}px, 0)`;\n }\n\n function onHandleDown(event: ReactPointerEvent<HTMLLabelElement>) {\n if (disabled || drag.current || (event.pointerType === \"mouse\" && event.button !== 0)) return;\n event.preventDefault();\n if (draft !== null) commitDraft();\n try {\n event.currentTarget.setPointerCapture(event.pointerId);\n } catch {\n // A pointer the browser is not tracking (a synthetic one) cannot be captured; the drag follows it all the same.\n }\n drag.current = {\n id: event.pointerId,\n lastX: event.clientX,\n travel: 0,\n raw: latest.current.current,\n start: latest.current.current,\n moved: false,\n locked: false,\n mouse: event.pointerType === \"mouse\",\n };\n cursor.current = { x: event.clientX, y: event.clientY };\n }\n\n function onHandleMove(event: ReactPointerEvent<HTMLLabelElement>) {\n const state = drag.current;\n if (!state || event.pointerId !== state.id) return;\n const locked = document.pointerLockElement === handleRef.current;\n const dx = locked ? event.movementX : event.clientX - state.lastX;\n state.lastX = event.clientX;\n state.travel += dx;\n if (!state.moved) {\n if (Math.abs(state.travel) < SLOP) return;\n state.moved = true;\n inputRef.current?.blur();\n document.documentElement.style.cursor = \"ew-resize\";\n if (pointerLock && state.mouse && handleRef.current?.requestPointerLock) {\n try {\n const request = handleRef.current.requestPointerLock() as unknown;\n if (request instanceof Promise) request.catch(() => {});\n } catch {\n // Pointer lock is optional.\n }\n }\n setScrubbing({ locked: false });\n }\n if (locked && !state.locked) {\n state.locked = true;\n setScrubbing({ locked: true });\n }\n\n // A stand-in cursor replaces the locked pointer and wraps at the window edges, the way long scrubs do in design tools.\n if (locked) {\n const width = window.innerWidth;\n cursor.current.x = (((cursor.current.x + dx) % width) + width) % width;\n } else cursor.current = { x: event.clientX, y: event.clientY };\n placeCursor();\n\n const amount = event.shiftKey ? coarse : event.altKey ? fine : step;\n const mode = event.shiftKey ? \"coarse\" : event.altKey ? \"fine\" : null;\n if (mode !== modifier) setModifier(mode);\n state.raw += (dx / pixelsPerStep) * amount;\n\n if (!canWrap) {\n const perPx = amount / pixelsPerStep;\n const over =\n state.raw > max\n ? (state.raw - max) / perPx\n : state.raw < min\n ? (state.raw - min) / perPx\n : 0;\n // Cap the stored overshoot so reversing starts moving the value again after a short relax, not a long walk back.\n if (Math.abs(over) > OVERSHOOT_CAP)\n state.raw = (over > 0 ? max : min) + Math.sign(over) * OVERSHOOT_CAP * perPx;\n paintStrain(\n prefersReducedMotion()\n ? 0\n : rubber(Math.max(-OVERSHOOT_CAP, Math.min(OVERSHOOT_CAP, over))),\n );\n }\n const next = constrain(Math.round(state.raw / amount) * amount);\n if (next !== latest.current.current) emit(next, \"scrub\", false);\n }\n\n function onHandleUp(event: ReactPointerEvent<HTMLLabelElement>) {\n const state = drag.current;\n if (!state || event.pointerId !== state.id) return;\n endScrub(false);\n }\n\n function onHandleClick(event: ReactMouseEvent<HTMLLabelElement>) {\n if (suppressClick.current) {\n suppressClick.current = false;\n event.preventDefault();\n return;\n }\n if (disabled) return;\n event.preventDefault();\n inputRef.current?.focus();\n inputRef.current?.select();\n }\n\n function onHandleDoubleClick() {\n if (disabled || resetValue === undefined) return;\n emit(constrain(resetValue), \"reset\", true);\n }\n\n // ---- Typing ----\n\n function onInputKeyDown(event: ReactKeyboardEvent<HTMLInputElement>) {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n event.preventDefault();\n if (draft !== null && !commitDraft()) return;\n stepBy(event.key === \"ArrowUp\" ? 1 : -1, event);\n requestAnimationFrame(() => inputRef.current?.select());\n return;\n }\n if (event.key === \"Enter\") {\n event.preventDefault();\n commitDraft();\n requestAnimationFrame(() => inputRef.current?.select());\n return;\n }\n if (event.key === \"Escape\" && draft !== null) {\n event.preventDefault();\n event.stopPropagation();\n setDraft(null);\n requestAnimationFrame(() => inputRef.current?.select());\n }\n }\n\n function onInputFocus(event: FocusEvent<HTMLInputElement>) {\n onFocus?.(event);\n event.currentTarget.select();\n }\n\n function onInputBlur(event: FocusEvent<HTMLInputElement>) {\n onBlur?.(event);\n commitDraft();\n }\n\n function cycleUnit() {\n if (unitList.length < 2) return;\n const next = unitList[(unitList.indexOf(unit) + 1) % unitList.length] ?? unit;\n if (unitProp === undefined) setInnerUnit(next);\n onUnitChange?.(next);\n latest.current = { ...latest.current, unit: next };\n }\n\n const text = draft ?? format(current, places);\n const spokenUnit = unit === \"%\" ? \"percent\" : unit === \"deg\" || unit === \"°\" ? \"degrees\" : unit;\n const valueText = `${format(current, places)}${unit ? ` ${spokenUnit}` : \"\"}`;\n const multiplier =\n modifier === \"coarse\"\n ? `×${format(coarse / step, 2)}`\n : modifier === \"fine\"\n ? `×${format(fine / step, 3)}`\n : null;\n const labelIsText = typeof label === \"string\" || typeof label === \"number\";\n\n return (\n <div\n data-slot=\"scrub-input\"\n data-size={size}\n data-disabled={disabled ? \"\" : undefined}\n data-scrubbing={scrubbing ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-releasing={releasing ? \"\" : undefined}\n className={cn(\n cnAll(\n \"group/scrub relative isolate grid w-full min-w-0 grid-cols-[auto_minmax(0,1fr)] items-stretch font-sans text-foreground\",\n sizeClass[size],\n \"data-[disabled]:opacity-(--opacity-disabled)\",\n ),\n className,\n )}\n >\n <div\n ref={shellRef}\n aria-hidden=\"true\"\n data-slot=\"scrub-input-shell\"\n className={cnAll(\n \"absolute inset-0 -z-10 overflow-hidden border-(length:--border-width) border-transparent bg-surface-muted will-change-transform\",\n size === \"sm\" ? \"rounded-sm\" : \"rounded-md\",\n \"motion-safe:transition-[background-color,border-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n // The shell springs home after a strain: its transform transitions only while `releasing`.\n \"group-data-[releasing]/scrub:motion-safe:transition-[transform,background-color,border-color] group-data-[releasing]/scrub:motion-safe:duration-(--duration-smooth) group-data-[releasing]/scrub:motion-safe:ease-smooth\",\n \"group-hover/scrub:border-border\",\n \"group-focus-within/scrub:border-foreground group-focus-within/scrub:bg-surface\",\n \"forced-colors:group-focus-within/scrub:outline-solid forced-colors:group-focus-within/scrub:outline-(length:--focus-ring-width) forced-colors:group-focus-within/scrub:outline-offset-(--focus-ring-offset)\",\n \"group-data-[scrubbing]/scrub:border-border-strong\",\n \"group-data-[invalid]/scrub:border-destructive-strong\",\n )}\n >\n {showTrack ? (\n <span\n data-slot=\"scrub-input-fill\"\n style={{ transform: `scaleX(${fraction})` }}\n className=\"pointer-events-none absolute inset-0 origin-left bg-foreground/6 group-data-[scrubbing]/scrub:bg-foreground/10 motion-safe:transition-transform motion-safe:duration-(--duration-smooth) motion-safe:ease-smooth group-data-[scrubbing]/scrub:motion-safe:transition-none\"\n />\n ) : null}\n </div>\n <label\n ref={handleRef}\n htmlFor={id}\n data-slot=\"scrub-input-handle\"\n data-text={labelIsText ? \"\" : undefined}\n onPointerDown={onHandleDown}\n onPointerMove={onHandleMove}\n onPointerUp={onHandleUp}\n onPointerCancel={onHandleUp}\n onClick={onHandleClick}\n onDoubleClick={onHandleDoubleClick}\n className=\"flex min-w-7.5 cursor-ew-resize touch-pan-y items-center justify-center ps-2.5 pe-1 text-text-secondary select-none motion-safe:transition-colors motion-safe:duration-(--duration-quick) data-[text]:font-medium hover:text-foreground group-data-[scrubbing]/scrub:text-foreground group-data-[disabled]/scrub:cursor-not-allowed [&_svg]:size-3.5 [&_svg]:flex-none\"\n >\n {label}\n </label>\n <div\n ref={bodyRef}\n data-slot=\"scrub-input-body\"\n className={cnAll(\n \"flex min-w-0 items-center gap-0.5 pe-2.5 data-[strain]:motion-safe:animate-field-strain\",\n \"group-data-[releasing]/scrub:motion-safe:transition-transform group-data-[releasing]/scrub:motion-safe:duration-(--duration-smooth) group-data-[releasing]/scrub:motion-safe:ease-smooth\",\n )}\n >\n <input\n {...rest}\n ref={inputRef}\n id={id}\n type=\"text\"\n inputMode=\"decimal\"\n autoComplete=\"off\"\n spellCheck={false}\n role=\"spinbutton\"\n aria-label={ariaLabel}\n aria-valuenow={current}\n aria-valuemin={Number.isFinite(min) ? min : undefined}\n aria-valuemax={Number.isFinite(max) ? max : undefined}\n aria-valuetext={valueText}\n aria-invalid={invalid || undefined}\n disabled={disabled}\n value={text}\n onChange={(event) => setDraft(event.target.value)}\n onFocus={onInputFocus}\n onBlur={onInputBlur}\n onKeyDown={onInputKeyDown}\n className={cnAll(\n \"m-0 h-full w-full min-w-0 flex-1 border-0 bg-transparent p-0 ps-1 font-sans text-foreground tabular-nums outline-none selection:bg-primary selection:text-primary-foreground disabled:cursor-not-allowed\",\n size === \"sm\" ? \"text-xs\" : \"text-sm\",\n )}\n />\n {unit ? (\n unitList.length > 1 ? (\n <AriaButton\n data-slot=\"scrub-input-unit\"\n isDisabled={disabled}\n onPress={cycleUnit}\n aria-label={`Unit: ${unit}. Change unit`}\n className=\"relative inline-flex h-6 min-w-6 flex-none cursor-pointer items-center justify-center rounded-xs border-0 bg-transparent px-1 text-xs text-text-secondary outline-none motion-safe:transition-[background-color,color,scale] motion-safe:duration-(--duration-quick) motion-safe:ease-settle data-[hovered]:bg-foreground/7 data-[hovered]:text-foreground data-[pressed]:scale-95 data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed\"\n >\n <SwapText swapKey={unit} motion=\"rise\">\n {unit}\n </SwapText>\n </AriaButton>\n ) : (\n <span\n aria-hidden=\"true\"\n data-slot=\"scrub-input-unit\"\n className=\"inline-flex h-6 min-w-4.5 flex-none items-center justify-center px-1 text-xs text-text-secondary\"\n >\n {unit}\n </span>\n )\n ) : null}\n </div>\n {multiplier && !scrubbing?.locked ? (\n <span\n aria-hidden=\"true\"\n data-slot=\"scrub-input-multiplier\"\n className=\"pointer-events-none absolute bottom-full mb-1.5 start-1.5 z-10 rounded-pill bg-foreground px-1.75 py-0.5 text-xs leading-4 text-background tabular-nums motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\"\n >\n {multiplier}\n </span>\n ) : null}\n {scrubbing?.locked\n ? createPortal(\n <div\n ref={(node) => {\n cursorRef.current = node;\n placeCursor();\n }}\n aria-hidden=\"true\"\n data-slot=\"scrub-input-cursor\"\n className=\"pointer-events-none fixed top-0 left-0 z-50 -mt-2 -ms-3.25 flex items-center gap-1\"\n >\n <svg\n width=\"26\"\n height=\"16\"\n viewBox=\"0 0 26 16\"\n className=\"block fill-foreground stroke-background drop-shadow-sm\"\n strokeWidth=\"1.4\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M1.5 8 L7.5 2.5 V6 H18.5 V2.5 L24.5 8 L18.5 13.5 V10 H7.5 V13.5 Z\" />\n </svg>\n {multiplier ? (\n <span className=\"rounded-pill bg-foreground px-1.5 py-px font-sans text-xs leading-4 text-background tabular-nums motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\">\n {multiplier}\n </span>\n ) : null}\n </div>,\n document.body,\n )\n : null}\n </div>\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scrub-input.js","names":["AriaButton"],"sources":["../../src/scrub-input/scrub-input.tsx"],"sourcesContent":["\"use client\";\n\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n type FocusEvent,\n type InputHTMLAttributes,\n type KeyboardEvent as ReactKeyboardEvent,\n type MouseEvent as ReactMouseEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Button as AriaButton } from \"react-aria-components\";\n\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { prefersReducedMotion } from \"../lib/motion.js\";\nimport { SwapText } from \"../lib/swap-text.js\";\nimport { evaluateScrubExpression } from \"./evaluate.js\";\n\nexport type ScrubInputSource = \"scrub\" | \"type\" | \"key\" | \"reset\";\n\nexport interface ScrubInputChangeDetails {\n /** What produced the value: a label drag, a typed entry, an arrow key, or a double click reset. */\n source: ScrubInputSource;\n /** The unit in effect after the change. */\n unit: string;\n}\n\nexport interface ScrubInputProps extends Omit<\n InputHTMLAttributes<HTMLInputElement>,\n | \"value\"\n | \"defaultValue\"\n | \"onChange\"\n | \"type\"\n | \"size\"\n | \"min\"\n | \"max\"\n | \"step\"\n | \"prefix\"\n | \"className\"\n> {\n /** Text or a 16px icon. It is the scrub handle. An icon label needs `aria-label` so the field keeps an accessible name. */\n label: ReactNode;\n /** The value. Controlled when given. */\n value?: number;\n /** @default 0 */\n defaultValue?: number;\n /** Fires for every change, including each step of a scrub. */\n onValueChange?: (value: number, details: ScrubInputChangeDetails) => void;\n /** Fires once when a change settles: a scrub ends, Enter or blur commits typing, or a key step lands. Save on this. */\n onValueCommit?: (value: number, details: ScrubInputChangeDetails) => void;\n /** @default -Infinity */\n min?: number;\n /** @default Infinity */\n max?: number;\n /** One step for the arrow keys and for scrubbing. @default 1 */\n step?: number;\n /** The step with Alt held. @default step / 10 */\n fineStep?: number;\n /** The step with Shift held; coarse steps land on the coarse grid. @default step * 10 */\n coarseStep?: number;\n /** Decimal places kept. Defaults to the precision of `step` or `fineStep`. */\n precision?: number;\n /** Pointer travel in px for one step while scrubbing. @default 2 */\n pixelsPerStep?: number;\n /** Wraps from `max` back to `min`, as a rotation does. Needs both limits. @default false */\n wrap?: boolean;\n /** The unit shown after the number. Controlled when given. With more than one entry in `units` it is a button that cycles them. */\n unit?: string;\n defaultUnit?: string;\n /** The units a typed value may carry, such as `[\"px\", \"%\"]`. Defaults to the current unit. */\n units?: readonly string[];\n onUnitChange?: (unit: string) => void;\n /** Double click the label to return here. */\n resetValue?: number;\n /** Shows where the value sits between `min` and `max` as a quiet fill. On by default when both limits are set. */\n track?: boolean;\n /** `md` is the compact control height, `sm` is 30px. @default \"md\" */\n size?: \"sm\" | \"md\";\n /** Locks the mouse pointer while it scrubs, when the browser allows it, so a long drag never runs out of screen. @default true */\n pointerLock?: boolean;\n /**\n * Shows the value without letting it change: the field stays focusable and its text\n * selectable, but the label does not scrub, the arrow keys do not step, typing and\n * expressions are refused, a double click does not reset, and the unit does not cycle,\n * so `onValueChange`, `onValueCommit` and `onUnitChange` never fire. Sets\n * `aria-readonly` and `data-readonly`. The native attribute, as `disabled` is.\n * @default false\n */\n readOnly?: boolean;\n /** Extra classes for the field. Merged last, so `w-40` sizes it. */\n className?: string;\n}\n\n/** Movement before a press on the label becomes a scrub, and the most pixels the field strains past a limit. */\nconst SLOP = 3;\nconst STRAIN = 12;\n/** The most overshoot kept, so reversing starts moving the value again after a short relax. */\nconst OVERSHOOT_CAP = 90;\n/** How far a refused step or an invalid entry kicks the number (px). */\nconst KICK = 6;\n/** The longest the spring home takes (the smooth spring, 720ms) plus a margin. */\nconst RELEASE_MS = 900;\n/** How long an invalid entry is flagged. */\nconst INVALID_MS = 900;\n\nconst decimalsOf = (value: number) => {\n const text = String(value);\n if (text.includes(\"e-\")) return Number(text.split(\"e-\")[1]);\n return (text.split(\".\")[1] ?? \"\").length;\n};\nconst roundTo = (value: number, places: number) => {\n const factor = 10 ** places;\n return Math.round(value * factor) / factor;\n};\n/** Rubber-band resistance: each extra pixel gives less, and the strain never exceeds `limit`. */\nconst rubber = (distance: number, limit = STRAIN) =>\n Math.sign(distance) * (1 - 1 / ((Math.abs(distance) * 0.55) / limit + 1)) * limit;\nconst format = (value: number, places: number) =>\n String(roundTo(value, places)).replace(/^-0$/, \"0\");\n\ninterface Drag {\n id: number;\n lastX: number;\n travel: number;\n raw: number;\n start: number;\n moved: boolean;\n locked: boolean;\n mouse: boolean;\n}\n\nconst sizeClass = {\n md: \"h-(--density-control-h-sm) rounded-md text-sm\",\n sm: \"h-7.5 rounded-sm text-xs\",\n} as const;\n\n/**\n * A compact number field for inspectors and property panels. Drag the label sideways to\n * scrub the value (the mouse pointer is locked when the browser allows it, so a long drag\n * never runs out of screen), or click in and type an exact value. A typed entry takes\n * arithmetic such as `12*2` or `(100-24)/2`, and a unit from `units` such as `50%`.\n *\n * Keyboard: the arrow keys step (Shift ten times coarser, Alt ten times finer), Enter\n * commits typing, Escape restores the last value (or cancels a scrub back to where it\n * began). A double click on the label restores `resetValue`. The input is a spinbutton, and\n * its value text spells out percent and degrees. `readOnly` keeps the value visible,\n * focusable and selectable while refusing every change.\n *\n * At a limit the field strains: dragged past `min` or `max` it stretches against\n * rubber-band resistance and springs back on release, and a refused step or an invalid\n * entry kicks the number. Reduced motion removes the strain, the kick and the fill's glide.\n *\n * @example\n * <ScrubInput label=\"W\" value={width} onValueChange={setWidth} min={0} units={[\"px\", \"%\"]} />\n */\nexport const ScrubInput = forwardRef<HTMLInputElement, ScrubInputProps>(function ScrubInput(\n {\n label,\n value,\n defaultValue = 0,\n onValueChange,\n onValueCommit,\n min = -Infinity,\n max = Infinity,\n step = 1,\n fineStep,\n coarseStep,\n precision,\n pixelsPerStep = 2,\n wrap = false,\n unit: unitProp,\n defaultUnit,\n units,\n onUnitChange,\n resetValue,\n track,\n size = \"md\",\n pointerLock = true,\n className,\n disabled,\n readOnly = false,\n id: idProp,\n onFocus,\n onBlur,\n onKeyDown,\n \"aria-label\": ariaLabel,\n ...rest\n },\n forwarded,\n) {\n const uid = useId();\n const id = idProp ?? `scrub-${uid.replace(/[^a-zA-Z0-9-]/g, \"\")}`;\n const inputRef = useRef<HTMLInputElement>(null);\n useImperativeHandle(forwarded, () => inputRef.current as HTMLInputElement);\n const shellRef = useRef<HTMLDivElement>(null);\n const bodyRef = useRef<HTMLDivElement>(null);\n const handleRef = useRef<HTMLLabelElement>(null);\n const cursorRef = useRef<HTMLDivElement>(null);\n\n const fine = fineStep ?? step / 10;\n const coarse = coarseStep ?? step * 10;\n const places = precision ?? Math.max(decimalsOf(step), decimalsOf(fine));\n const canWrap = wrap && Number.isFinite(min) && Number.isFinite(max) && max > min;\n /** Nothing the person does changes the value: disabled, or read-only. */\n const frozen = Boolean(disabled) || readOnly;\n const showTrack = (track ?? true) && Number.isFinite(min) && Number.isFinite(max) && max > min;\n\n const [inner, setInner] = useState(defaultValue);\n const current = value ?? inner;\n const [innerUnit, setInnerUnit] = useState(defaultUnit ?? unitProp ?? units?.[0] ?? \"\");\n const unit = unitProp ?? innerUnit;\n const unitList = units ?? (unit ? [unit] : []);\n const [draft, setDraft] = useState<string | null>(null);\n const [scrubbing, setScrubbing] = useState<null | { locked: boolean }>(null);\n const [modifier, setModifier] = useState<\"fine\" | \"coarse\" | null>(null);\n const [invalid, setInvalid] = useState(false);\n const [releasing, setReleasing] = useState(false);\n\n // What the handlers read, so a scrub in flight never sees a stale render.\n const latest = useRef({ current, unit });\n useEffect(() => {\n latest.current = { current, unit };\n }, [current, unit]);\n const drag = useRef<Drag | null>(null);\n const suppressClick = useRef(false);\n const timers = useRef({ invalid: 0, release: 0 });\n const cursor = useRef({ x: 0, y: 0 });\n useEffect(() => {\n const pending = timers.current;\n return () => {\n window.clearTimeout(pending.invalid);\n window.clearTimeout(pending.release);\n document.documentElement.style.removeProperty(\"cursor\");\n };\n }, []);\n\n const fraction = showTrack ? Math.min(1, Math.max(0, (current - min) / (max - min))) : 0;\n\n /** The strain past a limit in px (positive past max): the shell stretches from its far edge and the number leans with it. */\n function paintStrain(amount: number) {\n const shell = shellRef.current;\n const body = bodyRef.current;\n if (shell) {\n const width = Math.max(1, shell.offsetWidth);\n shell.style.transform = amount === 0 ? \"\" : `scaleX(${1 + Math.abs(amount) / width})`;\n shell.style.transformOrigin = amount >= 0 ? \"left\" : \"right\";\n }\n if (body) body.style.transform = amount === 0 ? \"\" : `translateX(${amount * 0.5}px)`;\n }\n\n /** Lets the shell and the number spring home: a transition switched on for the moment the strain is cleared. */\n function springHome() {\n paintStrain(0);\n if (prefersReducedMotion()) return;\n setReleasing(true);\n window.clearTimeout(timers.current.release);\n timers.current.release = window.setTimeout(() => setReleasing(false), RELEASE_MS);\n }\n\n /** A short kick of the number toward `direction`: a step refused at a limit, or an entry that was not understood. */\n function kick(direction: number) {\n const node = bodyRef.current;\n if (!node || prefersReducedMotion()) return;\n node.style.setProperty(\"--strain-x\", `${direction * KICK}px`);\n // Restart the animation: the attribute has to leave and come back across a style recalculation.\n node.removeAttribute(\"data-strain\");\n void node.offsetWidth;\n node.setAttribute(\"data-strain\", \"\");\n }\n\n const emit = useCallback(\n (next: number, source: ScrubInputSource, commit: boolean, nextUnit = latest.current.unit) => {\n if (next !== latest.current.current) {\n latest.current = { current: next, unit: nextUnit };\n if (value === undefined) setInner(next);\n onValueChange?.(next, { source, unit: nextUnit });\n }\n if (commit) onValueCommit?.(next, { source, unit: nextUnit });\n },\n [onValueChange, onValueCommit, value],\n );\n\n const constrain = (next: number) => {\n const rounded = roundTo(next, places);\n if (canWrap) {\n const span = max - min;\n return roundTo(((((rounded - min) % span) + span) % span) + min, places);\n }\n return Math.min(max, Math.max(min, rounded));\n };\n\n function flagInvalid() {\n setInvalid(true);\n window.clearTimeout(timers.current.invalid);\n timers.current.invalid = window.setTimeout(() => setInvalid(false), INVALID_MS);\n kick(-1);\n }\n\n function commitDraft(text: string | null = draft) {\n setDraft(null);\n if (text === null || readOnly) return true;\n if (text.trim() === format(current, places)) return true;\n const parsed = evaluateScrubExpression(text, unitList);\n if (!parsed) {\n flagInvalid();\n return false;\n }\n let nextUnit = latest.current.unit;\n if (parsed.unit && parsed.unit !== nextUnit) {\n nextUnit = parsed.unit;\n if (unitProp === undefined) setInnerUnit(nextUnit);\n onUnitChange?.(nextUnit);\n }\n if (!canWrap && (parsed.value > max || parsed.value < min)) kick(parsed.value > max ? 1 : -1);\n emit(constrain(parsed.value), \"type\", true, nextUnit);\n return true;\n }\n\n function stepBy(direction: 1 | -1, event: { shiftKey: boolean; altKey: boolean }) {\n const amount = event.shiftKey ? coarse : event.altKey ? fine : step;\n const base = latest.current.current;\n if (!canWrap && ((direction > 0 && base >= max) || (direction < 0 && base <= min))) {\n kick(direction);\n return;\n }\n // Coarse steps land on the coarse grid, so 13 with Shift goes to 20, not 23.\n const snapped = event.shiftKey\n ? (direction > 0\n ? Math.floor(base / amount + 1e-9) + 1\n : Math.ceil(base / amount - 1e-9) - 1) * amount\n : base + direction * amount;\n emit(constrain(snapped), \"key\", true);\n }\n\n // ---- Scrubbing ----\n\n const endScrub = useCallback(\n (cancel: boolean) => {\n const state = drag.current;\n drag.current = null;\n if (!state) return;\n document.documentElement.style.removeProperty(\"cursor\");\n if (document.pointerLockElement === handleRef.current) document.exitPointerLock();\n setScrubbing(null);\n setModifier(null);\n if (!state.moved) return;\n suppressClick.current = true;\n if (cancel) emit(state.start, \"scrub\", true);\n else onValueCommit?.(latest.current.current, { source: \"scrub\", unit: latest.current.unit });\n springHome();\n },\n // springHome only reads refs and sets state.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [emit, onValueCommit],\n );\n\n useEffect(() => {\n if (!scrubbing) return;\n const onKey = (event: KeyboardEvent) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n endScrub(true);\n return;\n }\n if (event.key === \"Shift\" || event.key === \"Alt\")\n setModifier(event.shiftKey ? \"coarse\" : event.altKey ? \"fine\" : null);\n };\n const onLock = () => {\n if (drag.current?.locked && document.pointerLockElement !== handleRef.current)\n endScrub(false);\n };\n window.addEventListener(\"keydown\", onKey);\n window.addEventListener(\"keyup\", onKey);\n document.addEventListener(\"pointerlockchange\", onLock);\n return () => {\n window.removeEventListener(\"keydown\", onKey);\n window.removeEventListener(\"keyup\", onKey);\n document.removeEventListener(\"pointerlockchange\", onLock);\n };\n }, [endScrub, scrubbing]);\n\n function placeCursor() {\n const node = cursorRef.current;\n if (node) node.style.transform = `translate3d(${cursor.current.x}px, ${cursor.current.y}px, 0)`;\n }\n\n function onHandleDown(event: ReactPointerEvent<HTMLLabelElement>) {\n if (frozen || drag.current || (event.pointerType === \"mouse\" && event.button !== 0)) return;\n event.preventDefault();\n if (draft !== null) commitDraft();\n try {\n event.currentTarget.setPointerCapture(event.pointerId);\n } catch {\n // A pointer the browser is not tracking (a synthetic one) cannot be captured; the drag follows it all the same.\n }\n drag.current = {\n id: event.pointerId,\n lastX: event.clientX,\n travel: 0,\n raw: latest.current.current,\n start: latest.current.current,\n moved: false,\n locked: false,\n mouse: event.pointerType === \"mouse\",\n };\n cursor.current = { x: event.clientX, y: event.clientY };\n }\n\n function onHandleMove(event: ReactPointerEvent<HTMLLabelElement>) {\n const state = drag.current;\n if (!state || event.pointerId !== state.id) return;\n const locked = document.pointerLockElement === handleRef.current;\n const dx = locked ? event.movementX : event.clientX - state.lastX;\n state.lastX = event.clientX;\n state.travel += dx;\n if (!state.moved) {\n if (Math.abs(state.travel) < SLOP) return;\n state.moved = true;\n inputRef.current?.blur();\n document.documentElement.style.cursor = \"ew-resize\";\n if (pointerLock && state.mouse && handleRef.current?.requestPointerLock) {\n try {\n const request = handleRef.current.requestPointerLock() as unknown;\n if (request instanceof Promise) request.catch(() => {});\n } catch {\n // Pointer lock is optional.\n }\n }\n setScrubbing({ locked: false });\n }\n if (locked && !state.locked) {\n state.locked = true;\n setScrubbing({ locked: true });\n }\n\n // A stand-in cursor replaces the locked pointer and wraps at the window edges, the way long scrubs do in design tools.\n if (locked) {\n const width = window.innerWidth;\n cursor.current.x = (((cursor.current.x + dx) % width) + width) % width;\n } else cursor.current = { x: event.clientX, y: event.clientY };\n placeCursor();\n\n const amount = event.shiftKey ? coarse : event.altKey ? fine : step;\n const mode = event.shiftKey ? \"coarse\" : event.altKey ? \"fine\" : null;\n if (mode !== modifier) setModifier(mode);\n state.raw += (dx / pixelsPerStep) * amount;\n\n if (!canWrap) {\n const perPx = amount / pixelsPerStep;\n const over =\n state.raw > max\n ? (state.raw - max) / perPx\n : state.raw < min\n ? (state.raw - min) / perPx\n : 0;\n // Cap the stored overshoot so reversing starts moving the value again after a short relax, not a long walk back.\n if (Math.abs(over) > OVERSHOOT_CAP)\n state.raw = (over > 0 ? max : min) + Math.sign(over) * OVERSHOOT_CAP * perPx;\n paintStrain(\n prefersReducedMotion()\n ? 0\n : rubber(Math.max(-OVERSHOOT_CAP, Math.min(OVERSHOOT_CAP, over))),\n );\n }\n const next = constrain(Math.round(state.raw / amount) * amount);\n if (next !== latest.current.current) emit(next, \"scrub\", false);\n }\n\n function onHandleUp(event: ReactPointerEvent<HTMLLabelElement>) {\n const state = drag.current;\n if (!state || event.pointerId !== state.id) return;\n endScrub(false);\n }\n\n function onHandleClick(event: ReactMouseEvent<HTMLLabelElement>) {\n if (suppressClick.current) {\n suppressClick.current = false;\n event.preventDefault();\n return;\n }\n if (disabled) return;\n event.preventDefault();\n inputRef.current?.focus();\n inputRef.current?.select();\n }\n\n function onHandleDoubleClick() {\n if (frozen || resetValue === undefined) return;\n emit(constrain(resetValue), \"reset\", true);\n }\n\n // ---- Typing ----\n\n function onInputKeyDown(event: ReactKeyboardEvent<HTMLInputElement>) {\n onKeyDown?.(event);\n if (event.defaultPrevented) return;\n if (readOnly) {\n // Enter never submits a surrounding form, read-only or not.\n if (event.key === \"Enter\") event.preventDefault();\n return;\n }\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n event.preventDefault();\n if (draft !== null && !commitDraft()) return;\n stepBy(event.key === \"ArrowUp\" ? 1 : -1, event);\n requestAnimationFrame(() => inputRef.current?.select());\n return;\n }\n if (event.key === \"Enter\") {\n event.preventDefault();\n commitDraft();\n requestAnimationFrame(() => inputRef.current?.select());\n return;\n }\n if (event.key === \"Escape\" && draft !== null) {\n event.preventDefault();\n event.stopPropagation();\n setDraft(null);\n requestAnimationFrame(() => inputRef.current?.select());\n }\n }\n\n function onInputFocus(event: FocusEvent<HTMLInputElement>) {\n onFocus?.(event);\n event.currentTarget.select();\n }\n\n function onInputBlur(event: FocusEvent<HTMLInputElement>) {\n onBlur?.(event);\n commitDraft();\n }\n\n function cycleUnit() {\n if (readOnly || unitList.length < 2) return;\n const next = unitList[(unitList.indexOf(unit) + 1) % unitList.length] ?? unit;\n if (unitProp === undefined) setInnerUnit(next);\n onUnitChange?.(next);\n latest.current = { ...latest.current, unit: next };\n }\n\n const text = draft ?? format(current, places);\n const spokenUnit = unit === \"%\" ? \"percent\" : unit === \"deg\" || unit === \"°\" ? \"degrees\" : unit;\n const valueText = `${format(current, places)}${unit ? ` ${spokenUnit}` : \"\"}`;\n const multiplier =\n modifier === \"coarse\"\n ? `×${format(coarse / step, 2)}`\n : modifier === \"fine\"\n ? `×${format(fine / step, 3)}`\n : null;\n const labelIsText = typeof label === \"string\" || typeof label === \"number\";\n\n return (\n <div\n data-slot=\"scrub-input\"\n data-size={size}\n data-disabled={disabled ? \"\" : undefined}\n data-readonly={readOnly ? \"\" : undefined}\n data-scrubbing={scrubbing ? \"\" : undefined}\n data-invalid={invalid ? \"\" : undefined}\n data-releasing={releasing ? \"\" : undefined}\n className={cn(\n cnAll(\n \"group/scrub relative isolate grid w-full min-w-0 grid-cols-[auto_minmax(0,1fr)] items-stretch font-sans text-foreground\",\n sizeClass[size],\n \"data-[disabled]:opacity-(--opacity-disabled)\",\n ),\n className,\n )}\n >\n <div\n ref={shellRef}\n aria-hidden=\"true\"\n data-slot=\"scrub-input-shell\"\n className={cnAll(\n \"absolute inset-0 -z-10 overflow-hidden border-(length:--border-width) border-transparent bg-surface-muted will-change-transform\",\n size === \"sm\" ? \"rounded-sm\" : \"rounded-md\",\n \"motion-safe:transition-[background-color,border-color] motion-safe:duration-(--duration-quick) motion-safe:ease-settle\",\n // The shell springs home after a strain: its transform transitions only while `releasing`.\n \"group-data-[releasing]/scrub:motion-safe:transition-[transform,background-color,border-color] group-data-[releasing]/scrub:motion-safe:duration-(--duration-smooth) group-data-[releasing]/scrub:motion-safe:ease-smooth\",\n \"group-hover/scrub:border-border\",\n \"group-focus-within/scrub:border-foreground group-focus-within/scrub:bg-surface\",\n \"forced-colors:group-focus-within/scrub:outline-solid forced-colors:group-focus-within/scrub:outline-(length:--focus-ring-width) forced-colors:group-focus-within/scrub:outline-offset-(--focus-ring-offset)\",\n \"group-data-[scrubbing]/scrub:border-border-strong\",\n \"group-data-[invalid]/scrub:border-destructive-strong\",\n )}\n >\n {showTrack ? (\n <span\n data-slot=\"scrub-input-fill\"\n style={{ transform: `scaleX(${fraction})` }}\n className=\"pointer-events-none absolute inset-0 origin-left bg-foreground/6 group-data-[scrubbing]/scrub:bg-foreground/10 motion-safe:transition-transform motion-safe:duration-(--duration-smooth) motion-safe:ease-smooth group-data-[scrubbing]/scrub:motion-safe:transition-none\"\n />\n ) : null}\n </div>\n <label\n ref={handleRef}\n htmlFor={id}\n data-slot=\"scrub-input-handle\"\n data-text={labelIsText ? \"\" : undefined}\n onPointerDown={onHandleDown}\n onPointerMove={onHandleMove}\n onPointerUp={onHandleUp}\n onPointerCancel={onHandleUp}\n onClick={onHandleClick}\n onDoubleClick={onHandleDoubleClick}\n className=\"flex min-w-7.5 cursor-ew-resize touch-pan-y items-center justify-center ps-2.5 pe-1 text-text-secondary select-none motion-safe:transition-colors motion-safe:duration-(--duration-quick) data-[text]:font-medium hover:text-foreground group-data-[scrubbing]/scrub:text-foreground group-data-[readonly]/scrub:cursor-default group-data-[readonly]/scrub:hover:text-text-secondary group-data-[disabled]/scrub:cursor-not-allowed [&_svg]:size-3.5 [&_svg]:flex-none\"\n >\n {label}\n </label>\n <div\n ref={bodyRef}\n data-slot=\"scrub-input-body\"\n className={cnAll(\n \"flex min-w-0 items-center gap-0.5 pe-2.5 data-[strain]:motion-safe:animate-field-strain\",\n \"group-data-[releasing]/scrub:motion-safe:transition-transform group-data-[releasing]/scrub:motion-safe:duration-(--duration-smooth) group-data-[releasing]/scrub:motion-safe:ease-smooth\",\n )}\n >\n <input\n {...rest}\n ref={inputRef}\n id={id}\n type=\"text\"\n inputMode=\"decimal\"\n autoComplete=\"off\"\n spellCheck={false}\n role=\"spinbutton\"\n aria-label={ariaLabel}\n aria-valuenow={current}\n aria-valuemin={Number.isFinite(min) ? min : undefined}\n aria-valuemax={Number.isFinite(max) ? max : undefined}\n aria-valuetext={valueText}\n aria-invalid={invalid || undefined}\n aria-readonly={readOnly || undefined}\n disabled={disabled}\n readOnly={readOnly}\n value={text}\n onChange={(event) => {\n if (!readOnly) setDraft(event.target.value);\n }}\n onFocus={onInputFocus}\n onBlur={onInputBlur}\n onKeyDown={onInputKeyDown}\n className={cnAll(\n \"m-0 h-full w-full min-w-0 flex-1 border-0 bg-transparent p-0 ps-1 font-sans text-foreground tabular-nums outline-none selection:bg-primary selection:text-primary-foreground read-only:caret-transparent disabled:cursor-not-allowed\",\n size === \"sm\" ? \"text-xs\" : \"text-sm\",\n )}\n />\n {unit ? (\n unitList.length > 1 && !readOnly ? (\n <AriaButton\n data-slot=\"scrub-input-unit\"\n isDisabled={disabled}\n onPress={cycleUnit}\n aria-label={`Unit: ${unit}. Change unit`}\n className=\"relative inline-flex h-6 min-w-6 flex-none cursor-pointer items-center justify-center rounded-xs border-0 bg-transparent px-1 text-xs text-text-secondary outline-none motion-safe:transition-[background-color,color,scale] motion-safe:duration-(--duration-quick) motion-safe:ease-settle data-[hovered]:bg-foreground/7 data-[hovered]:text-foreground data-[pressed]:scale-95 data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed\"\n >\n <SwapText swapKey={unit} motion=\"rise\">\n {unit}\n </SwapText>\n </AriaButton>\n ) : (\n <span\n aria-hidden=\"true\"\n data-slot=\"scrub-input-unit\"\n className=\"inline-flex h-6 min-w-4.5 flex-none items-center justify-center px-1 text-xs text-text-secondary\"\n >\n {unit}\n </span>\n )\n ) : null}\n </div>\n {multiplier && !scrubbing?.locked ? (\n <span\n aria-hidden=\"true\"\n data-slot=\"scrub-input-multiplier\"\n className=\"pointer-events-none absolute bottom-full mb-1.5 start-1.5 z-10 rounded-pill bg-foreground px-1.75 py-0.5 text-xs leading-4 text-background tabular-nums motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\"\n >\n {multiplier}\n </span>\n ) : null}\n {scrubbing?.locked\n ? createPortal(\n <div\n ref={(node) => {\n cursorRef.current = node;\n placeCursor();\n }}\n aria-hidden=\"true\"\n data-slot=\"scrub-input-cursor\"\n className=\"pointer-events-none fixed top-0 left-0 z-50 -mt-2 -ms-3.25 flex items-center gap-1\"\n >\n <svg\n width=\"26\"\n height=\"16\"\n viewBox=\"0 0 26 16\"\n className=\"block fill-foreground stroke-background drop-shadow-sm\"\n strokeWidth=\"1.4\"\n strokeLinejoin=\"round\"\n >\n <path d=\"M1.5 8 L7.5 2.5 V6 H18.5 V2.5 L24.5 8 L18.5 13.5 V10 H7.5 V13.5 Z\" />\n </svg>\n {multiplier ? (\n <span className=\"rounded-pill bg-foreground px-1.5 py-px font-sans text-xs leading-4 text-background tabular-nums motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\">\n {multiplier}\n </span>\n ) : null}\n </div>,\n document.body,\n )\n : null}\n </div>\n );\n});\n"],"mappings":"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"}
|
|
@@ -3,8 +3,28 @@ import { InputProps } from "react-aria-components";
|
|
|
3
3
|
export interface SearchFieldProps extends Omit<InputProps, "className" | "style" | "size" | "type" | "value" | "defaultValue" | "onChange"> {
|
|
4
4
|
/** Extra classes for the input. Merged last, so they win conflicts with ours. */
|
|
5
5
|
className?: string;
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* What the field searches, and its accessible name. Visible above the box,
|
|
8
|
+
* tied to the input with `htmlFor`, unless `labelHidden` is set.
|
|
9
|
+
*/
|
|
7
10
|
label: string;
|
|
11
|
+
/**
|
|
12
|
+
* Hide the label and keep it as the input's accessible name (`aria-label`),
|
|
13
|
+
* for a toolbar search whose purpose the surrounding UI already shows.
|
|
14
|
+
* @default false
|
|
15
|
+
*/
|
|
16
|
+
labelHidden?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Control scale of the box. Height and inline padding come from the same
|
|
19
|
+
* `--density-control-*` tokens as Input, Select and Button; `sm` also
|
|
20
|
+
* takes Input's 14px text (16px on a touch screen).
|
|
21
|
+
*
|
|
22
|
+
* This REPLACES the native `size` attribute, which sets a width in
|
|
23
|
+
* characters that the flex layout ignores.
|
|
24
|
+
*
|
|
25
|
+
* @default "md"
|
|
26
|
+
*/
|
|
27
|
+
size?: "sm" | "md" | "lg";
|
|
8
28
|
/** The current text. Controlled: the field has no state of its own. */
|
|
9
29
|
value: string;
|
|
10
30
|
/** Called with the new text on every edit, and with `""` when the clear button is pressed. */
|
|
@@ -20,6 +40,7 @@ export interface SearchFieldProps extends Omit<InputProps, "className" | "style"
|
|
|
20
40
|
*
|
|
21
41
|
* @example
|
|
22
42
|
* <SearchField label="Search contacts" value={query} onValueChange={setQuery} />
|
|
43
|
+
* <SearchField label="Search tickets" labelHidden size="sm" value={query} onValueChange={setQuery} />
|
|
23
44
|
*/
|
|
24
45
|
declare const SearchField$1: import("react").ForwardRefExoticComponent<SearchFieldProps & import("react").RefAttributes<HTMLInputElement>>;
|
|
25
46
|
//#endregion
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
import { cn } from "../lib/cn.js";
|
|
3
3
|
import { mergeRefs } from "../lib/merge-refs.js";
|
|
4
4
|
import { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from "../lib/motion.js";
|
|
5
|
-
import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
5
|
+
import { fieldBoxSize, fieldSurfaceVariants, fieldTextSize } from "../lib/field-surface.js";
|
|
6
6
|
import { FieldShell, useFieldWiring } from "../lib/field-shell.js";
|
|
7
|
+
import { cva } from "class-variance-authority";
|
|
7
8
|
import { forwardRef, useCallback, useMemo, useRef, useState } from "react";
|
|
8
9
|
import { Button, Group, Input } from "react-aria-components";
|
|
9
10
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -13,9 +14,21 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
13
14
|
* PasswordField for why a Group, and why `data-focus-within` darkens the border
|
|
14
15
|
* as well). `group/shell` lets the glass read the box's focus and filled state.
|
|
15
16
|
*/
|
|
16
|
-
const
|
|
17
|
+
const shellVariants = cva("group/shell flex items-center gap-(--density-control-gap) [&:where([data-focus-within])]:border-border-strong", {
|
|
18
|
+
variants: {
|
|
19
|
+
/**
|
|
20
|
+
* Height and inline padding from the control scale, the same three rungs
|
|
21
|
+
* and tokens as Input, Select and Button, so a toolbar search sits level
|
|
22
|
+
* with the buttons beside it.
|
|
23
|
+
*/
|
|
24
|
+
size: fieldBoxSize },
|
|
25
|
+
defaultVariants: { size: "md" }
|
|
26
|
+
});
|
|
17
27
|
/** Borderless, fill-less input; the browser's own clear button and search decoration are hidden because the field has its own. */
|
|
18
|
-
const
|
|
28
|
+
const inputVariants = cva("min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 font-sans text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden", {
|
|
29
|
+
variants: { size: fieldTextSize },
|
|
30
|
+
defaultVariants: { size: "md" }
|
|
31
|
+
});
|
|
19
32
|
const clearClass = "motion-safe:button-motion grid size-6 cursor-pointer place-items-center rounded-control border-0 bg-transparent p-0 text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground motion-safe:data-[pressed]:scale-96 data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed";
|
|
20
33
|
/** The glass wakes up with the field: muted at rest, full contrast while the field has focus or a value. */
|
|
21
34
|
function SearchGlyph() {
|
|
@@ -93,8 +106,9 @@ function ClearButton({ show, label, disabled, onClear }) {
|
|
|
93
106
|
*
|
|
94
107
|
* @example
|
|
95
108
|
* <SearchField label="Search contacts" value={query} onValueChange={setQuery} />
|
|
109
|
+
* <SearchField label="Search tickets" labelHidden size="sm" value={query} onValueChange={setQuery} />
|
|
96
110
|
*/
|
|
97
|
-
const SearchField$1 = forwardRef(function SearchField({ label, value, onValueChange, clearLabel = "Clear search", className, ...props }, ref) {
|
|
111
|
+
const SearchField$1 = forwardRef(function SearchField({ label, labelHidden = false, size = "md", value, onValueChange, clearLabel = "Clear search", className, ...props }, ref) {
|
|
98
112
|
const inputRef = useRef(null);
|
|
99
113
|
const refs = useMemo(() => mergeRefs(ref, inputRef), [ref]);
|
|
100
114
|
const field = useFieldWiring({
|
|
@@ -111,18 +125,19 @@ const SearchField$1 = forwardRef(function SearchField({ label, value, onValueCha
|
|
|
111
125
|
isDisabled: disabled,
|
|
112
126
|
"data-filled": value ? "true" : void 0,
|
|
113
127
|
"data-slot": "search-field-shell",
|
|
114
|
-
className:
|
|
128
|
+
className: cn(fieldSurfaceVariants(), shellVariants({ size })),
|
|
115
129
|
children: [
|
|
116
130
|
/* @__PURE__ */ jsx(SearchGlyph, {}),
|
|
117
131
|
/* @__PURE__ */ jsx(Input, {
|
|
118
132
|
ref: refs,
|
|
133
|
+
"aria-label": labelHidden ? label : void 0,
|
|
119
134
|
...props,
|
|
120
135
|
...field.controlProps,
|
|
121
136
|
type: "search",
|
|
122
137
|
value,
|
|
123
138
|
onChange: (event) => onValueChange(event.target.value),
|
|
124
139
|
"data-slot": "search-field",
|
|
125
|
-
className: cn(
|
|
140
|
+
className: cn(inputVariants({ size }), className)
|
|
126
141
|
}),
|
|
127
142
|
/* @__PURE__ */ jsx("span", {
|
|
128
143
|
className: "grid size-6 flex-none place-items-center",
|
|
@@ -140,7 +155,7 @@ const SearchField$1 = forwardRef(function SearchField({ label, value, onValueCha
|
|
|
140
155
|
});
|
|
141
156
|
if (!field.active) return control;
|
|
142
157
|
return /* @__PURE__ */ jsx(FieldShell, {
|
|
143
|
-
label,
|
|
158
|
+
label: labelHidden ? null : label,
|
|
144
159
|
controlId: field.controlId,
|
|
145
160
|
description: void 0,
|
|
146
161
|
hintId: void 0,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search-field.js","names":["AriaButton","SearchField","AriaGroup","AriaInput"],"sources":["../../src/search-field/search-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { forwardRef, useCallback, useMemo, useRef, useState } from \"react\";\nimport {\n Button as AriaButton,\n Group as AriaGroup,\n Input as AriaInput,\n type InputProps as AriaInputProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { FieldShell, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\n\n/**\n * The field's box: a React Aria `Group` carrying the shared field surface (see\n * PasswordField for why a Group, and why `data-focus-within` darkens the border\n * as well). `group/shell` lets the glass read the box's focus and filled state.\n */\nconst shellClass = cn(\n fieldSurfaceVariants(),\n \"group/shell flex items-center gap-(--density-control-gap) h-(--density-control-h-md) px-(--density-control-pad-md) [&:where([data-focus-within])]:border-border-strong\",\n);\n\n/** Borderless, fill-less input; the browser's own clear button and search decoration are hidden because the field has its own. */\nconst inputClass =\n \"min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 font-sans text-base text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\";\n\nconst clearClass =\n \"motion-safe:button-motion grid size-6 cursor-pointer place-items-center rounded-control border-0 bg-transparent p-0 text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground motion-safe:data-[pressed]:scale-96 data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed\";\n\n/** The glass wakes up with the field: muted at rest, full contrast while the field has focus or a value. */\nfunction SearchGlyph() {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"size-4.5 flex-none text-muted-foreground motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle group-data-[filled]/shell:text-foreground group-data-[focus-within]/shell:text-foreground\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.34-4.34\" />\n </svg>\n );\n}\n\n/**\n * The clear button, in a box that is always there so the field never changes\n * width. It scales in on the snappy spring while it fades and unblurs, and\n * leaves a little smaller. Nothing animates if it is there on first mount, and\n * while it leaves it is inert so it cannot be pressed or focused.\n */\nfunction ClearButton({\n show,\n label,\n disabled,\n onClear,\n}: {\n show: boolean;\n label: string;\n disabled: boolean;\n onClear: () => void;\n}) {\n const [initial] = useState(show);\n const [rendered, setRendered] = useState(show);\n const [animated, setAnimated] = useState(false);\n const leaving = rendered && !show;\n if (show && !rendered) setRendered(true);\n if (!animated && show !== initial) setAnimated(true);\n const unmount = useCallback(() => setRendered(false), []);\n useExitBackstop(leaving, unmount);\n // Reduced motion removes it at once: an animation that is switched off never ends.\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) unmount();\n }, [leaving, unmount]);\n if (!rendered) return null;\n return (\n <span\n ref={(node) => {\n if (!node) return;\n if (leaving) node.setAttribute(\"inert\", \"\");\n else node.removeAttribute(\"inert\");\n }}\n aria-hidden={leaving || undefined}\n onAnimationEnd={(event) => {\n if (\n leaving &&\n event.target === event.currentTarget &&\n event.animationName === \"field-clear-out\"\n )\n unmount();\n }}\n className={cn(\n \"grid size-6 place-items-center\",\n leaving\n ? \"pointer-events-none motion-safe:animate-field-clear-out\"\n : animated && \"motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\",\n )}\n >\n <AriaButton\n type=\"button\"\n isDisabled={disabled}\n aria-label={label}\n onPress={onClear}\n className={clearClass}\n >\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"size-4\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </AriaButton>\n </span>\n );\n}\n\nexport interface SearchFieldProps extends Omit<\n AriaInputProps,\n \"className\" | \"style\" | \"size\" | \"type\" | \"value\" | \"defaultValue\" | \"onChange\"\n> {\n /** Extra classes for the input. Merged last, so they win conflicts with ours. */\n className?: string;\n /** The visible label, tied to the input with `htmlFor`. */\n label: string;\n /** The current text. Controlled: the field has no state of its own. */\n value: string;\n /** Called with the new text on every edit, and with `\"\"` when the clear button is pressed. */\n onValueChange: (value: string) => void;\n /** The clear button's accessible name. @default \"Clear search\" */\n clearLabel?: string;\n}\n\n/**\n * A search input with a clear button. The button has a reserved box, so the\n * field never changes width as it comes and goes; pressing it clears the text\n * and returns focus to the input. `onChange` is not accepted: use `onValueChange`.\n * Native input props apply to the input, and `disabled` also disables the button.\n *\n * @example\n * <SearchField label=\"Search contacts\" value={query} onValueChange={setQuery} />\n */\nexport const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(function SearchField(\n { label, value, onValueChange, clearLabel = \"Clear search\", className, ...props },\n ref,\n) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, inputRef), [ref]);\n const field = useFieldWiring({\n enabled: true,\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description: undefined,\n error: undefined,\n });\n const disabled = Boolean(props.disabled);\n\n const control = (\n <AriaGroup\n role=\"presentation\"\n isDisabled={disabled}\n data-filled={value ? \"true\" : undefined}\n data-slot=\"search-field-shell\"\n className={shellClass}\n >\n <SearchGlyph />\n <AriaInput\n ref={refs}\n {...props}\n {...field.controlProps}\n type=\"search\"\n value={value}\n onChange={(event) => onValueChange(event.target.value)}\n data-slot=\"search-field\"\n className={cn(inputClass, className)}\n />\n {/* A clear button's box is reserved, so the field never changes width when it appears or leaves. */}\n <span className=\"grid size-6 flex-none place-items-center\">\n <ClearButton\n show={value !== \"\"}\n label={clearLabel}\n disabled={disabled}\n onClear={() => {\n onValueChange(\"\");\n inputRef.current?.focus();\n }}\n />\n </span>\n </AriaGroup>\n );\n\n if (!field.active) return control;\n return (\n <FieldShell\n label={label}\n controlId={field.controlId}\n description={undefined}\n hintId={undefined}\n error={undefined}\n errorId={undefined}\n >\n {control}\n </FieldShell>\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"search-field.js","names":["AriaButton","SearchField","AriaGroup","AriaInput"],"sources":["../../src/search-field/search-field.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useCallback, useMemo, useRef, useState } from \"react\";\nimport {\n Button as AriaButton,\n Group as AriaGroup,\n Input as AriaInput,\n type InputProps as AriaInputProps,\n} from \"react-aria-components\";\n\nimport { cn } from \"../lib/cn.js\";\nimport { FieldShell, useFieldWiring } from \"../lib/field-shell.js\";\nimport { fieldBoxSize, fieldSurfaceVariants, fieldTextSize } from \"../lib/field-surface.js\";\nimport { mergeRefs } from \"../lib/merge-refs.js\";\nimport { prefersReducedMotion, useExitBackstop, useIsomorphicLayoutEffect } from \"../lib/motion.js\";\n\n/**\n * The field's box: a React Aria `Group` carrying the shared field surface (see\n * PasswordField for why a Group, and why `data-focus-within` darkens the border\n * as well). `group/shell` lets the glass read the box's focus and filled state.\n */\nconst shellVariants = cva(\n \"group/shell flex items-center gap-(--density-control-gap) [&:where([data-focus-within])]:border-border-strong\",\n {\n variants: {\n /**\n * Height and inline padding from the control scale, the same three rungs\n * and tokens as Input, Select and Button, so a toolbar search sits level\n * with the buttons beside it.\n */\n size: fieldBoxSize,\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/** Borderless, fill-less input; the browser's own clear button and search decoration are hidden because the field has its own. */\nconst inputVariants = cva(\n \"min-w-0 flex-1 appearance-none border-0 bg-transparent p-0 font-sans text-foreground outline-none placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground data-[disabled]:cursor-not-allowed [&::-webkit-search-cancel-button]:hidden [&::-webkit-search-decoration]:hidden\",\n {\n variants: {\n // Input's type rule: `sm` is 14px, except on a coarse pointer, where iOS would zoom a focused field under 16px.\n size: fieldTextSize,\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nconst clearClass =\n \"motion-safe:button-motion grid size-6 cursor-pointer place-items-center rounded-control border-0 bg-transparent p-0 text-muted-foreground outline-none data-[hovered]:bg-muted data-[hovered]:text-foreground motion-safe:data-[pressed]:scale-96 data-[focus-visible]:outline-solid data-[focus-visible]:outline-(length:--focus-ring-width) data-[focus-visible]:outline-offset-(--focus-ring-offset) data-[focus-visible]:outline-(--color-ring) data-[disabled]:cursor-not-allowed\";\n\n/** The glass wakes up with the field: muted at rest, full contrast while the field has focus or a value. */\nfunction SearchGlyph() {\n return (\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"size-4.5 flex-none text-muted-foreground motion-safe:transition-colors motion-safe:duration-(--duration-quick) motion-safe:ease-settle group-data-[filled]/shell:text-foreground group-data-[focus-within]/shell:text-foreground\"\n >\n <circle cx=\"11\" cy=\"11\" r=\"8\" />\n <path d=\"m21 21-4.34-4.34\" />\n </svg>\n );\n}\n\n/**\n * The clear button, in a box that is always there so the field never changes\n * width. It scales in on the snappy spring while it fades and unblurs, and\n * leaves a little smaller. Nothing animates if it is there on first mount, and\n * while it leaves it is inert so it cannot be pressed or focused.\n */\nfunction ClearButton({\n show,\n label,\n disabled,\n onClear,\n}: {\n show: boolean;\n label: string;\n disabled: boolean;\n onClear: () => void;\n}) {\n const [initial] = useState(show);\n const [rendered, setRendered] = useState(show);\n const [animated, setAnimated] = useState(false);\n const leaving = rendered && !show;\n if (show && !rendered) setRendered(true);\n if (!animated && show !== initial) setAnimated(true);\n const unmount = useCallback(() => setRendered(false), []);\n useExitBackstop(leaving, unmount);\n // Reduced motion removes it at once: an animation that is switched off never ends.\n useIsomorphicLayoutEffect(() => {\n if (leaving && prefersReducedMotion()) unmount();\n }, [leaving, unmount]);\n if (!rendered) return null;\n return (\n <span\n ref={(node) => {\n if (!node) return;\n if (leaving) node.setAttribute(\"inert\", \"\");\n else node.removeAttribute(\"inert\");\n }}\n aria-hidden={leaving || undefined}\n onAnimationEnd={(event) => {\n if (\n leaving &&\n event.target === event.currentTarget &&\n event.animationName === \"field-clear-out\"\n )\n unmount();\n }}\n className={cn(\n \"grid size-6 place-items-center\",\n leaving\n ? \"pointer-events-none motion-safe:animate-field-clear-out\"\n : animated && \"motion-safe:animate-field-clear-in motion-reduce:animate-fade-in\",\n )}\n >\n <AriaButton\n type=\"button\"\n isDisabled={disabled}\n aria-label={label}\n onPress={onClear}\n className={clearClass}\n >\n <svg\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={2}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n className=\"size-4\"\n >\n <path d=\"M18 6 6 18\" />\n <path d=\"m6 6 12 12\" />\n </svg>\n </AriaButton>\n </span>\n );\n}\n\nexport interface SearchFieldProps extends Omit<\n AriaInputProps,\n \"className\" | \"style\" | \"size\" | \"type\" | \"value\" | \"defaultValue\" | \"onChange\"\n> {\n /** Extra classes for the input. Merged last, so they win conflicts with ours. */\n className?: string;\n /**\n * What the field searches, and its accessible name. Visible above the box,\n * tied to the input with `htmlFor`, unless `labelHidden` is set.\n */\n label: string;\n /**\n * Hide the label and keep it as the input's accessible name (`aria-label`),\n * for a toolbar search whose purpose the surrounding UI already shows.\n * @default false\n */\n labelHidden?: boolean;\n /**\n * Control scale of the box. Height and inline padding come from the same\n * `--density-control-*` tokens as Input, Select and Button; `sm` also\n * takes Input's 14px text (16px on a touch screen).\n *\n * This REPLACES the native `size` attribute, which sets a width in\n * characters that the flex layout ignores.\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** The current text. Controlled: the field has no state of its own. */\n value: string;\n /** Called with the new text on every edit, and with `\"\"` when the clear button is pressed. */\n onValueChange: (value: string) => void;\n /** The clear button's accessible name. @default \"Clear search\" */\n clearLabel?: string;\n}\n\n/**\n * A search input with a clear button. The button has a reserved box, so the\n * field never changes width as it comes and goes; pressing it clears the text\n * and returns focus to the input. `onChange` is not accepted: use `onValueChange`.\n * Native input props apply to the input, and `disabled` also disables the button.\n *\n * @example\n * <SearchField label=\"Search contacts\" value={query} onValueChange={setQuery} />\n * <SearchField label=\"Search tickets\" labelHidden size=\"sm\" value={query} onValueChange={setQuery} />\n */\nexport const SearchField = forwardRef<HTMLInputElement, SearchFieldProps>(function SearchField(\n {\n label,\n labelHidden = false,\n size = \"md\",\n value,\n onValueChange,\n clearLabel = \"Clear search\",\n className,\n ...props\n },\n ref,\n) {\n const inputRef = useRef<HTMLInputElement | null>(null);\n const refs = useMemo(() => mergeRefs(ref, inputRef), [ref]);\n const field = useFieldWiring({\n enabled: true,\n id: props.id,\n describedBy: props[\"aria-describedby\"],\n invalid: props[\"aria-invalid\"],\n description: undefined,\n error: undefined,\n });\n const disabled = Boolean(props.disabled);\n\n const control = (\n <AriaGroup\n role=\"presentation\"\n isDisabled={disabled}\n data-filled={value ? \"true\" : undefined}\n data-slot=\"search-field-shell\"\n className={cn(fieldSurfaceVariants(), shellVariants({ size }))}\n >\n <SearchGlyph />\n <AriaInput\n ref={refs}\n // A hidden label is still the name, inside a TextField too (one with no Label child has no other name).\n // Before the spread, so an explicit aria-label wins as it does over a visible one.\n aria-label={labelHidden ? label : undefined}\n {...props}\n {...field.controlProps}\n type=\"search\"\n value={value}\n onChange={(event) => onValueChange(event.target.value)}\n data-slot=\"search-field\"\n className={cn(inputVariants({ size }), className)}\n />\n {/* A clear button's box is reserved, so the field never changes width when it appears or leaves. */}\n <span className=\"grid size-6 flex-none place-items-center\">\n <ClearButton\n show={value !== \"\"}\n label={clearLabel}\n disabled={disabled}\n onClear={() => {\n onValueChange(\"\");\n inputRef.current?.focus();\n }}\n />\n </span>\n </AriaGroup>\n );\n\n if (!field.active) return control;\n return (\n <FieldShell\n label={labelHidden ? null : label}\n controlId={field.controlId}\n description={undefined}\n hintId={undefined}\n error={undefined}\n errorId={undefined}\n >\n {control}\n </FieldShell>\n );\n});\n"],"mappings":"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"}
|
package/dist/select/select.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare const selectTriggerVariants: (props?: ({
|
|
|
30
30
|
size?: "sm" | "md" | "lg" | null | undefined;
|
|
31
31
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
32
32
|
export declare const selectPopoverVariants: (props?: ({
|
|
33
|
-
reveal?: "
|
|
33
|
+
reveal?: "fade" | "grow" | null | undefined;
|
|
34
34
|
} & import("class-variance-authority/types").ClassProp) | undefined) => string;
|
|
35
35
|
export declare const selectItemVariants: (props?: import("class-variance-authority/types").ClassProp | undefined) => string;
|
|
36
36
|
/**
|
|
@@ -79,6 +79,15 @@ interface SelectProps$1 extends Omit<SelectProps<object>, "className" | "style"
|
|
|
79
79
|
* @default "md"
|
|
80
80
|
*/
|
|
81
81
|
size?: "sm" | "md" | "lg";
|
|
82
|
+
/**
|
|
83
|
+
* The field must be chosen. React Aria's own prop, documented here because
|
|
84
|
+
* it now also draws the required marker after the label, the same one Input,
|
|
85
|
+
* Textarea and PasswordField show. The hidden native select is `required`,
|
|
86
|
+
* so native form validation sees it, and the root gets `data-required`. The
|
|
87
|
+
* marker is aria-hidden, so the trigger's name stays the label's text.
|
|
88
|
+
* @default false
|
|
89
|
+
*/
|
|
90
|
+
isRequired?: boolean;
|
|
82
91
|
/** Extra classes for the wrapper. Merged last. */
|
|
83
92
|
className?: string;
|
|
84
93
|
}
|
package/dist/select/select.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { cn, cnAll } from "../lib/cn.js";
|
|
3
3
|
import { useIsomorphicLayoutEffect } from "../lib/motion.js";
|
|
4
|
-
import { fieldSurfaceVariants } from "../lib/field-surface.js";
|
|
4
|
+
import { fieldBoxSize, fieldSurfaceVariants, fieldTextSize } from "../lib/field-surface.js";
|
|
5
5
|
import { labelVariants } from "../label/label.js";
|
|
6
|
+
import { RequiredMarker } from "../lib/required-marker.js";
|
|
6
7
|
import { RollingValue } from "../lib/rolling-value.js";
|
|
7
8
|
import { cva } from "class-variance-authority";
|
|
8
9
|
import { forwardRef, useContext, useState } from "react";
|
|
@@ -34,6 +35,8 @@ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
|
34
35
|
* not `accent`, for the same reason.
|
|
35
36
|
*/
|
|
36
37
|
const selectVariants = cva(["group flex w-full flex-col gap-1.5 box-border font-sans"]);
|
|
38
|
+
/** The error line under a Select-based control (Select, TimePicker): one class, so they cannot drift. */
|
|
39
|
+
const selectErrorClass = "text-sm leading-(--leading-normal) text-destructive-strong";
|
|
37
40
|
const selectTriggerVariants = cva([
|
|
38
41
|
"group-data-[invalid]:border-destructive-strong",
|
|
39
42
|
fieldSurfaceVariants(),
|
|
@@ -61,9 +64,9 @@ const selectTriggerVariants = cva([
|
|
|
61
64
|
* equal to the field beside it.
|
|
62
65
|
*/
|
|
63
66
|
size: {
|
|
64
|
-
sm:
|
|
65
|
-
md:
|
|
66
|
-
lg:
|
|
67
|
+
sm: [fieldBoxSize.sm, fieldTextSize.sm],
|
|
68
|
+
md: [fieldBoxSize.md, fieldTextSize.md],
|
|
69
|
+
lg: [fieldBoxSize.lg, fieldTextSize.lg]
|
|
67
70
|
} },
|
|
68
71
|
defaultVariants: { size: "md" }
|
|
69
72
|
});
|
|
@@ -157,10 +160,10 @@ const Select$1 = forwardRef(function Select$2({ children, label, labelHidden, pl
|
|
|
157
160
|
"data-slot": "select",
|
|
158
161
|
className: cn(selectVariants(), className),
|
|
159
162
|
children: [
|
|
160
|
-
/* @__PURE__ */
|
|
163
|
+
/* @__PURE__ */ jsxs(Label, {
|
|
161
164
|
"data-slot": "select-label",
|
|
162
165
|
className: cnAll(labelVariants(), labelHidden && "sr-only"),
|
|
163
|
-
children: label
|
|
166
|
+
children: [label, props.isRequired ? /* @__PURE__ */ jsx(RequiredMarker, {}) : null]
|
|
164
167
|
}),
|
|
165
168
|
/* @__PURE__ */ jsxs(Button, {
|
|
166
169
|
ref,
|
|
@@ -180,7 +183,7 @@ const Select$1 = forwardRef(function Select$2({ children, label, labelHidden, pl
|
|
|
180
183
|
}),
|
|
181
184
|
errorMessage == null ? null : /* @__PURE__ */ jsx(FieldError, {
|
|
182
185
|
"data-slot": "select-error",
|
|
183
|
-
className:
|
|
186
|
+
className: selectErrorClass,
|
|
184
187
|
children: errorMessage
|
|
185
188
|
}),
|
|
186
189
|
/* @__PURE__ */ jsx(Popover, {
|
|
@@ -217,6 +220,6 @@ const SelectItem = forwardRef(function SelectItem({ className, children, textVal
|
|
|
217
220
|
});
|
|
218
221
|
});
|
|
219
222
|
//#endregion
|
|
220
|
-
export { Select$1 as Select, SelectItem, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants };
|
|
223
|
+
export { Select$1 as Select, SelectItem, selectErrorClass, selectItemVariants, selectPopoverVariants, selectTriggerVariants, selectVariants };
|
|
221
224
|
|
|
222
225
|
//# sourceMappingURL=select.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"select.js","names":["AriaSelectValue","Select","AriaSelect","AriaLabel","AriaButton","AriaFieldError","AriaPopover","AriaListBox","AriaListBoxItem"],"sources":["../../src/select/select.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useContext, useState, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n FieldError as AriaFieldError,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxItem as AriaListBoxItem,\n Popover as AriaPopover,\n Select as AriaSelect,\n SelectStateContext,\n SelectValue as AriaSelectValue,\n type ListBoxItemProps as AriaListBoxItemProps,\n type SelectProps as AriaSelectProps,\n} from \"react-aria-components\";\n\nimport { labelVariants } from \"../label/label.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { fieldSurfaceVariants } from \"../lib/field-surface.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { RollingValue } from \"../lib/rolling-value.js\";\n\n/**\n * Select, ported from the legacy shadcn/Radix component.\n *\n * FOUR PARTS COLLAPSE INTO ONE. The census reads `SelectItem` 35 and\n * `Select`, `SelectTrigger`, `SelectValue`, `SelectContent` 15 each --\n * those four always appear together in the same shape, exactly like\n * Dialog's header trio, so they are one component and a prop.\n * `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`\n * and `SelectScrollDownButton` have ZERO call sites and are not ported.\n *\n * `label` IS REQUIRED. The legacy exports a `SelectLabel` that nobody\n * renders and passes no `aria-label` anywhere, so all 15 selects are\n * unnamed comboboxes -- the same finding as Progress, Slider and\n * ScrollArea. Pass `labelHidden` when the surrounding UI already says it.\n *\n * The trigger reuses `fieldSurfaceVariants`, shared with Input and\n * Textarea. A select and a text field sit in the same form row and must\n * be the same box; the legacy styled them separately and drifted --\n * `h-10` here against Input's `h-9`, for no stated reason.\n *\n * The popover is `card`, not a `popover` role: `--popover` is\n * byte-identical to `--card` in the legacy theme. Item hover is `muted`,\n * not `accent`, for the same reason.\n */\n/*\n * `group` is load-bearing, not decoration. React Aria puts `data-invalid` on\n * THIS element, never on the trigger -- so the trigger's own\n * `data-[invalid]:` rule, inherited from the shared field surface, can never\n * match and `isInvalid` painted nothing at all. The trigger reads the state\n * from here instead.\n */\nexport const selectVariants = cva([\"group flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\nexport const selectTriggerVariants = cva(\n [\n // See selectVariants: the invalid state lives on the ROOT, so this reads it\n // from the group rather than from itself.\n \"group-data-[invalid]:border-destructive-strong\",\n fieldSurfaceVariants(),\n // While the menu is open the trigger is pressed and focus has moved into the\n // list, so the border keeps the darkness it had instead of falling back.\n \"[&:where([data-pressed])]:border-border-strong\",\n // The fill follows the border: hovered, pressed and open all read as one\n // state. Ordinary variants, not :where(): the base background is a plain\n // class, so a zero-specificity rule would lose to it. Nothing else paints\n // the background, and a disabled trigger is never hovered or pressed.\n \"data-[hovered]:not-data-[disabled]:bg-muted\",\n \"data-[pressed]:bg-muted\",\n \"group/trigger\",\n \"flex items-center justify-between gap-2 text-start\",\n \"leading-(--leading-normal) cursor-pointer\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n ],\n {\n variants: {\n /**\n * The same rungs and the same tokens as Input and Button. 14 of the\n * product's 32 `SelectTrigger` call sites override the height by hand,\n * and a Select sits in the same form row as a field -- giving Input the\n * axis without giving it here would be exactly the drift\n * `fieldSurfaceVariants` exists to prevent.\n *\n * Type matches Input's at every rung: 14px on `sm` (16px on a touch\n * screen), 16px on `md` and `lg`. A row whose two controls disagree\n * about type size reads as a mistake. The trigger is a button, so iOS\n * would not zoom on it; the coarse-pointer rule is there to keep it\n * equal to the field beside it.\n */\n size: {\n sm: \"h-(--density-control-h-sm) px-(--density-control-pad-sm) text-sm pointer-coarse:text-base\",\n md: \"h-(--density-control-h-md) px-(--density-control-pad-md) text-base\",\n lg: \"h-(--density-control-h-lg) px-(--density-control-pad-lg) text-base\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport const selectPopoverVariants = cva(\n [\n // --trigger-width is set inline by React Aria, so the list matches the\n // control it came from rather than guessing a width.\n \"w-(--trigger-width) min-w-32 overflow-y-auto\",\n // Grows from the trigger edge: offset toward it, a little small and clipped to\n // the trigger's height, then opening to full height; 120ms out. React Aria\n // marks the first frame data-entering and the leaving one data-exiting and\n // keeps the list mounted until the animation ends.\n \"[--menu-x:0px] [--menu-y:-6px] origin-top\",\n // The panel also GROWS from the trigger's height: --menu-from is where the clip\n // starts, --menu-flip 1 grows it from the bottom edge when it opens upward.\n \"[--menu-from:var(--density-control-h-md)] [--menu-flip:0]\",\n \"data-[placement=top]:[--menu-y:6px] data-[placement=top]:origin-bottom data-[placement=top]:[--menu-flip:1]\",\n \"data-[placement=left]:[--menu-x:6px] data-[placement=left]:[--menu-y:0px] data-[placement=left]:origin-right\",\n \"data-[placement=right]:[--menu-x:-6px] data-[placement=right]:[--menu-y:0px] data-[placement=right]:origin-left\",\n \"motion-reduce:data-[entering]:animate-select-fade-in\",\n \"motion-safe:data-[exiting]:animate-select-popover-out motion-reduce:data-[exiting]:animate-select-fade-out\",\n \"data-[exiting]:pointer-events-none\",\n \"rounded-panel border-(length:--border-width) border-border p-1\",\n \"bg-card text-card-foreground shadow-floating\",\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n /**\n * `grow` opens the panel from the trigger's height on the smooth spring (about 700ms). `fade` is the quick 240ms\n * settle for a list whose first row is a search field: a clipped panel would hide it while it grows.\n */\n reveal: {\n grow: \"motion-safe:data-[entering]:animate-select-popover-reveal\",\n fade: \"motion-safe:data-[entering]:animate-select-popover-in\",\n },\n },\n defaultVariants: { reveal: \"grow\" },\n },\n);\n\nexport const selectItemVariants = cva([\n \"relative flex cursor-pointer select-none items-center gap-2\",\n // The same 18px as a Menu row: the hover fill is a soft pill inside the panel.\n \"rounded-control py-1.5 pe-8 ps-2\",\n \"box-border text-sm leading-(--leading-normal) text-card-foreground\",\n // data-focused, not :hover. React Aria drives one highlight from both\n // pointer and keyboard, so the row under the mouse and the row the\n // arrows reached are the same row.\n \"outline-none data-[focused]:bg-muted\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-instant) motion-safe:ease-settle\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n]);\n\n/**\n * A single-choice picker.\n *\n * @example\n * <Select label=\"Country\" placeholder=\"Pick one\"\n * selectedKey={code} onSelectionChange={setCode}>\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n * <SelectItem id=\"us\">United States</SelectItem>\n * </Select>\n */\nexport interface SelectProps extends Omit<\n AriaSelectProps<object>,\n \"className\" | \"style\" | \"children\" | \"items\"\n> {\n /** `SelectItem` children. */\n children?: ReactNode;\n /**\n * What is being chosen, and the control's accessible name.\n *\n * **Required.** All 15 legacy selects are unnamed: the legacy exports a\n * `SelectLabel` that nobody renders and passes no `aria-label`.\n */\n label: ReactNode;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /** Shown while nothing is selected. */\n placeholder?: string;\n /**\n * Why the selection is wrong. Rendered under the control, and it is what\n * makes `isInvalid` reach a screen reader.\n *\n * React Aria marks the ROOT `data-invalid` and leaves the button untouched,\n * and an `aria-invalid` handed to that button is dropped by\n * `filterDOMProps`, whose allowlist stops at the labelling attributes -- the\n * same silent loss as `aria-current` on a pagination button. So without a\n * message, an invalid select is invalid only in colour, which is WCAG 1.4.1\n * on its own. React Aria wires this into `aria-describedby` for us.\n */\n errorMessage?: ReactNode;\n /**\n * Control scale of the trigger. Height and inline padding come from the\n * same `--density-control-*` tokens as Input and Button, so a Select, a\n * field and a button on one rung are the same height.\n *\n * Type does not scale with it, matching Input.\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n}\n\n/**\n * The shown value. React Aria's own `SelectValue` stays, visually hidden, as the\n * text a screen reader reads; the copy the eye sees rolls when the selection\n * changes: a later option rises from below, an earlier one drops from above. The\n * direction is read from the option's place in the list. A pre-selected value\n * does not animate on first mount.\n */\nfunction ValueRoll({ placeholder }: { placeholder?: string }) {\n const state = useContext(SelectStateContext);\n const key = state?.selectedKey ?? null;\n let index = -1;\n if (state && key != null) {\n let n = 0;\n for (const item of state.collection) {\n if (item.type !== \"item\") continue;\n if (item.key === key) index = n;\n n += 1;\n }\n }\n const [previous, setPrevious] = useState(index);\n const [dir, setDir] = useState(1);\n if (previous !== index) {\n setPrevious(index);\n setDir(index > previous ? 1 : -1);\n }\n const text = state?.selectedItem?.textValue ?? \"\";\n // React Aria resolves a default selection after the first render, so the key\n // starts as null and then settles. Until the collection is built nothing is\n // shown, and the swap remounts once it is, so that settling is not a change.\n const built = (state?.collection.size ?? 0) > 0 || state?.selectedKey != null;\n const [mounted, setMounted] = useState(false);\n useIsomorphicLayoutEffect(() => setMounted(true), []);\n const settled = built || mounted;\n const swapKey = key == null ? \"placeholder\" : `value-${String(key)}`;\n return (\n <RollingValue\n srValue={\n <AriaSelectValue data-slot=\"select-value\" className=\"sr-only\">\n {({ isPlaceholder, selectedText }) => (isPlaceholder ? placeholder : selectedText)}\n </AriaSelectValue>\n }\n swapKey={swapKey}\n dir={dir}\n instanceKey={settled ? \"live\" : \"boot\"}\n >\n {key == null ? (\n <span data-slot=\"select-placeholder\" className=\"text-muted-foreground\">\n {placeholder}\n </span>\n ) : (\n text\n )}\n </RollingValue>\n );\n}\n\nexport const Select = forwardRef<HTMLButtonElement, SelectProps>(function Select(\n { children, label, labelHidden, placeholder, errorMessage, size = \"md\", className, ...props },\n ref,\n) {\n return (\n <AriaSelect {...props} data-slot=\"select\" className={cn(selectVariants(), className)}>\n {/*\n The Label's own classes, so a Select's label and the Label over an\n Input in the same form are one style (same size, weight, colour and\n line height). The select-label slot stays for consumers that target it.\n */}\n <AriaLabel\n data-slot=\"select-label\"\n className={cnAll(labelVariants(), labelHidden && \"sr-only\")}\n >\n {label}\n </AriaLabel>\n\n {/*\n The ref goes on the TRIGGER, not the AriaSelect root above. The root\n is a field-level wrapper div; the trigger is the always-visible\n interactive surface a caller actually wants to focus or click,\n the same reasoning that puts Input's ref on the real control.\n */}\n <AriaButton ref={ref} data-slot=\"select-trigger\" className={selectTriggerVariants({ size })}>\n <ValueRoll placeholder={placeholder} />\n {/*\n aria-hidden: the chevron is decoration. The button already\n announces itself as a combobox, and a second name source only\n makes it announce twice.\n */}\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-muted-foreground motion-safe:transition-[rotate] motion-safe:duration-(--duration-gentle) motion-safe:ease-gentle group-aria-expanded/trigger:rotate-180\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n </AriaButton>\n\n {errorMessage == null ? null : (\n // Not conditional on isInvalid: React Aria renders FieldError only\n // while the field IS invalid, so gating it here as well would just be\n // two sources of truth for one state.\n <AriaFieldError\n data-slot=\"select-error\"\n className=\"text-sm leading-(--leading-normal) text-destructive-strong\"\n >\n {errorMessage}\n </AriaFieldError>\n )}\n\n {/* No border colour on the list: it is lifted by its shadow alone. */}\n <AriaPopover\n data-slot=\"select-popover\"\n className={cn(selectPopoverVariants(), \"border-0\")}\n >\n <AriaListBox data-slot=\"select-list\" className=\"outline-none\">\n {children}\n </AriaListBox>\n </AriaPopover>\n </AriaSelect>\n );\n});\n\n/**\n * One option. Its `id` is the value.\n *\n * @example\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n */\nexport interface SelectItemProps extends Omit<\n AriaListBoxItemProps,\n \"className\" | \"style\" | \"children\"\n> {\n children?: ReactNode;\n /**\n * The option's text, for the trigger and for typeahead.\n *\n * Derived from `children` when they are a string. Pass it explicitly\n * when the option renders anything richer than text.\n */\n textValue?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(function SelectItem(\n { className, children, textValue, ...props },\n ref,\n) {\n return (\n <AriaListBoxItem\n ref={ref}\n {...props}\n /*\n textValue, derived from string children.\n React Aria infers an item's text from its children -- but ONLY\n when they are a string. This item uses FUNCTION children so the\n tick can be rendered in JS, which defeats that inference: the\n trigger showed an empty value after selecting, and typeahead had\n nothing to match on. Neither is visible in the type system.\n */\n textValue={textValue ?? (typeof children === \"string\" ? children : undefined)}\n data-slot=\"select-item\"\n className={cn(selectItemVariants(), className)}\n >\n {({ isSelected }) => (\n <>\n {/*\n The tick is rendered in JS rather than toggled with CSS, so\n the precedence is explicit and a test can read it from the\n DOM. ps-8 above reserves the gutter, so selecting an option\n does not shift its label.\n */}\n {isSelected ? (\n <svg\n data-slot=\"select-item-check\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"absolute end-2 size-3.5 motion-safe:animate-select-check-in\"\n >\n <path d=\"m3.5 8.5 3 3 6-6\" />\n </svg>\n ) : null}\n {children}\n </>\n )}\n </AriaListBoxItem>\n );\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"select.js","names":["AriaSelectValue","Select","AriaSelect","AriaLabel","AriaButton","AriaFieldError","AriaPopover","AriaListBox","AriaListBoxItem"],"sources":["../../src/select/select.tsx"],"sourcesContent":["\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport { forwardRef, useContext, useState, type ReactNode } from \"react\";\nimport {\n Button as AriaButton,\n FieldError as AriaFieldError,\n Label as AriaLabel,\n ListBox as AriaListBox,\n ListBoxItem as AriaListBoxItem,\n Popover as AriaPopover,\n Select as AriaSelect,\n SelectStateContext,\n SelectValue as AriaSelectValue,\n type ListBoxItemProps as AriaListBoxItemProps,\n type SelectProps as AriaSelectProps,\n} from \"react-aria-components\";\n\nimport { labelVariants } from \"../label/label.js\";\nimport { cn, cnAll } from \"../lib/cn.js\";\nimport { fieldBoxSize, fieldSurfaceVariants, fieldTextSize } from \"../lib/field-surface.js\";\nimport { RequiredMarker } from \"../lib/required-marker.js\";\nimport { useIsomorphicLayoutEffect } from \"../lib/motion.js\";\nimport { RollingValue } from \"../lib/rolling-value.js\";\n\n/**\n * Select, ported from the legacy shadcn/Radix component.\n *\n * FOUR PARTS COLLAPSE INTO ONE. The census reads `SelectItem` 35 and\n * `Select`, `SelectTrigger`, `SelectValue`, `SelectContent` 15 each --\n * those four always appear together in the same shape, exactly like\n * Dialog's header trio, so they are one component and a prop.\n * `SelectGroup`, `SelectLabel`, `SelectSeparator`, `SelectScrollUpButton`\n * and `SelectScrollDownButton` have ZERO call sites and are not ported.\n *\n * `label` IS REQUIRED. The legacy exports a `SelectLabel` that nobody\n * renders and passes no `aria-label` anywhere, so all 15 selects are\n * unnamed comboboxes -- the same finding as Progress, Slider and\n * ScrollArea. Pass `labelHidden` when the surrounding UI already says it.\n *\n * The trigger reuses `fieldSurfaceVariants`, shared with Input and\n * Textarea. A select and a text field sit in the same form row and must\n * be the same box; the legacy styled them separately and drifted --\n * `h-10` here against Input's `h-9`, for no stated reason.\n *\n * The popover is `card`, not a `popover` role: `--popover` is\n * byte-identical to `--card` in the legacy theme. Item hover is `muted`,\n * not `accent`, for the same reason.\n */\n/*\n * `group` is load-bearing, not decoration. React Aria puts `data-invalid` on\n * THIS element, never on the trigger -- so the trigger's own\n * `data-[invalid]:` rule, inherited from the shared field surface, can never\n * match and `isInvalid` painted nothing at all. The trigger reads the state\n * from here instead.\n */\nexport const selectVariants = cva([\"group flex w-full flex-col gap-1.5 box-border font-sans\"]);\n\n/** The error line under a Select-based control (Select, TimePicker): one class, so they cannot drift. */\nexport const selectErrorClass = \"text-sm leading-(--leading-normal) text-destructive-strong\";\n\nexport const selectTriggerVariants = cva(\n [\n // See selectVariants: the invalid state lives on the ROOT, so this reads it\n // from the group rather than from itself.\n \"group-data-[invalid]:border-destructive-strong\",\n fieldSurfaceVariants(),\n // While the menu is open the trigger is pressed and focus has moved into the\n // list, so the border keeps the darkness it had instead of falling back.\n \"[&:where([data-pressed])]:border-border-strong\",\n // The fill follows the border: hovered, pressed and open all read as one\n // state. Ordinary variants, not :where(): the base background is a plain\n // class, so a zero-specificity rule would lose to it. Nothing else paints\n // the background, and a disabled trigger is never hovered or pressed.\n \"data-[hovered]:not-data-[disabled]:bg-muted\",\n \"data-[pressed]:bg-muted\",\n \"group/trigger\",\n \"flex items-center justify-between gap-2 text-start\",\n \"leading-(--leading-normal) cursor-pointer\",\n \"[&_svg]:pointer-events-none [&_svg]:shrink-0\",\n \"[&_svg:not([class*='size-'])]:size-(--icon-sm)\",\n ],\n {\n variants: {\n /**\n * The same rungs and the same tokens as Input and Button. 14 of the\n * product's 32 `SelectTrigger` call sites override the height by hand,\n * and a Select sits in the same form row as a field -- giving Input the\n * axis without giving it here would be exactly the drift\n * `fieldSurfaceVariants` exists to prevent.\n *\n * Type matches Input's at every rung: 14px on `sm` (16px on a touch\n * screen), 16px on `md` and `lg`. A row whose two controls disagree\n * about type size reads as a mistake. The trigger is a button, so iOS\n * would not zoom on it; the coarse-pointer rule is there to keep it\n * equal to the field beside it.\n */\n size: {\n sm: [fieldBoxSize.sm, fieldTextSize.sm],\n md: [fieldBoxSize.md, fieldTextSize.md],\n lg: [fieldBoxSize.lg, fieldTextSize.lg],\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport const selectPopoverVariants = cva(\n [\n // --trigger-width is set inline by React Aria, so the list matches the\n // control it came from rather than guessing a width.\n \"w-(--trigger-width) min-w-32 overflow-y-auto\",\n // Grows from the trigger edge: offset toward it, a little small and clipped to\n // the trigger's height, then opening to full height; 120ms out. React Aria\n // marks the first frame data-entering and the leaving one data-exiting and\n // keeps the list mounted until the animation ends.\n \"[--menu-x:0px] [--menu-y:-6px] origin-top\",\n // The panel also GROWS from the trigger's height: --menu-from is where the clip\n // starts, --menu-flip 1 grows it from the bottom edge when it opens upward.\n \"[--menu-from:var(--density-control-h-md)] [--menu-flip:0]\",\n \"data-[placement=top]:[--menu-y:6px] data-[placement=top]:origin-bottom data-[placement=top]:[--menu-flip:1]\",\n \"data-[placement=left]:[--menu-x:6px] data-[placement=left]:[--menu-y:0px] data-[placement=left]:origin-right\",\n \"data-[placement=right]:[--menu-x:-6px] data-[placement=right]:[--menu-y:0px] data-[placement=right]:origin-left\",\n \"motion-reduce:data-[entering]:animate-select-fade-in\",\n \"motion-safe:data-[exiting]:animate-select-popover-out motion-reduce:data-[exiting]:animate-select-fade-out\",\n \"data-[exiting]:pointer-events-none\",\n \"rounded-panel border-(length:--border-width) border-border p-1\",\n \"bg-card text-card-foreground shadow-floating\",\n \"box-border font-sans text-sm leading-(--leading-normal)\",\n ],\n {\n variants: {\n /**\n * `grow` opens the panel from the trigger's height on the smooth spring (about 700ms). `fade` is the quick 240ms\n * settle for a list whose first row is a search field: a clipped panel would hide it while it grows.\n */\n reveal: {\n grow: \"motion-safe:data-[entering]:animate-select-popover-reveal\",\n fade: \"motion-safe:data-[entering]:animate-select-popover-in\",\n },\n },\n defaultVariants: { reveal: \"grow\" },\n },\n);\n\nexport const selectItemVariants = cva([\n \"relative flex cursor-pointer select-none items-center gap-2\",\n // The same 18px as a Menu row: the hover fill is a soft pill inside the panel.\n \"rounded-control py-1.5 pe-8 ps-2\",\n \"box-border text-sm leading-(--leading-normal) text-card-foreground\",\n // data-focused, not :hover. React Aria drives one highlight from both\n // pointer and keyboard, so the row under the mouse and the row the\n // arrows reached are the same row.\n \"outline-none data-[focused]:bg-muted\",\n \"motion-safe:transition-colors motion-safe:duration-(--duration-instant) motion-safe:ease-settle\",\n \"data-[focus-visible]:outline-solid\",\n \"data-[focus-visible]:outline-(length:--focus-ring-width)\",\n \"data-[focus-visible]:outline-offset-(--focus-ring-offset)\",\n \"data-[focus-visible]:outline-(--color-ring)\",\n \"data-[disabled]:cursor-not-allowed data-[disabled]:opacity-(--opacity-disabled)\",\n]);\n\n/**\n * A single-choice picker.\n *\n * @example\n * <Select label=\"Country\" placeholder=\"Pick one\"\n * selectedKey={code} onSelectionChange={setCode}>\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n * <SelectItem id=\"us\">United States</SelectItem>\n * </Select>\n */\nexport interface SelectProps extends Omit<\n AriaSelectProps<object>,\n \"className\" | \"style\" | \"children\" | \"items\"\n> {\n /** `SelectItem` children. */\n children?: ReactNode;\n /**\n * What is being chosen, and the control's accessible name.\n *\n * **Required.** All 15 legacy selects are unnamed: the legacy exports a\n * `SelectLabel` that nobody renders and passes no `aria-label`.\n */\n label: ReactNode;\n /** Render the label for screen readers only. */\n labelHidden?: boolean;\n /** Shown while nothing is selected. */\n placeholder?: string;\n /**\n * Why the selection is wrong. Rendered under the control, and it is what\n * makes `isInvalid` reach a screen reader.\n *\n * React Aria marks the ROOT `data-invalid` and leaves the button untouched,\n * and an `aria-invalid` handed to that button is dropped by\n * `filterDOMProps`, whose allowlist stops at the labelling attributes -- the\n * same silent loss as `aria-current` on a pagination button. So without a\n * message, an invalid select is invalid only in colour, which is WCAG 1.4.1\n * on its own. React Aria wires this into `aria-describedby` for us.\n */\n errorMessage?: ReactNode;\n /**\n * Control scale of the trigger. Height and inline padding come from the\n * same `--density-control-*` tokens as Input and Button, so a Select, a\n * field and a button on one rung are the same height.\n *\n * Type does not scale with it, matching Input.\n *\n * @default \"md\"\n */\n size?: \"sm\" | \"md\" | \"lg\";\n /**\n * The field must be chosen. React Aria's own prop, documented here because\n * it now also draws the required marker after the label, the same one Input,\n * Textarea and PasswordField show. The hidden native select is `required`,\n * so native form validation sees it, and the root gets `data-required`. The\n * marker is aria-hidden, so the trigger's name stays the label's text.\n * @default false\n */\n isRequired?: boolean;\n /** Extra classes for the wrapper. Merged last. */\n className?: string;\n}\n\n/**\n * The shown value. React Aria's own `SelectValue` stays, visually hidden, as the\n * text a screen reader reads; the copy the eye sees rolls when the selection\n * changes: a later option rises from below, an earlier one drops from above. The\n * direction is read from the option's place in the list. A pre-selected value\n * does not animate on first mount.\n */\nfunction ValueRoll({ placeholder }: { placeholder?: string }) {\n const state = useContext(SelectStateContext);\n const key = state?.selectedKey ?? null;\n let index = -1;\n if (state && key != null) {\n let n = 0;\n for (const item of state.collection) {\n if (item.type !== \"item\") continue;\n if (item.key === key) index = n;\n n += 1;\n }\n }\n const [previous, setPrevious] = useState(index);\n const [dir, setDir] = useState(1);\n if (previous !== index) {\n setPrevious(index);\n setDir(index > previous ? 1 : -1);\n }\n const text = state?.selectedItem?.textValue ?? \"\";\n // React Aria resolves a default selection after the first render, so the key\n // starts as null and then settles. Until the collection is built nothing is\n // shown, and the swap remounts once it is, so that settling is not a change.\n const built = (state?.collection.size ?? 0) > 0 || state?.selectedKey != null;\n const [mounted, setMounted] = useState(false);\n useIsomorphicLayoutEffect(() => setMounted(true), []);\n const settled = built || mounted;\n const swapKey = key == null ? \"placeholder\" : `value-${String(key)}`;\n return (\n <RollingValue\n srValue={\n <AriaSelectValue data-slot=\"select-value\" className=\"sr-only\">\n {({ isPlaceholder, selectedText }) => (isPlaceholder ? placeholder : selectedText)}\n </AriaSelectValue>\n }\n swapKey={swapKey}\n dir={dir}\n instanceKey={settled ? \"live\" : \"boot\"}\n >\n {key == null ? (\n <span data-slot=\"select-placeholder\" className=\"text-muted-foreground\">\n {placeholder}\n </span>\n ) : (\n text\n )}\n </RollingValue>\n );\n}\n\nexport const Select = forwardRef<HTMLButtonElement, SelectProps>(function Select(\n { children, label, labelHidden, placeholder, errorMessage, size = \"md\", className, ...props },\n ref,\n) {\n return (\n <AriaSelect {...props} data-slot=\"select\" className={cn(selectVariants(), className)}>\n {/*\n The Label's own classes, so a Select's label and the Label over an\n Input in the same form are one style (same size, weight, colour and\n line height). The select-label slot stays for consumers that target it.\n */}\n <AriaLabel\n data-slot=\"select-label\"\n className={cnAll(labelVariants(), labelHidden && \"sr-only\")}\n >\n {label}\n {props.isRequired ? <RequiredMarker /> : null}\n </AriaLabel>\n\n {/*\n The ref goes on the TRIGGER, not the AriaSelect root above. The root\n is a field-level wrapper div; the trigger is the always-visible\n interactive surface a caller actually wants to focus or click,\n the same reasoning that puts Input's ref on the real control.\n */}\n <AriaButton ref={ref} data-slot=\"select-trigger\" className={selectTriggerVariants({ size })}>\n <ValueRoll placeholder={placeholder} />\n {/*\n aria-hidden: the chevron is decoration. The button already\n announces itself as a combobox, and a second name source only\n makes it announce twice.\n */}\n <svg\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"1.75\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"text-muted-foreground motion-safe:transition-[rotate] motion-safe:duration-(--duration-gentle) motion-safe:ease-gentle group-aria-expanded/trigger:rotate-180\"\n >\n <path d=\"m4 6 4 4 4-4\" />\n </svg>\n </AriaButton>\n\n {errorMessage == null ? null : (\n // Not conditional on isInvalid: React Aria renders FieldError only\n // while the field IS invalid, so gating it here as well would just be\n // two sources of truth for one state.\n <AriaFieldError data-slot=\"select-error\" className={selectErrorClass}>\n {errorMessage}\n </AriaFieldError>\n )}\n\n {/* No border colour on the list: it is lifted by its shadow alone. */}\n <AriaPopover\n data-slot=\"select-popover\"\n className={cn(selectPopoverVariants(), \"border-0\")}\n >\n <AriaListBox data-slot=\"select-list\" className=\"outline-none\">\n {children}\n </AriaListBox>\n </AriaPopover>\n </AriaSelect>\n );\n});\n\n/**\n * One option. Its `id` is the value.\n *\n * @example\n * <SelectItem id=\"gb\">United Kingdom</SelectItem>\n */\nexport interface SelectItemProps extends Omit<\n AriaListBoxItemProps,\n \"className\" | \"style\" | \"children\"\n> {\n children?: ReactNode;\n /**\n * The option's text, for the trigger and for typeahead.\n *\n * Derived from `children` when they are a string. Pass it explicitly\n * when the option renders anything richer than text.\n */\n textValue?: string;\n /** Extra classes. Merged last, so they win conflicts with ours. */\n className?: string;\n}\n\nexport const SelectItem = forwardRef<HTMLDivElement, SelectItemProps>(function SelectItem(\n { className, children, textValue, ...props },\n ref,\n) {\n return (\n <AriaListBoxItem\n ref={ref}\n {...props}\n /*\n textValue, derived from string children.\n React Aria infers an item's text from its children -- but ONLY\n when they are a string. This item uses FUNCTION children so the\n tick can be rendered in JS, which defeats that inference: the\n trigger showed an empty value after selecting, and typeahead had\n nothing to match on. Neither is visible in the type system.\n */\n textValue={textValue ?? (typeof children === \"string\" ? children : undefined)}\n data-slot=\"select-item\"\n className={cn(selectItemVariants(), className)}\n >\n {({ isSelected }) => (\n <>\n {/*\n The tick is rendered in JS rather than toggled with CSS, so\n the precedence is explicit and a test can read it from the\n DOM. ps-8 above reserves the gutter, so selecting an option\n does not shift its label.\n */}\n {isSelected ? (\n <svg\n data-slot=\"select-item-check\"\n aria-hidden=\"true\"\n viewBox=\"0 0 16 16\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n className=\"absolute end-2 size-3.5 motion-safe:animate-select-check-in\"\n >\n <path d=\"m3.5 8.5 3 3 6-6\" />\n </svg>\n ) : null}\n {children}\n </>\n )}\n </AriaListBoxItem>\n );\n});\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,MAAa,iBAAiB,IAAI,CAAC,yDAAyD,CAAC;;AAG7F,MAAa,mBAAmB;AAEhC,MAAa,wBAAwB,IACnC;CAGE;CACA,qBAAqB;CAGrB;CAKA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GACA;CACE,UAAU;;;;;;;;;;;;;;AAcR,MAAM;EACJ,IAAI,CAAC,aAAa,IAAI,cAAc,EAAE;EACtC,IAAI,CAAC,aAAa,IAAI,cAAc,EAAE;EACtC,IAAI,CAAC,aAAa,IAAI,cAAc,EAAE;CACxC,EACF;CACA,iBAAiB,EAAE,MAAM,KAAK;AAChC,CACF;AAEA,MAAa,wBAAwB,IACnC;CAGE;CAKA;CAGA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,GACA;CACE,UAAU;;;;;AAKR,QAAQ;EACN,MAAM;EACN,MAAM;CACR,EACF;CACA,iBAAiB,EAAE,QAAQ,OAAO;AACpC,CACF;AAEA,MAAa,qBAAqB,IAAI;CACpC;CAEA;CACA;CAIA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;;AAuED,SAAS,UAAU,EAAE,eAAyC;CAC5D,MAAM,QAAQ,WAAW,kBAAkB;CAC3C,MAAM,MAAM,OAAO,eAAe;CAClC,IAAI,QAAQ;CACZ,IAAI,SAAS,OAAO,MAAM;EACxB,IAAI,IAAI;EACR,KAAK,MAAM,QAAQ,MAAM,YAAY;GACnC,IAAI,KAAK,SAAS,QAAQ;GAC1B,IAAI,KAAK,QAAQ,KAAK,QAAQ;GAC9B,KAAK;EACP;CACF;CACA,MAAM,CAAC,UAAU,eAAe,SAAS,KAAK;CAC9C,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC;CAChC,IAAI,aAAa,OAAO;EACtB,YAAY,KAAK;EACjB,OAAO,QAAQ,WAAW,IAAI,EAAE;CAClC;CACA,MAAM,OAAO,OAAO,cAAc,aAAa;CAI/C,MAAM,SAAS,OAAO,WAAW,QAAQ,KAAK,KAAK,OAAO,eAAe;CACzE,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,gCAAgC,WAAW,IAAI,GAAG,CAAC,CAAC;CACpD,MAAM,UAAU,SAAS;CACzB,MAAM,UAAU,OAAO,OAAO,gBAAgB,SAAS,OAAO,GAAG;CACjE,OACE,oBAAC,cAAD;EACE,SACE,oBAACA,aAAD;GAAiB,aAAU;GAAe,WAAU;GAChD,WAAA,EAAE,eAAe,mBAAoB,gBAAgB,cAAc;EACtD,CAAA;EAEV;EACJ;EACL,aAAa,UAAU,SAAS;EAE/B,UAAA,OAAO,OACN,oBAAC,QAAD;GAAM,aAAU;GAAqB,WAAU;GAC5C,UAAA;EACG,CAAA,IAEN;CAEU,CAAA;AAElB;AAEA,MAAaC,WAAS,WAA2C,SAASA,SACxE,EAAE,UAAU,OAAO,aAAa,aAAa,cAAc,OAAO,MAAM,WAAW,GAAG,SACtF,KACA;CACA,OACE,qBAACC,QAAD;EAAY,GAAI;EAAO,aAAU;EAAS,WAAW,GAAG,eAAe,GAAG,SAAS;EAAnF,UAAA;GAME,qBAACC,OAAD;IACE,aAAU;IACV,WAAW,MAAM,cAAc,GAAG,eAAe,SAAS;IAF5D,UAAA,CAIG,OACA,MAAM,aAAa,oBAAC,gBAAD,CAAiB,CAAA,IAAI,IAChC;;GAQX,qBAACC,QAAD;IAAiB;IAAK,aAAU;IAAiB,WAAW,sBAAsB,EAAE,KAAK,CAAC;IAA1F,UAAA,CACE,oBAAC,WAAD,EAAwB,YAAc,CAAA,GAMtC,oBAAC,OAAD;KACE,eAAY;KACZ,SAAQ;KACR,MAAK;KACL,QAAO;KACP,aAAY;KACZ,eAAc;KACd,gBAAe;KACf,WAAU;KAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,eAAgB,CAAA;IACrB,CAAA,CACK;;GAEX,gBAAgB,OAAO,OAItB,oBAACC,YAAD;IAAgB,aAAU;IAAe,WAAW;IACjD,UAAA;GACa,CAAA;GAIlB,oBAACC,SAAD;IACE,aAAU;IACV,WAAW,GAAG,sBAAsB,GAAG,UAAU;IAEjD,UAAA,oBAACC,SAAD;KAAa,aAAU;KAAc,WAAU;KAC5C;IACU,CAAA;GACF,CAAA;EACH;;AAEhB,CAAC;AAwBD,MAAa,aAAa,WAA4C,SAAS,WAC7E,EAAE,WAAW,UAAU,WAAW,GAAG,SACrC,KACA;CACA,OACE,oBAACC,aAAD;EACO;EACL,GAAI;EASJ,WAAW,cAAc,OAAO,aAAa,WAAW,WAAW,KAAA;EACnE,aAAU;EACV,WAAW,GAAG,mBAAmB,GAAG,SAAS;EAE3C,WAAA,EAAE,iBACF,qBAAA,YAAA,EAAA,UAAA,CAOG,aACC,oBAAC,OAAD;GACE,aAAU;GACV,eAAY;GACZ,SAAQ;GACR,MAAK;GACL,QAAO;GACP,aAAY;GACZ,eAAc;GACd,gBAAe;GACf,WAAU;GAEV,UAAA,oBAAC,QAAD,EAAM,GAAE,mBAAoB,CAAA;EACzB,CAAA,IACH,MACH,QACD,EAAA,CAAA;CAEW,CAAA;AAErB,CAAC"}
|