@windmorph/react 0.1.2 → 0.2.1
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/dist/components/date-picker/date-picker-styles.d.ts +9 -9
- package/dist/components/dialog/Dialog.d.ts +4 -2
- package/dist/components/dialog/Dialog.js +2 -2
- package/dist/components/dialog/Dialog.js.map +1 -1
- package/dist/components/dialog/dialog-styles.js +5 -5
- package/dist/components/dialog/dialog-styles.js.map +1 -1
- package/dist/components/icon-button/IconButton.d.ts +3 -0
- package/dist/components/icon-button/IconButton.js +3 -0
- package/dist/components/icon-button/IconButton.js.map +1 -1
- package/dist/components/menu/use-reveal-entering.d.ts +19 -0
- package/dist/components/navigation/NavItem.js +1 -1
- package/dist/components/navigation/NavItem.js.map +1 -1
- package/dist/components/navigation/NavigationRail.d.ts +21 -2
- package/dist/components/navigation/NavigationRail.js +18 -4
- package/dist/components/navigation/NavigationRail.js.map +1 -1
- package/dist/components/navigation/nav-item-styles.js +4 -4
- package/dist/components/navigation/nav-item-styles.js.map +1 -1
- package/dist/components/navigation/navigation-styles.d.ts +21 -0
- package/dist/components/navigation/navigation-styles.js +6 -1
- package/dist/components/navigation/navigation-styles.js.map +1 -1
- package/dist/components/select/Select.js +6 -1
- package/dist/components/select/Select.js.map +1 -1
- package/dist/components/select/search-list.js +1 -1
- package/dist/components/select/search-list.js.map +1 -1
- package/dist/components/sheet/SideSheet.d.ts +2 -0
- package/dist/components/sheet/SideSheet.js +1 -1
- package/dist/components/sheet/SideSheet.js.map +1 -1
- package/dist/components/text-field/TextField.js +9 -1
- package/dist/components/text-field/TextField.js.map +1 -1
- package/dist/components/time-picker/TimeField.d.ts +118 -0
- package/dist/components/time-picker/TimeField.js +183 -0
- package/dist/components/time-picker/TimeField.js.map +1 -0
- package/dist/components/time-picker/TimeFieldDialog.js +70 -0
- package/dist/components/time-picker/TimeFieldDialog.js.map +1 -0
- package/dist/components/time-picker/TimePicker.js +1 -4
- package/dist/components/time-picker/TimePicker.js.map +1 -1
- package/dist/components/time-picker/hour-cycle.js +10 -0
- package/dist/components/time-picker/hour-cycle.js.map +1 -0
- package/dist/components/time-picker/time-format.js +84 -0
- package/dist/components/time-picker/time-format.js.map +1 -0
- package/dist/composites/button-group/ButtonGroup.d.ts +7 -0
- package/dist/composites/button-group/ButtonGroup.js +3 -2
- package/dist/composites/button-group/ButtonGroup.js.map +1 -1
- package/dist/composites/button-group/button-group-styles.d.ts +15 -3
- package/dist/composites/button-group/button-group-styles.js +16 -1
- package/dist/composites/button-group/button-group-styles.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +4 -2
- package/dist/primitives/ButtonBase.js +1 -0
- package/dist/primitives/ButtonBase.js.map +1 -1
- package/dist/primitives.d.ts +4 -1
- package/dist/primitives.js +4 -1
- package/dist/styles/utilities.css +8 -0
- package/dist/styles.compiled.css +1 -1
- package/dist/theme/ThemeProvider.js +17 -0
- package/dist/theme/ThemeProvider.js.map +1 -1
- package/dist/utils/closed-by.d.ts +5 -0
- package/dist/utils/placement.d.ts +11 -0
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TimePicker.js","names":[],"sources":["../../../src/components/time-picker/TimePicker.tsx"],"sourcesContent":["'use client';\n\nimport { Time } from '@internationalized/date';\nimport { animate, useReducedMotion, type AnimationPlaybackControls } from 'motion/react';\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type PointerEvent,\n type ReactElement,\n type Ref,\n type RefCallback,\n type RefObject,\n} from 'react';\nimport {\n mergeProps,\n mergeRefs,\n useFocusRing,\n useFocusVisible,\n useHover,\n useLocale,\n useNumberFormatter,\n} from 'react-aria';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport { useM3Spring } from '../../motion/use-m3-spring';\nimport { useM3Interaction } from '../../primitives/use-m3-interaction';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport {\n splitRootProps,\n type DescriptionAria,\n type RootDOMProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { KeyboardIcon, ScheduleIcon } from '../date-picker/picker-icons';\nimport { useMediaQuery } from '../date-picker/use-media-query';\nimport { IconButton } from '../icon-button/IconButton';\nimport { timePickerStyles } from './time-picker-styles';\n\n/** Compose's clock geometry (TimePicker.kt), in px on the 256px dial. */\nconst DIAL = 256;\nconst CENTER = DIAL / 2;\nconst OUTER_RADIUS = 101;\nconst INNER_RADIUS = 69;\nconst HANDLE = 48;\n/** A press nearer the centre than this picks the inner 24-hour ring (Compose's `MaxDistance`). */\nconst INNER_RING_MAX_DISTANCE = 74;\n/** Each number on the dial is a 48px target (M3: \"Targets for dial selectors should be 48x48dp\"). */\nconst TARGET = 48;\n\nexport interface TimePickerLabels {\n /** Name of the hour selector and field. @default \"Hour\" */\n hour: string;\n /** Name of the minute selector and field. @default \"Minute\" */\n minute: string;\n /** The AM option. @default \"AM\" */\n am: string;\n /** The PM option. @default \"PM\" */\n pm: string;\n /** Name of the AM / PM selector. @default \"AM or PM\" */\n period: string;\n /** Name of the dial. @default \"Clock\" */\n dial: string;\n /** {@link TimePickerModeToggle}'s name in dial mode. @default \"Toggle input picker\" */\n inputMode: string;\n /** {@link TimePickerModeToggle}'s name in input mode. @default \"Toggle dial picker\" */\n dialMode: string;\n /**\n * Name of a number on the dial (M3: \"{Value} Hours or minutes of {Total}\"), e.g.\n * \"7 hours of 12\" or \"35 minutes of 60\".\n * @default `${value} ${unit} of ${total}`\n */\n dialValue: (value: string, total: number, unit: 'hours' | 'minutes') => string;\n}\n\nexport interface TimePickerClassNames {\n /** The picker's root; `className` is merged here too. */\n root?: string;\n /** The hour and minute: selectors in dial mode, text fields in input mode. */\n field?: string;\n /** The labels under the input mode's fields (\"Hour\", \"Minute\"). */\n fieldLabel?: string;\n /** The colon between the hour and the minute. */\n separator?: string;\n /** The AM / PM selector. */\n period?: string;\n /** Each of the AM / PM selector's options. */\n periodOption?: string;\n /** The clock dial (dial mode): its face, numbers and hand are drawn inside it. */\n dial?: string;\n}\n\nexport type TimePickerMode = 'dial' | 'input';\n\n// No `aria-description` and the like: the picker's root is a plain container, and no single\n// element in it stands for the whole picker.\nexport interface TimePickerProps extends Omit<WrapperRootDOMProps, DescriptionAria> {\n /** Controlled time (`@internationalized/date` `Time`). */\n value?: Time;\n /** Initial time when uncontrolled. @default 12:00 */\n defaultValue?: Time;\n /** Called with the new time when the user changes it. */\n onChange?: (value: Time) => void;\n /** 12- or 24-hour clock. Left out, the locale's hour cycle (12 in en-US, 24 in en-GB). */\n hourCycle?: 12 | 24;\n /** The clock dial, or typed hour and minute fields (controlled). */\n mode?: TimePickerMode;\n /** Initial mode when uncontrolled. @default \"dial\" */\n defaultMode?: TimePickerMode;\n /** Called when the user switches between the dial and the typed input. */\n onModeChange?: (mode: TimePickerMode) => void;\n /**\n * `vertical` stacks the time over the dial; `horizontal` (landscape) puts them side by\n * side. `auto` picks vertical when the window is tall enough, horizontal when it is only\n * wide enough, and the typed input when neither fits, so the picker never scrolls (M3\n * time picker placement and adaptive design). @default \"auto\"\n */\n layout?: 'vertical' | 'horizontal' | 'auto';\n /** Words the picker shows and announces, with English defaults. */\n labels?: Partial<TimePickerLabels>;\n /**\n * Web density of the input mode, `0` to `-3`: each step takes 4px off the typed hour and\n * minute fields (72px at 0, 60px at -3). The dial keeps its size: M3 doesn't make time\n * pickers denser, since its 48px number targets are part of their accessibility. Left out,\n * the picker takes the density of the nearest `ThemeProvider` or `ThemeScope`.\n * @default 0\n */\n density?: Density;\n /** The root `<div>`'s id. */\n id?: string;\n /** Ref to the root `<div>` element. */\n ref?: Ref<HTMLDivElement>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Classes for each part, by slot. */\n classNames?: TimePickerClassNames;\n /** `data-*` attributes go to the root. */\n [data: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/*\n * Window sizes for `layout=\"auto\"`, from the picker in a `PickerDialog` (24px around the\n * panel, 24px padding, the title and the actions): the vertical dial needs 568px of\n * height; the horizontal one 452px of height and 640px of width.\n */\nconst SHORT_WINDOW = '(max-height: 567.98px)';\nconst NO_ROOM_FOR_DIAL = '(max-height: 451.98px), (max-height: 567.98px) and (max-width: 639.98px)';\n\n/** Where an `auto` time picker's dial fits: `vertical`, `horizontal` or `none`. */\nexport function useTimePickerRoom(): 'vertical' | 'horizontal' | 'none' {\n const short = useMediaQuery(SHORT_WINDOW);\n const noRoom = useMediaQuery(NO_ROOM_FOR_DIAL);\n return noRoom ? 'none' : short ? 'horizontal' : 'vertical';\n}\n\nfunction localeHourCycle(locale: string): 12 | 24 {\n const cycle = new Intl.DateTimeFormat(locale, { hour: 'numeric' }).resolvedOptions().hourCycle;\n return cycle === 'h11' || cycle === 'h12' ? 12 : 24;\n}\n\nconst defaultDialValue = (value: string, total: number, unit: 'hours' | 'minutes') =>\n `${value} ${unit} of ${total}`;\n\n/**\n * M3 time picker (Compose's `TimePicker` and `TimeInput`): hour and minute selectors you\n * can type in, with an AM / PM selector, and a clock dial whose numbers are buttons (press,\n * drag, or Tab to one and press Enter; the hour moves on to the minutes); or typed fields\n * in input mode. It turns to a horizontal layout, or the input mode, when the window has\n * no room for the dial. Put it in a {@link PickerDialog} for the modal form, with a\n * {@link TimePickerModeToggle}.\n *\n * @example\n * <TimePicker value={time} onChange={setTime} />\n */\nexport function TimePicker({\n value,\n defaultValue,\n onChange,\n hourCycle: hourCycleProp,\n mode: modeProp,\n defaultMode = 'dial',\n onModeChange,\n layout = 'auto',\n labels = {},\n density,\n id,\n ref,\n className,\n style,\n classNames,\n ...rest\n}: TimePickerProps) {\n const { root: data } = splitRootProps(rest, { wrapper: true });\n const { locale } = useLocale();\n const hourCycle = hourCycleProp ?? localeHourCycle(locale);\n const [time, setTime] = useControlledState(value, defaultValue ?? new Time(12, 0), onChange);\n const [chosenMode] = useControlledState(modeProp, defaultMode, onModeChange);\n const room = useTimePickerRoom();\n const fits = layout === 'auto' ? room : layout;\n const mode = fits === 'none' ? 'input' : chosenMode;\n const resolvedLayout = mode === 'input' || fits === 'none' ? 'vertical' : fits;\n const [selection, setSelection] = useState<'hour' | 'minute'>('hour');\n const styles = timePickerStyles({ hourCycle, mode, layout: resolvedLayout });\n const pad = useNumberFormatter({ minimumIntegerDigits: 2, useGrouping: false });\n const plain = useNumberFormatter({ useGrouping: false });\n const isPm = time.hour >= 12;\n const displayHour = hourCycle === 24 ? time.hour : time.hour % 12 || 12;\n // Two digits on both clocks, as Compose's selector and input show a 12-hour \"02\"\n // (`hourForDisplay.toLocalString(minDigits = 2)`); the dial's numbers stay unpadded.\n const formatHour = (n: number) => pad.format(n);\n const setPeriod = (pm: boolean) => {\n if (pm !== isPm) setTime(time.set({ hour: (time.hour + 12) % 24 }));\n };\n const commitHour = (hour: number) =>\n setTime(time.set({ hour: hourCycle === 24 ? hour : (hour % 12) + (isPm ? 12 : 0) }));\n const hourLabel = labels.hour ?? 'Hour';\n const minuteLabel = labels.minute ?? 'Minute';\n const minuteRef = useRef<HTMLInputElement>(null);\n // The page's direction where the picker sits (its `dir`, not only the locale's), which the\n // AM / PM options follow inside the time's left-to-right row (2026-10-17).\n const [domDirection, setDomDirection] = useState<'ltr' | 'rtl' | undefined>(undefined);\n const directionRef = useCallback<RefCallback<HTMLDivElement>>((element) => {\n if (element) setDomDirection(getComputedStyle(element).direction === 'rtl' ? 'rtl' : 'ltr');\n }, []);\n const rootRef = useMemo(() => mergeRefs<HTMLDivElement>(ref, directionRef), [ref, directionRef]);\n\n return (\n <div\n {...data}\n id={id}\n ref={rootRef}\n style={style}\n // The time picker's component tokens (time-picker-tokens.ts).\n data-tokens={mergeDataTokens('md-comp-time-picker', data)}\n data-mode={mode}\n data-layout={resolvedLayout}\n {...densityAttribute(density)}\n className={styles.root({ class: cn(classNames?.root, className) })}\n >\n {/* Time reads hour : minute, then AM / PM, in every direction (Compose forces LTR). */}\n <div dir=\"ltr\" className={styles.display()}>\n <div className={styles.time()}>\n <NumberField\n label={hourLabel}\n value={displayHour}\n min={hourCycle === 24 ? 0 : 1}\n max={hourCycle === 24 ? 23 : 12}\n format={formatHour}\n onCommit={commitHour}\n // Input mode moves on to the minute once the hour is complete (Compose's TimeInput).\n onComplete={mode === 'input' ? () => minuteRef.current?.focus() : undefined}\n selector={mode === 'dial'}\n selected={selection === 'hour'}\n onFocus={() => setSelection('hour')}\n styles={styles}\n classNames={classNames}\n />\n <span aria-hidden=\"true\" className={styles.separator({ class: classNames?.separator })}>\n :\n </span>\n <NumberField\n label={minuteLabel}\n value={time.minute}\n min={0}\n max={59}\n format={(n) => pad.format(n)}\n onCommit={(minute) => setTime(time.set({ minute }))}\n inputRef={minuteRef}\n selector={mode === 'dial'}\n selected={selection === 'minute'}\n onFocus={() => setSelection('minute')}\n styles={styles}\n classNames={classNames}\n />\n </div>\n {hourCycle === 12 && (\n <PeriodGroup\n label={labels.period ?? 'AM or PM'}\n am={labels.am ?? 'AM'}\n pm={labels.pm ?? 'PM'}\n isPm={isPm}\n onChange={setPeriod}\n direction={domDirection}\n styles={styles}\n classNames={classNames}\n />\n )}\n </div>\n {mode === 'dial' && (\n <Dial\n time={time}\n setTime={setTime}\n selection={selection}\n onHourPicked={() => setSelection('minute')}\n hourCycle={hourCycle}\n label={labels.dial ?? 'Clock'}\n valueLabel={labels.dialValue ?? defaultDialValue}\n format={(n, minutes) => (minutes || hourCycle === 24 ? pad.format(n) : plain.format(n))}\n plain={(n) => plain.format(n)}\n styles={styles}\n className={classNames?.dial}\n />\n )}\n </div>\n );\n}\n\nexport interface TimePickerModeToggleProps extends RootDOMProps<HTMLButtonElement> {\n /** The time picker's mode; the toggle switches to the other one. */\n mode: TimePickerMode;\n /** Called with the other mode when the toggle is pressed. */\n onModeChange: (mode: TimePickerMode) => void;\n /**\n * Words the toggle announces (`inputMode`, `dialMode`), with English defaults. Takes the\n * same object as the time picker's `labels`, so one set of words covers both.\n */\n labels?: Partial<TimePickerLabels>;\n /** Shown in dial mode. Defaults to Material Symbols' keyboard. */\n keyboardIcon?: ReactElement;\n /** Shown in input mode. Defaults to Material Symbols' schedule (a clock). */\n clockIcon?: ReactElement;\n /** Classes for the icon button; consumer classes win. */\n className?: string;\n /** Inline styles for the icon button. */\n style?: CSSProperties;\n /** The icon button's id. */\n id?: string;\n /** Ref to the icon button. */\n ref?: Ref<HTMLButtonElement>;\n}\n\n/**\n * The time picker's keyboard / clock icon button (M3: \"The input selector should be\n * accessible from the dial selector via the keyboard icon\"), for `PickerDialog`'s\n * `modeToggleButton`. Hidden when the window has no room for the dial, where an `auto`\n * time picker shows only the input.\n *\n * @example\n * <PickerDialog variant=\"time\" modeToggleButton={<TimePickerModeToggle mode={mode} onModeChange={setMode} />} …>\n * <TimePicker mode={mode} onModeChange={setMode} />\n * </PickerDialog>\n */\nexport function TimePickerModeToggle({\n mode,\n onModeChange,\n labels = {},\n keyboardIcon,\n clockIcon,\n className,\n style,\n id,\n ref,\n ...dom\n}: TimePickerModeToggleProps) {\n const room = useTimePickerRoom();\n if (room === 'none') return null;\n // The icon button is the toggle's root: it takes the DOM attributes too.\n const own = { ...dom, className, style, id, ref };\n return mode === 'dial' ? (\n <IconButton\n variant=\"standard\"\n // The dialog isn't dense, only the input mode's fields.\n density={0}\n icon={keyboardIcon ?? <KeyboardIcon />}\n aria-label={labels.inputMode ?? 'Toggle input picker'}\n onPress={() => onModeChange('input')}\n {...own}\n />\n ) : (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={clockIcon ?? <ScheduleIcon />}\n aria-label={labels.dialMode ?? 'Toggle dial picker'}\n onPress={() => onModeChange('dial')}\n {...own}\n />\n );\n}\n\n/**\n * The AM / PM selector: a radio group (M3: \"Radio button (in list)\"), so it is one Tab stop\n * on the checked option and the arrow keys move the choice, as WAI-ARIA's radio group.\n */\nfunction PeriodGroup({\n label,\n am,\n pm,\n isPm,\n onChange,\n direction: domDirection,\n styles,\n classNames,\n}: {\n label: string;\n am: string;\n pm: string;\n isPm: boolean;\n onChange: (pm: boolean) => void;\n /** The picker's laid-out direction, once known; the locale's until then (server render). */\n direction: 'ltr' | 'rtl' | undefined;\n styles: ReturnType<typeof timePickerStyles>;\n classNames: TimePickerClassNames | undefined;\n}) {\n const { direction: localeDirection } = useLocale();\n const direction = domDirection ?? localeDirection;\n const refs = useRef<(HTMLButtonElement | null)[]>([]);\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const forward = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight';\n const backward = direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft';\n const step =\n event.key === 'ArrowDown' || event.key === forward\n ? 1\n : event.key === 'ArrowUp' || event.key === backward\n ? -1\n : 0;\n if (step === 0) return;\n event.preventDefault();\n // Two options: either step moves to the other one, wrapping.\n const next = !isPm;\n onChange(next);\n refs.current[next ? 1 : 0]?.focus();\n };\n return (\n // The options keep the page's direction for their order in the horizontal layout.\n <div\n role=\"radiogroup\"\n aria-label={label}\n dir={direction}\n onKeyDown={onKeyDown}\n data-slot=\"period-selector\"\n className={styles.period({ class: classNames?.period })}\n >\n {[false, true].map((option) => (\n <PeriodOption\n key={String(option)}\n ref={(element) => {\n refs.current[option ? 1 : 0] = element;\n }}\n label={option ? pm : am}\n selected={isPm === option}\n onSelect={() => onChange(option)}\n className={styles.periodOption({ class: classNames?.periodOption })}\n />\n ))}\n </div>\n );\n}\n\nfunction PeriodOption({\n label,\n selected,\n onSelect,\n className,\n ref: refProp,\n}: {\n label: string;\n selected: boolean;\n onSelect: () => void;\n className: string;\n ref: (element: HTMLButtonElement | null) => void;\n}) {\n const ref = useRef<HTMLButtonElement>(null);\n const { interactionProps, dataAttributes } = useM3Interaction({}, ref);\n return (\n <button\n {...mergeProps(interactionProps, { onClick: onSelect })}\n {...dataAttributes}\n ref={(element) => {\n ref.current = element;\n refProp(element);\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n tabIndex={selected ? 0 : -1}\n data-slot=\"period-selector-option\"\n data-selected={selected || undefined}\n className={className}\n >\n {label}\n </button>\n );\n}\n\n/**\n * A typed hour or minute: the input mode's 96 × 72px field with its label below, or in\n * dial mode the 96 × 80px selector, which picks the dial's face when focused and takes\n * digits too (M3: \"The dial time picker supports both manual and dial input\").\n */\nfunction NumberField({\n label,\n value,\n min,\n max,\n format,\n onCommit,\n onComplete,\n inputRef,\n selector,\n selected,\n onFocus,\n styles,\n classNames,\n}: {\n label: string;\n value: number;\n min: number;\n max: number;\n format: (n: number) => string;\n onCommit: (value: number) => void;\n /** Called once a typed value can't take another digit (two digits, or \"3\" for an hour). */\n onComplete?: (() => void) | undefined;\n inputRef?: RefObject<HTMLInputElement | null>;\n selector: boolean;\n selected: boolean;\n onFocus: () => void;\n styles: ReturnType<typeof timePickerStyles>;\n classNames: TimePickerClassNames | undefined;\n}) {\n const [draft, setDraft] = useState<string | null>(null);\n const { focusProps, isFocusVisible } = useFocusRing();\n // A selector shows hover and pressed state layers (M3 states: Enabled, Hover, Focus, Pressed).\n const { hoverProps, isHovered } = useHover({ isDisabled: !selector });\n const [pressed, setPressed] = useState(false);\n const release = () => setPressed(false);\n const parsed = draft === null ? value : Number(draft);\n const invalid =\n draft !== null && (draft === '' || Number.isNaN(parsed) || parsed < min || parsed > max);\n const input = (\n <input\n {...mergeProps(focusProps, hoverProps, {\n onFocus: (event: { currentTarget: HTMLInputElement }) => {\n event.currentTarget.select();\n onFocus();\n },\n })}\n ref={inputRef}\n onPointerDown={() => selector && setPressed(true)}\n onPointerUp={release}\n onPointerLeave={release}\n onPointerCancel={release}\n inputMode=\"numeric\"\n aria-label={label}\n aria-invalid={invalid || undefined}\n maxLength={2}\n value={draft ?? format(value)}\n data-slot={selector ? 'time-selector' : undefined}\n data-selected={(selector && selected) || undefined}\n data-focus-visible={(selector && isFocusVisible) || undefined}\n data-hovered={(selector && isHovered) || undefined}\n data-pressed={(selector && pressed) || undefined}\n onChange={(event) => {\n const text = event.target.value.replace(/\\D/g, '');\n setDraft(text);\n const next = Number(text);\n if (text === '' || next < min || next > max) return;\n onCommit(next);\n if (text.length === 2 || next * 10 > max) onComplete?.();\n }}\n onBlur={() => setDraft(null)}\n onKeyDown={(event) => {\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return;\n event.preventDefault();\n const step = event.key === 'ArrowUp' ? 1 : -1;\n const next = value + step > max ? min : value + step < min ? max : value + step;\n setDraft(null);\n onCommit(next);\n }}\n className={\n selector\n ? styles.selector({ class: classNames?.field })\n : styles.field({ class: classNames?.field })\n }\n />\n );\n if (selector) return input;\n return (\n <label className={styles.fieldColumn()}>\n {input}\n <span aria-hidden=\"true\" className={styles.fieldLabel({ class: classNames?.fieldLabel })}>\n {label}\n </span>\n </label>\n );\n}\n\n/** Angle of a dial position (0 at 12 o'clock, clockwise), in turns. */\nconst turnOf = (x: number, y: number) =>\n (Math.atan2(x - CENTER, CENTER - y) / (2 * Math.PI) + 1) % 1;\nconst pointOf = (turn: number, radius: number) => ({\n x: CENTER + radius * Math.sin(turn * 2 * Math.PI),\n y: CENTER - radius * Math.cos(turn * 2 * Math.PI),\n});\n\ninterface DialNumber {\n value: number;\n turn: number;\n radius: number;\n}\n\n/** The numbers on the face: minutes in fives, hours 12 / 1–11, plus 12–23 inside for 24 hours. */\nfunction dialNumbers(minutes: boolean, hourCycle: 12 | 24): DialNumber[] {\n const ring = (radius: number, valueAt: (i: number) => number) =>\n Array.from({ length: 12 }, (_, i) => ({ value: valueAt(i), turn: i / 12, radius }));\n if (minutes) return ring(OUTER_RADIUS, (i) => i * 5);\n if (hourCycle === 12) return ring(OUTER_RADIUS, (i) => i || 12);\n // 24 hours: 00–11 on the outer ring, 12–23 on the inner one (as Compose and Android).\n return [...ring(OUTER_RADIUS, (i) => i), ...ring(INNER_RADIUS, (i) => i + 12)];\n}\n\n/** The shortest signed way round the dial from one turn to another, in turns (−½ to ½). */\nconst shortestTurn = (from: number, to: number) => ((((to - from) % 1) + 1.5) % 1) - 0.5;\n\n/**\n * How close the hand is to its value when it counts as there: Compose's turn ends at the\n * spring's visibility threshold of 0.01 rad (about 0.6°), and here within 1px of the radius\n * too, while the last fraction of the spring still plays out.\n */\nconst ARRIVED_TURN = 0.01 / (2 * Math.PI);\nconst ARRIVED_RADIUS = 1;\n\n/** Runs and clears the callbacks waiting in `queue`. */\nconst flush = (queue: RefObject<(() => void)[]>) => {\n const callbacks = queue.current;\n queue.current = [];\n for (const callback of callbacks) callback();\n};\n\n/**\n * The hand's track, handle and the clip of the `on-primary` numbers follow the drawn angle,\n * set as attributes from an animation (no React render per frame). It turns to a new value\n * on the default spatial spring along the shortest arc (M3: \"Spatial springs apply to …\n * rotation\"; Compose animates the selector on hour ↔ minute and taps), follows a drag\n * directly, and jumps under reduced motion. Returns `whenSettled`, which runs a callback\n * once the hand rests on its value.\n */\nfunction useDialHand(\n turn: number,\n radius: number,\n follow: RefObject<boolean>,\n parts: RefObject<{\n track: SVGLineElement | null;\n handle: SVGCircleElement | null;\n clip: SVGCircleElement | null;\n }>,\n) {\n const spring = useM3Spring('spatial', 'default');\n const reducedMotion = useReducedMotion() ?? false;\n const drawn = useRef({ turn, radius });\n const animation = useRef<AnimationPlaybackControls | null>(null);\n // Callbacks waiting for the hand to arrive (`whenSettled`), and whether it has.\n const settled = useRef<(() => void)[]>([]);\n const arrived = useRef(true);\n\n // Before paint, so the hand never flashes at its new value before turning there.\n useLayoutEffect(() => {\n const draw = (nextTurn: number, nextRadius: number) => {\n drawn.current = { turn: nextTurn, radius: nextRadius };\n const point = pointOf(nextTurn, nextRadius);\n const { track, handle, clip } = parts.current;\n track?.setAttribute('x2', String(point.x));\n track?.setAttribute('y2', String(point.y));\n for (const circle of [handle, clip]) {\n circle?.setAttribute('cx', String(point.x));\n circle?.setAttribute('cy', String(point.y));\n }\n };\n const from = drawn.current;\n const delta = shortestTurn(from.turn, turn);\n animation.current?.stop();\n animation.current = null;\n if (follow.current || reducedMotion || (delta === 0 && from.radius === radius)) {\n draw(turn, radius);\n arrived.current = true;\n flush(settled);\n return;\n }\n // React has written the new value's attributes; start from where the hand was.\n draw(from.turn, from.radius);\n arrived.current = false;\n // Two frames in a row within the threshold: the hand swinging past its value at speed\n // (the expressive spring overshoots by about 1.5%) doesn't count as there.\n let near = false;\n animation.current = animate(0, 1, {\n ...spring,\n onUpdate: (progress) => {\n draw(from.turn + delta * progress, from.radius + (radius - from.radius) * progress);\n const close =\n Math.abs(delta * (1 - progress)) <= ARRIVED_TURN &&\n Math.abs((radius - from.radius) * (1 - progress)) <= ARRIVED_RADIUS;\n if (close && near && !arrived.current) {\n arrived.current = true;\n flush(settled);\n }\n near = close;\n },\n // A spring rests within a small distance of its target; end exactly on the value.\n onComplete: () => {\n draw(turn, radius);\n animation.current = null;\n arrived.current = true;\n flush(settled);\n },\n });\n }, [turn, radius, follow, parts, reducedMotion, spring]);\n\n useEffect(\n () => () => {\n animation.current?.stop();\n settled.current = [];\n },\n [],\n );\n\n /** Runs `callback` once the hand has arrived at its value (at once if it already has). */\n return (callback: () => void) => {\n if (animation.current && !arrived.current) settled.current.push(callback);\n else callback();\n };\n}\n\n/**\n * Which faces to draw: the current one, and the one it replaced while that fades out\n * (Compose crossfades the hour and minute faces).\n */\nfunction useFaces(minutes: boolean) {\n const [faces, setFaces] = useState<{ current: boolean; leaving: boolean | null }>({\n current: minutes,\n leaving: null,\n });\n if (faces.current !== minutes) setFaces({ current: minutes, leaving: faces.current });\n const clearLeaving = () => setFaces((previous) => ({ ...previous, leaving: null }));\n return { leaving: faces.leaving, clearLeaving };\n}\n\nfunction Dial({\n time,\n setTime,\n selection,\n onHourPicked,\n hourCycle,\n label,\n valueLabel,\n format,\n plain,\n styles,\n className,\n}: {\n time: Time;\n setTime: (time: Time) => void;\n selection: 'hour' | 'minute';\n onHourPicked: () => void;\n hourCycle: 12 | 24;\n label: string;\n valueLabel: (value: string, total: number, unit: 'hours' | 'minutes') => string;\n /** A number as shown on the hour face (`minutes` false) or the minute face. */\n format: (n: number, minutes: boolean) => string;\n plain: (n: number) => string;\n styles: ReturnType<typeof timePickerStyles>;\n className?: string | undefined;\n}) {\n const svgRef = useRef<SVGSVGElement>(null);\n const numberRefs = useRef<(SVGGElement | null)[]>([]);\n const hand = useRef<{\n track: SVGLineElement | null;\n handle: SVGCircleElement | null;\n clip: SVGCircleElement | null;\n }>({ track: null, handle: null, clip: null });\n const pressing = useRef(false);\n // True while a press moves across the dial: the hand follows it without a spring.\n const dragging = useRef(false);\n const pendingFocus = useRef<number | null>(null);\n const [focused, setFocused] = useState<number | null>(null);\n const { isFocusVisible } = useFocusVisible();\n const clipId = useId();\n const minutes = selection === 'minute';\n const { leaving, clearLeaving } = useFaces(minutes);\n const isPm = time.hour >= 12;\n const numbers = dialNumbers(minutes, hourCycle);\n\n const selectedTurn = minutes ? time.minute / 60 : (time.hour % 12) / 12;\n const selectedRadius =\n !minutes && hourCycle === 24 && time.hour >= 12 ? INNER_RADIUS : OUTER_RADIUS;\n const handle = pointOf(selectedTurn, selectedRadius);\n const whenSettled = useDialHand(selectedTurn, selectedRadius, dragging, hand);\n // A tapped hour's switch to the minutes, waiting for the hand (cancelled by a new press,\n // a face change or unmounting).\n const switchTimer = useRef<number | undefined>(undefined);\n const cancelSwitch = () => {\n window.clearTimeout(switchTimer.current);\n switchTimer.current = undefined;\n };\n useEffect(() => cancelSwitch, [minutes]);\n const selectedValue = minutes ? time.minute : hourCycle === 24 ? time.hour : time.hour % 12 || 12;\n // The number Tab lands on: the chosen one, or the nearest five for other minutes.\n const tabStop = numbers.findIndex(({ value }) =>\n minutes ? value === (Math.round(time.minute / 5) * 5) % 60 : value === selectedValue,\n );\n\n // After an hour is chosen with the keyboard, focus follows to the chosen minute.\n useEffect(() => {\n if (pendingFocus.current === null) return;\n numberRefs.current[pendingFocus.current]?.focus();\n pendingFocus.current = null;\n });\n\n // The replaced face is gone once it has faded out (or at once without transitions).\n useEffect(() => {\n if (leaving === null) return;\n const timeout = window.setTimeout(clearLeaving, 1000);\n return () => window.clearTimeout(timeout);\n }, [leaving, clearLeaving]);\n\n const toTime = (value: number) =>\n minutes\n ? time.set({ minute: value })\n : time.set({ hour: hourCycle === 24 ? value : (value % 12) + (isPm ? 12 : 0) });\n\n // The minute face replaces the hour face's numbers, so focus on one of them would drop to\n // the page: it moves on to the chosen minute's number, as for the keyboard.\n const switchToMinutes = () => {\n if (svgRef.current?.contains(document.activeElement)) {\n pendingFocus.current = Math.round(time.minute / 5) % 12;\n }\n onHourPicked();\n };\n\n const activate = (value: number) => {\n setTime(toTime(value));\n if (!minutes) {\n pendingFocus.current = Math.round(time.minute / 5) % 12;\n onHourPicked();\n }\n };\n\n const pick = (event: PointerEvent<SVGSVGElement>) => {\n const box = svgRef.current!.getBoundingClientRect();\n const x = ((event.clientX - box.left) / box.width) * DIAL;\n const y = ((event.clientY - box.top) / box.height) * DIAL;\n const step = Math.round(turnOf(x, y) * 12) % 12;\n // Taps and drags pick minutes in fives (Compose); the keyboard and typing reach any minute.\n if (minutes) {\n setTime(time.set({ minute: step * 5 }));\n return;\n }\n const innerRing =\n hourCycle === 24 && Math.hypot(x - CENTER, y - CENTER) < INNER_RING_MAX_DISTANCE;\n const hour = hourCycle === 24 ? step + (innerRing ? 12 : 0) : step + (isPm ? 12 : 0);\n setTime(time.set({ hour }));\n };\n\n const onNumberKeyDown = (event: KeyboardEvent<SVGGElement>, index: number) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n activate(numbers[index]!.value);\n return;\n }\n const step = { ArrowUp: 1, ArrowRight: 1, ArrowDown: -1, ArrowLeft: -1 }[event.key];\n if (step === undefined) return;\n event.preventDefault();\n // Clockwise through the values; a 24-hour face runs on from 11 to 12 on the inner ring.\n const next = (index + step + numbers.length) % numbers.length;\n setFocused(next);\n numberRefs.current[next]?.focus();\n };\n\n const total = minutes ? 60 : hourCycle;\n const unit = minutes ? 'minutes' : 'hours';\n const endPress = () => {\n pressing.current = false;\n dragging.current = false;\n };\n return (\n <svg\n ref={svgRef}\n viewBox={`0 0 ${DIAL} ${DIAL}`}\n role=\"group\"\n aria-label={label}\n onPointerDown={(event) => {\n cancelSwitch();\n pressing.current = true;\n event.currentTarget.setPointerCapture?.(event.pointerId);\n pick(event);\n }}\n onPointerMove={(event) => {\n if (!pressing.current) return;\n dragging.current = true;\n pick(event);\n }}\n onPointerUp={() => {\n if (pressing.current && !minutes) {\n if (dragging.current) {\n // A drag already shows the hour under the finger: switch on release.\n switchToMinutes();\n } else {\n // A tap turns the hand to the hour, shows it for 100ms, then switches\n // (Compose's `onTap`: `rotateTo(animate = true)`, `delay(100)`).\n whenSettled(() => {\n switchTimer.current = window.setTimeout(switchToMinutes, 100);\n });\n }\n }\n endPress();\n }}\n onPointerCancel={endPress}\n className={cn(styles.dial(), className)}\n >\n <defs>\n <clipPath id={clipId}>\n <circle\n ref={(element) => {\n hand.current.clip = element;\n }}\n cx={handle.x}\n cy={handle.y}\n r={HANDLE / 2}\n />\n </clipPath>\n </defs>\n <circle\n cx={CENTER}\n cy={CENTER}\n r={CENTER}\n data-slot=\"clock-dial\"\n className={styles.dialFace()}\n />\n <line\n ref={(element) => {\n hand.current.track = element;\n }}\n x1={CENTER}\n y1={CENTER}\n x2={handle.x}\n y2={handle.y}\n strokeWidth={2}\n className={styles.dialTrack()}\n />\n <circle cx={CENTER} cy={CENTER} r={4} className={styles.dialHandle()} />\n <circle\n ref={(element) => {\n hand.current.handle = element;\n }}\n cx={handle.x}\n cy={handle.y}\n r={HANDLE / 2}\n data-slot=\"clock-dial-selector-handle\"\n className={styles.dialHandle()}\n />\n {leaving !== null && (\n <g\n key={`leaving-${String(leaving)}`}\n aria-hidden=\"true\"\n onTransitionEnd={clearLeaving}\n className={styles.dialFaceLeaving()}\n >\n {dialNumbers(leaving, hourCycle).map(({ value, turn, radius }, index) => {\n const point = pointOf(turn, radius);\n return (\n <text\n key={index}\n x={point.x}\n y={point.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className={styles.dialNumberText()}\n >\n {format(value, leaving)}\n </text>\n );\n })}\n </g>\n )}\n <g\n key={`face-${String(minutes)}`}\n className={leaving === null ? undefined : styles.dialFaceEntering()}\n >\n {numbers.map(({ value, turn, radius }, index) => {\n const point = pointOf(turn, radius);\n const selected = value === selectedValue && (minutes ? time.minute % 5 === 0 : true);\n return (\n <g\n key={index}\n ref={(element) => {\n numberRefs.current[index] = element;\n }}\n role=\"button\"\n tabIndex={index === (focused ?? tabStop) ? 0 : -1}\n aria-label={valueLabel(plain(value), total, unit)}\n aria-pressed={selected}\n data-selected={selected || undefined}\n onFocus={() => setFocused(index)}\n onBlur={() => setFocused(null)}\n onKeyDown={(event) => onNumberKeyDown(event, index)}\n onClick={(event) => {\n // Assistive technology activates with a click that has no pointer behind it.\n if (event.detail === 0) activate(value);\n }}\n className={styles.dialNumber()}\n >\n <circle cx={point.x} cy={point.y} r={TARGET / 2} className={styles.dialNumberHit()} />\n {focused === index && isFocusVisible && (\n <circle\n cx={point.x}\n cy={point.y}\n r={TARGET / 2 - 1}\n strokeWidth={3}\n className={styles.dialFocus()}\n />\n )}\n <text\n x={point.x}\n y={point.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n aria-hidden=\"true\"\n data-selected={selected || undefined}\n className={styles.dialNumberText()}\n >\n {format(value, minutes)}\n </text>\n </g>\n );\n })}\n {/*\n * The numbers again in `on-primary`, clipped to the handle, so whatever part of a\n * number the handle covers reads on it (M3: the selected label is `on-primary`;\n * Compose blends the two layers the same way).\n */}\n <g aria-hidden=\"true\" clipPath={`url(#${clipId})`} className=\"pointer-events-none\">\n {numbers.map(({ value, turn, radius }, index) => {\n const point = pointOf(turn, radius);\n return (\n <text\n key={index}\n x={point.x}\n y={point.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className={styles.dialNumberOnHandle()}\n >\n {format(value, minutes)}\n </text>\n );\n })}\n </g>\n </g>\n </svg>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA+CA,MAAM,OAAO;AACb,MAAM,SAAS,OAAO;AACtB,MAAM,eAAe;AACrB,MAAM,eAAe;AACrB,MAAM,SAAS;;AAEf,MAAM,0BAA0B;;AAEhC,MAAM,SAAS;AAmGf,MAAM,eAAe;AACrB,MAAM,mBAAmB;;AAGzB,SAAgB,oBAAwD;CACtE,MAAM,QAAQ,cAAc,YAAY;CAExC,OADe,cAAc,gBACjB,IAAI,SAAS,QAAQ,eAAe;AAClD;AAEA,SAAS,gBAAgB,QAAyB;CAChD,MAAM,QAAQ,IAAI,KAAK,eAAe,QAAQ,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;CACrF,OAAO,UAAU,SAAS,UAAU,QAAQ,KAAK;AACnD;AAEA,MAAM,oBAAoB,OAAe,OAAe,SACtD,GAAG,MAAM,GAAG,KAAK,MAAM;;;;;;;;;;;;AAazB,SAAgB,WAAW,EACzB,OACA,cACA,UACA,WAAW,eACX,MAAM,UACN,cAAc,QACd,cACA,SAAS,QACT,SAAS,CAAC,GACV,SACA,IACA,KACA,WACA,OACA,YACA,GAAG,QACe;CAClB,MAAM,EAAE,MAAM,SAAS,eAAe,MAAM,EAAE,SAAS,KAAK,CAAC;CAC7D,MAAM,EAAE,WAAW,UAAU;CAC7B,MAAM,YAAY,iBAAiB,gBAAgB,MAAM;CACzD,MAAM,CAAC,MAAM,WAAW,mBAAmB,OAAO,gBAAgB,IAAI,KAAK,IAAI,CAAC,GAAG,QAAQ;CAC3F,MAAM,CAAC,cAAc,mBAAmB,UAAU,aAAa,YAAY;CAC3E,MAAM,OAAO,kBAAkB;CAC/B,MAAM,OAAO,WAAW,SAAS,OAAO;CACxC,MAAM,OAAO,SAAS,SAAS,UAAU;CACzC,MAAM,iBAAiB,SAAS,WAAW,SAAS,SAAS,aAAa;CAC1E,MAAM,CAAC,WAAW,gBAAgB,SAA4B,MAAM;CACpE,MAAM,SAAS,iBAAiB;EAAE;EAAW;EAAM,QAAQ;CAAe,CAAC;CAC3E,MAAM,MAAM,mBAAmB;EAAE,sBAAsB;EAAG,aAAa;CAAM,CAAC;CAC9E,MAAM,QAAQ,mBAAmB,EAAE,aAAa,MAAM,CAAC;CACvD,MAAM,OAAO,KAAK,QAAQ;CAC1B,MAAM,cAAc,cAAc,KAAK,KAAK,OAAO,KAAK,OAAO,MAAM;CAGrE,MAAM,cAAc,MAAc,IAAI,OAAO,CAAC;CAC9C,MAAM,aAAa,OAAgB;EACjC,IAAI,OAAO,MAAM,QAAQ,KAAK,IAAI,EAAE,OAAO,KAAK,OAAO,MAAM,GAAG,CAAC,CAAC;CACpE;CACA,MAAM,cAAc,SAClB,QAAQ,KAAK,IAAI,EAAE,MAAM,cAAc,KAAK,OAAQ,OAAO,MAAO,OAAO,KAAK,GAAG,CAAC,CAAC;CACrF,MAAM,YAAY,OAAO,QAAQ;CACjC,MAAM,cAAc,OAAO,UAAU;CACrC,MAAM,YAAY,OAAyB,IAAI;CAG/C,MAAM,CAAC,cAAc,mBAAmB,SAAoC,KAAA,CAAS;CACrF,MAAM,eAAe,aAA0C,YAAY;EACzE,IAAI,SAAS,gBAAgB,iBAAiB,OAAO,CAAC,CAAC,cAAc,QAAQ,QAAQ,KAAK;CAC5F,GAAG,CAAC,CAAC;CACL,MAAM,UAAU,cAAc,UAA0B,KAAK,YAAY,GAAG,CAAC,KAAK,YAAY,CAAC;CAE/F,OACE,qBAAC,OAAD;EACE,GAAI;EACA;EACJ,KAAK;EACE;EAEP,eAAa,gBAAgB,uBAAuB,IAAI;EACxD,aAAW;EACX,eAAa;EACb,GAAI,iBAAiB,OAAO;EAC5B,WAAW,OAAO,KAAK,EAAE,OAAO,GAAG,YAAY,MAAM,SAAS,EAAE,CAAC;EAVnE,UAAA,CAaE,qBAAC,OAAD;GAAK,KAAI;GAAM,WAAW,OAAO,QAAQ;GAAzC,UAAA,CACE,qBAAC,OAAD;IAAK,WAAW,OAAO,KAAK;IAA5B,UAAA;KACE,oBAAC,aAAD;MACE,OAAO;MACP,OAAO;MACP,KAAK,cAAc,KAAK,IAAI;MAC5B,KAAK,cAAc,KAAK,KAAK;MAC7B,QAAQ;MACR,UAAU;MAEV,YAAY,SAAS,gBAAgB,UAAU,SAAS,MAAM,IAAI,KAAA;MAClE,UAAU,SAAS;MACnB,UAAU,cAAc;MACxB,eAAe,aAAa,MAAM;MAC1B;MACI;KACb,CAAA;KACD,oBAAC,QAAD;MAAM,eAAY;MAAO,WAAW,OAAO,UAAU,EAAE,OAAO,YAAY,UAAU,CAAC;MAAG,UAAA;KAElF,CAAA;KACN,oBAAC,aAAD;MACE,OAAO;MACP,OAAO,KAAK;MACZ,KAAK;MACL,KAAK;MACL,SAAS,MAAM,IAAI,OAAO,CAAC;MAC3B,WAAW,WAAW,QAAQ,KAAK,IAAI,EAAE,OAAO,CAAC,CAAC;MAClD,UAAU;MACV,UAAU,SAAS;MACnB,UAAU,cAAc;MACxB,eAAe,aAAa,QAAQ;MAC5B;MACI;KACb,CAAA;IACE;GACJ,CAAA,GAAA,cAAc,MACb,oBAAC,aAAD;IACE,OAAO,OAAO,UAAU;IACxB,IAAI,OAAO,MAAM;IACjB,IAAI,OAAO,MAAM;IACX;IACN,UAAU;IACV,WAAW;IACH;IACI;GACb,CAAA,CAEA;EACJ,CAAA,GAAA,SAAS,UACR,oBAAC,MAAD;GACQ;GACG;GACE;GACX,oBAAoB,aAAa,QAAQ;GAC9B;GACX,OAAO,OAAO,QAAQ;GACtB,YAAY,OAAO,aAAa;GAChC,SAAS,GAAG,YAAa,WAAW,cAAc,KAAK,IAAI,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC;GACrF,QAAQ,MAAM,MAAM,OAAO,CAAC;GACpB;GACR,WAAW,YAAY;EACxB,CAAA,CAEA;;AAET;;;;;;;;;;;;AAqCA,SAAgB,qBAAqB,EACnC,MACA,cACA,SAAS,CAAC,GACV,cACA,WACA,WACA,OACA,IACA,KACA,GAAG,OACyB;CAE5B,IADa,kBACN,MAAM,QAAQ,OAAO;CAE5B,MAAM,MAAM;EAAE,GAAG;EAAK;EAAW;EAAO;EAAI;CAAI;CAChD,OAAO,SAAS,SACd,oBAAC,YAAD;EACE,SAAQ;EAER,SAAS;EACT,MAAM,gBAAgB,oBAAC,cAAD,CAAe,CAAA;EACrC,cAAY,OAAO,aAAa;EAChC,eAAe,aAAa,OAAO;EACnC,GAAI;CACL,CAAA,IAED,oBAAC,YAAD;EACE,SAAQ;EACR,SAAS;EACT,MAAM,aAAa,oBAAC,cAAD,CAAe,CAAA;EAClC,cAAY,OAAO,YAAY;EAC/B,eAAe,aAAa,MAAM;EAClC,GAAI;CACL,CAAA;AAEL;;;;;AAMA,SAAS,YAAY,EACnB,OACA,IACA,IACA,MACA,UACA,WAAW,cACX,QACA,cAWC;CACD,MAAM,EAAE,WAAW,oBAAoB,UAAU;CACjD,MAAM,YAAY,gBAAgB;CAClC,MAAM,OAAO,OAAqC,CAAC,CAAC;CACpD,MAAM,aAAa,UAAyC;EAC1D,MAAM,UAAU,cAAc,QAAQ,cAAc;EACpD,MAAM,WAAW,cAAc,QAAQ,eAAe;EAOtD,KALE,MAAM,QAAQ,eAAe,MAAM,QAAQ,UACvC,IACA,MAAM,QAAQ,aAAa,MAAM,QAAQ,WACvC,KACA,OACK,GAAG;EAChB,MAAM,eAAe;EAErB,MAAM,OAAO,CAAC;EACd,SAAS,IAAI;EACb,KAAK,QAAQ,OAAO,IAAI,EAAE,EAAE,MAAM;CACpC;CACA,OAEE,oBAAC,OAAD;EACE,MAAK;EACL,cAAY;EACZ,KAAK;EACM;EACX,aAAU;EACV,WAAW,OAAO,OAAO,EAAE,OAAO,YAAY,OAAO,CAAC;EAErD,UAAA,CAAC,OAAO,IAAI,CAAC,CAAC,KAAK,WAClB,oBAAC,cAAD;GAEE,MAAM,YAAY;IAChB,KAAK,QAAQ,SAAS,IAAI,KAAK;GACjC;GACA,OAAO,SAAS,KAAK;GACrB,UAAU,SAAS;GACnB,gBAAgB,SAAS,MAAM;GAC/B,WAAW,OAAO,aAAa,EAAE,OAAO,YAAY,aAAa,CAAC;EACnE,GARM,OAAO,MAAM,CAQnB,CACF;CACE,CAAA;AAET;AAEA,SAAS,aAAa,EACpB,OACA,UACA,UACA,WACA,KAAK,WAOJ;CACD,MAAM,MAAM,OAA0B,IAAI;CAC1C,MAAM,EAAE,kBAAkB,mBAAmB,iBAAiB,CAAC,GAAG,GAAG;CACrE,OACE,oBAAC,UAAD;EACE,GAAI,WAAW,kBAAkB,EAAE,SAAS,SAAS,CAAC;EACtD,GAAI;EACJ,MAAM,YAAY;GAChB,IAAI,UAAU;GACd,QAAQ,OAAO;EACjB;EACA,MAAK;EACL,MAAK;EACL,gBAAc;EACd,UAAU,WAAW,IAAI;EACzB,aAAU;EACV,iBAAe,YAAY,KAAA;EAChB;EAEV,UAAA;CACK,CAAA;AAEZ;;;;;;AAOA,SAAS,YAAY,EACnB,OACA,OACA,KACA,KACA,QACA,UACA,YACA,UACA,UACA,UACA,SACA,QACA,cAgBC;CACD,MAAM,CAAC,OAAO,YAAY,SAAwB,IAAI;CACtD,MAAM,EAAE,YAAY,mBAAmB,aAAa;CAEpD,MAAM,EAAE,YAAY,cAAc,SAAS,EAAE,YAAY,CAAC,SAAS,CAAC;CACpE,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,MAAM,gBAAgB,WAAW,KAAK;CACtC,MAAM,SAAS,UAAU,OAAO,QAAQ,OAAO,KAAK;CACpD,MAAM,UACJ,UAAU,SAAS,UAAU,MAAM,OAAO,MAAM,MAAM,KAAK,SAAS,OAAO,SAAS;CACtF,MAAM,QACJ,oBAAC,SAAD;EACE,GAAI,WAAW,YAAY,YAAY,EACrC,UAAU,UAA+C;GACvD,MAAM,cAAc,OAAO;GAC3B,QAAQ;EACV,EACF,CAAC;EACD,KAAK;EACL,qBAAqB,YAAY,WAAW,IAAI;EAChD,aAAa;EACb,gBAAgB;EAChB,iBAAiB;EACjB,WAAU;EACV,cAAY;EACZ,gBAAc,WAAW,KAAA;EACzB,WAAW;EACX,OAAO,SAAS,OAAO,KAAK;EAC5B,aAAW,WAAW,kBAAkB,KAAA;EACxC,iBAAgB,YAAY,YAAa,KAAA;EACzC,sBAAqB,YAAY,kBAAmB,KAAA;EACpD,gBAAe,YAAY,aAAc,KAAA;EACzC,gBAAe,YAAY,WAAY,KAAA;EACvC,WAAW,UAAU;GACnB,MAAM,OAAO,MAAM,OAAO,MAAM,QAAQ,OAAO,EAAE;GACjD,SAAS,IAAI;GACb,MAAM,OAAO,OAAO,IAAI;GACxB,IAAI,SAAS,MAAM,OAAO,OAAO,OAAO,KAAK;GAC7C,SAAS,IAAI;GACb,IAAI,KAAK,WAAW,KAAK,OAAO,KAAK,KAAK,aAAa;EACzD;EACA,cAAc,SAAS,IAAI;EAC3B,YAAY,UAAU;GACpB,IAAI,MAAM,QAAQ,aAAa,MAAM,QAAQ,aAAa;GAC1D,MAAM,eAAe;GACrB,MAAM,OAAO,MAAM,QAAQ,YAAY,IAAI;GAC3C,MAAM,OAAO,QAAQ,OAAO,MAAM,MAAM,QAAQ,OAAO,MAAM,MAAM,QAAQ;GAC3E,SAAS,IAAI;GACb,SAAS,IAAI;EACf;EACA,WACE,WACI,OAAO,SAAS,EAAE,OAAO,YAAY,MAAM,CAAC,IAC5C,OAAO,MAAM,EAAE,OAAO,YAAY,MAAM,CAAC;CAEhD,CAAA;CAEH,IAAI,UAAU,OAAO;CACrB,OACE,qBAAC,SAAD;EAAO,WAAW,OAAO,YAAY;EAArC,UAAA,CACG,OACD,oBAAC,QAAD;GAAM,eAAY;GAAO,WAAW,OAAO,WAAW,EAAE,OAAO,YAAY,WAAW,CAAC;GACpF,UAAA;EACG,CAAA,CACD;;AAEX;;AAGA,MAAM,UAAU,GAAW,OACxB,KAAK,MAAM,IAAI,QAAQ,SAAS,CAAC,KAAK,IAAI,KAAK,MAAM,KAAK;AAC7D,MAAM,WAAW,MAAc,YAAoB;CACjD,GAAG,SAAS,SAAS,KAAK,IAAI,OAAO,IAAI,KAAK,EAAE;CAChD,GAAG,SAAS,SAAS,KAAK,IAAI,OAAO,IAAI,KAAK,EAAE;AAClD;;AASA,SAAS,YAAY,SAAkB,WAAkC;CACvE,MAAM,QAAQ,QAAgB,YAC5B,MAAM,KAAK,EAAE,QAAQ,GAAG,IAAI,GAAG,OAAO;EAAE,OAAO,QAAQ,CAAC;EAAG,MAAM,IAAI;EAAI;CAAO,EAAE;CACpF,IAAI,SAAS,OAAO,KAAK,eAAe,MAAM,IAAI,CAAC;CACnD,IAAI,cAAc,IAAI,OAAO,KAAK,eAAe,MAAM,KAAK,EAAE;CAE9D,OAAO,CAAC,GAAG,KAAK,eAAe,MAAM,CAAC,GAAG,GAAG,KAAK,eAAe,MAAM,IAAI,EAAE,CAAC;AAC/E;;AAGA,MAAM,gBAAgB,MAAc,SAAmB,KAAK,QAAQ,IAAK,OAAO,IAAK;;;;;;AAOrF,MAAM,eAAe,OAAQ,IAAI,KAAK;AACtC,MAAM,iBAAiB;;AAGvB,MAAM,SAAS,UAAqC;CAClD,MAAM,YAAY,MAAM;CACxB,MAAM,UAAU,CAAC;CACjB,KAAK,MAAM,YAAY,WAAW,SAAS;AAC7C;;;;;;;;;AAUA,SAAS,YACP,MACA,QACA,QACA,OAKA;CACA,MAAM,SAAS,YAAY,WAAW,SAAS;CAC/C,MAAM,gBAAgB,iBAAiB,KAAK;CAC5C,MAAM,QAAQ,OAAO;EAAE;EAAM;CAAO,CAAC;CACrC,MAAM,YAAY,OAAyC,IAAI;CAE/D,MAAM,UAAU,OAAuB,CAAC,CAAC;CACzC,MAAM,UAAU,OAAO,IAAI;CAG3B,sBAAsB;EACpB,MAAM,QAAQ,UAAkB,eAAuB;GACrD,MAAM,UAAU;IAAE,MAAM;IAAU,QAAQ;GAAW;GACrD,MAAM,QAAQ,QAAQ,UAAU,UAAU;GAC1C,MAAM,EAAE,OAAO,QAAQ,SAAS,MAAM;GACtC,OAAO,aAAa,MAAM,OAAO,MAAM,CAAC,CAAC;GACzC,OAAO,aAAa,MAAM,OAAO,MAAM,CAAC,CAAC;GACzC,KAAK,MAAM,UAAU,CAAC,QAAQ,IAAI,GAAG;IACnC,QAAQ,aAAa,MAAM,OAAO,MAAM,CAAC,CAAC;IAC1C,QAAQ,aAAa,MAAM,OAAO,MAAM,CAAC,CAAC;GAC5C;EACF;EACA,MAAM,OAAO,MAAM;EACnB,MAAM,QAAQ,aAAa,KAAK,MAAM,IAAI;EAC1C,UAAU,SAAS,KAAK;EACxB,UAAU,UAAU;EACpB,IAAI,OAAO,WAAW,iBAAkB,UAAU,KAAK,KAAK,WAAW,QAAS;GAC9E,KAAK,MAAM,MAAM;GACjB,QAAQ,UAAU;GAClB,MAAM,OAAO;GACb;EACF;EAEA,KAAK,KAAK,MAAM,KAAK,MAAM;EAC3B,QAAQ,UAAU;EAGlB,IAAI,OAAO;EACX,UAAU,UAAU,QAAQ,GAAG,GAAG;GAChC,GAAG;GACH,WAAW,aAAa;IACtB,KAAK,KAAK,OAAO,QAAQ,UAAU,KAAK,UAAU,SAAS,KAAK,UAAU,QAAQ;IAClF,MAAM,QACJ,KAAK,IAAI,SAAS,IAAI,SAAS,KAAK,gBACpC,KAAK,KAAK,SAAS,KAAK,WAAW,IAAI,SAAS,KAAK;IACvD,IAAI,SAAS,QAAQ,CAAC,QAAQ,SAAS;KACrC,QAAQ,UAAU;KAClB,MAAM,OAAO;IACf;IACA,OAAO;GACT;GAEA,kBAAkB;IAChB,KAAK,MAAM,MAAM;IACjB,UAAU,UAAU;IACpB,QAAQ,UAAU;IAClB,MAAM,OAAO;GACf;EACF,CAAC;CACH,GAAG;EAAC;EAAM;EAAQ;EAAQ;EAAO;EAAe;CAAM,CAAC;CAEvD,sBACc;EACV,UAAU,SAAS,KAAK;EACxB,QAAQ,UAAU,CAAC;CACrB,GACA,CAAC,CACH;;CAGA,QAAQ,aAAyB;EAC/B,IAAI,UAAU,WAAW,CAAC,QAAQ,SAAS,QAAQ,QAAQ,KAAK,QAAQ;OACnE,SAAS;CAChB;AACF;;;;;AAMA,SAAS,SAAS,SAAkB;CAClC,MAAM,CAAC,OAAO,YAAY,SAAwD;EAChF,SAAS;EACT,SAAS;CACX,CAAC;CACD,IAAI,MAAM,YAAY,SAAS,SAAS;EAAE,SAAS;EAAS,SAAS,MAAM;CAAQ,CAAC;CACpF,MAAM,qBAAqB,UAAU,cAAc;EAAE,GAAG;EAAU,SAAS;CAAK,EAAE;CAClF,OAAO;EAAE,SAAS,MAAM;EAAS;CAAa;AAChD;AAEA,SAAS,KAAK,EACZ,MACA,SACA,WACA,cACA,WACA,OACA,YACA,QACA,OACA,QACA,aAcC;CACD,MAAM,SAAS,OAAsB,IAAI;CACzC,MAAM,aAAa,OAA+B,CAAC,CAAC;CACpD,MAAM,OAAO,OAIV;EAAE,OAAO;EAAM,QAAQ;EAAM,MAAM;CAAK,CAAC;CAC5C,MAAM,WAAW,OAAO,KAAK;CAE7B,MAAM,WAAW,OAAO,KAAK;CAC7B,MAAM,eAAe,OAAsB,IAAI;CAC/C,MAAM,CAAC,SAAS,cAAc,SAAwB,IAAI;CAC1D,MAAM,EAAE,mBAAmB,gBAAgB;CAC3C,MAAM,SAAS,MAAM;CACrB,MAAM,UAAU,cAAc;CAC9B,MAAM,EAAE,SAAS,iBAAiB,SAAS,OAAO;CAClD,MAAM,OAAO,KAAK,QAAQ;CAC1B,MAAM,UAAU,YAAY,SAAS,SAAS;CAE9C,MAAM,eAAe,UAAU,KAAK,SAAS,KAAM,KAAK,OAAO,KAAM;CACrE,MAAM,iBACJ,CAAC,WAAW,cAAc,MAAM,KAAK,QAAQ,KAAK,eAAe;CACnE,MAAM,SAAS,QAAQ,cAAc,cAAc;CACnD,MAAM,cAAc,YAAY,cAAc,gBAAgB,UAAU,IAAI;CAG5E,MAAM,cAAc,OAA2B,KAAA,CAAS;CACxD,MAAM,qBAAqB;EACzB,OAAO,aAAa,YAAY,OAAO;EACvC,YAAY,UAAU,KAAA;CACxB;CACA,gBAAgB,cAAc,CAAC,OAAO,CAAC;CACvC,MAAM,gBAAgB,UAAU,KAAK,SAAS,cAAc,KAAK,KAAK,OAAO,KAAK,OAAO,MAAM;CAE/F,MAAM,UAAU,QAAQ,WAAW,EAAE,YACnC,UAAU,UAAW,KAAK,MAAM,KAAK,SAAS,CAAC,IAAI,IAAK,KAAK,UAAU,aACzE;CAGA,gBAAgB;EACd,IAAI,aAAa,YAAY,MAAM;EACnC,WAAW,QAAQ,aAAa,QAAQ,EAAE,MAAM;EAChD,aAAa,UAAU;CACzB,CAAC;CAGD,gBAAgB;EACd,IAAI,YAAY,MAAM;EACtB,MAAM,UAAU,OAAO,WAAW,cAAc,GAAI;EACpD,aAAa,OAAO,aAAa,OAAO;CAC1C,GAAG,CAAC,SAAS,YAAY,CAAC;CAE1B,MAAM,UAAU,UACd,UACI,KAAK,IAAI,EAAE,QAAQ,MAAM,CAAC,IAC1B,KAAK,IAAI,EAAE,MAAM,cAAc,KAAK,QAAS,QAAQ,MAAO,OAAO,KAAK,GAAG,CAAC;CAIlF,MAAM,wBAAwB;EAC5B,IAAI,OAAO,SAAS,SAAS,SAAS,aAAa,GACjD,aAAa,UAAU,KAAK,MAAM,KAAK,SAAS,CAAC,IAAI;EAEvD,aAAa;CACf;CAEA,MAAM,YAAY,UAAkB;EAClC,QAAQ,OAAO,KAAK,CAAC;EACrB,IAAI,CAAC,SAAS;GACZ,aAAa,UAAU,KAAK,MAAM,KAAK,SAAS,CAAC,IAAI;GACrD,aAAa;EACf;CACF;CAEA,MAAM,QAAQ,UAAuC;EACnD,MAAM,MAAM,OAAO,QAAS,sBAAsB;EAClD,MAAM,KAAM,MAAM,UAAU,IAAI,QAAQ,IAAI,QAAS;EACrD,MAAM,KAAM,MAAM,UAAU,IAAI,OAAO,IAAI,SAAU;EACrD,MAAM,OAAO,KAAK,MAAM,OAAO,GAAG,CAAC,IAAI,EAAE,IAAI;EAE7C,IAAI,SAAS;GACX,QAAQ,KAAK,IAAI,EAAE,QAAQ,OAAO,EAAE,CAAC,CAAC;GACtC;EACF;EACA,MAAM,YACJ,cAAc,MAAM,KAAK,MAAM,IAAI,QAAQ,IAAI,MAAM,IAAI;EAC3D,MAAM,OAAO,cAAc,KAAK,QAAQ,YAAY,KAAK,KAAK,QAAQ,OAAO,KAAK;EAClF,QAAQ,KAAK,IAAI,EAAE,KAAK,CAAC,CAAC;CAC5B;CAEA,MAAM,mBAAmB,OAAmC,UAAkB;EAC5E,IAAI,MAAM,QAAQ,WAAW,MAAM,QAAQ,KAAK;GAC9C,MAAM,eAAe;GACrB,SAAS,QAAQ,MAAM,CAAE,KAAK;GAC9B;EACF;EACA,MAAM,OAAO;GAAE,SAAS;GAAG,YAAY;GAAG,WAAW;GAAI,WAAW;EAAG,EAAE,MAAM;EAC/E,IAAI,SAAS,KAAA,GAAW;EACxB,MAAM,eAAe;EAErB,MAAM,QAAQ,QAAQ,OAAO,QAAQ,UAAU,QAAQ;EACvD,WAAW,IAAI;EACf,WAAW,QAAQ,KAAK,EAAE,MAAM;CAClC;CAEA,MAAM,QAAQ,UAAU,KAAK;CAC7B,MAAM,OAAO,UAAU,YAAY;CACnC,MAAM,iBAAiB;EACrB,SAAS,UAAU;EACnB,SAAS,UAAU;CACrB;CACA,OACE,qBAAC,OAAD;EACE,KAAK;EACL,SAAS,OAAO,KAAK,GAAG;EACxB,MAAK;EACL,cAAY;EACZ,gBAAgB,UAAU;GACxB,aAAa;GACb,SAAS,UAAU;GACnB,MAAM,cAAc,oBAAoB,MAAM,SAAS;GACvD,KAAK,KAAK;EACZ;EACA,gBAAgB,UAAU;GACxB,IAAI,CAAC,SAAS,SAAS;GACvB,SAAS,UAAU;GACnB,KAAK,KAAK;EACZ;EACA,mBAAmB;GACjB,IAAI,SAAS,WAAW,CAAC,SAAS;IAChC,IAAI,SAAS,SAEX,gBAAgB;SAIhB,kBAAkB;KAChB,YAAY,UAAU,OAAO,WAAW,iBAAiB,GAAG;IAC9D,CAAC;GAEL;GACA,SAAS;EACX;EACA,iBAAiB;EACjB,WAAW,GAAG,OAAO,KAAK,GAAG,SAAS;EAhCxC,UAAA;GAkCE,oBAAC,QAAD,EAAA,UACE,oBAAC,YAAD;IAAU,IAAI;IACZ,UAAA,oBAAC,UAAD;KACE,MAAM,YAAY;MAChB,KAAK,QAAQ,OAAO;KACtB;KACA,IAAI,OAAO;KACX,IAAI,OAAO;KACX,GAAG,SAAS;IACb,CAAA;GACO,CAAA,EACN,CAAA;GACN,oBAAC,UAAD;IACE,IAAI;IACJ,IAAI;IACJ,GAAG;IACH,aAAU;IACV,WAAW,OAAO,SAAS;GAC5B,CAAA;GACD,oBAAC,QAAD;IACE,MAAM,YAAY;KAChB,KAAK,QAAQ,QAAQ;IACvB;IACA,IAAI;IACJ,IAAI;IACJ,IAAI,OAAO;IACX,IAAI,OAAO;IACX,aAAa;IACb,WAAW,OAAO,UAAU;GAC7B,CAAA;GACD,oBAAC,UAAD;IAAQ,IAAI;IAAQ,IAAI;IAAQ,GAAG;IAAG,WAAW,OAAO,WAAW;GAAI,CAAA;GACvE,oBAAC,UAAD;IACE,MAAM,YAAY;KAChB,KAAK,QAAQ,SAAS;IACxB;IACA,IAAI,OAAO;IACX,IAAI,OAAO;IACX,GAAG,SAAS;IACZ,aAAU;IACV,WAAW,OAAO,WAAW;GAC9B,CAAA;GACA,YAAY,QACX,oBAAC,KAAD;IAEE,eAAY;IACZ,iBAAiB;IACjB,WAAW,OAAO,gBAAgB;IAEjC,UAAA,YAAY,SAAS,SAAS,CAAC,CAAC,KAAK,EAAE,OAAO,MAAM,UAAU,UAAU;KACvE,MAAM,QAAQ,QAAQ,MAAM,MAAM;KAClC,OACE,oBAAC,QAAD;MAEE,GAAG,MAAM;MACT,GAAG,MAAM;MACT,YAAW;MACX,kBAAiB;MACjB,WAAW,OAAO,eAAe;MAEhC,UAAA,OAAO,OAAO,OAAO;KAClB,GARC,KAQD;IAEV,CAAC;GACA,GApBI,WAAW,OAAO,OAAO,GAoB7B;GAEL,qBAAC,KAAD;IAEE,WAAW,YAAY,OAAO,KAAA,IAAY,OAAO,iBAAiB;IAFpE,UAAA,CAIG,QAAQ,KAAK,EAAE,OAAO,MAAM,UAAU,UAAU;KAC/C,MAAM,QAAQ,QAAQ,MAAM,MAAM;KAClC,MAAM,WAAW,UAAU,kBAAkB,UAAU,KAAK,SAAS,MAAM,IAAI;KAC/E,OACE,qBAAC,KAAD;MAEE,MAAM,YAAY;OAChB,WAAW,QAAQ,SAAS;MAC9B;MACA,MAAK;MACL,UAAU,WAAW,WAAW,WAAW,IAAI;MAC/C,cAAY,WAAW,MAAM,KAAK,GAAG,OAAO,IAAI;MAChD,gBAAc;MACd,iBAAe,YAAY,KAAA;MAC3B,eAAe,WAAW,KAAK;MAC/B,cAAc,WAAW,IAAI;MAC7B,YAAY,UAAU,gBAAgB,OAAO,KAAK;MAClD,UAAU,UAAU;OAElB,IAAI,MAAM,WAAW,GAAG,SAAS,KAAK;MACxC;MACA,WAAW,OAAO,WAAW;MAjB/B,UAAA;OAmBE,oBAAC,UAAD;QAAQ,IAAI,MAAM;QAAG,IAAI,MAAM;QAAG,GAAG,SAAS;QAAG,WAAW,OAAO,cAAc;OAAI,CAAA;OACpF,YAAY,SAAS,kBACpB,oBAAC,UAAD;QACE,IAAI,MAAM;QACV,IAAI,MAAM;QACV,GAAG,SAAS,IAAI;QAChB,aAAa;QACb,WAAW,OAAO,UAAU;OAC7B,CAAA;OAEH,oBAAC,QAAD;QACE,GAAG,MAAM;QACT,GAAG,MAAM;QACT,YAAW;QACX,kBAAiB;QACjB,eAAY;QACZ,iBAAe,YAAY,KAAA;QAC3B,WAAW,OAAO,eAAe;QAEhC,UAAA,OAAO,OAAO,OAAO;OAClB,CAAA;MACL;KAvCI,GAAA,KAuCJ;IAEP,CAAC,GAMD,oBAAC,KAAD;KAAG,eAAY;KAAO,UAAU,QAAQ,OAAO;KAAI,WAAU;KAC1D,UAAA,QAAQ,KAAK,EAAE,OAAO,MAAM,UAAU,UAAU;MAC/C,MAAM,QAAQ,QAAQ,MAAM,MAAM;MAClC,OACE,oBAAC,QAAD;OAEE,GAAG,MAAM;OACT,GAAG,MAAM;OACT,YAAW;OACX,kBAAiB;OACjB,WAAW,OAAO,mBAAmB;OAEpC,UAAA,OAAO,OAAO,OAAO;MAClB,GARC,KAQD;KAEV,CAAC;IACA,CAAA,CACF;GAxEI,GAAA,QAAQ,OAAO,OAAO,GAwE1B;EACA;;AAET"}
|
|
1
|
+
{"version":3,"file":"TimePicker.js","names":[],"sources":["../../../src/components/time-picker/TimePicker.tsx"],"sourcesContent":["'use client';\n\nimport { Time } from '@internationalized/date';\nimport { animate, useReducedMotion, type AnimationPlaybackControls } from 'motion/react';\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type PointerEvent,\n type ReactElement,\n type Ref,\n type RefCallback,\n type RefObject,\n} from 'react';\nimport {\n mergeProps,\n mergeRefs,\n useFocusRing,\n useFocusVisible,\n useHover,\n useLocale,\n useNumberFormatter,\n} from 'react-aria';\nimport { useControlledState } from 'react-stately/useControlledState';\nimport { useM3Spring } from '../../motion/use-m3-spring';\nimport { useM3Interaction } from '../../primitives/use-m3-interaction';\nimport { densityAttribute, type Density } from '../../tokens/density';\nimport { cn } from '../../utils/cn';\nimport { mergeDataTokens } from '../../utils/merge-data-tokens';\nimport {\n splitRootProps,\n type DescriptionAria,\n type RootDOMProps,\n type WrapperRootDOMProps,\n} from '../../utils/root-props';\nimport { KeyboardIcon, ScheduleIcon } from '../date-picker/picker-icons';\nimport { useMediaQuery } from '../date-picker/use-media-query';\nimport { IconButton } from '../icon-button/IconButton';\nimport { localeHourCycle } from './hour-cycle';\nimport { timePickerStyles } from './time-picker-styles';\n\n/** Compose's clock geometry (TimePicker.kt), in px on the 256px dial. */\nconst DIAL = 256;\nconst CENTER = DIAL / 2;\nconst OUTER_RADIUS = 101;\nconst INNER_RADIUS = 69;\nconst HANDLE = 48;\n/** A press nearer the centre than this picks the inner 24-hour ring (Compose's `MaxDistance`). */\nconst INNER_RING_MAX_DISTANCE = 74;\n/** Each number on the dial is a 48px target (M3: \"Targets for dial selectors should be 48x48dp\"). */\nconst TARGET = 48;\n\nexport interface TimePickerLabels {\n /** Name of the hour selector and field. @default \"Hour\" */\n hour: string;\n /** Name of the minute selector and field. @default \"Minute\" */\n minute: string;\n /** The AM option. @default \"AM\" */\n am: string;\n /** The PM option. @default \"PM\" */\n pm: string;\n /** Name of the AM / PM selector. @default \"AM or PM\" */\n period: string;\n /** Name of the dial. @default \"Clock\" */\n dial: string;\n /** {@link TimePickerModeToggle}'s name in dial mode. @default \"Toggle input picker\" */\n inputMode: string;\n /** {@link TimePickerModeToggle}'s name in input mode. @default \"Toggle dial picker\" */\n dialMode: string;\n /**\n * Name of a number on the dial (M3: \"{Value} Hours or minutes of {Total}\"), e.g.\n * \"7 hours of 12\" or \"35 minutes of 60\".\n * @default `${value} ${unit} of ${total}`\n */\n dialValue: (value: string, total: number, unit: 'hours' | 'minutes') => string;\n}\n\nexport interface TimePickerClassNames {\n /** The picker's root; `className` is merged here too. */\n root?: string;\n /** The hour and minute: selectors in dial mode, text fields in input mode. */\n field?: string;\n /** The labels under the input mode's fields (\"Hour\", \"Minute\"). */\n fieldLabel?: string;\n /** The colon between the hour and the minute. */\n separator?: string;\n /** The AM / PM selector. */\n period?: string;\n /** Each of the AM / PM selector's options. */\n periodOption?: string;\n /** The clock dial (dial mode): its face, numbers and hand are drawn inside it. */\n dial?: string;\n}\n\nexport type TimePickerMode = 'dial' | 'input';\n\n// No `aria-description` and the like: the picker's root is a plain container, and no single\n// element in it stands for the whole picker.\nexport interface TimePickerProps extends Omit<WrapperRootDOMProps, DescriptionAria> {\n /** Controlled time (`@internationalized/date` `Time`). */\n value?: Time;\n /** Initial time when uncontrolled. @default 12:00 */\n defaultValue?: Time;\n /** Called with the new time when the user changes it. */\n onChange?: (value: Time) => void;\n /** 12- or 24-hour clock. Left out, the locale's hour cycle (12 in en-US, 24 in en-GB). */\n hourCycle?: 12 | 24;\n /** The clock dial, or typed hour and minute fields (controlled). */\n mode?: TimePickerMode;\n /** Initial mode when uncontrolled. @default \"dial\" */\n defaultMode?: TimePickerMode;\n /** Called when the user switches between the dial and the typed input. */\n onModeChange?: (mode: TimePickerMode) => void;\n /**\n * `vertical` stacks the time over the dial; `horizontal` (landscape) puts them side by\n * side. `auto` picks vertical when the window is tall enough, horizontal when it is only\n * wide enough, and the typed input when neither fits, so the picker never scrolls (M3\n * time picker placement and adaptive design). @default \"auto\"\n */\n layout?: 'vertical' | 'horizontal' | 'auto';\n /** Words the picker shows and announces, with English defaults. */\n labels?: Partial<TimePickerLabels>;\n /**\n * Web density of the input mode, `0` to `-3`: each step takes 4px off the typed hour and\n * minute fields (72px at 0, 60px at -3). The dial keeps its size: M3 doesn't make time\n * pickers denser, since its 48px number targets are part of their accessibility. Left out,\n * the picker takes the density of the nearest `ThemeProvider` or `ThemeScope`.\n * @default 0\n */\n density?: Density;\n /** The root `<div>`'s id. */\n id?: string;\n /** Ref to the root `<div>` element. */\n ref?: Ref<HTMLDivElement>;\n /** Classes for the root element, merged with `classNames.root`; consumer classes win. */\n className?: string;\n /** Inline styles for the root element. */\n style?: CSSProperties;\n /** Classes for each part, by slot. */\n classNames?: TimePickerClassNames;\n /** `data-*` attributes go to the root. */\n [data: `data-${string}`]: string | number | boolean | undefined;\n}\n\n/*\n * Window sizes for `layout=\"auto\"`, from the picker in a `PickerDialog` (24px around the\n * panel, 24px padding, the title and the actions): the vertical dial needs 568px of\n * height; the horizontal one 452px of height and 640px of width.\n */\nconst SHORT_WINDOW = '(max-height: 567.98px)';\nconst NO_ROOM_FOR_DIAL = '(max-height: 451.98px), (max-height: 567.98px) and (max-width: 639.98px)';\n\n/** Where an `auto` time picker's dial fits: `vertical`, `horizontal` or `none`. */\nexport function useTimePickerRoom(): 'vertical' | 'horizontal' | 'none' {\n const short = useMediaQuery(SHORT_WINDOW);\n const noRoom = useMediaQuery(NO_ROOM_FOR_DIAL);\n return noRoom ? 'none' : short ? 'horizontal' : 'vertical';\n}\n\nconst defaultDialValue = (value: string, total: number, unit: 'hours' | 'minutes') =>\n `${value} ${unit} of ${total}`;\n\n/**\n * M3 time picker (Compose's `TimePicker` and `TimeInput`): hour and minute selectors you\n * can type in, with an AM / PM selector, and a clock dial whose numbers are buttons (press,\n * drag, or Tab to one and press Enter; the hour moves on to the minutes); or typed fields\n * in input mode. It turns to a horizontal layout, or the input mode, when the window has\n * no room for the dial. Put it in a {@link PickerDialog} for the modal form, with a\n * {@link TimePickerModeToggle}.\n *\n * @example\n * <TimePicker value={time} onChange={setTime} />\n */\nexport function TimePicker({\n value,\n defaultValue,\n onChange,\n hourCycle: hourCycleProp,\n mode: modeProp,\n defaultMode = 'dial',\n onModeChange,\n layout = 'auto',\n labels = {},\n density,\n id,\n ref,\n className,\n style,\n classNames,\n ...rest\n}: TimePickerProps) {\n const { root: data } = splitRootProps(rest, { wrapper: true });\n const { locale } = useLocale();\n const hourCycle = hourCycleProp ?? localeHourCycle(locale);\n const [time, setTime] = useControlledState(value, defaultValue ?? new Time(12, 0), onChange);\n const [chosenMode] = useControlledState(modeProp, defaultMode, onModeChange);\n const room = useTimePickerRoom();\n const fits = layout === 'auto' ? room : layout;\n const mode = fits === 'none' ? 'input' : chosenMode;\n const resolvedLayout = mode === 'input' || fits === 'none' ? 'vertical' : fits;\n const [selection, setSelection] = useState<'hour' | 'minute'>('hour');\n const styles = timePickerStyles({ hourCycle, mode, layout: resolvedLayout });\n const pad = useNumberFormatter({ minimumIntegerDigits: 2, useGrouping: false });\n const plain = useNumberFormatter({ useGrouping: false });\n const isPm = time.hour >= 12;\n const displayHour = hourCycle === 24 ? time.hour : time.hour % 12 || 12;\n // Two digits on both clocks, as Compose's selector and input show a 12-hour \"02\"\n // (`hourForDisplay.toLocalString(minDigits = 2)`); the dial's numbers stay unpadded.\n const formatHour = (n: number) => pad.format(n);\n const setPeriod = (pm: boolean) => {\n if (pm !== isPm) setTime(time.set({ hour: (time.hour + 12) % 24 }));\n };\n const commitHour = (hour: number) =>\n setTime(time.set({ hour: hourCycle === 24 ? hour : (hour % 12) + (isPm ? 12 : 0) }));\n const hourLabel = labels.hour ?? 'Hour';\n const minuteLabel = labels.minute ?? 'Minute';\n const minuteRef = useRef<HTMLInputElement>(null);\n // The page's direction where the picker sits (its `dir`, not only the locale's), which the\n // AM / PM options follow inside the time's left-to-right row (2026-10-17).\n const [domDirection, setDomDirection] = useState<'ltr' | 'rtl' | undefined>(undefined);\n const directionRef = useCallback<RefCallback<HTMLDivElement>>((element) => {\n if (element) setDomDirection(getComputedStyle(element).direction === 'rtl' ? 'rtl' : 'ltr');\n }, []);\n const rootRef = useMemo(() => mergeRefs<HTMLDivElement>(ref, directionRef), [ref, directionRef]);\n\n return (\n <div\n {...data}\n id={id}\n ref={rootRef}\n style={style}\n // The time picker's component tokens (time-picker-tokens.ts).\n data-tokens={mergeDataTokens('md-comp-time-picker', data)}\n data-mode={mode}\n data-layout={resolvedLayout}\n {...densityAttribute(density)}\n className={styles.root({ class: cn(classNames?.root, className) })}\n >\n {/* Time reads hour : minute, then AM / PM, in every direction (Compose forces LTR). */}\n <div dir=\"ltr\" className={styles.display()}>\n <div className={styles.time()}>\n <NumberField\n label={hourLabel}\n value={displayHour}\n min={hourCycle === 24 ? 0 : 1}\n max={hourCycle === 24 ? 23 : 12}\n format={formatHour}\n onCommit={commitHour}\n // Input mode moves on to the minute once the hour is complete (Compose's TimeInput).\n onComplete={mode === 'input' ? () => minuteRef.current?.focus() : undefined}\n selector={mode === 'dial'}\n selected={selection === 'hour'}\n onFocus={() => setSelection('hour')}\n styles={styles}\n classNames={classNames}\n />\n <span aria-hidden=\"true\" className={styles.separator({ class: classNames?.separator })}>\n :\n </span>\n <NumberField\n label={minuteLabel}\n value={time.minute}\n min={0}\n max={59}\n format={(n) => pad.format(n)}\n onCommit={(minute) => setTime(time.set({ minute }))}\n inputRef={minuteRef}\n selector={mode === 'dial'}\n selected={selection === 'minute'}\n onFocus={() => setSelection('minute')}\n styles={styles}\n classNames={classNames}\n />\n </div>\n {hourCycle === 12 && (\n <PeriodGroup\n label={labels.period ?? 'AM or PM'}\n am={labels.am ?? 'AM'}\n pm={labels.pm ?? 'PM'}\n isPm={isPm}\n onChange={setPeriod}\n direction={domDirection}\n styles={styles}\n classNames={classNames}\n />\n )}\n </div>\n {mode === 'dial' && (\n <Dial\n time={time}\n setTime={setTime}\n selection={selection}\n onHourPicked={() => setSelection('minute')}\n hourCycle={hourCycle}\n label={labels.dial ?? 'Clock'}\n valueLabel={labels.dialValue ?? defaultDialValue}\n format={(n, minutes) => (minutes || hourCycle === 24 ? pad.format(n) : plain.format(n))}\n plain={(n) => plain.format(n)}\n styles={styles}\n className={classNames?.dial}\n />\n )}\n </div>\n );\n}\n\nexport interface TimePickerModeToggleProps extends RootDOMProps<HTMLButtonElement> {\n /** The time picker's mode; the toggle switches to the other one. */\n mode: TimePickerMode;\n /** Called with the other mode when the toggle is pressed. */\n onModeChange: (mode: TimePickerMode) => void;\n /**\n * Words the toggle announces (`inputMode`, `dialMode`), with English defaults. Takes the\n * same object as the time picker's `labels`, so one set of words covers both.\n */\n labels?: Partial<TimePickerLabels>;\n /** Shown in dial mode. Defaults to Material Symbols' keyboard. */\n keyboardIcon?: ReactElement;\n /** Shown in input mode. Defaults to Material Symbols' schedule (a clock). */\n clockIcon?: ReactElement;\n /** Classes for the icon button; consumer classes win. */\n className?: string;\n /** Inline styles for the icon button. */\n style?: CSSProperties;\n /** The icon button's id. */\n id?: string;\n /** Ref to the icon button. */\n ref?: Ref<HTMLButtonElement>;\n}\n\n/**\n * The time picker's keyboard / clock icon button (M3: \"The input selector should be\n * accessible from the dial selector via the keyboard icon\"), for `PickerDialog`'s\n * `modeToggleButton`. Hidden when the window has no room for the dial, where an `auto`\n * time picker shows only the input.\n *\n * @example\n * <PickerDialog variant=\"time\" modeToggleButton={<TimePickerModeToggle mode={mode} onModeChange={setMode} />} …>\n * <TimePicker mode={mode} onModeChange={setMode} />\n * </PickerDialog>\n */\nexport function TimePickerModeToggle({\n mode,\n onModeChange,\n labels = {},\n keyboardIcon,\n clockIcon,\n className,\n style,\n id,\n ref,\n ...dom\n}: TimePickerModeToggleProps) {\n const room = useTimePickerRoom();\n if (room === 'none') return null;\n // The icon button is the toggle's root: it takes the DOM attributes too.\n const own = { ...dom, className, style, id, ref };\n return mode === 'dial' ? (\n <IconButton\n variant=\"standard\"\n // The dialog isn't dense, only the input mode's fields.\n density={0}\n icon={keyboardIcon ?? <KeyboardIcon />}\n aria-label={labels.inputMode ?? 'Toggle input picker'}\n onPress={() => onModeChange('input')}\n {...own}\n />\n ) : (\n <IconButton\n variant=\"standard\"\n density={0}\n icon={clockIcon ?? <ScheduleIcon />}\n aria-label={labels.dialMode ?? 'Toggle dial picker'}\n onPress={() => onModeChange('dial')}\n {...own}\n />\n );\n}\n\n/**\n * The AM / PM selector: a radio group (M3: \"Radio button (in list)\"), so it is one Tab stop\n * on the checked option and the arrow keys move the choice, as WAI-ARIA's radio group.\n */\nfunction PeriodGroup({\n label,\n am,\n pm,\n isPm,\n onChange,\n direction: domDirection,\n styles,\n classNames,\n}: {\n label: string;\n am: string;\n pm: string;\n isPm: boolean;\n onChange: (pm: boolean) => void;\n /** The picker's laid-out direction, once known; the locale's until then (server render). */\n direction: 'ltr' | 'rtl' | undefined;\n styles: ReturnType<typeof timePickerStyles>;\n classNames: TimePickerClassNames | undefined;\n}) {\n const { direction: localeDirection } = useLocale();\n const direction = domDirection ?? localeDirection;\n const refs = useRef<(HTMLButtonElement | null)[]>([]);\n const onKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n const forward = direction === 'rtl' ? 'ArrowLeft' : 'ArrowRight';\n const backward = direction === 'rtl' ? 'ArrowRight' : 'ArrowLeft';\n const step =\n event.key === 'ArrowDown' || event.key === forward\n ? 1\n : event.key === 'ArrowUp' || event.key === backward\n ? -1\n : 0;\n if (step === 0) return;\n event.preventDefault();\n // Two options: either step moves to the other one, wrapping.\n const next = !isPm;\n onChange(next);\n refs.current[next ? 1 : 0]?.focus();\n };\n return (\n // The options keep the page's direction for their order in the horizontal layout.\n <div\n role=\"radiogroup\"\n aria-label={label}\n dir={direction}\n onKeyDown={onKeyDown}\n data-slot=\"period-selector\"\n className={styles.period({ class: classNames?.period })}\n >\n {[false, true].map((option) => (\n <PeriodOption\n key={String(option)}\n ref={(element) => {\n refs.current[option ? 1 : 0] = element;\n }}\n label={option ? pm : am}\n selected={isPm === option}\n onSelect={() => onChange(option)}\n className={styles.periodOption({ class: classNames?.periodOption })}\n />\n ))}\n </div>\n );\n}\n\nfunction PeriodOption({\n label,\n selected,\n onSelect,\n className,\n ref: refProp,\n}: {\n label: string;\n selected: boolean;\n onSelect: () => void;\n className: string;\n ref: (element: HTMLButtonElement | null) => void;\n}) {\n const ref = useRef<HTMLButtonElement>(null);\n const { interactionProps, dataAttributes } = useM3Interaction({}, ref);\n return (\n <button\n {...mergeProps(interactionProps, { onClick: onSelect })}\n {...dataAttributes}\n ref={(element) => {\n ref.current = element;\n refProp(element);\n }}\n type=\"button\"\n role=\"radio\"\n aria-checked={selected}\n tabIndex={selected ? 0 : -1}\n data-slot=\"period-selector-option\"\n data-selected={selected || undefined}\n className={className}\n >\n {label}\n </button>\n );\n}\n\n/**\n * A typed hour or minute: the input mode's 96 × 72px field with its label below, or in\n * dial mode the 96 × 80px selector, which picks the dial's face when focused and takes\n * digits too (M3: \"The dial time picker supports both manual and dial input\").\n */\nfunction NumberField({\n label,\n value,\n min,\n max,\n format,\n onCommit,\n onComplete,\n inputRef,\n selector,\n selected,\n onFocus,\n styles,\n classNames,\n}: {\n label: string;\n value: number;\n min: number;\n max: number;\n format: (n: number) => string;\n onCommit: (value: number) => void;\n /** Called once a typed value can't take another digit (two digits, or \"3\" for an hour). */\n onComplete?: (() => void) | undefined;\n inputRef?: RefObject<HTMLInputElement | null>;\n selector: boolean;\n selected: boolean;\n onFocus: () => void;\n styles: ReturnType<typeof timePickerStyles>;\n classNames: TimePickerClassNames | undefined;\n}) {\n const [draft, setDraft] = useState<string | null>(null);\n const { focusProps, isFocusVisible } = useFocusRing();\n // A selector shows hover and pressed state layers (M3 states: Enabled, Hover, Focus, Pressed).\n const { hoverProps, isHovered } = useHover({ isDisabled: !selector });\n const [pressed, setPressed] = useState(false);\n const release = () => setPressed(false);\n const parsed = draft === null ? value : Number(draft);\n const invalid =\n draft !== null && (draft === '' || Number.isNaN(parsed) || parsed < min || parsed > max);\n const input = (\n <input\n {...mergeProps(focusProps, hoverProps, {\n onFocus: (event: { currentTarget: HTMLInputElement }) => {\n event.currentTarget.select();\n onFocus();\n },\n })}\n ref={inputRef}\n onPointerDown={() => selector && setPressed(true)}\n onPointerUp={release}\n onPointerLeave={release}\n onPointerCancel={release}\n inputMode=\"numeric\"\n aria-label={label}\n aria-invalid={invalid || undefined}\n maxLength={2}\n value={draft ?? format(value)}\n data-slot={selector ? 'time-selector' : undefined}\n data-selected={(selector && selected) || undefined}\n data-focus-visible={(selector && isFocusVisible) || undefined}\n data-hovered={(selector && isHovered) || undefined}\n data-pressed={(selector && pressed) || undefined}\n onChange={(event) => {\n const text = event.target.value.replace(/\\D/g, '');\n setDraft(text);\n const next = Number(text);\n if (text === '' || next < min || next > max) return;\n onCommit(next);\n if (text.length === 2 || next * 10 > max) onComplete?.();\n }}\n onBlur={() => setDraft(null)}\n onKeyDown={(event) => {\n if (event.key !== 'ArrowUp' && event.key !== 'ArrowDown') return;\n event.preventDefault();\n const step = event.key === 'ArrowUp' ? 1 : -1;\n const next = value + step > max ? min : value + step < min ? max : value + step;\n setDraft(null);\n onCommit(next);\n }}\n className={\n selector\n ? styles.selector({ class: classNames?.field })\n : styles.field({ class: classNames?.field })\n }\n />\n );\n if (selector) return input;\n return (\n <label className={styles.fieldColumn()}>\n {input}\n <span aria-hidden=\"true\" className={styles.fieldLabel({ class: classNames?.fieldLabel })}>\n {label}\n </span>\n </label>\n );\n}\n\n/** Angle of a dial position (0 at 12 o'clock, clockwise), in turns. */\nconst turnOf = (x: number, y: number) =>\n (Math.atan2(x - CENTER, CENTER - y) / (2 * Math.PI) + 1) % 1;\nconst pointOf = (turn: number, radius: number) => ({\n x: CENTER + radius * Math.sin(turn * 2 * Math.PI),\n y: CENTER - radius * Math.cos(turn * 2 * Math.PI),\n});\n\ninterface DialNumber {\n value: number;\n turn: number;\n radius: number;\n}\n\n/** The numbers on the face: minutes in fives, hours 12 / 1–11, plus 12–23 inside for 24 hours. */\nfunction dialNumbers(minutes: boolean, hourCycle: 12 | 24): DialNumber[] {\n const ring = (radius: number, valueAt: (i: number) => number) =>\n Array.from({ length: 12 }, (_, i) => ({ value: valueAt(i), turn: i / 12, radius }));\n if (minutes) return ring(OUTER_RADIUS, (i) => i * 5);\n if (hourCycle === 12) return ring(OUTER_RADIUS, (i) => i || 12);\n // 24 hours: 00–11 on the outer ring, 12–23 on the inner one (as Compose and Android).\n return [...ring(OUTER_RADIUS, (i) => i), ...ring(INNER_RADIUS, (i) => i + 12)];\n}\n\n/** The shortest signed way round the dial from one turn to another, in turns (−½ to ½). */\nconst shortestTurn = (from: number, to: number) => ((((to - from) % 1) + 1.5) % 1) - 0.5;\n\n/**\n * How close the hand is to its value when it counts as there: Compose's turn ends at the\n * spring's visibility threshold of 0.01 rad (about 0.6°), and here within 1px of the radius\n * too, while the last fraction of the spring still plays out.\n */\nconst ARRIVED_TURN = 0.01 / (2 * Math.PI);\nconst ARRIVED_RADIUS = 1;\n\n/** Runs and clears the callbacks waiting in `queue`. */\nconst flush = (queue: RefObject<(() => void)[]>) => {\n const callbacks = queue.current;\n queue.current = [];\n for (const callback of callbacks) callback();\n};\n\n/**\n * The hand's track, handle and the clip of the `on-primary` numbers follow the drawn angle,\n * set as attributes from an animation (no React render per frame). It turns to a new value\n * on the default spatial spring along the shortest arc (M3: \"Spatial springs apply to …\n * rotation\"; Compose animates the selector on hour ↔ minute and taps), follows a drag\n * directly, and jumps under reduced motion. Returns `whenSettled`, which runs a callback\n * once the hand rests on its value.\n */\nfunction useDialHand(\n turn: number,\n radius: number,\n follow: RefObject<boolean>,\n parts: RefObject<{\n track: SVGLineElement | null;\n handle: SVGCircleElement | null;\n clip: SVGCircleElement | null;\n }>,\n) {\n const spring = useM3Spring('spatial', 'default');\n const reducedMotion = useReducedMotion() ?? false;\n const drawn = useRef({ turn, radius });\n const animation = useRef<AnimationPlaybackControls | null>(null);\n // Callbacks waiting for the hand to arrive (`whenSettled`), and whether it has.\n const settled = useRef<(() => void)[]>([]);\n const arrived = useRef(true);\n\n // Before paint, so the hand never flashes at its new value before turning there.\n useLayoutEffect(() => {\n const draw = (nextTurn: number, nextRadius: number) => {\n drawn.current = { turn: nextTurn, radius: nextRadius };\n const point = pointOf(nextTurn, nextRadius);\n const { track, handle, clip } = parts.current;\n track?.setAttribute('x2', String(point.x));\n track?.setAttribute('y2', String(point.y));\n for (const circle of [handle, clip]) {\n circle?.setAttribute('cx', String(point.x));\n circle?.setAttribute('cy', String(point.y));\n }\n };\n const from = drawn.current;\n const delta = shortestTurn(from.turn, turn);\n animation.current?.stop();\n animation.current = null;\n if (follow.current || reducedMotion || (delta === 0 && from.radius === radius)) {\n draw(turn, radius);\n arrived.current = true;\n flush(settled);\n return;\n }\n // React has written the new value's attributes; start from where the hand was.\n draw(from.turn, from.radius);\n arrived.current = false;\n // Two frames in a row within the threshold: the hand swinging past its value at speed\n // (the expressive spring overshoots by about 1.5%) doesn't count as there.\n let near = false;\n animation.current = animate(0, 1, {\n ...spring,\n onUpdate: (progress) => {\n draw(from.turn + delta * progress, from.radius + (radius - from.radius) * progress);\n const close =\n Math.abs(delta * (1 - progress)) <= ARRIVED_TURN &&\n Math.abs((radius - from.radius) * (1 - progress)) <= ARRIVED_RADIUS;\n if (close && near && !arrived.current) {\n arrived.current = true;\n flush(settled);\n }\n near = close;\n },\n // A spring rests within a small distance of its target; end exactly on the value.\n onComplete: () => {\n draw(turn, radius);\n animation.current = null;\n arrived.current = true;\n flush(settled);\n },\n });\n }, [turn, radius, follow, parts, reducedMotion, spring]);\n\n useEffect(\n () => () => {\n animation.current?.stop();\n settled.current = [];\n },\n [],\n );\n\n /** Runs `callback` once the hand has arrived at its value (at once if it already has). */\n return (callback: () => void) => {\n if (animation.current && !arrived.current) settled.current.push(callback);\n else callback();\n };\n}\n\n/**\n * Which faces to draw: the current one, and the one it replaced while that fades out\n * (Compose crossfades the hour and minute faces).\n */\nfunction useFaces(minutes: boolean) {\n const [faces, setFaces] = useState<{ current: boolean; leaving: boolean | null }>({\n current: minutes,\n leaving: null,\n });\n if (faces.current !== minutes) setFaces({ current: minutes, leaving: faces.current });\n const clearLeaving = () => setFaces((previous) => ({ ...previous, leaving: null }));\n return { leaving: faces.leaving, clearLeaving };\n}\n\nfunction Dial({\n time,\n setTime,\n selection,\n onHourPicked,\n hourCycle,\n label,\n valueLabel,\n format,\n plain,\n styles,\n className,\n}: {\n time: Time;\n setTime: (time: Time) => void;\n selection: 'hour' | 'minute';\n onHourPicked: () => void;\n hourCycle: 12 | 24;\n label: string;\n valueLabel: (value: string, total: number, unit: 'hours' | 'minutes') => string;\n /** A number as shown on the hour face (`minutes` false) or the minute face. */\n format: (n: number, minutes: boolean) => string;\n plain: (n: number) => string;\n styles: ReturnType<typeof timePickerStyles>;\n className?: string | undefined;\n}) {\n const svgRef = useRef<SVGSVGElement>(null);\n const numberRefs = useRef<(SVGGElement | null)[]>([]);\n const hand = useRef<{\n track: SVGLineElement | null;\n handle: SVGCircleElement | null;\n clip: SVGCircleElement | null;\n }>({ track: null, handle: null, clip: null });\n const pressing = useRef(false);\n // True while a press moves across the dial: the hand follows it without a spring.\n const dragging = useRef(false);\n const pendingFocus = useRef<number | null>(null);\n const [focused, setFocused] = useState<number | null>(null);\n const { isFocusVisible } = useFocusVisible();\n const clipId = useId();\n const minutes = selection === 'minute';\n const { leaving, clearLeaving } = useFaces(minutes);\n const isPm = time.hour >= 12;\n const numbers = dialNumbers(minutes, hourCycle);\n\n const selectedTurn = minutes ? time.minute / 60 : (time.hour % 12) / 12;\n const selectedRadius =\n !minutes && hourCycle === 24 && time.hour >= 12 ? INNER_RADIUS : OUTER_RADIUS;\n const handle = pointOf(selectedTurn, selectedRadius);\n const whenSettled = useDialHand(selectedTurn, selectedRadius, dragging, hand);\n // A tapped hour's switch to the minutes, waiting for the hand (cancelled by a new press,\n // a face change or unmounting).\n const switchTimer = useRef<number | undefined>(undefined);\n const cancelSwitch = () => {\n window.clearTimeout(switchTimer.current);\n switchTimer.current = undefined;\n };\n useEffect(() => cancelSwitch, [minutes]);\n const selectedValue = minutes ? time.minute : hourCycle === 24 ? time.hour : time.hour % 12 || 12;\n // The number Tab lands on: the chosen one, or the nearest five for other minutes.\n const tabStop = numbers.findIndex(({ value }) =>\n minutes ? value === (Math.round(time.minute / 5) * 5) % 60 : value === selectedValue,\n );\n\n // After an hour is chosen with the keyboard, focus follows to the chosen minute.\n useEffect(() => {\n if (pendingFocus.current === null) return;\n numberRefs.current[pendingFocus.current]?.focus();\n pendingFocus.current = null;\n });\n\n // The replaced face is gone once it has faded out (or at once without transitions).\n useEffect(() => {\n if (leaving === null) return;\n const timeout = window.setTimeout(clearLeaving, 1000);\n return () => window.clearTimeout(timeout);\n }, [leaving, clearLeaving]);\n\n const toTime = (value: number) =>\n minutes\n ? time.set({ minute: value })\n : time.set({ hour: hourCycle === 24 ? value : (value % 12) + (isPm ? 12 : 0) });\n\n // The minute face replaces the hour face's numbers, so focus on one of them would drop to\n // the page: it moves on to the chosen minute's number, as for the keyboard.\n const switchToMinutes = () => {\n if (svgRef.current?.contains(document.activeElement)) {\n pendingFocus.current = Math.round(time.minute / 5) % 12;\n }\n onHourPicked();\n };\n\n const activate = (value: number) => {\n setTime(toTime(value));\n if (!minutes) {\n pendingFocus.current = Math.round(time.minute / 5) % 12;\n onHourPicked();\n }\n };\n\n const pick = (event: PointerEvent<SVGSVGElement>) => {\n const box = svgRef.current!.getBoundingClientRect();\n const x = ((event.clientX - box.left) / box.width) * DIAL;\n const y = ((event.clientY - box.top) / box.height) * DIAL;\n const step = Math.round(turnOf(x, y) * 12) % 12;\n // Taps and drags pick minutes in fives (Compose); the keyboard and typing reach any minute.\n if (minutes) {\n setTime(time.set({ minute: step * 5 }));\n return;\n }\n const innerRing =\n hourCycle === 24 && Math.hypot(x - CENTER, y - CENTER) < INNER_RING_MAX_DISTANCE;\n const hour = hourCycle === 24 ? step + (innerRing ? 12 : 0) : step + (isPm ? 12 : 0);\n setTime(time.set({ hour }));\n };\n\n const onNumberKeyDown = (event: KeyboardEvent<SVGGElement>, index: number) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n activate(numbers[index]!.value);\n return;\n }\n const step = { ArrowUp: 1, ArrowRight: 1, ArrowDown: -1, ArrowLeft: -1 }[event.key];\n if (step === undefined) return;\n event.preventDefault();\n // Clockwise through the values; a 24-hour face runs on from 11 to 12 on the inner ring.\n const next = (index + step + numbers.length) % numbers.length;\n setFocused(next);\n numberRefs.current[next]?.focus();\n };\n\n const total = minutes ? 60 : hourCycle;\n const unit = minutes ? 'minutes' : 'hours';\n const endPress = () => {\n pressing.current = false;\n dragging.current = false;\n };\n return (\n <svg\n ref={svgRef}\n viewBox={`0 0 ${DIAL} ${DIAL}`}\n role=\"group\"\n aria-label={label}\n onPointerDown={(event) => {\n cancelSwitch();\n pressing.current = true;\n event.currentTarget.setPointerCapture?.(event.pointerId);\n pick(event);\n }}\n onPointerMove={(event) => {\n if (!pressing.current) return;\n dragging.current = true;\n pick(event);\n }}\n onPointerUp={() => {\n if (pressing.current && !minutes) {\n if (dragging.current) {\n // A drag already shows the hour under the finger: switch on release.\n switchToMinutes();\n } else {\n // A tap turns the hand to the hour, shows it for 100ms, then switches\n // (Compose's `onTap`: `rotateTo(animate = true)`, `delay(100)`).\n whenSettled(() => {\n switchTimer.current = window.setTimeout(switchToMinutes, 100);\n });\n }\n }\n endPress();\n }}\n onPointerCancel={endPress}\n className={cn(styles.dial(), className)}\n >\n <defs>\n <clipPath id={clipId}>\n <circle\n ref={(element) => {\n hand.current.clip = element;\n }}\n cx={handle.x}\n cy={handle.y}\n r={HANDLE / 2}\n />\n </clipPath>\n </defs>\n <circle\n cx={CENTER}\n cy={CENTER}\n r={CENTER}\n data-slot=\"clock-dial\"\n className={styles.dialFace()}\n />\n <line\n ref={(element) => {\n hand.current.track = element;\n }}\n x1={CENTER}\n y1={CENTER}\n x2={handle.x}\n y2={handle.y}\n strokeWidth={2}\n className={styles.dialTrack()}\n />\n <circle cx={CENTER} cy={CENTER} r={4} className={styles.dialHandle()} />\n <circle\n ref={(element) => {\n hand.current.handle = element;\n }}\n cx={handle.x}\n cy={handle.y}\n r={HANDLE / 2}\n data-slot=\"clock-dial-selector-handle\"\n className={styles.dialHandle()}\n />\n {leaving !== null && (\n <g\n key={`leaving-${String(leaving)}`}\n aria-hidden=\"true\"\n onTransitionEnd={clearLeaving}\n className={styles.dialFaceLeaving()}\n >\n {dialNumbers(leaving, hourCycle).map(({ value, turn, radius }, index) => {\n const point = pointOf(turn, radius);\n return (\n <text\n key={index}\n x={point.x}\n y={point.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className={styles.dialNumberText()}\n >\n {format(value, leaving)}\n </text>\n );\n })}\n </g>\n )}\n <g\n key={`face-${String(minutes)}`}\n className={leaving === null ? undefined : styles.dialFaceEntering()}\n >\n {numbers.map(({ value, turn, radius }, index) => {\n const point = pointOf(turn, radius);\n const selected = value === selectedValue && (minutes ? time.minute % 5 === 0 : true);\n return (\n <g\n key={index}\n ref={(element) => {\n numberRefs.current[index] = element;\n }}\n role=\"button\"\n tabIndex={index === (focused ?? tabStop) ? 0 : -1}\n aria-label={valueLabel(plain(value), total, unit)}\n aria-pressed={selected}\n data-selected={selected || undefined}\n onFocus={() => setFocused(index)}\n onBlur={() => setFocused(null)}\n onKeyDown={(event) => onNumberKeyDown(event, index)}\n onClick={(event) => {\n // Assistive technology activates with a click that has no pointer behind it.\n if (event.detail === 0) activate(value);\n }}\n className={styles.dialNumber()}\n >\n <circle cx={point.x} cy={point.y} r={TARGET / 2} className={styles.dialNumberHit()} />\n {focused === index && isFocusVisible && (\n <circle\n cx={point.x}\n cy={point.y}\n r={TARGET / 2 - 1}\n strokeWidth={3}\n className={styles.dialFocus()}\n />\n )}\n <text\n x={point.x}\n y={point.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n aria-hidden=\"true\"\n data-selected={selected || undefined}\n className={styles.dialNumberText()}\n >\n {format(value, minutes)}\n </text>\n </g>\n );\n })}\n {/*\n * The numbers again in `on-primary`, clipped to the handle, so whatever part of a\n * number the handle covers reads on it (M3: the selected label is `on-primary`;\n * Compose blends the two layers the same way).\n */}\n <g aria-hidden=\"true\" clipPath={`url(#${clipId})`} className=\"pointer-events-none\">\n {numbers.map(({ value, turn, radius }, index) => {\n const point = pointOf(turn, radius);\n return (\n <text\n key={index}\n x={point.x}\n y={point.y}\n textAnchor=\"middle\"\n dominantBaseline=\"central\"\n className={styles.dialNumberOnHandle()}\n >\n {format(value, minutes)}\n </text>\n );\n })}\n </g>\n </g>\n </svg>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/components/time-picker/hour-cycle.ts
|
|
2
|
+
/** The locale's clock: 12 in en-US, 24 in en-GB. */
|
|
3
|
+
function localeHourCycle(locale) {
|
|
4
|
+
const cycle = new Intl.DateTimeFormat(locale, { hour: "numeric" }).resolvedOptions().hourCycle;
|
|
5
|
+
return cycle === "h11" || cycle === "h12" ? 12 : 24;
|
|
6
|
+
}
|
|
7
|
+
//#endregion
|
|
8
|
+
export { localeHourCycle };
|
|
9
|
+
|
|
10
|
+
//# sourceMappingURL=hour-cycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hour-cycle.js","names":[],"sources":["../../../src/components/time-picker/hour-cycle.ts"],"sourcesContent":["/** The locale's clock: 12 in en-US, 24 in en-GB. */\nexport function localeHourCycle(locale: string): 12 | 24 {\n const cycle = new Intl.DateTimeFormat(locale, { hour: 'numeric' }).resolvedOptions().hourCycle;\n return cycle === 'h11' || cycle === 'h12' ? 12 : 24;\n}\n"],"mappings":";;AACA,SAAgB,gBAAgB,QAAyB;CACvD,MAAM,QAAQ,IAAI,KAAK,eAAe,QAAQ,EAAE,MAAM,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC;CACrF,OAAO,UAAU,SAAS,UAAU,QAAQ,KAAK;AACnD"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Time } from "@internationalized/date";
|
|
2
|
+
//#region src/components/time-picker/time-format.ts
|
|
3
|
+
/** Any day: only the time of day is read, and this one has no clock change. */
|
|
4
|
+
const at = (time) => new Date(2e3, 0, 15, time.hour, time.minute);
|
|
5
|
+
/** Formats a time on the given clock. */
|
|
6
|
+
function formatTime(time, locale, hourCycle) {
|
|
7
|
+
return new Intl.DateTimeFormat(locale, {
|
|
8
|
+
hour: hourCycle === 24 ? "2-digit" : "numeric",
|
|
9
|
+
minute: "2-digit",
|
|
10
|
+
hourCycle: hourCycle === 24 ? "h23" : "h12"
|
|
11
|
+
}).format(at(time));
|
|
12
|
+
}
|
|
13
|
+
/** The locale's words for morning and afternoon, in lower case. */
|
|
14
|
+
function dayPeriods(locale) {
|
|
15
|
+
const period = (hour) => new Intl.DateTimeFormat(locale, {
|
|
16
|
+
hour: "numeric",
|
|
17
|
+
hourCycle: "h12"
|
|
18
|
+
}).formatToParts(new Date(2e3, 0, 15, hour)).find((part) => part.type === "dayPeriod")?.value.toLowerCase();
|
|
19
|
+
return {
|
|
20
|
+
am: period(9),
|
|
21
|
+
pm: period(21)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** The first digit of the decimal digit sets people commonly type or paste. */
|
|
25
|
+
const DIGIT_ZEROS = [
|
|
26
|
+
1632,
|
|
27
|
+
1776,
|
|
28
|
+
2406,
|
|
29
|
+
3664,
|
|
30
|
+
65296
|
|
31
|
+
];
|
|
32
|
+
function toLatinDigits(text) {
|
|
33
|
+
return text.replace(/\p{Nd}/gu, (digit) => {
|
|
34
|
+
const code = digit.codePointAt(0);
|
|
35
|
+
const zero = DIGIT_ZEROS.find((start) => code >= start && code <= start + 9);
|
|
36
|
+
return zero === void 0 ? digit : String(code - zero);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Reads a time as people type or paste it: `9`, `930`, `0930`, `9:30`, `9.30`, `9h30`,
|
|
41
|
+
* `21:30`, `9pm`, `9:30 pm`. Returns `null` for anything else, such as `9:3`, which could be
|
|
42
|
+
* 9:03 or 9:30.
|
|
43
|
+
*/
|
|
44
|
+
function parseTimeText(text, locale) {
|
|
45
|
+
let rest = toLatinDigits(text).trim().toLowerCase();
|
|
46
|
+
const local = dayPeriods(locale);
|
|
47
|
+
let period = null;
|
|
48
|
+
for (const [name, word] of [["am", local.am], ["pm", local.pm]]) if (word && /\p{L}/u.test(word) && rest.includes(word)) {
|
|
49
|
+
period = name;
|
|
50
|
+
rest = rest.replace(word, "").trim();
|
|
51
|
+
}
|
|
52
|
+
if (period === null) {
|
|
53
|
+
const latin = /\s*([ap])\.?(?:m\.?)?$/.exec(rest);
|
|
54
|
+
if (latin) {
|
|
55
|
+
period = latin[1] === "a" ? "am" : "pm";
|
|
56
|
+
rest = rest.slice(0, latin.index);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
rest = rest.replace(/h$/, "");
|
|
60
|
+
let hour;
|
|
61
|
+
let minute = 0;
|
|
62
|
+
const separated = /^(\d{1,2})[:.h\s](\d{2})(?:[:.]\d{2})?$/.exec(rest);
|
|
63
|
+
if (separated) {
|
|
64
|
+
hour = Number(separated[1]);
|
|
65
|
+
minute = Number(separated[2]);
|
|
66
|
+
} else if (/^\d{1,2}$/.test(rest)) hour = Number(rest);
|
|
67
|
+
else if (/^\d{3,4}$/.test(rest)) {
|
|
68
|
+
hour = Number(rest.slice(0, -2));
|
|
69
|
+
minute = Number(rest.slice(-2));
|
|
70
|
+
} else return null;
|
|
71
|
+
if (minute > 59) return null;
|
|
72
|
+
if (period !== null) {
|
|
73
|
+
if (hour === 0 || hour > 23) return null;
|
|
74
|
+
if (hour > 12) {
|
|
75
|
+
if (period === "am") return null;
|
|
76
|
+
} else hour = hour % 12 + (period === "pm" ? 12 : 0);
|
|
77
|
+
}
|
|
78
|
+
if (hour > 23) return null;
|
|
79
|
+
return new Time(hour, minute);
|
|
80
|
+
}
|
|
81
|
+
//#endregion
|
|
82
|
+
export { formatTime, parseTimeText };
|
|
83
|
+
|
|
84
|
+
//# sourceMappingURL=time-format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"time-format.js","names":[],"sources":["../../../src/components/time-picker/time-format.ts"],"sourcesContent":["import { Time } from '@internationalized/date';\n\n/** Any day: only the time of day is read, and this one has no clock change. */\nconst at = (time: Time) => new Date(2000, 0, 15, time.hour, time.minute);\n\n/** Formats a time on the given clock. */\nexport function formatTime(time: Time, locale: string, hourCycle: 12 | 24): string {\n return new Intl.DateTimeFormat(locale, {\n hour: hourCycle === 24 ? '2-digit' : 'numeric',\n minute: '2-digit',\n hourCycle: hourCycle === 24 ? 'h23' : 'h12',\n }).format(at(time));\n}\n\n/** The locale's words for morning and afternoon, in lower case. */\nfunction dayPeriods(locale: string) {\n const period = (hour: number) =>\n new Intl.DateTimeFormat(locale, { hour: 'numeric', hourCycle: 'h12' })\n .formatToParts(new Date(2000, 0, 15, hour))\n .find((part) => part.type === 'dayPeriod')\n ?.value.toLowerCase();\n return { am: period(9), pm: period(21) };\n}\n\n/** The first digit of the decimal digit sets people commonly type or paste. */\nconst DIGIT_ZEROS = [0x0660, 0x06f0, 0x0966, 0x0e50, 0xff10];\n\nfunction toLatinDigits(text: string) {\n return text.replace(/\\p{Nd}/gu, (digit) => {\n const code = digit.codePointAt(0)!;\n const zero = DIGIT_ZEROS.find((start) => code >= start && code <= start + 9);\n return zero === undefined ? digit : String(code - zero);\n });\n}\n\n/**\n * Reads a time as people type or paste it: `9`, `930`, `0930`, `9:30`, `9.30`, `9h30`,\n * `21:30`, `9pm`, `9:30 pm`. Returns `null` for anything else, such as `9:3`, which could be\n * 9:03 or 9:30.\n */\nexport function parseTimeText(text: string, locale: string): Time | null {\n let rest = toLatinDigits(text).trim().toLowerCase();\n const local = dayPeriods(locale);\n let period: 'am' | 'pm' | null = null;\n for (const [name, word] of [\n ['am', local.am],\n ['pm', local.pm],\n ] as const) {\n if (word && /\\p{L}/u.test(word) && rest.includes(word)) {\n period = name;\n rest = rest.replace(word, '').trim();\n }\n }\n if (period === null) {\n const latin = /\\s*([ap])\\.?(?:m\\.?)?$/.exec(rest);\n if (latin) {\n period = latin[1] === 'a' ? 'am' : 'pm';\n rest = rest.slice(0, latin.index);\n }\n }\n rest = rest.replace(/h$/, '');\n\n let hour: number;\n let minute = 0;\n const separated = /^(\\d{1,2})[:.h\\s](\\d{2})(?:[:.]\\d{2})?$/.exec(rest);\n if (separated) {\n hour = Number(separated[1]);\n minute = Number(separated[2]);\n } else if (/^\\d{1,2}$/.test(rest)) {\n hour = Number(rest);\n } else if (/^\\d{3,4}$/.test(rest)) {\n hour = Number(rest.slice(0, -2));\n minute = Number(rest.slice(-2));\n } else {\n return null;\n }\n if (minute > 59) return null;\n if (period !== null) {\n if (hour === 0 || hour > 23) return null;\n if (hour > 12) {\n // \"21:30 pm\" says the same thing twice; \"13 am\" contradicts itself.\n if (period === 'am') return null;\n } else {\n hour = (hour % 12) + (period === 'pm' ? 12 : 0);\n }\n }\n if (hour > 23) return null;\n return new Time(hour, minute);\n}\n"],"mappings":";;;AAGA,MAAM,MAAM,SAAe,IAAI,KAAK,KAAM,GAAG,IAAI,KAAK,MAAM,KAAK,MAAM;;AAGvE,SAAgB,WAAW,MAAY,QAAgB,WAA4B;CACjF,OAAO,IAAI,KAAK,eAAe,QAAQ;EACrC,MAAM,cAAc,KAAK,YAAY;EACrC,QAAQ;EACR,WAAW,cAAc,KAAK,QAAQ;CACxC,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAC;AACpB;;AAGA,SAAS,WAAW,QAAgB;CAClC,MAAM,UAAU,SACd,IAAI,KAAK,eAAe,QAAQ;EAAE,MAAM;EAAW,WAAW;CAAM,CAAC,CAAC,CACnE,cAAc,IAAI,KAAK,KAAM,GAAG,IAAI,IAAI,CAAC,CAAC,CAC1C,MAAM,SAAS,KAAK,SAAS,WAAW,CAAC,EACxC,MAAM,YAAY;CACxB,OAAO;EAAE,IAAI,OAAO,CAAC;EAAG,IAAI,OAAO,EAAE;CAAE;AACzC;;AAGA,MAAM,cAAc;CAAC;CAAQ;CAAQ;CAAQ;CAAQ;AAAM;AAE3D,SAAS,cAAc,MAAc;CACnC,OAAO,KAAK,QAAQ,aAAa,UAAU;EACzC,MAAM,OAAO,MAAM,YAAY,CAAC;EAChC,MAAM,OAAO,YAAY,MAAM,UAAU,QAAQ,SAAS,QAAQ,QAAQ,CAAC;EAC3E,OAAO,SAAS,KAAA,IAAY,QAAQ,OAAO,OAAO,IAAI;CACxD,CAAC;AACH;;;;;;AAOA,SAAgB,cAAc,MAAc,QAA6B;CACvE,IAAI,OAAO,cAAc,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,YAAY;CAClD,MAAM,QAAQ,WAAW,MAAM;CAC/B,IAAI,SAA6B;CACjC,KAAK,MAAM,CAAC,MAAM,SAAS,CACzB,CAAC,MAAM,MAAM,EAAE,GACf,CAAC,MAAM,MAAM,EAAE,CACjB,GACE,IAAI,QAAQ,SAAS,KAAK,IAAI,KAAK,KAAK,SAAS,IAAI,GAAG;EACtD,SAAS;EACT,OAAO,KAAK,QAAQ,MAAM,EAAE,CAAC,CAAC,KAAK;CACrC;CAEF,IAAI,WAAW,MAAM;EACnB,MAAM,QAAQ,yBAAyB,KAAK,IAAI;EAChD,IAAI,OAAO;GACT,SAAS,MAAM,OAAO,MAAM,OAAO;GACnC,OAAO,KAAK,MAAM,GAAG,MAAM,KAAK;EAClC;CACF;CACA,OAAO,KAAK,QAAQ,MAAM,EAAE;CAE5B,IAAI;CACJ,IAAI,SAAS;CACb,MAAM,YAAY,0CAA0C,KAAK,IAAI;CACrE,IAAI,WAAW;EACb,OAAO,OAAO,UAAU,EAAE;EAC1B,SAAS,OAAO,UAAU,EAAE;CAC9B,OAAO,IAAI,YAAY,KAAK,IAAI,GAC9B,OAAO,OAAO,IAAI;MACb,IAAI,YAAY,KAAK,IAAI,GAAG;EACjC,OAAO,OAAO,KAAK,MAAM,GAAG,EAAE,CAAC;EAC/B,SAAS,OAAO,KAAK,MAAM,EAAE,CAAC;CAChC,OACE,OAAO;CAET,IAAI,SAAS,IAAI,OAAO;CACxB,IAAI,WAAW,MAAM;EACnB,IAAI,SAAS,KAAK,OAAO,IAAI,OAAO;EACpC,IAAI,OAAO,IAEL;OAAA,WAAW,MAAM,OAAO;EAAA,OAE5B,OAAQ,OAAO,MAAO,WAAW,OAAO,KAAK;CAEjD;CACA,IAAI,OAAO,IAAI,OAAO;CACtB,OAAO,IAAI,KAAK,MAAM,MAAM;AAC9B"}
|
|
@@ -27,6 +27,13 @@ export interface ButtonGroupProps extends Omit<ComponentPropsWithRef<'div'>, 'on
|
|
|
27
27
|
* @default "standard"
|
|
28
28
|
*/
|
|
29
29
|
variant?: ButtonGroupVariant;
|
|
30
|
+
/**
|
|
31
|
+
* Whether a `connected` group spans its container (M3's guidance, and the default). `false`
|
|
32
|
+
* sizes the group to its buttons, for one beside other controls; the buttons stay equally
|
|
33
|
+
* wide. Has no effect on a `standard` group or one with `overflow`.
|
|
34
|
+
* @default true
|
|
35
|
+
*/
|
|
36
|
+
fullWidth?: boolean;
|
|
30
37
|
/**
|
|
31
38
|
* Size shared by every button and icon button in the group; it also sets the spacing.
|
|
32
39
|
* Unset, the buttons keep their own (or an enclosing toolbar's) size.
|
|
@@ -72,7 +72,7 @@ function ButtonGroup(props) {
|
|
|
72
72
|
directionRef
|
|
73
73
|
}) });
|
|
74
74
|
}
|
|
75
|
-
function ButtonGroupRoot({ variant = "standard", size, shape, buttonVariant, disabled, selectionMode = "none", selectedKeys, defaultSelectedKeys, onSelectionChange, deselectable = true, expandedRatio, overflow = false, labels, className, classNames, children, ref, directionRef, ...props }) {
|
|
75
|
+
function ButtonGroupRoot({ variant = "standard", size, shape, buttonVariant, disabled, selectionMode = "none", selectedKeys, defaultSelectedKeys, onSelectionChange, deselectable = true, expandedRatio, overflow = false, fullWidth = true, labels, className, classNames, children, ref, directionRef, ...props }) {
|
|
76
76
|
const domRef = useGroupRef(ref, directionRef);
|
|
77
77
|
const parent = useContext(ButtonContext);
|
|
78
78
|
const hasSelection = selectionMode !== "none";
|
|
@@ -155,7 +155,8 @@ function ButtonGroupRoot({ variant = "standard", size, shape, buttonVariant, dis
|
|
|
155
155
|
className: cn(buttonGroupStyles({
|
|
156
156
|
variant,
|
|
157
157
|
size: groupSize,
|
|
158
|
-
overflow
|
|
158
|
+
overflow,
|
|
159
|
+
fullWidth
|
|
159
160
|
}), classNames?.root, className),
|
|
160
161
|
children: hasSelection ? /* @__PURE__ */ jsx(ToggleGroupStateContext, {
|
|
161
162
|
value: state,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ButtonGroup.js","names":[],"sources":["../../../src/composites/button-group/ButtonGroup.tsx"],"sourcesContent":["'use client';\n\nimport {\n Children,\n isValidElement,\n useContext,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type HTMLAttributes,\n type Key,\n type ReactElement,\n type ReactNode,\n type Ref,\n type RefCallback,\n type RefObject,\n} from 'react';\nimport { mergeProps, mergeRefs, useObjectRef, useToggleButtonGroup, useToolbar } from 'react-aria';\nimport { useToggleGroupState } from 'react-stately';\nimport { Button } from '../../components/button/Button';\nimport type { ButtonShape, ButtonSize, ButtonVariant } from '../../components/button/button-styles';\nimport {\n ButtonContext,\n ToggleGroupStateContext,\n type ButtonContextValue,\n} from '../../components/button/ButtonContext';\nimport { IconButton } from '../../components/icon-button/IconButton';\nimport type { IconButtonVariant } from '../../components/icon-button/icon-button-styles';\nimport { MenuTrigger } from '../../components/menu/menu-trigger';\nimport { DomDirectionLocale } from '../../primitives/DomDirectionLocale';\nimport { cn } from '../../utils/cn';\nimport {\n focusCheckedRadio,\n notePointerDown,\n radioGroupKeyDown,\n} from '../../utils/radio-group-keys';\nimport { toolbarKeyDown } from '../../utils/toolbar-keys';\nimport type { VariantProps } from '../../utils/tv';\nimport { useWarnOnce } from '../../utils/warn-once';\nimport { buttonGroupStyles } from './button-group-styles';\nimport { LazyOverflowMenuList } from './lazy-overflow-menu';\nimport { useOverflow } from './use-overflow';\nimport { usePressExpansion } from './use-press-expansion';\n\n/** Material Symbols `more_vert` (Apache-2.0), the overflow indicator's icon. */\nfunction MoreVertIcon() {\n return (\n <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\">\n <path d=\"M480-160q-33 0-56.5-23.5T400-240q0-33 23.5-56.5T480-320q33 0 56.5 23.5T560-240q0 33-23.5 56.5T480-160Zm0-240q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm0-240q-33 0-56.5-23.5T400-720q0-33 23.5-56.5T480-800q33 0 56.5 23.5T560-720q0 33-23.5 56.5T480-640Z\" />\n </svg>\n );\n}\n\nexport type ButtonGroupVariant = NonNullable<VariantProps<typeof buttonGroupStyles>['variant']>;\nexport type ButtonGroupSelectionMode = 'none' | 'single' | 'multiple';\n\n/** Text the group adds for assistive tech and its tooltip; translate it for your locale. */\nexport interface ButtonGroupLabels {\n /** The overflow indicator's name and tooltip. @default \"More options\" */\n more: string;\n}\n\nconst DEFAULT_LABELS: ButtonGroupLabels = { more: 'More options' };\n\nexport interface ButtonGroupClassNames {\n /** The group's outer element; `className` is merged here too. */\n root?: string;\n /** The overflow indicator, the \"More options\" icon button (`overflow`). */\n overflowButton?: string;\n /** The overflow indicator's menu of folded buttons (`overflow`). */\n overflowMenu?: string;\n}\n\nexport interface ButtonGroupProps extends Omit<\n ComponentPropsWithRef<'div'>,\n 'onChange' | 'defaultValue'\n> {\n /**\n * `standard` spaces the buttons apart (18 / 12 / 8px by size); `connected` joins them 2px\n * apart with shared inner corners across the container's width (the M3 Expressive\n * replacement for segmented buttons). A standard group grows a pressed button into its\n * neighbours' space; a connected one only changes its shape unless `expandedRatio` is set.\n * @default \"standard\"\n */\n variant?: ButtonGroupVariant;\n /**\n * Size shared by every button and icon button in the group; it also sets the spacing.\n * Unset, the buttons keep their own (or an enclosing toolbar's) size.\n * @default \"sm\"\n */\n size?: ButtonSize;\n /**\n * Shape shared by the buttons. A `square` connected group gives its outer corners the\n * size of its inner ones (4 / 8 / 8 / 16 / 20px). Unset, the buttons keep their own.\n * @default \"round\"\n */\n shape?: ButtonShape;\n /**\n * Variant shared by every `Button` in the group. `filled`, `tonal` and `outlined` reach\n * the `IconButton`s too, so a group never mixes colour styles; a button's own `variant`\n * wins. Unset, the buttons keep their own.\n * @default \"filled\"\n */\n buttonVariant?: ButtonVariant;\n /** Disables every button in the group. @default false */\n disabled?: boolean;\n /**\n * Lets toggle buttons with a `value` act as one selection. `single` behaves like\n * radio buttons, `multiple` like checkboxes. @default \"none\"\n */\n selectionMode?: ButtonGroupSelectionMode;\n /** Controlled selection: the `value`s of the selected toggle buttons. */\n selectedKeys?: Iterable<string>;\n /** Initial selection when uncontrolled. */\n defaultSelectedKeys?: Iterable<string>;\n /** Called with the selected toggle buttons' `value`s when the selection changes. */\n onSelectionChange?: (keys: Set<string>) => void;\n /**\n * The last selected button can be deselected, leaving none. `false` keeps at least one\n * selected. @default true\n */\n deselectable?: boolean;\n /**\n * How much a pressed button grows, as a fraction of its width; its neighbours shrink by the\n * same amount and the group keeps its width. `0` turns it off. Standard groups default to\n * 0.15. Connected groups default to 0 (the spec: they \"don't affect adjacent buttons\", as\n * in Compose's connected sample); set a ratio to opt in, as Compose can by combining a\n * button group's `animateWidth` with the connected shapes. Under reduced motion there's\n * no bump. @default 0.15, or 0 when `variant` is \"connected\"\n */\n expandedRatio?: number;\n /**\n * Folds the trailing buttons that don't fit into a menu behind a trailing \"More options\"\n * icon button (M3: \"Buttons at the trailing edge of the button group can be customized to\n * collapse into an overflow menu at smaller breakpoints\"). The group then fills its\n * container's width and re-measures as it resizes. Each folded button becomes a menu\n * item with its label (an icon button's `aria-label`) and icon; disabled buttons stay\n * disabled, and toggles show their selection. Choosing an item presses the button.\n * @default false\n */\n overflow?: boolean;\n /**\n * Text for assistive tech, e.g. the overflow indicator's name.\n * @default { more: \"More options\" }\n */\n labels?: Partial<ButtonGroupLabels>;\n /** Classes for each part. `classNames.root` is merged with `className`. */\n classNames?: ButtonGroupClassNames;\n children?: ReactNode;\n}\n\n/** The props the group reads from its children for the overflow menu and warnings. */\ninterface ChildProps {\n variant?: string;\n toggle?: boolean;\n value?: string;\n disabled?: boolean;\n children?: ReactNode;\n leadingIcon?: ReactElement;\n selectedLeadingIcon?: ReactElement;\n icon?: ReactElement;\n selectedIcon?: ReactElement;\n 'aria-label'?: string;\n}\n\nconst ICON_BUTTON_VARIANTS: readonly string[] = ['filled', 'tonal', 'outlined'];\n\n/** The icon button variant a group's `buttonVariant` implies, if it exists for icon buttons. */\nfunction iconVariantOf(variant: ButtonVariant | undefined): IconButtonVariant | undefined {\n return variant && ICON_BUTTON_VARIANTS.includes(variant)\n ? (variant as IconButtonVariant)\n : undefined;\n}\n\ntype Position = NonNullable<ButtonContextValue['connected']>;\n\nfunction positionOf(index: number, count: number): Position | undefined {\n if (count < 2) return undefined;\n if (index === 0) return 'leading';\n if (index === count - 1) return 'trailing';\n return 'middle';\n}\n\n/** The group's ref, also reporting its element to `DomDirectionLocale`. */\nfunction useGroupRef(ref: Ref<HTMLDivElement> | undefined, directionRef: RefCallback<HTMLElement>) {\n // Arrow keys follow the laid-out direction, which React Aria reads from its locale.\n const merged = useMemo(\n () => mergeRefs<HTMLDivElement>(ref, directionRef as RefCallback<HTMLDivElement>),\n [ref, directionRef],\n );\n return useObjectRef<HTMLDivElement>(merged);\n}\n\n/**\n * M3 Expressive button group. Shares size, shape, variant and disabled with its `Button`\n * and `IconButton` children. A standard group grows a pressed button while its neighbours\n * shrink; a `connected` group joins the buttons with shared inner corners and, by default,\n * only changes the pressed button's shape (`expandedRatio` opts it into the bump). Tab reaches the group and the arrow keys move\n * between its buttons, wrapping, with Home and End (a `toolbar`). With `selectionMode`, toggle\n * buttons that have a `value` form a single or multiple selection; a single selection is a\n * radio group, whose arrow keys select as they move.\n *\n * Children must be direct `Button` / `IconButton` elements so each gets its position.\n *\n * @example\n * <ButtonGroup variant=\"connected\" selectionMode=\"single\" defaultSelectedKeys={['week']}\n * aria-label=\"Calendar view\">\n * <Button toggle value=\"day\">Day</Button>\n * <Button toggle value=\"week\">Week</Button>\n * <Button toggle value=\"month\">Month</Button>\n * </ButtonGroup>\n */\nexport function ButtonGroup(props: ButtonGroupProps) {\n return (\n <DomDirectionLocale>\n {(directionRef) => <ButtonGroupRoot {...props} directionRef={directionRef} />}\n </DomDirectionLocale>\n );\n}\n\nfunction ButtonGroupRoot({\n variant = 'standard',\n size,\n shape,\n buttonVariant,\n disabled,\n selectionMode = 'none',\n selectedKeys,\n defaultSelectedKeys,\n onSelectionChange,\n deselectable = true,\n expandedRatio,\n overflow = false,\n labels,\n className,\n classNames,\n children,\n ref,\n directionRef,\n ...props\n}: ButtonGroupProps & { directionRef: RefCallback<HTMLElement> }) {\n const domRef = useGroupRef(ref, directionRef);\n const parent = useContext(ButtonContext);\n const hasSelection = selectionMode !== 'none';\n\n // React Stately reports a change even when the selection is unchanged (e.g. clicking\n // the only selected button of a group that isn't `deselectable`); only real changes reach callers.\n const currentKeys = useRef<ReadonlySet<Key>>(new Set());\n const handleSelectionChange = (keys: Set<Key>) => {\n const previous = currentKeys.current;\n if (keys.size === previous.size && [...keys].every((key) => previous.has(key))) return;\n onSelectionChange?.(keys as Set<string>);\n };\n\n const state = useToggleGroupState({\n selectionMode: hasSelection ? selectionMode : 'single',\n selectedKeys,\n defaultSelectedKeys,\n onSelectionChange: handleSelectionChange,\n disallowEmptySelection: !deselectable,\n isDisabled: disabled,\n });\n const { groupProps } = useToggleButtonGroup(\n { ...props, isDisabled: disabled, orientation: 'horizontal' },\n state,\n domRef,\n );\n // Without a selection the group is a React Aria toolbar: Tab enters and leaves it as one\n // stop, and the arrow keys move between its buttons.\n const { toolbarProps } = useToolbar({ ...props, orientation: 'horizontal' }, domRef);\n const groupAria = withGroupKeys(\n hasSelection ? groupProps : toolbarProps,\n selectionMode === 'single',\n );\n\n useLayoutEffect(() => {\n currentKeys.current = state.selectedKeys;\n });\n\n usePressExpansion(domRef, expandedRatio ?? (variant === 'standard' ? 0.15 : 0));\n\n const groupSize = size ?? parent.size ?? 'sm';\n const iconButtonVariant = iconVariantOf(buttonVariant);\n const shared = useMemo<ButtonContextValue>(\n () => ({\n variant: buttonVariant ?? parent.variant,\n size: size ?? parent.size,\n shape: shape ?? parent.shape,\n disabled: disabled ?? parent.disabled,\n iconButtonVariant: iconButtonVariant ?? parent.iconButtonVariant,\n density: parent.density,\n }),\n [buttonVariant, iconButtonVariant, size, shape, disabled, parent],\n );\n\n const items = Children.toArray(children).filter(isValidElement) as ReactElement<ChildProps>[];\n const visible = useOverflow(domRef, overflow, items.length);\n const overflowing = overflow && visible < items.length;\n\n useGroupWarnings(variant, items, buttonVariant, iconButtonVariant);\n\n // Connected corners follow the buttons that show, the overflow indicator last.\n const shown = overflowing ? visible + 1 : items.length;\n const contextFor = (index: number): ButtonContextValue =>\n variant === 'connected' ? { ...shared, connected: positionOf(index, shown) } : shared;\n const content = items.map((child, index) => (\n <ButtonContext key={child.key ?? index} value={contextFor(Math.min(index, shown - 1))}>\n {child}\n </ButtonContext>\n ));\n if (overflow) {\n content.push(\n <ButtonContext key=\"overflow\" value={contextFor(shown - 1)}>\n <OverflowMenu\n groupRef={domRef}\n hidden={items.slice(visible)}\n offset={visible}\n label={labels?.more ?? DEFAULT_LABELS.more}\n selectionMode={selectionMode}\n selectedKeys={state.selectedKeys}\n disabled={Boolean(disabled ?? parent.disabled)}\n classNames={classNames}\n />\n </ButtonContext>,\n );\n }\n\n return (\n <div\n {...mergeProps(props, groupAria)}\n ref={domRef}\n // Public styling hooks for apps, as on Button: the group's variant and the size it spaces\n // its buttons for (its own, an enclosing toolbar's, or `sm`).\n data-variant={variant}\n data-size={groupSize}\n className={cn(\n buttonGroupStyles({ variant, size: groupSize, overflow }),\n classNames?.root,\n className,\n )}\n >\n {hasSelection ? (\n <ToggleGroupStateContext value={state}>{content}</ToggleGroupStateContext>\n ) : (\n content\n )}\n </div>\n );\n}\n\n/**\n * The group's arrow keys. React Aria's toolbar keys (kept for Tab, which enters and leaves\n * the group as one stop) are replaced: a toolbar's arrows wrap and Home and End reach its\n * ends (WAI-ARIA APG toolbar), and a single selection is a radio group whose arrows also\n * select (APG radio group, as `RadioGroup` does) and which Tab enters at the selected\n * button. A group nested in a toolbar leaves its keys to the toolbar, as React Aria does.\n */\nfunction withGroupKeys(\n aria: HTMLAttributes<HTMLElement>,\n single: boolean,\n): HTMLAttributes<HTMLElement> {\n const ariaKeyDown = aria.onKeyDownCapture;\n if (!ariaKeyDown) return aria;\n return {\n ...aria,\n onKeyDownCapture: (event) => {\n if (event.key === 'Tab') ariaKeyDown(event);\n else if (single) radioGroupKeyDown(event);\n else toolbarKeyDown(event, 'horizontal');\n },\n onFocus: single ? (event) => void focusCheckedRadio(event) : undefined,\n onPointerDownCapture: single ? notePointerDown : undefined,\n };\n}\n\n/**\n * Development warnings, once per group, for what the M3 button group guidelines advise\n * against: standard icon buttons and text buttons in a group (\"they have no container\n * treatment\"), and a connected group with nothing to select (\"Avoid using a connected\n * group when none of the buttons can be toggled\"). Only the variants the group or the\n * buttons set count: a toolbar's standard default doesn't.\n */\nfunction useGroupWarnings(\n variant: ButtonGroupVariant,\n items: ReactElement<ChildProps>[],\n buttonVariant: ButtonVariant | undefined,\n iconButtonVariant: IconButtonVariant | undefined,\n) {\n const containerless = items.some((child) =>\n child.type === IconButton\n ? (child.props.variant ?? iconButtonVariant) === 'standard'\n : child.type === Button && (child.props.variant ?? buttonVariant) === 'text',\n );\n const nothingToSelect =\n variant === 'connected' && items.length > 0 && !items.some((child) => child.props.toggle);\n useWarnOnce(\n containerless,\n '[@windmorph/react] Avoid standard icon buttons and text buttons in a ButtonGroup (M3): they have no container, so the group has nothing to shape.',\n );\n useWarnOnce(\n nothingToSelect,\n '[@windmorph/react] A connected ButtonGroup is for selection (M3): use toggle buttons in it, or a standard group for actions.',\n );\n}\n\ninterface OverflowMenuProps {\n groupRef: RefObject<HTMLDivElement | null>;\n /** The folded buttons, in order. */\n hidden: ReactElement<ChildProps>[];\n /** The index of the first folded button among the group's children. */\n offset: number;\n label: string;\n selectionMode: ButtonGroupSelectionMode;\n selectedKeys: ReadonlySet<Key>;\n disabled: boolean;\n classNames: ButtonGroupClassNames | undefined;\n}\n\n/**\n * The overflow indicator (Compose `ButtonGroupDefaults.OverflowIndicator`: a filled icon\n * button with `more_vert` and a \"More options\" tooltip, here the group's icon button\n * variant) and its menu. Choosing an item clicks the folded button itself, so its own\n * press handler, link, toggle or group selection runs as if it had been pressed. The menu's\n * code loads once a button folds (see `LazyOverflowMenuList`); the indicator is always\n * rendered, so the group can measure it.\n */\nfunction OverflowMenu({\n groupRef,\n hidden,\n offset,\n label,\n selectionMode,\n selectedKeys,\n disabled,\n classNames,\n}: OverflowMenuProps) {\n // Folded standalone toggles keep their state inside them: read it when the menu opens\n // and after each choice.\n const [pressed, setPressed] = useState<ReadonlySet<string>>(new Set());\n const elementAt = (key: string) =>\n groupRef.current?.children[Number(key)] as HTMLElement | undefined;\n const keyOf = (index: number) => String(offset + index);\n const readPressed = () =>\n setPressed(\n new Set(\n hidden\n .map((_, index) => keyOf(index))\n .filter((key) => elementAt(key)?.getAttribute('aria-pressed') === 'true'),\n ),\n );\n\n // A choice made with the mouse closed the menu with focus still on its item, which then\n // left with the menu and dropped focus on <body>. Once the menu has closed, focus still in\n // it (or on the page) goes back to the indicator, as after a keyboard choice (2026-10-15).\n const indicator = useRef<HTMLElement | null>(null);\n const keepFocus = () =>\n requestAnimationFrame(() => {\n const active = document.activeElement;\n if (!active || active === document.body || active.closest('[role=\"menu\"]')) {\n indicator.current?.focus({ preventScroll: true });\n }\n });\n\n const grouped = selectionMode !== 'none';\n const hasToggles = hidden.some((child) => child.props.toggle);\n const menuSelectionMode = grouped ? selectionMode : hasToggles ? 'multiple' : 'none';\n const selected = hidden\n .map((child, index) => ({ child, key: keyOf(index) }))\n .filter(({ child, key }) =>\n grouped\n ? child.props.value !== undefined && selectedKeys.has(child.props.value)\n : pressed.has(key),\n )\n .map(({ key }) => key);\n const disabledKeys = hidden\n .map((child, index) => ({ child, key: keyOf(index) }))\n .filter(({ child }) => disabled || child.props.disabled)\n .map(({ key }) => key);\n\n return (\n <MenuTrigger onOpenChange={(open) => (open ? readPressed() : keepFocus())}>\n <IconButton\n icon={<MoreVertIcon />}\n aria-label={label}\n className={classNames?.overflowButton}\n onPressStart={(event) => {\n indicator.current = event.target as HTMLElement;\n }}\n />\n {hidden.length > 0 && (\n <LazyOverflowMenuList\n items={hidden.map((child, index) => ({\n key: keyOf(index),\n ...describe(child, selected.includes(keyOf(index))),\n // Only a toggle (or a button in the group's selection) is checkable; an action\n // stays a plain item even when the menu is selectable.\n checkable: grouped ? child.props.value !== undefined : Boolean(child.props.toggle),\n }))}\n selectionMode={menuSelectionMode}\n selectedKeys={selected}\n disabledKeys={disabledKeys}\n classNames={{ panel: classNames?.overflowMenu }}\n onAction={(key) => {\n elementAt(String(key))?.click();\n // The button updates once this event is handled.\n setTimeout(readPressed);\n }}\n />\n )}\n </MenuTrigger>\n );\n}\n\n/** A folded button's menu label and icon. */\nfunction describe(child: ReactElement<ChildProps>, isSelected: boolean) {\n const { props } = child;\n if (child.type === IconButton) {\n return {\n label: props['aria-label'],\n icon: (isSelected && props.selectedIcon) || props.icon,\n };\n }\n return {\n label: props.children,\n icon: (isSelected && props.selectedLeadingIcon) || props.leadingIcon,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ButtonGroup.js","names":[],"sources":["../../../src/composites/button-group/ButtonGroup.tsx"],"sourcesContent":["'use client';\n\nimport {\n Children,\n isValidElement,\n useContext,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type ComponentPropsWithRef,\n type HTMLAttributes,\n type Key,\n type ReactElement,\n type ReactNode,\n type Ref,\n type RefCallback,\n type RefObject,\n} from 'react';\nimport { mergeProps, mergeRefs, useObjectRef, useToggleButtonGroup, useToolbar } from 'react-aria';\nimport { useToggleGroupState } from 'react-stately';\nimport { Button } from '../../components/button/Button';\nimport type { ButtonShape, ButtonSize, ButtonVariant } from '../../components/button/button-styles';\nimport {\n ButtonContext,\n ToggleGroupStateContext,\n type ButtonContextValue,\n} from '../../components/button/ButtonContext';\nimport { IconButton } from '../../components/icon-button/IconButton';\nimport type { IconButtonVariant } from '../../components/icon-button/icon-button-styles';\nimport { MenuTrigger } from '../../components/menu/menu-trigger';\nimport { DomDirectionLocale } from '../../primitives/DomDirectionLocale';\nimport { cn } from '../../utils/cn';\nimport {\n focusCheckedRadio,\n notePointerDown,\n radioGroupKeyDown,\n} from '../../utils/radio-group-keys';\nimport { toolbarKeyDown } from '../../utils/toolbar-keys';\nimport type { VariantProps } from '../../utils/tv';\nimport { useWarnOnce } from '../../utils/warn-once';\nimport { buttonGroupStyles } from './button-group-styles';\nimport { LazyOverflowMenuList } from './lazy-overflow-menu';\nimport { useOverflow } from './use-overflow';\nimport { usePressExpansion } from './use-press-expansion';\n\n/** Material Symbols `more_vert` (Apache-2.0), the overflow indicator's icon. */\nfunction MoreVertIcon() {\n return (\n <svg viewBox=\"0 -960 960 960\" fill=\"currentColor\">\n <path d=\"M480-160q-33 0-56.5-23.5T400-240q0-33 23.5-56.5T480-320q33 0 56.5 23.5T560-240q0 33-23.5 56.5T480-160Zm0-240q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm0-240q-33 0-56.5-23.5T400-720q0-33 23.5-56.5T480-800q33 0 56.5 23.5T560-720q0 33-23.5 56.5T480-640Z\" />\n </svg>\n );\n}\n\nexport type ButtonGroupVariant = NonNullable<VariantProps<typeof buttonGroupStyles>['variant']>;\nexport type ButtonGroupSelectionMode = 'none' | 'single' | 'multiple';\n\n/** Text the group adds for assistive tech and its tooltip; translate it for your locale. */\nexport interface ButtonGroupLabels {\n /** The overflow indicator's name and tooltip. @default \"More options\" */\n more: string;\n}\n\nconst DEFAULT_LABELS: ButtonGroupLabels = { more: 'More options' };\n\nexport interface ButtonGroupClassNames {\n /** The group's outer element; `className` is merged here too. */\n root?: string;\n /** The overflow indicator, the \"More options\" icon button (`overflow`). */\n overflowButton?: string;\n /** The overflow indicator's menu of folded buttons (`overflow`). */\n overflowMenu?: string;\n}\n\nexport interface ButtonGroupProps extends Omit<\n ComponentPropsWithRef<'div'>,\n 'onChange' | 'defaultValue'\n> {\n /**\n * `standard` spaces the buttons apart (18 / 12 / 8px by size); `connected` joins them 2px\n * apart with shared inner corners across the container's width (the M3 Expressive\n * replacement for segmented buttons). A standard group grows a pressed button into its\n * neighbours' space; a connected one only changes its shape unless `expandedRatio` is set.\n * @default \"standard\"\n */\n variant?: ButtonGroupVariant;\n /**\n * Whether a `connected` group spans its container (M3's guidance, and the default). `false`\n * sizes the group to its buttons, for one beside other controls; the buttons stay equally\n * wide. Has no effect on a `standard` group or one with `overflow`.\n * @default true\n */\n fullWidth?: boolean;\n /**\n * Size shared by every button and icon button in the group; it also sets the spacing.\n * Unset, the buttons keep their own (or an enclosing toolbar's) size.\n * @default \"sm\"\n */\n size?: ButtonSize;\n /**\n * Shape shared by the buttons. A `square` connected group gives its outer corners the\n * size of its inner ones (4 / 8 / 8 / 16 / 20px). Unset, the buttons keep their own.\n * @default \"round\"\n */\n shape?: ButtonShape;\n /**\n * Variant shared by every `Button` in the group. `filled`, `tonal` and `outlined` reach\n * the `IconButton`s too, so a group never mixes colour styles; a button's own `variant`\n * wins. Unset, the buttons keep their own.\n * @default \"filled\"\n */\n buttonVariant?: ButtonVariant;\n /** Disables every button in the group. @default false */\n disabled?: boolean;\n /**\n * Lets toggle buttons with a `value` act as one selection. `single` behaves like\n * radio buttons, `multiple` like checkboxes. @default \"none\"\n */\n selectionMode?: ButtonGroupSelectionMode;\n /** Controlled selection: the `value`s of the selected toggle buttons. */\n selectedKeys?: Iterable<string>;\n /** Initial selection when uncontrolled. */\n defaultSelectedKeys?: Iterable<string>;\n /** Called with the selected toggle buttons' `value`s when the selection changes. */\n onSelectionChange?: (keys: Set<string>) => void;\n /**\n * The last selected button can be deselected, leaving none. `false` keeps at least one\n * selected. @default true\n */\n deselectable?: boolean;\n /**\n * How much a pressed button grows, as a fraction of its width; its neighbours shrink by the\n * same amount and the group keeps its width. `0` turns it off. Standard groups default to\n * 0.15. Connected groups default to 0 (the spec: they \"don't affect adjacent buttons\", as\n * in Compose's connected sample); set a ratio to opt in, as Compose can by combining a\n * button group's `animateWidth` with the connected shapes. Under reduced motion there's\n * no bump. @default 0.15, or 0 when `variant` is \"connected\"\n */\n expandedRatio?: number;\n /**\n * Folds the trailing buttons that don't fit into a menu behind a trailing \"More options\"\n * icon button (M3: \"Buttons at the trailing edge of the button group can be customized to\n * collapse into an overflow menu at smaller breakpoints\"). The group then fills its\n * container's width and re-measures as it resizes. Each folded button becomes a menu\n * item with its label (an icon button's `aria-label`) and icon; disabled buttons stay\n * disabled, and toggles show their selection. Choosing an item presses the button.\n * @default false\n */\n overflow?: boolean;\n /**\n * Text for assistive tech, e.g. the overflow indicator's name.\n * @default { more: \"More options\" }\n */\n labels?: Partial<ButtonGroupLabels>;\n /** Classes for each part. `classNames.root` is merged with `className`. */\n classNames?: ButtonGroupClassNames;\n children?: ReactNode;\n}\n\n/** The props the group reads from its children for the overflow menu and warnings. */\ninterface ChildProps {\n variant?: string;\n toggle?: boolean;\n value?: string;\n disabled?: boolean;\n children?: ReactNode;\n leadingIcon?: ReactElement;\n selectedLeadingIcon?: ReactElement;\n icon?: ReactElement;\n selectedIcon?: ReactElement;\n 'aria-label'?: string;\n}\n\nconst ICON_BUTTON_VARIANTS: readonly string[] = ['filled', 'tonal', 'outlined'];\n\n/** The icon button variant a group's `buttonVariant` implies, if it exists for icon buttons. */\nfunction iconVariantOf(variant: ButtonVariant | undefined): IconButtonVariant | undefined {\n return variant && ICON_BUTTON_VARIANTS.includes(variant)\n ? (variant as IconButtonVariant)\n : undefined;\n}\n\ntype Position = NonNullable<ButtonContextValue['connected']>;\n\nfunction positionOf(index: number, count: number): Position | undefined {\n if (count < 2) return undefined;\n if (index === 0) return 'leading';\n if (index === count - 1) return 'trailing';\n return 'middle';\n}\n\n/** The group's ref, also reporting its element to `DomDirectionLocale`. */\nfunction useGroupRef(ref: Ref<HTMLDivElement> | undefined, directionRef: RefCallback<HTMLElement>) {\n // Arrow keys follow the laid-out direction, which React Aria reads from its locale.\n const merged = useMemo(\n () => mergeRefs<HTMLDivElement>(ref, directionRef as RefCallback<HTMLDivElement>),\n [ref, directionRef],\n );\n return useObjectRef<HTMLDivElement>(merged);\n}\n\n/**\n * M3 Expressive button group. Shares size, shape, variant and disabled with its `Button`\n * and `IconButton` children. A standard group grows a pressed button while its neighbours\n * shrink; a `connected` group joins the buttons with shared inner corners and, by default,\n * only changes the pressed button's shape (`expandedRatio` opts it into the bump). Tab reaches the group and the arrow keys move\n * between its buttons, wrapping, with Home and End (a `toolbar`). With `selectionMode`, toggle\n * buttons that have a `value` form a single or multiple selection; a single selection is a\n * radio group, whose arrow keys select as they move.\n *\n * Children must be direct `Button` / `IconButton` elements so each gets its position.\n *\n * @example\n * <ButtonGroup variant=\"connected\" selectionMode=\"single\" defaultSelectedKeys={['week']}\n * aria-label=\"Calendar view\">\n * <Button toggle value=\"day\">Day</Button>\n * <Button toggle value=\"week\">Week</Button>\n * <Button toggle value=\"month\">Month</Button>\n * </ButtonGroup>\n */\nexport function ButtonGroup(props: ButtonGroupProps) {\n return (\n <DomDirectionLocale>\n {(directionRef) => <ButtonGroupRoot {...props} directionRef={directionRef} />}\n </DomDirectionLocale>\n );\n}\n\nfunction ButtonGroupRoot({\n variant = 'standard',\n size,\n shape,\n buttonVariant,\n disabled,\n selectionMode = 'none',\n selectedKeys,\n defaultSelectedKeys,\n onSelectionChange,\n deselectable = true,\n expandedRatio,\n overflow = false,\n fullWidth = true,\n labels,\n className,\n classNames,\n children,\n ref,\n directionRef,\n ...props\n}: ButtonGroupProps & { directionRef: RefCallback<HTMLElement> }) {\n const domRef = useGroupRef(ref, directionRef);\n const parent = useContext(ButtonContext);\n const hasSelection = selectionMode !== 'none';\n\n // React Stately reports a change even when the selection is unchanged (e.g. clicking\n // the only selected button of a group that isn't `deselectable`); only real changes reach callers.\n const currentKeys = useRef<ReadonlySet<Key>>(new Set());\n const handleSelectionChange = (keys: Set<Key>) => {\n const previous = currentKeys.current;\n if (keys.size === previous.size && [...keys].every((key) => previous.has(key))) return;\n onSelectionChange?.(keys as Set<string>);\n };\n\n const state = useToggleGroupState({\n selectionMode: hasSelection ? selectionMode : 'single',\n selectedKeys,\n defaultSelectedKeys,\n onSelectionChange: handleSelectionChange,\n disallowEmptySelection: !deselectable,\n isDisabled: disabled,\n });\n const { groupProps } = useToggleButtonGroup(\n { ...props, isDisabled: disabled, orientation: 'horizontal' },\n state,\n domRef,\n );\n // Without a selection the group is a React Aria toolbar: Tab enters and leaves it as one\n // stop, and the arrow keys move between its buttons.\n const { toolbarProps } = useToolbar({ ...props, orientation: 'horizontal' }, domRef);\n const groupAria = withGroupKeys(\n hasSelection ? groupProps : toolbarProps,\n selectionMode === 'single',\n );\n\n useLayoutEffect(() => {\n currentKeys.current = state.selectedKeys;\n });\n\n usePressExpansion(domRef, expandedRatio ?? (variant === 'standard' ? 0.15 : 0));\n\n const groupSize = size ?? parent.size ?? 'sm';\n const iconButtonVariant = iconVariantOf(buttonVariant);\n const shared = useMemo<ButtonContextValue>(\n () => ({\n variant: buttonVariant ?? parent.variant,\n size: size ?? parent.size,\n shape: shape ?? parent.shape,\n disabled: disabled ?? parent.disabled,\n iconButtonVariant: iconButtonVariant ?? parent.iconButtonVariant,\n density: parent.density,\n }),\n [buttonVariant, iconButtonVariant, size, shape, disabled, parent],\n );\n\n const items = Children.toArray(children).filter(isValidElement) as ReactElement<ChildProps>[];\n const visible = useOverflow(domRef, overflow, items.length);\n const overflowing = overflow && visible < items.length;\n\n useGroupWarnings(variant, items, buttonVariant, iconButtonVariant);\n\n // Connected corners follow the buttons that show, the overflow indicator last.\n const shown = overflowing ? visible + 1 : items.length;\n const contextFor = (index: number): ButtonContextValue =>\n variant === 'connected' ? { ...shared, connected: positionOf(index, shown) } : shared;\n const content = items.map((child, index) => (\n <ButtonContext key={child.key ?? index} value={contextFor(Math.min(index, shown - 1))}>\n {child}\n </ButtonContext>\n ));\n if (overflow) {\n content.push(\n <ButtonContext key=\"overflow\" value={contextFor(shown - 1)}>\n <OverflowMenu\n groupRef={domRef}\n hidden={items.slice(visible)}\n offset={visible}\n label={labels?.more ?? DEFAULT_LABELS.more}\n selectionMode={selectionMode}\n selectedKeys={state.selectedKeys}\n disabled={Boolean(disabled ?? parent.disabled)}\n classNames={classNames}\n />\n </ButtonContext>,\n );\n }\n\n return (\n <div\n {...mergeProps(props, groupAria)}\n ref={domRef}\n // Public styling hooks for apps, as on Button: the group's variant and the size it spaces\n // its buttons for (its own, an enclosing toolbar's, or `sm`).\n data-variant={variant}\n data-size={groupSize}\n className={cn(\n buttonGroupStyles({ variant, size: groupSize, overflow, fullWidth }),\n classNames?.root,\n className,\n )}\n >\n {hasSelection ? (\n <ToggleGroupStateContext value={state}>{content}</ToggleGroupStateContext>\n ) : (\n content\n )}\n </div>\n );\n}\n\n/**\n * The group's arrow keys. React Aria's toolbar keys (kept for Tab, which enters and leaves\n * the group as one stop) are replaced: a toolbar's arrows wrap and Home and End reach its\n * ends (WAI-ARIA APG toolbar), and a single selection is a radio group whose arrows also\n * select (APG radio group, as `RadioGroup` does) and which Tab enters at the selected\n * button. A group nested in a toolbar leaves its keys to the toolbar, as React Aria does.\n */\nfunction withGroupKeys(\n aria: HTMLAttributes<HTMLElement>,\n single: boolean,\n): HTMLAttributes<HTMLElement> {\n const ariaKeyDown = aria.onKeyDownCapture;\n if (!ariaKeyDown) return aria;\n return {\n ...aria,\n onKeyDownCapture: (event) => {\n if (event.key === 'Tab') ariaKeyDown(event);\n else if (single) radioGroupKeyDown(event);\n else toolbarKeyDown(event, 'horizontal');\n },\n onFocus: single ? (event) => void focusCheckedRadio(event) : undefined,\n onPointerDownCapture: single ? notePointerDown : undefined,\n };\n}\n\n/**\n * Development warnings, once per group, for what the M3 button group guidelines advise\n * against: standard icon buttons and text buttons in a group (\"they have no container\n * treatment\"), and a connected group with nothing to select (\"Avoid using a connected\n * group when none of the buttons can be toggled\"). Only the variants the group or the\n * buttons set count: a toolbar's standard default doesn't.\n */\nfunction useGroupWarnings(\n variant: ButtonGroupVariant,\n items: ReactElement<ChildProps>[],\n buttonVariant: ButtonVariant | undefined,\n iconButtonVariant: IconButtonVariant | undefined,\n) {\n const containerless = items.some((child) =>\n child.type === IconButton\n ? (child.props.variant ?? iconButtonVariant) === 'standard'\n : child.type === Button && (child.props.variant ?? buttonVariant) === 'text',\n );\n const nothingToSelect =\n variant === 'connected' && items.length > 0 && !items.some((child) => child.props.toggle);\n useWarnOnce(\n containerless,\n '[@windmorph/react] Avoid standard icon buttons and text buttons in a ButtonGroup (M3): they have no container, so the group has nothing to shape.',\n );\n useWarnOnce(\n nothingToSelect,\n '[@windmorph/react] A connected ButtonGroup is for selection (M3): use toggle buttons in it, or a standard group for actions.',\n );\n}\n\ninterface OverflowMenuProps {\n groupRef: RefObject<HTMLDivElement | null>;\n /** The folded buttons, in order. */\n hidden: ReactElement<ChildProps>[];\n /** The index of the first folded button among the group's children. */\n offset: number;\n label: string;\n selectionMode: ButtonGroupSelectionMode;\n selectedKeys: ReadonlySet<Key>;\n disabled: boolean;\n classNames: ButtonGroupClassNames | undefined;\n}\n\n/**\n * The overflow indicator (Compose `ButtonGroupDefaults.OverflowIndicator`: a filled icon\n * button with `more_vert` and a \"More options\" tooltip, here the group's icon button\n * variant) and its menu. Choosing an item clicks the folded button itself, so its own\n * press handler, link, toggle or group selection runs as if it had been pressed. The menu's\n * code loads once a button folds (see `LazyOverflowMenuList`); the indicator is always\n * rendered, so the group can measure it.\n */\nfunction OverflowMenu({\n groupRef,\n hidden,\n offset,\n label,\n selectionMode,\n selectedKeys,\n disabled,\n classNames,\n}: OverflowMenuProps) {\n // Folded standalone toggles keep their state inside them: read it when the menu opens\n // and after each choice.\n const [pressed, setPressed] = useState<ReadonlySet<string>>(new Set());\n const elementAt = (key: string) =>\n groupRef.current?.children[Number(key)] as HTMLElement | undefined;\n const keyOf = (index: number) => String(offset + index);\n const readPressed = () =>\n setPressed(\n new Set(\n hidden\n .map((_, index) => keyOf(index))\n .filter((key) => elementAt(key)?.getAttribute('aria-pressed') === 'true'),\n ),\n );\n\n // A choice made with the mouse closed the menu with focus still on its item, which then\n // left with the menu and dropped focus on <body>. Once the menu has closed, focus still in\n // it (or on the page) goes back to the indicator, as after a keyboard choice (2026-10-15).\n const indicator = useRef<HTMLElement | null>(null);\n const keepFocus = () =>\n requestAnimationFrame(() => {\n const active = document.activeElement;\n if (!active || active === document.body || active.closest('[role=\"menu\"]')) {\n indicator.current?.focus({ preventScroll: true });\n }\n });\n\n const grouped = selectionMode !== 'none';\n const hasToggles = hidden.some((child) => child.props.toggle);\n const menuSelectionMode = grouped ? selectionMode : hasToggles ? 'multiple' : 'none';\n const selected = hidden\n .map((child, index) => ({ child, key: keyOf(index) }))\n .filter(({ child, key }) =>\n grouped\n ? child.props.value !== undefined && selectedKeys.has(child.props.value)\n : pressed.has(key),\n )\n .map(({ key }) => key);\n const disabledKeys = hidden\n .map((child, index) => ({ child, key: keyOf(index) }))\n .filter(({ child }) => disabled || child.props.disabled)\n .map(({ key }) => key);\n\n return (\n <MenuTrigger onOpenChange={(open) => (open ? readPressed() : keepFocus())}>\n <IconButton\n icon={<MoreVertIcon />}\n aria-label={label}\n className={classNames?.overflowButton}\n onPressStart={(event) => {\n indicator.current = event.target as HTMLElement;\n }}\n />\n {hidden.length > 0 && (\n <LazyOverflowMenuList\n items={hidden.map((child, index) => ({\n key: keyOf(index),\n ...describe(child, selected.includes(keyOf(index))),\n // Only a toggle (or a button in the group's selection) is checkable; an action\n // stays a plain item even when the menu is selectable.\n checkable: grouped ? child.props.value !== undefined : Boolean(child.props.toggle),\n }))}\n selectionMode={menuSelectionMode}\n selectedKeys={selected}\n disabledKeys={disabledKeys}\n classNames={{ panel: classNames?.overflowMenu }}\n onAction={(key) => {\n elementAt(String(key))?.click();\n // The button updates once this event is handled.\n setTimeout(readPressed);\n }}\n />\n )}\n </MenuTrigger>\n );\n}\n\n/** A folded button's menu label and icon. */\nfunction describe(child: ReactElement<ChildProps>, isSelected: boolean) {\n const { props } = child;\n if (child.type === IconButton) {\n return {\n label: props['aria-label'],\n icon: (isSelected && props.selectedIcon) || props.icon,\n };\n }\n return {\n label: props.children,\n icon: (isSelected && props.selectedLeadingIcon) || props.leadingIcon,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA+CA,SAAS,eAAe;CACtB,OACE,oBAAC,OAAD;EAAK,SAAQ;EAAiB,MAAK;EACjC,UAAA,oBAAC,QAAD,EAAM,GAAE,iTAAkT,CAAA;CACvT,CAAA;AAET;AAWA,MAAM,iBAAoC,EAAE,MAAM,eAAe;AA8GjE,MAAM,uBAA0C;CAAC;CAAU;CAAS;AAAU;;AAG9E,SAAS,cAAc,SAAmE;CACxF,OAAO,WAAW,qBAAqB,SAAS,OAAO,IAClD,UACD,KAAA;AACN;AAIA,SAAS,WAAW,OAAe,OAAqC;CACtE,IAAI,QAAQ,GAAG,OAAO,KAAA;CACtB,IAAI,UAAU,GAAG,OAAO;CACxB,IAAI,UAAU,QAAQ,GAAG,OAAO;CAChC,OAAO;AACT;;AAGA,SAAS,YAAY,KAAsC,cAAwC;CAEjG,MAAM,SAAS,cACP,UAA0B,KAAK,YAA2C,GAChF,CAAC,KAAK,YAAY,CACpB;CACA,OAAO,aAA6B,MAAM;AAC5C;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,YAAY,OAAyB;CACnD,OACE,oBAAC,oBAAD,EAAA,WACI,iBAAiB,oBAAC,iBAAD;EAAiB,GAAI;EAAqB;CAAe,CAAA,EAC1D,CAAA;AAExB;AAEA,SAAS,gBAAgB,EACvB,UAAU,YACV,MACA,OACA,eACA,UACA,gBAAgB,QAChB,cACA,qBACA,mBACA,eAAe,MACf,eACA,WAAW,OACX,YAAY,MACZ,QACA,WACA,YACA,UACA,KACA,cACA,GAAG,SAC6D;CAChE,MAAM,SAAS,YAAY,KAAK,YAAY;CAC5C,MAAM,SAAS,WAAW,aAAa;CACvC,MAAM,eAAe,kBAAkB;CAIvC,MAAM,cAAc,uBAAyB,IAAI,IAAI,CAAC;CACtD,MAAM,yBAAyB,SAAmB;EAChD,MAAM,WAAW,YAAY;EAC7B,IAAI,KAAK,SAAS,SAAS,QAAQ,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,QAAQ,SAAS,IAAI,GAAG,CAAC,GAAG;EAChF,oBAAoB,IAAmB;CACzC;CAEA,MAAM,QAAQ,oBAAoB;EAChC,eAAe,eAAe,gBAAgB;EAC9C;EACA;EACA,mBAAmB;EACnB,wBAAwB,CAAC;EACzB,YAAY;CACd,CAAC;CACD,MAAM,EAAE,eAAe,qBACrB;EAAE,GAAG;EAAO,YAAY;EAAU,aAAa;CAAa,GAC5D,OACA,MACF;CAGA,MAAM,EAAE,iBAAiB,WAAW;EAAE,GAAG;EAAO,aAAa;CAAa,GAAG,MAAM;CACnF,MAAM,YAAY,cAChB,eAAe,aAAa,cAC5B,kBAAkB,QACpB;CAEA,sBAAsB;EACpB,YAAY,UAAU,MAAM;CAC9B,CAAC;CAED,kBAAkB,QAAQ,kBAAkB,YAAY,aAAa,MAAO,EAAE;CAE9E,MAAM,YAAY,QAAQ,OAAO,QAAQ;CACzC,MAAM,oBAAoB,cAAc,aAAa;CACrD,MAAM,SAAS,eACN;EACL,SAAS,iBAAiB,OAAO;EACjC,MAAM,QAAQ,OAAO;EACrB,OAAO,SAAS,OAAO;EACvB,UAAU,YAAY,OAAO;EAC7B,mBAAmB,qBAAqB,OAAO;EAC/C,SAAS,OAAO;CAClB,IACA;EAAC;EAAe;EAAmB;EAAM;EAAO;EAAU;CAAM,CAClE;CAEA,MAAM,QAAQ,SAAS,QAAQ,QAAQ,CAAC,CAAC,OAAO,cAAc;CAC9D,MAAM,UAAU,YAAY,QAAQ,UAAU,MAAM,MAAM;CAC1D,MAAM,cAAc,YAAY,UAAU,MAAM;CAEhD,iBAAiB,SAAS,OAAO,eAAe,iBAAiB;CAGjE,MAAM,QAAQ,cAAc,UAAU,IAAI,MAAM;CAChD,MAAM,cAAc,UAClB,YAAY,cAAc;EAAE,GAAG;EAAQ,WAAW,WAAW,OAAO,KAAK;CAAE,IAAI;CACjF,MAAM,UAAU,MAAM,KAAK,OAAO,UAChC,oBAAC,eAAD;EAAwC,OAAO,WAAW,KAAK,IAAI,OAAO,QAAQ,CAAC,CAAC;EACjF,UAAA;CACY,GAFK,MAAM,OAAO,KAElB,CAChB;CACD,IAAI,UACF,QAAQ,KACN,oBAAC,eAAD;EAA8B,OAAO,WAAW,QAAQ,CAAC;EACvD,UAAA,oBAAC,cAAD;GACE,UAAU;GACV,QAAQ,MAAM,MAAM,OAAO;GAC3B,QAAQ;GACR,OAAO,QAAQ,QAAQ,eAAe;GACvB;GACf,cAAc,MAAM;GACpB,UAAU,QAAQ,YAAY,OAAO,QAAQ;GACjC;EACb,CAAA;CACY,GAXI,UAWJ,CACjB;CAGF,OACE,oBAAC,OAAD;EACE,GAAI,WAAW,OAAO,SAAS;EAC/B,KAAK;EAGL,gBAAc;EACd,aAAW;EACX,WAAW,GACT,kBAAkB;GAAE;GAAS,MAAM;GAAW;GAAU;EAAU,CAAC,GACnE,YAAY,MACZ,SACF;EAEC,UAAA,eACC,oBAAC,yBAAD;GAAyB,OAAO;GAAQ,UAAA;EAAiC,CAAA,IAEzE;CAEC,CAAA;AAET;;;;;;;;AASA,SAAS,cACP,MACA,QAC6B;CAC7B,MAAM,cAAc,KAAK;CACzB,IAAI,CAAC,aAAa,OAAO;CACzB,OAAO;EACL,GAAG;EACH,mBAAmB,UAAU;GAC3B,IAAI,MAAM,QAAQ,OAAO,YAAY,KAAK;QACrC,IAAI,QAAQ,kBAAkB,KAAK;QACnC,eAAe,OAAO,YAAY;EACzC;EACA,SAAS,UAAU,UAAU,KAAK,kBAAkB,KAAK,IAAI,KAAA;EAC7D,sBAAsB,SAAS,kBAAkB,KAAA;CACnD;AACF;;;;;;;;AASA,SAAS,iBACP,SACA,OACA,eACA,mBACA;CACA,MAAM,gBAAgB,MAAM,MAAM,UAChC,MAAM,SAAS,cACV,MAAM,MAAM,WAAW,uBAAuB,aAC/C,MAAM,SAAS,WAAW,MAAM,MAAM,WAAW,mBAAmB,MAC1E;CACA,MAAM,kBACJ,YAAY,eAAe,MAAM,SAAS,KAAK,CAAC,MAAM,MAAM,UAAU,MAAM,MAAM,MAAM;CAC1F,YACE,eACA,mJACF;CACA,YACE,iBACA,8HACF;AACF;;;;;;;;;AAuBA,SAAS,aAAa,EACpB,UACA,QACA,QACA,OACA,eACA,cACA,UACA,cACoB;CAGpB,MAAM,CAAC,SAAS,cAAc,yBAA8B,IAAI,IAAI,CAAC;CACrE,MAAM,aAAa,QACjB,SAAS,SAAS,SAAS,OAAO,GAAG;CACvC,MAAM,SAAS,UAAkB,OAAO,SAAS,KAAK;CACtD,MAAM,oBACJ,WACE,IAAI,IACF,OACG,KAAK,GAAG,UAAU,MAAM,KAAK,CAAC,CAAC,CAC/B,QAAQ,QAAQ,UAAU,GAAG,CAAC,EAAE,aAAa,cAAc,MAAM,MAAM,CAC5E,CACF;CAKF,MAAM,YAAY,OAA2B,IAAI;CACjD,MAAM,kBACJ,4BAA4B;EAC1B,MAAM,SAAS,SAAS;EACxB,IAAI,CAAC,UAAU,WAAW,SAAS,QAAQ,OAAO,QAAQ,iBAAe,GACvE,UAAU,SAAS,MAAM,EAAE,eAAe,KAAK,CAAC;CAEpD,CAAC;CAEH,MAAM,UAAU,kBAAkB;CAClC,MAAM,aAAa,OAAO,MAAM,UAAU,MAAM,MAAM,MAAM;CAC5D,MAAM,oBAAoB,UAAU,gBAAgB,aAAa,aAAa;CAC9E,MAAM,WAAW,OACd,KAAK,OAAO,WAAW;EAAE;EAAO,KAAK,MAAM,KAAK;CAAE,EAAE,CAAC,CACrD,QAAQ,EAAE,OAAO,UAChB,UACI,MAAM,MAAM,UAAU,KAAA,KAAa,aAAa,IAAI,MAAM,MAAM,KAAK,IACrE,QAAQ,IAAI,GAAG,CACrB,CAAC,CACA,KAAK,EAAE,UAAU,GAAG;CACvB,MAAM,eAAe,OAClB,KAAK,OAAO,WAAW;EAAE;EAAO,KAAK,MAAM,KAAK;CAAE,EAAE,CAAC,CACrD,QAAQ,EAAE,YAAY,YAAY,MAAM,MAAM,QAAQ,CAAC,CACvD,KAAK,EAAE,UAAU,GAAG;CAEvB,OACE,qBAAC,aAAD;EAAa,eAAe,SAAU,OAAO,YAAY,IAAI,UAAU;EAAvE,UAAA,CACE,oBAAC,YAAD;GACE,MAAM,oBAAC,cAAD,CAAe,CAAA;GACrB,cAAY;GACZ,WAAW,YAAY;GACvB,eAAe,UAAU;IACvB,UAAU,UAAU,MAAM;GAC5B;EACD,CAAA,GACA,OAAO,SAAS,KACf,oBAAC,sBAAD;GACE,OAAO,OAAO,KAAK,OAAO,WAAW;IACnC,KAAK,MAAM,KAAK;IAChB,GAAG,SAAS,OAAO,SAAS,SAAS,MAAM,KAAK,CAAC,CAAC;IAGlD,WAAW,UAAU,MAAM,MAAM,UAAU,KAAA,IAAY,QAAQ,MAAM,MAAM,MAAM;GACnF,EAAE;GACF,eAAe;GACf,cAAc;GACA;GACd,YAAY,EAAE,OAAO,YAAY,aAAa;GAC9C,WAAW,QAAQ;IACjB,UAAU,OAAO,GAAG,CAAC,CAAC,EAAE,MAAM;IAE9B,WAAW,WAAW;GACxB;EACD,CAAA,CAEQ;;AAEjB;;AAGA,SAAS,SAAS,OAAiC,YAAqB;CACtE,MAAM,EAAE,UAAU;CAClB,IAAI,MAAM,SAAS,YACjB,OAAO;EACL,OAAO,MAAM;EACb,MAAO,cAAc,MAAM,gBAAiB,MAAM;CACpD;CAEF,OAAO;EACL,OAAO,MAAM;EACb,MAAO,cAAc,MAAM,uBAAwB,MAAM;CAC3D;AACF"}
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
export declare const buttonGroupStyles: import("tailwind-variants").TVReturnType<{
|
|
24
24
|
variant: {
|
|
25
25
|
standard: "inline-flex";
|
|
26
|
-
connected: "grid
|
|
26
|
+
connected: "grid grid-flow-col auto-cols-[1fr] gap-[2px] data-pinned:flex";
|
|
27
27
|
};
|
|
28
28
|
size: {
|
|
29
29
|
xs: "";
|
|
@@ -32,6 +32,10 @@ export declare const buttonGroupStyles: import("tailwind-variants").TVReturnType
|
|
|
32
32
|
lg: "";
|
|
33
33
|
xl: "";
|
|
34
34
|
};
|
|
35
|
+
fullWidth: {
|
|
36
|
+
true: "";
|
|
37
|
+
false: "";
|
|
38
|
+
};
|
|
35
39
|
overflow: {
|
|
36
40
|
true: "w-full min-w-0 *:data-overflow-hidden:hidden data-measuring:flex data-measuring:w-max data-measuring:*:grow-0";
|
|
37
41
|
false: "";
|
|
@@ -39,7 +43,7 @@ export declare const buttonGroupStyles: import("tailwind-variants").TVReturnType
|
|
|
39
43
|
}, undefined, "items-center", {
|
|
40
44
|
variant: {
|
|
41
45
|
standard: "inline-flex";
|
|
42
|
-
connected: "grid
|
|
46
|
+
connected: "grid grid-flow-col auto-cols-[1fr] gap-[2px] data-pinned:flex";
|
|
43
47
|
};
|
|
44
48
|
size: {
|
|
45
49
|
xs: "";
|
|
@@ -48,6 +52,10 @@ export declare const buttonGroupStyles: import("tailwind-variants").TVReturnType
|
|
|
48
52
|
lg: "";
|
|
49
53
|
xl: "";
|
|
50
54
|
};
|
|
55
|
+
fullWidth: {
|
|
56
|
+
true: "";
|
|
57
|
+
false: "";
|
|
58
|
+
};
|
|
51
59
|
overflow: {
|
|
52
60
|
true: "w-full min-w-0 *:data-overflow-hidden:hidden data-measuring:flex data-measuring:w-max data-measuring:*:grow-0";
|
|
53
61
|
false: "";
|
|
@@ -55,7 +63,7 @@ export declare const buttonGroupStyles: import("tailwind-variants").TVReturnType
|
|
|
55
63
|
}, undefined, import("tailwind-variants").TVReturnTypeLike<{
|
|
56
64
|
variant: {
|
|
57
65
|
standard: "inline-flex";
|
|
58
|
-
connected: "grid
|
|
66
|
+
connected: "grid grid-flow-col auto-cols-[1fr] gap-[2px] data-pinned:flex";
|
|
59
67
|
};
|
|
60
68
|
size: {
|
|
61
69
|
xs: "";
|
|
@@ -64,6 +72,10 @@ export declare const buttonGroupStyles: import("tailwind-variants").TVReturnType
|
|
|
64
72
|
lg: "";
|
|
65
73
|
xl: "";
|
|
66
74
|
};
|
|
75
|
+
fullWidth: {
|
|
76
|
+
true: "";
|
|
77
|
+
false: "";
|
|
78
|
+
};
|
|
67
79
|
overflow: {
|
|
68
80
|
true: "w-full min-w-0 *:data-overflow-hidden:hidden data-measuring:flex data-measuring:w-max data-measuring:*:grow-0";
|
|
69
81
|
false: "";
|