@motir/design-system 0.15.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/theme/StyleVignette.js +3 -3
- package/dist/components/theme/StyleVignette.js.map +1 -1
- package/dist/components/ui/Combobox.js +2 -2
- package/dist/components/ui/Combobox.js.map +1 -1
- package/dist/components/ui/MultiSelectPicker.js +1 -1
- package/dist/components/ui/MultiSelectPicker.js.map +1 -1
- package/dist/contexts/theme-context.js +25 -1
- package/dist/contexts/theme-context.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/specimen/TokensSpecimen.js +3 -3
- package/dist/specimen/TokensSpecimen.js.map +1 -1
- package/dist/theme/fontSets.d.ts +38 -5
- package/dist/theme/fontSets.js +33 -3
- package/dist/theme/fontSets.js.map +1 -1
- package/package.json +1 -1
- package/theme.css +93 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/utils/fullscreen.ts","../../../src/components/ui/Combobox.tsx"],"names":["useSyncExternalStore","useState","rect","gap","inset","spaceBelow","spaceAbove","placeAbove","avail","menu","Fragment"],"mappings":"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","file":"Combobox.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { useState, useSyncExternalStore, type RefObject } from 'react';\n\n/**\n * The element currently in NATIVE full screen (`document.fullscreenElement`), or\n * `null` — re-read on every `fullscreenchange`.\n *\n * While an element is in native full screen the browser paints ONLY that\n * element's subtree, in the top layer. Anything portalled to `document.body` —\n * a dialog, a popover, a picker's menu — is not shown at all, whatever its\n * z-index (MOTIR-7658: the roadmap canvas's quick view opened invisibly and\n * held focus and scroll lock until Esc).\n *\n * A portalling primitive does not use this directly: it uses\n * `usePortalContainer`, which also refuses to follow full screen into the\n * primitive's own subtree.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function useFullscreenElement(): Element | null {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n}\n\nfunction getSnapshot(): Element | null {\n return document.fullscreenElement ?? null;\n}\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\n/**\n * The element a portalling surface mounts into: the element in native full\n * screen while there is one (MOTIR-7658), else `null` — which the caller turns\n * into `document.body`.\n *\n * `own` is the surface's portalled root (a dialog's content, a menu). When the\n * element that goes full screen is INSIDE it — the native full-screen button of\n * a `<video>` in a dialog — the container stays where it was (MOTIR-8560).\n * Following it would re-portal the surface into its own descendant: React\n * unmounts the subtree to move it, the full-screen element leaves the document,\n * the browser exits full screen, and the surface snaps back to a fresh copy.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function usePortalContainer(own: RefObject<Element | null>): Element | null {\n const [store] = useState(() => createPortalContainerStore(own));\n return useSyncExternalStore(store.subscribe, store.getSnapshot, getServerSnapshot);\n}\n\nfunction createPortalContainerStore(own: RefObject<Element | null>) {\n let container: Element | null =\n typeof document === 'undefined' ? null : (document.fullscreenElement ?? null);\n return {\n subscribe(onChange: () => void): () => void {\n const sync = () => {\n const next = document.fullscreenElement ?? null;\n const root = own.current;\n // The surface's own descendant went full screen: stay put.\n if (next !== null && root !== null && root.contains(next)) return;\n if (next === container) return;\n container = next;\n onChange();\n };\n // A change between creating the store and subscribing to it.\n sync();\n document.addEventListener('fullscreenchange', sync);\n return () => document.removeEventListener('fullscreenchange', sync);\n },\n getSnapshot: (): Element | null => container,\n };\n}\n","'use client';\n\nimport {\n Fragment,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Check, ChevronsUpDown, LoaderCircle } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { usePortalContainer } from '../../utils/fullscreen';\n\n// Run layout effects on the client, fall back to useEffect during SSR (the menu\n// only mounts client-side anyway — see the `mounted` gate below).\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n// `false` during SSR / first hydration render, `true` on the client thereafter —\n// gates the createPortal call so document.body is guaranteed present. Hydration-\n// safe (matches the server's `false`) and avoids setState-in-effect.\nconst subscribeNoop = () => () => {};\n\n/**\n * The LEADING-VISUAL slot, on the trigger and on every option row (MOTIR-3080).\n *\n * ⚠️ `inline-flex` is load-bearing, not decoration. The wrapper is a flex item of\n * its row, so IT is blockified — but whatever a caller passes INSIDE it is not,\n * and a plain `<span>` therefore stays `display: inline`, where width and height\n * do not apply at all. `StatusPicker`'s 10px status dot rendered as a 2 × 18\n * sliver for exactly that reason: three cards of palette work (MOTIR-1273 /\n * -2073 / -2075) measured that dot's HUE while its box was two pixels wide.\n *\n * It survived every check because the markup was right — the classes ask for\n * 10 × 10, `getComputedStyle` reports 10px, and only the USED value disagreed.\n * A used value exists only in a browser, so `tests/e2e/issue-detail-flow` MEASURES\n * it rather than asserting the element exists.\n *\n * Making the slot a flex CONTAINER blockifies its child, which is what the slot's\n * contract (\"a leading visual\") always implied. A lucide `<svg>` is replaced and\n * was never affected, so no existing caller's icon changes size.\n */\nconst ICON_SLOT = 'inline-flex shrink-0 items-center';\n\nfunction useMounted() {\n return useSyncExternalStore(\n subscribeNoop,\n () => true,\n () => false,\n );\n}\n\n/**\n * Combobox — an accessible select/combobox primitive (Subtask 2.3.4). A trigger\n * button (`role=\"combobox\"`, `aria-haspopup=\"listbox\"`) opens an anchored panel\n * holding an optional type-ahead filter and a `role=\"listbox\"` of\n * `role=\"option\"` rows. The active option is tracked with `aria-activedescendant`\n * on whichever control holds focus (the filter input when `searchable`, else the\n * listbox) — the CommandPalette pattern that already clears the STRICT axe sweep.\n *\n * Deliberately NOT built on the Radix Popover primitive: that injects\n * `aria-haspopup=\"dialog\"` + dialog focus semantics onto its trigger, which\n * conflicts with the listbox combobox pattern. This is a self-contained anchored\n * dropdown (click-outside + Escape + focus return handled here) so the ARIA is\n * exactly the WAI-ARIA combobox shape.\n *\n * Composed by `components/issues/TypePicker` (searchable=false, 5 options) and\n * `ParentPicker` (searchable, async candidate list).\n */\nexport interface ComboboxOption<T extends string> {\n value: T;\n /** Primary text — also the accessible name of the option. */\n label: string;\n /** Extra text matched by the filter (e.g. a PROD-N identifier). */\n keywords?: string;\n /** Leading visual (a kind icon); decorative — label carries the name. */\n icon?: ReactNode;\n /** Trailing muted text (e.g. the identifier). */\n secondary?: string;\n /**\n * A one-line description drawn UNDER the label in the menu row only — never in\n * the trigger, which stays the label alone (the workspace role picker's\n * \"what this role does\" line, MOTIR-6465). Decorative for the accessible name,\n * which stays `label`.\n */\n description?: string;\n /** Trailing rich content pinned to the option row's far end (e.g. a state\n * Pill or a \"Linked to …\" chip in the PR-link picker, MOTIR-1596).\n * Decorative — the accessible name is `label`; renders after `secondary`. */\n trailing?: ReactNode;\n /**\n * Optional section label. When consecutive options carry different `group`\n * values, a non-interactive header row is rendered at each transition (the\n * advanced filter's \"Fields\" / \"Custom fields\" / \"Other\" field menu). Pass\n * options pre-sorted by group; headers follow list order, so a group whose\n * every option is filtered out disappears with it. Decorative — not part of\n * the listbox's option indices, so keyboard nav is unaffected.\n */\n group?: string;\n /**\n * The option is shown but cannot be picked (MOTIR-5528 — a status move an\n * approval holds). It stays FOCUSABLE and is announced as unavailable\n * (`aria-disabled`), rather than skipped, so a keyboard reader learns the move\n * exists and why it is held; a click or Enter on it does nothing. Its label\n * reads in `--el-text-secondary`, never an opacity dim, which keeps AA.\n */\n disabled?: boolean;\n /**\n * The language the LABEL is written in (a BCP 47 code), rendered as the `lang`\n * attribute on this option's row and, when it is selected, on the trigger's\n * label (MOTIR-7758 — a language picker names each language in its own script,\n * so a reader announces 日本語 in Japanese inside an English page). Omit it and\n * the label inherits the page's language, as before.\n */\n lang?: string;\n}\n\nexport interface ComboboxProps<T extends string> {\n options: ComboboxOption<T>[];\n value: T | null;\n onChange: (value: T) => void;\n /** Accessible name for the trigger + listbox. */\n label: string;\n /** Trigger text when nothing is selected. */\n placeholder?: string;\n searchable?: boolean;\n searchPlaceholder?: string;\n /** Empty-listbox content. A string, or rich nodes (e.g. a \"no matches\" line\n * plus a hint sub-line — use block `<span>`s, since this renders in a `<p>`). */\n emptyText?: ReactNode;\n loading?: boolean;\n loadingText?: string;\n disabled?: boolean;\n /** id for the trigger button. */\n id?: string;\n className?: string;\n /**\n * Mount already-open (Subtask 2.5.5) — for an inline-edit cell that renders the\n * picker on a single click, so the menu is open immediately (mirrors\n * `DatePicker`'s `autoOpen`). Focus lands on the search input / listbox.\n */\n autoOpen?: boolean;\n /**\n * Fired whenever the menu transitions open → closed (a pick, Escape,\n * click-outside, or toggling the trigger). The inline-edit cell uses it to\n * leave edit mode and return to the static pill/avatar (Subtask 2.5.5).\n */\n onClose?: () => void;\n /**\n * Server-driven search mode (Subtask 6.9.2 — the link picker). When\n * `onQueryChange` is provided the type-ahead query is CONTROLLED by the parent\n * (`query`) and client-side filtering is SKIPPED — `options` is taken as the\n * server's authoritative result for the current query (the parent debounces\n * its fetch off `onQueryChange`, the same contract as `MultiSelectPicker`).\n * Omit both for the default self-filtering behaviour. The parent owns the\n * query lifecycle, so opening the menu does NOT reset it.\n */\n query?: string;\n onQueryChange?: (query: string) => void;\n /**\n * A non-interactive note pinned BELOW the listbox, inside the open menu\n * (MOTIR-5582 — the Monitoring room's minimum-level control says, at the\n * action that causes it, that lowering also re-checks earlier issues). It is\n * not an option: keyboard navigation and the listbox's option indices are\n * unaffected, and it is announced as ordinary text. Omit it for no note.\n */\n footer?: ReactNode;\n /**\n * A leading glyph drawn on the TRIGGER only, before the selected label (or the\n * placeholder), and never in the option rows — unlike an option's `icon`,\n * which is drawn in both (MOTIR-7758: the signed-out language control's\n * `Languages` glyph). Decorative: it is rendered `aria-hidden`, and the\n * trigger's accessible name stays `label`.\n */\n triggerIcon?: ReactNode;\n /**\n * The value the trigger shows is being applied (MOTIR-7758). The trigger is\n * marked `aria-busy` and a spinner takes the chevron's 16px slot, so the\n * trigger keeps its size. It does NOT disable the control — a caller that\n * wants that passes `disabled` as well. Pair it with a polite status that says\n * what is happening; this prop draws nothing a reader hears.\n */\n busy?: boolean;\n /**\n * Which edge of the trigger the menu lines up with (MOTIR-7758). `start`\n * (the default) aligns the menu's left edge with the trigger's, as before;\n * `end` aligns the RIGHT edges, so a menu wider than a trigger that sits in a\n * right-hand corner grows leftwards instead of running past the viewport.\n */\n align?: 'start' | 'end';\n}\n\n// Walk up from a node to the nearest ancestor that clips overflow, classifying\n// it as scrolling (overflow auto/scroll — the menu can be scrolled into view)\n// or a hard `hidden`/`clip` box (no scroll — the menu is trapped). Used by the\n// inline (in-dialog) branch to decide whether to clamp + flip the menu (only a\n// hard non-scrolling clip needs it). Stops at <body>; returns null if nothing\n// up the chain clips.\nfunction nearestClipBox(el: HTMLElement): { box: HTMLElement; scrolls: boolean } | null {\n let node = el.parentElement;\n while (node && node !== document.body) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'auto' || oy === 'scroll') return { box: node, scrolls: true };\n if (oy === 'hidden' || oy === 'clip') return { box: node, scrolls: false };\n node = node.parentElement;\n }\n return null;\n}\n\n// The element a `position: fixed` descendant of `el` is laid out against, or\n// null when that is the viewport. A transform (the centered Modal panel's\n// `-translate-x/y-1/2`, which Tailwind v4 emits as the `translate` property), a\n// filter, a backdrop-filter (the glassmorphism material), `contain` or a\n// matching `will-change` all make an ancestor the containing block for fixed\n// descendants — so a fixed child of the Modal panel is positioned relative to\n// the PANEL, not the viewport.\nfunction fixedContainingBlock(el: HTMLElement): HTMLElement | null {\n let node = el.parentElement;\n while (node && node !== document.documentElement) {\n const s = getComputedStyle(node);\n const set = (v: string | undefined) => !!v && v !== 'none';\n if (\n set(s.transform) ||\n set(s.translate) ||\n set(s.scale) ||\n set(s.rotate) ||\n set(s.perspective) ||\n set(s.filter) ||\n set(s.backdropFilter) ||\n /\\b(paint|layout|strict|content)\\b/.test(s.contain || '') ||\n /\\b(transform|translate|scale|rotate|perspective|filter)\\b/.test(s.willChange || '')\n ) {\n return node;\n }\n node = node.parentElement;\n }\n return null;\n}\n\n// An element's padding box in viewport coordinates — the edge its overflow\n// clips at (and, for a containing block, the box fixed offsets are measured\n// from).\nfunction paddingBox(el: HTMLElement): {\n top: number;\n left: number;\n bottom: number;\n right: number;\n} {\n const r = el.getBoundingClientRect();\n const top = r.top + el.clientTop;\n const left = r.left + el.clientLeft;\n // happy-dom reports clientHeight/Width 0; fall back to the border box there.\n const height = el.clientHeight || r.height;\n const width = el.clientWidth || r.width;\n return { top, left, bottom: top + height, right: left + width };\n}\n\n// The right edge of the viewport a `position: fixed` box is laid out against —\n// the layout viewport, which excludes a vertical scrollbar (`innerWidth` does not).\nfunction viewportRight(): number {\n return document.documentElement.clientWidth || window.innerWidth;\n}\n\nexport function Combobox<T extends string>({\n options,\n value,\n onChange,\n label,\n placeholder = 'Select…',\n searchable = false,\n searchPlaceholder = 'Search…',\n emptyText = 'No matches',\n loading = false,\n loadingText = 'Loading…',\n disabled = false,\n id,\n className,\n autoOpen = false,\n onClose,\n query: controlledQuery,\n onQueryChange,\n footer,\n triggerIcon,\n busy = false,\n align = 'start',\n}: ComboboxProps<T>) {\n // A DISABLED combobox never opens — not even when it mounts with `autoOpen`\n // (MOTIR-6173). An inline editor that mounts open for an actor who may not\n // write used to hand them a live menu whose pick the server then refused\n // (MOTIR-4822); the trigger was disabled, the menu was not.\n const [open, setOpen] = useState(autoOpen && !disabled);\n // Server-driven mode (6.9.2): when `onQueryChange` is provided the query is\n // controlled by the parent and client filtering is bypassed (the server's\n // `options` are authoritative). Otherwise the query is internal state.\n const serverFiltered = onQueryChange !== undefined;\n const [internalQuery, setInternalQuery] = useState('');\n const query = serverFiltered ? (controlledQuery ?? '') : internalQuery;\n const setQuery = (q: string) => (serverFiltered ? onQueryChange(q) : setInternalQuery(q));\n const [activeIndex, setActiveIndex] = useState(0);\n // The menu is portaled to <body> by default, so it escapes any clipping\n // ancestor — a short table's overflow:hidden\n // (bug-inline-edit-clipped-when-table-short) OR the Advanced-filter popover's\n // overflow-hidden + overflow-y-auto body (MOTIR-1346). We render INLINE only\n // inside a focus-trapping MODAL (the shared Modal primitive — `data-surface=\n // \"modal\"`; `aria-modal` covers any other), where a portaled menu would land\n // outside the dialog's focus scope (focus-trap war → a click on it hangs /\n // dismisses) AND the modal centers with a CSS transform that breaks a fixed-\n // positioned child's coords. A NON-modal Radix Popover (the filter builder,\n // promote-sprint, mark-duplicate — `data-surface=\"popover\"`) carries\n // `role=\"dialog\"` for a11y but does NOT trap focus, so it portals safely —\n // keyed on the modal surface, NOT `role`, so those popovers escape the clip\n // too. Only render the portal once mounted, since createPortal needs document.body.\n const mounted = useMounted();\n // Viewport-anchored position for the portaled menu + the listbox's available\n // height, recomputed from the trigger rect on open / scroll / resize.\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n const [listMaxHeight, setListMaxHeight] = useState(256);\n // Inline (in-dialog) branch only: whether the menu is flipped ABOVE the\n // trigger because there's more room there than below within the dialog's\n // overflow-hidden clip box (bug-combobox-menu-clipped-inside-modal).\n const [inlineAbove, setInlineAbove] = useState(false);\n // Inline (in-dialog) branch, SCROLLING clip only (a `Modal.Body`): the fixed\n // offsets that lift the menu out of the scroll box while keeping it in the\n // DOM — and so inside the dialog's focus scope (MOTIR-7655). Null otherwise.\n const [inlineFixed, setInlineFixed] = useState<CSSProperties | null>(null);\n const baseId = useId();\n const listId = `${baseId}-listbox`;\n const optionId = (i: number) => `${baseId}-opt-${i}`;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n // The portal target: the element in native full screen while there is one,\n // else <body> — the browser paints nothing outside a full-screen element\n // (MOTIR-7658) — unless that element is inside the menu (MOTIR-8560).\n const fullscreenContainer = usePortalContainer(menuRef);\n\n const filtered = useMemo(() => {\n // Server-driven mode: `options` is already the query's result — never filter\n // it again client-side (a substring re-filter would hide trigram / token\n // matches the server legitimately returned).\n if (serverFiltered || !searchable || query.trim() === '') return options;\n const needle = query.trim().toLowerCase();\n return options.filter(\n (o) =>\n o.label.toLowerCase().includes(needle) ||\n (o.secondary ? o.secondary.toLowerCase().includes(needle) : false) ||\n (o.keywords ? o.keywords.toLowerCase().includes(needle) : false),\n );\n }, [options, query, searchable, serverFiltered]);\n\n const selected = value != null ? (options.find((o) => o.value === value) ?? null) : null;\n\n // The active row, clamped to the (possibly just-filtered) list — DERIVED, not\n // an effect, so a shrinking filter never needs a setState-in-effect.\n const active = filtered.length > 0 ? Math.min(activeIndex, filtered.length - 1) : 0;\n\n // Anchor the portaled menu to the trigger in VIEWPORT coordinates (position:\n // fixed), flipping above the trigger when there's more room there, and capping\n // the listbox height to the available space so it never runs off-screen.\n const updatePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n const rect = trigger.getBoundingClientRect();\n const gap = 4; // matches the old mt-1\n const viewportH = window.innerHeight;\n const spaceBelow = viewportH - rect.bottom - gap;\n const spaceAbove = rect.top - gap;\n const placeBelow = spaceBelow >= spaceAbove;\n const avail = Math.max(120, placeBelow ? spaceBelow : spaceAbove);\n // Reserve room for the optional search input + container padding.\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n const style: CSSProperties = {\n position: 'fixed',\n minWidth: Math.round(rect.width),\n };\n // `end` pins the menu's right edge to the trigger's, so it grows leftwards.\n if (align === 'end') style.right = Math.round(viewportRight() - rect.right);\n else style.left = Math.round(rect.left);\n if (placeBelow) style.top = Math.round(rect.bottom + gap);\n else style.bottom = Math.round(viewportH - rect.top + gap);\n setMenuStyle(style);\n }, [searchable, align]);\n\n // The in-dialog branch renders INLINE (an absolute child of the trigger),\n // so — unlike the body-portaled branch — it cannot escape the modal's\n // `overflow-hidden` panel: an absolute child contributes nothing to the\n // dialog body's scroll height, so a menu taller than the room below the\n // trigger is simply CLIPPED, not scrolled-to (bug-combobox-menu-clipped-\n // inside-modal). Mirror the portaled branch's logic against the DIALOG's\n // clip box instead of the viewport: measure the space above/below the\n // trigger inside the dialog, flip to whichever side is taller, and clamp the\n // listbox height to it so the menu scrolls INTERNALLY and always fits.\n const updateInlinePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n // Find the nearest ancestor that CLIPS the inline menu. A NON-scrolling\n // `overflow: hidden` clip — the centered Modal panel's `overflow-hidden\n // max-h-[90vh]` box — traps the menu, so clamp + flip against THAT box\n // (bug-combobox-menu-clipped-inside-modal). A SCROLLING clip lifts the menu\n // out with fixed positioning instead (MOTIR-7655, below), bounded above by\n // the scroll box's visible top so it never lands under the panel's header\n // (the regression the first clamp-against-the-scroll-box cut caused).\n const clip = nearestClipBox(trigger);\n if (!clip) {\n setInlineFixed(null);\n setInlineAbove(false);\n setListMaxHeight(256); // == the max-h-64 fallback (original behaviour)\n return;\n }\n if (clip.scrolls) {\n // A SCROLLING clip (`Modal.Body`, a popover's scroll body) does trap an\n // absolute menu after all: the menu extends the box's scroll area, the\n // part past its visible edge is cut, and the box grows a second\n // scrollbar — on a short dialog body that leaves one option visible\n // (MOTIR-7655). Lift the menu out instead: `position: fixed`, still in\n // the DOM. A fixed box is clipped only by the ancestors on its\n // containing-block chain, so the scroll box stops clipping it while the\n // panel (the containing block, through its transform) still bounds it.\n const rect = trigger.getBoundingClientRect();\n const gap = 4; // matches mt-1 / mb-1\n const inset = 8; // stay a hair inside the panel's rounded clip edge\n const cb = fixedContainingBlock(trigger);\n // The region the menu may paint in: never above the scroll box's visible\n // top (that is the dialog's header), and never past the viewport or any\n // clip at or above the containing block (the panel) — which lets the\n // menu overlay the dialog's footer, as a dropdown should.\n let top = Math.max(0, paddingBox(clip.box).top);\n let bottom = window.innerHeight;\n let node: HTMLElement | null = cb;\n while (node && node !== document.body && node !== document.documentElement) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'hidden' || oy === 'clip' || oy === 'auto' || oy === 'scroll') {\n const b = paddingBox(node);\n top = Math.max(top, b.top);\n bottom = Math.min(bottom, b.bottom);\n }\n node = node.parentElement;\n }\n const spaceBelow = bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // The menu's own chrome (search input, padding, footer note), measured\n // when it is laid out; the portaled branch's budget before that.\n const menu = menuRef.current;\n const list = listRef.current;\n const measured = menu && list ? menu.offsetHeight - list.offsetHeight : 0;\n const chrome = measured > 0 ? measured : searchable ? 52 : 12;\n setListMaxHeight(Math.max(80, Math.min(256, avail - chrome)));\n // Fixed offsets are measured from the containing block's padding box\n // (the viewport when there is none).\n const origin = cb\n ? paddingBox(cb)\n : { top: 0, left: 0, bottom: window.innerHeight, right: viewportRight() };\n const style: CSSProperties = {\n position: 'fixed',\n minWidth: Math.round(rect.width),\n };\n if (align === 'end') style.right = Math.round(origin.right - rect.right);\n else style.left = Math.round(rect.left - origin.left);\n if (placeAbove) style.bottom = Math.round(origin.bottom - (rect.top - gap));\n else style.top = Math.round(rect.bottom + gap - origin.top);\n setInlineAbove(placeAbove);\n setInlineFixed(style);\n return;\n }\n setInlineFixed(null);\n const rect = trigger.getBoundingClientRect();\n const clipRect = clip.box.getBoundingClientRect();\n const gap = 4; // matches mt-1 / mb-1\n const inset = 8; // stay a hair inside the panel's rounded clip edge\n const spaceBelow = clipRect.bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - clipRect.top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n setInlineAbove(placeAbove);\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // Reserve room for the optional search input + container padding (same\n // budget as the portaled branch).\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n }, [searchable, align]);\n\n // On open: focus the right control (the only side effect — query/active reset\n // happens in openMenu so this effect never calls setState).\n useEffect(() => {\n if (!(open && mounted)) return;\n const t = setTimeout(() => {\n if (searchable) inputRef.current?.focus();\n else listRef.current?.focus();\n }, 0);\n return () => clearTimeout(t);\n }, [open, mounted, searchable]);\n\n // Position the menu before paint, and keep it glued to the trigger while open\n // (ancestor scroll uses capture so a scrolling table re-anchors the menu).\n // Skip entirely for the inline (in-dialog) branch — it positions via CSS.\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updatePosition();\n const onReflow = () => updatePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updatePosition, filtered.length]);\n\n // Inline (in-dialog) counterpart: measure the menu against the dialog's clip\n // box before paint and keep it clamped while open (re-measure on resize, and\n // on capture-phase scroll so a scrolling dialog body re-anchors it).\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updateInlinePosition();\n const onReflow = () => updateInlinePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updateInlinePosition, filtered.length]);\n\n // Click-outside closes, restoring focus to the trigger. The menu is portaled\n // out of containerRef, so a click on it must also count as \"inside\".\n useEffect(() => {\n if (!open) return;\n function onDocMouseDown(e: MouseEvent) {\n const target = e.target as Node;\n if (containerRef.current?.contains(target) || menuRef.current?.contains(target)) return;\n closeMenu();\n }\n document.addEventListener('mousedown', onDocMouseDown);\n return () => document.removeEventListener('mousedown', onDocMouseDown);\n }, [open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Keep the active row in view.\n useEffect(() => {\n if (open) document.getElementById(optionId(active))?.scrollIntoView({ block: 'nearest' });\n }, [active, open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n function openMenu() {\n if (disabled) return;\n // Uncontrolled mode resets its own type-ahead on open; server-driven mode\n // leaves the query to the parent (it owns the lifecycle — resets on form\n // open / relationship change), so reopening keeps the current results.\n if (!serverFiltered) setInternalQuery('');\n const sel = value != null ? options.findIndex((o) => o.value === value) : -1;\n setActiveIndex(sel >= 0 ? sel : 0);\n setOpen(true);\n }\n\n // Close the menu, notifying the consumer (Subtask 2.5.5's inline-edit cell\n // leaves edit mode on close). Every close path funnels through here.\n function closeMenu() {\n setOpen(false);\n onClose?.();\n }\n\n function closeAndRefocus() {\n closeMenu();\n triggerRef.current?.focus();\n }\n\n function commit(i: number) {\n if (disabled) return;\n const opt = filtered[i];\n if (!opt || opt.disabled) return;\n onChange(opt.value);\n closeAndRefocus();\n }\n\n function onListKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setActiveIndex(Math.min(active + 1, filtered.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setActiveIndex(Math.max(active - 1, 0));\n } else if (e.key === 'Home') {\n e.preventDefault();\n setActiveIndex(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n setActiveIndex(filtered.length - 1);\n } else if (e.key === 'Enter') {\n e.preventDefault();\n commit(active);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n closeAndRefocus();\n }\n }\n\n function onTriggerKeyDown(e: React.KeyboardEvent) {\n if (!open && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n openMenu();\n } else if (open && e.key === 'Escape') {\n // The open menu takes focus from a `setTimeout(0)` (the on-open effect),\n // so an Escape pressed before that fires lands HERE, not on the list.\n // Ignoring it left the menu open, and the next click on the trigger then\n // toggled it SHUT — the picker closed for good (MOTIR-7345). Focus is\n // already on the trigger, so close without moving it.\n e.preventDefault();\n closeMenu();\n }\n }\n\n const activeId = filtered.length > 0 ? optionId(active) : undefined;\n // A `role=\"listbox\"` MUST own `role=\"option\"` children, so the listbox role\n // only applies when options are actually rendered. While loading or with no\n // matches the panel holds a status message instead — present it as a\n // `role=\"status\"` live region (announced to AT, and not an empty/childless\n // listbox, which is a critical `aria-required-children` axe violation). The\n // empty state is the link picker's DEFAULT on-open state since the 6.9.2\n // query-driven retrofit, so this is now reachable on every open.\n const hasOptions = !loading && filtered.length > 0;\n\n // Portal out to escape a short table's overflow:hidden — UNLESS we're inside a\n // focus-trapping dialog, where an inline menu is required (see the `mounted`\n // comment above).\n //\n // ⚠️ This is SETTLED IN A LAYOUT EFFECT, not read during render. It used to be\n // read during render on the stated invariant that \"the only ref-null case is\n // autoOpen, used solely by inline-edit cells (always in a table, never a\n // dialog)\". That invariant no longer holds: the quick-view peek's editable\n // rail (MOTIR-2563) is an `autoOpen` caller INSIDE the modal. The render-time\n // read fails permanently for it — `useMounted` returns `true` on the very\n // first client render, `triggerRef.current` is still null at that point, and\n // `subscribeNoop` means nothing ever re-renders to correct it. The menu then\n // portals to <body>, lands outside the dialog's focus scope, and every one of\n // its options is `aria-hidden` to assistive tech and to the accessibility\n // tree. A layout effect runs after the ref attaches and before paint, so the\n // branch is right for click-opened AND mount-opened menus alike.\n const [inDialog, setInDialog] = useState(false);\n useIsomorphicLayoutEffect(() => {\n setInDialog(!!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]'));\n }, [open]);\n\n const menuInner = (\n <>\n {searchable ? (\n <input\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded\n aria-controls={listId}\n aria-activedescendant={activeId}\n aria-label={searchPlaceholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value);\n setActiveIndex(0);\n }}\n onKeyDown={onListKeyDown}\n placeholder={searchPlaceholder}\n className=\"border-(--el-border) bg-(--el-page-bg) mb-1 w-full rounded-(--radius-input) border px-(--spacing-control-x) py-(--spacing-control-y) text-sm focus-visible:outline-none\"\n />\n ) : null}\n <div\n ref={listRef}\n id={listId}\n role={hasOptions ? 'listbox' : 'status'}\n aria-label={label}\n tabIndex={hasOptions && !searchable ? 0 : -1}\n aria-activedescendant={hasOptions && !searchable ? activeId : undefined}\n onKeyDown={hasOptions && !searchable ? onListKeyDown : undefined}\n // Both branches cap height to their measured available space (inline\n // style wins over the max-h-64 fallback): the portaled menu against the\n // viewport, the inline (in-dialog) menu against the dialog's clip box —\n // so an over-tall list scrolls internally instead of being clipped.\n style={{ maxHeight: listMaxHeight }}\n className=\"max-h-64 overflow-y-auto focus:outline-none\"\n >\n {loading ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{loadingText}</p>\n ) : filtered.length === 0 ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{emptyText}</p>\n ) : (\n filtered.map((opt, i) => {\n const isSelected = opt.value === value;\n const isActive = i === active;\n // A non-interactive group header at each group transition (in list\n // order, so a fully-filtered group's header drops with it). Not an\n // option row — keyboard nav over `filtered` is unaffected.\n const header =\n opt.group !== undefined && opt.group !== filtered[i - 1]?.group ? (\n <div\n key={`group-${opt.group}`}\n role=\"presentation\"\n className=\"px-(--spacing-control-x) pt-2 pb-1 font-mono text-[11px] font-semibold tracking-wider text-(--el-text-eyebrow) uppercase\"\n >\n {opt.group}\n </div>\n ) : null;\n return (\n <Fragment key={opt.value}>\n {header}\n <div\n id={optionId(i)}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={opt.disabled ? true : undefined}\n lang={opt.lang}\n onMouseEnter={() => setActiveIndex(i)}\n onClick={() => commit(i)}\n className={cn(\n 'flex items-center gap-2 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-sm',\n opt.disabled ? 'cursor-default' : 'cursor-pointer',\n isActive ? 'bg-(--el-option-active-bg)' : '',\n opt.disabled ? 'text-(--el-text-secondary)' : 'text-(--el-text)',\n )}\n >\n {opt.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {opt.icon}\n </span>\n ) : null}\n {opt.description ? (\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate\">{opt.label}</span>\n <span className=\"text-(--el-text-secondary) max-w-[18rem] text-xs leading-snug whitespace-normal\">\n {opt.description}\n </span>\n </span>\n ) : (\n <span className=\"truncate\">{opt.label}</span>\n )}\n {opt.secondary ? (\n // --el-text-identifier (= --color-slate, the -secondary\n // weight), NOT -muted: muted (#787671) fails AA (4.16:1) on\n // the panel surface (#f6f5f4) at this 12px size (the\n // sidebar-caption contrast lesson) — identifier/slate clears it.\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {opt.secondary}\n </span>\n ) : null}\n {opt.trailing ? (\n // Rich trailing content (a Pill / chip). Pinned right; takes\n // the `ml-auto` itself when there is no `secondary` before it.\n <span\n className={cn(\n 'flex shrink-0 items-center gap-1.5',\n opt.secondary ? 'ml-1.5' : 'ml-auto',\n )}\n >\n {opt.trailing}\n </span>\n ) : null}\n {isSelected ? (\n // The selected-row check uses the accent (matching\n // MultiSelectPicker) — unifies the check-icon colour across\n // the two pickers (was bare ink here, accent there).\n <Check\n className=\"ml-1 h-4 w-4 shrink-0 text-(--el-accent-on-surface)\"\n aria-hidden\n />\n ) : null}\n </div>\n </Fragment>\n );\n })\n )}\n </div>\n {footer ? (\n <p className=\"border-(--el-border-soft) mt-1 max-w-[18rem] border-t px-(--spacing-control-x) pt-2 pb-1 text-xs leading-relaxed text-(--el-text-secondary)\">\n {footer}\n </p>\n ) : null}\n </>\n );\n\n // The shared menu panel. In a dialog: an inline absolute panel (the original,\n // proven layout). Otherwise: a body-portaled panel with viewport-anchored\n // fixed positioning that escapes every overflow ancestor.\n const menu = inDialog ? (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n style={inlineFixed ?? undefined}\n className={cn(\n 'z-50 w-max max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-1',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n // Lifted out of a scrolling clip (MOTIR-7655): positioned by\n // `inlineFixed`. Otherwise an absolute child of the trigger that flips\n // above it when the dialog has more room there, so a tall list near the\n // modal's bottom edge isn't clipped (clamped + scrolled).\n inlineFixed\n ? null\n : cn(\n 'absolute min-w-full',\n align === 'end' ? 'right-0' : 'left-0',\n inlineAbove ? 'bottom-full mb-1' : 'top-full mt-1',\n ),\n )}\n >\n {menuInner}\n </div>\n ) : (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n // Width sizes to the widest option but never narrower than the trigger\n // (minWidth set in updatePosition) and is capped so a long label can't run\n // off-screen. Hidden until positioned to avoid a first-paint flash at 0,0.\n style={menuStyle ?? { position: 'fixed', visibility: 'hidden' }}\n className={cn(\n 'z-50 w-max max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-1',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n )}\n >\n {menuInner}\n </div>\n );\n\n return (\n // `data-inner-dismiss` while the menu is open — see MultiSelectPicker:\n // an enclosing Radix layer's onEscapeKeyDown checks it so Esc closes the\n // MENU (our own handler) instead of dismissing the whole layer.\n <div ref={containerRef} data-inner-dismiss={open ? true : undefined} className=\"relative\">\n <button\n ref={triggerRef}\n type=\"button\"\n id={id}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n aria-label={label}\n aria-busy={busy || undefined}\n disabled={disabled}\n onClick={() => (open ? closeMenu() : openMenu())}\n onKeyDown={onTriggerKeyDown}\n className={cn(\n 'border-(--el-border) bg-(--el-page-bg) flex h-(--height-control) w-full items-center gap-2 rounded-(--radius-input) border px-(--spacing-control-x) text-sm',\n 'focus-visible:ring-(--focus-ring-color) focus-visible:outline-none focus-visible:ring-2',\n 'disabled:opacity-50',\n className,\n )}\n >\n {triggerIcon ? (\n <span aria-hidden className={ICON_SLOT}>\n {triggerIcon}\n </span>\n ) : null}\n {selected ? (\n <>\n {selected.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {selected.icon}\n </span>\n ) : null}\n <span lang={selected.lang} className=\"text-(--el-text) truncate\">\n {selected.label}\n </span>\n {selected.secondary ? (\n // identifier (= slate, the -secondary weight): AA on the trigger surface at 12px (as above).\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {selected.secondary}\n </span>\n ) : null}\n </>\n ) : (\n <span className=\"text-(--el-text-muted) truncate\">{placeholder}</span>\n )}\n {busy ? (\n // The spinner takes the chevron's 16px slot, so the trigger keeps its size.\n <LoaderCircle\n data-combobox-busy=\"\"\n className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0 animate-spin\"\n aria-hidden\n />\n ) : (\n <ChevronsUpDown className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0\" aria-hidden />\n )}\n </button>\n\n {open\n ? inDialog\n ? menu\n : mounted\n ? createPortal(menu, fullscreenContainer ?? document.body)\n : null\n : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/utils/fullscreen.ts","../../../src/components/ui/Combobox.tsx"],"names":["useSyncExternalStore","useState","rect","gap","inset","spaceBelow","spaceAbove","placeAbove","avail","menu","Fragment"],"mappings":"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","file":"Combobox.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { useState, useSyncExternalStore, type RefObject } from 'react';\n\n/**\n * The element currently in NATIVE full screen (`document.fullscreenElement`), or\n * `null` — re-read on every `fullscreenchange`.\n *\n * While an element is in native full screen the browser paints ONLY that\n * element's subtree, in the top layer. Anything portalled to `document.body` —\n * a dialog, a popover, a picker's menu — is not shown at all, whatever its\n * z-index (MOTIR-7658: the roadmap canvas's quick view opened invisibly and\n * held focus and scroll lock until Esc).\n *\n * A portalling primitive does not use this directly: it uses\n * `usePortalContainer`, which also refuses to follow full screen into the\n * primitive's own subtree.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function useFullscreenElement(): Element | null {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n}\n\nfunction getSnapshot(): Element | null {\n return document.fullscreenElement ?? null;\n}\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\n/**\n * The element a portalling surface mounts into: the element in native full\n * screen while there is one (MOTIR-7658), else `null` — which the caller turns\n * into `document.body`.\n *\n * `own` is the surface's portalled root (a dialog's content, a menu). When the\n * element that goes full screen is INSIDE it — the native full-screen button of\n * a `<video>` in a dialog — the container stays where it was (MOTIR-8560).\n * Following it would re-portal the surface into its own descendant: React\n * unmounts the subtree to move it, the full-screen element leaves the document,\n * the browser exits full screen, and the surface snaps back to a fresh copy.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function usePortalContainer(own: RefObject<Element | null>): Element | null {\n const [store] = useState(() => createPortalContainerStore(own));\n return useSyncExternalStore(store.subscribe, store.getSnapshot, getServerSnapshot);\n}\n\nfunction createPortalContainerStore(own: RefObject<Element | null>) {\n let container: Element | null =\n typeof document === 'undefined' ? null : (document.fullscreenElement ?? null);\n return {\n subscribe(onChange: () => void): () => void {\n const sync = () => {\n const next = document.fullscreenElement ?? null;\n const root = own.current;\n // The surface's own descendant went full screen: stay put.\n if (next !== null && root !== null && root.contains(next)) return;\n if (next === container) return;\n container = next;\n onChange();\n };\n // A change between creating the store and subscribing to it.\n sync();\n document.addEventListener('fullscreenchange', sync);\n return () => document.removeEventListener('fullscreenchange', sync);\n },\n getSnapshot: (): Element | null => container,\n };\n}\n","'use client';\n\nimport {\n Fragment,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Check, ChevronsUpDown, LoaderCircle } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { usePortalContainer } from '../../utils/fullscreen';\n\n// Run layout effects on the client, fall back to useEffect during SSR (the menu\n// only mounts client-side anyway — see the `mounted` gate below).\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n// `false` during SSR / first hydration render, `true` on the client thereafter —\n// gates the createPortal call so document.body is guaranteed present. Hydration-\n// safe (matches the server's `false`) and avoids setState-in-effect.\nconst subscribeNoop = () => () => {};\n\n/**\n * The LEADING-VISUAL slot, on the trigger and on every option row (MOTIR-3080).\n *\n * ⚠️ `inline-flex` is load-bearing, not decoration. The wrapper is a flex item of\n * its row, so IT is blockified — but whatever a caller passes INSIDE it is not,\n * and a plain `<span>` therefore stays `display: inline`, where width and height\n * do not apply at all. `StatusPicker`'s 10px status dot rendered as a 2 × 18\n * sliver for exactly that reason: three cards of palette work (MOTIR-1273 /\n * -2073 / -2075) measured that dot's HUE while its box was two pixels wide.\n *\n * It survived every check because the markup was right — the classes ask for\n * 10 × 10, `getComputedStyle` reports 10px, and only the USED value disagreed.\n * A used value exists only in a browser, so `tests/e2e/issue-detail-flow` MEASURES\n * it rather than asserting the element exists.\n *\n * Making the slot a flex CONTAINER blockifies its child, which is what the slot's\n * contract (\"a leading visual\") always implied. A lucide `<svg>` is replaced and\n * was never affected, so no existing caller's icon changes size.\n */\nconst ICON_SLOT = 'inline-flex shrink-0 items-center';\n\nfunction useMounted() {\n return useSyncExternalStore(\n subscribeNoop,\n () => true,\n () => false,\n );\n}\n\n/**\n * Combobox — an accessible select/combobox primitive (Subtask 2.3.4). A trigger\n * button (`role=\"combobox\"`, `aria-haspopup=\"listbox\"`) opens an anchored panel\n * holding an optional type-ahead filter and a `role=\"listbox\"` of\n * `role=\"option\"` rows. The active option is tracked with `aria-activedescendant`\n * on whichever control holds focus (the filter input when `searchable`, else the\n * listbox) — the CommandPalette pattern that already clears the STRICT axe sweep.\n *\n * Deliberately NOT built on the Radix Popover primitive: that injects\n * `aria-haspopup=\"dialog\"` + dialog focus semantics onto its trigger, which\n * conflicts with the listbox combobox pattern. This is a self-contained anchored\n * dropdown (click-outside + Escape + focus return handled here) so the ARIA is\n * exactly the WAI-ARIA combobox shape.\n *\n * Composed by `components/issues/TypePicker` (searchable=false, 5 options) and\n * `ParentPicker` (searchable, async candidate list).\n */\nexport interface ComboboxOption<T extends string> {\n value: T;\n /** Primary text — also the accessible name of the option. */\n label: string;\n /** Extra text matched by the filter (e.g. a PROD-N identifier). */\n keywords?: string;\n /** Leading visual (a kind icon); decorative — label carries the name. */\n icon?: ReactNode;\n /** Trailing muted text (e.g. the identifier). */\n secondary?: string;\n /**\n * A one-line description drawn UNDER the label in the menu row only — never in\n * the trigger, which stays the label alone (the workspace role picker's\n * \"what this role does\" line, MOTIR-6465). Decorative for the accessible name,\n * which stays `label`.\n */\n description?: string;\n /** Trailing rich content pinned to the option row's far end (e.g. a state\n * Pill or a \"Linked to …\" chip in the PR-link picker, MOTIR-1596).\n * Decorative — the accessible name is `label`; renders after `secondary`. */\n trailing?: ReactNode;\n /**\n * Optional section label. When consecutive options carry different `group`\n * values, a non-interactive header row is rendered at each transition (the\n * advanced filter's \"Fields\" / \"Custom fields\" / \"Other\" field menu). Pass\n * options pre-sorted by group; headers follow list order, so a group whose\n * every option is filtered out disappears with it. Decorative — not part of\n * the listbox's option indices, so keyboard nav is unaffected.\n */\n group?: string;\n /**\n * The option is shown but cannot be picked (MOTIR-5528 — a status move an\n * approval holds). It stays FOCUSABLE and is announced as unavailable\n * (`aria-disabled`), rather than skipped, so a keyboard reader learns the move\n * exists and why it is held; a click or Enter on it does nothing. Its label\n * reads in `--el-text-secondary`, never an opacity dim, which keeps AA.\n */\n disabled?: boolean;\n /**\n * The language the LABEL is written in (a BCP 47 code), rendered as the `lang`\n * attribute on this option's row and, when it is selected, on the trigger's\n * label (MOTIR-7758 — a language picker names each language in its own script,\n * so a reader announces 日本語 in Japanese inside an English page). Omit it and\n * the label inherits the page's language, as before.\n */\n lang?: string;\n}\n\nexport interface ComboboxProps<T extends string> {\n options: ComboboxOption<T>[];\n value: T | null;\n onChange: (value: T) => void;\n /** Accessible name for the trigger + listbox. */\n label: string;\n /** Trigger text when nothing is selected. */\n placeholder?: string;\n searchable?: boolean;\n searchPlaceholder?: string;\n /** Empty-listbox content. A string, or rich nodes (e.g. a \"no matches\" line\n * plus a hint sub-line — use block `<span>`s, since this renders in a `<p>`). */\n emptyText?: ReactNode;\n loading?: boolean;\n loadingText?: string;\n disabled?: boolean;\n /** id for the trigger button. */\n id?: string;\n className?: string;\n /**\n * Mount already-open (Subtask 2.5.5) — for an inline-edit cell that renders the\n * picker on a single click, so the menu is open immediately (mirrors\n * `DatePicker`'s `autoOpen`). Focus lands on the search input / listbox.\n */\n autoOpen?: boolean;\n /**\n * Fired whenever the menu transitions open → closed (a pick, Escape,\n * click-outside, or toggling the trigger). The inline-edit cell uses it to\n * leave edit mode and return to the static pill/avatar (Subtask 2.5.5).\n */\n onClose?: () => void;\n /**\n * Server-driven search mode (Subtask 6.9.2 — the link picker). When\n * `onQueryChange` is provided the type-ahead query is CONTROLLED by the parent\n * (`query`) and client-side filtering is SKIPPED — `options` is taken as the\n * server's authoritative result for the current query (the parent debounces\n * its fetch off `onQueryChange`, the same contract as `MultiSelectPicker`).\n * Omit both for the default self-filtering behaviour. The parent owns the\n * query lifecycle, so opening the menu does NOT reset it.\n */\n query?: string;\n onQueryChange?: (query: string) => void;\n /**\n * A non-interactive note pinned BELOW the listbox, inside the open menu\n * (MOTIR-5582 — the Monitoring room's minimum-level control says, at the\n * action that causes it, that lowering also re-checks earlier issues). It is\n * not an option: keyboard navigation and the listbox's option indices are\n * unaffected, and it is announced as ordinary text. Omit it for no note.\n */\n footer?: ReactNode;\n /**\n * A leading glyph drawn on the TRIGGER only, before the selected label (or the\n * placeholder), and never in the option rows — unlike an option's `icon`,\n * which is drawn in both (MOTIR-7758: the signed-out language control's\n * `Languages` glyph). Decorative: it is rendered `aria-hidden`, and the\n * trigger's accessible name stays `label`.\n */\n triggerIcon?: ReactNode;\n /**\n * The value the trigger shows is being applied (MOTIR-7758). The trigger is\n * marked `aria-busy` and a spinner takes the chevron's 16px slot, so the\n * trigger keeps its size. It does NOT disable the control — a caller that\n * wants that passes `disabled` as well. Pair it with a polite status that says\n * what is happening; this prop draws nothing a reader hears.\n */\n busy?: boolean;\n /**\n * Which edge of the trigger the menu lines up with (MOTIR-7758). `start`\n * (the default) aligns the menu's left edge with the trigger's, as before;\n * `end` aligns the RIGHT edges, so a menu wider than a trigger that sits in a\n * right-hand corner grows leftwards instead of running past the viewport.\n */\n align?: 'start' | 'end';\n}\n\n// Walk up from a node to the nearest ancestor that clips overflow, classifying\n// it as scrolling (overflow auto/scroll — the menu can be scrolled into view)\n// or a hard `hidden`/`clip` box (no scroll — the menu is trapped). Used by the\n// inline (in-dialog) branch to decide whether to clamp + flip the menu (only a\n// hard non-scrolling clip needs it). Stops at <body>; returns null if nothing\n// up the chain clips.\nfunction nearestClipBox(el: HTMLElement): { box: HTMLElement; scrolls: boolean } | null {\n let node = el.parentElement;\n while (node && node !== document.body) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'auto' || oy === 'scroll') return { box: node, scrolls: true };\n if (oy === 'hidden' || oy === 'clip') return { box: node, scrolls: false };\n node = node.parentElement;\n }\n return null;\n}\n\n// The element a `position: fixed` descendant of `el` is laid out against, or\n// null when that is the viewport. A transform (the centered Modal panel's\n// `-translate-x/y-1/2`, which Tailwind v4 emits as the `translate` property), a\n// filter, a backdrop-filter (the glassmorphism material), `contain` or a\n// matching `will-change` all make an ancestor the containing block for fixed\n// descendants — so a fixed child of the Modal panel is positioned relative to\n// the PANEL, not the viewport.\nfunction fixedContainingBlock(el: HTMLElement): HTMLElement | null {\n let node = el.parentElement;\n while (node && node !== document.documentElement) {\n const s = getComputedStyle(node);\n const set = (v: string | undefined) => !!v && v !== 'none';\n if (\n set(s.transform) ||\n set(s.translate) ||\n set(s.scale) ||\n set(s.rotate) ||\n set(s.perspective) ||\n set(s.filter) ||\n set(s.backdropFilter) ||\n /\\b(paint|layout|strict|content)\\b/.test(s.contain || '') ||\n /\\b(transform|translate|scale|rotate|perspective|filter)\\b/.test(s.willChange || '')\n ) {\n return node;\n }\n node = node.parentElement;\n }\n return null;\n}\n\n// An element's padding box in viewport coordinates — the edge its overflow\n// clips at (and, for a containing block, the box fixed offsets are measured\n// from).\nfunction paddingBox(el: HTMLElement): {\n top: number;\n left: number;\n bottom: number;\n right: number;\n} {\n const r = el.getBoundingClientRect();\n const top = r.top + el.clientTop;\n const left = r.left + el.clientLeft;\n // happy-dom reports clientHeight/Width 0; fall back to the border box there.\n const height = el.clientHeight || r.height;\n const width = el.clientWidth || r.width;\n return { top, left, bottom: top + height, right: left + width };\n}\n\n// The right edge of the viewport a `position: fixed` box is laid out against —\n// the layout viewport, which excludes a vertical scrollbar (`innerWidth` does not).\nfunction viewportRight(): number {\n return document.documentElement.clientWidth || window.innerWidth;\n}\n\nexport function Combobox<T extends string>({\n options,\n value,\n onChange,\n label,\n placeholder = 'Select…',\n searchable = false,\n searchPlaceholder = 'Search…',\n emptyText = 'No matches',\n loading = false,\n loadingText = 'Loading…',\n disabled = false,\n id,\n className,\n autoOpen = false,\n onClose,\n query: controlledQuery,\n onQueryChange,\n footer,\n triggerIcon,\n busy = false,\n align = 'start',\n}: ComboboxProps<T>) {\n // A DISABLED combobox never opens — not even when it mounts with `autoOpen`\n // (MOTIR-6173). An inline editor that mounts open for an actor who may not\n // write used to hand them a live menu whose pick the server then refused\n // (MOTIR-4822); the trigger was disabled, the menu was not.\n const [open, setOpen] = useState(autoOpen && !disabled);\n // Server-driven mode (6.9.2): when `onQueryChange` is provided the query is\n // controlled by the parent and client filtering is bypassed (the server's\n // `options` are authoritative). Otherwise the query is internal state.\n const serverFiltered = onQueryChange !== undefined;\n const [internalQuery, setInternalQuery] = useState('');\n const query = serverFiltered ? (controlledQuery ?? '') : internalQuery;\n const setQuery = (q: string) => (serverFiltered ? onQueryChange(q) : setInternalQuery(q));\n const [activeIndex, setActiveIndex] = useState(0);\n // The menu is portaled to <body> by default, so it escapes any clipping\n // ancestor — a short table's overflow:hidden\n // (bug-inline-edit-clipped-when-table-short) OR the Advanced-filter popover's\n // overflow-hidden + overflow-y-auto body (MOTIR-1346). We render INLINE only\n // inside a focus-trapping MODAL (the shared Modal primitive — `data-surface=\n // \"modal\"`; `aria-modal` covers any other), where a portaled menu would land\n // outside the dialog's focus scope (focus-trap war → a click on it hangs /\n // dismisses) AND the modal centers with a CSS transform that breaks a fixed-\n // positioned child's coords. A NON-modal Radix Popover (the filter builder,\n // promote-sprint, mark-duplicate — `data-surface=\"popover\"`) carries\n // `role=\"dialog\"` for a11y but does NOT trap focus, so it portals safely —\n // keyed on the modal surface, NOT `role`, so those popovers escape the clip\n // too. Only render the portal once mounted, since createPortal needs document.body.\n const mounted = useMounted();\n // Viewport-anchored position for the portaled menu + the listbox's available\n // height, recomputed from the trigger rect on open / scroll / resize.\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n const [listMaxHeight, setListMaxHeight] = useState(256);\n // Inline (in-dialog) branch only: whether the menu is flipped ABOVE the\n // trigger because there's more room there than below within the dialog's\n // overflow-hidden clip box (bug-combobox-menu-clipped-inside-modal).\n const [inlineAbove, setInlineAbove] = useState(false);\n // Inline (in-dialog) branch, SCROLLING clip only (a `Modal.Body`): the fixed\n // offsets that lift the menu out of the scroll box while keeping it in the\n // DOM — and so inside the dialog's focus scope (MOTIR-7655). Null otherwise.\n const [inlineFixed, setInlineFixed] = useState<CSSProperties | null>(null);\n const baseId = useId();\n const listId = `${baseId}-listbox`;\n const optionId = (i: number) => `${baseId}-opt-${i}`;\n\n const containerRef = useRef<HTMLDivElement>(null);\n const triggerRef = useRef<HTMLButtonElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const listRef = useRef<HTMLDivElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n // The portal target: the element in native full screen while there is one,\n // else <body> — the browser paints nothing outside a full-screen element\n // (MOTIR-7658) — unless that element is inside the menu (MOTIR-8560).\n const fullscreenContainer = usePortalContainer(menuRef);\n\n const filtered = useMemo(() => {\n // Server-driven mode: `options` is already the query's result — never filter\n // it again client-side (a substring re-filter would hide trigram / token\n // matches the server legitimately returned).\n if (serverFiltered || !searchable || query.trim() === '') return options;\n const needle = query.trim().toLowerCase();\n return options.filter(\n (o) =>\n o.label.toLowerCase().includes(needle) ||\n (o.secondary ? o.secondary.toLowerCase().includes(needle) : false) ||\n (o.keywords ? o.keywords.toLowerCase().includes(needle) : false),\n );\n }, [options, query, searchable, serverFiltered]);\n\n const selected = value != null ? (options.find((o) => o.value === value) ?? null) : null;\n\n // The active row, clamped to the (possibly just-filtered) list — DERIVED, not\n // an effect, so a shrinking filter never needs a setState-in-effect.\n const active = filtered.length > 0 ? Math.min(activeIndex, filtered.length - 1) : 0;\n\n // Anchor the portaled menu to the trigger in VIEWPORT coordinates (position:\n // fixed), flipping above the trigger when there's more room there, and capping\n // the listbox height to the available space so it never runs off-screen.\n const updatePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n const rect = trigger.getBoundingClientRect();\n const gap = 4; // matches the old mt-1\n const viewportH = window.innerHeight;\n const spaceBelow = viewportH - rect.bottom - gap;\n const spaceAbove = rect.top - gap;\n const placeBelow = spaceBelow >= spaceAbove;\n const avail = Math.max(120, placeBelow ? spaceBelow : spaceAbove);\n // Reserve room for the optional search input + container padding.\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n const style: CSSProperties = {\n position: 'fixed',\n minWidth: Math.round(rect.width),\n };\n // `end` pins the menu's right edge to the trigger's, so it grows leftwards.\n if (align === 'end') style.right = Math.round(viewportRight() - rect.right);\n else style.left = Math.round(rect.left);\n if (placeBelow) style.top = Math.round(rect.bottom + gap);\n else style.bottom = Math.round(viewportH - rect.top + gap);\n setMenuStyle(style);\n }, [searchable, align]);\n\n // The in-dialog branch renders INLINE (an absolute child of the trigger),\n // so — unlike the body-portaled branch — it cannot escape the modal's\n // `overflow-hidden` panel: an absolute child contributes nothing to the\n // dialog body's scroll height, so a menu taller than the room below the\n // trigger is simply CLIPPED, not scrolled-to (bug-combobox-menu-clipped-\n // inside-modal). Mirror the portaled branch's logic against the DIALOG's\n // clip box instead of the viewport: measure the space above/below the\n // trigger inside the dialog, flip to whichever side is taller, and clamp the\n // listbox height to it so the menu scrolls INTERNALLY and always fits.\n const updateInlinePosition = useCallback(() => {\n const trigger = triggerRef.current;\n if (!trigger) return;\n // Find the nearest ancestor that CLIPS the inline menu. A NON-scrolling\n // `overflow: hidden` clip — the centered Modal panel's `overflow-hidden\n // max-h-[90vh]` box — traps the menu, so clamp + flip against THAT box\n // (bug-combobox-menu-clipped-inside-modal). A SCROLLING clip lifts the menu\n // out with fixed positioning instead (MOTIR-7655, below), bounded above by\n // the scroll box's visible top so it never lands under the panel's header\n // (the regression the first clamp-against-the-scroll-box cut caused).\n const clip = nearestClipBox(trigger);\n if (!clip) {\n setInlineFixed(null);\n setInlineAbove(false);\n setListMaxHeight(256); // == the max-h-64 fallback (original behaviour)\n return;\n }\n if (clip.scrolls) {\n // A SCROLLING clip (`Modal.Body`, a popover's scroll body) does trap an\n // absolute menu after all: the menu extends the box's scroll area, the\n // part past its visible edge is cut, and the box grows a second\n // scrollbar — on a short dialog body that leaves one option visible\n // (MOTIR-7655). Lift the menu out instead: `position: fixed`, still in\n // the DOM. A fixed box is clipped only by the ancestors on its\n // containing-block chain, so the scroll box stops clipping it while the\n // panel (the containing block, through its transform) still bounds it.\n const rect = trigger.getBoundingClientRect();\n const gap = 4; // matches mt-1 / mb-1\n const inset = 8; // stay a hair inside the panel's rounded clip edge\n const cb = fixedContainingBlock(trigger);\n // The region the menu may paint in: never above the scroll box's visible\n // top (that is the dialog's header), and never past the viewport or any\n // clip at or above the containing block (the panel) — which lets the\n // menu overlay the dialog's footer, as a dropdown should.\n let top = Math.max(0, paddingBox(clip.box).top);\n let bottom = window.innerHeight;\n let node: HTMLElement | null = cb;\n while (node && node !== document.body && node !== document.documentElement) {\n const oy = getComputedStyle(node).overflowY;\n if (oy === 'hidden' || oy === 'clip' || oy === 'auto' || oy === 'scroll') {\n const b = paddingBox(node);\n top = Math.max(top, b.top);\n bottom = Math.min(bottom, b.bottom);\n }\n node = node.parentElement;\n }\n const spaceBelow = bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // The menu's own chrome (search input, padding, footer note), measured\n // when it is laid out; the portaled branch's budget before that.\n const menu = menuRef.current;\n const list = listRef.current;\n const measured = menu && list ? menu.offsetHeight - list.offsetHeight : 0;\n const chrome = measured > 0 ? measured : searchable ? 52 : 12;\n setListMaxHeight(Math.max(80, Math.min(256, avail - chrome)));\n // Fixed offsets are measured from the containing block's padding box\n // (the viewport when there is none).\n const origin = cb\n ? paddingBox(cb)\n : { top: 0, left: 0, bottom: window.innerHeight, right: viewportRight() };\n const style: CSSProperties = {\n position: 'fixed',\n minWidth: Math.round(rect.width),\n };\n if (align === 'end') style.right = Math.round(origin.right - rect.right);\n else style.left = Math.round(rect.left - origin.left);\n if (placeAbove) style.bottom = Math.round(origin.bottom - (rect.top - gap));\n else style.top = Math.round(rect.bottom + gap - origin.top);\n setInlineAbove(placeAbove);\n setInlineFixed(style);\n return;\n }\n setInlineFixed(null);\n const rect = trigger.getBoundingClientRect();\n const clipRect = clip.box.getBoundingClientRect();\n const gap = 4; // matches mt-1 / mb-1\n const inset = 8; // stay a hair inside the panel's rounded clip edge\n const spaceBelow = clipRect.bottom - rect.bottom - gap - inset;\n const spaceAbove = rect.top - clipRect.top - gap - inset;\n const placeAbove = spaceAbove > spaceBelow;\n setInlineAbove(placeAbove);\n const avail = Math.max(80, placeAbove ? spaceAbove : spaceBelow);\n // Reserve room for the optional search input + container padding (same\n // budget as the portaled branch).\n setListMaxHeight(Math.max(80, Math.min(256, avail - (searchable ? 52 : 12))));\n }, [searchable, align]);\n\n // On open: focus the right control (the only side effect — query/active reset\n // happens in openMenu so this effect never calls setState).\n useEffect(() => {\n if (!(open && mounted)) return;\n const t = setTimeout(() => {\n if (searchable) inputRef.current?.focus();\n else listRef.current?.focus();\n }, 0);\n return () => clearTimeout(t);\n }, [open, mounted, searchable]);\n\n // Position the menu before paint, and keep it glued to the trigger while open\n // (ancestor scroll uses capture so a scrolling table re-anchors the menu).\n // Skip entirely for the inline (in-dialog) branch — it positions via CSS.\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updatePosition();\n const onReflow = () => updatePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updatePosition, filtered.length]);\n\n // Inline (in-dialog) counterpart: measure the menu against the dialog's clip\n // box before paint and keep it clamped while open (re-measure on resize, and\n // on capture-phase scroll so a scrolling dialog body re-anchors it).\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted)) return;\n if (!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]')) return;\n updateInlinePosition();\n const onReflow = () => updateInlinePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, updateInlinePosition, filtered.length]);\n\n // Click-outside closes, restoring focus to the trigger. The menu is portaled\n // out of containerRef, so a click on it must also count as \"inside\".\n useEffect(() => {\n if (!open) return;\n function onDocMouseDown(e: MouseEvent) {\n const target = e.target as Node;\n if (containerRef.current?.contains(target) || menuRef.current?.contains(target)) return;\n closeMenu();\n }\n document.addEventListener('mousedown', onDocMouseDown);\n return () => document.removeEventListener('mousedown', onDocMouseDown);\n }, [open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n // Keep the active row in view.\n useEffect(() => {\n if (open) document.getElementById(optionId(active))?.scrollIntoView({ block: 'nearest' });\n }, [active, open]); // eslint-disable-line react-hooks/exhaustive-deps\n\n function openMenu() {\n if (disabled) return;\n // Uncontrolled mode resets its own type-ahead on open; server-driven mode\n // leaves the query to the parent (it owns the lifecycle — resets on form\n // open / relationship change), so reopening keeps the current results.\n if (!serverFiltered) setInternalQuery('');\n const sel = value != null ? options.findIndex((o) => o.value === value) : -1;\n setActiveIndex(sel >= 0 ? sel : 0);\n setOpen(true);\n }\n\n // Close the menu, notifying the consumer (Subtask 2.5.5's inline-edit cell\n // leaves edit mode on close). Every close path funnels through here.\n function closeMenu() {\n setOpen(false);\n onClose?.();\n }\n\n function closeAndRefocus() {\n closeMenu();\n triggerRef.current?.focus();\n }\n\n function commit(i: number) {\n if (disabled) return;\n const opt = filtered[i];\n if (!opt || opt.disabled) return;\n onChange(opt.value);\n closeAndRefocus();\n }\n\n function onListKeyDown(e: React.KeyboardEvent) {\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setActiveIndex(Math.min(active + 1, filtered.length - 1));\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setActiveIndex(Math.max(active - 1, 0));\n } else if (e.key === 'Home') {\n e.preventDefault();\n setActiveIndex(0);\n } else if (e.key === 'End') {\n e.preventDefault();\n setActiveIndex(filtered.length - 1);\n } else if (e.key === 'Enter') {\n e.preventDefault();\n commit(active);\n } else if (e.key === 'Escape') {\n e.preventDefault();\n closeAndRefocus();\n }\n }\n\n function onTriggerKeyDown(e: React.KeyboardEvent) {\n if (!open && (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ')) {\n e.preventDefault();\n openMenu();\n } else if (open && e.key === 'Escape') {\n // The open menu takes focus from a `setTimeout(0)` (the on-open effect),\n // so an Escape pressed before that fires lands HERE, not on the list.\n // Ignoring it left the menu open, and the next click on the trigger then\n // toggled it SHUT — the picker closed for good (MOTIR-7345). Focus is\n // already on the trigger, so close without moving it.\n e.preventDefault();\n closeMenu();\n }\n }\n\n const activeId = filtered.length > 0 ? optionId(active) : undefined;\n // A `role=\"listbox\"` MUST own `role=\"option\"` children, so the listbox role\n // only applies when options are actually rendered. While loading or with no\n // matches the panel holds a status message instead — present it as a\n // `role=\"status\"` live region (announced to AT, and not an empty/childless\n // listbox, which is a critical `aria-required-children` axe violation). The\n // empty state is the link picker's DEFAULT on-open state since the 6.9.2\n // query-driven retrofit, so this is now reachable on every open.\n const hasOptions = !loading && filtered.length > 0;\n\n // Portal out to escape a short table's overflow:hidden — UNLESS we're inside a\n // focus-trapping dialog, where an inline menu is required (see the `mounted`\n // comment above).\n //\n // ⚠️ This is SETTLED IN A LAYOUT EFFECT, not read during render. It used to be\n // read during render on the stated invariant that \"the only ref-null case is\n // autoOpen, used solely by inline-edit cells (always in a table, never a\n // dialog)\". That invariant no longer holds: the quick-view peek's editable\n // rail (MOTIR-2563) is an `autoOpen` caller INSIDE the modal. The render-time\n // read fails permanently for it — `useMounted` returns `true` on the very\n // first client render, `triggerRef.current` is still null at that point, and\n // `subscribeNoop` means nothing ever re-renders to correct it. The menu then\n // portals to <body>, lands outside the dialog's focus scope, and every one of\n // its options is `aria-hidden` to assistive tech and to the accessibility\n // tree. A layout effect runs after the ref attaches and before paint, so the\n // branch is right for click-opened AND mount-opened menus alike.\n const [inDialog, setInDialog] = useState(false);\n useIsomorphicLayoutEffect(() => {\n setInDialog(!!triggerRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]'));\n }, [open]);\n\n const menuInner = (\n <>\n {searchable ? (\n <input\n ref={inputRef}\n type=\"text\"\n role=\"combobox\"\n aria-expanded\n aria-controls={listId}\n aria-activedescendant={activeId}\n aria-label={searchPlaceholder}\n value={query}\n onChange={(e) => {\n setQuery(e.target.value);\n setActiveIndex(0);\n }}\n onKeyDown={onListKeyDown}\n placeholder={searchPlaceholder}\n className=\"border-(--el-border) bg-(--el-page-bg) mb-1 w-full rounded-(--radius-input) border px-(--spacing-control-x) py-(--spacing-control-y) text-sm focus-visible:outline-none\"\n />\n ) : null}\n <div\n ref={listRef}\n id={listId}\n role={hasOptions ? 'listbox' : 'status'}\n aria-label={label}\n tabIndex={hasOptions && !searchable ? 0 : -1}\n aria-activedescendant={hasOptions && !searchable ? activeId : undefined}\n onKeyDown={hasOptions && !searchable ? onListKeyDown : undefined}\n // Both branches cap height to their measured available space (inline\n // style wins over the max-h-64 fallback): the portaled menu against the\n // viewport, the inline (in-dialog) menu against the dialog's clip box —\n // so an over-tall list scrolls internally instead of being clipped.\n style={{ maxHeight: listMaxHeight }}\n className=\"max-h-64 overflow-y-auto focus:outline-none\"\n >\n {loading ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{loadingText}</p>\n ) : filtered.length === 0 ? (\n <p className=\"text-(--el-text-muted) px-2.5 py-2 text-sm\">{emptyText}</p>\n ) : (\n filtered.map((opt, i) => {\n const isSelected = opt.value === value;\n const isActive = i === active;\n // A non-interactive group header at each group transition (in list\n // order, so a fully-filtered group's header drops with it). Not an\n // option row — keyboard nav over `filtered` is unaffected.\n const header =\n opt.group !== undefined && opt.group !== filtered[i - 1]?.group ? (\n <div\n key={`group-${opt.group}`}\n role=\"presentation\"\n className=\"px-(--spacing-control-x) pt-2 pb-1 font-mono text-[11px] font-semibold tracking-wider text-(--el-text-eyebrow) uppercase\"\n >\n {opt.group}\n </div>\n ) : null;\n return (\n <Fragment key={opt.value}>\n {header}\n <div\n id={optionId(i)}\n role=\"option\"\n aria-selected={isSelected}\n aria-disabled={opt.disabled ? true : undefined}\n lang={opt.lang}\n onMouseEnter={() => setActiveIndex(i)}\n onClick={() => commit(i)}\n className={cn(\n 'flex items-center gap-2 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-sm',\n opt.disabled ? 'cursor-default' : 'cursor-pointer',\n isActive ? 'bg-(--el-option-active-bg)' : '',\n opt.disabled ? 'text-(--el-text-secondary)' : 'text-(--el-text)',\n )}\n >\n {opt.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {opt.icon}\n </span>\n ) : null}\n {opt.description ? (\n <span className=\"flex min-w-0 flex-col\">\n <span className=\"truncate\">{opt.label}</span>\n <span className=\"text-(--el-text-secondary) max-w-[18rem] text-xs leading-snug whitespace-normal\">\n {opt.description}\n </span>\n </span>\n ) : (\n <span className=\"truncate\">{opt.label}</span>\n )}\n {opt.secondary ? (\n // --el-text-identifier (= --color-slate, the -secondary\n // weight), NOT -muted: muted (#787671) fails AA (4.16:1) on\n // the panel surface (#f6f5f4) at this 12px size (the\n // sidebar-caption contrast lesson) — identifier/slate clears it.\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {opt.secondary}\n </span>\n ) : null}\n {opt.trailing ? (\n // Rich trailing content (a Pill / chip). Pinned right; takes\n // the `ml-auto` itself when there is no `secondary` before it.\n <span\n className={cn(\n 'flex shrink-0 items-center gap-1.5',\n opt.secondary ? 'ml-1.5' : 'ml-auto',\n )}\n >\n {opt.trailing}\n </span>\n ) : null}\n {isSelected ? (\n // The selected-row check uses the accent (matching\n // MultiSelectPicker) — unifies the check-icon colour across\n // the two pickers (was bare ink here, accent there).\n <Check\n className=\"ml-1 h-4 w-4 shrink-0 text-(--el-accent-on-surface)\"\n aria-hidden\n />\n ) : null}\n </div>\n </Fragment>\n );\n })\n )}\n </div>\n {footer ? (\n <p className=\"border-(--el-border-soft) mt-1 max-w-[18rem] border-t px-(--spacing-control-x) pt-2 pb-1 text-xs leading-relaxed text-(--el-text-secondary)\">\n {footer}\n </p>\n ) : null}\n </>\n );\n\n // The shared menu panel. In a dialog: an inline absolute panel (the original,\n // proven layout). Otherwise: a body-portaled panel with viewport-anchored\n // fixed positioning that escapes every overflow ancestor.\n const menu = inDialog ? (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n style={inlineFixed ?? undefined}\n className={cn(\n 'z-50 w-max max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-(--spacing-card-padding-4)',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n // Lifted out of a scrolling clip (MOTIR-7655): positioned by\n // `inlineFixed`. Otherwise an absolute child of the trigger that flips\n // above it when the dialog has more room there, so a tall list near the\n // modal's bottom edge isn't clipped (clamped + scrolled).\n inlineFixed\n ? null\n : cn(\n 'absolute min-w-full',\n align === 'end' ? 'right-0' : 'left-0',\n inlineAbove ? 'bottom-full mb-1' : 'top-full mt-1',\n ),\n )}\n >\n {menuInner}\n </div>\n ) : (\n <div\n ref={menuRef}\n data-menu-surface=\"\"\n // Width sizes to the widest option but never narrower than the trigger\n // (minWidth set in updatePosition) and is capped so a long label can't run\n // off-screen. Hidden until positioned to avoid a first-paint flash at 0,0.\n style={menuStyle ?? { position: 'fixed', visibility: 'hidden' }}\n className={cn(\n 'z-50 w-max max-w-[18rem] rounded-(--radius-card) bg-(--el-page-bg) p-(--spacing-card-padding-4)',\n 'shadow-(--shadow-elevated) border border-(--el-border)',\n )}\n >\n {menuInner}\n </div>\n );\n\n return (\n // `data-inner-dismiss` while the menu is open — see MultiSelectPicker:\n // an enclosing Radix layer's onEscapeKeyDown checks it so Esc closes the\n // MENU (our own handler) instead of dismissing the whole layer.\n <div ref={containerRef} data-inner-dismiss={open ? true : undefined} className=\"relative\">\n <button\n ref={triggerRef}\n type=\"button\"\n id={id}\n role=\"combobox\"\n aria-haspopup=\"listbox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n aria-label={label}\n aria-busy={busy || undefined}\n disabled={disabled}\n onClick={() => (open ? closeMenu() : openMenu())}\n onKeyDown={onTriggerKeyDown}\n className={cn(\n 'border-(--el-border) bg-(--el-page-bg) flex h-(--height-control) w-full items-center gap-2 rounded-(--radius-input) border px-(--spacing-control-x) text-sm',\n 'focus-visible:ring-(--focus-ring-color) focus-visible:outline-none focus-visible:ring-2',\n 'disabled:opacity-50',\n className,\n )}\n >\n {triggerIcon ? (\n <span aria-hidden className={ICON_SLOT}>\n {triggerIcon}\n </span>\n ) : null}\n {selected ? (\n <>\n {selected.icon ? (\n <span aria-hidden className={ICON_SLOT}>\n {selected.icon}\n </span>\n ) : null}\n <span lang={selected.lang} className=\"text-(--el-text) truncate\">\n {selected.label}\n </span>\n {selected.secondary ? (\n // identifier (= slate, the -secondary weight): AA on the trigger surface at 12px (as above).\n <span className=\"text-(--el-text-identifier) ml-auto truncate text-xs\">\n {selected.secondary}\n </span>\n ) : null}\n </>\n ) : (\n <span className=\"text-(--el-text-muted) truncate\">{placeholder}</span>\n )}\n {busy ? (\n // The spinner takes the chevron's 16px slot, so the trigger keeps its size.\n <LoaderCircle\n data-combobox-busy=\"\"\n className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0 animate-spin\"\n aria-hidden\n />\n ) : (\n <ChevronsUpDown className=\"text-(--el-icon-muted) ml-auto h-4 w-4 shrink-0\" aria-hidden />\n )}\n </button>\n\n {open\n ? inDialog\n ? menu\n : mounted\n ? createPortal(menu, fullscreenContainer ?? document.body)\n : null\n : null}\n </div>\n );\n}\n"]}
|
|
@@ -289,7 +289,7 @@ function MultiSelectPicker({
|
|
|
289
289
|
maxHeight: listMaxHeight
|
|
290
290
|
},
|
|
291
291
|
className: cn(
|
|
292
|
-
"w-max max-w-72 min-w-60 overflow-y-auto rounded-(--radius-card) border border-(--el-border) bg-(--el-page-bg) p-
|
|
292
|
+
"w-max max-w-72 min-w-60 overflow-y-auto rounded-(--radius-card) border border-(--el-border) bg-(--el-page-bg) p-(--spacing-card-padding-4) shadow-(--shadow-elevated)",
|
|
293
293
|
inModal ? "absolute top-full left-0 z-10 mt-1 max-h-64" : "z-50"
|
|
294
294
|
),
|
|
295
295
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/utils/fullscreen.ts","../../../src/components/ui/MultiSelectPicker.tsx"],"names":["useSyncExternalStore","useState"],"mappings":"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","file":"MultiSelectPicker.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { useState, useSyncExternalStore, type RefObject } from 'react';\n\n/**\n * The element currently in NATIVE full screen (`document.fullscreenElement`), or\n * `null` — re-read on every `fullscreenchange`.\n *\n * While an element is in native full screen the browser paints ONLY that\n * element's subtree, in the top layer. Anything portalled to `document.body` —\n * a dialog, a popover, a picker's menu — is not shown at all, whatever its\n * z-index (MOTIR-7658: the roadmap canvas's quick view opened invisibly and\n * held focus and scroll lock until Esc).\n *\n * A portalling primitive does not use this directly: it uses\n * `usePortalContainer`, which also refuses to follow full screen into the\n * primitive's own subtree.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function useFullscreenElement(): Element | null {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n}\n\nfunction getSnapshot(): Element | null {\n return document.fullscreenElement ?? null;\n}\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\n/**\n * The element a portalling surface mounts into: the element in native full\n * screen while there is one (MOTIR-7658), else `null` — which the caller turns\n * into `document.body`.\n *\n * `own` is the surface's portalled root (a dialog's content, a menu). When the\n * element that goes full screen is INSIDE it — the native full-screen button of\n * a `<video>` in a dialog — the container stays where it was (MOTIR-8560).\n * Following it would re-portal the surface into its own descendant: React\n * unmounts the subtree to move it, the full-screen element leaves the document,\n * the browser exits full screen, and the surface snaps back to a fresh copy.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function usePortalContainer(own: RefObject<Element | null>): Element | null {\n const [store] = useState(() => createPortalContainerStore(own));\n return useSyncExternalStore(store.subscribe, store.getSnapshot, getServerSnapshot);\n}\n\nfunction createPortalContainerStore(own: RefObject<Element | null>) {\n let container: Element | null =\n typeof document === 'undefined' ? null : (document.fullscreenElement ?? null);\n return {\n subscribe(onChange: () => void): () => void {\n const sync = () => {\n const next = document.fullscreenElement ?? null;\n const root = own.current;\n // The surface's own descendant went full screen: stay put.\n if (next !== null && root !== null && root.contains(next)) return;\n if (next === container) return;\n container = next;\n onChange();\n };\n // A change between creating the store and subscribing to it.\n sync();\n document.addEventListener('fullscreenchange', sync);\n return () => document.removeEventListener('fullscreenchange', sync);\n },\n getSnapshot: (): Element | null => container,\n };\n}\n","'use client';\n\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ComponentType,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Check, Plus, TriangleAlert, X } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { usePortalContainer } from '../../utils/fullscreen';\n\n// Run the position measurement before paint on the client; fall back to\n// useEffect during SSR (the listbox only ever opens client-side).\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n// `false` during SSR / first hydration render, `true` on the client thereafter —\n// gates the createPortal call so document.body is guaranteed present (mirrors\n// Combobox's useMounted). Hydration-safe and avoids setState-in-effect.\nconst subscribeNoop = () => () => {};\nfunction useMounted() {\n return useSyncExternalStore(\n subscribeNoop,\n () => true,\n () => false,\n );\n}\n\n// MultiSelectPicker (Story 5.4 · Subtask 5.4.8) — the generic chip-input\n// primitive, per design/work-items/labels-components-watch.mock.html panel 1:\n// value chips (label · remove ×) + a type-to-filter input in an input-shaped\n// box, with an anchored `aria-multiselectable` listbox of OptionRow-vocabulary\n// rows (22px glyph slot · truncating label · trailing Check in --el-accent).\n// The ONE new primitive the story earns — labels, components, and the future\n// Epic-6 facet editors all compose it.\n//\n// PURE: options in, selection out — NO fetching inside. The caller owns the\n// option set (filtering / debounced autocomplete) via the controlled `query`;\n// `onCreate` adds the folksonomy create-row when the query matches nothing;\n// `cap` disables the input at the limit (chips stay removable); a value's\n// `tint` colours its chip + its option row's swatch dot (labels pass the\n// name-hash tint; components pass none and get the neutral default).\n//\n// Keyboard (complete, the mock's panel-1 legend): type filters · ↑↓ moves the\n// active row · Enter toggles it (multi-select: the menu does NOT close) ·\n// Backspace on an empty input removes the last chip · Esc closes and returns\n// focus to the input. `aria-activedescendant` tracks the active row, per the\n// shipped Combobox a11y bar.\n\n/** One of the six `--el-tint-*` pastel token names (chip + swatch colour). */\nexport type MultiSelectTint = 'peach' | 'rose' | 'mint' | 'lavender' | 'sky' | 'yellow';\n\nexport interface MultiSelectOption {\n id: string;\n label: string;\n /** Pastel tint for the chip + the option row's swatch dot; absent = neutral. */\n tint?: MultiSelectTint;\n /** Glyph for the option row's 22px slot and the chip (e.g. the component glyph). */\n glyph?: ComponentType<{ className?: string }>;\n}\n\nexport interface MultiSelectPickerProps {\n /** The selected values, rendered as chips. */\n values: MultiSelectOption[];\n /** The current option set — the caller filters/fetches; no fetching inside. */\n options: MultiSelectOption[];\n /** Toggle an option from the listbox (add when unselected, remove when selected). */\n onToggle: (option: MultiSelectOption) => void;\n /** Remove a chip (the × button / Backspace-on-empty path). */\n onRemove: (value: MultiSelectOption) => void;\n /**\n * Folksonomy create — when present, a query matching no option (and no\n * selected value) case-insensitively appends the create-row.\n */\n onCreate?: (name: string) => void;\n /** The controlled filter query (the caller debounces its fetch off this). */\n query: string;\n onQueryChange: (query: string) => void;\n /** Per-value cap — at the cap the input disables; chips stay removable. */\n cap?: number;\n /** Accessible name for the input + the listbox. */\n label: string;\n placeholder: string;\n /** Localized create-row text for the current query (e.g. \"Create 'perf-q3'\"). */\n createLabel?: (query: string) => string;\n /** Localized accessible name for a chip's remove button. */\n removeLabel: (label: string) => string;\n /** Listbox empty state (e.g. \"No components defined\"). */\n emptyText?: string;\n /** Quiet line under the box (cap notice, admin link) — the mock's field-hint. */\n hint?: ReactNode;\n /** Inline error (the typed 422), announced via role=\"alert\". */\n error?: string | null;\n /** Disables the whole control (a pending action). */\n disabled?: boolean;\n /** Extra classes for the chip-input BOX (the bordered element) — e.g. the\n * filter builder's dashed pending-row treatment (Subtask 6.1.4). */\n className?: string;\n}\n\n// Static class maps — Tailwind needs literal class strings per tint. The chip\n// background routes through the DEDICATED `--el-label-1..6` ramp (MOTIR-1274 ·\n// 1266.3): the picker's only `tint` consumer is the label chip (lib/labels/\n// labelTint.ts maps a name → one of these six in this exact order), so a palette\n// can tune label hues apart from roles/avatars. Each `--el-label-N` defaults to\n// its prior `--el-tint-*` value, so this is a zero-visual-change rename.\nconst TINT_CHIP: Record<MultiSelectTint, string> = {\n peach: 'bg-(--el-label-1)',\n rose: 'bg-(--el-label-2)',\n mint: 'bg-(--el-label-3)',\n lavender: 'bg-(--el-label-4)',\n sky: 'bg-(--el-label-5)',\n yellow: 'bg-(--el-label-6)',\n};\n\n/**\n * A value chip — exported so the rail cards' DISPLAY / read-only modes render\n * the exact same chip the picker edits (mock panels 0 / 2 / 3). Neutral by\n * default (the Pill-neutral colours); a `tint` puts the hue in the background\n * with `--el-text-strong` text (finding #35 AA). With `onRemove` the trailing\n * × renders (12px, accessible name via `removeLabel`).\n */\nexport function ValueChip({\n option,\n onRemove,\n removeLabel,\n disabled,\n}: {\n option: MultiSelectOption;\n onRemove?: (value: MultiSelectOption) => void;\n removeLabel?: (label: string) => string;\n disabled?: boolean;\n}) {\n const Glyph = option.glyph;\n return (\n <span\n className={cn(\n 'inline-flex max-w-full items-center gap-1.5 rounded-(--radius-badge) px-(--spacing-chip-x) py-(--spacing-chip-y) font-sans text-xs font-medium',\n option.tint\n ? cn(TINT_CHIP[option.tint], 'text-(--el-text-strong)')\n : 'bg-(--el-chip-bg) text-(--el-text-secondary) border border-(--el-chip-border)',\n )}\n >\n {Glyph ? <Glyph className=\"h-3 w-3 shrink-0 text-(--el-icon-muted)\" aria-hidden /> : null}\n <span className=\"min-w-0 truncate\">{option.label}</span>\n {onRemove ? (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={() => onRemove(option)}\n aria-label={removeLabel ? removeLabel(option.label) : option.label}\n className={cn(\n '-mr-1 inline-flex items-center justify-center rounded-(--radius-badge) p-px focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:outline-none',\n option.tint\n ? 'text-(--el-text-strong) opacity-65 hover:bg-(--el-text-strong)/10 hover:opacity-100'\n : 'text-(--el-icon-muted) hover:bg-(--el-muted) hover:text-(--el-text)',\n )}\n >\n <X className=\"h-3 w-3\" aria-hidden />\n </button>\n ) : null}\n </span>\n );\n}\n\n/** A pulse-skeleton chip (the mock's panel-6 loading grammar, at chip size). */\nexport function ChipSkeleton({ className }: { className?: string }) {\n return (\n <span\n className={cn('h-[22px] animate-pulse rounded-(--radius-badge) bg-(--el-muted)', className)}\n aria-hidden\n />\n );\n}\n\nexport function MultiSelectPicker({\n values,\n options,\n onToggle,\n onRemove,\n onCreate,\n query,\n onQueryChange,\n cap,\n label,\n placeholder,\n createLabel,\n removeLabel,\n emptyText,\n hint,\n error,\n disabled,\n className,\n}: MultiSelectPickerProps) {\n const baseId = useId();\n const listId = `${baseId}-listbox`;\n const rootRef = useRef<HTMLDivElement>(null);\n const boxRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n // The listbox is PORTALED to <body> with viewport-anchored `position: fixed`\n // (MOTIR-1346) so it escapes the Advanced-filter popover's `overflow-hidden`\n // and its `overflow-y-auto` scroll body — an in-popover absolute panel is\n // trapped by those and gets cut off near the edge. Same mechanism as the\n // Combobox menu (the bug-inline-edit-clipped-when-table-short fix). Only render\n // the portal once mounted, since createPortal needs document.body.\n const mounted = useMounted();\n // The portal target: the element in native full screen while there is one,\n // else <body> — the browser paints nothing outside a full-screen element\n // (MOTIR-7658) — unless that element is inside the menu (MOTIR-8560).\n const fullscreenContainer = usePortalContainer(menuRef);\n // Portal everywhere EXCEPT inside a focus-trapping modal (the Modal primitive,\n // `data-surface=\"modal\"`), where a body-portaled option click fights the trap /\n // dismisses the modal — there we render the listbox INLINE. Same rule + marker\n // as the Combobox menu. Detected in a layout effect (not during render) so it's\n // set before paint with no ref-read-in-render.\n const [inModal, setInModal] = useState(false);\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n const [listMaxHeight, setListMaxHeight] = useState(256);\n\n const atCap = cap != null && values.length >= cap;\n const inputDisabled = disabled || atCap;\n\n // The create-row appends when the trimmed query matches no option AND no\n // selected value case-insensitively (a case-insensitive match surfaces the\n // existing casing instead — mock panel 2).\n const trimmed = query.trim();\n const lower = trimmed.toLowerCase();\n const hasExactMatch =\n options.some((o) => o.label.toLowerCase() === lower) ||\n values.some((v) => v.label.toLowerCase() === lower);\n const showCreate = !!onCreate && trimmed.length > 0 && !hasExactMatch;\n\n // The navigable rows: options first, the create-row last.\n const rowCount = options.length + (showCreate ? 1 : 0);\n const clampedActive = rowCount === 0 ? 0 : Math.min(activeIndex, rowCount - 1);\n const activeId = open && rowCount > 0 ? `${baseId}-opt-${clampedActive}` : undefined;\n\n // Close on outside pointerdown (the picker stays open across toggles). The\n // listbox is portaled out of rootRef, so a click on it must count as \"inside\"\n // (else the option click closes the menu before it registers).\n useEffect(() => {\n if (!open) return;\n function onPointerDown(e: PointerEvent) {\n const t = e.target as Node;\n if (rootRef.current?.contains(t) || menuRef.current?.contains(t)) return;\n setOpen(false);\n }\n document.addEventListener('pointerdown', onPointerDown);\n return () => document.removeEventListener('pointerdown', onPointerDown);\n }, [open]);\n\n // Anchor the portaled listbox to the box in VIEWPORT coordinates (position:\n // fixed), flipping above when there's more room there and capping the height to\n // the available space so it never runs off-screen. Recomputed on open / scroll\n // / resize. (Mirrors Combobox.updatePosition — the proven, E2E-stable mechanism:\n // because the menu lives at <body> and is fully visible, a click never has to\n // scroll a clipped container to reach an option, so it stays put.)\n const updatePosition = useCallback(() => {\n const box = boxRef.current;\n if (!box) return;\n const rect = box.getBoundingClientRect();\n const gap = 4; // matches the old mt-1\n const viewportH = window.innerHeight;\n const spaceBelow = viewportH - rect.bottom - gap;\n const spaceAbove = rect.top - gap;\n const placeBelow = spaceBelow >= spaceAbove;\n const avail = Math.max(120, placeBelow ? spaceBelow : spaceAbove);\n setListMaxHeight(Math.max(80, Math.min(256, avail - 8)));\n const style: CSSProperties = {\n position: 'fixed',\n left: Math.round(rect.left),\n minWidth: Math.round(rect.width),\n };\n if (placeBelow) style.top = Math.round(rect.bottom + gap);\n else style.bottom = Math.round(viewportH - rect.top + gap);\n setMenuStyle(style);\n }, []);\n\n // Detect (before paint) whether we sit inside a focus-trapping modal, to pick\n // the inline vs portal branch. Read in an effect, not during render.\n useIsomorphicLayoutEffect(() => {\n if (!open) return;\n setInModal(!!boxRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]'));\n }, [open]);\n\n // Position before paint, and keep it glued to the box while open (capture-phase\n // scroll so a scrolling dialog body re-anchors it).\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted) || inModal) return; // inline (modal) branch positions via CSS\n updatePosition();\n const onReflow = () => updatePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, inModal, updatePosition, rowCount]);\n\n // The caller owns the query across a create: a rejected name stays in the\n // input for correction (mock panel 2); the card clears it on success.\n function commitRow(index: number) {\n const option = options[index];\n if (option) onToggle(option);\n else if (showCreate) onCreate?.(trimmed);\n }\n\n function onKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {\n if (e.key === 'Escape') {\n if (open) {\n // Consume the Esc that closes OUR listbox (stopPropagation included):\n // inside an enclosing dismissable layer (the 6.1.4 filter builder's\n // Radix Popover) the same keydown would otherwise also close the\n // whole dialog. With the listbox already closed, Esc passes through\n // to the container — the layered-dismiss order users expect.\n e.preventDefault();\n e.stopPropagation();\n setOpen(false);\n }\n return;\n }\n if (e.key === 'Backspace' && query.length === 0) {\n const last = values[values.length - 1];\n if (last) onRemove(last);\n return;\n }\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n if (!open) {\n setOpen(true);\n return;\n }\n if (rowCount === 0) return;\n const delta = e.key === 'ArrowDown' ? 1 : -1;\n setActiveIndex((clampedActive + delta + rowCount) % rowCount);\n return;\n }\n if (e.key === 'Enter') {\n e.preventDefault();\n if (open && rowCount > 0) commitRow(clampedActive);\n }\n }\n\n // Inside a focus-trapping modal: render the listbox INLINE (in rootRef's\n // subtree). Otherwise: portal it to <body> so it escapes clipping ancestors.\n const wrapMenu = (el: ReactNode) =>\n inModal ? el : createPortal(el, fullscreenContainer ?? document.body);\n\n return (\n // `data-inner-dismiss` (set while the listbox is open) lets an enclosing\n // Radix layer's `onEscapeKeyDown` defer to us: Radix listens for Escape\n // in the CAPTURE phase at the document, so our own bubble-phase handler\n // alone can't stop it from dismissing the whole popover/dialog first\n // (the 6.1.4 filter builder hit exactly this).\n <div ref={rootRef} data-inner-dismiss={open ? true : undefined} className=\"relative font-sans\">\n {/* The box — input-shaped (mock panel 1): chips + the filter input. A\n click anywhere focuses the input; at the cap the box goes quiet but\n chips stay removable. */}\n <div\n ref={boxRef}\n onClick={() => inputRef.current?.focus()}\n className={cn(\n 'flex min-h-(--height-input) cursor-text flex-wrap items-center gap-1.5 rounded-(--radius-input) border border-(--el-border) bg-(--el-page-bg) px-(--spacing-control-x) py-(--spacing-control-y)',\n 'focus-within:ring-2 focus-within:ring-(--focus-ring-color) focus-within:ring-offset-1',\n inputDisabled && 'cursor-not-allowed bg-(--el-surface-soft)',\n className,\n )}\n >\n {values.map((v) => (\n <ValueChip\n key={v.id}\n option={v}\n onRemove={onRemove}\n removeLabel={removeLabel}\n disabled={disabled}\n />\n ))}\n <input\n ref={inputRef}\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n aria-activedescendant={activeId}\n aria-autocomplete=\"list\"\n aria-label={label}\n disabled={inputDisabled}\n value={query}\n placeholder={values.length === 0 ? placeholder : undefined}\n onChange={(e) => {\n onQueryChange(e.target.value);\n setOpen(true);\n setActiveIndex(0);\n }}\n onFocus={() => setOpen(true)}\n onKeyDown={onKeyDown}\n // `bg-transparent`, so the placeholder paints on the WRAPPER's fill —\n // `--el-surface-soft` once the picker is disabled, where muted is\n // 4.34:1. Secondary is 6.51:1 there and 6.80:1 on the enabled white.\n className=\"min-w-[60px] flex-1 border-0 bg-transparent py-0.5 text-sm text-(--el-text) outline-none placeholder:text-(--el-text-secondary) disabled:cursor-not-allowed\"\n />\n </div>\n\n {open && mounted\n ? wrapMenu(\n <div\n ref={menuRef}\n id={listId}\n role=\"listbox\"\n aria-multiselectable=\"true\"\n aria-label={label}\n data-menu-surface=\"\"\n // Portal branch: viewport-anchored `position: fixed` (hidden until\n // positioned to avoid a first-paint flash at 0,0). Inline (modal)\n // branch: absolute, glued below the box, capped at max-h-64.\n style={\n inModal\n ? { maxHeight: listMaxHeight }\n : {\n ...(menuStyle ?? { position: 'fixed', visibility: 'hidden' }),\n maxHeight: listMaxHeight,\n }\n }\n className={cn(\n 'w-max max-w-72 min-w-60 overflow-y-auto rounded-(--radius-card) border border-(--el-border) bg-(--el-page-bg) p-1 shadow-(--shadow-elevated)',\n inModal ? 'absolute top-full left-0 z-10 mt-1 max-h-64' : 'z-50',\n )}\n >\n {options.map((opt, i) => {\n const selected = values.some((v) => v.id === opt.id);\n const Glyph = opt.glyph;\n return (\n <button\n key={opt.id}\n id={`${baseId}-opt-${i}`}\n type=\"button\"\n role=\"option\"\n aria-selected={selected}\n disabled={disabled}\n // Keep focus in the input across a toggle (the menu stays open).\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => commitRow(i)}\n onMouseEnter={() => setActiveIndex(i)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-left text-sm text-(--el-text) hover:bg-(--el-option-active-bg) focus-visible:outline-none',\n i === clampedActive && 'bg-(--el-option-active-bg)',\n )}\n >\n <span className=\"flex w-[22px] shrink-0 items-center justify-center text-(--el-icon-muted)\">\n {opt.tint ? (\n <span\n aria-hidden\n className={cn(\n 'h-2.5 w-2.5 rounded-full border border-(--el-border)',\n TINT_CHIP[opt.tint],\n )}\n />\n ) : Glyph ? (\n <Glyph className=\"h-3.5 w-3.5\" aria-hidden />\n ) : null}\n </span>\n <span className=\"min-w-0 flex-1 truncate\">{opt.label}</span>\n <Check\n aria-hidden\n className={cn(\n 'h-4 w-4 shrink-0 text-(--el-accent-on-surface)',\n selected ? 'opacity-100' : 'opacity-0',\n )}\n />\n </button>\n );\n })}\n\n {showCreate ? (\n <button\n id={`${baseId}-opt-${options.length}`}\n type=\"button\"\n role=\"option\"\n aria-selected={false}\n disabled={disabled}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => commitRow(options.length)}\n onMouseEnter={() => setActiveIndex(options.length)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-left text-sm text-(--el-text) hover:bg-(--el-option-active-bg) focus-visible:outline-none',\n clampedActive === options.length && 'bg-(--el-option-active-bg)',\n )}\n >\n <span className=\"flex w-[22px] shrink-0 items-center justify-center text-(--el-accent-on-surface)\">\n <Plus className=\"h-3.5 w-3.5\" aria-hidden />\n </span>\n <span className=\"min-w-0 flex-1 truncate\">\n {createLabel ? createLabel(trimmed) : trimmed}\n </span>\n </button>\n ) : null}\n\n {options.length === 0 && !showCreate ? (\n <div className=\"px-(--spacing-control-x) py-(--spacing-control-y) text-sm text-(--el-text-muted)\">\n {emptyText}\n </div>\n ) : null}\n </div>,\n )\n : null}\n\n {error ? (\n <p\n role=\"alert\"\n className=\"mt-1.5 flex items-center gap-1.5 font-sans text-xs text-(--el-danger)\"\n >\n <TriangleAlert className=\"h-3.5 w-3.5 shrink-0\" aria-hidden />\n {error}\n </p>\n ) : null}\n {!error && hint ? (\n <div className=\"mt-1.5 font-sans text-xs text-(--el-text-helper)\">{hint}</div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/cn.ts","../../../src/utils/fullscreen.ts","../../../src/components/ui/MultiSelectPicker.tsx"],"names":["useSyncExternalStore","useState"],"mappings":"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","file":"MultiSelectPicker.js","sourcesContent":["import { clsx, type ClassValue } from 'clsx';\nimport { twMerge } from 'tailwind-merge';\n\n/**\n * Merge Tailwind class names without conflicts.\n *\n * `clsx` handles conditional inclusion (booleans, undefineds, arrays);\n * `twMerge` resolves conflicting Tailwind classes (later wins, e.g.\n * `px-2 px-4` → `px-4`). Together they are the de facto standard for\n * composing Tailwind classes in React components.\n *\n * @example\n * cn('px-2 py-1', isActive && 'bg-primary', className)\n */\nexport function cn(...inputs: ClassValue[]): string {\n return twMerge(clsx(inputs));\n}\n","'use client';\n\nimport { useState, useSyncExternalStore, type RefObject } from 'react';\n\n/**\n * The element currently in NATIVE full screen (`document.fullscreenElement`), or\n * `null` — re-read on every `fullscreenchange`.\n *\n * While an element is in native full screen the browser paints ONLY that\n * element's subtree, in the top layer. Anything portalled to `document.body` —\n * a dialog, a popover, a picker's menu — is not shown at all, whatever its\n * z-index (MOTIR-7658: the roadmap canvas's quick view opened invisibly and\n * held focus and scroll lock until Esc).\n *\n * A portalling primitive does not use this directly: it uses\n * `usePortalContainer`, which also refuses to follow full screen into the\n * primitive's own subtree.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function useFullscreenElement(): Element | null {\n return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n}\n\nfunction subscribe(onChange: () => void): () => void {\n document.addEventListener('fullscreenchange', onChange);\n return () => document.removeEventListener('fullscreenchange', onChange);\n}\n\nfunction getSnapshot(): Element | null {\n return document.fullscreenElement ?? null;\n}\n\nfunction getServerSnapshot(): null {\n return null;\n}\n\n/**\n * The element a portalling surface mounts into: the element in native full\n * screen while there is one (MOTIR-7658), else `null` — which the caller turns\n * into `document.body`.\n *\n * `own` is the surface's portalled root (a dialog's content, a menu). When the\n * element that goes full screen is INSIDE it — the native full-screen button of\n * a `<video>` in a dialog — the container stays where it was (MOTIR-8560).\n * Following it would re-portal the surface into its own descendant: React\n * unmounts the subtree to move it, the full-screen element leaves the document,\n * the browser exits full screen, and the surface snaps back to a fresh copy.\n *\n * `null` on the server and in a document with no Fullscreen API.\n */\nexport function usePortalContainer(own: RefObject<Element | null>): Element | null {\n const [store] = useState(() => createPortalContainerStore(own));\n return useSyncExternalStore(store.subscribe, store.getSnapshot, getServerSnapshot);\n}\n\nfunction createPortalContainerStore(own: RefObject<Element | null>) {\n let container: Element | null =\n typeof document === 'undefined' ? null : (document.fullscreenElement ?? null);\n return {\n subscribe(onChange: () => void): () => void {\n const sync = () => {\n const next = document.fullscreenElement ?? null;\n const root = own.current;\n // The surface's own descendant went full screen: stay put.\n if (next !== null && root !== null && root.contains(next)) return;\n if (next === container) return;\n container = next;\n onChange();\n };\n // A change between creating the store and subscribing to it.\n sync();\n document.addEventListener('fullscreenchange', sync);\n return () => document.removeEventListener('fullscreenchange', sync);\n },\n getSnapshot: (): Element | null => container,\n };\n}\n","'use client';\n\nimport {\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useRef,\n useState,\n useSyncExternalStore,\n type CSSProperties,\n type ComponentType,\n type ReactNode,\n} from 'react';\nimport { createPortal } from 'react-dom';\nimport { Check, Plus, TriangleAlert, X } from 'lucide-react';\nimport { cn } from '../../utils/cn';\nimport { usePortalContainer } from '../../utils/fullscreen';\n\n// Run the position measurement before paint on the client; fall back to\n// useEffect during SSR (the listbox only ever opens client-side).\nconst useIsomorphicLayoutEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect;\n\n// `false` during SSR / first hydration render, `true` on the client thereafter —\n// gates the createPortal call so document.body is guaranteed present (mirrors\n// Combobox's useMounted). Hydration-safe and avoids setState-in-effect.\nconst subscribeNoop = () => () => {};\nfunction useMounted() {\n return useSyncExternalStore(\n subscribeNoop,\n () => true,\n () => false,\n );\n}\n\n// MultiSelectPicker (Story 5.4 · Subtask 5.4.8) — the generic chip-input\n// primitive, per design/work-items/labels-components-watch.mock.html panel 1:\n// value chips (label · remove ×) + a type-to-filter input in an input-shaped\n// box, with an anchored `aria-multiselectable` listbox of OptionRow-vocabulary\n// rows (22px glyph slot · truncating label · trailing Check in --el-accent).\n// The ONE new primitive the story earns — labels, components, and the future\n// Epic-6 facet editors all compose it.\n//\n// PURE: options in, selection out — NO fetching inside. The caller owns the\n// option set (filtering / debounced autocomplete) via the controlled `query`;\n// `onCreate` adds the folksonomy create-row when the query matches nothing;\n// `cap` disables the input at the limit (chips stay removable); a value's\n// `tint` colours its chip + its option row's swatch dot (labels pass the\n// name-hash tint; components pass none and get the neutral default).\n//\n// Keyboard (complete, the mock's panel-1 legend): type filters · ↑↓ moves the\n// active row · Enter toggles it (multi-select: the menu does NOT close) ·\n// Backspace on an empty input removes the last chip · Esc closes and returns\n// focus to the input. `aria-activedescendant` tracks the active row, per the\n// shipped Combobox a11y bar.\n\n/** One of the six `--el-tint-*` pastel token names (chip + swatch colour). */\nexport type MultiSelectTint = 'peach' | 'rose' | 'mint' | 'lavender' | 'sky' | 'yellow';\n\nexport interface MultiSelectOption {\n id: string;\n label: string;\n /** Pastel tint for the chip + the option row's swatch dot; absent = neutral. */\n tint?: MultiSelectTint;\n /** Glyph for the option row's 22px slot and the chip (e.g. the component glyph). */\n glyph?: ComponentType<{ className?: string }>;\n}\n\nexport interface MultiSelectPickerProps {\n /** The selected values, rendered as chips. */\n values: MultiSelectOption[];\n /** The current option set — the caller filters/fetches; no fetching inside. */\n options: MultiSelectOption[];\n /** Toggle an option from the listbox (add when unselected, remove when selected). */\n onToggle: (option: MultiSelectOption) => void;\n /** Remove a chip (the × button / Backspace-on-empty path). */\n onRemove: (value: MultiSelectOption) => void;\n /**\n * Folksonomy create — when present, a query matching no option (and no\n * selected value) case-insensitively appends the create-row.\n */\n onCreate?: (name: string) => void;\n /** The controlled filter query (the caller debounces its fetch off this). */\n query: string;\n onQueryChange: (query: string) => void;\n /** Per-value cap — at the cap the input disables; chips stay removable. */\n cap?: number;\n /** Accessible name for the input + the listbox. */\n label: string;\n placeholder: string;\n /** Localized create-row text for the current query (e.g. \"Create 'perf-q3'\"). */\n createLabel?: (query: string) => string;\n /** Localized accessible name for a chip's remove button. */\n removeLabel: (label: string) => string;\n /** Listbox empty state (e.g. \"No components defined\"). */\n emptyText?: string;\n /** Quiet line under the box (cap notice, admin link) — the mock's field-hint. */\n hint?: ReactNode;\n /** Inline error (the typed 422), announced via role=\"alert\". */\n error?: string | null;\n /** Disables the whole control (a pending action). */\n disabled?: boolean;\n /** Extra classes for the chip-input BOX (the bordered element) — e.g. the\n * filter builder's dashed pending-row treatment (Subtask 6.1.4). */\n className?: string;\n}\n\n// Static class maps — Tailwind needs literal class strings per tint. The chip\n// background routes through the DEDICATED `--el-label-1..6` ramp (MOTIR-1274 ·\n// 1266.3): the picker's only `tint` consumer is the label chip (lib/labels/\n// labelTint.ts maps a name → one of these six in this exact order), so a palette\n// can tune label hues apart from roles/avatars. Each `--el-label-N` defaults to\n// its prior `--el-tint-*` value, so this is a zero-visual-change rename.\nconst TINT_CHIP: Record<MultiSelectTint, string> = {\n peach: 'bg-(--el-label-1)',\n rose: 'bg-(--el-label-2)',\n mint: 'bg-(--el-label-3)',\n lavender: 'bg-(--el-label-4)',\n sky: 'bg-(--el-label-5)',\n yellow: 'bg-(--el-label-6)',\n};\n\n/**\n * A value chip — exported so the rail cards' DISPLAY / read-only modes render\n * the exact same chip the picker edits (mock panels 0 / 2 / 3). Neutral by\n * default (the Pill-neutral colours); a `tint` puts the hue in the background\n * with `--el-text-strong` text (finding #35 AA). With `onRemove` the trailing\n * × renders (12px, accessible name via `removeLabel`).\n */\nexport function ValueChip({\n option,\n onRemove,\n removeLabel,\n disabled,\n}: {\n option: MultiSelectOption;\n onRemove?: (value: MultiSelectOption) => void;\n removeLabel?: (label: string) => string;\n disabled?: boolean;\n}) {\n const Glyph = option.glyph;\n return (\n <span\n className={cn(\n 'inline-flex max-w-full items-center gap-1.5 rounded-(--radius-badge) px-(--spacing-chip-x) py-(--spacing-chip-y) font-sans text-xs font-medium',\n option.tint\n ? cn(TINT_CHIP[option.tint], 'text-(--el-text-strong)')\n : 'bg-(--el-chip-bg) text-(--el-text-secondary) border border-(--el-chip-border)',\n )}\n >\n {Glyph ? <Glyph className=\"h-3 w-3 shrink-0 text-(--el-icon-muted)\" aria-hidden /> : null}\n <span className=\"min-w-0 truncate\">{option.label}</span>\n {onRemove ? (\n <button\n type=\"button\"\n disabled={disabled}\n onClick={() => onRemove(option)}\n aria-label={removeLabel ? removeLabel(option.label) : option.label}\n className={cn(\n '-mr-1 inline-flex items-center justify-center rounded-(--radius-badge) p-px focus-visible:ring-2 focus-visible:ring-(--focus-ring-color) focus-visible:outline-none',\n option.tint\n ? 'text-(--el-text-strong) opacity-65 hover:bg-(--el-text-strong)/10 hover:opacity-100'\n : 'text-(--el-icon-muted) hover:bg-(--el-muted) hover:text-(--el-text)',\n )}\n >\n <X className=\"h-3 w-3\" aria-hidden />\n </button>\n ) : null}\n </span>\n );\n}\n\n/** A pulse-skeleton chip (the mock's panel-6 loading grammar, at chip size). */\nexport function ChipSkeleton({ className }: { className?: string }) {\n return (\n <span\n className={cn('h-[22px] animate-pulse rounded-(--radius-badge) bg-(--el-muted)', className)}\n aria-hidden\n />\n );\n}\n\nexport function MultiSelectPicker({\n values,\n options,\n onToggle,\n onRemove,\n onCreate,\n query,\n onQueryChange,\n cap,\n label,\n placeholder,\n createLabel,\n removeLabel,\n emptyText,\n hint,\n error,\n disabled,\n className,\n}: MultiSelectPickerProps) {\n const baseId = useId();\n const listId = `${baseId}-listbox`;\n const rootRef = useRef<HTMLDivElement>(null);\n const boxRef = useRef<HTMLDivElement>(null);\n const inputRef = useRef<HTMLInputElement>(null);\n const menuRef = useRef<HTMLDivElement>(null);\n const [open, setOpen] = useState(false);\n const [activeIndex, setActiveIndex] = useState(0);\n // The listbox is PORTALED to <body> with viewport-anchored `position: fixed`\n // (MOTIR-1346) so it escapes the Advanced-filter popover's `overflow-hidden`\n // and its `overflow-y-auto` scroll body — an in-popover absolute panel is\n // trapped by those and gets cut off near the edge. Same mechanism as the\n // Combobox menu (the bug-inline-edit-clipped-when-table-short fix). Only render\n // the portal once mounted, since createPortal needs document.body.\n const mounted = useMounted();\n // The portal target: the element in native full screen while there is one,\n // else <body> — the browser paints nothing outside a full-screen element\n // (MOTIR-7658) — unless that element is inside the menu (MOTIR-8560).\n const fullscreenContainer = usePortalContainer(menuRef);\n // Portal everywhere EXCEPT inside a focus-trapping modal (the Modal primitive,\n // `data-surface=\"modal\"`), where a body-portaled option click fights the trap /\n // dismisses the modal — there we render the listbox INLINE. Same rule + marker\n // as the Combobox menu. Detected in a layout effect (not during render) so it's\n // set before paint with no ref-read-in-render.\n const [inModal, setInModal] = useState(false);\n const [menuStyle, setMenuStyle] = useState<CSSProperties | null>(null);\n const [listMaxHeight, setListMaxHeight] = useState(256);\n\n const atCap = cap != null && values.length >= cap;\n const inputDisabled = disabled || atCap;\n\n // The create-row appends when the trimmed query matches no option AND no\n // selected value case-insensitively (a case-insensitive match surfaces the\n // existing casing instead — mock panel 2).\n const trimmed = query.trim();\n const lower = trimmed.toLowerCase();\n const hasExactMatch =\n options.some((o) => o.label.toLowerCase() === lower) ||\n values.some((v) => v.label.toLowerCase() === lower);\n const showCreate = !!onCreate && trimmed.length > 0 && !hasExactMatch;\n\n // The navigable rows: options first, the create-row last.\n const rowCount = options.length + (showCreate ? 1 : 0);\n const clampedActive = rowCount === 0 ? 0 : Math.min(activeIndex, rowCount - 1);\n const activeId = open && rowCount > 0 ? `${baseId}-opt-${clampedActive}` : undefined;\n\n // Close on outside pointerdown (the picker stays open across toggles). The\n // listbox is portaled out of rootRef, so a click on it must count as \"inside\"\n // (else the option click closes the menu before it registers).\n useEffect(() => {\n if (!open) return;\n function onPointerDown(e: PointerEvent) {\n const t = e.target as Node;\n if (rootRef.current?.contains(t) || menuRef.current?.contains(t)) return;\n setOpen(false);\n }\n document.addEventListener('pointerdown', onPointerDown);\n return () => document.removeEventListener('pointerdown', onPointerDown);\n }, [open]);\n\n // Anchor the portaled listbox to the box in VIEWPORT coordinates (position:\n // fixed), flipping above when there's more room there and capping the height to\n // the available space so it never runs off-screen. Recomputed on open / scroll\n // / resize. (Mirrors Combobox.updatePosition — the proven, E2E-stable mechanism:\n // because the menu lives at <body> and is fully visible, a click never has to\n // scroll a clipped container to reach an option, so it stays put.)\n const updatePosition = useCallback(() => {\n const box = boxRef.current;\n if (!box) return;\n const rect = box.getBoundingClientRect();\n const gap = 4; // matches the old mt-1\n const viewportH = window.innerHeight;\n const spaceBelow = viewportH - rect.bottom - gap;\n const spaceAbove = rect.top - gap;\n const placeBelow = spaceBelow >= spaceAbove;\n const avail = Math.max(120, placeBelow ? spaceBelow : spaceAbove);\n setListMaxHeight(Math.max(80, Math.min(256, avail - 8)));\n const style: CSSProperties = {\n position: 'fixed',\n left: Math.round(rect.left),\n minWidth: Math.round(rect.width),\n };\n if (placeBelow) style.top = Math.round(rect.bottom + gap);\n else style.bottom = Math.round(viewportH - rect.top + gap);\n setMenuStyle(style);\n }, []);\n\n // Detect (before paint) whether we sit inside a focus-trapping modal, to pick\n // the inline vs portal branch. Read in an effect, not during render.\n useIsomorphicLayoutEffect(() => {\n if (!open) return;\n setInModal(!!boxRef.current?.closest('[data-surface=\"modal\"],[aria-modal=\"true\"]'));\n }, [open]);\n\n // Position before paint, and keep it glued to the box while open (capture-phase\n // scroll so a scrolling dialog body re-anchors it).\n useIsomorphicLayoutEffect(() => {\n if (!(open && mounted) || inModal) return; // inline (modal) branch positions via CSS\n updatePosition();\n const onReflow = () => updatePosition();\n window.addEventListener('scroll', onReflow, true);\n window.addEventListener('resize', onReflow);\n return () => {\n window.removeEventListener('scroll', onReflow, true);\n window.removeEventListener('resize', onReflow);\n };\n }, [open, mounted, inModal, updatePosition, rowCount]);\n\n // The caller owns the query across a create: a rejected name stays in the\n // input for correction (mock panel 2); the card clears it on success.\n function commitRow(index: number) {\n const option = options[index];\n if (option) onToggle(option);\n else if (showCreate) onCreate?.(trimmed);\n }\n\n function onKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {\n if (e.key === 'Escape') {\n if (open) {\n // Consume the Esc that closes OUR listbox (stopPropagation included):\n // inside an enclosing dismissable layer (the 6.1.4 filter builder's\n // Radix Popover) the same keydown would otherwise also close the\n // whole dialog. With the listbox already closed, Esc passes through\n // to the container — the layered-dismiss order users expect.\n e.preventDefault();\n e.stopPropagation();\n setOpen(false);\n }\n return;\n }\n if (e.key === 'Backspace' && query.length === 0) {\n const last = values[values.length - 1];\n if (last) onRemove(last);\n return;\n }\n if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {\n e.preventDefault();\n if (!open) {\n setOpen(true);\n return;\n }\n if (rowCount === 0) return;\n const delta = e.key === 'ArrowDown' ? 1 : -1;\n setActiveIndex((clampedActive + delta + rowCount) % rowCount);\n return;\n }\n if (e.key === 'Enter') {\n e.preventDefault();\n if (open && rowCount > 0) commitRow(clampedActive);\n }\n }\n\n // Inside a focus-trapping modal: render the listbox INLINE (in rootRef's\n // subtree). Otherwise: portal it to <body> so it escapes clipping ancestors.\n const wrapMenu = (el: ReactNode) =>\n inModal ? el : createPortal(el, fullscreenContainer ?? document.body);\n\n return (\n // `data-inner-dismiss` (set while the listbox is open) lets an enclosing\n // Radix layer's `onEscapeKeyDown` defer to us: Radix listens for Escape\n // in the CAPTURE phase at the document, so our own bubble-phase handler\n // alone can't stop it from dismissing the whole popover/dialog first\n // (the 6.1.4 filter builder hit exactly this).\n <div ref={rootRef} data-inner-dismiss={open ? true : undefined} className=\"relative font-sans\">\n {/* The box — input-shaped (mock panel 1): chips + the filter input. A\n click anywhere focuses the input; at the cap the box goes quiet but\n chips stay removable. */}\n <div\n ref={boxRef}\n onClick={() => inputRef.current?.focus()}\n className={cn(\n 'flex min-h-(--height-input) cursor-text flex-wrap items-center gap-1.5 rounded-(--radius-input) border border-(--el-border) bg-(--el-page-bg) px-(--spacing-control-x) py-(--spacing-control-y)',\n 'focus-within:ring-2 focus-within:ring-(--focus-ring-color) focus-within:ring-offset-1',\n inputDisabled && 'cursor-not-allowed bg-(--el-surface-soft)',\n className,\n )}\n >\n {values.map((v) => (\n <ValueChip\n key={v.id}\n option={v}\n onRemove={onRemove}\n removeLabel={removeLabel}\n disabled={disabled}\n />\n ))}\n <input\n ref={inputRef}\n role=\"combobox\"\n aria-expanded={open}\n aria-controls={open ? listId : undefined}\n aria-activedescendant={activeId}\n aria-autocomplete=\"list\"\n aria-label={label}\n disabled={inputDisabled}\n value={query}\n placeholder={values.length === 0 ? placeholder : undefined}\n onChange={(e) => {\n onQueryChange(e.target.value);\n setOpen(true);\n setActiveIndex(0);\n }}\n onFocus={() => setOpen(true)}\n onKeyDown={onKeyDown}\n // `bg-transparent`, so the placeholder paints on the WRAPPER's fill —\n // `--el-surface-soft` once the picker is disabled, where muted is\n // 4.34:1. Secondary is 6.51:1 there and 6.80:1 on the enabled white.\n className=\"min-w-[60px] flex-1 border-0 bg-transparent py-0.5 text-sm text-(--el-text) outline-none placeholder:text-(--el-text-secondary) disabled:cursor-not-allowed\"\n />\n </div>\n\n {open && mounted\n ? wrapMenu(\n <div\n ref={menuRef}\n id={listId}\n role=\"listbox\"\n aria-multiselectable=\"true\"\n aria-label={label}\n data-menu-surface=\"\"\n // Portal branch: viewport-anchored `position: fixed` (hidden until\n // positioned to avoid a first-paint flash at 0,0). Inline (modal)\n // branch: absolute, glued below the box, capped at max-h-64.\n style={\n inModal\n ? { maxHeight: listMaxHeight }\n : {\n ...(menuStyle ?? { position: 'fixed', visibility: 'hidden' }),\n maxHeight: listMaxHeight,\n }\n }\n className={cn(\n 'w-max max-w-72 min-w-60 overflow-y-auto rounded-(--radius-card) border border-(--el-border) bg-(--el-page-bg) p-(--spacing-card-padding-4) shadow-(--shadow-elevated)',\n inModal ? 'absolute top-full left-0 z-10 mt-1 max-h-64' : 'z-50',\n )}\n >\n {options.map((opt, i) => {\n const selected = values.some((v) => v.id === opt.id);\n const Glyph = opt.glyph;\n return (\n <button\n key={opt.id}\n id={`${baseId}-opt-${i}`}\n type=\"button\"\n role=\"option\"\n aria-selected={selected}\n disabled={disabled}\n // Keep focus in the input across a toggle (the menu stays open).\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => commitRow(i)}\n onMouseEnter={() => setActiveIndex(i)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-left text-sm text-(--el-text) hover:bg-(--el-option-active-bg) focus-visible:outline-none',\n i === clampedActive && 'bg-(--el-option-active-bg)',\n )}\n >\n <span className=\"flex w-[22px] shrink-0 items-center justify-center text-(--el-icon-muted)\">\n {opt.tint ? (\n <span\n aria-hidden\n className={cn(\n 'h-2.5 w-2.5 rounded-full border border-(--el-border)',\n TINT_CHIP[opt.tint],\n )}\n />\n ) : Glyph ? (\n <Glyph className=\"h-3.5 w-3.5\" aria-hidden />\n ) : null}\n </span>\n <span className=\"min-w-0 flex-1 truncate\">{opt.label}</span>\n <Check\n aria-hidden\n className={cn(\n 'h-4 w-4 shrink-0 text-(--el-accent-on-surface)',\n selected ? 'opacity-100' : 'opacity-0',\n )}\n />\n </button>\n );\n })}\n\n {showCreate ? (\n <button\n id={`${baseId}-opt-${options.length}`}\n type=\"button\"\n role=\"option\"\n aria-selected={false}\n disabled={disabled}\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => commitRow(options.length)}\n onMouseEnter={() => setActiveIndex(options.length)}\n className={cn(\n 'flex w-full items-center gap-2.5 rounded-(--radius-control) px-(--spacing-control-x) py-(--spacing-control-y) text-left text-sm text-(--el-text) hover:bg-(--el-option-active-bg) focus-visible:outline-none',\n clampedActive === options.length && 'bg-(--el-option-active-bg)',\n )}\n >\n <span className=\"flex w-[22px] shrink-0 items-center justify-center text-(--el-accent-on-surface)\">\n <Plus className=\"h-3.5 w-3.5\" aria-hidden />\n </span>\n <span className=\"min-w-0 flex-1 truncate\">\n {createLabel ? createLabel(trimmed) : trimmed}\n </span>\n </button>\n ) : null}\n\n {options.length === 0 && !showCreate ? (\n <div className=\"px-(--spacing-control-x) py-(--spacing-control-y) text-sm text-(--el-text-muted)\">\n {emptyText}\n </div>\n ) : null}\n </div>,\n )\n : null}\n\n {error ? (\n <p\n role=\"alert\"\n className=\"mt-1.5 flex items-center gap-1.5 font-sans text-xs text-(--el-danger)\"\n >\n <TriangleAlert className=\"h-3.5 w-3.5 shrink-0\" aria-hidden />\n {error}\n </p>\n ) : null}\n {!error && hint ? (\n <div className=\"mt-1.5 font-sans text-xs text-(--el-text-helper)\">{hint}</div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
@@ -443,6 +443,29 @@ var FONT_SET_REGISTRY = {
|
|
|
443
443
|
}
|
|
444
444
|
}
|
|
445
445
|
},
|
|
446
|
+
// Not in dooooWeb: Traditional Chinese (Taiwan) for motir.co's `/zh-tw`
|
|
447
|
+
// (MOTIR-8937). The zh-Hans set with its Traditional-form faces.
|
|
448
|
+
"zh-Hant": {
|
|
449
|
+
id: "zh-Hant",
|
|
450
|
+
label: "Traditional Chinese",
|
|
451
|
+
lang: "zh-TW",
|
|
452
|
+
cjk: true,
|
|
453
|
+
roles: {
|
|
454
|
+
sans: { default: "noto-sans-tc", members: [google("noto-sans-tc", "Noto Sans TC", false)] },
|
|
455
|
+
serif: {
|
|
456
|
+
default: "noto-serif-tc",
|
|
457
|
+
members: [
|
|
458
|
+
google("noto-serif-tc", "Noto Serif TC", false),
|
|
459
|
+
// A handwritten-style face, so a pick rather than the default.
|
|
460
|
+
google("lxgw-wenkai-tc", "LXGW WenKai TC", false)
|
|
461
|
+
]
|
|
462
|
+
},
|
|
463
|
+
mono: {
|
|
464
|
+
default: "noto-sans-tc",
|
|
465
|
+
members: [google("noto-sans-tc", "Noto Sans TC", false, "sans")]
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
},
|
|
446
469
|
// dooooWeb: `japanese`.
|
|
447
470
|
ja: {
|
|
448
471
|
id: "ja",
|
|
@@ -503,7 +526,8 @@ var LOCALE_FONT_SET = {
|
|
|
503
526
|
it: "latin",
|
|
504
527
|
nl: "latin",
|
|
505
528
|
pl: "latin",
|
|
506
|
-
pt: "latin"
|
|
529
|
+
pt: "latin",
|
|
530
|
+
"zh-tw": "zh-Hant"
|
|
507
531
|
};
|
|
508
532
|
function isFontSetLocale(value) {
|
|
509
533
|
return typeof value === "string" && FONT_SET_LOCALES.includes(value);
|