@exsto/ds 0.0.1-rc.15 → 0.0.1-rc.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +4 -0
  2. package/dist/index.js +2 -1
  3. package/dist/source-hash.txt +1 -1
  4. package/dist/styles.css +96 -0
  5. package/dist/ui/src/components/edyn/date-picker/date-picker.d.ts +1 -0
  6. package/dist/ui/src/components/edyn/date-picker/date-picker.js +23 -3
  7. package/dist/ui/src/components/edyn/date-picker/date-picker.js.map +1 -1
  8. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js +13 -2
  9. package/dist/ui/src/components/edyn/date-range-picker/date-range-picker.js.map +1 -1
  10. package/dist/ui/src/components/edyn/multi-select/multi-select.js +8 -2
  11. package/dist/ui/src/components/edyn/multi-select/multi-select.js.map +1 -1
  12. package/dist/ui/src/components/edyn/presence/index.d.ts +1 -0
  13. package/dist/ui/src/components/edyn/presence/presence.d.ts +15 -0
  14. package/dist/ui/src/components/edyn/presence/presence.js +58 -0
  15. package/dist/ui/src/components/edyn/presence/presence.js.map +1 -0
  16. package/dist/ui/src/components/edyn/presence/presence.test.d.ts +8 -0
  17. package/dist/ui/src/components/ui/alert-dialog/alert-dialog.js +2 -0
  18. package/dist/ui/src/components/ui/alert-dialog/alert-dialog.js.map +1 -1
  19. package/dist/ui/src/components/ui/calendar/calendar.behavior.test.d.ts +9 -0
  20. package/dist/ui/src/components/ui/calendar/calendar.d.ts +1 -1
  21. package/dist/ui/src/components/ui/calendar/calendar.js +33 -2
  22. package/dist/ui/src/components/ui/calendar/calendar.js.map +1 -1
  23. package/dist/ui/src/components/ui/context-menu/context-menu.js +2 -0
  24. package/dist/ui/src/components/ui/context-menu/context-menu.js.map +1 -1
  25. package/dist/ui/src/components/ui/dialog/dialog.js +2 -0
  26. package/dist/ui/src/components/ui/dialog/dialog.js.map +1 -1
  27. package/dist/ui/src/components/ui/drawer/drawer.js +2 -0
  28. package/dist/ui/src/components/ui/drawer/drawer.js.map +1 -1
  29. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js +2 -0
  30. package/dist/ui/src/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  31. package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js +2 -0
  32. package/dist/ui/src/components/ui/navigation-menu/navigation-menu.js.map +1 -1
  33. package/dist/ui/src/components/ui/popover/popover.js +2 -0
  34. package/dist/ui/src/components/ui/popover/popover.js.map +1 -1
  35. package/dist/ui/src/components/ui/select/select.js +2 -0
  36. package/dist/ui/src/components/ui/select/select.js.map +1 -1
  37. package/dist/ui/src/components/ui/sheet/sheet.js +2 -0
  38. package/dist/ui/src/components/ui/sheet/sheet.js.map +1 -1
  39. package/dist/ui/src/index.d.ts +1 -0
  40. package/dist/ui/src/lib/focus-guards.d.ts +2 -0
  41. package/dist/ui/src/lib/focus-guards.js +52 -0
  42. package/dist/ui/src/lib/focus-guards.js.map +1 -0
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"multi-select.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/multi-select/multi-select.tsx"],"sourcesContent":["import { Combobox } from '@base-ui/react/combobox';\nimport { ScrollArea } from '@edyn/ui/components/ui/scroll-area';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@edyn/ui/components/ui/tooltip';\nimport { popupItemClass } from '@edyn/ui/lib/popup-list';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-react';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport {\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n type RefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\n/** An option: `leading` goes before its label (an avatar, a status dot), `count` after it (how many it would show). */\nexport type TMultiSelectOption = { value: string; label: string; count?: number; leading?: ReactNode };\n\n/** Every word the component prints, so a product can translate it. */\nexport interface IMultiSelectLabels {\n noMatches: string;\n loading: string;\n /** Shown when `error` is `true`; a string `error` replaces it. */\n error: string;\n retry: string;\n loadMore: string;\n loadingMore: string;\n clear: string;\n selectedCount: (count: number) => string;\n}\n\nexport const DEFAULT_MULTI_SELECT_LABELS: IMultiSelectLabels = {\n noMatches: 'No matches.',\n loading: 'Loading…',\n error: 'Could not load the options.',\n retry: 'Try again',\n loadMore: 'Load more',\n loadingMore: 'Loading…',\n clear: 'Clear',\n selectedCount: (count) => `${count} selected`,\n};\n\n/** Above this many options a list draws only the rows on screen. */\nexport const VIRTUALIZE_FROM = 100;\n\nconst ITEM_HEIGHT = 32;\n\n/** Anything else a button takes (data-*, aria-*, title) goes to the trigger. */\nexport interface IMultiSelectProps\n extends Omit<\n ComponentPropsWithoutRef<'button'>,\n 'placeholder' | 'value' | 'defaultValue' | 'id' | 'disabled' | 'className' | 'children' | 'onChange'\n > {\n /** The trigger's text with nothing picked, which means no filter (\"All dealer groups\"). */\n placeholder: string;\n /** The search field's prompt (\"Search dealer groups…\"). */\n searchPlaceholder: string;\n /** The options to show now: all of a short fixed list, or in async mode the ones for the typed text. */\n options: readonly TMultiSelectOption[];\n value: readonly string[];\n onValueChange: (value: string[]) => void;\n /**\n * What the trigger says once something is chosen. `selection` (default) names the one chosen or counts several;\n * `label` keeps the label and only counts, so a value the page shows elsewhere (as a removable chip, say) is\n * not repeated here: nothing chosen reads `placeholder`, anything chosen reads `labels.selectedCount(n)`.\n */\n summary?: 'selection' | 'label';\n /** For a Label's htmlFor. */\n id?: string;\n 'aria-label'?: string;\n /**\n * Marks the trigger invalid: the kit's destructive border, and a screen reader says so. For a form that does not use\n * Field; inside one, its invalid state already does this.\n */\n 'aria-invalid'?: boolean | 'true' | 'false';\n /**\n * The id of the text that explains it (the error). Added to what a Field names already, never replacing it.\n */\n 'aria-describedby'?: string;\n /** Shown but not changeable (a read-only form). */\n disabled?: boolean;\n /** Layout only (a width); the look is the kit's. */\n className?: string;\n /**\n * Passing this turns on ASYNC MODE, for lists that grow (people, dealer groups) and must\n * be searched by the API rather than loaded whole and filtered in the browser. The\n * component then never filters: it reports the typed text (after `searchDelay`) and shows\n * whatever `options` the caller supplies for it. It is also called with '' each time the\n * list opens, so the first page is fetched on demand, not at page load.\n */\n onSearchChange?: (text: string) => void;\n /** How long typing must pause before `onSearchChange` is called. Async mode only. */\n searchDelay?: number;\n /**\n * The options behind the chosen values, for the ones not in the current `options`. A\n * search that no longer returns a chosen value must not make it vanish from the trigger\n * or the count, so the caller says what it is called. Without a label a value shows as\n * itself.\n */\n selectedOptions?: readonly TMultiSelectOption[];\n /** The options for the typed text are on their way. The old ones stay until they arrive. */\n loading?: boolean;\n /** The request failed: `true` for the default message, or the message itself. */\n error?: boolean | string;\n onRetry?: () => void;\n /** There is another page after `options`. */\n hasMore?: boolean;\n onLoadMore?: () => void;\n /** The next page is on its way. */\n loadingMore?: boolean;\n labels?: Partial<IMultiSelectLabels>;\n}\n\n/**\n * The options behind the chosen values, in the order they were chosen. A value with no\n * known option is kept and shown as itself rather than dropped: a selection must never\n * change just because a list did.\n */\nexport const resolveChosen = (\n value: readonly string[],\n options: readonly TMultiSelectOption[],\n selectedOptions: readonly TMultiSelectOption[] = [],\n): TMultiSelectOption[] => {\n const known = new Map<string, TMultiSelectOption>();\n for (const option of [...selectedOptions, ...options]) {\n known.set(option.value, option);\n }\n return value.map((entry) => known.get(entry) ?? { value: entry, label: entry });\n};\n\n/** What the trigger says: the placeholder, the one picked, or a count. */\nexport const summarize = (\n chosen: readonly TMultiSelectOption[],\n placeholder: string,\n labels: IMultiSelectLabels,\n summary: 'selection' | 'label' = 'selection',\n): string => {\n if (chosen.length === 0) {\n return placeholder;\n }\n if (summary === 'label') {\n return labels.selectedCount(chosen.length);\n }\n return chosen.length === 1 ? (chosen[0]?.label ?? '') : labels.selectedCount(chosen.length);\n};\n\n/** Every chosen name, for a tooltip on a trigger whose text is cut. With `label` the names are not shown, so it is the text itself. */\nexport const fullSelection = (\n chosen: readonly TMultiSelectOption[],\n placeholder: string,\n labels: IMultiSelectLabels,\n summary: 'selection' | 'label' = 'selection',\n): string =>\n summary === 'selection' && chosen.length > 0 ? chosen.map((option) => option.label).join(', ') : summarize(chosen, placeholder, labels, summary);\n\nconst POPUP =\n 'flex max-h-(--available-height) w-(--anchor-width) min-w-64 max-w-[max(24rem,var(--anchor-width))] origin-(--transform-origin) flex-col overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-md outline-none duration-100 motion-reduce:animate-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95';\n\nconst TRIGGER =\n 'flex h-8 w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-2.5 text-sm text-foreground transition-colors outline-none focus-visible:border-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive data-popup-open:border-ring dark:bg-input/30';\n\nconst Option = ({\n option,\n index,\n style,\n measure,\n highlighted,\n}: {\n option: TMultiSelectOption;\n index: number;\n style?: CSSProperties;\n measure?: (node: Element | null) => void;\n highlighted: boolean;\n}) => {\n const [cut, setCut] = useState(false);\n // A callback ref, so measuring starts when the label mounts and stops when it goes; the key on the label below mounts a fresh one\n // when the text changes, which a resize observer alone would miss at an unchanged width.\n const measureLabel = useCallback((element: HTMLSpanElement | null) => {\n if (!element) {\n return;\n }\n const measureCut = (): void => setCut(element.scrollWidth > element.clientWidth);\n measureCut();\n if (typeof ResizeObserver === 'undefined') {\n return;\n }\n const observer = new ResizeObserver(measureCut);\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n\n return (\n <Combobox.Item\n value={option}\n index={index}\n ref={measure}\n data-index={index}\n style={style}\n data-slot=\"multi-select-item\"\n className={cn(popupItemClass('base-ui'), 'group')}\n >\n {/* A checkbox, filled when chosen: a set of toggles reads as one. */}\n <span\n data-slot=\"multi-select-item-indicator\"\n aria-hidden=\"true\"\n className=\"flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground transition-colors group-data-[selected]:border-primary group-data-[selected]:bg-primary\"\n >\n <IconCheck className=\"size-3 opacity-0 group-data-[selected]:opacity-100\" stroke={3} />\n </span>\n {option.leading}\n {/* The option is the highlighted one under the pointer and under the keyboard alike, so one controlled tooltip serves both: the full label while the text is cut. */}\n <Tooltip open={highlighted && cut}>\n <TooltipTrigger\n key={option.label}\n data-slot=\"multi-select-item-label\"\n data-overflowing={cut ? '' : undefined}\n render={<span ref={measureLabel} className=\"min-w-0 flex-1 truncate\" />}\n >\n {option.label}\n </TooltipTrigger>\n <TooltipContent side=\"right\">{option.label}</TooltipContent>\n </Tooltip>\n {option.count !== undefined && <span className=\"text-xs text-muted-foreground tabular-nums\">{option.count}</span>}\n </Combobox.Item>\n );\n};\n\n// The options on offer, every one drawn, or past VIRTUALIZE_FROM only those on screen. The\n// list is the listbox itself (the options its direct children), sized to the whole list\n// when virtualized; the ScrollArea wraps it.\nconst Options = ({\n scroller,\n scrollTo,\n busy,\n highlightedValue,\n}: {\n scroller: RefObject<HTMLDivElement | null>;\n scrollTo: RefObject<(index: number) => void>;\n busy: boolean;\n highlightedValue: string | null;\n}) => {\n const items = Combobox.useFilteredItems<TMultiSelectOption>();\n const virtual = items.length > VIRTUALIZE_FROM;\n // The scroller is an ancestor, so its ref is set only after this list's own effects:\n // one more render once mounted gives the virtualizer its element.\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => scroller.current,\n estimateSize: () => ITEM_HEIGHT,\n overscan: 12,\n enabled: virtual && mounted,\n });\n useEffect(() => {\n scrollTo.current = virtual ? (index) => virtualizer.scrollToIndex(index, { align: 'auto' }) : () => undefined;\n }, [scrollTo, virtual, virtualizer]);\n if (!virtual) {\n return (\n <Combobox.List aria-busy={busy || undefined} className=\"p-1 outline-none\">\n {items.map((option, index) => (\n <Option key={option.value} option={option} index={index} highlighted={highlightedValue === option.value} />\n ))}\n </Combobox.List>\n );\n }\n return (\n <Combobox.List\n data-virtualized=\"\"\n aria-busy={busy || undefined}\n className=\"relative m-1 outline-none\"\n style={{ height: virtualizer.getTotalSize() }}\n >\n {virtualizer.getVirtualItems().map((row) => {\n const option = items[row.index];\n return option === undefined ? null : (\n <Option\n key={option.value}\n option={option}\n index={row.index}\n highlighted={highlightedValue === option.value}\n measure={virtualizer.measureElement}\n style={{ position: 'absolute', top: 0, left: 0, right: 0, transform: `translateY(${row.start}px)` }}\n />\n );\n })}\n </Combobox.List>\n );\n};\n\n/**\n * A choice of several from a list: a trigger shaped like a Select that says what is picked\n * (the placeholder, the one picked, or a count), and a popup with a search field and checkbox\n * options, with the shared popup list style, the ScrollArea's bar and fades, and virtualization\n * past VIRTUALIZE_FROM options. Nothing chosen means no filter.\n *\n * For a short fixed list pass `options` and nothing else: the component filters them as the\n * user types. For a list that grows, pass `onSearchChange` and it becomes a window onto a\n * server: the caller fetches the options for the typed text, and says when they are loading,\n * failed, or have another page.\n */\nexport const MultiSelect = ({\n placeholder,\n searchPlaceholder,\n options,\n value,\n onValueChange,\n id,\n disabled = false,\n className,\n onSearchChange,\n searchDelay = 250,\n selectedOptions,\n loading = false,\n error = false,\n onRetry,\n hasMore = false,\n onLoadMore,\n loadingMore = false,\n labels: labelOverrides,\n summary = 'selection',\n ...rest\n}: IMultiSelectProps) => {\n const labels = useMemo(() => ({ ...DEFAULT_MULTI_SELECT_LABELS, ...labelOverrides }), [labelOverrides]);\n const isAsync = onSearchChange !== undefined;\n const chosen = useMemo(() => resolveChosen(value, options, selectedOptions), [value, options, selectedOptions]);\n // Chosen options the current page does not contain stay on offer at the top, so they can\n // still be unchecked after a search has moved on.\n const items = useMemo(() => {\n const offered = new Set(options.map((option) => option.value));\n return [...chosen.filter((option) => !offered.has(option.value)), ...options];\n }, [chosen, options]);\n const refScroller = useRef<HTMLDivElement | null>(null);\n const refScrollTo = useRef<(index: number) => void>(() => undefined);\n const refTimer = useRef<number | undefined>(undefined);\n const refTriggerText = useRef<HTMLSpanElement | null>(null);\n const refSearch = useRef<HTMLInputElement | null>(null);\n // The popup and the trigger's tooltip are two hover surfaces on one element, so the tooltip stands down while the popup is open.\n const [popupOpen, setPopupOpen] = useState(false);\n // The option under the pointer or the keyboard, whichever moved last, for the tooltip that shows a cut label in full.\n const [highlightedValue, setHighlightedValue] = useState<string | null>(null);\n // The text last reported through onSearchChange, and whether the popup is open. Base UI\n // reports the input as empty when the popup opens and again when it closes; reporting\n // those would fetch the first page twice on every open, and again while closed.\n const refReported = useRef<string | undefined>(undefined);\n const refOpen = useRef(false);\n const errorMessage = typeof error === 'string' ? error : labels.error;\n const failed = error !== false;\n\n useEffect(() => () => window.clearTimeout(refTimer.current), []);\n\n // Base UI gives the search field a key handler that closes the popup on Escape and keeps the key from the page, but not\n // the trigger: with focus back on the trigger and the popup still open (a click that landed while a Sheet slid in,\n // `focus()`, Shift+Tab) Escape reached the document, where an enclosing Sheet or Dialog closed along with the popup.\n // So the trigger hands Escape to the search field, the path that already works, and the key goes no further.\n const closeFromTrigger = (event: ReactKeyboardEvent<HTMLElement>) => {\n if (event.key !== 'Escape' || !popupOpen || refSearch.current === null) {\n return;\n }\n event.stopPropagation();\n event.preventDefault();\n refSearch.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));\n };\n\n return (\n <Combobox.Root\n multiple\n disabled={disabled}\n items={items}\n filter={isAsync ? null : undefined}\n value={chosen}\n onValueChange={(next: TMultiSelectOption[]) => onValueChange(next.map((option) => option.value))}\n onInputValueChange={(text) => {\n if (onSearchChange === undefined || !refOpen.current) {\n return;\n }\n window.clearTimeout(refTimer.current);\n if (text === refReported.current) {\n return;\n }\n refTimer.current = window.setTimeout(() => {\n refReported.current = text;\n onSearchChange(text);\n }, searchDelay);\n }}\n onOpenChange={(open) => {\n window.clearTimeout(refTimer.current);\n setPopupOpen(open);\n if (!open) {\n setHighlightedValue(null);\n }\n refOpen.current = open;\n refReported.current = open ? '' : undefined;\n if (open && onSearchChange !== undefined) {\n onSearchChange('');\n }\n }}\n itemToStringLabel={(option: TMultiSelectOption) => option.label}\n isItemEqualToValue={(a: TMultiSelectOption, b: TMultiSelectOption) => a.value === b.value}\n virtualized={items.length > VIRTUALIZE_FROM}\n onItemHighlighted={(item, details) => {\n setHighlightedValue(item?.value ?? null);\n if (details.reason === 'keyboard' && details.index >= 0) {\n refScrollTo.current(details.index);\n }\n }}\n >\n {/* A tooltip only when the text is really cut, which is known when it is about to open. */}\n <Tooltip\n disabled={popupOpen}\n onOpenChange={(open, details) => {\n const element = refTriggerText.current;\n if (open && !(element && element.scrollWidth > element.clientWidth)) {\n details.cancel();\n }\n }}\n >\n <TooltipTrigger\n data-slot=\"multi-select-trigger\"\n render={\n <Combobox.Trigger\n {...rest}\n id={id}\n data-set={chosen.length > 0 ? '' : undefined}\n onKeyDown={closeFromTrigger}\n className={cn(TRIGGER, className)}\n />\n }\n >\n <span ref={refTriggerText} className={cn('min-w-0 flex-1 truncate text-left', chosen.length === 0 && 'text-muted-foreground')}>\n {summarize(chosen, placeholder, labels, summary)}\n </span>\n <IconChevronDown aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n </TooltipTrigger>\n <TooltipContent>{fullSelection(chosen, placeholder, labels, summary)}</TooltipContent>\n </Tooltip>\n <Combobox.Portal>\n <Combobox.Positioner sideOffset={4} align=\"start\" className=\"z-50\">\n <Combobox.Popup data-slot=\"multi-select-content\" className={POPUP}>\n <div className=\"flex shrink-0 items-center gap-2 border-b border-border px-3\">\n <IconSearch aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <Combobox.Input\n ref={refSearch}\n placeholder={searchPlaceholder}\n aria-label={searchPlaceholder.replace(/…$/, '')}\n className=\"h-10 w-full min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n {/* The live region stays mounted and only its words change, which is what makes a\n screen reader announce them. */}\n <Combobox.Status data-slot=\"multi-select-status\" className=\"px-3 py-2 text-sm text-muted-foreground empty:p-0\">\n {failed ? (\n <span className=\"flex items-center justify-between gap-2 text-destructive\">\n {errorMessage}\n {onRetry !== undefined && (\n <button type=\"button\" onClick={onRetry} className=\"rounded-md px-2 py-1 font-medium text-foreground hover:bg-foreground/6\">\n {labels.retry}\n </button>\n )}\n </span>\n ) : loading ? (\n labels.loading\n ) : null}\n </Combobox.Status>\n <Combobox.Empty className=\"px-3 py-6 text-center text-sm text-muted-foreground empty:p-0\">\n {loading || failed ? null : labels.noMatches}\n </Combobox.Empty>\n <ScrollArea type=\"always\" fade viewportRef={refScroller} className=\"max-h-72 min-h-0\">\n <Options scroller={refScroller} scrollTo={refScrollTo} busy={loading} highlightedValue={highlightedValue} />\n </ScrollArea>\n {hasMore && onLoadMore !== undefined && (\n <div className=\"shrink-0 border-t border-border p-1\">\n <button\n type=\"button\"\n disabled={loadingMore}\n onClick={onLoadMore}\n className=\"h-8 w-full rounded-md px-2 text-sm font-medium text-foreground hover:bg-foreground/6 disabled:pointer-events-none disabled:opacity-50\"\n >\n {loadingMore ? labels.loadingMore : labels.loadMore}\n </button>\n </div>\n )}\n {chosen.length > 0 && (\n <div className=\"flex shrink-0 items-center justify-between gap-2 border-t border-border py-1 pr-1 pl-3 text-xs text-muted-foreground\">\n <span className=\"tabular-nums\">{labels.selectedCount(chosen.length)}</span>\n <button\n type=\"button\"\n onClick={() => onValueChange([])}\n className=\"rounded-md px-2 py-1 font-medium text-foreground hover:bg-foreground/6\"\n >\n {labels.clear}\n </button>\n </div>\n )}\n </Combobox.Popup>\n </Combobox.Positioner>\n </Combobox.Portal>\n </Combobox.Root>\n );\n};\n"],"mappings":";;;;;;;;;;AAoCA,IAAa,8BAAkD;CAC7D,WAAW;CACX,SAAS;CACT,OAAO;CACP,OAAO;CACP,UAAU;CACV,aAAa;CACb,OAAO;CACP,gBAAgB,UAAU,GAAG,MAAM;AACrC;;AAGA,IAAa,kBAAkB;AAE/B,IAAM,cAAc;;;;;;AAyEpB,IAAa,iBACX,OACA,SACA,kBAAiD,CAAC,MACzB;CACzB,MAAM,wBAAQ,IAAI,IAAgC;CAClD,KAAK,MAAM,UAAU,CAAC,GAAG,iBAAiB,GAAG,OAAO,GAClD,MAAM,IAAI,OAAO,OAAO,MAAM;CAEhC,OAAO,MAAM,KAAK,UAAU,MAAM,IAAI,KAAK,KAAK;EAAE,OAAO;EAAO,OAAO;CAAM,CAAC;AAChF;;AAGA,IAAa,aACX,QACA,aACA,QACA,UAAiC,gBACtB;CACX,IAAI,OAAO,WAAW,GACpB,OAAO;CAET,IAAI,YAAY,SACd,OAAO,OAAO,cAAc,OAAO,MAAM;CAE3C,OAAO,OAAO,WAAW,IAAK,OAAO,EAAE,EAAE,SAAS,KAAM,OAAO,cAAc,OAAO,MAAM;AAC5F;;AAGA,IAAa,iBACX,QACA,aACA,QACA,UAAiC,gBAEjC,YAAY,eAAe,OAAO,SAAS,IAAI,OAAO,KAAK,WAAW,OAAO,KAAK,CAAC,CAAC,KAAK,IAAI,IAAI,UAAU,QAAQ,aAAa,QAAQ,OAAO;AAEjJ,IAAM,QACJ;AAEF,IAAM,UACJ;AAEF,IAAM,UAAU,EACd,QACA,OACA,OACA,SACA,kBAOI;CACJ,MAAM,CAAC,KAAK,UAAU,SAAS,KAAK;CAGpC,MAAM,eAAe,aAAa,YAAoC;EACpE,IAAI,CAAC,SACH;EAEF,MAAM,mBAAyB,OAAO,QAAQ,cAAc,QAAQ,WAAW;EAC/E,WAAW;EACX,IAAI,OAAO,mBAAmB,aAC5B;EAEF,MAAM,WAAW,IAAI,eAAe,UAAU;EAC9C,SAAS,QAAQ,OAAO;EACxB,aAAa,SAAS,WAAW;CACnC,GAAG,CAAC,CAAC;CAEL,OACE,qBAAC,SAAS,MAAV;EACE,OAAO;EACA;EACP,KAAK;EACL,cAAY;EACL;EACP,aAAU;EACV,WAAW,GAAG,eAAe,SAAS,GAAG,OAAO;EAPlD,UAAA;GAUE,oBAAC,QAAD;IACE,aAAU;IACV,eAAY;IACZ,WAAU;IAEV,UAAA,oBAAC,WAAD;KAAW,WAAU;KAAqD,QAAQ;IAAI,CAAA;GAClF,CAAA;GACL,OAAO;GAER,qBAAC,SAAD;IAAS,MAAM,eAAe;IAA9B,UAAA,CACE,oBAAC,gBAAD;KAEE,aAAU;KACV,oBAAkB,MAAM,KAAK,KAAA;KAC7B,QAAQ,oBAAC,QAAD;MAAM,KAAK;MAAc,WAAU;KAA2B,CAAA;KAErE,UAAA,OAAO;IACM,GANT,OAAO,KAME,GAChB,oBAAC,gBAAD;KAAgB,MAAK;KAAS,UAAA,OAAO;IAAsB,CAAA,CACpD;;GACR,OAAO,UAAU,KAAA,KAAa,oBAAC,QAAD;IAAM,WAAU;IAA8C,UAAA,OAAO;GAAY,CAAA;EACnG;;AAEnB;AAKA,IAAM,WAAW,EACf,UACA,UACA,MACA,uBAMI;CACJ,MAAM,QAAQ,SAAS,iBAAqC;CAC5D,MAAM,UAAU,MAAM,SAAA;CAGtB,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAC5C,gBAAgB,WAAW,IAAI,GAAG,CAAC,CAAC;CACpC,MAAM,cAAc,eAAe;EACjC,OAAO,MAAM;EACb,wBAAwB,SAAS;EACjC,oBAAoB;EACpB,UAAU;EACV,SAAS,WAAW;CACtB,CAAC;CACD,gBAAgB;EACd,SAAS,UAAU,WAAW,UAAU,YAAY,cAAc,OAAO,EAAE,OAAO,OAAO,CAAC,UAAU,KAAA;CACtG,GAAG;EAAC;EAAU;EAAS;CAAW,CAAC;CACnC,IAAI,CAAC,SACH,OACE,oBAAC,SAAS,MAAV;EAAe,aAAW,QAAQ,KAAA;EAAW,WAAU;EACpD,UAAA,MAAM,KAAK,QAAQ,UAClB,oBAAC,QAAD;GAAmC;GAAe;GAAO,aAAa,qBAAqB,OAAO;EAAQ,GAA7F,OAAO,KAAsF,CAC3G;CACY,CAAA;CAGnB,OACE,oBAAC,SAAS,MAAV;EACE,oBAAiB;EACjB,aAAW,QAAQ,KAAA;EACnB,WAAU;EACV,OAAO,EAAE,QAAQ,YAAY,aAAa,EAAE;EAE3C,UAAA,YAAY,gBAAgB,CAAC,CAAC,KAAK,QAAQ;GAC1C,MAAM,SAAS,MAAM,IAAI;GACzB,OAAO,WAAW,KAAA,IAAY,OAC5B,oBAAC,QAAD;IAEU;IACR,OAAO,IAAI;IACX,aAAa,qBAAqB,OAAO;IACzC,SAAS,YAAY;IACrB,OAAO;KAAE,UAAU;KAAY,KAAK;KAAG,MAAM;KAAG,OAAO;KAAG,WAAW,cAAc,IAAI,MAAM;IAAK;GACnG,GANM,OAAO,KAMb;EAEL,CAAC;CACY,CAAA;AAEnB;;;;;;;;;;;;AAaA,IAAa,eAAe,EAC1B,aACA,mBACA,SACA,OACA,eACA,IACA,WAAW,OACX,WACA,gBACA,cAAc,KACd,iBACA,UAAU,OACV,QAAQ,OACR,SACA,UAAU,OACV,YACA,cAAc,OACd,QAAQ,gBACR,UAAU,aACV,GAAG,WACoB;CACvB,MAAM,SAAS,eAAe;EAAE,GAAG;EAA6B,GAAG;CAAe,IAAI,CAAC,cAAc,CAAC;CACtG,MAAM,UAAU,mBAAmB,KAAA;CACnC,MAAM,SAAS,cAAc,cAAc,OAAO,SAAS,eAAe,GAAG;EAAC;EAAO;EAAS;CAAe,CAAC;CAG9G,MAAM,QAAQ,cAAc;EAC1B,MAAM,UAAU,IAAI,IAAI,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;EAC7D,OAAO,CAAC,GAAG,OAAO,QAAQ,WAAW,CAAC,QAAQ,IAAI,OAAO,KAAK,CAAC,GAAG,GAAG,OAAO;CAC9E,GAAG,CAAC,QAAQ,OAAO,CAAC;CACpB,MAAM,cAAc,OAA8B,IAAI;CACtD,MAAM,cAAc,aAAsC,KAAA,CAAS;CACnE,MAAM,WAAW,OAA2B,KAAA,CAAS;CACrD,MAAM,iBAAiB,OAA+B,IAAI;CAC1D,MAAM,YAAY,OAAgC,IAAI;CAEtD,MAAM,CAAC,WAAW,gBAAgB,SAAS,KAAK;CAEhD,MAAM,CAAC,kBAAkB,uBAAuB,SAAwB,IAAI;CAI5E,MAAM,cAAc,OAA2B,KAAA,CAAS;CACxD,MAAM,UAAU,OAAO,KAAK;CAC5B,MAAM,eAAe,OAAO,UAAU,WAAW,QAAQ,OAAO;CAChE,MAAM,SAAS,UAAU;CAEzB,sBAAsB,OAAO,aAAa,SAAS,OAAO,GAAG,CAAC,CAAC;CAM/D,MAAM,oBAAoB,UAA2C;EACnE,IAAI,MAAM,QAAQ,YAAY,CAAC,aAAa,UAAU,YAAY,MAChE;EAEF,MAAM,gBAAgB;EACtB,MAAM,eAAe;EACrB,UAAU,QAAQ,cAAc,IAAI,cAAc,WAAW;GAAE,KAAK;GAAU,SAAS;GAAM,YAAY;EAAK,CAAC,CAAC;CAClH;CAEA,OACE,qBAAC,SAAS,MAAV;EACE,UAAA;EACU;EACH;EACP,QAAQ,UAAU,OAAO,KAAA;EACzB,OAAO;EACP,gBAAgB,SAA+B,cAAc,KAAK,KAAK,WAAW,OAAO,KAAK,CAAC;EAC/F,qBAAqB,SAAS;GAC5B,IAAI,mBAAmB,KAAA,KAAa,CAAC,QAAQ,SAC3C;GAEF,OAAO,aAAa,SAAS,OAAO;GACpC,IAAI,SAAS,YAAY,SACvB;GAEF,SAAS,UAAU,OAAO,iBAAiB;IACzC,YAAY,UAAU;IACtB,eAAe,IAAI;GACrB,GAAG,WAAW;EAChB;EACA,eAAe,SAAS;GACtB,OAAO,aAAa,SAAS,OAAO;GACpC,aAAa,IAAI;GACjB,IAAI,CAAC,MACH,oBAAoB,IAAI;GAE1B,QAAQ,UAAU;GAClB,YAAY,UAAU,OAAO,KAAK,KAAA;GAClC,IAAI,QAAQ,mBAAmB,KAAA,GAC7B,eAAe,EAAE;EAErB;EACA,oBAAoB,WAA+B,OAAO;EAC1D,qBAAqB,GAAuB,MAA0B,EAAE,UAAU,EAAE;EACpF,aAAa,MAAM,SAAA;EACnB,oBAAoB,MAAM,YAAY;GACpC,oBAAoB,MAAM,SAAS,IAAI;GACvC,IAAI,QAAQ,WAAW,cAAc,QAAQ,SAAS,GACpD,YAAY,QAAQ,QAAQ,KAAK;EAErC;EAxCF,UAAA,CA2CE,qBAAC,SAAD;GACE,UAAU;GACV,eAAe,MAAM,YAAY;IAC/B,MAAM,UAAU,eAAe;IAC/B,IAAI,QAAQ,EAAE,WAAW,QAAQ,cAAc,QAAQ,cACrD,QAAQ,OAAO;GAEnB;GAPF,UAAA,CASE,qBAAC,gBAAD;IACE,aAAU;IACV,QACE,oBAAC,SAAS,SAAV;KACE,GAAI;KACA;KACJ,YAAU,OAAO,SAAS,IAAI,KAAK,KAAA;KACnC,WAAW;KACX,WAAW,GAAG,SAAS,SAAS;IACjC,CAAA;IATL,UAAA,CAYE,oBAAC,QAAD;KAAM,KAAK;KAAgB,WAAW,GAAG,qCAAqC,OAAO,WAAW,KAAK,uBAAuB;KACzH,UAAA,UAAU,QAAQ,aAAa,QAAQ,OAAO;IAC3C,CAAA,GACN,oBAAC,iBAAD;KAAiB,eAAY;KAAO,WAAU;IAAyC,CAAA,CACzE;GAChB,CAAA,GAAA,oBAAC,gBAAD,EAAA,UAAiB,cAAc,QAAQ,aAAa,QAAQ,OAAO,EAAkB,CAAA,CAC9E;EACT,CAAA,GAAA,oBAAC,SAAS,QAAV,EAAA,UACE,oBAAC,SAAS,YAAV;GAAqB,YAAY;GAAG,OAAM;GAAQ,WAAU;GAC1D,UAAA,qBAAC,SAAS,OAAV;IAAgB,aAAU;IAAuB,WAAW;IAA5D,UAAA;KACE,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAAC,YAAD;OAAY,eAAY;OAAO,WAAU;MAAyC,CAAA,GAClF,oBAAC,SAAS,OAAV;OACE,KAAK;OACL,aAAa;OACb,cAAY,kBAAkB,QAAQ,MAAM,EAAE;OAC9C,WAAU;MACX,CAAA,CACE;;KAGL,oBAAC,SAAS,QAAV;MAAiB,aAAU;MAAsB,WAAU;MACxD,UAAA,SACC,qBAAC,QAAD;OAAM,WAAU;OAAhB,UAAA,CACG,cACA,YAAY,KAAA,KACX,oBAAC,UAAD;QAAQ,MAAK;QAAS,SAAS;QAAS,WAAU;QAC/C,UAAA,OAAO;OACF,CAAA,CAEN;MACJ,CAAA,IAAA,UACF,OAAO,UACL;KACW,CAAA;KACjB,oBAAC,SAAS,OAAV;MAAgB,WAAU;MACvB,UAAA,WAAW,SAAS,OAAO,OAAO;KACrB,CAAA;KAChB,oBAAC,YAAD;MAAY,MAAK;MAAS,MAAA;MAAK,aAAa;MAAa,WAAU;MACjE,UAAA,oBAAC,SAAD;OAAS,UAAU;OAAa,UAAU;OAAa,MAAM;OAA2B;MAAmB,CAAA;KACjG,CAAA;KACX,WAAW,eAAe,KAAA,KACzB,oBAAC,OAAD;MAAK,WAAU;MACb,UAAA,oBAAC,UAAD;OACE,MAAK;OACL,UAAU;OACV,SAAS;OACT,WAAU;OAET,UAAA,cAAc,OAAO,cAAc,OAAO;MACrC,CAAA;KACL,CAAA;KAEN,OAAO,SAAS,KACf,qBAAC,OAAD;MAAK,WAAU;MAAf,UAAA,CACE,oBAAC,QAAD;OAAM,WAAU;OAAgB,UAAA,OAAO,cAAc,OAAO,MAAM;MAAQ,CAAA,GAC1E,oBAAC,UAAD;OACE,MAAK;OACL,eAAe,cAAc,CAAC,CAAC;OAC/B,WAAU;OAET,UAAA,OAAO;MACF,CAAA,CACL;;IAEO;;EACG,CAAA,EACN,CAAA,CACJ;;AAEnB"}
1
+ {"version":3,"file":"multi-select.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/multi-select/multi-select.tsx"],"sourcesContent":["import { Combobox } from '@base-ui/react/combobox';\nimport { ScrollArea } from '@edyn/ui/components/ui/scroll-area';\nimport { Tooltip, TooltipContent, TooltipTrigger } from '@edyn/ui/components/ui/tooltip';\nimport { useExposedFocusGuards } from '@edyn/ui/lib/focus-guards';\nimport { popupItemClass } from '@edyn/ui/lib/popup-list';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconCheck, IconChevronDown, IconSearch } from '@tabler/icons-react';\nimport { useVirtualizer } from '@tanstack/react-virtual';\nimport {\n type ComponentPropsWithoutRef,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n type RefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\n\n/** An option: `leading` goes before its label (an avatar, a status dot), `count` after it (how many it would show). */\nexport type TMultiSelectOption = { value: string; label: string; count?: number; leading?: ReactNode };\n\n/** Every word the component prints, so a product can translate it. */\nexport interface IMultiSelectLabels {\n noMatches: string;\n loading: string;\n /** Shown when `error` is `true`; a string `error` replaces it. */\n error: string;\n retry: string;\n loadMore: string;\n loadingMore: string;\n clear: string;\n selectedCount: (count: number) => string;\n}\n\nexport const DEFAULT_MULTI_SELECT_LABELS: IMultiSelectLabels = {\n noMatches: 'No matches.',\n loading: 'Loading…',\n error: 'Could not load the options.',\n retry: 'Try again',\n loadMore: 'Load more',\n loadingMore: 'Loading…',\n clear: 'Clear',\n selectedCount: (count) => `${count} selected`,\n};\n\n/** Above this many options a list draws only the rows on screen. */\nexport const VIRTUALIZE_FROM = 100;\n\nconst ITEM_HEIGHT = 32;\n\n/** Anything else a button takes (data-*, aria-*, title) goes to the trigger. */\nexport interface IMultiSelectProps\n extends Omit<\n ComponentPropsWithoutRef<'button'>,\n 'placeholder' | 'value' | 'defaultValue' | 'id' | 'disabled' | 'className' | 'children' | 'onChange'\n > {\n /** The trigger's text with nothing picked, which means no filter (\"All dealer groups\"). */\n placeholder: string;\n /** The search field's prompt (\"Search dealer groups…\"). */\n searchPlaceholder: string;\n /** The options to show now: all of a short fixed list, or in async mode the ones for the typed text. */\n options: readonly TMultiSelectOption[];\n value: readonly string[];\n onValueChange: (value: string[]) => void;\n /**\n * What the trigger says once something is chosen. `selection` (default) names the one chosen or counts several;\n * `label` keeps the label and only counts, so a value the page shows elsewhere (as a removable chip, say) is\n * not repeated here: nothing chosen reads `placeholder`, anything chosen reads `labels.selectedCount(n)`.\n */\n summary?: 'selection' | 'label';\n /** For a Label's htmlFor. */\n id?: string;\n 'aria-label'?: string;\n /**\n * Marks the trigger invalid: the kit's destructive border, and a screen reader says so. For a form that does not use\n * Field; inside one, its invalid state already does this.\n */\n 'aria-invalid'?: boolean | 'true' | 'false';\n /**\n * The id of the text that explains it (the error). Added to what a Field names already, never replacing it.\n */\n 'aria-describedby'?: string;\n /** Shown but not changeable (a read-only form). */\n disabled?: boolean;\n /** Layout only (a width); the look is the kit's. */\n className?: string;\n /**\n * Passing this turns on ASYNC MODE, for lists that grow (people, dealer groups) and must\n * be searched by the API rather than loaded whole and filtered in the browser. The\n * component then never filters: it reports the typed text (after `searchDelay`) and shows\n * whatever `options` the caller supplies for it. It is also called with '' each time the\n * list opens, so the first page is fetched on demand, not at page load.\n */\n onSearchChange?: (text: string) => void;\n /** How long typing must pause before `onSearchChange` is called. Async mode only. */\n searchDelay?: number;\n /**\n * The options behind the chosen values, for the ones not in the current `options`. A\n * search that no longer returns a chosen value must not make it vanish from the trigger\n * or the count, so the caller says what it is called. Without a label a value shows as\n * itself.\n */\n selectedOptions?: readonly TMultiSelectOption[];\n /** The options for the typed text are on their way. The old ones stay until they arrive. */\n loading?: boolean;\n /** The request failed: `true` for the default message, or the message itself. */\n error?: boolean | string;\n onRetry?: () => void;\n /** There is another page after `options`. */\n hasMore?: boolean;\n onLoadMore?: () => void;\n /** The next page is on its way. */\n loadingMore?: boolean;\n labels?: Partial<IMultiSelectLabels>;\n}\n\n/**\n * The options behind the chosen values, in the order they were chosen. A value with no\n * known option is kept and shown as itself rather than dropped: a selection must never\n * change just because a list did.\n */\nexport const resolveChosen = (\n value: readonly string[],\n options: readonly TMultiSelectOption[],\n selectedOptions: readonly TMultiSelectOption[] = [],\n): TMultiSelectOption[] => {\n const known = new Map<string, TMultiSelectOption>();\n for (const option of [...selectedOptions, ...options]) {\n known.set(option.value, option);\n }\n return value.map((entry) => known.get(entry) ?? { value: entry, label: entry });\n};\n\n/** What the trigger says: the placeholder, the one picked, or a count. */\nexport const summarize = (\n chosen: readonly TMultiSelectOption[],\n placeholder: string,\n labels: IMultiSelectLabels,\n summary: 'selection' | 'label' = 'selection',\n): string => {\n if (chosen.length === 0) {\n return placeholder;\n }\n if (summary === 'label') {\n return labels.selectedCount(chosen.length);\n }\n return chosen.length === 1 ? (chosen[0]?.label ?? '') : labels.selectedCount(chosen.length);\n};\n\n/** Every chosen name, for a tooltip on a trigger whose text is cut. With `label` the names are not shown, so it is the text itself. */\nexport const fullSelection = (\n chosen: readonly TMultiSelectOption[],\n placeholder: string,\n labels: IMultiSelectLabels,\n summary: 'selection' | 'label' = 'selection',\n): string =>\n summary === 'selection' && chosen.length > 0 ? chosen.map((option) => option.label).join(', ') : summarize(chosen, placeholder, labels, summary);\n\nconst POPUP =\n 'flex max-h-(--available-height) w-(--anchor-width) min-w-64 max-w-[max(24rem,var(--anchor-width))] origin-(--transform-origin) flex-col overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-md outline-none duration-100 motion-reduce:animate-none data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95';\n\nconst TRIGGER =\n 'flex h-8 w-full min-w-0 cursor-pointer items-center justify-between gap-2 rounded-lg border border-input bg-transparent px-2.5 text-sm text-foreground transition-colors outline-none focus-visible:border-ring disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive data-popup-open:border-ring dark:bg-input/30';\n\nconst Option = ({\n option,\n index,\n style,\n measure,\n highlighted,\n}: {\n option: TMultiSelectOption;\n index: number;\n style?: CSSProperties;\n measure?: (node: Element | null) => void;\n highlighted: boolean;\n}) => {\n const [cut, setCut] = useState(false);\n // A callback ref, so measuring starts when the label mounts and stops when it goes; the key on the label below mounts a fresh one\n // when the text changes, which a resize observer alone would miss at an unchanged width.\n const measureLabel = useCallback((element: HTMLSpanElement | null) => {\n if (!element) {\n return;\n }\n const measureCut = (): void => setCut(element.scrollWidth > element.clientWidth);\n measureCut();\n if (typeof ResizeObserver === 'undefined') {\n return;\n }\n const observer = new ResizeObserver(measureCut);\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n\n return (\n <Combobox.Item\n value={option}\n index={index}\n ref={measure}\n data-index={index}\n style={style}\n data-slot=\"multi-select-item\"\n className={cn(popupItemClass('base-ui'), 'group')}\n >\n {/* A checkbox, filled when chosen: a set of toggles reads as one. */}\n <span\n data-slot=\"multi-select-item-indicator\"\n aria-hidden=\"true\"\n className=\"flex size-4 shrink-0 items-center justify-center rounded-sm border border-input text-primary-foreground transition-colors group-data-[selected]:border-primary group-data-[selected]:bg-primary\"\n >\n <IconCheck className=\"size-3 opacity-0 group-data-[selected]:opacity-100\" stroke={3} />\n </span>\n {option.leading}\n {/* The option is the highlighted one under the pointer and under the keyboard alike, so one controlled tooltip serves both: the full label while the text is cut. */}\n <Tooltip open={highlighted && cut}>\n <TooltipTrigger\n key={option.label}\n data-slot=\"multi-select-item-label\"\n data-overflowing={cut ? '' : undefined}\n render={<span ref={measureLabel} className=\"min-w-0 flex-1 truncate\" />}\n >\n {option.label}\n </TooltipTrigger>\n <TooltipContent side=\"right\">{option.label}</TooltipContent>\n </Tooltip>\n {option.count !== undefined && <span className=\"text-xs text-muted-foreground tabular-nums\">{option.count}</span>}\n </Combobox.Item>\n );\n};\n\n// The options on offer, every one drawn, or past VIRTUALIZE_FROM only those on screen. The\n// list is the listbox itself (the options its direct children), sized to the whole list\n// when virtualized; the ScrollArea wraps it.\nconst Options = ({\n scroller,\n scrollTo,\n busy,\n highlightedValue,\n}: {\n scroller: RefObject<HTMLDivElement | null>;\n scrollTo: RefObject<(index: number) => void>;\n busy: boolean;\n highlightedValue: string | null;\n}) => {\n const items = Combobox.useFilteredItems<TMultiSelectOption>();\n const virtual = items.length > VIRTUALIZE_FROM;\n // The scroller is an ancestor, so its ref is set only after this list's own effects:\n // one more render once mounted gives the virtualizer its element.\n const [mounted, setMounted] = useState(false);\n useEffect(() => setMounted(true), []);\n const virtualizer = useVirtualizer({\n count: items.length,\n getScrollElement: () => scroller.current,\n estimateSize: () => ITEM_HEIGHT,\n overscan: 12,\n enabled: virtual && mounted,\n });\n useEffect(() => {\n scrollTo.current = virtual ? (index) => virtualizer.scrollToIndex(index, { align: 'auto' }) : () => undefined;\n }, [scrollTo, virtual, virtualizer]);\n if (!virtual) {\n return (\n <Combobox.List aria-busy={busy || undefined} className=\"p-1 outline-none\">\n {items.map((option, index) => (\n <Option key={option.value} option={option} index={index} highlighted={highlightedValue === option.value} />\n ))}\n </Combobox.List>\n );\n }\n return (\n <Combobox.List\n data-virtualized=\"\"\n aria-busy={busy || undefined}\n className=\"relative m-1 outline-none\"\n style={{ height: virtualizer.getTotalSize() }}\n >\n {virtualizer.getVirtualItems().map((row) => {\n const option = items[row.index];\n return option === undefined ? null : (\n <Option\n key={option.value}\n option={option}\n index={row.index}\n highlighted={highlightedValue === option.value}\n measure={virtualizer.measureElement}\n style={{ position: 'absolute', top: 0, left: 0, right: 0, transform: `translateY(${row.start}px)` }}\n />\n );\n })}\n </Combobox.List>\n );\n};\n\n/** Rendered inside the portal, so it exists only while the list is open: the focus guards around the list are not aria-hidden. */\nconst ExposedFocusGuards = () => {\n useExposedFocusGuards();\n return null;\n};\n\n/**\n * A choice of several from a list: a trigger shaped like a Select that says what is picked\n * (the placeholder, the one picked, or a count), and a popup with a search field and checkbox\n * options, with the shared popup list style, the ScrollArea's bar and fades, and virtualization\n * past VIRTUALIZE_FROM options. Nothing chosen means no filter.\n *\n * For a short fixed list pass `options` and nothing else: the component filters them as the\n * user types. For a list that grows, pass `onSearchChange` and it becomes a window onto a\n * server: the caller fetches the options for the typed text, and says when they are loading,\n * failed, or have another page.\n */\nexport const MultiSelect = ({\n placeholder,\n searchPlaceholder,\n options,\n value,\n onValueChange,\n id,\n disabled = false,\n className,\n onSearchChange,\n searchDelay = 250,\n selectedOptions,\n loading = false,\n error = false,\n onRetry,\n hasMore = false,\n onLoadMore,\n loadingMore = false,\n labels: labelOverrides,\n summary = 'selection',\n ...rest\n}: IMultiSelectProps) => {\n const labels = useMemo(() => ({ ...DEFAULT_MULTI_SELECT_LABELS, ...labelOverrides }), [labelOverrides]);\n const isAsync = onSearchChange !== undefined;\n const chosen = useMemo(() => resolveChosen(value, options, selectedOptions), [value, options, selectedOptions]);\n // Chosen options the current page does not contain stay on offer at the top, so they can\n // still be unchecked after a search has moved on.\n const items = useMemo(() => {\n const offered = new Set(options.map((option) => option.value));\n return [...chosen.filter((option) => !offered.has(option.value)), ...options];\n }, [chosen, options]);\n const refScroller = useRef<HTMLDivElement | null>(null);\n const refScrollTo = useRef<(index: number) => void>(() => undefined);\n const refTimer = useRef<number | undefined>(undefined);\n const refTriggerText = useRef<HTMLSpanElement | null>(null);\n const refSearch = useRef<HTMLInputElement | null>(null);\n // The popup and the trigger's tooltip are two hover surfaces on one element, so the tooltip stands down while the popup is open.\n const [popupOpen, setPopupOpen] = useState(false);\n // The option under the pointer or the keyboard, whichever moved last, for the tooltip that shows a cut label in full.\n const [highlightedValue, setHighlightedValue] = useState<string | null>(null);\n // The text last reported through onSearchChange, and whether the popup is open. Base UI\n // reports the input as empty when the popup opens and again when it closes; reporting\n // those would fetch the first page twice on every open, and again while closed.\n const refReported = useRef<string | undefined>(undefined);\n const refOpen = useRef(false);\n const errorMessage = typeof error === 'string' ? error : labels.error;\n const failed = error !== false;\n\n useEffect(() => () => window.clearTimeout(refTimer.current), []);\n\n // Base UI gives the search field a key handler that closes the popup on Escape and keeps the key from the page, but not\n // the trigger: with focus back on the trigger and the popup still open (a click that landed while a Sheet slid in,\n // `focus()`, Shift+Tab) Escape reached the document, where an enclosing Sheet or Dialog closed along with the popup.\n // So the trigger hands Escape to the search field, the path that already works, and the key goes no further.\n const closeFromTrigger = (event: ReactKeyboardEvent<HTMLElement>) => {\n if (event.key !== 'Escape' || !popupOpen || refSearch.current === null) {\n return;\n }\n event.stopPropagation();\n event.preventDefault();\n refSearch.current.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, cancelable: true }));\n };\n\n return (\n <Combobox.Root\n multiple\n disabled={disabled}\n items={items}\n filter={isAsync ? null : undefined}\n value={chosen}\n onValueChange={(next: TMultiSelectOption[]) => onValueChange(next.map((option) => option.value))}\n onInputValueChange={(text) => {\n if (onSearchChange === undefined || !refOpen.current) {\n return;\n }\n window.clearTimeout(refTimer.current);\n if (text === refReported.current) {\n return;\n }\n refTimer.current = window.setTimeout(() => {\n refReported.current = text;\n onSearchChange(text);\n }, searchDelay);\n }}\n onOpenChange={(open) => {\n window.clearTimeout(refTimer.current);\n setPopupOpen(open);\n if (!open) {\n setHighlightedValue(null);\n }\n refOpen.current = open;\n refReported.current = open ? '' : undefined;\n if (open && onSearchChange !== undefined) {\n onSearchChange('');\n }\n }}\n itemToStringLabel={(option: TMultiSelectOption) => option.label}\n isItemEqualToValue={(a: TMultiSelectOption, b: TMultiSelectOption) => a.value === b.value}\n virtualized={items.length > VIRTUALIZE_FROM}\n onItemHighlighted={(item, details) => {\n setHighlightedValue(item?.value ?? null);\n if (details.reason === 'keyboard' && details.index >= 0) {\n refScrollTo.current(details.index);\n }\n }}\n >\n {/* A tooltip only when the text is really cut, which is known when it is about to open. */}\n <Tooltip\n disabled={popupOpen}\n onOpenChange={(open, details) => {\n const element = refTriggerText.current;\n if (open && !(element && element.scrollWidth > element.clientWidth)) {\n details.cancel();\n }\n }}\n >\n <TooltipTrigger\n data-slot=\"multi-select-trigger\"\n render={\n <Combobox.Trigger\n {...rest}\n id={id}\n data-set={chosen.length > 0 ? '' : undefined}\n onKeyDown={closeFromTrigger}\n className={cn(TRIGGER, className)}\n />\n }\n >\n <span ref={refTriggerText} className={cn('min-w-0 flex-1 truncate text-left', chosen.length === 0 && 'text-muted-foreground')}>\n {summarize(chosen, placeholder, labels, summary)}\n </span>\n <IconChevronDown aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n </TooltipTrigger>\n <TooltipContent>{fullSelection(chosen, placeholder, labels, summary)}</TooltipContent>\n </Tooltip>\n <Combobox.Portal>\n <ExposedFocusGuards />\n <Combobox.Positioner sideOffset={4} align=\"start\" className=\"z-50\">\n <Combobox.Popup data-slot=\"multi-select-content\" className={POPUP}>\n <div className=\"flex shrink-0 items-center gap-2 border-b border-border px-3\">\n <IconSearch aria-hidden=\"true\" className=\"size-4 shrink-0 text-muted-foreground\" />\n <Combobox.Input\n ref={refSearch}\n placeholder={searchPlaceholder}\n aria-label={searchPlaceholder.replace(/…$/, '')}\n className=\"h-10 w-full min-w-0 bg-transparent text-sm outline-none placeholder:text-muted-foreground\"\n />\n </div>\n {/* The live region stays mounted and only its words change, which is what makes a\n screen reader announce them. */}\n <Combobox.Status data-slot=\"multi-select-status\" className=\"px-3 py-2 text-sm text-muted-foreground empty:p-0\">\n {failed ? (\n <span className=\"flex items-center justify-between gap-2 text-destructive\">\n {errorMessage}\n {onRetry !== undefined && (\n <button type=\"button\" onClick={onRetry} className=\"rounded-md px-2 py-1 font-medium text-foreground hover:bg-foreground/6\">\n {labels.retry}\n </button>\n )}\n </span>\n ) : loading ? (\n labels.loading\n ) : null}\n </Combobox.Status>\n <Combobox.Empty className=\"px-3 py-6 text-center text-sm text-muted-foreground empty:p-0\">\n {loading || failed ? null : labels.noMatches}\n </Combobox.Empty>\n <ScrollArea type=\"always\" fade viewportRef={refScroller} className=\"max-h-72 min-h-0\">\n <Options scroller={refScroller} scrollTo={refScrollTo} busy={loading} highlightedValue={highlightedValue} />\n </ScrollArea>\n {hasMore && onLoadMore !== undefined && (\n <div className=\"shrink-0 border-t border-border p-1\">\n <button\n type=\"button\"\n disabled={loadingMore}\n onClick={onLoadMore}\n className=\"h-8 w-full rounded-md px-2 text-sm font-medium text-foreground hover:bg-foreground/6 disabled:pointer-events-none disabled:opacity-50\"\n >\n {loadingMore ? labels.loadingMore : labels.loadMore}\n </button>\n </div>\n )}\n {chosen.length > 0 && (\n <div className=\"flex shrink-0 items-center justify-between gap-2 border-t border-border py-1 pr-1 pl-3 text-xs text-muted-foreground\">\n <span className=\"tabular-nums\">{labels.selectedCount(chosen.length)}</span>\n <button\n type=\"button\"\n onClick={() => onValueChange([])}\n className=\"rounded-md px-2 py-1 font-medium text-foreground hover:bg-foreground/6\"\n >\n {labels.clear}\n </button>\n </div>\n )}\n </Combobox.Popup>\n </Combobox.Positioner>\n </Combobox.Portal>\n </Combobox.Root>\n );\n};\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export * from './presence';
@@ -0,0 +1,15 @@
1
+ import { ComponentPropsWithoutRef } from 'react';
2
+ /** Where a person is, as a chat or a directory shows it. */
3
+ export declare const PRESENCE_STATES: readonly ["available", "busy", "away", "out-of-office", "offline", "unknown"];
4
+ export type TPresenceState = (typeof PRESENCE_STATES)[number];
5
+ /** The words for each state, for assistive technology and for the visible label. A product that is not in English passes its own. */
6
+ export declare const DEFAULT_PRESENCE_LABELS: Readonly<Record<TPresenceState, string>>;
7
+ export interface IPresenceProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children'> {
8
+ state: TPresenceState;
9
+ /** Show the state's word beside the dot. Off, the dot stands alone and the word is there for assistive technology only. */
10
+ showLabel?: boolean;
11
+ /** Other words for the state (a translation, or "In a meeting"); the default words are in `DEFAULT_PRESENCE_LABELS`. */
12
+ label?: string;
13
+ }
14
+ /** A person's presence: a small dot, and optionally its word. Always has the word as text, so it is never a picture alone. */
15
+ export declare const Presence: ({ state, showLabel, label, className, ...props }: IPresenceProps) => import('react').JSX.Element;
@@ -0,0 +1,58 @@
1
+ import { cn } from "../../../lib/utils.js";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ //#region ../ui/src/components/edyn/presence/presence.tsx
4
+ /** Where a person is, as a chat or a directory shows it. */
5
+ var PRESENCE_STATES = [
6
+ "available",
7
+ "busy",
8
+ "away",
9
+ "out-of-office",
10
+ "offline",
11
+ "unknown"
12
+ ];
13
+ /** The words for each state, for assistive technology and for the visible label. A product that is not in English passes its own. */
14
+ var DEFAULT_PRESENCE_LABELS = {
15
+ available: "Available",
16
+ busy: "Busy",
17
+ away: "Away",
18
+ "out-of-office": "Out of office",
19
+ offline: "Offline",
20
+ unknown: "Unknown"
21
+ };
22
+ /**
23
+ * Each state has its own colour and its own shape, because colour alone is not a signal (WCAG 1.4.1): available is a solid dot,
24
+ * busy a solid dot with a bar across it, away a solid dot with a bite out of it, out of office a solid dot with an arrow pointing out
25
+ * (the info colour, the one hue the other states leave free), offline a hollow ring and unknown a dashed ring.
26
+ * The colours are the theme's tokens, so they follow light and dark, and each clears 3:1 against the page, a card and a muted
27
+ * surface in both (e2e/presence.spec.ts measures it).
28
+ */
29
+ var DOT = {
30
+ available: "bg-success",
31
+ busy: "bg-destructive after:absolute after:inset-x-0.5 after:top-1/2 after:h-0.5 after:-translate-y-1/2 after:bg-background",
32
+ away: "bg-warning after:absolute after:-top-px after:-right-px after:size-1.5 after:rounded-full after:bg-background",
33
+ "out-of-office": "bg-info after:absolute after:top-1/2 after:left-1/2 after:-translate-x-1/3 after:-translate-y-1/2 after:border-y-3 after:border-l-5 after:border-y-transparent after:border-l-background",
34
+ offline: "border-2 border-muted-foreground",
35
+ unknown: "border-2 border-dashed border-muted-foreground"
36
+ };
37
+ /** A person's presence: a small dot, and optionally its word. Always has the word as text, so it is never a picture alone. */
38
+ var Presence = ({ state, showLabel = false, label, className, ...props }) => {
39
+ return /* @__PURE__ */ jsxs("span", {
40
+ "data-slot": "presence",
41
+ "data-state": state,
42
+ className: cn("inline-flex items-center gap-1.5 text-sm", className),
43
+ ...props,
44
+ children: [/* @__PURE__ */ jsx("span", {
45
+ "aria-hidden": "true",
46
+ "data-slot": "presence-dot",
47
+ className: cn("relative size-2.5 shrink-0 rounded-full", DOT[state])
48
+ }), /* @__PURE__ */ jsx("span", {
49
+ "data-slot": "presence-label",
50
+ className: cn(!showLabel && "sr-only"),
51
+ children: label ?? DEFAULT_PRESENCE_LABELS[state]
52
+ })]
53
+ });
54
+ };
55
+ //#endregion
56
+ export { DEFAULT_PRESENCE_LABELS, PRESENCE_STATES, Presence };
57
+
58
+ //# sourceMappingURL=presence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"presence.js","names":[],"sources":["../../../../../../../ui/src/components/edyn/presence/presence.tsx"],"sourcesContent":["import { cn } from '@edyn/ui/lib/utils';\nimport type { ComponentPropsWithoutRef } from 'react';\n\n/** Where a person is, as a chat or a directory shows it. */\nexport const PRESENCE_STATES = ['available', 'busy', 'away', 'out-of-office', 'offline', 'unknown'] as const;\n\nexport type TPresenceState = (typeof PRESENCE_STATES)[number];\n\n/** The words for each state, for assistive technology and for the visible label. A product that is not in English passes its own. */\nexport const DEFAULT_PRESENCE_LABELS: Readonly<Record<TPresenceState, string>> = {\n available: 'Available',\n busy: 'Busy',\n away: 'Away',\n 'out-of-office': 'Out of office',\n offline: 'Offline',\n unknown: 'Unknown',\n};\n\n/**\n * Each state has its own colour and its own shape, because colour alone is not a signal (WCAG 1.4.1): available is a solid dot,\n * busy a solid dot with a bar across it, away a solid dot with a bite out of it, out of office a solid dot with an arrow pointing out\n * (the info colour, the one hue the other states leave free), offline a hollow ring and unknown a dashed ring.\n * The colours are the theme's tokens, so they follow light and dark, and each clears 3:1 against the page, a card and a muted\n * surface in both (e2e/presence.spec.ts measures it).\n */\nconst DOT: Readonly<Record<TPresenceState, string>> = {\n available: 'bg-success',\n busy: 'bg-destructive after:absolute after:inset-x-0.5 after:top-1/2 after:h-0.5 after:-translate-y-1/2 after:bg-background',\n away: 'bg-warning after:absolute after:-top-px after:-right-px after:size-1.5 after:rounded-full after:bg-background',\n 'out-of-office':\n 'bg-info after:absolute after:top-1/2 after:left-1/2 after:-translate-x-1/3 after:-translate-y-1/2 after:border-y-3 after:border-l-5 after:border-y-transparent after:border-l-background',\n offline: 'border-2 border-muted-foreground',\n unknown: 'border-2 border-dashed border-muted-foreground',\n};\n\nexport interface IPresenceProps extends Omit<ComponentPropsWithoutRef<'span'>, 'children'> {\n state: TPresenceState;\n /** Show the state's word beside the dot. Off, the dot stands alone and the word is there for assistive technology only. */\n showLabel?: boolean;\n /** Other words for the state (a translation, or \"In a meeting\"); the default words are in `DEFAULT_PRESENCE_LABELS`. */\n label?: string;\n}\n\n/** A person's presence: a small dot, and optionally its word. Always has the word as text, so it is never a picture alone. */\nexport const Presence = ({ state, showLabel = false, label, className, ...props }: IPresenceProps) => {\n return (\n <span data-slot=\"presence\" data-state={state} className={cn('inline-flex items-center gap-1.5 text-sm', className)} {...props}>\n <span aria-hidden=\"true\" data-slot=\"presence-dot\" className={cn('relative size-2.5 shrink-0 rounded-full', DOT[state])} />\n <span data-slot=\"presence-label\" className={cn(!showLabel && 'sr-only')}>\n {label ?? DEFAULT_PRESENCE_LABELS[state]}\n </span>\n </span>\n );\n};\n"],"mappings":";;;;AAIA,IAAa,kBAAkB;CAAC;CAAa;CAAQ;CAAQ;CAAiB;CAAW;AAAS;;AAKlG,IAAa,0BAAoE;CAC/E,WAAW;CACX,MAAM;CACN,MAAM;CACN,iBAAiB;CACjB,SAAS;CACT,SAAS;AACX;;;;;;;;AASA,IAAM,MAAgD;CACpD,WAAW;CACX,MAAM;CACN,MAAM;CACN,iBACE;CACF,SAAS;CACT,SAAS;AACX;;AAWA,IAAa,YAAY,EAAE,OAAO,YAAY,OAAO,OAAO,WAAW,GAAG,YAA4B;CACpG,OACE,qBAAC,QAAD;EAAM,aAAU;EAAW,cAAY;EAAO,WAAW,GAAG,4CAA4C,SAAS;EAAG,GAAI;EAAxH,UAAA,CACE,oBAAC,QAAD;GAAM,eAAY;GAAO,aAAU;GAAe,WAAW,GAAG,2CAA2C,IAAI,MAAM;EAAI,CAAA,GACzH,oBAAC,QAAD;GAAM,aAAU;GAAiB,WAAW,GAAG,CAAC,aAAa,SAAS;GACnE,UAAA,SAAS,wBAAwB;EAC9B,CAAA,CACF;;AAEV"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Presence: a dot and the word for a person's state.
3
+ *
4
+ * PATH TAKEN: what the component renders (the word, what is hidden, what is decorative, how each state differs) does not depend
5
+ * on layout, so happy-dom is enough. That the colours clear 3:1 in both themes and the label contrast needs a real browser:
6
+ * e2e/presence.spec.ts measures it.
7
+ */
8
+ export {};
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
2
  import { Button } from "../button/button.js";
3
+ import { useExposedFocusGuards } from "../../../lib/focus-guards.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { AlertDialog } from "@base-ui/react/alert-dialog";
5
6
  //#region ../ui/src/components/ui/alert-dialog/alert-dialog.tsx
@@ -29,6 +30,7 @@ var AlertDialogOverlay = ({ className, ...props }) => {
29
30
  });
30
31
  };
31
32
  var AlertDialogContent = ({ className, size = "default", ...props }) => {
33
+ useExposedFocusGuards();
32
34
  return /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [/* @__PURE__ */ jsx(AlertDialogOverlay, {}), /* @__PURE__ */ jsx(AlertDialog.Popup, {
33
35
  "data-slot": "alert-dialog-content",
34
36
  "data-size": size,
@@ -1 +1 @@
1
- {"version":3,"file":"alert-dialog.js","names":[],"sources":["../../../../../../../ui/src/components/ui/alert-dialog/alert-dialog.tsx"],"sourcesContent":["import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';\nimport { cn } from '@edyn/ui/lib/utils';\nimport type * as React from 'react';\n\nimport { Button } from '../button';\n\nexport const AlertDialog = ({ ...props }: AlertDialogPrimitive.Root.Props) => {\n return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n};\n\nexport const AlertDialogTrigger = ({ ...props }: AlertDialogPrimitive.Trigger.Props) => {\n return <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />;\n};\n\nexport const AlertDialogPortal = ({ ...props }: AlertDialogPrimitive.Portal.Props) => {\n return <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />;\n};\n\nexport const AlertDialogOverlay = ({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) => {\n return (\n <AlertDialogPrimitive.Backdrop\n data-slot=\"alert-dialog-overlay\"\n className={cn(\n 'fixed inset-0 isolate z-50 bg-overlay duration-100 motion-reduce:animate-none supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogContent = ({\n className,\n size = 'default',\n ...props\n}: AlertDialogPrimitive.Popup.Props & {\n size?: 'default' | 'sm';\n}) => {\n return (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Popup\n data-slot=\"alert-dialog-content\"\n data-size={size}\n className={cn(\n 'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 motion-reduce:animate-none outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n className,\n )}\n {...props}\n />\n </AlertDialogPortal>\n );\n};\n\nexport const AlertDialogHeader = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(\n 'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogFooter = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(\n '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogMedia = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return (\n <div\n data-slot=\"alert-dialog-media\"\n className={cn(\n \"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogTitle = ({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) => {\n return (\n <AlertDialogPrimitive.Title\n data-slot=\"alert-dialog-title\"\n className={cn(\n 'font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogDescription = ({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) => {\n return (\n <AlertDialogPrimitive.Description\n data-slot=\"alert-dialog-description\"\n className={cn(\n 'text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogAction = ({ className, ...props }: React.ComponentProps<typeof Button>) => {\n return <Button data-slot=\"alert-dialog-action\" className={cn(className)} {...props} />;\n};\n\nexport const AlertDialogCancel = ({\n className,\n variant = 'outline',\n size = 'default',\n ...props\n}: AlertDialogPrimitive.Close.Props & Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) => {\n return (\n <AlertDialogPrimitive.Close\n data-slot=\"alert-dialog-cancel\"\n className={cn(className)}\n render={<Button variant={variant} size={size} />}\n {...props}\n />\n );\n};\n"],"mappings":";;;;;AAMA,IAAa,iBAAe,EAAE,GAAG,YAA6C;CAC5E,OAAO,oBAAC,YAAqB,MAAtB;EAA2B,aAAU;EAAe,GAAI;CAAQ,CAAA;AACzE;AAEA,IAAa,sBAAsB,EAAE,GAAG,YAAgD;CACtF,OAAO,oBAAC,YAAqB,SAAtB;EAA8B,aAAU;EAAuB,GAAI;CAAQ,CAAA;AACpF;AAEA,IAAa,qBAAqB,EAAE,GAAG,YAA+C;CACpF,OAAO,oBAAC,YAAqB,QAAtB;EAA6B,aAAU;EAAsB,GAAI;CAAQ,CAAA;AAClF;AAEA,IAAa,sBAAsB,EAAE,WAAW,GAAG,YAAiD;CAClG,OACE,oBAAC,YAAqB,UAAtB;EACE,aAAU;EACV,WAAW,GACT,mNACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,sBAAsB,EACjC,WACA,OAAO,WACP,GAAG,YAGC;CACJ,OACE,qBAAC,mBAAD,EAAA,UAAA,CACE,oBAAC,oBAAD,CAAqB,CAAA,GACrB,oBAAC,YAAqB,OAAtB;EACE,aAAU;EACV,aAAW;EACX,WAAW,GACT,ycACA,SACF;EACA,GAAI;CACL,CAAA,CACgB,EAAA,CAAA;AAEvB;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAAyC;CACzF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,sZACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAAyC;CACzF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,iNACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,oBAAoB,EAAE,WAAW,GAAG,YAAyC;CACxF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,8KACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,oBAAoB,EAAE,WAAW,GAAG,YAAqE;CACpH,OACE,oBAAC,YAAqB,OAAtB;EACE,aAAU;EACV,WAAW,GACT,kKACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,0BAA0B,EAAE,WAAW,GAAG,YAA2E;CAChI,OACE,oBAAC,YAAqB,aAAtB;EACE,aAAU;EACV,WAAW,GACT,kIACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAAiD;CACjG,OAAO,oBAAC,QAAD;EAAQ,aAAU;EAAsB,WAAW,GAAG,SAAS;EAAG,GAAI;CAAQ,CAAA;AACvF;AAEA,IAAa,qBAAqB,EAChC,WACA,UAAU,WACV,OAAO,WACP,GAAG,YACmG;CACtG,OACE,oBAAC,YAAqB,OAAtB;EACE,aAAU;EACV,WAAW,GAAG,SAAS;EACvB,QAAQ,oBAAC,QAAD;GAAiB;GAAe;EAAO,CAAA;EAC/C,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"alert-dialog.js","names":[],"sources":["../../../../../../../ui/src/components/ui/alert-dialog/alert-dialog.tsx"],"sourcesContent":["import { AlertDialog as AlertDialogPrimitive } from '@base-ui/react/alert-dialog';\nimport { useExposedFocusGuards } from '@edyn/ui/lib/focus-guards';\nimport { cn } from '@edyn/ui/lib/utils';\nimport type * as React from 'react';\n\nimport { Button } from '../button';\n\nexport const AlertDialog = ({ ...props }: AlertDialogPrimitive.Root.Props) => {\n return <AlertDialogPrimitive.Root data-slot=\"alert-dialog\" {...props} />;\n};\n\nexport const AlertDialogTrigger = ({ ...props }: AlertDialogPrimitive.Trigger.Props) => {\n return <AlertDialogPrimitive.Trigger data-slot=\"alert-dialog-trigger\" {...props} />;\n};\n\nexport const AlertDialogPortal = ({ ...props }: AlertDialogPrimitive.Portal.Props) => {\n return <AlertDialogPrimitive.Portal data-slot=\"alert-dialog-portal\" {...props} />;\n};\n\nexport const AlertDialogOverlay = ({ className, ...props }: AlertDialogPrimitive.Backdrop.Props) => {\n return (\n <AlertDialogPrimitive.Backdrop\n data-slot=\"alert-dialog-overlay\"\n className={cn(\n 'fixed inset-0 isolate z-50 bg-overlay duration-100 motion-reduce:animate-none supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogContent = ({\n className,\n size = 'default',\n ...props\n}: AlertDialogPrimitive.Popup.Props & {\n size?: 'default' | 'sm';\n}) => {\n useExposedFocusGuards();\n return (\n <AlertDialogPortal>\n <AlertDialogOverlay />\n <AlertDialogPrimitive.Popup\n data-slot=\"alert-dialog-content\"\n data-size={size}\n className={cn(\n 'group/alert-dialog-content fixed top-1/2 left-1/2 z-50 grid w-full -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-popover-foreground ring-1 ring-foreground/10 duration-100 motion-reduce:animate-none outline-none data-[size=default]:max-w-xs data-[size=sm]:max-w-xs data-[size=default]:sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n className,\n )}\n {...props}\n />\n </AlertDialogPortal>\n );\n};\n\nexport const AlertDialogHeader = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return (\n <div\n data-slot=\"alert-dialog-header\"\n className={cn(\n 'grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-4 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogFooter = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return (\n <div\n data-slot=\"alert-dialog-footer\"\n className={cn(\n '-mx-4 -mb-4 flex flex-col-reverse gap-2 rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogMedia = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return (\n <div\n data-slot=\"alert-dialog-media\"\n className={cn(\n \"mb-2 inline-flex size-10 items-center justify-center rounded-md bg-muted sm:group-data-[size=default]/alert-dialog-content:row-span-2 *:[svg:not([class*='size-'])]:size-6\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogTitle = ({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Title>) => {\n return (\n <AlertDialogPrimitive.Title\n data-slot=\"alert-dialog-title\"\n className={cn(\n 'font-heading text-base font-medium sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogDescription = ({ className, ...props }: React.ComponentProps<typeof AlertDialogPrimitive.Description>) => {\n return (\n <AlertDialogPrimitive.Description\n data-slot=\"alert-dialog-description\"\n className={cn(\n 'text-sm text-balance text-muted-foreground md:text-pretty *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const AlertDialogAction = ({ className, ...props }: React.ComponentProps<typeof Button>) => {\n return <Button data-slot=\"alert-dialog-action\" className={cn(className)} {...props} />;\n};\n\nexport const AlertDialogCancel = ({\n className,\n variant = 'outline',\n size = 'default',\n ...props\n}: AlertDialogPrimitive.Close.Props & Pick<React.ComponentProps<typeof Button>, 'variant' | 'size'>) => {\n return (\n <AlertDialogPrimitive.Close\n data-slot=\"alert-dialog-cancel\"\n className={cn(className)}\n render={<Button variant={variant} size={size} />}\n {...props}\n />\n );\n};\n"],"mappings":";;;;;;AAOA,IAAa,iBAAe,EAAE,GAAG,YAA6C;CAC5E,OAAO,oBAAC,YAAqB,MAAtB;EAA2B,aAAU;EAAe,GAAI;CAAQ,CAAA;AACzE;AAEA,IAAa,sBAAsB,EAAE,GAAG,YAAgD;CACtF,OAAO,oBAAC,YAAqB,SAAtB;EAA8B,aAAU;EAAuB,GAAI;CAAQ,CAAA;AACpF;AAEA,IAAa,qBAAqB,EAAE,GAAG,YAA+C;CACpF,OAAO,oBAAC,YAAqB,QAAtB;EAA6B,aAAU;EAAsB,GAAI;CAAQ,CAAA;AAClF;AAEA,IAAa,sBAAsB,EAAE,WAAW,GAAG,YAAiD;CAClG,OACE,oBAAC,YAAqB,UAAtB;EACE,aAAU;EACV,WAAW,GACT,mNACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,sBAAsB,EACjC,WACA,OAAO,WACP,GAAG,YAGC;CACJ,sBAAsB;CACtB,OACE,qBAAC,mBAAD,EAAA,UAAA,CACE,oBAAC,oBAAD,CAAqB,CAAA,GACrB,oBAAC,YAAqB,OAAtB;EACE,aAAU;EACV,aAAW;EACX,WAAW,GACT,ycACA,SACF;EACA,GAAI;CACL,CAAA,CACgB,EAAA,CAAA;AAEvB;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAAyC;CACzF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,sZACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAAyC;CACzF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,iNACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,oBAAoB,EAAE,WAAW,GAAG,YAAyC;CACxF,OACE,oBAAC,OAAD;EACE,aAAU;EACV,WAAW,GACT,8KACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,oBAAoB,EAAE,WAAW,GAAG,YAAqE;CACpH,OACE,oBAAC,YAAqB,OAAtB;EACE,aAAU;EACV,WAAW,GACT,kKACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,0BAA0B,EAAE,WAAW,GAAG,YAA2E;CAChI,OACE,oBAAC,YAAqB,aAAtB;EACE,aAAU;EACV,WAAW,GACT,kIACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAAiD;CACjG,OAAO,oBAAC,QAAD;EAAQ,aAAU;EAAsB,WAAW,GAAG,SAAS;EAAG,GAAI;CAAQ,CAAA;AACvF;AAEA,IAAa,qBAAqB,EAChC,WACA,UAAU,WACV,OAAO,WACP,GAAG,YACmG;CACtG,OACE,oBAAC,YAAqB,OAAtB;EACE,aAAU;EACV,WAAW,GAAG,SAAS;EACvB,QAAQ,oBAAC,QAAD;GAAiB;GAAe;EAAO,CAAA;EAC/C,GAAI;CACL,CAAA;AAEL"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * A day button shows its number and is named with the date: the number has to be a word of that name (WCAG 2.5.3, Label in Name).
3
+ *
4
+ * PATH TAKEN: the names are text react-day-picker builds from a date and a locale, so happy-dom is enough. axe 4.14 and later reads
5
+ * a name word by word ("30" is not found in "30th"), and the version in this repository reads it as a string, so the rule is
6
+ * written out here (the same words, split the same way) and holds whichever axe runs. e2e/label-in-name.spec.ts does the same in a
7
+ * browser, over the pickers.
8
+ */
9
+ export {};
@@ -1,7 +1,7 @@
1
1
  import { DayButton, DayPicker, Locale } from 'react-day-picker';
2
2
  import { Button } from '../button';
3
3
  import * as React from 'react';
4
- export declare const Calendar: ({ className, classNames, showOutsideDays, captionLayout, buttonVariant, locale, formatters, components, ...props }: React.ComponentProps<typeof DayPicker> & {
4
+ export declare const Calendar: ({ className, classNames, showOutsideDays, captionLayout, buttonVariant, locale, formatters, labels, components, ...props }: React.ComponentProps<typeof DayPicker> & {
5
5
  buttonVariant?: React.ComponentProps<typeof Button>["variant"];
6
6
  }) => React.JSX.Element;
7
7
  export declare const CalendarDayButton: ({ className, day, modifiers, locale, ...props }: React.ComponentProps<typeof DayButton> & {
@@ -3,15 +3,46 @@ import { Button, buttonVariants } from "../button/button.js";
3
3
  import * as React from "react";
4
4
  import { jsx } from "react/jsx-runtime";
5
5
  import { IconChevronDown, IconChevronLeft, IconChevronRight } from "@tabler/icons-react";
6
- import { DayPicker, getDefaultClassNames } from "react-day-picker";
6
+ import { DateLib, DayPicker, getDefaultClassNames } from "react-day-picker";
7
7
  //#region ../ui/src/components/ui/calendar/calendar.tsx
8
- var Calendar = ({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", locale, formatters, components, ...props }) => {
8
+ /** Whether `day` stands in `label` as a word of its own, not as the start of "30th" or "30일" (axe 4.14 and later reads the name word by word). */
9
+ var hasWord = (label, day) => new RegExp(`(^|[^\\p{L}\\p{N}])${day}($|[^\\p{L}\\p{N}])`, "u").test(label);
10
+ /**
11
+ * A day button's accessible name. A button that shows "30" has to be named with that 30 as a word of its own (WCAG 2.5.3, Label in
12
+ * Name): axe 4.14 and later (label-content-name-mismatch) reads the name word by word, and react-day-picker's "Monday, November 30th,
13
+ * 2026" has no word "30", only "30th".
14
+ *
15
+ * The name a locale gives (react-day-picker/locale, English included) is kept when the day is a word in it ("Montag, 30. November
16
+ * 2026"), loses an English ordinal ("Monday, November 30, 2026") and has the day put in front when it is glued to a unit
17
+ * ("30, 2026년 11월 30일 월요일"). A locale with no names of its own is written "Monday, 30 November 2026" in its words, with
18
+ * react-day-picker's English "Today" and "selected"; a product that translates those passes `labels`, which wins.
19
+ */
20
+ var labelDayButton = (date, modifiers, options, dateLib) => {
21
+ const lib = dateLib ?? new DateLib(options);
22
+ const day = lib.format(date, "d");
23
+ const own = lib.options.locale?.labels?.labelDayButton;
24
+ if (own !== void 0) {
25
+ const label = typeof own === "string" ? own : own(date, modifiers, options, lib);
26
+ if (hasWord(label, day)) return label;
27
+ const plain = label.replace(new RegExp(`(?<![\\p{L}\\p{N}])${day}(st|nd|rd|th)(?![\\p{L}\\p{N}])`, "u"), day);
28
+ return hasWord(plain, day) ? plain : `${day}, ${label}`;
29
+ }
30
+ let label = lib.format(date, "EEEE, d MMMM yyyy");
31
+ if (modifiers.today) label = `Today, ${label}`;
32
+ if (modifiers.selected) label = `${label}, selected`;
33
+ return label;
34
+ };
35
+ var Calendar = ({ className, classNames, showOutsideDays = true, captionLayout = "label", buttonVariant = "ghost", locale, formatters, labels, components, ...props }) => {
9
36
  const defaultClassNames = getDefaultClassNames();
10
37
  return /* @__PURE__ */ jsx(DayPicker, {
11
38
  showOutsideDays,
12
39
  className: cn("group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent", String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`, String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`, className),
13
40
  captionLayout,
14
41
  locale,
42
+ labels: {
43
+ labelDayButton,
44
+ ...labels
45
+ },
15
46
  formatters: {
16
47
  formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: "short" }),
17
48
  ...formatters
@@ -1 +1 @@
1
- {"version":3,"file":"calendar.js","names":[],"sources":["../../../../../../../ui/src/components/ui/calendar/calendar.tsx"],"sourcesContent":["import { cn } from '@edyn/ui/lib/utils';\nimport { IconChevronDown, IconChevronLeft, IconChevronRight } from '@tabler/icons-react';\nimport * as React from 'react';\nimport { type DayButton, DayPicker, getDefaultClassNames, type Locale } from 'react-day-picker';\nimport { Button, buttonVariants } from '../button';\n\nexport const Calendar = ({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = 'label',\n buttonVariant = 'ghost',\n locale,\n formatters,\n components,\n ...props\n}: React.ComponentProps<typeof DayPicker> & {\n buttonVariant?: React.ComponentProps<typeof Button>['variant'];\n}) => {\n const defaultClassNames = getDefaultClassNames();\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\n 'group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent',\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className,\n )}\n captionLayout={captionLayout}\n locale={locale}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: 'short' }),\n ...formatters,\n }}\n classNames={{\n root: cn('w-fit', defaultClassNames.root),\n months: cn('relative flex flex-col gap-4 md:flex-row', defaultClassNames.months),\n month: cn('flex w-full flex-col gap-4', defaultClassNames.month),\n nav: cn('absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1', defaultClassNames.nav),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',\n defaultClassNames.button_previous,\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',\n defaultClassNames.button_next,\n ),\n month_caption: cn('flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)', defaultClassNames.month_caption),\n dropdowns: cn('flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium', defaultClassNames.dropdowns),\n dropdown_root: cn('relative rounded-(--cell-radius)', defaultClassNames.dropdown_root),\n dropdown: cn('absolute inset-0 bg-popover opacity-0', defaultClassNames.dropdown),\n caption_label: cn(\n 'font-medium select-none',\n captionLayout === 'label'\n ? 'text-sm'\n : 'flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground',\n defaultClassNames.caption_label,\n ),\n month_grid: cn('w-full border-collapse', defaultClassNames.month_grid),\n weekdays: cn('flex', defaultClassNames.weekdays),\n weekday: cn('flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none', defaultClassNames.weekday),\n week: cn('mt-2 flex w-full', defaultClassNames.week),\n week_number_header: cn('w-(--cell-size) select-none', defaultClassNames.week_number_header),\n week_number: cn('text-[0.8rem] text-muted-foreground select-none', defaultClassNames.week_number),\n day: cn(\n 'group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)',\n props.showWeekNumber\n ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)'\n : '[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)',\n defaultClassNames.day,\n ),\n range_start: cn(\n 'relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted',\n defaultClassNames.range_start,\n ),\n range_middle: cn('rounded-none', defaultClassNames.range_middle),\n range_end: cn(\n 'relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted',\n defaultClassNames.range_end,\n ),\n today: cn('rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none', defaultClassNames.today),\n outside: cn('text-muted-foreground aria-selected:text-muted-foreground', defaultClassNames.outside),\n disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),\n hidden: cn('invisible', defaultClassNames.hidden),\n ...classNames,\n }}\n components={{\n Root: ({ className, rootRef, ...props }) => {\n return <div data-slot=\"calendar\" ref={rootRef} className={cn(className)} {...props} />;\n },\n Chevron: ({ className, orientation, ...props }) => {\n if (orientation === 'left') {\n return <IconChevronLeft className={cn('size-4', className)} {...props} />;\n }\n\n if (orientation === 'right') {\n return <IconChevronRight className={cn('size-4', className)} {...props} />;\n }\n\n return <IconChevronDown className={cn('size-4', className)} {...props} />;\n },\n DayButton: ({ ...props }) => <CalendarDayButton locale={locale} {...props} />,\n WeekNumber: ({ children, ...props }) => {\n return (\n <td {...props}>\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">{children}</div>\n </td>\n );\n },\n ...components,\n }}\n {...props}\n />\n );\n};\n\nexport const CalendarDayButton = ({\n className,\n day,\n modifiers,\n locale,\n ...props\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) => {\n const defaultClassNames = getDefaultClassNames();\n\n const refButton = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (modifiers.focused) {\n refButton.current?.focus();\n }\n }, [modifiers.focused]);\n\n return (\n <Button\n ref={refButton}\n variant=\"ghost\"\n size=\"icon\"\n data-day={day.date.toLocaleDateString(locale?.code)}\n data-selected-single={modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle}\n data-range-start={modifiers.range_start}\n data-range-end={modifiers.range_end}\n data-range-middle={modifiers.range_middle}\n className={cn(\n 'relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70',\n defaultClassNames.day,\n className,\n )}\n {...props}\n />\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"calendar.js","names":[],"sources":["../../../../../../../ui/src/components/ui/calendar/calendar.tsx"],"sourcesContent":["import { cn } from '@edyn/ui/lib/utils';\nimport { IconChevronDown, IconChevronLeft, IconChevronRight } from '@tabler/icons-react';\nimport * as React from 'react';\nimport { DateLib, type DayButton, DayPicker, getDefaultClassNames, type Locale, type labelDayButton as TLabelDayButton } from 'react-day-picker';\nimport { Button, buttonVariants } from '../button';\n\n/** Whether `day` stands in `label` as a word of its own, not as the start of \"30th\" or \"30일\" (axe 4.14 and later reads the name word by word). */\nconst hasWord = (label: string, day: string): boolean => new RegExp(`(^|[^\\\\p{L}\\\\p{N}])${day}($|[^\\\\p{L}\\\\p{N}])`, 'u').test(label);\n\n/**\n * A day button's accessible name. A button that shows \"30\" has to be named with that 30 as a word of its own (WCAG 2.5.3, Label in\n * Name): axe 4.14 and later (label-content-name-mismatch) reads the name word by word, and react-day-picker's \"Monday, November 30th,\n * 2026\" has no word \"30\", only \"30th\".\n *\n * The name a locale gives (react-day-picker/locale, English included) is kept when the day is a word in it (\"Montag, 30. November\n * 2026\"), loses an English ordinal (\"Monday, November 30, 2026\") and has the day put in front when it is glued to a unit\n * (\"30, 2026년 11월 30일 월요일\"). A locale with no names of its own is written \"Monday, 30 November 2026\" in its words, with\n * react-day-picker's English \"Today\" and \"selected\"; a product that translates those passes `labels`, which wins.\n */\nconst labelDayButton: typeof TLabelDayButton = (date, modifiers, options, dateLib) => {\n const lib = dateLib ?? new DateLib(options);\n const day = lib.format(date, 'd');\n const own = lib.options.locale?.labels?.labelDayButton;\n if (own !== undefined) {\n const label = typeof own === 'string' ? own : own(date, modifiers, options, lib);\n if (hasWord(label, day)) {\n return label;\n }\n // English writes 30th, so \"30th\" becomes \"30\"; a unit glued to the day (30일) stays, and the day goes in front.\n const plain = label.replace(new RegExp(`(?<![\\\\p{L}\\\\p{N}])${day}(st|nd|rd|th)(?![\\\\p{L}\\\\p{N}])`, 'u'), day);\n return hasWord(plain, day) ? plain : `${day}, ${label}`;\n }\n let label = lib.format(date, 'EEEE, d MMMM yyyy');\n if (modifiers.today) {\n label = `Today, ${label}`;\n }\n if (modifiers.selected) {\n label = `${label}, selected`;\n }\n return label;\n};\n\nexport const Calendar = ({\n className,\n classNames,\n showOutsideDays = true,\n captionLayout = 'label',\n buttonVariant = 'ghost',\n locale,\n formatters,\n labels,\n components,\n ...props\n}: React.ComponentProps<typeof DayPicker> & {\n buttonVariant?: React.ComponentProps<typeof Button>['variant'];\n}) => {\n const defaultClassNames = getDefaultClassNames();\n\n return (\n <DayPicker\n showOutsideDays={showOutsideDays}\n className={cn(\n 'group/calendar bg-background p-2 [--cell-radius:var(--radius-md)] [--cell-size:--spacing(7)] in-data-[slot=card-content]:bg-transparent in-data-[slot=popover-content]:bg-transparent',\n String.raw`rtl:**:[.rdp-button\\_next>svg]:rotate-180`,\n String.raw`rtl:**:[.rdp-button\\_previous>svg]:rotate-180`,\n className,\n )}\n captionLayout={captionLayout}\n locale={locale}\n labels={{ labelDayButton, ...labels }}\n formatters={{\n formatMonthDropdown: (date) => date.toLocaleString(locale?.code, { month: 'short' }),\n ...formatters,\n }}\n classNames={{\n root: cn('w-fit', defaultClassNames.root),\n months: cn('relative flex flex-col gap-4 md:flex-row', defaultClassNames.months),\n month: cn('flex w-full flex-col gap-4', defaultClassNames.month),\n nav: cn('absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1', defaultClassNames.nav),\n button_previous: cn(\n buttonVariants({ variant: buttonVariant }),\n 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',\n defaultClassNames.button_previous,\n ),\n button_next: cn(\n buttonVariants({ variant: buttonVariant }),\n 'size-(--cell-size) p-0 select-none aria-disabled:opacity-50',\n defaultClassNames.button_next,\n ),\n month_caption: cn('flex h-(--cell-size) w-full items-center justify-center px-(--cell-size)', defaultClassNames.month_caption),\n dropdowns: cn('flex h-(--cell-size) w-full items-center justify-center gap-1.5 text-sm font-medium', defaultClassNames.dropdowns),\n dropdown_root: cn('relative rounded-(--cell-radius)', defaultClassNames.dropdown_root),\n dropdown: cn('absolute inset-0 bg-popover opacity-0', defaultClassNames.dropdown),\n caption_label: cn(\n 'font-medium select-none',\n captionLayout === 'label'\n ? 'text-sm'\n : 'flex items-center gap-1 rounded-(--cell-radius) text-sm [&>svg]:size-3.5 [&>svg]:text-muted-foreground',\n defaultClassNames.caption_label,\n ),\n month_grid: cn('w-full border-collapse', defaultClassNames.month_grid),\n weekdays: cn('flex', defaultClassNames.weekdays),\n weekday: cn('flex-1 rounded-(--cell-radius) text-[0.8rem] font-normal text-muted-foreground select-none', defaultClassNames.weekday),\n week: cn('mt-2 flex w-full', defaultClassNames.week),\n week_number_header: cn('w-(--cell-size) select-none', defaultClassNames.week_number_header),\n week_number: cn('text-[0.8rem] text-muted-foreground select-none', defaultClassNames.week_number),\n day: cn(\n 'group/day relative aspect-square h-full w-full rounded-(--cell-radius) p-0 text-center select-none [&:last-child[data-selected=true]_button]:rounded-r-(--cell-radius)',\n props.showWeekNumber\n ? '[&:nth-child(2)[data-selected=true]_button]:rounded-l-(--cell-radius)'\n : '[&:first-child[data-selected=true]_button]:rounded-l-(--cell-radius)',\n defaultClassNames.day,\n ),\n range_start: cn(\n 'relative isolate z-0 rounded-l-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:right-0 after:w-4 after:bg-muted',\n defaultClassNames.range_start,\n ),\n range_middle: cn('rounded-none', defaultClassNames.range_middle),\n range_end: cn(\n 'relative isolate z-0 rounded-r-(--cell-radius) bg-muted after:absolute after:inset-y-0 after:left-0 after:w-4 after:bg-muted',\n defaultClassNames.range_end,\n ),\n today: cn('rounded-(--cell-radius) bg-muted text-foreground data-[selected=true]:rounded-none', defaultClassNames.today),\n outside: cn('text-muted-foreground aria-selected:text-muted-foreground', defaultClassNames.outside),\n disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),\n hidden: cn('invisible', defaultClassNames.hidden),\n ...classNames,\n }}\n components={{\n Root: ({ className, rootRef, ...props }) => {\n return <div data-slot=\"calendar\" ref={rootRef} className={cn(className)} {...props} />;\n },\n Chevron: ({ className, orientation, ...props }) => {\n if (orientation === 'left') {\n return <IconChevronLeft className={cn('size-4', className)} {...props} />;\n }\n\n if (orientation === 'right') {\n return <IconChevronRight className={cn('size-4', className)} {...props} />;\n }\n\n return <IconChevronDown className={cn('size-4', className)} {...props} />;\n },\n DayButton: ({ ...props }) => <CalendarDayButton locale={locale} {...props} />,\n WeekNumber: ({ children, ...props }) => {\n return (\n <td {...props}>\n <div className=\"flex size-(--cell-size) items-center justify-center text-center\">{children}</div>\n </td>\n );\n },\n ...components,\n }}\n {...props}\n />\n );\n};\n\nexport const CalendarDayButton = ({\n className,\n day,\n modifiers,\n locale,\n ...props\n}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) => {\n const defaultClassNames = getDefaultClassNames();\n\n const refButton = React.useRef<HTMLButtonElement>(null);\n React.useEffect(() => {\n if (modifiers.focused) {\n refButton.current?.focus();\n }\n }, [modifiers.focused]);\n\n return (\n <Button\n ref={refButton}\n variant=\"ghost\"\n size=\"icon\"\n data-day={day.date.toLocaleDateString(locale?.code)}\n data-selected-single={modifiers.selected && !modifiers.range_start && !modifiers.range_end && !modifiers.range_middle}\n data-range-start={modifiers.range_start}\n data-range-end={modifiers.range_end}\n data-range-middle={modifiers.range_middle}\n className={cn(\n 'relative isolate z-10 flex aspect-square size-auto w-full min-w-(--cell-size) flex-col gap-1 border-0 leading-none font-normal group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-[3px] group-data-[focused=true]/day:ring-ring/50 data-[range-end=true]:rounded-(--cell-radius) data-[range-end=true]:rounded-r-(--cell-radius) data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground data-[range-middle=true]:rounded-none data-[range-middle=true]:bg-muted data-[range-middle=true]:text-foreground data-[range-start=true]:rounded-(--cell-radius) data-[range-start=true]:rounded-l-(--cell-radius) data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground dark:hover:text-foreground [&>span]:text-xs [&>span]:opacity-70',\n defaultClassNames.day,\n className,\n )}\n {...props}\n />\n );\n};\n"],"mappings":";;;;;;;;AAOA,IAAM,WAAW,OAAe,QAAyB,IAAI,OAAO,sBAAsB,IAAI,sBAAsB,GAAG,CAAC,CAAC,KAAK,KAAK;;;;;;;;;;;AAYnI,IAAM,kBAA0C,MAAM,WAAW,SAAS,YAAY;CACpF,MAAM,MAAM,WAAW,IAAI,QAAQ,OAAO;CAC1C,MAAM,MAAM,IAAI,OAAO,MAAM,GAAG;CAChC,MAAM,MAAM,IAAI,QAAQ,QAAQ,QAAQ;CACxC,IAAI,QAAQ,KAAA,GAAW;EACrB,MAAM,QAAQ,OAAO,QAAQ,WAAW,MAAM,IAAI,MAAM,WAAW,SAAS,GAAG;EAC/E,IAAI,QAAQ,OAAO,GAAG,GACpB,OAAO;EAGT,MAAM,QAAQ,MAAM,QAAQ,IAAI,OAAO,sBAAsB,IAAI,kCAAkC,GAAG,GAAG,GAAG;EAC5G,OAAO,QAAQ,OAAO,GAAG,IAAI,QAAQ,GAAG,IAAI,IAAI;CAClD;CACA,IAAI,QAAQ,IAAI,OAAO,MAAM,mBAAmB;CAChD,IAAI,UAAU,OACZ,QAAQ,UAAU;CAEpB,IAAI,UAAU,UACZ,QAAQ,GAAG,MAAM;CAEnB,OAAO;AACT;AAEA,IAAa,YAAY,EACvB,WACA,YACA,kBAAkB,MAClB,gBAAgB,SAChB,gBAAgB,SAChB,QACA,YACA,QACA,YACA,GAAG,YAGC;CACJ,MAAM,oBAAoB,qBAAqB;CAE/C,OACE,oBAAC,WAAD;EACmB;EACjB,WAAW,GACT,yLACA,OAAO,GAAG,6CACV,OAAO,GAAG,iDACV,SACF;EACe;EACP;EACR,QAAQ;GAAE;GAAgB,GAAG;EAAO;EACpC,YAAY;GACV,sBAAsB,SAAS,KAAK,eAAe,QAAQ,MAAM,EAAE,OAAO,QAAQ,CAAC;GACnF,GAAG;EACL;EACA,YAAY;GACV,MAAM,GAAG,SAAS,kBAAkB,IAAI;GACxC,QAAQ,GAAG,4CAA4C,kBAAkB,MAAM;GAC/E,OAAO,GAAG,8BAA8B,kBAAkB,KAAK;GAC/D,KAAK,GAAG,2EAA2E,kBAAkB,GAAG;GACxG,iBAAiB,GACf,eAAe,EAAE,SAAS,cAAc,CAAC,GACzC,+DACA,kBAAkB,eACpB;GACA,aAAa,GACX,eAAe,EAAE,SAAS,cAAc,CAAC,GACzC,+DACA,kBAAkB,WACpB;GACA,eAAe,GAAG,4EAA4E,kBAAkB,aAAa;GAC7H,WAAW,GAAG,uFAAuF,kBAAkB,SAAS;GAChI,eAAe,GAAG,oCAAoC,kBAAkB,aAAa;GACrF,UAAU,GAAG,yCAAyC,kBAAkB,QAAQ;GAChF,eAAe,GACb,2BACA,kBAAkB,UACd,YACA,0GACJ,kBAAkB,aACpB;GACA,YAAY,GAAG,0BAA0B,kBAAkB,UAAU;GACrE,UAAU,GAAG,QAAQ,kBAAkB,QAAQ;GAC/C,SAAS,GAAG,8FAA8F,kBAAkB,OAAO;GACnI,MAAM,GAAG,oBAAoB,kBAAkB,IAAI;GACnD,oBAAoB,GAAG,+BAA+B,kBAAkB,kBAAkB;GAC1F,aAAa,GAAG,mDAAmD,kBAAkB,WAAW;GAChG,KAAK,GACH,0KACA,MAAM,iBACF,0EACA,wEACJ,kBAAkB,GACpB;GACA,aAAa,GACX,iIACA,kBAAkB,WACpB;GACA,cAAc,GAAG,gBAAgB,kBAAkB,YAAY;GAC/D,WAAW,GACT,gIACA,kBAAkB,SACpB;GACA,OAAO,GAAG,sFAAsF,kBAAkB,KAAK;GACvH,SAAS,GAAG,6DAA6D,kBAAkB,OAAO;GAClG,UAAU,GAAG,oCAAoC,kBAAkB,QAAQ;GAC3E,QAAQ,GAAG,aAAa,kBAAkB,MAAM;GAChD,GAAG;EACL;EACA,YAAY;GACV,OAAO,EAAE,WAAW,SAAS,GAAG,YAAY;IAC1C,OAAO,oBAAC,OAAD;KAAK,aAAU;KAAW,KAAK;KAAS,WAAW,GAAG,SAAS;KAAG,GAAI;IAAQ,CAAA;GACvF;GACA,UAAU,EAAE,WAAW,aAAa,GAAG,YAAY;IACjD,IAAI,gBAAgB,QAClB,OAAO,oBAAC,iBAAD;KAAiB,WAAW,GAAG,UAAU,SAAS;KAAG,GAAI;IAAQ,CAAA;IAG1E,IAAI,gBAAgB,SAClB,OAAO,oBAAC,kBAAD;KAAkB,WAAW,GAAG,UAAU,SAAS;KAAG,GAAI;IAAQ,CAAA;IAG3E,OAAO,oBAAC,iBAAD;KAAiB,WAAW,GAAG,UAAU,SAAS;KAAG,GAAI;IAAQ,CAAA;GAC1E;GACA,YAAY,EAAE,GAAG,YAAY,oBAAC,mBAAD;IAA2B;IAAQ,GAAI;GAAQ,CAAA;GAC5E,aAAa,EAAE,UAAU,GAAG,YAAY;IACtC,OACE,oBAAC,MAAD;KAAI,GAAI;KACN,UAAA,oBAAC,OAAD;MAAK,WAAU;MAAmE;KAAc,CAAA;IAC9F,CAAA;GAER;GACA,GAAG;EACL;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,qBAAqB,EAChC,WACA,KACA,WACA,QACA,GAAG,YACwE;CAC3E,MAAM,oBAAoB,qBAAqB;CAE/C,MAAM,YAAY,MAAM,OAA0B,IAAI;CACtD,MAAM,gBAAgB;EACpB,IAAI,UAAU,SACZ,UAAU,SAAS,MAAM;CAE7B,GAAG,CAAC,UAAU,OAAO,CAAC;CAEtB,OACE,oBAAC,QAAD;EACE,KAAK;EACL,SAAQ;EACR,MAAK;EACL,YAAU,IAAI,KAAK,mBAAmB,QAAQ,IAAI;EAClD,wBAAsB,UAAU,YAAY,CAAC,UAAU,eAAe,CAAC,UAAU,aAAa,CAAC,UAAU;EACzG,oBAAkB,UAAU;EAC5B,kBAAgB,UAAU;EAC1B,qBAAmB,UAAU;EAC7B,WAAW,GACT,w7BACA,kBAAkB,KAClB,SACF;EACA,GAAI;CACL,CAAA;AAEL"}
@@ -1,5 +1,6 @@
1
1
  "use client";
2
2
  import { cn } from "../../../lib/utils.js";
3
+ import { useExposedFocusGuards } from "../../../lib/focus-guards.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { IconCheck, IconChevronRight } from "@tabler/icons-react";
5
6
  import { ContextMenu } from "@base-ui/react/context-menu";
@@ -24,6 +25,7 @@ var ContextMenuTrigger = ({ className, ...props }) => {
24
25
  });
25
26
  };
26
27
  var ContextMenuContent = ({ className, align = "start", alignOffset = 4, side = "right", sideOffset = 0, ...props }) => {
28
+ useExposedFocusGuards();
27
29
  return /* @__PURE__ */ jsx(ContextMenu.Portal, { children: /* @__PURE__ */ jsx(ContextMenu.Positioner, {
28
30
  className: "isolate z-50 outline-none",
29
31
  align,
@@ -1 +1 @@
1
- {"version":3,"file":"context-menu.js","names":[],"sources":["../../../../../../../ui/src/components/ui/context-menu/context-menu.tsx"],"sourcesContent":["'use client';\n\nimport { ContextMenu as ContextMenuPrimitive } from '@base-ui/react/context-menu';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconCheck, IconChevronRight } from '@tabler/icons-react';\nimport type * as React from 'react';\n\nexport const ContextMenu = ({ ...props }: ContextMenuPrimitive.Root.Props) => {\n return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />;\n};\n\nexport const ContextMenuPortal = ({ ...props }: ContextMenuPrimitive.Portal.Props) => {\n return <ContextMenuPrimitive.Portal data-slot=\"context-menu-portal\" {...props} />;\n};\n\nexport const ContextMenuTrigger = ({ className, ...props }: ContextMenuPrimitive.Trigger.Props) => {\n return <ContextMenuPrimitive.Trigger data-slot=\"context-menu-trigger\" className={cn('select-none', className)} {...props} />;\n};\n\nexport const ContextMenuContent = ({\n className,\n align = 'start',\n alignOffset = 4,\n side = 'right',\n sideOffset = 0,\n ...props\n}: ContextMenuPrimitive.Popup.Props & Pick<ContextMenuPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'side' | 'sideOffset'>) => {\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.Positioner\n className=\"isolate z-50 outline-none\"\n align={align}\n alignOffset={alignOffset}\n side={side}\n sideOffset={sideOffset}\n >\n <ContextMenuPrimitive.Popup\n data-slot=\"context-menu-content\"\n className={cn(\n 'z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'motion-reduce:animate-none',\n className,\n )}\n {...props}\n />\n </ContextMenuPrimitive.Positioner>\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport const ContextMenuGroup = ({ ...props }: ContextMenuPrimitive.Group.Props) => {\n return <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />;\n};\n\nexport const ContextMenuLabel = ({\n className,\n inset,\n ...props\n}: ContextMenuPrimitive.GroupLabel.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.GroupLabel\n data-slot=\"context-menu-label\"\n data-inset={inset}\n className={cn('px-1.5 py-1 text-xs font-medium text-muted-foreground data-[inset=true]:pl-7', className)}\n {...props}\n />\n );\n};\n\nexport const ContextMenuItem = ({\n className,\n inset,\n variant = 'default',\n ...props\n}: ContextMenuPrimitive.Item.Props & {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}) => {\n return (\n <ContextMenuPrimitive.Item\n data-slot=\"context-menu-item\"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n \"group/context-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:*:[svg]:text-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const ContextMenuSub = ({ ...props }: ContextMenuPrimitive.SubmenuRoot.Props) => {\n return <ContextMenuPrimitive.SubmenuRoot data-slot=\"context-menu-sub\" {...props} />;\n};\n\nexport const ContextMenuSubTrigger = ({\n className,\n inset,\n children,\n ...props\n}: ContextMenuPrimitive.SubmenuTrigger.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.SubmenuTrigger\n data-slot=\"context-menu-sub-trigger\"\n data-inset={inset}\n className={cn(\n \"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n {children}\n <IconChevronRight className=\"ml-auto\" />\n </ContextMenuPrimitive.SubmenuTrigger>\n );\n};\n\nexport const ContextMenuSubContent = ({ ...props }: React.ComponentProps<typeof ContextMenuContent>) => {\n return <ContextMenuContent data-slot=\"context-menu-sub-content\" className=\"shadow-lg\" side=\"right\" {...props} />;\n};\n\nexport const ContextMenuCheckboxItem = ({\n className,\n children,\n checked,\n inset,\n ...props\n}: ContextMenuPrimitive.CheckboxItem.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.CheckboxItem\n data-slot=\"context-menu-checkbox-item\"\n data-inset={inset}\n className={cn(\n \"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute right-2\">\n <ContextMenuPrimitive.CheckboxItemIndicator>\n <IconCheck />\n </ContextMenuPrimitive.CheckboxItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.CheckboxItem>\n );\n};\n\nexport const ContextMenuRadioGroup = ({ ...props }: ContextMenuPrimitive.RadioGroup.Props) => {\n return <ContextMenuPrimitive.RadioGroup data-slot=\"context-menu-radio-group\" {...props} />;\n};\n\nexport const ContextMenuRadioItem = ({\n className,\n children,\n inset,\n ...props\n}: ContextMenuPrimitive.RadioItem.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.RadioItem\n data-slot=\"context-menu-radio-item\"\n data-inset={inset}\n className={cn(\n \"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n <span className=\"pointer-events-none absolute right-2\">\n <ContextMenuPrimitive.RadioItemIndicator>\n <IconCheck />\n </ContextMenuPrimitive.RadioItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.RadioItem>\n );\n};\n\nexport const ContextMenuSeparator = ({ className, ...props }: ContextMenuPrimitive.Separator.Props) => {\n return <ContextMenuPrimitive.Separator data-slot=\"context-menu-separator\" className={cn('-mx-1 my-1 h-px bg-border', className)} {...props} />;\n};\n\nexport const ContextMenuShortcut = ({ className, ...props }: React.ComponentProps<'span'>) => {\n return (\n <span\n data-slot=\"context-menu-shortcut\"\n className={cn('ml-auto text-xs tracking-widest text-muted-foreground group-focus/context-menu-item:text-accent-foreground', className)}\n {...props}\n />\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"context-menu.js","names":[],"sources":["../../../../../../../ui/src/components/ui/context-menu/context-menu.tsx"],"sourcesContent":["'use client';\n\nimport { ContextMenu as ContextMenuPrimitive } from '@base-ui/react/context-menu';\nimport { useExposedFocusGuards } from '@edyn/ui/lib/focus-guards';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconCheck, IconChevronRight } from '@tabler/icons-react';\nimport type * as React from 'react';\n\nexport const ContextMenu = ({ ...props }: ContextMenuPrimitive.Root.Props) => {\n return <ContextMenuPrimitive.Root data-slot=\"context-menu\" {...props} />;\n};\n\nexport const ContextMenuPortal = ({ ...props }: ContextMenuPrimitive.Portal.Props) => {\n return <ContextMenuPrimitive.Portal data-slot=\"context-menu-portal\" {...props} />;\n};\n\nexport const ContextMenuTrigger = ({ className, ...props }: ContextMenuPrimitive.Trigger.Props) => {\n return <ContextMenuPrimitive.Trigger data-slot=\"context-menu-trigger\" className={cn('select-none', className)} {...props} />;\n};\n\nexport const ContextMenuContent = ({\n className,\n align = 'start',\n alignOffset = 4,\n side = 'right',\n sideOffset = 0,\n ...props\n}: ContextMenuPrimitive.Popup.Props & Pick<ContextMenuPrimitive.Positioner.Props, 'align' | 'alignOffset' | 'side' | 'sideOffset'>) => {\n useExposedFocusGuards();\n return (\n <ContextMenuPrimitive.Portal>\n <ContextMenuPrimitive.Positioner\n className=\"isolate z-50 outline-none\"\n align={align}\n alignOffset={alignOffset}\n side={side}\n sideOffset={sideOffset}\n >\n <ContextMenuPrimitive.Popup\n data-slot=\"context-menu-content\"\n className={cn(\n 'z-50 max-h-(--available-height) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover p-1 text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 outline-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n 'motion-reduce:animate-none',\n className,\n )}\n {...props}\n />\n </ContextMenuPrimitive.Positioner>\n </ContextMenuPrimitive.Portal>\n );\n};\n\nexport const ContextMenuGroup = ({ ...props }: ContextMenuPrimitive.Group.Props) => {\n return <ContextMenuPrimitive.Group data-slot=\"context-menu-group\" {...props} />;\n};\n\nexport const ContextMenuLabel = ({\n className,\n inset,\n ...props\n}: ContextMenuPrimitive.GroupLabel.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.GroupLabel\n data-slot=\"context-menu-label\"\n data-inset={inset}\n className={cn('px-1.5 py-1 text-xs font-medium text-muted-foreground data-[inset=true]:pl-7', className)}\n {...props}\n />\n );\n};\n\nexport const ContextMenuItem = ({\n className,\n inset,\n variant = 'default',\n ...props\n}: ContextMenuPrimitive.Item.Props & {\n inset?: boolean;\n variant?: 'default' | 'destructive';\n}) => {\n return (\n <ContextMenuPrimitive.Item\n data-slot=\"context-menu-item\"\n data-inset={inset}\n data-variant={variant}\n className={cn(\n \"group/context-menu-item relative flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 focus:*:[svg]:text-accent-foreground data-[variant=destructive]:*:[svg]:text-destructive\",\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const ContextMenuSub = ({ ...props }: ContextMenuPrimitive.SubmenuRoot.Props) => {\n return <ContextMenuPrimitive.SubmenuRoot data-slot=\"context-menu-sub\" {...props} />;\n};\n\nexport const ContextMenuSubTrigger = ({\n className,\n inset,\n children,\n ...props\n}: ContextMenuPrimitive.SubmenuTrigger.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.SubmenuTrigger\n data-slot=\"context-menu-sub-trigger\"\n data-inset={inset}\n className={cn(\n \"flex cursor-default items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-open:bg-accent data-open:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n {children}\n <IconChevronRight className=\"ml-auto\" />\n </ContextMenuPrimitive.SubmenuTrigger>\n );\n};\n\nexport const ContextMenuSubContent = ({ ...props }: React.ComponentProps<typeof ContextMenuContent>) => {\n return <ContextMenuContent data-slot=\"context-menu-sub-content\" className=\"shadow-lg\" side=\"right\" {...props} />;\n};\n\nexport const ContextMenuCheckboxItem = ({\n className,\n children,\n checked,\n inset,\n ...props\n}: ContextMenuPrimitive.CheckboxItem.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.CheckboxItem\n data-slot=\"context-menu-checkbox-item\"\n data-inset={inset}\n className={cn(\n \"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n checked={checked}\n {...props}\n >\n <span className=\"pointer-events-none absolute right-2\">\n <ContextMenuPrimitive.CheckboxItemIndicator>\n <IconCheck />\n </ContextMenuPrimitive.CheckboxItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.CheckboxItem>\n );\n};\n\nexport const ContextMenuRadioGroup = ({ ...props }: ContextMenuPrimitive.RadioGroup.Props) => {\n return <ContextMenuPrimitive.RadioGroup data-slot=\"context-menu-radio-group\" {...props} />;\n};\n\nexport const ContextMenuRadioItem = ({\n className,\n children,\n inset,\n ...props\n}: ContextMenuPrimitive.RadioItem.Props & {\n inset?: boolean;\n}) => {\n return (\n <ContextMenuPrimitive.RadioItem\n data-slot=\"context-menu-radio-item\"\n data-inset={inset}\n className={cn(\n \"relative flex cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[inset=true]:pl-7 data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n className,\n )}\n {...props}\n >\n <span className=\"pointer-events-none absolute right-2\">\n <ContextMenuPrimitive.RadioItemIndicator>\n <IconCheck />\n </ContextMenuPrimitive.RadioItemIndicator>\n </span>\n {children}\n </ContextMenuPrimitive.RadioItem>\n );\n};\n\nexport const ContextMenuSeparator = ({ className, ...props }: ContextMenuPrimitive.Separator.Props) => {\n return <ContextMenuPrimitive.Separator data-slot=\"context-menu-separator\" className={cn('-mx-1 my-1 h-px bg-border', className)} {...props} />;\n};\n\nexport const ContextMenuShortcut = ({ className, ...props }: React.ComponentProps<'span'>) => {\n return (\n <span\n data-slot=\"context-menu-shortcut\"\n className={cn('ml-auto text-xs tracking-widest text-muted-foreground group-focus/context-menu-item:text-accent-foreground', className)}\n {...props}\n />\n );\n};\n"],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
2
  import { Button } from "../button/button.js";
3
+ import { useExposedFocusGuards } from "../../../lib/focus-guards.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { IconX } from "@tabler/icons-react";
5
6
  import { Dialog } from "@base-ui/react/dialog";
@@ -36,6 +37,7 @@ var DialogOverlay = ({ className, ...props }) => {
36
37
  });
37
38
  };
38
39
  var DialogContent = ({ className, children, showCloseButton = true, ...props }) => {
40
+ useExposedFocusGuards();
39
41
  return /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(Dialog.Popup, {
40
42
  "data-slot": "dialog-content",
41
43
  className: cn("fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 motion-reduce:animate-none outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className),
@@ -1 +1 @@
1
- {"version":3,"file":"dialog.js","names":[],"sources":["../../../../../../../ui/src/components/ui/dialog/dialog.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconX } from '@tabler/icons-react';\nimport type * as React from 'react';\nimport { Button } from '../button';\n\nexport const Dialog = ({ ...props }: DialogPrimitive.Root.Props) => {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n};\n\nexport const DialogTrigger = ({ ...props }: DialogPrimitive.Trigger.Props) => {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n};\n\nexport const DialogPortal = ({ ...props }: DialogPrimitive.Portal.Props) => {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n};\n\nexport const DialogClose = ({ ...props }: DialogPrimitive.Close.Props) => {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n};\n\nexport const DialogOverlay = ({ className, ...props }: DialogPrimitive.Backdrop.Props) => {\n return (\n <DialogPrimitive.Backdrop\n data-slot=\"dialog-overlay\"\n className={cn(\n 'fixed inset-0 isolate z-50 bg-overlay duration-100 motion-reduce:animate-none supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogContent = ({\n className,\n children,\n showCloseButton = true,\n ...props\n}: DialogPrimitive.Popup.Props & {\n showCloseButton?: boolean;\n}) => {\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Popup\n data-slot=\"dialog-content\"\n className={cn(\n 'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 motion-reduce:animate-none outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close data-slot=\"dialog-close\" render={<Button variant=\"ghost\" className=\"absolute top-2 right-2\" size=\"icon-sm\" />}>\n <IconX />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n};\n\nexport const DialogHeader = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return <div data-slot=\"dialog-header\" className={cn('flex flex-col gap-2', className)} {...props} />;\n};\n\nexport const DialogFooter = ({\n className,\n showCloseButton = false,\n children,\n ...props\n}: React.ComponentProps<'div'> & {\n showCloseButton?: boolean;\n}) => {\n return (\n <div data-slot=\"dialog-footer\" className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)} {...props}>\n {children}\n {showCloseButton && <DialogPrimitive.Close render={<Button variant=\"outline\" />}>Close</DialogPrimitive.Close>}\n </div>\n );\n};\n\nexport const DialogTitle = ({ className, ...props }: DialogPrimitive.Title.Props) => {\n return <DialogPrimitive.Title data-slot=\"dialog-title\" className={cn('text-base leading-none font-medium', className)} {...props} />;\n};\n\nexport const DialogDescription = ({ className, ...props }: DialogPrimitive.Description.Props) => {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn('text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground', className)}\n {...props}\n />\n );\n};\n"],"mappings":";;;;;;AAMA,IAAa,YAAU,EAAE,GAAG,YAAwC;CAClE,OAAO,oBAAC,OAAgB,MAAjB;EAAsB,aAAU;EAAS,GAAI;CAAQ,CAAA;AAC9D;AAEA,IAAa,iBAAiB,EAAE,GAAG,YAA2C;CAC5E,OAAO,oBAAC,OAAgB,SAAjB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,IAAa,gBAAgB,EAAE,GAAG,YAA0C;CAC1E,OAAO,oBAAC,OAAgB,QAAjB;EAAwB,aAAU;EAAgB,GAAI;CAAQ,CAAA;AACvE;AAEA,IAAa,eAAe,EAAE,GAAG,YAAyC;CACxE,OAAO,oBAAC,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,IAAa,iBAAiB,EAAE,WAAW,GAAG,YAA4C;CACxF,OACE,oBAAC,OAAgB,UAAjB;EACE,aAAU;EACV,WAAW,GACT,mNACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,iBAAiB,EAC5B,WACA,UACA,kBAAkB,MAClB,GAAG,YAGC;CACJ,OACE,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAChB,qBAAC,OAAgB,OAAjB;EACE,aAAU;EACV,WAAW,GACT,qYACA,SACF;EACA,GAAI;EANN,UAAA,CAQG,UACA,mBACC,qBAAC,OAAgB,OAAjB;GAAuB,aAAU;GAAe,QAAQ,oBAAC,QAAD;IAAQ,SAAQ;IAAQ,WAAU;IAAyB,MAAK;GAAW,CAAA;GAAnI,UAAA,CACE,oBAAC,OAAD,CAAQ,CAAA,GACR,oBAAC,QAAD;IAAM,WAAU;IAAU,UAAA;GAAW,CAAA,CAChB;EAEJ,CAAA,CAAA;CACX,CAAA,CAAA,EAAA,CAAA;AAElB;AAEA,IAAa,gBAAgB,EAAE,WAAW,GAAG,YAAyC;CACpF,OAAO,oBAAC,OAAD;EAAK,aAAU;EAAgB,WAAW,GAAG,uBAAuB,SAAS;EAAG,GAAI;CAAQ,CAAA;AACrG;AAEA,IAAa,gBAAgB,EAC3B,WACA,kBAAkB,OAClB,UACA,GAAG,YAGC;CACJ,OACE,qBAAC,OAAD;EAAK,aAAU;EAAgB,WAAW,GAAG,0DAA0D,SAAS;EAAG,GAAI;EAAvH,UAAA,CACG,UACA,mBAAmB,oBAAC,OAAgB,OAAjB;GAAuB,QAAQ,oBAAC,QAAD,EAAQ,SAAQ,UAAW,CAAA;GAAG,UAAA;EAA4B,CAAA,CAC1G;;AAET;AAEA,IAAa,eAAe,EAAE,WAAW,GAAG,YAAyC;CACnF,OAAO,oBAAC,OAAgB,OAAjB;EAAuB,aAAU;EAAe,WAAW,GAAG,sCAAsC,SAAS;EAAG,GAAI;CAAQ,CAAA;AACrI;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAA+C;CAC/F,OACE,oBAAC,OAAgB,aAAjB;EACE,aAAU;EACV,WAAW,GAAG,sGAAsG,SAAS;EAC7H,GAAI;CACL,CAAA;AAEL"}
1
+ {"version":3,"file":"dialog.js","names":[],"sources":["../../../../../../../ui/src/components/ui/dialog/dialog.tsx"],"sourcesContent":["import { Dialog as DialogPrimitive } from '@base-ui/react/dialog';\nimport { useExposedFocusGuards } from '@edyn/ui/lib/focus-guards';\nimport { cn } from '@edyn/ui/lib/utils';\nimport { IconX } from '@tabler/icons-react';\nimport type * as React from 'react';\nimport { Button } from '../button';\n\nexport const Dialog = ({ ...props }: DialogPrimitive.Root.Props) => {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n};\n\nexport const DialogTrigger = ({ ...props }: DialogPrimitive.Trigger.Props) => {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n};\n\nexport const DialogPortal = ({ ...props }: DialogPrimitive.Portal.Props) => {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n};\n\nexport const DialogClose = ({ ...props }: DialogPrimitive.Close.Props) => {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n};\n\nexport const DialogOverlay = ({ className, ...props }: DialogPrimitive.Backdrop.Props) => {\n return (\n <DialogPrimitive.Backdrop\n data-slot=\"dialog-overlay\"\n className={cn(\n 'fixed inset-0 isolate z-50 bg-overlay duration-100 motion-reduce:animate-none supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0',\n className,\n )}\n {...props}\n />\n );\n};\n\nexport const DialogContent = ({\n className,\n children,\n showCloseButton = true,\n ...props\n}: DialogPrimitive.Popup.Props & {\n showCloseButton?: boolean;\n}) => {\n useExposedFocusGuards();\n return (\n <DialogPortal>\n <DialogOverlay />\n <DialogPrimitive.Popup\n data-slot=\"dialog-content\"\n className={cn(\n 'fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-xl bg-popover p-4 text-sm text-popover-foreground ring-1 ring-foreground/10 duration-100 motion-reduce:animate-none outline-none sm:max-w-sm data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95',\n className,\n )}\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close data-slot=\"dialog-close\" render={<Button variant=\"ghost\" className=\"absolute top-2 right-2\" size=\"icon-sm\" />}>\n <IconX />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Popup>\n </DialogPortal>\n );\n};\n\nexport const DialogHeader = ({ className, ...props }: React.ComponentProps<'div'>) => {\n return <div data-slot=\"dialog-header\" className={cn('flex flex-col gap-2', className)} {...props} />;\n};\n\nexport const DialogFooter = ({\n className,\n showCloseButton = false,\n children,\n ...props\n}: React.ComponentProps<'div'> & {\n showCloseButton?: boolean;\n}) => {\n return (\n <div data-slot=\"dialog-footer\" className={cn('flex flex-col-reverse gap-2 sm:flex-row sm:justify-end', className)} {...props}>\n {children}\n {showCloseButton && <DialogPrimitive.Close render={<Button variant=\"outline\" />}>Close</DialogPrimitive.Close>}\n </div>\n );\n};\n\nexport const DialogTitle = ({ className, ...props }: DialogPrimitive.Title.Props) => {\n return <DialogPrimitive.Title data-slot=\"dialog-title\" className={cn('text-base leading-none font-medium', className)} {...props} />;\n};\n\nexport const DialogDescription = ({ className, ...props }: DialogPrimitive.Description.Props) => {\n return (\n <DialogPrimitive.Description\n data-slot=\"dialog-description\"\n className={cn('text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground', className)}\n {...props}\n />\n );\n};\n"],"mappings":";;;;;;;AAOA,IAAa,YAAU,EAAE,GAAG,YAAwC;CAClE,OAAO,oBAAC,OAAgB,MAAjB;EAAsB,aAAU;EAAS,GAAI;CAAQ,CAAA;AAC9D;AAEA,IAAa,iBAAiB,EAAE,GAAG,YAA2C;CAC5E,OAAO,oBAAC,OAAgB,SAAjB;EAAyB,aAAU;EAAiB,GAAI;CAAQ,CAAA;AACzE;AAEA,IAAa,gBAAgB,EAAE,GAAG,YAA0C;CAC1E,OAAO,oBAAC,OAAgB,QAAjB;EAAwB,aAAU;EAAgB,GAAI;CAAQ,CAAA;AACvE;AAEA,IAAa,eAAe,EAAE,GAAG,YAAyC;CACxE,OAAO,oBAAC,OAAgB,OAAjB;EAAuB,aAAU;EAAe,GAAI;CAAQ,CAAA;AACrE;AAEA,IAAa,iBAAiB,EAAE,WAAW,GAAG,YAA4C;CACxF,OACE,oBAAC,OAAgB,UAAjB;EACE,aAAU;EACV,WAAW,GACT,mNACA,SACF;EACA,GAAI;CACL,CAAA;AAEL;AAEA,IAAa,iBAAiB,EAC5B,WACA,UACA,kBAAkB,MAClB,GAAG,YAGC;CACJ,sBAAsB;CACtB,OACE,qBAAC,cAAD,EAAA,UAAA,CACE,oBAAC,eAAD,CAAgB,CAAA,GAChB,qBAAC,OAAgB,OAAjB;EACE,aAAU;EACV,WAAW,GACT,qYACA,SACF;EACA,GAAI;EANN,UAAA,CAQG,UACA,mBACC,qBAAC,OAAgB,OAAjB;GAAuB,aAAU;GAAe,QAAQ,oBAAC,QAAD;IAAQ,SAAQ;IAAQ,WAAU;IAAyB,MAAK;GAAW,CAAA;GAAnI,UAAA,CACE,oBAAC,OAAD,CAAQ,CAAA,GACR,oBAAC,QAAD;IAAM,WAAU;IAAU,UAAA;GAAW,CAAA,CAChB;EAEJ,CAAA,CAAA;CACX,CAAA,CAAA,EAAA,CAAA;AAElB;AAEA,IAAa,gBAAgB,EAAE,WAAW,GAAG,YAAyC;CACpF,OAAO,oBAAC,OAAD;EAAK,aAAU;EAAgB,WAAW,GAAG,uBAAuB,SAAS;EAAG,GAAI;CAAQ,CAAA;AACrG;AAEA,IAAa,gBAAgB,EAC3B,WACA,kBAAkB,OAClB,UACA,GAAG,YAGC;CACJ,OACE,qBAAC,OAAD;EAAK,aAAU;EAAgB,WAAW,GAAG,0DAA0D,SAAS;EAAG,GAAI;EAAvH,UAAA,CACG,UACA,mBAAmB,oBAAC,OAAgB,OAAjB;GAAuB,QAAQ,oBAAC,QAAD,EAAQ,SAAQ,UAAW,CAAA;GAAG,UAAA;EAA4B,CAAA,CAC1G;;AAET;AAEA,IAAa,eAAe,EAAE,WAAW,GAAG,YAAyC;CACnF,OAAO,oBAAC,OAAgB,OAAjB;EAAuB,aAAU;EAAe,WAAW,GAAG,sCAAsC,SAAS;EAAG,GAAI;CAAQ,CAAA;AACrI;AAEA,IAAa,qBAAqB,EAAE,WAAW,GAAG,YAA+C;CAC/F,OACE,oBAAC,OAAgB,aAAjB;EACE,aAAU;EACV,WAAW,GAAG,sGAAsG,SAAS;EAC7H,GAAI;CACL,CAAA;AAEL"}
@@ -1,5 +1,6 @@
1
1
  import { cn } from "../../../lib/utils.js";
2
2
  import { Button } from "../button/button.js";
3
+ import { useExposedFocusGuards } from "../../../lib/focus-guards.js";
3
4
  import { jsx, jsxs } from "react/jsx-runtime";
4
5
  import { IconX } from "@tabler/icons-react";
5
6
  import { Dialog } from "@base-ui/react/dialog";
@@ -30,6 +31,7 @@ var DrawerOverlay = ({ className, ...props }) => {
30
31
  });
31
32
  };
32
33
  var DrawerContent = ({ className, children, side = "right", showCloseButton = true, ...props }) => {
34
+ useExposedFocusGuards();
33
35
  return /* @__PURE__ */ jsxs(Dialog.Portal, {
34
36
  "data-slot": "drawer-portal",
35
37
  children: [/* @__PURE__ */ jsx(DrawerOverlay, {}), /* @__PURE__ */ jsxs(Dialog.Popup, {