@oprt/cli 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +91 -2
- package/dist/bin.js +1653 -0
- package/dist/contract.json +1 -0
- package/dist/index.cjs +1445 -0
- package/dist/index.d.cts +211 -0
- package/dist/index.d.ts +211 -0
- package/dist/index.js +1400 -0
- package/dist/read-8a9euzJx.d.cts +246 -0
- package/dist/read-8a9euzJx.d.ts +246 -0
- package/dist/read.cjs +283 -0
- package/dist/read.d.cts +1 -0
- package/dist/read.d.ts +1 -0
- package/dist/read.js +250 -0
- package/dist/registry.json +1 -0
- package/package.json +64 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":"0.1.0","entries":[{"name":"ActionStatus","slug":"action-status","kind":"component","description":"Stateless authoritative action outcome display with optional application-supported recovery callbacks. When the focused recovery is withdrawn or disabled, focus moves to the result, a group named by its label. Long text wraps.","files":["ActionStatus/ActionStatus.manifest.ts","ActionStatus/ActionStatus.tsx","ActionStatus/index.ts","Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useActionFocus.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Alert","slug":"alert","kind":"component","description":"Inline status message with a severity icon, semantic left-edge accent, optional title, and optional dismiss control.","files":["Alert/Alert.manifest.ts","Alert/Alert.tsx","Alert/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"AppShell","slug":"app-shell","kind":"component","description":"In-page operational workspace with explicit identity, mode, connection, UTC reference time, and caller-supplied markings.","files":["AppShell/AppShell.manifest.ts","AppShell/AppShell.tsx","AppShell/index.ts","Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/navigationLandmark.ts","_internal/suggestions.ts","_internal/timestamp.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"AttachmentPreview","slug":"attachment-preview","kind":"component","description":"Named attachment metadata with an escaped text preview, safe image source or readable fallback.","files":["AttachmentPreview/AttachmentPreview.manifest.ts","AttachmentPreview/AttachmentPreview.tsx","AttachmentPreview/index.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","_internal/bytes.tsx","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Avatar","slug":"avatar","kind":"component","description":"Square, initials-based avatar. Renders monospace initials from the name, or an image when src is provided. It is named once, at its root, by alt or by name, and keeps its size in a crowded row.","files":["Avatar/Avatar.manifest.ts","Avatar/Avatar.tsx","Avatar/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Badge","slug":"badge","kind":"component","description":"Read-only uppercase status display with outlined and text appearances. Use meaningful status wording so color is not the only cue; dots and icons are optional. A status never truncates: a label that does not fit wraps, long tokens break anywhere, and the badge grows with it. An outlined badge keeps its width, so a short status is never squeezed into a broken word beside a long sibling, and is never wider than its container; a text badge can shrink with its layout. Code and kbd retain their casing; wrap other case-sensitive names or IDs in a child with normal-case tracking-normal.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Breadcrumb","slug":"breadcrumb","kind":"component","description":"Self-contained navigation trail rendered from a flat array of items, with the final (or flagged) item marked as the current page.","files":["Breadcrumb/Breadcrumb.manifest.ts","Breadcrumb/Breadcrumb.tsx","Breadcrumb/index.ts","_internal/manifest-entry.ts"],"dependencies":["@oprt/icons","clsx","react"],"devDependencies":[]},{"name":"BulkActionBar","slug":"bulk-action-bar","kind":"component","description":"Displays an explicit equipment selection snapshot, off-page count, source and filter context, with controlled bulk action requests.","files":["BulkActionBar/BulkActionBar.manifest.ts","BulkActionBar/BulkActionBar.tsx","BulkActionBar/index.ts","BulkActionBar/selection.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","DescriptionList/DescriptionList.manifest.ts","DescriptionList/DescriptionList.tsx","DescriptionList/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/timestamp.ts","_internal/unavailable.ts","_internal/useActionFocus.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"BulkResults","slug":"bulk-results","kind":"component","description":"Named per-equipment result table in selection order. Missing IDs remain Not reported; malformed result coverage is visible and never counted as success.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","BulkActionBar/BulkActionBar.manifest.ts","BulkActionBar/BulkActionBar.tsx","BulkActionBar/index.ts","BulkActionBar/selection.ts","BulkResults/BulkResults.manifest.ts","BulkResults/BulkResults.tsx","BulkResults/index.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Checkbox/Checkbox.manifest.ts","Checkbox/Checkbox.tsx","Checkbox/index.ts","DataGrid/DataGrid.manifest.ts","DataGrid/DataGrid.tsx","DataGrid/PageControls.tsx","DataGrid/index.ts","DataTable/DataTable.manifest.ts","DataTable/DataTable.tsx","DataTable/index.ts","DataTable/sort.ts","DataTable/useScrollsSideways.ts","DataTable/useTableWindow.ts","DescriptionList/DescriptionList.manifest.ts","DescriptionList/DescriptionList.tsx","DescriptionList/index.ts","Select/Select.manifest.ts","Select/Select.tsx","Select/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/numberText.tsx","_internal/overlay.ts","_internal/suggestions.ts","_internal/timestamp.ts","_internal/unavailable.ts","_internal/useActionFocus.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Button","slug":"button","kind":"component","description":"Primary interactive element for triggering actions. Short action labels display uppercase. A label that does not fit wraps and centers, and the button grows with it; a one-line button keeps its exact height, also in a row that stretches its items. A label made of text and an element (an identifier in code, a case-sensitive name in a span) wraps as one block, and an icon at either end stays beside it, drawn at 12, 16 and 20 px for sm, md and lg so it fits the one-line height. Code and kbd retain their casing, and code breaks anywhere; wrap other case-sensitive names or IDs in a child with normal-case tracking-normal oprt-wrap-anywhere. Shape: the primary variant carries a cut top-right corner at rest, and every enabled button takes a second cut at the opposite corner on hover, press and keyboard focus; an armed danger button fills with hazard stripes; segments of an attached ButtonGroup stay square. The fill and edge are drawn by a layer clipped to that shape, so bg-* and border-* classes do not change them: set --oprt-cut-fill and --oprt-cut-edge instead. The oprt-cut-none class squares off one button, or every button inside the element that carries it.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"ButtonGroup","slug":"button-group","kind":"component","description":"Segmented row or column of Buttons with collapsed shared borders. A row that does not fit wraps onto more rows and grows taller, so no action is hidden; attached segments keep their shared edges across rows and take the height of the tallest segment in their row, so the edges run straight when one label wrapped; in a detached group each segment keeps its own height. A column stacks. Attached segments stay square: the group turns off the cut a Button otherwise carries.","files":["ButtonGroup/ButtonGroup.manifest.ts","ButtonGroup/ButtonGroup.tsx","ButtonGroup/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Calendar","slug":"calendar","kind":"component","description":"Controlled Gregorian calendar with date-only strings, keyboard day/month navigation, strict date bounds and visible configuration errors.","files":["Calendar/Calendar.manifest.ts","Calendar/Calendar.tsx","Calendar/index.ts","_internal/aria.ts","_internal/date.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/timestamp.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@internationalized/date","@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Card","slug":"card","kind":"component","description":"Bordered surface container. Use with CardHeader, CardBody, CardFooter.","files":["Card/Card.manifest.ts","Card/Card.tsx","Card/index.ts","_internal/manifest-entry.ts"],"dependencies":["clsx","react"],"devDependencies":[]},{"name":"Chart","slug":"chart","kind":"component","description":"Native SVG charts with stable series identity and exact data tables. Line, area, and bar support shared pointer/keyboard readouts and an optional controlled observation window.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Chart/Chart.manifest.ts","Chart/Chart.tsx","Chart/ChartControls.tsx","Chart/geometry.ts","Chart/index.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"Checkbox","slug":"checkbox","kind":"component","description":"Binary or tri-state checkbox. Shared OPERATOR Check icon when checked and Minus icon when indeterminate, inside an accent outline.","files":["Checkbox/Checkbox.manifest.ts","Checkbox/Checkbox.tsx","Checkbox/index.ts","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Collapsible","slug":"collapsible","kind":"component","description":"Accordion container whose CollapsibleItem children expand and collapse; single or multiple open at a time.","files":["Collapsible/Collapsible.manifest.ts","Collapsible/Collapsible.tsx","Collapsible/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"CollectionState","slug":"collection-state","kind":"component","description":"Named collection state with supplied explanation and recovery actions.","files":["CollectionState/CollectionState.manifest.ts","CollectionState/CollectionState.tsx","CollectionState/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"ComboBox","slug":"combo-box","kind":"component","description":"Searchable single-value selection with keyboard navigation and caller-supplied asynchronous results.","files":["ComboBox/ComboBox.manifest.ts","ComboBox/ComboBox.tsx","ComboBox/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"CommandPalette","slug":"command-palette","kind":"component","description":"Modal command launcher: search field over a filtered, keyboard-navigable action list. Command and group labels display uppercase; search input, empty messages, and keyboard shortcuts retain their casing.","files":["CommandPalette/CommandPalette.manifest.ts","CommandPalette/CommandPalette.tsx","CommandPalette/index.ts","_internal/manifest-entry.ts","_internal/overlay.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"ConfirmDialog","slug":"confirm-dialog","kind":"component","description":"Confirmation modal for destructive or high-consequence actions. Composes Dialog + Button (+ Field + Input for typed confirmation). Flat API: pass title, optional description, onConfirm, and an optional confirmPhrase the user must type exactly to enable Confirm.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","ConfirmDialog/ConfirmDialog.manifest.ts","ConfirmDialog/ConfirmDialog.tsx","ConfirmDialog/index.ts","Dialog/Dialog.manifest.ts","Dialog/Dialog.tsx","Dialog/index.ts","Field/Field.manifest.ts","Field/Field.tsx","Field/index.ts","Input/Input.manifest.ts","Input/Input.tsx","Input/index.ts","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"ContextMenu","slug":"context-menu","kind":"component","description":"Context actions with a focusable surface and an equivalent visible menu button.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","ContextMenu/ContextMenu.manifest.ts","ContextMenu/ContextMenu.tsx","ContextMenu/index.ts","_internal/MotionPopover.tsx","_internal/devValidate.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"CoordinateInput","slug":"coordinate-input","kind":"component","description":"Decimal-degree latitude and longitude fields with bounded validation and explicit unknown or invalid position states.","files":["CoordinateInput/CoordinateInput.manifest.ts","CoordinateInput/CoordinateInput.tsx","CoordinateInput/index.ts","NumberInput/NumberInput.manifest.ts","NumberInput/NumberInput.tsx","NumberInput/index.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria-components"],"devDependencies":["react-aria-components"]},{"name":"CopyButton","slug":"copy-button","kind":"component","description":"Copies a supplied exact string with visible pending, success and failure feedback.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","CopyButton/CopyButton.manifest.ts","CopyButton/CopyButton.tsx","CopyButton/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsMounted.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"DataGrid","slug":"data-grid","kind":"component","description":"Native table with stable-ID selection, sorting, controlled column widths and pinning, saved-view state, pagination, and optional virtualization.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Checkbox/Checkbox.manifest.ts","Checkbox/Checkbox.tsx","Checkbox/index.ts","DataGrid/DataGrid.manifest.ts","DataGrid/DataGrid.tsx","DataGrid/PageControls.tsx","DataGrid/index.ts","DataTable/DataTable.manifest.ts","DataTable/DataTable.tsx","DataTable/index.ts","DataTable/sort.ts","DataTable/useScrollsSideways.ts","DataTable/useTableWindow.ts","Select/Select.manifest.ts","Select/Select.tsx","Select/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/numberText.tsx","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"DataTable","slug":"data-table","kind":"component","description":"Data-driven table with sorting, highlighting, and cell rendering.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","DataTable/DataTable.manifest.ts","DataTable/DataTable.tsx","DataTable/index.ts","DataTable/sort.ts","DataTable/useScrollsSideways.ts","DataTable/useTableWindow.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"DatePicker","slug":"date-picker","kind":"component","description":"Controlled Gregorian date-only picker with segmented editing, calendar overlay and a draft-validity signal for incomplete or rejected edits.","files":["Calendar/Calendar.manifest.ts","Calendar/Calendar.tsx","Calendar/index.ts","DatePicker/DatePicker.manifest.ts","DatePicker/DatePicker.tsx","DatePicker/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/date.ts","_internal/devValidate.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/segmentField.ts","_internal/suggestions.ts","_internal/timestamp.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@internationalized/date","@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"DateRangePicker","slug":"date-range-picker","kind":"component","description":"Controlled ordered Gregorian date range with independently editable start and end fields and separate endpoint calendars. Equal-day ranges are valid.","files":["Calendar/Calendar.manifest.ts","Calendar/Calendar.tsx","Calendar/index.ts","DatePicker/DatePicker.manifest.ts","DatePicker/DatePicker.tsx","DatePicker/index.ts","DateRangePicker/DateRangePicker.manifest.ts","DateRangePicker/DateRangePicker.tsx","DateRangePicker/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/date.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/segmentField.ts","_internal/suggestions.ts","_internal/timestamp.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@internationalized/date","@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"DateTimePicker","slug":"date-time-picker","kind":"component","description":"Controlled instant editor with an explicit IANA time zone, minute-level date/time drafts, Apply, and explicit daylight-saving overlap choices.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Calendar/Calendar.manifest.ts","Calendar/Calendar.tsx","Calendar/index.ts","DatePicker/DatePicker.manifest.ts","DatePicker/DatePicker.tsx","DatePicker/index.ts","DateTimePicker/DateTimePicker.manifest.ts","DateTimePicker/DateTimePicker.tsx","DateTimePicker/index.ts","RadioGroup/RadioGroup.manifest.ts","RadioGroup/RadioGroup.tsx","RadioGroup/index.ts","TimeField/TimeField.manifest.ts","TimeField/TimeField.tsx","TimeField/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/controlStyles.ts","_internal/date.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/segmentField.ts","_internal/suggestions.ts","_internal/timestamp.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@internationalized/date","@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"DescriptionList","slug":"description-list","kind":"component","description":"Semantic label/value facts with wrapping, exact values, and stacked, responsive aligned, or compact detail layouts.","files":["DescriptionList/DescriptionList.manifest.ts","DescriptionList/DescriptionList.tsx","DescriptionList/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/numberText.tsx","_internal/suggestions.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Dialog","slug":"dialog","kind":"component","description":"Modal dialog with a flat API: pass title, optional description, trigger, footer, and body children. Portal'd overlay + content built on React Aria Components (ModalOverlay + Modal + Dialog).","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Dialog/Dialog.manifest.ts","Dialog/Dialog.tsx","Dialog/index.ts","_internal/aria.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"Divider","slug":"divider","kind":"component","description":"Thin rule that separates content, with an optional centered uppercase label (horizontal only). A horizontal rule takes the width of its container and a vertical one the height of its flex row. A long label wraps between its two lines and breaks anywhere. A semantic labeled divider is named by its label.","files":["Divider/Divider.manifest.ts","Divider/Divider.tsx","Divider/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["clsx","react","react-aria-components"],"devDependencies":[]},{"name":"DropdownMenu","slug":"dropdown-menu","kind":"component","description":"Portal'd action menu anchored to a trigger element. Holds DropdownMenuItem, DropdownMenuSeparator, and DropdownMenuLabel children.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","DropdownMenu/DropdownMenu.manifest.ts","DropdownMenu/DropdownMenu.tsx","DropdownMenu/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/devValidate.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"EntityPreview","slug":"entity-preview","kind":"component","description":"Entity identity with aligned label/value rows for condition, structured facts and report quality in transient or persistent inspection.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","DescriptionList/DescriptionList.manifest.ts","DescriptionList/DescriptionList.tsx","DescriptionList/index.ts","EntityPreview/EntityPreview.manifest.ts","EntityPreview/EntityPreview.tsx","EntityPreview/index.ts","OperationalStatus/OperationalStatus.manifest.ts","OperationalStatus/OperationalStatus.tsx","OperationalStatus/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/timestamp.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"EventTimeline","slug":"event-timeline","kind":"component","description":"UTC event graphic with lanes, clipped duration bars, point markers, and a complete accessible event list. Event titles lead each entry, severity aligns at the trailing edge, and timestamps/categories form a secondary metadata row.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","EventTimeline/EventTimeline.manifest.ts","EventTimeline/EventTimeline.tsx","EventTimeline/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/timestamp.ts","_internal/unavailable.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Field","slug":"field","kind":"component","description":"Form field wrapper providing label, help text, and error display. Labels, help, errors, and entered values preserve their original casing. A required Field makes its control required. NumberInput, CoordinateInput and MultiSelect carry their own label and are not placed in a Field.","files":["Field/Field.manifest.ts","Field/Field.tsx","Field/index.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["clsx","react"],"devDependencies":[]},{"name":"FileUpload","slug":"file-upload","kind":"component","description":"Choose, drop or paste files with bounded validation and readable rejection feedback.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","FileUpload/FileUpload.manifest.ts","FileUpload/FileUpload.tsx","FileUpload/index.ts","_internal/bytes.tsx","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsMounted.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"FilterBar","slug":"filter-bar","kind":"component","description":"Toggle-button filters for a view, wrapping onto more rows when they do not fit. Single-select by default; set multiple for multi-select.","files":["FilterBar/FilterBar.manifest.ts","FilterBar/FilterBar.tsx","FilterBar/index.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"FormErrorSummary","slug":"form-error-summary","kind":"component","description":"Focusable error summary with actions that reveal or focus invalid form controls.","files":["FormErrorSummary/FormErrorSummary.manifest.ts","FormErrorSummary/FormErrorSummary.tsx","FormErrorSummary/index.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"FormSection","slug":"form-section","kind":"component","description":"Native fieldset with a legend, optional description and supplied form controls.","files":["FormSection/FormSection.manifest.ts","FormSection/FormSection.tsx","FormSection/index.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"InlineEdit","slug":"inline-edit","kind":"component","description":"Controlled inline text editing with explicit save, cancel and recoverable validation or save errors.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","InlineEdit/InlineEdit.manifest.ts","InlineEdit/InlineEdit.tsx","InlineEdit/index.ts","Input/Input.manifest.ts","Input/Input.tsx","Input/index.ts","Textarea/Textarea.manifest.ts","Textarea/Textarea.tsx","Textarea/index.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsMounted.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":["react-aria-components"]},{"name":"Input","slug":"input","kind":"component","description":"Single-line input with optional prefix/suffix slots and prompt-symbol variant. Date and time fields preserve native editing and picker interaction with decorative OPERATOR Calendar/Clock icons and a dark native color scheme.","files":["Input/Input.manifest.ts","Input/Input.tsx","Input/index.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":["react-aria-components"]},{"name":"Inspector","slug":"inspector","kind":"component","description":"Modeless named details aside that leaves background content and focus navigation available.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Inspector/Inspector.manifest.ts","Inspector/Inspector.tsx","Inspector/index.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"JobStatus","slug":"job-status","kind":"component","description":"Application-supplied job state and validated item progress with explicit cancellation/retry requests. Requests never establish a job outcome. A pending request is announced through a live region that is always present; native attributes reach the job region; long text wraps.","files":["ActionStatus/ActionStatus.manifest.ts","ActionStatus/ActionStatus.tsx","ActionStatus/index.ts","Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","JobStatus/JobStatus.manifest.ts","JobStatus/JobStatus.tsx","JobStatus/index.ts","Progress/Progress.manifest.ts","Progress/Progress.tsx","Progress/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useActionFocus.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"LayerControl","slug":"layer-control","kind":"component","description":"Controlled top-first map layers with visibility, opacity and keyboard-operable ordering.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Checkbox/Checkbox.manifest.ts","Checkbox/Checkbox.tsx","Checkbox/index.ts","Field/Field.manifest.ts","Field/Field.tsx","Field/index.ts","LayerControl/LayerControl.manifest.ts","LayerControl/LayerControl.tsx","LayerControl/index.ts","Slider/Slider.manifest.ts","Slider/Slider.tsx","Slider/index.ts","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/numberText.tsx","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"Link","slug":"link","kind":"component","description":"Native anchor navigation with an evident link treatment and token-based keyboard focus. It is inline, so a link in a sentence wraps with the sentence; as a flex or grid item it is a 24px target. Only http, https, mailto, tel, relative and hash destinations are rendered.","files":["Link/Link.manifest.ts","Link/Link.tsx","Link/index.ts","_internal/manifest-entry.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"MapControls","slug":"map-controls","kind":"component","description":"Engine-independent map navigation requests with supplied zoom and north-bearing readouts.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","MapControls/MapControls.manifest.ts","MapControls/MapControls.tsx","MapControls/index.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria"],"devDependencies":[]},{"name":"MapLegend","slug":"map-legend","kind":"component","description":"Text and shape map legend for supplied point, line and area meanings.","files":["MapLegend/MapLegend.manifest.ts","MapLegend/MapLegend.tsx","MapLegend/index.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"MapScale","slug":"map-scale","kind":"component","description":"Bounded 1/2/5 ground-distance scale from a supplied local map resolution.","files":["MapScale/MapScale.manifest.ts","MapScale/MapScale.tsx","MapScale/index.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria"],"devDependencies":[]},{"name":"MediaFrame","slug":"media-frame","kind":"component","description":"Named media surface with supplied freshness state, capture time and retained-frame disclosure.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","MediaFrame/MediaFrame.manifest.ts","MediaFrame/MediaFrame.tsx","MediaFrame/index.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/timestamp.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria"],"devDependencies":[]},{"name":"MultiSelect","slug":"multi-select","kind":"component","description":"Native checkbox group with shared OPERATOR Check icons, stable values, labeled options, and explicit validation.","files":["MultiSelect/MultiSelect.manifest.ts","MultiSelect/MultiSelect.tsx","MultiSelect/index.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"NumberInput","slug":"number-input","kind":"component","description":"Numeric entry with an explicit unit, keyboard stepping, range and step validation, and unknown-value state.","files":["NumberInput/NumberInput.manifest.ts","NumberInput/NumberInput.tsx","NumberInput/index.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria-components"],"devDependencies":["react-aria-components"]},{"name":"OperationalStatus","slug":"operational-status","kind":"component","description":"Application-asserted data quality, source, and UTC update time in shared label/value detail rows.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","DescriptionList/DescriptionList.manifest.ts","DescriptionList/DescriptionList.tsx","DescriptionList/index.ts","OperationalStatus/OperationalStatus.manifest.ts","OperationalStatus/OperationalStatus.tsx","OperationalStatus/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/timestamp.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"PlaybackControls","slug":"playback-controls","kind":"component","description":"Controlled UTC timestamp transport with a bounded scrubber, play/pause, optional event stepping, playback speed and return to a supplied live reference.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","PlaybackControls/PlaybackControls.manifest.ts","PlaybackControls/PlaybackControls.tsx","PlaybackControls/index.ts","Select/Select.manifest.ts","Select/Select.tsx","Select/index.ts","Slider/Slider.manifest.ts","Slider/Slider.tsx","Slider/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/numberText.tsx","_internal/overlay.ts","_internal/suggestions.ts","_internal/timestamp.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"Popover","slug":"popover","kind":"component","description":"Named nonmodal interactive details anchored to an explicitly activated trigger.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Popover/Popover.manifest.ts","Popover/Popover.tsx","Popover/index.ts","Tooltip/Tooltip.manifest.ts","Tooltip/Tooltip.tsx","Tooltip/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Progress","slug":"progress","kind":"component","description":"Determinate or indeterminate progress indicator rendered as a filled bar or a row of block glyphs. It fills the width its container gives it.","files":["Progress/Progress.manifest.ts","Progress/Progress.tsx","Progress/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"PropertyFilter","slug":"property-filter","kind":"component","description":"Controlled flat property-filter editor with text or enum values, all/any matching and removable, semantically validated clauses.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Field/Field.manifest.ts","Field/Field.tsx","Field/index.ts","Input/Input.manifest.ts","Input/Input.tsx","Input/index.ts","PropertyFilter/PropertyFilter.manifest.ts","PropertyFilter/PropertyFilter.tsx","PropertyFilter/index.ts","PropertyFilter/tokenError.ts","Select/Select.manifest.ts","Select/Select.tsx","Select/index.ts","TagGroup/TagGroup.manifest.ts","TagGroup/TagGroup.tsx","TagGroup/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"RadioGroup","slug":"radio-group","kind":"component","description":"Group of mutually exclusive options. Square outline with the shared OPERATOR Check icon when selected.","files":["RadioGroup/RadioGroup.manifest.ts","RadioGroup/RadioGroup.tsx","RadioGroup/index.ts","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Select","slug":"select","kind":"component","description":"Single-select dropdown. Trigger matches Input geometry for one line and grows when its text wraps; inside a Field it fills the Field's width. Portal'd list with an accent border and the shared OPERATOR Check icon on the selected option; an empty Select opens a list that says so.","files":["Select/Select.manifest.ts","Select/Select.tsx","Select/index.ts","_internal/MotionPopover.tsx","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Sidebar","slug":"sidebar","kind":"component","description":"Vertical navigation rail, never wider than its container. Use with SidebarLogo, SidebarSection, and SidebarFooter. For the frame of a whole app (the workspace's name, the data connection, the time), pass the Sidebar to AppShell as its navigation instead of laying the frame out by hand. Standing alone it is a navigation landmark named Sidebar; inside AppShell's navigation it is a plain block, and the shell's landmark names the navigation. In a host of bounded height the sections scroll in place between the logo and the footer, which stay put.","files":["Sidebar/Sidebar.manifest.ts","Sidebar/Sidebar.tsx","Sidebar/index.ts","_internal/manifest-entry.ts","_internal/navigationLandmark.ts"],"dependencies":["@radix-ui/react-slot","clsx","react"],"devDependencies":[]},{"name":"Skeleton","slug":"skeleton","kind":"component","description":"Loading placeholder. block renders one pulsing bar; text stacks `lines` bars with a shortened last line. Bars fill their container and never widen it, whatever width the caller asks for, and are drawn in a system color in forced-colors mode.","files":["Skeleton/Skeleton.manifest.ts","Skeleton/Skeleton.tsx","Skeleton/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Slider","slug":"slider","kind":"component","description":"Single-thumb or range slider. Pass value: number for single, [number, number] for range. Track in the control outline color; active range in accent; square thumbs that stay inside the slider at both ends. Inside a Field it is named by the label (range thumbs add lower and upper) and a Field error marks it invalid, drawing its thumbs in the error color.","files":["Slider/Slider.manifest.ts","Slider/Slider.tsx","Slider/index.ts","_internal/aria.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/numberText.tsx","_internal/overlay.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"Sparkline","slug":"sparkline","kind":"component","description":"Compact SVG line or area trend with preserved missing-data gaps and a complete accessible sample description.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Chart/Chart.manifest.ts","Chart/Chart.tsx","Chart/ChartControls.tsx","Chart/geometry.ts","Chart/index.ts","Sparkline/Sparkline.manifest.ts","Sparkline/Sparkline.tsx","Sparkline/index.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/numberText.tsx","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria","react-aria-components"],"devDependencies":[]},{"name":"SplitPane","slug":"split-pane","kind":"component","description":"Two panels with a keyboard and pointer resize separator and two step buttons on the divider; a horizontal split stacks below a container-width threshold.","files":["SplitPane/SplitPane.manifest.ts","SplitPane/SplitPane.tsx","SplitPane/index.ts","_internal/devValidate.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/suggestions.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"StatCard","slug":"stat-card","kind":"component","description":"Single-metric display: label, mono numeric value, and an optional trend indicator paired with an arrow glyph (non-color cue per WCAG 1.4.1). It keeps a minimum footprint (169 px tall, up to 180 px wide, narrower inside a narrower container) and its text wraps. It measures its own width, so a large number steps down in size to fit it (never below the body text size), and in a row that sizes its items to their content it is 180 px wide.","files":["StatCard/StatCard.manifest.ts","StatCard/StatCard.tsx","StatCard/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/numberText.tsx","_internal/suggestions.ts"],"dependencies":["@oprt/icons","clsx","react"],"devDependencies":[]},{"name":"Tabs","slug":"tabs","kind":"component","description":"Tabbed interface root. Underline-accent style. Wraps TabsList and one TabsContent per TabsTrigger.","files":["Tabs/Tabs.manifest.ts","Tabs/Tabs.tsx","Tabs/index.ts","Tabs/useTabsIndicator.ts","Tabs/useTabsScroll.ts","_internal/devValidate.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/useElementWidth.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["clsx","react","react-aria-components"],"devDependencies":[]},{"name":"TagGroup","slug":"tag-group","kind":"component","description":"A labeled collection of tags with optional controlled removal, native keyboard navigation and authored label casing.","files":["TagGroup/TagGroup.manifest.ts","TagGroup/TagGroup.tsx","TagGroup/index.ts","_internal/manifest-entry.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Textarea","slug":"textarea","kind":"component","description":"Multi-line text input. Fixed rows (default 4); scroll on overflow. No native resize handle (rounded grip violates radius rule).","files":["Textarea/Textarea.manifest.ts","Textarea/Textarea.tsx","Textarea/index.ts","_internal/controlStyles.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/overlay.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria-components"],"devDependencies":["react-aria-components"]},{"name":"TimeField","slug":"time-field","kind":"component","description":"Controlled 24-hour wall-time field with segmented editing and explicit draft validity. It carries no date or time zone.","files":["Calendar/Calendar.manifest.ts","Calendar/Calendar.tsx","Calendar/index.ts","TimeField/TimeField.manifest.ts","TimeField/TimeField.tsx","TimeField/index.ts","_internal/aria.ts","_internal/date.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/segmentField.ts","_internal/timestamp.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@internationalized/date","@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"TimeRange","slug":"time-range","kind":"component","description":"Controlled UTC range with separate native date and time fields, OPERATOR Calendar/Clock icons, validated drafts, an Apply action, and optional presets. One line under the fields gives the draft range in 24-hour UTC with milliseconds, and an edit not yet applied is said so.","files":["Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Input/Input.manifest.ts","Input/Input.tsx","Input/index.ts","TimeRange/TimeRange.manifest.ts","TimeRange/TimeRange.tsx","TimeRange/index.ts","_internal/controlStyles.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/timestamp.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["@oprt/icons","@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":["react-aria-components"]},{"name":"Toast","slug":"toast","kind":"component","description":"A single transient notification with a severity icon, title, optional description, and dismiss control. It closes itself after its duration or when dismissed, by calling onOpenChange(false); the application removes it.","files":["Toast/Toast.manifest.ts","Toast/Toast.tsx","Toast/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/suggestions.ts"],"dependencies":["@oprt/icons","@radix-ui/react-toast","class-variance-authority","clsx","react"],"devDependencies":[]},{"name":"Toggle","slug":"toggle","kind":"component","description":"Binary on/off switch. Square thumb sliding inside a square track. Accent fill when on; an invalid switch draws its track in the error color. In forced-colors mode the on state, the thumb and keyboard focus use system colors.","files":["Toggle/Toggle.manifest.ts","Toggle/Toggle.tsx","Toggle/index.ts","_internal/aria.ts","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/useIsomorphicLayoutEffect.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Toolbar","slug":"toolbar","kind":"component","description":"Horizontal or vertical action bar with roving-tabindex arrow-key navigation over its focusable children. A horizontal toolbar that does not fit wraps onto more rows and grows taller, so no action is hidden; arrow keys follow reading order across rows. A vertical toolbar stacks.","files":["Toolbar/Toolbar.manifest.ts","Toolbar/Toolbar.tsx","Toolbar/index.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/suggestions.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"Tooltip","slug":"tooltip","kind":"component","description":"Supplementary text on hover or keyboard focus of a trigger, wrapping at about 20rem. A disabled Button trigger stays focusable and announced unavailable, so focus or a tap shows the explanation. Built on React Aria Components (TooltipTrigger + Tooltip), portal'd, with a bordered surface.","files":["Tooltip/Tooltip.manifest.ts","Tooltip/Tooltip.tsx","Tooltip/index.ts","_internal/aria.ts","_internal/devValidate.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts","_internal/overlay.ts","_internal/suggestions.ts","_internal/unavailable.ts"],"dependencies":["class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"TreeView","slug":"tree-view","kind":"component","description":"React Aria hierarchical treegrid with stable string IDs, single selection, expansion, arrow-key navigation, and typeahead. Labels wrap and keep a readable width; a hierarchy deeper than the tree is wide scrolls sideways inside the tree, and keyboard focus brings the focused row's label into view. Accepts id, style, aria-labelledby, aria-describedby and data attributes.","files":["TreeView/TreeView.manifest.ts","TreeView/TreeView.tsx","TreeView/index.ts","_internal/manifest-entry.ts","_internal/mergeRefs.ts"],"dependencies":["@oprt/icons","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"UploadQueue","slug":"upload-queue","kind":"component","description":"Controlled file queue with supplied transfer states, byte progress and explicit recovery actions.","files":["Badge/Badge.manifest.ts","Badge/Badge.tsx","Badge/index.ts","Button/Button.manifest.ts","Button/Button.tsx","Button/index.ts","Progress/Progress.manifest.ts","Progress/Progress.tsx","Progress/index.ts","UploadQueue/UploadQueue.manifest.ts","UploadQueue/UploadQueue.tsx","UploadQueue/index.ts","_internal/bytes.tsx","_internal/devValidate.ts","_internal/fieldIds.ts","_internal/hydration.ts","_internal/manifest-entry.ts","_internal/motion.ts","_internal/suggestions.ts","_internal/unavailable.ts","_internal/useIsomorphicLayoutEffect.ts","_internal/useRestoreFocusOnRemoval.ts"],"dependencies":["@radix-ui/react-slot","class-variance-authority","clsx","react","react-aria-components"],"devDependencies":[]},{"name":"AssetManagement","slug":"asset-management","kind":"recipe","description":"Manage an industrial equipment register, inspect condition and ownership, create records and revise service assignments with explicit save outcomes.","files":["patterns/operational-domain.ts","patterns/resource-management/demo.tsx","patterns/resource-management/fixtures.ts","patterns/resource-management/index.ts","patterns/resource-management/resource-form.tsx","patterns/resource-management/resource-management.tsx","patterns/resource-management/state.ts","patterns/resource-transfer/csv.ts","patterns/resource-transfer/export.ts","patterns/resource-transfer/transfer-state.ts"],"dependencies":["@oprt/react","react"],"devDependencies":[]},{"name":"CommunicationsConsole","slug":"communications-console","kind":"recipe","description":"Coordinate fictional military support channels, compose messages, distinguish delivery from acknowledgment and retain drafts through connection changes.","files":["patterns/communications-console/communications-console.tsx","patterns/communications-console/communications-state.ts","patterns/communications-console/demo.tsx","patterns/communications-console/fixtures.ts","patterns/communications-console/index.ts","patterns/communications-console/types.ts","patterns/operational-domain.ts"],"dependencies":["@oprt/react","react"],"devDependencies":[]},{"name":"FacilityMonitoring","slug":"facility-monitoring","kind":"recipe","description":"Review supplied cameras, equipment, map and events at one shared time, with explicit recording gaps, session bookmarks, decoded-frame snapshots and camera-request outcomes.","files":["patterns/facility-monitoring/demo.tsx","patterns/facility-monitoring/facility-monitoring.tsx","patterns/facility-monitoring/fixtures.ts","patterns/facility-monitoring/index.ts","patterns/facility-monitoring/review-state.ts","patterns/facility-monitoring/synchronized-video.tsx","patterns/facility-monitoring/training-media.ts","patterns/facility-monitoring/types.ts","patterns/geospatial-workspace/engine-contract.ts","patterns/geospatial-workspace/engine.ts","patterns/geospatial-workspace/feature-data.ts","patterns/geospatial-workspace/fixtures.ts","patterns/geospatial-workspace/geo-map.tsx","patterns/geospatial-workspace/geometry.ts","patterns/geospatial-workspace/renderer-data.ts","patterns/geospatial-workspace/types.ts","patterns/geospatial-workspace/utc-stamp.tsx","patterns/operational-domain.ts"],"dependencies":["@oprt/react","ol","react"],"devDependencies":[]},{"name":"IncidentResponse","slug":"incident-response","kind":"recipe","description":"Triage military support incidents, assign case ownership, review evidence, resolve cases and prepare a shift handover.","files":["patterns/incident-response/demo.tsx","patterns/incident-response/fixtures.ts","patterns/incident-response/incident-response.tsx","patterns/incident-response/incident-state.ts","patterns/incident-response/index.ts","patterns/incident-response/types.ts","patterns/operational-domain.ts"],"dependencies":["@oprt/react","react"],"devDependencies":[]},{"name":"MaintenancePlanning","slug":"maintenance-planning","kind":"recipe","description":"Schedule industrial maintenance in crew and resource lanes, review prerequisites and resolve time and availability conflicts before applying changes.","files":["patterns/operational-domain.ts","patterns/planning-scheduling/demo.tsx","patterns/planning-scheduling/fixtures.ts","patterns/planning-scheduling/index.ts","patterns/planning-scheduling/planning-scheduling.tsx","patterns/planning-scheduling/schedule-state.ts"],"dependencies":["@oprt/react","react"],"devDependencies":[]},{"name":"OperationsMap","slug":"operations-map","kind":"recipe","description":"Inspect military base-support reports on a linked map and table, with explicit source freshness, retained positions, uncertainty, measurement and accepted annotations.","files":["patterns/geospatial-workspace/demo.tsx","patterns/geospatial-workspace/engine-contract.ts","patterns/geospatial-workspace/engine.ts","patterns/geospatial-workspace/feature-data.ts","patterns/geospatial-workspace/fixtures.ts","patterns/geospatial-workspace/geo-map.tsx","patterns/geospatial-workspace/geometry.ts","patterns/geospatial-workspace/geospatial-workspace.tsx","patterns/geospatial-workspace/index.ts","patterns/geospatial-workspace/military-fixture.ts","patterns/geospatial-workspace/renderer-data.ts","patterns/geospatial-workspace/report-results.tsx","patterns/geospatial-workspace/types.ts","patterns/geospatial-workspace/utc-stamp.tsx","patterns/geospatial-workspace/workspace-state.ts","patterns/operational-domain.ts"],"dependencies":["@oprt/react","ol","react"],"devDependencies":[]}],"files":{"_internal/aria.ts":"import { createElement, useCallback, type JSX, type ReactElement, type Ref } from \"react\";\nimport { useMergedRefs } from \"./mergeRefs\";\n\n/**\n * Shared React Aria interop helpers.\n *\n * Two frictions show up in every migrated component, so they are solved once\n * here rather than re-derived per file.\n */\n\n/**\n * Drop keys whose value is `undefined`.\n *\n * `exactOptionalPropertyTypes: true` (tsconfig.base.json) forbids passing an\n * explicit `undefined` for an optional prop, and React Aria's prop types do not\n * admit `undefined` either. Optional passthroughs must therefore be *absent*,\n * not undefined.\n */\nexport function definedOnly<T extends Record<string, unknown>>(\n props: T,\n): { [K in keyof T]?: Exclude<T[K], undefined> } {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(props)) if (v !== undefined) out[k] = v;\n return out as { [K in keyof T]?: Exclude<T[K], undefined> };\n}\n\n/**\n * Force `aria-invalid` onto a React Aria element.\n *\n * RAC surfaces invalidity as `data-invalid` for styling and forwards `data-*`,\n * but strips `aria-invalid` from its own components (verified against\n * react-aria-components 1.20). On a Select the trigger is a `<button>`, so\n * without this an assistive-tech user tabbing to an invalid field is never told\n * it is invalid — a Section 508 / WCAG 3.3.1 regression against the Radix\n * implementation we are replacing.\n *\n * Returns a ref callback to merge with the forwarded ref. It runs only in a browser, so the\n * attribute is missing from server-rendered markup and appears at hydration. Pair it with\n * `renderInvalidButton` or `switchValidationProps`, which render it on the server.\n */\nexport function useAriaInvalidRef<T extends HTMLElement>(\n forwarded: Ref<T> | undefined,\n invalid: boolean,\n): (node: T | null) => void | (() => void) {\n const sync = useCallback(\n (node: T | null) => {\n if (!node) return;\n if (invalid) node.setAttribute(\"aria-invalid\", \"true\");\n else node.removeAttribute(\"aria-invalid\");\n },\n [invalid],\n );\n return useMergedRefs<T>(sync, forwarded);\n}\n\n/**\n * `render` for a React Aria `Button` that writes `aria-invalid=\"true\"` while it renders, so the\n * attribute is in the server markup and not only added once a ref callback runs after hydration.\n *\n * <AriaButton render={renderInvalidButton(isInvalid)} ref={...}>\n *\n * It renders the same `<button>` React Aria would, with every prop it computed, and adds nothing\n * when `invalid` is false.\n */\nexport function renderInvalidButton(\n invalid: boolean,\n): (props: JSX.IntrinsicElements[\"button\"]) => ReactElement {\n return (props) => createElement(\"button\", invalid ? { ...props, \"aria-invalid\": \"true\" } : props);\n}\n\n/**\n * Props for React Aria's `Switch` that put `aria-invalid` and `aria-required` on its input in the\n * server markup. `SwitchProps` omits `isInvalid` and `isRequired` from its types, but the Switch\n * hands both to the input at runtime, so spread this onto it:\n *\n * <AriaSwitch {...switchValidationProps({ invalid: isInvalid, required: isRequired })}>\n *\n * It also adds `data-invalid` to the switch's root, which React Aria always does for an invalid\n * control.\n *\n * `validationBehavior` is pinned to \"aria\". With \"native\", React Aria renders the browser's\n * `required` attribute instead of `aria-required`, and the browser then runs its own constraint\n * validation (a native bubble, a blocked submit) beside the error Field already shows. \"aria\" is\n * what Switch does today; its newer SwitchField defaults to \"native\", so the pin keeps a future\n * default from changing what the control renders.\n */\nexport function switchValidationProps({\n invalid,\n required,\n}: {\n invalid: boolean;\n required: boolean;\n}): { isInvalid?: true; isRequired?: true; validationBehavior: \"aria\" } {\n return {\n ...(invalid ? { isInvalid: true as const } : {}),\n ...(required ? { isRequired: true as const } : {}),\n validationBehavior: \"aria\",\n };\n}\n","_internal/bytes.tsx":"\"use client\";\n\n/**\n * Exact byte counts. Components show the count as supplied, never a rounded \"1.2 MB\", because an\n * operator reconciling a transfer needs the number itself. There is deliberately no unit scaling.\n */\n\n/** A byte count is a non-negative integer that survives JSON and arithmetic exactly. */\nexport function isByteCount(value: unknown): value is number {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\n/** Joins a count to its unit, so a line never ends between them (Stage 1 R4, design call DC-11). */\nexport const NUMBER_UNIT_SPACE = \"\\u00a0\";\n\n/**\n * `<count> bytes`, with the number in the data typeface, or \"Invalid file size.\" when the value is\n * not a byte count. Render it inside the element that wraps the line.\n *\n * The number and its unit are joined by a no-break space (Stage 1 R4, design call DC-11: a number\n * and its unit stay together), so a narrow container wraps before the number or after the unit,\n * never between them. The count keeps plain digits, as DC-11's rulings do for counts.\n */\nexport function ByteCountLabel({ value }: { value: unknown }) {\n if (!isByteCount(value)) return <>Invalid file size.</>;\n return (\n <>\n <span className=\"font-mono tabular-nums\">{value}</span>\n {`${NUMBER_UNIT_SPACE}bytes`}\n </>\n );\n}\n","_internal/controlStyles.ts":"import { FORCED_ACTIVE_ROW, MENU_EMPTY, MENU_ROW, POPOVER_WIDTH } from \"./overlay\";\n\n/*\n * Shared looks of the form controls (Stage 1, batch B04).\n *\n * Every class here is written out in full: Tailwind finds the classes it generates by reading the\n * compiled modules, so a class assembled at run time from parts would never be generated.\n */\n\n/*\n * Forced colors (Windows high contrast): one treatment for focus, state and selection across\n * Checkbox, RadioGroup, Slider, Select and ComboBox, matching Toggle (B01, Toggle-R1).\n *\n * Forced colors replace every background with the page color (Canvas), flatten border colors to\n * one system color in every state and drop box-shadow (CSS Color Adjustment 1, section 3.1), so a\n * cue drawn only with a background, a border color or a shadow disappears. So:\n * - keyboard focus is a 2px CanvasText outline, 2px out;\n * - what is active or chosen is drawn in Highlight, with HighlightText on it, or by a mark in\n * currentColor, which forced colors keep;\n * - what is disabled is drawn in GrayText, not by opacity alone.\n * A system color is kept as it is in forced colors. An element kept from being adjusted\n * (`forced-color-adjust: none`) keeps every color it is given, so it is given system colors only.\n */\n\n/**\n * Tailwind's `outline-hidden` is a 2px transparent outline, which forced colors paint as a visible\n * one. A control that carries it at all times would look focused at all times; this takes it away\n * in forced colors, where the focus rules below draw the outline only on focus.\n */\nexport const FORCED_NO_RESTING_OUTLINE = \"forced-colors:[outline-style:none]\";\n\n/** Keyboard focus on an element that carries React Aria's data-focus-visible (a Select trigger). */\nexport const FORCED_FOCUS_OUTLINE =\n \"forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:outline-offset-2 forced-colors:data-[focus-visible]:outline-[color:CanvasText]\";\n\n/** Focus anywhere in a field group: a ComboBox's text field and its button. */\nexport const FORCED_FOCUS_WITHIN =\n \"forced-colors:focus-within:outline-solid forced-colors:focus-within:outline-2 forced-colors:focus-within:outline-offset-2 forced-colors:focus-within:outline-[color:CanvasText]\";\n\n/**\n * A Checkbox's box, styled from its label root (group/checkbox). Its checked and mixed marks are\n * icons in currentColor, which forced colors keep.\n */\nexport const FORCED_CHECKBOX_BOX =\n \"forced-colors:group-data-[focus-visible]/checkbox:outline-solid forced-colors:group-data-[focus-visible]/checkbox:outline-2 forced-colors:group-data-[focus-visible]/checkbox:outline-offset-2 forced-colors:group-data-[focus-visible]/checkbox:outline-[color:CanvasText] forced-colors:group-data-[disabled]/checkbox:border-[color:GrayText] forced-colors:group-data-[disabled]/checkbox:text-[color:GrayText]\";\n\n/**\n * A Radio, on its label: its box (.oprt-radio-box) and its check mark (.oprt-radio-inner, in\n * currentColor), and its text when disabled.\n */\nexport const FORCED_RADIO =\n \"forced-colors:data-[focus-visible]:[&_.oprt-radio-box]:outline-solid forced-colors:data-[focus-visible]:[&_.oprt-radio-box]:outline-2 forced-colors:data-[focus-visible]:[&_.oprt-radio-box]:outline-offset-2 forced-colors:data-[focus-visible]:[&_.oprt-radio-box]:outline-[color:CanvasText] forced-colors:data-[disabled]:text-[color:GrayText] forced-colors:data-[disabled]:[&_.oprt-radio-box]:border-[color:GrayText] forced-colors:data-[disabled]:[&_.oprt-radio-inner]:text-[color:GrayText]\";\n\n/**\n * A slider's track, a background that forced colors would replace with the page color: kept from\n * being adjusted, and drawn as a CanvasText edge on Canvas. Its filled range and its thumbs are drawn\n * inside it, so they are not adjusted either and take system colors below.\n */\nexport const FORCED_SLIDER_TRACK =\n \"forced-colors:[forced-color-adjust:none] forced-colors:border forced-colors:border-[color:CanvasText] forced-colors:bg-[color:Canvas] forced-colors:data-[disabled]:border-[color:GrayText]\";\n\n/** The filled range, in Highlight. */\nexport const FORCED_SLIDER_FILL =\n \"forced-colors:bg-[color:Highlight] forced-colors:data-[disabled]:bg-[color:GrayText]\";\n\n/**\n * A thumb: Canvas with a CanvasText edge; keyboard focus (on its 24px target, group/slider) is the\n * outline, and the focus border color and shadow, and an invalid thumb's error color, which\n * would show in their own colors since the track is not adjusted, give way to CanvasText and none.\n * Disabled wins over invalid (`[&&]` raises it, as in Toggle): Tailwind writes `invalid` after it.\n */\nexport const FORCED_SLIDER_THUMB =\n \"forced-colors:bg-[color:Canvas] forced-colors:border-[color:CanvasText] forced-colors:group-data-[focus-visible]/slider:outline-solid forced-colors:group-data-[focus-visible]/slider:outline-2 forced-colors:group-data-[focus-visible]/slider:outline-offset-2 forced-colors:group-data-[focus-visible]/slider:outline-[color:CanvasText] forced-colors:group-data-[focus-visible]/slider:border-[color:CanvasText] forced-colors:group-data-[focus-visible]/slider:shadow-none forced-colors:group-data-[invalid]/slider:border-[color:CanvasText] forced-colors:group-data-[disabled]/slider:[&&]:border-[color:GrayText]\";\n\n/*\n * The listbox popup that Select and ComboBox share (Select-R6). It follows the popover width rule\n * (design call DC-7): above or below its trigger the list takes its content width (w-max), wraps at\n * about 30rem and is never wider than the viewport less the screen margin on each side. React Aria\n * exposes the trigger's width on the popover as --trigger-width, the list's minimum, which the\n * viewport caps too. React Aria writes an inline max-height that fits the space beside the trigger;\n * the list scrolls inside it.\n */\nexport const LISTBOX_SURFACE = `z-50 min-w-[min(var(--trigger-width),calc(100vw-var(--oprt-space-4)))] ${POPOVER_WIDTH} overflow-auto overscroll-contain border border-oprt-accent bg-oprt-surface-1 font-sans shadow-oprt-focus`;\n\n/**\n * An option row: the metrics of a menu row (a one-line row is exactly 32 px, and a wrapped one keeps\n * the same padding as it grows), the options' own type, and their focus, selected and disabled\n * states. The text goes in a MENU_ITEM_TEXT span, so long names and identifiers break anywhere.\n *\n * In forced colors the option about to be chosen (data-focused: React Aria's keyboard focus in a\n * Select's list, or the active option of a ComboBox, whose focus stays in its text field) is drawn\n * in Highlight with HighlightText, the system's own mark for it, and no row carries the outline\n * forced colors would paint at rest: FORCED_ACTIVE_ROW, the rule the menu and CommandPalette rows\n * share, so the three kinds of row cannot drift. A selected option keeps its check mark, drawn in\n * currentColor.\n */\nexport const LISTBOX_ROW = `${MENU_ROW} cursor-pointer font-sans text-oprt-base font-medium normal-case text-oprt-text outline-hidden data-[focused]:bg-oprt-surface-3 data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[selected]:text-oprt-accent data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 ${FORCED_ACTIVE_ROW}`;\n\n/** The non-interactive message an empty or loading list shows, as an empty menu does. */\nexport const LISTBOX_EMPTY = MENU_EMPTY;\n","_internal/date.ts":"import {\n CalendarDate,\n parseDate,\n Time,\n fromAbsolute,\n toCalendarDate,\n toCalendarDateTime,\n toZoned,\n} from \"@internationalized/date\";\nimport { isValidTimestamp } from \"./timestamp\";\n\nexport function parseTimeOnly(value: unknown): Time | null {\n if (typeof value !== \"string\" || !/^(?:[01]\\d|2[0-3]):[0-5]\\d$/.test(value)) return null;\n const [hour, minute] = value.split(\":\").map(Number);\n return new Time(hour!, minute!);\n}\n\nexport function formatTimeOnly(value: { hour: number; minute: number }) {\n return `${String(value.hour).padStart(2, \"0\")}:${String(value.minute).padStart(2, \"0\")}`;\n}\n\nexport function timeConfiguration(value: string | null, min?: string, max?: string) {\n const time = parseTimeOnly(value);\n const minimum = min === undefined ? new Time(0, 0) : parseTimeOnly(min);\n const maximum = max === undefined ? new Time(23, 59) : parseTimeOnly(max);\n const invalidBounds = !minimum || !maximum || minimum.compare(maximum) > 0;\n const error = invalidBounds\n ? \"Invalid time bounds. Use HH:mm times with the minimum before or equal to the maximum.\"\n : value !== null && !time\n ? \"Invalid time. Use HH:mm from 00:00 through 23:59.\"\n : time && minimum && time.compare(minimum) < 0\n ? `Choose a time at or after ${min}.`\n : time && maximum && time.compare(maximum) > 0\n ? `Choose a time at or before ${max}.`\n : null;\n return {\n time,\n min: invalidBounds ? new Time(0, 0) : minimum!,\n max: invalidBounds ? new Time(23, 59) : maximum!,\n invalidBounds,\n error,\n };\n}\n\nexport function readTimeSegments(\n segments: readonly { type: string; value?: number | null; isPlaceholder: boolean }[],\n): DateDraftSnapshot {\n const fields = [\"hour\", \"minute\"].map((type) =>\n segments.find((segment) => segment.type === type),\n );\n if (fields.every((field) => field?.isPlaceholder))\n return { value: null, empty: true, valid: true };\n if (fields.some((field) => !field || field.isPlaceholder || field.value == null))\n return { value: null, empty: false, valid: false };\n const value = fields.map((field) => String(field!.value).padStart(2, \"0\")).join(\":\");\n return { value, empty: false, valid: !!parseTimeOnly(value) };\n}\n\n/**\n * Whether `timeZone` names an IANA zone. Accepts `unknown` because the value arrives from props\n * that JavaScript callers and agents can get wrong: anything that is not a string is simply not a\n * zone, so the caller shows its \"Invalid time zone\" message instead of throwing during render.\n */\nexport function validTimeZone(timeZone: unknown): boolean {\n if (typeof timeZone !== \"string\") return false;\n if (!timeZone || timeZone.trim() !== timeZone || /^[+-]/.test(timeZone)) return false;\n try {\n new Intl.DateTimeFormat(\"en-US\", { timeZone }).format(0);\n return true;\n } catch {\n return false;\n }\n}\n\n/** Exact epoch milliseconds a Date can hold: an integer within 8.64e15 of the epoch. */\nexport function validInstant(value: unknown): value is number {\n return isValidTimestamp(value, { integer: true });\n}\n\nexport function resolveWallTime(\n date: string,\n time: string,\n timeZone: string,\n): { status: \"invalid\"; error: string } | { status: \"valid\"; earlier: number; later: number } {\n if (!validTimeZone(timeZone))\n return { status: \"invalid\", error: \"Invalid time zone. Supply an IANA time zone.\" };\n const day = parseDateOnly(date);\n const clock = parseTimeOnly(time);\n if (!day || !clock) return { status: \"invalid\", error: \"Enter a complete, valid date and time.\" };\n const wall = toCalendarDateTime(day, clock);\n try {\n const earlier = toZoned(wall, timeZone, \"earlier\");\n const later = toZoned(wall, timeZone, \"later\");\n if (\n toCalendarDateTime(earlier).compare(wall) !== 0 ||\n toCalendarDateTime(later).compare(wall) !== 0\n ) {\n return {\n status: \"invalid\",\n error: `This time does not exist in ${timeZone} because the clock moves forward. Choose another time.`,\n };\n }\n return {\n status: \"valid\",\n earlier: earlier.toDate().getTime(),\n later: later.toDate().getTime(),\n };\n } catch {\n return {\n status: \"invalid\",\n error: \"This date and time cannot be resolved in the supplied zone.\",\n };\n }\n}\n\nexport function instantWallTime(value: number | null, timeZone: string) {\n if (value === null) return { date: null, time: null, occurrence: null, error: null };\n if (!validInstant(value))\n return {\n date: null,\n time: null,\n occurrence: null,\n error: \"Invalid instant. Supply finite epoch milliseconds.\",\n };\n if (!validTimeZone(timeZone))\n return {\n date: null,\n time: null,\n occurrence: null,\n error: \"Invalid time zone. Supply an IANA time zone.\",\n };\n try {\n const zoned = fromAbsolute(value, timeZone);\n const date = toCalendarDate(zoned).toString();\n const time = formatTimeOnly(zoned);\n // Past year 9999 the library clamps the year and keeps the month, day and time, which reads\n // as a plausible but wrong date. A faithful conversion converts back to the same instant, so\n // anything else is outside the supported range. Years below 1 fail the date check.\n if (zoned.toDate().getTime() !== value || !parseDateOnly(date))\n return {\n date: null,\n time: null,\n occurrence: null,\n error: \"The date must use a year from 0001 through 9999.\",\n };\n const resolved = resolveWallTime(date, time, timeZone);\n let occurrence: \"earlier\" | \"later\" | null = null;\n if (resolved.status === \"valid\" && resolved.earlier !== resolved.later) {\n // Compare zone offsets so sub-minute precision does not erase occurrence.\n occurrence =\n fromAbsolute(resolved.later, timeZone).offset === zoned.offset ? \"later\" : \"earlier\";\n }\n return { date, time, occurrence, error: null };\n } catch {\n return {\n date: null,\n time: null,\n occurrence: null,\n error: \"Invalid instant for the supplied time zone.\",\n };\n }\n}\n\nexport function parseDateOnly(value: unknown): CalendarDate | null {\n if (typeof value !== \"string\" || !/^\\d{4}-\\d{2}-\\d{2}$/.test(value)) return null;\n try {\n const date = parseDate(value);\n return date.year >= 1 && date.year <= 9999 && date.toString() === value ? date : null;\n } catch {\n return null;\n }\n}\n\nexport function dateConfiguration(value: string | null, min?: string, max?: string) {\n const date = parseDateOnly(value);\n const minimum = min === undefined ? new CalendarDate(1, 1, 1) : parseDateOnly(min);\n const maximum = max === undefined ? new CalendarDate(9999, 12, 31) : parseDateOnly(max);\n const invalidBounds = !minimum || !maximum || minimum.compare(maximum) > 0;\n const error = invalidBounds\n ? \"Invalid date bounds. Use YYYY-MM-DD dates with the minimum before or equal to the maximum.\"\n : value !== null && !date\n ? \"Invalid date. Use a real Gregorian date in YYYY-MM-DD format.\"\n : date && minimum && date.compare(minimum) < 0\n ? `Choose a date on or after ${min}.`\n : date && maximum && date.compare(maximum) > 0\n ? `Choose a date on or before ${max}.`\n : null;\n return {\n date,\n min: invalidBounds ? new CalendarDate(1, 1, 1) : minimum!,\n max: invalidBounds ? new CalendarDate(9999, 12, 31) : maximum!,\n invalidBounds,\n error,\n };\n}\n\nexport function dateWithinBounds(date: CalendarDate, min: CalendarDate, max: CalendarDate) {\n return date.compare(min) >= 0 && date.compare(max) <= 0;\n}\n\nexport type DateDraftSnapshot = { value: string | null; empty: boolean; valid: boolean };\n\n/** Read visible segment values, which can differ from RAC's last committed value. */\nexport function readDateSegments(\n segments: readonly { type: string; value?: number | null; isPlaceholder: boolean }[],\n): DateDraftSnapshot {\n const fields = [\"year\", \"month\", \"day\"].map((type) =>\n segments.find((segment) => segment.type === type),\n );\n const empty = fields.every((field) => field?.isPlaceholder);\n if (empty) return { value: null, empty: true, valid: true };\n if (fields.some((field) => !field || field.isPlaceholder || field.value == null)) {\n return { value: null, empty: false, valid: false };\n }\n const value = fields\n .map((field, index) => String(field!.value).padStart(index === 0 ? 4 : 2, \"0\"))\n .join(\"-\");\n return { value, empty: false, valid: parseDateOnly(value) !== null };\n}\n","_internal/devValidate.ts":"import type { Component } from \"./manifest-entry\";\nimport { SUGGESTION_OVERRIDES } from \"./suggestions\";\n\n// Module-level dedupe so React re-renders don't re-log the same mistake.\nconst seen = new Set<string>();\n\n// Values come from callers, so a value like \"toString\" or \"__proto__\" must not resolve through\n// Object.prototype. Only a table's own entries count.\nconst own = (table: object, key: string) => Object.prototype.hasOwnProperty.call(table, key);\n\nfunction editorialSuggestion(component: string, prop: string, value: string): string | undefined {\n if (!own(SUGGESTION_OVERRIDES, component)) return undefined;\n const byProp = SUGGESTION_OVERRIDES[component]!;\n if (!own(byProp, prop)) return undefined;\n const byValue = byProp[prop]!;\n return own(byValue, value) ? byValue[value] : undefined;\n}\n\n/**\n * Dev only. The component that called the hook that called this, read from the stack: the first\n * frame outside `_internal` and React, named by its file (`Button/Button.tsx` is \"Button\"). It lets\n * a shared hook report a bad prop for the component that was given it, without every component\n * passing its own name. Undefined when the stack does not say, such as in a bundle that has merged\n * the files; the caller then falls back to a generic name.\n */\nexport function callingComponent(): string | undefined {\n for (const frame of (new Error().stack ?? \"\").split(\"\\n\").slice(1)) {\n if (\n /\\/_internal\\/|node_modules\\/(?:\\.pnpm\\/[^/]*\\/node_modules\\/)?react(?:-dom)?\\//.test(frame)\n )\n continue;\n const file = /([^/\\\\()\\s]+?)\\.(?:[cm]?[jt]sx?)(?:\\?[^:)\\s]*)?:\\d+:\\d+\\)?\\s*$/.exec(frame)?.[1];\n if (file !== undefined) return /^[A-Z]/.test(file) ? file : undefined;\n }\n return undefined;\n}\n\n/**\n * Dev-only: validate string-valued enum props against the component's manifest\n * entry. On a mismatch, console.error a structured payload (PRD §6.3) and return\n * — never throws, never affects render. Stripped from production builds.\n *\n * Pass every prop the component received, not a hand-picked list: each prop the manifest\n * declares as an enum is checked, and everything else (booleans, numbers, handlers, children,\n * props without a manifest definition, `undefined`) is skipped. A new enum value added to the\n * manifest is then validated without touching the call.\n */\nexport function devValidate(\n component: string,\n manifestProps: Component[\"props\"],\n received: Record<string, unknown>,\n): void {\n if (process.env.NODE_ENV === \"production\") return;\n for (const [prop, value] of Object.entries(received)) {\n // Only string enums. Skips boolean (e.g. Checkbox checked), numbers, undefined.\n if (typeof value !== \"string\") continue;\n const def = own(manifestProps, prop) ? manifestProps[prop] : undefined;\n if (!def || def.type !== \"enum\") continue;\n if (def.values.includes(value)) continue;\n\n const key = `${component}.${prop}.${value}`;\n if (seen.has(key)) continue;\n seen.add(key);\n\n const suggestion =\n editorialSuggestion(component, prop, value) ??\n `Valid values for \\`${prop}\\`: ${def.values.join(\", \")}` +\n (def.default !== undefined ? ` (default: ${def.default}).` : \".\");\n\n console.error(\n JSON.stringify(\n {\n component,\n error: \"INVALID_PROP_VALUE\",\n prop,\n received: value,\n valid_values: def.values,\n suggestion,\n },\n null,\n 2,\n ),\n );\n }\n}\n","_internal/fieldIds.ts":"import { createContext, useContext } from \"react\";\nimport { useHydratedId } from \"./hydration\";\n\n/*\n * Form-control wiring (Stage 1 Field-R2, fieldWiringPlan step 1): one source of truth for a\n * control's id, name, description, invalid and required state.\n *\n * Labels, help and errors were wired two ways: through the Field context, which eight controls\n * each re-implemented with their own differences, and through ids hand-built from a plain useId in\n * the controls that carry their own label, which broke after a hydration id mismatch. Controls in a\n * Field call useFieldWiring; controls that carry their own label mint their part ids with\n * usePartIds and join their descriptions with joinIds. The ids keep their `${id}-<part>` shape.\n */\n\n/** What a Field hands the control inside it. */\nexport type FieldContextValue = {\n id: string;\n /** id of the rendered <label>. Composite controls whose root is a container\n * element (role=\"radiogroup\", role=\"group\", a slider group) cannot be named\n * by `<label htmlFor>` — htmlFor only labels form elements. Those components\n * must consume this as aria-labelledby instead, or they ship with no\n * accessible name at all. */\n labelId: string;\n describedBy: string | undefined;\n invalid: boolean;\n /** A required Field makes its control required (coordinator ruling, following DC-10). */\n required: boolean;\n};\n\nexport const FieldContext = createContext<FieldContextValue | null>(null);\n\n/**\n * Joins ids into one IDREF list: parts that are not there are skipped, a list is split and a\n * repeated id kept once, and nothing left is `undefined`, so no empty attribute is written.\n */\nexport function joinIds(\n ...ids: ReadonlyArray<string | false | null | undefined>\n): string | undefined {\n const joined = new Set<string>();\n for (const id of ids) {\n if (!id) continue;\n for (const token of id.split(/\\s+/)) if (token) joined.add(token);\n }\n return joined.size > 0 ? [...joined].join(\" \") : undefined;\n}\n\nexport type PartIds<Part extends string> = { base: string } & { [Name in Part]: string };\n\n/** The part names a control may mint: \"base\" is the control's own id, which a part must not replace. */\ntype PartName<Part extends string> = Exclude<Part, \"base\">;\n\n/**\n * The ids of a control's parts, `${base}-${part}` each. The base is the explicit `id` when there is\n * one, used verbatim, else an id minted by useId and reconciled with the markup it hydrated:\n * `readRendered` returns the base as the page carries it (a label's htmlFor, an attribute on the\n * root), read once at mount. When the page hydrated with ids that disagree with React's, the parts\n * switch to the page's, so a part mounted later (an error, a status line) matches what the control\n * already points at (see useHydratedId).\n */\nexport function usePartIds<const Part extends string>(\n parts: readonly PartName<Part>[],\n options: { id?: string | undefined; readRendered?: () => string | null | undefined } = {},\n): PartIds<Part> {\n const { id, readRendered } = options;\n // Read at mount only, and only when there is no explicit id, as Field always did: an explicit id\n // never needs reconciling, and reading it would only cost a render.\n const generated = useHydratedId(() => (id === undefined ? readRendered?.() : undefined));\n const base = id ?? generated;\n const ids: Record<string, string> = { base };\n for (const part of parts) ids[part] = `${base}-${part}`;\n return ids as PartIds<Part>;\n}\n\n/** The wiring props a control accepts of its own. */\nexport type FieldWiringProps = {\n id?: string | undefined;\n \"aria-label\"?: string | undefined;\n \"aria-labelledby\"?: string | undefined;\n \"aria-describedby\"?: string | undefined;\n \"aria-invalid\"?: boolean | \"true\" | \"false\" | \"grammar\" | \"spelling\" | undefined;\n /** The control's own `invalid` prop (null is a variant left unset). */\n invalid?: boolean | null | undefined;\n required?: boolean | undefined;\n};\n\nexport type FieldWiringOptions = {\n /**\n * How a Field's label names the control. \"for\": its htmlFor does (a native input or textarea).\n * \"labelledby\": the element that carries the role is not one a label's htmlFor can name (a\n * radiogroup, a slider group, a hidden input under a styled root, a trigger that names its popup),\n * so it takes the label's id as aria-labelledby, unless it has an aria-label or a visible label\n * of its own.\n */\n label: \"for\" | \"labelledby\";\n /** The control renders a visible label of its own (ComboBox's `label`), which replaces the Field's. */\n ownLabel?: boolean;\n};\n\nexport type FieldWiring = {\n /** Its own id, else the Field's, which the Field label's htmlFor points at. */\n id: string | undefined;\n /** Its own aria-labelledby, else the Field label's id for a control named that way. */\n labelledBy: string | undefined;\n /** Its own aria-describedby, which replaces the Field's help or error, else the Field's. */\n describedBy: string | undefined;\n /** Its own `invalid`, then its own aria-invalid, then the Field's error. */\n invalid: boolean;\n /**\n * What the element's aria-invalid says: true when it is invalid, else the caller's own \"grammar\"\n * or \"spelling\" token, which is a kind of invalid the control does not draw, else nothing. A\n * control renders this, never the caller's aria-invalid through a spread (review B7 M1).\n */\n ariaInvalid: true | \"grammar\" | \"spelling\" | undefined;\n /** Its own `required`, false included, else the Field's. */\n required: boolean;\n /** The Field around the control, if any. */\n field: FieldContextValue | null;\n};\n\n/** Whether an aria-invalid value says \"invalid\": `true` or \"true\". */\nfunction ariaInvalidValue(value: FieldWiringProps[\"aria-invalid\"]): boolean | undefined {\n if (value === undefined) return undefined;\n return value === true || value === \"true\";\n}\n\n/** The caller's aria-invalid when it is a token of its own (\"grammar\", \"spelling\"), not a boolean. */\nfunction ariaInvalidToken(\n value: FieldWiringProps[\"aria-invalid\"],\n): \"grammar\" | \"spelling\" | undefined {\n return typeof value === \"string\" && value !== \"true\" && value !== \"false\" ? value : undefined;\n}\n\n/**\n * A control's wiring: what it was given of its own, and what the Field around it supplies. What\n * the control is given always wins.\n */\nexport function useFieldWiring(own: FieldWiringProps, options: FieldWiringOptions): FieldWiring {\n const field = useContext(FieldContext);\n const namedByField = options.label === \"labelledby\" && !own[\"aria-label\"] && !options.ownLabel;\n const invalid = own.invalid ?? ariaInvalidValue(own[\"aria-invalid\"]) ?? field?.invalid ?? false;\n return {\n id: own.id ?? field?.id,\n labelledBy: own[\"aria-labelledby\"] ?? (namedByField ? field?.labelId : undefined),\n describedBy: own[\"aria-describedby\"] ?? field?.describedBy,\n invalid,\n // Its own `invalid` decides first, so a token shows only where `invalid` was not given.\n ariaInvalid: invalid\n ? true\n : own.invalid == null\n ? ariaInvalidToken(own[\"aria-invalid\"])\n : undefined,\n required: own.required ?? field?.required ?? false,\n field,\n };\n}\n","_internal/hydration.ts":"import { useId, useRef, useState, useSyncExternalStore } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\n\n/**\n * Hydration-safe ids.\n *\n * React's useId is meant to agree between the server render and hydration, but\n * it doesn't always: on the docs site, when a page's client chunk loaded late,\n * every id in the page hydrated one tree level off the server's. Production\n * hydration keeps the server's attribute values, so the page keeps the server's\n * ids while React holds different ones, and whatever React writes afterward (a\n * popup's aria-labelledby, a panel mounted on selection) points at ids the page\n * doesn't carry. These helpers keep what React writes in agreement with the page.\n */\n\n/**\n * A `key` for a React Aria root that is handed ids, such as a Field's id and\n * label id. React Aria keeps the ids it mounts with, so when they change (a\n * Field switches to the ids its markup carries, see useHydratedId) the tree\n * remounts to pick them up; otherwise it would keep writing the old ones.\n */\nexport function keyForIds(...forwardedIds: (string | undefined)[]): string {\n return forwardedIds.join(\" \");\n}\n\nconst subscribeNoop = () => () => {};\nconst getClientSnapshot = () => true;\nconst getServerSnapshot = () => false;\n\nconst PROBE_ATTRIBUTE = \"data-oprt-hydration\";\n\n/** Whether the document carries an element whose probe attribute is `probe`. */\nfunction markupCarries(probe: string): boolean {\n for (const element of document.querySelectorAll(`[${PROBE_ATTRIBUTE}]`)) {\n if (element.getAttribute(PROBE_ATTRIBUTE) === probe) return true;\n }\n return false;\n}\n\n// Development only. The probe is a convention between this hook and the component that renders it,\n// so a component that forgets it is a mistake worth saying out loud, once, with where it is. Only a\n// client-only mount can say so: a hydrating mount that cannot find its probe remounts, and nothing\n// after that tells a forgotten probe from a probe that sits outside the keyed subtree and so still\n// carries the server's id.\nconst warnedAbout = new Set<string>();\nconst HOOK_NAME = \"useHydrationRoot\";\n\n/** The line of the stack just above this hook's own frame: the component that called it. Best effort. */\nfunction callerOfHook(): string {\n const lines = new Error().stack?.split(\"\\n\") ?? [];\n const own = lines.findIndex((line) => line.includes(HOOK_NAME));\n // A minifier renames the hook; the component that called it is then the third frame.\n return (lines[own >= 0 ? own + 1 : 3] ?? \"an unknown component\").trim();\n}\n\nfunction warnProbeMissing(origin: string | undefined) {\n const where = origin ?? \"an unknown component\";\n if (warnedAbout.has(where)) return;\n warnedAbout.add(where);\n console.warn(\n `${HOOK_NAME}: no element in the document carries this root's probeProps, so it remounts ` +\n \"after every hydration and a keyboard user loses their place. Spread hydration.probeProps \" +\n `on an element the component renders. Called from ${where}`,\n );\n}\n\n/**\n * A `key` for a React Aria root that wires ids between elements it renders at different times\n * (tabs and their panels, a trigger and the popup it opens), which remounts only when hydration\n * really disagrees with the server markup.\n *\n * const hydration = useHydrationRoot(finalId, labelledBy);\n * <AriaSelect key={hydration.key} {...hydration.probeProps} ...>\n *\n * `probeProps` must reach an element the component renders into the server markup: the keyed root\n * itself where it accepts DOM attributes, otherwise a wrapper around or inside it (an element\n * outside the keyed subtree works too: it keeps the server's id, which is all the comparison\n * needs). The attribute carries an id React minted on the server. When hydrating, the hook looks for that id in the\n * page. Finding it means this part of the tree hydrated with the ids the server used, so nothing\n * needs repairing and the key never changes: focus stays where the user left it and effects inside\n * the root run once. Not finding it means the ids disagree, and the root remounts so the client\n * mints every id React Aria wires up from then on.\n *\n * This replaces the earlier `useHydrationKey`, which could not tell the two cases apart and\n * remounted after every hydration. A caller that forgets `probeProps` gets that old behavior, and in\n * development a client-only mount (a test, a single-page app) warns, naming where the hook was\n * called. Client-only mounts have nothing to compare and never remount. The key also changes with\n * `forwardedIds`, as in `keyForIds`.\n *\n * The page is searched through `document`, so a component rendered inside a shadow root is always\n * treated as disagreeing: it remounts after hydration, as it always did.\n */\nexport function useHydrationRoot(...forwardedIds: (string | undefined)[]): {\n key: string;\n probeProps: { [PROBE_ATTRIBUTE]: string };\n} {\n const probe = useId();\n const hydrated = useSyncExternalStore(subscribeNoop, getClientSnapshot, getServerSnapshot);\n // The first render of a hydrating mount reads the server snapshot. A client-only mount never does.\n const hydratingMount = useRef<boolean>();\n if (hydratingMount.current === undefined) hydratingMount.current = !hydrated;\n const [disagrees, setDisagrees] = useState(false);\n const clientOnly = useRef(hydrated);\n const origin = useRef<string>();\n if (process.env.NODE_ENV !== \"production\" && origin.current === undefined) {\n origin.current = callerOfHook();\n }\n\n useIsomorphicLayoutEffect(() => {\n // The commit that finishes hydration is the first with the client snapshot.\n if (!hydrated || !hydratingMount.current) return;\n hydratingMount.current = false;\n if (!markupCarries(probe)) setDisagrees(true);\n }, [hydrated, probe]);\n\n useIsomorphicLayoutEffect(() => {\n if (process.env.NODE_ENV === \"production\" || !clientOnly.current) return;\n if (!markupCarries(probe)) warnProbeMissing(origin.current);\n }, [probe]);\n\n return {\n key: `${disagrees ? \"client\" : \"server\"} ${keyForIds(...forwardedIds)}`,\n probeProps: { [PROBE_ATTRIBUTE]: probe },\n };\n}\n\n/**\n * useId, reconciled with the markup it hydrated.\n *\n * `readRenderedId` returns the id as the page carries it (a label's htmlFor,\n * say). Right after mounting, if that differs from React's id, the component was\n * hydrated from markup with the server's id, so it switches to that one and\n * everything rendered from it afterward, including elements mounted later,\n * matches the page. A no-op when the two agree, as on client-only mounts.\n */\nexport function useHydratedId(readRenderedId: () => string | null | undefined): string {\n const reactId = useId();\n const [renderedId, setRenderedId] = useState<string>();\n // Read once, at mount: compare against the markup this instance mounted with.\n const read = useRef(readRenderedId);\n useIsomorphicLayoutEffect(() => {\n const id = read.current();\n if (id && id !== reactId) setRenderedId(id);\n }, [reactId]);\n return renderedId ?? reactId;\n}\n","_internal/manifest-entry.ts":"// A component's manifest entry, as the @oprt/manifest schema describes it, written as plain\n// types. The components describe themselves with these, so an app that renders them never installs\n// the manifest package or its validator. manifest-entry.test.ts fails if they drift from the schema.\n\nexport type Prop =\n | {\n type: \"enum\";\n values: string[];\n default?: string | undefined;\n required?: boolean | undefined;\n description?: string | undefined;\n }\n | {\n type: \"boolean\";\n default?: boolean | undefined;\n required?: boolean | undefined;\n description?: string | undefined;\n }\n | {\n type: \"string\";\n default?: string | undefined;\n required?: boolean | undefined;\n description?: string | undefined;\n }\n | {\n type: \"number\";\n default?: number | undefined;\n required?: boolean | undefined;\n description?: string | undefined;\n }\n | { type: \"function\"; required?: boolean | undefined; description?: string | undefined }\n | {\n type: \"array\";\n items?: Record<string, unknown> | undefined;\n required?: boolean | undefined;\n description?: string | undefined;\n }\n | { type: \"node\"; required?: boolean | undefined; description?: string | undefined }\n | {\n type: \"union\";\n types: (\"boolean\" | \"string\" | \"number\" | \"function\" | \"array\" | \"node\")[];\n required?: boolean | undefined;\n description?: string | undefined;\n };\n\nexport type Slot = { type: string; required?: boolean | undefined };\n\nexport type Component = {\n description: string;\n props: Record<string, Prop>;\n slots?: Record<string, Slot> | undefined;\n composable_with?: string[] | undefined;\n never_inside?: string[] | undefined;\n example: string;\n};\n","_internal/mergeRefs.ts":"import {\n useMemo,\n useRef,\n version,\n type ForwardedRef,\n type MutableRefObject,\n type Ref,\n type RefObject,\n} from \"react\";\n\n/** Any ref a component can be handed, including the nothing a missing ref gives. */\nexport type MergeableRef<T> = Ref<T> | ForwardedRef<T> | undefined;\n\n// React 19 lets a callback ref return a cleanup function and then does not call the ref with null\n// on detach. React 18 ignores a returned function, and 18.3 warns about one, so only hand it out\n// where it is understood.\nconst supportsRefCleanup = Number.parseInt(version, 10) >= 19;\n\n/**\n * Point one ref at `node`: call a callback ref, or set an object ref's `current`. A missing ref\n * or a legacy string ref is skipped. Returns whatever a callback ref returned, which under React\n * 19 may be its cleanup function.\n */\nexport function setRef<T>(ref: MergeableRef<T>, node: T | null): void | (() => void) {\n if (typeof ref === \"function\") return (ref as (node: T | null) => void | (() => void))(node);\n if (ref !== null && typeof ref === \"object\") (ref as MutableRefObject<T | null>).current = node;\n}\n\n/**\n * One callback ref that feeds several. Detaching sets every ref to null (and under React 19 runs\n * the cleanup a ref returned instead). It is a new function on every call, so inside a component\n * use `useMergedRefs`, which keeps the identity React needs to avoid re-attaching.\n */\nexport function mergeRefs<T>(...refs: MergeableRef<T>[]): (node: T | null) => void | (() => void) {\n return (node) => {\n if (node === null) {\n for (const ref of refs) setRef(ref, null);\n return;\n }\n const cleanups = refs.map((ref) => {\n const result = setRef(ref, node);\n return typeof result === \"function\" ? result : null;\n });\n if (!supportsRefCleanup) return;\n return () => {\n refs.forEach((ref, index) => {\n const cleanup = cleanups[index];\n if (cleanup) cleanup();\n else setRef(ref, null);\n });\n };\n };\n}\n\n/**\n * Merge a forwarded ref with the refs a component keeps for itself.\n *\n * The returned callback is stable while the refs passed in are. That matters: React detaches and\n * re-attaches a callback ref whenever its identity changes, which calls a consumer's\n * `ref={(el) => el?.focus()}` with null and then the node again on every render. A merge written\n * inline, or memoized on the wrong inputs, does exactly that. When an input ref changes, the\n * merged ref changes too, so the old ref is cleared and the new one attached.\n */\nexport function useMergedRefs<T>(\n ...refs: MergeableRef<T>[]\n): (node: T | null) => void | (() => void) {\n // The refs themselves are the dependencies: one call site always passes the same number.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n return useMemo(() => mergeRefs(...refs), refs);\n}\n\n/**\n * An object ref whose `current` feeds a callback ref: for a React Aria part that takes an object\n * ref (a hidden input's `inputRef`) while the component's own ref is a callback, such as a merged\n * forwarded ref. Setting a node keeps whatever cleanup the callback returned (React 19), and\n * setting null runs that cleanup or, without one, calls the callback with null, as React itself\n * does. A switch from one node to another detaches the first.\n */\nexport function useInputRefObject<T>(\n setNode: (node: T | null) => void | (() => void),\n): RefObject<T | null> {\n const node = useRef<T | null>(null);\n const cleanup = useRef<(() => void) | null>(null);\n return useMemo(\n () => ({\n get current() {\n return node.current;\n },\n set current(next: T | null) {\n const previous = node.current;\n node.current = next;\n if (previous !== null) {\n const run = cleanup.current;\n cleanup.current = null;\n if (run) run();\n else setNode(null);\n }\n if (next !== null) {\n const result = setNode(next);\n cleanup.current = typeof result === \"function\" ? result : null;\n }\n },\n }),\n [setNode],\n );\n}\n","_internal/motion.ts":"import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n type DOMAttributes,\n type MouseEvent,\n type FocusEvent,\n type PointerEvent as ReactPointerEvent,\n} from \"react\";\nimport type { Component } from \"./manifest-entry\";\nimport { callingComponent, devValidate } from \"./devValidate\";\n\nexport type MotionMode = \"auto\" | \"none\";\nexport const MotionContext = createContext<MotionMode>(\"auto\");\n\n// Every manifest that has a `motion` prop declares this enum (a test keeps them in step), so the\n// prop is validated here once instead of in each component. Used only by the development check.\nconst MOTION_VALUES = [\"auto\", \"none\"];\nconst MOTION_PROPS: Component[\"props\"] = {\n motion: { type: \"enum\", values: MOTION_VALUES, default: \"auto\" },\n};\n\n/**\n * Local to the interaction, never a global last-input flag. CSS owns reduced motion.\n *\n * In development an unknown `mode` logs the same structured INVALID_PROP_VALUE as other enum\n * props, once per component and value. The message names the component: pass `component` to say\n * which one, or leave it out and the call site says (see callingComponent).\n */\nexport function useMotion(mode: MotionMode = \"auto\", component?: string) {\n if (process.env.NODE_ENV !== \"production\" && !MOTION_VALUES.includes(mode)) {\n devValidate(component ?? callingComponent() ?? \"OPERATOR component\", MOTION_PROPS, {\n motion: mode,\n });\n }\n const inherited = useContext(MotionContext);\n const [ready, setReady] = useState(false);\n const [keyboard, setKeyboard] = useState(false);\n const pointer = useRef(false);\n const frame = useRef<number>();\n const contactCleanup = useRef<() => void>();\n const clearContact = useCallback(() => {\n contactCleanup.current?.();\n contactCleanup.current = undefined;\n }, []);\n const cancel = useCallback(() => {\n if (frame.current !== undefined) cancelAnimationFrame(frame.current);\n }, []);\n const afterPaint = useCallback(\n (callback: () => void) => {\n cancel();\n frame.current = requestAnimationFrame(() => {\n frame.current = requestAnimationFrame(() => {\n frame.current = undefined;\n callback();\n });\n });\n },\n [cancel],\n );\n\n useEffect(() => {\n // Initial server/client state is already the correct resting state.\n afterPaint(() => setReady(true));\n return () => {\n cancel();\n clearContact();\n };\n }, [afterPaint, cancel, clearContact]);\n\n const resume = useCallback(\n () =>\n afterPaint(() => {\n setReady(true);\n setKeyboard(false);\n pointer.current = false;\n }),\n [afterPaint],\n );\n const handlers = {\n onPointerDownCapture: (event: ReactPointerEvent<HTMLElement>) => {\n cancel();\n clearContact();\n pointer.current = true;\n setReady(true);\n setKeyboard(false);\n // Contact can end outside this root, or be canceled by touch scrolling.\n // Keep the pointer path through its native click, then release it.\n const root = event.currentTarget;\n const release = (event?: globalThis.PointerEvent) => {\n if (event && !root.contains(event.target as Node | null)) pointer.current = false;\n clearContact();\n resume();\n };\n const abandon = () => {\n pointer.current = false;\n release();\n };\n document.addEventListener(\"pointerup\", release, true);\n document.addEventListener(\"pointercancel\", abandon, true);\n window.addEventListener(\"blur\", abandon);\n contactCleanup.current = () => {\n document.removeEventListener(\"pointerup\", release, true);\n document.removeEventListener(\"pointercancel\", abandon, true);\n window.removeEventListener(\"blur\", abandon);\n };\n },\n onKeyDownCapture: () => {\n cancel();\n pointer.current = false;\n setKeyboard(true);\n },\n onKeyUpCapture: resume,\n onBlurCapture: (event: FocusEvent<HTMLElement>) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null)) resume();\n },\n onClickCapture: (event: MouseEvent<HTMLElement>) => {\n // Assistive activation and element.click() have no pointer contact.\n if (event.detail === 0 && !pointer.current) setKeyboard(true);\n resume();\n },\n };\n const effective: MotionMode =\n mode === \"none\" || inherited === \"none\" || !ready || keyboard ? \"none\" : \"auto\";\n return {\n mode: effective,\n props: { \"data-oprt-motion\": effective, ...handlers },\n settleInput: () => {\n // Call after the primitive reads the edited value so controlled text is\n // not reset by a policy render during native input capture.\n cancel();\n pointer.current = false;\n setKeyboard(true);\n resume();\n },\n };\n}\n\n/** Capture first, then keep the consumer's handler and the primitive's event path. */\nexport function composeMotionHandlers<T extends DOMAttributes<HTMLElement>>(\n motion: ReturnType<typeof useMotion>[\"props\"],\n consumer: T,\n) {\n const captures = { ...motion };\n for (const key of [\n \"onPointerDownCapture\",\n \"onKeyDownCapture\",\n \"onKeyUpCapture\",\n \"onBlurCapture\",\n \"onClickCapture\",\n ] as const) {\n const internal = motion[key] as ((event: never) => void) | undefined;\n const external = consumer[key] as ((event: never) => void) | undefined;\n captures[key] = ((event: never) => {\n internal?.(event);\n external?.(event);\n }) as never;\n }\n return captures;\n}\n","_internal/MotionPopover.tsx":"\"use client\";\n\nimport {\n Popover,\n PopoverContext,\n useSlottedContext,\n type PopoverProps,\n} from \"react-aria-components\";\nimport { useLayoutEffect, useEffect, useRef, type JSX, type Ref, type RefObject } from \"react\";\nimport clsx from \"clsx\";\nimport { useMergedRefs } from \"./mergeRefs\";\nimport type { useMotion } from \"./motion\";\n\ntype MotionPopoverProps = Omit<PopoverProps, \"className\" | \"render\"> & {\n className?: string;\n motionState: ReturnType<typeof useMotion>;\n /** Named nonmodal surfaces own opening and cancellable close focus. */\n focusManagement?: \"automatic\" | \"manual\";\n /**\n * Where focus returns when the popover closes with focus inside it, for a popover opened by a\n * control other than the element it is anchored to. Without it focus returns to that anchor,\n * the trigger. DatePicker anchors its calendar to the whole date field and opens it from the\n * calendar button, and Escape returns to the button (Stage 1, B08). When the element has left\n * the page or is disabled, focus returns to the trigger as before.\n */\n returnFocusRef?: RefObject<HTMLElement | null>;\n};\n\n/** Whether `element` is still in the page and can take focus back. */\nfunction canTakeFocus(element: HTMLElement | null | undefined): element is HTMLElement {\n return !!element && element.isConnected && !element.matches(\":disabled\");\n}\n\nfunction PopoverSurface({\n domProps,\n isExiting,\n triggerRef,\n returnFocusRef,\n trigger,\n placement,\n focusManagement,\n}: {\n domProps: JSX.IntrinsicElements[\"div\"];\n isExiting: boolean;\n triggerRef: RefObject<Element | null> | undefined;\n returnFocusRef: RefObject<HTMLElement | null> | undefined;\n trigger: string | null;\n placement: string | null;\n focusManagement: \"automatic\" | \"manual\";\n}) {\n const surface = useRef<HTMLDivElement | null>(null);\n const surfaceRef = useMergedRefs<HTMLDivElement>(\n surface,\n // RAC hands over a callback or an object ref; a legacy string ref is not one it uses.\n domProps.ref as Ref<HTMLDivElement> | undefined,\n );\n const wasExiting = useRef(isExiting);\n const restoreFocus = useRef(false);\n const previousPlacement = useRef(placement);\n useLayoutEffect(() => {\n const node = surface.current;\n if (!node) return;\n if (previousPlacement.current && previousPlacement.current !== placement) {\n node.setAttribute(\"data-oprt-repositioned\", \"\");\n }\n previousPlacement.current = placement;\n // Re-enable the retained subtree before asking it to receive focus again.\n if (!isExiting) {\n node.removeAttribute(\"inert\");\n node.removeAttribute(\"aria-hidden\");\n if (wasExiting.current) node.removeAttribute(\"data-oprt-repositioned\");\n }\n if (isExiting && !wasExiting.current)\n restoreFocus.current = node.contains(document.activeElement);\n if (\n !isExiting &&\n wasExiting.current &&\n focusManagement === \"automatic\" &&\n trigger !== \"ComboBox\" &&\n !node.contains(document.activeElement)\n ) {\n const target = node.querySelector<HTMLElement>(\"[data-focused], [role=menu], [role=listbox]\");\n target?.focus({ preventScroll: true });\n }\n // React 18 and 19 differ in how they serialize the inert JSX attribute.\n node.toggleAttribute(\"inert\", isExiting);\n if (isExiting) node.setAttribute(\"aria-hidden\", \"true\");\n if (!isExiting && focusManagement === \"manual\" && wasExiting.current) {\n node.querySelector<HTMLElement>(\"[data-oprt-popover-dialog]\")?.focus({ preventScroll: true });\n }\n wasExiting.current = isExiting;\n }, [isExiting, trigger, triggerRef, placement, focusManagement]);\n useEffect(() => {\n // RAC releases outside inert in passive cleanup. Restore focus while the\n // closed popup remains mounted for its visual exit.\n if (focusManagement === \"automatic\" && isExiting && restoreFocus.current) {\n const active = document.activeElement;\n if (active === document.body || surface.current?.contains(active)) {\n const opener = returnFocusRef?.current;\n const target = canTakeFocus(opener) ? opener : (triggerRef?.current as HTMLElement | null);\n target?.focus({ preventScroll: true });\n }\n restoreFocus.current = false;\n }\n }, [isExiting, triggerRef, returnFocusRef, focusManagement]);\n return (\n <div\n {...domProps}\n // RAC forwards the trigger's dialog label even onto a generic surface.\n // Manual callers name their inner dialog; automatic overlays keep RAC's label.\n aria-labelledby={focusManagement === \"manual\" ? undefined : domProps[\"aria-labelledby\"]}\n ref={surfaceRef}\n />\n );\n}\n\n/** Use the trigger's policy so a newly mounted portal can animate on pointer open. */\nexport function MotionPopover({\n motionState,\n className,\n focusManagement = \"automatic\",\n returnFocusRef,\n ...props\n}: MotionPopoverProps) {\n const context = useSlottedContext(PopoverContext);\n // RAC prefers the prop over the context, so the surface must too, or it returns focus to a\n // trigger the popover is not anchored to.\n const triggerRef = props.triggerRef ?? context?.triggerRef;\n // RAC 1.20 forwards this runtime Overlay option but omits it from PopoverProps.\n // Its Overlay implements disableFocusManagement by omitting FocusScope. Keep\n // the adapter scoped to explicit manual callers; menus/selectors retain defaults.\n const overlayPolicy = focusManagement === \"manual\" ? { disableFocusManagement: true } : {};\n return (\n <Popover\n {...props}\n {...overlayPolicy}\n className={clsx(\"oprt-motion oprt-motion-popover\", className)}\n {...motionState.props}\n render={(domProps, { isExiting, trigger, placement }) => (\n <PopoverSurface\n domProps={{ ...domProps, ...motionState.props }}\n isExiting={isExiting}\n trigger={trigger}\n placement={placement}\n triggerRef={triggerRef}\n returnFocusRef={returnFocusRef}\n focusManagement={focusManagement}\n />\n )}\n />\n );\n}\n","_internal/navigationLandmark.ts":"import { createContext, useContext } from \"react\";\n\n/**\n * One navigation, one landmark.\n *\n * AppShell wraps its `navigation` slot in a landmark named for the workspace and provides `true`\n * around it. A Sidebar inside renders a plain block instead of a second, nested landmark, which a\n * screen reader would announce as \"navigation\" twice. The rail reads its place from context, not\n * from the shell looking at its `navigation` prop, so a rail inside a component of the\n * application's own is found too. Outside such a provider a Sidebar is its own landmark, as before.\n */\nconst InsideNavigationLandmarkContext = createContext(false);\n\nexport const InsideNavigationLandmarkProvider = InsideNavigationLandmarkContext.Provider;\n\n/** True when an ancestor already is the navigation landmark for this content. */\nexport function useInsideNavigationLandmark(): boolean {\n return useContext(InsideNavigationLandmarkContext);\n}\n","_internal/numberText.tsx":"\"use client\";\n\nimport { Fragment } from \"react\";\nimport clsx from \"clsx\";\n\n// --- Reading a value (design call DC-11, with the coordinator's ruling on edge cases) ---------------\n//\n// A number is one token: it is never split between its digits. A grouped number may break only\n// after a separator that groups its digits, and a sign, a currency sign or code or a percent sign in\n// front of it and a suffix such as \"k\", \"%\", a currency sign (US$, $US, a code) or a unit (kB, km/h,\n// 12 degrees C) behind it stay with it, with any direction marks Intl writes around them. Anything\n// else (a status, an identifier, a timestamp) may break anywhere, as R4 asks.\n//\n// StatCard reads its value with these, and steps its type down to the card itself. NumberText below\n// renders the same rule at the size of the text around it, for values that a component shows in a\n// table cell, a list or a readout.\n\nconst DIGIT = \"\\\\p{Nd}\"; // a decimal digit in any script\nconst SPACE = \"[\\\\u00a0\\\\u202f\\\\u2009 ]\"; // a space, no-break, narrow no-break or thin space\nconst NO_BREAK = \"[\\\\u00a0\\\\u202f\\\\u2009]\";\n// Direction marks (LRM, RLM, Arabic letter mark): Intl writes them around the sign of a negative\n// number in right-to-left languages.\nconst BIDI = \"[\\\\u200e\\\\u200f\\\\u061c]*\";\n// A sign or comparison sign, and a currency sign (US$, $) or code (USD, CHF) with the space after\n// it. A code needs its space, so that an identifier such as ERR404 or TRK-104 is not a number.\nconst SIGN = \"[+\\\\-\\\\u2212\\\\u00b1~\\\\u2248<>\\\\u2264\\\\u2265]{1,2}\";\nconst CURRENCY = `(?:(?:[A-Z]{1,3}\\\\p{Sc}|\\\\p{Sc})${SPACE}?|[A-Z]{3}${SPACE})`;\n// A percent or per mille sign in front of the digits, as tr-TR (%12) and eu (% 12) write it.\nconst PERCENT_FIRST = `[%\\\\u2030\\\\u066a]${SPACE}?`;\nconst PREFIX = `${BIDI}(?:${SIGN})?${BIDI}(?:${CURRENCY}|${PERCENT_FIRST})?(?:${SIGN})?`;\n// What groups digits: comma, space, no-break, narrow no-break and thin space, ' and the right single\n// quotation mark of de-CH, and the Arabic thousands separator.\nconst SEPARATOR = \"[,\\\\u00a0\\\\u202f\\\\u2009'\\\\u2019\\\\u066c ]\";\nconst DECIMAL = \"[.,\\\\u066b]\"; // the last is the Arabic decimal separator\nconst EXPONENT = `(?:[eE][+\\\\-\\\\u2212]?${DIGIT}+)?`;\n// A letter with the combining marks that follow it (the unit of an Indic language is a letter and a\n// vowel sign), and one to three of them: k, M, kPa, kr, Mio.\nconst LETTER = \"(?:\\\\p{L}\\\\p{M}*)\";\nconst LETTERS = `${LETTER}{1,3}`;\n// What follows the number and belongs to it. Each alternative may follow the direction marks that\n// Intl writes after the space (he-IL puts an RLM between the space and the sign).\nconst SUFFIX = `(?:${[\n // A degree or prime mark and its letter (the C of 12 degrees C), and the Devanagari abbreviation\n // sign that hi-IN ends it with.\n `${SPACE}?${BIDI}[\\\\u00b0\\\\u2032\\\\u2033]${LETTER}?\\\\u0970?`,\n // A percent or per mille sign, which may follow a space.\n `${SPACE}?${BIDI}[%\\\\u2030\\\\u066a]`,\n // A currency sign, which may follow a space and be followed by a country ($US, $ US).\n `${SPACE}?${BIDI}\\\\p{Sc}(?:${NO_BREAK}?${LETTERS})?`,\n // A currency sign of up to three letters (US$), which may follow a space.\n `${SPACE}?${BIDI}\\\\p{L}{1,3}\\\\p{Sc}`,\n // One to three letters (k, M, kPa, kr, a code such as USD), attached or after a no-break space.\n `${NO_BREAK}?${BIDI}${LETTERS}`,\n // A unit divided by another (km/h) or raised to a power (m/s squared), written against the digits.\n // After a space the unit is read word by word (readNumber), as before.\n `${LETTERS}[/\\\\u00b7\\\\u22c5]${LETTERS}[\\\\u00b2\\\\u00b3]?`,\n].join(\"|\")})?`;\nconst REST = `(?:${DECIMAL}${DIGIT}+)?${EXPONENT}${SUFFIX}${BIDI}`;\n// A number groups its digits with one separator throughout (the back-reference `\\3` repeats the\n// first one), so in 987 654,321 (sv-SE, fr-FR) the comma after the space groups is the decimal\n// mark, not a group.\nconst NUMBER = {\n // 1,234,567.89 and 1 234 567,89: groups of three, however they are separated.\n western: new RegExp(\n `^(${PREFIX}${DIGIT}{1,3})((${SEPARATOR})${DIGIT}{3}(?:\\\\3${DIGIT}{3})*)(${REST})$`,\n \"u\",\n ),\n // 12,34,567.89 (Indian lakh grouping): a last group of three, groups of two before it.\n indian: new RegExp(\n `^(${PREFIX}${DIGIT}{1,2})((${SEPARATOR})${DIGIT}{2}(?:\\\\3${DIGIT}{2})*\\\\3${DIGIT}{3})(${REST})$`,\n \"u\",\n ),\n // 1.234.567,89: dots group, a comma is the decimal mark. One dot group needs the comma, so that a\n // decimal like 1.234 is never taken for 1234.\n dotted: new RegExp(\n `^(${PREFIX}${DIGIT}{1,3})((?:\\\\.${DIGIT}{3})+)((?:,${DIGIT}+)?${EXPONENT}${SUFFIX}${BIDI})$`,\n \"u\",\n ),\n // Not grouped: digits with an optional decimal part. It never breaks at all.\n plain: new RegExp(\n `^${PREFIX}(?:${DIGIT}+(?:${DECIMAL}${DIGIT}+)?|${DECIMAL}${DIGIT}+)${EXPONENT}${SUFFIX}${BIDI}$`,\n \"u\",\n ),\n};\nconst GROUP = new RegExp(`(${SEPARATOR})(${DIGIT}+)`, \"gu\");\n\nexport type NumberParts = {\n /** Every piece ends with the separator after which the number may break. */\n lead: string[];\n /** The last group with its decimals and suffix, or the whole number when it is not grouped. */\n tail: string;\n};\n\n// Ruling (DC-11 boundary): an ungrouped run of more than 15 whole digits is past a number's exact\n// precision, so it is an identifier (a serial, an ICCID, a tracking number), which may wrap anywhere\n// (R4). Only the whole-number digits count: digits after a decimal mark are a fraction, a measured\n// value, so 0.0000012345678901234567 is a number (MediaFrame shows every decimal of a latency).\n// Grouped numbers and shorter runs are numbers. StatCard and NumberText both read with this.\nconst EXACT_DIGITS = 15;\n// The whole-number digits of an ungrouped number: its first run of digits, unless a decimal mark\n// comes before it (.123 has none).\nconst WHOLE_DIGITS = /(?:^|[^.,\\u066b\\p{Nd}])(\\p{Nd}+)/u;\n\n// The commonest value in a table, a readout or a card: ASCII digits, an optional sign and an optional\n// decimal point, and at most 15 whole digits. None of the grouped readings below can match it (they\n// need a separator, or two dot groups), and it passes the plain one with room to spare, so it is read\n// as a plain number without the Unicode patterns. Everything else (a unit, a currency sign, another\n// script's digits, a decimal comma, a longer run of digits) goes the long way, as before.\nconst SIMPLE_NUMBER = /^[+-]?\\d{1,15}(?:\\.\\d+)?$/;\n\n/**\n * A grouped number as pieces, a plain one as a single tail, and null for anything else, including\n * an ungrouped run of more than 15 whole digits, which is an identifier.\n */\nexport function numberParts(text: string): NumberParts | null {\n if (SIMPLE_NUMBER.test(text)) return { lead: [], tail: text };\n for (const kind of [\"western\", \"indian\", \"dotted\"] as const) {\n const match = NUMBER[kind].exec(text);\n if (!match) continue;\n // The decimals and suffix are the last capture in each pattern.\n const rest = match[match.length - 1]!;\n const groups = Array.from(match[2]!.matchAll(kind === \"dotted\" ? /(\\.)(\\p{Nd}+)/gu : GROUP));\n if (kind === \"dotted\" && groups.length < 2 && !rest.startsWith(\",\")) continue;\n const lead: string[] = [];\n let digits = match[1]!;\n for (const [, separator, next] of groups) {\n lead.push(digits + separator);\n digits = next!;\n }\n return { lead, tail: digits + rest };\n }\n if (!NUMBER.plain.test(text)) return null;\n return (WHOLE_DIGITS.exec(text)?.[1]!.length ?? 0) > EXACT_DIGITS\n ? null\n : { lead: [], tail: text };\n}\n\n/** The first word of a unit stays with the number; the rest of it may wrap. */\nexport function unitWords(unit: string | undefined): { first: string; rest: string } | null {\n const trimmed = unit?.trim();\n if (!trimmed) return null;\n const space = trimmed.search(/\\s/);\n return space === -1\n ? { first: trimmed, rest: \"\" }\n : { first: trimmed.slice(0, space), rest: trimmed.slice(space).trim() };\n}\n\nconst segmenter =\n typeof Intl !== \"undefined\" && \"Segmenter\" in Intl\n ? new Intl.Segmenter(undefined, { granularity: \"grapheme\" })\n : null;\n\n/** The text and its last character as the reader sees it (a letter with its marks, a ZWJ emoji). */\nexport function splitLast(text: string): [string, string] {\n if (segmenter) {\n let start = 0;\n let last = \"\";\n for (const { segment, index } of segmenter.segment(text)) {\n start = index;\n last = segment;\n }\n return [text.slice(0, start), last];\n }\n const characters = Array.from(text);\n const last = characters.pop() ?? \"\";\n return [characters.join(\"\"), last];\n}\n\n// --- A number with a unit written after it ------------------------------------------------------------\n//\n// Values often arrive as one string, \"1,234,567.890 kPa\", with the unit after an ordinary space. The\n// unit is the word that follows the longest number at the start of the text, when it looks like a\n// unit: up to eight letters and unit signs, with no digit or underscore (kPa, L/s, USD, m/s with\n// a superscript 2, a degree sign and C). Any other word stays text that wraps on its own (in\n// \"2 SENSOR_FEED_UNAVAILABLE\" only the 2 is held together), and a word that starts with a digit is\n// no unit at all (\"12 34\" is not twelve units of 34). Only text that starts with a number is read\n// this way: a number inside a sentence is left alone.\n//\n// An ungrouped run of more than 15 whole digits is an identifier, not a number (`numberParts`).\n\n// A space, no-break, narrow no-break or thin space between the number and its unit.\nconst GAP = /[\\u0020\\u00a0\\u202f\\u2009]+/gu;\nconst STARTS_WITH_DIGIT = /^\\p{Nd}/u;\nconst FIRST_WORD = /^\\S+/u;\n// Letters and the signs units are written with: percent and per mille, degree and primes, the slash\n// and middle dots of compound units, superscript powers and minus, parentheses, apostrophe, point\n// and hyphen. No decimal digit, no underscore.\nconst UNIT_CHARACTERS =\n /^[\\p{L}\\p{M}%\\u2030\\u00b0\\u2032\\u2033/\\u00b7\\u22c5\\u00b2\\u00b3\\u00b9\\u2070-\\u2079\\u207b()'.-]{1,8}$/u;\nconst UNIT_MARK = /[\\p{L}%\\u2030\\u00b0]/u;\nconst isUnitWord = (word: string) => UNIT_CHARACTERS.test(word) && UNIT_MARK.test(word);\n\n/** How NumberText lays a value out. Joined, the pieces are the value's text, exactly. */\nexport type NumberReading = {\n /** Every group before the last, each ending with the separator after which the number may break. */\n lead: string[];\n /** The last group with its decimals and suffix (the whole number when it is not grouped). */\n tail: string;\n /** The space before the unit, as written, and the unit's first word: they stay with `tail`. */\n gap: string;\n first: string;\n /**\n * What follows, with its leading space, which wraps as any text does: the rest of a long unit, or\n * all of the text after the number when the next word is not a unit.\n */\n rest: string;\n};\n\n/**\n * Reads a value as a number and its unit, or returns null when it is not a number. A JS number\n * reads as written (`String(value)`, never grouped); a string may carry its unit after a space.\n * With `unit`, the value itself must be a number and the unit is that text.\n */\nexport function readNumber(value: unknown, unit?: string): NumberReading | null {\n if (typeof value !== \"string\" && typeof value !== \"number\") return null;\n const text = String(value).trim();\n const words = unitWords(unit);\n const whole = numberParts(text);\n if (words) {\n return (\n whole && { ...whole, gap: \" \", first: words.first, rest: words.rest && ` ${words.rest}` }\n );\n }\n if (whole) return { ...whole, gap: \"\", first: \"\", rest: \"\" };\n // The longest number before a space, with what follows it.\n const gaps = Array.from(text.matchAll(GAP)).reverse();\n for (const gap of gaps) {\n const after = text.slice(gap.index + gap[0].length);\n if (!after || STARTS_WITH_DIGIT.test(after)) continue;\n const parts = numberParts(text.slice(0, gap.index));\n if (!parts) continue;\n const first = FIRST_WORD.exec(after)![0];\n // A unit stays with the number; any other word wraps as text, with the space before it.\n return isUnitWord(first)\n ? { ...parts, gap: gap[0], first, rest: after.slice(first.length) }\n : { ...parts, gap: \"\", first: \"\", rest: gap[0] + after };\n }\n return null;\n}\n\n/** Whether NumberText lays this value out as a number (with or without a unit after it). */\nexport const isNumberText = (value: unknown): value is string | number =>\n readNumber(value) !== null;\n\nexport type NumberTextProps = {\n /** A number, or text that may be one: \"1,234,567.890\", \"1,234,567.890 kPa\", \"-0.4 kPa\", \"42\". */\n value: string | number;\n /** A unit shown after the value, when it is not already written in `value`. */\n unit?: string | undefined;\n /** Classes for the outer span: the typeface and color of the host, and its white-space. */\n className?: string | undefined;\n};\n\n/**\n * A value as an operator reads it: a number breaks only between its digit groups, after a\n * separator, and its last group and the unit stay together (R4 and design call DC-11).\n *\n * - `1,234,567.890 kPa` may break as `1,234,` / `567.890 kPa`, never inside a group and never\n * between the number and its unit. A sign, a currency sign or a suffix (\"k\", \"%\") stays with it.\n * - The last group and the unit's first word are one run that never wraps, and an ungrouped number\n * (`42`, `1234567.89`) is one too. The text is at body size here, DC-11's floor, so a run wider\n * than its whole line is not split: at worst it runs into its host's padding.\n * - A word after the number that is not a unit (a code, an identifier) wraps as text, and an\n * ungrouped run of more than 15 digits is an identifier, not a number.\n * - Anything that is not a number (a status, an identifier, a timestamp) is left to its host's\n * wrapping; with `unit`, the unit stays beside its last character.\n *\n * Every piece is inline, and the text content is the value's own text, so copying and screen\n * readers get it exactly. A host with `white-space: nowrap` keeps the whole value on one line.\n */\nexport function NumberText(props: NumberTextProps) {\n return numberText(props);\n}\n\n/**\n * What NumberText renders, as a plain function for a host that lays out thousands of values (a\n * table's cells): it has no hooks, so calling it needs no component of its own, and a table of a\n * thousand rows does not mount thousands of them just to hand back a span. The markup is the same.\n */\nexport function numberText({ value, unit, className }: NumberTextProps) {\n const reading = readNumber(value, unit);\n if (!reading) {\n const text = String(value);\n const words = unitWords(unit);\n if (!words) return <span className={className}>{text}</span>;\n const [head, last] = splitLast(text);\n return (\n <span className={className}>\n {head}\n <span className=\"whitespace-nowrap\">{`${last}\\u00a0${words.first}`}</span>\n {words.rest && ` ${words.rest}`}\n </span>\n );\n }\n const { lead, tail, gap, first, rest } = reading;\n const run = tail + gap + first;\n // Nothing in the value may break: one span that never wraps.\n if (lead.length === 0 && !rest) {\n return (\n <span\n data-oprt-number=\"\"\n className={\n className?.includes(\"whitespace-nowrap\") &&\n className.split(/\\s+/).includes(\"whitespace-nowrap\")\n ? className\n : clsx(className, \"whitespace-nowrap\")\n }\n >\n {run}\n </span>\n );\n }\n return (\n <span data-oprt-number=\"\" className={className}>\n {lead.map((piece, index) => (\n <Fragment key={index}>\n <span className=\"whitespace-nowrap\">{piece}</span>\n <wbr />\n </Fragment>\n ))}\n <span data-oprt-number-run=\"\" className=\"whitespace-nowrap\">\n {run}\n </span>\n {rest}\n </span>\n );\n}\n","_internal/overlay.ts":"import { cva } from \"class-variance-authority\";\nimport { Fragment, isValidElement, type ReactElement, type ReactNode } from \"react\";\nimport type { Placement } from \"react-aria-components\";\n\nexport type OverlaySide = \"top\" | \"right\" | \"bottom\" | \"left\";\nexport type OverlayAlign = \"start\" | \"center\" | \"end\";\n\n/**\n * React Aria's placement for a side and an alignment (Popover-W2-1). Above or below the trigger it\n * takes start and end, which it mirrors right to left. Beside the trigger its alignments are top\n * and bottom: \"right start\" is not a placement, and React Aria read it as \"right left\" and lined up\n * the bottom edges, so align=\"start\" beside a trigger behaved like \"end\". Start lines up the top\n * edges and end the bottom edges.\n */\nexport function toPlacement(side: OverlaySide, align: OverlayAlign): Placement {\n if (align === \"center\") return side;\n if (side === \"left\" || side === \"right\") return `${side} ${align === \"start\" ? \"top\" : \"bottom\"}`;\n return `${side} ${align}`;\n}\n\n/**\n * How far every floating surface keeps from the screen edges, in px (Popover-W2-2): 16, the inset\n * of Dialog and CommandPalette. Each surface hands it to React Aria as containerPadding, which\n * React Aria keeps where it slides a surface along its trigger to fit and where it caps its height\n * (its own default is 12). Beside the trigger, where React Aria does not limit the width, the\n * far-side margin keeps the same distance, and the width cap below leaves it on both sides.\n */\nexport const SCREEN_MARGIN = 16;\n\n/*\n * Shared rules for floating surfaces (Stage 1, batch B02).\n *\n * Width (design call DC-7): text wraps at a readable measure, and a surface is never wider than\n * the viewport less the screen margin on each side (--oprt-space-2 each, --oprt-space-4 in all).\n * Popovers, menus, the Select and ComboBox lists and DatePicker's calendar all follow it.\n *\n * Above or below its trigger a surface takes its own content width within that cap (w-max), and\n * React Aria shifts it to fit. React Aria places it there by its left edge, which for end or\n * center alignment, or a shift to fit, follows the width it measured; a surface sized by the room\n * left of that edge (width: auto) then changed width each time it was placed, and each change\n * re-ran the placement inside a ResizeObserver callback until the browser reported a loop\n * (DropdownMenu-W2-1). Beside its trigger a surface keeps shrinking into the room there, where its\n * position does not follow its width.\n */\nconst CONTENT_WIDTH_ABOVE_OR_BELOW = \"data-[placement=top]:w-max data-[placement=bottom]:w-max\";\n/**\n * Beside its trigger a surface shrinks into the room there; the margin on its far side, the\n * screen margin (2u), stops it short of the screen edge, also after it flips sides, instead of\n * running flush to it (Popover-W2-1).\n */\nconst MARGIN_BESIDE = \"data-[placement=left]:ml-2u data-[placement=right]:mr-2u\";\n\n/** Tooltips wrap at about 20rem. */\nexport const TOOLTIP_WIDTH = `max-w-[min(20rem,calc(100vw-var(--oprt-space-4)))] ${CONTENT_WIDTH_ABOVE_OR_BELOW} ${MARGIN_BESIDE}`;\n\n/**\n * Popovers and menus wrap at about 30rem. One surface can be widened through its className by\n * setting --oprt-popover-max-width (for example `[--oprt-popover-max-width:48rem]`); the\n * viewport cap still applies.\n */\nexport const POPOVER_WIDTH = `max-w-[min(var(--oprt-popover-max-width,30rem),calc(100vw-var(--oprt-space-4)))] ${CONTENT_WIDTH_ABOVE_OR_BELOW} ${MARGIN_BESIDE}`;\n\n/*\n * Action menus (design call DC-6): DropdownMenu and ContextMenu share one surface, motion, offset,\n * row metrics, wrapping, width rule and written danger cue.\n */\n\n/** Distance between a menu and its trigger, in px. */\nexport const MENU_OFFSET = 4;\n\n/**\n * The menu popover. React Aria writes an inline max-height that fits the space beside the\n * trigger; the popover scrolls inside it, so a tall menu never spills over its trigger (R7).\n */\nexport const MENU_SURFACE = `z-50 min-w-[10rem] ${POPOVER_WIDTH} overflow-y-auto overscroll-contain border border-oprt-border bg-oprt-surface-1 py-1u font-sans`;\n\n/**\n * The metrics of a row in a menu or in CommandPalette's list. As in Button, the vertical padding\n * is what is left of the control height around one line and the 2 px of border (7 px), so a\n * one-line row is exactly 32 px and a wrapped or danger row keeps the same padding as it grows.\n */\nexport const MENU_ROW =\n \"flex min-h-oprt-md items-center justify-between gap-3u border border-transparent px-3u py-[calc((var(--oprt-height-md)-2px-var(--oprt-line-base))/2)]\";\n\n/**\n * A menu or list row in forced colors (Windows high contrast). Forced colors replace backgrounds\n * with the page color, flatten border colors and drop shadows, so the row about to be chosen\n * (data-focused) lost its only cue; and Tailwind's `outline-hidden`, a transparent outline, is painted,\n * so every row looked focused. The active row is drawn in Highlight with HighlightText, the system's\n * own pair for it, kept from being adjusted; the resting outline goes; a disabled row is GrayText.\n * Shared by menu rows, CommandPalette rows and listbox options. Parts of a row drawn in a color of\n * their own (an icon, a shortcut) take the row's color in forced colors (`forced-colors:[color:inherit]`).\n */\nexport const FORCED_ACTIVE_ROW =\n \"forced-colors:[outline-style:none] forced-colors:data-[focused]:[forced-color-adjust:none] forced-colors:data-[focused]:bg-[color:Highlight] forced-colors:data-[focused]:border-[color:Highlight] forced-colors:data-[focused]:text-[color:HighlightText] forced-colors:data-[focused]:shadow-none forced-colors:data-[disabled]:text-[color:GrayText]\";\n\n/** A menu row: the shared metrics, plus the menu's own focus, disabled and danger states. */\nexport const menuItemStyles = cva(\n `${MENU_ROW} text-oprt-base font-medium outline-hidden cursor-pointer data-[focused]:bg-oprt-surface-3 data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed ${FORCED_ACTIVE_ROW}`,\n {\n variants: { danger: { true: \"text-oprt-error\", false: \"text-oprt-text\" } },\n defaultVariants: { danger: false },\n },\n);\n\n/** The label column: shrinks, and long labels and identifiers break anywhere (R4). */\nexport const MENU_ITEM_TEXT = \"min-w-0 oprt-wrap-anywhere\";\n/** The label itself: an uppercase control label; code, kbd and IDs keep their own case. */\nexport const MENU_ITEM_LABEL =\n \"uppercase tracking-oprt-caps [&_code]:normal-case [&_code]:tracking-normal [&_kbd]:normal-case [&_kbd]:tracking-normal\";\n/** The written cue under a danger item's label, so danger is not shown by color alone. */\nexport const MENU_ITEM_DESCRIPTION = \"block text-oprt-sm normal-case tracking-normal\";\nexport const DESTRUCTIVE_ACTION = \"Destructive action\";\n/** Display text for an application-owned shortcut. */\nexport const MENU_ITEM_SHORTCUT =\n \"shrink-0 font-mono text-oprt-sm normal-case tracking-normal text-oprt-text-dim forced-colors:[color:inherit]\";\n/** The non-interactive message an empty menu, or CommandPalette with no result, shows. */\nexport const MENU_EMPTY = \"m-0 px-3u py-2u text-oprt-base normal-case text-oprt-text-dim\";\n\n/**\n * Whether a trigger prop is one element a primitive can clone and hand a ref to. A string,\n * number, fragment or list is not; the component wraps it in a control of its own instead of\n * letting React Aria throw.\n */\nexport function isSingleElement(node: ReactNode): node is ReactElement {\n return isValidElement(node) && node.type !== Fragment;\n}\n","_internal/segmentField.ts":"import {\n createContext,\n useCallback,\n useRef,\n useState,\n type FocusEvent,\n type KeyboardEvent,\n} from \"react\";\nimport type { DateDraftSnapshot } from \"./date\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\n\n/*\n * Plumbing shared by the segmented date and time fields (DatePicker, TimeField, and through them\n * DateRangePicker and DateTimePicker), which edit a value through React Aria's spin-button\n * segments and keep the operator's partial draft apart from the value the application holds.\n * One copy, so a fix lands once (DatePicker-R7).\n */\n\n/** Whether two draft snapshots show the same thing. */\nexport function sameSnapshot(a: DateDraftSnapshot, b: DateDraftSnapshot): boolean {\n return a.value === b.value && a.empty === b.empty && a.valid === b.valid;\n}\n\n/**\n * Hands the field's visible draft to `report` after each commit, but only when the draft or\n * `report` itself changed (TimeField-R2).\n *\n * React Aria builds a new `segments` array on every render, so an unguarded effect reported after\n * every commit and left React's same-state bail-out to end the cycle. In development StrictMode,\n * the Next.js default, React re-runs mount effects outside the commit, which queues that first\n * report at a lower priority. Every later update was then rebased on the state from before it, so\n * the bail-out never happened, and the first value the application accepted looped until React\n * stopped it with \"Maximum update depth exceeded\".\n *\n * Give `report` a new identity whenever what it writes to changes (the value it is the draft of),\n * so a reset draft is reported again even when the segments read the same.\n */\nexport function useReportSegments<S>(\n segments: S,\n read: (segments: S) => DateDraftSnapshot,\n report: (snapshot: DateDraftSnapshot) => void,\n): void {\n const last = useRef<{ snapshot: DateDraftSnapshot; report: typeof report } | null>(null);\n useIsomorphicLayoutEffect(() => {\n const snapshot = read(segments);\n const previous = last.current;\n if (previous && previous.report === report && sameSnapshot(previous.snapshot, snapshot)) return;\n last.current = { snapshot, report };\n report(snapshot);\n }, [segments, read, report]);\n}\n\n/**\n * Props for the element that stands for a segmented field, or a group of them, to the rest of the\n * page: it takes focus (tabIndex -1), so an error summary can focus it by id, and passes focus on\n * to its first segment. Nothing happens while `disabled`, or when `keep` says the focus should stay\n * on the element itself (DateTimePicker keeps focus that it takes back for a disabled Apply).\n */\nexport function firstSegmentFocus<E extends HTMLElement>(\n disabled: boolean,\n keep?: (event: FocusEvent<E>) => boolean,\n): { tabIndex: -1; onFocus: (event: FocusEvent<E>) => void } {\n return {\n tabIndex: -1,\n onFocus: (event) => {\n if (event.target !== event.currentTarget || disabled || keep?.(event)) return;\n event.currentTarget.querySelector<HTMLElement>(\"[role=spinbutton]\")?.focus();\n },\n };\n}\n\n/**\n * The box around a field's segments: a control outlined at 3:1 (DC-3); disabled, it dims its text\n * instead of fading (DC-9, the disabled-field ruling). The browser's ring is suppressed only while\n * the box itself has focus, so forced colors paint no permanent outline (DatePicker-R3).\n */\nexport const FIELD_BOX_STYLES =\n \"flex min-h-oprt-md min-w-0 items-center border border-oprt-border-control bg-oprt-surface-2 px-1u focus-visible:outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[invalid]:border-oprt-error data-[disabled]:cursor-not-allowed data-[disabled]:text-oprt-text-dim\";\n\n/**\n * One segment. React Aria marks a focused segment data-focused, and the house treatment is a fill\n * and the focus shadow; forced colors drop both, so the focused segment also draws a\n * system-colored outline inside itself there (DatePicker-R3).\n */\nexport const SEGMENT_STYLES =\n \"flex min-h-3u min-w-3u items-center justify-center px-0.5u font-mono text-oprt-base font-medium tabular-nums focus:outline-hidden forced-colors:data-[focused]:outline-solid forced-colors:data-[focused]:outline-2 forced-colors:data-[focused]:-outline-offset-2 forced-colors:data-[focused]:outline-[CanvasText] data-[type=literal]:min-w-0 data-[type=literal]:px-0 data-[placeholder]:text-oprt-text-placeholder data-[disabled]:data-[placeholder]:text-oprt-text-dim data-[focused]:bg-oprt-surface-3 data-[focus-visible]:shadow-oprt-focus data-[disabled]:cursor-not-allowed\";\n\n/**\n * A field's label, in Field's style: its own case, no caps spacing (B04 NumberInput-R5 ruling),\n * and an unbroken identifier in it wraps anywhere instead of running out of a narrow field (R4).\n */\nexport const FIELD_LABEL_STYLES = \"m-0 min-w-0 font-medium oprt-wrap-anywhere\";\n\n/**\n * The two fields of DateRangePicker (start and end) and DateTimePicker (date and time) sit side by\n * side when each gets 192 px of the picker's own width, and stack otherwise, whatever the window\n * (DateRangePicker-R1, DateTimePicker-R1, R1). Intrinsic columns, not a container query, so a\n * picker inside a shrink-to-fit host still lays out.\n */\nexport const FIELD_COLUMNS =\n \"grid min-w-0 grid-cols-[repeat(auto-fit,minmax(min(100%,calc(24*var(--oprt-space-1))),1fr))] items-start gap-2u\";\n\nconst EDITING_KEYS = new Set([\n \"Backspace\",\n \"Delete\",\n \"ArrowUp\",\n \"ArrowDown\",\n \"PageUp\",\n \"PageDown\",\n \"Home\",\n \"End\",\n]);\n\n/** Whether a key press edits a segment: a digit or an editing key, with no shortcut modifier. */\nexport function isEditingKey(\n event: Pick<KeyboardEvent, \"key\" | \"altKey\" | \"ctrlKey\" | \"metaKey\">,\n): boolean {\n return (\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey &&\n (/^[0-9]$/.test(event.key) || EDITING_KEYS.has(event.key))\n );\n}\n\ntype DraftState = {\n base: string | null;\n snapshot: DateDraftSnapshot;\n rejected: boolean;\n /** What to tell the operator about a rejected edit, such as the bound it crossed. */\n rejection: string | null;\n};\n\n/**\n * The draft of a segmented field: what its segments show, which can differ from the value the\n * application holds (a partial or impossible date, an edit outside the bounds), and whether the\n * field rejected the operator's last edit.\n *\n * - `value` is the controlled value; a new one replaces the draft in the same render.\n * - `displayed` is that value as the field shows it, or null when it shows nothing.\n * - `valid` says whether `value` itself is one the field accepts (null counts).\n * - `inactive` is a disabled, read-only or misconfigured field, which an editing key does not\n * wake.\n *\n * Spread `editProps` on an element around the field: an editing key or typed input clears a\n * rejection, and leaving the field with an incomplete or impossible draft rejects it. React Aria\n * then emits a constrained value as it blurs, which the field must ignore: see `blurRejected`.\n * Hand `report` to `useReportSegments`.\n */\nexport function useSegmentDraft({\n value,\n displayed,\n valid,\n inactive,\n}: {\n value: string | null;\n displayed: string | null;\n valid: boolean;\n inactive: boolean;\n}) {\n const initial: DraftState = {\n base: value,\n snapshot: { value: displayed, empty: displayed === null, valid },\n rejected: false,\n rejection: null,\n };\n const [draft, setDraft] = useState(initial);\n const current = draft.base === value ? draft : initial;\n const raw = useRef(current.snapshot);\n const rejectBlur = useRef(false);\n const report = useCallback(\n (snapshot: DateDraftSnapshot) => {\n raw.current = snapshot;\n setDraft((previous) =>\n previous.base === value && sameSnapshot(previous.snapshot, snapshot)\n ? previous\n : previous.base === value\n ? { ...previous, snapshot }\n : { base: value, snapshot, rejected: false, rejection: null },\n );\n },\n [value],\n );\n const beginEdit = () => {\n if (inactive) return;\n rejectBlur.current = false;\n setDraft((previous) =>\n previous.rejected ? { ...previous, rejected: false, rejection: null } : previous,\n );\n };\n return {\n snapshot: current.snapshot,\n rejected: current.rejected,\n /** The message for the rejected edit, or null to use the field's general one. */\n rejection: current.rejected ? current.rejection : null,\n /** The field holds something other than `value`: an edit, or a rejected one. */\n pending:\n current.rejected ||\n current.snapshot.value !== displayed ||\n current.snapshot.empty !== (displayed === null),\n report,\n editProps: {\n onKeyDownCapture: (event: KeyboardEvent<HTMLElement>) => {\n if (isEditingKey(event)) beginEdit();\n },\n onBeforeInputCapture: beginEdit,\n onBlurCapture: (event: FocusEvent<HTMLElement>) => {\n if (event.currentTarget.contains(event.relatedTarget as Node | null)) return;\n // React Aria constrains an impossible date as the field blurs. Reject that emission and\n // keep the invalid-draft signal until the operator edits again or the value changes.\n rejectBlur.current = !raw.current.valid;\n if (rejectBlur.current)\n setDraft((previous) => ({ ...previous, rejected: true, rejection: null }));\n },\n },\n /** Whether React Aria's next change is the constrained value it emits after a rejected blur. */\n blurRejected: () => rejectBlur.current,\n /** Starts a new choice (opening the calendar): React Aria's next change is the operator's. */\n releaseBlur: () => {\n rejectBlur.current = false;\n },\n /** Rejects the operator's edit, with a message that says why (the bound it crossed), if any. */\n reject: (message?: string | null) =>\n setDraft((previous) => ({ ...previous, rejected: true, rejection: message ?? null })),\n accept: () => setDraft((previous) => ({ ...previous, rejected: false, rejection: null })),\n };\n}\n\n/**\n * What a group that owns segmented fields (DateRangePicker, DateTimePicker) tells the DatePicker\n * and TimeField inside it. Not public API.\n */\nexport type SegmentGroup = {\n /**\n * The group is required: every segment is marked required for assistive technology, and the\n * group alone says that a value is missing, so the message is not repeated (DateRangePicker-R3).\n */\n required: boolean;\n /**\n * Days outside these `YYYY-MM-DD` dates cannot be chosen in a DatePicker's calendar. The group\n * checks its own bounds and names them, so the field adds no message of its own\n * (DateTimePicker-R3).\n */\n calendar?: { min: string | undefined; max: string | undefined };\n};\n\nexport const SegmentGroupContext = createContext<SegmentGroup | null>(null);\n","_internal/suggestions.ts":"// Editorial redirects for the highest-value agent mistakes.\n// Shape: component -> prop -> received-bad-value -> human suggestion.\n// Anything not listed here falls back to a generic manifest-derived message.\nexport const SUGGESTION_OVERRIDES: Record<string, Record<string, Record<string, string>>> = {\n Button: {\n variant: {\n success:\n \"'success' is not a Button variant. Use variant='primary' for positive actions, or a Badge (variant='success') to display status.\",\n error: \"'error' is not a Button variant. Use variant='danger' for destructive actions.\",\n warning:\n \"'warning' is not a Button variant. Use variant='danger', or an Alert/Badge to display status.\",\n info: \"'info' is not a Button variant. Use variant='secondary' or 'tertiary'.\",\n },\n },\n Badge: {\n variant: {\n primary:\n \"'primary' is not a Badge variant. Use 'neutral', or a status variant (success/warning/error/info).\",\n danger: \"'danger' is not a Badge variant. Use 'error' for failed/critical status.\",\n },\n },\n};\n","_internal/timestamp.ts":"/**\n * UTC timestamps for components that show an instant, given as epoch milliseconds.\n *\n * Operators see one rendering of an instant (design call DC-4):\n *\n * - `\"seconds\"` (the default): `YYYY-MM-DD HH:MM:SS UTC`. Families adopt this one.\n * - `\"milliseconds\"`: `YYYY-MM-DD HH:MM:SS.mmm UTC`, always three digits. Only components where\n * sub-second timing is the point use it: playback position, event sequence and timelines.\n *\n * Raw ISO strings are not displayed. A machine-readable `<time dateTime>` attribute stays ISO,\n * `new Date(value).toISOString()`, once `isValidTimestamp` has accepted the value.\n *\n * Both helpers are total: they never throw on bad input, so a component can show its own\n * \"Time unknown\" text instead of failing to render.\n */\n\n/** How much of the clock to show: whole seconds, or seconds with three digits of milliseconds. */\nexport type TimestampPrecision = \"seconds\" | \"milliseconds\";\n\nexport type TimestampValidity = {\n /** Require a whole number of milliseconds. Use it where the value is an exact captured instant. */\n integer?: boolean;\n /**\n * `\"1-9999\"` restricts the UTC year to 0001 through 9999 for components whose inputs and\n * parsers cannot express anything else. Default `\"any\"`: every instant a Date can hold.\n */\n years?: \"any\" | \"1-9999\";\n};\n\n// A Date holds at most this many milliseconds either side of the epoch (+/-275760 years).\nconst MAX_TIME = 8.64e15;\n\n/**\n * Whether `value` is an instant: a finite number inside the Date range. Not a string, a Date or\n * a numeric string, which callers must convert on purpose.\n */\nexport function isValidTimestamp(value: unknown, options: TimestampValidity = {}): value is number {\n if (typeof value !== \"number\" || !Number.isFinite(value) || Math.abs(value) > MAX_TIME) {\n return false;\n }\n if (options.integer === true && !Number.isInteger(value)) return false;\n if (options.years === \"1-9999\") {\n const year = new Date(value).getUTCFullYear();\n if (year < 1 || year > 9999) return false;\n }\n return true;\n}\n\n/**\n * `YYYY-MM-DD HH:MM:SS UTC`, with `.mmm` after the seconds for `precision: \"milliseconds\"`.\n * Seconds drop the milliseconds; they are never rounded up. Years outside 0000 through 9999 keep\n * the extended-year form Date uses (`+010000-01-01`), so a far-future instant is never shown as\n * a different date. Returns null when `value` is not an instant (see `isValidTimestamp`).\n */\nexport function formatUtcTimestamp(\n value: unknown,\n options: { precision?: TimestampPrecision } = {},\n): string | null {\n if (!isValidTimestamp(value)) return null;\n const iso = new Date(value).toISOString();\n const separator = iso.indexOf(\"T\");\n // iso ends in \"Z\"; what is left of the time is HH:MM:SS.mmm.\n const clock = iso.slice(separator + 1, -1);\n const shown = options.precision === \"milliseconds\" ? clock : clock.slice(0, 8);\n return `${iso.slice(0, separator)} ${shown} UTC`;\n}\n","_internal/unavailable.ts":"import { createContext, useContext } from \"react\";\n\n/**\n * Design call DC-1: an unavailable control that carries an explanation stays reachable.\n *\n * A Tooltip, or a Popover with a description, provides `true` around its trigger when the\n * trigger is disabled. A disabled Button inside it then renders `aria-disabled=\"true\"` instead\n * of the native `disabled` attribute: it stays in the Tab order, announces itself as unavailable,\n * shows the explanation on focus or tap, keeps its disabled look, and never activates. Outside\n * such a provider a disabled Button keeps the native attribute, as before.\n */\nconst FocusableWhenUnavailableContext = createContext(false);\n\nexport const FocusableWhenUnavailableProvider = FocusableWhenUnavailableContext.Provider;\n\n/** True when the nearest provider asks a disabled control to stay focusable. */\nexport function useFocusableWhenUnavailable(): boolean {\n return useContext(FocusableWhenUnavailableContext);\n}\n","_internal/useActionFocus.ts":"import {\n useImperativeHandle,\n useRef,\n type FocusEvent,\n type FocusEventHandler,\n type ForwardedRef,\n} from \"react\";\nimport { useRestoreFocusOnRemoval } from \"./useRestoreFocusOnRemoval\";\n\ntype CallerFocusHandlers = {\n onFocusCapture?: FocusEventHandler<HTMLDivElement> | undefined;\n onBlurCapture?: FocusEventHandler<HTMLDivElement> | undefined;\n};\n\n/**\n * Keep focus in the result context if its focused action is removed or disabled, and only then:\n * a user who has moved on is not pulled back. The region itself takes focus (it has tabIndex -1).\n *\n * Spread the result on the root: its ref, and focus handlers that run the caller's own first.\n */\nexport function useActionFocus(\n ref: ForwardedRef<HTMLDivElement>,\n { onFocusCapture, onBlurCapture }: CallerFocusHandlers = {},\n) {\n const root = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => root.current!, []);\n const restore = useRestoreFocusOnRemoval(root);\n return {\n ref: root,\n onFocusCapture: (event: FocusEvent<HTMLDivElement>) => {\n onFocusCapture?.(event);\n restore.onFocusCapture(event);\n },\n onBlurCapture: (event: FocusEvent<HTMLDivElement>) => {\n onBlurCapture?.(event);\n restore.onBlurCapture(event);\n },\n };\n}\n","_internal/useElementWidth.ts":"import { useEffect, useRef, useState, type RefObject } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\n\n/**\n * A ResizeObserver, or null where the platform has none: jsdom, other test DOMs and engines\n * that predate it. Components must keep working there, so never construct one unguarded.\n */\nexport function createResizeObserver(callback: ResizeObserverCallback): ResizeObserver | null {\n return typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(callback);\n}\n\n/** The values a measurement can be reduced to. Primitives keep a repeated answer from re-rendering. */\ntype Measured = string | number | boolean | null;\n\ntype ElementWidthOptions<T extends Measured> = {\n /**\n * What the first render returns, and what is kept wherever nothing can be measured (server\n * rendering, an engine or test DOM without ResizeObserver). Choose the layout you want there.\n */\n initial: T;\n /** Widths at or below this are not a measurement and are ignored. Default 0, which skips a hidden container. */\n min?: number;\n /**\n * Reduce the measured width to the value to keep, such as `width < threshold` to know whether\n * a layout is stacked. A render happens only when the result changes, so dragging a window\n * edge does not re-render on every frame. Return a number, string, boolean or null. A\n * different function is applied to the last measured width without waiting for a resize.\n */\n select?: (width: number) => T;\n};\n\n/**\n * The width of the element in `ref`, kept current as it resizes.\n *\n * It measures the content box, which is what `ResizeObserver` reports as `contentRect.width`:\n * padding and border are excluded, and CSS transforms do not change it. Reading\n * `getBoundingClientRect().width` once and then switching to the observer, as components used to,\n * measured two different boxes. Every value, including the first, now comes from the observer, so\n * nothing is measured until the observer's first report. Until then, and always where the platform\n * has no ResizeObserver, the hook returns `initial`.\n *\n * It observes the element present when the component mounts.\n */\nexport function useElementWidth<E extends Element, T extends Measured = number>(\n ref: RefObject<E | null>,\n { initial, min = 0, select }: ElementWidthOptions<T>,\n): T {\n const [value, setValue] = useState<T>(initial);\n const measured = useRef<number | null>(null);\n const latest = useRef({ min, select });\n useIsomorphicLayoutEffect(() => {\n latest.current = { min, select };\n });\n\n useEffect(() => {\n const element = ref.current;\n if (!element) return;\n const observer = createResizeObserver((entries) => {\n const width = entries[entries.length - 1]?.contentRect.width;\n if (width === undefined || !Number.isFinite(width) || width <= latest.current.min) return;\n measured.current = width;\n const choose = latest.current.select;\n setValue(choose ? choose(width) : (width as unknown as T));\n });\n observer?.observe(element);\n return () => observer?.disconnect();\n }, [ref]);\n\n useEffect(() => {\n if (select && measured.current !== null) setValue(select(measured.current));\n }, [select]);\n\n return value;\n}\n","_internal/useIsMounted.ts":"import { useCallback, useRef } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\n\n/**\n * A stable `() => boolean` that reports whether the component is currently mounted, for async\n * work that must not update state or call back into the application after unmount.\n *\n * It is false while rendering and becomes true in the layout phase of the commit that mounts\n * the component, so passive effects and everything after them see true. It goes false in\n * cleanup, and true again if Strict Mode replays the effect.\n *\n * It only reports mounting. Teardown that belongs to the caller (canceling a request, bumping a\n * generation counter, clearing a session) stays in the caller's own effect cleanup.\n */\nexport function useIsMounted(): () => boolean {\n const mounted = useRef(false);\n useIsomorphicLayoutEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n return useCallback(() => mounted.current, []);\n}\n","_internal/useIsomorphicLayoutEffect.ts":"import { useEffect, useLayoutEffect } from \"react\";\n\n/**\n * `useLayoutEffect` in a browser, `useEffect` wherever there is no DOM.\n *\n * Layout effects never run on the server, and React 18 warns when it renders one there. Use this\n * for work that has to happen before the browser paints, such as measuring or publishing a value\n * the first paint depends on. The choice is made once, when the module loads.\n *\n * The hook name is not one `react-hooks/exhaustive-deps` knows. Configure it with\n * `additionalHooks: \"(useIsomorphicLayoutEffect)\"`, or the rule skips every call.\n */\nexport const useIsomorphicLayoutEffect =\n typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n","_internal/useRestoreFocusOnRemoval.ts":"import { useCallback, useEffect, useRef, type FocusEvent, type RefObject } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"./useIsomorphicLayoutEffect\";\n\ntype RestoreFocusOptions<S> = {\n /**\n * Called when a descendant takes focus. Return what `findTarget` will need later, such as the\n * element's row index, or null to leave this element untracked. By default every descendant is\n * tracked with no state.\n */\n track?: (element: HTMLElement) => S | null;\n /**\n * Whether the tracked element no longer takes focus. Default: it left the document or is\n * disabled.\n */\n isGone?: (element: HTMLElement, state: S) => boolean;\n /**\n * Where focus goes. Default: the root, which then needs `tabIndex={-1}`. Return null to leave\n * focus where it is.\n */\n findTarget?: (state: S, prior: HTMLElement) => HTMLElement | null | undefined;\n};\n\ntype Tracked<S> = { element: HTMLElement; state: S };\n\n/** The default rule for \"no longer takes focus\": it left the document, or it is disabled. */\nconst goneByDefault = (element: HTMLElement) =>\n !element.isConnected || element.matches(\":disabled\");\n\n/**\n * Keep keyboard focus inside a region when the control that held it is removed or disabled.\n *\n * Returns `onFocusCapture` and `onBlurCapture` for the root element, to chain after any the\n * caller's own props supply. It remembers which descendant has focus and, after each commit,\n * moves focus to the target only when that element is gone, disabled and focus is still on it or\n * has fallen to the page. Every other case leaves focus alone, above all a user who has moved on:\n * a blur that happens while the element is still there and enabled forgets it, so a later removal\n * never pulls focus back into a component the user already left.\n *\n * Choosing the target stays with the caller (`findTarget`): the next row, the Add button, the\n * region itself.\n */\nexport function useRestoreFocusOnRemoval<E extends HTMLElement, S = undefined>(\n root: RefObject<E | null>,\n options: RestoreFocusOptions<S> = {},\n): {\n onFocusCapture: (event: FocusEvent<E>) => void;\n onBlurCapture: (event: FocusEvent<E>) => void;\n} {\n const tracked = useRef<Tracked<S> | null>(null);\n const latest = useRef(options);\n useIsomorphicLayoutEffect(() => {\n latest.current = options;\n });\n\n // Stable, and reads the latest options when it runs.\n const isGone = useCallback(\n (element: HTMLElement, state: S) => (latest.current.isGone ?? goneByDefault)(element, state),\n [],\n );\n\n const onFocusCapture = useCallback(\n (event: FocusEvent<E>) => {\n const element = event.target as HTMLElement;\n const host = root.current;\n // The root takes focus as a fallback, and focus inside a portal is not part of this region.\n if (!host || element === host || !host.contains(element)) {\n tracked.current = null;\n return;\n }\n const track = latest.current.track;\n const state = track ? track(element) : (undefined as S);\n tracked.current = state === null ? null : { element, state };\n },\n [root],\n );\n\n const onBlurCapture = useCallback(\n (event: FocusEvent<E>) => {\n const element = event.target as HTMLElement;\n if (tracked.current?.element !== element) return;\n // Some browsers blur an element as they remove it, while it is still attached. Decide after\n // the removal has happened: still there and still usable means the user moved away.\n queueMicrotask(() => {\n const current = tracked.current;\n if (current?.element !== element) return;\n if (!isGone(element, current.state)) tracked.current = null;\n });\n },\n [isGone],\n );\n\n useEffect(() => {\n const prior = tracked.current;\n if (!prior || !isGone(prior.element, prior.state)) return;\n tracked.current = null;\n const active = document.activeElement;\n if (active !== null && active !== document.body && active !== prior.element) return;\n const find = latest.current.findTarget;\n const target = find ? find(prior.state, prior.element) : root.current;\n target?.focus();\n });\n\n return { onFocusCapture, onBlurCapture };\n}\n","ActionStatus/ActionStatus.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ActionStatusManifest: Record<string, Component> = {\n ActionStatus: {\n description:\n \"Stateless authoritative action outcome display with optional application-supported recovery callbacks. When the focused recovery is withdrawn or disabled, focus moves to the result, a group named by its label. Long text wraps.\",\n props: {\n state: {\n type: \"enum\",\n values: [\n \"submitted\",\n \"accepted\",\n \"rejected\",\n \"in-progress\",\n \"complete\",\n \"partial\",\n \"failed\",\n \"timeout\",\n \"queued\",\n \"running\",\n \"cancel-requested\",\n \"canceled\",\n ],\n required: true,\n description:\n \"Authoritative application-supplied lifecycle state. Timeout does not imply rejection or completion. A cancel request does not establish cancellation; a supplied complete result can still follow cancel-requested.\",\n },\n label: {\n type: \"string\",\n required: true,\n },\n message: {\n type: \"string\",\n },\n requestId: {\n type: \"string\",\n },\n onRetry: {\n type: \"function\",\n description: \"Render Retry only when retry is supported. Does not change state.\",\n },\n onCancel: {\n type: \"function\",\n description: \"Render Cancel only when cancellation is supported. Does not change state.\",\n },\n retryLabel: {\n type: \"string\",\n default: \"Retry\",\n },\n cancelLabel: {\n type: \"string\",\n default: \"Cancel\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description: \"Disables recovery actions without hiding supplied outcomes.\",\n },\n },\n composable_with: [\"Inspector\", \"Alert\", \"AppShell\"],\n never_inside: [\"Button\"],\n example:\n '<ActionStatus state={result.state} label=\"Refresh source\" message={result.message} requestId={result.id} onRetry={canRetry ? retry : undefined} />',\n },\n};\n","ActionStatus/ActionStatus.tsx":"\"use client\";\n\nimport { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Badge } from \"../Badge/Badge\";\nimport { Button } from \"../Button/Button\";\nimport { useActionFocus } from \"../_internal/useActionFocus\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { ActionStatusManifest } from \"./ActionStatus.manifest\";\nconst statusStyles = cva(\n \"flex min-w-0 flex-col gap-2u border border-oprt-border p-2u font-sans text-oprt-base normal-case text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\nexport type ActionState =\n | \"submitted\"\n | \"accepted\"\n | \"rejected\"\n | \"in-progress\"\n | \"complete\"\n | \"partial\"\n | \"failed\"\n | \"timeout\"\n | \"queued\"\n | \"running\"\n | \"cancel-requested\"\n | \"canceled\";\nexport type ActionStatusProps = HTMLAttributes<HTMLDivElement> & {\n state: ActionState;\n label: string;\n message?: string;\n requestId?: string;\n // `| undefined` so that an application can pass `supported ? handler : undefined`, which is what\n // the docs show, under exactOptionalPropertyTypes.\n onRetry?: (() => void) | undefined;\n onCancel?: (() => void) | undefined;\n retryLabel?: string;\n cancelLabel?: string;\n disabled?: boolean;\n};\nconst variants = {\n submitted: \"info\",\n accepted: \"info\",\n rejected: \"error\",\n \"in-progress\": \"info\",\n complete: \"success\",\n partial: \"warning\",\n failed: \"error\",\n timeout: \"warning\",\n queued: \"info\",\n running: \"info\",\n \"cancel-requested\": \"info\",\n canceled: \"neutral\",\n} as const;\n\n/**\n * The supplied outcome as one polite, atomic status region: the label, the state badge, the request\n * ID and the message, with any `children` (an explanation the host adds) announced with them.\n * ActionStatus frames it and adds recovery; JobStatus composes it with its own lines. It is not\n * exported from the package.\n *\n * Every text here wraps anywhere (R4), so a long label, identifier or message stays inside the\n * block.\n */\nexport function ActionStatusLine({\n state,\n label,\n message,\n requestId,\n children,\n}: {\n state: ActionState;\n label: string;\n message?: string | undefined;\n requestId?: string | undefined;\n children?: ReactNode;\n}) {\n return (\n <div\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className=\"flex min-w-0 flex-col gap-1u\"\n >\n <span className=\"min-w-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-caps\">\n {label}\n </span>\n <div>\n <Badge variant={variants[state]} indicator=\"dot\">\n {state}\n </Badge>\n </div>\n {requestId && (\n <span className=\"min-w-0 oprt-wrap-anywhere font-mono\">Request: {requestId}</span>\n )}\n {message && <p className=\"m-0 min-w-0 oprt-wrap-anywhere\">{message}</p>}\n {children}\n </div>\n );\n}\n\n/** Stateless authoritative result: clicking recovery never changes supplied state. */\nexport const ActionStatus = forwardRef<HTMLDivElement, ActionStatusProps>((allProps, ref) => {\n const {\n state,\n label,\n message,\n requestId,\n onRetry,\n onCancel,\n retryLabel = \"Retry\",\n cancelLabel = \"Cancel\",\n disabled = false,\n className,\n onFocusCapture,\n onBlurCapture,\n ...props\n } = allProps;\n // When the focused recovery is withdrawn or disabled (the documented flow: the application stops\n // supplying onRetry once the request is accepted) focus moves to the result, not to the page.\n const focus = useActionFocus(ref, { onFocusCapture, onBlurCapture });\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"ActionStatus\", ActionStatusManifest.ActionStatus!.props, { ...allProps });\n }\n\n return (\n // The result is a group named by its label, so the place focus moves to is announced by name,\n // as JobStatus's region is. A role or name the caller gives wins.\n <div\n tabIndex={-1}\n role=\"group\"\n aria-label={label}\n {...props}\n {...focus}\n className={clsx(statusStyles(), className)}\n >\n <ActionStatusLine state={state} label={label} message={message} requestId={requestId} />\n {(onRetry || onCancel) && (\n <div className=\"flex flex-wrap gap-1u\">\n {onRetry && (\n <Button variant=\"secondary\" size=\"sm\" disabled={disabled} onClick={onRetry}>\n {retryLabel}\n </Button>\n )}\n {onCancel && (\n <Button variant=\"tertiary\" size=\"sm\" disabled={disabled} onClick={onCancel}>\n {cancelLabel}\n </Button>\n )}\n </div>\n )}\n </div>\n );\n});\nActionStatus.displayName = \"ActionStatus\";\n","ActionStatus/index.ts":"export { ActionStatus, type ActionStatusProps, type ActionState } from \"./ActionStatus\";\nexport { ActionStatusManifest } from \"./ActionStatus.manifest\";\n","Alert/Alert.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const AlertManifest: Record<string, Component> = {\n Alert: {\n description:\n \"Inline status message with a severity icon, semantic left-edge accent, optional title, and optional dismiss control.\",\n props: {\n variant: {\n type: \"enum\",\n values: [\"info\", \"success\", \"warning\", \"error\"],\n required: true,\n description:\n \"Severity. Drives icon, accent color, and ARIA role (error/warning = alert, info/success = status).\",\n },\n title: {\n type: \"string\",\n description: \"Optional uppercase heading shown above the description.\",\n },\n dismissible: {\n type: \"boolean\",\n default: false,\n description: \"Render a trailing Dismiss button that calls onDismiss.\",\n },\n onDismiss: {\n type: \"function\",\n description: \"Called when the Dismiss button is activated.\",\n },\n },\n slots: {\n children: { type: \"any\", required: false },\n },\n composable_with: [\"Card\", \"CardBody\"],\n never_inside: [\"Alert\", \"Button\", \"Badge\"],\n example: '<Alert variant=\"error\" title=\"LINK LOST\">Telemetry stream dropped.</Alert>',\n },\n};\n","Alert/Alert.tsx":"\"use client\";\n\nimport { Info, CheckCircle, AlertTriangle, XCircle, X } from \"@oprt/icons\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\nimport { AlertManifest } from \"./Alert.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\n// Severity glyphs pair semantic color with a non-color indicator (WCAG 1.4.1).\nconst ICONS = {\n info: Info,\n success: CheckCircle,\n warning: AlertTriangle,\n error: XCircle,\n} as const;\n\nconst containerStyles = cva(\n \"flex gap-2u px-3u py-2u bg-oprt-surface-1 border border-oprt-border border-l-2 font-sans\",\n {\n variants: {\n variant: {\n info: \"border-l-oprt-info\",\n success: \"border-l-oprt-success\",\n warning: \"border-l-oprt-warning\",\n error: \"border-l-oprt-error\",\n },\n },\n },\n);\n\nconst accentStyles = cva(\"\", {\n variants: {\n variant: {\n info: \"text-oprt-info\",\n success: \"text-oprt-success\",\n warning: \"text-oprt-warning\",\n error: \"text-oprt-error\",\n },\n },\n});\n\nexport type AlertProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\"> &\n VariantProps<typeof containerStyles> & {\n variant: \"info\" | \"success\" | \"warning\" | \"error\";\n title?: string;\n dismissible?: boolean;\n onDismiss?: () => void;\n children?: ReactNode;\n };\n\nexport const Alert = forwardRef<HTMLDivElement, AlertProps>((allProps, ref) => {\n const {\n className,\n variant,\n title,\n dismissible = false,\n onDismiss,\n children,\n ...props\n } = allProps;\n // error/warning are assertive interruptions (\"fail loud\"); info/success are polite.\n const role = variant === \"error\" || variant === \"warning\" ? \"alert\" : \"status\";\n const Icon = ICONS[variant];\n // An absent body takes no slot: an empty one still took the column gap under the title.\n const hasBody = children != null && typeof children !== \"boolean\" && children !== \"\";\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Alert\", AlertManifest.Alert!.props, allProps);\n }\n\n return (\n <div ref={ref} role={role} className={clsx(containerStyles({ variant }), className)} {...props}>\n <span aria-hidden=\"true\" className={clsx(\"shrink-0\", accentStyles({ variant }))}>\n <Icon size={16} />\n </span>\n {/*\n min-w-0 lets the column shrink below its longest word, and the wrap utility breaks that\n word, so Dismiss stays inside the alert.\n */}\n <div className=\"flex min-w-0 flex-1 flex-col gap-1u oprt-wrap-anywhere\">\n {title && (\n <p\n className={clsx(\n \"m-0 text-oprt-sm uppercase tracking-oprt-sm\",\n accentStyles({ variant }),\n )}\n >\n {title}\n </p>\n )}\n {hasBody && <div className=\"text-oprt-base normal-case text-oprt-text\">{children}</div>}\n </div>\n {dismissible && (\n <button\n type=\"button\"\n aria-label=\"Dismiss\"\n onClick={onDismiss}\n className=\"inline-flex items-center justify-center shrink-0 h-oprt-sm w-3u border border-transparent text-oprt-text-dim transition-colors duration-oprt-fast hover:text-oprt-text focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-hidden\"\n >\n <X size={12} />\n </button>\n )}\n </div>\n );\n});\nAlert.displayName = \"Alert\";\n","Alert/index.ts":"export { Alert, type AlertProps } from \"./Alert\";\nexport { AlertManifest } from \"./Alert.manifest\";\n","AppShell/AppShell.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const AppShellManifest: Record<string, Component> = {\n AppShell: {\n description:\n \"In-page operational workspace with explicit identity, mode, connection, UTC reference time, and caller-supplied markings.\",\n props: {\n identity: {\n type: \"string\",\n required: true,\n description: \"Application-supplied user or role identity; displayed verbatim.\",\n },\n title: {\n type: \"string\",\n default: \"Workspace\",\n },\n mode: {\n type: \"enum\",\n values: [\"live\", \"paused\", \"replay\", \"training\"],\n default: \"live\",\n description: \"Presentation context. Does not connect to services or freeze data itself.\",\n },\n connection: {\n type: \"enum\",\n values: [\"connected\", \"connecting\", \"reconnecting\", \"offline\", \"unknown\"],\n default: \"unknown\",\n },\n time: {\n type: \"number\",\n description:\n \"Supplied reference time in epoch milliseconds, shown as YYYY-MM-DD HH:MM:SS UTC. Missing, null or invalid renders Time unknown; no running clock.\",\n },\n markings: {\n type: \"node\",\n description: \"Caller-supplied policy/training markings. No classification is inferred.\",\n },\n navigation: {\n type: \"node\",\n description:\n \"Navigation slot, usually a Sidebar, before the content in reading order. Beside the content when the shell is at least 640px wide (the shell's own width, not the window's); above it when narrower, capped at about eight rows and scrolling inside its named navigation landmark. The slot is the one navigation landmark, named from the title (\\\"<title> navigation\\\"); a Sidebar inside it adds no landmark of its own.\",\n },\n toolbar: {\n type: \"node\",\n description:\n \"Toolbar slot under the header; it grows with a toolbar that wraps onto more rows.\",\n },\n footer: {\n type: \"node\",\n },\n },\n slots: {\n children: {\n type: \"ReactNode\",\n required: true,\n },\n },\n composable_with: [\n \"Sidebar\",\n \"Inspector\",\n \"TreeView\",\n \"OperationalStatus\",\n \"TimeRange\",\n \"DataGrid\",\n ],\n never_inside: [\"Button\"],\n example:\n '<AppShell identity=\"Exercise analyst\" mode=\"training\" connection=\"offline\" markings=\"EXERCISE\" time={referenceTime}><DataGrid columns={columns} data={rows} getRowId={(row) => row.id} /></AppShell>',\n },\n};\n","AppShell/AppShell.tsx":"\"use client\";\n\nimport { forwardRef, type HTMLAttributes, type ReactNode, useId } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Badge } from \"../Badge/Badge\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { InsideNavigationLandmarkProvider } from \"../_internal/navigationLandmark\";\nimport { formatUtcTimestamp, isValidTimestamp } from \"../_internal/timestamp\";\nimport { AppShellManifest } from \"./AppShell.manifest\";\n\n// The shell fills its parent and is an inline-size container (Stage 1 R1, R3), so its body follows\n// the shell's own width, never the window's: a shell in a narrow panel on a wide screen stacks.\nconst shellStyles = cva(\n \"@container flex w-full min-w-0 flex-col border border-oprt-border bg-oprt-surface-1 font-sans text-oprt-base normal-case text-oprt-text\",\n);\nexport type WorkspaceMode = \"live\" | \"paused\" | \"replay\" | \"training\";\nexport type WorkspaceConnection =\n | \"connected\"\n | \"connecting\"\n | \"reconnecting\"\n | \"offline\"\n | \"unknown\";\nexport type AppShellProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\"> & {\n title?: string;\n identity: string;\n mode?: WorkspaceMode;\n connection?: WorkspaceConnection;\n time?: number | null;\n markings?: ReactNode;\n navigation?: ReactNode;\n toolbar?: ReactNode;\n footer?: ReactNode;\n};\n\nconst connectionVariants = {\n connected: \"success\",\n connecting: \"warning\",\n reconnecting: \"warning\",\n offline: \"warning\",\n unknown: \"neutral\",\n} as const;\n\n/** A connection the shell does not know (devValidate reports it) is drawn neutral, never as a warning. */\nfunction connectionVariant(connection: string) {\n return Object.prototype.hasOwnProperty.call(connectionVariants, connection)\n ? connectionVariants[connection as WorkspaceConnection]\n : \"neutral\";\n}\n\n/** In-page workspace; the host owns document landmarks and policy markings. */\nexport const AppShell = forwardRef<HTMLDivElement, AppShellProps>(\n (\n {\n title = \"Workspace\",\n identity,\n mode = \"live\",\n connection = \"unknown\",\n time,\n markings,\n navigation,\n toolbar,\n footer,\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const id = useId();\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"AppShell\", AppShellManifest.AppShell!.props, { mode, connection });\n }\n\n // The operator sees \"YYYY-MM-DD HH:MM:SS UTC\" (design call DC-4); the machine-readable instant\n // keeps its milliseconds. null: not an instant, shown as \"Time unknown\".\n const shown = isValidTimestamp(time) ? formatUtcTimestamp(time) : null;\n const isoTime = isValidTimestamp(time) ? new Date(time).toISOString() : null;\n const split = shown?.indexOf(\" \") ?? -1;\n return (\n <div\n {...props}\n ref={ref}\n role=\"region\"\n aria-labelledby={id}\n className={clsx(shellStyles(), className)}\n >\n {markings && (\n <div className=\"border-b border-oprt-border bg-oprt-surface-2 px-2u py-1u text-center font-medium uppercase tracking-oprt-caps oprt-wrap-anywhere\">\n {markings}\n </div>\n )}\n <div className=\"flex flex-wrap items-center justify-between gap-2u border-b border-oprt-border p-2u\">\n <h2\n id={id}\n className=\"m-0 min-w-0 text-oprt-md font-medium uppercase tracking-oprt-caps oprt-wrap-anywhere\"\n >\n {title}\n </h2>\n <div className=\"flex min-w-0 flex-wrap items-center gap-2u\">\n <span className=\"min-w-0 normal-case oprt-wrap-anywhere\">{identity}</span>\n <Badge variant={mode === \"live\" ? \"info\" : \"neutral\"} indicator=\"dot\">\n {mode}\n </Badge>\n <Badge variant={connectionVariant(connection)} indicator=\"dot\">\n {connection}\n </Badge>\n {isoTime && shown ? (\n // The date and the time wrap apart; neither breaks inside, so \"UTC\" stays with the\n // time.\n <time className=\"font-mono\" dateTime={isoTime}>\n <span className=\"whitespace-nowrap\">{shown.slice(0, split)}</span>{\" \"}\n <span className=\"whitespace-nowrap\">{shown.slice(split + 1)}</span>\n </time>\n ) : (\n <span>Time unknown</span>\n )}\n </div>\n </div>\n {toolbar && <div className=\"border-b border-oprt-border p-2u\">{toolbar}</div>}\n {/* The navigation sits beside the content from 640 px of the shell's own width (R1, R3).\n Stacked above it, it is capped at about eight rows and scrolls inside its named\n landmark (design call DC-15); side by side there is no cap. This is the one navigation\n landmark: a Sidebar in the slot reads the provider and renders no landmark of its own. */}\n <div className=\"flex min-w-0 flex-col @oprt-lg:flex-row\">\n {navigation && (\n <nav\n aria-label={`${title} navigation`}\n className=\"min-w-0 max-h-40u overflow-y-auto border-b border-oprt-border p-2u @oprt-lg:max-h-none @oprt-lg:basis-1/4 @oprt-lg:overflow-visible @oprt-lg:border-b-0 @oprt-lg:border-r\"\n >\n <InsideNavigationLandmarkProvider value={true}>\n {navigation}\n </InsideNavigationLandmarkProvider>\n </nav>\n )}\n <div className=\"min-w-0 flex-1 break-words p-2u\">{children}</div>\n </div>\n {footer && (\n <div className=\"min-w-0 break-words border-t border-oprt-border p-2u\">{footer}</div>\n )}\n </div>\n );\n },\n);\nAppShell.displayName = \"AppShell\";\n","AppShell/index.ts":"export {\n AppShell,\n type AppShellProps,\n type WorkspaceMode,\n type WorkspaceConnection,\n} from \"./AppShell\";\nexport { AppShellManifest } from \"./AppShell.manifest\";\n","AttachmentPreview/AttachmentPreview.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const AttachmentPreviewManifest: Record<string, Component> = {\n AttachmentPreview: {\n description:\n \"Named attachment metadata with an escaped text preview, safe image source or readable fallback.\",\n props: {\n label: { type: \"string\", required: true },\n attachment: {\n type: \"node\",\n required: true,\n description:\n \"AttachmentMetadata object: stable id, name, sizeBytes, optional mediaType. No file reads or persistence are performed.\",\n },\n preview: {\n type: \"node\",\n required: true,\n description:\n \"AttachmentPreviewContent: {kind:'image',src,alt}, {kind:'text',text} or {kind:'unavailable',message}. Text is escaped and limited to 16,384 displayed characters. Images permit relative/http/https/blob sources and base64 PNG/JPEG/GIF/WebP/AVIF/BMP data URLs; decoding failures remain readable. No HTML or document iframe rendering.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description: \"Disables download/remove actions while preserving the preview.\",\n },\n onDownload: {\n type: \"function\",\n description:\n \"Requests a download by attachment ID. Invocation does not prove a file was saved.\",\n },\n onRemove: {\n type: \"function\",\n description:\n \"Requests removal by attachment ID. The caller owns replacement and focus if removing the entire preview.\",\n },\n className: {\n type: \"string\",\n description:\n \"Classes for the root. Set --oprt-preview-max-height here to change the image and text preview cap from its 60vh default, for example [--oprt-preview-max-height:24rem].\",\n },\n },\n composable_with: [\"FileUpload\", \"UploadQueue\", \"Dialog\"],\n never_inside: [\"Button\"],\n example:\n '<AttachmentPreview label=\"Equipment file\" attachment={attachment} preview={{kind:\"text\",text:csvPreview}} onDownload={requestDownload} />',\n },\n};\n","AttachmentPreview/AttachmentPreview.tsx":"\"use client\";\n\nimport { forwardRef, useRef, useState, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { ByteCountLabel } from \"../_internal/bytes\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nexport type AttachmentMetadata = {\n id: string;\n name: string;\n sizeBytes: number;\n mediaType?: string;\n};\nexport type AttachmentPreviewContent =\n | { kind: \"image\"; src: string; alt: string }\n | { kind: \"text\"; text: string }\n | { kind: \"unavailable\"; message: string };\nexport type AttachmentPreviewProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n attachment: AttachmentMetadata;\n preview: AttachmentPreviewContent;\n disabled?: boolean;\n onDownload?: (id: string) => void;\n onRemove?: (id: string) => void;\n};\nconst previewStyles = cva(\n \"flex min-w-0 flex-col gap-2u border border-oprt-border bg-oprt-surface-1 p-2u font-sans text-oprt-base normal-case text-oprt-text\",\n);\n/**\n * Coordinator ruling (AttachmentPreview-R2): an image or text preview is capped at 60% of the window\n * by default. A host sets its own cap through `--oprt-preview-max-height` on the preview, as DC-7's\n * `--oprt-popover-max-width` does, for example `className=\"[--oprt-preview-max-height:24rem]\"`.\n */\nconst PREVIEW_HEIGHT = \"max-h-[var(--oprt-preview-max-height,60vh)]\";\nfunction safeImageSource(src: string) {\n if (\n !src ||\n src.trim() !== src ||\n Array.from(src).some(\n (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127,\n )\n )\n return false;\n if (src.startsWith(\"data:\"))\n return /^data:image\\/(?:png|jpeg|gif|webp|avif|bmp);base64,[a-z0-9+/=]+$/i.test(src);\n try {\n return [\"https:\", \"http:\", \"blob:\"].includes(\n new URL(src, \"https://operator.invalid/\").protocol,\n );\n } catch {\n return false;\n }\n}\nexport const AttachmentPreview = forwardRef<HTMLDivElement, AttachmentPreviewProps>(\n (\n {\n label,\n attachment,\n preview,\n disabled = false,\n onDownload,\n onRemove,\n className,\n \"aria-labelledby\": labelledBy,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRefs(root, ref);\n // Hydration-safe part ids: after a hydration that disagreed with the server's ids, the base is\n // the one the page carries, so a label the application adds later still names the region.\n const ids = usePartIds([\"label\"], {\n id: props.id,\n readRendered: () => root.current?.getAttribute(\"data-oprt-ids\"),\n });\n const imageKey = `${attachment.id}\\0${preview.kind === \"image\" ? preview.src : \"\"}`;\n const [imageState, setImageState] = useState({ key: imageKey, failed: false });\n if (imageState.key !== imageKey) setImageState({ key: imageKey, failed: false });\n const imageError =\n preview.kind === \"image\"\n ? !safeImageSource(preview.src)\n ? \"Image preview source is not supported.\"\n : imageState.key === imageKey && imageState.failed\n ? \"Image preview could not be loaded.\"\n : null\n : null;\n return (\n <div\n {...props}\n ref={mergedRef}\n role=\"region\"\n data-oprt-ids={ids.base}\n aria-labelledby={joinIds(ids.label, labelledBy)}\n className={clsx(previewStyles(), className)}\n >\n <h3\n id={ids.label}\n className=\"m-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-sm\"\n >\n {label}\n </h3>\n <p className=\"m-0 oprt-wrap-anywhere\">{attachment.name}</p>\n {/* The count and its unit never part (R4, DC-11); a long media type breaks anywhere. */}\n <p className=\"m-0 text-oprt-text-dim\">\n <ByteCountLabel value={attachment.sizeBytes} />\n {attachment.mediaType && (\n <>\n {\" \"}\n · <span className=\"oprt-wrap-anywhere\">{attachment.mediaType}</span>\n </>\n )}\n </p>\n {preview.kind === \"image\" &&\n (imageError ? (\n <p role=\"status\" className=\"m-0 text-oprt-error\">\n {imageError}\n </p>\n ) : (\n <img\n key={imageKey}\n src={preview.src}\n alt={preview.alt}\n onError={() =>\n setImageState((current) =>\n current.key === imageKey ? { key: imageKey, failed: true } : current,\n )\n }\n className={clsx(PREVIEW_HEIGHT, \"max-w-full self-start object-contain\")}\n />\n ))}\n {preview.kind === \"text\" && (\n <>\n <pre\n role=\"region\"\n className={clsx(\n PREVIEW_HEIGHT,\n \"m-0 overflow-auto whitespace-pre-wrap oprt-wrap-anywhere font-mono outline-hidden focus-visible:shadow-oprt-focus\",\n )}\n tabIndex={0}\n aria-label={`Text preview of ${attachment.name}`}\n >\n {preview.text.slice(0, 16384)}\n </pre>\n {preview.text.length > 16384 && (\n <p className=\"m-0 text-oprt-text-dim\">Preview truncated to 16,384 characters.</p>\n )}\n </>\n )}\n {preview.kind === \"unavailable\" && (\n <p className=\"m-0 oprt-wrap-anywhere text-oprt-text-dim\">{preview.message}</p>\n )}\n {(onDownload || onRemove) && (\n <div className=\"flex flex-wrap gap-1u\">\n {onDownload && (\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled}\n aria-label={`Download ${attachment.name}`}\n onClick={() => {\n if (!disabled) onDownload(attachment.id);\n }}\n >\n Download\n </Button>\n )}\n {onRemove && (\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={disabled}\n aria-label={`Remove ${attachment.name}`}\n onClick={() => {\n if (!disabled) onRemove(attachment.id);\n }}\n >\n Remove\n </Button>\n )}\n </div>\n )}\n </div>\n );\n },\n);\nAttachmentPreview.displayName = \"AttachmentPreview\";\n","AttachmentPreview/index.ts":"export {\n AttachmentPreview,\n type AttachmentPreviewProps,\n type AttachmentMetadata,\n type AttachmentPreviewContent,\n} from \"./AttachmentPreview\";\nexport { AttachmentPreviewManifest } from \"./AttachmentPreview.manifest\";\n","Avatar/Avatar.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const AvatarManifest: Record<string, Component> = {\n Avatar: {\n description:\n \"Square, initials-based avatar. Renders monospace initials from the name, or an image when src is provided. It is named once, at its root, by alt or by name, and keeps its size in a crowded row.\",\n props: {\n name: { type: \"string\", required: true },\n src: { type: \"string\" },\n alt: {\n type: \"string\",\n description:\n \"Accessible name of the avatar. Defaults to name. An empty string hides the avatar from assistive technology, for one beside a visible name.\",\n },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n delayMs: {\n type: \"number\",\n description:\n \"Milliseconds to hold back the initials while an image loads. The initials show at once if the image fails.\",\n },\n },\n composable_with: [\"Card\", \"Sidebar\", \"DataTable\"],\n never_inside: [\"Badge\"],\n example: '<Avatar name=\"Ada Lovelace\" />',\n },\n};\n","Avatar/Avatar.tsx":"\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, useCallback, useEffect, useState, type ComponentPropsWithoutRef } from \"react\";\nimport clsx from \"clsx\";\nimport { User } from \"@oprt/icons\";\nimport { AvatarManifest } from \"./Avatar.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\nconst rootStyles = cva(\n // shrink-0: the root clips its content (overflow-hidden), so as a flex item its automatic minimum\n // size is zero and a long neighbor in a no-wrap row would squeeze it into a sliver.\n \"inline-flex shrink-0 items-center justify-center overflow-hidden bg-oprt-surface-2 border border-oprt-border align-middle\",\n {\n variants: {\n size: {\n sm: \"h-oprt-sm w-oprt-sm\",\n md: \"h-oprt-md w-oprt-md\",\n lg: \"h-oprt-lg w-oprt-lg\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n/** The neutral glyph shown for a name with no initials, as large as the box allows. */\nconst glyphSize = { sm: 12, md: 16, lg: 20 } as const;\n\n/** The first whole character of a word: a code point, so one outside the BMP is not cut in half. */\nconst firstCharacter = (word: string): string => Array.from(word)[0] ?? \"\";\n\n/**\n * Derive up-to-two uppercase initials from a name: the first character of the\n * first up to two whitespace-delimited words. \"Ada Lovelace\" -> \"AL\",\n * \"root\" -> \"R\". Falls back to empty string for blank input. Characters are\n * whole code points, and each is upper-cased on its own and cut to one\n * character again, so \"ßeta Test\" gives \"ST\" and not \"SST\".\n */\nfunction getInitials(name: string): string {\n return name\n .trim()\n .split(/\\s+/)\n .filter(Boolean)\n .slice(0, 2)\n .map((word) => firstCharacter(firstCharacter(word).toUpperCase()))\n .join(\"\");\n}\n\n/**\n * There is no React Aria Components primitive for an avatar (it is not an\n * interactive widget and carries no ARIA pattern), so the image/initials\n * fallback is implemented directly here. Behavior matches the previous Radix\n * implementation: the initials are shown until the image reports `load`, at\n * which point the image replaces them; an `error` keeps the initials forever.\n */\ntype LoadStatus = \"idle\" | \"loaded\" | \"error\";\n/** What the browser told us about one source. It says nothing about any other. */\ntype Load = { src: string | undefined; status: Exclude<LoadStatus, \"idle\"> };\n\nexport type AvatarProps = Omit<ComponentPropsWithoutRef<\"span\">, \"size\"> &\n VariantProps<typeof rootStyles> & {\n /** Person/entity name — drives the initials fallback and the accessible name. */\n name: string;\n /** Optional image source. When absent, initials render immediately. */\n src?: string;\n /**\n * Accessible name of the avatar. Defaults to `name`. An empty string marks the avatar\n * decorative and hides it from assistive technology, as it should be beside a visible name.\n */\n alt?: string;\n /** Delay (ms) before the initials show while the image loads. They show at once if it fails. */\n delayMs?: number;\n };\n\nexport const Avatar = forwardRef<HTMLSpanElement, AvatarProps>((allProps, ref) => {\n const { className, size, name, src, alt, delayMs, ...props } = allProps;\n const initials = getInitials(name);\n\n // The status belongs to the source it was reported for, so a new src starts idle in the very\n // commit that changes it, and a result that arrives in the layout phase is not overwritten by\n // a reset in a later effect.\n const [load, setLoad] = useState<Load | null>(null);\n const status: LoadStatus = load !== null && load.src === src ? load.status : \"idle\";\n // Radix held the fallback back for `delayMs` to avoid a flash of initials\n // on a fast connection; preserve that.\n const [fallbackReady, setFallbackReady] = useState(delayMs === undefined);\n\n useEffect(() => {\n if (delayMs === undefined) {\n setFallbackReady(true);\n return;\n }\n setFallbackReady(false);\n const t = setTimeout(() => setFallbackReady(true), delayMs);\n return () => clearTimeout(t);\n }, [delayMs, src]);\n\n // A cached image can finish loading before React attaches onLoad, so also\n // check `complete` when the node mounts or its source changes.\n const imgRef = useCallback(\n (node: HTMLImageElement | null) => {\n if (node?.complete && node.naturalWidth > 0) setLoad({ src, status: \"loaded\" });\n },\n [src],\n );\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Avatar\", AvatarManifest.Avatar!.props, { ...allProps });\n }\n\n const showImage = src !== undefined && status === \"loaded\";\n // A failed image has nothing left to wait for, so the delay only covers a load in progress.\n const showFallback = !showImage && (fallbackReady || status === \"error\");\n\n // The avatar is named once, at its root: by `alt`, or by `name`. Its children are\n // presentational, so a photo and the initials are never announced twice or differently. A\n // blank name, or an empty `alt`, names nothing, and the avatar is hidden from assistive technology.\n const label = alt ?? name;\n const named = label.trim() !== \"\";\n const resolvedSize = size ?? \"md\";\n\n return (\n <span\n ref={ref}\n className={clsx(rootStyles({ size: resolvedSize }), className)}\n {...(named ? { role: \"img\", \"aria-label\": label } : { \"aria-hidden\": true })}\n {...props}\n >\n {src !== undefined && status !== \"error\" && (\n <img\n ref={imgRef}\n // Kept mounted while loading so the browser actually fetches it,\n // but `hidden` (display:none) keeps it out of the layout until it has loaded.\n className={clsx(\"h-full w-full object-cover\", !showImage && \"hidden\")}\n src={src}\n alt=\"\"\n onLoad={() => setLoad({ src, status: \"loaded\" })}\n onError={() => setLoad({ src, status: \"error\" })}\n />\n )}\n {showFallback &&\n (initials !== \"\" ? (\n <span aria-hidden=\"true\" className=\"font-mono text-oprt-base text-oprt-text\">\n {initials}\n </span>\n ) : (\n <User aria-hidden=\"true\" size={glyphSize[resolvedSize]} className=\"text-oprt-text-dim\" />\n ))}\n </span>\n );\n});\nAvatar.displayName = \"Avatar\";\n","Avatar/index.ts":"export { Avatar, type AvatarProps } from \"./Avatar\";\nexport { AvatarManifest } from \"./Avatar.manifest\";\n","Badge/Badge.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const BadgeManifest: Record<string, Component> = {\n Badge: {\n description:\n \"Read-only uppercase status display with outlined and text appearances. Use meaningful status wording so color is not the only cue; dots and icons are optional. A status never truncates: a label that does not fit wraps, long tokens break anywhere, and the badge grows with it. An outlined badge keeps its width, so a short status is never squeezed into a broken word beside a long sibling, and is never wider than its container; a text badge can shrink with its layout. Code and kbd retain their casing; wrap other case-sensitive names or IDs in a child with normal-case tracking-normal.\",\n props: {\n variant: {\n type: \"enum\",\n values: [\"success\", \"warning\", \"error\", \"info\", \"neutral\"],\n default: \"neutral\",\n },\n appearance: {\n type: \"enum\",\n values: [\"outlined\", \"text\"],\n default: \"outlined\",\n description:\n \"Outlined adds a border, padding, and a minimum label height for standalone summaries: exactly that height for one line, and it grows when the label wraps, its lines centered. Text removes the box and wraps within available width for dense tables, lists, and metadata.\",\n },\n size: {\n type: \"enum\",\n values: [\"sm\", \"md\"],\n default: \"sm\",\n description:\n \"Typography size. One-line outlined labels are 20px (sm) or 24px (md) high with 8px inline padding. Text labels have no minimum height or padding. Explicit indicators use a 4px gap and align with the label's first line. Use Button or Toggle for actions.\",\n },\n indicator: {\n type: \"enum\",\n values: [\"dot\", \"icon\", \"none\"],\n default: \"none\",\n description:\n \"Optional decorative indicator for either appearance and every variant. Defaults to none; meaningful visible status text supplies the non-color cue.\",\n },\n icon: {\n type: \"node\",\n description: \"ReactNode rendered when indicator='icon'. Decorative; mark with aria-hidden.\",\n },\n },\n composable_with: [\"DataTable\", \"Card\", \"Toolbar\", \"Sidebar\"],\n never_inside: [\"Badge\", \"Button\"],\n example: '<Badge variant=\"success\" indicator=\"dot\">ONLINE</Badge>',\n },\n};\n","Badge/Badge.tsx":"\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Children, forwardRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\nimport { BadgeManifest } from \"./Badge.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\nconst badgeStyles = cva(\n // items-start: an indicator is aligned with the label's first line, not centered against all of it.\n \"inline-flex w-fit items-start align-middle gap-0.5u font-sans uppercase bg-transparent [&_svg]:shrink-0 [&_code]:normal-case [&_code]:tracking-normal [&_kbd]:normal-case [&_kbd]:tracking-normal\",\n {\n variants: {\n variant: {\n success: \"text-oprt-success border-oprt-success/40\",\n warning: \"text-oprt-warning border-oprt-warning/40\",\n error: \"text-oprt-error border-oprt-error/40\",\n info: \"text-oprt-info border-oprt-info/40\",\n neutral: \"text-oprt-text-dim border-oprt-border\",\n },\n appearance: {\n // A status is never cut off and never spills out of a narrow container: a label that does\n // not fit wraps (long tokens break anywhere) and the badge grows with it, its lines centered\n // like a one-line label. A one-line badge keeps its exact height (the compound variants).\n // It never shrinks: squeezed beside a long sibling in a row that does not wrap, a short\n // status such as LIVE would break mid-word. `max-w-full` keeps it inside its container.\n // The centering has no specificity, as Button's has, so a caller's `text-left` wins.\n outlined:\n \"min-w-0 max-w-full shrink-0 whitespace-normal oprt-wrap-anywhere [:where(&)]:text-center border\",\n text: \"min-w-0 max-w-full shrink whitespace-normal oprt-wrap-anywhere border-0 p-0\",\n },\n size: {\n sm: \"text-oprt-sm font-medium tracking-oprt-sm\",\n md: \"text-oprt-base font-medium tracking-oprt-caps\",\n },\n },\n compoundVariants: [\n // Only outlined passive labels have a minimum height and inline padding. The vertical padding\n // is what is left of the height around one 16px line and the 2px of border (1px at sm and\n // 3px at md), so a one-line badge is exactly 20px or 24px and a wrapped one still has room.\n {\n appearance: \"outlined\",\n size: \"sm\",\n class:\n \"min-h-oprt-badge-sm px-1u py-[calc((var(--oprt-badge-height-sm)-2px-var(--oprt-line-sm))/2)]\",\n },\n {\n appearance: \"outlined\",\n size: \"md\",\n class:\n \"min-h-oprt-badge-md px-1u py-[calc((var(--oprt-badge-height-md)-2px-var(--oprt-line-base))/2)]\",\n },\n ],\n defaultVariants: { variant: \"neutral\", appearance: \"outlined\", size: \"sm\" },\n },\n);\n\n// The dot is 1u (8px) and the first line is 16px at both sizes, so a 0.5u (4px) top margin centers\n// the dot on the first line, whether the label is one line or several.\nconst dotStyles = cva(\"inline-block shrink-0 self-start mt-0.5u w-1u h-1u\", {\n variants: {\n variant: {\n success: \"bg-oprt-success\",\n warning: \"bg-oprt-warning\",\n error: \"bg-oprt-error\",\n info: \"bg-oprt-info\",\n neutral: \"bg-oprt-text-dim\",\n },\n },\n defaultVariants: { variant: \"neutral\" },\n});\n\n// An icon of any height is centered in a box as tall as the first line.\nconst iconBoxStyles = cva(\"flex shrink-0 items-center\", {\n variants: {\n size: {\n sm: \"h-[var(--oprt-line-sm)]\",\n md: \"h-[var(--oprt-line-base)]\",\n },\n },\n defaultVariants: { size: \"sm\" },\n});\n\nexport type BadgeProps = HTMLAttributes<HTMLSpanElement> &\n VariantProps<typeof badgeStyles> & {\n indicator?: \"dot\" | \"icon\" | \"none\";\n icon?: ReactNode;\n };\n\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>((allProps, ref) => {\n const {\n className,\n variant = \"neutral\",\n appearance = \"outlined\",\n size,\n indicator = \"none\",\n icon,\n children,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Badge\", BadgeManifest.Badge!.props, { ...allProps });\n }\n\n // A null enum value would mean \"no variant\", which is no styling at all.\n const resolved = {\n variant: variant ?? undefined,\n appearance: appearance ?? undefined,\n size: size ?? undefined,\n };\n // Plain text is one flex item and flows as one block. A label with an element in it (a value\n // span, as the manifest recommends for case-sensitive names) would become several flex items\n // side by side, so it goes in one label element and flows as a sentence.\n const composite = Children.toArray(children).some(\n (child) => typeof child !== \"string\" && typeof child !== \"number\",\n );\n\n return (\n <span\n ref={ref}\n data-oprt-badge=\"\"\n data-appearance={appearance ?? \"outlined\"}\n className={clsx(badgeStyles(resolved), className)}\n {...props}\n >\n {indicator === \"dot\" && (\n <span\n data-oprt-badge-dot=\"\"\n aria-hidden=\"true\"\n className={dotStyles({ variant: resolved.variant })}\n />\n )}\n {indicator === \"icon\" && (\n <span data-oprt-badge-icon=\"\" className={iconBoxStyles({ size: resolved.size })}>\n {icon}\n </span>\n )}\n {composite ? (\n <span data-oprt-badge-label=\"\" className=\"min-w-0\">\n {children}\n </span>\n ) : (\n children\n )}\n </span>\n );\n});\nBadge.displayName = \"Badge\";\n","Badge/index.ts":"export { Badge, type BadgeProps } from \"./Badge\";\nexport { BadgeManifest } from \"./Badge.manifest\";\n","Breadcrumb/Breadcrumb.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const BreadcrumbManifest: Record<string, Component> = {\n Breadcrumb: {\n description:\n \"Self-contained navigation trail rendered from a flat array of items, with the final (or flagged) item marked as the current page.\",\n props: {\n items: {\n type: \"array\",\n required: true,\n items: {\n label: \"string\",\n href: \"string (optional — omit to render plain text)\",\n current: \"boolean (optional — defaults to true on the last item)\",\n },\n description:\n \"Ordered trail. Each entry: { label: string; href?: string; current?: boolean }. Exactly one item is current: the first that sets current:true, otherwise the last unless it sets current:false. Items after the current one keep their links. A trail that does not fit wraps onto more lines; an empty trail renders nothing.\",\n },\n separator: {\n type: \"node\",\n description:\n \"ReactNode rendered between items (default: a small chevron-right SVG). Decorative; rendered aria-hidden. Pass a string like '/' for a slash separator.\",\n },\n },\n composable_with: [\"Card\"],\n never_inside: [\"Breadcrumb\"],\n example:\n '<Breadcrumb items={[{label:\"HOME\",href:\"/\"},{label:\"OPS\",href:\"/ops\"},{label:\"TRACK 7\"}]} />',\n },\n};\n","Breadcrumb/Breadcrumb.tsx":"\"use client\";\n\nimport { ChevronRight } from \"@oprt/icons\";\nimport { forwardRef, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\n\nexport type BreadcrumbItem = {\n label: string;\n href?: string;\n current?: boolean;\n};\n\nexport type BreadcrumbProps = Omit<ComponentPropsWithoutRef<\"nav\">, \"children\"> & {\n items: BreadcrumbItem[];\n separator?: ReactNode;\n};\n\n/**\n * The one current page: the first item flagged `current: true`, otherwise the last item unless it\n * says `current: false` (then none). -1 means no item is current.\n */\nfunction currentIndex(items: readonly BreadcrumbItem[]): number {\n const flagged = items.findIndex((item) => item.current === true);\n if (flagged !== -1) return flagged;\n const last = items.length - 1;\n return items[last]?.current === false ? -1 : last;\n}\n\nexport const Breadcrumb = forwardRef<HTMLElement, BreadcrumbProps>(\n ({ items, separator = <ChevronRight size={12} />, className, ...props }, ref) => {\n // An empty trail is no navigation: render nothing rather than an empty landmark.\n if (items.length === 0) return null;\n const current = currentIndex(items);\n return (\n <nav\n ref={ref}\n aria-label=\"Breadcrumb\"\n className={clsx(\"font-sans normal-case\", className)}\n {...props}\n >\n {/* A trail that does not fit wraps onto more lines (design call DC-8): nothing is hidden,\n an item keeps its separator, and a label wider than the line breaks anywhere. */}\n <ol className=\"m-0 flex min-w-0 list-none flex-wrap items-center gap-1u p-0\">\n {items.map((it, i) => {\n const last = i === items.length - 1;\n const isCurrent = i === current;\n return (\n <li key={`${it.label}-${i}`} className=\"flex min-w-0 items-center gap-1u\">\n {isCurrent || !it.href ? (\n <span\n aria-current={isCurrent ? \"page\" : undefined}\n className={clsx(\n \"min-w-0 text-oprt-base oprt-wrap-anywhere\",\n isCurrent ? \"text-oprt-text\" : \"text-oprt-text-dim\",\n )}\n >\n {it.label}\n </span>\n ) : (\n <a\n href={it.href}\n className=\"inline-flex min-h-3u min-w-3u items-center border border-transparent text-oprt-base font-medium text-oprt-text-dim no-underline oprt-wrap-anywhere hover:text-oprt-text focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-hidden\"\n >\n {it.label}\n </a>\n )}\n {!last && (\n <span\n aria-hidden=\"true\"\n className=\"flex shrink-0 items-center text-oprt-text-muted\"\n >\n {separator}\n </span>\n )}\n </li>\n );\n })}\n </ol>\n </nav>\n );\n },\n);\nBreadcrumb.displayName = \"Breadcrumb\";\n","Breadcrumb/index.ts":"export { Breadcrumb, type BreadcrumbProps, type BreadcrumbItem } from \"./Breadcrumb\";\nexport { BreadcrumbManifest } from \"./Breadcrumb.manifest\";\n","BulkActionBar/BulkActionBar.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const BulkActionBarManifest: Record<string, Component> = {\n BulkActionBar: {\n description:\n \"Displays an explicit equipment selection snapshot, off-page count, source and filter context, with controlled bulk action requests.\",\n props: {\n label: { type: \"string\", required: true },\n selection: {\n type: \"node\",\n required: true,\n description:\n \"BulkSelectionSnapshot: {id,scope:'visible'|'selected'|'all-matching',ids:readonly string[],sourceId,sourceRevision,filterDescription,capturedAt:number}. IDs must be unique and nonempty. The application resolves all matching IDs before supplying them.\",\n },\n visibleIds: {\n type: \"array\",\n required: true,\n description:\n \"Unique current visible-page IDs, used to explain retained selection outside that page.\",\n },\n actions: {\n type: \"array\",\n required: true,\n description:\n \"BulkAction[]: unique id, label, optional disabled, disabledReason and danger. A disabledReason shows under its action, as the action's description, only while the action is disabled.\",\n },\n onAction: {\n type: \"function\",\n required: true,\n description:\n \"(actionId, snapshot) => void. Receives an independent frozen copy, including its frozen IDs. Confirmation and authority checks belong to the application.\",\n },\n onClearSelection: {\n type: \"function\",\n description:\n \"Requests clearing selection without changing it locally. Available to recover from malformed snapshots.\",\n },\n disabled: { type: \"boolean\", default: false },\n headingLevel: {\n type: \"number\",\n default: 3,\n description:\n \"Label heading level: 2, 3, 4, 5, or 6. Choose it for the hosting surface's heading hierarchy.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"DataGrid\", \"ConfirmDialog\", \"BulkResults\", \"JobStatus\", \"DescriptionList\"],\n never_inside: [\"Button\"],\n example:\n '<BulkActionBar label=\"Equipment actions\" selection={snapshot} visibleIds={pageIds} actions={[{id:\"flag\",label:\"Flag for inspection\"}]} onAction={reviewAction} />',\n },\n};\n","BulkActionBar/BulkActionBar.tsx":"\"use client\";\n\nimport { forwardRef, useId } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { DescriptionList } from \"../DescriptionList/DescriptionList\";\nimport { formatUtcTimestamp } from \"../_internal/timestamp\";\nimport { useActionFocus } from \"../_internal/useActionFocus\";\nimport { copySelection, idsError, scopeLabels, selectionError } from \"./selection\";\nexport type BulkSelectionSnapshot = {\n id: string;\n scope: \"visible\" | \"selected\" | \"all-matching\";\n ids: readonly string[];\n sourceId: string;\n sourceRevision: string;\n filterDescription: string;\n capturedAt: number;\n};\nexport type BulkAction = {\n id: string;\n label: string;\n disabled?: boolean;\n disabledReason?: string;\n danger?: boolean;\n};\nexport type BulkActionBarProps = {\n label: string;\n selection: BulkSelectionSnapshot;\n visibleIds: readonly string[];\n actions: readonly BulkAction[];\n onAction: (actionId: string, selection: BulkSelectionSnapshot) => void;\n onClearSelection?: () => void;\n disabled?: boolean;\n /** The heading level of the label, for the hosting surface's outline. Default 3. */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n className?: string;\n};\nconst barStyles = cva(\n \"flex min-w-0 flex-col gap-2u border border-oprt-border bg-oprt-surface-1 p-2u font-sans text-oprt-base text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\nexport const BulkActionBar = forwardRef<HTMLDivElement, BulkActionBarProps>(\n (\n {\n label,\n selection,\n visibleIds,\n actions,\n onAction,\n onClearSelection,\n disabled = false,\n headingLevel = 3,\n className,\n },\n ref,\n ) => {\n const id = useId();\n // Focus stays in the bar when the focused action is disabled or withdrawn (BulkResults does the same).\n const focus = useActionFocus(ref);\n const Heading = `h${headingLevel}` as \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\";\n const invalidSelection = selectionError(selection);\n const invalidVisible = idsError(visibleIds);\n const invalidActions =\n idsError(actions.map((action) => action.id)) ||\n (actions.some((action) => typeof action.label !== \"string\" || !action.label.trim())\n ? \"Action labels are required.\"\n : null);\n const error = invalidSelection\n ? `Invalid selection: ${invalidSelection}`\n : invalidVisible\n ? `Invalid visible IDs: ${invalidVisible}`\n : invalidActions\n ? `Invalid actions: ${invalidActions}`\n : null;\n const visible = new Set(visibleIds);\n const outside =\n invalidSelection || invalidVisible\n ? 0\n : selection.ids.filter((item) => !visible.has(item)).length;\n return (\n <div\n {...focus}\n role=\"region\"\n aria-label={label}\n tabIndex={-1}\n className={clsx(barStyles(), className)}\n >\n <Heading className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base font-medium uppercase tracking-oprt-sm\">\n {label}\n </Heading>\n {error && <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-error\">{error}</p>}\n {!invalidSelection && (\n <>\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere\">\n {selection.ids.length} equipment · {scopeLabels[selection.scope]}\n </p>\n {!invalidVisible && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-text-dim\">\n {outside} selected {outside === 1 ? \"ID\" : \"IDs\"} outside the visible page.\n </p>\n )}\n <DescriptionList\n layout=\"details\"\n items={[\n {\n id: \"source\",\n label: \"Source\",\n value: `${selection.sourceId} · ${selection.sourceRevision}`,\n mono: true,\n },\n {\n id: \"filters\",\n label: \"Filters\",\n value: selection.filterDescription || \"No filters\",\n },\n {\n // DC-4: the capture time in UTC to the second; the exact instant stays in dateTime.\n id: \"captured\",\n label: \"Captured\",\n value: (\n <time dateTime={new Date(selection.capturedAt).toISOString()}>\n {formatUtcTimestamp(selection.capturedAt)}\n </time>\n ),\n mono: true,\n },\n ]}\n />\n </>\n )}\n <div className=\"flex flex-wrap items-start gap-2u\">\n {actions.map((action, index) => {\n // A reason explains an unavailable action, so it shows only while the action is.\n const reason = action.disabled && action.disabledReason ? action.disabledReason : null;\n return (\n <div\n key={invalidActions ? index : action.id}\n className=\"flex min-w-0 flex-col gap-1u\"\n >\n <Button\n className=\"max-w-full oprt-wrap-anywhere\"\n size=\"sm\"\n variant={action.danger ? \"danger\" : \"secondary\"}\n disabled={disabled || !!error || selection.ids.length === 0 || action.disabled}\n aria-describedby={reason ? `${id}-reason-${index}` : undefined}\n onClick={() => {\n if (!disabled && !error && selection.ids.length > 0 && !action.disabled)\n onAction(action.id, copySelection(selection));\n }}\n >\n {action.label}\n </Button>\n {reason && (\n <p\n id={`${id}-reason-${index}`}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-text-dim\"\n >\n {reason}\n </p>\n )}\n </div>\n );\n })}\n {onClearSelection && (\n <Button\n className=\"max-w-full oprt-wrap-anywhere\"\n size=\"sm\"\n variant=\"tertiary\"\n disabled={disabled}\n onClick={() => {\n if (!disabled) onClearSelection();\n }}\n >\n Clear selection\n </Button>\n )}\n </div>\n </div>\n );\n },\n);\nBulkActionBar.displayName = \"BulkActionBar\";\n","BulkActionBar/index.ts":"export {\n BulkActionBar,\n type BulkActionBarProps,\n type BulkSelectionSnapshot,\n type BulkAction,\n} from \"./BulkActionBar\";\nexport { BulkActionBarManifest } from \"./BulkActionBar.manifest\";\n","BulkActionBar/selection.ts":"import type { BulkSelectionSnapshot } from \"./BulkActionBar\";\nimport { isValidTimestamp } from \"../_internal/timestamp\";\n\nexport function idsError(ids: readonly string[]): string | null {\n if (!Array.isArray(ids) || ids.some((id) => typeof id !== \"string\" || !id.trim()))\n return \"IDs must be nonempty strings.\";\n if (new Set(ids).size !== ids.length) return \"IDs must be unique.\";\n return null;\n}\n\nexport function selectionError(selection: BulkSelectionSnapshot): string | null {\n if (!selection || ![\"visible\", \"selected\", \"all-matching\"].includes(selection.scope))\n return \"Invalid selection scope.\";\n const invalidIds = idsError(selection.ids);\n if (invalidIds) return invalidIds;\n if (\n [selection.id, selection.sourceId, selection.sourceRevision].some(\n (value) => typeof value !== \"string\" || !value.trim(),\n )\n )\n return \"A snapshot ID, source ID, and source revision are required.\";\n if (typeof selection.filterDescription !== \"string\") return \"A filter description is required.\";\n if (!isValidTimestamp(selection.capturedAt, { integer: true }))\n return \"Snapshot capture time must be valid epoch milliseconds.\";\n return null;\n}\n\nexport const scopeLabels = {\n visible: \"Visible rows\",\n selected: \"Selected IDs\",\n \"all-matching\": \"All matching results\",\n} as const;\n\nexport function copySelection(selection: BulkSelectionSnapshot): BulkSelectionSnapshot {\n return Object.freeze({\n id: selection.id,\n scope: selection.scope,\n ids: Object.freeze([...selection.ids]),\n sourceId: selection.sourceId,\n sourceRevision: selection.sourceRevision,\n filterDescription: selection.filterDescription,\n capturedAt: selection.capturedAt,\n });\n}\n","BulkResults/BulkResults.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const BulkResultsManifest: Record<string, Component> = {\n BulkResults: {\n description:\n \"Named per-equipment result table in selection order. Missing IDs remain Not reported; malformed result coverage is visible and never counted as success.\",\n props: {\n label: { type: \"string\", required: true },\n selection: {\n type: \"node\",\n required: true,\n description:\n \"BulkSelectionSnapshot for the exact original request. IDs must be unique and nonempty.\",\n },\n items: {\n type: \"array\",\n required: true,\n description:\n \"BulkItemResult[]: {id,label,state:'queued'|'running'|'succeeded'|'failed'|'canceled'|'skipped',message?,retryable?}. Only snapshot IDs are accepted, once each.\",\n },\n pendingRetryIds: {\n type: \"array\",\n description:\n \"Known unique IDs with a supplied pending retry request. Their actions remain disabled and previous results remain visible.\",\n },\n onRetryRequest: {\n type: \"function\",\n description:\n \"(id) => void. Offered only for explicitly retryable failed items. Calling it does not change supplied outcomes.\",\n },\n disabled: { type: \"boolean\", default: false },\n pageSize: {\n type: \"number\",\n default: 100,\n description:\n \"Rows per page. A request with more IDs than this is shown a page at a time, in selection order, with PREVIOUS PAGE and NEXT PAGE; the summary still counts every ID. Invalid or less-than-one values use 100; fractional values round down.\",\n },\n headingLevel: {\n type: \"number\",\n default: 3,\n description:\n \"Label heading level: 2, 3, 4, 5, or 6. Choose it for the hosting surface's heading hierarchy.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"JobStatus\", \"BulkActionBar\", \"Badge\", \"Button\", \"DescriptionList\"],\n never_inside: [\"Button\"],\n example:\n '<BulkResults label=\"Inspection results\" selection={snapshot} items={results} pendingRetryIds={pendingIds} onRetryRequest={reviewRetry} />',\n },\n};\n","BulkResults/BulkResults.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Badge } from \"../Badge/Badge\";\nimport { Button } from \"../Button/Button\";\nimport { DescriptionList } from \"../DescriptionList/DescriptionList\";\nimport { idsError, scopeLabels, selectionError } from \"../BulkActionBar/selection\";\nimport { PageControls } from \"../DataGrid/PageControls\";\nimport { useScrollsSideways } from \"../DataTable/useScrollsSideways\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\nimport type { BulkSelectionSnapshot } from \"../BulkActionBar/BulkActionBar\";\nexport type BulkItemResult = {\n id: string;\n label: string;\n state: \"queued\" | \"running\" | \"succeeded\" | \"failed\" | \"canceled\" | \"skipped\";\n message?: string;\n retryable?: boolean;\n};\nexport type BulkResultsProps = {\n label: string;\n selection: BulkSelectionSnapshot;\n items: readonly BulkItemResult[];\n pendingRetryIds?: readonly string[];\n onRetryRequest?: (id: string) => void;\n disabled?: boolean;\n /**\n * Rows per page. A request with more IDs than this is shown a page at a time, with DataGrid's\n * paging controls; the summary still counts every ID. Default 100.\n */\n pageSize?: number;\n /** The heading level of the label, for the hosting surface's outline. Default 3. */\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n className?: string;\n};\nconst resultStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\nconst variants = {\n queued: \"info\",\n running: \"info\",\n succeeded: \"success\",\n failed: \"error\",\n canceled: \"neutral\",\n skipped: \"neutral\",\n} as const;\nconst NO_IDS: readonly string[] = [];\nconst DEFAULT_PAGE_SIZE = 100;\n// Readable column widths: the table scrolls inside its own box before a column gets narrower than\n// this (R6), so words and outcomes never break mid-word in a narrow panel. Retry takes its button's.\nconst COLUMNS = [\n { heading: \"Equipment\", width: \"min-w-16u\" },\n { heading: \"Outcome\", width: \"min-w-12u\" },\n { heading: \"Details\", width: \"min-w-16u\" },\n { heading: \"Actions\", width: \"\" },\n] as const;\n\n/** Where focus was in the table, for when its control is taken away: the row's item ID and place. */\ntype FocusedRow = { itemId: string | null; index: number };\n\nexport const BulkResults = forwardRef<HTMLDivElement, BulkResultsProps>(\n (\n {\n label,\n selection,\n items,\n pendingRetryIds = NO_IDS,\n onRetryRequest,\n disabled = false,\n pageSize = DEFAULT_PAGE_SIZE,\n headingLevel = 3,\n className,\n },\n ref,\n ) => {\n const Heading = `h${headingLevel}` as \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\";\n const root = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => root.current!, []);\n const [box, scrolls] = useScrollsSideways();\n // Rows by item ID, for focus: rows carry no DOM id, so any supplied ID is safe to render.\n const rows = useRef(new Map<string, HTMLTableRowElement>());\n\n const invalidSelection = selectionError(selection);\n const selected = new Set(invalidSelection ? [] : selection.ids);\n const invalidResults =\n idsError(items.map((item) => item.id)) ||\n (items.some((item) => !selected.has(item.id))\n ? \"A result ID is outside the selection snapshot.\"\n : null) ||\n (items.some(\n (item) =>\n !Object.hasOwn(variants, item.state) ||\n typeof item.label !== \"string\" ||\n !item.label.trim() ||\n (item.message !== undefined && typeof item.message !== \"string\"),\n )\n ? \"Every result needs a valid state, label, and message.\"\n : null);\n const invalidRetries =\n idsError(pendingRetryIds) ||\n (pendingRetryIds.some((item) => !selected.has(item))\n ? \"A pending retry ID is outside the selection snapshot.\"\n : null);\n const resultMap = new Map((invalidResults ? [] : items).map((item) => [item.id, item]));\n const pending = useMemo(() => new Set(pendingRetryIds), [pendingRetryIds]);\n const missing = invalidSelection\n ? 0\n : selection.ids.filter((item) => !resultMap.has(item)).length;\n const succeeded = invalidResults\n ? 0\n : items.filter((item) => item.state === \"succeeded\").length;\n\n // Pages, for a request larger than one page (BulkResults-R4). A new snapshot starts on page 1.\n const ids = invalidSelection ? NO_IDS : selection.ids;\n const size =\n Number.isFinite(pageSize) && pageSize >= 1 ? Math.floor(pageSize) : DEFAULT_PAGE_SIZE;\n const pageCount = Math.max(1, Math.ceil(ids.length / size));\n const [paging, setPaging] = useState({ snapshot: selection.id, page: 1 });\n const requestedPage = paging.snapshot === selection.id ? paging.page : 1;\n const page = Math.min(pageCount, Math.max(1, requestedPage));\n const pageIds = ids.length > size ? ids.slice((page - 1) * size, page * size) : ids;\n\n // A Retry that becomes pending or disappears hands focus to its row's equipment, so the user\n // stays where they were; if the row is gone, to the row now in its place, else to the results.\n const restoreFocus = useRestoreFocusOnRemoval<HTMLDivElement, FocusedRow>(root, {\n track: (element) => {\n for (const [itemId, row] of rows.current)\n if (row.contains(element)) return { itemId, index: pageIds.indexOf(itemId) };\n return { itemId: null, index: -1 };\n },\n findTarget: ({ itemId, index }) => {\n const own = itemId === null ? undefined : rows.current.get(itemId);\n if (own) return own.querySelector<HTMLElement>(\"th\");\n const nextId = index >= 0 ? pageIds[Math.min(index, pageIds.length - 1)] : undefined;\n const next = nextId === undefined ? undefined : rows.current.get(nextId);\n return (\n next?.querySelector<HTMLElement>(\"button:not(:disabled)\") ??\n next?.querySelector<HTMLElement>(\"th\") ??\n root.current\n );\n },\n });\n\n // The sentence for a newly pending retry is written into a live region that is on the page from\n // the start, two frames after the change, once focus has moved: a screen reader announces the\n // move and would cut off a sentence queued before it (JobStatus does the same). The first frame\n // clears the region, so a sentence the same as the last one is still announced.\n const [spoken, setSpoken] = useState(\"\");\n // IDs whose sentence has been written and that are still pending. An ID that resolves is\n // forgotten, so the next request for it is announced again. IDs pending at mount are not news.\n const announced = useRef(new Set(invalidRetries ? [] : pendingRetryIds));\n // The latest results and pending IDs, read when the pending IDs change (layout effects run first).\n const latest = useRef({ results: resultMap, pending: pendingRetryIds });\n useIsomorphicLayoutEffect(() => {\n latest.current = { results: resultMap, pending: pendingRetryIds };\n });\n // The effect follows what is pending, not the array: an application that renders again with a\n // new array of the same IDs neither cancels nor repeats the sentence.\n const pendingKey = JSON.stringify(pendingRetryIds);\n useEffect(() => {\n const current = invalidRetries ? [] : latest.current.pending;\n for (const itemId of [...announced.current])\n if (!current.includes(itemId)) announced.current.delete(itemId);\n const added = current.filter((itemId) => !announced.current.has(itemId));\n const label = (itemId: string) => latest.current.results.get(itemId)?.label ?? itemId;\n // Nothing new: what was said is no longer current, so the region is cleared.\n const text =\n added.length === 0\n ? \"\"\n : added.length === 1\n ? `Retry request pending for ${label(added[0]!)}. Awaiting an application update.`\n : `Retry requests pending for ${added.length} items. Awaiting an application update.`;\n let second = 0;\n const first = requestAnimationFrame(() => {\n setSpoken(\"\");\n second = requestAnimationFrame(() => {\n setSpoken(text);\n for (const itemId of added) announced.current.add(itemId);\n });\n });\n return () => {\n cancelAnimationFrame(first);\n cancelAnimationFrame(second);\n };\n }, [pendingKey, invalidRetries]);\n\n return (\n <div\n ref={root}\n role=\"region\"\n aria-label={label}\n tabIndex={-1}\n className={clsx(resultStyles(), className)}\n onFocusCapture={restoreFocus.onFocusCapture}\n onBlurCapture={restoreFocus.onBlurCapture}\n >\n <Heading className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base font-medium uppercase tracking-oprt-sm\">\n {label}\n </Heading>\n <p aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only m-0\">\n {spoken}\n </p>\n {invalidSelection ? (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-error\">\n Invalid selection: {invalidSelection}\n </p>\n ) : (\n <>\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere\">\n {scopeLabels[selection.scope]} · {selection.ids.length} requested · {succeeded}{\" \"}\n succeeded · {missing} not reported\n </p>\n <DescriptionList\n layout=\"details\"\n items={[\n {\n id: \"source\",\n label: \"Source\",\n value: `${selection.sourceId} · ${selection.sourceRevision}`,\n mono: true,\n },\n {\n id: \"filters\",\n label: \"Filters\",\n value: selection.filterDescription || \"No filters\",\n },\n ]}\n />\n {invalidResults && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-error\">\n Invalid results: {invalidResults} Outcomes are withheld until corrected.\n </p>\n )}\n {invalidRetries && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-error\">\n Invalid retry requests: {invalidRetries}\n </p>\n )}\n {selection.ids.length === 0 ? (\n <p className=\"m-0\">No equipment was requested.</p>\n ) : (\n <>\n {/* Columns keep a readable width and the table scrolls sideways inside this box\n (R6). While it scrolls the box is a named tab stop; while it fits it is none. */}\n <div\n ref={box}\n className={clsx(\n \"min-w-0 max-w-full overflow-x-auto\",\n scrolls &&\n \"border border-transparent focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus\",\n )}\n {...(scrolls\n ? { role: \"region\", \"aria-label\": `${label} scroll viewport`, tabIndex: 0 }\n : {})}\n >\n <table aria-label={label} className=\"m-0 w-full border-collapse\">\n <thead>\n <tr>\n {COLUMNS.map(({ heading, width }) => (\n <th\n key={heading}\n scope=\"col\"\n className={clsx(\n \"border-b border-oprt-border px-1u py-2u text-left font-medium uppercase tracking-oprt-sm\",\n width,\n )}\n >\n {heading}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {pageIds.map((itemId) => {\n const item = resultMap.get(itemId);\n const isPending = pending.has(itemId);\n const retryable =\n !invalidResults &&\n !invalidRetries &&\n item?.state === \"failed\" &&\n item.retryable === true &&\n !!onRetryRequest;\n return (\n <tr\n key={itemId}\n ref={(node) => {\n if (node) rows.current.set(itemId, node);\n else rows.current.delete(itemId);\n }}\n className=\"border-b border-oprt-border\"\n >\n <th\n scope=\"row\"\n tabIndex={-1}\n className=\"min-w-0 px-1u py-2u text-left align-top font-medium oprt-wrap-anywhere outline-hidden focus-visible:shadow-oprt-focus\"\n >\n {item?.label ?? itemId}\n {item && (\n <span className=\"block oprt-wrap-anywhere font-mono text-oprt-text-dim\">\n {itemId}\n </span>\n )}\n </th>\n <td className=\"px-1u py-2u align-top\">\n <Badge\n appearance=\"text\"\n indicator=\"none\"\n variant={item ? variants[item.state] : \"neutral\"}\n >\n {item?.state ?? \"Not reported\"}\n </Badge>\n </td>\n <td className=\"min-w-0 px-1u py-2u align-top oprt-wrap-anywhere\">\n {item?.message ?? \"—\"}\n {isPending && (\n <p className=\"m-0\">\n Retry requested; awaiting an application update.\n </p>\n )}\n </td>\n <td className=\"px-1u py-2u align-top\">\n {retryable && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n className=\"max-w-full\"\n aria-label={`Retry ${item.label}`}\n disabled={disabled || isPending}\n onClick={() => {\n if (!disabled && !isPending && retryable)\n onRetryRequest?.(itemId);\n }}\n >\n Retry\n </Button>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n {ids.length > size && (\n <PageControls\n page={page}\n pageCount={pageCount}\n disabled={false}\n onPageChange={(next) => setPaging({ snapshot: selection.id, page: next })}\n />\n )}\n </>\n )}\n </>\n )}\n </div>\n );\n },\n);\nBulkResults.displayName = \"BulkResults\";\n","BulkResults/index.ts":"export { BulkResults, type BulkResultsProps, type BulkItemResult } from \"./BulkResults\";\nexport { BulkResultsManifest } from \"./BulkResults.manifest\";\n","Button/Button.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ButtonManifest: Record<string, Component> = {\n Button: {\n description:\n \"Primary interactive element for triggering actions. Short action labels display uppercase. A label that does not fit wraps and centers, and the button grows with it; a one-line button keeps its exact height, also in a row that stretches its items. A label made of text and an element (an identifier in code, a case-sensitive name in a span) wraps as one block, and an icon at either end stays beside it, drawn at 12, 16 and 20 px for sm, md and lg so it fits the one-line height. Code and kbd retain their casing, and code breaks anywhere; wrap other case-sensitive names or IDs in a child with normal-case tracking-normal oprt-wrap-anywhere. Shape: the primary variant carries a cut top-right corner at rest, and every enabled button takes a second cut at the opposite corner on hover, press and keyboard focus; an armed danger button fills with hazard stripes; segments of an attached ButtonGroup stay square. The fill and edge are drawn by a layer clipped to that shape, so bg-* and border-* classes do not change them: set --oprt-cut-fill and --oprt-cut-edge instead. The oprt-cut-none class squares off one button, or every button inside the element that carries it.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer/touch feedback. None disables motion; keyboard and system reduced motion are always immediate.\",\n },\n variant: {\n type: \"enum\",\n values: [\"primary\", \"secondary\", \"tertiary\", \"danger\", \"ghost\"],\n default: \"primary\",\n },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n disabled: {\n type: \"boolean\",\n default: false,\n description:\n \"Unavailable: dim text on the control outline, no hover, and it never activates. A Button that is a Tooltip or Popover trigger with an explanation stays in the Tab order (aria-disabled) so the explanation can be reached.\",\n },\n asChild: {\n type: \"boolean\",\n default: false,\n description:\n \"Renders the child element in place of the button, for example a link; the child's own content is not laid out for you, so put a label made of text and an element in one span. A disabled asChild Button is aria-disabled, leaves the Tab order and blocks the click and the middle click, but a browser still offers the link's address (open in a new tab, copy link) and a child can override the block: show unavailable navigation as plain text.\",\n },\n },\n composable_with: [\"ButtonGroup\", \"Toolbar\", \"CardFooter\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<Button variant=\"primary\" size=\"md\">DEPLOY</Button>',\n },\n};\n","Button/Button.tsx":"\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n Children,\n Fragment,\n cloneElement,\n forwardRef,\n isValidElement,\n type ButtonHTMLAttributes,\n type MouseEvent,\n type ReactNode,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { useMotion, composeMotionHandlers, type MotionMode } from \"../_internal/motion\";\nimport { useFocusableWhenUnavailable } from \"../_internal/unavailable\";\nimport { ButtonManifest } from \"./Button.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\n// The shape is the cut (`oprt-cut`, in @oprt/tailwind): the main action carries a cut\n// top-right corner at rest, and every button takes a second cut, at the opposite corner, on hover,\n// press and keyboard focus. The recipe draws the fill and the edge in a layer clipped to that\n// shape, from `--oprt-cut-fill` and `--oprt-cut-edge`, which the variants set per state. The\n// element paints neither: it keeps a transparent 1px border, so its size is what it always was and\n// forced colors, where the layer steps aside, turn that border into the boundary.\n//\n// The disabled look is written for the native `:disabled` state and for `[data-disabled]`, which a\n// Button carries when it is unavailable but stays focusable (see below). Every property a variant\n// changes on hover is set again for the disabled look, which sorts after it in the stylesheet, so\n// an unavailable button shows no hover and no press. The recipe leaves an unavailable button\n// uncut on contact for the same reason.\nconst buttonStyles = cva(\n [\n \"oprt-cut border border-transparent\",\n // The label is centered, one line or several (design call DC-8). `:where(&)` gives that default\n // no specificity, as the browser's own button style has, so a caller's `text-left` still wins.\n \"inline-flex items-center justify-center gap-1u [:where(&)]:text-center whitespace-normal\",\n // The height is a minimum (sizes, below), so a row that stretches its items would grow a\n // one-line button to its tallest neighbor. `fit-content` is not `auto`, so stretch leaves it\n // alone, and the minimum height still applies.\n \"h-fit\",\n // Tracking is NOT set here — it belongs to the size variant, because the\n // correct em value depends on the font size the variant selects.\n \"font-sans font-semibold uppercase [&_code]:normal-case [&_code]:tracking-normal [&_code]:oprt-wrap-anywhere [&_kbd]:normal-case [&_kbd]:tracking-normal\",\n \"oprt-motion oprt-motion-press\",\n // Keyboard focus is the system's ring around the element's own box, with the edge of the shape\n // lit inside it.\n \"focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:[--oprt-cut-edge:var(--oprt-border-focus)]\",\n \"disabled:text-oprt-text-dim disabled:cursor-not-allowed\",\n \"data-[disabled]:text-oprt-text-dim data-[disabled]:cursor-not-allowed data-[disabled]:focus-visible:border-oprt-border-focus data-[disabled]:focus-visible:[--oprt-cut-edge:var(--oprt-border-focus)]\",\n ].join(\" \"),\n {\n variants: {\n variant: {\n // The main action is the one button cut at rest, and a disabled one keeps that cut.\n primary:\n \"oprt-cut-standing text-oprt-bg [--oprt-cut-fill:var(--oprt-accent)] [--oprt-cut-edge:var(--oprt-accent-active)] hover:[--oprt-cut-fill:var(--oprt-accent-hover)] active:[--oprt-cut-fill:var(--oprt-accent-active)] disabled:[--oprt-cut-fill:var(--oprt-surface-3)] disabled:[--oprt-cut-edge:var(--oprt-border-control)] data-[disabled]:[--oprt-cut-fill:var(--oprt-surface-3)] data-[disabled]:[--oprt-cut-edge:var(--oprt-border-control)]\",\n secondary:\n \"text-oprt-accent [--oprt-cut-fill:transparent] [--oprt-cut-edge:var(--oprt-accent)] hover:[--oprt-cut-fill:var(--oprt-accent-ghost)] disabled:[--oprt-cut-fill:transparent] disabled:[--oprt-cut-edge:var(--oprt-border-control)] data-[disabled]:[--oprt-cut-fill:transparent] data-[disabled]:[--oprt-cut-edge:var(--oprt-border-control)]\",\n // Outline variants draw their boundary in the control outline (design call DC-3).\n tertiary:\n \"text-oprt-text [--oprt-cut-fill:transparent] [--oprt-cut-edge:var(--oprt-border-control)] hover:[--oprt-cut-edge:var(--oprt-accent)] disabled:[--oprt-cut-fill:transparent] disabled:[--oprt-cut-edge:var(--oprt-border-control)] data-[disabled]:[--oprt-cut-fill:transparent] data-[disabled]:[--oprt-cut-edge:var(--oprt-border-control)]\",\n // Danger keeps its red at full strength; only a disabled one drops to the gray outline\n // (DC-9). Armed (hovered, focused, pressed), it fills with hazard stripes in place of a flat\n // tint: `oprt-cut-hazard`.\n danger:\n \"oprt-cut-hazard text-oprt-error [--oprt-cut-fill:transparent] [--oprt-cut-edge:var(--oprt-error)] disabled:[--oprt-cut-fill:transparent] disabled:[--oprt-cut-edge:var(--oprt-border-control)] data-[disabled]:[--oprt-cut-fill:transparent] data-[disabled]:[--oprt-cut-edge:var(--oprt-border-control)]\",\n // Ghost: no container stroke — text-only until hover, and still none when disabled.\n // Ideal for asChild links.\n ghost:\n \"text-oprt-text [--oprt-cut-fill:transparent] [--oprt-cut-edge:transparent] hover:[--oprt-cut-fill:var(--oprt-accent-ghost)] disabled:[--oprt-cut-fill:transparent] disabled:[--oprt-cut-edge:transparent] data-[disabled]:[--oprt-cut-fill:transparent] data-[disabled]:[--oprt-cut-edge:transparent]\",\n },\n size: {\n // Sizes carry size AND tracking; weight is set once on the base\n // (font-semibold, 600). text-oprt-base (11px) is the MIL-STD-1472H floor.\n // The height is a minimum, so a label that wraps grows the button instead of spilling out\n // of it. The vertical padding is what is left of the height around one line and the 2 px\n // of border, so a one-line button is exactly its token height (24, 32 and 40 px).\n sm: \"oprt-cut-sm min-h-oprt-sm px-2u py-[calc((var(--oprt-height-sm)-2px-var(--oprt-line-base))/2)] text-oprt-base tracking-oprt-caps\",\n md: \"oprt-cut-md min-h-oprt-md px-3u py-[calc((var(--oprt-height-md)-2px-var(--oprt-line-base))/2)] text-oprt-base tracking-oprt-caps\",\n lg: \"oprt-cut-lg min-h-oprt-lg px-4u py-[calc((var(--oprt-height-lg)-2px-var(--oprt-line-md))/2)] text-oprt-md tracking-oprt-caps-lg\",\n },\n },\n defaultVariants: { variant: \"primary\", size: \"md\" },\n },\n);\n\nexport type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> &\n VariantProps<typeof buttonStyles> & {\n asChild?: boolean;\n motion?: MotionMode;\n };\n\ntype ChildProps = { children?: ReactNode; \"aria-hidden\"?: unknown };\n\n/** The children as one flat list, with fragments opened so an icon inside one is still found. */\nfunction flatten(children: ReactNode, prefix = \"\"): ReactNode[] {\n return Children.toArray(children).flatMap((child) => {\n if (!isValidElement<ChildProps>(child)) return [child];\n if (child.type === Fragment) return flatten(child.props.children, `${prefix}${child.key}/`);\n return [prefix ? cloneElement(child, { key: `${prefix}${child.key}` }) : child];\n });\n}\n\n/**\n * An icon sits beside the label rather than in it: an svg, an element the caller hid from\n * assistive technology, or a component used with no children, which is how an icon component is\n * used (`<Download size={16} />`).\n */\nfunction isIcon(node: ReactNode): boolean {\n if (!isValidElement<ChildProps>(node)) return false;\n const hidden = node.props[\"aria-hidden\"];\n return (\n node.type === \"svg\" ||\n hidden === true ||\n hidden === \"true\" ||\n (typeof node.type !== \"string\" && node.props.children == null)\n );\n}\n\n/**\n * Every child of a flex container is its own flex item, so a label with an element in it (\"Open\"\n * and a `<code>` identifier) would sit as two columns side by side instead of one centered block.\n * Such a label goes in one element, as a Badge label does. Icons at either end stay separate items\n * beside it. Plain text, an icon and plain text, and an icon alone are returned as null: they are\n * already right, and their markup stays exactly as it was.\n */\nfunction splitLabel(children: ReactNode) {\n const parts = flatten(children);\n const edge = (node: ReactNode) =>\n isIcon(node) || (typeof node === \"string\" && node.trim() === \"\");\n let start = 0;\n let end = parts.length;\n while (start < end && edge(parts[start])) start += 1;\n while (end > start && edge(parts[end - 1])) end -= 1;\n const label = parts.slice(start, end);\n const composite = label.some((part) => typeof part !== \"string\" && typeof part !== \"number\");\n return composite ? { before: parts.slice(0, start), label, after: parts.slice(end) } : null;\n}\n\n// Dev only: once, because a list of unavailable links would otherwise report on every row.\nlet reportedUnavailableLink = false;\nfunction reportUnavailableLink() {\n if (process.env.NODE_ENV === \"production\" || reportedUnavailableLink) return;\n reportedUnavailableLink = true;\n console.error(\n JSON.stringify(\n {\n component: \"Button\",\n error: \"UNAVAILABLE_LINK\",\n prop: \"disabled\",\n received: \"asChild with disabled\",\n suggestion:\n \"A disabled asChild link is aria-disabled and cancels clicks and middle clicks, but a browser still offers its address (open in a new tab, copy link), and a child that sets its own props can override the block. Show navigation that is not available yet as plain text, and render the link when it is.\",\n },\n null,\n 2,\n ),\n );\n}\n\n/**\n * Cancels a click before anything else sees it. It runs in the capture phase so a child rendered\n * with `asChild` never gets its own `onClick`, and it cancels the default action, which is what\n * submits a form or follows a link.\n */\nfunction stopActivation(event: MouseEvent<HTMLElement>) {\n event.preventDefault();\n event.stopPropagation();\n}\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>((allProps, ref) => {\n const {\n className,\n variant,\n size,\n asChild = false,\n motion = \"auto\",\n disabled,\n children,\n ...props\n } = allProps;\n const motionState = useMotion(motion, \"Button\");\n const keepFocusable = useFocusableWhenUnavailable();\n const Comp = asChild ? Slot : \"button\";\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Button\", ButtonManifest.Button!.props, { ...allProps });\n }\n\n // `disabled` has three ways to look and behave alike (it never activates in any of them):\n // - a <button> is natively disabled: it also leaves the Tab order;\n // - a <button> inside a provider that asks for it (a Tooltip or Popover trigger with an\n // explanation, design call DC-1) is aria-disabled instead: it stays in the Tab order and\n // announces itself as unavailable, so the explanation can appear on focus;\n // - a child rendered with `asChild` cannot be natively disabled (an <a> has no such state),\n // so it is aria-disabled too, and leaves the Tab order unless the provider says otherwise.\n const unavailable = disabled === true;\n const native = unavailable && !asChild && !keepFocusable;\n const blocked = unavailable && !native;\n if (blocked && asChild) reportUnavailableLink();\n // A blocked Button runs none of the caller's click handlers. Focus, blur, hover and key events\n // still reach theirs, which is how an explanation is shown. A middle click is `auxclick`, not\n // `click`, and a browser opens a link in a new tab on it, so it is canceled too.\n const own = blocked\n ? {\n ...props,\n onClick: undefined,\n onClickCapture: stopActivation,\n onAuxClickCapture: stopActivation,\n }\n : props;\n // An asChild child's content is the caller's own, and the Slot needs the child itself.\n const split = asChild ? null : splitLabel(children);\n\n return (\n <Comp\n ref={ref}\n type={asChild ? undefined : \"button\"}\n className={clsx(\n buttonStyles({ variant: variant ?? undefined, size: size ?? undefined }),\n className,\n )}\n {...own}\n {...composeMotionHandlers(motionState.props, own)}\n {...(native ? { disabled: true } : {})}\n {...(blocked\n ? {\n \"aria-disabled\": true,\n \"data-disabled\": \"true\",\n ...(asChild && !keepFocusable ? { tabIndex: -1 } : {}),\n }\n : {})}\n >\n {split ? (\n <>\n {split.before}\n <span data-oprt-button-label=\"\" className=\"min-w-0 [&_svg]:inline-block\">\n {split.label}\n </span>\n {split.after}\n </>\n ) : (\n children\n )}\n </Comp>\n );\n});\nButton.displayName = \"Button\";\n","Button/index.ts":"export { Button, type ButtonProps } from \"./Button\";\nexport { ButtonManifest } from \"./Button.manifest\";\n","ButtonGroup/ButtonGroup.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ButtonGroupManifest: Record<string, Component> = {\n ButtonGroup: {\n description:\n \"Segmented row or column of Buttons with collapsed shared borders. A row that does not fit wraps onto more rows and grows taller, so no action is hidden; attached segments keep their shared edges across rows and take the height of the tallest segment in their row, so the edges run straight when one label wrapped; in a detached group each segment keeps its own height. A column stacks. Attached segments stay square: the group turns off the cut a Button otherwise carries.\",\n props: {\n orientation: {\n type: \"enum\",\n values: [\"horizontal\", \"vertical\"],\n default: \"horizontal\",\n },\n attached: { type: \"boolean\", default: true },\n },\n slots: {\n children: { type: \"Button\", required: true },\n },\n composable_with: [\"Toolbar\", \"CardFooter\"],\n example: \"<ButtonGroup>\\n <Button>COPY</Button>\\n <Button>PASTE</Button>\\n</ButtonGroup>\",\n },\n};\n","ButtonGroup/ButtonGroup.tsx":"\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport clsx from \"clsx\";\nimport { ButtonGroupManifest } from \"./ButtonGroup.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\n// A horizontal group that does not fit flows onto more rows and grows taller (design call DC-8);\n// a vertical one already stacks. A caller who puts the group in a scroll region of their own passes\n// `flex-nowrap`.\nconst groupStyles = cva(\"inline-flex max-w-full\", {\n variants: {\n orientation: {\n horizontal: \"flex-wrap\",\n vertical: \"flex-col\",\n },\n attached: {\n // Negative 1px margins collapse adjacent 1px borders into a single shared edge (the\n // compound variants below). hover/focus-visible get relative+z-10 so the active\n // border/ring is painted on top and never clipped by the neighboring segment, on any row.\n // A Button has a height of its own so that a stretching row cannot grow a one-line button;\n // the segments of an attached group are the exception, and take the height of the tallest\n // segment in their row so the shared edges run straight.\n // Joined segments are one object, and a cut corner inside the group would notch it, so the\n // group turns the cut off for everything in it (`oprt-cut-none`, see the cut recipe).\n true: \"oprt-cut-none [&>*]:h-auto [&>*:hover]:relative [&>*:focus-visible]:relative [&>*:hover]:z-10 [&>*:focus-visible]:z-10\",\n false: \"gap-1u\",\n },\n },\n compoundVariants: [\n // Every segment, not only the non-first ones, takes the negative margin: the first of each row\n // then shares its top edge with the row above, and the 1px of padding keeps the first row and\n // the first column flush with the group's own edge instead of one pixel outside it.\n { orientation: \"horizontal\", attached: true, class: \"pl-px pt-px [&>*]:-ml-px [&>*]:-mt-px\" },\n { orientation: \"vertical\", attached: true, class: \"pt-px [&>*]:-mt-px\" },\n ],\n defaultVariants: { orientation: \"horizontal\", attached: true },\n});\n\nexport type ButtonGroupProps = HTMLAttributes<HTMLDivElement> & VariantProps<typeof groupStyles>;\n\nexport const ButtonGroup = forwardRef<HTMLDivElement, ButtonGroupProps>((allProps, ref) => {\n const { className, orientation = \"horizontal\", attached = true, ...props } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"ButtonGroup\", ButtonGroupManifest.ButtonGroup!.props, { ...allProps });\n }\n\n return (\n <div\n ref={ref}\n role=\"group\"\n className={clsx(\n // A null enum value would mean \"no variant\", which is no layout at all.\n groupStyles({ orientation: orientation ?? undefined, attached: attached ?? undefined }),\n className,\n )}\n {...props}\n />\n );\n});\nButtonGroup.displayName = \"ButtonGroup\";\n","ButtonGroup/index.ts":"export { ButtonGroup, type ButtonGroupProps } from \"./ButtonGroup\";\nexport { ButtonGroupManifest } from \"./ButtonGroup.manifest\";\n","Calendar/Calendar.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const CalendarManifest: Record<string, Component> = {\n Calendar: {\n description:\n \"Controlled Gregorian calendar with date-only strings, keyboard day/month navigation, strict date bounds and visible configuration errors.\",\n props: {\n label: { type: \"string\", required: true },\n value: {\n type: \"string\",\n required: true,\n description:\n \"YYYY-MM-DD in years 0001–9999, or null for no selection. Never an instant or a local Date.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description: \"Requests the chosen date-only string. The parent accepts it through value.\",\n },\n min: { type: \"string\", description: \"Inclusive YYYY-MM-DD lower bound.\" },\n max: { type: \"string\", description: \"Inclusive YYYY-MM-DD upper bound.\" },\n disabled: { type: \"boolean\", default: false },\n readOnly: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n id: { type: \"string\" },\n className: { type: \"string\" },\n },\n composable_with: [\"FormSection\", \"DatePicker\"],\n never_inside: [\"Button\"],\n example:\n '<Calendar label=\"Inspection date\" value={date} onValueChange={setDate} min=\"2026-01-01\" max=\"2026-12-31\" />',\n },\n};\n","Calendar/Calendar.tsx":"\"use client\";\n\nimport { forwardRef, useId, useMemo } from \"react\";\nimport { GregorianCalendar, toCalendar } from \"@internationalized/date\";\nimport {\n Button,\n Calendar as AriaCalendar,\n CalendarCell,\n CalendarGrid,\n CalendarGridBody,\n CalendarGridHeader,\n CalendarHeaderCell,\n Heading,\n} from \"react-aria-components\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { ChevronLeft, ChevronRight } from \"@oprt/icons\";\nimport { dateConfiguration, dateWithinBounds } from \"../_internal/date\";\nimport { definedOnly } from \"../_internal/aria\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\n\nexport type CalendarProps = {\n label: string;\n value: string | null;\n onValueChange: (value: string | null) => void;\n min?: string;\n max?: string;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n id?: string;\n className?: string;\n};\n\n/**\n * The wrapper: as wide as its content and never wider than its container. Its floor is seven 24 px\n * day cells, stated explicitly: as an item in a flex row or a grid track its minimum would\n * otherwise be its 224 px content width, and it could not narrow beside other content.\n */\nconst calendarStyles = cva(\n \"flex w-fit min-w-[calc(7*var(--oprt-space-3))] max-w-full flex-col gap-2u font-sans text-oprt-base text-oprt-text\",\n);\n\n/**\n * The calendar's own box, shared with DatePicker's popover. It asks for seven 32 px day cells and\n * gives way to its container down to seven 24 px cells, the minimum target, so the cells shrink\n * before anything overflows (Calendar-W0-1). The grid divides that width into equal columns.\n */\nexport const CALENDAR_ROOT =\n \"flex w-[calc(7*var(--oprt-space-4))] min-w-[calc(7*var(--oprt-space-3))] max-w-full flex-col gap-2u\";\n\n/*\n * Focus. React Aria marks keyboard focus with data-focus-visible; the house treatment is the focus\n * border and shadow. Forced colors drop the shadow and repaint every border alike, so focus also\n * draws a system-colored outline there, on the focused control only (Calendar-R2). The browser's\n * own ring is suppressed with a transparent outline only while focused: a permanent one is painted\n * on every control in forced colors.\n */\nconst FOCUS_OUTLINE =\n \"focus-visible:outline-hidden forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:outline-[CanvasText]\";\n\n/** Month buttons: controls outlined at 3:1 (DC-3); disabled dims the arrow, no fade (DC-9). */\nconst navigationStyles = `flex min-h-3u min-w-3u shrink-0 items-center justify-center border border-oprt-border-control bg-oprt-surface-2 text-oprt-text ${FOCUS_OUTLINE} forced-colors:data-[focus-visible]:outline-offset-2 data-[hovered]:border-oprt-accent data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[disabled]:cursor-not-allowed data-[disabled]:text-oprt-text-dim`;\n\n/**\n * Day cells fill their column and stay square, from 24 px up to 32 px. The outline is drawn inside\n * the cell, which touches its neighbors.\n */\nconst dayStyles = `flex aspect-square w-full min-h-3u min-w-3u items-center justify-center border border-transparent font-sans text-oprt-base font-medium tabular-nums ${FOCUS_OUTLINE} forced-colors:data-[focus-visible]:-outline-offset-2 data-[outside-month]:text-oprt-text-dim data-[selected]:border-oprt-accent data-[selected]:bg-oprt-surface-3 data-[selected]:font-semibold data-[hovered]:border-oprt-border data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50 forced-colors:data-[selected]:border-[Highlight]`;\n\n/** Internal shared calendar drawing; selection state belongs to its RAC parent. */\nexport function CalendarContent() {\n return (\n <>\n <div className=\"flex items-center justify-between gap-2u\">\n <Button slot=\"previous\" className={navigationStyles}>\n <ChevronLeft size={16} />\n </Button>\n <Heading className=\"m-0 text-center font-sans text-oprt-base font-semibold uppercase tracking-oprt-caps\" />\n <Button slot=\"next\" className={navigationStyles}>\n <ChevronRight size={16} />\n </Button>\n </div>\n <CalendarGrid className=\"w-full table-fixed border-collapse\" weekdayStyle=\"short\">\n <CalendarGridHeader>\n {(day) => (\n <CalendarHeaderCell className=\"p-0 font-medium text-oprt-text-dim\">\n {day}\n </CalendarHeaderCell>\n )}\n </CalendarGridHeader>\n <CalendarGridBody>\n {(date) => <CalendarCell date={date} className={dayStyles} />}\n </CalendarGridBody>\n </CalendarGrid>\n </>\n );\n}\n\nexport const Calendar = forwardRef<HTMLDivElement, CalendarProps>(\n (\n {\n label,\n value,\n onValueChange,\n min,\n max,\n disabled = false,\n readOnly = false,\n required = false,\n id,\n className,\n },\n ref,\n ) => {\n const generated = useId();\n const errorId = `${id ?? generated}-error`;\n const hydration = useHydrationRoot(id);\n const config = useMemo(() => dateConfiguration(value, min, max), [value, min, max]);\n const error = config.error ?? (required && value === null ? \"A date is required.\" : null);\n return (\n // The key remounts the error with the calendar, so both take the client's ids when hydration\n // disagrees with the server markup and the calendar's description still names the error\n // (Calendar-R1).\n <div\n key={hydration.key}\n {...hydration.probeProps}\n className={clsx(calendarStyles(), className)}\n >\n <AriaCalendar\n {...definedOnly({ id, \"aria-describedby\": error ? errorId : undefined })}\n ref={ref}\n aria-label={label}\n value={config.date}\n minValue={config.min}\n maxValue={config.max}\n isDisabled={disabled || config.invalidBounds}\n isReadOnly={readOnly}\n isInvalid={!!error}\n createCalendar={() => new GregorianCalendar()}\n className={CALENDAR_ROOT}\n onChange={(next) => {\n const date = toCalendar(next, new GregorianCalendar());\n if (\n !disabled &&\n !readOnly &&\n !config.invalidBounds &&\n dateWithinBounds(date, config.min, config.max)\n )\n onValueChange(date.toString());\n }}\n >\n <CalendarContent />\n </AriaCalendar>\n {error && (\n <p id={errorId} className=\"m-0 max-w-full text-oprt-error\">\n {error}\n </p>\n )}\n </div>\n );\n },\n);\nCalendar.displayName = \"Calendar\";\n","Calendar/index.ts":"export { Calendar } from \"./Calendar\";\nexport type { CalendarProps } from \"./Calendar\";\nexport { CalendarManifest } from \"./Calendar.manifest\";\n","Card/Card.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const CardManifest: Record<string, Component> = {\n Card: {\n description: \"Bordered surface container. Use with CardHeader, CardBody, CardFooter.\",\n props: {},\n slots: {\n children: { type: \"CardHeader | CardBody | CardFooter\", required: true },\n },\n composable_with: [\"DataTable\", \"Sidebar\"],\n never_inside: [\"Card\"],\n example:\n \"<Card>\\n <CardHeader>STATUS</CardHeader>\\n <CardBody>All systems nominal.</CardBody>\\n</Card>\",\n },\n CardHeader: {\n description:\n \"Uppercase card title row, allows text + Badge + small buttons. Code and kbd retain their casing; wrap other names, IDs, or supporting text in a child with normal-case tracking-normal.\",\n props: {},\n slots: {\n children: { type: \"text | Badge | Button(size=sm)\", required: false },\n },\n composable_with: [\"Card\"],\n never_inside: [\"CardHeader\", \"CardBody\", \"CardFooter\"],\n example: \"<CardHeader>STATUS</CardHeader>\",\n },\n CardBody: {\n description: \"Card body slot — arbitrary content, preserving original casing.\",\n props: {},\n slots: {\n children: { type: \"any\", required: false },\n },\n composable_with: [\"Card\"],\n never_inside: [\"CardHeader\", \"CardBody\", \"CardFooter\"],\n example: \"<CardBody>All systems nominal.</CardBody>\",\n },\n CardFooter: {\n description: \"Card footer slot — action row, allows Button and ButtonGroup.\",\n props: {},\n slots: {\n children: { type: \"Button | ButtonGroup\", required: false },\n },\n composable_with: [\"Card\"],\n never_inside: [\"CardHeader\", \"CardBody\", \"CardFooter\"],\n example: '<CardFooter><Button variant=\"primary\">DEPLOY</Button></CardFooter>',\n },\n};\n\nexport const CardCompositionRules = {\n Card: {\n allowed_children: [\"CardHeader\", \"CardBody\", \"CardFooter\"],\n children: {\n CardHeader: { allowed_children: [\"text\", \"Badge\", \"Button\"] },\n CardBody: { allowed_children: [\"any\"] },\n CardFooter: { allowed_children: [\"Button\", \"ButtonGroup\"] },\n },\n },\n};\n","Card/Card.tsx":"\"use client\";\n\nimport { forwardRef, type HTMLAttributes } from \"react\";\nimport clsx from \"clsx\";\n\nexport type CardProps = HTMLAttributes<HTMLDivElement>;\n\nexport const Card = forwardRef<HTMLDivElement, CardProps>(({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={clsx(\n \"flex min-w-0 flex-col bg-oprt-surface-1 border border-oprt-border font-sans\",\n className,\n )}\n {...props}\n />\n));\nCard.displayName = \"Card\";\n\n/** Short section title. Use `normal-case tracking-normal` on any nested name or value. */\nexport type CardHeaderProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CardHeader = forwardRef<HTMLDivElement, CardHeaderProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={clsx(\n // A long title takes its own row and wraps; a badge or button that does not fit beside it\n // drops to the next one. The gap keeps them apart in either case.\n \"flex min-w-0 flex-wrap items-center justify-between gap-x-2u gap-y-1u px-3u py-2u border-b border-oprt-border oprt-wrap-anywhere text-oprt-sm uppercase tracking-oprt-sm text-oprt-text [&_code]:normal-case [&_code]:tracking-normal [&_kbd]:normal-case [&_kbd]:tracking-normal\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardHeader.displayName = \"CardHeader\";\n\nexport type CardBodyProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CardBody = forwardRef<HTMLDivElement, CardBodyProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n // break-words, not the anywhere utility: the body holds any content, and a table or a nested\n // component keeps the widths it computes from its own words.\n className={clsx(\n \"min-w-0 break-words px-3u py-3u text-oprt-md normal-case text-oprt-text\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardBody.displayName = \"CardBody\";\n\nexport type CardFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const CardFooter = forwardRef<HTMLDivElement, CardFooterProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={clsx(\n // Actions that do not fit side by side wrap onto further rows, right-aligned: a plain row\n // ran off its start edge, where nothing can scroll to it.\n \"flex flex-wrap items-center justify-end gap-2u px-3u py-2u border-t border-oprt-border\",\n className,\n )}\n {...props}\n />\n ),\n);\nCardFooter.displayName = \"CardFooter\";\n","Card/index.ts":"export {\n Card,\n CardHeader,\n CardBody,\n CardFooter,\n type CardProps,\n type CardHeaderProps,\n type CardBodyProps,\n type CardFooterProps,\n} from \"./Card\";\nexport { CardManifest, CardCompositionRules } from \"./Card.manifest\";\n","Chart/Chart.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ChartManifest: Record<string, Component> = {\n Chart: {\n description:\n \"Native SVG charts with stable series identity and exact data tables. Line, area, and bar support shared pointer/keyboard readouts and an optional controlled observation window.\",\n props: {\n label: {\n type: \"string\",\n required: true,\n description: \"Visible chart heading and accessible graphic name.\",\n },\n series: {\n type: \"array\",\n required: true,\n description:\n \"Readonly ChartSeries[] with unique stable IDs. Points are { x: number, y: number | null, label?: string }; null and non-finite coordinates are missing. Line/area preserve authored order; use ascending x. Heatmap requires unique x within each series.\",\n items: {\n id: \"string (unique, stable)\",\n label: \"string\",\n data: \"readonly ChartPoint[]\",\n color: \"1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 (optional categorical token)\",\n },\n },\n variant: {\n type: \"enum\",\n values: [\"line\", \"area\", \"bar\", \"scatter\", \"histogram\", \"heatmap\"],\n default: \"line\",\n },\n fill: {\n type: \"enum\",\n values: [\"solid\", \"hatched\"],\n default: \"solid\",\n description:\n \"Area and bar fill treatment. Hatching adds texture while outlines carry series color and measurement.\",\n },\n description: {\n type: \"string\",\n description: \"Visible supporting context, also included in the graphic description.\",\n },\n xLabel: {\n type: \"string\",\n description: \"X axis label. Defaults to Sample, or Value for histograms.\",\n },\n xUnit: { type: \"string\", description: \"X axis unit. Histogram x values use unit instead.\" },\n yLabel: {\n type: \"string\",\n description: \"Y axis label. Defaults to Value, or Count for histograms.\",\n },\n unit: {\n type: \"string\",\n description: \"Measured value unit; shown on axes, thresholds, and the heatmap scale.\",\n },\n formatX: {\n type: \"function\",\n description:\n '(finiteX: number) => string. Formats x tick labels, the full start and end of the window in the line under the plot, and sample positions when no authored point label exists. For a time axis, return YYYY-MM-DD HH:MM:SS and set xUnit=\"UTC\". Histogram bin bounds remain numeric.',\n },\n hiddenSeries: {\n type: \"array\",\n description:\n \"Controlled hidden series IDs. Omit for internal visibility state. Hidden series are excluded from scales; raw data inspection still includes them.\",\n },\n onHiddenSeriesChange: {\n type: \"function\",\n description:\n \"(ids: string[]) => void. Reports the requested hidden IDs; supplied hiddenSeries remains controlled.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description:\n \"Disables legend and range changes. Pointer/keyboard inspection and View data remain available.\",\n },\n rangeSelection: {\n type: \"boolean\",\n default: false,\n description:\n \"Shows a miniature overview with draggable and keyboard-operable start/end handles for line, area, and bar. Needs a non-zero x domain and at least one finite sample coordinate. Other variants ignore it.\",\n },\n xDomain: {\n type: \"node\",\n description:\n \"Optional ChartRange { start, end } defining the full observation bounds for line/area/bar, including periods without measurements. Defaults to the extent of supplied x values. Pass snapshot bounds when linking time controls.\",\n },\n range: {\n type: \"node\",\n description:\n \"Controlled ChartRange { start: number, end: number } in x units. Filters line/area/bar samples inclusively. Overlapping bounds clamp to the full domain; invalid or reversed bounds use it in full. Valid disjoint controlled ranges stay empty. Omit for internal range state. Supply full history for the overview.\",\n },\n onRangeChange: {\n type: \"function\",\n description:\n \"(range: ChartRange) => void. Reports requested x bounds from range handles or reset. Supplied range remains controlled. Keyboard steps span 0.1% of the full domain; handles do not snap to observations.\",\n },\n cursor: {\n type: \"number\",\n description:\n \"Optional supplied x position for line/area/bar. Draws an independent review marker without selecting or interpolating a measurement. Invalid/out-of-window positions are omitted; null clears it.\",\n },\n cursorLabel: {\n type: \"string\",\n default: \"Review position\",\n description: \"Visible label for the supplied marker. Its value uses formatX and xUnit.\",\n },\n bins: {\n type: \"number\",\n default: 10,\n description:\n \"Histogram bin count, floored and clamped to 1–100. Non-finite values use 10. Constant data uses one bin. Each histogram bins valid point.y values on a shared range; the last bin includes the maximum.\",\n },\n threshold: {\n type: \"node\",\n description:\n \"Optional { value: number, label: string } on the measured y axis (count for histograms). Adds a dashed line and text. Non-finite values and heatmap thresholds are ignored.\",\n },\n },\n composable_with: [\"Card\", \"DataGrid\", \"TimeRange\", \"SplitPane\"],\n never_inside: [\"Chart\", \"Button\"],\n example:\n '<Chart label=\"Link latency\" unit=\"ms\" xLabel=\"Elapsed\" xUnit=\"s\" series={[{ id: \"alpha\", label: \"Alpha\", data: [{ x: 0, y: 22 }, { x: 1, y: null }, { x: 2, y: 25 }] }]} />',\n },\n};\n","Chart/Chart.tsx":"\"use client\";\n\nimport {\n forwardRef,\n memo,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type PointerEvent,\n type ReactNode,\n} from \"react\";\nimport { useLocale, useNumberFormatter } from \"react-aria\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { usePartIds } from \"../_internal/fieldIds\";\nimport { NumberText } from \"../_internal/numberText\";\nimport { FocusableWhenUnavailableProvider } from \"../_internal/unavailable\";\nimport { useElementWidth } from \"../_internal/useElementWidth\";\nimport { ChartManifest } from \"./Chart.manifest\";\nimport { ChartInspection, ChartRangeSelector } from \"./ChartControls\";\nimport {\n extent,\n fraction,\n indexByX,\n interpolate,\n intervalSeparator,\n isFinitePoint,\n linePath,\n markerPoints,\n nearestIndex,\n niceAxis,\n scale,\n segments,\n sequentialClasses,\n seriesColorClass,\n seriesIdentity,\n tickLabels,\n ticks,\n withoutFloatNoise,\n type Domain,\n} from \"./geometry\";\n\nconst chartStyles = cva(\n \"min-w-0 w-full border border-oprt-border bg-oprt-surface-1 p-3u font-sans text-oprt-text\",\n {\n variants: { variant: { line: \"\", area: \"\", bar: \"\", scatter: \"\", histogram: \"\", heatmap: \"\" } },\n defaultVariants: { variant: \"line\" },\n },\n);\n\nexport type ChartVariant = \"line\" | \"area\" | \"bar\" | \"scatter\" | \"histogram\" | \"heatmap\";\nexport interface ChartPoint {\n x: number;\n y: number | null;\n label?: string;\n}\nexport interface ChartSeries {\n id: string;\n label: string;\n data: readonly ChartPoint[];\n color?: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;\n}\nexport interface ChartRange {\n start: number;\n end: number;\n}\nexport type ChartProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n series: readonly ChartSeries[];\n variant?: ChartVariant;\n fill?: \"solid\" | \"hatched\";\n description?: string;\n xLabel?: string;\n xUnit?: string;\n yLabel?: string;\n unit?: string;\n hiddenSeries?: readonly string[];\n onHiddenSeriesChange?: (ids: string[]) => void;\n disabled?: boolean;\n bins?: number;\n formatX?: (value: number) => string;\n threshold?: { value: number; label: string };\n rangeSelection?: boolean;\n xDomain?: ChartRange;\n range?: ChartRange;\n onRangeChange?: (range: ChartRange) => void;\n /** Supplied x-axis position, independent of local sample inspection. */\n cursor?: number | null;\n cursorLabel?: string;\n};\n\n/** The y-axis gutter for labels of up to nine characters; it grows for longer ones. */\nconst LEFT = 72;\n/** The gutter's calibration: nine 11 px mono characters, and the 8 px gap before the plot. */\nconst Y_LABEL_CHARACTER_WIDTH = (LEFT - 8) / 9;\nconst TOP = 16;\nconst BOTTOM = 236;\nconst HEIGHT = BOTTOM - TOP;\n// One user unit stays one CSS pixel after measurement; only geometry reflows.\nconst SVG_HEIGHT = 270;\n// Conservative 11px mono glyph + tracking allowance, including viewport edges.\nconst LABEL_CHARACTER_WIDTH = 8;\nconst cellClass = \"border-b border-oprt-border px-2u py-1u text-left\";\nconst dashPatterns = [undefined, \"7 3\", \"2 3\", \"8 3 2 3\", \"12 4\", \"4 2\", \"1 4\", \"9 2 1 2\"] as const;\nconst withUnit = (label: string, unit?: string) => (\n <>\n {label}\n {unit && <span className=\"normal-case tracking-normal\"> ({unit})</span>}\n </>\n);\nconst valueWithUnit = (value: number | string, unit?: string) =>\n `${value}${unit ? ` ${unit}` : \"\"}`;\n/**\n * Every number Chart writes as text, the data table included, keeps every supplied digit and\n * groups them in the application's locale, as DatePicker reads it (MapControls-R3 and the\n * coordinator's ruling): the same value reads the same everywhere in one chart. Twenty-one\n * significant digits hold any number's shortest exact form, so nothing is rounded (a fraction-digit\n * limit would round 0.0000012345678901234567). The axis stays compact. A constant, so React Aria\n * keeps one formatter per locale.\n */\nconst EXACT_NUMBER = { maximumSignificantDigits: 21 };\n/** A heatmap cell's compact label (coordinator ruling): four significant digits, grouped. */\nconst CELL_NUMBER = { maximumSignificantDigits: 4 };\nconst colorClass = (item: ChartSeries) => seriesColorClass(item.id, item.color);\n/** Data colors that forced-colors mode must not replace: the heatmap's cells and scale. */\nconst KEEP_COLORS = \"forced-colors:[forced-color-adjust:none]\";\nconst shortenLabel = (label: string, limit: number) =>\n label.length > limit ? `${label.slice(0, Math.max(1, limit - 1))}…` : label;\n\n/** Shape and line pattern carry series identity alongside categorical color. */\nfunction Marker({\n identity,\n x,\n y,\n size = 3,\n solid = false,\n}: {\n identity: number;\n x: number;\n y: number;\n size?: number;\n solid?: boolean;\n}) {\n const common = {\n fill: solid ? \"currentColor\" : \"var(--oprt-surface-1)\",\n stroke: \"currentColor\",\n strokeWidth: \"var(--oprt-chart-stroke-width)\",\n vectorEffect: \"non-scaling-stroke\",\n };\n switch (identity) {\n case 1:\n return <rect {...common} x={x - size} y={y - size} width={size * 2} height={size * 2} />;\n case 2:\n return (\n <path\n {...common}\n d={`M${x},${y - size * 1.4} L${x + size},${y} L${x},${y + size * 1.4} L${x - size},${y} Z`}\n />\n );\n case 3:\n return (\n <path\n {...common}\n d={`M${x},${y - size * 1.3} L${x + size * 1.2},${y + size} L${x - size * 1.2},${y + size} Z`}\n />\n );\n case 4:\n return (\n <path\n {...common}\n d={`M${x},${y + size * 1.3} L${x + size * 1.2},${y - size} L${x - size * 1.2},${y - size} Z`}\n />\n );\n case 5:\n return (\n <path\n {...common}\n d={`M${x - size},${y} H${x + size} M${x},${y - size} V${y + size}`}\n fill=\"none\"\n />\n );\n case 6:\n return (\n <path\n {...common}\n d={`M${x - size},${y - size} L${x + size},${y + size} M${x + size},${y - size} L${x - size},${y + size}`}\n fill=\"none\"\n />\n );\n case 7:\n return (\n <>\n <circle {...common} cx={x} cy={y} r={size} fill=\"var(--oprt-surface-1)\" />\n {solid && <circle cx={x} cy={y} r={1} fill=\"currentColor\" />}\n </>\n );\n default:\n return <circle {...common} cx={x} cy={y} r={size} />;\n }\n}\n\ninterface Histogram {\n domain: Domain;\n edges: number[];\n counts: Map<string, number[]>;\n missing: number;\n}\nfunction histogram(series: readonly ChartSeries[], bins: number): Histogram {\n const finite = series.flatMap((item) => item.data.filter(isFinitePoint));\n const domain = extent(finite.map((point) => point.y));\n const count =\n domain.min === domain.max\n ? 1\n : Number.isFinite(bins)\n ? Math.min(100, Math.max(1, Math.floor(bins)))\n : 10;\n const edges = Array.from({ length: count + 1 }, (_, index) => interpolate(domain, index / count));\n const counts = new Map<string, number[]>();\n let missing = 0;\n for (const item of series) {\n const values = Array<number>(count).fill(0);\n for (const point of item.data) {\n if (!isFinitePoint(point)) {\n missing++;\n continue;\n }\n const index = Math.min(count - 1, Math.floor(fraction(point.y, domain) * count));\n values[index] = (values[index] ?? 0) + 1;\n }\n counts.set(item.id, values);\n }\n return { domain, edges, counts, missing };\n}\n\n/**\n * A bin's interval in the chart's number format. Its ends are computed, so float noise is dropped\n * (28.55, not 28.549999999999997), and a semicolon separates them where the decimal mark is a\n * comma (\"[12,3; 28,55)\" in de-DE; coordinator ruling).\n */\nfunction binLabel(\n data: Histogram,\n index: number,\n formatNumber: (value: number) => string,\n separator: string,\n): string {\n const start = formatNumber(withoutFloatNoise(data.edges[index]!));\n const end = formatNumber(withoutFloatNoise(data.edges[index + 1]!));\n return start === end\n ? start\n : `[${start}${separator}${end}${index === data.edges.length - 2 ? \"]\" : \")\"}`;\n}\n\n/**\n * The exact data table. Memoized, and given only props that change with the data or visibility, so\n * inspecting a sample or emphasizing a series never redraws it (Chart-R4).\n */\nconst DataView = memo(function DataView({\n id,\n label,\n series,\n hidden,\n distribution,\n xHeading,\n yHeading,\n formatX,\n formatNumber,\n separator,\n}: {\n id: string;\n label: string;\n series: readonly ChartSeries[];\n hidden: ReadonlySet<string>;\n distribution: Histogram | undefined;\n xHeading: ReactNode;\n yHeading: ReactNode;\n formatX: ((value: number) => string) | undefined;\n /** The chart's number text, so a value reads the same here as in the readout. */\n formatNumber: (value: number) => string;\n /** What separates the ends of a bin's interval in the locale. */\n separator: string;\n}) {\n return (\n <div\n id={id}\n className=\"max-h-screen overflow-auto border border-oprt-border focus-visible:outline-hidden focus-visible:shadow-oprt-focus\"\n tabIndex={0}\n role=\"region\"\n aria-label={`${label} data table`}\n >\n <table className=\"w-full border-collapse text-oprt-base\" aria-label={`${label} data`}>\n <caption className=\"sr-only\">\n {label} data.{\" \"}\n {distribution\n ? \"Bin intervals include their lower boundary; the final bin also includes its upper boundary.\"\n : \"All supplied samples, including hidden series. Missing means a null or non-finite value.\"}\n </caption>\n <thead className=\"bg-oprt-surface-2 font-semibold uppercase tracking-oprt-caps\">\n <tr>\n <th scope=\"col\" className={cellClass}>\n Series\n </th>\n <th scope=\"col\" className={cellClass}>\n {xHeading}\n </th>\n <th scope=\"col\" className={cellClass}>\n {yHeading}\n </th>\n <th scope=\"col\" className={cellClass}>\n Visibility\n </th>\n </tr>\n </thead>\n <tbody>\n {series.flatMap((item) =>\n distribution\n ? (distribution.counts.get(item.id) ?? []).map((count, index) => (\n <tr key={`${item.id}:${index}`}>\n <th scope=\"row\" className={clsx(cellClass, \"font-medium\")}>\n {item.label}\n </th>\n <td className={clsx(cellClass, \"font-mono\")}>\n {binLabel(distribution, index, formatNumber, separator)}\n </td>\n <td className={clsx(cellClass, \"font-mono\")}>{formatNumber(count)}</td>\n <td className={cellClass}>{hidden.has(item.id) ? \"Hidden\" : \"Visible\"}</td>\n </tr>\n ))\n : item.data.map((point, index) => (\n <tr key={`${item.id}:${index}`}>\n <th scope=\"row\" className={clsx(cellClass, \"font-medium\")}>\n {item.label}\n </th>\n <td className={clsx(cellClass, \"font-mono\")}>\n {Number.isFinite(point.x)\n ? (point.label ?? formatX?.(point.x) ?? formatNumber(point.x))\n : \"Missing\"}\n </td>\n <td className={clsx(cellClass, \"font-mono\")}>\n {point.y !== null && Number.isFinite(point.y)\n ? formatNumber(point.y)\n : \"Missing\"}\n </td>\n <td className={cellClass}>{hidden.has(item.id) ? \"Hidden\" : \"Visible\"}</td>\n </tr>\n )),\n )}\n </tbody>\n </table>\n </div>\n );\n});\n\n/** The plot's scales at one width. */\ninterface PlotGeometry {\n /** Where the plot starts: the y-axis gutter, wide enough for its labels. */\n left: number;\n right: number;\n width: number;\n groupWidth: number;\n baseline: number;\n x: (value: number) => number;\n y: (value: number) => number;\n}\n\n/** What a series' marks are drawn from. It changes with the data, the window or the width, never with inspection or emphasis. */\ninterface MarksContext {\n variant: ChartVariant;\n fill: \"solid\" | \"hatched\";\n hatchId: string;\n geometry: PlotGeometry;\n visibleCount: number;\n distribution: Histogram | undefined;\n xValues: readonly number[];\n heatDomain: Domain;\n unit: string | undefined;\n formatX: ((value: number) => string) | undefined;\n /** A number as Chart writes it: exact digits, grouped in the locale. */\n formatNumber: (value: number) => string;\n /**\n * A heatmap cell's compact label: four significant digits, grouped in the locale; the exact\n * value stays in the cell's title and the table (coordinator ruling, as DC-18).\n */\n formatCell: (value: number) => string;\n /** What separates the ends of a bin's interval in the locale. */\n separator: string;\n}\n\nfunction seriesMarks(\n item: ChartSeries,\n seriesIndex: number,\n samples: ReadonlyMap<number, ChartPoint> | undefined,\n context: MarksContext,\n): ReactNode {\n const {\n variant,\n fill,\n hatchId,\n geometry: { left, x, y, baseline, width, groupWidth },\n visibleCount,\n distribution,\n xValues,\n heatDomain,\n unit,\n formatX,\n formatNumber,\n formatCell,\n separator,\n } = context;\n const identity = seriesIdentity(item.id);\n if (variant === \"histogram\" && distribution) {\n const values = distribution.counts.get(item.id) ?? [];\n const binWidth = width / Math.max(1, values.length);\n const barWidth = (binWidth * 0.8) / Math.max(1, visibleCount);\n return values.map((count, index) => (\n <g key={index}>\n <rect\n x={left + index * binWidth + binWidth * 0.1 + seriesIndex * barWidth}\n y={Math.min(y(count), baseline)}\n width={barWidth}\n height={Math.abs(y(count) - baseline)}\n fill=\"currentColor\"\n fillOpacity=\"var(--oprt-chart-bar-opacity)\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n vectorEffect=\"non-scaling-stroke\"\n />\n {count > 0 && (\n <Marker\n identity={identity}\n x={left + index * binWidth + binWidth * 0.1 + (seriesIndex + 0.5) * barWidth}\n y={y(count)}\n size={Math.min(3, barWidth / 3)}\n />\n )}\n <title>{`${item.label}: ${binLabel(distribution, index, formatNumber, separator)}, ${formatNumber(count)} samples`}</title>\n </g>\n ));\n }\n if (variant === \"bar\") {\n const barWidth = groupWidth / Math.max(1, visibleCount);\n return item.data.filter(isFinitePoint).map((point, index) => (\n <g key={index} data-chart-point=\"\">\n <rect\n x={x(point.x) - groupWidth / 2 + barWidth * seriesIndex}\n y={Math.min(y(point.y), baseline)}\n width={barWidth * 0.9}\n height={Math.abs(y(point.y) - baseline)}\n fill={fill === \"hatched\" ? `url(#${hatchId})` : \"currentColor\"}\n fillOpacity={fill === \"hatched\" ? 1 : \"var(--oprt-chart-bar-opacity)\"}\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n vectorEffect=\"non-scaling-stroke\"\n />\n <Marker\n identity={identity}\n x={x(point.x) - groupWidth / 2 + barWidth * (seriesIndex + 0.45)}\n y={y(point.y)}\n size={Math.min(3, barWidth / 3)}\n />\n <title>{`${item.label}: ${point.label ?? formatX?.(point.x) ?? formatNumber(point.x)}, ${valueWithUnit(formatNumber(point.y), unit)}`}</title>\n </g>\n ));\n }\n if (variant === \"heatmap\") {\n const cellWidth = width / Math.max(1, xValues.length);\n const cellHeight = HEIGHT / Math.max(1, visibleCount);\n return xValues.map((value, index) => {\n // A direct lookup per cell, not a search through the series (Chart-R5).\n const point = samples?.get(value);\n const valid = point && isFinitePoint(point);\n const className = valid\n ? sequentialClasses[Math.min(4, Math.floor(fraction(point.y, heatDomain) * 5))]\n : \"text-oprt-chart-grid\";\n const cellX = left + index * cellWidth;\n const cellY = TOP + seriesIndex * cellHeight;\n return (\n <g key={value} data-chart-cell=\"\" data-chart-missing={valid ? undefined : \"\"}>\n {/* The sequential palette is the data, like an image: forced colors keep it (Chart-R14). A\n missing cell stays an outline in the system colors. */}\n <rect\n x={cellX + 1}\n y={cellY + 1}\n width={Math.max(0, cellWidth - 2)}\n height={Math.max(0, cellHeight - 2)}\n className={clsx(className, valid && KEEP_COLORS)}\n fill={valid ? \"currentColor\" : \"none\"}\n stroke={valid ? \"none\" : \"currentColor\"}\n />\n {xValues.length <= 12 &&\n visibleCount <= 8 &&\n cellHeight >= 24 &&\n (valid ? formatCell(point.y).length : 1) * LABEL_CHARACTER_WIDTH + 8 <= cellWidth && (\n <text\n x={cellX + cellWidth / 2}\n y={cellY + cellHeight / 2}\n dominantBaseline=\"middle\"\n textAnchor=\"middle\"\n className={clsx(\n \"font-mono text-oprt-base\",\n valid\n ? clsx(\"fill-current text-oprt-bg\", KEEP_COLORS)\n : \"fill-current text-oprt-text-dim\",\n )}\n >\n {valid ? formatCell(point.y) : \"—\"}\n </text>\n )}\n <title>{`${item.label}: ${point?.label ?? formatX?.(value) ?? formatNumber(value)}, ${valid ? valueWithUnit(formatNumber(point.y), unit) : \"Missing\"}`}</title>\n </g>\n );\n });\n }\n const valid = item.data.filter(isFinitePoint);\n if (variant === \"scatter\") {\n // Scatter has no crosshair readout, so each visible mark keeps its title and hover target.\n return valid.map((point, index) => (\n <g key={index} data-chart-point=\"\">\n <circle cx={x(point.x)} cy={y(point.y)} r={8} fill=\"transparent\" />\n <g data-chart-marker=\"\">\n <Marker identity={identity} x={x(point.x)} y={y(point.y)} size={3.5} />\n </g>\n <title>{`${item.label}: ${point.label ?? formatX?.(point.x) ?? formatNumber(point.x)}, ${valueWithUnit(formatNumber(point.y), unit)}`}</title>\n </g>\n ));\n }\n // Line and area: every finite sample is a vertex of its run's path, and only thinned marker\n // artwork is drawn over it. No element is drawn per sample (design call DC-19): the crosshair\n // readout and keyboard inspection give each sample's values, and View data lists every one.\n const runs = segments(item.data);\n const last = valid[valid.length - 1];\n return (\n <>\n {runs.map((segment, index) => {\n const path = linePath(segment, x, y);\n return (\n <g key={index} data-chart-segment={item.id}>\n {variant === \"area\" && (\n <path\n d={`${path} L${x(segment[segment.length - 1]!.x)},${baseline} L${x(segment[0]!.x)},${baseline} Z`}\n fill=\"currentColor\"\n fillOpacity=\"var(--oprt-chart-area-opacity)\"\n />\n )}\n {variant === \"area\" && fill === \"hatched\" && (\n <path\n d={`${path} L${x(segment[segment.length - 1]!.x)},${baseline} L${x(segment[0]!.x)},${baseline} Z`}\n fill={`url(#${hatchId})`}\n />\n )}\n <path\n data-chart-line=\"\"\n d={path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n strokeDasharray={dashPatterns[identity]}\n strokeLinecap=\"butt\"\n strokeLinejoin=\"miter\"\n strokeMiterlimit={2}\n vectorEffect=\"non-scaling-stroke\"\n />\n </g>\n );\n })}\n {[...markerPoints(runs, x)].map((point, index) => (\n <g key={index} data-chart-marker=\"\">\n <Marker\n identity={identity}\n x={x(point.x)}\n y={y(point.y)}\n size={2.5}\n solid={point === last}\n />\n </g>\n ))}\n </>\n );\n}\n\nexport const Chart = forwardRef<HTMLDivElement, ChartProps>((allProps, ref) => {\n const {\n label,\n series,\n variant = \"line\",\n fill = \"solid\",\n description,\n xLabel,\n xUnit,\n yLabel,\n unit,\n hiddenSeries,\n onHiddenSeriesChange,\n disabled = false,\n bins = 10,\n formatX,\n threshold,\n rangeSelection = false,\n xDomain: suppliedXDomain,\n range,\n onRangeChange,\n cursor,\n cursorLabel = \"Review position\",\n className,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Chart\", ChartManifest.Chart!.props, { ...allProps });\n }\n // The heading is always rendered, so it carries the ids the page was served with. Parts mounted\n // later (the graphic, the hatch pattern and its fills, the data table) take their ids from it, so\n // they match the page after a hydration whose ids disagree with the server's (Chart-R9).\n const labelRef = useRef<HTMLHeadingElement>(null);\n const { locale } = useLocale();\n const numberFormat = useNumberFormatter(EXACT_NUMBER);\n const separator = useMemo(() => intervalSeparator(locale), [locale]);\n // -0 reads as 0.\n const formatNumber = useCallback(\n (value: number) => numberFormat.format(value + 0),\n [numberFormat],\n );\n const cellFormat = useNumberFormatter(CELL_NUMBER);\n const formatCell = useCallback((value: number) => cellFormat.format(value + 0), [cellFormat]);\n const ids = usePartIds([\"label\", \"description\", \"hatch\", \"data\"], {\n readRendered: () => labelRef.current?.id.replace(/-label$/, \"\"),\n });\n const plotRef = useRef<HTMLDivElement>(null);\n // One measurement: the plot's content box, as its ResizeObserver reports it (Chart-R2). Until the\n // first report, and where there is no ResizeObserver, the plot is 640 px wide. A hidden chart\n // (zero width) keeps its last usable geometry until it becomes visible.\n const plotWidth = useElementWidth(plotRef, { initial: 640, min: 0 });\n const [localHidden, setLocalHidden] = useState<readonly string[]>([]);\n const [showData, setShowData] = useState(false);\n const [localRange, setLocalRange] = useState<ChartRange>();\n const [inspection, setInspection] = useState<number | null>(null);\n const [pinned, setPinned] = useState(false);\n const [inspectionFocused, setInspectionFocused] = useState(false);\n const [emphasized, setEmphasized] = useState<string | null>(null);\n const inspectionRef = useRef<HTMLInputElement>(null);\n\n // The model comes from the data, visibility and window only, so inspecting a sample or\n // emphasizing a series reuses all of it (Chart-R1).\n const hiddenKey = JSON.stringify(hiddenSeries ?? localHidden);\n const hidden = useMemo(() => new Set<string>(JSON.parse(hiddenKey) as string[]), [hiddenKey]);\n const inspectable = variant === \"line\" || variant === \"area\" || variant === \"bar\";\n const fullVisible = useMemo(\n () => series.filter((item) => !hidden.has(item.id)),\n [series, hidden],\n );\n // Visibility never changes the overview's time domain or the selected window.\n const dataDomain = useMemo(\n () => extent(series.flatMap((item) => item.data.map((point) => point.x))),\n [series],\n );\n const suppliedStart = suppliedXDomain?.start;\n const suppliedEnd = suppliedXDomain?.end;\n const fullDomain = useMemo<Domain>(\n () =>\n inspectable &&\n suppliedStart !== undefined &&\n suppliedEnd !== undefined &&\n Number.isFinite(suppliedStart) &&\n Number.isFinite(suppliedEnd) &&\n suppliedStart < suppliedEnd\n ? { min: suppliedStart, max: suppliedEnd }\n : dataDomain,\n [inspectable, suppliedStart, suppliedEnd, dataDomain],\n );\n const requested = range ?? localRange;\n const requestedStart = requested?.start;\n const requestedEnd = requested?.end;\n const controlledRange = range !== undefined;\n // The window shown: a valid request clamped to the full domain; a valid controlled request that\n // misses the domain as it is (it holds no samples); otherwise the full domain.\n const view = useMemo(() => {\n const full = { start: fullDomain.min, end: fullDomain.max, valid: false };\n if (!inspectable || requestedStart === undefined || requestedEnd === undefined) return full;\n if (\n !Number.isFinite(requestedStart) ||\n !Number.isFinite(requestedEnd) ||\n requestedStart >= requestedEnd\n )\n return full;\n if (requestedEnd >= fullDomain.min && requestedStart <= fullDomain.max) {\n return {\n start: Math.max(fullDomain.min, requestedStart),\n end: Math.min(fullDomain.max, requestedEnd),\n valid: true,\n };\n }\n return controlledRange\n ? { start: requestedStart, end: requestedEnd, valid: true }\n : { ...full, valid: true };\n }, [inspectable, requestedStart, requestedEnd, controlledRange, fullDomain]);\n const narrowed = inspectable && (view.start > dataDomain.min || view.end < dataDomain.max);\n const visible = useMemo(\n () =>\n narrowed\n ? fullVisible.map((item) => ({\n ...item,\n data: item.data.filter(\n (point) =>\n !Number.isFinite(point.x) || (point.x >= view.start && point.x <= view.end),\n ),\n }))\n : fullVisible,\n [narrowed, fullVisible, view.start, view.end],\n );\n // Each visible series' samples by x, built once per data or window change: the crosshair, the\n // readout, the spoken value and heatmap cells look a sample up directly (Chart-R1, R5).\n const samplesAt = useMemo(\n () => new Map(visible.map((item) => [item, indexByX(item.data)] as const)),\n [visible],\n );\n const finite = useMemo(\n () => visible.flatMap((item) => item.data.filter(isFinitePoint)),\n [visible],\n );\n const distribution = useMemo(\n () => (variant === \"histogram\" ? histogram(visible, bins) : undefined),\n [variant, visible, bins],\n );\n const thresholdValue =\n variant !== \"heatmap\" && threshold && Number.isFinite(threshold.value)\n ? threshold.value\n : undefined;\n const yAxis = useMemo(() => {\n const values = distribution\n ? [...distribution.counts.values()].flat()\n : finite.map((point) => point.y);\n if (thresholdValue !== undefined) values.push(thresholdValue);\n return niceAxis(\n extent(values, variant === \"bar\" || variant === \"area\" || variant === \"histogram\"),\n variant === \"histogram\",\n );\n }, [distribution, finite, thresholdValue, variant]);\n const xValues = useMemo(\n () =>\n [\n ...new Set(\n visible.flatMap((item) => item.data.map((point) => point.x)).filter(Number.isFinite),\n ),\n ].sort((a, b) => a - b),\n [visible],\n );\n const windowedAxis =\n inspectable && (rangeSelection || view.valid || suppliedXDomain !== undefined);\n const xDomain = useMemo<Domain>(\n () =>\n windowedAxis\n ? { min: view.start, max: view.end }\n : (distribution?.domain ??\n (xValues.length > 0\n ? { min: xValues[0]!, max: xValues[xValues.length - 1]! }\n : extent([]))),\n [windowedAxis, view.start, view.end, distribution, xValues],\n );\n const heatDomain = useMemo(\n () => (variant === \"heatmap\" ? extent(finite.map((point) => point.y)) : dataDomain),\n [variant, finite, dataDomain],\n );\n const anyFiniteX = useMemo(\n () => series.some((item) => item.data.some((point) => Number.isFinite(point.x))),\n [series],\n );\n const missing = useMemo(\n () =>\n visible.reduce(\n (total, item) => total + item.data.filter((point) => !isFinitePoint(point)).length,\n 0,\n ),\n [visible],\n );\n const histogramView = distribution !== undefined;\n const xHeading = useMemo(\n () => (histogramView ? withUnit(xLabel ?? \"Value\", unit) : withUnit(xLabel ?? \"Sample\", xUnit)),\n [histogramView, xLabel, xUnit, unit],\n );\n const yHeading = useMemo(\n () => (histogramView ? (yLabel ?? \"Count\") : withUnit(yLabel ?? \"Value\", unit)),\n [histogramView, yLabel, unit],\n );\n const empty =\n series.length === 0\n ? \"No data to display.\"\n : visible.length === 0\n ? \"All series are hidden.\"\n : narrowed && xValues.length === 0\n ? \"No samples in this range.\"\n : finite.length === 0\n ? \"No finite data to display.\"\n : undefined;\n\n // The y labels come first: the gutter grows to hold them (coordinator ruling on the B7\n // re-review), so no label is cut. Heatmap rows are numbered and fit the default gutter.\n const yLabels = useMemo(\n () => (variant === \"heatmap\" ? [] : tickLabels(yAxis.ticks, locale)),\n [variant, yAxis, locale],\n );\n const longestYLabel = Math.max(9, ...yLabels.map((label) => label.length));\n const left = Math.max(LEFT, Math.ceil(8 + longestYLabel * Y_LABEL_CHARACTER_WIDTH));\n // The geometry depends on the model, the width and the gutter only.\n const geometry = useMemo<PlotGeometry>(() => {\n const right = Math.max(left + 1, plotWidth - 24);\n const width = right - left;\n // Slot padding keeps grouped bars within the plotting area even at domain ends.\n const initialX = scale(xDomain, left, right);\n let slotWidth = width;\n for (let index = 1; index < xValues.length; index++)\n slotWidth = Math.min(slotWidth, initialX(xValues[index]!) - initialX(xValues[index - 1]!));\n const groupWidth = Math.min(48, slotWidth * 0.7);\n const inset = variant === \"bar\" ? groupWidth / 2 : 0;\n const y = scale(yAxis.domain, BOTTOM, TOP);\n return {\n left,\n right,\n width,\n groupWidth,\n x: scale(xDomain, left + inset, right - inset),\n y,\n baseline: y(0),\n };\n }, [left, plotWidth, xDomain, xValues, variant, yAxis]);\n const xTickLabels = useMemo(() => {\n const { left, width, x } = geometry;\n // Each x tick shows its exact value: the author's formatX text, else the number in the chart's\n // one format, as the range line, readout and table write it (B7 review C1, coordinator ruling).\n // Only an interpolated interior tick loses the float noise of its arithmetic; a heatmap column is\n // a sample and an end tick is the window's own bound, so both stay as supplied (B7 re-review N4).\n const textFor = (value: number, interpolated: boolean) =>\n formatX && !histogramView\n ? formatX(value)\n : formatNumber(interpolated ? withoutFloatNoise(value) : value);\n const columnText =\n variant === \"heatmap\" && xValues.length <= 6\n ? xValues.map((value) => textFor(value, false))\n : [];\n const heatmapTicksFit =\n columnText.length > 0 &&\n columnText.every(\n (text) => text.length * LABEL_CHARACTER_WIDTH + 16 <= width / Math.max(1, xValues.length),\n );\n const xTicks =\n variant === \"heatmap\"\n ? xValues.flatMap((value, index) =>\n heatmapTicksFit || index === 0 || index === xValues.length - 1\n ? [\n {\n value,\n position: left + ((index + 0.5) * width) / xValues.length,\n interpolated: false,\n },\n ]\n : [],\n )\n : ticks(xDomain, width < 384 ? 2 : 3).map((value, index, all) => ({\n value,\n position: x(value),\n interpolated: index > 0 && index < all.length - 1,\n }));\n const texts = xTicks.map((tick) => textFor(tick.value, tick.interpolated));\n return xTicks.map(({ position }, index) => {\n // Endpoint labels may use the side gutters. Interior labels share the\n // distance between ticks, reserving an eight-pixel gap between neighbors.\n const start =\n index === 0 ? 0 : Math.min(plotWidth, (xTicks[index - 1]!.position + position) / 2 + 4);\n const end =\n index === xTicks.length - 1\n ? plotWidth\n : Math.min(plotWidth, (position + xTicks[index + 1]!.position) / 2 - 4);\n const text = texts[index]!;\n const shortText = shortenLabel(\n text,\n Math.max(1, Math.floor((end - start) / LABEL_CHARACTER_WIDTH)),\n );\n const halfWidth = (shortText.length * LABEL_CHARACTER_WIDTH) / 2;\n return {\n text,\n shortText,\n x: Math.max(start + halfWidth, Math.min(end - halfWidth, position)),\n };\n });\n }, [geometry, histogramView, formatX, formatNumber, xValues, variant, xDomain, plotWidth]);\n\n // Every layer that does not follow the pointer is a memoized element: React skips it entirely when\n // only inspection or emphasis changes, which is all a hover does (Chart-R1).\n const hatchId = ids.hatch;\n const marksLayer = useMemo(() => {\n const context: MarksContext = {\n variant,\n fill,\n hatchId,\n geometry,\n visibleCount: visible.length,\n distribution,\n xValues,\n heatDomain,\n unit,\n formatX,\n formatNumber,\n formatCell,\n separator,\n };\n return visible.map((item, index) => (\n <g data-chart-series={item.id} className={colorClass(item)}>\n {seriesMarks(item, index, samplesAt.get(item), context)}\n </g>\n ));\n }, [\n variant,\n fill,\n hatchId,\n geometry,\n visible,\n distribution,\n xValues,\n heatDomain,\n unit,\n formatX,\n formatNumber,\n formatCell,\n separator,\n samplesAt,\n ]);\n // Heatmap rows carry their series number, which the legend shows beside the full name (DC-18).\n // Numbers follow `series`, hidden series included, so a row keeps its number when others hide.\n const seriesNumber = useMemo(\n () => new Map(series.map((item, index) => [item.id, index + 1] as const)),\n [series],\n );\n const yAxisLayer = useMemo(() => {\n if (variant === \"heatmap\") {\n return (\n <g aria-hidden=\"true\">\n {visible.map((item, index) => (\n <text\n key={item.id}\n data-chart-row=\"\"\n x={geometry.left - 8}\n y={TOP + (HEIGHT / visible.length) * (index + 0.5)}\n textAnchor=\"end\"\n dominantBaseline=\"middle\"\n className=\"fill-current font-mono text-oprt-base text-oprt-chart-axis\"\n >\n {seriesNumber.get(item.id)}\n </text>\n ))}\n </g>\n );\n }\n return (\n <g data-chart-y-axis=\"\" aria-hidden=\"true\">\n {yAxis.ticks.map((value, index) => (\n <g key={index}>\n <line\n x1={geometry.left}\n x2={geometry.right}\n y1={Math.round(geometry.y(value)) + 0.5}\n y2={Math.round(geometry.y(value)) + 0.5}\n className=\"text-oprt-chart-grid\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-guide-width)\"\n strokeDasharray={value === 0 ? undefined : \"2 4\"}\n strokeOpacity={value === 0 ? 1 : 0.65}\n />\n <line\n x1={geometry.left - 4}\n x2={geometry.left}\n y1={Math.round(geometry.y(value)) + 0.5}\n y2={Math.round(geometry.y(value)) + 0.5}\n className=\"text-oprt-chart-axis\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-guide-width)\"\n />\n <text\n x={geometry.left - 8}\n y={geometry.y(value)}\n textAnchor=\"end\"\n dominantBaseline=\"middle\"\n className=\"fill-current font-mono text-oprt-base text-oprt-chart-axis\"\n >\n {yLabels[index]}\n <title>{formatNumber(value)}</title>\n </text>\n </g>\n ))}\n </g>\n );\n }, [variant, visible, yAxis, yLabels, geometry, seriesNumber, formatNumber]);\n const xAxisLayer = useMemo(\n () => (\n <g data-chart-x-axis=\"\" aria-hidden=\"true\">\n {xTickLabels.map((tick, index) => (\n <text\n key={index}\n x={tick.x}\n y={BOTTOM + 22}\n textAnchor=\"middle\"\n className=\"fill-current font-mono text-oprt-base text-oprt-chart-axis\"\n >\n {tick.shortText}\n <title>{tick.text}</title>\n </text>\n ))}\n </g>\n ),\n [xTickLabels],\n );\n\n // Inspection: an index into the sorted sample positions, looked up directly.\n const xIndex = useMemo(\n () => new Map(xValues.map((value, index) => [value, index] as const)),\n [xValues],\n );\n const activeIndex = inspection === null ? undefined : xIndex.get(inspection);\n const activeX = activeIndex === undefined ? null : inspection;\n const reviewCursor =\n inspectable &&\n cursor != null &&\n Number.isFinite(cursor) &&\n cursor >= xDomain.min &&\n cursor <= xDomain.max\n ? cursor\n : null;\n useEffect(() => {\n if (activeX === null || !inspectable) {\n setInspection(null);\n setPinned(false);\n }\n // Removing a focused input does not dispatch blur in React.\n if (!inspectable || xValues.length === 0) setInspectionFocused(false);\n }, [activeX, inspectable, xValues.length]);\n const inspectIndex = activeIndex ?? Math.max(0, xValues.length - 1);\n const formatPosition = useCallback(\n (value: number) => valueWithUnit(formatX?.(value) ?? formatNumber(value), xUnit),\n [formatX, formatNumber, xUnit],\n );\n // A sample's authored label, when every visible series with a sample there gives the same one,\n // as the data table shows it (Chart-R12). Otherwise the position is formatX text with xUnit.\n function authoredLabel(value: number): string | undefined {\n let found: string | undefined;\n for (const item of visible) {\n const point = samplesAt.get(item)?.get(value);\n if (!point) continue;\n if (!point.label || (found !== undefined && point.label !== found)) return undefined;\n found = point.label;\n }\n return found;\n }\n // The position as page text: an authored label as written, else the number or formatX text with\n // its unit kept beside it (DC-11).\n const renderSamplePosition = (value: number) =>\n authoredLabel(value) ?? (\n <NumberText value={formatX?.(value) ?? formatNumber(value)} unit={xUnit} />\n );\n function describeSample(index: number) {\n const value = xValues[index];\n if (value === undefined) return \"No sample\";\n return `${authoredLabel(value) ?? formatPosition(value)}. ${visible\n .map((item) => {\n const point = samplesAt.get(item)?.get(value);\n return `${item.label}: ${point && isFinitePoint(point) ? valueWithUnit(formatNumber(point.y), unit) : \"Missing\"}`;\n })\n .join(\". \")}`;\n }\n function inspectPointer(event: PointerEvent<SVGSVGElement>) {\n if (!inspectable || !xValues.length) return;\n const bounds = event.currentTarget.getBoundingClientRect();\n if (!bounds.width) return;\n const px = ((event.clientX - bounds.left) * plotWidth) / bounds.width;\n if (px < geometry.left || px > geometry.right) return;\n // The positions ascend with the values, so the nearest sample is a binary search.\n setInspection(xValues[nearestIndex(xValues, px, geometry.x)] ?? null);\n }\n const changeRange = useCallback(\n (next: ChartRange) => {\n if (disabled) return;\n if (!controlledRange) setLocalRange(next);\n onRangeChange?.(next);\n setInspection(null);\n setPinned(false);\n },\n [disabled, controlledRange, onRangeChange],\n );\n\n function toggle(item: ChartSeries) {\n const next = new Set(hidden);\n if (next.has(item.id)) next.delete(item.id);\n else next.add(item.id);\n const ids = [...next];\n if (hiddenSeries === undefined) setLocalHidden(ids);\n onHiddenSeriesChange?.(ids);\n }\n\n // The window's ends: the first and last heatmap columns, else the x domain shown.\n const firstX = xValues[0];\n const lastX = xValues[xValues.length - 1];\n const windowEnds: readonly [number, number] | null = empty\n ? null\n : variant === \"heatmap\"\n ? firstX === undefined || lastX === undefined\n ? null\n : [firstX, lastX]\n : [xDomain.min, xDomain.max];\n // A histogram's x axis is the measured value, in `unit`; other axes use formatX and xUnit.\n const renderPosition = useCallback(\n (value: number) =>\n histogramView ? (\n <NumberText value={formatNumber(value)} unit={unit} />\n ) : (\n <NumberText value={formatX?.(value) ?? formatNumber(value)} unit={xUnit} />\n ),\n [histogramView, unit, formatX, formatNumber, xUnit],\n );\n\n const { x, y } = geometry;\n return (\n <div\n {...props}\n ref={ref}\n data-oprt-chart={variant}\n className={clsx(chartStyles({ variant }), className)}\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2u\">\n {/* A heading, as every sibling panel's title is (Chart-R16). */}\n <h3\n ref={labelRef}\n id={ids.label}\n className=\"m-0 min-w-0 flex-1 font-semibold text-oprt-base uppercase tracking-oprt-caps oprt-wrap-anywhere\"\n >\n {label}\n </h3>\n {/* Reference the table only while it is mounted, so the reference never dangles. */}\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n aria-expanded={showData}\n aria-controls={showData ? ids.data : undefined}\n onClick={() => setShowData(!showData)}\n >\n View data\n </Button>\n </div>\n {description && (\n <p className=\"m-0 mt-1u text-oprt-base text-oprt-text-dim oprt-wrap-anywhere\">\n {description}\n </p>\n )}\n <p id={ids.description} className=\"sr-only\">\n {description ? `${description} ` : \"\"}\n {variant} chart. {formatNumber(visible.length)} visible series. {formatNumber(missing)}{\" \"}\n missing samples. {empty ?? \"Use View data to inspect values.\"}\n </p>\n <div className=\"mt-2u flex justify-between gap-2u text-oprt-base uppercase tracking-oprt-caps text-oprt-text-dim\">\n <span className=\"min-w-0 oprt-wrap-anywhere\">\n {variant === \"heatmap\" ? \"Series\" : yHeading}\n </span>\n {variant === \"heatmap\" && <span className=\"min-w-0 oprt-wrap-anywhere\">{yHeading}</span>}\n </div>\n {/* Escape clears an inspection and goes no further; with nothing to clear it is left to the\n dialog or popover around the chart (Chart-R10). */}\n <div\n ref={plotRef}\n className=\"relative min-w-0 w-full border border-transparent focus-within:border-oprt-border-focus focus-within:shadow-oprt-focus\"\n onKeyDownCapture={(event) => {\n if (event.key !== \"Escape\" || (activeX === null && !pinned)) return;\n event.preventDefault();\n event.stopPropagation();\n setInspection(null);\n setPinned(false);\n }}\n >\n {inspectable && xValues.length > 0 && (\n <ChartInspection\n label={label}\n index={inspectIndex}\n count={xValues.length}\n describe={describeSample}\n inputRef={inspectionRef}\n onChange={(index) => {\n setInspection(xValues[index] ?? null);\n setPinned(true);\n }}\n onFocus={() => {\n setInspectionFocused(true);\n setInspection(xValues[inspectIndex] ?? null);\n setPinned(true);\n }}\n onBlur={() => setInspectionFocused(false)}\n />\n )}\n {empty ? (\n <p className=\"m-0 py-6u text-oprt-base text-oprt-text-dim\" role=\"status\">\n {empty}\n </p>\n ) : (\n <svg\n role=\"img\"\n aria-labelledby={ids.label}\n aria-describedby={ids.description}\n viewBox={`0 0 ${plotWidth} ${SVG_HEIGHT}`}\n height={SVG_HEIGHT}\n preserveAspectRatio=\"xMinYMin meet\"\n className=\"block w-full overflow-hidden\"\n onPointerMove={(event) => {\n if (!pinned && !inspectionFocused) inspectPointer(event);\n }}\n onPointerLeave={() => {\n if (!pinned && !inspectionFocused) setInspection(null);\n }}\n onPointerDown={(event) => {\n if (inspectable && event.button === 0) {\n inspectionRef.current?.focus({ preventScroll: true });\n inspectPointer(event);\n setPinned(true);\n }\n }}\n >\n {fill === \"hatched\" && (variant === \"area\" || variant === \"bar\") && (\n <defs>\n <pattern id={hatchId} width={8} height={8} patternUnits=\"userSpaceOnUse\">\n <path\n d=\"M-2 2 L2 -2 M0 8 L8 0 M6 10 L10 6\"\n stroke=\"var(--oprt-text-dim)\"\n strokeWidth=\"var(--oprt-chart-guide-width)\"\n strokeOpacity=\"var(--oprt-chart-bar-opacity)\"\n />\n </pattern>\n </defs>\n )}\n {yAxisLayer}\n {visible.map((item, index) => (\n <g\n key={item.id}\n className=\"transition-opacity duration-oprt-normal motion-reduce:transition-none\"\n opacity={emphasized && emphasized !== item.id ? 0.35 : 1}\n aria-hidden=\"true\"\n >\n {marksLayer[index]}\n </g>\n ))}\n {thresholdValue !== undefined && (\n <line\n data-chart-threshold=\"\"\n x1={geometry.left}\n x2={geometry.right}\n y1={y(thresholdValue)}\n y2={y(thresholdValue)}\n className=\"text-oprt-chart-threshold\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-guide-width)\"\n strokeDasharray=\"6 4\"\n />\n )}\n {reviewCursor !== null && (\n <line\n data-chart-cursor=\"\"\n aria-hidden=\"true\"\n pointerEvents=\"none\"\n x1={x(reviewCursor)}\n x2={x(reviewCursor)}\n y1={TOP}\n y2={BOTTOM}\n className=\"text-oprt-accent\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n />\n )}\n {inspectable && activeX !== null && (\n <g data-chart-crosshair=\"\" aria-hidden=\"true\" pointerEvents=\"none\">\n <line\n x1={x(activeX)}\n x2={x(activeX)}\n y1={TOP}\n y2={BOTTOM}\n stroke=\"var(--oprt-text-dim)\"\n strokeWidth=\"var(--oprt-chart-guide-width)\"\n strokeDasharray=\"3 3\"\n />\n {visible.map((item, index) => {\n const point = samplesAt.get(item)?.get(activeX);\n if (!point || !isFinitePoint(point)) return null;\n const pointX =\n variant === \"bar\"\n ? x(activeX) -\n geometry.groupWidth / 2 +\n (geometry.groupWidth / visible.length) * (index + 0.45)\n : x(activeX);\n return (\n <g key={item.id} className={colorClass(item)}>\n <Marker\n identity={seriesIdentity(item.id)}\n x={pointX}\n y={y(point.y)}\n size={4}\n solid\n />\n </g>\n );\n })}\n </g>\n )}\n {xAxisLayer}\n </svg>\n )}\n </div>\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-2u\">\n {/* DC-18: the axis stays compact, and this line always gives the window's full start and end\n as page text, so no full label is hover-only. Each end stays whole where it fits (R4). */}\n {windowEnds && (\n <p\n data-chart-window=\"\"\n className=\"m-0 min-w-0 font-mono text-oprt-base text-oprt-text-dim oprt-wrap-anywhere\"\n >\n <span className=\"inline-block max-w-full\">{renderPosition(windowEnds[0])}</span>\n {\" to \"}\n <span className=\"inline-block max-w-full\">{renderPosition(windowEnds[1])}</span>\n </p>\n )}\n <p className=\"m-0 ml-auto min-w-0 text-right text-oprt-base uppercase tracking-oprt-caps text-oprt-text-dim oprt-wrap-anywhere\">\n {xHeading}\n </p>\n </div>\n {reviewCursor !== null && (\n <p className=\"m-0 text-oprt-base text-oprt-text-dim oprt-wrap-anywhere\">\n {cursorLabel}: {renderSamplePosition(reviewCursor)}\n </p>\n )}\n {inspectable && (\n <div\n role=\"region\"\n aria-label={`${label} readout`}\n className=\"mt-2u border-y border-oprt-border bg-oprt-surface-2 px-2u py-1u\"\n >\n <div className=\"flex min-h-3u flex-wrap items-center justify-between gap-1u\">\n <span className=\"min-w-0 font-mono text-oprt-base oprt-wrap-anywhere\">\n {activeX !== null\n ? renderSamplePosition(activeX)\n : \"Hover or focus the plot to inspect\"}\n </span>\n {/* Unavailable, it stays focusable and says so, so clearing never strands keyboard focus;\n the readout's own text says why (DC-1 pattern, Chart-R11). */}\n <FocusableWhenUnavailableProvider value>\n <Button\n variant=\"ghost\"\n size=\"sm\"\n disabled={activeX === null}\n onClick={() => {\n setInspection(null);\n setPinned(false);\n }}\n >\n Clear inspection\n </Button>\n </FocusableWhenUnavailableProvider>\n </div>\n <dl className=\"m-0 flex flex-wrap gap-x-3u gap-y-1u pb-1u\">\n {visible.map((item) => {\n const point = activeX === null ? undefined : samplesAt.get(item)?.get(activeX);\n return (\n <div key={item.id} className=\"flex min-w-0 items-center gap-1u\">\n <svg\n width=\"16\"\n height=\"16\"\n viewBox=\"0 0 16 16\"\n aria-hidden=\"true\"\n className={clsx(\"shrink-0\", colorClass(item))}\n >\n <Marker identity={seriesIdentity(item.id)} x={8} y={8} size={3} />\n </svg>\n <dt className=\"min-w-0 text-oprt-base text-oprt-text-dim oprt-wrap-anywhere\">\n {item.label}\n </dt>\n <dd className=\"m-0 font-mono text-oprt-base\">\n {activeX === null ? (\n \"—\"\n ) : point && isFinitePoint(point) ? (\n <NumberText value={formatNumber(point.y)} unit={unit} />\n ) : (\n \"Missing\"\n )}\n </dd>\n </div>\n );\n })}\n </dl>\n </div>\n )}\n {thresholdValue !== undefined && threshold && (\n <p className=\"m-0 mt-1u font-mono text-oprt-base text-oprt-chart-threshold oprt-wrap-anywhere\">\n {threshold.label}:{\" \"}\n <NumberText value={formatNumber(thresholdValue)} unit={distribution ? undefined : unit} />\n </p>\n )}\n {missing > 0 && (\n <p className=\"m-0 mt-1u text-oprt-base text-oprt-text-dim\">\n {distribution\n ? `${formatNumber(missing)} missing samples excluded from bins.`\n : `${formatNumber(missing)} missing samples shown as gaps.`}\n </p>\n )}\n {variant === \"heatmap\" && finite.length > 0 && (\n <div\n className=\"mt-2u flex flex-wrap items-center gap-1u text-oprt-base\"\n aria-label=\"Value scale\"\n >\n <NumberText\n value={formatNumber(heatDomain.min)}\n unit={unit}\n className=\"min-w-0 font-mono\"\n />\n <span className=\"flex shrink-0 gap-1u\" aria-hidden=\"true\">\n {sequentialClasses.map((color) => (\n <span key={color} className={clsx(\"h-1u w-3u bg-current\", color, KEEP_COLORS)} />\n ))}\n </span>\n <NumberText\n value={formatNumber(heatDomain.max)}\n unit={unit}\n className=\"min-w-0 font-mono\"\n />\n </div>\n )}\n {series.length > 0 && (\n <div\n className=\"mt-2u flex flex-wrap gap-1u\"\n role=\"group\"\n aria-label={`${label} series visibility`}\n >\n {series.map((item) => (\n <Button\n key={item.id}\n variant=\"ghost\"\n size=\"sm\"\n className=\"min-w-0 max-w-full oprt-wrap-anywhere\"\n aria-label={\n variant === \"heatmap\" ? `${seriesNumber.get(item.id)} ${item.label}` : item.label\n }\n aria-pressed={!hidden.has(item.id)}\n disabled={disabled}\n onClick={() => {\n toggle(item);\n setEmphasized(null);\n }}\n onPointerEnter={() => {\n if (!hidden.has(item.id)) setEmphasized(item.id);\n }}\n onPointerLeave={() => setEmphasized(null)}\n onFocus={() => {\n if (!hidden.has(item.id)) setEmphasized(item.id);\n }}\n onBlur={() => setEmphasized(null)}\n >\n {variant !== \"heatmap\" && (\n <svg\n aria-hidden=\"true\"\n width=\"28\"\n height=\"16\"\n viewBox=\"0 0 28 16\"\n className={clsx(\"mr-1u shrink-0\", colorClass(item))}\n >\n {(variant === \"line\" || variant === \"area\") && (\n <path\n d=\"M1 8 H27\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n strokeDasharray={dashPatterns[seriesIdentity(item.id)]}\n />\n )}\n {(variant === \"bar\" || variant === \"histogram\") && (\n <rect\n x={8}\n y={5}\n width={12}\n height={9}\n fill=\"currentColor\"\n fillOpacity=\"var(--oprt-chart-bar-opacity)\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n />\n )}\n <Marker\n identity={seriesIdentity(item.id)}\n x={14}\n y={variant === \"bar\" || variant === \"histogram\" ? 5 : 8}\n size={2.5}\n solid={variant === \"line\" || variant === \"area\"}\n />\n </svg>\n )}\n {variant === \"heatmap\" && (\n <span className=\"mr-1u font-mono\">{seriesNumber.get(item.id)}</span>\n )}\n {item.label}\n {hidden.has(item.id) && <span className=\"ml-1u text-oprt-text-dim\">Hidden</span>}\n </Button>\n ))}\n </div>\n )}\n {rangeSelection && inspectable && fullDomain.min < fullDomain.max && anyFiniteX && (\n <ChartRangeSelector\n label={label}\n series={fullVisible}\n domain={fullDomain}\n range={view}\n format={formatPosition}\n renderPosition={renderPosition}\n disabled={disabled}\n onChange={changeRange}\n />\n )}\n {showData && (\n <div className=\"mt-2u\">\n <DataView\n id={ids.data}\n label={label}\n series={distribution ? visible : series}\n hidden={hidden}\n distribution={distribution}\n xHeading={xHeading}\n yHeading={yHeading}\n formatX={formatX}\n formatNumber={formatNumber}\n separator={separator}\n />\n </div>\n )}\n </div>\n );\n});\nChart.displayName = \"Chart\";\n","Chart/ChartControls.tsx":"\"use client\";\n\nimport { memo, useEffect, useMemo, useRef, type ReactNode, type RefObject } from \"react\";\nimport { Slider, SliderFill, SliderThumb, SliderTrack } from \"react-aria-components\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport type { ChartRange, ChartSeries } from \"./Chart\";\nimport {\n extent,\n fraction,\n interpolate,\n isFinitePoint,\n linePath,\n scale,\n segments,\n seriesColorClass,\n type Domain,\n} from \"./geometry\";\n\n/** A single keyboard stop; React Aria owns slider navigation and focus. */\nexport function ChartInspection({\n label,\n index,\n count,\n describe,\n onChange,\n onFocus,\n onBlur,\n inputRef,\n}: {\n label: string;\n index: number;\n count: number;\n describe: (index: number) => string;\n onChange: (index: number) => void;\n onFocus: () => void;\n onBlur: () => void;\n inputRef: RefObject<HTMLInputElement>;\n}) {\n const hydration = useHydrationRoot();\n // RAC exposes the native input ref but no value-text formatter. Keep its\n // behavior intact and give the input an exact, domain-specific spoken value.\n useEffect(() => {\n inputRef.current?.setAttribute(\"aria-valuetext\", describe(index));\n });\n return (\n <Slider\n key={hydration.key}\n {...hydration.probeProps}\n aria-label={`Inspect ${label}`}\n minValue={0}\n maxValue={Math.max(1, count - 1)}\n value={index}\n onChange={(value: number) => onChange(Math.min(count - 1, value))}\n className=\"absolute inset-0 pointer-events-none\"\n >\n <SliderTrack className=\"sr-only\">\n <SliderThumb inputRef={inputRef} onFocus={onFocus} onBlur={onBlur} />\n </SliderTrack>\n </Slider>\n );\n}\n\n/**\n * Normalized positions avoid timestamp precision and overflowing numeric spans. Memoized: Chart\n * passes stable props, so inspecting a sample or emphasizing a series does not redraw the overview.\n */\nexport const ChartRangeSelector = memo(function ChartRangeSelector({\n label,\n series,\n domain,\n range,\n format,\n renderPosition,\n disabled,\n onChange,\n}: {\n label: string;\n series: readonly ChartSeries[];\n domain: Domain;\n range: ChartRange;\n /** The spoken value of a handle. */\n format: (value: number) => string;\n /** A window end as page text, its number kept with its unit. */\n renderPosition: (value: number) => ReactNode;\n disabled: boolean;\n onChange: (value: ChartRange) => void;\n}) {\n const hydration = useHydrationRoot();\n const startRef = useRef<HTMLInputElement>(null);\n const endRef = useRef<HTMLInputElement>(null);\n useEffect(() => {\n startRef.current?.setAttribute(\"aria-valuetext\", format(range.start));\n endRef.current?.setAttribute(\"aria-valuetext\", format(range.end));\n });\n const start = fraction(range.start, domain) * 1000;\n const end = fraction(range.end, domain) * 1000;\n // The overview draws the full history, so it changes with the data and domain only, not with the window.\n const overview = useMemo(() => {\n const overviewSeries = series.map((item) => ({\n ...item,\n data: item.data.filter(\n (point) => !Number.isFinite(point.x) || (point.x >= domain.min && point.x <= domain.max),\n ),\n }));\n const overviewY = scale(\n extent(\n overviewSeries.flatMap((item) => item.data.filter(isFinitePoint).map((point) => point.y)),\n ),\n 44,\n 4,\n );\n const overviewX = scale(domain, 0, 1000);\n return overviewSeries.map((item) => (\n <g key={item.id} className={seriesColorClass(item.id, item.color)}>\n {segments(item.data).map((run, index) => (\n <path\n key={index}\n d={linePath(run, overviewX, overviewY)}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n vectorEffect=\"non-scaling-stroke\"\n />\n ))}\n </g>\n ));\n }, [series, domain]);\n const full = range.start === domain.min && range.end === domain.max;\n return (\n <div className=\"mt-3u border-t border-oprt-border pt-2u\" data-chart-range=\"\">\n <div className=\"mb-1u flex flex-wrap items-center justify-between gap-1u\">\n <span className=\"text-oprt-base font-semibold uppercase tracking-oprt-caps\">\n Observation window\n </span>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={disabled || full}\n onClick={() => onChange({ start: domain.min, end: domain.max })}\n >\n Reset range\n </Button>\n </div>\n <Slider\n key={hydration.key}\n {...hydration.probeProps}\n aria-label={`${label} observation window`}\n minValue={0}\n maxValue={1000}\n step={1}\n value={[start, end]}\n isDisabled={disabled}\n className=\"px-1.5u data-[disabled]:opacity-50\"\n onChange={(values: number[]) => {\n const lower = values[0]!;\n const upper = values[1]!;\n // Preserve a non-zero interval, including when a thumb meets its sibling.\n if (lower >= upper) return;\n onChange({\n start: interpolate(domain, lower / 1000),\n end: interpolate(domain, upper / 1000),\n });\n }}\n >\n <SliderTrack className=\"relative h-6u w-full touch-none border-y border-oprt-border bg-oprt-surface-2\">\n <svg\n data-chart-overview=\"\"\n aria-hidden=\"true\"\n viewBox=\"0 0 1000 48\"\n preserveAspectRatio=\"none\"\n className=\"pointer-events-none absolute inset-0 h-full w-full\"\n >\n {overview}\n <rect x={0} y={0} width={start} height={48} fill=\"var(--oprt-bg)\" opacity={0.75} />\n <rect\n x={end}\n y={0}\n width={1000 - end}\n height={48}\n fill=\"var(--oprt-bg)\"\n opacity={0.75}\n />\n </svg>\n <SliderFill className=\"pointer-events-none absolute h-full border-y border-oprt-accent\" />\n {[\"start\", \"end\"].map((edge, index) => (\n <SliderThumb\n key={edge}\n index={index}\n aria-label={`${label} window ${edge}`}\n inputRef={index === 0 ? startRef : endRef}\n className=\"group/range top-1/2 flex h-full min-h-3u w-3u cursor-ew-resize items-center justify-center outline-hidden data-[disabled]:cursor-not-allowed\"\n >\n <span\n className={clsx(\n \"flex h-full w-1u items-center justify-center border border-oprt-accent bg-oprt-surface-1\",\n \"group-data-[focus-visible]/range:shadow-oprt-focus group-data-[focus-visible]/range:border-oprt-border-focus\",\n )}\n >\n <span className=\"h-2u border-l border-oprt-accent\" />\n </span>\n </SliderThumb>\n ))}\n </SliderTrack>\n </Slider>\n <div className=\"mt-1u flex flex-wrap justify-between gap-1u font-mono text-oprt-base text-oprt-text-dim oprt-wrap-anywhere\">\n <span className=\"min-w-0\">{renderPosition(range.start)}</span>\n <span className=\"min-w-0\">{renderPosition(range.end)}</span>\n </div>\n </div>\n );\n});\n","Chart/geometry.ts":"/** SVG coordinates are geometry; appearance is supplied by OPERATOR tokens. */\nexport interface Domain {\n min: number;\n max: number;\n}\nexport interface Coordinate {\n x: number;\n y: number | null;\n}\nexport interface FiniteCoordinate {\n x: number;\n y: number;\n}\n\nexport function isFinitePoint<T extends Coordinate>(point: T): point is T & FiniteCoordinate {\n return Number.isFinite(point.x) && point.y !== null && Number.isFinite(point.y);\n}\n\nexport function extent(values: readonly number[], includeZero = false): Domain {\n let min = includeZero ? 0 : Infinity;\n let max = includeZero ? 0 : -Infinity;\n for (const value of values) {\n if (!Number.isFinite(value)) continue;\n min = Math.min(min, value);\n max = Math.max(max, value);\n }\n return min === Infinity ? { min: 0, max: 1 } : { min, max };\n}\n\n/** Halving operands avoids an overflowing span when the domain straddles zero. */\nexport function fraction(value: number, domain: Domain): number {\n if (domain.min === domain.max) return 0.5;\n const span = domain.max - domain.min;\n const ratio = Number.isFinite(span)\n ? (value - domain.min) / span\n : (value / 2 - domain.min / 2) / (domain.max / 2 - domain.min / 2);\n return Math.max(0, Math.min(1, ratio));\n}\n\nexport function interpolate(domain: Domain, fraction: number): number {\n if (fraction === 0) return domain.min;\n if (fraction === 1) return domain.max;\n return domain.min * (1 - fraction) + domain.max * fraction;\n}\n\nexport function scale(domain: Domain, start: number, end: number) {\n return (value: number) => start + fraction(value, domain) * (end - start);\n}\n\nexport function ticks(domain: Domain, count = 5): number[] {\n if (domain.min === domain.max) return [domain.min];\n return Array.from({ length: count }, (_, index) => interpolate(domain, index / (count - 1)));\n}\n\n/** Round display ticks outwards; raw values stay untouched in the data view. */\nexport function niceAxis(\n domain: Domain,\n wholeNumbers = false,\n): { domain: Domain; ticks: number[] } {\n const fallback = () => ({ domain, ticks: ticks(domain) });\n const span = domain.max - domain.min;\n if (!Number.isFinite(span) || span <= 0) return fallback();\n const rough = span / 4;\n const magnitude = 10 ** Math.floor(Math.log10(rough));\n if (!Number.isFinite(magnitude) || magnitude === 0) return fallback();\n const ratio = rough / magnitude;\n const step = Math.max(\n wholeNumbers ? 1 : 0,\n (ratio <= 1 ? 1 : ratio <= 2 ? 2 : ratio <= 5 ? 5 : 10) * magnitude,\n );\n const min = Math.floor(domain.min / step) * step;\n const max = Math.ceil(domain.max / step) * step;\n const count = Math.round((max - min) / step);\n if (![min, max, step, count].every(Number.isFinite) || step <= 0 || count < 1 || count > 10)\n return fallback();\n const values = Array.from({ length: count + 1 }, (_, index) =>\n Number((min + index * step).toPrecision(14)),\n );\n // Rounding must never crop an extreme finite sample.\n if (values[0]! > domain.min || values[values.length - 1]! < domain.max) return fallback();\n return { domain: { min: values[0]!, max: values[values.length - 1]! }, ticks: values };\n}\n\n/** Missing samples split paths in authored order; they are never interpolated. */\nexport function segments(points: readonly Coordinate[]): FiniteCoordinate[][] {\n const result: FiniteCoordinate[][] = [];\n let segment: FiniteCoordinate[] = [];\n for (const point of points) {\n if (isFinitePoint(point)) segment.push(point);\n else if (segment.length) {\n result.push(segment);\n segment = [];\n }\n }\n if (segment.length) result.push(segment);\n return result;\n}\n\n/** Keep every segment boundary; thin only marker artwork, never the line/data. */\nexport function markerPoints(\n runs: readonly FiniteCoordinate[][],\n x: (value: number) => number,\n): Set<FiniteCoordinate> {\n const marked = new Set<FiniteCoordinate>();\n for (const run of runs) {\n if (!run.length) continue;\n const first = run[0]!;\n const last = run[run.length - 1]!;\n marked.add(first);\n let previousX = x(first.x);\n for (const point of run.slice(1, -1)) {\n const position = x(point.x);\n if (Math.abs(position - previousX) >= 48 && Math.abs(x(last.x) - position) >= 48) {\n marked.add(point);\n previousX = position;\n }\n }\n marked.add(last);\n }\n return marked;\n}\n\n/**\n * Samples by x, built once per data change so a lookup is direct instead of a search. A repeated x\n * keeps its first sample, as `Array.prototype.find` would; non-finite x never matches.\n */\nexport function indexByX<T extends Coordinate>(points: readonly T[]): Map<number, T> {\n const index = new Map<number, T>();\n for (const point of points) {\n if (Number.isFinite(point.x) && !index.has(point.x)) index.set(point.x, point);\n }\n return index;\n}\n\n/**\n * The index of the value whose position is nearest `position`, or -1 for no values. `values` are\n * ascending and `toPosition` keeps their order, so this is a binary search; a tie keeps the earlier\n * value.\n */\nexport function nearestIndex(\n values: readonly number[],\n position: number,\n toPosition: (value: number) => number,\n): number {\n if (values.length === 0) return -1;\n let low = 0;\n let high = values.length;\n // The first value at or after the position.\n while (low < high) {\n const middle = (low + high) >>> 1;\n if (toPosition(values[middle]!) < position) low = middle + 1;\n else high = middle;\n }\n if (low === 0) return 0;\n if (low === values.length) return values.length - 1;\n const before = Math.abs(toPosition(values[low - 1]!) - position);\n const after = Math.abs(toPosition(values[low]!) - position);\n return after < before ? low : low - 1;\n}\n\nexport function linePath(\n points: readonly FiniteCoordinate[],\n x: (value: number) => number,\n y: (value: number) => number,\n) {\n return points\n .map((point, index) => `${index === 0 ? \"M\" : \"L\"}${x(point.x)},${y(point.y)}`)\n .join(\" \");\n}\n\nexport function seriesIdentity(id: string): number {\n let hash = 2166136261;\n for (let i = 0; i < id.length; i++) hash = Math.imul(hash ^ id.charCodeAt(i), 16777619);\n return (hash >>> 0) % 8;\n}\n\nexport const categoryClasses = [\n \"text-oprt-cat-1\",\n \"text-oprt-cat-2\",\n \"text-oprt-cat-3\",\n \"text-oprt-cat-4\",\n \"text-oprt-cat-5\",\n \"text-oprt-cat-6\",\n \"text-oprt-cat-7\",\n \"text-oprt-cat-8\",\n] as const;\n\n/** A categorical color token, 1 to 8. */\nexport type CategoryColor = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;\n\n/**\n * The color class of a series wherever it is drawn (a Chart's marks, legend and overview, a\n * Sparkline): its chosen token when it has one, else the one its stable ID gives it, so a detail\n * trend matches its source plot. With neither, the first token.\n */\nexport function seriesColorClass(id: string | undefined, color?: number): string {\n if (color !== undefined && Number.isInteger(color) && color >= 1 && color <= 8) {\n return categoryClasses[color - 1]!;\n }\n return categoryClasses[id === undefined ? 0 : seriesIdentity(id)]!;\n}\n\nexport const sequentialClasses = [\n \"text-oprt-data-seq-1\",\n \"text-oprt-data-seq-2\",\n \"text-oprt-data-seq-3\",\n \"text-oprt-data-seq-4\",\n \"text-oprt-data-seq-5\",\n] as const;\n\n/** Whether `value` shows exactly with `decimals` fraction digits (within float noise). */\nconst exactAt = (value: number, decimals: number) =>\n Math.abs(Number(value.toFixed(decimals)) - value) <= Math.abs(value) * 1e-12;\n\n/**\n * A computed position (an interpolated tick, a bin edge) without the float noise its arithmetic\n * adds, such as 28.549999999999997 for 28.55. Fifteen significant digits keep every supplied value.\n */\nexport const withoutFloatNoise = (value: number) =>\n Number.isFinite(value) ? Number(value.toPrecision(15)) : value;\n\n/**\n * Grouped y labels of up to this many characters stay plain; the chart's gutter grows to hold them\n * (\"10,000,000\", \"-2,000,000\"). Beyond, the axis is written in scientific notation (coordinator\n * ruling on the B7 re-review).\n */\nexport const Y_LABEL_LIMIT = 11;\n// Every engine accepts up to 20 fraction digits; past that Intl.NumberFormat throws.\nconst MAX_FRACTION_DIGITS = 20;\n\n/**\n * One value in scientific notation, as exact as `limit` characters allow: the value of a flat\n * series, the single tick of its axis. Its exponent is never cut; the exact value stays in the\n * tick's title, the readout and the table.\n */\nfunction fittedScientific(value: number, locale: string, limit: number): string {\n const exactDigits = /\\.(\\d+)e/.exec(value.toExponential())?.[1]?.length ?? 0;\n for (let digits = exactDigits; ; digits--) {\n const text = new Intl.NumberFormat(locale, {\n notation: \"scientific\",\n maximumFractionDigits: digits,\n }).format(value);\n if (text.length <= limit || digits === 0) return text;\n }\n}\n\n/**\n * Labels for the y axis's gridlines, which are whole multiples of their spacing (niceAxis), in the\n * chart's one number format: digits grouped in `locale` as the readout, the table and the range\n * line group them (coordinator ruling). Every label has the decimals the spacing needs, so\n * neighboring gridlines never read the same (Chart-R6), and more if a tick needs them to show its\n * exact value. Labels longer than Y_LABEL_LIMIT, or spacings finer than Intl can write in fixed\n * notation, go to scientific notation with the digits the spacing needs (\"1.2338E7\"); the chart's\n * gutter fits every label, so none is cut. A single tick (a flat series) is written in full when\n * it fits, else in scientific notation fitted to the limit. The x axis does not use this: its\n * ticks are interpolated and its heatmap columns are samples, and each shows its exact value.\n */\nexport function tickLabels(values: readonly number[], locale: string): string[] {\n const finite = values.filter(Number.isFinite).sort((a, b) => a - b);\n let step = Infinity;\n for (let index = 1; index < finite.length; index++) {\n const gap = finite[index]! - finite[index - 1]!;\n if (gap > 0) step = Math.min(step, gap);\n }\n if (!Number.isFinite(step)) {\n const exact = new Intl.NumberFormat(locale, { maximumSignificantDigits: 21 });\n return values.map((value) => {\n const text = exact.format(value + 0);\n return text.length <= Y_LABEL_LIMIT ? text : fittedScientific(value, locale, Y_LABEL_LIMIT);\n });\n }\n // The epsilon keeps a spacing such as 0.0099999999999909 at its intended power of ten.\n const stepExponent = Math.floor(Math.log10(step) + 1e-9);\n let decimals = Math.max(0, -stepExponent);\n while (decimals < 6 && !finite.every((value) => exactAt(value, decimals))) decimals++;\n if (decimals <= MAX_FRACTION_DIGITS) {\n const format = new Intl.NumberFormat(locale, {\n minimumFractionDigits: decimals,\n maximumFractionDigits: decimals,\n });\n const labels = values.map((value) => format.format(value + 0));\n if (labels.every((label) => label.length <= Y_LABEL_LIMIT)) return labels;\n }\n const largest = Math.max(...finite.map(Math.abs));\n const largestExponent = largest === 0 ? stepExponent : Math.floor(Math.log10(largest) + 1e-9);\n const digits = Math.min(16, Math.max(0, largestExponent - stepExponent));\n const scientific = new Intl.NumberFormat(locale, {\n notation: \"scientific\",\n minimumFractionDigits: digits,\n maximumFractionDigits: digits,\n });\n const zero = new Intl.NumberFormat(locale).format(0);\n return values.map((value) => (value === 0 ? zero : scientific.format(value)));\n}\n\n/**\n * What separates the two ends of an interval in `locale`: a comma, unless the locale's decimal\n * mark is a comma, then a semicolon (\"[12.3, 28.55)\" in en-US, \"[12,3; 28,55)\" in de-DE).\n */\nexport function intervalSeparator(locale: string): string {\n const decimal = new Intl.NumberFormat(locale)\n .formatToParts(1.5)\n .find((part) => part.type === \"decimal\");\n return decimal?.value === \",\" ? \"; \" : \", \";\n}\n","Chart/index.ts":"export {\n Chart,\n type ChartProps,\n type ChartPoint,\n type ChartSeries,\n type ChartVariant,\n type ChartRange,\n} from \"./Chart\";\nexport { ChartManifest } from \"./Chart.manifest\";\n","Checkbox/Checkbox.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const CheckboxManifest: Record<string, Component> = {\n Checkbox: {\n description:\n \"Binary or tri-state checkbox. Shared OPERATOR Check icon when checked and Minus icon when indeterminate, inside an accent outline.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer/touch feedback. None disables motion; keyboard and system reduced motion are always immediate.\",\n },\n checked: {\n type: \"enum\",\n values: [\"true\", \"false\", \"indeterminate\"],\n description: \"Pass boolean for binary state, or 'indeterminate' for tri-state.\",\n },\n defaultChecked: {\n type: \"enum\",\n values: [\"true\", \"false\", \"indeterminate\"],\n },\n onCheckedChange: {\n type: \"function\",\n description:\n \"(checked: boolean | 'indeterminate') => void. Called when the operator checks or clears the box; the only way to observe a change.\",\n },\n disabled: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n name: { type: \"string\" },\n value: { type: \"string\" },\n },\n composable_with: [\"Field\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<Field label=\"ACKNOWLEDGE\" required>\\n <Checkbox />\\n</Field>',\n },\n};\n","Checkbox/Checkbox.tsx":"\"use client\";\n\nimport { Checkbox as AriaCheckbox } from \"react-aria-components\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, useRef, useState, type MouseEvent } from \"react\";\nimport clsx from \"clsx\";\nimport { useMotion, type MotionMode } from \"../_internal/motion\";\nimport { Check, Minus } from \"@oprt/icons\";\nimport { useFieldWiring } from \"../_internal/fieldIds\";\nimport { CheckboxManifest } from \"./Checkbox.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly, useAriaInvalidRef } from \"../_internal/aria\";\nimport { keyForIds } from \"../_internal/hydration\";\nimport { FORCED_CHECKBOX_BOX } from \"../_internal/controlStyles\";\nimport { useInputRefObject } from \"../_internal/mergeRefs\";\n\nconst rootStyles = cva(\n // Selected state = a checkmark on the surface (not a solid fill). The box border\n // turns accent; the check (or, for tri-state, the bar) is the accent mark inside.\n //\n // RAC state attributes replace the Radix ones: data-selected / data-indeterminate\n // instead of data-state, data-focus-visible instead of the :focus-visible\n // pseudo-class (the role element is a hidden <input>, so the pseudo-class would\n // never match the hidden input), data-disabled instead of :disabled. A named\n // label group forwards those states to the visual box inside its 24px target.\n // RAC has no\n // \"unselected\" attribute, so the resting border is the base class and the selected\n // variants override it (attribute selectors out-specify a plain class). The resting border is\n // the control outline (design call DC-3); an invalid box takes the error color whether it is\n // checked or not (the Toggle-R5 precedent): Tailwind sorts these state variants by name, so\n // `selected` would win a tie, and `[&&]` raises the invalid rule above it.\n \"relative inline-flex items-center justify-center border bg-oprt-surface-2 oprt-motion-track group-data-[focus-visible]/checkbox:border-oprt-border-focus group-data-[focus-visible]/checkbox:shadow-oprt-focus group-data-[disabled]/checkbox:opacity-50 group-data-[disabled]/checkbox:cursor-not-allowed border-oprt-border-control group-data-[selected]/checkbox:border-oprt-accent group-data-[indeterminate]/checkbox:border-oprt-accent group-data-[invalid]/checkbox:[&&]:border-oprt-error\",\n {\n variants: {\n size: {\n sm: \"h-2u w-2u\",\n md: \"h-2.5u w-2.5u\",\n lg: \"h-3u w-3u\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport type CheckboxProps = VariantProps<typeof rootStyles> & {\n motion?: MotionMode;\n checked?: boolean | \"indeterminate\";\n defaultChecked?: boolean | \"indeterminate\";\n onCheckedChange?: (checked: boolean | \"indeterminate\") => void;\n disabled?: boolean;\n required?: boolean;\n name?: string;\n value?: string;\n className?: string;\n id?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: boolean | \"true\" | \"false\";\n};\n\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>((props, ref) => {\n const {\n className,\n motion = \"auto\",\n size,\n id,\n checked,\n defaultChecked,\n onCheckedChange,\n disabled,\n required,\n name,\n value,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n } = props;\n const motionState = useMotion(motion);\n // The shared Field rules (Stage 1 Field-R2): its own id, names, description and aria-invalid\n // win over the Field's; a required Field makes it required (coordinator ruling on DC-B04-1).\n const {\n id: finalId,\n labelledBy,\n describedBy,\n invalid: isInvalid,\n required: isRequired,\n } = useFieldWiring(\n {\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n required,\n },\n { label: \"labelledby\" },\n );\n\n if (process.env.NODE_ENV !== \"production\") {\n // Every prop it received: each one the manifest lists as an enum is checked (checked and\n // defaultChecked too), and booleans are skipped (Checkbox-R5).\n devValidate(\"Checkbox\", CheckboxManifest.Checkbox!.props, { ...props });\n }\n\n // Radix modeled tri-state as a third value of `checked`; RAC splits it into\n // isSelected + isIndeterminate. Uncontrolled indeterminate has to clear on the\n // first user change, which Radix did internally.\n const isControlled = checked !== undefined;\n const [uncontrolledIndeterminate, setUncontrolledIndeterminate] = useState(\n defaultChecked === \"indeterminate\",\n );\n const isIndeterminate = isControlled ? checked === \"indeterminate\" : uncontrolledIndeterminate;\n\n const setNode = useAriaInvalidRef<HTMLInputElement>(ref, isInvalid);\n // React Aria takes the hidden input's ref as an object; the public ref lands on that input,\n // the element with role=\"checkbox\".\n const inputRef = useInputRefObject(setNode);\n const deferredInputMotion = useRef<Event | null>(null);\n const motionProps = {\n ...motionState.props,\n onClickCapture: (event: MouseEvent<HTMLElement>) => {\n // WebKit forwards external label activation as a detail=0 input click.\n // Rendering motion in capture would restore controlled checked before\n // RAC reads the browser's new value. Settle it after onChange instead.\n const nativeClick = event.nativeEvent;\n const deferMotion = event.target === inputRef.current && event.detail === 0;\n deferredInputMotion.current = deferMotion ? nativeClick : null;\n if (deferMotion) {\n // Native dispatch may run microtasks between capture and bubble.\n // Expire only after the whole event, without clearing a newer request.\n setTimeout(() => {\n if (deferredInputMotion.current === nativeClick) deferredInputMotion.current = null;\n }, 0);\n } else {\n motionState.props.onClickCapture(event);\n }\n },\n };\n\n // exactOptionalPropertyTypes forbids passing an explicit `undefined` for an\n // optional prop, so build the props object conditionally.\n const rootProps: Record<string, unknown> = {\n isDisabled: disabled ?? false,\n isRequired,\n isInvalid,\n isIndeterminate,\n onChange: (next: boolean) => {\n const settleMotion = deferredInputMotion.current;\n deferredInputMotion.current = null;\n if (!isControlled) setUncontrolledIndeterminate(false);\n onCheckedChange?.(next);\n if (settleMotion) motionState.settleInput();\n },\n ...definedOnly({\n id: finalId,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n name,\n value,\n }),\n };\n if (isControlled) rootProps[\"isSelected\"] = checked === true;\n if (defaultChecked !== undefined) {\n rootProps[\"defaultSelected\"] = defaultChecked === true;\n }\n\n return (\n <AriaCheckbox\n // RAC keeps the ids it mounted with (see keyForIds).\n key={keyForIds(finalId, labelledBy)}\n {...rootProps}\n {...motionProps}\n render={(domProps) => <label {...domProps} {...motionProps} />}\n inputRef={inputRef}\n className={clsx(\n \"oprt-motion group/checkbox relative inline-flex self-start w-fit items-center justify-center min-h-3u min-w-3u cursor-pointer data-[disabled]:cursor-not-allowed\",\n className,\n )}\n >\n {/* Forced colors keep the marks (currentColor) but not focus, drawn as a border color and a\n shadow: the box takes the shared forced-colors treatment (Checkbox-R1). */}\n <span className={clsx(rootStyles({ size }), \"text-oprt-accent\", FORCED_CHECKBOX_BOX)}>\n {/* Checked → checkmark; indeterminate → bar. Both accent on the surface.\n RAC has no Indicator that unmounts, so both marks are always in the\n DOM and toggled by the parent's data-selected / data-indeterminate. */}\n <Check\n size={size === \"sm\" ? 12 : size === \"lg\" ? 20 : 16}\n className=\"oprt-cb-check oprt-motion-check shrink-0\"\n />\n <Minus\n size={size === \"sm\" ? 12 : size === \"lg\" ? 20 : 16}\n className=\"oprt-cb-bar oprt-motion-check shrink-0\"\n />\n </span>\n </AriaCheckbox>\n );\n});\nCheckbox.displayName = \"Checkbox\";\n","Checkbox/index.ts":"export { Checkbox, type CheckboxProps } from \"./Checkbox\";\nexport { CheckboxManifest } from \"./Checkbox.manifest\";\n","Collapsible/Collapsible.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const CollapsibleManifest: Record<string, Component> = {\n Collapsible: {\n description:\n \"Accordion container whose CollapsibleItem children expand and collapse; single or multiple open at a time.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer/touch feedback. None disables motion; keyboard and system reduced motion are always immediate.\",\n },\n type: { type: \"enum\", values: [\"single\", \"multiple\"], default: \"single\" },\n defaultValue: {\n type: \"union\",\n types: [\"string\", \"array\"],\n description:\n \"Initial open item: a string (type=single, empty for none) or a string[] (type=multiple).\",\n },\n value: {\n type: \"union\",\n types: [\"string\", \"array\"],\n description:\n \"Controlled open item: a string (type=single, empty for none) or a string[] (type=multiple).\",\n },\n onValueChange: { type: \"function\" },\n collapsible: { type: \"boolean\", default: true },\n },\n slots: {\n children: { type: \"CollapsibleItem\", required: true },\n },\n composable_with: [\"Card\"],\n example:\n '<Collapsible type=\"single\">\\n <CollapsibleItem value=\"net\" title=\"NETWORK\">All links nominal.</CollapsibleItem>\\n <CollapsibleItem value=\"pwr\" title=\"POWER\">Battery 98%.</CollapsibleItem>\\n</Collapsible>',\n },\n CollapsibleItem: {\n description:\n \"A single expandable section with an uppercase trigger title and collapsible content body.\",\n props: {\n value: { type: \"string\", required: true },\n title: { type: \"string\", required: true },\n disabled: { type: \"boolean\", default: false },\n },\n slots: {\n children: { type: \"any\", required: true },\n },\n composable_with: [\"Collapsible\"],\n example: '<CollapsibleItem value=\"net\" title=\"NETWORK\">All links nominal.</CollapsibleItem>',\n },\n};\n\nexport const CollapsibleCompositionRules = {\n Collapsible: {\n allowed_children: [\"CollapsibleItem\"],\n },\n};\n","Collapsible/Collapsible.tsx":"\"use client\";\n\nimport {\n DisclosureGroup,\n Disclosure,\n DisclosurePanel,\n Button as AriaButton,\n Heading,\n type Key,\n type DisclosureProps,\n type DisclosureGroupProps,\n} from \"react-aria-components\";\nimport { mergeProps } from \"react-aria\";\nimport { forwardRef, useRef, useState, type ComponentPropsWithoutRef, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\nimport { ChevronDown } from \"@oprt/icons\";\nimport { CollapsibleManifest } from \"./Collapsible.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport {\n useMotion,\n composeMotionHandlers,\n MotionContext,\n type MotionMode,\n} from \"../_internal/motion\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\nfunction CollapsiblePanel({ expanded, children }: { expanded: boolean; children: ReactNode }) {\n const panel = useRef<HTMLDivElement>(null);\n const latestExpanded = useRef(expanded);\n latestExpanded.current = expanded;\n useIsomorphicLayoutEffect(() => {\n const node = panel.current;\n if (!node) return;\n node.toggleAttribute(\"inert\", !expanded);\n if (!expanded && node.contains(document.activeElement)) {\n document.getElementById(node.getAttribute(\"aria-labelledby\") ?? \"\")?.focus();\n }\n // RAC 1.20 waits to hide until motion finishes. Inert excludes descendants\n // immediately. Guard a stale completion hiding a panel reopened during exit.\n const observer = new MutationObserver(() => {\n if (latestExpanded.current && node.hasAttribute(\"hidden\")) node.removeAttribute(\"hidden\");\n });\n observer.observe(node, { attributes: true, attributeFilter: [\"hidden\"] });\n return () => observer.disconnect();\n }, [expanded]);\n return (\n <DisclosurePanel\n ref={panel}\n role=\"region\"\n className=\"oprt-motion-panel text-oprt-md normal-case text-oprt-text\"\n >\n {/* The panel clips what is wider than itself (its height animation needs overflow: clip),\n so its body breaks long text, as CardBody does, instead of letting it run out of sight. */}\n <div className=\"min-w-0 break-words px-3u py-2u\">{children}</div>\n </DisclosurePanel>\n );\n}\n\n/**\n * React Aria renders only some of the attributes it is handed: global attributes, data-* and pointer\n * and mouse events, not aria-*, title or key and focus handlers, and an item's `id` is its\n * collection key. The props types accept every div attribute, so add back what it dropped onto the\n * element it rendered (Collapsible-R3). A handler it rendered runs once; one it has its own of is\n * chained.\n */\nfunction withDroppedProps(\n rendered: Record<string, unknown>,\n accepted: Record<string, unknown>,\n): Record<string, unknown> {\n const dropped: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(accepted)) {\n if (value === undefined) continue;\n if (!(key in rendered) || rendered[key] === undefined) dropped[key] = value;\n else if (typeof value === \"function\" && rendered[key] !== value) dropped[key] = value;\n }\n return mergeProps(rendered, dropped);\n}\n\n/**\n * Our public value is Radix-shaped (`\"\"` / `\"a\"` for single, `string[]` for\n * multiple); RAC's DisclosureGroup keys off a Set. Empty string means \"nothing\n * open\", never a key named \"\".\n */\nfunction toKeySet(value: string | string[] | undefined): Set<Key> {\n if (value === undefined) return new Set();\n if (Array.isArray(value)) return new Set(value);\n return new Set(value === \"\" ? [] : [value]);\n}\n\nexport type CollapsibleProps = Omit<\n ComponentPropsWithoutRef<\"div\">,\n \"type\" | \"value\" | \"defaultValue\" | \"onValueChange\"\n> & {\n motion?: MotionMode;\n type?: \"single\" | \"multiple\";\n value?: string | string[];\n defaultValue?: string | string[];\n onValueChange?: (value: string | string[]) => void;\n /** single-type only: allow the open item to collapse. Default true. */\n collapsible?: boolean;\n children: ReactNode;\n};\n\nexport const Collapsible = forwardRef<HTMLDivElement, CollapsibleProps>(\n (\n {\n type = \"single\",\n motion = \"auto\",\n value,\n defaultValue,\n onValueChange,\n collapsible = true,\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const motionState = useMotion(motion);\n // RAC has no equivalent of Radix's `collapsible={false}` (a single-type\n // accordion that refuses to close its open item), so the expanded set is\n // held here and the collapse-to-empty transition is filtered out. Holding\n // it here also lets `onValueChange` keep emitting our Radix-shaped value.\n const isControlled = value !== undefined;\n const [uncontrolledKeys, setUncontrolledKeys] = useState<Set<Key>>(() =>\n toKeySet(defaultValue),\n );\n const expandedKeys = isControlled ? toKeySet(value) : uncontrolledKeys;\n\n const handleExpandedChange = (keys: Set<Key>) => {\n if (type === \"single\" && !collapsible && keys.size === 0) return;\n if (!isControlled) setUncontrolledKeys(keys);\n if (!onValueChange) return;\n const list = [...keys].map(String);\n onValueChange(type === \"single\" ? (list[0] ?? \"\") : list);\n };\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Collapsible\", CollapsibleManifest.Collapsible!.props, { type });\n }\n\n const rootProps: DisclosureGroupProps = {\n ...(props as DisclosureGroupProps),\n allowsMultipleExpanded: type === \"multiple\",\n expandedKeys,\n onExpandedChange: handleExpandedChange,\n // `min-w-0`: as an unsized flex item the group gives way to what its row leaves, instead of\n // holding the width of the widest unbroken token in an open panel (break-words does not\n // lower a minimum size) and widening the page.\n className: clsx(\"oprt-motion w-full min-w-0 border-t border-oprt-border\", className),\n ...composeMotionHandlers(motionState.props, props),\n };\n\n return (\n <MotionContext.Provider value={motionState.mode}>\n <DisclosureGroup\n ref={ref}\n {...rootProps}\n render={(domProps) => (\n <div\n {...withDroppedProps(domProps as Record<string, unknown>, props)}\n {...composeMotionHandlers(motionState.props, props)}\n />\n )}\n >\n {children}\n </DisclosureGroup>\n </MotionContext.Provider>\n );\n },\n);\nCollapsible.displayName = \"Collapsible\";\n\nexport type CollapsibleItemProps = Omit<ComponentPropsWithoutRef<\"div\">, \"value\" | \"title\"> & {\n value: string;\n title: string;\n disabled?: boolean;\n children: ReactNode;\n};\n\nexport const CollapsibleItem = forwardRef<HTMLDivElement, CollapsibleItemProps>(\n ({ value, title, disabled, className, children, ...props }, ref) => {\n // RAC keys a Disclosure inside a group by `id`; the public prop stays\n // `value` so the component API is unchanged for consumers.\n const itemProps: DisclosureProps = {\n ...(props as DisclosureProps),\n id: value,\n isDisabled: disabled ?? false,\n // `group` moves from the trigger to the Disclosure: RAC exposes the open\n // state as data-expanded on the Disclosure, where Radix exposed\n // data-state=open on the trigger itself.\n className: clsx(\"oprt-motion-disclosure group border-b border-oprt-border\", className),\n };\n\n return (\n <Disclosure\n ref={ref}\n {...itemProps}\n // The item's own id (React Aria keeps `id` as the collection key) and the other attributes\n // it drops go on the item's element, so an in-page link can reach the section.\n render={(domProps) => (\n <div {...withDroppedProps(domProps as Record<string, unknown>, props)} />\n )}\n >\n {({ isExpanded }) => (\n <>\n <Heading className=\"m-0\">\n <AriaButton\n slot=\"trigger\"\n className={clsx(\n \"flex w-full items-center justify-between gap-2u px-3u text-left text-oprt-base font-medium uppercase tracking-oprt-caps text-oprt-text group-data-[expanded]:text-oprt-accent border border-transparent data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus outline-hidden disabled:opacity-50 disabled:cursor-not-allowed\",\n // A long title wraps and the trigger grows (the DC-8 rule): the height is a\n // minimum, and the padding is what is left of it around one line and the 2 px of\n // border, so a one-line trigger is exactly 40 px.\n \"min-h-oprt-lg py-[calc((var(--oprt-height-lg)-2px-var(--oprt-line-base))/2)]\",\n // Forced colors drop the focus border color and shadow and would paint the\n // transparent outline on every trigger: only a focused one draws one\n // (Collapsible-R5).\n \"forced-colors:outline-0 forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:-outline-offset-2 forced-colors:data-[focus-visible]:outline-[CanvasText]\",\n )}\n >\n <span className=\"min-w-0 oprt-wrap-anywhere\">{title}</span>\n <ChevronDown\n size={12}\n className=\"oprt-motion-chevron shrink-0 group-data-[expanded]:rotate-180\"\n />\n </AriaButton>\n </Heading>\n <CollapsiblePanel expanded={isExpanded}>{children}</CollapsiblePanel>\n </>\n )}\n </Disclosure>\n );\n },\n);\nCollapsibleItem.displayName = \"CollapsibleItem\";\n","Collapsible/index.ts":"export {\n Collapsible,\n CollapsibleItem,\n type CollapsibleProps,\n type CollapsibleItemProps,\n} from \"./Collapsible\";\nexport { CollapsibleManifest, CollapsibleCompositionRules } from \"./Collapsible.manifest\";\n","CollectionState/CollectionState.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const CollectionStateManifest: Record<string, Component> = {\n CollectionState: {\n description: \"Named collection state with supplied explanation and recovery actions.\",\n props: {\n variant: {\n type: \"enum\",\n values: [\"empty\", \"no-results\", \"error\", \"access-denied\"],\n required: true,\n description:\n \"Supplied collection outcome. Empty means no source records; no-results means no matching records.\",\n },\n title: {\n type: \"string\",\n required: true,\n description: \"Visible heading that names the region and explains its state.\",\n },\n description: {\n type: \"node\",\n description: \"Optional supporting content associated with the region.\",\n },\n actions: {\n type: \"node\",\n description:\n \"Caller-supplied recovery controls. Activation never changes the supplied collection state.\",\n },\n headingLevel: {\n type: \"number\",\n default: 2,\n description:\n \"Native heading level from 2 through 6; match the surrounding document hierarchy.\",\n },\n \"aria-live\": {\n type: \"enum\",\n values: [\"off\", \"polite\", \"assertive\"],\n description:\n \"Optional caller-owned live-region behavior. No announcement is added by default.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"Button\", \"Link\", \"DataGrid\", \"PropertyFilter\"],\n never_inside: [\"Button\"],\n example:\n '<CollectionState variant=\"error\" title=\"Equipment unavailable\" description=\"The source did not respond.\" actions={<Button onClick={retry}>Retry</Button>} />',\n },\n};\n","CollectionState/CollectionState.tsx":"\"use client\";\n\nimport { forwardRef, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { CollectionStateManifest } from \"./CollectionState.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\nconst headingStyles = cva(\n \"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base font-medium uppercase tracking-oprt-sm\",\n {\n variants: {\n variant: {\n empty: \"text-oprt-text\",\n \"no-results\": \"text-oprt-text\",\n error: \"text-oprt-error\",\n \"access-denied\": \"text-oprt-text\",\n },\n },\n },\n);\n\nexport type CollectionStateProps = Omit<HTMLAttributes<HTMLDivElement>, \"title\" | \"children\"> & {\n variant: \"empty\" | \"no-results\" | \"error\" | \"access-denied\";\n title: string;\n description?: ReactNode;\n actions?: ReactNode;\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n};\n\n/** Supplied collection state. Recovery actions and announcement timing belong to the caller. */\nexport const CollectionState = forwardRef<HTMLDivElement, CollectionStateProps>((allProps, ref) => {\n const {\n variant,\n title,\n description,\n actions,\n headingLevel = 2,\n className,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n ...props\n } = allProps;\n const id = useId();\n const titleId = `${id}-title`;\n const descriptionId = `${id}-description`;\n const Heading = `h${headingLevel}` as const;\n const hasDescription = description != null && typeof description !== \"boolean\";\n const hasActions = actions != null && typeof actions !== \"boolean\";\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"CollectionState\", CollectionStateManifest.CollectionState!.props, allProps);\n }\n\n return (\n <div\n {...props}\n ref={ref}\n role=\"region\"\n aria-labelledby={[titleId, labelledBy].filter(Boolean).join(\" \")}\n aria-describedby={\n [hasDescription && descriptionId, describedBy].filter(Boolean).join(\" \") || undefined\n }\n data-variant={variant}\n className={clsx(\n \"flex min-w-0 flex-col gap-2u border border-oprt-border bg-oprt-surface-1 p-3u font-sans text-oprt-base normal-case tracking-normal text-oprt-text\",\n className,\n )}\n >\n <Heading id={titleId} className={headingStyles({ variant })}>\n {title}\n </Heading>\n {hasDescription && (\n <div id={descriptionId} className=\"min-w-0 oprt-wrap-anywhere text-oprt-text-dim [&_p]:m-0\">\n {description}\n </div>\n )}\n {hasActions && <div className=\"flex min-w-0 flex-wrap items-center gap-1u\">{actions}</div>}\n </div>\n );\n});\nCollectionState.displayName = \"CollectionState\";\n","CollectionState/index.ts":"export { CollectionState, type CollectionStateProps } from \"./CollectionState\";\nexport { CollectionStateManifest } from \"./CollectionState.manifest\";\n","ComboBox/ComboBox.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ComboBoxManifest: Record<string, Component> = {\n ComboBox: {\n description:\n \"Searchable single-value selection with keyboard navigation and caller-supplied asynchronous results.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer arrival, exit, and open-state chevron rotation; keyboard, assistive activation, and system reduced motion remain immediate.\",\n },\n options: {\n type: \"array\",\n required: true,\n description:\n \"Available options. Values must be unique and stable; labels preserve authored case.\",\n items: {\n value: \"string (required, unique)\",\n label: \"string (required)\",\n disabled: \"boolean (default false)\",\n },\n },\n value: {\n type: \"string\",\n description:\n \"Controlled selected option value, or null to clear selection. Omit for internal selection state.\",\n },\n defaultValue: {\n type: \"string\",\n description: \"Initial selected option value for uncontrolled use.\",\n },\n onValueChange: {\n type: \"function\",\n description:\n \"(value: string | null) => void. Called on option selection or when clearing the input clears the selected value.\",\n },\n inputValue: {\n type: \"string\",\n description: \"Controlled search input text; independent from the selected value.\",\n },\n onInputChange: {\n type: \"function\",\n description:\n \"(text: string) => void. Use with inputValue for caller-owned search and asynchronous results.\",\n },\n label: {\n type: \"string\",\n description: \"Visible accessible label. Alternatively, use Field or aria-label.\",\n },\n \"aria-label\": {\n type: \"string\",\n description: \"Accessible name when there is no visible label or surrounding Field.\",\n },\n \"aria-describedby\": {\n type: \"string\",\n description:\n \"Description IDs; otherwise inherits the surrounding Field help or error message.\",\n },\n placeholder: { type: \"string\" },\n disabled: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n invalid: {\n type: \"boolean\",\n default: false,\n description: \"Error appearance and aria-invalid; otherwise inherits Field error state.\",\n },\n loading: {\n type: \"boolean\",\n default: false,\n description:\n \"Announces loading and marks input busy. Supplied options remain selectable unless disabled.\",\n },\n filter: {\n type: \"boolean\",\n default: true,\n description:\n \"Language-sensitive contains matching by default. Set false when the caller already filters options or supplies asynchronous results.\",\n },\n emptyMessage: { type: \"string\", default: \"No options found.\" },\n name: { type: \"string\", description: \"Form field name; submits the selected option value.\" },\n id: {\n type: \"string\",\n description: \"Input ID; otherwise inherits Field or React Aria's generated ID.\",\n },\n },\n composable_with: [\"Field\", \"DataGrid\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<ComboBox\\n label=\"STATION\"\\n options={[{ value: \"alpha\", label: \"Alpha station\" }, { value: \"bravo\", label: \"Bravo station\" }]}\\n value={station}\\n onValueChange={setStation}\\n placeholder=\"Search stations…\"\\n/>',\n },\n};\n","ComboBox/ComboBox.tsx":"\"use client\";\n\nimport { forwardRef, useRef } from \"react\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport {\n ComboBox as AriaComboBox,\n Input,\n Button,\n Label,\n ListBox,\n ListBoxItem,\n} from \"react-aria-components\";\nimport { ChevronDown, Check } from \"@oprt/icons\";\nimport { ComboBoxManifest } from \"./ComboBox.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { useFieldWiring } from \"../_internal/fieldIds\";\nimport { definedOnly, useAriaInvalidRef } from \"../_internal/aria\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport { MotionContext, useMotion, type MotionMode } from \"../_internal/motion\";\nimport { MotionPopover } from \"../_internal/MotionPopover\";\nimport { MENU_ITEM_TEXT, SCREEN_MARGIN } from \"../_internal/overlay\";\nimport {\n FORCED_FOCUS_WITHIN,\n FORCED_NO_RESTING_OUTLINE,\n LISTBOX_EMPTY,\n LISTBOX_ROW,\n LISTBOX_SURFACE,\n} from \"../_internal/controlStyles\";\n\nconst inputStyles = cva(\n \"min-w-0 w-full border bg-oprt-surface-2 font-sans text-oprt-md font-medium normal-case text-oprt-text placeholder:text-oprt-text-placeholder disabled:placeholder:text-oprt-text-dim outline-hidden focus-visible:shadow-oprt-focus disabled:cursor-not-allowed disabled:bg-oprt-surface-3 disabled:border-oprt-border-control disabled:text-oprt-text-dim\",\n {\n variants: {\n size: { sm: \"h-oprt-sm px-2u\", md: \"h-oprt-md px-2u\", lg: \"h-oprt-lg px-3u\" },\n invalid: {\n // The control outline (design call DC-3).\n false: \"border-oprt-border-control focus-visible:border-oprt-border-focus\",\n true: \"border-oprt-error focus-visible:border-oprt-error\",\n },\n },\n defaultVariants: { size: \"md\", invalid: false },\n },\n);\nexport type ComboBoxOption = { value: string; label: string; disabled?: boolean };\nexport type ComboBoxProps = VariantProps<typeof inputStyles> & {\n motion?: MotionMode;\n options: ComboBoxOption[];\n value?: string | null;\n defaultValue?: string | null;\n onValueChange?: (value: string | null) => void;\n inputValue?: string;\n onInputChange?: (value: string) => void;\n label?: string;\n \"aria-label\"?: string;\n \"aria-describedby\"?: string;\n placeholder?: string;\n disabled?: boolean;\n required?: boolean;\n loading?: boolean;\n filter?: boolean;\n emptyMessage?: string;\n name?: string;\n id?: string;\n className?: string;\n};\n\n// Development only, once per value: a selected value outside `options` shows an empty field while\n// the form still submits its key (ComboBox-R5).\nconst reportedValues = new Set<string>();\nfunction reportUnknownValue(received: string, options: readonly ComboBoxOption[]) {\n if (reportedValues.has(received)) return;\n reportedValues.add(received);\n console.error(\n JSON.stringify(\n {\n component: \"ComboBox\",\n error: \"UNKNOWN_VALUE\",\n prop: \"value\",\n received,\n valid_values: options.map((option) => option.value),\n suggestion:\n \"Keep the selected option in `options` (include it in remote results), or clear the value. Otherwise the field shows nothing while the form still submits the value.\",\n },\n null,\n 2,\n ),\n );\n}\n\nexport const ComboBox = forwardRef<HTMLInputElement, ComboBoxProps>((props, ref) => {\n const {\n motion = \"auto\",\n options,\n value,\n defaultValue,\n onValueChange,\n inputValue,\n onInputChange,\n label,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": ariaDescribedBy,\n placeholder,\n disabled = false,\n required,\n size = \"md\",\n invalid,\n loading = false,\n filter = true,\n emptyMessage = \"No options found.\",\n name,\n id,\n className,\n } = props;\n const motionState = useMotion(motion);\n const editing = useRef(false);\n // The shared Field rules (Stage 1 Field-R2): its own label or aria-label replaces the Field's\n // label; a required Field makes it required (coordinator ruling on DC-B04-1).\n const {\n id: inputId,\n labelledBy,\n describedBy,\n invalid: isInvalid,\n required: isRequired,\n } = useFieldWiring(\n { id, \"aria-label\": ariaLabel, \"aria-describedby\": ariaDescribedBy, invalid, required },\n { label: \"labelledby\", ownLabel: Boolean(label) },\n );\n // Named only by an aria-label, React Aria would name the chevron \"Show suggestions\" and the list\n // \"Suggestions Show suggestions\", the same for every such field (ComboBox-R7). The aria-label\n // becomes a visually hidden label instead, which React Aria wires to the field, the chevron and\n // the list alike.\n const hiddenLabel = !label && ariaLabel ? ariaLabel : undefined;\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"ComboBox\", ComboBoxManifest.ComboBox!.props, { ...props });\n // A caller who owns the query and its results (filter off, inputValue controlled) changes\n // the options while the operator types, so a missing selection is expected there.\n const selected = value ?? defaultValue;\n if (\n typeof selected === \"string\" &&\n (filter || inputValue === undefined) &&\n !options.some((option) => option.value === selected)\n )\n reportUnknownValue(selected, options);\n }\n const inputRef = useAriaInvalidRef(ref, isInvalid);\n // The listbox mounts on open and names itself by the label, so the label\n // and input must carry ids React still agrees with.\n const hydration = useHydrationRoot(inputId, labelledBy);\n const rootProps = definedOnly({\n selectedKey: value,\n defaultSelectedKey: defaultValue,\n inputValue,\n onInputChange: (text: string) => {\n if (editing.current) {\n editing.current = false;\n motionState.settleInput();\n }\n onInputChange?.(text);\n },\n name,\n \"aria-label\": hiddenLabel ? undefined : ariaLabel,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n });\n return (\n <MotionContext.Provider value={motionState.mode}>\n <AriaComboBox\n key={hydration.key}\n {...hydration.probeProps}\n {...rootProps}\n isDisabled={disabled}\n isRequired={isRequired}\n isInvalid={isInvalid}\n allowsEmptyCollection\n {...(!filter ? { defaultFilter: () => true } : {})}\n onSelectionChange={(key) => onValueChange?.(key === null ? null : String(key))}\n render={(domProps) => (\n <div\n {...domProps}\n {...motionState.props}\n onInputCapture={(event) => {\n if ((event.target as HTMLElement).getAttribute(\"role\") === \"combobox\") {\n editing.current = true;\n }\n }}\n onInput={() => {\n queueMicrotask(() => {\n editing.current = false;\n });\n }}\n />\n )}\n className={clsx(\n \"oprt-motion flex min-w-0 flex-col gap-1u font-sans text-oprt-base normal-case text-oprt-text\",\n className,\n )}\n >\n {/* A field label in its authored case, styled as Field's (NumberInput-R5); it wraps\n and long identifiers break anywhere (ComboBox-B2M-1..3). */}\n {label && (\n <Label className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base font-medium text-oprt-text\">\n {label}\n </Label>\n )}\n {hiddenLabel && <Label className=\"sr-only\">{hiddenLabel}</Label>}\n {/* In forced colors, focus in the field is an outline around the field and its button\n (ComboBox-R2); neither carries the outline forced colors would paint at rest. */}\n <div className={clsx(\"flex min-w-0 items-stretch\", FORCED_FOCUS_WITHIN)}>\n <Input\n ref={inputRef}\n {...definedOnly({ id: inputId, placeholder })}\n aria-busy={loading}\n className={clsx(inputStyles({ size, invalid: isInvalid }), FORCED_NO_RESTING_OUTLINE)}\n />\n <Button\n className={clsx(\n \"inline-flex min-w-3u shrink-0 items-center justify-center border border-l-0 border-oprt-border-control bg-oprt-surface-2 px-1u text-oprt-text outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[disabled]:cursor-not-allowed data-[disabled]:bg-oprt-surface-3 data-[disabled]:border-oprt-border-control data-[disabled]:text-oprt-text-dim\",\n FORCED_NO_RESTING_OUTLINE,\n )}\n >\n <ChevronDown size={12} className=\"oprt-motion-popover-chevron\" aria-hidden=\"true\" />\n </Button>\n </div>\n {/* One polite status region from the start, so the loading text is announced when it\n arrives; a region created together with its text often is not (ComboBox-R4, the\n JobStatus-R2 precedent). Empty, it is visually hidden and takes no room. */}\n <p role=\"status\" className={loading ? \"m-0 text-oprt-text-dim\" : \"sr-only\"}>\n {loading ? \"Loading options.\" : null}\n </p>\n {/* The listbox popup shared with Select: the popover width rule (design call DC-7)\n with the field's width as the minimum, the 16 px screen margin, and option text that\n breaks anywhere, so an unbroken option never runs the list off a phone screen. */}\n <MotionPopover\n motionState={motionState}\n className={LISTBOX_SURFACE}\n offset={2}\n containerPadding={SCREEN_MARGIN}\n >\n <ListBox\n className=\"outline-hidden\"\n renderEmptyState={() => (\n <p className={LISTBOX_EMPTY}>{loading ? \"Loading options.\" : emptyMessage}</p>\n )}\n >\n {options.map((option) => (\n <ListBoxItem\n key={option.value}\n id={option.value}\n textValue={option.label}\n isDisabled={option.disabled ?? false}\n className={LISTBOX_ROW}\n >\n {({ isSelected }) => (\n <>\n <span className={MENU_ITEM_TEXT}>{option.label}</span>\n {isSelected && <Check size={12} className=\"shrink-0\" aria-hidden=\"true\" />}\n </>\n )}\n </ListBoxItem>\n ))}\n </ListBox>\n </MotionPopover>\n </AriaComboBox>\n </MotionContext.Provider>\n );\n});\nComboBox.displayName = \"ComboBox\";\n","ComboBox/index.ts":"export { ComboBox, type ComboBoxProps, type ComboBoxOption } from \"./ComboBox\";\nexport { ComboBoxManifest } from \"./ComboBox.manifest\";\n","CommandPalette/CommandPalette.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const CommandPaletteManifest: Record<string, Component> = {\n CommandPalette: {\n description:\n \"Modal command launcher: search field over a filtered, keyboard-navigable action list. Command and group labels display uppercase; search input, empty messages, and keyboard shortcuts retain their casing.\",\n props: {\n commands: { type: \"array\", required: true },\n placeholder: { type: \"string\", default: \"Type a command…\" },\n emptyMessage: { type: \"string\", default: \"No results\" },\n open: { type: \"boolean\" },\n defaultOpen: {\n type: \"boolean\",\n default: false,\n description: \"Initial open state when the palette manages its own state.\",\n },\n onOpenChange: { type: \"function\" },\n },\n composable_with: [],\n never_inside: [\"CommandPalette\"],\n example:\n '<CommandPalette\\n open={open}\\n onOpenChange={setOpen}\\n commands={[\\n { id: \"new\", label: \"New File\", shortcut: \"⌘N\", onSelect: () => {} },\\n { id: \"open\", label: \"Open…\", shortcut: \"⌘O\", onSelect: () => {} },\\n ]}\\n/>',\n },\n};\n","CommandPalette/CommandPalette.tsx":"\"use client\";\n\nimport { Search } from \"@oprt/icons\";\nimport {\n Autocomplete,\n Dialog,\n Header,\n Heading,\n Input,\n ListBox,\n ListBoxItem,\n ListBoxSection,\n Modal,\n ModalOverlay,\n TextField,\n useFilter,\n type Key,\n} from \"react-aria-components\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { FORCED_ACTIVE_ROW, MENU_EMPTY, MENU_ITEM_SHORTCUT, MENU_ROW } from \"../_internal/overlay\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\nexport type Command = {\n id: string;\n label: string;\n group?: string;\n shortcut?: string;\n icon?: ReactNode;\n onSelect: () => void;\n disabled?: boolean;\n};\n\nexport type CommandPaletteProps = {\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n commands: Command[];\n placeholder?: string;\n emptyMessage?: string;\n className?: string;\n};\n\n// RAC state attributes replace the Radix/aria ones the previous implementation\n// styled against: the virtually-focused row is data-[focused] (was\n// aria-selected, which we set by hand) and a disabled row is data-[disabled]\n// (was aria-disabled). Rows share the menus' metrics: one line is exactly one\n// control height, so ten rows still fill the list, and a wrapped label grows\n// its row with the same padding instead of overlapping the next (R4).\nconst itemStyles = `m-0 ${MENU_ROW} text-oprt-base font-medium text-oprt-text cursor-pointer list-none outline-hidden data-[focused]:border-oprt-border-focus data-[focused]:shadow-oprt-focus data-[focused]:bg-oprt-surface-3 data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed ${FORCED_ACTIVE_ROW}`;\n\n/** What a click can land on that takes focus: the control a launcher click names as the opener. */\nconst FOCUSABLE =\n \"button, a[href], input, select, textarea, summary, [tabindex], [contenteditable]:not([contenteditable='false'])\";\n\n/**\n * A run of consecutive commands sharing the same group, mirroring the previous\n * implementation's \"emit a header whenever `group` changes\" behavior. Runs\n * with a group become a RAC ListBoxSection; ungrouped runs are bare items.\n */\ntype Run = { key: string; group: string | undefined; items: Command[] };\n\n/**\n * Scrolls the list, never the page behind the modal, by the least distance\n * that shows `row` in full. A group's first enabled command brings the group's\n * top (its header) with it, so wrapping to the start never strands the label\n * above the fold.\n */\nfunction reveal(list: HTMLElement, row: HTMLElement) {\n const group = row.closest('[role=\"group\"]');\n const first = group?.querySelector('[role=\"option\"]:not([aria-disabled=\"true\"])');\n const lead = group && list.contains(group) && first === row ? group : row;\n const viewTop = list.getBoundingClientRect().top + list.clientTop;\n const top = lead.getBoundingClientRect().top - viewTop;\n const bottom = row.getBoundingClientRect().bottom - viewTop;\n if (top < 0) list.scrollTop += top;\n else if (bottom > list.clientHeight) list.scrollTop += bottom - list.clientHeight;\n}\n\n/** `value`, once it has stopped changing for `delay` ms: an announcement that waits out typing. */\nfunction useSettled<T>(value: T, delay: number): T {\n const [settled, setSettled] = useState(value);\n useEffect(() => {\n const timer = setTimeout(() => setSettled(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n return settled;\n}\n\n/**\n * React Aria returns focus to whatever held it when the palette opened. Safari does not focus a\n * button on click, so for a launcher button that is the page, and focus would be lost on close.\n * The click that opened the palette names the opener; it gets focus back only when focus would\n * otherwise stay on the page.\n */\nfunction useReturnFocusToOpener(isOpen: boolean) {\n const clicked = useRef<HTMLElement | null>(null);\n const opener = useRef<HTMLElement | null>(null);\n useEffect(() => {\n const record = (event: MouseEvent) => {\n const target =\n event.target instanceof Element ? event.target.closest<HTMLElement>(FOCUSABLE) : null;\n clicked.current = target;\n // Only a click in the task that opens the palette names its opener.\n setTimeout(() => {\n if (clicked.current === target) clicked.current = null;\n }, 0);\n };\n document.addEventListener(\"click\", record, true);\n return () => document.removeEventListener(\"click\", record, true);\n }, []);\n useIsomorphicLayoutEffect(() => {\n if (!isOpen) return;\n opener.current = clicked.current;\n return () => {\n const target = opener.current;\n opener.current = null;\n if (!target) return;\n // After React Aria's own attempt, which also waits a frame.\n requestAnimationFrame(() => {\n const active = document.activeElement;\n if (target.isConnected && (active === null || active === document.body))\n target.focus({ preventScroll: true });\n });\n };\n }, [isOpen]);\n}\n\n/**\n * The search field, the command list and the status that announces an empty result. It mounts\n * with the open palette, so each opening starts from an empty query.\n */\nfunction PaletteContent({\n commands,\n placeholder,\n emptyMessage,\n onRun,\n}: {\n commands: Command[];\n placeholder: string;\n emptyMessage: string;\n onRun: (command: Command) => void;\n}) {\n const [query, setQuery] = useState(\"\");\n\n const byId = useMemo(() => {\n const map = new Map<string, Command>();\n for (const cmd of commands) map.set(cmd.id, cmd);\n return map;\n }, [commands]);\n\n const hasGroups = useMemo(\n () => commands.some((cmd) => cmd.group !== undefined && cmd.group !== \"\"),\n [commands],\n );\n\n const runs = useMemo(() => {\n const out: Run[] = [];\n for (const cmd of commands) {\n const group =\n hasGroups && cmd.group !== undefined && cmd.group !== \"\" ? cmd.group : undefined;\n const last = out[out.length - 1];\n if (last && last.group === group) last.items.push(cmd);\n else out.push({ key: `${group ?? \"\"}::${cmd.id}`, group, items: [cmd] });\n }\n return out;\n }, [commands, hasGroups]);\n\n const disabledKeys = useMemo(\n () => commands.filter((cmd) => cmd.disabled).map((cmd) => cmd.id),\n [commands],\n );\n\n // RAC's Autocomplete filters the ListBox collection with this; the previous\n // implementation hand-rolled both. `contains` is locale-aware and\n // case-insensitive at sensitivity \"base\", matching the old\n // toLowerCase().includes() behavior. Group names stay searchable, so the\n // filter falls back to the command's group when the label does not match.\n const { contains } = useFilter({ sensitivity: \"base\" });\n const filter = useCallback(\n (textValue: string, inputValue: string, node: { key: Key }) => {\n if (inputValue === \"\") return true;\n if (contains(textValue, inputValue)) return true;\n const group = byId.get(String(node.key))?.group;\n return group !== undefined && group !== \"\" ? contains(group, inputValue) : false;\n },\n [contains, byId],\n );\n\n // The list shows the empty message; a status region says it too, once typing pauses (R2).\n const noMatch =\n query !== \"\" && !commands.some((cmd) => filter(cmd.label, query, { key: cmd.id }));\n const announcement = useSettled(noMatch ? emptyMessage : \"\", 300);\n\n const onAction = useCallback(\n (key: Key) => {\n const cmd = byId.get(String(key));\n if (cmd && !cmd.disabled) onRun(cmd);\n },\n [byId, onRun],\n );\n\n // React Aria scrolls the virtually focused row into view from a\n // requestAnimationFrame callback: a frame late, and not at all while frames\n // are stalled (a throttled tab, a frame-stepped capture). Follow the\n // data-[focused] row as soon as React commits it instead. Attribute changes\n // move the active row; child changes (filtering) move it within the list.\n const listObserver = useRef<MutationObserver | null>(null);\n const listRef = useCallback((list: HTMLDivElement | null) => {\n listObserver.current?.disconnect();\n listObserver.current = null;\n if (!list) return;\n const observer = new MutationObserver(() => {\n const row = list.querySelector<HTMLElement>('[role=\"option\"][data-focused]');\n if (row) reveal(list, row);\n });\n observer.observe(list, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [\"data-focused\"],\n });\n listObserver.current = observer;\n }, []);\n\n const renderItem = (cmd: Command) => (\n <ListBoxItem key={cmd.id} id={cmd.id} textValue={cmd.label} className={itemStyles}>\n <span className=\"flex min-w-0 items-center gap-2u\">\n {cmd.icon && (\n <span\n aria-hidden=\"true\"\n className=\"shrink-0 text-oprt-text-dim forced-colors:[color:inherit]\"\n >\n {cmd.icon}\n </span>\n )}\n <span className=\"min-w-0 uppercase tracking-oprt-caps oprt-wrap-anywhere\">{cmd.label}</span>\n </span>\n {cmd.shortcut && <span className={MENU_ITEM_SHORTCUT}>{cmd.shortcut}</span>}\n </ListBoxItem>\n );\n\n return (\n <>\n <Autocomplete filter={filter} inputValue={query} onInputChange={setQuery}>\n <div className=\"flex shrink-0 items-center gap-2u px-3u border border-transparent border-b-oprt-border focus-within:border-oprt-border-focus focus-within:shadow-oprt-focus\">\n <span aria-hidden=\"true\" className=\"text-oprt-text-dim\">\n <Search size={14} />\n </span>\n <TextField aria-label=\"Command Palette\" className=\"w-full\">\n <Input\n autoFocus\n // The Autocomplete wires up aria-controls and\n // aria-activedescendant; role/aria-expanded preserve the\n // combobox contract the Radix version exposed.\n role=\"combobox\"\n aria-expanded=\"true\"\n placeholder={placeholder}\n className=\"w-full bg-transparent h-oprt-lg text-oprt-md normal-case text-oprt-text font-sans font-medium outline-hidden placeholder:text-oprt-text-placeholder\"\n />\n </TextField>\n </div>\n\n <ListBox\n ref={listRef}\n aria-label=\"Commands\"\n selectionMode=\"none\"\n // autoFocus makes the first row active as soon as the palette\n // opens (RAC otherwise waits for a keypress); shouldFocusWrap\n // preserves the previous modulo arrow-key wrap-around.\n autoFocus=\"first\"\n shouldFocusWrap\n disabledKeys={disabledKeys}\n onAction={onAction}\n // Ten rows, then scroll; on a short screen the list shrinks below\n // that (min-h-0) so the palette still fits and the list scrolls.\n className=\"m-0 min-h-0 p-0 list-none max-h-40u overflow-auto outline-hidden\"\n renderEmptyState={() => <div className={`${MENU_EMPTY} list-none`}>{emptyMessage}</div>}\n >\n {runs.map((run) =>\n run.group !== undefined ? (\n <ListBoxSection key={run.key} id={run.key}>\n <Header className=\"m-0 px-3u py-1u text-oprt-sm uppercase tracking-oprt-sm text-oprt-text-dim list-none oprt-wrap-anywhere\">\n {run.group}\n </Header>\n {run.items.map(renderItem)}\n </ListBoxSection>\n ) : (\n run.items.map(renderItem)\n ),\n )}\n </ListBox>\n </Autocomplete>\n <div role=\"status\" className=\"sr-only\">\n {announcement}\n </div>\n </>\n );\n}\n\n/**\n * The forwarded ref receives the `<section role=\"dialog\">` React Aria renders, so it is typed\n * HTMLElement (it was HTMLDivElement, which never matched; object and callback refs typed for a\n * div are still accepted).\n */\nexport const CommandPalette = forwardRef<HTMLElement, CommandPaletteProps>(\n (\n {\n open,\n defaultOpen,\n onOpenChange,\n commands,\n placeholder = \"Type a command…\",\n emptyMessage = \"No results\",\n className,\n },\n ref,\n ) => {\n // Internal open state mirrors the overlay so we can close on selection in\n // either controlled or uncontrolled mode (unchanged from the Radix version).\n const [internalOpen, setInternalOpen] = useState(defaultOpen ?? false);\n const isControlled = open !== undefined;\n const isOpen = isControlled ? open : internalOpen;\n useReturnFocusToOpener(isOpen);\n\n const setOpen = useCallback(\n (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n },\n [isControlled, onOpenChange],\n );\n\n const onRun = useCallback(\n (cmd: Command) => {\n cmd.onSelect();\n setOpen(false);\n },\n [setOpen],\n );\n\n return (\n // The overlay is as tall as the visible viewport React Aria measures (above an on-screen\n // keyboard too) and keeps a margin on every side, so the palette never touches the edges.\n <ModalOverlay\n isOpen={isOpen}\n onOpenChange={setOpen}\n isDismissable\n className=\"fixed inset-x-0 top-0 z-40 flex h-[var(--visual-viewport-height,100dvh)] flex-col items-center bg-oprt-bg/80 p-2u\"\n >\n {/* Modal owns the overlay lifecycle but must not introduce a box of its\n own: display:contents lays the spacer and the panel out in the overlay. */}\n <Modal className=\"contents\">\n {/* The palette's 15% top offset, as a spacer that shrinks first on a short screen\n (then the list does). Clicks pass through it to the overlay, which dismisses. */}\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none shrink-[999] basis-[calc(15dvh-var(--oprt-space-2))]\"\n />\n {/* RAC's Dialog renders a <section role=\"dialog\"> where Radix rendered\n a <div role=\"dialog\">; the forwarded ref lands on it. */}\n <Dialog\n ref={ref}\n className={clsx(\n \"relative z-50 flex min-h-0 w-full max-w-lg flex-col bg-oprt-surface-1 border border-oprt-border font-sans focus:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus\",\n className,\n )}\n >\n <Heading slot=\"title\" className=\"sr-only\">\n Command Palette\n </Heading>\n <PaletteContent\n commands={commands}\n placeholder={placeholder}\n emptyMessage={emptyMessage}\n onRun={onRun}\n />\n </Dialog>\n </Modal>\n </ModalOverlay>\n );\n },\n);\nCommandPalette.displayName = \"CommandPalette\";\n","CommandPalette/index.ts":"export { CommandPalette, type CommandPaletteProps, type Command } from \"./CommandPalette\";\nexport { CommandPaletteManifest } from \"./CommandPalette.manifest\";\n","ConfirmDialog/ConfirmDialog.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ConfirmDialogManifest: Record<string, Component> = {\n ConfirmDialog: {\n description:\n \"Confirmation modal for destructive or high-consequence actions. Composes Dialog + Button (+ Field + Input for typed confirmation). Flat API: pass title, optional description, onConfirm, and an optional confirmPhrase the user must type exactly to enable Confirm.\",\n props: {\n title: { type: \"string\", required: true },\n description: { type: \"string\" },\n confirmLabel: { type: \"string\", default: \"CONFIRM\" },\n cancelLabel: { type: \"string\", default: \"CANCEL\" },\n confirmVariant: {\n type: \"enum\",\n values: [\"primary\", \"danger\"],\n default: \"primary\",\n },\n confirmPhrase: { type: \"string\" },\n onConfirm: { type: \"function\", required: true },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n open: { type: \"boolean\" },\n defaultOpen: { type: \"boolean\" },\n onOpenChange: { type: \"function\" },\n },\n slots: {\n trigger: { type: \"focusable element | text (text renders a Button)\" },\n },\n composable_with: [\"Card\", \"Toolbar\"],\n never_inside: [\"Dialog\", \"ConfirmDialog\"],\n example:\n '<ConfirmDialog\\n title=\"DELETE UNIT\"\\n description=\"This permanently removes UNIT-07 from the roster.\"\\n confirmPhrase=\"UNIT-07\"\\n confirmVariant=\"danger\"\\n confirmLabel=\"DELETE\"\\n trigger={<Button variant=\"danger\">DELETE</Button>}\\n onConfirm={() => deleteUnit(\"UNIT-07\")}\\n/>',\n },\n};\n","ConfirmDialog/ConfirmDialog.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useState, type ReactNode } from \"react\";\nimport { Dialog } from \"../Dialog/Dialog\";\nimport { Button } from \"../Button/Button\";\nimport { Input } from \"../Input/Input\";\nimport { Field } from \"../Field/Field\";\nimport { ConfirmDialogManifest } from \"./ConfirmDialog.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\nexport type ConfirmDialogProps = {\n trigger?: ReactNode;\n title: string;\n description?: string;\n confirmLabel?: string;\n cancelLabel?: string;\n confirmVariant?: \"primary\" | \"danger\";\n /** When set, the user must type this phrase exactly to enable Confirm. */\n confirmPhrase?: string;\n onConfirm: () => void;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n size?: \"sm\" | \"md\" | \"lg\";\n className?: string;\n};\n\n/** The ref receives Dialog's `<section role=\"dialog\">`, so it is typed HTMLElement. */\nexport const ConfirmDialog = forwardRef<HTMLElement, ConfirmDialogProps>((props, ref) => {\n const {\n trigger,\n title,\n description,\n confirmLabel = \"CONFIRM\",\n cancelLabel = \"CANCEL\",\n confirmVariant = \"primary\",\n confirmPhrase,\n onConfirm,\n open,\n defaultOpen,\n onOpenChange,\n size,\n className,\n } = props;\n // Controlled when `open` is provided; otherwise manage internal state.\n const isControlled = open !== undefined;\n const [internalOpen, setInternalOpen] = useState<boolean>(defaultOpen ?? false);\n const resolvedOpen = isControlled ? open : internalOpen;\n\n const setOpen = (next: boolean) => {\n if (!isControlled) setInternalOpen(next);\n onOpenChange?.(next);\n };\n\n // Typed-confirmation state. Reset to \"\" whenever the dialog closes so a\n // re-open always starts from an empty, disabled-Confirm state.\n const [phrase, setPhrase] = useState(\"\");\n useEffect(() => {\n if (!resolvedOpen) setPhrase(\"\");\n }, [resolvedOpen]);\n\n const confirmDisabled = confirmPhrase !== undefined && phrase.trim() !== confirmPhrase;\n\n // exactOptionalPropertyTypes: only forward optional props that are defined,\n // so we never pass an explicit `undefined` to an optional Dialog prop.\n const optionalProps: {\n trigger?: ReactNode;\n description?: string;\n size?: \"sm\" | \"md\" | \"lg\";\n className?: string;\n } = {};\n if (trigger !== undefined) optionalProps.trigger = trigger;\n if (description !== undefined) optionalProps.description = description;\n if (size !== undefined) optionalProps.size = size;\n if (className !== undefined) optionalProps.className = className;\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"ConfirmDialog\", ConfirmDialogManifest.ConfirmDialog!.props, {\n ...props,\n confirmVariant,\n });\n }\n\n return (\n <Dialog\n ref={ref}\n title={title}\n open={resolvedOpen}\n onOpenChange={setOpen}\n {...optionalProps}\n footer={\n <>\n <Button variant=\"tertiary\" onClick={() => setOpen(false)}>\n {cancelLabel}\n </Button>\n <Button\n variant={confirmVariant}\n disabled={confirmDisabled}\n onClick={() => {\n onConfirm();\n setOpen(false);\n }}\n >\n {confirmLabel}\n </Button>\n </>\n }\n >\n {confirmPhrase !== undefined ? (\n // The Field's visible label names the input (WCAG 2.5.3).\n <Field label={`Type \"${confirmPhrase}\" to confirm`}>\n <Input value={phrase} onChange={(e) => setPhrase(e.target.value)} autoComplete=\"off\" />\n </Field>\n ) : null}\n </Dialog>\n );\n});\nConfirmDialog.displayName = \"ConfirmDialog\";\n","ConfirmDialog/index.ts":"export { ConfirmDialog, type ConfirmDialogProps } from \"./ConfirmDialog\";\nexport { ConfirmDialogManifest } from \"./ConfirmDialog.manifest\";\n","ContextMenu/ContextMenu.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ContextMenuManifest: Record<string, Component> = {\n ContextMenu: {\n description: \"Context actions with a focusable surface and an equivalent visible menu button.\",\n props: {\n label: {\n type: \"string\",\n required: true,\n description: \"Names the surface; the visible button is named `${label} actions`.\",\n },\n items: {\n type: \"array\",\n required: true,\n description:\n \"Stable unique id and readable label, with optional shortcut text, disabled and danger flags.\",\n },\n onAction: {\n type: \"function\",\n required: true,\n description:\n \"Receives the selected item ID. Both activation paths use the same supplied actions.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description:\n \"Disables both menu activation paths without disabling supplied child content.\",\n },\n emptyMessage: {\n type: \"string\",\n default: \"No actions available\",\n description: \"Non-interactive row the menu shows when items is empty.\",\n },\n className: { type: \"string\" },\n },\n slots: { children: { type: \"ReactNode\", required: true } },\n composable_with: [\"InlineEdit\", \"DataGrid\"],\n never_inside: [\"Button\"],\n example:\n '<ContextMenu label=\"Alpha relay\" items={[{id: \"inspect\", label: \"Inspect\"}]} onAction={handleAction}>EQ-01</ContextMenu>',\n },\n};\n","ContextMenu/ContextMenu.tsx":"\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Keyboard, Menu, MenuItem, MenuTrigger, Pressable, Text } from \"react-aria-components\";\nimport { usePress } from \"react-aria\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport { composeMotionHandlers, MotionContext, useMotion } from \"../_internal/motion\";\nimport { MotionPopover } from \"../_internal/MotionPopover\";\nimport {\n DESTRUCTIVE_ACTION,\n MENU_EMPTY,\n MENU_ITEM_DESCRIPTION,\n MENU_ITEM_LABEL,\n MENU_ITEM_SHORTCUT,\n MENU_ITEM_TEXT,\n MENU_OFFSET,\n MENU_SURFACE,\n menuItemStyles,\n SCREEN_MARGIN,\n} from \"../_internal/overlay\";\n\nconst surfaceStyles = cva(\n \"min-h-3u min-w-0 border border-transparent outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus\",\n);\n\n/** macOS, not iPadOS: its context-menu shortcut is Ctrl+Enter. */\nfunction isMac(): boolean {\n if (typeof navigator === \"undefined\") return false;\n const platform =\n (navigator as Navigator & { userAgentData?: { platform?: string } }).userAgentData?.platform ||\n navigator.platform;\n return /^mac/i.test(platform) && !(navigator.maxTouchPoints > 1);\n}\n\nfunction ContextSurface({\n label,\n disabled,\n children,\n}: {\n label: string;\n disabled: boolean;\n children: ReactNode;\n}) {\n const surfaceRef = useRef<HTMLDivElement>(null);\n // Consume MenuTrigger's public press context, but apply only its context-menu\n // handler. Pressable would turn this group into a press target and consume\n // Space inside descendant controls. Normal typing/activation stays native.\n const { pressProps } = usePress({ ref: surfaceRef, isDisabled: disabled });\n return (\n <div\n ref={surfaceRef}\n id={pressProps.id}\n role=\"group\"\n aria-label={label}\n tabIndex={disabled ? -1 : 0}\n className={surfaceStyles()}\n onContextMenu={\n disabled\n ? undefined\n : (event) => {\n // Native context clicks do not consistently focus a generic surface.\n // Establish the restoration target before RAC opens its focus scope.\n event.currentTarget.focus({ preventScroll: true });\n pressProps.onContextMenu?.(event);\n }\n }\n onKeyDown={(event) => {\n if (disabled || event.target !== event.currentTarget || event.defaultPrevented) return;\n const contextKey = event.key === \"ContextMenu\" || (event.shiftKey && event.key === \"F10\");\n // macOS's own shortcut, which some browsers do not turn into a contextmenu event.\n const macShortcut =\n event.ctrlKey && !event.metaKey && !event.altKey && event.key === \"Enter\" && isMac();\n if (!contextKey && !macShortcut) return;\n // Browsers differ on whether these keys synthesize contextmenu. Use the\n // same native event path as right click and prevent a second default one.\n event.preventDefault();\n const target = event.currentTarget;\n const rect = target.getBoundingClientRect();\n target.dispatchEvent(\n new MouseEvent(\"contextmenu\", {\n bubbles: true,\n cancelable: true,\n button: 2,\n clientX: rect.left,\n clientY: rect.bottom,\n }),\n );\n }}\n >\n {children}\n </div>\n );\n}\n\nexport interface ContextMenuItem {\n id: string;\n label: string;\n shortcut?: string;\n disabled?: boolean;\n danger?: boolean;\n}\nexport type ContextMenuProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n items: readonly ContextMenuItem[];\n onAction: (id: string) => void;\n disabled?: boolean;\n /** Shown as a non-interactive row when `items` is empty. */\n emptyMessage?: string;\n children: ReactNode;\n};\nexport const ContextMenu = forwardRef<HTMLDivElement, ContextMenuProps>(\n (\n {\n label,\n items,\n onAction,\n disabled = false,\n emptyMessage = \"No actions available\",\n children,\n className,\n ...props\n },\n ref,\n ) => {\n const [open, setOpen] = useState<\"surface\" | \"button\" | null>(null);\n const hydration = useHydrationRoot();\n // Both menus share DropdownMenu's motion (design call DC-6); ContextMenu has no motion prop,\n // so the system and reduced-motion settings alone decide.\n const motionState = useMotion(\"auto\", \"ContextMenu\");\n useEffect(() => {\n if (disabled) setOpen(null);\n }, [disabled]);\n const menu = () => (\n <MotionPopover\n motionState={motionState}\n offset={MENU_OFFSET}\n containerPadding={SCREEN_MARGIN}\n placement=\"bottom start\"\n className={MENU_SURFACE}\n >\n <Menu\n aria-label={`${label} actions`}\n aria-labelledby=\"\"\n className=\"outline-hidden\"\n items={items}\n renderEmptyState={() => <div className={MENU_EMPTY}>{emptyMessage}</div>}\n onAction={(key) => {\n const item = items.find((candidate) => candidate.id === key);\n if (!disabled && item && !item.disabled) onAction(item.id);\n }}\n >\n {(item) => (\n <MenuItem\n id={item.id}\n textValue={item.label}\n isDisabled={item.disabled ?? false}\n className={menuItemStyles({ danger: item.danger ?? false })}\n >\n <span className={MENU_ITEM_TEXT}>\n <Text slot=\"label\" className={MENU_ITEM_LABEL}>\n {item.label}\n </Text>\n {item.danger && (\n <Text slot=\"description\" className={MENU_ITEM_DESCRIPTION}>\n {DESTRUCTIVE_ACTION}\n </Text>\n )}\n </span>\n {item.shortcut && <Keyboard className={MENU_ITEM_SHORTCUT}>{item.shortcut}</Keyboard>}\n </MenuItem>\n )}\n </Menu>\n </MotionPopover>\n );\n return (\n <MotionContext.Provider value={motionState.mode}>\n <div\n {...props}\n {...composeMotionHandlers(motionState.props, props)}\n {...hydration.probeProps}\n ref={ref}\n className={clsx(\n \"flex min-w-0 flex-wrap items-center gap-1u font-sans text-oprt-base normal-case tracking-normal text-oprt-text\",\n className,\n )}\n >\n <MenuTrigger\n key={`${hydration.key}-surface`}\n trigger=\"contextMenu\"\n isOpen={!disabled && open === \"surface\"}\n onOpenChange={(next) => setOpen(next && !disabled ? \"surface\" : null)}\n >\n <ContextSurface label={label} disabled={disabled}>\n {children}\n </ContextSurface>\n {menu()}\n </MenuTrigger>\n <MenuTrigger\n key={`${hydration.key}-button`}\n isOpen={!disabled && open === \"button\"}\n onOpenChange={(next) => setOpen(next && !disabled ? \"button\" : null)}\n >\n {/* Looks like a small secondary Button, as menu triggers elsewhere do (DC-6). */}\n <Pressable isDisabled={disabled}>\n <Button\n variant=\"secondary\"\n size=\"sm\"\n disabled={disabled}\n aria-label={`${label} actions`}\n >\n Actions\n </Button>\n </Pressable>\n {menu()}\n </MenuTrigger>\n </div>\n </MotionContext.Provider>\n );\n },\n);\nContextMenu.displayName = \"ContextMenu\";\n","ContextMenu/index.ts":"export { ContextMenu, type ContextMenuProps, type ContextMenuItem } from \"./ContextMenu\";\nexport { ContextMenuManifest } from \"./ContextMenu.manifest\";\n","CoordinateInput/CoordinateInput.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const CoordinateInputManifest: Record<string, Component> = {\n CoordinateInput: {\n description:\n \"Decimal-degree latitude and longitude fields with bounded validation and explicit unknown or invalid position states.\",\n props: {\n label: { type: \"string\", default: \"Coordinates\" },\n value: {\n type: \"node\",\n description:\n \"Controlled { latitude: number | null, longitude: number | null }. Latitude is -90…90; longitude is -180…180, inclusive. Null axes are unknown.\",\n },\n defaultValue: {\n type: \"node\",\n description: \"Initial uncontrolled coordinates. Defaults to both axes null.\",\n },\n onValueChange: {\n type: \"function\",\n description:\n \"(position: CoordinateValue) => void. Only valid axis edits or a cleared null axis are emitted; the other axis is preserved. Does not normalize or wrap out-of-range input.\",\n },\n onValidationChange: {\n type: \"function\",\n description:\n \"(invalid: boolean) => void. Reports aggregate invalid state initially and on change, including local typed drafts, supplied values and error. Missing/null axes are unknown rather than invalid; callers separately require both axes before Go-to or submission.\",\n },\n disabled: { type: \"boolean\", default: false },\n description: { type: \"string\" },\n error: { type: \"string\" },\n latitudeName: { type: \"string\", description: \"Native form field name for latitude.\" },\n longitudeName: { type: \"string\", description: \"Native form field name for longitude.\" },\n },\n composable_with: [\"Card\", \"NumberInput\"],\n never_inside: [\"Button\"],\n example:\n '<CoordinateInput label=\"Observation position\" value={position} onValueChange={setPosition} />',\n },\n};\n","CoordinateInput/CoordinateInput.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useRef, useState, type FieldsetHTMLAttributes } from \"react\";\nimport clsx from \"clsx\";\nimport { NumberInput } from \"../NumberInput/NumberInput\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\nexport type CoordinateValue = { latitude: number | null; longitude: number | null };\nexport type CoordinateInputProps = Omit<\n FieldsetHTMLAttributes<HTMLFieldSetElement>,\n \"onChange\" | \"children\" | \"value\" | \"defaultValue\"\n> & {\n label?: string;\n value?: CoordinateValue;\n defaultValue?: CoordinateValue;\n onValueChange?: (value: CoordinateValue) => void;\n onValidationChange?: (invalid: boolean) => void;\n description?: string;\n error?: string;\n latitudeName?: string;\n longitudeName?: string;\n};\nconst unknown: CoordinateValue = { latitude: null, longitude: null };\nexport const CoordinateInput = forwardRef<HTMLFieldSetElement, CoordinateInputProps>(\n (\n {\n label = \"Coordinates\",\n value,\n defaultValue = unknown,\n onValueChange,\n onValidationChange,\n description,\n error,\n latitudeName,\n longitudeName,\n disabled = false,\n className,\n \"aria-describedby\": describedBy,\n ...props\n },\n ref,\n ) => {\n // The status, description and error ids are minted with the shared usePartIds and reconciled\n // with the page through the fieldset's data-oprt-ids, so a status or error that changes after a\n // hydration whose ids disagree with the server's still describes the group (CoordinateInput-R2).\n const fieldset = useRef<HTMLFieldSetElement | null>(null);\n const fieldsetRef = useMergedRefs<HTMLFieldSetElement>(fieldset, ref);\n const ids = usePartIds([\"status\", \"description\", \"error\"], {\n readRendered: () => fieldset.current?.getAttribute(\"data-oprt-ids\"),\n });\n const [local, setLocal] = useState(defaultValue);\n const [invalidAxes, setInvalidAxes] = useState({ latitude: false, longitude: false });\n const position = value ?? local;\n const invalid =\n Boolean(error) ||\n invalidAxes.latitude ||\n invalidAxes.longitude ||\n (position.latitude !== null &&\n (!Number.isFinite(position.latitude) || Math.abs(position.latitude) > 90)) ||\n (position.longitude !== null &&\n (!Number.isFinite(position.longitude) || Math.abs(position.longitude) > 180));\n const validationRef = useRef(onValidationChange);\n validationRef.current = onValidationChange;\n useEffect(() => {\n validationRef.current?.(invalid);\n }, [invalid]);\n const status = invalid\n ? \"Position invalid\"\n : position.latitude === null || position.longitude === null\n ? \"Position unknown\"\n : \"Decimal degrees\";\n const change = (axis: keyof CoordinateValue, next: number | null) => {\n if (disabled) return;\n const nextPosition = { ...position, [axis]: next };\n if (value === undefined) setLocal(nextPosition);\n onValueChange?.(nextPosition);\n };\n return (\n <fieldset\n {...props}\n ref={fieldsetRef}\n data-oprt-ids={ids.base}\n disabled={disabled}\n aria-invalid={invalid || undefined}\n aria-describedby={joinIds(\n describedBy,\n ids.status,\n description && ids.description,\n error && ids.error,\n )}\n className={clsx(\n \"m-0 min-w-0 border p-2u font-sans text-oprt-base normal-case text-oprt-text\",\n invalid ? \"border-oprt-error\" : \"border-oprt-border\",\n className,\n )}\n >\n {/* A field label in its authored case, styled as Field's (NumberInput-R5); the legend,\n description, status and message wrap and long identifiers break anywhere\n (CoordinateInput-B2M-1..3). */}\n <legend className=\"min-w-0 px-1u oprt-wrap-anywhere text-oprt-base font-medium text-oprt-text\">\n {label}\n </legend>\n {description && (\n <p\n id={ids.description}\n className=\"m-0 mb-1u min-w-0 oprt-wrap-anywhere text-oprt-text-dim\"\n >\n {description}\n </p>\n )}\n {/* Latitude and longitude sit side by side only when the group itself has room for two\n fields of 12rem, and stack otherwise, by its own width and never the window's (R1). An\n intrinsic grid needs no container query, whose inline-size containment would collapse\n the group in a shrink-to-fit host. */}\n <div className=\"grid grid-cols-[repeat(auto-fit,minmax(min(100%,12rem),1fr))] gap-2u\">\n <NumberInput\n label=\"Latitude\"\n unit=\"°\"\n value={position.latitude}\n onValueChange={(next) => change(\"latitude\", next)}\n onValidationChange={(validation) =>\n setInvalidAxes((axes) =>\n axes.latitude === Boolean(validation)\n ? axes\n : { ...axes, latitude: Boolean(validation) },\n )\n }\n min={-90}\n max={90}\n disabled={disabled}\n {...(latitudeName !== undefined ? { name: latitudeName } : {})}\n />\n <NumberInput\n label=\"Longitude\"\n unit=\"°\"\n value={position.longitude}\n onValueChange={(next) => change(\"longitude\", next)}\n onValidationChange={(validation) =>\n setInvalidAxes((axes) =>\n axes.longitude === Boolean(validation)\n ? axes\n : { ...axes, longitude: Boolean(validation) },\n )\n }\n min={-180}\n max={180}\n disabled={disabled}\n {...(longitudeName !== undefined ? { name: longitudeName } : {})}\n />\n </div>\n <p\n id={ids.status}\n role=\"status\"\n className={clsx(\n \"m-0 mt-1u min-w-0 oprt-wrap-anywhere\",\n invalid ? \"text-oprt-error\" : \"text-oprt-text-dim\",\n )}\n >\n {status}\n </p>\n {error && (\n // A caller's error is an alert, as in a Field (MultiSelect-R2).\n <p\n id={ids.error}\n role=\"alert\"\n className=\"m-0 mt-1u min-w-0 oprt-wrap-anywhere text-oprt-error\"\n >\n {error}\n </p>\n )}\n </fieldset>\n );\n },\n);\nCoordinateInput.displayName = \"CoordinateInput\";\n","CoordinateInput/index.ts":"export {\n CoordinateInput,\n type CoordinateInputProps,\n type CoordinateValue,\n} from \"./CoordinateInput\";\nexport { CoordinateInputManifest } from \"./CoordinateInput.manifest\";\n","CopyButton/CopyButton.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const CopyButtonManifest: Record<string, Component> = {\n CopyButton: {\n description:\n \"Copies a supplied exact string with visible pending, success and failure feedback.\",\n props: {\n value: {\n type: \"string\",\n required: true,\n description: \"Exact string to copy; whitespace and precision are preserved.\",\n },\n label: {\n type: \"string\",\n required: true,\n description:\n 'Accessible action name identifying the value. Feedback reads this label, then the outcome: \"Copy equipment ID: copied\".',\n },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n disabled: { type: \"boolean\", default: false },\n className: { type: \"string\", description: \"Classes applied to the native action button.\" },\n onCopy: {\n type: \"function\",\n description:\n \"Receives { value } after the requested write resolves. Suppressed after context changes or unmount.\",\n },\n onCopyError: {\n type: \"function\",\n description:\n \"Receives { value, error } if writing fails or is unavailable. Suppressed after context changes or unmount.\",\n },\n },\n composable_with: [\"DescriptionList\", \"EntityPreview\", \"Popover\", \"Inspector\", \"Toolbar\"],\n never_inside: [\"Button\", \"Link\"],\n example: '<CopyButton value={equipment.id} label=\"Copy equipment ID\" size=\"sm\" />',\n },\n};\n","CopyButton/CopyButton.tsx":"\"use client\";\n\nimport { forwardRef, useRef, useState } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport { Copy } from \"@oprt/icons\";\nimport { Button, type ButtonProps } from \"../Button/Button\";\nimport { CopyButtonManifest } from \"./CopyButton.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { useIsMounted } from \"../_internal/useIsMounted\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\nconst copyStyles = cva(\"inline-flex min-w-0 flex-wrap items-center gap-1u\");\n/** The Copy icon at the size other Buttons with an icon use: it fits the one-line content box. */\nconst iconSize = { sm: 12, md: 16, lg: 20 } as const;\nconst feedbackStyles = cva(\"min-w-0 break-words font-sans text-oprt-base normal-case\", {\n variants: {\n state: {\n pending: \"text-oprt-text-dim\",\n success: \"text-oprt-success\",\n error: \"text-oprt-error\",\n },\n },\n});\n\nexport type CopyResult = { value: string };\nexport type CopyError = { value: string; error: unknown };\nexport type CopyButtonProps = {\n /** Exact string passed to clipboard.writeText, including whitespace. */\n value: string;\n /** Accessible action name identifying the supplied value. */\n label: string;\n onCopy?: (result: CopyResult) => void;\n onCopyError?: (result: CopyError) => void;\n disabled?: boolean;\n size?: ButtonProps[\"size\"];\n /** Classes applied to the native action button. */\n className?: string;\n};\ntype Feedback = {\n value: string;\n label: string;\n state: \"pending\" | \"success\" | \"error\";\n};\n\n/** Copies only on activation; clipboard outcomes remain tied to the requested context. */\nexport const CopyButton = forwardRef<HTMLButtonElement, CopyButtonProps>((allProps, ref) => {\n const { value, label, onCopy, onCopyError, disabled = false, size = \"md\", className } = allProps;\n const [pending, setPending] = useState(false);\n const [feedback, setFeedback] = useState<Feedback | null>(null);\n const pendingRef = useRef(false);\n const isMounted = useIsMounted();\n const context = useRef({ value, label, revision: 0 });\n\n if (process.env.NODE_ENV !== \"production\") {\n // The inner Button would report a bad size as \"Button\", which names the wrong component.\n devValidate(\"CopyButton\", CopyButtonManifest.CopyButton!.props, { ...allProps });\n }\n\n // Publish only committed context before promise continuations can run.\n useIsomorphicLayoutEffect(() => {\n if (context.current.value !== value || context.current.label !== label) {\n context.current = { value, label, revision: context.current.revision + 1 };\n setFeedback(null);\n }\n }, [value, label]);\n\n async function copy() {\n if (disabled || pendingRef.current) return;\n pendingRef.current = true;\n setPending(true);\n const request = { value, label, revision: context.current.revision };\n setFeedback({ value, label, state: \"pending\" });\n let outcome: { state: \"success\" } | { state: \"error\"; error: unknown };\n try {\n // Browser support is read only inside this explicit interaction path.\n const clipboard = navigator.clipboard;\n if (typeof clipboard?.writeText !== \"function\") {\n throw new Error(\"Clipboard writing is unavailable.\");\n }\n await clipboard.writeText(request.value);\n outcome = { state: \"success\" };\n } catch (error) {\n outcome = { state: \"error\", error };\n }\n\n pendingRef.current = false;\n if (!isMounted()) return;\n setPending(false);\n if (\n context.current.revision !== request.revision ||\n context.current.value !== request.value ||\n context.current.label !== request.label\n )\n return;\n\n setFeedback({ value: request.value, label: request.label, state: outcome.state });\n try {\n if (outcome.state === \"success\") onCopy?.({ value: request.value });\n else onCopyError?.({ value: request.value, error: outcome.error });\n } catch {\n // Consumer callback errors cannot change or reclassify the clipboard outcome.\n }\n }\n\n const visibleFeedback = feedback?.value === value && feedback.label === label ? feedback : null;\n return (\n <span className={copyStyles()}>\n <Button\n ref={ref}\n variant=\"tertiary\"\n size={size}\n className={className}\n aria-label={label}\n aria-disabled={disabled || pending}\n aria-busy={pending}\n disabled={disabled}\n onClick={() => {\n void copy();\n }}\n >\n <Copy aria-hidden=\"true\" size={iconSize[size ?? \"md\"]} />\n Copy\n </Button>\n <span\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className={feedbackStyles({ state: visibleFeedback?.state })}\n >\n {/* The label first, then the outcome: it reads correctly with or without a leading verb. */}\n {visibleFeedback?.state === \"pending\" && `${visibleFeedback.label}: copying…`}\n {visibleFeedback?.state === \"success\" && `${visibleFeedback.label}: copied`}\n {visibleFeedback?.state === \"error\" &&\n `${visibleFeedback.label}: failed. Select the value and copy it manually.`}\n </span>\n </span>\n );\n});\nCopyButton.displayName = \"CopyButton\";\n","CopyButton/index.ts":"export { CopyButton, type CopyButtonProps, type CopyResult, type CopyError } from \"./CopyButton\";\nexport { CopyButtonManifest } from \"./CopyButton.manifest\";\n","DataGrid/DataGrid.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DataGridManifest: Record<string, Component> = {\n DataGrid: {\n description:\n \"Native table with stable-ID selection, sorting, controlled column widths and pinning, saved-view state, pagination, and optional virtualization.\",\n props: {\n columns: {\n type: \"array\",\n required: true,\n description:\n \"DataTableColumn descriptors plus render(value, originalRow) for display and compare(a, b) for raw-value sorting. compare must be pure; a new compare function sorts again, so define columns outside render or memoize them. Columns sort unless sortable is false. Keys must be unique.\",\n items: {\n key: \"string (required, unique)\",\n label: \"string (required)\",\n as: \"text | number | code | timestamp | badge\",\n highlight: \"boolean (default: false)\",\n width: \"string (CSS width; a px value seeds the column width)\",\n align: \"left | right\",\n sortable: \"boolean\",\n render: \"(value, row) => ReactNode\",\n compare: \"(a, b) => number\",\n minWidth: \"number (CSS pixels; default 96; minimum 48)\",\n maxWidth: \"number (CSS pixels; default 640)\",\n },\n },\n data: {\n type: \"array\",\n required: true,\n description:\n \"Client mode: complete filtered row set. Server mode: current page only; order and row count are preserved.\",\n },\n getRowId: {\n type: \"function\",\n required: true,\n description:\n \"(originalRow) => string. Return a unique, stable ID across filtering, sorting, and pagination; do not use an array index. Keep it pure: IDs are read again only when data changes, not when only the function does.\",\n },\n caption: {\n type: \"string\",\n default: \"Data grid\",\n description: \"Accessible native table caption and controls group name.\",\n },\n selectedRowIds: {\n type: \"array\",\n description:\n \"Controlled IDs. Supplying this or onSelectionChange enables selection. IDs outside the displayed data are preserved.\",\n },\n onSelectionChange: {\n type: \"function\",\n description:\n \"(ids: string[]) => void. Select page changes only current-page IDs. Without selectedRowIds, selection is stored internally.\",\n },\n sort: {\n type: \"node\",\n description:\n \"Controlled { key: string, direction: 'asc' | 'desc' } or null for source order. Omit for internal sorting state. Uses raw values before custom rendering and announces the active direction with aria-sort on the corresponding table header.\",\n },\n onSortChange: {\n type: \"function\",\n description:\n \"(sort: DataGridSort | null) => void. Server mode only reports the requested sort; the caller supplies sorted rows.\",\n },\n page: {\n type: \"number\",\n default: 1,\n description:\n \"One-based controlled page. Omit for internal pagination state. Out-of-range values are clamped and reported once through onPageChange.\",\n },\n onPageChange: {\n type: \"function\",\n description:\n \"(page: number) => void. A supplied page remains controlled until its owner updates it.\",\n },\n pageSize: {\n type: \"number\",\n default: 10,\n description:\n \"Rows per client page and denominator for server page counts. Invalid or less-than-one values use 10; fractional values round down.\",\n },\n mode: { type: \"enum\", values: [\"client\", \"server\"], default: \"client\" },\n rowCount: {\n type: \"number\",\n description:\n \"Total filtered row count in server mode. Defaults to data.length; provide the total to enable navigation beyond the supplied page.\",\n },\n visibleColumnKeys: {\n type: \"array\",\n description:\n \"Controlled displayed column keys. Omit for internal visibility state; all columns are visible initially. An empty list shows a recovery message.\",\n },\n onVisibleColumnsChange: {\n type: \"function\",\n description:\n \"(keys: string[]) => void. Receives visibility changes from the COLUMNS controls.\",\n },\n columnWidths: {\n type: \"node\",\n description:\n \"Controlled Record<columnKey, CSS pixel width>. Omit for internal widths. Values are clamped to each column's minWidth/maxWidth; default width is 160 or a pixel width from the descriptor. Stored keys outside the current columns are preserved in resize callbacks.\",\n },\n onColumnWidthsChange: {\n type: \"function\",\n description:\n \"(widths: DataGridColumnWidths) => void. Columns controls support direct numeric entry, Arrow keys (8 pixels), and Home/End (bounds).\",\n },\n pinnedColumnKeys: {\n type: \"array\",\n description:\n \"Controlled leading-edge pinned keys in display order. Omit for internal state. Hidden pinned columns retain their preference; unknown keys are ignored for display.\",\n },\n onPinnedColumnsChange: {\n type: \"function\",\n description:\n \"(keys: string[]) => void. Called by keyboard-accessible Pin checkboxes in Columns.\",\n },\n virtualization: {\n type: \"node\",\n description:\n \"Opt-in { height: number, rowHeight?: number, estimateRowHeight?: number, overscan?: number }, CSS pixels. Fixed rowHeight clips overlong cell contents; omit rowHeight to measure wrapped rows with ResizeObserver, starting at estimateRowHeight (default 40). Minimum row height 24; default overscan 3, minimum 1. Client mode windows all filtered rows; server mode windows only the supplied page.\",\n },\n viewMode: {\n type: \"enum\",\n values: [\"paginated\", \"virtualized\"],\n description:\n \"Controlled display mode. Virtualization must be supplied for virtualized mode. Defaults to virtualized with configuration, paginated without it. The built-in toggle always offers a paginated native-table alternative.\",\n },\n onViewModeChange: {\n type: \"function\",\n description:\n \"(mode: DataGridViewMode) => void. Controlled owners must apply the requested mode for the fallback toggle to update the table.\",\n },\n loading: {\n type: \"boolean\",\n default: false,\n description:\n \"Marks the table busy, announces loading, and disables grid-owned controls and row activation while retaining supplied data.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description:\n \"Disables grid-owned actions and row activation. Custom interactive cell content remains the caller's responsibility.\",\n },\n dense: { type: \"boolean\", default: false },\n emptyMessage: { type: \"string\", default: \"No results.\" },\n onRowClick: {\n type: \"function\",\n description:\n \"(originalRow, indexInSuppliedData) => void. Uses the existing DataTable's native first-cell button; independent cell controls do not activate the row.\",\n },\n },\n composable_with: [\"Card\", \"ComboBox\"],\n never_inside: [\"DataTable\", \"DataGrid\"],\n example:\n '<DataGrid\\n caption=\"Station telemetry\"\\n columns={[{ key: \"name\", label: \"STATION\" }, { key: \"latency\", label: \"LATENCY / MS\", as: \"number\" }]}\\n data={rows}\\n getRowId={(row) => String(row.id)}\\n selectedRowIds={selected}\\n onSelectionChange={setSelected}\\n pageSize={10}\\n/>',\n },\n};\n","DataGrid/DataGrid.tsx":"\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { Checkbox } from \"../Checkbox/Checkbox\";\nimport {\n DataTable,\n type DataTableColumn,\n type DataTableRow,\n type DataTableSortDescriptor,\n type DataTableVirtualization,\n} from \"../DataTable/DataTable\";\nimport { Select, SelectOption } from \"../Select/Select\";\nimport { sortedOrder } from \"../DataTable/sort\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { DataGridManifest } from \"./DataGrid.manifest\";\nimport { PageControls } from \"./PageControls\";\n\nexport type DataGridSort = DataTableSortDescriptor;\nexport type DataGridColumnWidths = Record<string, number>;\nexport type DataGridVirtualization = DataTableVirtualization;\nexport type DataGridViewMode = \"paginated\" | \"virtualized\";\nexport type DataGridColumn = DataTableColumn & {\n render?: (value: unknown, row: DataTableRow) => ReactNode;\n compare?: (a: unknown, b: unknown) => number;\n minWidth?: number;\n maxWidth?: number;\n};\nexport type DataGridProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"onSelect\"> & {\n columns: DataGridColumn[];\n data: DataTableRow[];\n getRowId: (row: DataTableRow) => string;\n caption?: string;\n selectedRowIds?: readonly string[];\n onSelectionChange?: (ids: string[]) => void;\n sort?: DataGridSort | null;\n onSortChange?: (sort: DataGridSort | null) => void;\n page?: number;\n onPageChange?: (page: number) => void;\n pageSize?: number;\n mode?: \"client\" | \"server\";\n rowCount?: number;\n visibleColumnKeys?: readonly string[];\n onVisibleColumnsChange?: (keys: string[]) => void;\n columnWidths?: DataGridColumnWidths;\n onColumnWidthsChange?: (widths: DataGridColumnWidths) => void;\n pinnedColumnKeys?: readonly string[];\n onPinnedColumnsChange?: (keys: string[]) => void;\n virtualization?: DataGridVirtualization;\n viewMode?: DataGridViewMode;\n onViewModeChange?: (mode: DataGridViewMode) => void;\n loading?: boolean;\n disabled?: boolean;\n dense?: boolean;\n emptyMessage?: string;\n onRowClick?: (row: DataTableRow, index: number) => void;\n};\n\nfunction ColumnWidthInput({\n value,\n min,\n max,\n disabled,\n label,\n onCommit,\n}: {\n value: number;\n min: number;\n max: number;\n disabled: boolean;\n label: string;\n onCommit: (value: number) => void;\n}) {\n const [draft, setDraft] = useState<{ text: string; base: number } | null>(null);\n useEffect(() => setDraft(null), [value]);\n const text = draft?.base === value ? draft.text : String(value);\n const commit = (requested: number) => {\n setDraft(null);\n if (!disabled && Number.isFinite(requested)) onCommit(Math.min(max, Math.max(min, requested)));\n };\n return (\n <input\n type=\"number\"\n aria-label={label}\n min={min}\n max={max}\n step={1}\n value={text}\n disabled={disabled}\n // A field: the control outline (DC-3), and disabled like the other fields, with dim text and\n // the same outline rather than a faded control.\n className=\"h-oprt-md w-12u border border-oprt-border-control bg-oprt-surface-2 px-1u font-mono text-oprt-base font-medium focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus disabled:cursor-not-allowed disabled:text-oprt-text-dim\"\n onChange={(event) => setDraft({ text: event.target.value, base: value })}\n onBlur={() => {\n if (draft?.base === value) commit(text.trim() === \"\" ? Number.NaN : Number(text));\n }}\n onKeyDown={(event) => {\n if (event.key === \"Escape\") {\n event.preventDefault();\n setDraft(null);\n return;\n }\n const base = text.trim() !== \"\" && Number.isFinite(Number(text)) ? Number(text) : value;\n const next =\n event.key === \"Enter\"\n ? text.trim() === \"\"\n ? Number.NaN\n : Number(text)\n : event.key === \"Home\"\n ? min\n : event.key === \"End\"\n ? max\n : event.key === \"ArrowUp\" || event.key === \"ArrowRight\"\n ? base + 8\n : event.key === \"ArrowDown\" || event.key === \"ArrowLeft\"\n ? base - 8\n : undefined;\n if (next !== undefined) {\n event.preventDefault();\n commit(next);\n }\n }}\n />\n );\n}\n\n/**\n * The selection without the IDs on the page. The page's set is built here, when it is wanted (a\n * click on the select-all box), not each time the rows change: a grid of 10,000 rows reads it once.\n */\nfunction withoutPage(selection: readonly string[], pageIds: readonly string[]): string[] {\n const onPage = new Set(pageIds);\n return selection.filter((id) => !onPage.has(id));\n}\n\nexport const DataGrid = forwardRef<HTMLDivElement, DataGridProps>((allProps, ref) => {\n const {\n columns,\n data,\n getRowId,\n caption = \"Data grid\",\n selectedRowIds,\n onSelectionChange,\n sort,\n onSortChange,\n page,\n onPageChange,\n pageSize = 10,\n mode = \"client\",\n rowCount,\n visibleColumnKeys,\n onVisibleColumnsChange,\n columnWidths,\n onColumnWidthsChange,\n pinnedColumnKeys,\n onPinnedColumnsChange,\n virtualization,\n viewMode,\n onViewModeChange,\n loading = false,\n disabled = false,\n dense = false,\n emptyMessage = \"No results.\",\n onRowClick,\n className,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"DataGrid\", DataGridManifest.DataGrid!.props, allProps);\n }\n const [localSelection, setLocalSelection] = useState<string[]>([]);\n const [localSort, setLocalSort] = useState<DataGridSort | null>(null);\n const [localPage, setLocalPage] = useState(1);\n const [hiddenColumns, setHiddenColumns] = useState<string[]>([]);\n const [columnsOpen, setColumnsOpen] = useState(false);\n const [localWidths, setLocalWidths] = useState<DataGridColumnWidths>({});\n const [localPinned, setLocalPinned] = useState<string[]>([]);\n const [localView, setLocalView] = useState<DataGridViewMode>(\"virtualized\");\n const columnsId = useId();\n const blocked = disabled || loading;\n const selection = selectedRowIds ?? localSelection;\n const selectionSet = useMemo(() => new Set(selection), [selection]);\n const selectionEnabled = selectedRowIds !== undefined || Boolean(onSelectionChange);\n const effectiveSort = sort === undefined ? localSort : sort;\n const visibleKeys =\n visibleColumnKeys ??\n columns.filter((column) => !hiddenColumns.includes(column.key)).map((column) => column.key);\n const widths = columnWidths ?? localWidths;\n const pinned = [...new Set(pinnedColumnKeys ?? localPinned)].filter((key) =>\n columns.some((column) => column.key === key),\n );\n const visibleColumns = [\n ...pinned.flatMap((key) =>\n columns.filter((column) => column.key === key && visibleKeys.includes(key)),\n ),\n ...columns.filter((column) => visibleKeys.includes(column.key) && !pinned.includes(column.key)),\n ];\n const virtual = Boolean(virtualization) && (viewMode ?? localView) === \"virtualized\";\n const widthBounds = (column: DataGridColumn) => {\n const min = Number.isFinite(column.minWidth) ? Math.max(48, column.minWidth!) : 96;\n const max = Number.isFinite(column.maxWidth)\n ? Math.max(min, column.maxWidth!)\n : Math.max(min, 640);\n return { min, max };\n };\n const widthFor = (column: DataGridColumn) => {\n const { min, max } = widthBounds(column);\n const requested =\n widths[column.key] ?? (column.width?.endsWith(\"px\") ? Number.parseFloat(column.width) : 160);\n return Math.min(max, Math.max(min, Number.isFinite(requested) ? requested : 160));\n };\n const resizeColumn = (column: DataGridColumn, requested: number) => {\n if (blocked || !Number.isFinite(requested)) return;\n const { min, max } = widthBounds(column);\n const next = { ...widths, [column.key]: Math.min(max, Math.max(min, requested)) };\n if (columnWidths === undefined) setLocalWidths(next);\n onColumnWidthsChange?.(next);\n };\n const safePageSize = Number.isFinite(pageSize) && pageSize >= 1 ? Math.floor(pageSize) : 10;\n const count =\n mode === \"server\" && rowCount !== undefined && Number.isFinite(rowCount)\n ? Math.max(0, Math.floor(rowCount))\n : data.length;\n const pageCount = Math.max(1, Math.ceil(count / safePageSize));\n const requestedPage = page ?? localPage;\n const currentPage = Math.min(\n pageCount,\n Math.max(1, Number.isFinite(requestedPage) ? Math.floor(requestedPage) : 1),\n );\n const correction = useRef<string | null>(null);\n\n useEffect(() => {\n if (requestedPage === currentPage) {\n correction.current = null;\n return;\n }\n const key = `${requestedPage}:${currentPage}`;\n if (correction.current === key) return;\n correction.current = key;\n if (page === undefined) setLocalPage(currentPage);\n onPageChange?.(currentPage);\n }, [requestedPage, currentPage, page, onPageChange]);\n\n const changePage = (next: number) => {\n if (blocked) return;\n if (page === undefined) setLocalPage(next);\n onPageChange?.(next);\n };\n const changeSort = (next: DataGridSort | null) => {\n if (blocked) return;\n if (sort === undefined) setLocalSort(next);\n onSortChange?.(next);\n };\n const changeSelection = (next: string[]) => {\n if (blocked) return;\n if (selectedRowIds === undefined) setLocalSelection(next);\n onSelectionChange?.(next);\n };\n const sortColumn = columns.find(\n (column) => column.key === effectiveSort?.key && column.sortable !== false,\n );\n // Sorted only when the rows, the sorted column, its comparison or the direction change: a row\n // toggle, a page, the column settings or the view switch reuse the order (H2, DataGrid-R1).\n const sortKey = sortColumn?.key;\n const sortCompare = sortColumn?.compare;\n const direction = effectiveSort?.direction;\n const sorted = useMemo(() => {\n if (mode !== \"client\" || sortKey === undefined || direction === undefined) return data;\n return sortedOrder(\n data.map((row) => row[sortKey]),\n direction === \"asc\" ? 1 : -1,\n sortCompare,\n ).map((position) => data[position]!);\n }, [data, mode, sortKey, direction, sortCompare]);\n const pageStart = (currentPage - 1) * safePageSize;\n const paged = mode === \"client\" && !virtual;\n const pageRows = useMemo(\n () => (paged ? sorted.slice(pageStart, pageStart + safePageSize) : sorted),\n [sorted, paged, pageStart, safePageSize],\n );\n // IDs are read again when the rows change, not when only getRowId is a new function, such as an\n // inline one: the docs ask for a pure getRowId. It is kept during render, not after it, so new\n // rows use the function given with them.\n const latestGetRowId = useRef(getRowId);\n latestGetRowId.current = getRowId;\n // The IDs of the rows on the page are read when something asks for them, once for each set of\n // rows: the select-all box needs them when there is a selection to compare with, and its click\n // needs them. A grid with nothing selected reads none at mount (10,000 rows, 10,000 IDs).\n const pageIdsOf = useMemo(() => {\n let ids: string[] | null = null;\n return () => (ids ??= pageRows.map((row) => latestGetRowId.current(row)));\n }, [pageRows]);\n const { allSelected, someSelected } = useMemo(() => {\n if (selectionSet.size === 0) return { allSelected: false, someSelected: false };\n const pageIds = pageIdsOf();\n let selected = 0;\n for (const id of pageIds) if (selectionSet.has(id)) selected++;\n return {\n allSelected: pageIds.length > 0 && selected === pageIds.length,\n someSelected: selected > 0,\n };\n }, [pageIdsOf, selectionSet]);\n // Synthetic cell keys never shadow a caller's descriptor. Original rows stay\n // separate so custom renderers cannot change sorting, IDs, or row actions.\n let selectionKey = \"__oprt_selection\";\n while (columns.some((column) => column.key === selectionKey)) selectionKey += \"_\";\n const tableColumns: DataTableColumn[] = visibleColumns.map(\n ({ compare: _compare, minWidth: _min, maxWidth: _max, ...column }) => column,\n );\n if (selectionEnabled)\n tableColumns.push({\n key: selectionKey,\n label: \"SELECT\",\n render: (_value, row) => {\n const id = getRowId(row);\n return (\n <Checkbox\n aria-label={`Select row ${id}`}\n checked={selectionSet.has(id)}\n disabled={blocked}\n onCheckedChange={(checked) =>\n changeSelection(\n checked ? [...new Set([...selection, id])] : selection.filter((key) => key !== id),\n )\n }\n />\n );\n },\n });\n const sized = virtual || pinned.length > 0 || Object.keys(widths).length > 0;\n const tableWidths = Object.fromEntries(\n visibleColumns.map((column) => [column.key, widthFor(column)]),\n );\n if (selectionEnabled) tableWidths[selectionKey] = 96;\n const sortableColumns = columns.filter((column) => column.sortable !== false);\n // Select values need a sentinel for unsorted which cannot collide with keys.\n let unsortedKey = \"__oprt_unsorted\";\n while (columns.some((column) => column.key === unsortedKey)) unsortedKey += \"_\";\n\n return (\n <div\n ref={ref}\n className={clsx(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base normal-case text-oprt-text\",\n className,\n )}\n {...props}\n >\n <div\n className=\"flex flex-wrap items-center gap-x-2u gap-y-1u\"\n aria-label={`${caption} controls`}\n role=\"group\"\n >\n {sortableColumns.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1u\" role=\"group\" aria-label=\"Sort order\">\n <Select\n aria-label=\"SORT BY\"\n size=\"sm\"\n value={sortColumn ? sortColumn.key : unsortedKey}\n disabled={blocked}\n onValueChange={(key) =>\n changeSort(\n key === unsortedKey\n ? null\n : { key, direction: effectiveSort?.direction ?? \"asc\" },\n )\n }\n >\n <SelectOption value={unsortedKey}>SOURCE ORDER</SelectOption>\n {sortableColumns.map((column) => (\n <SelectOption key={column.key} value={column.key}>\n {column.label}\n </SelectOption>\n ))}\n </Select>\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={blocked || !sortColumn}\n onClick={() =>\n effectiveSort &&\n changeSort({\n ...effectiveSort,\n direction: effectiveSort.direction === \"asc\" ? \"desc\" : \"asc\",\n })\n }\n >\n {effectiveSort?.direction === \"desc\" ? \"DESCENDING\" : \"ASCENDING\"}\n </Button>\n </div>\n )}\n {/* Reference the column list only while it is mounted: it is written in\n the same render, so the ids agree even when hydration's disagree\n with the server's. */}\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={blocked}\n aria-expanded={columnsOpen}\n aria-controls={columnsOpen ? columnsId : undefined}\n onClick={() => setColumnsOpen(!columnsOpen)}\n >\n COLUMNS\n </Button>\n {virtualization && (\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={blocked}\n onClick={() => {\n const next = virtual ? \"paginated\" : \"virtualized\";\n if (viewMode === undefined) setLocalView(next);\n onViewModeChange?.(next);\n }}\n >\n {virtual ? \"Use paginated view\" : \"Use virtualized view\"}\n </Button>\n )}\n {selectionEnabled && (\n <div className=\"inline-flex items-center gap-1u font-medium tracking-oprt-caps\">\n <Checkbox\n aria-label={virtual && mode === \"client\" ? \"Select results\" : \"Select page\"}\n checked={allSelected ? true : someSelected ? \"indeterminate\" : false}\n disabled={blocked || pageRows.length === 0}\n onCheckedChange={(checked) =>\n changeSelection(\n checked\n ? [...new Set([...selection, ...pageIdsOf()])]\n : withoutPage(selection, pageIdsOf()),\n )\n }\n />\n {virtual && mode === \"client\" ? \"SELECT RESULTS\" : \"SELECT PAGE\"}\n </div>\n )}\n </div>\n {columnsOpen && (\n <fieldset\n id={columnsId}\n className=\"m-0 flex flex-wrap gap-2u border border-oprt-border p-2u\"\n >\n <legend className=\"px-1u font-medium tracking-oprt-caps\">COLUMN SETTINGS</legend>\n {columns.map((column) => (\n <div key={column.key} className=\"flex flex-col gap-1u font-medium\">\n <div className=\"inline-flex items-center gap-1u\">\n <Checkbox\n aria-label={column.label}\n checked={visibleKeys.includes(column.key)}\n disabled={blocked}\n onCheckedChange={(checked) => {\n const next = checked\n ? [...new Set([...visibleKeys, column.key])]\n : visibleKeys.filter((key) => key !== column.key);\n if (visibleColumnKeys === undefined)\n setHiddenColumns(\n columns.filter((item) => !next.includes(item.key)).map((item) => item.key),\n );\n onVisibleColumnsChange?.(next);\n }}\n />\n {column.label}\n </div>\n <div className=\"inline-flex items-center gap-1u\">\n <Checkbox\n aria-label={`Pin to start: ${column.label}`}\n checked={pinned.includes(column.key)}\n disabled={blocked}\n onCheckedChange={(checked) => {\n const next = checked\n ? [...pinned, column.key]\n : pinned.filter((key) => key !== column.key);\n if (pinnedColumnKeys === undefined) setLocalPinned(next);\n onPinnedColumnsChange?.(next);\n }}\n />\n Pin to start\n </div>\n <label className=\"m-0 flex flex-col gap-1u font-medium\">\n {column.label} width\n <span className=\"inline-flex items-center gap-1u\">\n <ColumnWidthInput\n label={`${column.label} width`}\n min={widthBounds(column).min}\n max={widthBounds(column).max}\n value={widthFor(column)}\n disabled={blocked}\n onCommit={(next) => resizeColumn(column, next)}\n />\n <span className=\"font-mono text-oprt-text-dim\">px</span>\n </span>\n </label>\n </div>\n ))}\n </fieldset>\n )}\n {visibleColumns.length === 0 ? (\n <p className=\"m-0 border border-oprt-border p-3u text-oprt-text-dim\">\n Choose a column to display results.\n </p>\n ) : (\n <DataTable\n columns={tableColumns}\n data={pageRows}\n sortDescriptor={sortColumn ? effectiveSort : null}\n caption={caption}\n dense={dense}\n emptyMessage={emptyMessage}\n aria-busy={loading}\n getRowId={getRowId}\n totalRowCount={count}\n rowIndexOffset={mode === \"client\" && virtual ? 0 : (currentPage - 1) * safePageSize}\n pinnedColumnKeys={pinned}\n {...(sized ? { columnWidths: tableWidths } : {})}\n {...(virtual && virtualization ? { virtualization } : {})}\n {...(onRowClick && !blocked\n ? {\n onRowClick: (row: DataTableRow) => {\n onRowClick(row, data.indexOf(row));\n },\n }\n : {})}\n />\n )}\n {/* On the page from the start, so the message written into it is announced: a live region\n created together with its text is commonly not (DataGrid-R3). */}\n <p role=\"status\" className={loading ? \"m-0 text-oprt-text-dim\" : \"sr-only\"}>\n {loading ? \"Loading results.\" : \"\"}\n </p>\n <div className=\"flex flex-wrap items-center justify-between gap-2u\">\n <p className=\"m-0 font-mono tabular-nums text-oprt-text-dim\">\n {count} {count === 1 ? \"ROW\" : \"ROWS\"}\n {selectionEnabled ? ` · ${selection.length} SELECTED` : \"\"}\n </p>\n {(!virtual || mode === \"server\") && (\n <PageControls\n page={currentPage}\n pageCount={pageCount}\n disabled={blocked}\n onPageChange={changePage}\n />\n )}\n </div>\n </div>\n );\n});\nDataGrid.displayName = \"DataGrid\";\n","DataGrid/index.ts":"export {\n DataGrid,\n type DataGridProps,\n type DataGridColumn,\n type DataGridSort,\n type DataGridColumnWidths,\n type DataGridVirtualization,\n type DataGridViewMode,\n} from \"./DataGrid\";\nexport { DataGridManifest } from \"./DataGrid.manifest\";\n","DataGrid/PageControls.tsx":"\"use client\";\n\nimport { Button } from \"../Button/Button\";\n\n/**\n * DataGrid's paging row: PREVIOUS PAGE, the page announced politely, NEXT PAGE. BulkResults pages a\n * large request with the same controls.\n */\nexport function PageControls({\n page,\n pageCount,\n disabled,\n onPageChange,\n}: {\n /** One-based, already clamped to 1..pageCount. */\n page: number;\n pageCount: number;\n disabled: boolean;\n onPageChange: (page: number) => void;\n}) {\n return (\n <div className=\"flex flex-wrap items-center gap-1u\">\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={disabled || page <= 1}\n onClick={() => onPageChange(page - 1)}\n >\n PREVIOUS PAGE\n </Button>\n <span className=\"font-mono tabular-nums\" aria-live=\"polite\">\n PAGE {page} OF {pageCount}\n </span>\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={disabled || page >= pageCount}\n onClick={() => onPageChange(page + 1)}\n >\n NEXT PAGE\n </Button>\n </div>\n );\n}\n","DataTable/DataTable.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DataTableManifest: Record<string, Component> = {\n DataTable: {\n description: \"Data-driven table with sorting, highlighting, and cell rendering.\",\n props: {\n columns: {\n type: \"array\",\n required: true,\n description:\n \"Column descriptors. Each item: { key: string (required), label: string (required), as?: 'text'|'number'|'code'|'timestamp'|'badge' (default 'text'), highlight?: boolean (default false), sortable?: boolean (default inherits table 'sortable'), width?: string (CSS width), align?: 'left'|'right' }. 'number'/'timestamp' cells render mono + right-aligned; with columnWidths a 'number' cell wraps only between digit groups and keeps a unit with its last group; 'badge' maps the value to a text-only Badge (success/warning/error/info use their semantic color, otherwise neutral). Use a custom render for outlined badges or an explicit indicator.\",\n items: {\n key: \"string (required)\",\n label: \"string (required)\",\n as: \"enum [text, number, code, timestamp, badge] (default: text)\",\n highlight: \"boolean (default: false)\",\n sortable: \"boolean (default: inherits from table)\",\n width: \"string\",\n align: \"enum [left, right]\",\n render: \"(value, originalRow) => ReactNode; called for mounted cells only\",\n },\n },\n data: {\n type: \"array\",\n required: true,\n description: \"Array of row objects (Record<string, unknown>) keyed by column.key.\",\n },\n sortable: {\n type: \"boolean\",\n default: false,\n description:\n \"Enables column header sort buttons (cycles ascending → descending → unsorted) when sortDescriptor is omitted. Per-column 'sortable: false' opts a column out. Designed for about 150 rows without virtualization: development warns above that; use DataGrid or virtualization for larger result sets.\",\n },\n sortDescriptor: {\n type: \"node\",\n description:\n \"Read-only { key: string, direction: 'asc' | 'desc' } metadata for rows already sorted by the caller. The matching header exposes aria-sort and a direction glyph; data order is preserved and internal header sort controls are disabled even when sortable is true. Pass null for externally managed source order. Omit to retain existing internal sortable behavior.\",\n },\n dense: {\n type: \"boolean\",\n default: false,\n description:\n \"Compact rows: 32px minimum height with 4px vertical cell padding. Default rows use a 40px minimum and 8px padding. Rows grow with content; explicit virtualization.rowHeight overrides these dimensions.\",\n },\n onRowClick: {\n type: \"function\",\n description:\n \"(row, indexInSuppliedData) => void. The index is the row's index in data, also after a header sort. Adds a native action button to the first cell while preserving table row semantics. Text and number cells become the button label; React content remains independent beside an Open row button. Nested controls and editable content do not activate the row. Enter/Space activate the row button.\",\n },\n getRowId: {\n type: \"function\",\n description:\n \"(row, index) => string. Stable React key per row; falls back to the index. Keep it pure: IDs are read again only when data changes, not when only the function does.\",\n },\n caption: {\n type: \"string\",\n description:\n \"Accessible table caption, visually hidden (sr-only). It also names the table's scroll region, '<caption> scroll viewport': a table whose columns do not fit scrolls sideways inside its own box, which is a keyboard tab stop while it scrolls; the page never widens.\",\n },\n emptyMessage: {\n type: \"string\",\n default: \"No results.\",\n description: \"Message spanning all columns when data is empty.\",\n },\n columnWidths: {\n type: \"node\",\n description:\n \"Record<columnKey, CSS pixel width> for fixed table layout. Unspecified columns use 160 pixels. DataGrid provides bounded user controls.\",\n },\n pinnedColumnKeys: {\n type: \"array\",\n description:\n \"Column keys to keep sticky at the leading edge. Supply pinned columns first in columns, in the desired order, with columnWidths for accurate offsets. DataGrid manages this ordering automatically.\",\n },\n virtualization: {\n type: \"node\",\n description:\n \"Optional {height, rowHeight?, estimateRowHeight?, overscan?}. Native rows are windowed in a labeled scrolling region. Omit rowHeight for measured variable heights; fixed rows clip overlong content. Prefer DataGrid for its built-in paginated fallback. Stable getRowId is required for retained focus across data changes.\",\n },\n rowIndexOffset: {\n type: \"number\",\n default: 0,\n description:\n \"Zero-based offset into the complete result set. Data rows announce offset + local index + 2, including the header.\",\n },\n totalRowCount: {\n type: \"number\",\n description:\n \"Total data rows excluding the header. Native table aria-rowcount includes the header as one additional row.\",\n },\n },\n composable_with: [\"Card\"],\n never_inside: [\"DataTable\"],\n example:\n '<DataTable\\n columns={[\\n { key: \"service\", label: \"SERVICE\" },\\n { key: \"latency\", label: \"LATENCY\", as: \"number\", highlight: true },\\n { key: \"status\", label: \"STATUS\", as: \"badge\" }\\n ]}\\n data={rows}\\n sortable\\n/>',\n },\n};\n","DataTable/DataTable.tsx":"\"use client\";\n\nimport { ChevronUp, ChevronDown } from \"@oprt/icons\";\nimport {\n forwardRef,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n type TableHTMLAttributes,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { Badge } from \"../Badge/Badge\";\nimport { numberText } from \"../_internal/numberText\";\nimport { sortedOrder } from \"./sort\";\nimport { useScrollsSideways } from \"./useScrollsSideways\";\nimport { useTableWindow, type DataTableVirtualization, type RowKeys } from \"./useTableWindow\";\n\nexport type { DataTableVirtualization } from \"./useTableWindow\";\n\nexport type DataTableColumn = {\n key: string;\n label: string;\n as?: \"text\" | \"number\" | \"code\" | \"timestamp\" | \"badge\";\n highlight?: boolean;\n sortable?: boolean;\n width?: string;\n align?: \"left\" | \"right\";\n render?: (value: unknown, row: DataTableRow) => ReactNode;\n};\n\nexport type DataTableRow = Record<string, unknown>;\nexport type DataTableSortDescriptor = { key: string; direction: \"asc\" | \"desc\" };\n\nexport type DataTableProps = Omit<TableHTMLAttributes<HTMLTableElement>, \"children\"> & {\n columns: DataTableColumn[];\n data: DataTableRow[];\n sortable?: boolean;\n /** Read-only metadata for rows already sorted by the caller. Null means source order. */\n sortDescriptor?: DataTableSortDescriptor | null;\n dense?: boolean;\n onRowClick?: (row: DataTableRow, index: number) => void;\n getRowId?: (row: DataTableRow, index: number) => string;\n caption?: string;\n emptyMessage?: string;\n columnWidths?: Record<string, number>;\n pinnedColumnKeys?: readonly string[];\n virtualization?: DataTableVirtualization;\n /** Zero-based offset in the complete result set, excluding the header. */\n rowIndexOffset?: number;\n /** Complete data-row count, excluding the header. */\n totalRowCount?: number;\n};\n\ntype SortDir = \"asc\" | \"desc\";\ntype SortState = { key: string; dir: SortDir } | null;\n\nconst SEMANTIC_BADGE = new Set([\"success\", \"warning\", \"error\", \"info\"]);\nconst CELL_CONTROL_SELECTOR =\n \"button, a, input, select, textarea, label, summary, [role='button'], [role='link'], [role='checkbox'], [role='radio'], [role='switch'], [role='combobox'], [role='slider'], [role='textbox'], [tabindex], [contenteditable]:not([contenteditable='false'])\";\n\n// The sort glyph IS the non-color sort indicator (shape, not color) per WCAG 1.4.1.\nfunction SortGlyph({ dir }: { dir: SortDir | null }) {\n return (\n <span\n aria-hidden=\"true\"\n className={clsx(\n \"inline-flex h-oprt-sm w-1.5u shrink-0 flex-col items-center justify-center\",\n dir ? \"text-oprt-accent\" : \"text-oprt-text-dim\",\n )}\n >\n {dir !== \"desc\" && <ChevronUp size={12} />}\n {dir !== \"asc\" && <ChevronDown size={12} />}\n </span>\n );\n}\n\n/**\n * How many rows a sortable table that is not windowed stays within (the size envelope in the docs).\n * Sorting moves every row in the document, and the browser then lays them all out again: about half\n * a millisecond a row on a slower device, so a click on a header past this point takes more than\n * 100 ms to show its result. Larger result sets belong in DataGrid, paged or windowed.\n */\nexport const SORTABLE_ROWS_UNWINDOWED = 150;\n\nfunction isRightAligned(column: DataTableColumn): boolean {\n return (\n column.align === \"right\" ||\n ((column.as === \"number\" || column.as === \"timestamp\") && column.align !== \"left\")\n );\n}\n\n// The typeface of a number or a timestamp. Automatic layout keeps it on one line; in fixed layout\n// (columns with widths) it may wrap, and a number breaks only where DC-11 allows (NumberText).\nconst NUMERIC_AUTO = \"font-mono tabular-nums whitespace-nowrap\";\nconst NUMERIC_FIXED = \"font-mono tabular-nums\";\n\nfunction renderCell(column: DataTableColumn, value: unknown, fixedWidth: boolean) {\n const numeric = fixedWidth ? NUMERIC_FIXED : NUMERIC_AUTO;\n switch (column.as) {\n case \"number\":\n // DC-11: in fixed layout a number breaks only after a group separator and its unit stays with\n // the last group; an ungrouped number never breaks. Automatic layout keeps it on one line.\n // Called, not rendered as an element: a thousand rows would mount thousands of components\n // that only hand back a span.\n if (typeof value === \"string\" || typeof value === \"number\")\n return numberText({ value, className: numeric });\n return <span className={numeric}>{value as ReactNode}</span>;\n case \"timestamp\":\n return <span className={numeric}>{value as ReactNode}</span>;\n case \"code\":\n return <span className=\"font-mono\">{value as ReactNode}</span>;\n case \"badge\": {\n const key = String(value).toLowerCase();\n if (SEMANTIC_BADGE.has(key)) {\n return (\n <Badge\n appearance=\"text\"\n variant={key as \"success\" | \"warning\" | \"error\" | \"info\"}\n indicator=\"none\"\n >\n {value as ReactNode}\n </Badge>\n );\n }\n return (\n <Badge appearance=\"text\" variant=\"neutral\">\n {value as ReactNode}\n </Badge>\n );\n }\n default:\n if (column.highlight) {\n return <span className=\"text-oprt-accent font-medium\">{value as ReactNode}</span>;\n }\n return value as ReactNode;\n }\n}\n\nexport const DataTable = forwardRef<HTMLTableElement, DataTableProps>(\n (\n {\n columns,\n data,\n sortable = false,\n sortDescriptor,\n dense = false,\n onRowClick,\n getRowId,\n caption,\n emptyMessage = \"No results.\",\n columnWidths,\n pinnedColumnKeys = [],\n virtualization,\n rowIndexOffset = 0,\n totalRowCount,\n className,\n style,\n onBlurCapture,\n ...props\n },\n ref,\n ) => {\n const [sort, setSort] = useState<SortState>(null);\n const externallySorted = sortDescriptor !== undefined;\n\n const toggleSort = (key: string) => {\n setSort((prev) => {\n if (!prev || prev.key !== key) return { key, dir: \"asc\" };\n if (prev.dir === \"asc\") return { key, dir: \"desc\" };\n return null;\n });\n };\n\n const columnSortable = (col: DataTableColumn) =>\n !externallySorted && sortable && col.sortable !== false;\n\n // The sorted positions in the supplied data, kept until the data or the sort changes: a scroll,\n // a focus or a measurement does not sort again (H2).\n const order = useMemo(\n () =>\n !externallySorted && sort\n ? sortedOrder(\n data.map((row) => row[sort.key]),\n sort.dir === \"asc\" ? 1 : -1,\n )\n : null,\n [data, externallySorted, sort],\n );\n const rows = useMemo(\n () => (order ? order.map((position) => data[position]!) : data),\n [data, order],\n );\n // Row keys and where each is, read once per set of rows: a scroll reads no row IDs (H3), and\n // neither does a render that only brings a new getRowId function, such as an inline one. The\n // docs ask for a pure getRowId (the same row, the same ID); the latest one reads new rows.\n // It is kept during render, not after it, so a render with new rows uses the function given\n // with them.\n const latestGetRowId = useRef(getRowId);\n latestGetRowId.current = getRowId;\n const withRowIds = getRowId !== undefined;\n const rowKeys = useMemo<RowKeys>(() => {\n const read = withRowIds ? latestGetRowId.current : undefined;\n const keys = rows.map((row, position) => read?.(row, position) ?? String(position));\n const index = new Map<string, number>();\n keys.forEach((key, position) => {\n if (!index.has(key)) index.set(key, position);\n });\n return { keys, index };\n }, [rows, withRowIds]);\n\n // Development: a sortable table that is not windowed and holds more rows than the documented\n // envelope (DataTable-W3-1). Once per table.\n const sortsItself =\n !externallySorted && sortable && columns.some((column) => column.sortable !== false);\n const outsideEnvelope =\n sortsItself && !virtualization && data.length > SORTABLE_ROWS_UNWINDOWED;\n const warned = useRef(false);\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\" || !outsideEnvelope || warned.current) return;\n warned.current = true;\n console.warn(\n JSON.stringify(\n {\n component: \"DataTable\",\n warning: \"OUTSIDE_SIZE_ENVELOPE\",\n prop: \"data\",\n received: `${data.length} rows, sortable, without virtualization`,\n suggestion: `A sortable DataTable that is not windowed stays responsive up to about ${SORTABLE_ROWS_UNWINDOWED} rows: sorting moves every row and the browser lays them all out again. Use DataGrid, which pages or windows its rows, or pass virtualization.`,\n },\n null,\n 2,\n ),\n );\n }, [outsideEnvelope, data.length]);\n\n const ariaSortFor = (col: DataTableColumn): \"ascending\" | \"descending\" | \"none\" | undefined => {\n if (externallySorted) {\n if (!sortDescriptor || sortDescriptor.key !== col.key) return undefined;\n return sortDescriptor.direction === \"asc\" ? \"ascending\" : \"descending\";\n }\n if (!columnSortable(col)) return undefined;\n if (!sort || sort.key !== col.key) return \"none\";\n return sort.dir === \"asc\" ? \"ascending\" : \"descending\";\n };\n\n const keys = rowKeys.keys;\n const rowWindow = useTableWindow(rowKeys, virtualization);\n const [box, scrolls] = useScrollsSideways(!virtualization);\n const sized = Boolean(columnWidths);\n const widths = columns.map((column) => {\n const width = columnWidths?.[column.key];\n return width !== undefined && Number.isFinite(width) ? Math.max(24, width) : 160;\n });\n const leftFor = (index: number) =>\n columns\n .slice(0, index)\n .reduce(\n (offset, column, i) => offset + (pinnedColumnKeys.includes(column.key) ? widths[i]! : 0),\n 0,\n );\n const indexed = totalRowCount !== undefined || Boolean(virtualization) || rowIndexOffset > 0;\n // How a column's cells look is the same in every row, so its classes and style are worked out\n // once per column, not once per cell: a table of a thousand rows has ten of them, not ten thousand.\n const cellLook = columns.map((col, columnIndex) => {\n const pinned = pinnedColumnKeys.includes(col.key);\n return {\n className: clsx(\n \"border-b border-oprt-border px-oprt-cell-inline align-middle [&>button]:align-middle [&>label]:align-middle\",\n dense ? \"py-oprt-cell-block-dense h-oprt-row-dense\" : \"py-oprt-cell-block h-oprt-row\",\n isRightAligned(col) && \"text-right\",\n sized && \"break-words\",\n pinned && \"z-10 bg-oprt-surface-1\",\n ),\n style: {\n ...(pinned ? { position: \"sticky\" as const, left: leftFor(columnIndex) } : {}),\n ...(rowWindow.fixed !== undefined && virtualization\n ? { height: rowWindow.fixed, paddingTop: 0, paddingBottom: 0 }\n : {}),\n },\n };\n });\n const body: ReactNode[] = [];\n let lastIndex = 0;\n const spacer = (from: number, to: number) => {\n if (to <= from) return;\n body.push(\n <tr key={`spacer-${from}`} aria-hidden=\"true\">\n <td\n colSpan={Math.max(columns.length, 1)}\n style={{\n height: rowWindow.offsetOf(to) - rowWindow.offsetOf(from),\n padding: 0,\n border: 0,\n }}\n />\n </tr>,\n );\n };\n const renderRow = (i: number) => {\n const row = rows[i]!;\n const rowKey = keys[i]!;\n // The row's index in the supplied data, as DataGrid reports it: after a header sort it is not\n // the row's position on screen (DataTable-R5).\n const source = order ? order[i]! : i;\n const clickable = Boolean(onRowClick);\n body.push(\n <tr\n key={rowKey}\n ref={rowWindow.rowRef(rowKey)}\n aria-rowindex={indexed ? rowIndexOffset + i + 2 : undefined}\n data-row-id={rowKey}\n onFocusCapture={() => rowWindow.onFocusRow(rowKey, i)}\n {...(clickable\n ? {\n onClick: (e: React.MouseEvent<HTMLTableRowElement>) => {\n const control = (e.target as Element).closest(CELL_CONTROL_SELECTOR);\n if (e.defaultPrevented || (control && e.currentTarget.contains(control))) return;\n onRowClick?.(row, source);\n },\n }\n : {})}\n className={clsx(clickable && \"cursor-pointer hover:bg-oprt-surface-2\")}\n >\n {columns.map((col, columnIndex) => {\n const value = col.render ? col.render(row[col.key], row) : row[col.key];\n const rendered = col.render ? (value as ReactNode) : renderCell(col, value, sized);\n const textValue =\n col.as !== \"badge\" && (typeof value === \"string\" || typeof value === \"number\");\n const look = cellLook[columnIndex]!;\n const content =\n clickable && columnIndex === 0 ? (\n <>\n {!textValue && rendered}\n <button\n type=\"button\"\n className={clsx(\n \"inline-flex items-center h-oprt-sm text-oprt-base font-medium hover:underline border border-transparent focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-hidden\",\n !sized && \"whitespace-nowrap\",\n !textValue && \"ml-1u uppercase tracking-oprt-caps\",\n )}\n onClick={() => onRowClick?.(row, source)}\n >\n {textValue ? rendered : `Open row ${rowIndexOffset + i + 1}`}\n </button>\n </>\n ) : (\n rendered\n );\n return (\n <td key={col.key} style={look.style} className={look.className}>\n {rowWindow.fixed !== undefined && virtualization ? (\n <div style={{ maxHeight: rowWindow.fixed - 1 }} className=\"overflow-hidden\">\n {content}\n </div>\n ) : (\n content\n )}\n </td>\n );\n })}\n </tr>,\n );\n };\n if (rowWindow.indices === null) {\n for (let i = 0; i < rows.length; i++) renderRow(i);\n } else {\n for (const i of rowWindow.indices) {\n spacer(lastIndex, i);\n lastIndex = i + 1;\n renderRow(i);\n }\n spacer(lastIndex, rows.length);\n }\n const table = (\n <table\n ref={ref}\n className={clsx(\n \"w-full border-collapse font-sans text-oprt-base normal-case text-oprt-text\",\n className,\n )}\n style={{\n ...(sized\n ? { tableLayout: \"fixed\", width: widths.reduce((sum, width) => sum + width, 0) }\n : {}),\n ...style,\n }}\n aria-rowcount={indexed ? (totalRowCount ?? rows.length) + 1 : undefined}\n onBlurCapture={(event) => {\n if (!event.currentTarget.contains(event.relatedTarget as Node | null))\n rowWindow.clearFocus();\n onBlurCapture?.(event);\n }}\n {...props}\n >\n {caption && <caption className=\"sr-only\">{caption}</caption>}\n <thead>\n <tr aria-rowindex={indexed ? 1 : undefined}>\n {columns.map((col, columnIndex) => {\n const active = columnSortable(col);\n const dir = externallySorted\n ? sortDescriptor?.key === col.key\n ? sortDescriptor.direction\n : null\n : sort && sort.key === col.key\n ? sort.dir\n : null;\n return (\n <th\n key={col.key}\n scope=\"col\"\n aria-sort={ariaSortFor(col)}\n style={{\n ...(sized\n ? { width: widths[columnIndex] }\n : col.width\n ? { width: col.width }\n : {}),\n ...(pinnedColumnKeys.includes(col.key)\n ? { position: \"sticky\", left: leftFor(columnIndex) }\n : {}),\n }}\n className={clsx(\n \"px-oprt-cell-inline text-oprt-sm font-medium uppercase tracking-oprt-sm text-oprt-text-dim border-b border-oprt-border align-middle\",\n !sized && \"whitespace-nowrap\",\n dense\n ? \"py-oprt-cell-block-dense h-oprt-row-dense\"\n : \"py-oprt-cell-block h-oprt-row\",\n isRightAligned(col) ? \"text-right\" : \"text-left\",\n pinnedColumnKeys.includes(col.key) && \"z-10 bg-oprt-surface-1\",\n )}\n >\n {active ? (\n <button\n type=\"button\"\n onClick={() => toggleSort(col.key)}\n className=\"inline-flex items-center gap-1u h-oprt-sm whitespace-nowrap text-oprt-base font-medium uppercase tracking-oprt-sm border border-transparent focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-hidden\"\n >\n {col.label}\n <SortGlyph dir={dir} />\n </button>\n ) : externallySorted && dir ? (\n <span className=\"inline-flex items-center gap-1u\">\n {col.label}\n <SortGlyph dir={dir} />\n </span>\n ) : (\n col.label\n )}\n </th>\n );\n })}\n </tr>\n </thead>\n <tbody>\n {body}\n {rows.length === 0 && (\n <tr>\n <td\n colSpan={Math.max(columns.length, 1)}\n className=\"px-2u py-4u text-oprt-text-dim border-b border-oprt-border\"\n >\n {emptyMessage}\n </td>\n </tr>\n )}\n </tbody>\n </table>\n );\n const regionName = `${caption ?? \"Data table\"} scroll viewport`;\n return virtualization ? (\n <div\n ref={rowWindow.viewport}\n role=\"region\"\n aria-label={regionName}\n tabIndex={0}\n style={{ height: rowWindow.height, overflowAnchor: \"none\" }}\n className=\"overflow-auto border border-oprt-border focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus\"\n onScroll={rowWindow.onScroll}\n >\n {table}\n </div>\n ) : (\n // Columns that do not fit scroll sideways inside the table's own box, never the page (R6).\n // While they do, the box is a named region and a tab stop, so a keyboard can scroll it; while\n // the table fits it is a plain box that adds no tab stop.\n <div\n ref={box}\n data-oprt-table-scroll=\"\"\n className={clsx(\n \"max-w-full overflow-x-auto\",\n scrolls &&\n \"border border-transparent focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus\",\n )}\n {...(scrolls ? { role: \"region\", \"aria-label\": regionName, tabIndex: 0 } : {})}\n >\n {table}\n </div>\n );\n },\n);\nDataTable.displayName = \"DataTable\";\n","DataTable/index.ts":"export {\n DataTable,\n type DataTableProps,\n type DataTableColumn,\n type DataTableRow,\n type DataTableSortDescriptor,\n type DataTableVirtualization,\n} from \"./DataTable\";\nexport { DataTableManifest } from \"./DataTable.manifest\";\n","DataTable/sort.ts":"/**\n * Sorting rows by one column, shared by DataTable's header sort and DataGrid's client mode.\n *\n * The order is exactly what each table produced with its own copy of the comparison before they\n * shared this one: finite numbers compare as numbers, everything else as text with\n * `String.prototype.localeCompare` (`null` and `undefined` as empty text), and equal values keep\n * their supplied order in both directions. What changed is the cost. The column's values are read\n * once, not twice per comparison, and the sort orders positions instead of rows; a column of plain\n * numbers is compared from a typed array, and a column of text ranks its distinct values once so\n * the sort itself compares integers. A column that mixes numbers and text uses `compareCellValues`\n * itself. The browser's sort makes the same decisions from the same comparison results, so the order\n * cannot change.\n */\n\nexport type CellCompare = (a: unknown, b: unknown) => number;\n\nconst isFiniteNumber = (value: unknown): value is number =>\n typeof value === \"number\" && Number.isFinite(value);\n\nconst textOf = (value: unknown) => String(value ?? \"\");\n\n/** The tables' comparison of two cell values. */\nexport function compareCellValues(a: unknown, b: unknown): number {\n if (isFiniteNumber(a) && isFiniteNumber(b)) return a - b;\n return textOf(a).localeCompare(textOf(b));\n}\n\n/**\n * The positions of `values` in sorted order: `direction` 1 ascends, -1 descends, and equal values\n * keep their order. `compare` replaces the tables' comparison (DataGrid's column `compare`); a\n * non-finite result counts as equal.\n */\nexport function sortedOrder(\n values: readonly unknown[],\n direction: 1 | -1,\n compare?: CellCompare,\n): number[] {\n const order = Array.from({ length: values.length }, (_, index) => index);\n if (compare) {\n order.sort((i, j) => {\n const result = compare(values[i], values[j]);\n return Number.isFinite(result) ? result * direction : 0;\n });\n return order;\n }\n let numbers = 0;\n for (const value of values) if (isFiniteNumber(value)) numbers++;\n if (numbers === values.length) {\n const numeric = Float64Array.from(values as readonly number[]);\n order.sort((i, j) => (numeric[i]! - numeric[j]!) * direction);\n return order;\n }\n if (numbers === 0) {\n // Only text: rank each distinct value once, then compare ranks. Values the collation finds\n // equal share a rank, so they stay tied, as they were.\n const texts = values.map(textOf);\n const distinct = [...new Set(texts)].sort((a, b) => a.localeCompare(b));\n const ranks = new Map<string, number>();\n let rank = -1;\n let previous: string | undefined;\n for (const text of distinct) {\n if (previous === undefined || previous.localeCompare(text) !== 0) rank++;\n ranks.set(text, rank);\n previous = text;\n }\n const ranked = Int32Array.from(texts, (text) => ranks.get(text)!);\n order.sort((i, j) => (ranked[i]! - ranked[j]!) * direction);\n return order;\n }\n // Numbers and text together, which is rare: the tables' comparison itself, value by value.\n order.sort((i, j) => compareCellValues(values[i], values[j]) * direction);\n return order;\n}\n","DataTable/useScrollsSideways.ts":"import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\nimport { createResizeObserver } from \"../_internal/useElementWidth\";\n\n/**\n * Whether a box's content is wider than the box, so the box scrolls sideways (spec R6: a scroll\n * region is a tab stop with a name while it scrolls, and adds no tab stop while its content fits).\n *\n * Returns a ref for the box and the answer. The hook follows the element in that ref, so a box that\n * appears after the component mounts, or is replaced, is measured too; a new box counts as fitting\n * until it is measured. It measures once the box is in place and again whenever the box, or one of\n * its children, changes size. The first render, on the server and on the client, never claims it, so\n * their markup agrees. Without ResizeObserver it measures when the box appears.\n *\n * A box that has keyboard focus keeps the answer `true` until focus leaves it, and then measures\n * again: if the content came to fit while the box was focused (a panel widened, a phone turned),\n * taking its tab stop away would drop focus to the page. DataTable and BulkResults use it.\n *\n * The ref only records the box. It sets no state, so a table that mounts does not render a second\n * time, rows and all, just to hand its box over: the effect that follows the box runs after each\n * render of the component that calls the hook, which is also the render that puts the box there.\n * It runs before the first paint, where the browser has to lay the box out anyway. Measuring after\n * the paint instead lays a large table out a second time whenever something changed since the\n * first layout (a font arriving, for one), and that second layout is the user's wait.\n */\nexport function useScrollsSideways(\n enabled = true,\n): [(element: HTMLElement | null) => void, boolean] {\n // The box in the ref now, and the one being followed (with the way to stop following it).\n const box = useRef<HTMLElement | null>(null);\n const followed = useRef<{ element: HTMLElement; stop: () => void } | null>(null);\n // The box that scrolls, or null. Kept as the element, so an answer never carries over to a new box.\n const [scrolling, setScrolling] = useState<HTMLElement | null>(null);\n const answered = useRef(scrolling);\n useIsomorphicLayoutEffect(() => {\n answered.current = scrolling;\n });\n const setBox = useCallback((element: HTMLElement | null) => {\n box.current = element;\n // A new box does not inherit the answer for the old one: it counts as fitting until measured.\n if (element !== null && answered.current !== null && answered.current !== element) {\n setScrolling(null);\n }\n }, []);\n useIsomorphicLayoutEffect(() => {\n const element = enabled ? box.current : null;\n if (followed.current?.element === element) return;\n followed.current?.stop();\n followed.current = null;\n if (!element) return;\n // What the component shows for this box now. A measurement that agrees sets nothing, so a box\n // that fits costs no render.\n let current = answered.current === element;\n // One pixel of difference is rounding, not content to reach.\n const measure = (focused = element.ownerDocument.activeElement === element) => {\n const scrolls = element.scrollWidth > element.clientWidth + 1 || focused;\n if (scrolls === current) return;\n current = scrolls;\n setScrolling(scrolls ? element : null);\n };\n const blurred = () => measure(false);\n measure();\n element.addEventListener(\"blur\", blurred);\n const observer = createResizeObserver(() => measure());\n if (observer) {\n observer.observe(element);\n for (const child of Array.from(element.children)) observer.observe(child);\n }\n followed.current = {\n element,\n stop: () => {\n element.removeEventListener(\"blur\", blurred);\n observer?.disconnect();\n },\n };\n });\n // Stop following when the component goes.\n useEffect(\n () => () => {\n followed.current?.stop();\n followed.current = null;\n },\n [],\n );\n return [setBox, enabled && scrolling !== null && scrolling === box.current];\n}\n","DataTable/useTableWindow.ts":"import { useEffect, useMemo, useRef, useState } from \"react\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\nexport type DataTableVirtualization = {\n /** Scroll viewport height in CSS pixels. */\n height: number;\n /** Fixed row height; omit to measure wrapped/variable-height rows. Minimum 24. */\n rowHeight?: number;\n /** Initial estimate for measured rows, refined with ResizeObserver. */\n estimateRowHeight?: number;\n overscan?: number;\n};\nconst finite = (value: number | undefined, fallback: number, minimum: number) =>\n value !== undefined && Number.isFinite(value) ? Math.max(minimum, value) : fallback;\n\n/** The row keys in display order, and the position of each. DataTable memoizes it on its rows. */\nexport type RowKeys = { keys: readonly string[]; index: ReadonlyMap<string, number> };\n\ntype RowRef = (node: HTMLTableRowElement | null) => void;\n\nconst NO_HEIGHTS: ReadonlyMap<string, number> = new Map();\n/** A measured row shorter than a target is not a measurement (a row being laid out, or hidden). */\nconst MIN_ROW = 24;\n\n/**\n * Which rows a windowed table mounts. Native rows remain in document order; focused rows and their\n * neighbors stay mounted.\n *\n * The work it does grows with the visible rows, not with all of them: the keys and their positions\n * come memoized from DataTable, fixed rows need no offsets at all, measured rows keep one offsets\n * table that changes only with the keys or a measurement, and a scroll renders the table again only\n * when it brings a row into view or takes one out (at most once per row crossed, never within one),\n * so the overscan always stays mounted beyond each edge. Measured rows share one ResizeObserver,\n * which watches each row while it is mounted.\n */\nexport function useTableWindow(rowKeys: RowKeys, options: DataTableVirtualization | undefined) {\n const { keys, index } = rowKeys;\n const count = keys.length;\n const windowed = options !== undefined;\n const height = finite(options?.height, 320, 24);\n const fixed = options?.rowHeight === undefined ? undefined : finite(options.rowHeight, 32, 24);\n const estimate = fixed ?? finite(options?.estimateRowHeight, 40, 24);\n const overscan = Math.floor(finite(options?.overscan, 3, 1));\n const measured = windowed && fixed === undefined;\n\n const viewport = useRef<HTMLDivElement>(null);\n const [heights, setHeights] = useState(NO_HEIGHTS);\n // The scroll position the mounted rows were chosen for. A scroll that keeps the same rows in view\n // (one within a row) leaves it, and the table, alone; one that brings another row into view or\n // takes one out moves it, so the overscan stays ready beyond each edge.\n const [anchor, setAnchor] = useState(0);\n const [focusedKey, setFocusedKey] = useState<string | null>(null);\n\n // Where each measured row starts: a row not measured yet takes the estimate. Fixed rows start at\n // a multiple of their height and need no table.\n const offsets = useMemo(() => {\n if (!measured) return null;\n const result = new Float64Array(keys.length + 1);\n for (let row = 0; row < keys.length; row++)\n result[row + 1] = result[row]! + (heights.get(keys[row]!) ?? estimate);\n return result;\n }, [measured, keys, heights, estimate]);\n const geometry = useMemo<Geometry>(\n () => ({ count, offsets, estimate, height }),\n [count, offsets, estimate, height],\n );\n const offsetOf = (row: number) => offsetIn(geometry, row);\n const [start, end] = windowed ? mountedRange(geometry, anchor, overscan) : [0, count];\n /** Whether the rows to mount at this scroll position differ from the mounted ones. */\n const moved = (scrollTop: number) => {\n const [from, to] = mountedRange(geometry, scrollTop, overscan);\n return from !== start || to !== end;\n };\n\n // Mounted rows in order: the window, and the focused row with its neighbors wherever they are.\n // An unwindowed table mounts every row and needs no list.\n let indices: number[] | null = null;\n if (windowed) {\n const focused = focusedKey === null ? undefined : index.get(focusedKey);\n const kept =\n focused === undefined\n ? []\n : [focused - 1, focused, focused + 1].filter(\n (row) => row >= 0 && row < count && (row < start || row >= end),\n );\n indices = kept.filter((row) => row < start);\n for (let row = start; row < end; row++) indices.push(row);\n for (const row of kept) if (row >= end) indices.push(row);\n }\n\n // After any other change (data, measurements, the viewport's height) the rows for the current\n // scroll position may differ from the mounted ones: choose them again before the browser paints.\n useIsomorphicLayoutEffect(() => {\n const element = viewport.current;\n if (!windowed || !element) return;\n const [from, to] = mountedRange(geometry, element.scrollTop, overscan);\n if (from !== start || to !== end) setAnchor(element.scrollTop);\n }, [windowed, geometry, overscan, start, end]);\n\n // Measured rows: one observer for the table, which each row joins while it is mounted.\n const nodes = useRef(new Map<string, HTMLTableRowElement>());\n const keyOf = useRef(new WeakMap<Element, string>());\n const observer = useRef<ResizeObserver | null>(null);\n const rowRefs = useRef(new Map<string, RowRef>());\n useEffect(() => {\n if (!measured || typeof ResizeObserver === \"undefined\") return;\n const resizes = new ResizeObserver((entries) => {\n const sizes: [string, number][] = [];\n for (const entry of entries) {\n const key = keyOf.current.get(entry.target);\n const size =\n entry.borderBoxSize?.[0]?.blockSize ?? entry.target.getBoundingClientRect().height;\n if (key !== undefined && size >= MIN_ROW) sizes.push([key, size]);\n }\n if (sizes.length > 0) setHeights((current) => withHeights(current, sizes));\n });\n observer.current = resizes;\n for (const node of nodes.current.values()) resizes.observe(node);\n return () => {\n resizes.disconnect();\n observer.current = null;\n };\n }, [measured]);\n // Without ResizeObserver, measure the mounted rows once they are in place.\n useEffect(() => {\n if (!measured || typeof ResizeObserver !== \"undefined\") return;\n const sizes: [string, number][] = [];\n for (const [key, node] of nodes.current) {\n const size = node.getBoundingClientRect().height;\n if (size >= MIN_ROW) sizes.push([key, size]);\n }\n if (sizes.length > 0) setHeights((current) => withHeights(current, sizes));\n }, [measured, start, end]);\n // Forget the measurements and row callbacks of rows that are gone from the data.\n useEffect(() => {\n if (!measured) return;\n for (const key of rowRefs.current.keys())\n if (!index.has(key) && !nodes.current.has(key)) rowRefs.current.delete(key);\n setHeights((current) => {\n let next: Map<string, number> | null = null;\n for (const key of current.keys())\n if (!index.has(key)) (next ??= new Map(current)).delete(key);\n return next ?? current;\n });\n }, [measured, index]);\n\n /** A measured row's ref: one callback while the row is mounted, so React attaches it once. */\n const rowRef = (key: string): RowRef | undefined => {\n if (!measured) return undefined;\n let ref = rowRefs.current.get(key);\n if (!ref) {\n ref = (node) => {\n if (node) {\n nodes.current.set(key, node);\n keyOf.current.set(node, key);\n observer.current?.observe(node);\n return;\n }\n const previous = nodes.current.get(key);\n if (previous) observer.current?.unobserve(previous);\n nodes.current.delete(key);\n rowRefs.current.delete(key);\n };\n rowRefs.current.set(key, ref);\n }\n return ref;\n };\n\n return {\n viewport,\n height,\n fixed,\n /** The mounted rows in order, or null when every row is mounted (an unwindowed table). */\n indices,\n offsetOf,\n rowRef,\n onScroll: () => {\n const element = viewport.current;\n if (element && moved(element.scrollTop)) setAnchor(element.scrollTop);\n },\n onFocusRow: (key: string, row: number) => {\n if (!windowed || key === focusedKey) return;\n setFocusedKey(key);\n const element = viewport.current;\n if (!element) return;\n // Tab from a retained neighbor scrolls it into view. Ordinary scrolling\n // does not move the focused control or force the viewport back to it.\n const rowTop = offsetOf(row);\n const rowBottom = offsetOf(row + 1);\n const header = element.querySelector(\"thead\")?.getBoundingClientRect().height ?? 0;\n const visibleTop = element.scrollTop;\n if (rowTop < visibleTop) element.scrollTop = rowTop;\n else if (rowBottom + header > visibleTop + height)\n element.scrollTop = rowBottom + header - height;\n setAnchor(element.scrollTop);\n },\n clearFocus: () => {\n if (windowed && focusedKey !== null) setFocusedKey(null);\n },\n };\n}\n\n/** What turns a scroll position into rows: fixed rows have no offsets table. */\ntype Geometry = {\n count: number;\n offsets: Float64Array | null;\n estimate: number;\n height: number;\n};\n\n/** Where a row starts. */\nfunction offsetIn({ offsets, estimate }: Geometry, row: number): number {\n return offsets ? offsets[row]! : row * estimate;\n}\n\n/** The row at a scroll position. */\nfunction rowAt({ count, offsets, estimate }: Geometry, position: number): number {\n if (count === 0) return 0;\n if (!offsets) return Math.min(count - 1, Math.max(0, Math.floor(position / estimate)));\n let low = 0;\n let high = count;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n if (offsets[mid + 1]! <= position) low = mid + 1;\n else high = mid;\n }\n return Math.min(count - 1, low);\n}\n\n/** The rows in view at a scroll position: the first, and one past the last. */\nfunction inView(geometry: Geometry, scrollTop: number): [number, number] {\n const top = Math.min(\n scrollTop,\n Math.max(0, offsetIn(geometry, geometry.count) - geometry.height),\n );\n return [\n rowAt(geometry, top),\n Math.min(geometry.count, rowAt(geometry, top + geometry.height) + 1),\n ];\n}\n\n/** The rows to mount at a scroll position: those in view and `overscan` more beyond each edge. */\nfunction mountedRange(geometry: Geometry, scrollTop: number, overscan: number): [number, number] {\n const [first, last] = inView(geometry, scrollTop);\n return [Math.max(0, first - overscan), Math.min(geometry.count, last + overscan)];\n}\n\n/** `current` with the new sizes, or `current` itself when none of them changes anything. */\nfunction withHeights(\n current: ReadonlyMap<string, number>,\n sizes: readonly (readonly [string, number])[],\n): ReadonlyMap<string, number> {\n let next: Map<string, number> | null = null;\n for (const [key, size] of sizes)\n if (current.get(key) !== size) (next ??= new Map(current)).set(key, size);\n return next ?? current;\n}\n","DatePicker/DatePicker.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DatePickerManifest: Record<string, Component> = {\n DatePicker: {\n description:\n \"Controlled Gregorian date-only picker with segmented editing, calendar overlay and a draft-validity signal for incomplete or rejected edits.\",\n props: {\n label: { type: \"string\", required: true },\n value: {\n type: \"string\",\n required: true,\n description: \"YYYY-MM-DD in years 0001–9999, or null. Values contain no time or timezone.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description:\n \"Requests a complete valid date or null after clearing. Invalid or coerced dates are not emitted.\",\n },\n onDraftValidityChange: {\n type: \"function\",\n description:\n \"Callback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. Reports initial and changed validity. False for partial, impossible, out-of-bounds or required-empty edits. Block Review/Save while false, even if value still contains a previous valid date.\",\n },\n min: { type: \"string\", description: \"Inclusive YYYY-MM-DD lower bound.\" },\n max: { type: \"string\", description: \"Inclusive YYYY-MM-DD upper bound.\" },\n disabled: { type: \"boolean\", default: false },\n readOnly: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n motion: { type: \"enum\", values: [\"auto\", \"none\"], default: \"auto\" },\n id: {\n type: \"string\",\n description:\n \"ID of the focusable date-control group. Focusing it enters the first segment; the forwarded HTMLDivElement ref has the same behavior.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"FormSection\", \"FormErrorSummary\"],\n never_inside: [\"Button\"],\n example:\n '<DatePicker id=\"inspection-date\" label=\"Inspection date\" value={date} onValueChange={setDate} onDraftValidityChange={setDateValid} required />',\n },\n};\n","DatePicker/DatePicker.tsx":"\"use client\";\n\nimport { forwardRef, useContext, useEffect, useId, useMemo, useRef } from \"react\";\nimport {\n Button,\n Calendar as AriaCalendar,\n DateFieldStateContext,\n DateInput,\n DatePicker as AriaDatePicker,\n DateSegment,\n Dialog,\n Group,\n I18nProvider,\n Label,\n Text,\n useLocale,\n type DateSegmentProps,\n} from \"react-aria-components\";\nimport { GregorianCalendar, toCalendar } from \"@internationalized/date\";\nimport { Calendar as CalendarIcon } from \"@oprt/icons\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport type { CalendarProps } from \"../Calendar/Calendar\";\nimport { CALENDAR_ROOT, CalendarContent } from \"../Calendar/Calendar\";\nimport { useMotion, type MotionMode } from \"../_internal/motion\";\nimport { MotionPopover } from \"../_internal/MotionPopover\";\nimport { POPOVER_WIDTH, SCREEN_MARGIN } from \"../_internal/overlay\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport {\n FIELD_BOX_STYLES,\n FIELD_LABEL_STYLES,\n SEGMENT_STYLES,\n SegmentGroupContext,\n firstSegmentFocus,\n useReportSegments,\n useSegmentDraft,\n} from \"../_internal/segmentField\";\nimport {\n dateConfiguration,\n dateWithinBounds,\n parseDateOnly,\n readDateSegments,\n type DateDraftSnapshot,\n} from \"../_internal/date\";\n\nexport type DatePickerProps = CalendarProps & {\n onDraftValidityChange?: (valid: boolean, details: { pending: boolean }) => void;\n motion?: MotionMode;\n};\n\nconst fieldStyles = cva(\"flex min-w-0 flex-col gap-1u font-sans text-oprt-base text-oprt-text\");\n\nfunction ObservedYear({\n segment,\n report,\n}: {\n segment: DateSegmentProps[\"segment\"];\n report: (snapshot: DateDraftSnapshot) => void;\n}) {\n const state = useContext(DateFieldStateContext)!;\n useReportSegments(state.segments, readDateSegments, report);\n return <DateSegment segment={segment} className={SEGMENT_STYLES} />;\n}\n\nexport const DatePicker = forwardRef<HTMLDivElement, DatePickerProps>(\n (\n {\n label,\n value,\n onValueChange,\n onDraftValidityChange,\n min,\n max,\n disabled = false,\n readOnly = false,\n required = false,\n id,\n className,\n motion = \"auto\",\n },\n ref,\n ) => {\n const generated = useId();\n const inputId = id ?? `oprt-date-${generated}`;\n const hydration = useHydrationRoot(inputId);\n const { locale } = useLocale();\n const gregorianLocale = useMemo(\n () => new Intl.Locale(locale, { calendar: \"gregory\" }).toString(),\n [locale],\n );\n const config = useMemo(() => dateConfiguration(value, min, max), [value, min, max]);\n const displayedValue = config.date?.toString() ?? null;\n const motionState = useMotion(motion);\n // The calendar is anchored to the date group but opened from this button, and Escape or a\n // chosen day returns focus here (DatePicker-B2M-1), not to the group's first segment.\n const calendarButton = useRef<HTMLButtonElement>(null);\n // Inside DateRangePicker or DateTimePicker: a required group marks these segments required\n // (DateRangePicker-R3), and DateTimePicker limits the calendar to the days its instant bounds\n // allow (DateTimePicker-R3), naming those bounds itself.\n const group = useContext(SegmentGroupContext);\n const calendarMin = parseDateOnly(group?.calendar?.min);\n const calendarMax = parseDateOnly(group?.calendar?.max);\n const minValue = calendarMin && calendarMin.compare(config.min) > 0 ? calendarMin : config.min;\n const maxValue = calendarMax && calendarMax.compare(config.max) < 0 ? calendarMax : config.max;\n const draft = useSegmentDraft({\n value,\n displayed: displayedValue,\n valid: value === null || !!config.date,\n inactive: disabled || readOnly || config.invalidBounds,\n });\n const draftDate = parseDateOnly(draft.snapshot.value);\n const draftValid =\n draft.snapshot.valid &&\n !draft.rejected &&\n (draft.snapshot.empty\n ? !required\n : !!draftDate && dateWithinBounds(draftDate, config.min, config.max));\n const error =\n config.error ??\n (!draftValid\n ? draft.snapshot.empty && required\n ? \"A date is required.\"\n : // The bound an edit crossed, named as for a supplied value (DatePicker-R6).\n (draft.rejection ?? \"Enter a complete, valid date within the allowed bounds.\")\n : null);\n const validityCallback = useRef(onDraftValidityChange);\n validityCallback.current = onDraftValidityChange;\n const pending = draft.pending;\n useEffect(() => validityCallback.current?.(!error, { pending }), [error, pending]);\n return (\n <I18nProvider locale={gregorianLocale}>\n <AriaDatePicker\n key={hydration.key}\n {...hydration.probeProps}\n id={inputId}\n value={config.date}\n minValue={minValue}\n maxValue={maxValue}\n granularity=\"day\"\n isDisabled={disabled || config.invalidBounds}\n isReadOnly={readOnly}\n isRequired={required || !!group?.required}\n isInvalid={!!error}\n validationBehavior=\"aria\"\n className={clsx(fieldStyles(), className)}\n onOpenChange={(open) => {\n if (open) draft.releaseBlur();\n }}\n onChange={(next) => {\n if (disabled || readOnly || config.invalidBounds || draft.blurRejected()) return;\n const date = next ? toCalendar(next, new GregorianCalendar()) : null;\n if (date && !parseDateOnly(date.toString())) {\n draft.reject();\n return;\n }\n if (date && !dateWithinBounds(date, config.min, config.max)) {\n draft.reject(dateConfiguration(date.toString(), min, max).error);\n return;\n }\n draft.accept();\n onValueChange(date?.toString() ?? null);\n }}\n >\n <Label className={FIELD_LABEL_STYLES}>\n {label}\n {/* Shown, not read: aria-required says it (DatePicker-R2). */}\n {required && <span aria-hidden=\"true\"> *</span>}\n </Label>\n <Group\n ref={ref}\n {...firstSegmentFocus(disabled)}\n className={`${FIELD_BOX_STYLES} gap-1u`}\n >\n {/* Editing keys and blur, around the segments only: moving to the calendar button\n leaves the field. */}\n <div className=\"contents\" {...draft.editProps}>\n <DateInput className=\"flex min-w-0 flex-1 items-center\">\n {(segment) =>\n segment.type === \"year\" ? (\n <ObservedYear segment={segment} report={draft.report} />\n ) : (\n <DateSegment segment={segment} className={SEGMENT_STYLES} />\n )\n }\n </DateInput>\n </div>\n <Button\n ref={calendarButton}\n // React Aria appends the field label: \"Choose date Inspection date\" (DatePicker-R2).\n aria-label=\"Choose date\"\n {...motionState.props}\n className=\"oprt-motion flex min-h-3u min-w-3u shrink-0 items-center justify-center border border-transparent text-oprt-text focus-visible:outline-hidden forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:-outline-offset-2 forced-colors:data-[focus-visible]:outline-[CanvasText] data-[hovered]:border-oprt-border data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[disabled]:cursor-not-allowed data-[disabled]:text-oprt-text-dim\"\n >\n <CalendarIcon size={16} />\n </Button>\n </Group>\n {error && (\n <Text slot=\"errorMessage\" className=\"m-0 text-oprt-error\">\n {error}\n </Text>\n )}\n {/* The shared overlay rules (DatePicker-R1, DatePicker-B2M-2): 16 px from the screen\n edges, the popover width (DC-7), and scrolling inside the height React Aria allows,\n so every day stays reachable in a short or landscape window. */}\n <MotionPopover\n motionState={motionState}\n returnFocusRef={calendarButton}\n offset={4}\n containerPadding={SCREEN_MARGIN}\n className={`z-50 ${POPOVER_WIDTH} overflow-y-auto overscroll-contain border border-oprt-border bg-oprt-surface-1 p-2u text-oprt-text`}\n >\n <Dialog className=\"outline-hidden\">\n <AriaCalendar\n createCalendar={() => new GregorianCalendar()}\n className={CALENDAR_ROOT}\n >\n <CalendarContent />\n </AriaCalendar>\n </Dialog>\n </MotionPopover>\n </AriaDatePicker>\n </I18nProvider>\n );\n },\n);\nDatePicker.displayName = \"DatePicker\";\n","DatePicker/index.ts":"export { DatePicker } from \"./DatePicker\";\nexport type { DatePickerProps } from \"./DatePicker\";\nexport { DatePickerManifest } from \"./DatePicker.manifest\";\n","DateRangePicker/DateRangePicker.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DateRangePickerManifest: Record<string, Component> = {\n DateRangePicker: {\n description:\n \"Controlled ordered Gregorian date range with independently editable start and end fields and separate endpoint calendars. Equal-day ranges are valid.\",\n props: {\n label: { type: \"string\", required: true },\n value: {\n type: \"node\",\n required: true,\n description:\n \"{ start: YYYY-MM-DD, end: YYYY-MM-DD }, or null. Dates carry no time or time zone.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description:\n \"Requests complete ordered ranges or null when both endpoints are cleared. Partial and reversed ranges are retained as invalid drafts.\",\n },\n onDraftValidityChange: {\n type: \"function\",\n description:\n \"Callback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. False for incomplete, invalid, out-of-bounds, required-empty, or unaccepted range drafts. Block Review/Save while false.\",\n },\n startLabel: { type: \"string\", default: \"Start date\" },\n endLabel: { type: \"string\", default: \"End date\" },\n min: { type: \"string\", description: \"Inclusive YYYY-MM-DD lower bound for both endpoints.\" },\n max: { type: \"string\", description: \"Inclusive YYYY-MM-DD upper bound for both endpoints.\" },\n disabled: { type: \"boolean\", default: false },\n readOnly: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n id: {\n type: \"string\",\n description:\n \"ID of the focusable group. Focusing it or its forwarded HTMLDivElement ref enters the first start-date segment.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"FormSection\", \"FormErrorSummary\", \"DatePicker\"],\n never_inside: [\"Button\"],\n example:\n '<DateRangePicker id=\"inspection-window\" label=\"Inspection window\" value={range} onValueChange={setRange} onDraftValidityChange={setRangeValid} required />',\n },\n};\n","DateRangePicker/DateRangePicker.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useRef, useState } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport type { CalendarProps } from \"../Calendar/Calendar\";\nimport { DatePicker } from \"../DatePicker/DatePicker\";\nimport { dateConfiguration, parseDateOnly } from \"../_internal/date\";\nimport { definedOnly } from \"../_internal/aria\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport {\n FIELD_COLUMNS,\n FIELD_LABEL_STYLES,\n SegmentGroupContext,\n firstSegmentFocus,\n} from \"../_internal/segmentField\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nexport type DateRangeValue = { start: string; end: string };\nexport type DateRangePickerProps = Omit<CalendarProps, \"value\" | \"onValueChange\"> & {\n value: DateRangeValue | null;\n onValueChange: (value: DateRangeValue | null) => void;\n onDraftValidityChange?: (valid: boolean, details: { pending: boolean }) => void;\n startLabel?: string;\n endLabel?: string;\n};\nconst rangeStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base text-oprt-text focus-visible:outline-hidden focus-visible:shadow-oprt-focus\",\n);\n/** The context a required range gives its two fields. */\nconst REQUIRED_GROUP = { required: true } as const;\n/** The pair as the picker compares it: by its two dates, whatever order a host writes them in. */\nconst keyOf = (value: DateRangeValue | null) =>\n value === null ? \"null\" : JSON.stringify([value.start, value.end]);\nexport const DateRangePicker = forwardRef<HTMLDivElement, DateRangePickerProps>(\n (\n {\n label,\n value,\n onValueChange,\n onDraftValidityChange,\n startLabel = \"Start date\",\n endLabel = \"End date\",\n min,\n max,\n disabled = false,\n readOnly = false,\n required = false,\n id,\n className,\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n const setRoot = useMergedRefs<HTMLDivElement>(root, ref);\n // Part ids reconciled with the markup they hydrated (fieldWiringPlan step 6).\n const ids = usePartIds([\"label\", \"error\", \"status\"], {\n id,\n readRendered: () => root.current?.id,\n });\n const base = keyOf(value);\n // An outside replacement of the pair discards both fields' local segment drafts by remounting\n // them; the picker's own accepted edit must not. Which one a new value is, is kept in state and\n // derived during render, so a second render (StrictMode, the Next.js development default) comes\n // to the same answer (DateRangePicker-R2).\n const [replacement, setReplacement] = useState<{\n base: string;\n generation: number;\n requested: string | null;\n }>({ base, generation: 0, requested: null });\n if (replacement.base !== base) {\n setReplacement({\n base,\n generation: replacement.generation + (replacement.requested === base ? 0 : 1),\n requested: null,\n });\n }\n const request = (next: DateRangeValue | null) => {\n const key = keyOf(next);\n setReplacement((previous) => ({ ...previous, requested: key }));\n onValueChange(next);\n };\n const initial = {\n base,\n start: value?.start ?? null,\n end: value?.end ?? null,\n startValid: true,\n endValid: true,\n startPending: false,\n endPending: false,\n };\n const [draft, setDraft] = useState(initial);\n const current = draft.base === base ? draft : initial;\n const start = parseDateOnly(current.start);\n const end = parseDateOnly(current.end);\n const startConfig = dateConfiguration(current.start, min, max);\n const endConfig = dateConfiguration(current.end, min, max);\n const empty = current.start === null && current.end === null;\n const pending =\n current.startPending ||\n current.endPending ||\n current.start !== (value?.start ?? null) ||\n current.end !== (value?.end ?? null);\n const error =\n startConfig.error ||\n endConfig.error ||\n (!current.startValid || !current.endValid\n ? \"Complete both date fields before continuing.\"\n : empty\n ? required\n ? \"A date range is required.\"\n : null\n : !start || !end\n ? \"Enter both a start date and an end date.\"\n : start.compare(end) > 0\n ? \"Start date must be on or before end date.\"\n : null);\n const callback = useRef(onDraftValidityChange);\n callback.current = onDraftValidityChange;\n useEffect(() => callback.current?.(!error && !pending, { pending }), [error, pending]);\n const setEndpoint = (part: \"start\" | \"end\", next: string | null) => {\n if (disabled || readOnly) return;\n const nextDraft = { ...current, [part]: next };\n setDraft(nextDraft);\n // A valid endpoint cannot publish the other endpoint's older committed\n // value while that other field contains an incomplete local segment draft.\n if (!(part === \"start\" ? current.endValid : current.startValid)) return;\n if (nextDraft.start === null && nextDraft.end === null) {\n request(null);\n return;\n }\n const nextStart = parseDateOnly(nextDraft.start);\n const nextEnd = parseDateOnly(nextDraft.end);\n if (\n !nextStart ||\n !nextEnd ||\n nextStart.compare(nextEnd) > 0 ||\n dateConfiguration(nextDraft.start, min, max).error ||\n dateConfiguration(nextDraft.end, min, max).error\n )\n return;\n request({ start: nextDraft.start!, end: nextDraft.end! });\n };\n const reportEndpoint = (part: \"start\" | \"end\", valid: boolean, pending: boolean) =>\n setDraft((previous) => {\n const active = previous.base === base ? previous : initial;\n const validityKey = part === \"start\" ? \"startValid\" : \"endValid\";\n const pendingKey = part === \"start\" ? \"startPending\" : \"endPending\";\n return active[validityKey] === valid && active[pendingKey] === pending\n ? previous\n : { ...active, [validityKey]: valid, [pendingKey]: pending };\n });\n return (\n <div\n id={ids.base}\n ref={setRoot}\n role=\"group\"\n aria-labelledby={ids.label}\n aria-describedby={joinIds(error && ids.error, !error && pending && ids.status)}\n aria-invalid={!!error || undefined}\n className={clsx(rangeStyles(), className)}\n {...firstSegmentFocus<HTMLDivElement>(disabled)}\n >\n <p id={ids.label} className={FIELD_LABEL_STYLES}>\n {label}\n {/* Shown, not read: the segments carry aria-required (DatePicker-R2). */}\n {required && <span aria-hidden=\"true\"> *</span>}\n </p>\n {/* A required range marks every segment required; the group alone says the range is\n missing (DateRangePicker-R3). */}\n <SegmentGroupContext.Provider value={required ? REQUIRED_GROUP : null}>\n <div key={replacement.generation} className={FIELD_COLUMNS}>\n <DatePicker\n className=\"min-w-0 flex-1\"\n label={startLabel}\n value={current.start}\n onValueChange={(next) => setEndpoint(\"start\", next)}\n onDraftValidityChange={(valid, details) =>\n reportEndpoint(\"start\", valid, details.pending)\n }\n disabled={disabled}\n readOnly={readOnly}\n {...definedOnly({ min, max })}\n />\n <DatePicker\n className=\"min-w-0 flex-1\"\n label={endLabel}\n value={current.end}\n onValueChange={(next) => setEndpoint(\"end\", next)}\n onDraftValidityChange={(valid, details) =>\n reportEndpoint(\"end\", valid, details.pending)\n }\n disabled={disabled}\n readOnly={readOnly}\n {...definedOnly({ min, max })}\n />\n </div>\n </SegmentGroupContext.Provider>\n {error && (\n <p id={ids.error} className=\"m-0 text-oprt-error\">\n {error}\n </p>\n )}\n {!error && pending && (\n <p id={ids.status} className=\"m-0 text-oprt-text-dim\">\n The range change has not been applied.\n </p>\n )}\n </div>\n );\n },\n);\nDateRangePicker.displayName = \"DateRangePicker\";\n","DateRangePicker/index.ts":"export { DateRangePicker } from \"./DateRangePicker\";\nexport type { DateRangePickerProps, DateRangeValue } from \"./DateRangePicker\";\nexport { DateRangePickerManifest } from \"./DateRangePicker.manifest\";\n","DateTimePicker/DateTimePicker.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DateTimePickerManifest: Record<string, Component> = {\n DateTimePicker: {\n description:\n \"Controlled instant editor with an explicit IANA time zone, minute-level date/time drafts, Apply, and explicit daylight-saving overlap choices.\",\n props: {\n label: { type: \"string\", required: true },\n value: {\n type: \"number\",\n required: true,\n description:\n \"Integer epoch milliseconds, or null. Untouched values retain their supplied seconds and milliseconds.\",\n },\n timeZone: {\n type: \"string\",\n required: true,\n description:\n \"IANA time zone used to display and resolve the wall-time draft. Visible beside the fields.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description:\n \"Requests an instant or null only after Apply. Minute-level edits commit with zero seconds and milliseconds.\",\n },\n onDraftValidityChange: {\n type: \"function\",\n description:\n \"Callback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. False for invalid, incomplete, or unapplied edits. Returns true when the host accepts Apply or supplies another valid value. Block Review/Save while false.\",\n },\n min: { type: \"number\", description: \"Inclusive epoch-millisecond lower bound.\" },\n max: { type: \"number\", description: \"Inclusive epoch-millisecond upper bound.\" },\n disabled: { type: \"boolean\", default: false },\n readOnly: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n id: {\n type: \"string\",\n description:\n \"ID of the focusable group. Focusing it or its forwarded HTMLDivElement ref enters the first date segment.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"FormSection\", \"FormErrorSummary\", \"DatePicker\", \"TimeField\"],\n never_inside: [\"Button\"],\n example:\n '<DateTimePicker id=\"window-start\" label=\"Window start\" value={instant} timeZone=\"America/Los_Angeles\" onValueChange={setInstant} onDraftValidityChange={setInstantValid} required />',\n },\n};\n","DateTimePicker/DateTimePicker.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useMemo, useRef, useState, type FocusEvent } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { DatePicker } from \"../DatePicker/DatePicker\";\nimport { TimeField } from \"../TimeField/TimeField\";\nimport { Button } from \"../Button/Button\";\nimport { RadioGroup, Radio } from \"../RadioGroup/RadioGroup\";\nimport { instantWallTime, resolveWallTime, validInstant, validTimeZone } from \"../_internal/date\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport {\n FIELD_COLUMNS,\n FIELD_LABEL_STYLES,\n SegmentGroupContext,\n firstSegmentFocus,\n type SegmentGroup,\n} from \"../_internal/segmentField\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { formatUtcTimestamp } from \"../_internal/timestamp\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\nexport type DateTimePickerProps = {\n label: string;\n value: number | null;\n onValueChange: (value: number | null) => void;\n onDraftValidityChange?: (valid: boolean, details: { pending: boolean }) => void;\n timeZone: string;\n min?: number;\n max?: number;\n disabled?: boolean;\n readOnly?: boolean;\n required?: boolean;\n id?: string;\n className?: string;\n};\nconst pickerStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base text-oprt-text focus-visible:outline-hidden focus-visible:shadow-oprt-focus\",\n);\nexport const DateTimePicker = forwardRef<HTMLDivElement, DateTimePickerProps>(\n (\n {\n label,\n value,\n onValueChange,\n onDraftValidityChange,\n timeZone,\n min,\n max,\n disabled = false,\n readOnly = false,\n required = false,\n id,\n className,\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n const setRoot = useMergedRefs<HTMLDivElement>(root, ref);\n // Part ids reconciled with the markup they hydrated, so the time-zone line, rendered once, and\n // the description rewritten with each error agree after a hydration id mismatch\n // (DateTimePicker-R5, fieldWiringPlan step 6).\n const ids = usePartIds([\"label\", \"zone\", \"error\", \"status\"], {\n id,\n readRendered: () => root.current?.id,\n });\n // A JavaScript caller or an agent can pass something that is not a string: show the message,\n // never throw while printing it (DateTimePicker-R6).\n const zoneName = typeof timeZone === \"string\" ? timeZone : \"invalid\";\n const base = `${String(value)}|${String(timeZone)}`;\n const wall = useMemo(() => instantWallTime(value, timeZone), [value, timeZone]);\n const initial = {\n base,\n date: wall.date,\n time: wall.time,\n occurrence: wall.occurrence,\n dateValid: true,\n timeValid: true,\n dirty: false,\n };\n const [draft, setDraft] = useState(initial);\n const current = draft.base === base ? draft : initial;\n const zoneValid = validTimeZone(timeZone);\n const boundsValid =\n (min === undefined || validInstant(min)) &&\n (max === undefined || validInstant(max)) &&\n !(min !== undefined && max !== undefined && min > max);\n const resolution =\n current.date && current.time ? resolveWallTime(current.date, current.time, timeZone) : null;\n const ambiguous = resolution?.status === \"valid\" && resolution.earlier !== resolution.later;\n const candidate =\n resolution?.status === \"valid\"\n ? current.occurrence === \"later\"\n ? resolution.later\n : resolution.earlier\n : null;\n const empty = current.date === null && current.time === null;\n const checkedInstant = current.dirty ? candidate : value;\n const error = !zoneValid\n ? \"Invalid time zone. Supply an IANA time zone.\"\n : !boundsValid\n ? \"Invalid instant bounds. Supply finite epoch milliseconds in ascending order.\"\n : !current.dirty && wall.error\n ? wall.error\n : !current.dateValid || !current.timeValid\n ? \"Complete the date and time before applying this change.\"\n : empty\n ? required\n ? \"A date and time are required.\"\n : null\n : !current.date || !current.time\n ? \"Enter both a date and a time.\"\n : resolution?.status === \"invalid\"\n ? resolution.error\n : ambiguous && current.occurrence === null\n ? \"Choose the earlier or later occurrence of this repeated time.\"\n : checkedInstant !== null && min !== undefined && checkedInstant < min\n ? `Choose an instant at or after ${formatUtcTimestamp(min)}.`\n : checkedInstant !== null && max !== undefined && checkedInstant > max\n ? `Choose an instant at or before ${formatUtcTimestamp(max)}.`\n : null;\n // The date's calendar offers only the days that hold an instant within the bounds: the wall\n // dates of the bounds in this zone. The picker names the bounds itself, so the date field adds\n // no message of its own (DateTimePicker-R3). A required picker marks every segment required\n // and says once that a value is missing (DateRangePicker-R3).\n const group = useMemo<SegmentGroup>(() => {\n const dayOf = (bound: number | undefined) =>\n bound !== undefined && zoneValid && boundsValid\n ? (instantWallTime(bound, timeZone).date ?? undefined)\n : undefined;\n return { required, calendar: { min: dayOf(min), max: dayOf(max) } };\n }, [required, min, max, timeZone, zoneValid, boundsValid]);\n // When the application accepts Apply, the focused Apply becomes unavailable: focus stays in the\n // picker, on its group, instead of falling to the page (DateTimePicker-R4). The group keeps\n // that focus rather than passing it to the first segment.\n const restoring = useRef(false);\n const restore = useRestoreFocusOnRemoval(root, {\n findTarget: () => {\n restoring.current = true;\n queueMicrotask(() => {\n restoring.current = false;\n });\n return root.current;\n },\n });\n const keepFocus = (event: FocusEvent<HTMLDivElement>) => {\n if (restoring.current) {\n restoring.current = false;\n return true;\n }\n // Focus that comes from inside the picker stays where it lands.\n const from = event.relatedTarget;\n return from instanceof Node && event.currentTarget.contains(from);\n };\n const callback = useRef(onDraftValidityChange);\n callback.current = onDraftValidityChange;\n useEffect(\n () => callback.current?.(!error && !current.dirty, { pending: current.dirty }),\n [error, current.dirty],\n );\n const edit = (part: \"date\" | \"time\", next: string | null) => {\n if (disabled || readOnly) return;\n setDraft({ ...current, [part]: next, dirty: true, occurrence: null });\n };\n const reportValidity = (part: \"dateValid\" | \"timeValid\", valid: boolean) =>\n setDraft((previous) => {\n const active = previous.base === base ? previous : initial;\n if (active[part] === valid) return previous;\n return { ...active, [part]: valid, dirty: active.dirty || !valid };\n });\n return (\n <div\n ref={setRoot}\n id={ids.base}\n role=\"group\"\n aria-labelledby={ids.label}\n aria-describedby={joinIds(\n ids.zone,\n error && ids.error,\n !error && current.dirty && ids.status,\n )}\n aria-invalid={!!error || undefined}\n className={clsx(pickerStyles(), className)}\n onFocusCapture={restore.onFocusCapture}\n onBlurCapture={restore.onBlurCapture}\n {...firstSegmentFocus<HTMLDivElement>(disabled, keepFocus)}\n >\n <p id={ids.label} className={FIELD_LABEL_STYLES}>\n {label}\n {/* Shown, not read: the segments carry aria-required (DatePicker-R2). */}\n {required && <span aria-hidden=\"true\"> *</span>}\n </p>\n {/* A long zone name breaks anywhere rather than run out of the picker (DateTimePicker-B2M-1). */}\n <p id={ids.zone} className=\"m-0 text-oprt-text-dim oprt-wrap-anywhere\">\n Time zone: <span>{zoneName}</span>\n </p>\n <SegmentGroupContext.Provider value={group}>\n <div key={base} className={FIELD_COLUMNS}>\n <DatePicker\n label={`${label} date`}\n value={current.date}\n onValueChange={(next) => edit(\"date\", next)}\n onDraftValidityChange={(valid) => reportValidity(\"dateValid\", valid)}\n disabled={disabled || !zoneValid || !boundsValid}\n readOnly={readOnly}\n className=\"min-w-0 flex-1\"\n />\n <TimeField\n label={`${label} time`}\n value={current.time}\n onValueChange={(next) => edit(\"time\", next)}\n onDraftValidityChange={(valid) => reportValidity(\"timeValid\", valid)}\n disabled={disabled || !zoneValid || !boundsValid}\n readOnly={readOnly}\n className=\"min-w-0 flex-1\"\n />\n </div>\n </SegmentGroupContext.Provider>\n {ambiguous && (\n <div className=\"flex flex-col gap-1u\">\n <p className=\"m-0 text-oprt-text-dim oprt-wrap-anywhere\">\n This wall time occurs twice in {zoneName}.\n </p>\n <RadioGroup\n aria-label=\"Repeated time occurrence\"\n value={current.occurrence ?? \"\"}\n disabled={disabled || readOnly}\n onValueChange={(occurrence) => {\n if (!disabled && !readOnly && (occurrence === \"earlier\" || occurrence === \"later\"))\n setDraft({ ...current, occurrence, dirty: true });\n }}\n >\n {/* DC-4: an instant as YYYY-MM-DD HH:MM:SS UTC, never raw ISO (DateTimePicker-R2). */}\n <Radio value=\"earlier\">\n Earlier occurrence (\n {formatUtcTimestamp(resolution!.status === \"valid\" ? resolution!.earlier : 0)})\n </Radio>\n <Radio value=\"later\">\n Later occurrence (\n {formatUtcTimestamp(resolution!.status === \"valid\" ? resolution!.later : 0)})\n </Radio>\n </RadioGroup>\n </div>\n )}\n {error && (\n <p id={ids.error} className=\"m-0 text-oprt-error\">\n {error}\n </p>\n )}\n {!error && current.dirty && (\n <p id={ids.status} className=\"m-0 text-oprt-text-dim\">\n Apply this change before continuing.\n </p>\n )}\n <Button\n className=\"self-start\"\n disabled={disabled || readOnly || !current.dirty || !!error}\n onClick={() => {\n if (disabled || readOnly || error || !current.dirty) return;\n const next = empty ? null : candidate;\n // Matching the controlled value needs no host update; it already represents this draft.\n if (next === value) setDraft({ ...current, dirty: false });\n onValueChange(next);\n }}\n >\n Apply\n </Button>\n </div>\n );\n },\n);\nDateTimePicker.displayName = \"DateTimePicker\";\n","DateTimePicker/index.ts":"export { DateTimePicker } from \"./DateTimePicker\";\nexport type { DateTimePickerProps } from \"./DateTimePicker\";\nexport { DateTimePickerManifest } from \"./DateTimePicker.manifest\";\n","DescriptionList/DescriptionList.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DescriptionListManifest: Record<string, Component> = {\n DescriptionList: {\n description:\n \"Semantic label/value facts with wrapping, exact values, and stacked, responsive aligned, or compact detail layouts.\",\n props: {\n items: {\n type: \"array\",\n required: true,\n description:\n \"Readonly DescriptionListItem array. IDs are stable unique keys within this list, never DOM IDs. Direct booleans render as true/false; zero and other ReactNode values are preserved. Supply explicit text for missing values (development warns for null, undefined or empty text) and include units in the value. A value that is a number, optionally followed by its unit, wraps only between digit groups and keeps the unit with its last group; a word after it that is not a unit wraps as text, and more than 15 ungrouped digits are an identifier that wraps anywhere.\",\n items: {\n id: \"string (required, unique)\",\n label: \"ReactNode (required)\",\n value: \"ReactNode (required)\",\n mono: \"boolean (optional)\",\n },\n },\n layout: {\n type: \"enum\",\n values: [\"stacked\", \"aligned\", \"details\"],\n default: \"stacked\",\n description:\n \"Stacked places each value below its label. Aligned uses columns when space permits and stacks in narrow containers. Details keeps 40/60 columns with 9px medium uppercase muted labels, 11px right-aligned values, baseline alignment, 8px row spacing and a 16px column gap. Long content wraps in either column.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\n \"EntityPreview\",\n \"OperationalStatus\",\n \"Inspector\",\n \"Popover\",\n \"Card\",\n \"Link\",\n \"CopyButton\",\n ],\n never_inside: [\"Button\", \"Badge\", \"Link\"],\n example:\n '<DescriptionList layout=\"aligned\" items={[{ id: \"pressure\", label: \"Pressure\", value: \"0 kPa\", mono: true }, { id: \"enabled\", label: \"Enabled\", value: false }]} />',\n },\n};\n","DescriptionList/DescriptionList.tsx":"\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { forwardRef, useEffect, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { DescriptionListManifest } from \"./DescriptionList.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { NumberText, isNumberText } from \"../_internal/numberText\";\n\nconst listStyles = cva(\"m-0 min-w-0 font-sans text-oprt-base text-oprt-text\", {\n variants: {\n layout: {\n stacked: \"flex flex-col gap-2u\",\n aligned: \"flex flex-col gap-2u\",\n details: \"flex flex-col gap-1u\",\n },\n },\n defaultVariants: { layout: \"stacked\" },\n});\n\nconst itemStyles = cva(\"grid min-w-0\", {\n variants: {\n layout: {\n stacked: \"grid-cols-1 gap-1u\",\n aligned: \"grid-cols-[repeat(auto-fit,minmax(min(100%,8rem),1fr))] gap-y-1u gap-x-3u\",\n details: \"grid-cols-[minmax(0,2fr)_minmax(0,3fr)] items-baseline gap-y-1u gap-x-2u\",\n },\n },\n defaultVariants: { layout: \"stacked\" },\n});\n\nconst labelStyles = cva(\n \"m-0 min-w-0 oprt-wrap-anywhere text-oprt-text-dim uppercase tracking-oprt-sm\",\n {\n variants: {\n layout: { stacked: \"\", aligned: \"\", details: \"text-oprt-sm font-medium\" },\n },\n defaultVariants: { layout: \"stacked\" },\n },\n);\n\n// Text breaks anywhere when it must (R4: identifiers are never cut); a number breaks only between\n// its digit groups and keeps its unit with the last group (DC-11, through NumberText). A badge value\n// sits on the row's baseline like text, so a wrapped status keeps its label level with its first\n// line (Badge centers itself otherwise).\nconst valueStyles = cva(\n \"m-0 min-w-0 oprt-wrap-anywhere normal-case tracking-normal [&>[data-oprt-badge]]:align-baseline\",\n {\n variants: {\n layout: { stacked: \"\", aligned: \"\", details: \"text-right\" },\n },\n defaultVariants: { layout: \"stacked\" },\n },\n);\n\nexport type DescriptionListItem = {\n id: string;\n label: ReactNode;\n value: ReactNode;\n mono?: boolean;\n};\n\nexport type DescriptionListProps = Omit<HTMLAttributes<HTMLDListElement>, \"children\"> & {\n items: readonly DescriptionListItem[];\n layout?: \"stacked\" | \"aligned\" | \"details\";\n};\n\n/** A value the caller left out: the term would stand alone, on screen and for a screen reader. */\nconst isMissing = (value: ReactNode) => value === null || value === undefined || value === \"\";\n\nexport const DescriptionList = forwardRef<HTMLDListElement, DescriptionListProps>(\n ({ items, layout = \"stacked\", className, ...props }, ref) => {\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"DescriptionList\", DescriptionListManifest.DescriptionList!.props, { layout });\n }\n\n // Development: name each item whose value is missing, once per list (DescriptionList-R4).\n // Rendering is unchanged; zero and false are values.\n const reported = useRef(new Set<string>());\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n const fresh = items\n .filter(({ id, value }) => isMissing(value) && !reported.current.has(id))\n .map(({ id }) => id);\n if (fresh.length === 0) return;\n for (const id of fresh) reported.current.add(id);\n console.warn(\n JSON.stringify(\n {\n component: \"DescriptionList\",\n warning: \"MISSING_VALUE\",\n prop: \"items\",\n received: `value of ${fresh.map((id) => JSON.stringify(id)).join(\", \")}`,\n suggestion:\n \"Supply explicit text for a missing value, such as Not supplied: an empty value leaves its term standing alone, on screen and for a screen reader. Zero and false are shown as they are.\",\n },\n null,\n 2,\n ),\n );\n }, [items]);\n\n return (\n <dl\n ref={ref}\n className={clsx(listStyles({ layout }), className)}\n data-layout={layout}\n {...props}\n >\n {items.map(({ id, label, value, mono }) => (\n <div key={id} className={itemStyles({ layout })}>\n <dt className={labelStyles({ layout })}>{label}</dt>\n <dd className={clsx(valueStyles({ layout }), mono && \"font-mono\")}>\n {typeof value === \"boolean\" ? (\n String(value)\n ) : isNumberText(value) ? (\n <NumberText value={value} />\n ) : (\n value\n )}\n </dd>\n </div>\n ))}\n </dl>\n );\n },\n);\nDescriptionList.displayName = \"DescriptionList\";\n","DescriptionList/index.ts":"export {\n DescriptionList,\n type DescriptionListProps,\n type DescriptionListItem,\n} from \"./DescriptionList\";\nexport { DescriptionListManifest } from \"./DescriptionList.manifest\";\n","Dialog/Dialog.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DialogManifest: Record<string, Component> = {\n Dialog: {\n description:\n \"Modal dialog with a flat API: pass title, optional description, trigger, footer, and body children. Portal'd overlay + content built on React Aria Components (ModalOverlay + Modal + Dialog).\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer/touch feedback. None disables motion; keyboard and system reduced motion are always immediate.\",\n },\n title: { type: \"string\", required: true },\n description: { type: \"string\" },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n open: { type: \"boolean\" },\n defaultOpen: { type: \"boolean\" },\n onOpenChange: { type: \"function\" },\n },\n slots: {\n trigger: { type: \"focusable element | text (text renders a Button)\" },\n children: { type: \"any\", required: true },\n footer: { type: \"Button | ButtonGroup\" },\n },\n composable_with: [\"Button\", \"ButtonGroup\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<Dialog\\n title=\"DEPLOY UNIT\"\\n description=\"Confirm deployment to the selected theater.\"\\n trigger={<Button variant=\"primary\">DEPLOY</Button>}\\n footer={\\n <>\\n <Button variant=\"secondary\">CANCEL</Button>\\n <Button variant=\"primary\">CONFIRM</Button>\\n </>\\n }\\n>\\n <p>This action cannot be undone.</p>\\n</Dialog>',\n },\n};\n","Dialog/Dialog.tsx":"\"use client\";\n\nimport { X } from \"@oprt/icons\";\nimport {\n DialogTrigger,\n Dialog as AriaDialog,\n ModalOverlay,\n Modal,\n Heading,\n Text,\n Button as AriaButton,\n Pressable,\n} from \"react-aria-components\";\nimport { mergeProps } from \"react-aria\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n forwardRef,\n cloneElement,\n useEffect,\n useId,\n useRef,\n useState,\n useLayoutEffect,\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ReactNode,\n type RefObject,\n type ReactElement,\n type DOMAttributes,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { DialogManifest } from \"./Dialog.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly } from \"../_internal/aria\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport {\n useMotion,\n composeMotionHandlers,\n MotionContext,\n type MotionMode,\n} from \"../_internal/motion\";\nimport { isSingleElement } from \"../_internal/overlay\";\n\n/**\n * React Aria's Pressable clones a single child element; its `children` type is\n * narrower than our public `trigger: ReactNode`.\n */\ntype PressableChild = ComponentProps<typeof Pressable>[\"children\"];\n/** Pressable forwards unknown DOM props to the child but does not type them. */\ntype PressableExtraProps = Partial<ComponentProps<typeof Pressable>>;\n\n// The modal fits the overlay's content box (the visible viewport less its margin) and lays out as\n// a column, so the dialog inside can shrink and scroll its body (design call DC-5).\nconst surfaceStyles = cva(\"oprt-motion-surface relative z-50 flex max-h-full w-full flex-col\", {\n variants: {\n size: {\n sm: \"max-w-sm\",\n md: \"max-w-md\",\n lg: \"max-w-lg\",\n },\n },\n defaultVariants: { size: \"md\" },\n});\n\n/** A retained surface does not remount when a close reverses. */\nfunction DialogReentryFocus({\n open,\n dialog,\n children,\n}: {\n open: boolean;\n dialog: RefObject<HTMLElement | null>;\n children: ReactNode;\n}) {\n const wasOpen = useRef(open);\n useLayoutEffect(() => {\n if (open && !wasOpen.current && !dialog.current?.contains(document.activeElement)) {\n dialog.current?.focus({ preventScroll: true });\n }\n wasOpen.current = open;\n }, [open, dialog]);\n return <>{children}</>;\n}\n\n/**\n * Whether `element` is scrolling its content: then it needs a keyboard path and a name (R6).\n * Watches the box and the content inside it, since either can change on its own.\n */\nfunction useScrolls(\n element: RefObject<HTMLElement | null>,\n content: RefObject<HTMLElement | null>,\n): boolean {\n const [scrolls, setScrolls] = useState(false);\n useEffect(() => {\n const box = element.current;\n if (!box || typeof ResizeObserver === \"undefined\") return;\n const measure = () => setScrolls(box.scrollHeight > box.clientHeight + 1);\n const observer = new ResizeObserver(measure);\n observer.observe(box);\n if (content.current) observer.observe(content.current);\n measure();\n return () => observer.disconnect();\n }, [element, content]);\n return scrolls;\n}\n\n/**\n * The dialog's middle: description and body. It takes the height the pinned title and footer\n * leave and scrolls inside it, as a named, focusable region while it scrolls.\n */\nfunction DialogBody({ title, children }: { title: string; children: ReactNode }) {\n const body = useRef<HTMLDivElement>(null);\n const content = useRef<HTMLDivElement>(null);\n const scrolls = useScrolls(body, content);\n return (\n <div\n ref={body}\n data-oprt-dialog-body=\"\"\n // The gutter (padding offset by a negative margin) keeps the focus ring of a control at the\n // edge, such as ConfirmDialog's phrase field, inside the clipping scroll box.\n className=\"-m-0.5u min-h-0 flex-1 overflow-y-auto overscroll-contain p-0.5u outline-hidden focus-visible:shadow-oprt-focus forced-colors:focus-visible:outline-solid forced-colors:focus-visible:outline-2 forced-colors:focus-visible:-outline-offset-2 forced-colors:focus-visible:outline-[CanvasText]\"\n {...(scrolls ? { tabIndex: 0, role: \"region\", \"aria-label\": `${title} content` } : {})}\n >\n <div ref={content} className=\"flex flex-col gap-3u\">\n {children}\n </div>\n </div>\n );\n}\n\n/**\n * React Aria's Dialog renders only global attributes, pointer events and its own handlers. The\n * props type accepts every div attribute, so add back what it dropped: keyboard, focus and input\n * handlers, aria-* states, and anything it left undefined (it always renders `style` and `slot`\n * keys). A handler it did render runs once; one it has its own of is chained.\n */\nfunction withDroppedProps(\n rendered: DOMAttributes<HTMLElement> & Record<string, unknown>,\n accepted: Record<string, unknown>,\n) {\n const dropped: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(accepted)) {\n if (value === undefined) continue;\n if (!(key in rendered) || rendered[key] === undefined) dropped[key] = value;\n else if (typeof value === \"function\" && rendered[key] !== value) dropped[key] = value;\n }\n return mergeProps(rendered, dropped);\n}\n\nexport type DialogProps = Omit<ComponentPropsWithoutRef<\"div\">, \"size\" | \"title\"> &\n VariantProps<typeof surfaceStyles> & {\n motion?: MotionMode;\n title: string;\n description?: string;\n trigger?: ReactNode;\n footer?: ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n };\n\n/**\n * The forwarded ref receives the `<section role=\"dialog\">` React Aria renders, so it is typed\n * HTMLElement (it was HTMLDivElement, which never matched; object and callback refs typed for a\n * div are still accepted).\n */\nexport const Dialog = forwardRef<HTMLElement, DialogProps>(\n (\n {\n title,\n motion = \"auto\",\n description,\n trigger,\n footer,\n open,\n defaultOpen,\n onOpenChange,\n size = \"md\",\n className,\n children,\n ...props\n },\n ref,\n ) => {\n const motionState = useMotion(motion);\n const dialogRef = useRef<HTMLElement | null>(null);\n const setDialogRef = useMergedRefs<HTMLElement>(dialogRef, ref);\n // exactOptionalPropertyTypes forbids forwarding an explicit `undefined` for\n // an optional React Aria prop, so drop the undefined entries entirely.\n const rootProps = definedOnly({\n isOpen: open,\n defaultOpen,\n onOpenChange,\n });\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Dialog\", DialogManifest.Dialog!.props, { ...props, size });\n }\n\n // React Aria only wires `Text slot=\"description\"` to aria-describedby for\n // role=\"alertdialog\" (see useDialog: contentId is dropped for plain\n // dialogs). Radix's Dialog.Description always described the content, so\n // wire it by hand rather than lose the announcement.\n const generatedId = useId();\n const describedBy = description ? `${generatedId}description` : undefined;\n // What React Aria itself reads to name and describe the dialog; everything else the props type\n // accepts reaches the dialog element through withDroppedProps.\n const naming = definedOnly({\n id: props.id,\n role: props.role === \"alertdialog\" ? (\"alertdialog\" as const) : undefined,\n \"aria-label\": props[\"aria-label\"],\n \"aria-labelledby\": props[\"aria-labelledby\"],\n \"aria-describedby\": props[\"aria-describedby\"] ?? describedBy,\n });\n\n const overlay = (\n // With a trigger the open state is owned by DialogTrigger and read from\n // context; standalone the ModalOverlay owns it. `isDismissable` restores\n // the Radix behavior of closing on an outside click (React Aria opts out\n // by default). Escape is handled by React Aria unless explicitly disabled.\n // The overlay is as tall as the visible viewport React Aria measures, so the dialog also\n // fits above an on-screen keyboard.\n <ModalOverlay\n {...(trigger ? {} : rootProps)}\n isDismissable\n className=\"oprt-motion oprt-motion-overlay fixed inset-x-0 top-0 z-40 flex h-[var(--visual-viewport-height,100dvh)] items-center justify-center p-2u bg-oprt-bg/80\"\n {...motionState.props}\n render={(domProps) => <div {...domProps} {...motionState.props} />}\n >\n {/* Animate the real Modal box; keep the public ref on the named dialog. */}\n <Modal className={surfaceStyles({ size })}>\n {({ state }) => (\n <DialogReentryFocus open={state.isOpen} dialog={dialogRef}>\n <AriaDialog\n ref={setDialogRef}\n className={clsx(\n \"flex min-h-0 w-full flex-col gap-3u bg-oprt-surface-1 border border-oprt-border p-4u font-sans focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-hidden forced-colors:focus-visible:outline-solid forced-colors:focus-visible:outline-2 forced-colors:focus-visible:-outline-offset-2 forced-colors:focus-visible:outline-[CanvasText]\",\n className,\n )}\n {...naming}\n render={(domProps) => (\n <section {...withDroppedProps(domProps as Record<string, unknown>, props)} />\n )}\n >\n {/* Title and Close stay pinned; pr-2u keeps a long title clear of Close. */}\n <Heading\n slot=\"title\"\n className=\"m-0 min-w-0 shrink-0 pr-2u text-oprt-lg uppercase tracking-oprt-lg text-oprt-text oprt-wrap-anywhere\"\n >\n {title}\n </Heading>\n <AriaButton\n slot=\"close\"\n aria-label=\"Close\"\n className=\"absolute right-2u top-2u inline-flex items-center justify-center w-3u h-oprt-sm border border-transparent text-oprt-text-dim hover:text-oprt-text outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus\"\n >\n <X size={12} />\n </AriaButton>\n <DialogBody title={title}>\n {description && (\n <Text\n slot=\"description\"\n elementType=\"p\"\n id={describedBy}\n className=\"m-0 min-w-0 text-oprt-base normal-case text-oprt-text-dim oprt-wrap-anywhere\"\n >\n {description}\n </Text>\n )}\n {children}\n </DialogBody>\n {footer && (\n <div className=\"flex shrink-0 flex-wrap justify-end gap-2u\">{footer}</div>\n )}\n </AriaDialog>\n </DialogReentryFocus>\n )}\n </Modal>\n </ModalOverlay>\n );\n\n if (!trigger)\n return <MotionContext.Provider value={motionState.mode}>{overlay}</MotionContext.Provider>;\n\n // One element is cloned as it is; plain text (or a number, fragment or list) becomes the\n // label of a Button instead of reaching React Aria, which would throw.\n const triggerElement = (\n isSingleElement(trigger) ? trigger : <Button>{trigger}</Button>\n ) as ReactElement<DOMAttributes<HTMLElement>>;\n\n return (\n <MotionContext.Provider value={motionState.mode}>\n <DialogTrigger {...rootProps}>\n {/* Pressable is React Aria's equivalent of Radix's `asChild` trigger:\n it clones the single child and merges the press + ARIA trigger props\n (aria-expanded / aria-controls) onto it. React Aria deliberately\n omits aria-haspopup for non-menu overlays, so re-add the value Radix\n emitted to keep the trigger's DOM contract unchanged. */}\n <Pressable {...({ \"aria-haspopup\": \"dialog\" } as PressableExtraProps)}>\n {\n cloneElement(\n triggerElement,\n composeMotionHandlers(motionState.props, triggerElement.props),\n ) as PressableChild\n }\n </Pressable>\n {overlay}\n </DialogTrigger>\n </MotionContext.Provider>\n );\n },\n);\nDialog.displayName = \"Dialog\";\n","Dialog/index.ts":"export { Dialog, type DialogProps } from \"./Dialog\";\nexport { DialogManifest } from \"./Dialog.manifest\";\n","Divider/Divider.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DividerManifest: Record<string, Component> = {\n Divider: {\n description:\n \"Thin rule that separates content, with an optional centered uppercase label (horizontal only). A horizontal rule takes the width of its container and a vertical one the height of its flex row. A long label wraps between its two lines and breaks anywhere. A semantic labeled divider is named by its label.\",\n props: {\n orientation: {\n type: \"enum\",\n values: [\"horizontal\", \"vertical\"],\n default: \"horizontal\",\n description:\n \"The direction of the rule. A vertical rule stretches to the height of its flex row, so put it in a flex row or give it a height.\",\n },\n decorative: { type: \"boolean\", default: true },\n label: { type: \"string\" },\n },\n composable_with: [\"Card\", \"Sidebar\"],\n example: \"<Divider />\",\n },\n};\n","Divider/Divider.tsx":"\"use client\";\n\nimport { Separator } from \"react-aria-components\";\nimport { forwardRef, type ComponentPropsWithoutRef, type Ref } from \"react\";\nimport clsx from \"clsx\";\nimport { DividerManifest } from \"./Divider.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\nexport type DividerProps = Omit<ComponentPropsWithoutRef<\"div\">, \"children\"> & {\n /** Visual only when true (role=\"none\"); exposes role=\"separator\" when false. */\n decorative?: boolean;\n /**\n * The direction of the rule. A horizontal rule takes the width of its container; a vertical one\n * takes the height of its flex row, so it needs a flex row (or a height of its own) to show.\n */\n orientation?: \"horizontal\" | \"vertical\";\n /** Centered uppercase label. Horizontal orientation only. */\n label?: string;\n};\n\nconst lineStyles = \"bg-oprt-border\";\n\nexport const Divider = forwardRef<HTMLDivElement, DividerProps>((allProps, ref) => {\n const {\n className,\n orientation: requested = \"horizontal\",\n decorative = true,\n label,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Divider\", DividerManifest.Divider!.props, { ...allProps });\n }\n // A null enum value would mean \"no variant\": read it as the default.\n const orientation = requested ?? \"horizontal\";\n\n // Labeled variant: horizontal only. Two line pieces flanking a centered\n // label, with a single semantic separator role for assistive tech.\n if (label && orientation === \"horizontal\") {\n return (\n <div\n ref={ref}\n role={decorative ? \"none\" : \"separator\"}\n aria-orientation={decorative ? undefined : \"horizontal\"}\n // A separator's children are presentational and only the author can name it, so a\n // semantic labeled divider is named by its label. A caller's own name still wins.\n {...(decorative ? {} : { \"aria-label\": label })}\n className={clsx(\"flex items-center gap-2u\", className)}\n {...props}\n >\n {/* The lines keep a minimum length, so a label that wraps is still flanked by a rule. */}\n <div aria-hidden=\"true\" className={clsx(\"flex-1 h-px min-w-2u\", lineStyles)} />\n <span className=\"m-0 min-w-0 text-center text-oprt-sm uppercase tracking-oprt-sm text-oprt-text-dim oprt-wrap-anywhere\">\n {label}\n </span>\n <div aria-hidden=\"true\" className={clsx(\"flex-1 h-px min-w-2u\", lineStyles)} />\n </div>\n );\n }\n\n // A vertical rule is stretched to the height of its row (`self-stretch`). It must not ask for\n // `height: 100%`: in a row without a fixed height that percentage cannot resolve, and the rule\n // would drop out of the stretch and measure 0px.\n const lineClass = clsx(\n lineStyles,\n orientation === \"vertical\" ? \"w-px self-stretch\" : \"h-px w-full\",\n className,\n );\n\n // A decorative divider must expose role=\"none\". React Aria's `useSeparator`\n // has no `decorative` concept: for any non-`hr` element it unconditionally\n // emits role=\"separator\", and RAC merges that *after* DOM props so it cannot\n // be overridden. Its only role-less path renders an `<hr>`, which would swap\n // the element type and pick up the preflight `border-top-width` rule on top\n // of our 1px background line. So the presentational branch renders the node\n // directly — byte-for-byte the DOM Radix produced, and there is no ARIA\n // behavior left for RAC to provide once the role is \"none\".\n if (decorative) {\n return (\n <div ref={ref} role=\"none\" data-orientation={orientation} className={lineClass} {...props} />\n );\n }\n\n // Semantic separator: RAC owns role=\"separator\" + aria-orientation\n // (omitted when horizontal, which is the implicit ARIA default).\n return (\n <Separator\n ref={ref as Ref<HTMLElement>}\n elementType=\"div\"\n orientation={orientation}\n data-orientation={orientation}\n className={lineClass}\n {...(props as object)}\n />\n );\n});\nDivider.displayName = \"Divider\";\n","Divider/index.ts":"export { Divider, type DividerProps } from \"./Divider\";\nexport { DividerManifest } from \"./Divider.manifest\";\n","DropdownMenu/DropdownMenu.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const DropdownMenuManifest: Record<string, Component> = {\n DropdownMenu: {\n description:\n \"Portal'd action menu anchored to a trigger element. Holds DropdownMenuItem, DropdownMenuSeparator, and DropdownMenuLabel children.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer arrival and exit; keyboard, assistive activation, and system reduced motion remain immediate.\",\n },\n trigger: {\n type: \"node\",\n required: true,\n description:\n \"One focusable element that forwards its ref and handlers, such as a Button; plain text renders a secondary Button.\",\n },\n open: { type: \"boolean\" },\n defaultOpen: { type: \"boolean\" },\n onOpenChange: { type: \"function\" },\n align: { type: \"enum\", values: [\"start\", \"center\", \"end\"], default: \"start\" },\n side: { type: \"enum\", values: [\"top\", \"right\", \"bottom\", \"left\"], default: \"bottom\" },\n },\n slots: {\n children: {\n type: \"DropdownMenuItem | DropdownMenuSeparator | DropdownMenuLabel\",\n required: true,\n },\n },\n composable_with: [\"Toolbar\", \"CardHeader\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<DropdownMenu trigger={<Button variant=\"secondary\">ACTIONS</Button>}>\\n <DropdownMenuLabel>ACTIONS</DropdownMenuLabel>\\n <DropdownMenuItem shortcut=\"⌘C\">COPY</DropdownMenuItem>\\n <DropdownMenuSeparator />\\n <DropdownMenuItem danger>DELETE</DropdownMenuItem>\\n</DropdownMenu>',\n },\n DropdownMenuItem: {\n description:\n \"Selectable uppercase action row inside a DropdownMenu. Keyboard shortcuts, code, and kbd retain their casing. Wrap other case-sensitive names or IDs in a child with normal-case tracking-normal. Supports a danger style.\",\n props: {\n shortcut: {\n type: \"string\",\n description: \"Display text for an application-owned shortcut; it describes the item.\",\n },\n danger: {\n type: \"boolean\",\n default: false,\n description: \"Colors the item and adds a written “Destructive action” description.\",\n },\n disabled: { type: \"boolean\", default: false },\n textValue: {\n type: \"string\",\n description:\n \"Plain text for typeahead when children are not a string (for example a <code> ID).\",\n },\n onSelect: {\n type: \"function\",\n description:\n \"Receives a cancelable event; call event.preventDefault() to keep the menu open.\",\n },\n },\n composable_with: [\"DropdownMenu\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<DropdownMenuItem shortcut=\"⌘C\">COPY</DropdownMenuItem>',\n },\n DropdownMenuSeparator: {\n description: \"Thin horizontal rule grouping items inside a DropdownMenu.\",\n props: {},\n composable_with: [\"DropdownMenu\"],\n never_inside: [\"Button\", \"Badge\"],\n example: \"<DropdownMenuSeparator />\",\n },\n DropdownMenuLabel: {\n description: \"Non-interactive uppercase section heading inside a DropdownMenu.\",\n props: {},\n composable_with: [\"DropdownMenu\"],\n never_inside: [\"Button\", \"Badge\"],\n example: \"<DropdownMenuLabel>ACTIONS</DropdownMenuLabel>\",\n },\n};\n\nexport const DropdownMenuCompositionRules = {\n DropdownMenu: {\n allowed_children: [\"DropdownMenuItem\", \"DropdownMenuSeparator\", \"DropdownMenuLabel\"],\n },\n};\n","DropdownMenu/DropdownMenu.tsx":"\"use client\";\n\nimport {\n MenuTrigger,\n Menu,\n MenuItem,\n Separator,\n Header,\n Pressable,\n Text,\n Keyboard,\n} from \"react-aria-components\";\nimport {\n createContext,\n forwardRef,\n useCallback,\n useContext,\n useRef,\n useState,\n type ComponentPropsWithoutRef,\n type MutableRefObject,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { DropdownMenuManifest } from \"./DropdownMenu.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly } from \"../_internal/aria\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport { MotionContext, useMotion, type MotionMode } from \"../_internal/motion\";\nimport { MotionPopover } from \"../_internal/MotionPopover\";\nimport {\n DESTRUCTIVE_ACTION,\n MENU_ITEM_DESCRIPTION,\n MENU_ITEM_LABEL,\n MENU_ITEM_SHORTCUT,\n MENU_ITEM_TEXT,\n MENU_OFFSET,\n MENU_SURFACE,\n SCREEN_MARGIN,\n isSingleElement,\n menuItemStyles,\n // Radix took `side` + `align`; React Aria takes one placement string, which toPlacement builds.\n toPlacement,\n} from \"../_internal/overlay\";\n\n/**\n * `Pressable` types its child as a host ReactElement. Our public `trigger` prop\n * is a ReactNode and must stay one: one element is cloned as it is, and plain\n * text (or a number, fragment or list) becomes the label of a secondary Button,\n * the look of menu triggers elsewhere (DC-6).\n */\ntype PressableChild = ComponentPropsWithoutRef<typeof Pressable>[\"children\"];\n\n/**\n * Set by an item whose `onSelect` canceled its event. React Aria asks the menu to close right\n * after the item's action, in the same task, and that one request is declined.\n */\nconst KeepOpenContext = createContext<MutableRefObject<boolean> | null>(null);\n\nexport type DropdownMenuProps = {\n motion?: MotionMode;\n trigger: ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n align?: \"start\" | \"center\" | \"end\";\n side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n className?: string;\n children: ReactNode;\n};\n\nexport const DropdownMenu = forwardRef<HTMLDivElement, DropdownMenuProps>((props, ref) => {\n const {\n motion = \"auto\",\n trigger,\n open,\n defaultOpen = false,\n onOpenChange,\n align = \"start\",\n side = \"bottom\",\n className,\n children,\n } = props;\n const motionState = useMotion(motion);\n // The menu is always controlled here, so a close the host's item canceled can be declined.\n const [localOpen, setLocalOpen] = useState(defaultOpen);\n const isOpen = open ?? localOpen;\n const keepOpen = useRef(false);\n const requestOpen = useCallback(\n (next: boolean) => {\n if (!next && keepOpen.current) {\n keepOpen.current = false;\n return;\n }\n if (open === undefined) setLocalOpen(next);\n onOpenChange?.(next);\n },\n [open, onOpenChange],\n );\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"DropdownMenu\", DropdownMenuManifest.DropdownMenu!.props, {\n ...props,\n align,\n side,\n });\n }\n\n // The menu mounts on open and names itself by the trigger's id, so the\n // trigger must carry an id React still agrees with. MenuTrigger renders no element of\n // its own, so the probe rides on the wrapper around the trigger.\n const hydration = useHydrationRoot();\n\n return (\n <MotionContext.Provider value={motionState.mode}>\n <KeepOpenContext.Provider value={keepOpen}>\n <MenuTrigger key={hydration.key} isOpen={isOpen} onOpenChange={requestOpen}>\n {/* Radix cloned the trigger through `asChild`; RAC hands the trigger\n wiring down via a PressResponder, which `Pressable` applies to an\n arbitrary child element. Same public contract: consumers pass their\n own element and it becomes the menu button. */}\n {/* Capture on a native ancestor: custom triggers may filter DOM capture props. */}\n <span className=\"contents\" {...motionState.props} {...hydration.probeProps}>\n <Pressable>\n {\n (isSingleElement(trigger) ? (\n trigger\n ) : (\n <Button variant=\"secondary\">{trigger}</Button>\n )) as PressableChild\n }\n </Pressable>\n </span>\n <MotionPopover\n motionState={motionState}\n offset={MENU_OFFSET}\n containerPadding={SCREEN_MARGIN}\n placement={toPlacement(side, align)}\n className={clsx(MENU_SURFACE, className)}\n >\n <Menu ref={ref} className=\"outline-hidden\">\n {children}\n </Menu>\n </MotionPopover>\n </MenuTrigger>\n </KeepOpenContext.Provider>\n </MotionContext.Provider>\n );\n});\nDropdownMenu.displayName = \"DropdownMenu\";\n\nexport type DropdownMenuItemProps = Omit<\n ComponentPropsWithoutRef<\"div\">,\n \"onSelect\" | \"children\"\n> & {\n disabled?: boolean;\n textValue?: string;\n /** Fired when the item is activated. Call `event.preventDefault()` to keep the menu open. */\n onSelect?: (event: Event) => void;\n shortcut?: string;\n danger?: boolean;\n children: ReactNode;\n};\n\nexport const DropdownMenuItem = forwardRef<HTMLDivElement, DropdownMenuItemProps>(\n (\n { className, shortcut, danger = false, disabled, textValue, onSelect, children, ...props },\n ref,\n ) => {\n const keepOpen = useContext(KeepOpenContext);\n // Passthrough DOM props must not carry explicit `undefined` (RAC's prop\n // types reject it under exactOptionalPropertyTypes).\n const itemProps: Record<string, unknown> = { ...definedOnly(props) };\n if (textValue !== undefined) itemProps[\"textValue\"] = textValue;\n else if (typeof children === \"string\") itemProps[\"textValue\"] = children;\n if (onSelect) {\n // RAC's onAction takes no argument. Radix passed a cancelable Event, so\n // hand consumers an equivalent one rather than `undefined`, and honor\n // its cancellation as Radix did: the menu stays open.\n itemProps[\"onAction\"] = () => {\n const event = new CustomEvent(\"menuitemselect\", { cancelable: true });\n onSelect(event);\n if (!event.defaultPrevented || !keepOpen) return;\n keepOpen.current = true;\n // React Aria asks to close in this same task; one that never comes must not decline a\n // later close.\n queueMicrotask(() => {\n keepOpen.current = false;\n });\n };\n }\n\n return (\n <MenuItem\n ref={ref}\n isDisabled={disabled ?? false}\n className={clsx(menuItemStyles({ danger }), className)}\n {...itemProps}\n >\n <span className={MENU_ITEM_TEXT}>\n <Text slot=\"label\" className={MENU_ITEM_LABEL}>\n {children}\n </Text>\n {danger && (\n <Text slot=\"description\" className={MENU_ITEM_DESCRIPTION}>\n {DESTRUCTIVE_ACTION}\n </Text>\n )}\n </span>\n {shortcut && <Keyboard className={MENU_ITEM_SHORTCUT}>{shortcut}</Keyboard>}\n </MenuItem>\n );\n },\n);\nDropdownMenuItem.displayName = \"DropdownMenuItem\";\n\nexport type DropdownMenuSeparatorProps = ComponentPropsWithoutRef<\"div\">;\n\nexport const DropdownMenuSeparator = forwardRef<HTMLDivElement, DropdownMenuSeparatorProps>(\n ({ className, ...props }, ref) => (\n <Separator\n ref={ref as Ref<HTMLDivElement>}\n elementType=\"div\"\n className={clsx(\"h-px bg-oprt-border my-1u\", className)}\n {...definedOnly(props)}\n />\n ),\n);\nDropdownMenuSeparator.displayName = \"DropdownMenuSeparator\";\n\nexport type DropdownMenuLabelProps = ComponentPropsWithoutRef<\"div\">;\n\nexport const DropdownMenuLabel = forwardRef<HTMLDivElement, DropdownMenuLabelProps>(\n ({ className, ...props }, ref) => (\n // RAC's Header is a valid collection leaf inside a Menu, but a bare\n // <header> element maps to the `banner` landmark role, which is illegal\n // inside role=\"menu\". role=\"presentation\" strips that back to the inert\n // text node Radix's Label rendered.\n <Header\n ref={ref as Ref<HTMLElement>}\n role=\"presentation\"\n className={clsx(\n \"m-0 px-3u py-1u text-oprt-sm uppercase tracking-oprt-sm text-oprt-text-dim oprt-wrap-anywhere [&_code]:normal-case [&_code]:tracking-normal [&_kbd]:normal-case [&_kbd]:tracking-normal\",\n className,\n )}\n {...definedOnly(props)}\n />\n ),\n);\nDropdownMenuLabel.displayName = \"DropdownMenuLabel\";\n","DropdownMenu/index.ts":"export {\n DropdownMenu,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuLabel,\n type DropdownMenuProps,\n type DropdownMenuItemProps,\n type DropdownMenuSeparatorProps,\n type DropdownMenuLabelProps,\n} from \"./DropdownMenu\";\nexport { DropdownMenuManifest, DropdownMenuCompositionRules } from \"./DropdownMenu.manifest\";\n","EntityPreview/EntityPreview.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const EntityPreviewManifest: Record<string, Component> = {\n EntityPreview: {\n description:\n \"Entity identity with aligned label/value rows for condition, structured facts and report quality in transient or persistent inspection.\",\n props: {\n entity: {\n type: \"node\",\n required: true,\n description:\n \"EntityPreviewRecord: id, name, readonly DescriptionListItem facts, and quality (OperationalStatus state/source/updatedAt/detail); optional typeLabel, summary, and condition { label, variant }. Application identity is never used as a DOM ID. An ID identical to the name appears only in the heading. Pass a complete replacement record on update. Condition and facts use DescriptionList details; report quality uses OperationalStatus. Text-only badges, muted labels and right-aligned values share 40/60 columns with wrapping. No data fetching, health inference, or freshness calculation.\",\n },\n media: {\n type: \"node\",\n description:\n \"Optional supplied image, video, or other media content. The caller owns sizing, alternative text, transport, and controls.\",\n },\n actions: {\n type: \"node\",\n description:\n \"Optional named controls such as Link and CopyButton. The caller owns actions and selection.\",\n },\n headingLevel: {\n type: \"number\",\n default: 3,\n description:\n \"Entity name heading level: 2, 3, 4, 5, or 6. Choose it for the hosting surface's heading hierarchy.\",\n },\n className: { type: \"string\" },\n },\n slots: { media: { type: \"ReactNode\" }, actions: { type: \"ReactNode\" } },\n composable_with: [\n \"Popover\",\n \"Inspector\",\n \"DescriptionList\",\n \"OperationalStatus\",\n \"Badge\",\n \"Link\",\n \"CopyButton\",\n \"Button\",\n ],\n never_inside: [\"Button\", \"Badge\", \"Link\", \"Tooltip\"],\n example:\n '<EntityPreview entity={{ id: \"pump-07\", name: \"West pump\", facts: [{ id: \"pressure\", label: \"Pressure\", value: \"0 kPa\", mono: true }], condition: { label: \"Healthy\", variant: \"success\" }, quality: { state: \"stale\", source: \"Maintenance report\", updatedAt: null } }} />',\n },\n};\n","EntityPreview/EntityPreview.tsx":"\"use client\";\n\nimport { forwardRef, useId, type HTMLAttributes, type ReactNode } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Badge, type BadgeProps } from \"../Badge/Badge\";\nimport { DescriptionList, type DescriptionListItem } from \"../DescriptionList/DescriptionList\";\nimport {\n OperationalStatus,\n type OperationalStatusProps,\n} from \"../OperationalStatus/OperationalStatus\";\n\nconst previewStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base normal-case tracking-normal text-oprt-text\",\n);\n\nexport type EntityPreviewRecord = {\n id: string;\n name: string;\n typeLabel?: string;\n summary?: string;\n facts: readonly DescriptionListItem[];\n condition?: { label: string; variant: BadgeProps[\"variant\"] };\n quality: Pick<OperationalStatusProps, \"state\" | \"source\" | \"updatedAt\" | \"detail\">;\n};\n\nexport type EntityPreviewProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n entity: EntityPreviewRecord;\n media?: ReactNode;\n actions?: ReactNode;\n headingLevel?: 2 | 3 | 4 | 5 | 6;\n};\n\n/** Supplied record content; its hosting surface owns selection and focus. */\nexport const EntityPreview = forwardRef<HTMLDivElement, EntityPreviewProps>(\n ({ entity, media, actions, headingLevel = 3, className, ...props }, ref) => {\n const instanceId = useId();\n const headingId = `${instanceId}-name`;\n const conditionId = `${instanceId}-condition`;\n const Heading = `h${headingLevel}` as \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\";\n\n return (\n <div\n role=\"group\"\n aria-labelledby={headingId}\n {...props}\n ref={ref}\n className={clsx(previewStyles(), className)}\n >\n <div className=\"flex min-w-0 flex-col gap-1u\">\n <Heading\n id={headingId}\n className=\"m-0 oprt-wrap-anywhere text-oprt-lg font-semibold normal-case tracking-normal\"\n >\n {entity.name}\n </Heading>\n {(entity.typeLabel || entity.id !== entity.name) && (\n <div className=\"flex min-w-0 flex-wrap items-baseline justify-between gap-x-2u gap-y-0.5u text-oprt-text-dim\">\n {entity.typeLabel && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-sm font-medium uppercase tracking-oprt-sm\">\n {entity.typeLabel}\n </p>\n )}\n {entity.id !== entity.name && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere font-mono normal-case tracking-normal\">\n {entity.id}\n </p>\n )}\n </div>\n )}\n {entity.summary && (\n <p className=\"m-0 mt-1u oprt-wrap-anywhere normal-case tracking-normal text-oprt-text-dim\">\n {entity.summary}\n </p>\n )}\n </div>\n\n {media != null && (\n <div className=\"min-w-0 [&_img]:max-w-full [&_video]:max-w-full\">{media}</div>\n )}\n\n {(entity.condition || entity.facts.length > 0) && (\n <div className=\"min-w-0 flex flex-col gap-1u border-t border-oprt-border pt-2u\">\n {entity.condition && (\n <div role=\"group\" aria-labelledby={conditionId}>\n <DescriptionList\n layout=\"details\"\n items={[\n {\n id: \"condition\",\n label: <span id={conditionId}>Condition</span>,\n value: (\n <Badge\n variant={entity.condition.variant ?? \"neutral\"}\n appearance=\"text\"\n size=\"md\"\n className=\"ml-auto tracking-oprt-sm\"\n >\n {entity.condition.label}\n </Badge>\n ),\n },\n ]}\n />\n </div>\n )}\n {entity.facts.length > 0 && <DescriptionList items={entity.facts} layout=\"details\" />}\n </div>\n )}\n\n <OperationalStatus {...entity.quality} className=\"border-t border-oprt-border pt-2u\" />\n\n {actions != null && (\n <div className=\"flex min-w-0 flex-wrap items-center gap-1u border-t border-oprt-border pt-2u\">\n {actions}\n </div>\n )}\n </div>\n );\n },\n);\nEntityPreview.displayName = \"EntityPreview\";\n","EntityPreview/index.ts":"export { EntityPreview, type EntityPreviewProps, type EntityPreviewRecord } from \"./EntityPreview\";\nexport { EntityPreviewManifest } from \"./EntityPreview.manifest\";\n","EventTimeline/EventTimeline.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const EventTimelineManifest: Record<string, Component> = {\n EventTimeline: {\n description:\n \"UTC event graphic with lanes, clipped duration bars, point markers, and a complete accessible event list. Event titles lead each entry, severity aligns at the trailing edge, and timestamps/categories form a secondary metadata row.\",\n props: {\n events: {\n type: \"array\",\n required: true,\n description:\n \"Readonly TimelineEvent array. Invalid timestamps and reversed intervals are omitted with an explicit count. The visible list is sorted by start time.\",\n items: {\n id: \"string (required, unique)\",\n label: \"string (required)\",\n start: \"number (required, epoch milliseconds)\",\n end: \"number (optional, epoch milliseconds, inclusive)\",\n lane: \"string (optional, defaults to Events)\",\n description: \"string (optional)\",\n severity: \"enum [neutral, info, success, warning, error] (default neutral)\",\n },\n },\n start: {\n type: \"number\",\n required: true,\n description: \"Inclusive visible window start, Unix epoch milliseconds.\",\n },\n end: {\n type: \"number\",\n required: true,\n description: \"Inclusive visible window end, Unix epoch milliseconds. Must follow start.\",\n },\n selectedId: {\n type: \"string\",\n description:\n \"Controlled selected event ID, or null for no selection. Filtering the visible window never changes the supplied selection.\",\n },\n onSelectionChange: {\n type: \"function\",\n description:\n \"(id: string) => void. Adds native selection buttons to the event list; Enter and Space select an event.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description: \"Disables selection buttons while keeping data readable.\",\n },\n label: {\n type: \"string\",\n default: \"Event timeline\",\n description:\n 'Accessible name of the group. The event list is named after it with \"list\" added (label=\"Service events\" names the list \"Service events list\").',\n },\n emptyMessage: { type: \"string\", default: \"No events in this time range.\" },\n },\n composable_with: [\"TimeRange\", \"SplitPane\", \"Card\"],\n never_inside: [\"Button\"],\n example:\n '<EventTimeline events={events} start={range.start} end={range.end} selectedId={selectedId} onSelectionChange={setSelectedId} label=\"Service events\" />',\n },\n};\n","EventTimeline/EventTimeline.tsx":"\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport {\n forwardRef,\n memo,\n useCallback,\n useId,\n useMemo,\n useRef,\n useState,\n type HTMLAttributes,\n} from \"react\";\nimport { Badge } from \"../Badge/Badge\";\nimport { Button } from \"../Button/Button\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { formatUtcTimestamp, isValidTimestamp } from \"../_internal/timestamp\";\nimport { useElementWidth } from \"../_internal/useElementWidth\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\nconst timelineStyles = cva(\"flex min-w-0 flex-col gap-2u font-sans text-oprt-text\");\nconst markStyles = cva(\"fill-current\", {\n variants: {\n severity: {\n neutral: \"text-oprt-text-dim\",\n info: \"text-oprt-info\",\n success: \"text-oprt-success\",\n warning: \"text-oprt-warning\",\n error: \"text-oprt-error\",\n },\n },\n defaultVariants: { severity: \"neutral\" },\n});\n\nexport interface TimelineEvent {\n id: string;\n label: string;\n start: number;\n end?: number;\n lane?: string;\n description?: string;\n severity?: \"neutral\" | \"info\" | \"success\" | \"warning\" | \"error\";\n}\nexport type EventTimelineProps = HTMLAttributes<HTMLDivElement> & {\n events: readonly TimelineEvent[];\n start: number;\n end: number;\n selectedId?: string | null;\n onSelectionChange?: (id: string) => void;\n disabled?: boolean;\n label?: string;\n emptyMessage?: string;\n};\n\n/** Event sequence is the point of a timeline, so it shows milliseconds (DC-4). */\nconst utc = (value: number) => formatUtcTimestamp(value, { precision: \"milliseconds\" }) ?? \"\";\nconst eventValid = (event: TimelineEvent) =>\n isValidTimestamp(event.start) &&\n (event.end === undefined || (isValidTimestamp(event.end) && event.end >= event.start));\nconst laneOf = (event: TimelineEvent) => event.lane || \"Events\";\n\n/** Until the component has measured itself, and wherever it cannot, it draws at this width. */\nconst DEFAULT_WIDTH = 1000;\n/** Widths at or below this are a hidden or collapsed container, not a measurement. */\nconst MIN_WIDTH = 16;\n\ntype MarkerProps = {\n id: string;\n severity: TimelineEvent[\"severity\"];\n x: number;\n /** The bar's width for a duration event, null for a point event. */\n width: number | null;\n selected: boolean;\n};\n\n/** One event on its lane. Memoized: a selection change redraws only the two markers it touches. */\nconst Marker = memo(function Marker({ id, severity, x, width, selected }: MarkerProps) {\n const classes = clsx(\n markStyles({ severity: severity ?? \"neutral\" }),\n selected && \"stroke-oprt-text\",\n );\n return width !== null ? (\n <rect\n data-event-id={id}\n x={x}\n y={selected ? 10 : 14}\n width={width}\n height={selected ? 28 : 20}\n className={classes}\n vectorEffect=\"non-scaling-stroke\"\n />\n ) : (\n <path\n data-event-id={id}\n d={`M ${x} 8 L ${x + 5} 24 L ${x} 40 L ${x - 5} 24 Z`}\n className={classes}\n vectorEffect=\"non-scaling-stroke\"\n />\n );\n});\n\ntype RowProps = {\n event: TimelineEvent;\n selected: boolean;\n /** Whether the list offers selection: the title is then a button. */\n selectable: boolean;\n disabled: boolean;\n detailsId: string;\n /** Stable for the life of the timeline, so a row re-renders only when its own event changes. */\n onSelect: (id: string) => void;\n};\n\n/**\n * One entry of the event list. Memoized, so a selection change re-renders the two rows it touches\n * and a width change none (EventTimeline-R2). The title row is a flex row: the title shrinks and\n * wraps anywhere (R4) while the severity column keeps its width at the trailing edge\n * (EventTimeline-W0-1).\n */\nconst TimelineRow = memo(function TimelineRow({\n event,\n selected,\n selectable,\n disabled,\n detailsId,\n onSelect,\n}: RowProps) {\n return (\n <li\n className={clsx(\n \"flex min-w-0 flex-col gap-1u border p-2u\",\n selected ? \"border-oprt-accent bg-oprt-surface-2\" : \"border-oprt-border\",\n )}\n >\n <div className=\"flex min-w-0 items-start gap-2u\">\n {selectable ? (\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={disabled}\n className=\"h-auto! min-h-3u min-w-0 flex-1 justify-start! px-0! whitespace-normal text-left normal-case! tracking-normal hover:underline\"\n aria-pressed={selected}\n aria-describedby={detailsId}\n onClick={() => {\n if (!disabled) onSelect(event.id);\n }}\n >\n <span className=\"block min-w-0 oprt-wrap-anywhere\">{event.label}</span>\n </Button>\n ) : (\n <span className=\"min-w-0 flex-1 text-oprt-base font-medium normal-case oprt-wrap-anywhere\">\n {event.label}\n </span>\n )}\n <div className=\"flex shrink-0 flex-col items-end gap-1u\">\n <Badge variant={event.severity ?? \"neutral\"} indicator=\"dot\">\n {event.severity ?? \"neutral\"}\n </Badge>\n {selected && (\n <span className=\"text-oprt-base font-medium uppercase tracking-oprt-caps\">\n Selected\n </span>\n )}\n </div>\n </div>\n <div id={detailsId} className=\"flex min-w-0 flex-col gap-1u text-oprt-base normal-case\">\n <div className=\"flex flex-wrap items-baseline gap-x-2u gap-y-1u text-oprt-text-dim\">\n <span className=\"min-w-0 font-mono oprt-wrap-anywhere\">\n <time dateTime={new Date(event.start).toISOString()}>{utc(event.start)}</time>\n {event.end !== undefined && (\n <>\n {\" – \"}\n <time dateTime={new Date(event.end).toISOString()}>{utc(event.end)}</time>\n </>\n )}\n </span>\n {event.lane && <span className=\"min-w-0 oprt-wrap-anywhere\">{event.lane}</span>}\n </div>\n {event.description && <p className=\"m-0 oprt-wrap-anywhere\">{event.description}</p>}\n </div>\n </li>\n );\n});\n\nexport const EventTimeline = forwardRef<HTMLDivElement, EventTimelineProps>(\n (\n {\n events,\n start,\n end,\n selectedId,\n onSelectionChange,\n disabled = false,\n label = \"Event timeline\",\n emptyMessage = \"No events in this time range.\",\n className,\n ...props\n },\n forwardedRef,\n ) => {\n const id = useId();\n const rootRef = useRef<HTMLDivElement>(null);\n const setRef = useMergedRefs<HTMLDivElement>(rootRef, forwardedRef);\n // The shared observer reports only after its first resize callback, so the component also\n // measures itself once before the first paint; otherwise the lanes would first be drawn at\n // the default width.\n const observed = useElementWidth<HTMLDivElement, number | null>(rootRef, {\n initial: null,\n min: MIN_WIDTH,\n });\n const [firstWidth, setFirstWidth] = useState<number | null>(null);\n useIsomorphicLayoutEffect(() => {\n const width = rootRef.current?.getBoundingClientRect().width;\n if (width !== undefined && Number.isFinite(width) && width > MIN_WIDTH) setFirstWidth(width);\n }, []);\n const plotWidth = observed ?? firstWidth ?? DEFAULT_WIDTH;\n\n const validRange = isValidTimestamp(start) && isValidTimestamp(end) && start < end;\n // Derived once per change of the data, not on every selection or width change.\n const { visible, omitted, lanes } = useMemo(() => {\n const validEvents = events.filter(eventValid);\n const inWindow = validRange\n ? validEvents\n .filter((event) => event.start <= end && (event.end ?? event.start) >= start)\n .sort((a, b) => a.start - b.start)\n : [];\n const byLane = new Map<string, TimelineEvent[]>();\n for (const event of inWindow) {\n const lane = laneOf(event);\n const list = byLane.get(lane);\n if (list) list.push(event);\n else byLane.set(lane, [event]);\n }\n return { visible: inWindow, omitted: events.length - validEvents.length, lanes: byLane };\n }, [events, start, end, validRange]);\n\n // Rows keep one select function for the life of the timeline; it calls the latest callback.\n const selectionRef = useRef(onSelectionChange);\n useIsomorphicLayoutEffect(() => {\n selectionRef.current = onSelectionChange;\n });\n const select = useCallback((eventId: string) => selectionRef.current?.(eventId), []);\n\n // Reserve space at both endpoints so point markers remain inside the graphic.\n const extent = plotWidth - 16;\n const position = (time: number) =>\n 8 + Math.max(0, Math.min(extent, ((time - start) / (end - start)) * extent));\n return (\n <div\n {...props}\n ref={setRef}\n role=\"group\"\n aria-label={label}\n className={clsx(timelineStyles(), className)}\n >\n {!validRange ? (\n <p role=\"status\" className=\"m-0 text-oprt-base text-oprt-text-dim\">\n Choose a valid time range with start before end.\n </p>\n ) : (\n <>\n <div className=\"flex flex-wrap justify-between gap-1u font-mono text-oprt-base text-oprt-text-dim\">\n <time dateTime={new Date(start).toISOString()}>{utc(start)}</time>\n <time dateTime={new Date(end).toISOString()}>{utc(end)}</time>\n </div>\n {visible.length === 0 ? (\n <p className=\"m-0 text-oprt-base text-oprt-text-dim\">{emptyMessage}</p>\n ) : (\n <>\n <div aria-hidden=\"true\" className=\"flex flex-col gap-1u\">\n {[...lanes].map(([lane, laneEvents]) => (\n <div key={lane} className=\"min-w-0\">\n <p className=\"m-0 text-oprt-base font-medium uppercase tracking-oprt-caps oprt-wrap-anywhere\">\n {lane}\n </p>\n <svg\n viewBox={`0 0 ${plotWidth} 48`}\n preserveAspectRatio=\"none\"\n className=\"block h-6u w-full overflow-hidden\"\n focusable=\"false\"\n >\n <line\n x1=\"8\"\n x2={plotWidth - 8}\n y1=\"24\"\n y2=\"24\"\n className=\"stroke-oprt-border\"\n vectorEffect=\"non-scaling-stroke\"\n />\n {laneEvents.map((event) => {\n const x = position(event.start);\n const duration = event.end !== undefined && event.end > event.start;\n return (\n <Marker\n key={event.id}\n id={event.id}\n severity={event.severity}\n x={x}\n width={duration ? Math.max(2, position(event.end!) - x) : null}\n selected={selectedId === event.id}\n />\n );\n })}\n </svg>\n </div>\n ))}\n </div>\n {/* \"list\", not \"events\": the label often ends in \"events\" already (EventTimeline-R1). */}\n <ol aria-label={`${label} list`} className=\"m-0 flex list-none flex-col gap-1u p-0\">\n {visible.map((event, index) => (\n <TimelineRow\n key={event.id}\n event={event}\n selected={selectedId === event.id}\n selectable={!!onSelectionChange}\n disabled={disabled}\n detailsId={`${id}-details-${index}`}\n onSelect={select}\n />\n ))}\n </ol>\n </>\n )}\n </>\n )}\n {omitted > 0 && (\n <p className=\"m-0 text-oprt-base text-oprt-text-dim\">\n {omitted} {omitted === 1 ? \"event omitted\" : \"events omitted\"} because timestamps are\n invalid.\n </p>\n )}\n </div>\n );\n },\n);\nEventTimeline.displayName = \"EventTimeline\";\n","EventTimeline/index.ts":"export { EventTimeline, type EventTimelineProps, type TimelineEvent } from \"./EventTimeline\";\nexport { EventTimelineManifest } from \"./EventTimeline.manifest\";\n","Field/Field.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const FieldManifest: Record<string, Component> = {\n Field: {\n description:\n \"Form field wrapper providing label, help text, and error display. Labels, help, errors, and entered values preserve their original casing. A required Field makes its control required. NumberInput, CoordinateInput and MultiSelect carry their own label and are not placed in a Field.\",\n props: {\n controlId: {\n type: \"string\",\n description:\n \"Stable child control ID used by the label and context. Native id remains the wrapper ID; generated control IDs are the default.\",\n },\n label: { type: \"string\", required: true },\n help: { type: \"string\" },\n error: { type: \"string\" },\n required: {\n type: \"boolean\",\n default: false,\n description:\n \"Marks the label and makes the control inside required: announced as required, and the browser stops a native submission while it is empty. A form that validates itself sets noValidate on the form, which keeps the announcement. A control's own required={false} wins.\",\n },\n },\n slots: {\n children: {\n type: \"Input | Textarea | Select | ComboBox | Checkbox | RadioGroup | Toggle | Slider\",\n required: true,\n },\n },\n composable_with: [\"Card\", \"FormSection\", \"FormErrorSummary\"],\n never_inside: [\"Field\", \"Button\"],\n example:\n '<Field label=\"ENDPOINT\" error=\"Required\">\\n <Input placeholder=\"api.system.io\" />\\n</Field>',\n },\n};\n","Field/Field.tsx":"\"use client\";\n\nimport { forwardRef, useContext, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\n// The context lives with the shared wiring helpers, so controls read it through useFieldWiring\n// without importing Field (Stage 1 Field-R2).\nimport { FieldContext, usePartIds, type FieldContextValue } from \"../_internal/fieldIds\";\n\nexport function useFieldContext(): FieldContextValue {\n const ctx = useContext(FieldContext);\n if (!ctx) {\n throw new Error(\"useFieldContext must be used inside a <Field>.\");\n }\n return ctx;\n}\n\nexport function useOptionalFieldContext(): FieldContextValue | null {\n return useContext(FieldContext);\n}\n\nexport type FieldProps = HTMLAttributes<HTMLDivElement> & {\n /** Stable ID for the child control; the native `id` still identifies the wrapper. */\n controlId?: string;\n label: string;\n help?: string;\n error?: string;\n required?: boolean;\n children: ReactNode;\n};\n\nexport const Field = forwardRef<HTMLDivElement, FieldProps>(\n ({ controlId, label, help, error, required, children, className, ...props }, ref) => {\n // Controls mount popups later that point at these ids (a Select's listbox\n // names itself by the label), so they must match the markup the Field\n // hydrated; the label's htmlFor carries the id as the page has it.\n // The part ids keep their shape: the control's id followed by -label, -help and -error.\n const labelRef = useRef<HTMLLabelElement>(null);\n const ids = usePartIds([\"label\", \"help\", \"error\"], {\n id: controlId,\n readRendered: () => labelRef.current?.htmlFor,\n });\n const id = ids.base;\n const describedBy = error ? ids.error : help ? ids.help : undefined;\n\n const ctx: FieldContextValue = {\n id,\n labelId: ids.label,\n describedBy,\n invalid: Boolean(error),\n required: Boolean(required),\n };\n\n return (\n <FieldContext.Provider value={ctx}>\n <div\n ref={ref}\n // min-w-0: the Field can shrink below its widest word, so a narrow host never has to grow\n // around it (Field-W0-1). The label, help and error wrap, and long identifiers break\n // anywhere (R4), so none of them widens the page (Field-B2M-1..3).\n className={clsx(\"flex min-w-0 flex-col gap-1u font-sans normal-case\", className)}\n {...props}\n >\n <label\n ref={labelRef}\n id={ids.label}\n htmlFor={id}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base font-medium text-oprt-text\"\n >\n {label}\n {required && <span aria-hidden=\"true\"> *</span>}\n </label>\n {children}\n {error ? (\n <p\n id={ids.error}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base text-oprt-error\"\n role=\"alert\"\n >\n {error}\n </p>\n ) : help ? (\n <p\n id={ids.help}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base text-oprt-text-dim\"\n >\n {help}\n </p>\n ) : null}\n </div>\n </FieldContext.Provider>\n );\n },\n);\nField.displayName = \"Field\";\n","Field/index.ts":"export { Field, useFieldContext, useOptionalFieldContext, type FieldProps } from \"./Field\";\nexport { FieldManifest } from \"./Field.manifest\";\n","FileUpload/FileUpload.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const FileUploadManifest: Record<string, Component> = {\n FileUpload: {\n description:\n \"Choose, drop or paste files with bounded validation and readable rejection feedback.\",\n props: {\n label: {\n type: \"string\",\n required: true,\n description: \"Visible name for the file selection group.\",\n },\n description: { type: \"node\", description: \"Optional guidance associated with the group.\" },\n accept: {\n type: \"array\",\n description:\n \"Allowed extensions (.csv), MIME types (text/csv), or MIME wildcards (image/*). Case-insensitive, OR matching. Empty means any type. Browser hints do not validate content.\",\n },\n multiple: { type: \"boolean\", default: false },\n maxFiles: {\n type: \"number\",\n description:\n \"Positive integer limit per selection batch. Single-file mode always permits at most one file. Files are considered in input order, including files rejected by type/size.\",\n },\n maxFileSizeBytes: {\n type: \"number\",\n description:\n \"Inclusive nonnegative integer byte limit. Zero allows only empty files. Omitted means unlimited.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description: \"Blocks all input paths and invalidates in-flight file resolution.\",\n },\n onFiles: {\n type: \"function\",\n required: true,\n description:\n \"Receives accepted readonly File[] in input order. Selection does not establish upload or persistence. No callback for chooser cancellation or an empty accepted set.\",\n },\n onReject: {\n type: \"function\",\n description:\n \"Receives readonly FileRejection[]: name, code (type/size/count/directory/unsupported/read), message. Rejections are also displayed.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"UploadQueue\", \"AttachmentPreview\", \"FormSection\"],\n never_inside: [\"Button\"],\n example:\n '<FileUpload label=\"Equipment import\" accept={[\".csv\"]} maxFileSizeBytes={1048576} onFiles={selectFiles} onReject={reportIssues} />',\n },\n};\n","FileUpload/FileUpload.tsx":"\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useRef,\n useState,\n type HTMLAttributes,\n type ReactNode,\n} from \"react\";\nimport { Button as AriaButton, DropZone, FileTrigger, type DropItem } from \"react-aria-components\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { NUMBER_UNIT_SPACE } from \"../_internal/bytes\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { useIsMounted } from \"../_internal/useIsMounted\";\nexport type FileRejection = {\n name: string;\n code: \"type\" | \"size\" | \"count\" | \"directory\" | \"unsupported\" | \"read\";\n message: string;\n};\nexport type FileUploadProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n description?: ReactNode;\n accept?: readonly string[];\n multiple?: boolean;\n maxFiles?: number;\n maxFileSizeBytes?: number;\n disabled?: boolean;\n onFiles: (files: readonly File[]) => void;\n onReject?: (issues: readonly FileRejection[]) => void;\n};\nconst uploadStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base normal-case text-oprt-text\",\n);\n// When the area cannot take files it is not faded as a whole (FileUpload-B2M-4): that put the prompt\n// below 4.5:1 and faded Choose files twice. The prompt dims and Choose files shows Button's own\n// disabled look (design call DC-9).\nconst dropStyles = cva(\n \"flex min-w-0 flex-col items-start gap-2u border border-dashed border-oprt-border bg-oprt-surface-1 p-3u data-[drop-target]:border-oprt-accent data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus\",\n);\nconst validAccept = (token: string) =>\n /^\\.[a-z0-9][a-z0-9._+-]*$/i.test(token) ||\n /^(?:[a-z0-9!#$&^_.+-]+\\/(?:[a-z0-9!#$&^_.+-]+|\\*)|\\*\\/\\*)$/i.test(token);\ntype Candidate = { name: string; read: () => Promise<File> } | { issue: FileRejection };\n\n/** Selection only. Every input path is validated before application callbacks run. */\nexport const FileUpload = forwardRef<HTMLDivElement, FileUploadProps>(\n (\n {\n label,\n description,\n accept,\n multiple = false,\n maxFiles,\n maxFileSizeBytes,\n disabled = false,\n onFiles,\n onReject,\n className,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRefs(root, ref);\n // Hydration-safe part ids: after a hydration that disagreed with the server's ids, the base is\n // the one the page carries, so a description or error added later is still associated.\n const ids = usePartIds([\"label\", \"description\", \"configuration\"], {\n id: props.id,\n readRendered: () => root.current?.getAttribute(\"data-oprt-ids\"),\n });\n const [issues, setIssues] = useState<readonly FileRejection[]>([]);\n const [reading, setReading] = useState(false);\n const [feedback, setFeedback] = useState(\"\");\n const isMounted = useIsMounted();\n const generation = useRef(0);\n const types = accept?.map((token) => token.trim().toLowerCase()) ?? [];\n const configurationError = accept?.some((token) => !validAccept(token.trim()))\n ? \"Invalid accept configuration. Use file extensions or MIME types.\"\n : maxFiles !== undefined && (!Number.isSafeInteger(maxFiles) || maxFiles < 1)\n ? \"Invalid maxFiles configuration. Supply a positive integer.\"\n : maxFileSizeBytes !== undefined &&\n (!Number.isSafeInteger(maxFileSizeBytes) || maxFileSizeBytes < 0)\n ? \"Invalid maxFileSizeBytes configuration. Supply a nonnegative integer.\"\n : \"\";\n // Keep invalid numeric configurations distinct from omitted limits (JSON encodes NaN as null).\n const signature = JSON.stringify([types, multiple, String(maxFiles), String(maxFileSizeBytes)]);\n const latest = useRef({ disabled, configurationError, signature, onFiles, onReject });\n latest.current = { disabled, configurationError, signature, onFiles, onReject };\n useEffect(() => {\n // Unmounting, or React 19's Activity hiding the tree, retires a drop still being read. The\n // counter is a request generation, not a node, so the cleanup bumps its live value.\n const requests = generation;\n return () => {\n requests.current++;\n };\n }, []);\n useEffect(() => {\n generation.current++;\n setReading(false);\n }, [signature, disabled]);\n\n async function select(candidates: readonly Candidate[]) {\n if (!candidates.length || latest.current.disabled || latest.current.configurationError)\n return;\n const request = ++generation.current;\n const constraints = signature;\n setIssues([]);\n setFeedback(\"\");\n setReading(true);\n const results = await Promise.allSettled(\n candidates.map(async (candidate) =>\n \"issue\" in candidate ? candidate : { file: await candidate.read() },\n ),\n );\n if (\n !isMounted() ||\n generation.current !== request ||\n latest.current.disabled ||\n latest.current.configurationError ||\n latest.current.signature !== constraints\n )\n return;\n const accepted: File[] = [];\n const rejected: FileRejection[] = [];\n const countLimit = multiple ? (maxFiles ?? Infinity) : 1;\n let fileCount = 0;\n results.forEach((result, index) => {\n const candidate = candidates[index]!;\n if (result.status === \"rejected\") {\n rejected.push({\n name: \"name\" in candidate ? candidate.name : \"File\",\n code: \"read\",\n message: \"File could not be read.\",\n });\n return;\n }\n if (\"issue\" in result.value) {\n rejected.push(result.value.issue);\n return;\n }\n const value = result.value.file;\n fileCount++;\n if (fileCount > countLimit)\n rejected.push({\n name: value.name,\n code: \"count\",\n message: `Choose at most ${countLimit} file${countLimit === 1 ? \"\" : \"s\"} per selection.`,\n });\n else if (\n types.length &&\n !types.some(\n (type) =>\n type === \"*/*\" ||\n (type.startsWith(\".\")\n ? value.name.toLowerCase().endsWith(type)\n : type.endsWith(\"/*\")\n ? value.type.toLowerCase().startsWith(type.slice(0, -1))\n : value.type.toLowerCase() === type),\n )\n )\n rejected.push({ name: value.name, code: \"type\", message: \"File type is not accepted.\" });\n else if (maxFileSizeBytes !== undefined && value.size > maxFileSizeBytes)\n rejected.push({\n name: value.name,\n code: \"size\",\n // The limit and its unit stay on one line (R4, design call DC-11).\n message: `File exceeds ${maxFileSizeBytes}${NUMBER_UNIT_SPACE}bytes.`,\n });\n else accepted.push(value);\n });\n setReading(false);\n setIssues(rejected);\n setFeedback(\n `${accepted.length} file${accepted.length === 1 ? \"\" : \"s\"} selected locally${rejected.length ? `; ${rejected.length} rejected` : \"\"}.`,\n );\n if (accepted.length) latest.current.onFiles(Object.freeze(accepted));\n if (rejected.length) latest.current.onReject?.(Object.freeze(rejected));\n }\n function dropped(items: readonly DropItem[]) {\n return select(\n items.map(\n (item): Candidate =>\n item.kind === \"file\"\n ? { name: item.name, read: () => item.getFile() }\n : {\n issue: {\n name: item.kind === \"directory\" ? item.name : \"Dropped item\",\n code: item.kind === \"directory\" ? \"directory\" : \"unsupported\",\n message:\n item.kind === \"directory\"\n ? \"Directories are not supported. Choose individual files.\"\n : \"Only files are supported.\",\n },\n },\n ),\n );\n }\n const blocked = disabled || !!configurationError;\n const hasDescription = description != null && typeof description !== \"boolean\";\n return (\n <div\n {...props}\n ref={mergedRef}\n role=\"group\"\n data-oprt-ids={ids.base}\n aria-labelledby={joinIds(ids.label, labelledBy)}\n aria-describedby={joinIds(\n hasDescription && ids.description,\n configurationError && ids.configuration,\n describedBy,\n )}\n className={clsx(uploadStyles(), className)}\n >\n <p id={ids.label} className=\"m-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-sm\">\n {label}\n </p>\n {hasDescription && (\n <div\n id={ids.description}\n className=\"min-w-0 oprt-wrap-anywhere text-oprt-text-dim [&_p]:m-0\"\n >\n {description}\n </div>\n )}\n <DropZone\n aria-label={`${label} drop area`}\n isDisabled={blocked}\n className={dropStyles()}\n onDrop={(event) => {\n void dropped(event.items);\n }}\n getDropOperation={(_types, operations) =>\n operations.includes(\"copy\") ? \"copy\" : \"cancel\"\n }\n >\n <p className={clsx(\"m-0\", blocked && \"text-oprt-text-dim\")}>\n Drop files here or choose files.\n </p>\n <FileTrigger\n allowsMultiple={multiple}\n {...(accept ? { acceptedFileTypes: accept } : {})}\n onSelect={(files) => {\n void select(\n Array.from(files ?? []).map((file) => ({\n name: file.name,\n read: async () => file,\n })),\n );\n }}\n >\n <Button asChild variant=\"secondary\">\n <AriaButton isDisabled={blocked}>Choose files</AriaButton>\n </Button>\n </FileTrigger>\n </DropZone>\n {configurationError && (\n <p id={ids.configuration} role=\"alert\" className=\"m-0 text-oprt-error\">\n {configurationError}\n </p>\n )}\n {issues.length > 0 && (\n <ul className=\"m-0 flex min-w-0 list-disc flex-col gap-1u pl-3u text-oprt-error\">\n {issues.map((issue, index) => (\n <li key={index} className=\"oprt-wrap-anywhere\">\n {issue.name}: {issue.message}\n </li>\n ))}\n </ul>\n )}\n <p role=\"status\" aria-atomic=\"true\" className=\"m-0 text-oprt-text-dim\">\n {reading ? \"Reading selected files…\" : feedback}\n </p>\n </div>\n );\n },\n);\nFileUpload.displayName = \"FileUpload\";\n","FileUpload/index.ts":"export { FileUpload, type FileUploadProps, type FileRejection } from \"./FileUpload\";\nexport { FileUploadManifest } from \"./FileUpload.manifest\";\n","FilterBar/FilterBar.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const FilterBarManifest: Record<string, Component> = {\n FilterBar: {\n description:\n \"Toggle-button filters for a view, wrapping onto more rows when they do not fit. Single-select by default; set multiple for multi-select.\",\n props: {\n filters: {\n type: \"array\",\n required: true,\n description: \"Readonly string[] of filter labels, each shown once, in order.\",\n },\n value: {\n type: \"union\",\n types: [\"string\", \"array\"],\n description:\n \"Controlled selection: string in single mode; readonly string[] when multiple=true. Use an empty string or array to clear the corresponding mode. A selected value that is not in filters shows at the end of the bar as a selected filter marked unavailable, which the operator can remove.\",\n },\n defaultValue: {\n type: \"union\",\n types: [\"string\", \"array\"],\n description:\n \"Initial uncontrolled selection: string in single mode; readonly string[] when multiple=true.\",\n },\n multiple: {\n type: \"boolean\",\n default: false,\n description:\n \"When true, value, defaultValue, and onValueChange use string[] and a selected filter can be cleared; otherwise they use string and the filters are radios, so the active filter stays selected until another is chosen.\",\n },\n ariaLabel: {\n type: \"string\",\n default: \"Filters\",\n description: \"Accessible name for the group.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description: \"Disable pointer and keyboard selection while retaining the active filters.\",\n },\n onValueChange: {\n type: \"function\",\n description:\n \"Receives the active string in single mode or the complete string[] selection when multiple=true.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"Card\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<FilterBar filters={[\"ALL\",\"FRIENDLY\",\"HOSTILE\",\"NEUTRAL\",\"UNKNOWN\"]} defaultValue=\"ALL\" />',\n },\n};\n","FilterBar/FilterBar.tsx":"\"use client\";\n\nimport { ToggleButtonGroup, ToggleButton, type Key } from \"react-aria-components\";\nimport { mergeProps } from \"react-aria\";\nimport { filterDOMProps } from \"react-aria/filterDOMProps\";\nimport { forwardRef, useEffect, useRef, useState, type ComponentPropsWithoutRef } from \"react\";\nimport clsx from \"clsx\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\n\n// RAC state attributes differ from Radix: data-selected (not data-state=on),\n// data-focus-visible (not the focus-visible pseudo-class), and data-hovered, which React Aria sets\n// only on an enabled filter.\nconst itemStyles = [\n // Design call DC-8 and its ruling: the height is a minimum, so a label that wraps grows its filter\n // instead of spilling out, and the label is centered on one line or several. The padding is what\n // is left of 32 px around one line and the border, so a one-line filter keeps its exact height,\n // and `h-fit` keeps a row that stretches its items from growing it to its tallest neighbor. An\n // unbroken label breaks anywhere instead of widening the bar (R4).\n \"inline-flex h-fit min-h-oprt-md min-w-0 max-w-full flex-col items-center justify-center whitespace-normal text-center oprt-wrap-anywhere\",\n \"px-2u py-[calc((var(--oprt-height-md)-2px-var(--oprt-line-base))/2)]\",\n // The boundary of an unselected filter is the control outline (design call DC-3).\n \"border border-oprt-border-control text-oprt-base font-medium uppercase tracking-oprt-caps text-oprt-text-dim\",\n // Hover brightens an enabled, unselected filter only: a disabled one keeps its look.\n \"data-[hovered]:[&:not([data-selected])]:text-oprt-text\",\n \"data-[selected]:text-oprt-bg data-[selected]:bg-oprt-accent data-[selected]:border-oprt-accent-light\",\n // The system's focus ring replaces the browser's own.\n \"focus-visible:outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus\",\n \"disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-oprt-fast\",\n // Forced colors replace backgrounds and borders with system colors and drop the shadow, so the\n // selected filter would look like the others and focus would vanish. Selection is redrawn from\n // Highlight; a disabled selected filter stays filled, in GrayText (`:disabled` outranks the\n // enabled rule); focus becomes an outline.\n \"forced-colors:data-[selected]:[forced-color-adjust:none] forced-colors:data-[selected]:bg-[color:Highlight] forced-colors:data-[selected]:text-[color:HighlightText] forced-colors:data-[selected]:border-[color:Highlight]\",\n \"forced-colors:data-[selected]:disabled:bg-[color:GrayText] forced-colors:data-[selected]:disabled:text-[color:Canvas] forced-colors:data-[selected]:disabled:border-[color:GrayText]\",\n \"forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:outline-offset-2 forced-colors:data-[focus-visible]:outline-[color:CanvasText]\",\n].join(\" \");\n\n/** The written cue on a supplied value that is not among the filters, so it is not shown by color alone. */\nconst UNAVAILABLE = \"Unavailable\";\n/**\n * What an unavailable value shows as. An empty one (an empty query parameter split on commas gives\n * [\"\"]) shows the dash InlineEdit shows for an empty value (ruling on the B7 review's Minor 3).\n */\nconst shownAs = (value: string) => (value === \"\" ? \"—\" : value);\n\ntype FilterBarBaseProps = Omit<\n ComponentPropsWithoutRef<\"div\">,\n \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n /** Filter labels rendered as toggle buttons. Each label appears once. */\n filters: readonly string[];\n /** Accessible name for the toggle-button group. */\n ariaLabel?: string;\n /** Disable the entire filter group without discarding its selection. */\n disabled?: boolean;\n};\n\ntype SingleFilterBarProps = {\n multiple?: false;\n /** Controlled active filter, or an empty string for no selection. */\n value?: string;\n /** Uncontrolled initial active filter value. */\n defaultValue?: string;\n /** Fires with the newly active filter value. */\n onValueChange?: (value: string) => void;\n};\n\ntype MultipleFilterBarProps = {\n multiple: true;\n /** Controlled active filters, or an empty array for no selection. */\n value?: readonly string[];\n /** Uncontrolled initial active filters. */\n defaultValue?: readonly string[];\n /** Fires with all active filters after a selection changes. */\n onValueChange?: (value: string[]) => void;\n};\n\nexport type FilterBarProps = FilterBarBaseProps & (SingleFilterBarProps | MultipleFilterBarProps);\n\n/** Normalize either public selection mode onto React Aria's key collection. */\nfunction toKeys(value: string | readonly string[]): readonly string[] {\n if (typeof value !== \"string\") return value;\n return value === \"\" ? [] : [value];\n}\n\ntype Mistake = \"unknown value\" | \"duplicate filter\" | \"not applied\";\n\n// Dev only: once per mistake, so a re-render does not repeat it. Every message is written after the\n// production check, and every call sits under one, so a production bundle drops them all.\nconst reported = new Set<string>();\nfunction report(mistake: Mistake, prop: string, received: string) {\n if (process.env.NODE_ENV === \"production\") return;\n const key = `${mistake}\\0${prop}\\0${received}`;\n if (reported.has(key)) return;\n reported.add(key);\n const [error, suggestion] =\n mistake === \"unknown value\"\n ? [\n \"UNKNOWN_FILTER_VALUE\",\n \"The selection names a value that is not in `filters`. FilterBar shows it as a selected filter marked unavailable, at the end of the bar, which the operator can remove. Add it to `filters` if it is still offered, or leave it out of the selection.\",\n ]\n : mistake === \"duplicate filter\"\n ? [\n \"DUPLICATE_FILTER\",\n \"Each filter label must be unique: FilterBar shows a repeated label once. Remove the duplicate from `filters`.\",\n ]\n : [\n \"PROP_NOT_APPLIED\",\n prop === \"role\"\n ? \"FilterBar's group is a radio group in single mode and a toolbar in multiple mode; a role is not applied.\"\n : prop === \"aria-orientation\"\n ? \"FilterBar's filters wrap onto rows and arrow keys follow reading order; aria-orientation is not applied.\"\n : \"FilterBar draws its own filters; dangerouslySetInnerHTML is not applied. Supply them as `filters`.\",\n ];\n console.error(\n JSON.stringify({ component: \"FilterBar\", error, prop, received, suggestion }, null, 2),\n );\n}\n\n/**\n * Div props the group cannot take: React Aria's own role and orientation are what make the filters\n * a radio group or toolbar, and FilterBar draws its own buttons.\n */\nconst NOT_APPLIED = [\"role\", \"aria-orientation\", \"dangerouslySetInnerHTML\"] as const;\n\n/** The buttons of the bar, in order. */\nconst buttonsIn = (root: HTMLElement | null) =>\n root ? Array.from(root.querySelectorAll<HTMLButtonElement>(\"button\")) : [];\n\nexport const FilterBar = forwardRef<HTMLDivElement, FilterBarProps>((componentProps, ref) => {\n const {\n className,\n filters,\n value,\n defaultValue,\n onValueChange: _onValueChange,\n multiple = false,\n ariaLabel = \"Filters\",\n disabled = false,\n ...props\n } = componentProps;\n const root = useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRefs(root, ref);\n\n // The bar keeps its own copy of an uncontrolled selection, so it can show a supplied value that is\n // not among the filters. React Aria is always given the selection.\n const [ownSelection, setOwnSelection] = useState<readonly string[]>(() =>\n toKeys(defaultValue ?? \"\"),\n );\n const selected = value !== undefined ? toKeys(value) : ownSelection;\n const offered = [...new Set(filters)];\n const duplicates = [\n ...new Set(filters.filter((filter, index) => filters.indexOf(filter) !== index)),\n ];\n // Coordinator ruling (FilterBar-R1): a selected value that is not among the filters (a retired\n // class, a saved view, a list built from the data loaded now) shows as a selected filter marked\n // unavailable, at the end of the bar, so the operator sees it and can remove it.\n const unavailable = [...new Set(selected)].filter((key) => !offered.includes(key));\n\n const sourceProp = value !== undefined ? \"value\" : \"defaultValue\";\n // JSON keeps an empty value apart from no value at all.\n const unavailableKey = JSON.stringify(unavailable);\n const duplicatesKey = JSON.stringify(duplicates);\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n for (const key of JSON.parse(unavailableKey) as string[])\n report(\"unknown value\", sourceProp, key);\n for (const key of JSON.parse(duplicatesKey) as string[])\n report(\"duplicate filter\", \"filters\", key);\n }, [unavailableKey, duplicatesKey, sourceProp]);\n\n function changeSelection(keys: Set<Key>) {\n const next = Array.from(keys, String);\n // React Aria reports re-choosing the active filter in single mode as a new, identical selection.\n const current = new Set(selected);\n if (next.length === current.size && next.every((key) => current.has(key))) return;\n if (value === undefined) setOwnSelection(next);\n if (componentProps.multiple) componentProps.onValueChange?.(next);\n else componentProps.onValueChange?.(next[0] ?? \"\");\n }\n\n // When the focused filter goes (the operator removed an unavailable value, or the application\n // changed the filters), focus moves to the filter now in its place, else the last one.\n const restore = useRestoreFocusOnRemoval<HTMLDivElement, { index: number }>(root, {\n track: (element) => {\n const index = buttonsIn(root.current).indexOf(element as HTMLButtonElement);\n return index >= 0 ? { index } : null;\n },\n findTarget: ({ index }) => {\n const enabled = buttonsIn(root.current).filter((button) => !button.disabled);\n return enabled[Math.min(index, enabled.length - 1)] ?? null;\n },\n });\n // The props type is the div's, and is never narrowed (the rule the B7 review's Minor 4 set for\n // TagGroup). React Aria places some div props on the group itself: id, data-*, global attributes\n // and events, and the labeling props. The rest would be dropped, so they are added to the group\n // element through `render`, below, chained with React Aria's own handlers. Three cannot apply.\n const forwarded = filterDOMProps(props as Parameters<typeof filterDOMProps>[0], {\n global: true,\n labelable: true,\n });\n const dropped: ComponentPropsWithoutRef<\"div\"> = { ...props };\n for (const name of [...Object.keys(forwarded), ...NOT_APPLIED])\n delete dropped[name as keyof typeof dropped];\n const notApplied = NOT_APPLIED.filter((name) => props[name] !== undefined).join(\" \");\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n for (const name of notApplied ? notApplied.split(\" \") : []) report(\"not applied\", name, name);\n }, [notApplied]);\n\n return (\n <ToggleButtonGroup\n ref={mergedRef}\n isDisabled={disabled}\n aria-label={ariaLabel}\n selectionMode={multiple ? \"multiple\" : \"single\"}\n // Coordinator ruling (FilterBar-R2): single mode is a radio group, so activating the active\n // filter again does not clear it. An application still clears with value \"\".\n disallowEmptySelection={!multiple}\n selectedKeys={selected}\n onSelectionChange={changeSelection}\n // Design call DC-8: the filters wrap onto further rows; the bar never widens its container.\n className={clsx(\"flex min-w-0 max-w-full flex-wrap gap-1u font-sans\", className)}\n {...(props as Record<string, unknown>)}\n // The group element is drawn here, so the props React Aria drops reach it, and so focus\n // restoration can listen on it (React Aria passes no focus handlers through). Handlers chain:\n // the caller's, then React Aria's, then focus restoration's.\n render={(domProps) => (\n <div\n {...mergeProps(dropped, domProps, {\n onFocusCapture: restore.onFocusCapture,\n onBlurCapture: restore.onBlurCapture,\n })}\n />\n )}\n >\n {offered.map((filter) => (\n <ToggleButton key={filter} id={filter} className={itemStyles}>\n {filter}\n </ToggleButton>\n ))}\n {unavailable.map((key) => (\n // The name joins the value and its cue with a pause; it contains the visible text.\n <ToggleButton\n key={key}\n id={key}\n aria-label={`${shownAs(key)}, ${UNAVAILABLE}`}\n className={itemStyles}\n >\n {shownAs(key)}\n <span className=\"font-normal normal-case tracking-normal\">{UNAVAILABLE}</span>\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n );\n});\nFilterBar.displayName = \"FilterBar\";\n","FilterBar/index.ts":"export { FilterBar, type FilterBarProps } from \"./FilterBar\";\nexport { FilterBarManifest } from \"./FilterBar.manifest\";\n","FormErrorSummary/FormErrorSummary.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const FormErrorSummaryManifest: Record<string, Component> = {\n FormErrorSummary: {\n description: \"Focusable error summary with actions that reveal or focus invalid form controls.\",\n props: {\n errors: {\n type: \"array\",\n required: true,\n description:\n \"Readonly errors with stable unique id, readable message and targetId for the control.\",\n },\n title: {\n type: \"string\",\n default: \"Check these fields\",\n description: \"Visible heading that names the summary section.\",\n },\n onErrorSelect: {\n type: \"function\",\n description:\n \"Optional reveal/focus handler receiving the selected error. Replaces direct target focus; reveal hidden controls before focusing them after render.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"Field\", \"FormSection\", \"Input\"],\n never_inside: [\"Button\"],\n example: \"<FormErrorSummary ref={summaryRef} errors={errors} onErrorSelect={revealAndFocus} />\",\n },\n};\n","FormErrorSummary/FormErrorSummary.tsx":"\"use client\";\n\nimport { forwardRef, useRef, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\nconst summaryStyles = cva(\n \"flex min-w-0 flex-col gap-2u border border-oprt-error bg-oprt-surface-1 p-3u font-sans text-oprt-base normal-case tracking-normal text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\n// Each message keeps its authored case, as it does under its Field, so identifiers and values in it\n// are not case-changed (coordinator ruling, FormErrorSummary-R1). The title stays an uppercase label.\nconst errorStyles = cva(\n \"inline-flex min-h-3u min-w-3u max-w-full items-center border border-transparent px-1u py-0.5u text-left text-oprt-base font-medium normal-case tracking-normal text-oprt-text underline underline-offset-2 hover:text-oprt-accent outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus oprt-wrap-anywhere\",\n);\n\nexport interface FormErrorSummaryError {\n id: string;\n message: string;\n targetId: string;\n}\nexport type FormErrorSummaryProps = Omit<HTMLAttributes<HTMLElement>, \"title\" | \"children\"> & {\n errors: readonly FormErrorSummaryError[];\n title?: string;\n onErrorSelect?: (error: FormErrorSummaryError) => void;\n};\n\n/** Callers choose when to focus the summary and how to reveal hidden fields. */\nexport const FormErrorSummary = forwardRef<HTMLElement, FormErrorSummaryProps>(\n (\n {\n errors,\n title = \"Check these fields\",\n onErrorSelect,\n className,\n tabIndex = -1,\n \"aria-labelledby\": labelledBy,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLElement>(null);\n const mergedRef = useMergedRefs(root, ref);\n // Hydration-safe part ids: after a hydration that disagreed with the server's ids, the base is\n // the one the page carries, so a label the application adds later keeps the title in the name.\n const ids = usePartIds([\"title\"], {\n id: props.id,\n readRendered: () => root.current?.getAttribute(\"data-oprt-ids\"),\n });\n return (\n <section\n {...props}\n ref={mergedRef}\n tabIndex={tabIndex}\n data-oprt-ids={ids.base}\n aria-labelledby={joinIds(ids.title, labelledBy)}\n className={clsx(summaryStyles(), className)}\n >\n <h2\n id={ids.title}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base font-medium uppercase tracking-oprt-sm text-oprt-error\"\n >\n {title}\n </h2>\n <ul className=\"m-0 flex min-w-0 list-none flex-col gap-1u p-0\">\n {errors.map((error) => (\n <li key={error.id} className=\"m-0 min-w-0 p-0\">\n <button\n type=\"button\"\n className={errorStyles()}\n onClick={(event) => {\n if (onErrorSelect) onErrorSelect(error);\n else event.currentTarget.ownerDocument.getElementById(error.targetId)?.focus();\n }}\n >\n {error.message}\n </button>\n </li>\n ))}\n </ul>\n </section>\n );\n },\n);\nFormErrorSummary.displayName = \"FormErrorSummary\";\n","FormErrorSummary/index.ts":"export {\n FormErrorSummary,\n type FormErrorSummaryProps,\n type FormErrorSummaryError,\n} from \"./FormErrorSummary\";\nexport { FormErrorSummaryManifest } from \"./FormErrorSummary.manifest\";\n","FormSection/FormSection.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const FormSectionManifest: Record<string, Component> = {\n FormSection: {\n description: \"Native fieldset with a legend, optional description and supplied form controls.\",\n props: {\n legend: {\n type: \"string\",\n required: true,\n description: \"Visible legend that names the group.\",\n },\n description: {\n type: \"node\",\n description: \"Supporting content associated with the fieldset.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description: \"Uses native fieldset disabling for descendant form controls.\",\n },\n className: { type: \"string\" },\n },\n slots: { children: { type: \"ReactNode\", required: true } },\n composable_with: [\"Field\", \"Input\", \"Checkbox\", \"FormErrorSummary\"],\n never_inside: [\"Button\"],\n example:\n '<FormSection legend=\"Equipment details\" description=\"Use the registered name.\">\\n <Field controlId=\"resource-name\" label=\"Name\"><Input /></Field>\\n</FormSection>',\n },\n};\n","FormSection/FormSection.tsx":"\"use client\";\n\nimport { forwardRef, useRef, type FieldsetHTMLAttributes, type ReactNode } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\nconst sectionStyles = cva(\n \"m-0 min-w-0 border border-oprt-border bg-oprt-surface-1 p-3u font-sans text-oprt-base normal-case tracking-normal text-oprt-text\",\n);\n\nexport type FormSectionProps = FieldsetHTMLAttributes<HTMLFieldSetElement> & {\n legend: string;\n description?: ReactNode;\n children: ReactNode;\n};\n\n/** A native group. Field values and validation remain with the caller. */\nexport const FormSection = forwardRef<HTMLFieldSetElement, FormSectionProps>(\n (\n { legend, description, children, className, \"aria-describedby\": describedBy, ...props },\n ref,\n ) => {\n const root = useRef<HTMLFieldSetElement>(null);\n const mergedRef = useMergedRefs(root, ref);\n // Hydration-safe part ids: after a hydration that disagreed with the server's ids, the base is\n // the one the page carries, so a description the application adds later keeps this one too.\n const ids = usePartIds([\"description\"], {\n id: props.id,\n readRendered: () => root.current?.getAttribute(\"data-oprt-ids\"),\n });\n const hasDescription = description != null && typeof description !== \"boolean\";\n return (\n <fieldset\n {...props}\n ref={mergedRef}\n data-oprt-ids={ids.base}\n aria-describedby={joinIds(hasDescription && ids.description, describedBy)}\n className={clsx(sectionStyles(), className)}\n >\n <legend className=\"m-0 max-w-full oprt-wrap-anywhere px-1u text-oprt-base font-medium uppercase tracking-oprt-sm\">\n {legend}\n </legend>\n <div className=\"flex min-w-0 flex-col gap-2u\">\n {hasDescription && (\n <div\n id={ids.description}\n className=\"min-w-0 oprt-wrap-anywhere text-oprt-text-dim [&_p]:m-0\"\n >\n {description}\n </div>\n )}\n {children}\n </div>\n </fieldset>\n );\n },\n);\nFormSection.displayName = \"FormSection\";\n","FormSection/index.ts":"export { FormSection, type FormSectionProps } from \"./FormSection\";\nexport { FormSectionManifest } from \"./FormSection.manifest\";\n","InlineEdit/index.ts":"export { InlineEdit, type InlineEditProps } from \"./InlineEdit\";\nexport { InlineEditManifest } from \"./InlineEdit.manifest\";\n","InlineEdit/InlineEdit.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const InlineEditManifest: Record<string, Component> = {\n InlineEdit: {\n description:\n \"Controlled inline text editing with explicit save, cancel and recoverable validation or save errors.\",\n props: {\n label: {\n type: \"string\",\n required: true,\n description: \"Names the editor and its Edit, Save and Cancel actions.\",\n },\n value: {\n type: \"string\",\n required: true,\n description:\n \"Authoritative displayed value. Case is preserved. A change while the operator is typing keeps the draft and shows a status line naming the new value.\",\n },\n onSave: {\n type: \"function\",\n required: true,\n description:\n \"Receives the exact draft; return a promise for asynchronous acceptance and reject to retain the draft with an error.\",\n },\n validate: {\n type: \"function\",\n description: \"Returns an error message or undefined before saving.\",\n },\n disabled: { type: \"boolean\", default: false },\n readOnly: {\n type: \"boolean\",\n default: false,\n description: \"Displays the value without an Edit action.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"ContextMenu\", \"DataGrid\", \"FormSection\"],\n never_inside: [\"Button\"],\n example:\n '<InlineEdit label=\"Equipment name\" value={name} onSave={saveName} validate={(value) => value.trim() ? undefined : \"Enter a name.\"} />',\n },\n};\n","InlineEdit/InlineEdit.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useRef, useState, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button\";\nimport { Input } from \"../Input\";\nimport { Textarea } from \"../Textarea\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useIsMounted } from \"../_internal/useIsMounted\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\nconst rootStyles = cva(\n \"flex min-w-0 flex-col gap-1u font-sans text-oprt-base normal-case tracking-normal text-oprt-text\",\n);\n\ntype EditSession = { baseline: string; label: string };\ntype SaveAttempt = { session: EditSession; submitted: string };\n\nfunction matchesAttempt(attempt: SaveAttempt, value: string, label: string) {\n const acceptedReplacement = value === attempt.submitted && value !== attempt.session.baseline;\n return (\n (label === attempt.session.label || acceptedReplacement) &&\n (value === attempt.session.baseline || value === attempt.submitted)\n );\n}\n\nexport type InlineEditProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n value: string;\n onSave: (value: string) => void | Promise<void>;\n disabled?: boolean;\n readOnly?: boolean;\n validate?: (value: string) => string | undefined;\n};\nexport const InlineEdit = forwardRef<HTMLDivElement, InlineEditProps>(\n (\n { label, value, onSave, disabled = false, readOnly = false, validate, className, ...props },\n ref,\n ) => {\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(value);\n const [error, setError] = useState<string | null>(null);\n const [pending, setPending] = useState(false);\n const [multiline, setMultiline] = useState(false);\n const [returnFocus, setReturnFocus] = useState<SaveAttempt | null>(null);\n // The supplied value, when it changed while the operator was typing (design call DC-20).\n const [changedTo, setChangedTo] = useState<string | null>(null);\n const rootRef = useRef<HTMLDivElement>(null);\n const mergedRef = useMergedRefs(rootRef, ref);\n const inputRef = useRef<HTMLInputElement | HTMLTextAreaElement | null>(null);\n const editRef = useRef<HTMLButtonElement>(null);\n // Set when a save is rejected: the input takes focus back once it is enabled again.\n const refocusInput = useRef(false);\n const session = useRef<EditSession | null>(null);\n const request = useRef<SaveAttempt | null>(null);\n const composing = useRef(false);\n const isMounted = useIsMounted();\n const latest = useRef({ label, value, disabled, readOnly });\n latest.current = { label, value, disabled, readOnly };\n // The error and the change notice exist only while editing, after hydration, so their ids need\n // no reconciling with server markup; they come from the shared helper all the same.\n const ids = usePartIds([\"error\", \"changed\"]);\n\n // Unmounting ends the session, so a save still in flight cannot reach this editor again.\n useEffect(\n () => () => {\n session.current = null;\n request.current = null;\n },\n [],\n );\n\n useEffect(() => {\n const current = session.current;\n if (!current) return;\n const attempt = request.current;\n // Revoked editability ends the session. So does a change while a save is pending that is\n // neither the submitted value nor the one the save started from: its completion is ignored.\n if (disabled || readOnly || (attempt && !matchesAttempt(attempt, value, label))) {\n session.current = null;\n request.current = null;\n setEditing(false);\n setPending(false);\n setError(null);\n setReturnFocus(null);\n setChangedTo(null);\n return;\n }\n if (attempt || (current.label === label && current.baseline === value)) return;\n // Design call DC-20: a value or label supplied while the operator is typing (a sync, another\n // operator's rename) never discards the draft. The editor stays open with focus in the input,\n // and a status line names the new value. The session now starts from it, so Save submits the\n // draft for the adapter to decide and Cancel shows the new value.\n session.current = { baseline: value, label };\n if (current.baseline !== value) setChangedTo(value);\n }, [value, label, disabled, readOnly]);\n\n useEffect(() => {\n if (editing) inputRef.current?.focus();\n }, [editing]);\n // A pending save disables the input and Save, and a browser moves focus from a disabled control\n // to the page. After a rejection, focus returns to the input once it is enabled again, unless\n // the operator has moved on outside the editor.\n useEffect(() => {\n if (pending || !refocusInput.current) return;\n refocusInput.current = false;\n const active = document.activeElement;\n if (active === null || active === document.body || rootRef.current?.contains(active))\n inputRef.current?.focus();\n }, [pending]);\n useEffect(() => {\n if (!returnFocus || editing) return;\n if (!disabled && !readOnly && matchesAttempt(returnFocus, value, label))\n editRef.current?.focus();\n setReturnFocus(null);\n }, [returnFocus, editing, disabled, readOnly, label, value]);\n\n function start() {\n if (disabled || readOnly) return;\n session.current = { baseline: value, label };\n composing.current = false;\n setMultiline(/[\\r\\n]/.test(value));\n setDraft(value);\n setError(null);\n setChangedTo(null);\n setEditing(true);\n }\n function cancel() {\n if (request.current || !session.current) return;\n setReturnFocus({ session: session.current, submitted: value });\n session.current = null;\n setEditing(false);\n setError(null);\n setChangedTo(null);\n }\n function isCurrent(attempt: SaveAttempt) {\n const current = latest.current;\n return (\n isMounted() &&\n request.current === attempt &&\n session.current === attempt.session &&\n !current.disabled &&\n !current.readOnly &&\n matchesAttempt(attempt, current.value, current.label)\n );\n }\n async function save() {\n if (request.current || !session.current || disabled || readOnly || composing.current) return;\n const validationError = validate?.(draft);\n if (validationError) {\n setError(validationError);\n inputRef.current?.focus();\n return;\n }\n const attempt: SaveAttempt = { session: session.current, submitted: draft };\n request.current = attempt;\n setPending(true);\n setError(null);\n try {\n await onSave(draft);\n if (!isCurrent(attempt)) return;\n request.current = null;\n session.current = null;\n setPending(false);\n setEditing(false);\n setChangedTo(null);\n setReturnFocus(attempt);\n } catch (reason) {\n if (!isCurrent(attempt)) return;\n request.current = null;\n refocusInput.current = true;\n setPending(false);\n setError(\n reason instanceof Error && reason.message ? reason.message : \"Could not save. Try again.\",\n );\n }\n }\n\n const Control = multiline ? Textarea : Input;\n return (\n <div\n {...props}\n ref={mergedRef}\n className={clsx(rootStyles(), className)}\n aria-busy={pending || undefined}\n >\n {editing ? (\n <>\n <div className=\"flex min-w-0 flex-wrap items-center gap-1u\">\n <Control\n ref={(node: HTMLInputElement | HTMLTextAreaElement | null) => {\n inputRef.current = node;\n }}\n aria-label={label}\n value={draft}\n disabled={pending || disabled || readOnly}\n aria-invalid={Boolean(error)}\n aria-describedby={joinIds(changedTo !== null && ids.changed, error && ids.error)}\n className=\"min-w-0 flex-1\"\n onChange={(event) => {\n setDraft(event.target.value);\n setError(null);\n }}\n onCompositionStart={() => {\n composing.current = true;\n }}\n onCompositionEnd={() => {\n composing.current = false;\n }}\n onKeyDown={(event) => {\n if (composing.current || event.nativeEvent.isComposing || event.keyCode === 229)\n return;\n if (event.key === \"Enter\" && !multiline) {\n event.preventDefault();\n void save();\n } else if (event.key === \"Escape\" && !pending) {\n event.preventDefault();\n event.stopPropagation();\n cancel();\n }\n }}\n />\n <Button\n size=\"sm\"\n aria-label={`Save ${label}`}\n disabled={pending || disabled || readOnly}\n onClick={() => {\n void save();\n }}\n >\n Save\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n aria-label={`Cancel ${label}`}\n disabled={pending}\n onClick={cancel}\n >\n Cancel\n </Button>\n </div>\n {/* The change notice's live region is there, empty and visually hidden, for the whole\n editing session, so a screen reader announces the sentence when it is filled in\n (B7 review, Minor 1); a region created already filled is often not announced. */}\n <p\n id={ids.changed}\n className={clsx(\n \"m-0 oprt-wrap-anywhere text-oprt-base text-oprt-text-dim\",\n changedTo === null && \"sr-only\",\n )}\n role=\"status\"\n >\n {changedTo !== null && <>Changed to “{changedTo || \"—\"}” while you were editing.</>}\n </p>\n {/* The saving status follows the same rule: there, empty, for the whole editing\n session, and filled when a save starts. */}\n <p\n className={clsx(\"m-0 text-oprt-base text-oprt-text-dim\", !pending && \"sr-only\")}\n role=\"status\"\n >\n {pending && \"Saving…\"}\n </p>\n {error && (\n <p\n id={ids.error}\n className=\"m-0 oprt-wrap-anywhere text-oprt-base text-oprt-error\"\n role=\"alert\"\n >\n {error}\n </p>\n )}\n </>\n ) : (\n <div className=\"flex min-w-0 flex-wrap items-center gap-2u\">\n <span className=\"min-w-0 whitespace-break-spaces oprt-wrap-anywhere\">\n {value || \"—\"}\n </span>\n {!readOnly && (\n <Button\n ref={editRef}\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Edit ${label}`}\n disabled={disabled}\n onClick={start}\n >\n Edit\n </Button>\n )}\n </div>\n )}\n </div>\n );\n },\n);\nInlineEdit.displayName = \"InlineEdit\";\n","Input/index.ts":"export { Input, type InputProps } from \"./Input\";\nexport { InputManifest } from \"./Input.manifest\";\n","Input/Input.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const InputManifest: Record<string, Component> = {\n Input: {\n description:\n \"Single-line input with optional prefix/suffix slots and prompt-symbol variant. Date and time fields preserve native editing and picker interaction with decorative OPERATOR Calendar/Clock icons and a dark native color scheme.\",\n props: {\n type: {\n type: \"enum\",\n values: [\n \"text\",\n \"number\",\n \"password\",\n \"search\",\n \"email\",\n \"tel\",\n \"url\",\n \"date\",\n \"time\",\n \"datetime-local\",\n \"month\",\n \"week\",\n ],\n default: \"text\",\n description:\n \"The text-like and date-like input types. TypeScript accepts any HTML input type, but one that is not a text field (checkbox, radio, file, range, color, hidden, button, submit, reset, image) breaks the field's look and is reported in development; use Checkbox, RadioGroup, Slider or FileUpload instead.\",\n },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n placeholder: { type: \"string\" },\n disabled: { type: \"boolean\", default: false },\n invalid: {\n type: \"boolean\",\n description:\n \"Marks the field invalid: an error border and aria-invalid. Wins over aria-invalid, which wins over a surrounding Field's error, as on Textarea.\",\n },\n promptSymbol: { type: \"boolean\", default: false },\n prefix: {\n type: \"node\",\n description: \"ReactNode rendered before the input (icon, currency symbol, etc.).\",\n },\n suffix: {\n type: \"node\",\n description:\n \"ReactNode rendered after the input (icon, clear button, unit, etc.). For interactive elements use an accessible Button or icon-button.\",\n },\n },\n composable_with: [\"Field\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<Field label=\"ENDPOINT\">\\n <Input placeholder=\"api.system.io\" />\\n</Field>',\n },\n};\n","Input/Input.tsx":"\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type InputHTMLAttributes, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\nimport { InputManifest } from \"./Input.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { useFieldWiring } from \"../_internal/fieldIds\";\nimport { FORCED_FOCUS_WITHIN, FORCED_NO_RESTING_OUTLINE } from \"../_internal/controlStyles\";\nimport { Calendar, Clock } from \"@oprt/icons\";\n\nconst wrapperStyles = cva(\n // Disabled looks like DC-9's disabled buttons (coordinator ruling on DC-B04-2): dim text, the\n // control outline and a muted fill, with no fade of the whole field, which took an affix to\n // 2.14:1 (Input-B2M-5); the outline also replaces an invalid field's red, as for danger buttons.\n \"inline-flex min-w-0 max-w-full items-center gap-1u border bg-oprt-surface-2 normal-case transition-colors duration-oprt-fast focus-within:shadow-oprt-focus has-[input:disabled]:cursor-not-allowed has-[input:disabled]:bg-oprt-surface-3 has-[input:disabled]:border-oprt-border-control\",\n {\n variants: {\n invalid: {\n true: \"border-oprt-error focus-within:border-oprt-error\",\n // The control outline (design call DC-3); focus in the focus token, as the siblings do.\n false: \"border-oprt-border-control focus-within:border-oprt-border-focus\",\n },\n // The height is a minimum: a long prefix or suffix wraps in its own column and the field\n // grows taller (coordinator ruling on DC-B04-3, Input-B2M-1..3), while a one-line field keeps\n // exactly its token height, as DC-8 rules for buttons. The value's 24px line fits md and lg;\n // sm caps it inside its 2px of border, as the fixed height did. A capped input cannot stretch\n // to a field that grew taller, and a stretched item that hits its cap sits at the top, so sm\n // centers it with auto margins instead (Stage 1 final review, I-2).\n size: {\n sm: \"min-h-oprt-sm px-2u [&_input]:my-auto [&_input]:max-h-[calc(var(--oprt-height-sm)-2px)]\",\n md: \"min-h-oprt-md px-2u\",\n lg: \"min-h-oprt-lg px-3u\",\n },\n },\n defaultVariants: { invalid: false, size: \"md\" },\n },\n);\n\nconst inputStyles = cva(\n // The value keeps about ten characters beside a long affix, and less only in a container\n // narrower than that (Input-B2M-1..3); the field around it can shrink in a narrow host (Input-W0-1).\n // It stretches to the field's height, so the whole field is the click and measured target and\n // not the one line of text inside it (operations-map-R4); a text input centers its text itself.\n \"peer min-w-[min(10ch,100%)] flex-1 self-stretch bg-transparent border-0 outline-hidden font-sans font-medium text-oprt-md text-oprt-text placeholder:text-oprt-text-placeholder disabled:placeholder:text-oprt-text-dim disabled:cursor-not-allowed disabled:text-oprt-text-dim\",\n);\n\nexport type InputProps = Omit<InputHTMLAttributes<HTMLInputElement>, \"size\" | \"prefix\"> &\n Pick<VariantProps<typeof wrapperStyles>, \"size\"> & {\n prefix?: ReactNode;\n suffix?: ReactNode;\n promptSymbol?: boolean;\n /**\n * Marks the field invalid: the error border and aria-invalid, as on Textarea, Select and\n * ComboBox. Wins over aria-invalid, which wins over a surrounding Field's error.\n */\n invalid?: boolean;\n };\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>((allProps, ref) => {\n const {\n className,\n size,\n prefix,\n suffix,\n promptSymbol,\n type = \"text\",\n id,\n invalid: invalidProp,\n required: requiredProp,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n ...props\n } = allProps;\n // A Field's label names the input through its htmlFor; the id, description, invalid and\n // required state follow the shared rules (Stage 1 Field-R2). They are kept out of the spread\n // below, so what is computed here is what renders.\n const {\n id: finalId,\n describedBy,\n invalid,\n ariaInvalid: finalAriaInvalid,\n required,\n } = useFieldWiring(\n {\n id,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n invalid: invalidProp,\n required: requiredProp,\n },\n { label: \"for\" },\n );\n\n if (process.env.NODE_ENV !== \"production\") {\n // Every prop it received: `size` and `type` are the manifest's enums. The manifest lists the\n // text-like and date-like types; one that breaks the field (checkbox, file, range…) is\n // reported, while TypeScript still accepts any input type (Input-R2).\n devValidate(\"Input\", InputManifest.Input!.props, { ...allProps });\n }\n\n const temporal = type === \"date\" || type === \"time\" || type === \"datetime-local\";\n const PickerIcon = type === \"time\" ? Clock : Calendar;\n const control = (\n <input\n ref={ref}\n id={finalId}\n type={type}\n aria-invalid={finalAriaInvalid}\n aria-describedby={describedBy}\n required={required}\n className={clsx(\n inputStyles(),\n // Forced colors paint outline-hidden's transparent outline; the field draws focus instead.\n FORCED_NO_RESTING_OUTLINE,\n temporal &&\n \"h-full w-full pr-3u [color-scheme:dark] [&::-webkit-calendar-picker-indicator]:absolute [&::-webkit-calendar-picker-indicator]:right-0 [&::-webkit-calendar-picker-indicator]:top-1/2 [&::-webkit-calendar-picker-indicator]:-translate-y-1/2 [&::-webkit-calendar-picker-indicator]:m-0 [&::-webkit-calendar-picker-indicator]:h-oprt-sm [&::-webkit-calendar-picker-indicator]:w-oprt-sm [&::-webkit-calendar-picker-indicator]:p-0 [&::-webkit-calendar-picker-indicator]:opacity-0 [&::-webkit-calendar-picker-indicator]:cursor-pointer\",\n )}\n {...props}\n />\n );\n\n return (\n // In forced colors, which drop the focus shadow and flatten the border, focus anywhere in the\n // field is a 2px CanvasText outline around it, as for ComboBox (Input-B5-1).\n <div className={clsx(wrapperStyles({ invalid, size }), FORCED_FOCUS_WITHIN, className)}>\n {promptSymbol && (\n <span aria-hidden=\"true\" className=\"text-oprt-accent font-mono text-oprt-md\">\n {\">\"}\n </span>\n )}\n {prefix && <span className=\"min-w-0 oprt-wrap-anywhere text-oprt-text-dim\">{prefix}</span>}\n {temporal ? (\n <div className=\"relative flex min-w-0 flex-1 items-center self-stretch\">\n {control}\n {/* Keep the native picker hit area and keyboard behavior. Only its\n drawing is replaced; the overlay never intercepts input. */}\n <span\n data-oprt-picker-icon=\"\"\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-y-0 right-0 flex w-3u items-center justify-center bg-oprt-surface-2 text-oprt-text-dim peer-disabled:bg-oprt-surface-3\"\n >\n <PickerIcon size={16} />\n </span>\n </div>\n ) : (\n control\n )}\n {suffix && <span className=\"min-w-0 oprt-wrap-anywhere text-oprt-text-dim\">{suffix}</span>}\n </div>\n );\n});\nInput.displayName = \"Input\";\n","Inspector/index.ts":"export { Inspector, type InspectorProps } from \"./Inspector\";\nexport { InspectorManifest } from \"./Inspector.manifest\";\n","Inspector/Inspector.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const InspectorManifest: Record<string, Component> = {\n Inspector: {\n description:\n \"Modeless named details aside that leaves background content and focus navigation available.\",\n props: {\n open: {\n type: \"boolean\",\n default: true,\n },\n title: {\n type: \"string\",\n required: true,\n description: \"Accessible title; entity names preserve supplied casing.\",\n },\n description: {\n type: \"string\",\n },\n onClose: {\n type: \"function\",\n description:\n \"Shows Close when supplied. The caller manages closing state and return focus.\",\n },\n },\n slots: {\n children: {\n type: \"ReactNode\",\n },\n },\n composable_with: [\"AppShell\", \"SplitPane\", \"OperationalStatus\", \"ActionStatus\"],\n never_inside: [\"Button\"],\n example:\n \"<Inspector open={open} title={asset.name} onClose={closeAndRestoreFocus}><OperationalStatus state={asset.status} source={source} updatedAt={asset.updatedAt} /></Inspector>\",\n },\n};\n","Inspector/Inspector.tsx":"\"use client\";\n\nimport { forwardRef, useRef, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\n// break-words, as CardBody: the inspector holds any content, and a token in it wraps instead of\n// widening the page.\nconst inspectorStyles = cva(\n \"flex min-w-0 flex-col gap-2u break-words border border-oprt-border bg-oprt-surface-1 p-2u font-sans text-oprt-base normal-case text-oprt-text\",\n);\nexport type InspectorProps = Omit<HTMLAttributes<HTMLElement>, \"title\"> & {\n open?: boolean;\n title: string;\n description?: string;\n onClose?: () => void;\n};\n/** Modeless details. Does not move or trap focus; the caller owns return focus. */\nexport const Inspector = forwardRef<HTMLElement, InspectorProps>(\n (\n {\n open = true,\n title,\n description,\n onClose,\n children,\n className,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLElement | null>(null);\n const setRef = useMergedRefs<HTMLElement>(root, ref);\n // The title and description ids, reconciled with the markup a hydration kept, so a part\n // rendered later (a description that arrives after the page loads) matches what the aside\n // points at (fieldWiringPlan step 6).\n const ids = usePartIds([\"title\", \"description\"], {\n readRendered: () => root.current?.getAttribute(\"data-oprt-ids\"),\n });\n if (!open) return null;\n return (\n <aside\n {...props}\n ref={setRef}\n data-oprt-ids={ids.base}\n aria-labelledby={joinIds(ids.title, labelledBy)}\n aria-describedby={joinIds(description ? ids.description : undefined, describedBy)}\n className={clsx(inspectorStyles(), className)}\n >\n {/* A long title wraps and breaks anywhere; Close drops below it and stays in view. */}\n <div className=\"flex flex-wrap items-start justify-between gap-1u\">\n <h3\n id={ids.title}\n className=\"m-0 min-w-0 text-oprt-md font-medium normal-case oprt-wrap-anywhere\"\n >\n {title}\n </h3>\n {onClose && (\n <Button variant=\"tertiary\" size=\"sm\" onClick={onClose} aria-label={`Close ${title}`}>\n Close\n </Button>\n )}\n </div>\n {description && (\n <p id={ids.description} className=\"m-0 min-w-0 text-oprt-text-dim oprt-wrap-anywhere\">\n {description}\n </p>\n )}\n {children}\n </aside>\n );\n },\n);\nInspector.displayName = \"Inspector\";\n","JobStatus/index.ts":"export { JobStatus, type JobStatusProps, type JobState } from \"./JobStatus\";\nexport { JobStatusManifest } from \"./JobStatus.manifest\";\n","JobStatus/JobStatus.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const JobStatusManifest: Record<string, Component> = {\n JobStatus: {\n description:\n \"Application-supplied job state and validated item progress with explicit cancellation/retry requests. Requests never establish a job outcome. A pending request is announced through a live region that is always present; native attributes reach the job region; long text wraps.\",\n props: {\n jobId: { type: \"string\", required: true },\n label: { type: \"string\", required: true },\n state: {\n type: \"enum\",\n values: [\n \"queued\",\n \"running\",\n \"cancel-requested\",\n \"canceled\",\n \"complete\",\n \"partial\",\n \"failed\",\n ],\n required: true,\n },\n message: { type: \"string\" },\n progress: {\n type: \"node\",\n description:\n \"{ completed: number; total: number | null }. Nonnegative safe integers; completed cannot exceed a known total. Null total is indeterminate; zero shows zero items without a percentage.\",\n },\n requestPending: {\n type: \"enum\",\n values: [\"cancel\", \"retry\"],\n description:\n \"Pending request kind, or null. Prevents duplicate requests without changing the supplied state.\",\n },\n requestError: {\n type: \"string\",\n description: \"Request-delivery feedback supplied independently of the job outcome.\",\n },\n onCancelRequest: {\n type: \"function\",\n description:\n \"(jobId) => void. Offered only while queued or running. Does not establish cancellation.\",\n },\n onRetryRequest: {\n type: \"function\",\n description:\n \"(jobId) => void. Offered only for failed, partial, or canceled jobs. The application chooses eligible work and request identity.\",\n },\n disabled: { type: \"boolean\", default: false },\n className: { type: \"string\" },\n },\n composable_with: [\"ActionStatus\", \"Progress\", \"Button\", \"BulkResults\"],\n never_inside: [\"Button\"],\n example:\n '<JobStatus jobId=\"import-1\" label=\"Import equipment\" state=\"cancel-requested\" progress={{completed:2,total:5}} />',\n },\n};\n","JobStatus/JobStatus.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useState, type HTMLAttributes } from \"react\";\nimport { useActionFocus } from \"../_internal/useActionFocus\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { ActionStatusLine } from \"../ActionStatus/ActionStatus\";\nimport { Progress } from \"../Progress/Progress\";\nimport { Button } from \"../Button/Button\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { JobStatusManifest } from \"./JobStatus.manifest\";\nexport type JobState =\n | \"queued\"\n | \"running\"\n | \"cancel-requested\"\n | \"canceled\"\n | \"complete\"\n | \"partial\"\n | \"failed\";\nexport type JobStatusProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"role\"> & {\n jobId: string;\n label: string;\n state: JobState;\n message?: string;\n progress?: { completed: number; total: number | null };\n requestPending?: \"cancel\" | \"retry\" | null;\n requestError?: string;\n onCancelRequest?: (jobId: string) => void;\n onRetryRequest?: (jobId: string) => void;\n disabled?: boolean;\n};\nconst jobStyles = cva(\n \"flex min-w-0 flex-col gap-2u border border-oprt-border p-2u font-sans text-oprt-base normal-case text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\nexport const JobStatus = forwardRef<HTMLDivElement, JobStatusProps>((allProps, ref) => {\n const {\n jobId,\n label,\n state,\n message,\n progress,\n requestPending = null,\n requestError,\n onCancelRequest,\n onRetryRequest,\n disabled = false,\n className,\n onFocusCapture,\n onBlurCapture,\n ...props\n } = allProps;\n const focus = useActionFocus(ref, { onFocusCapture, onBlurCapture });\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"JobStatus\", JobStatusManifest.JobStatus!.props, allProps);\n }\n\n const validProgress =\n !progress ||\n (Number.isSafeInteger(progress.completed) &&\n progress.completed >= 0 &&\n (progress.total === null ||\n (Number.isSafeInteger(progress.total) &&\n progress.total >= 0 &&\n progress.completed <= progress.total)));\n const canCancel = !!onCancelRequest && (state === \"queued\" || state === \"running\");\n const canRetry = !!onRetryRequest && [\"failed\", \"partial\", \"canceled\"].includes(state);\n const blocked = disabled || requestPending !== null;\n // The sentence for a pending request is written into a live region that is on the page from the\n // start: a live region created together with its text is commonly not announced. The visible copy\n // is plain text, hidden from assistive technology so it is not read twice.\n const pendingText = requestPending\n ? `${requestPending === \"cancel\" ? \"Cancellation\" : \"Retry\"} request pending. Awaiting an application update.`\n : \"\";\n // The same update usually disables the focused request button, and focus then moves to the job\n // (the restore effect above). A screen reader announces that move and cuts off speech it had\n // queued, so the sentence is written two frames after it, as Radix's toast announcer does.\n const [spokenText, setSpokenText] = useState(pendingText);\n useEffect(() => {\n let second = 0;\n const first = requestAnimationFrame(() => {\n second = requestAnimationFrame(() => setSpokenText(pendingText));\n });\n return () => {\n cancelAnimationFrame(first);\n cancelAnimationFrame(second);\n };\n }, [pendingText]);\n return (\n <div\n {...props}\n {...focus}\n role=\"region\"\n aria-label={label}\n tabIndex={-1}\n className={clsx(jobStyles(), className)}\n >\n <ActionStatusLine state={state} label={label} message={message}>\n {/* Inside the status region, so the explanation is announced with the state change. */}\n {state === \"cancel-requested\" && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere\">\n Cancellation was requested. Work may still finish.\n </p>\n )}\n </ActionStatusLine>\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere font-mono\">Job: {jobId}</p>\n {!validProgress && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-error\">\n Invalid progress. Supply nonnegative integer counts with completed no greater than total.\n </p>\n )}\n {progress && validProgress && (\n <div className=\"flex min-w-0 flex-col gap-1u\">\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere font-mono tabular-nums\">\n {progress.total === 0\n ? \"0 items\"\n : progress.total === null\n ? `${progress.completed} items processed; total unknown`\n : `${progress.completed} of ${progress.total} items processed`}\n </p>\n {progress.total !== 0 && (\n <Progress\n label={`${label} progress`}\n {...(progress.total === null\n ? {}\n : { value: progress.completed, max: progress.total })}\n />\n )}\n </div>\n )}\n <p aria-live=\"polite\" aria-atomic=\"true\" className=\"sr-only m-0\">\n {spokenText}\n </p>\n {requestPending && (\n <p aria-hidden=\"true\" className=\"m-0 min-w-0 oprt-wrap-anywhere\">\n {pendingText}\n </p>\n )}\n {requestError && (\n <p className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-error\" role=\"alert\">\n {requestError}\n </p>\n )}\n {(canCancel || canRetry) && (\n <div className=\"flex flex-wrap gap-1u\">\n {canCancel && (\n <Button\n className=\"max-w-full\"\n size=\"sm\"\n variant=\"tertiary\"\n disabled={blocked}\n onClick={() => {\n if (!blocked && canCancel) onCancelRequest?.(jobId);\n }}\n >\n Request cancellation\n </Button>\n )}\n {canRetry && (\n <Button\n className=\"max-w-full\"\n size=\"sm\"\n variant=\"secondary\"\n disabled={blocked}\n onClick={() => {\n if (!blocked && canRetry) onRetryRequest?.(jobId);\n }}\n >\n Request retry\n </Button>\n )}\n </div>\n )}\n </div>\n );\n});\nJobStatus.displayName = \"JobStatus\";\n","LayerControl/index.ts":"export { LayerControl } from \"./LayerControl\";\nexport type { LayerControlItem, LayerControlProps } from \"./LayerControl\";\nexport { LayerControlManifest } from \"./LayerControl.manifest\";\n","LayerControl/LayerControl.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const LayerControlManifest: Record<string, Component> = {\n LayerControl: {\n description:\n \"Controlled top-first map layers with visibility, opacity and keyboard-operable ordering.\",\n props: {\n label: { type: \"string\", default: \"Map layers\" },\n layers: {\n type: \"array\",\n required: true,\n description:\n \"Readonly LayerControlItem[]. IDs must be unique and nonempty. First item is drawn on top. Nonfinite/out-of-range opacity stays unavailable; zero is transparent.\",\n items: {\n id: \"string\",\n label: \"string\",\n visible: \"boolean\",\n opacity: \"number (0..1)\",\n description: \"string (optional)\",\n disabled: \"boolean (optional)\",\n },\n },\n onLayersChange: {\n type: \"function\",\n description:\n \"(layers: readonly LayerControlItem[]) => void. Receives a new frozen array of copied frozen items. Requests do not change supplied values. Omit for read-only controls. Disabled rows cannot mutate or be crossed.\",\n },\n disabled: { type: \"boolean\", default: false },\n className: { type: \"string\" },\n },\n composable_with: [\"MapControls\", \"MapLegend\", \"Slider\", \"Checkbox\", \"Field\"],\n never_inside: [\"Button\"],\n example: \"<LayerControl layers={layers} onLayersChange={setLayers} />\",\n },\n};\n","LayerControl/LayerControl.tsx":"\"use client\";\n\nimport { forwardRef, useImperativeHandle, useRef, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { Checkbox } from \"../Checkbox/Checkbox\";\nimport { Slider } from \"../Slider/Slider\";\nimport { Field } from \"../Field/Field\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\n\nexport interface LayerControlItem {\n id: string;\n label: string;\n visible: boolean;\n opacity: number;\n description?: string;\n disabled?: boolean;\n}\nexport type LayerControlProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label?: string;\n layers: readonly LayerControlItem[];\n onLayersChange?: (layers: readonly LayerControlItem[]) => void;\n disabled?: boolean;\n};\nconst controlStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\nconst rowStyles = cva(\n \"flex min-w-0 flex-col gap-2u border border-oprt-border p-2u outline-hidden focus-visible:shadow-oprt-focus\",\n);\n\n/** Controlled top-first layer list; disabled rows retain their fixed positions. */\nexport const LayerControl = forwardRef<HTMLDivElement, LayerControlProps>(\n (\n {\n label = \"Map layers\",\n layers,\n onLayersChange,\n disabled = false,\n className,\n id,\n onFocusCapture,\n onBlurCapture,\n \"aria-labelledby\": labelledBy,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n // Part ids match the page after a hydration whose ids disagree with the server's, so a row\n // added later is labeled the same way as the rows the page was served with.\n const labelRef = useRef<HTMLHeadingElement>(null);\n const ids = usePartIds([\"label\"], {\n id,\n readRendered: () => labelRef.current?.id.replace(/-label$/, \"\"),\n });\n const rows = useRef(new Map<string, HTMLLIElement>());\n useImperativeHandle(ref, () => root.current!, []);\n const invalidIds = layers.some(\n (item, index) =>\n !item.id.trim() || layers.findIndex((other) => other.id === item.id) !== index,\n );\n const blocked = disabled || !onLayersChange || invalidIds;\n const unavailable = (index: number) => blocked || layers[index]?.disabled === true;\n const canMove = (index: number, direction: -1 | 1) =>\n !unavailable(index) &&\n index + direction >= 0 &&\n index + direction < layers.length &&\n !unavailable(index + direction);\n const emit = (next: readonly LayerControlItem[]) => {\n if (!blocked) onLayersChange?.(Object.freeze(next.map((item) => Object.freeze({ ...item }))));\n };\n const change = (\n index: number,\n patch: Partial<Pick<LayerControlItem, \"visible\" | \"opacity\">>,\n ) => {\n if (!unavailable(index))\n emit(layers.map((item, position) => (position === index ? { ...item, ...patch } : item)));\n };\n const move = (index: number, direction: -1 | 1) => {\n if (!canMove(index, direction)) return;\n const next = [...layers];\n [next[index], next[index + direction]] = [next[index + direction]!, next[index]!];\n emit(next);\n };\n // A removed or disabled control hands focus to its row, else to the row now at its place, else\n // to the group. Only while the user is still there: one who has moved on is left alone.\n const restoreFocus = useRestoreFocusOnRemoval<HTMLDivElement, { id: string; index: number }>(\n root,\n {\n track: (element) => {\n const index = layers.findIndex((item) => rows.current.get(item.id)?.contains(element));\n return index < 0 ? null : { id: layers[index]!.id, index };\n },\n findTarget: ({ id: layerId, index }) =>\n rows.current.get(layerId) ??\n rows.current.get(layers[Math.min(index, layers.length - 1)]?.id ?? \"\") ??\n root.current,\n },\n );\n return (\n <div\n {...props}\n id={id}\n ref={root}\n role=\"group\"\n tabIndex={-1}\n aria-labelledby={joinIds(ids.label, labelledBy)}\n className={clsx(controlStyles(), className)}\n onFocusCapture={(event) => {\n onFocusCapture?.(event);\n restoreFocus.onFocusCapture(event);\n }}\n onBlurCapture={(event) => {\n onBlurCapture?.(event);\n restoreFocus.onBlurCapture(event);\n }}\n >\n <h3\n ref={labelRef}\n id={ids.label}\n className=\"m-0 min-w-0 font-medium uppercase tracking-oprt-sm oprt-wrap-anywhere\"\n >\n {label}\n </h3>\n <p className=\"m-0 text-oprt-text-dim\">Top of this list is drawn on top of the map.</p>\n {invalidIds && (\n <p className=\"m-0 text-oprt-error\">\n Layer IDs must be unique and nonempty. Editing is unavailable until corrected.\n </p>\n )}\n {layers.length === 0 ? (\n <p className=\"m-0\">No layers supplied.</p>\n ) : (\n <ol aria-label={label} className=\"m-0 flex min-w-0 list-none flex-col gap-2u p-0\">\n {layers.map((item, index) => {\n const validOpacity =\n Number.isFinite(item.opacity) && item.opacity >= 0 && item.opacity <= 1;\n return (\n <li\n key={invalidIds ? index : item.id}\n ref={(node) => {\n if (node) rows.current.set(item.id, node);\n else rows.current.delete(item.id);\n }}\n tabIndex={-1}\n className={rowStyles()}\n >\n <div className=\"flex min-w-0 items-start gap-1u\">\n <Checkbox\n id={`${ids.base}-visibility-${index}`}\n aria-label={`Show ${item.label}`}\n checked={item.visible}\n disabled={unavailable(index)}\n onCheckedChange={(checked) => {\n if (typeof checked === \"boolean\") change(index, { visible: checked });\n }}\n />\n <label\n htmlFor={`${ids.base}-visibility-${index}`}\n className=\"m-0 min-w-0 font-medium uppercase tracking-oprt-sm oprt-wrap-anywhere\"\n >\n {item.label}\n </label>\n </div>\n {item.description && (\n <p className=\"m-0 text-oprt-text-dim oprt-wrap-anywhere\">{item.description}</p>\n )}\n {validOpacity ? (\n <Field\n label={`${item.label} opacity percent`}\n className=\"min-w-0 oprt-wrap-anywhere [&>label]:uppercase [&>label]:tracking-oprt-sm\"\n >\n <Slider\n min={0}\n max={100}\n step={1}\n value={item.opacity * 100}\n disabled={unavailable(index)}\n onValueChange={(value: number) => {\n if (Number.isFinite(value) && value >= 0 && value <= 100)\n change(index, { opacity: value / 100 });\n }}\n />\n <p className=\"m-0 font-mono tabular-nums\">\n {Math.round(item.opacity * 100)}%\n </p>\n </Field>\n ) : (\n <p className=\"m-0 text-oprt-text-dim\">Opacity unavailable</p>\n )}\n <div className=\"flex min-w-0 flex-wrap gap-1u\">\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n aria-label={`Move ${item.label} up`}\n disabled={!canMove(index, -1)}\n onClick={() => move(index, -1)}\n >\n Move up\n </Button>\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n aria-label={`Move ${item.label} down`}\n disabled={!canMove(index, 1)}\n onClick={() => move(index, 1)}\n >\n Move down\n </Button>\n </div>\n </li>\n );\n })}\n </ol>\n )}\n </div>\n );\n },\n);\nLayerControl.displayName = \"LayerControl\";\n","Link/index.ts":"export { Link, type LinkProps } from \"./Link\";\nexport { LinkManifest } from \"./Link.manifest\";\n","Link/Link.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const LinkManifest: Record<string, Component> = {\n Link: {\n description:\n \"Native anchor navigation with an evident link treatment and token-based keyboard focus. It is inline, so a link in a sentence wraps with the sentence; as a flex or grid item it is a 24px target. Only http, https, mailto, tel, relative and hash destinations are rendered.\",\n props: {\n href: {\n type: \"string\",\n required: true,\n description:\n \"Native anchor destination; no click interception or router dependency. Only http, https, mailto, tel, relative and hash destinations are rendered; any other scheme (javascript:, vbscript:, data:, blob:, file:) renders as plain text, not a link and not a Tab stop. A blob: or data: destination is rendered when download is set; offer only files your page generates from content you trust, and give a file built from untrusted content to a Button that starts the download instead of a persistent link.\",\n },\n children: {\n type: \"node\",\n description:\n \"Accessible link name. Authored names and prose preserve their case; provide aria-label for icon-only links.\",\n },\n target: {\n type: \"string\",\n description:\n \"Native browsing context, such as _blank. Explain new-window behavior in the accessible name.\",\n },\n rel: {\n type: \"string\",\n description:\n \"Native relationship attributes; use noopener noreferrer for external new-window links.\",\n },\n download: {\n type: \"union\",\n types: [\"string\", \"boolean\"],\n description: \"Native download attribute, subject to browser behavior.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"DescriptionList\", \"EntityPreview\", \"Popover\", \"Inspector\", \"Card\"],\n never_inside: [\"Button\", \"Link\"],\n example: '<Link href=\"/equipment/pump-07\">Pump 07</Link>',\n },\n};\n","Link/Link.tsx":"\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport {\n Children,\n forwardRef,\n isValidElement,\n type AnchorHTMLAttributes,\n type ReactNode,\n} from \"react\";\n\n// An inline link, so a link in a sentence wraps with the sentence. The vertical padding is what is\n// left of the 24px minimum height around one 16px line and the 2px of border: it adds nothing to an\n// inline link's line, but when the link is a flex or grid item (a block) it fills the minimum height\n// exactly and centers the text. `box-decoration-clone` gives every wrapped line its own padding and\n// focus ring. An icon-only link is its own 24px box.\nconst linkStyles = cva(\n \"min-h-oprt-sm min-w-oprt-sm border border-transparent py-[calc((var(--oprt-height-sm)-2px-var(--oprt-line-base))/2)] box-decoration-clone font-sans text-oprt-base font-medium text-oprt-accent underline underline-offset-2 oprt-wrap-anywhere [&_svg]:inline-block hover:text-oprt-accent-hover aria-disabled:text-[color:inherit] aria-disabled:hover:text-[color:inherit] aria-disabled:no-underline aria-disabled:cursor-default focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus forced-colors:focus-visible:outline-solid forced-colors:focus-visible:outline-2 forced-colors:focus-visible:outline-[CanvasText]\",\n {\n variants: {\n // An icon-only link is a 24px box of its own, with the icon centered in it.\n iconOnly: { true: \"inline-flex items-center justify-center\", false: \"\" },\n },\n defaultVariants: { iconOnly: false },\n },\n);\n\ntype ChildProps = { children?: ReactNode; \"aria-hidden\"?: unknown };\n\n/**\n * Whether the link holds an icon and nothing else: one element that is an svg, an element hidden\n * from assistive technology, or a component used with no children (`<Download size={16} />`).\n * It is decided from the children, not by a selector: `:only-child` ignores text, so an icon\n * beside a word was taken for icon-only and moved to its own line in a sentence.\n */\nfunction isIconOnly(children: ReactNode): boolean {\n const parts = Children.toArray(children).filter(\n (part) => !(typeof part === \"string\" && part.trim() === \"\"),\n );\n const [only] = parts;\n if (parts.length !== 1 || !isValidElement<ChildProps>(only)) return false;\n const hidden = only.props[\"aria-hidden\"];\n return (\n only.type === \"svg\" ||\n hidden === true ||\n hidden === \"true\" ||\n (typeof only.type !== \"string\" && only.props.children == null)\n );\n}\n\nexport type LinkProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\"> & {\n /**\n * Native navigation destination. Use Button for actions. Only http, https, mailto, tel, relative\n * and hash destinations are rendered; any other scheme renders as plain text, not a link.\n */\n href: string;\n};\n\n/** Schemes a link may point at. Anything else, including javascript: and data:, is not rendered. */\nconst SAFE_SCHEMES = new Set([\"http\", \"https\", \"mailto\", \"tel\"]);\n/** Generated files, which only ever download: a blob: or data: href is allowed with `download`. */\nconst DOWNLOAD_SCHEMES = new Set([\"blob\", \"data\"]);\n\n/**\n * The scheme of a URL as the browser's parser reads it, or null for a relative reference. The\n * parser drops leading and trailing control characters and spaces, and every tab and newline\n * anywhere, before it reads the scheme, so \" JaVaScRiPt:\" and \"java<TAB>script:\" are javascript:.\n */\nfunction schemeOf(destination: string): string | null {\n // Code points 0 to 32 are the control characters and the space, at either end.\n let start = 0;\n let end = destination.length;\n while (start < end && destination.charCodeAt(start) <= 0x20) start += 1;\n while (end > start && destination.charCodeAt(end - 1) <= 0x20) end -= 1;\n const url = destination.slice(start, end).replace(/[\\t\\n\\r]/g, \"\");\n const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(url);\n return scheme ? scheme[1]!.toLowerCase() : null;\n}\n\nfunction isSafeHref(destination: string, download: boolean): boolean {\n const scheme = schemeOf(destination);\n return scheme === null || SAFE_SCHEMES.has(scheme) || (download && DOWNLOAD_SCHEMES.has(scheme));\n}\n\n// Dev only: once per destination, so a re-render does not repeat the same mistake.\nconst reported = new Set<string>();\nfunction reportUnsafeHref(destination: string) {\n if (process.env.NODE_ENV === \"production\" || reported.has(destination)) return;\n reported.add(destination);\n console.error(\n JSON.stringify(\n {\n component: \"Link\",\n error: \"UNSAFE_HREF\",\n prop: \"href\",\n received: destination.slice(0, 80),\n suggestion:\n \"Link renders only http, https, mailto, tel, relative and hash destinations (and blob: or data: with download). Any other scheme renders as plain text, not a link and not a Tab stop. Use a Button for an action.\",\n },\n null,\n 2,\n ),\n );\n}\n\n/** A native anchor: the browser owns navigation, modifiers, and context menus. */\nexport const Link = forwardRef<HTMLAnchorElement, LinkProps>(\n ({ className, href, children, ...props }, ref) => {\n // A destination is data, and a javascript: URL in an href runs script when it is clicked. An\n // unsafe one leaves the anchor without an href: plain text, not a link and not a Tab stop. An\n // anchor without an href has the generic role, so `aria-disabled` is not announced; it is there\n // for the plain-text look (the `aria-disabled:` classes) and for tests.\n const safe =\n typeof href !== \"string\" ||\n isSafeHref(href, props.download !== undefined && props.download !== false);\n if (!safe) reportUnsafeHref(href);\n return (\n <a\n ref={ref}\n className={clsx(linkStyles({ iconOnly: isIconOnly(children) }), className)}\n {...(safe ? { href } : { \"aria-disabled\": true })}\n {...props}\n >\n {children}\n </a>\n );\n },\n);\nLink.displayName = \"Link\";\n","MapControls/index.ts":"export { MapControls } from \"./MapControls\";\nexport type { MapAction, MapControlsProps } from \"./MapControls\";\nexport { MapControlsManifest } from \"./MapControls.manifest\";\n","MapControls/MapControls.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const MapControlsManifest: Record<string, Component> = {\n MapControls: {\n description:\n \"Engine-independent map navigation requests with supplied zoom and north-bearing readouts.\",\n props: {\n label: { type: \"string\", default: \"Map controls\" },\n zoom: {\n type: \"number\",\n description:\n \"Supplied finite zoom within minZoom/maxZoom; null or missing is unknown. Readouts round to two decimals without changing the supplied value.\",\n },\n bearing: {\n type: \"number\",\n description:\n \"Supplied clockwise degrees from north in [0,360). Null, missing or invalid values remain unknown. Display rounds to two decimals, so a bearing that rounds to 360 shows 0.\",\n },\n minZoom: { type: \"number\", default: 2 },\n maxZoom: { type: \"number\", default: 20 },\n onAction: {\n type: \"function\",\n description:\n \"(action: MapAction) => void. Requests zoom-in, zoom-out, pan-north/east/south/west, reset-north or fit; no internal viewport change. Omission disables actions.\",\n },\n disabled: { type: \"boolean\", default: false },\n className: { type: \"string\" },\n },\n composable_with: [\"MapScale\", \"LayerControl\", \"MapLegend\", \"CoordinateInput\", \"AppShell\"],\n never_inside: [\"Button\"],\n example: \"<MapControls zoom={view.zoom} bearing={view.bearing} onAction={requestMapAction} />\",\n },\n};\n","MapControls/MapControls.tsx":"\"use client\";\n\nimport { forwardRef, useImperativeHandle, useRef, type HTMLAttributes } from \"react\";\nimport { useNumberFormatter } from \"react-aria\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\n\nexport type MapAction =\n | \"zoom-in\"\n | \"zoom-out\"\n | \"pan-north\"\n | \"pan-east\"\n | \"pan-south\"\n | \"pan-west\"\n | \"reset-north\"\n | \"fit\";\nexport type MapControlsProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label?: string;\n zoom?: number | null;\n bearing?: number | null;\n minZoom?: number;\n maxZoom?: number;\n onAction?: (action: MapAction) => void;\n disabled?: boolean;\n};\n// The group takes focus only as a fallback, when every action it holds became unavailable.\nconst controlStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\n/** Two decimals; a constant, so React Aria keeps one formatter per locale. */\nconst READOUT = { maximumFractionDigits: 2 };\n/** A bearing rounded as it is shown, kept in [0, 360): 359.996 shows 0, and -0 shows 0 (MapControls-R2). */\nconst shownBearing = (bearing: number) => ((Math.round(bearing * 100) / 100) % 360) + 0;\nconst actions: readonly { id: MapAction; label: string }[] = [\n { id: \"zoom-in\", label: \"Zoom in\" },\n { id: \"zoom-out\", label: \"Zoom out\" },\n { id: \"pan-north\", label: \"Pan north\" },\n { id: \"pan-east\", label: \"Pan east\" },\n { id: \"pan-south\", label: \"Pan south\" },\n { id: \"pan-west\", label: \"Pan west\" },\n { id: \"reset-north\", label: \"Reset north\" },\n { id: \"fit\", label: \"Fit map\" },\n];\n/** Navigation requests; only supplied engine feedback changes the readouts. */\nexport const MapControls = forwardRef<HTMLDivElement, MapControlsProps>(\n (\n {\n label = \"Map controls\",\n zoom = null,\n bearing = null,\n minZoom = 2,\n maxZoom = 20,\n onAction,\n disabled = false,\n className,\n id,\n \"aria-labelledby\": labelledBy,\n onFocusCapture,\n onBlurCapture,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => root.current!, []);\n // Part ids match the page after a hydration whose ids disagree with the server's.\n const labelRef = useRef<HTMLHeadingElement>(null);\n const ids = usePartIds([\"label\"], {\n id,\n readRendered: () => labelRef.current?.id.replace(/-label$/, \"\"),\n });\n // Digits grouped in the application's locale, as DatePicker reads it (MapControls-R3).\n const readout = useNumberFormatter(READOUT);\n const buttons = useRef(new Map<MapAction, HTMLButtonElement>());\n const limitsValid = Number.isFinite(minZoom) && Number.isFinite(maxZoom) && minZoom <= maxZoom;\n const validZoom =\n typeof zoom === \"number\" &&\n Number.isFinite(zoom) &&\n limitsValid &&\n zoom >= minZoom &&\n zoom <= maxZoom;\n const validBearing =\n typeof bearing === \"number\" && Number.isFinite(bearing) && bearing >= 0 && bearing < 360;\n const unavailable = (action: MapAction) =>\n disabled ||\n !onAction ||\n (action === \"zoom-in\" && (!validZoom || zoom! >= maxZoom)) ||\n (action === \"zoom-out\" && (!validZoom || zoom! <= minZoom)) ||\n (action === \"reset-north\" && !validBearing);\n // A zoom button that its own accepted request disables at a limit hands focus to the other one;\n // any other action that becomes unavailable hands it to the group (MapControls-R1).\n const restoreFocus = useRestoreFocusOnRemoval<HTMLDivElement, MapAction | null>(root, {\n track: (element) =>\n [...buttons.current].find(([, button]) => button === element)?.[0] ?? null,\n findTarget: (action) => {\n const opposite =\n action === \"zoom-in\" ? \"zoom-out\" : action === \"zoom-out\" ? \"zoom-in\" : null;\n const other = opposite ? buttons.current.get(opposite) : undefined;\n return other?.isConnected && !other.disabled ? other : root.current;\n },\n });\n return (\n <div\n {...props}\n id={id}\n ref={root}\n role=\"group\"\n tabIndex={-1}\n aria-labelledby={joinIds(ids.label, labelledBy)}\n className={clsx(controlStyles(), className)}\n onFocusCapture={(event) => {\n onFocusCapture?.(event);\n restoreFocus.onFocusCapture(event);\n }}\n onBlurCapture={(event) => {\n onBlurCapture?.(event);\n restoreFocus.onBlurCapture(event);\n }}\n >\n <h3\n ref={labelRef}\n id={ids.label}\n className=\"m-0 min-w-0 font-medium uppercase tracking-oprt-sm oprt-wrap-anywhere\"\n >\n {label}\n </h3>\n <div className=\"flex flex-wrap gap-2u font-mono tabular-nums\">\n <p className=\"m-0\">{validZoom ? `Zoom ${readout.format(zoom!)}` : \"Zoom unknown\"}</p>\n <p className=\"m-0\">\n {validBearing\n ? `Bearing ${readout.format(shownBearing(bearing!))}°`\n : \"Bearing unknown\"}\n </p>\n </div>\n {!limitsValid && (\n <p className=\"m-0 text-oprt-error\">\n Zoom limits are invalid. Supply finite minimum and maximum values in order.\n </p>\n )}\n <div className=\"flex min-w-0 flex-wrap gap-1u\">\n {actions.map((action) => (\n <Button\n key={action.id}\n ref={(button) => {\n if (button) buttons.current.set(action.id, button);\n else buttons.current.delete(action.id);\n }}\n size=\"sm\"\n variant=\"secondary\"\n disabled={unavailable(action.id)}\n onClick={() => {\n if (!unavailable(action.id)) onAction?.(action.id);\n }}\n >\n {action.label}\n </Button>\n ))}\n </div>\n </div>\n );\n },\n);\nMapControls.displayName = \"MapControls\";\n","MapLegend/index.ts":"export { MapLegend } from \"./MapLegend\";\nexport type { MapLegendItem, MapLegendProps } from \"./MapLegend\";\nexport { MapLegendManifest } from \"./MapLegend.manifest\";\n","MapLegend/MapLegend.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const MapLegendManifest: Record<string, Component> = {\n MapLegend: {\n description: \"Text and shape map legend for supplied point, line and area meanings.\",\n props: {\n label: { type: \"string\", default: \"Map legend\" },\n items: {\n type: \"array\",\n required: true,\n description:\n \"Readonly MapLegendItem[]; labels/descriptions explain the supplied geometry. Variants use existing tokens; geometry kind and line/area pattern are always visible as text. Point symbols remain solid.\",\n items: {\n id: \"string\",\n label: \"string\",\n kind: \"point | line | area\",\n variant: \"neutral | success | warning | error | info | accent (optional; neutral)\",\n pattern: \"solid | dashed (optional; solid; applies to line/area)\",\n description: \"string (optional)\",\n },\n },\n className: { type: \"string\" },\n },\n composable_with: [\"LayerControl\", \"MapControls\", \"DescriptionList\"],\n never_inside: [\"Button\"],\n example:\n '<MapLegend items={[{ id: \"coverage\", label: \"Reported coverage\", kind: \"area\", variant: \"info\", description: \"Supplied model footprint\" }]} />',\n },\n};\n","MapLegend/MapLegend.tsx":"\"use client\";\n\nimport { forwardRef, useRef, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\n\nexport interface MapLegendItem {\n id: string;\n label: string;\n kind: \"point\" | \"line\" | \"area\";\n variant?: \"neutral\" | \"success\" | \"warning\" | \"error\" | \"info\" | \"accent\";\n pattern?: \"solid\" | \"dashed\";\n description?: string;\n}\nexport type MapLegendProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label?: string;\n items: readonly MapLegendItem[];\n};\nconst legendStyles = cva(\"flex min-w-0 flex-col gap-2u font-sans text-oprt-base text-oprt-text\");\nconst symbolStyles = cva(\"block shrink-0 forced-colors:text-[CanvasText]\", {\n variants: {\n kind: {\n point: \"h-1u w-1u bg-current forced-colors:border forced-colors:border-[CanvasText]\",\n line: \"w-3u border-t-2 border-current\",\n area: \"h-2u w-3u border-2 border-current\",\n },\n variant: {\n neutral: \"text-oprt-text-dim\",\n success: \"text-oprt-success\",\n warning: \"text-oprt-warning\",\n error: \"text-oprt-error\",\n info: \"text-oprt-info\",\n accent: \"text-oprt-accent\",\n },\n pattern: { solid: \"border-solid\", dashed: \"border-dashed\" },\n },\n defaultVariants: { variant: \"neutral\", pattern: \"solid\" },\n});\nconst kinds = { point: \"Point\", line: \"Line\", area: \"Area\" } as const;\nexport const MapLegend = forwardRef<HTMLDivElement, MapLegendProps>(\n (\n { label = \"Map legend\", items, className, id, \"aria-labelledby\": labelledBy, ...props },\n ref,\n ) => {\n // Part ids match the page after a hydration whose ids disagree with the server's.\n const labelRef = useRef<HTMLHeadingElement>(null);\n const ids = usePartIds([\"label\"], {\n id,\n readRendered: () => labelRef.current?.id.replace(/-label$/, \"\"),\n });\n return (\n <div\n {...props}\n id={id}\n ref={ref}\n role=\"group\"\n aria-labelledby={joinIds(ids.label, labelledBy)}\n className={clsx(legendStyles(), className)}\n >\n <h3\n ref={labelRef}\n id={ids.label}\n className=\"m-0 min-w-0 font-medium uppercase tracking-oprt-sm oprt-wrap-anywhere\"\n >\n {label}\n </h3>\n {items.length === 0 ? (\n <p className=\"m-0\">No legend items supplied.</p>\n ) : (\n <ul aria-label={label} className=\"m-0 flex min-w-0 list-none flex-col gap-2u p-0\">\n {items.map((item, index) => (\n <li key={`${item.id}-${index}`} className=\"flex min-w-0 items-start gap-2u\">\n <span\n aria-hidden=\"true\"\n className=\"flex h-3u w-3u shrink-0 items-center justify-center\"\n >\n <span\n className={symbolStyles({\n kind: item.kind,\n variant: item.variant ?? \"neutral\",\n pattern: item.kind === \"point\" ? \"solid\" : (item.pattern ?? \"solid\"),\n })}\n />\n </span>\n <div className=\"min-w-0 oprt-wrap-anywhere\">\n <p className=\"m-0 font-medium\">{item.label}</p>\n <p className=\"m-0 text-oprt-text-dim\">{kinds[item.kind]}</p>\n {item.kind !== \"point\" && (\n <p className=\"m-0 text-oprt-text-dim\">\n {item.pattern === \"dashed\" ? \"Dashed\" : \"Solid\"}\n </p>\n )}\n {item.description && <p className=\"m-0\">{item.description}</p>}\n </div>\n </li>\n ))}\n </ul>\n )}\n </div>\n );\n },\n);\nMapLegend.displayName = \"MapLegend\";\n","MapScale/index.ts":"export { MapScale } from \"./MapScale\";\nexport type { MapScaleProps } from \"./MapScale\";\nexport { MapScaleManifest } from \"./MapScale.manifest\";\n","MapScale/MapScale.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const MapScaleManifest: Record<string, Component> = {\n MapScale: {\n description: \"Bounded 1/2/5 ground-distance scale from a supplied local map resolution.\",\n props: {\n label: { type: \"string\", default: \"Map scale\" },\n metersPerPixel: {\n type: \"number\",\n description:\n \"Positive finite local ground meters per CSS pixel. Missing/null/nonpositive/nonfinite values show Scale unavailable. Adapter owns projection and scale location.\",\n },\n maxWidth: {\n type: \"number\",\n default: 120,\n description:\n \"Positive finite maximum bar width in CSS pixels. Recomputes against narrower measured container width rather than shrinking a mislabeled bar.\",\n },\n units: {\n type: \"enum\",\n values: [\"metric\", \"imperial\"],\n default: \"metric\",\n description:\n \"Metric uses m/km; imperial uses ft/mi. Both derive from the same supplied ground resolution.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"MapControls\", \"MapLegend\", \"AppShell\"],\n never_inside: [\"Button\"],\n example: '<MapScale metersPerPixel={frame.metersPerPixel} units=\"metric\" />',\n },\n};\n","MapScale/MapScale.tsx":"\"use client\";\n\nimport { forwardRef, useRef, type HTMLAttributes } from \"react\";\nimport { useNumberFormatter } from \"react-aria\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { NumberText } from \"../_internal/numberText\";\nimport { useElementWidth } from \"../_internal/useElementWidth\";\nimport { MapScaleManifest } from \"./MapScale.manifest\";\n\nexport type MapScaleProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label?: string;\n metersPerPixel?: number | null;\n maxWidth?: number;\n units?: \"metric\" | \"imperial\";\n};\n/** A constant, so React Aria keeps one formatter per locale. */\nconst DISTANCE = { maximumSignificantDigits: 15 };\nconst scaleStyles = cva(\"flex min-w-0 flex-col gap-1u font-sans text-oprt-base text-oprt-text\");\nfunction scale(metersPerPixel: number | null, width: number, units: \"metric\" | \"imperial\") {\n if (\n metersPerPixel === null ||\n !Number.isFinite(metersPerPixel) ||\n metersPerPixel <= 0 ||\n !Number.isFinite(width) ||\n width <= 0\n )\n return null;\n const meters = metersPerPixel * width;\n if (!Number.isFinite(meters) || meters <= 0) return null;\n const [factor, unit] =\n units === \"imperial\"\n ? meters >= 1609.344\n ? ([1609.344, \"mi\"] as const)\n : ([0.3048, \"ft\"] as const)\n : meters >= 1000\n ? ([1000, \"km\"] as const)\n : ([1, \"m\"] as const);\n const bound = meters / factor,\n magnitude = 10 ** Math.floor(Math.log10(bound));\n const distance = [5, 2, 1]\n .map((multiple) => multiple * magnitude)\n .find((candidate) => candidate <= bound);\n if (!distance || !Number.isFinite(distance)) return null;\n const pixels = (distance * factor) / metersPerPixel;\n if (!Number.isFinite(pixels) || pixels <= 0 || pixels > width) return null;\n return { pixels, distance, unit };\n}\n/** Ground scale at the adapter's supplied location; never infers DPI or projection. */\nexport const MapScale = forwardRef<HTMLDivElement, MapScaleProps>((allProps, ref) => {\n const {\n label = \"Map scale\",\n metersPerPixel = null,\n maxWidth = 120,\n units = \"metric\",\n className,\n id,\n \"aria-labelledby\": labelledBy,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"MapScale\", MapScaleManifest.MapScale!.props, { ...allProps });\n }\n // Part ids match the page after a hydration whose ids disagree with the server's.\n const labelRef = useRef<HTMLHeadingElement>(null);\n const ids = usePartIds([\"label\"], {\n id,\n readRendered: () => labelRef.current?.id.replace(/-label$/, \"\"),\n });\n // Digits grouped in the application's locale, as DatePicker reads it (MapControls-R3).\n const format = useNumberFormatter(DISTANCE);\n const available = useRef<HTMLDivElement>(null);\n // Unmeasured (null) until the observer reports, and wherever there is no ResizeObserver; the\n // bar then uses maxWidth.\n const measuredWidth = useElementWidth<HTMLDivElement, number | null>(available, {\n initial: null,\n });\n const value =\n Number.isFinite(maxWidth) && maxWidth > 0\n ? scale(\n metersPerPixel,\n measuredWidth === null ? maxWidth : Math.min(maxWidth, measuredWidth),\n units,\n )\n : null;\n return (\n <div\n {...props}\n id={id}\n ref={ref}\n role=\"group\"\n aria-labelledby={joinIds(ids.label, labelledBy)}\n className={clsx(scaleStyles(), className)}\n >\n <h3\n ref={labelRef}\n id={ids.label}\n className=\"m-0 min-w-0 font-medium uppercase tracking-oprt-sm oprt-wrap-anywhere\"\n >\n {label}\n </h3>\n <div ref={available} className=\"min-w-0 w-full\">\n {value ? (\n <>\n <span\n data-testid=\"scale-bar\"\n aria-hidden=\"true\"\n className=\"block h-1u border-x-2 border-b-2 border-oprt-text forced-colors:border-[CanvasText]\"\n style={{ width: value.pixels }}\n />\n {/* The distance breaks only after a digit group, and its unit stays with the last\n one (DC-11, MapScale-R1). */}\n <p className=\"m-0 font-mono tabular-nums\">\n <NumberText value={format.format(value.distance)} unit={value.unit} />\n </p>\n </>\n ) : (\n <p className=\"m-0 text-oprt-text-dim\">Scale unavailable</p>\n )}\n </div>\n </div>\n );\n});\nMapScale.displayName = \"MapScale\";\n","MediaFrame/index.ts":"export { MediaFrame } from \"./MediaFrame\";\nexport type { MediaFrameProps, MediaFrameState } from \"./MediaFrame\";\nexport { MediaFrameManifest } from \"./MediaFrame.manifest\";\n","MediaFrame/MediaFrame.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const MediaFrameManifest: Record<string, Component> = {\n MediaFrame: {\n description:\n \"Named media surface with supplied freshness state, capture time and retained-frame disclosure.\",\n props: {\n label: { type: \"string\", required: true },\n state: {\n type: \"enum\",\n values: [\"live\", \"delayed\", \"paused\", \"recorded\", \"reconnecting\", \"unavailable\"],\n required: true,\n description:\n \"Caller-supplied state. Reconnecting and unavailable always disclose that a current frame is unavailable.\",\n },\n capturedAt: {\n type: \"number\",\n required: true,\n description:\n \"Actual supplied frame capture time in epoch milliseconds, or null (fractional milliseconds allowed). Shown as YYYY-MM-DD HH:MM:SS UTC; the time element keeps the exact ISO instant in dateTime. Invalid dates and null display an unknown capture time.\",\n },\n source: {\n type: \"string\",\n description: \"Readable source identity; does not initiate a connection.\",\n },\n latencyMs: {\n type: \"number\",\n description:\n \"Supplied nonnegative latency in milliseconds, or null for unknown. Invalid values display unknown; omitted values are hidden.\",\n },\n retained: {\n type: \"boolean\",\n default: false,\n description: \"Explicitly labels imagery as a retained frame that is not current.\",\n },\n detail: {\n type: \"string\",\n description: \"Persistent application explanation of the media state.\",\n },\n aspectRatio: {\n type: \"number\",\n default: 1.7777777777777777,\n description:\n \"Media width divided by height; defaults to 16/9. Nonpositive or nonfinite ratios fall back to 16/9.\",\n },\n children: {\n type: \"node\",\n description:\n \"Application-owned media, image, canvas or fallback. Direct media children fit within the aspect-ratio surface without cropping.\",\n },\n actions: {\n type: \"node\",\n description:\n \"Application-supplied accessible actions. Availability, permissions and outcomes remain with the caller.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"PlaybackControls\", \"Button\", \"AttachmentPreview\", \"Inspector\"],\n never_inside: [\"Button\"],\n example:\n '<MediaFrame label=\"Pump camera\" state=\"recorded\" capturedAt={confirmedFrameTime} source=\"CAM-12\" detail=\"Training footage\"><video muted playsInline src={recordingUrl} /></MediaFrame>',\n },\n};\n","MediaFrame/MediaFrame.tsx":"\"use client\";\n\nimport { forwardRef, useRef, type HTMLAttributes, type ReactNode } from \"react\";\nimport { useNumberFormatter } from \"react-aria\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Badge } from \"../Badge/Badge\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { NumberText } from \"../_internal/numberText\";\nimport { formatUtcTimestamp, isValidTimestamp } from \"../_internal/timestamp\";\nimport { MediaFrameManifest } from \"./MediaFrame.manifest\";\n\n/**\n * Every supplied digit, grouped in the locale: 21 significant digits hold any number's shortest\n * exact form, so nothing is rounded. A constant, so React Aria keeps one formatter per locale.\n */\nconst EXACT_NUMBER = { maximumSignificantDigits: 21 };\nconst frameStyles = cva(\n \"flex min-w-0 flex-col border border-oprt-border bg-oprt-surface-1 font-sans text-oprt-base normal-case text-oprt-text\",\n);\n\nexport type MediaFrameState =\n | \"live\"\n | \"delayed\"\n | \"paused\"\n | \"recorded\"\n | \"reconnecting\"\n | \"unavailable\";\n\nexport type MediaFrameProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n state: MediaFrameState;\n capturedAt: number | null;\n source?: string;\n latencyMs?: number | null;\n retained?: boolean;\n detail?: string;\n aspectRatio?: number;\n children?: ReactNode;\n actions?: ReactNode;\n};\n\nexport const MediaFrame = forwardRef<HTMLDivElement, MediaFrameProps>((allProps, ref) => {\n const {\n label,\n state,\n capturedAt,\n source,\n latencyMs,\n retained = false,\n detail,\n aspectRatio = 16 / 9,\n children,\n actions,\n className,\n id,\n \"aria-labelledby\": labelledBy,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"MediaFrame\", MediaFrameManifest.MediaFrame!.props, { ...allProps });\n }\n // Part ids match the page after a hydration whose ids disagree with the server's.\n const labelRef = useRef<HTMLHeadingElement>(null);\n const ids = usePartIds([\"label\"], {\n id,\n readRendered: () => labelRef.current?.id.replace(/-label$/, \"\"),\n });\n // Exact latency, its digits grouped in the application's locale (MediaFrame-R2).\n const latency = useNumberFormatter(EXACT_NUMBER);\n // DC-4: operators read the date and time to the second; the exact instant stays machine-readable\n // in dateTime (MediaFrame-R1). A video's mediaTime gives fractional milliseconds, which is valid.\n const valid = isValidTimestamp(capturedAt);\n const shownTime = valid ? formatUtcTimestamp(capturedAt) : null;\n const timestamp = valid ? new Date(capturedAt).toISOString() : null;\n const unavailable = state === \"unavailable\" || state === \"reconnecting\";\n const ratio = Number.isFinite(aspectRatio) && aspectRatio > 0 ? aspectRatio : 16 / 9;\n\n return (\n <div\n {...props}\n id={id}\n ref={ref}\n role=\"region\"\n aria-labelledby={joinIds(ids.label, labelledBy)}\n className={clsx(frameStyles(), className)}\n >\n <div className=\"flex flex-wrap items-center justify-between gap-2u border-b border-oprt-border p-2u\">\n <h3\n ref={labelRef}\n id={ids.label}\n className=\"m-0 min-w-0 font-medium uppercase tracking-oprt-sm oprt-wrap-anywhere\"\n >\n {label}\n </h3>\n <Badge appearance=\"text\" variant=\"neutral\">\n {state}\n </Badge>\n </div>\n <div\n className=\"relative flex w-full min-w-0 items-center justify-center overflow-hidden bg-oprt-bg [&>canvas]:h-full [&>canvas]:w-full [&>canvas]:object-contain [&>img]:h-full [&>img]:w-full [&>img]:object-contain [&>video]:h-full [&>video]:w-full [&>video]:object-contain\"\n style={{ aspectRatio: ratio === 16 / 9 ? \"16 / 9\" : String(ratio) }}\n >\n {children ?? <p className=\"m-0 p-2u text-oprt-text-dim\">No media supplied.</p>}\n </div>\n <div className=\"flex min-w-0 flex-col gap-1u border-t border-oprt-border p-2u\">\n {source && (\n <p className=\"m-0 text-oprt-text-dim oprt-wrap-anywhere\">\n Source: <span>{source}</span>\n </p>\n )}\n {unavailable && <p className=\"m-0\">Current frame unavailable.</p>}\n {retained && <p className=\"m-0\">Retained frame — not current.</p>}\n <p className=\"m-0 text-oprt-text-dim oprt-wrap-anywhere\">\n {timestamp && shownTime ? (\n <>\n {retained || unavailable ? \"Last frame captured: \" : \"Captured: \"}\n <time className=\"font-mono tabular-nums\" dateTime={timestamp}>\n {shownTime}\n </time>\n </>\n ) : (\n \"Capture time unknown.\"\n )}\n </p>\n {latencyMs !== undefined && (\n <p className=\"m-0 text-oprt-text-dim\">\n {latencyMs !== null && Number.isFinite(latencyMs) && latencyMs >= 0 ? (\n <>\n Latency:{\" \"}\n <NumberText\n value={latency.format(latencyMs + 0)}\n unit=\"ms\"\n className=\"font-mono tabular-nums\"\n />\n </>\n ) : (\n \"Latency unknown.\"\n )}\n </p>\n )}\n {detail && <p className=\"m-0 oprt-wrap-anywhere\">{detail}</p>}\n {actions && <div className=\"flex flex-wrap items-center gap-1u\">{actions}</div>}\n </div>\n </div>\n );\n});\nMediaFrame.displayName = \"MediaFrame\";\n","MultiSelect/index.ts":"export { MultiSelect, type MultiSelectProps, type MultiSelectOption } from \"./MultiSelect\";\nexport { MultiSelectManifest } from \"./MultiSelect.manifest\";\n","MultiSelect/MultiSelect.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const MultiSelectManifest: Record<string, Component> = {\n MultiSelect: {\n description:\n \"Native checkbox group with shared OPERATOR Check icons, stable values, labeled options, and explicit validation.\",\n props: {\n label: {\n type: \"string\",\n required: true,\n description: \"Visible fieldset legend and accessible group name.\",\n },\n options: {\n type: \"array\",\n required: true,\n description: \"Unique stable values. Disabled options remain visible.\",\n items: {\n value: \"string (required, unique)\",\n label: \"string (required)\",\n disabled: \"boolean\",\n },\n },\n value: {\n type: \"array\",\n description:\n \"Controlled selected values; values missing from options are preserved during changes.\",\n },\n defaultValue: {\n type: \"array\",\n description: \"Initial values for uncontrolled selection. Defaults to an empty array.\",\n },\n onValueChange: {\n type: \"function\",\n description:\n \"(values: string[]) => void. Controlled state only changes when the owner supplies new values.\",\n },\n name: {\n type: \"string\",\n description:\n \"Native form name shared by selected checkboxes. FormData returns one entry per selected, enabled option.\",\n },\n disabled: { type: \"boolean\", default: false },\n description: { type: \"string\" },\n error: {\n type: \"string\",\n description:\n \"Visible validation text; associated with the group and paired with aria-invalid.\",\n },\n },\n composable_with: [\"Card\"],\n never_inside: [\"Button\"],\n example:\n '<MultiSelect label=\"Sensors\" options={[{ value: \"radar\", label: \"Radar\" }, { value: \"optical\", label: \"Optical\" }]} value={sensors} onValueChange={setSensors} />',\n },\n};\n","MultiSelect/MultiSelect.tsx":"\"use client\";\n\nimport { forwardRef, useRef, useState, type FieldsetHTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Check } from \"@oprt/icons\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\nexport type MultiSelectOption = { value: string; label: string; disabled?: boolean };\nexport type MultiSelectProps = Omit<\n FieldsetHTMLAttributes<HTMLFieldSetElement>,\n \"onChange\" | \"children\" | \"value\" | \"defaultValue\"\n> & {\n label: string;\n options: readonly MultiSelectOption[];\n value?: readonly string[];\n defaultValue?: readonly string[];\n onValueChange?: (value: string[]) => void;\n name?: string;\n description?: string;\n error?: string;\n};\nconst styles = cva(\"m-0 min-w-0 border p-2u font-sans text-oprt-base normal-case text-oprt-text\", {\n variants: { invalid: { true: \"border-oprt-error\", false: \"border-oprt-border\" } },\n defaultVariants: { invalid: false },\n});\n\n/** An always-visible native checkbox group. Values omitted from options survive updates. */\nexport const MultiSelect = forwardRef<HTMLFieldSetElement, MultiSelectProps>(\n (\n {\n label,\n options,\n value,\n defaultValue = [],\n onValueChange,\n name,\n description,\n error,\n disabled = false,\n className,\n \"aria-describedby\": describedBy,\n ...props\n },\n ref,\n ) => {\n // The description and error ids are minted with the shared usePartIds and reconciled with the\n // page through the fieldset's data-oprt-ids, so an error that arrives after a hydration whose\n // ids disagree with the server's still leaves the description in place (MultiSelect-R3).\n const fieldset = useRef<HTMLFieldSetElement | null>(null);\n const fieldsetRef = useMergedRefs<HTMLFieldSetElement>(fieldset, ref);\n const ids = usePartIds([\"description\", \"error\"], {\n readRendered: () => fieldset.current?.getAttribute(\"data-oprt-ids\"),\n });\n const [local, setLocal] = useState<readonly string[]>(defaultValue);\n const selected = value ?? local;\n const descriptions = joinIds(describedBy, description && ids.description, error && ids.error);\n return (\n <fieldset\n {...props}\n ref={fieldsetRef}\n data-oprt-ids={ids.base}\n disabled={disabled}\n aria-invalid={Boolean(error) || undefined}\n aria-describedby={descriptions}\n className={clsx(styles({ invalid: Boolean(error) }), className)}\n >\n {/* A field label in its authored case, styled as Field's (NumberInput-R5); the legend,\n description, option names and message wrap and long identifiers break anywhere\n (MultiSelect-B2M-1..3). */}\n <legend className=\"min-w-0 px-1u oprt-wrap-anywhere text-oprt-base font-medium text-oprt-text\">\n {label}\n </legend>\n {description && (\n <p\n id={ids.description}\n className=\"m-0 mb-1u min-w-0 oprt-wrap-anywhere text-oprt-text-dim\"\n >\n {description}\n </p>\n )}\n <div className=\"flex flex-wrap gap-2u\">\n {options.map((option) => (\n <label\n key={option.value}\n className=\"m-0 inline-flex min-h-3u items-center gap-1u font-medium has-[:disabled]:cursor-not-allowed has-[:disabled]:opacity-50\"\n >\n <span className=\"relative inline-flex h-3u w-3u shrink-0\">\n <input\n type=\"checkbox\"\n name={name}\n value={option.value}\n checked={selected.includes(option.value)}\n disabled={disabled || option.disabled}\n className=\"peer m-0 h-3u w-3u appearance-none border border-oprt-border-control bg-oprt-surface-2 checked:border-oprt-accent focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus\"\n onChange={(event) => {\n if (disabled || option.disabled) return;\n const next = event.target.checked\n ? [...new Set([...selected, option.value])]\n : selected.filter((key) => key !== option.value);\n if (value === undefined) setLocal(next);\n onValueChange?.(next);\n }}\n />\n <Check\n size={20}\n className=\"pointer-events-none absolute inset-0 m-auto hidden text-oprt-accent peer-checked:block\"\n />\n </span>\n <span className=\"min-w-0 oprt-wrap-anywhere\">{option.label}</span>\n </label>\n ))}\n </div>\n {options.length === 0 && <p className=\"m-0 text-oprt-text-dim\">No options available.</p>}\n {error && (\n // A caller's error is an alert, as in a Field (MultiSelect-R2).\n <p\n id={ids.error}\n role=\"alert\"\n className=\"m-0 mt-1u min-w-0 oprt-wrap-anywhere text-oprt-error\"\n >\n {error}\n </p>\n )}\n </fieldset>\n );\n },\n);\nMultiSelect.displayName = \"MultiSelect\";\n","NumberInput/index.ts":"export { NumberInput, type NumberInputProps } from \"./NumberInput\";\nexport { NumberInputManifest } from \"./NumberInput.manifest\";\n","NumberInput/NumberInput.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const NumberInputManifest: Record<string, Component> = {\n NumberInput: {\n description:\n \"Numeric entry with an explicit unit, keyboard stepping, range and step validation, and unknown-value state.\",\n props: {\n label: { type: \"string\", required: true },\n unit: {\n type: \"string\",\n required: true,\n description:\n \"Visible and described unit such as m, kn or °. Empty units are invalid; no conversion is performed.\",\n },\n value: {\n type: \"number\",\n description:\n \"Controlled number or null for unknown. Invalid drafts stay visible and never emit NaN or out-of-range values.\",\n },\n defaultValue: {\n type: \"number\",\n description: \"Initial uncontrolled number, or null (the default).\",\n },\n onValueChange: {\n type: \"function\",\n description:\n \"(value: number | null) => void. Emits valid numeric edits or null when cleared. Does not emit invalid drafts.\",\n },\n onValidationChange: {\n type: \"function\",\n description:\n \"(error: string | null) => void. Reports draft and supplied-value validation; use this to block application submission while a draft is invalid.\",\n },\n min: { type: \"number\" },\n max: { type: \"number\" },\n step: {\n type: \"string\",\n default: \"any\",\n description:\n \"Positive number or 'any'. Numeric steps validate from min (or zero); Arrow keys use 1 for 'any'.\",\n },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n disabled: { type: \"boolean\", default: false },\n readOnly: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n description: { type: \"string\" },\n error: {\n type: \"string\",\n description: \"Caller-supplied validation error, displayed before built-in validation.\",\n },\n name: {\n type: \"string\",\n description: \"Native form field name. Constraint validation blocks invalid numeric drafts.\",\n },\n },\n composable_with: [\"Card\", \"CoordinateInput\"],\n never_inside: [\"Button\"],\n example:\n '<NumberInput label=\"Altitude\" unit=\"m\" value={altitude} onValueChange={setAltitude} min={0} max={20000} step={10} />',\n },\n};\n","NumberInput/NumberInput.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useRef, useState, type InputHTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { FORCED_FOCUS_WITHIN, FORCED_NO_RESTING_OUTLINE } from \"../_internal/controlStyles\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { NumberInputManifest } from \"./NumberInput.manifest\";\n\nexport type NumberInputProps = Omit<\n InputHTMLAttributes<HTMLInputElement>,\n \"type\" | \"value\" | \"defaultValue\" | \"onChange\" | \"min\" | \"max\" | \"step\" | \"size\"\n> & {\n label: string;\n unit: string;\n value?: number | null;\n defaultValue?: number | null;\n onValueChange?: (value: number | null) => void;\n onValidationChange?: (error: string | null) => void;\n min?: number;\n max?: number;\n step?: number | \"any\";\n size?: \"sm\" | \"md\" | \"lg\";\n description?: string;\n error?: string;\n};\nconst styles = cva(\n // Disabled looks like DC-9's disabled buttons (coordinator ruling on DC-B04-2, NumberInput-R1):\n // dim text, the control outline and a muted fill; CoordinateInput's axes inherit it.\n \"flex min-w-0 items-center gap-1u border bg-oprt-surface-2 px-2u focus-within:shadow-oprt-focus has-[input:disabled]:cursor-not-allowed has-[input:disabled]:bg-oprt-surface-3 has-[input:disabled]:border-oprt-border-control\",\n {\n variants: {\n invalid: {\n true: \"border-oprt-error\",\n // The control outline (design call DC-3).\n false: \"border-oprt-border-control focus-within:border-oprt-border-focus\",\n },\n // The height is a minimum: a long unit wraps in its own column beside the number and the\n // field grows taller (coordinator ruling on DC-B04-3, NumberInput-R3), while a one-line field\n // keeps exactly its token height; sm caps the number's 24px line inside its 2px of border.\n size: {\n // Capped at sm, so centered with auto margins rather than stretched (see Input).\n sm: \"min-h-oprt-sm [&_input]:my-auto [&_input]:max-h-[calc(var(--oprt-height-sm)-2px)]\",\n md: \"min-h-oprt-md\",\n lg: \"min-h-oprt-lg\",\n },\n },\n defaultVariants: { invalid: false, size: \"md\" },\n },\n);\nfunction validate(\n text: string,\n unit: string,\n min: number | undefined,\n max: number | undefined,\n step: number | \"any\",\n required: boolean,\n) {\n if (!unit.trim()) return \"A unit is required.\";\n if (\n (min !== undefined && !Number.isFinite(min)) ||\n (max !== undefined && !Number.isFinite(max)) ||\n (min !== undefined && max !== undefined && min > max)\n )\n return \"Invalid numeric range.\";\n if (step !== \"any\" && (!Number.isFinite(step) || step <= 0))\n return \"Step must be a positive finite number.\";\n if (text.trim() === \"\") return required ? \"A value is required.\" : null;\n if (\n !/^[+-]?(?:\\d+\\.?\\d*|\\.\\d+)(?:e[+-]?\\d+)?$/i.test(text.trim()) ||\n !Number.isFinite(Number(text))\n )\n return \"Enter a finite number.\";\n const number = Number(text);\n if (min !== undefined && number < min) return `Enter a value of at least ${min} ${unit}.`;\n if (max !== undefined && number > max) return `Enter a value of at most ${max} ${unit}.`;\n if (step !== \"any\") {\n const steps = (number - (min ?? 0)) / step;\n if (Math.abs(steps - Math.round(steps)) > 1e-7)\n return `Use increments of ${step} ${unit} from ${min ?? 0}.`;\n }\n return null;\n}\n\n/**\n * A number as a plain decimal, with no exponent: ARIA takes a decimal for aria-valuenow, and React\n * would write 1.2345678901234568e+21 or 1.5e-7 (NumberInput-B2M-4). The visible value and\n * aria-valuetext keep the operator's own form.\n */\nfunction plainDecimal(value: number): string {\n return value.toLocaleString(\"en-US\", { useGrouping: false, maximumFractionDigits: 20 });\n}\n\nexport const NumberInput = forwardRef<HTMLInputElement, NumberInputProps>((allProps, ref) => {\n const {\n label,\n unit,\n value,\n defaultValue = null,\n onValueChange,\n onValidationChange,\n min,\n max,\n step = \"any\",\n size = \"md\",\n description,\n error,\n required = false,\n disabled = false,\n readOnly = false,\n className,\n id,\n onBlur,\n onKeyDown,\n \"aria-describedby\": describedBy,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n // Every prop it received; `size` is the manifest's enum (NumberInput-R6).\n devValidate(\"NumberInput\", NumberInputManifest.NumberInput!.props, { ...allProps });\n }\n // The part ids (the input's id followed by -unit, -description, -error and -unknown) are minted\n // with the shared usePartIds and reconciled with the page through the label's htmlFor, so a\n // description that changes after a hydration whose ids disagree with the server's still points\n // at the unit and the help (NumberInput-R2).\n const labelRef = useRef<HTMLLabelElement>(null);\n const ids = usePartIds([\"unit\", \"description\", \"error\", \"unknown\"], {\n id,\n readRendered: () => labelRef.current?.htmlFor,\n });\n const inputId = ids.base;\n const inputRef = useRef<HTMLInputElement | null>(null);\n // One ref callback for as long as the consumer's ref is the same, so a stable `ref={…}` is\n // attached once instead of being called with null and the node again on every keystroke.\n const mergedRef = useMergedRefs<HTMLInputElement>(inputRef, ref);\n const [local, setLocal] = useState<number | null>(defaultValue);\n const numeric = value === undefined ? local : value;\n // Drafts preserve signs, trailing decimals and invalid text without publishing NaN.\n const [draft, setDraft] = useState<{ text: string; base: number | null } | null>(null);\n const text =\n draft && Object.is(draft.base, numeric) ? draft.text : numeric === null ? \"\" : String(numeric);\n const validation = error || validate(text, unit, min, max, step, required);\n const validationRef = useRef(onValidationChange);\n validationRef.current = onValidationChange;\n useEffect(() => {\n inputRef.current?.setCustomValidity(validation ?? \"\");\n validationRef.current?.(validation ?? null);\n }, [validation]);\n const commit = (next: number | null) => {\n if (value === undefined) setLocal(next);\n onValueChange?.(next);\n };\n const descriptions = joinIds(\n describedBy,\n ids.unit,\n description && ids.description,\n validation ? ids.error : text === \"\" && ids.unknown,\n );\n return (\n <div\n className={clsx(\n \"flex min-w-0 flex-col gap-1u font-sans text-oprt-base normal-case text-oprt-text\",\n className,\n )}\n >\n <label\n ref={labelRef}\n htmlFor={inputId}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base font-medium text-oprt-text\"\n >\n {label}\n </label>\n {/* In forced colors focus is a 2px CanvasText outline around the field (NumberInput-B5-1). */}\n <div className={clsx(styles({ size, invalid: Boolean(validation) }), FORCED_FOCUS_WITHIN)}>\n <input\n {...props}\n ref={mergedRef}\n id={inputId}\n type=\"text\"\n role=\"spinbutton\"\n inputMode=\"decimal\"\n value={text}\n disabled={disabled}\n readOnly={readOnly}\n required={required}\n aria-valuemin={min}\n aria-valuemax={max}\n // React's type asks for a number, which it would write with an exponent; the attribute\n // is text, so the plain decimal string is written as it is.\n aria-valuenow={\n (text !== \"\" && !validation ? plainDecimal(Number(text)) : undefined) as\n | number\n | undefined\n }\n aria-valuetext={\n validation ? `Invalid: ${text}` : text === \"\" ? \"Unknown\" : `${text} ${unit}`\n }\n aria-invalid={Boolean(validation) || undefined}\n aria-describedby={descriptions}\n // The number starts from nothing (`flex-1`, as in Input) and takes what the unit leaves, down\n // to ten characters (NumberInput-R3): a short unit never loses a pixel to the row, so it\n // stays on one line and a one-line field keeps its height (NumberInput-B7-1). With\n // `w-full` its basis was the whole field and flex shrinking took width from every unit.\n // `ch` is the advance of a \"0\" without the letter-spacing that text-oprt-md adds after every\n // character, so the ten characters' tracking is added: 10ch alone was 3px short of\n // \"1234567.89\". Two more pixels are the caret's room at the end of the text, which WebKit\n // counts in the field's scroll width and a typed tenth digit needs to stay in view.\n // `self-stretch` makes the number as tall as the field, so the whole field is the target\n // and not the one line of it (operations-map-R4); a text input centers its own text.\n className={clsx(\n \"min-w-[min(calc(10ch_+_10_*_var(--oprt-tracking-md)_+_2px),100%)] flex-1 self-stretch border-0 bg-transparent font-mono text-oprt-md font-medium tabular-nums text-oprt-text outline-hidden disabled:cursor-not-allowed disabled:text-oprt-text-dim\",\n FORCED_NO_RESTING_OUTLINE,\n )}\n onChange={(event) => {\n if (disabled || readOnly) return;\n const nextText = event.target.value;\n const invalid = validate(nextText, unit, min, max, step, required);\n const next = nextText.trim() === \"\" ? null : Number(nextText);\n setDraft({ text: nextText, base: value === undefined && !invalid ? next : numeric });\n if (!invalid) commit(next);\n }}\n onBlur={(event) => {\n if (!validation) setDraft(null);\n onBlur?.(event);\n }}\n onKeyDown={(event) => {\n onKeyDown?.(event);\n if (event.defaultPrevented || disabled || readOnly) return;\n let next: number | undefined;\n if (event.key === \"ArrowUp\" || event.key === \"ArrowDown\") {\n const delta = step === \"any\" ? 1 : step;\n const base =\n text.trim() !== \"\" && Number.isFinite(Number(text)) ? Number(text) : (min ?? 0);\n next = Number((base + (event.key === \"ArrowUp\" ? delta : -delta)).toPrecision(14));\n } else if (event.key === \"Home\" && min !== undefined) next = min;\n else if (event.key === \"End\" && max !== undefined) next = max;\n if (next === undefined) return;\n event.preventDefault();\n next = Math.min(max ?? Infinity, Math.max(min ?? -Infinity, next));\n if (validate(String(next), unit, min, max, step, required)) return;\n setDraft(null);\n commit(next);\n }}\n />\n <span id={ids.unit} className=\"min-w-0 oprt-wrap-anywhere font-mono text-oprt-text-dim\">\n {unit || \"Unit missing\"}\n </span>\n </div>\n {description && (\n <p id={ids.description} className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-text-dim\">\n {description}\n </p>\n )}\n {validation ? (\n // A caller's error is an alert, as in a Field (MultiSelect-R2); the field's own\n // as-you-type message stays a description, so typing raises no alert per keystroke.\n <p\n id={ids.error}\n {...(error ? { role: \"alert\" } : {})}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-error\"\n >\n {validation}\n </p>\n ) : (\n text === \"\" && (\n <p id={ids.unknown} className=\"m-0 text-oprt-text-dim\">\n Unknown\n </p>\n )\n )}\n </div>\n );\n});\nNumberInput.displayName = \"NumberInput\";\n","OperationalStatus/index.ts":"export {\n OperationalStatus,\n type OperationalStatusProps,\n type OperationalState,\n} from \"./OperationalStatus\";\nexport { OperationalStatusManifest } from \"./OperationalStatus.manifest\";\n","OperationalStatus/OperationalStatus.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const OperationalStatusManifest: Record<string, Component> = {\n OperationalStatus: {\n description:\n \"Application-asserted data quality, source, and UTC update time in shared label/value detail rows.\",\n props: {\n state: {\n type: \"enum\",\n values: [\"current\", \"stale\", \"partial\", \"unavailable\", \"unknown\"],\n default: \"unknown\",\n description:\n \"Application assertion rendered as a text-only badge; never inferred from connection or the wall clock.\",\n },\n source: {\n type: \"string\",\n description: \"Provenance label; defaults to Unknown.\",\n },\n updatedAt: {\n type: \"number\",\n description:\n \"Epoch milliseconds, including zero. Shown as UTC to the second, 'YYYY-MM-DD HH:MM:SS UTC', with the date and the time wrapping separately; the machine-readable time element keeps the exact instant. Null, missing, or invalid renders Time unknown in the Updated row.\",\n },\n detail: {\n type: \"string\",\n description: \"Missing coverage, stale threshold, quality, or last-known context.\",\n },\n },\n composable_with: [\n \"AppShell\",\n \"Inspector\",\n \"DataGrid\",\n \"EntityPreview\",\n \"DescriptionList\",\n \"Badge\",\n ],\n never_inside: [\"Button\"],\n example:\n '<OperationalStatus state=\"partial\" source=\"Supplied report\" updatedAt={lastReport} detail=\"One source has not reported.\" />',\n },\n};\n","OperationalStatus/OperationalStatus.tsx":"\"use client\";\n\nimport { forwardRef, useId, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Badge } from \"../Badge/Badge\";\nimport { DescriptionList } from \"../DescriptionList/DescriptionList\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { formatUtcTimestamp, isValidTimestamp } from \"../_internal/timestamp\";\nimport { OperationalStatusManifest } from \"./OperationalStatus.manifest\";\nconst statusStyles = cva(\n \"flex min-w-0 flex-col gap-1u font-sans text-oprt-base normal-case tracking-normal text-oprt-text\",\n);\nexport type OperationalState = \"current\" | \"stale\" | \"partial\" | \"unavailable\" | \"unknown\";\nexport type OperationalStatusProps = HTMLAttributes<HTMLDivElement> & {\n state?: OperationalState;\n source?: string;\n updatedAt?: number | null;\n detail?: string;\n};\nconst variants = {\n current: \"success\",\n stale: \"warning\",\n partial: \"warning\",\n unavailable: \"error\",\n unknown: \"neutral\",\n} as const;\n/** Freshness is an application assertion; no wall-clock or transport inference. */\nexport const OperationalStatus = forwardRef<HTMLDivElement, OperationalStatusProps>(\n (allProps, ref) => {\n const { state = \"unknown\", source, updatedAt, detail, className, ...props } = allProps;\n const qualityId = `${useId()}-quality`;\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"OperationalStatus\", OperationalStatusManifest.OperationalStatus!.props, {\n ...allProps,\n });\n }\n\n // The operator sees \"YYYY-MM-DD HH:MM:SS UTC\" (design call DC-4); the machine-readable instant\n // keeps its milliseconds. null: not an instant, shown as \"Time unknown\".\n const shown = isValidTimestamp(updatedAt) ? formatUtcTimestamp(updatedAt) : null;\n const isoTime = isValidTimestamp(updatedAt) ? new Date(updatedAt).toISOString() : null;\n const split = shown?.indexOf(\" \") ?? -1;\n return (\n <div\n role=\"group\"\n aria-labelledby={qualityId}\n {...props}\n ref={ref}\n className={clsx(statusStyles(), className)}\n >\n <DescriptionList\n layout=\"details\"\n items={[\n {\n id: \"quality\",\n label: <span id={qualityId}>Data quality</span>,\n value: (\n <Badge\n appearance=\"text\"\n size=\"md\"\n variant={variants[state]}\n className=\"ml-auto tracking-oprt-sm\"\n >\n {state}\n </Badge>\n ),\n },\n { id: \"source\", label: \"Source\", value: source || \"Unknown\" },\n {\n id: \"updated\",\n label: \"Updated\",\n value:\n isoTime && shown ? (\n // The date and the time wrap apart; neither breaks inside, so \"UTC\" stays with\n // the time.\n <time dateTime={isoTime} className=\"font-mono\">\n <span className=\"whitespace-nowrap\">{shown.slice(0, split)}</span>{\" \"}\n <span className=\"whitespace-nowrap\">{shown.slice(split + 1)}</span>\n </time>\n ) : (\n \"Time unknown\"\n ),\n },\n ]}\n />\n {detail && <p className=\"m-0 mt-1u oprt-wrap-anywhere text-oprt-text-dim\">{detail}</p>}\n </div>\n );\n },\n);\nOperationalStatus.displayName = \"OperationalStatus\";\n","patterns/communications-console/communications-console.tsx":"\"use client\";\n\nimport { Fragment, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport { Badge, Button, Checkbox, EntityPreview, Field, Textarea } from \"@oprt/react\";\nimport { formatUtcInstant } from \"../operational-domain\";\nimport type {\n CommunicationsActionRequest,\n CommunicationsActionResult,\n CommunicationsConsoleProps,\n CommunicationsMessage,\n} from \"./types\";\n\ntype Draft = { body: string; acknowledgementRequested: boolean };\nconst emptyDraft: Draft = { body: \"\", acknowledgementRequested: false };\nconst focus =\n \"outline-hidden focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-oprt-border-focus\";\n\n/** \"HH:MM\" in UTC. Kept for callers of the earlier helper; the console itself shows zone and date. */\nexport function communicationsTime(at: number) {\n return new Date(at).toISOString().slice(11, 16);\n}\n\nconst isoOf = (at: number) => new Date(at).toISOString();\nconst dayOf = (at: number) => isoOf(at).slice(0, 10);\n/** \"HH:MM:SS UTC\" for a message, \"HH:MM UTC\" where the room is tight. */\nconst clock = (at: number, seconds: boolean) => `${isoOf(at).slice(11, seconds ? 19 : 16)} UTC`;\n/** \"YYYY-MM-DD HH:MM:SS UTC\". Whole seconds: the console does not show sub-second timing. */\nconst stamp = (at: number) => formatUtcInstant(isoOf(at), { milliseconds: \"never\" });\n/** One line of room for the end of a list, in pixels: closer than this counts as at the end. */\nconst END_SLACK = 24;\n\n/** A time with its date and zone; a narrow column breaks between the date and the time, never inside either. */\nfunction Stamp({ at, className }: { at: number; className?: string }) {\n const text = stamp(at);\n return (\n <time dateTime={isoOf(at)} {...(className ? { className } : {})}>\n <span className=\"inline-block\">{text.slice(0, 10)}</span>{\" \"}\n <span className=\"inline-block\">{text.slice(11)}</span>\n </time>\n );\n}\n\n/** Channel history is authoritative; local state holds drafts and request feedback only. */\nexport function CommunicationsConsole(props: CommunicationsConsoleProps) {\n return <ConsoleWorkspace key={props.snapshot.id} {...props} />;\n}\n\nfunction ConsoleWorkspace({\n snapshot,\n selectedChannelId,\n onSelectedChannelChange,\n onAction,\n}: CommunicationsConsoleProps) {\n const instanceId = useId();\n const [drafts, setDrafts] = useState<Record<string, Draft>>({});\n const [pending, setPending] = useState<Record<string, string | undefined>>({});\n const [feedback, setFeedback] = useState<Record<string, CommunicationsActionResult | undefined>>(\n {},\n );\n const inFlight = useRef(new Set<string>());\n const attempts = useRef(new Map<string, string>());\n const sequence = useRef(0);\n const conversation = useRef<HTMLElement>(null);\n const composer = useRef<HTMLTextAreaElement>(null);\n const messageList = useRef<HTMLOListElement>(null);\n const rail = useRef<HTMLUListElement>(null);\n // Where the message list was last left: at the newest message or not, and how many it held.\n const nearEnd = useRef(true);\n const listState = useRef<{ channelId: string | null; count: number }>({\n channelId: null,\n count: 0,\n });\n const [unseen, setUnseen] = useState(0);\n const selection = useRef(selectedChannelId);\n const selectionFocus = useRef<string | null>(null);\n const latestRequested = useRef(false);\n const returnFocus = useRef<{\n channelId: string;\n origin: Element | null;\n target: \"composer\" | \"conversation\";\n } | null>(null);\n const channel = snapshot.channels.find((item) => item.id === selectedChannelId);\n const connected = snapshot.connection === \"connected\";\n const draft = channel ? (drafts[channel.id] ?? emptyDraft) : emptyDraft;\n const channelPending = channel ? pending[channel.id] : undefined;\n const result = channel ? feedback[channel.id] : undefined;\n const messages = snapshot.messages\n .filter((message) => message.channelId === channel?.id)\n .sort((left, right) => left.sentAt - right.sentAt);\n const needsAcknowledgement = (message: CommunicationsMessage) =>\n message.sender.id !== snapshot.operator.id &&\n message.delivery === \"delivered\" &&\n message.acknowledgementRequested &&\n !message.acknowledgement;\n const awaitingOperator = messages.filter(needsAcknowledgement).length;\n\n const channelId = channel?.id ?? null;\n const messageCount = messages.length;\n useLayoutEffect(() => {\n selection.current = selectedChannelId;\n if (selectionFocus.current !== null && selectionFocus.current === selectedChannelId) {\n conversation.current?.focus();\n selectionFocus.current = null;\n }\n const list = messageList.current;\n const wantsLatest = latestRequested.current;\n latestRequested.current = false;\n const known = listState.current;\n if (known.channelId !== channelId) {\n // A channel opens at its newest message, where the operator's attention is needed.\n listState.current = { channelId, count: messageCount };\n nearEnd.current = true;\n if (list) list.scrollTop = list.scrollHeight;\n setUnseen(0);\n } else {\n const added = messageCount - known.count;\n known.count = messageCount;\n if (list && (wantsLatest || (added > 0 && nearEnd.current))) {\n list.scrollTop = list.scrollHeight;\n nearEnd.current = true;\n setUnseen(0);\n } else if (added > 0) {\n // The operator is reading earlier messages: do not move the list under them, say so.\n setUnseen((count) => count + added);\n }\n }\n const requested = returnFocus.current;\n if (requested && !pending[requested.channelId]) {\n returnFocus.current = null;\n // The browser may leave focus on the control it was on (a disabled one, in some engines)\n // or drop it to the page; in either case the operator has not chosen to go elsewhere.\n if (\n requested.channelId === selectedChannelId &&\n (document.activeElement === requested.origin || document.activeElement === document.body)\n ) {\n (requested.target === \"composer\" ? composer.current : conversation.current)?.focus();\n }\n }\n }, [selectedChannelId, channelId, messageCount, snapshot.messages, pending]);\n\n // The channel list is a sideways strip on a narrow window: keep the open channel in view after\n // a selection and after any resize, scrolling the strip only (never the page).\n useLayoutEffect(() => {\n const strip = rail.current;\n if (!strip) return;\n const reveal = () => {\n const active = strip.querySelector<HTMLElement>('[aria-pressed=\"true\"]');\n if (!active || strip.scrollWidth <= strip.clientWidth) return;\n const box = strip.getBoundingClientRect();\n const item = active.getBoundingClientRect();\n if (item.left < box.left) strip.scrollLeft -= box.left - item.left;\n else if (item.right > box.right) strip.scrollLeft += item.right - box.right;\n };\n reveal();\n const observer = new ResizeObserver(reveal);\n observer.observe(strip);\n return () => observer.disconnect();\n }, [selectedChannelId]);\n\n function jumpToLatest() {\n const list = messageList.current;\n if (!list) return;\n list.scrollTop = list.scrollHeight;\n nearEnd.current = true;\n setUnseen(0);\n list.focus({ preventScroll: true });\n }\n\n function showFirstAwaiting() {\n const first = messages.find(needsAcknowledgement);\n const control = first ? document.getElementById(`${instanceId}-ack-${first.id}`) : null;\n control?.scrollIntoView?.({ block: \"nearest\" });\n control?.focus();\n }\n\n function selectChannel(channelId: string) {\n if (selectedChannelId === channelId) {\n selectionFocus.current = null;\n conversation.current?.focus();\n return;\n }\n selectionFocus.current = channelId;\n onSelectedChannelChange(channelId);\n }\n\n function updateDraft(next: Partial<Draft>) {\n if (!channel) return;\n setDrafts((current) => ({ ...current, [channel.id]: { ...draft, ...next } }));\n }\n\n async function act(\n action:\n | Omit<Extract<CommunicationsActionRequest, { type: \"send\" }>, \"requestId\">\n | Omit<Extract<CommunicationsActionRequest, { type: \"acknowledge\" }>, \"requestId\">,\n ) {\n if (!onAction || !connected || inFlight.current.has(action.channelId)) return;\n const channelId = action.channelId;\n const fingerprint = JSON.stringify(action);\n const requestId =\n attempts.current.get(fingerprint) ??\n globalThis.crypto?.randomUUID?.() ??\n `${instanceId}-${++sequence.current}`;\n attempts.current.set(fingerprint, requestId);\n inFlight.current.add(channelId);\n setPending((current) => ({\n ...current,\n [channelId]: action.type === \"send\" ? \"send\" : action.messageId,\n }));\n setFeedback((current) => ({ ...current, [channelId]: undefined }));\n const origin = document.activeElement;\n try {\n const response = await onAction({ ...action, requestId });\n setFeedback((current) => ({ ...current, [channelId]: response }));\n // Closing this operation must not discard another payload's uncertain outcome.\n attempts.current.delete(fingerprint);\n if (response.kind === \"accepted\") {\n if (action.type === \"send\") {\n setDrafts((current) => ({ ...current, [channelId]: emptyDraft }));\n if (selection.current === channelId) {\n latestRequested.current = true;\n returnFocus.current = { channelId, origin, target: \"composer\" };\n }\n } else if (selection.current === channelId) {\n returnFocus.current = { channelId, origin, target: \"conversation\" };\n }\n }\n } catch {\n setFeedback((current) => ({\n ...current,\n [channelId]: {\n kind: \"rejected\",\n message:\n \"The action could not be confirmed. Your draft is retained. Review channel history before retrying.\",\n },\n }));\n } finally {\n inFlight.current.delete(channelId);\n setPending((current) => ({ ...current, [channelId]: undefined }));\n }\n }\n\n return (\n <section\n aria-label={snapshot.title}\n className=\"w-full min-w-0 font-sans text-oprt-base normal-case tracking-normal text-oprt-text\"\n >\n <header className=\"mb-3u flex flex-wrap items-start justify-between gap-2u\">\n <div className=\"min-w-0\">\n <h2 className=\"m-0 text-oprt-xl font-semibold normal-case tracking-normal\">\n {snapshot.title}\n </h2>\n <p className=\"m-0 mt-0.5u text-oprt-text-dim\">{snapshot.location}</p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2u pt-1u\">\n <Badge appearance=\"text\" variant={connected ? \"success\" : \"warning\"}>\n {connected ? \"Relay connected\" : \"Connection lost\"}\n </Badge>\n <Stamp at={snapshot.observedAt} className=\"font-mono text-oprt-text-dim\" />\n </div>\n </header>\n\n {!connected && (\n <p\n role=\"alert\"\n className=\"m-0 mb-2u border border-oprt-border bg-oprt-surface-2 p-2u text-oprt-warning\"\n >\n Connection lost. Messages show the last received snapshot. Drafts remain editable; sending\n and acknowledgements are unavailable until the connection returns.\n </p>\n )}\n\n <div className=\"flex min-w-0 flex-wrap border border-oprt-border bg-oprt-surface-1\">\n <aside\n aria-label=\"Channel navigation\"\n className=\"min-w-0 flex-[1_1_13rem] bg-oprt-surface-2\"\n >\n <div className=\"flex items-baseline justify-between gap-2u border-b border-oprt-border px-2u py-2u\">\n <h3 className=\"m-0 text-oprt-md font-semibold normal-case\">Channels</h3>\n <span className=\"font-mono text-oprt-text-dim\">\n {String(snapshot.channels.length).padStart(2, \"0\")}\n </span>\n </div>\n <nav aria-label=\"Communications channels\">\n <ul ref={rail} className=\"m-0 flex list-none overflow-x-auto p-0 sm:block\">\n {snapshot.channels.map((item) => {\n const channelMessages = snapshot.messages.filter(\n (message) => message.channelId === item.id,\n );\n const attention = channelMessages.filter(needsAcknowledgement).length;\n const lastMessage = [...channelMessages].sort((a, b) => b.sentAt - a.sentAt)[0];\n const hasDraft = Boolean(drafts[item.id]?.body);\n const today = dayOf(snapshot.observedAt);\n // The last message's time: \"HH:MM UTC\" today, with its date above when it is older.\n const lastDay = lastMessage ? dayOf(lastMessage.sentAt) : today;\n const badge = hasDraft\n ? \"Draft saved\"\n : attention\n ? `${attention} to acknowledge`\n : item.canPost\n ? `${channelMessages.length} ${channelMessages.length === 1 ? \"message\" : \"messages\"}`\n : \"Read only\";\n const isOpen = channel?.id === item.id;\n return (\n <li\n key={item.id}\n className=\"min-w-[11rem] shrink-0 border-b border-oprt-border sm:min-w-0\"\n >\n <button\n type=\"button\"\n // The name carries what the button shows, after the name the tests and\n // readers expect first: the ID, the last message time and the badge.\n aria-label={`${item.name} channel, ${item.id}${lastMessage ? `, last message ${lastDay === today ? \"\" : `${lastDay} `}${clock(lastMessage.sentAt, false)}` : \"\"}, ${badge}${attention && hasDraft ? `, ${attention} to acknowledge` : \"\"}`}\n aria-pressed={isOpen}\n onClick={() => selectChannel(item.id)}\n // The open channel also gets a bar down its edge: a tint and a text color\n // are not a shape, and the bar survives the focus outline.\n className={`relative block w-full min-w-0 px-2u py-2u text-left hover:bg-oprt-surface-3 ${focus} ${isOpen ? \"bg-oprt-surface-3 text-oprt-text before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:bg-oprt-accent forced-colors:before:bg-[Highlight]\" : \"text-oprt-text-dim\"}`}\n >\n <span className=\"flex min-w-0 items-baseline justify-between gap-1u\">\n <span className=\"break-words text-oprt-md font-medium\">{item.name}</span>\n {lastMessage && (\n <time\n dateTime={isoOf(lastMessage.sentAt)}\n className=\"shrink-0 text-right font-mono text-oprt-sm text-oprt-text-dim\"\n >\n {lastDay !== today && <span className=\"block\">{lastDay}</span>}\n <span className=\"inline-block\">{clock(lastMessage.sentAt, false)}</span>\n </time>\n )}\n </span>\n <span className=\"mt-0.5u flex min-w-0 flex-wrap items-center justify-between gap-1u\">\n <span className=\"font-mono text-oprt-sm text-oprt-text-dim\">{item.id}</span>\n {hasDraft ? (\n <Badge appearance=\"text\" variant=\"info\">\n {badge}\n </Badge>\n ) : attention ? (\n <Badge appearance=\"text\" variant=\"warning\">\n {badge}\n </Badge>\n ) : (\n <Badge appearance=\"text\">{badge}</Badge>\n )}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n </nav>\n <div className=\"flex flex-wrap items-baseline gap-x-2u gap-y-0.5u p-2u\">\n <p className=\"m-0 font-medium\">{snapshot.operator.name}</p>\n <p className=\"m-0 text-oprt-text-dim\">{snapshot.operator.role}</p>\n </div>\n {channel && (\n <details className=\"min-w-0 border-t border-oprt-border p-2u\">\n <summary className={`min-h-3u cursor-pointer font-medium ${focus}`}>\n Channel details\n </summary>\n <div className=\"mt-2u\">\n <EntityPreview\n headingLevel={4}\n entity={{\n id: channel.id,\n name: channel.name,\n typeLabel: \"Support communications\",\n summary: channel.purpose,\n facts: [\n { id: \"lead\", label: \"Channel lead\", value: channel.lead },\n { id: \"operator\", label: \"Your identity\", value: snapshot.operator.name },\n {\n id: \"participants\",\n label: \"Participants\",\n value: String(channel.participants.length),\n },\n {\n id: \"posting\",\n label: \"Posting\",\n value: channel.canPost ? \"Available to you\" : \"Read only\",\n },\n ],\n quality: {\n state: connected ? \"current\" : \"stale\",\n source: snapshot.source,\n updatedAt: snapshot.observedAt,\n detail: connected\n ? \"Latest supplied channel snapshot.\"\n : \"Connection lost; last received snapshot retained.\",\n },\n }}\n />\n <h4 className=\"m-0 mt-3u text-oprt-base font-medium normal-case\">Participants</h4>\n <ul className=\"m-0 mt-1u list-none flex flex-col gap-1u p-0\">\n {channel.participants.map((person) => (\n <li key={person.id} className=\"min-w-0 break-words\">\n <span className=\"block\">{person.name}</span>\n <span className=\"text-oprt-text-dim\">{person.role}</span>\n </li>\n ))}\n </ul>\n </div>\n </details>\n )}\n </aside>\n\n <section\n ref={conversation}\n tabIndex={-1}\n aria-label=\"Channel conversation\"\n className={`min-w-0 flex-[4_1_29rem] border-l border-oprt-border ${focus}`}\n >\n {channel ? (\n <>\n <header className=\"flex flex-wrap items-center justify-between gap-1u border-b border-oprt-border px-3u py-2u\">\n <div className=\"min-w-0\">\n <h3 className=\"m-0 text-oprt-lg font-semibold normal-case tracking-normal\">\n {channel.name}\n </h3>\n <p className=\"m-0 mt-0.5u text-oprt-text-dim\">{channel.purpose}</p>\n </div>\n <span className=\"font-mono text-oprt-sm text-oprt-text-dim\">{channel.id}</span>\n </header>\n <div className=\"flex flex-wrap items-center justify-between gap-1u border-b border-oprt-border px-3u py-1u\">\n <span className=\"text-oprt-text-dim\">\n <span className=\"inline-block font-mono\">{dayOf(snapshot.observedAt)}</span> · All\n times UTC\n </span>\n <div className=\"flex flex-wrap items-center gap-1u\">\n <p aria-live=\"polite\" className=\"m-0 text-oprt-text-dim\">\n {unseen > 0\n ? `${unseen} new ${unseen === 1 ? \"message\" : \"messages\"} below.`\n : \"\"}\n </p>\n {unseen > 0 && (\n <Button size=\"sm\" variant=\"secondary\" onClick={jumpToLatest}>\n Jump to latest\n </Button>\n )}\n {awaitingOperator > 0 && (\n <Button size=\"sm\" variant=\"tertiary\" onClick={showFirstAwaiting}>\n {awaitingOperator} awaiting your acknowledgement\n </Button>\n )}\n </div>\n </div>\n {messages.length ? (\n <ol\n ref={messageList}\n aria-label={`${channel.name} messages`}\n tabIndex={0}\n onScroll={(event) => {\n const list = event.currentTarget;\n nearEnd.current =\n list.scrollHeight - list.scrollTop - list.clientHeight <= END_SLACK;\n if (nearEnd.current) setUnseen(0);\n }}\n className={`m-0 max-h-[34rem] min-w-0 list-none overflow-y-auto p-0 ${focus}`}\n >\n {messages.map((message, index) => {\n const ownMessage = message.sender.id === snapshot.operator.id;\n // A new day starts with its date; the first message needs one only when it is\n // not on the snapshot's day, which the strip above the list already names.\n const day = dayOf(message.sentAt);\n const previousDay = index > 0 ? dayOf(messages[index - 1]!.sentAt) : null;\n const newDay =\n previousDay === null\n ? day !== dayOf(snapshot.observedAt)\n : day !== previousDay;\n return (\n <Fragment key={message.id}>\n {newDay && (\n <li className=\"border-b border-oprt-border bg-oprt-surface-2 px-3u py-1u font-mono text-oprt-sm text-oprt-text-dim\">\n {day}\n </li>\n )}\n <li\n className={`min-w-0 border-b border-oprt-border px-3u py-2u ${ownMessage ? \"bg-oprt-surface-2\" : \"\"}`}\n >\n <div className=\"flex flex-wrap items-baseline justify-between gap-x-2u gap-y-0.5u\">\n <div className=\"flex min-w-0 flex-wrap items-baseline gap-x-1u gap-y-0.5u\">\n <span className=\"break-words text-oprt-md font-semibold\">\n {message.sender.name}\n </span>\n <span className=\"text-oprt-text-dim\">\n {ownMessage ? \"You\" : message.sender.role}\n </span>\n </div>\n <time\n dateTime={isoOf(message.sentAt)}\n className=\"whitespace-nowrap font-mono text-oprt-text-dim\"\n >\n {clock(message.sentAt, true)}\n </time>\n </div>\n <p className=\"m-0 mt-1u max-w-[72ch] whitespace-pre-wrap text-oprt-md leading-relaxed [overflow-wrap:anywhere]\">\n {message.body}\n </p>\n <div className=\"mt-1u flex flex-wrap items-center justify-between gap-x-2u gap-y-1u\">\n <div className=\"flex min-w-0 flex-wrap items-center gap-x-2u gap-y-0.5u\">\n <span className=\"font-mono text-oprt-sm text-oprt-text-dim\">\n {message.id}\n </span>\n <Badge\n appearance=\"text\"\n variant={\n message.delivery === \"failed\"\n ? \"error\"\n : message.delivery === \"pending\"\n ? \"warning\"\n : \"neutral\"\n }\n >\n {message.delivery === \"delivered\"\n ? \"Delivered to channel\"\n : message.delivery === \"pending\"\n ? \"Delivery pending\"\n : \"Delivery failed\"}\n </Badge>\n {message.acknowledgement ? (\n <span className=\"text-oprt-success\">\n Acknowledged by {message.acknowledgement.by.name} ·{\" \"}\n <time\n dateTime={isoOf(message.acknowledgement.at)}\n className=\"whitespace-nowrap font-mono\"\n >\n {dayOf(message.acknowledgement.at) === day\n ? clock(message.acknowledgement.at, true)\n : `${dayOf(message.acknowledgement.at)} ${clock(message.acknowledgement.at, true)}`}\n </time>\n </span>\n ) : message.acknowledgementRequested ? (\n <Badge appearance=\"text\" variant=\"warning\">\n {ownMessage\n ? \"Awaiting acknowledgement\"\n : \"Acknowledgement requested\"}\n </Badge>\n ) : (\n <span className=\"text-oprt-sm text-oprt-text-dim\">\n No acknowledgement requested\n </span>\n )}\n </div>\n {needsAcknowledgement(message) && (\n <Button\n id={`${instanceId}-ack-${message.id}`}\n variant=\"secondary\"\n size=\"sm\"\n aria-label={`Acknowledge message ${message.id}`}\n disabled={!connected || !onAction}\n // While a request is out the button says so but keeps focus: a\n // disabled button can drop the operator's place to the page.\n {...(channelPending\n ? { \"aria-disabled\": true, \"data-disabled\": \"true\" }\n : {})}\n onClick={() => {\n if (channelPending) return;\n void act({\n type: \"acknowledge\",\n channelId: channel.id,\n messageId: message.id,\n });\n }}\n >\n {channelPending === message.id ? \"Acknowledging…\" : \"Acknowledge\"}\n </Button>\n )}\n </div>\n </li>\n </Fragment>\n );\n })}\n </ol>\n ) : (\n <div className=\"px-3u py-5u\">\n <p className=\"m-0 text-oprt-md font-medium\">No messages in {channel.name} yet.</p>\n <p className=\"m-0 mt-1u text-oprt-text-dim\">\n Start a conversation with the channel participants.\n </p>\n </div>\n )}\n\n <div className=\"px-3u py-2u\">\n {channel.canPost ? (\n <form\n aria-label={`Compose to ${channel.name}`}\n onSubmit={(event) => {\n event.preventDefault();\n if (!draft.body.trim() || channelPending) return;\n void act({\n type: \"send\",\n channelId: channel.id,\n body: draft.body.trim(),\n acknowledgementRequested: draft.acknowledgementRequested,\n });\n }}\n >\n <Field\n label={`Message to ${channel.name}`}\n help={\n connected\n ? \"Enter adds a new line. Use Send message to submit.\"\n : \"Draft retained in this session. Sending resumes when the connection returns.\"\n }\n >\n <Textarea\n ref={composer}\n rows={3}\n maxLength={2_000}\n value={draft.body}\n disabled={!onAction}\n // Read-only while a request is out, so a focused composer keeps focus.\n readOnly={Boolean(channelPending)}\n placeholder=\"Write an update for this channel…\"\n onChange={(event) => updateDraft({ body: event.target.value })}\n />\n </Field>\n <div className=\"mt-2u flex flex-wrap items-center justify-between gap-2u\">\n <div className=\"flex items-center gap-1u\">\n <Checkbox\n id={`${instanceId}-acknowledgement`}\n aria-label=\"Request acknowledgement\"\n checked={draft.acknowledgementRequested}\n onCheckedChange={(checked) =>\n updateDraft({ acknowledgementRequested: checked === true })\n }\n disabled={Boolean(channelPending) || !onAction}\n />\n <label htmlFor={`${instanceId}-acknowledgement`} className=\"cursor-pointer\">\n Request acknowledgement\n </label>\n </div>\n <div className=\"flex items-center gap-2u\">\n <span\n className=\"font-mono text-oprt-sm text-oprt-text-dim\"\n aria-label={`${draft.body.length} of 2000 characters`}\n >\n {draft.body.length} / 2000\n </span>\n <Button\n type=\"submit\"\n size=\"sm\"\n disabled={!connected || !onAction || !draft.body.trim()}\n // Unavailable while the request is out, yet still focused: a disabled\n // button can drop the operator's place to the page.\n {...(channelPending\n ? { \"aria-disabled\": true, \"data-disabled\": \"true\" }\n : {})}\n >\n {channelPending === \"send\" ? \"Sending…\" : \"Send message\"}\n </Button>\n </div>\n </div>\n {!onAction && (\n <p className=\"m-0 mt-1u text-oprt-text-dim\">\n Posting is unavailable in this view.\n </p>\n )}\n </form>\n ) : (\n <p className=\"m-0 text-oprt-text-dim\">\n {channel.readOnlyReason ?? \"This channel is read-only.\"}\n </p>\n )}\n {/* Two regions that are always mounted, each for one outcome: a region whose role\n changes, or one marked polite, is not reliably announced as an alert. */}\n <p\n role=\"status\"\n className={`m-0 text-oprt-text-dim ${result?.kind === \"accepted\" ? \"mt-2u\" : \"\"}`}\n >\n {result?.kind === \"accepted\" ? result.message : \"\"}\n </p>\n <p\n role=\"alert\"\n className={`m-0 text-oprt-error ${result?.kind === \"rejected\" ? \"mt-2u\" : \"\"}`}\n >\n {result?.kind === \"rejected\" ? result.message : \"\"}\n </p>\n </div>\n <footer className=\"flex flex-wrap items-center justify-between gap-1u border-t border-oprt-border px-3u py-1u text-oprt-sm text-oprt-text-dim\">\n <span>Source: {snapshot.source}</span>\n <span>\n Snapshot <Stamp at={snapshot.observedAt} className=\"font-mono\" />\n </span>\n </footer>\n </>\n ) : (\n <p className=\"m-0 p-3u text-oprt-text-dim\">Select a channel to view its messages.</p>\n )}\n </section>\n </div>\n </section>\n );\n}\n","patterns/communications-console/communications-state.ts":"import type {\n CommunicationsActionRequest,\n CommunicationsActionResult,\n CommunicationsSnapshot,\n} from \"./types\";\n\nexport function applyCommunicationsAction(\n snapshot: CommunicationsSnapshot,\n request: CommunicationsActionRequest,\n at: number,\n): { snapshot: CommunicationsSnapshot; result: CommunicationsActionResult } {\n const reject = (message: string) => ({\n snapshot,\n result: { kind: \"rejected\" as const, message },\n });\n const channel = snapshot.channels.find((item) => item.id === request.channelId);\n if (!channel) return reject(\"This channel is no longer available. Select another channel.\");\n if (!request.requestId.trim()) return reject(\"A request identity is required. Try again.\");\n\n // A transport retry can arrive after the first accepted response was lost.\n if (request.type === \"send\") {\n const previous = snapshot.messages.find((message) => message.requestId === request.requestId);\n if (previous) {\n if (\n previous.channelId !== request.channelId ||\n previous.body !== request.body.trim() ||\n previous.acknowledgementRequested !== request.acknowledgementRequested\n )\n return reject(\"This request identity was already used for a different message.\");\n return {\n snapshot,\n result: {\n kind: \"accepted\",\n message: `${previous.id} was already accepted in this local exercise.`,\n },\n };\n }\n }\n if (snapshot.connection !== \"connected\") {\n return reject(\n \"Connection lost. Your draft is retained; restore the connection before sending.\",\n );\n }\n if (!Number.isFinite(at) || at < snapshot.observedAt) {\n return reject(\"The action time is older than the source. Refresh the channel before retrying.\");\n }\n if (!channel.participants.some((person) => person.id === snapshot.operator.id)) {\n return reject(\"You are no longer a participant in this channel.\");\n }\n\n if (request.type === \"send\") {\n if (!channel.canPost) return reject(channel.readOnlyReason ?? \"This channel is read-only.\");\n const body = request.body.trim();\n if (!body || body.length > 2_000)\n return reject(\"Write a message between 1 and 2,000 characters.\");\n const number =\n Math.max(\n 0,\n ...snapshot.messages.map((message) => Number(message.id.replace(/^MSG-/, \"\")) || 0),\n ) + 1;\n const id = `MSG-${String(number).padStart(4, \"0\")}`;\n return {\n snapshot: {\n ...snapshot,\n observedAt: at,\n messages: [\n ...snapshot.messages,\n {\n id,\n channelId: channel.id,\n sender: snapshot.operator,\n body,\n sentAt: at,\n delivery: \"delivered\",\n acknowledgementRequested: request.acknowledgementRequested,\n requestId: request.requestId,\n },\n ],\n },\n result: {\n kind: \"accepted\",\n message: `${id} delivered in this local exercise.${request.acknowledgementRequested ? \" Recipient acknowledgement is still pending.\" : \"\"}`,\n },\n };\n }\n\n const message = snapshot.messages.find(\n (item) => item.id === request.messageId && item.channelId === channel.id,\n );\n if (!message) return reject(\"The message is no longer available in this channel.\");\n if (message.sender.id === snapshot.operator.id)\n return reject(\"The sender cannot acknowledge their own message.\");\n if (message.delivery !== \"delivered\")\n return reject(\"Delivery must be confirmed before acknowledging this message.\");\n if (!message.acknowledgementRequested)\n return reject(\"This message does not request an acknowledgement.\");\n if (message.acknowledgement) {\n return {\n snapshot,\n result: { kind: \"accepted\", message: `${message.id} already has an acknowledgement.` },\n };\n }\n return {\n snapshot: {\n ...snapshot,\n observedAt: at,\n messages: snapshot.messages.map((item) =>\n item.id === message.id ? { ...item, acknowledgement: { by: snapshot.operator, at } } : item,\n ),\n },\n result: {\n kind: \"accepted\",\n message: `${message.id} acknowledged by ${snapshot.operator.name} in this local exercise.`,\n },\n };\n}\n","patterns/communications-console/demo.tsx":"\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { Badge, Button } from \"@oprt/react\";\nimport { CommunicationsConsole } from \"./communications-console\";\nimport { createCommunicationsSnapshot } from \"./fixtures\";\nimport { applyCommunicationsAction } from \"./communications-state\";\nimport type { CommunicationsSnapshot } from \"./types\";\n\nexport function CommunicationsConsoleDemo() {\n const [snapshot, setSnapshot] = useState(createCommunicationsSnapshot);\n const source = useRef(snapshot);\n const [selectedChannelId, setSelectedChannelId] = useState<string | null>(\"NET-01\");\n const connected = snapshot.connection === \"connected\";\n const receiptTarget = [...snapshot.messages]\n .reverse()\n .find(\n (message) =>\n message.channelId === selectedChannelId &&\n message.sender.id === snapshot.operator.id &&\n message.delivery === \"delivered\" &&\n message.acknowledgementRequested &&\n !message.acknowledgement,\n );\n\n function publish(next: CommunicationsSnapshot) {\n source.current = next;\n setSnapshot(next);\n }\n\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-2u\">\n <CommunicationsConsole\n snapshot={snapshot}\n selectedChannelId={selectedChannelId}\n onSelectedChannelChange={setSelectedChannelId}\n onAction={(request) => {\n const next = applyCommunicationsAction(\n source.current,\n request,\n source.current.observedAt + 60_000,\n );\n publish(next.snapshot);\n return next.result;\n }}\n />\n <div className=\"flex flex-wrap items-center gap-x-2u gap-y-0.5u\">\n <Badge appearance=\"text\" variant=\"neutral\">\n Fictional exercise\n </Badge>\n <span className=\"text-oprt-base text-oprt-text-dim\">\n Local messages only · No external service connected\n </span>\n </div>\n <details className=\"min-w-0 border-t border-oprt-border pt-2u text-oprt-base\">\n <summary className=\"min-h-3u cursor-pointer font-medium text-oprt-text-dim focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-oprt-border-focus\">\n Exercise controls\n </summary>\n <p className=\"m-0 mt-2u text-oprt-text-dim\">\n Change the local relay state or supply a recipient receipt. Restoring the connection does\n not submit a saved draft.\n </p>\n <div className=\"mt-2u flex flex-wrap gap-1u\">\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n onClick={() =>\n publish({\n ...source.current,\n connection: connected ? \"disconnected\" : \"connected\",\n observedAt: connected\n ? source.current.observedAt\n : source.current.observedAt + 60_000,\n })\n }\n >\n {connected ? \"Simulate connection loss\" : \"Restore connection\"}\n </Button>\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={!connected || !receiptTarget}\n onClick={() => {\n if (!receiptTarget) return;\n const channel = source.current.channels.find(\n (item) => item.id === receiptTarget.channelId,\n );\n const recipient = channel?.participants.find(\n (person) => person.id !== source.current.operator.id,\n );\n if (!recipient) return;\n const at = source.current.observedAt + 60_000;\n publish({\n ...source.current,\n observedAt: at,\n messages: source.current.messages.map((message) =>\n message.id === receiptTarget.id\n ? { ...message, acknowledgement: { by: recipient, at } }\n : message,\n ),\n });\n }}\n >\n Receive acknowledgement\n </Button>\n </div>\n </details>\n </div>\n );\n}\n","patterns/communications-console/fixtures.ts":"import type { CommunicationsPerson, CommunicationsSnapshot } from \"./types\";\n\nconst operator: CommunicationsPerson = {\n id: \"watch-operator\",\n name: \"Sgt Alex Morgan\",\n role: \"Support watch\",\n};\nconst facilities: CommunicationsPerson = {\n id: \"facilities-desk\",\n name: \"Facilities desk\",\n role: \"Base services\",\n};\nconst transport: CommunicationsPerson = {\n id: \"transport-desk\",\n name: \"Transport desk\",\n role: \"Vehicle support\",\n};\nconst supervisor: CommunicationsPerson = {\n id: \"watch-supervisor\",\n name: \"Lt Jordan Hayes\",\n role: \"Watch supervisor\",\n};\nconst supply: CommunicationsPerson = {\n id: \"supply-desk\",\n name: \"Supply desk\",\n role: \"Stores and receipts\",\n};\nconst at = (time: string) => Date.parse(`2026-10-02T${time}:00Z`);\n\n/** Fictional support traffic. Every action stays inside the current browser session. */\nexport function createCommunicationsSnapshot(): CommunicationsSnapshot {\n return {\n id: \"kestrel-support-exercise\",\n title: \"Communications Console\",\n location: \"Camp Kestrel support watch\",\n source: \"Kestrel exercise relay\",\n observedAt: at(\"09:20\"),\n connection: \"connected\",\n operator,\n channels: [\n {\n id: \"NET-01\",\n name: \"Base support\",\n purpose: \"Coordinate base services and the next watch handover.\",\n lead: \"Lt Jordan Hayes\",\n participants: [operator, facilities, transport, supervisor, supply],\n canPost: true,\n },\n {\n id: \"NET-02\",\n name: \"Transport\",\n purpose: \"Vehicle readiness, driver handovers, and routine base movements.\",\n lead: \"Transport desk\",\n participants: [operator, transport, supervisor],\n canPost: true,\n },\n {\n id: \"NET-03\",\n name: \"Facilities\",\n purpose: \"Building access, inspections, and maintenance coordination.\",\n lead: \"Facilities desk\",\n participants: [operator, facilities, supervisor],\n canPost: true,\n },\n {\n id: \"NET-04\",\n name: \"Supply\",\n purpose: \"Record routine stores requests and confirm receipt of supplies.\",\n lead: \"Supply desk\",\n participants: [operator, supply],\n canPost: true,\n },\n {\n id: \"NET-05\",\n name: \"Watch notices\",\n purpose: \"Published notices for the current support watch.\",\n lead: \"Lt Jordan Hayes\",\n participants: [operator, facilities, transport, supervisor, supply],\n canPost: false,\n readOnlyReason: \"Only the watch supervisor can post to this channel.\",\n },\n ],\n messages: [\n {\n id: \"MSG-0410\",\n channelId: \"NET-01\",\n sender: supervisor,\n body: \"Morning support handover is open. Keep facilities, transport, and supply updates in this channel.\",\n sentAt: at(\"09:02\"),\n delivery: \"delivered\",\n acknowledgementRequested: false,\n },\n {\n id: \"MSG-0411\",\n channelId: \"NET-01\",\n sender: operator,\n body: \"The standby generator inspection is complete. Facilities, confirm the log is ready for the 10:00 handover.\",\n sentAt: at(\"09:08\"),\n delivery: \"delivered\",\n acknowledgementRequested: true,\n },\n {\n id: \"MSG-0412\",\n channelId: \"NET-01\",\n sender: facilities,\n body: \"Please acknowledge the east service entrance reopening at 09:45, after the safety check. The inspection log is ready for handover.\",\n sentAt: at(\"09:16\"),\n delivery: \"delivered\",\n acknowledgementRequested: true,\n },\n {\n id: \"MSG-0413\",\n channelId: \"NET-02\",\n sender: transport,\n body: \"Two utility vehicles have completed their daily checks. Keys and inspection sheets are at the transport desk.\",\n sentAt: at(\"09:12\"),\n delivery: \"delivered\",\n acknowledgementRequested: false,\n },\n {\n id: \"MSG-0414\",\n channelId: \"NET-03\",\n sender: facilities,\n body: \"Workshop ventilation service is scheduled for 11:00. Please keep the service bay clear until the inspection is signed off.\",\n sentAt: at(\"09:14\"),\n delivery: \"delivered\",\n acknowledgementRequested: true,\n },\n {\n id: \"MSG-0415\",\n channelId: \"NET-05\",\n sender: supervisor,\n body: \"Support watch handover begins at 10:00 in the services office. Bring current inspection logs and outstanding supply requests.\",\n sentAt: at(\"09:18\"),\n delivery: \"delivered\",\n acknowledgementRequested: false,\n },\n ],\n };\n}\n","patterns/communications-console/index.ts":"export { CommunicationsConsole } from \"./communications-console\";\nexport { CommunicationsConsoleDemo } from \"./demo\";\nexport { createCommunicationsSnapshot } from \"./fixtures\";\nexport { applyCommunicationsAction } from \"./communications-state\";\nexport type {\n CommunicationsPerson,\n CommunicationsChannel,\n CommunicationsMessage,\n CommunicationsSnapshot,\n CommunicationsActionRequest,\n CommunicationsActionResult,\n CommunicationsConsoleProps,\n} from \"./types\";\n","patterns/communications-console/types.ts":"export interface CommunicationsPerson {\n id: string;\n name: string;\n role: string;\n}\n\nexport interface CommunicationsChannel {\n id: string;\n name: string;\n purpose: string;\n lead: string;\n participants: readonly CommunicationsPerson[];\n canPost: boolean;\n readOnlyReason?: string;\n}\n\nexport interface CommunicationsMessage {\n id: string;\n channelId: string;\n sender: CommunicationsPerson;\n body: string;\n sentAt: number;\n delivery: \"pending\" | \"delivered\" | \"failed\";\n acknowledgementRequested: boolean;\n acknowledgement?: { by: CommunicationsPerson; at: number };\n /** Application idempotency key, retained when an uncertain send is retried. */\n requestId?: string;\n}\n\nexport interface CommunicationsSnapshot {\n /** Replacing this identity resets channel drafts and pending feedback. */\n id: string;\n title: string;\n location: string;\n source: string;\n observedAt: number;\n connection: \"connected\" | \"disconnected\";\n operator: CommunicationsPerson;\n channels: readonly CommunicationsChannel[];\n messages: readonly CommunicationsMessage[];\n}\n\nexport type CommunicationsActionRequest = {\n requestId: string;\n channelId: string;\n} & (\n | { type: \"send\"; body: string; acknowledgementRequested: boolean }\n | { type: \"acknowledge\"; messageId: string }\n);\n\nexport interface CommunicationsActionResult {\n kind: \"accepted\" | \"rejected\";\n message: string;\n}\n\nexport interface CommunicationsConsoleProps {\n snapshot: CommunicationsSnapshot;\n selectedChannelId: string | null;\n onSelectedChannelChange: (channelId: string) => void;\n /** Publish authoritative messages separately. Keep requestId idempotent when retrying. */\n onAction?: (\n request: CommunicationsActionRequest,\n ) => CommunicationsActionResult | Promise<CommunicationsActionResult>;\n}\n","patterns/facility-monitoring/demo.tsx":"\"use client\";\n\nimport { useCallback, useEffect, useMemo, useState } from \"react\";\nimport { Checkbox, Field } from \"@oprt/react\";\nimport { FacilityMonitoring } from \"./facility-monitoring\";\nimport { createFacilitySnapshot } from \"./fixtures\";\nimport { initialReview, transitionReview } from \"./review-state\";\nimport type { CameraCommandResult } from \"./types\";\n\n/**\n * The training clip is a 459 KB base64 string. It is imported on demand, so it travels in its own\n * chunk and no page that merely imports the demo carries it. `null` while it loads.\n */\nfunction useTrainingMedia(): { media: string | null; failed: boolean } {\n const [loaded, setLoaded] = useState<{ media: string | null; failed: boolean }>({\n media: null,\n failed: false,\n });\n useEffect(() => {\n let live = true;\n import(\"./training-media\").then(\n (module) => live && setLoaded({ media: module.facilityTrainingMedia, failed: false }),\n () => live && setLoaded({ media: null, failed: true }),\n );\n return () => {\n live = false;\n };\n }, []);\n return loaded;\n}\n\nexport function FacilityMonitoringDemo() {\n const { media, failed } = useTrainingMedia();\n const [rejectCommands, setRejectCommands] = useState(false);\n\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-3u\">\n {media ? (\n <TrainingConsole media={media} rejectCommands={rejectCommands} />\n ) : failed ? (\n <p role=\"alert\" className=\"m-0 text-oprt-error\">\n The training footage could not be loaded. Reload the page to try again.\n </p>\n ) : (\n <p role=\"status\" className=\"m-0 text-oprt-text-dim\">\n Loading training footage…\n </p>\n )}\n <details className=\"border-t border-oprt-border pt-2u text-oprt-text-dim\">\n <summary className=\"min-h-[var(--oprt-height-sm)] cursor-pointer py-1u text-oprt-base font-medium focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-oprt-border-focus\">\n Simulation controls\n </summary>\n <div className=\"flex flex-col gap-2u\">\n <p className=\"m-0 text-oprt-base text-oprt-text-dim\">\n Training fixture · fixed source time 14:31 UTC. Generated silent still-scene footage;\n equipment reports and camera responses are simulated.\n </p>\n <Field label=\"Reject camera requests\">\n <Checkbox\n checked={rejectCommands}\n onCheckedChange={(checked) => setRejectCommands(checked === true)}\n />\n </Field>\n </div>\n </details>\n </div>\n );\n}\n\nfunction TrainingConsole({ media, rejectCommands }: { media: string; rejectCommands: boolean }) {\n const snapshot = useMemo(() => createFacilitySnapshot(media), [media]);\n const [review, setReview] = useState(() => initialReview(snapshot));\n\n useEffect(() => {\n if (review.mode !== \"review\" || !review.playing) return;\n let previous = performance.now();\n const interval = window.setInterval(() => {\n const now = performance.now();\n const elapsedMs = now - previous;\n previous = now;\n setReview((current) => transitionReview(current, { type: \"tick\", elapsedMs }, snapshot));\n }, 250);\n return () => window.clearInterval(interval);\n }, [review.mode, review.playing, snapshot]);\n\n // One function per setting: a new one on every 250 ms tick would read as a new camera service.\n const onCameraCommand = useCallback(\n async (): Promise<CameraCommandResult> =>\n rejectCommands\n ? {\n state: \"rejected\",\n message: \"Training service rejected the camera request. The view was not changed.\",\n }\n : {\n state: \"accepted\",\n message:\n \"Training service accepted the camera request. Completion has not been reported.\",\n },\n [rejectCommands],\n );\n\n return (\n <FacilityMonitoring\n snapshot={snapshot}\n review={review}\n onReviewChange={setReview}\n onCameraCommand={onCameraCommand}\n />\n );\n}\n","patterns/facility-monitoring/facility-monitoring.tsx":"\"use client\";\n\nimport { memo, useCallback, useEffect, useMemo, useReducer, useRef, useState } from \"react\";\nimport {\n ActionStatus,\n AttachmentPreview,\n Badge,\n Button,\n Chart,\n EntityPreview,\n EventTimeline,\n Inspector,\n MediaFrame,\n PlaybackControls,\n TimeRange,\n type ActionState,\n type MediaFrameProps,\n} from \"@oprt/react\";\nimport { GeoMap } from \"../geospatial-workspace/geo-map\";\nimport type { GeoSnapshot } from \"../geospatial-workspace/types\";\nimport { formatUtcEpoch } from \"../operational-domain\";\nimport { initialFacilityLayers, initialFacilityView } from \"./fixtures\";\nimport { resolveEquipment, resolveRecording, reviewAt, transitionReview } from \"./review-state\";\nimport {\n SynchronizedVideo,\n type VideoCaptureHandle,\n type VideoFrameState,\n} from \"./synchronized-video\";\nimport type {\n FacilityBookmark,\n FacilityCamera,\n FacilityMonitoringProps,\n FacilitySnapshot,\n} from \"./types\";\n\n/**\n * A time as \"YYYY-MM-DD HH:MM:SS UTC\", always in whole seconds: playback moves the cursor between\n * seconds, and nothing here needs sub-second timing. `zone: false` drops the trailing \"UTC\" for the\n * chart, which appends its own unit.\n */\nconst utc = (time: number, zone = true) => {\n const text = formatUtcEpoch(time, { milliseconds: \"never\" });\n return zone ? text : text.replace(/ UTC$/, \"\");\n};\nconst chartTime = (time: number) => utc(time, false);\n/** Shared by every render, so a memoized child sees the same value each time. */\nconst noAnnotations: readonly never[] = [];\nconst noop = () => {};\nconst StableChart = memo(Chart);\nconst StableEventTimeline = memo(EventTimeline);\n\ntype FrameRecord = { key: string; src: string | undefined; value: VideoFrameState };\n/**\n * Whether a new frame report changes nothing the console shows. The video reports every few\n * hundred milliseconds while it plays; the console shows the state, the message, the whole second\n * it captured, and whether the frame is close enough to the cursor to count as synchronized.\n */\nfunction sameFrame(previous: FrameRecord, next: FrameRecord, wanted: number | null): boolean {\n if (previous.key !== next.key || previous.src !== next.src) return false;\n const a = previous.value;\n const b = next.value;\n if (a.state !== b.state || a.message !== b.message) return false;\n if (a.mediaTime === b.mediaTime) return true;\n if (a.mediaTime === null || b.mediaTime === null) return false;\n const close = (mediaTime: number) => wanted !== null && Math.abs(mediaTime - wanted) <= 0.25;\n return (\n Math.floor(a.mediaTime) === Math.floor(b.mediaTime) && close(a.mediaTime) === close(b.mediaTime)\n );\n}\nconst focusStyle =\n \"outline-hidden focus-visible:shadow-oprt-focus forced-colors:focus-visible:outline-solid forced-colors:focus-visible:outline-2\";\nconst titleStyle = \"m-0 text-oprt-base font-semibold uppercase tracking-oprt-caps\";\n\n/** One application-owned cursor drives camera, equipment, map and trend evidence. */\nexport function FacilityMonitoring({\n snapshot,\n review,\n onReviewChange,\n onCameraCommand,\n}: FacilityMonitoringProps) {\n const [cameraId, setCameraId] = useState<string | null>(snapshot.cameras[0]?.id ?? null);\n const [equipmentId, setEquipmentId] = useState<string | null>(\n snapshot.cameras[0]?.equipmentId ?? snapshot.equipment[0]?.id ?? null,\n );\n const [wall, setWall] = useState(false);\n const [showRange, setShowRange] = useState(false);\n const [bookmarks, setBookmarks] = useState<FacilityBookmark[]>([]);\n const [eventId, setEventId] = useState<string | null>(null);\n const [view, setView] = useState(initialFacilityView);\n const frameReport = useRef<FrameRecord | null>(null);\n const [, redrawForFrame] = useReducer((count: number) => count + 1, 0);\n const [retry, setRetry] = useState(0);\n const [capture, setCapture] = useState<{ src: string; at: number; camera: string } | null>(null);\n const [captureStatus, setCaptureStatus] = useState<{\n state: ActionState;\n message: string;\n } | null>(null);\n const [command, setCommand] = useState<{\n state: ActionState;\n message: string;\n requestId?: string;\n } | null>(null);\n const inspector = useRef<HTMLElement>(null);\n const media = useRef<VideoCaptureHandle>(null);\n const requestVersion = useRef(0);\n const captureVersion = useRef(0);\n const bookmarkCount = useRef(0);\n const bookmarkList = useRef<HTMLDivElement>(null);\n const bookmarkButton = useRef<HTMLButtonElement>(null);\n const focusAfterBookmarks = useRef<string | null>(null);\n const [bookmarkNote, setBookmarkNote] = useState(\"\");\n const sourceKey = JSON.stringify([snapshot.id, snapshot.source]);\n const visibleBookmarks = bookmarks.filter((bookmark) => bookmark.sourceKey === sourceKey);\n const selectedCamera = snapshot.cameras.find((camera) => camera.id === cameraId);\n const time = reviewAt(snapshot, review);\n const resolved = useMemo(() => resolveEquipment(snapshot, time), [snapshot, time]);\n // The map only needs a new snapshot when the evidence changes, not on every tick of the cursor.\n const held = useRef<{ snapshot: FacilitySnapshot; value: GeoSnapshot } | null>(null);\n if (\n !held.current ||\n held.current.snapshot !== snapshot ||\n held.current.value.revision !== resolved.revision\n )\n held.current = { snapshot, value: resolved };\n const mapSnapshot = held.current.value;\n const selectedEquipment = mapSnapshot.entities.find((entity) => entity.record.id === equipmentId);\n const cameraTime =\n review.mode === \"live\" &&\n selectedCamera &&\n Number.isFinite(selectedCamera.latencyMs) &&\n selectedCamera.latencyMs! >= 0\n ? time - selectedCamera.latencyMs!\n : time;\n const clip =\n selectedCamera && (review.mode === \"review\" || selectedCamera.connection === \"connected\")\n ? resolveRecording(selectedCamera, cameraTime)\n : null;\n const frameKey = JSON.stringify([\n sourceKey,\n selectedCamera?.id,\n clip?.recording.id,\n clip?.recording.start,\n clip?.recording.end,\n clip?.recording.mediaOffset,\n retry,\n ]);\n // The latest report is read here, so a render between two redraws still sees the newest frame.\n const frame = frameReport.current;\n const actualFrame =\n frame?.key === frameKey && frame.src === clip?.recording.src ? frame.value : null;\n const wantedMediaTime = useRef<number | null>(null);\n wantedMediaTime.current = clip?.mediaTime ?? null;\n const synchronized =\n !!clip &&\n !!actualFrame &&\n actualFrame.state === \"ready\" &&\n actualFrame.mediaTime !== null &&\n Math.abs(actualFrame.mediaTime - clip.mediaTime) <= 0.25;\n const capturedAt =\n clip && actualFrame?.mediaTime != null\n ? clip.recording.start + (actualFrame.mediaTime - clip.recording.mediaOffset) * 1000\n : null;\n const clipSrc = clip?.recording.src;\n const onFrameChange = useCallback(\n (value: VideoFrameState) => {\n const previous = frameReport.current;\n const next = { key: frameKey, src: clipSrc, value };\n frameReport.current = next;\n if (!previous || !sameFrame(previous, next, wantedMediaTime.current)) redrawForFrame();\n },\n [frameKey, clipSrc],\n );\n const currentKey = useRef(frameKey);\n currentKey.current = frameKey;\n\n // Keyed to whether a camera service exists, not to the function: a host that passes a new\n // function on every render would otherwise clear each confirmation within a quarter second.\n const hasCameraService = Boolean(onCameraCommand);\n useEffect(() => {\n requestVersion.current++;\n captureVersion.current++;\n setCommand(null);\n setCaptureStatus(null);\n }, [sourceKey, cameraId, hasCameraService]);\n useEffect(\n () => () => {\n requestVersion.current++;\n captureVersion.current++;\n },\n [],\n );\n\n // Handlers that go to memoized children keep one identity and read the latest props from here.\n const latest = useRef({ snapshot, review, onReviewChange });\n latest.current = { snapshot, review, onReviewChange };\n const selectCamera = useCallback((camera: FacilityCamera) => {\n setCameraId(camera.id);\n setEquipmentId(camera.equipmentId);\n }, []);\n const cameras = snapshot.cameras;\n const selectEquipment = useCallback(\n (id: string | null) => {\n setEquipmentId(id);\n const related = cameras.find((camera) => camera.equipmentId === id);\n setCameraId(related?.id ?? null);\n },\n [cameras],\n );\n const seek = useCallback((at: number) => {\n const current = latest.current;\n current.onReviewChange(\n transitionReview(current.review, { type: \"seek\", time: at }, current.snapshot),\n );\n }, []);\n const openFromWall = useCallback(\n (camera: FacilityCamera) => {\n selectCamera(camera);\n setWall(false);\n },\n [selectCamera],\n );\n const inspectOnMap = useCallback(\n (id: string | null) => {\n selectEquipment(id);\n inspector.current?.focus();\n },\n [selectEquipment],\n );\n const selectFromTimeline = useCallback(\n (id: string | null) => {\n const { snapshot: current } = latest.current;\n const event = current.events.find((item) => item.id === id);\n if (!event) return;\n setEventId(event.id);\n if (event.cameraId) {\n const camera = current.cameras.find((item) => item.id === event.cameraId);\n if (camera) selectCamera(camera);\n } else if (event.equipmentId) selectEquipment(event.equipmentId);\n seek(event.start);\n },\n [selectCamera, selectEquipment, seek],\n );\n function addBookmark() {\n bookmarkCount.current++;\n const name = utc(time);\n setBookmarks((current) => [\n ...current,\n {\n id: `${snapshot.id}:${bookmarkCount.current}`,\n name,\n cursor: time,\n cameraId,\n equipmentId,\n sourceKey,\n },\n ]);\n setBookmarkNote(`Bookmark ${name} added.`);\n }\n function removeBookmark(bookmark: FacilityBookmark) {\n const index = visibleBookmarks.findIndex((item) => item.id === bookmark.id);\n const neighbor = visibleBookmarks[index + 1] ?? visibleBookmarks[index - 1];\n // Focus moves to the neighboring bookmark, or back to the control that adds one.\n focusAfterBookmarks.current = neighbor?.id ?? \"add\";\n setBookmarks((current) => current.filter((item) => item.id !== bookmark.id));\n setBookmarkNote(`Bookmark ${bookmark.name} removed.`);\n }\n useEffect(() => {\n const target = focusAfterBookmarks.current;\n if (target === null) return;\n focusAfterBookmarks.current = null;\n const restore = [\n ...(bookmarkList.current?.querySelectorAll<HTMLElement>(\"[data-restore-bookmark]\") ?? []),\n ].find((element) => element.dataset.restoreBookmark === target);\n (target === \"add\" ? bookmarkButton.current : restore)?.focus();\n }, [bookmarks]);\n async function takeSnapshot() {\n if (!synchronized || !media.current || !selectedCamera || !clip) return;\n const version = ++captureVersion.current;\n const key = frameKey;\n const cameraName = selectedCamera.name;\n setCaptureStatus({ state: \"in-progress\", message: \"Capturing the confirmed frame…\" });\n try {\n const result = await media.current.capture();\n if (version !== captureVersion.current || key !== currentKey.current) return;\n if (Math.abs(result.mediaTime - clip.mediaTime) > 0.25)\n throw new Error(\"The displayed frame moved. Pause review and try again.\");\n const at = clip.recording.start + (result.mediaTime - clip.recording.mediaOffset) * 1000;\n setCapture({ src: result.src, at, camera: cameraName });\n setCaptureStatus({\n state: \"complete\",\n message: `Snapshot captured at ${utc(at)}. Stored in this session.`,\n });\n } catch (error) {\n if (version === captureVersion.current && key === currentKey.current)\n setCaptureStatus({\n state: \"failed\",\n message:\n error instanceof Error\n ? error.message\n : \"Snapshot unavailable. Try again when the frame is ready.\",\n });\n }\n }\n const commandReason = !selectedCamera\n ? \"Select a camera to see its controls.\"\n : review.mode !== \"live\"\n ? \"Camera controls are read-only during historical review.\"\n : !selectedCamera.canControl\n ? (selectedCamera.controlReason ?? \"Camera access is read-only.\")\n : selectedCamera.connection !== \"connected\"\n ? \"Camera controls are unavailable while disconnected.\"\n : !onCameraCommand\n ? \"No camera-control integration supplied.\"\n : null;\n async function resetCamera() {\n if (commandReason || !selectedCamera || !onCameraCommand || command?.state === \"in-progress\")\n return;\n const version = ++requestVersion.current;\n setCommand({ state: \"in-progress\", message: \"Requesting the configured camera view…\" });\n try {\n const result = await onCameraCommand({\n cameraId: selectedCamera.id,\n command: \"reset-view\",\n requestedAt: time,\n });\n if (version === requestVersion.current) setCommand(result);\n } catch {\n if (version === requestVersion.current)\n setCommand({\n state: \"failed\",\n message: \"Camera request failed. No outcome was confirmed.\",\n });\n }\n }\n const mediaState: MediaFrameProps[\"state\"] = !clip\n ? selectedCamera?.connection === \"reconnecting\"\n ? \"reconnecting\"\n : \"unavailable\"\n : actualFrame?.state === \"error\"\n ? \"unavailable\"\n : !synchronized\n ? \"reconnecting\"\n : review.mode === \"live\"\n ? selectedCamera?.latencyMs === null ||\n !Number.isFinite(selectedCamera?.latencyMs) ||\n selectedCamera!.latencyMs! < 0\n ? \"recorded\"\n : selectedCamera && (selectedCamera.latencyMs ?? 0) > 0\n ? \"delayed\"\n : \"live\"\n : review.playing\n ? \"recorded\"\n : \"paused\";\n const detail = !selectedCamera\n ? \"No camera is associated with this equipment.\"\n : review.mode === \"live\" && selectedCamera.connection !== \"connected\"\n ? \"Camera access is unavailable. Equipment and history remain accessible.\"\n : !clip\n ? \"No recording at the selected time.\"\n : actualFrame?.state === \"error\"\n ? (actualFrame.message ?? \"Media could not be decoded.\")\n : !synchronized\n ? \"Waiting for the requested frame. Displayed imagery may be from the previous position.\"\n : review.mode === \"live\" &&\n (selectedCamera.latencyMs === null ||\n !Number.isFinite(selectedCamera.latencyMs) ||\n selectedCamera.latencyMs < 0)\n ? \"Live alignment unknown. Showing the recording at the requested source time.\"\n : \"Generated silent still-scene recording for this training example.\";\n const pressure = snapshot.equipment.find((equipment) => equipment.id === equipmentId);\n const series = useMemo(\n () => [\n {\n id: equipmentId ?? \"pressure\",\n label: pressure?.name ?? \"No equipment\",\n data: pressure?.history.map((report) => ({ x: report.at, y: report.pressure })) ?? [],\n },\n ],\n [equipmentId, pressure],\n );\n const selectedEvent =\n snapshot.events.find((event) => event.id === eventId && event.start === time) ??\n snapshot.events.find((event) => event.start === time);\n\n return (\n <section\n aria-label=\"Facility monitoring\"\n className=\"not-prose flex min-w-0 flex-col gap-3u font-sans text-oprt-base text-oprt-text\"\n >\n <header className=\"flex flex-wrap items-start justify-between gap-2u border-b border-oprt-border pb-2u\">\n <div className=\"flex min-w-0 flex-col gap-1u\">\n <h2 className=\"m-0 text-oprt-xl font-semibold\">\n {snapshot.title ?? \"West utility plant\"}\n </h2>\n <p className=\"m-0 text-oprt-text-dim\">{snapshot.source}</p>\n </div>\n <div className=\"flex flex-col items-end gap-1u\">\n <Badge\n appearance=\"text\"\n indicator=\"none\"\n variant={review.mode === \"live\" ? \"success\" : \"info\"}\n >\n {review.mode === \"live\" ? \"Following source\" : \"Historical review\"}\n </Badge>\n <span className=\"font-mono\">{utc(time)}</span>\n </div>\n </header>\n\n <div\n role=\"group\"\n aria-label=\"Camera selection\"\n className=\"flex flex-wrap items-center gap-1u\"\n >\n {snapshot.cameras.map((camera) => (\n <Button\n key={camera.id}\n size=\"sm\"\n variant={camera.id === cameraId ? \"secondary\" : \"ghost\"}\n aria-label={`Select ${camera.name}`}\n aria-pressed={camera.id === cameraId}\n onClick={() => selectCamera(camera)}\n >\n {camera.name}\n </Button>\n ))}\n <Button size=\"sm\" variant=\"tertiary\" aria-pressed={wall} onClick={() => setWall(!wall)}>\n Camera wall\n </Button>\n </div>\n\n {wall && (\n <div\n aria-label=\"Camera wall\"\n className=\"grid min-w-0 grid-cols-[repeat(auto-fit,minmax(min(100%,24rem),1fr))] gap-2u\"\n >\n {snapshot.cameras.map((camera) => (\n <CameraWallTile\n key={`${sourceKey}:${camera.id}`}\n camera={camera}\n time={time}\n historical={review.mode === \"review\"}\n playing={review.mode === \"review\" && review.playing}\n rate={review.playbackRate}\n onOpen={openFromWall}\n />\n ))}\n </div>\n )}\n\n <div className=\"flex min-w-0 flex-wrap items-start gap-3u\">\n <div className=\"flex min-w-0 flex-[2_1_32rem] flex-col gap-2u\">\n <MediaFrame\n label={selectedCamera?.name ?? \"Camera view\"}\n state={mediaState}\n capturedAt={capturedAt}\n {...(review.mode === \"live\" && selectedCamera\n ? { latencyMs: selectedCamera.latencyMs }\n : {})}\n {...(selectedCamera ? { source: selectedCamera.source } : {})}\n retained={!!clip && !!actualFrame && !synchronized && actualFrame.mediaTime !== null}\n detail={detail}\n actions={\n <>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={!synchronized || captureStatus?.state === \"in-progress\"}\n onClick={takeSnapshot}\n >\n Capture snapshot\n </Button>\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n disabled={!selectedCamera || !clip}\n onClick={() => setRetry((value) => value + 1)}\n >\n Reload media\n </Button>\n </>\n }\n >\n {clip && (\n <SynchronizedVideo\n key={frameKey}\n ref={media}\n src={clip.recording.src}\n time={clip.mediaTime}\n playing={review.mode === \"review\" && review.playing}\n playbackRate={review.playbackRate}\n label={`${selectedCamera!.name} footage`}\n onFrameChange={onFrameChange}\n />\n )}\n </MediaFrame>\n <div className=\"flex flex-wrap items-center gap-2u\">\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n disabled={!!commandReason || command?.state === \"in-progress\"}\n onClick={resetCamera}\n >\n Reset camera view\n </Button>\n {commandReason && <p className=\"m-0 text-oprt-text-dim\">{commandReason}</p>}\n </div>\n {command && <ActionStatus label=\"Camera request\" {...command} />}\n {captureStatus && <ActionStatus label=\"Snapshot\" {...captureStatus} />}\n </div>\n <div className=\"flex min-w-0 flex-[1_1_21rem] flex-col gap-2u\">\n <div role=\"group\" aria-label=\"Equipment list\" className=\"flex flex-wrap gap-1u\">\n {snapshot.equipment.map((equipment) => (\n <Button\n key={equipment.id}\n size=\"sm\"\n variant={equipment.id === equipmentId ? \"secondary\" : \"tertiary\"}\n aria-pressed={equipment.id === equipmentId}\n onClick={() => selectEquipment(equipment.id)}\n >\n {equipment.name}\n </Button>\n ))}\n </div>\n <Inspector\n ref={inspector}\n title=\"Equipment at review time\"\n tabIndex={-1}\n className={focusStyle}\n >\n {selectedEquipment ? (\n <EntityPreview\n entity={{\n ...selectedEquipment.record,\n facts: [\n { id: \"review-time\", label: \"Review time\", value: utc(time), mono: true },\n ...selectedEquipment.record.facts,\n ],\n }}\n />\n ) : (\n <p className=\"m-0 text-oprt-text-dim\">\n Select equipment to inspect the available evidence.\n </p>\n )}\n </Inspector>\n </div>\n </div>\n\n <div className=\"border-y border-oprt-border py-2u\">\n <PlaybackControls\n label=\"Review time\"\n start={review.range.start}\n end={review.range.end}\n value={time}\n playing={review.playing}\n onValueChange={seek}\n onPlayingChange={(playing) => {\n const ready =\n playing && time >= review.range.end\n ? transitionReview(review, { type: \"seek\", time: review.range.start }, snapshot)\n : review;\n onReviewChange(transitionReview(ready, { type: \"play\", playing }, snapshot));\n }}\n playbackRate={review.playbackRate}\n onPlaybackRateChange={(rate) =>\n onReviewChange(transitionReview(review, { type: \"rate\", rate }, snapshot))\n }\n eventTimes={snapshot.events.map((event) => event.start)}\n liveTime={snapshot.liveTime}\n followingLive={review.mode === \"live\"}\n onReturnToLive={() =>\n onReviewChange(transitionReview(review, { type: \"live\" }, snapshot))\n }\n />\n <div className=\"mt-2u flex flex-wrap gap-1u\">\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n aria-expanded={showRange}\n onClick={() => setShowRange(!showRange)}\n >\n Review window\n </Button>\n <Button ref={bookmarkButton} size=\"sm\" variant=\"tertiary\" onClick={addBookmark}>\n Bookmark this time\n </Button>\n </div>\n <p\n role=\"status\"\n aria-label=\"Bookmarks\"\n className={`m-0 text-oprt-text-dim ${bookmarkNote ? \"mt-2u\" : \"\"}`}\n >\n {bookmarkNote}\n </p>\n {showRange && (\n <div className=\"mt-2u\">\n <TimeRange\n label=\"Review window\"\n value={review.range}\n min={snapshot.range.start}\n max={snapshot.range.end}\n onValueChange={(range) =>\n onReviewChange(transitionReview(review, { type: \"range\", range }, snapshot))\n }\n />\n </div>\n )}\n </div>\n\n {visibleBookmarks.length > 0 && (\n <div\n ref={bookmarkList}\n className=\"flex flex-col gap-1u\"\n aria-label=\"Saved review bookmarks\"\n >\n <h3 className={titleStyle}>Session bookmarks</h3>\n {visibleBookmarks.map((bookmark) => (\n <div key={bookmark.id} className=\"flex flex-wrap items-center gap-1u\">\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n aria-label={`Restore bookmark ${bookmark.name}`}\n data-restore-bookmark={bookmark.id}\n onClick={() => {\n const camera = snapshot.cameras.find((item) => item.id === bookmark.cameraId);\n if (camera) selectCamera(camera);\n else {\n setCameraId(null);\n setEquipmentId(bookmark.equipmentId);\n }\n onReviewChange(\n transitionReview(\n { ...review, range: snapshot.range },\n { type: \"seek\", time: bookmark.cursor },\n snapshot,\n ),\n );\n }}\n >\n {bookmark.name} ·{\" \"}\n {snapshot.cameras.find((item) => item.id === bookmark.cameraId)?.name ??\n \"Equipment review\"}\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n aria-label={`Remove bookmark ${bookmark.name}`}\n onClick={() => removeBookmark(bookmark)}\n >\n Remove\n </Button>\n </div>\n ))}\n </div>\n )}\n\n <div className=\"grid min-w-0 grid-cols-[repeat(auto-fit,minmax(min(100%,28rem),1fr))] gap-3u\">\n <div className=\"flex min-w-0 flex-col gap-2u\">\n <GeoMap\n snapshot={mapSnapshot}\n entities={mapSnapshot.entities}\n selectedId={equipmentId}\n onSelectedIdChange={selectEquipment}\n view={view}\n onViewChange={setView}\n layers={initialFacilityLayers}\n annotations={noAnnotations}\n measurement={noAnnotations}\n mode=\"inspect\"\n onPoint={noop}\n onFrame={noop}\n onInspect={inspectOnMap}\n />\n <p className=\"m-0 text-oprt-text-dim\">\n Locations use reports available at the review time. Equipment without a position remains\n in the list.\n </p>\n </div>\n <StableChart\n label=\"Equipment pressure history\"\n series={series}\n xDomain={snapshot.range}\n range={review.range}\n cursor={time}\n cursorLabel=\"Review time\"\n unit=\"bar\"\n xLabel=\"Time\"\n xUnit=\"UTC\"\n formatX={chartTime}\n description=\"Supplied samples. Gaps stay missing; the solid review marker does not interpolate a pressure reading.\"\n />\n </div>\n\n <section aria-label=\"Facility event history\" className=\"flex min-w-0 flex-col gap-2u\">\n <h3 className={titleStyle}>Event history</h3>\n <StableEventTimeline\n label=\"Facility events\"\n events={snapshot.events}\n start={review.range.start}\n end={review.range.end}\n selectedId={selectedEvent?.id ?? null}\n onSelectionChange={selectFromTimeline}\n />\n </section>\n {capture && (\n <AttachmentPreview\n label=\"Captured camera frame\"\n attachment={{\n id: \"local-frame\",\n name: `${capture.camera} · ${utc(capture.at)}`,\n sizeBytes: Math.floor(((capture.src.split(\",\")[1]?.length ?? 0) * 3) / 4),\n mediaType: \"image/png\",\n }}\n preview={{\n kind: \"image\",\n src: capture.src,\n alt: `Generated training image from ${capture.camera}, captured ${utc(capture.at)}`,\n }}\n onDownload={() => {\n const anchor = document.createElement(\"a\");\n anchor.href = capture.src;\n anchor.download = \"facility-training-snapshot.png\";\n anchor.click();\n }}\n onRemove={() => setCapture(null)}\n />\n )}\n </section>\n );\n}\n\nconst CameraWallTile = memo(function CameraWallTile({\n camera,\n time,\n historical,\n playing,\n rate,\n onOpen,\n}: {\n camera: FacilityCamera;\n time: number;\n historical: boolean;\n playing: boolean;\n rate: number;\n onOpen: (camera: FacilityCamera) => void;\n}) {\n const [frame, setFrame] = useState<{ src: string; value: VideoFrameState } | null>(null);\n const latency =\n camera.latencyMs !== null && Number.isFinite(camera.latencyMs) && camera.latencyMs >= 0\n ? camera.latencyMs\n : null;\n const clip =\n historical || camera.connection === \"connected\"\n ? resolveRecording(camera, time - (historical ? 0 : (latency ?? 0)))\n : null;\n const src = clip?.recording.src ?? \"\";\n const onFrameChange = useCallback((value: VideoFrameState) => setFrame({ src, value }), [src]);\n const actual = frame?.src === src ? frame.value : null;\n const ready =\n !!clip &&\n actual?.state === \"ready\" &&\n actual.mediaTime !== null &&\n Math.abs(actual.mediaTime - clip.mediaTime) <= 0.25;\n const capturedAt =\n clip && actual?.mediaTime != null\n ? clip.recording.start + (actual.mediaTime - clip.recording.mediaOffset) * 1000\n : null;\n const state =\n !clip || actual?.state === \"error\"\n ? \"unavailable\"\n : !ready\n ? \"reconnecting\"\n : historical\n ? playing\n ? \"recorded\"\n : \"paused\"\n : latency === null\n ? \"recorded\"\n : latency > 0\n ? \"delayed\"\n : \"live\";\n return (\n <MediaFrame\n label={`${camera.name} overview`}\n state={state}\n capturedAt={capturedAt}\n source={camera.source}\n retained={!!clip && capturedAt !== null && !ready}\n detail={\n !clip\n ? \"No camera image available.\"\n : actual?.state === \"error\"\n ? (actual.message ?? \"Media could not be decoded.\")\n : !ready\n ? \"Waiting for the requested frame.\"\n : !historical && latency === null\n ? \"Live alignment unknown.\"\n : \"Frame aligned to the shared review time.\"\n }\n actions={\n <Button size=\"sm\" variant=\"tertiary\" onClick={() => onOpen(camera)}>\n Open {camera.name}\n </Button>\n }\n >\n {clip && (\n <SynchronizedVideo\n key={clip.recording.id}\n src={src}\n time={clip.mediaTime}\n playing={playing}\n playbackRate={rate}\n label={`${camera.name} overview footage`}\n onFrameChange={onFrameChange}\n />\n )}\n </MediaFrame>\n );\n});\n","patterns/facility-monitoring/fixtures.ts":"import type { LayerControlItem } from \"@oprt/react\";\nimport { utilitySite } from \"../geospatial-workspace/fixtures\";\nimport { formatUtcEpoch } from \"../operational-domain\";\nimport type { GeoEntity, GeoPoint, GeoView } from \"../geospatial-workspace/types\";\nimport type { FacilityEquipment, FacilitySnapshot } from \"./types\";\n\nexport const facilityStart = Date.parse(\"2026-10-01T14:30:00Z\");\nexport const initialFacilityView: GeoView = { center: [-122.3004, 37.8], zoom: 16, bearing: 0 };\nexport const initialFacilityLayers: readonly LayerControlItem[] = [\n { id: \"entities\", label: \"Equipment\", visible: true, opacity: 1 },\n { id: \"tracks\", label: \"Reported tracks\", visible: true, opacity: 0.8 },\n { id: \"uncertainty\", label: \"Position uncertainty\", visible: true, opacity: 0.65 },\n { id: \"coverage\", label: \"Reported coverage\", visible: true, opacity: 0.4 },\n { id: \"site\", label: \"Local site plan\", visible: true, opacity: 1 },\n];\n\nconst source = \"Harbor facility / synthetic training reference\";\nconst at = (seconds: number) => facilityStart + seconds * 1_000;\nconst iso = (seconds: number) => new Date(at(seconds)).toISOString();\n/** Operator-facing text reads \"YYYY-MM-DD HH:MM:SS UTC\", never the raw ISO string. */\nconst stamp = (seconds: number) => formatUtcEpoch(at(seconds), { milliseconds: \"never\" });\n/** A number stays with its unit: the no-break space keeps \"6.2\" and \"bar\" on one line. */\nconst bar = (value: number) => `${value}\\u00A0bar`;\nconst pumpPosition: GeoPoint = [-122.3018, 37.8005];\nconst yardPosition: GeoPoint = [-122.299, 37.7995];\n\nfunction report(\n id: string,\n name: string,\n seconds: number,\n pressure: number | null,\n position: GeoPoint | null,\n quality: \"current\" | \"stale\" | \"unknown\" = \"current\",\n capturedSeconds = seconds,\n reportSource = source,\n): FacilityEquipment[\"history\"][number] {\n const entity: GeoEntity = {\n record: {\n id,\n name,\n typeLabel: id === \"PUMP-01\" ? \"Pump\" : \"Valve\",\n summary: \"Supplied exercise report. Conditions and limits are application assertions.\",\n condition: {\n label:\n pressure === null\n ? \"Pressure unknown\"\n : pressure > 7\n ? \"Above supplied limit\"\n : \"Within supplied range\",\n variant: pressure === null ? \"neutral\" : pressure > 7 ? \"warning\" : \"success\",\n },\n quality: {\n state: quality,\n source: reportSource,\n updatedAt: at(capturedSeconds),\n detail:\n quality === \"stale\"\n ? `Report received at ${stamp(seconds)}; captured at ${stamp(capturedSeconds)} and explicitly marked stale by the fixture.`\n : \"Synthetic observation; no connection to deployed equipment.\",\n },\n facts: [\n {\n id: \"pressure\",\n label: \"Pressure\",\n value: pressure === null ? \"Unknown\" : bar(pressure),\n mono: true,\n },\n { id: \"limit\", label: \"Supplied upper limit\", value: bar(7), mono: true },\n { id: \"received\", label: \"Report received\", value: stamp(seconds), mono: true },\n ],\n },\n position,\n positionState: position === null ? \"unknown\" : quality === \"stale\" ? \"last-known\" : \"reported\",\n observedAt: quality === \"unknown\" ? null : iso(capturedSeconds),\n };\n return { at: at(seconds), entity, pressure };\n}\n\n/** The supplied 65-second local clip keeps the 60-second review endpoint decodable. */\nexport function createFacilitySnapshot(mediaSrc: string): FacilitySnapshot {\n return {\n id: \"facility-training-01\",\n title: \"West utility plant\",\n source,\n range: { start: facilityStart, end: at(60) },\n liveTime: at(60),\n equipment: [\n {\n id: \"PUMP-01\",\n name: \"Intake pump 01\",\n history: [\n report(\"PUMP-01\", \"Intake pump 01\", 0, 0, pumpPosition),\n report(\"PUMP-01\", \"Intake pump 01\", 10, 5.8, pumpPosition),\n report(\"PUMP-01\", \"Intake pump 01\", 20, 8.2, pumpPosition),\n report(\"PUMP-01\", \"Intake pump 01\", 35, 6.4, pumpPosition),\n report(\"PUMP-01\", \"Intake pump 01\", 40, 6.4, pumpPosition, \"stale\", 35),\n report(\n \"PUMP-01\",\n \"Intake pump 01\",\n 50,\n 6.1,\n pumpPosition,\n \"current\",\n 50,\n \"Maintenance logger / alternate exercise source\",\n ),\n report(\"PUMP-01\", \"Intake pump 01\", 60, 6.2, pumpPosition),\n ],\n },\n {\n id: \"VALVE-02\",\n name: \"Yard isolation valve 02\",\n history: [\n report(\"VALVE-02\", \"Yard isolation valve 02\", 0, null, null, \"unknown\"),\n report(\"VALVE-02\", \"Yard isolation valve 02\", 15, 0, yardPosition),\n report(\"VALVE-02\", \"Yard isolation valve 02\", 40, null, yardPosition, \"stale\", 15),\n ],\n },\n { id: \"GAUGE-03\", name: \"Unreported portable gauge 03\", history: [] },\n ],\n cameras: [\n {\n id: \"CAM-ALPHA\",\n name: \"Pump room alpha\",\n equipmentId: \"PUMP-01\",\n position: pumpPosition,\n connection: \"connected\",\n latencyMs: 0,\n source,\n canControl: true,\n recordings: [\n {\n id: \"alpha-before-gap\",\n start: facilityStart,\n end: at(25),\n src: mediaSrc,\n mediaOffset: 0,\n },\n { id: \"alpha-after-gap\", start: at(35), end: at(65), src: mediaSrc, mediaOffset: 35 },\n ],\n },\n {\n id: \"CAM-BRAVO\",\n name: \"Yard bravo\",\n equipmentId: \"VALVE-02\",\n position: yardPosition,\n connection: \"unavailable\",\n latencyMs: null,\n source,\n recordings: [],\n canControl: false,\n controlReason: \"Camera control permission is not available for this exercise role.\",\n },\n ],\n events: [\n {\n id: \"pressure-rise\",\n label: \"Pressure above supplied limit\",\n start: at(20),\n severity: \"warning\",\n lane: \"Equipment\",\n equipmentId: \"PUMP-01\",\n cameraId: \"CAM-ALPHA\",\n description: `Synthetic pressure report: ${bar(8.2)} against the supplied ${bar(7)} limit.`,\n },\n {\n id: \"recording-gap\",\n label: \"Recording unavailable\",\n start: at(25),\n end: at(35),\n severity: \"neutral\",\n lane: \"Camera\",\n equipmentId: \"PUMP-01\",\n cameraId: \"CAM-ALPHA\",\n description: \"No recording was supplied for this interval. Review retains the shared time.\",\n },\n {\n id: \"stale-report\",\n label: \"Equipment report marked stale\",\n start: at(40),\n severity: \"warning\",\n lane: \"Equipment\",\n equipmentId: \"PUMP-01\",\n cameraId: \"CAM-ALPHA\",\n description: `The report received at ${stamp(40)} retains the ${stamp(35)} observation.`,\n },\n {\n id: \"alternate-source\",\n label: \"Alternate equipment source\",\n start: at(50),\n severity: \"info\",\n lane: \"Equipment\",\n equipmentId: \"PUMP-01\",\n cameraId: \"CAM-ALPHA\",\n description:\n \"Equipment evidence came from the maintenance logger; camera footage keeps its own source.\",\n },\n ],\n site: utilitySite,\n };\n}\n","patterns/facility-monitoring/index.ts":"export { FacilityMonitoring } from \"./facility-monitoring\";\nexport { FacilityMonitoringDemo } from \"./demo\";\nexport {\n initialReview,\n transitionReview,\n resolveEquipment,\n resolveRecording,\n reviewAt,\n} from \"./review-state\";\nexport { createFacilitySnapshot } from \"./fixtures\";\nexport type {\n FacilitySnapshot,\n FacilityReviewState,\n FacilityMonitoringProps,\n FacilityCamera,\n FacilityEquipment,\n FacilityRecording,\n FacilityBookmark,\n CameraCommandRequest,\n CameraCommandResult,\n} from \"./types\";\n","patterns/facility-monitoring/review-state.ts":"import type { TimeRangeValue } from \"@oprt/react\";\nimport type { GeoEntity, GeoSnapshot } from \"../geospatial-workspace/types\";\nimport type {\n FacilityCamera,\n FacilityEquipment,\n FacilityRecording,\n FacilityReviewState,\n FacilitySnapshot,\n} from \"./types\";\n\nexport type FacilityReviewAction =\n | { type: \"seek\"; time: number }\n | { type: \"play\"; playing: boolean }\n | { type: \"rate\"; rate: number }\n | { type: \"range\"; range: TimeRangeValue }\n | { type: \"live\" }\n | { type: \"tick\"; elapsedMs: number };\n\nexport const facilityPlaybackRates = [0.5, 1, 2, 4] as const;\nconst validTime = (time: number) =>\n Number.isFinite(time) && !Number.isNaN(new Date(time).getTime());\nconst validRange = ({ start, end }: TimeRangeValue) =>\n validTime(start) && validTime(end) && start < end;\nconst supportedRate = (rate: number) => facilityPlaybackRates.some((value) => value === rate);\nconst clamp = (time: number, range: TimeRangeValue) =>\n validTime(time) ? Math.min(range.end, Math.max(range.start, time)) : range.start;\n\n/** Invalid review preferences fall back to the supplied recording bounds. */\nfunction reviewRange(snapshot: FacilitySnapshot, range: TimeRangeValue): TimeRangeValue | null {\n if (!validRange(snapshot.range)) return null;\n if (!validRange(range)) return { ...snapshot.range };\n const bounded = {\n start: Math.max(snapshot.range.start, range.start),\n end: Math.min(snapshot.range.end, range.end),\n };\n return validRange(bounded) ? bounded : { ...snapshot.range };\n}\n\nexport function initialReview(snapshot: FacilitySnapshot): FacilityReviewState {\n const range = validRange(snapshot.range) ? { ...snapshot.range } : { start: 0, end: 0 };\n return {\n mode: \"live\",\n range,\n cursor: clamp(snapshot.liveTime, range),\n playing: false,\n playbackRate: 1,\n };\n}\n\n/** The caller owns liveTime. Review never reads a clock or advances that reference. */\nexport function reviewAt(snapshot: FacilitySnapshot, review: FacilityReviewState): number {\n const range = reviewRange(snapshot, review.range);\n if (!range) return Number.NaN;\n return clamp(review.mode === \"live\" ? snapshot.liveTime : review.cursor, range);\n}\n\nexport function transitionReview(\n review: FacilityReviewState,\n action: FacilityReviewAction,\n snapshot: FacilitySnapshot,\n): FacilityReviewState {\n const range = reviewRange(snapshot, review.range);\n if (!range) return { ...review, playing: false };\n const cursor = reviewAt(snapshot, review);\n switch (action.type) {\n case \"seek\":\n return validTime(action.time)\n ? { ...review, mode: \"review\", range, cursor: clamp(action.time, range), playing: false }\n : review;\n case \"play\":\n return {\n ...review,\n mode: action.playing ? \"review\" : review.mode,\n range,\n cursor,\n playing: action.playing && cursor < range.end && supportedRate(review.playbackRate),\n };\n case \"rate\":\n return supportedRate(action.rate) ? { ...review, playbackRate: action.rate } : review;\n case \"range\": {\n if (!validRange(action.range)) return review;\n const nextRange = {\n start: Math.max(snapshot.range.start, action.range.start),\n end: Math.min(snapshot.range.end, action.range.end),\n };\n return validRange(nextRange)\n ? {\n ...review,\n mode: \"review\",\n range: nextRange,\n cursor: clamp(cursor, nextRange),\n playing: false,\n }\n : review;\n }\n case \"live\":\n return {\n ...initialReview(snapshot),\n playbackRate: supportedRate(review.playbackRate) ? review.playbackRate : 1,\n };\n case \"tick\": {\n if (\n review.mode !== \"review\" ||\n !review.playing ||\n !Number.isFinite(action.elapsedMs) ||\n action.elapsedMs <= 0\n )\n return review;\n if (!supportedRate(review.playbackRate)) return { ...review, playing: false };\n // Compare remaining time first so a very large finite tick cannot overflow.\n const remaining = range.end - cursor;\n const atEnd = action.elapsedMs >= remaining / review.playbackRate;\n return {\n ...review,\n range,\n cursor: atEnd ? range.end : cursor + action.elapsedMs * review.playbackRate,\n playing: !atEnd,\n };\n }\n }\n}\n\n/** Date.parse accepts rolled-over dates such as February 30; reject those. */\nfunction observationTime(value: string): number {\n const time = Date.parse(value);\n const calendar = /^\\d{4}-\\d{2}-\\d{2}T/.exec(value)?.[0].slice(0, 10);\n if (!validTime(time) || !calendar) return Number.NaN;\n const midnight = Date.parse(`${calendar}T00:00:00Z`);\n return validTime(midnight) && new Date(midnight).toISOString().startsWith(calendar)\n ? time\n : Number.NaN;\n}\n\nfunction availableFrame(frame: FacilityEquipment[\"history\"][number], at: number): boolean {\n if (!validTime(frame.at) || frame.at > at) return false;\n const captured = frame.entity.observedAt;\n if (captured !== null) {\n const capturedAt = observationTime(captured);\n if (!validTime(capturedAt) || capturedAt > at) return false;\n }\n const updated = frame.entity.record.quality.updatedAt;\n return updated == null || (validTime(updated) && updated <= at);\n}\n\nfunction missingEntity(equipment: FacilityEquipment, source: string): GeoEntity {\n return {\n record: {\n id: equipment.id,\n name: equipment.name,\n facts: [],\n quality: {\n state: \"unavailable\",\n source,\n updatedAt: null,\n detail: \"No equipment report is available at this review time.\",\n },\n },\n position: null,\n positionState: \"unknown\",\n observedAt: null,\n };\n}\n\nexport function resolveEquipment(snapshot: FacilitySnapshot, at: number): GeoSnapshot {\n const ceiling =\n validTime(at) && validRange(snapshot.range) ? Math.min(at, snapshot.range.end) : Number.NaN;\n const evidence: (readonly [\n string,\n number | null,\n number | null,\n string | null,\n string | null,\n ])[] = [];\n let available = 0;\n let latestObservation: number | null = null;\n const entities = snapshot.equipment.map((equipment) => {\n let frame: FacilityEquipment[\"history\"][number] | undefined;\n let frameIndex: number | null = null;\n if (validTime(ceiling))\n equipment.history.forEach((candidate, index) => {\n // Last supplied frame wins ties, allowing a corrected observation.\n if (availableFrame(candidate, ceiling) && (!frame || candidate.at >= frame.at)) {\n frame = candidate;\n frameIndex = index;\n }\n });\n if (!frame) {\n evidence.push([equipment.id, null, null, null, null]);\n return missingEntity(equipment, snapshot.source);\n }\n available += 1;\n const entity = frame.entity;\n evidence.push([\n equipment.id,\n frame.at,\n frameIndex,\n entity.record.quality.source ?? null,\n entity.observedAt,\n ]);\n if (entity.observedAt !== null) {\n const observed = observationTime(entity.observedAt);\n if (latestObservation === null || observed > latestObservation) latestObservation = observed;\n }\n return {\n ...entity,\n record: { ...entity.record, id: equipment.id },\n ...(entity.track\n ? {\n track: entity.track.filter((point) => {\n const observed = observationTime(point.observedAt);\n return validTime(observed) && observed <= ceiling;\n }),\n }\n : {}),\n };\n });\n return {\n id: snapshot.id,\n // Stable while the evidence is unchanged; source replacement creates a new frame.\n revision: JSON.stringify([snapshot.source, evidence]),\n source: snapshot.source,\n observedAt: latestObservation === null ? null : new Date(latestObservation).toISOString(),\n availability:\n available === 0\n ? \"unavailable\"\n : entities.every((entity) => entity.record.quality.state === \"current\")\n ? \"current\"\n : \"partial\",\n detail: \"Equipment reports at or before the shared review cursor.\",\n entities,\n site: snapshot.site,\n };\n}\n\n/** No nearest-recording fallback: a gap is unavailable footage. */\nexport function resolveRecording(\n camera: FacilityCamera,\n at: number,\n): { recording: FacilityRecording; mediaTime: number } | null {\n if (!validTime(at)) return null;\n let recording: FacilityRecording | undefined;\n for (const candidate of camera.recordings) {\n if (\n !validRange(candidate) ||\n !candidate.src.trim() ||\n !Number.isFinite(candidate.mediaOffset) ||\n candidate.mediaOffset < 0\n )\n continue;\n if (\n candidate.start <= at &&\n at < candidate.end &&\n (!recording || candidate.start >= recording.start)\n )\n recording = candidate;\n }\n if (!recording) return null;\n const mediaTime = recording.mediaOffset + (at - recording.start) / 1_000;\n return Number.isFinite(mediaTime) ? { recording, mediaTime } : null;\n}\n","patterns/facility-monitoring/synchronized-video.tsx":"\"use client\";\n\nimport { forwardRef, useImperativeHandle, useLayoutEffect, useRef } from \"react\";\n\nexport type VideoFrameState = {\n state: \"loading\" | \"seeking\" | \"ready\" | \"waiting\" | \"ended\" | \"error\";\n mediaTime: number | null;\n message?: string;\n};\n\nexport type VideoCaptureHandle = {\n capture: () => Promise<{ src: string; mediaTime: number }>;\n};\n\nexport type SynchronizedVideoProps = {\n src: string | null;\n /** Requested position in seconds from the start of this recording. */\n time: number;\n playing: boolean;\n playbackRate: number;\n label: string;\n onFrameChange: (frame: VideoFrameState) => void;\n};\n\ntype VideoSession = {\n applyControls: () => void;\n capture: VideoCaptureHandle[\"capture\"];\n};\n\n// This recipe's tolerance is an integration choice, not a core component default.\nconst synchronizationTolerance = 0.25;\nconst pausedSeekDeadlineMs = 5_000;\n\nexport const SynchronizedVideo = forwardRef<VideoCaptureHandle, SynchronizedVideoProps>(\n ({ src, time, playing, playbackRate, label, onFrameChange }, ref) => {\n const videoRef = useRef<HTMLVideoElement>(null);\n const sessionRef = useRef<VideoSession | null>(null);\n const latest = useRef({ src, time, playing, playbackRate, onFrameChange });\n latest.current = { src, time, playing, playbackRate, onFrameChange };\n\n useImperativeHandle(\n ref,\n () => ({\n capture: async () => {\n if (!sessionRef.current)\n throw new Error(\"No synchronized frame is available to capture.\");\n return sessionRef.current.capture();\n },\n }),\n [],\n );\n\n useLayoutEffect(() => {\n const video = videoRef.current;\n if (!video) return;\n let active = true;\n let frame: VideoFrameState | null = null;\n let confirmedTime: number | null = null;\n let pendingSeek = false;\n let waiting = false;\n let failed = false;\n let failureKind: \"source\" | \"request\" | \"playback\" = \"source\";\n let lastPlaying = latest.current.playing;\n let ended = false;\n let playRequested = false;\n let playAttempt = 0;\n let seekDeadline: number | undefined;\n let seekTarget: number | null = null;\n let seekRetried = false;\n const clearSeekDeadline = () => {\n window.clearTimeout(seekDeadline);\n seekDeadline = undefined;\n };\n const isCurrent = () => active && videoRef.current === video && latest.current.src === src;\n const matchesSource = () =>\n Boolean(src && video.currentSrc && video.currentSrc === video.src);\n const drawable = () => video.readyState >= 2 && video.videoWidth > 0 && video.videoHeight > 0;\n const synchronized = () =>\n Number.isFinite(video.currentTime) &&\n Math.abs(video.currentTime - latest.current.time) <= synchronizationTolerance;\n const emit = (next: VideoFrameState) => {\n if (!isCurrent()) return;\n if (\n frame?.state === next.state &&\n frame.mediaTime === next.mediaTime &&\n frame.message === next.message\n )\n return;\n frame = next;\n latest.current.onFrameChange(next);\n };\n const fail = (message: string, kind: typeof failureKind = \"source\") => {\n clearSeekDeadline();\n failed = true;\n failureKind = kind;\n playAttempt += 1;\n playRequested = false;\n video.pause();\n emit({ state: \"error\", mediaTime: confirmedTime, message });\n };\n const armSeekDeadline = () => {\n if (latest.current.playing || failed || seekDeadline !== undefined) return;\n const target = latest.current.time;\n seekDeadline = window.setTimeout(() => {\n seekDeadline = undefined;\n if (!isCurrent() || failed || latest.current.playing || latest.current.time !== target)\n return;\n if (!pendingSeek && !video.seeking && frame?.state === \"ready\") return;\n if (seekRetried) {\n fail(\"Recording seek did not complete. Reload media to try again.\");\n return;\n }\n // A native paused seek can stall without an error or a seeked event.\n // Restart this decoder once; only fresh decoder observations can recover it.\n seekRetried = true;\n pendingSeek = false;\n waiting = false;\n ended = false;\n confirmedTime = null;\n playAttempt += 1;\n playRequested = false;\n emit({\n state: \"loading\",\n mediaTime: null,\n message: \"Recording seek stalled. Reloading the selected recording once.\",\n });\n video.pause();\n video.load();\n armSeekDeadline();\n }, pausedSeekDeadlineMs);\n };\n const playIfRequested = () => {\n if (\n !isCurrent() ||\n !latest.current.playing ||\n failed ||\n pendingSeek ||\n video.seeking ||\n playRequested ||\n video.readyState < 1\n )\n return;\n playRequested = true;\n const attempt = ++playAttempt;\n try {\n const promise = video.play();\n // Older implementations can return undefined from play().\n promise?.catch((error: unknown) => {\n if (!isCurrent() || attempt !== playAttempt || !latest.current.playing) return;\n fail(\n `Playback failed: ${error instanceof Error ? error.message : \"the browser rejected playback.\"}`,\n \"playback\",\n );\n });\n } catch (error) {\n if (isCurrent() && attempt === playAttempt)\n fail(\n `Playback failed: ${error instanceof Error ? error.message : \"the browser rejected playback.\"}`,\n \"playback\",\n );\n }\n };\n\n const applyControls = () => {\n if (!isCurrent()) return;\n const request = latest.current;\n if (request.time !== seekTarget || request.playing) {\n clearSeekDeadline();\n seekTarget = request.time;\n // A target change must not abandon a decoder reload still awaiting metadata.\n if (request.playing || video.readyState >= 1) seekRetried = false;\n }\n const retryPlayback = !lastPlaying && request.playing;\n lastPlaying = request.playing;\n if (!src) {\n fail(\"No recording is available at this position.\");\n return;\n }\n if (!Number.isFinite(request.time) || request.time < 0) {\n fail(\"Requested recording time is invalid.\", \"request\");\n return;\n }\n if (!Number.isFinite(request.playbackRate) || request.playbackRate <= 0) {\n fail(\"Requested playback rate is invalid.\", \"request\");\n return;\n }\n if (!request.playing) {\n playAttempt += 1;\n playRequested = false;\n video.pause();\n }\n try {\n video.playbackRate = request.playbackRate;\n } catch {\n fail(\"The browser does not support the requested playback rate.\", \"request\");\n return;\n }\n if (video.readyState < 1) {\n if (seekRetried) armSeekDeadline();\n return;\n }\n if (!Number.isFinite(video.duration) || request.time > video.duration) {\n fail(\"Requested position is outside the recording range.\", \"request\");\n return;\n }\n const reconfirmRequest = failed && failureKind === \"request\";\n if (reconfirmRequest || (failed && failureKind === \"playback\" && retryPlayback)) {\n failed = false;\n }\n if (failed) return;\n if (reconfirmRequest || !synchronized()) {\n pendingSeek = true;\n ended = false;\n emit({\n state: \"seeking\",\n mediaTime: confirmedTime,\n message: \"Waiting for the requested frame to decode.\",\n });\n try {\n video.currentTime = request.time;\n armSeekDeadline();\n } catch {\n fail(\"The browser could not seek to the requested frame.\", \"request\");\n }\n } else {\n if (pendingSeek || video.seeking) armSeekDeadline();\n playIfRequested();\n }\n };\n\n // Only decoder observations confirm actual frame time. Setting currentTime\n // is a request and does not establish that the displayed frame changed.\n const confirmFrame = () => {\n if (!isCurrent() || failed || ended || waiting || pendingSeek || video.seeking) return;\n if (!drawable()) {\n emit({\n state: \"loading\",\n mediaTime: confirmedTime,\n message: \"Waiting for a decoded frame.\",\n });\n return;\n }\n if (!matchesSource()) {\n fail(\"Decoded frame source does not match the selected recording.\");\n return;\n }\n if (!synchronized()) {\n // Native playback can lead a host's sampled cursor between clock ticks.\n // Only a control update requests a seek; decoder events never rewind it.\n emit({\n state: \"seeking\",\n mediaTime: confirmedTime,\n message: \"Displayed frame is outside the requested position; awaiting synchronization.\",\n });\n return;\n }\n clearSeekDeadline();\n confirmedTime = video.currentTime;\n emit({ state: \"ready\", mediaTime: confirmedTime });\n playIfRequested();\n };\n const onMetadata = () => applyControls();\n const onData = () => confirmFrame();\n const onSeeking = () => {\n if (!isCurrent() || failed) return;\n pendingSeek = true;\n emit({\n state: \"seeking\",\n mediaTime: confirmedTime,\n message: \"Waiting for the requested frame to decode.\",\n });\n };\n const onSeeked = () => {\n if (!isCurrent() || failed) return;\n pendingSeek = false;\n waiting = false;\n ended = false;\n confirmFrame();\n };\n const onWaiting = () => {\n if (!isCurrent() || failed) return;\n waiting = true;\n emit({\n state: \"waiting\",\n mediaTime: confirmedTime,\n message: \"Recording is buffering; the last confirmed frame may be retained.\",\n });\n };\n const onCanPlay = () => {\n if (!isCurrent() || failed) return;\n waiting = false;\n playIfRequested();\n };\n const onEnded = () => {\n if (!isCurrent() || failed) return;\n clearSeekDeadline();\n ended = true;\n playRequested = false;\n emit({ state: \"ended\", mediaTime: confirmedTime, message: \"Recording ended.\" });\n };\n const onError = () => {\n if (isCurrent()) fail(video.error?.message || \"Recording could not be loaded or decoded.\");\n };\n const listeners: [string, EventListener][] = [\n [\"loadedmetadata\", onMetadata],\n [\"loadeddata\", onData],\n [\"seeking\", onSeeking],\n [\"seeked\", onSeeked],\n [\"timeupdate\", confirmFrame],\n [\"waiting\", onWaiting],\n [\"canplay\", onCanPlay],\n [\"playing\", onCanPlay],\n [\"ended\", onEnded],\n [\"error\", onError],\n ];\n for (const [event, listener] of listeners) video.addEventListener(event, listener);\n const session: VideoSession = {\n applyControls,\n capture: async () => {\n if (\n !isCurrent() ||\n frame?.state !== \"ready\" ||\n failed ||\n waiting ||\n ended ||\n pendingSeek ||\n video.seeking ||\n !matchesSource() ||\n !drawable() ||\n !synchronized()\n ) {\n throw new Error(\n \"A decoded, synchronized frame is required to capture. Wait for seeking or buffering to finish.\",\n );\n }\n const canvas = document.createElement(\"canvas\");\n canvas.width = video.videoWidth;\n canvas.height = video.videoHeight;\n const context = canvas.getContext(\"2d\");\n if (!context) throw new Error(\"Canvas capture is unavailable in this browser.\");\n const mediaTime = video.currentTime;\n context.drawImage(video, 0, 0, canvas.width, canvas.height);\n const image = canvas.toDataURL(\"image/png\");\n if (!image.startsWith(\"data:image/png;base64,\"))\n throw new Error(\"The browser could not encode the captured frame as PNG.\");\n return { src: image, mediaTime };\n },\n };\n sessionRef.current = session;\n emit({ state: \"loading\", mediaTime: null, message: \"Loading the selected recording.\" });\n if (src) {\n // Cleanup releases the decoder; StrictMode can then reuse the same node.\n video.setAttribute(\"src\", src);\n video.load();\n }\n applyControls();\n\n return () => {\n active = false;\n clearSeekDeadline();\n playAttempt += 1;\n for (const [event, listener] of listeners) video.removeEventListener(event, listener);\n video.pause();\n video.removeAttribute(\"src\");\n video.load();\n if (sessionRef.current === session) sessionRef.current = null;\n };\n }, [src]);\n\n useLayoutEffect(() => {\n sessionRef.current?.applyControls();\n }, [src, time, playing, playbackRate]);\n\n return (\n <video\n key={src ?? \"no-recording\"}\n ref={videoRef}\n src={src ?? undefined}\n muted\n playsInline\n preload=\"auto\"\n aria-label={label}\n className=\"h-full w-full object-contain\"\n />\n );\n },\n);\nSynchronizedVideo.displayName = \"SynchronizedVideo\";\n","patterns/facility-monitoring/training-media.ts":"/** Generated silent still-scene training recording. Embedded for portable copied recipes. */\nexport const facilityTrainingMedia =\n \"data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAABKObW9vdgAAAGxtdmhkAAAAAAAAAAAAAAAAAAAD6AAA/egAAQAAAQAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAEbl0cmFrAAAAXHRraGQAAAADAAAAAAAAAAAAAAABAAAAAAAA/egAAAAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAAAAA8AAAAIcAAAAAAAkZWR0cwAAABxlbHN0AAAAAAAAAAEAAP3oAAAQAAABAAAAABExbWRpYQAAACBtZGhkAAAAAAAAAAAAAAAAAABAAAAQQABVxAAAAAAALWhkbHIAAAAAAAAAAHZpZGUAAAAAAAAAAAAAAABWaWRlb0hhbmRsZXIAAAAQ3G1pbmYAAAAUdm1oZAAAAAEAAAAAAAAAAAAAACRkaW5mAAAAHGRyZWYAAAAAAAAAAQAAAAx1cmwgAAAAAQAAEJxzdGJsAAAArHN0c2QAAAAAAAAAAQAAAJxhdmMxAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAA8ACHABIAAAASAAAAAAAAAABFUxhdmM2Mi4xMS4xMDAgbGlieDI2NAAAAAAAAAAAAAAAGP//AAAAMmF2Y0MBTUAo/+EAGWdNQCi5AiB4CL9wgAAAAwCAAAAIB4wYwjABAAZo6EOUTIAAAAAUYnRydAAAAAAAAEI1AAAAAAAAABhzdHRzAAAAAAAAAAEAAAIIAAAIAAAAABRzdHNzAAAAAAAAAAEAAAABAAAHWGN0dHMAAAAAAAAA6QAAAAEAABAAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAUAAAAAABAAAgAAAAAAMAAAAAAAAABAAACAAAAAABAABQAAAAAAEAACAAAAAAAwAAAAAAAAAEAAAIAAAAAAEAAFAAAAAAAQAAIAAAAAADAAAAAAAAAAQAAAgAAAAAAQAAOAAAAAABAAAYAAAAAAIAAAAAAAAAAgAACAAAAAAcc3RzYwAAAAAAAAABAAAAAQAAAggAAAABAAAINHN0c3oAAAAAAAAAAAAAAggAAcy7AAABmAAAAFUAAABlAAAAaQAAAGoAAABXAAAATQAAACEAAAAtAAAAuQAAACkAAAAxAAAANAAAADYAAAA0AAAALgAAACoAAAAsAAAAXQAAAC8AAAAjAAAALAAAAC0AAAAyAAAALQAAACgAAAAhAAAAUQAAACwAAAAhAAAAJQAAADAAAAA7AAAANQAAACQAAAAiAAAATwAAACUAAAAlAAAAIwAAACQAAAAuAAAAKAAAACAAAAAqAAAARQAAACYAAAAgAAAAGwAAAB4AAAApAAAAJAAAABwAAAAjAAAAQAAAACQAAAAjAAAAHQAAACsAAAAuAAAAKwAAAB4AAAAhAAAARgAAACkAAAAiAAAAGwAAABsAAAApAAAAIgAAABwAAAAhAAAAQQAAACoAAAAfAAAAHgAAACAAAAAqAAAAJQAAABoAAAAhAAAARAAAAC8AAAAgAAAAHwAAACMAAAAlAAAAIwAAACIAAAAiAAAAOAAAACkAAAAhAAAAHQAAACUAAAA4AAAALAAAAB4AAAAhAAAASAAAAC0AAAAmAAAAJwAAAC4AAAA+AAAALQAAACQAAAAjAAAAQwAAAC0AAAAjAAAAHwAAAC8AAAA3AAAANQAAAB4AAAAnAAAAQgAAAC8AAAAjAAAAIQAAACQAAAAsAAAAKQAAABwAAAAjAAAAMwAAACsAAAAfAAAAIAAAACMAAAAlAAAAIwAAABsAAAAgAAAAMQAAACoAAAAeAAAAIAAAACEAAAAjAAAAIQAAABwAAAAfAAAAMwAAACoAAAAiAAAAIAAAACQAAAArAAAAJAAAABwAAAAhAAAAMwAAAC0AAAAgAAAAIAAAACQAAAAnAAAAIQAAABsAAAAiAAAAMwAAAC0AAAAjAAAAIAAAACIAAAArAAAAJAAAABsAAAAjAAAALwAAAC0AAAAfAAAAIgAAACMAAAAqAAAAJAAAABwAAAAgAAAALwAAAC0AAAAhAAAAJAAAACgAAAAvAAAAJwAAAB0AAAAiAAAALwAAAC8AAAAfAAAAJAAAACQAAAAlAAAAIwAAABwAAAAhAAAAMwAAACwAAAAiAAAAJQAAACUAAAAvAAAAJgAAABwAAAAiAAAALwAAACwAAAAfAAAAIQAAACIAAAAnAAAAIgAAABwAAAAgAAAALwAAAC0AAAAgAAAAIwAAACIAAAAqAAAAJAAAAB8AAAAjAAAALwAAAC4AAAAjAAAAIQAAACMAAAArAAAAJAAAABwAAAAiAAAALwAAAC4AAAAiAAAAIQAAACQAAAAuAAAAIgAAAB8AAAAlAAAAMgAAAC8AAAAiAAAAIgAAACIAAAAlAAAAIgAAAB4AAAAkAAAALwAAACsAAAAkAAAAIAAAACEAAAAoAAAAIQAAABwAAAAhAAAAMgAAACYAAAAkAAAAHAAAACcAAAAvAAAAJAAAABsAAAAlAAAANwAAACcAAAAfAAAAHAAAAB0AAAAiAAAAGwAAABsAAAAjAAAANwAAACoAAAAfAAAAHAAAABwAAAAfAAAAGwAAABwAAAAgAAAAMQAAACgAAAAhAAAAHAAAABwAAAAhAAAAHQAAABsAAAAhAAAAMgAAACgAAAAfAAAAHAAAABwAAAAfAAAAGwAAABsAAAAhAAAALwAAACgAAAAdAAAAHAAAABwAAAAeAAAAGwAAABwAAAAgAAAALwAAACcAAAAeAAAAHAAAABwAAAAeAAAAGwAAAB4AAAAgAAAALwAAACUAAAAeAAAAHAAAABwAAAAeAAAAGwAAAB4AAAAhAAAALwAAACYAAAAgAAAAHAAAABwAAAAfAAAAHgAAABwAAAAgAAAAMQAAACgAAAAdAAAAHAAAABwAAAAeAAAAHQAAABwAAAAgAAAALwAAACcAAAAgAAAAHAAAAB0AAAAnAAAAIwAAABwAAAAhAAAAMwAAACkAAAAhAAAAHgAAACMAAAAoAAAAIgAAAB4AAAAjAAAALwAAACsAAAAfAAAAHAAAAB0AAAAjAAAAHQAAABsAAAAhAAAAMQAAACgAAAAgAAAAHAAAAB8AAAAlAAAAHQAAABsAAAAgAAAALwAAACcAAAAfAAAAHAAAABwAAAAgAAAAHQAAABsAAAAgAAAALwAAACgAAAAgAAAAHAAAAB0AAAAnAAAAHQAAABsAAAAjAAAALwAAACkAAAAeAAAAHAAAABwAAAAfAAAAGwAAABsAAAAiAAAALwAAACgAAAAdAAAAHAAAABwAAAAeAAAAGwAAABwAAAAgAAAALwAAACcAAAAgAAAAHAAAAB4AAAAnAAAAHwAAAB0AAAAhAAAALwAAACoAAAAdAAAAHAAAABwAAAAfAAAAGwAAABsAAAAhAAAAMQAAACgAAAAeAAAAHAAAABwAAAAgAAAAGwAAABsAAAAhAAAALwAAACgAAAAdAAAAHAAAABwAAAAeAAAAGwAAABwAAAAgAAAALwAAACcAAAAgAAAAHAAAAB4AAAAnAAAAHwAAABsAAAAhAAAAPgAAACwAAAAjAAAAHAAAACMAAAAsAAAAIwAAABwAAAAkAAAA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\";\n","patterns/facility-monitoring/types.ts":"import type { TimelineEvent, TimeRangeValue } from \"@oprt/react\";\nimport type { GeoEntity, GeoPoint, GeoSiteFeature } from \"../geospatial-workspace/types\";\n\nexport interface FacilityReviewState {\n mode: \"live\" | \"review\";\n range: { start: number; end: number };\n cursor: number;\n playing: boolean;\n playbackRate: number;\n}\n\nexport interface FacilityRecording {\n id: string;\n /** Inclusive absolute start and exclusive absolute end, in milliseconds. */\n start: number;\n end: number;\n src: string;\n /** Position in the media at start, in seconds. */\n mediaOffset: number;\n}\n\nexport interface FacilityCamera {\n id: string;\n name: string;\n equipmentId: string;\n position: GeoPoint | null;\n connection: \"connected\" | \"reconnecting\" | \"unavailable\";\n latencyMs: number | null;\n source: string;\n recordings: readonly FacilityRecording[];\n canControl: boolean;\n controlReason?: string;\n}\n\nexport interface FacilityEquipment {\n id: string;\n name: string;\n history: readonly { at: number; entity: GeoEntity; pressure: number | null }[];\n}\n\nexport interface FacilityEvent extends TimelineEvent {\n equipmentId?: string;\n cameraId?: string;\n}\n\nexport interface FacilitySnapshot {\n id: string;\n title?: string;\n source: string;\n range: TimeRangeValue;\n liveTime: number;\n equipment: readonly FacilityEquipment[];\n cameras: readonly FacilityCamera[];\n events: readonly FacilityEvent[];\n site: readonly GeoSiteFeature[];\n}\n\nexport interface FacilityBookmark {\n id: string;\n name: string;\n cursor: number;\n cameraId: string | null;\n equipmentId: string | null;\n sourceKey: string;\n}\n\nexport interface CameraCommandRequest {\n cameraId: string;\n command: \"reset-view\";\n requestedAt: number;\n}\n\nexport interface CameraCommandResult {\n state: \"accepted\" | \"rejected\" | \"failed\" | \"complete\";\n message: string;\n requestId?: string;\n}\n\nexport interface FacilityMonitoringProps {\n snapshot: FacilitySnapshot;\n review: FacilityReviewState;\n onReviewChange: (review: FacilityReviewState) => void;\n onCameraCommand?: (request: CameraCommandRequest) => Promise<CameraCommandResult>;\n}\n","patterns/geospatial-workspace/demo.tsx":"\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { Button, Checkbox, Field, Select, SelectOption } from \"@oprt/react\";\nimport type { OperationalDomain } from \"../operational-domain\";\nimport { militaryGeoSnapshot } from \"./military-fixture\";\nimport { GeospatialWorkspace } from \"./geospatial-workspace\";\nimport { fixtureObservedAt, initialGeoLayers, initialGeoView, utilitySnapshot } from \"./fixtures\";\nimport type {\n GeoAnnotation,\n GeoAnnotationDraft,\n GeoAnnotationResult,\n GeoBookmark,\n GeoSnapshot,\n} from \"./types\";\n\n/** Camp Meridian base-support reports with local annotations and bookmarks. */\nexport function OperationsMapDemo() {\n return <GeospatialScenario domain=\"military\" />;\n}\n\n/** Internal compatibility host for the industrial regression fixture. */\nexport function GeospatialWorkspaceDemo({\n initialDomain = \"military\",\n}: { initialDomain?: OperationalDomain } = {}) {\n return <GeospatialScenario key={initialDomain} domain={initialDomain} />;\n}\n\nfunction GeospatialScenario({ domain }: { domain: OperationalDomain }) {\n const initialSnapshot = domain === \"military\" ? militaryGeoSnapshot : utilitySnapshot;\n const [snapshot, setSnapshot] = useState<GeoSnapshot>(initialSnapshot);\n const [selectedId, setSelectedId] = useState<string | null>(\n initialSnapshot.entities[6]!.record.id,\n );\n const [annotations, setAnnotations] = useState<readonly GeoAnnotation[]>([]);\n const [bookmarks, setBookmarks] = useState<readonly GeoBookmark[]>([\n {\n id: \"site-overview\",\n name: domain === \"military\" ? \"Camp overview\" : \"Utility district\",\n view: initialGeoView,\n layers: initialGeoLayers,\n },\n ]);\n const [rejectAnnotation, setRejectAnnotation] = useState(false);\n const [allowAnnotations, setAllowAnnotations] = useState(true);\n const [missingTiles, setMissingTiles] = useState(false);\n const [notice, setNotice] = useState(\"\");\n const sequence = useRef(1);\n const mounted = useRef(true);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n };\n }, []);\n const current = useRef({ snapshot, rejectAnnotation, allowAnnotations });\n current.current = { snapshot, rejectAnnotation, allowAnnotations };\n async function createAnnotation(draft: GeoAnnotationDraft): Promise<GeoAnnotationResult> {\n const requestedSource = current.current.snapshot;\n await Promise.resolve();\n if (!mounted.current)\n return { status: \"failed\", message: \"The annotation host is no longer available.\" };\n const state = current.current;\n if (\n state.snapshot.id !== requestedSource.id ||\n state.snapshot.revision !== requestedSource.revision\n )\n return {\n status: \"failed\",\n message: \"The reports changed while saving. Review the retained draft and try again.\",\n };\n if (draft.sourceId !== state.snapshot.id)\n return {\n status: \"failed\",\n message: \"The source changed. Review the annotation against the current source.\",\n };\n if (!state.allowAnnotations)\n return { status: \"failed\", message: \"Annotation changes are unavailable for this session.\" };\n if (state.rejectAnnotation)\n return {\n status: \"failed\",\n message:\n \"The simulated source rejected the annotation. Your draft is retained; clear Reject annotation in Simulation controls and try again.\",\n };\n const annotation: GeoAnnotation = {\n ...draft,\n id: `note-${++sequence.current}`,\n createdAt: fixtureObservedAt,\n };\n setAnnotations((items) => [...items, annotation]);\n return { status: \"saved\", annotation };\n }\n return (\n <div className=\"not-prose flex min-w-0 flex-col gap-3u\" data-testid=\"operations-map-demo\">\n <GeospatialWorkspace\n snapshot={snapshot}\n selectedId={selectedId}\n onSelectedIdChange={setSelectedId}\n annotations={annotations}\n {...(allowAnnotations\n ? {\n onCreateAnnotation: createAnnotation,\n onRemoveAnnotation: (id: string) =>\n setAnnotations((items) => items.filter((item) => item.id !== id)),\n }\n : {})}\n bookmarks={bookmarks}\n onBookmarksChange={setBookmarks}\n initialView={initialGeoView}\n {...(missingTiles\n ? {\n tileSource: {\n url: \"/geospatial-missing/{z}/{x}/{y}.png\",\n attribution: \"Local missing-tile fixture\",\n },\n }\n : {})}\n />\n <details className=\"border-t border-oprt-border pt-2u text-oprt-text-dim\">\n <summary className=\"min-h-[var(--oprt-height-sm)] cursor-pointer py-1u text-oprt-base font-medium focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-oprt-border-focus\">\n Simulation controls\n </summary>\n <div className=\"pt-2u\">\n <div className=\"flex min-w-0 flex-wrap items-end gap-2u\">\n <Field label=\"Source availability\">\n <Select\n value={snapshot.availability}\n onValueChange={(value) =>\n setSnapshot((currentSnapshot) => ({\n ...currentSnapshot,\n revision: `snapshot-${++sequence.current}`,\n availability: value as GeoSnapshot[\"availability\"],\n detail:\n value === \"current\"\n ? \"Complete supplied report collection.\"\n : value === \"partial\"\n ? \"The western sector report is incomplete; some records may be missing.\"\n : value === \"offline\"\n ? \"Offline snapshot retained from 12:00 UTC; no current source connection.\"\n : \"The source is unavailable; retained exercise records are shown.\",\n }))\n }\n >\n <SelectOption value=\"current\">Current</SelectOption>\n <SelectOption value=\"partial\">Partial report</SelectOption>\n <SelectOption value=\"offline\">Offline snapshot</SelectOption>\n <SelectOption value=\"unavailable\">Unavailable source</SelectOption>\n </Select>\n </Field>\n <Field label=\"Allow annotations\">\n <Checkbox\n checked={allowAnnotations}\n onCheckedChange={(value) => setAllowAnnotations(value === true)}\n />\n </Field>\n <Field label=\"Reject annotation\">\n <Checkbox\n checked={rejectAnnotation}\n onCheckedChange={(value) => setRejectAnnotation(value === true)}\n />\n </Field>\n <Field label=\"Missing tiles\">\n <Checkbox\n checked={missingTiles}\n onCheckedChange={(value) => setMissingTiles(value === true)}\n />\n </Field>\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n disabled={\n !selectedId || !snapshot.entities.some((item) => item.record.id === selectedId)\n }\n onClick={() => {\n setSnapshot((value) => ({\n ...value,\n revision: `snapshot-${++sequence.current}`,\n entities: value.entities.filter((item) => item.record.id !== selectedId),\n }));\n setNotice(\n \"The selected report was removed from the supplied snapshot. Its identity remains selected.\",\n );\n }}\n >\n Remove selected report\n </Button>\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n onClick={() => {\n setSnapshot({ ...initialSnapshot, revision: `snapshot-${++sequence.current}` });\n setNotice(\"The supplied reports were restored.\");\n }}\n >\n Restore reports\n </Button>\n </div>\n </div>\n </details>\n <p role=\"status\" className=\"m-0 text-oprt-base text-oprt-text-dim\">\n {notice}\n </p>\n </div>\n );\n}\n","patterns/geospatial-workspace/engine-contract.ts":"import type { GeoFrame, GeoMapProps, GeoPoint, GeoView } from \"./types\";\n\nexport type EngineState = Pick<\n GeoMapProps,\n | \"snapshot\"\n | \"entities\"\n | \"selectedId\"\n | \"view\"\n | \"layers\"\n | \"annotations\"\n | \"measurement\"\n | \"mode\"\n | \"tileSource\"\n>;\nexport type TileState = \"loading\" | \"ready\" | \"error\";\nexport interface EngineCallbacks {\n onFrame: (frame: GeoFrame) => void;\n onViewChange: (view: GeoView) => void;\n onPoint: (point: GeoPoint) => void;\n onTileState: (state: TileState) => void;\n}\nexport interface GeoEngine {\n update: (state: EngineState, callbacks: EngineCallbacks) => void;\n dispose: () => void;\n}\n","patterns/geospatial-workspace/engine.ts":"import Map from \"ol/Map.js\";\nimport View from \"ol/View.js\";\nimport Feature from \"ol/Feature.js\";\nimport Point from \"ol/geom/Point.js\";\nimport LineString from \"ol/geom/LineString.js\";\nimport Polygon from \"ol/geom/Polygon.js\";\nimport Geometry from \"ol/geom/Geometry.js\";\nimport VectorLayer from \"ol/layer/Vector.js\";\nimport TileLayer from \"ol/layer/Tile.js\";\nimport VectorSource from \"ol/source/Vector.js\";\nimport Cluster from \"ol/source/Cluster.js\";\nimport XYZ from \"ol/source/XYZ.js\";\nimport Style from \"ol/style/Style.js\";\nimport Fill from \"ol/style/Fill.js\";\nimport Stroke from \"ol/style/Stroke.js\";\nimport CircleStyle from \"ol/style/Circle.js\";\nimport Text from \"ol/style/Text.js\";\nimport { defaults as interactions } from \"ol/interaction/defaults.js\";\nimport KeyboardPan from \"ol/interaction/KeyboardPan.js\";\nimport KeyboardZoom from \"ol/interaction/KeyboardZoom.js\";\nimport { fromLonLat, getPointResolution, toLonLat } from \"ol/proj.js\";\nimport { unByKey } from \"ol/Observable.js\";\nimport type { EventsKey } from \"ol/events.js\";\nimport type { Coordinate } from \"ol/coordinate.js\";\nimport type { StyleLike } from \"ol/style/Style.js\";\nimport type { EngineCallbacks, EngineState, GeoEngine } from \"./engine-contract\";\nimport { createFeatureTracker, unwrapLongitude, type FeatureData } from \"./feature-data\";\nimport { isGeoPoint, normalizeGeoView, normalizeLongitude, sameGeoFrame } from \"./geometry\";\nimport type { GeoFrame, GeoPoint, GeoView } from \"./types\";\n\nconst layerIds = [\n \"site\",\n \"coverage\",\n \"uncertainty\",\n \"tracks\",\n \"entities\",\n \"measurement\",\n \"annotations\",\n] as const;\ntype LayerId = (typeof layerIds)[number];\nconst radian = Math.PI / 180;\nconst publicPoint = (coordinate: Coordinate): GeoPoint => {\n const point = toLonLat(coordinate);\n return [normalizeLongitude(point[0]!), point[1]!];\n};\nconst near = (a: number, b: number) => Math.abs(a - b) < 0.0000001;\nconst sameView = (a: GeoView, b: GeoView) =>\n near(a.center[0], b.center[0]) &&\n near(a.center[1], b.center[1]) &&\n near(a.zoom, b.zoom) &&\n near(a.bearing, b.bearing);\n\n/** Loaded only by GeoMap's enabled client effect. It never creates a remote source by default. */\nexport function createGeoEngine(\n target: HTMLElement,\n initial: EngineState,\n initialCallbacks: EngineCallbacks,\n): GeoEngine {\n let state = initial,\n callbacks = initialCallbacks,\n disposed = false;\n let pendingFrame = 0,\n lastFrame: GeoFrame | null = null,\n tileKey = \"\",\n tileFailed = false,\n tileLoads = 0;\n const keys: EventsKey[] = [],\n tileKeys: EventsKey[] = [];\n const features = createFeatureTracker();\n const theme = getComputedStyle(target);\n const token = (name: string) => theme.getPropertyValue(`--oprt-${name}`).trim();\n const forced = window.matchMedia(\"(forced-colors: active)\").matches;\n const colors = {\n text: forced ? \"CanvasText\" : token(\"text\"),\n muted: forced ? \"CanvasText\" : token(\"text-dim\"),\n surface: forced ? \"Canvas\" : token(\"surface-2\"),\n line: forced ? \"CanvasText\" : token(\"border-strong\"),\n accent: forced ? \"Highlight\" : token(\"accent\"),\n info: forced ? \"CanvasText\" : token(\"info\"),\n warning: forced ? \"CanvasText\" : token(\"warning\"),\n water: forced ? \"Canvas\" : token(\"info-subtle\"),\n };\n if (Object.values(colors).some((value) => !value))\n throw new Error(\"OPERATOR theme tokens are unavailable.\");\n const lineWidth = parseFloat(token(\"chart-stroke-width\"));\n const pointRadius = parseFloat(token(\"space-0-5\"));\n if (![lineWidth, pointRadius].every((value) => Number.isFinite(value) && value > 0))\n throw new Error(\"OPERATOR geometry tokens are unavailable.\");\n const font = `${token(\"weight-sm\")} ${token(\"type-base\")} ${token(\"font-sans\")}`;\n const stroke = (color: string, dashed = false) =>\n new Stroke({\n color,\n width: lineWidth,\n ...(dashed ? { lineDash: [pointRadius, pointRadius] } : {}),\n });\n const pointStyle = new Style({\n image: new CircleStyle({\n radius: pointRadius,\n stroke: stroke(colors.accent),\n fill: new Fill({ color: colors.surface }),\n }),\n });\n // Styles are built once. OpenLayers asks for a style per feature on every render, so a function\n // that built new objects would allocate a Stroke or Fill for each feature on each frame of a pan.\n const coverageStyle = new Style({ stroke: stroke(colors.info, true) });\n const uncertaintyStyle = new Style({ stroke: stroke(colors.warning, true) });\n const markStyle = (color: string) =>\n new Style({\n stroke: stroke(color),\n image: new CircleStyle({ radius: pointRadius, fill: new Fill({ color: colors.warning }) }),\n });\n const tracksStyle = markStyle(colors.accent);\n const measurementStyle = markStyle(colors.warning);\n // Site and annotation styles depend on the feature's kind and label (and on the zoom, for the\n // site's labels), so they are kept by those values and shared between features.\n const siteStyles = new globalThis.Map<string, Style>();\n const annotationStyles = new globalThis.Map<string, Style>();\n const siteStyle = (kind: string, label: string, labeled: boolean) => {\n const key = labeled ? `${kind}\\n${label}` : kind;\n let style = siteStyles.get(key);\n if (!style) {\n style = new Style({\n stroke: stroke(colors.line, kind === \"boundary\"),\n fill: new Fill({ color: kind === \"water\" ? colors.water : colors.surface }),\n ...(labeled\n ? {\n text: new Text({\n text: label,\n font,\n placement: kind === \"road\" ? \"line\" : \"point\",\n fill: new Fill({ color: colors.muted }),\n stroke: new Stroke({ color: colors.surface, width: lineWidth * 3 }),\n }),\n }\n : {}),\n });\n siteStyles.set(key, style);\n }\n return style;\n };\n const annotationStyle = (label: string) => {\n let style = annotationStyles.get(label);\n if (!style) {\n style = new Style({\n image: new CircleStyle({\n radius: pointRadius,\n fill: new Fill({ color: colors.warning }),\n stroke: stroke(colors.text),\n }),\n text: new Text({\n text: label,\n font,\n offsetY: -pointRadius * 3,\n fill: new Fill({ color: colors.text }),\n stroke: new Stroke({ color: colors.surface, width: lineWidth * 3 }),\n }),\n });\n annotationStyles.set(label, style);\n }\n return style;\n };\n const sources = Object.fromEntries(\n layerIds.map((id) => [id, new VectorSource<Feature<Geometry>>({ wrapX: false })]),\n ) as Record<LayerId, VectorSource<Feature<Geometry>>>;\n const clusters = new Cluster({\n source: sources.entities,\n distance: 40,\n minDistance: 0,\n wrapX: false,\n });\n const layerStyles: Record<LayerId, StyleLike> = {\n entities: pointStyle,\n coverage: coverageStyle,\n uncertainty: uncertaintyStyle,\n tracks: tracksStyle,\n measurement: measurementStyle,\n site: (feature) =>\n siteStyle(\n feature.get(\"kind\") as string,\n feature.get(\"label\") as string,\n view.getZoom()! >= 16,\n ),\n annotations: (feature) => annotationStyle(feature.get(\"label\") as string),\n };\n const layers = Object.fromEntries(\n layerIds.map((id) => [\n id,\n new VectorLayer({\n source: id === \"entities\" ? clusters : sources[id],\n declutter: id === \"site\" || id === \"annotations\" ? id : false,\n style: layerStyles[id],\n }),\n ]),\n ) as Record<LayerId, VectorLayer>;\n const tileLayer = new TileLayer({ zIndex: -1 });\n const normalized = normalizeGeoView(initial.view);\n const view = new View({\n center: fromLonLat([...normalized.center]),\n zoom: normalized.zoom,\n rotation: normalized.bearing * radian,\n minZoom: 2,\n maxZoom: 20,\n constrainResolution: false,\n multiWorld: true,\n });\n const keyboardTarget = target.parentElement ?? target;\n const keyboardCondition = (event: import(\"ol/MapBrowserEvent.js\").default) =>\n event.originalEvent.target === keyboardTarget;\n const controls = interactions({\n keyboard: false,\n altShiftDragRotate: false,\n pinchRotate: false,\n zoomDuration: 0,\n });\n controls.extend([\n new KeyboardPan({ condition: keyboardCondition, duration: 0 }),\n new KeyboardZoom({ condition: keyboardCondition, duration: 0 }),\n ]);\n const map = new Map({\n target,\n view,\n layers: [tileLayer, ...layerIds.map((id) => layers[id])],\n controls: [],\n interactions: controls,\n keyboardEventTarget: keyboardTarget,\n });\n const actualView = (): GeoView =>\n normalizeGeoView({\n center: publicPoint(view.getCenter() ?? [0, 0]),\n zoom: view.getZoom() ?? 2,\n bearing: view.getRotation() / radian,\n });\n const worldLongitude = () => {\n const extent = view.getProjection().getExtent();\n return ((view.getCenter()?.[0] ?? 0) * 360) / (extent[2]! - extent[0]!);\n };\n const project = (point: GeoPoint, center: number) =>\n fromLonLat([unwrapLongitude(point[0], center), point[1]]);\n const lineCoordinates = (points: readonly GeoPoint[], center: number): Coordinate[] => {\n let previous = center;\n return points.map((point) => {\n const longitude = previous + normalizeLongitude(point[0] - previous);\n previous = longitude;\n return fromLonLat([longitude, point[1]]);\n });\n };\n const polygon = (points: readonly GeoPoint[], center: number) => {\n const ring = lineCoordinates(points, center);\n ring.push([...ring[0]!]);\n return new Polygon([ring]);\n };\n\n function emitFrame() {\n pendingFrame = 0;\n if (disposed) return;\n const size = map.getSize();\n if (!size || size[0]! <= 0 || size[1]! <= 0) return;\n const inViewport = (pixel: Coordinate | null): pixel is Coordinate =>\n !!pixel &&\n pixel.every(Number.isFinite) &&\n pixel[0]! >= 0 &&\n pixel[1]! >= 0 &&\n pixel[0]! <= size[0]! &&\n pixel[1]! <= size[1]!;\n const visibleIds: string[] = [];\n let selectedPixel: readonly [number, number] | null = null;\n for (const feature of sources.entities.getFeatures()) {\n const coordinate = (feature.getGeometry() as Point).getCoordinates();\n const pixel = map.getPixelFromCoordinate(coordinate);\n if (!inViewport(pixel)) continue;\n const id = feature.get(\"entityId\") as string;\n visibleIds.push(id);\n if (\n id === state.selectedId &&\n layers.entities.getVisible() &&\n layers.entities.getOpacity() > 0\n )\n selectedPixel = [pixel[0]!, pixel[1]!];\n }\n const groupIds = new Set<string>();\n const groups =\n layers.entities.getVisible() && layers.entities.getOpacity() > 0\n ? clusters.getFeatures().flatMap((feature) => {\n const coordinate = (feature.getGeometry() as Point).getCoordinates();\n const pixel = map.getPixelFromCoordinate(coordinate);\n if (!inViewport(pixel)) return [];\n const ids = (feature.get(\"features\") as Feature[])\n .map((child) => child.get(\"entityId\") as string)\n .sort();\n let hash = 2166136261;\n for (const character of JSON.stringify(ids)) {\n hash ^= character.charCodeAt(0);\n hash = Math.imul(hash, 16777619);\n }\n const baseId = `equipment:${ids.length}:${(hash >>> 0).toString(36)}`;\n let id = baseId,\n suffix = 0;\n while (groupIds.has(id)) id = `${baseId}:${++suffix}`;\n groupIds.add(id);\n return [\n { id, ids, point: publicPoint(coordinate), pixel: [pixel[0]!, pixel[1]!] as const },\n ];\n })\n : [];\n const center = view.getCenter(),\n resolution = view.getResolution();\n const metersPerPixel =\n center && resolution\n ? getPointResolution(view.getProjection(), resolution, center, \"m\")\n : null;\n const frame = {\n view: actualView(),\n visibleIds: visibleIds.sort(),\n groups,\n selectedPixel,\n metersPerPixel,\n };\n if (!sameGeoFrame(lastFrame, frame)) {\n lastFrame = frame;\n callbacks.onFrame(frame);\n }\n }\n const scheduleFrame = () => {\n if (!disposed && !pendingFrame) pendingFrame = requestAnimationFrame(emitFrame);\n };\n function updateVectors(data: FeatureData) {\n // Reproject at the current longitude so dateline neighbors remain adjacent.\n const center = worldLongitude();\n for (const source of Object.values(sources)) source.clear(true);\n siteStyles.clear();\n annotationStyles.clear();\n const batches = Object.fromEntries(\n layerIds.map((id) => [id, [] as Feature<Geometry>[]]),\n ) as Record<LayerId, Feature<Geometry>[]>;\n for (const spec of data.specs) {\n const geometry =\n spec.shape === \"point\"\n ? new Point(project(spec.points[0]!, center))\n : spec.shape === \"line\"\n ? new LineString(lineCoordinates(spec.points, center))\n : polygon(spec.points, center);\n batches[spec.layer].push(new Feature({ geometry, ...spec.properties }));\n }\n for (const id of layerIds) sources[id].addFeatures(batches[id]);\n }\n function updateTiles() {\n const next = JSON.stringify(state.tileSource ?? null);\n if (next === tileKey) return;\n tileKey = next;\n unByKey(tileKeys.splice(0));\n tileLayer.getSource()?.dispose();\n tileLayer.setSource(null);\n tileFailed = false;\n tileLoads = 0;\n if (!state.tileSource) return;\n try {\n const url = new URL(state.tileSource.url, window.location.href);\n if (![\"http:\", \"https:\"].includes(url.protocol) || !state.tileSource.attribution.trim())\n throw new Error(\"Invalid tile source\");\n const source = new XYZ({ url: state.tileSource.url, crossOrigin: \"anonymous\", wrapX: true });\n tileKeys.push(\n source.on(\"tileloadstart\", () => {\n if (!disposed) {\n tileLoads++;\n if (!tileFailed) callbacks.onTileState(\"loading\");\n }\n }),\n source.on(\"tileloadend\", () => {\n if (!disposed) {\n tileLoads = Math.max(0, tileLoads - 1);\n if (!tileFailed && tileLoads === 0) callbacks.onTileState(\"ready\");\n }\n }),\n source.on(\"tileloaderror\", () => {\n if (!disposed) {\n tileLoads = Math.max(0, tileLoads - 1);\n tileFailed = true;\n callbacks.onTileState(\"error\");\n }\n }),\n );\n tileLayer.setSource(source);\n callbacks.onTileState(\"loading\");\n } catch {\n callbacks.onTileState(\"error\");\n }\n }\n function update(next: EngineState, nextCallbacks: EngineCallbacks) {\n if (disposed) return;\n state = next;\n callbacks = nextCallbacks;\n // The new callbacks have not been given a frame yet. One that the previous callbacks were\n // refused (GeoMap ignores a callback superseded by this update) is delivered again.\n lastFrame = null;\n const requested = normalizeGeoView(state.view);\n if (!sameView(actualView(), requested)) {\n view.cancelAnimations();\n view.setCenter(project(requested.center, requested.center[0]));\n view.setZoom(requested.zoom);\n view.setRotation(requested.bearing * radian);\n }\n // Features are redrawn when their inputs change, or when the view center crosses the seam at\n // which one of them is drawn on another copy of the world. A pan inside a seam changes neither.\n const redraw = features.sync(state, worldLongitude());\n if (redraw) updateVectors(redraw);\n for (const id of layerIds) {\n const index = state.layers.findIndex((layer) => layer.id === id),\n supplied = state.layers[index];\n const validOpacity =\n supplied &&\n Number.isFinite(supplied.opacity) &&\n supplied.opacity >= 0 &&\n supplied.opacity <= 1;\n layers[id].setVisible(!!supplied?.visible && !!validOpacity);\n layers[id].setOpacity(validOpacity ? supplied.opacity : 0);\n layers[id].setZIndex(index < 0 ? 0 : state.layers.length - index);\n }\n updateTiles();\n map.updateSize();\n map.render();\n scheduleFrame();\n }\n keys.push(\n map.on(\"postrender\", scheduleFrame),\n map.on(\"moveend\", () => {\n if (disposed) return;\n const next = actualView();\n if (!sameView(next, normalizeGeoView(state.view))) callbacks.onViewChange(next);\n scheduleFrame();\n }),\n map.on(\"singleclick\", (event) => {\n if (disposed || state.mode === \"inspect\") return;\n const point = publicPoint(event.coordinate);\n if (isGeoPoint(point)) callbacks.onPoint(point);\n }),\n );\n const observer = new ResizeObserver(() => {\n if (!disposed) {\n map.updateSize();\n scheduleFrame();\n }\n });\n observer.observe(target);\n const dispose = () => {\n if (disposed) return;\n disposed = true;\n observer.disconnect();\n if (pendingFrame) cancelAnimationFrame(pendingFrame);\n unByKey(keys);\n unByKey(tileKeys);\n clusters.setSource(null);\n map.setTarget(undefined);\n map.dispose();\n clusters.dispose();\n tileLayer.getSource()?.dispose();\n tileLayer.dispose();\n for (const id of layerIds) {\n sources[id].dispose();\n layers[id].dispose();\n }\n };\n try {\n update(initial, initialCallbacks);\n } catch (error) {\n dispose();\n throw error;\n }\n return { update, dispose };\n}\n","patterns/geospatial-workspace/feature-data.ts":"import type { EngineState } from \"./engine-contract\";\nimport { getUncertaintyFootprint, isGeoPoint, normalizeLongitude } from \"./geometry\";\nimport type { GeoPoint } from \"./types\";\n\n/** What the map draws and from where. Pure data: no OpenLayers, so it is unit-testable. */\nexport type FeatureLayer =\n | \"site\"\n | \"coverage\"\n | \"uncertainty\"\n | \"tracks\"\n | \"entities\"\n | \"measurement\"\n | \"annotations\";\nexport interface FeatureSpec {\n layer: FeatureLayer;\n shape: \"point\" | \"line\" | \"polygon\";\n /** A point has one position. A line or polygon ring is unwrapped from its first vertex. */\n points: readonly GeoPoint[];\n properties?: Readonly<Record<string, unknown>>;\n}\nexport interface FeatureData {\n /** In draw order: entities by ID, then site, annotations and measurement. */\n specs: readonly FeatureSpec[];\n /** The longitude each spec is wrapped from, one per spec. */\n anchors: Float64Array;\n}\ntype FeatureInputs = Pick<EngineState, \"snapshot\" | \"entities\" | \"annotations\" | \"measurement\">;\n\nconst compareIds = new Intl.Collator().compare;\n\n/** The copy of a longitude that is nearest the view center, so shapes stay adjacent across ±180°. */\nexport const unwrapLongitude = (longitude: number, center: number) =>\n center + normalizeLongitude(longitude - center);\n\n/**\n * How many whole turns separate a longitude from its nearest copy to the center. It is an integer\n * that never decreases as the center moves east, and changes at exactly the center where\n * {@link unwrapLongitude} switches to another copy (it uses the same arithmetic).\n */\nconst turns = (longitude: number, center: number) =>\n Math.round((unwrapLongitude(longitude, center) - longitude) / 360);\n\n/**\n * Sums {@link turns} over every anchor. Each term only grows as the center moves east, so the sum\n * is the same for two centers exactly when no feature is drawn on another copy of the world. A\n * pan inside one seam leaves it alone; crossing a feature's seam changes it.\n */\nexport function seamSignature(anchors: Float64Array, center: number): number {\n let sum = 0;\n for (let index = 0; index < anchors.length; index++) sum += turns(anchors[index]!, center);\n return sum;\n}\n\n/** Everything the supplied inputs draw, in the order the previous renderer added it. */\nexport function buildFeatureData(input: FeatureInputs): FeatureData {\n const specs: FeatureSpec[] = [];\n const entities = [...input.entities].sort((a, b) => compareIds(a.record.id, b.record.id));\n for (const entity of entities) {\n if (isGeoPoint(entity.position)) {\n specs.push({\n layer: \"entities\",\n shape: \"point\",\n points: [entity.position],\n properties: { entityId: entity.record.id },\n });\n if (entity.uncertaintyMeters !== undefined) {\n const footprint = getUncertaintyFootprint(entity.position, entity.uncertaintyMeters);\n if (footprint?.length)\n specs.push({ layer: \"uncertainty\", shape: \"polygon\", points: footprint });\n }\n }\n if (entity.coverage && entity.coverage.length >= 3 && entity.coverage.every(isGeoPoint))\n specs.push({ layer: \"coverage\", shape: \"polygon\", points: entity.coverage });\n if (\n entity.track &&\n entity.track.length >= 2 &&\n entity.track.every((sample) => isGeoPoint(sample.position))\n )\n specs.push({\n layer: \"tracks\",\n shape: \"line\",\n points: entity.track.map((sample) => sample.position),\n });\n }\n for (const feature of input.snapshot.site) {\n if (\n !feature.points.every(isGeoPoint) ||\n feature.points.length < (feature.kind === \"road\" ? 2 : 3)\n )\n continue;\n specs.push({\n layer: \"site\",\n shape: feature.kind === \"road\" ? \"line\" : \"polygon\",\n points: feature.points,\n properties: { kind: feature.kind, label: feature.label },\n });\n }\n for (const annotation of input.annotations)\n if (annotation.sourceId === input.snapshot.id && isGeoPoint(annotation.position))\n specs.push({\n layer: \"annotations\",\n shape: \"point\",\n points: [annotation.position],\n properties: { label: annotation.label },\n });\n if (input.measurement.length && input.measurement.every(isGeoPoint)) {\n for (const point of input.measurement)\n specs.push({ layer: \"measurement\", shape: \"point\", points: [point] });\n if (input.measurement.length >= 2)\n specs.push({ layer: \"measurement\", shape: \"line\", points: input.measurement });\n }\n const anchors = new Float64Array(specs.length);\n specs.forEach((spec, index) => {\n anchors[index] = spec.points[0]![0];\n });\n return { specs, anchors };\n}\n\n/**\n * Decides when the drawn features are out of date. The inputs are compared by identity (a host\n * that changes a collection supplies a new one), the view only through {@link seamSignature}. It\n * answers with the data to draw, or null when the features on the map are still right.\n */\nexport function createFeatureTracker() {\n let inputs: FeatureInputs | null = null;\n let data: FeatureData | null = null;\n let signature = NaN;\n return {\n sync(next: FeatureInputs, center: number): FeatureData | null {\n const sameInputs =\n inputs !== null &&\n data !== null &&\n inputs.snapshot.id === next.snapshot.id &&\n inputs.snapshot.revision === next.snapshot.revision &&\n inputs.snapshot.site === next.snapshot.site &&\n inputs.entities === next.entities &&\n inputs.annotations === next.annotations &&\n inputs.measurement === next.measurement;\n if (!sameInputs || data === null) {\n data = buildFeatureData(next);\n inputs = {\n snapshot: next.snapshot,\n entities: next.entities,\n annotations: next.annotations,\n measurement: next.measurement,\n };\n signature = seamSignature(data.anchors, center);\n return data;\n }\n const seams = seamSignature(data.anchors, center);\n if (seams === signature) return null;\n signature = seams;\n return data;\n },\n };\n}\n","patterns/geospatial-workspace/fixtures.ts":"import type { LayerControlItem } from \"@oprt/react\";\nimport type { GeoEntity, GeoPoint, GeoSiteFeature, GeoSnapshot, GeoView } from \"./types\";\n\nexport const initialGeoView: GeoView = { center: [-122.3, 37.8], zoom: 16, bearing: 0 };\nexport const initialGeoLayers: readonly LayerControlItem[] = [\n { id: \"annotations\", label: \"Annotations\", visible: true, opacity: 1 },\n { id: \"measurement\", label: \"Measurement\", visible: true, opacity: 1 },\n { id: \"entities\", label: \"Equipment\", visible: true, opacity: 1 },\n { id: \"uncertainty\", label: \"Position uncertainty\", visible: true, opacity: 0.65 },\n { id: \"tracks\", label: \"Last-known tracks\", visible: true, opacity: 0.8 },\n { id: \"coverage\", label: \"Reported coverage\", visible: true, opacity: 0.4 },\n { id: \"site\", label: \"Local site plan\", visible: true, opacity: 1 },\n];\nexport const fixtureObservedAt = \"2026-09-30T12:00:00Z\";\nconst point = (x: number, y: number): GeoPoint => [-122.3 + x, 37.8 + y];\nfunction rectangle(x: number, y: number, width: number, height: number): readonly GeoPoint[] {\n return [\n point(x, y),\n point(x + width, y),\n point(x + width, y + height),\n point(x, y + height),\n point(x, y),\n ];\n}\nconst buildings: GeoSiteFeature[] = [\n {\n id: \"pump-house\",\n label: \"Pump house\",\n kind: \"building\",\n points: rectangle(-0.0021, 0.00025, 0.0006, 0.0005),\n },\n {\n id: \"control-room\",\n label: \"Control room\",\n kind: \"building\",\n points: rectangle(-0.0009, -0.0008, 0.0006, 0.0004),\n },\n {\n id: \"workshop\",\n label: \"Workshop\",\n kind: \"building\",\n points: rectangle(0.0008, -0.00075, 0.001, 0.00045),\n },\n {\n id: \"power-house\",\n label: \"Power house\",\n kind: \"building\",\n points: rectangle(0.0011, 0.0008, 0.0008, 0.00045),\n },\n {\n id: \"stores\",\n label: \"Stores\",\n kind: \"building\",\n points: rectangle(-0.0008, 0.0007, 0.0008, 0.00035),\n },\n {\n id: \"treatment-a\",\n label: \"Treatment A\",\n kind: \"building\",\n points: rectangle(-0.002, -0.0012, 0.0006, 0.0006),\n },\n {\n id: \"treatment-b\",\n label: \"Treatment B\",\n kind: \"building\",\n points: rectangle(-0.002, -0.002, 0.0006, 0.0006),\n },\n {\n id: \"tank-a\",\n label: \"Storage A\",\n kind: \"building\",\n points: rectangle(0.0008, 0.00165, 0.0004, 0.0004),\n },\n {\n id: \"tank-b\",\n label: \"Storage B\",\n kind: \"building\",\n points: rectangle(0.0014, 0.00165, 0.0004, 0.0004),\n },\n];\nexport const utilitySite: readonly GeoSiteFeature[] = [\n {\n id: \"site-boundary\",\n label: \"Utility district\",\n kind: \"boundary\",\n points: rectangle(-0.003, -0.0027, 0.006, 0.0055),\n },\n {\n id: \"water-channel\",\n label: \"Water channel\",\n kind: \"water\",\n points: [\n point(-0.004, -0.004),\n point(-0.0032, -0.004),\n point(-0.0032, 0.004),\n point(-0.004, 0.004),\n point(-0.004, -0.004),\n ],\n },\n {\n id: \"service-road\",\n label: \"Service road\",\n kind: \"road\",\n points: [\n point(-0.0025, -0.003),\n point(-0.0011, -0.0023),\n point(0.0001, -0.0018),\n point(0.0001, 0.0017),\n point(0.0026, 0.0026),\n ],\n },\n {\n id: \"west-access\",\n label: \"West access\",\n kind: \"road\",\n points: [point(-0.003, 0.00005), point(0.0001, 0.00005), point(0.0028, 0.00005)],\n },\n ...buildings,\n];\nfunction equipment(\n id: string,\n name: string,\n typeLabel: string,\n position: GeoPoint | null,\n condition: string,\n variant: \"success\" | \"warning\" | \"neutral\",\n detail: string,\n): GeoEntity {\n return {\n record: {\n id,\n name,\n typeLabel,\n summary: detail,\n condition: { label: condition, variant },\n quality: {\n state: position === null ? \"unknown\" : \"current\",\n source: \"Utility survey / local fixture\",\n updatedAt: Date.parse(fixtureObservedAt),\n detail: \"Supplied fictional observation; no live equipment connection.\",\n },\n facts: [\n { id: \"area\", label: \"Area\", value: \"Harbor utility district\" },\n { id: \"reference\", label: \"Reference\", value: id, mono: true },\n ],\n },\n position,\n positionState: position === null ? \"unknown\" : \"reported\",\n observedAt: position === null ? null : fixtureObservedAt,\n };\n}\nconst vehicle: GeoEntity = {\n ...equipment(\n \"VEH-07\",\n \"Service vehicle 07\",\n \"Maintenance\",\n point(0.0021, -0.0017),\n \"Last-known normal\",\n \"neutral\",\n \"Position is retained from the last report; current movement is unknown.\",\n ),\n positionState: \"last-known\",\n observedAt: \"2026-09-30T11:42:00Z\",\n uncertaintyMeters: 65,\n track: [\n { position: point(-0.0009, -0.0022), observedAt: \"2026-09-30T11:36:00Z\" },\n { position: point(0.0001, -0.0018), observedAt: \"2026-09-30T11:38:00Z\" },\n { position: point(0.0011, -0.00175), observedAt: \"2026-09-30T11:40:00Z\" },\n { position: point(0.0021, -0.0017), observedAt: \"2026-09-30T11:42:00Z\" },\n ],\n};\nvehicle.record = {\n ...vehicle.record,\n quality: {\n ...vehicle.record.quality,\n state: \"stale\",\n updatedAt: vehicle.observedAt === null ? null : Date.parse(vehicle.observedAt),\n detail: \"Last report received at 11:42 UTC. Position uncertainty supplied as 65 m.\",\n },\n};\nexport const utilityEntities: readonly GeoEntity[] = [\n equipment(\n \"PUMP-01\",\n \"Intake pump 01\",\n \"Pump\",\n point(-0.0018, 0.0005),\n \"Ready for service\",\n \"success\",\n \"Duty pump operating normally. Intake valve shares the pump-house location.\",\n ),\n equipment(\n \"VALVE-01\",\n \"Intake valve 01\",\n \"Valve\",\n point(-0.0018, 0.0005),\n \"Inspection due\",\n \"warning\",\n \"Quarterly seal inspection due at 13:00 UTC. Pump operation remains available.\",\n ),\n equipment(\n \"SENSOR-11\",\n \"Pressure sensor 11\",\n \"Sensor\",\n point(0.00032, 0.0003),\n \"Nominal\",\n \"success\",\n \"Reported pressure 6.2 bar. Operating limits belong to the source application.\",\n ),\n equipment(\n \"SENSOR-12\",\n \"Flow sensor 12\",\n \"Sensor\",\n point(0.00038, 0.00033),\n \"Nominal\",\n \"success\",\n \"Reported flow 48 L/s. Nearby sensors remain individually selectable.\",\n ),\n equipment(\n \"POWER-02\",\n \"Power house 02\",\n \"Power\",\n point(0.0015, 0.00105),\n \"Nominal\",\n \"success\",\n \"Supplied equipment position within the eastern service block.\",\n ),\n {\n ...equipment(\n \"CAM-03\",\n \"Perimeter camera 03\",\n \"Camera\",\n point(-0.0007, -0.0006),\n \"Available\",\n \"success\",\n \"Reported observation footprint covers the southern service road.\",\n ),\n coverage: [\n point(-0.0007, -0.0006),\n point(-0.0027, -0.0017),\n point(-0.0017, -0.0025),\n point(0.0006, -0.0024),\n point(-0.0007, -0.0006),\n ],\n },\n vehicle,\n equipment(\n \"GAUGE-04\",\n \"Portable gauge 04\",\n \"Gauge\",\n null,\n \"Location unknown\",\n \"neutral\",\n \"Present in the supplied inventory without geographic coordinates.\",\n ),\n equipment(\n \"PUMP-REMOTE\",\n \"Remote pump station\",\n \"Pump\",\n point(0.027, 0.014),\n \"Inspection due\",\n \"warning\",\n \"Remote station east of the main facility. Seal inspection due at 13:00 UTC.\",\n ),\n];\nexport const utilitySnapshot: GeoSnapshot = {\n title: \"Harbor utilities · field operations\",\n id: \"harbor-utility\",\n revision: \"survey-1\",\n source: \"Harbor utility district / exercise survey\",\n observedAt: fixtureObservedAt,\n availability: \"current\",\n entities: utilityEntities,\n site: utilitySite,\n};\n","patterns/geospatial-workspace/geo-map.tsx":"\"use client\";\n\nimport {\n memo,\n useCallback,\n useEffect,\n useId,\n useLayoutEffect,\n useMemo,\n useRef,\n useState,\n type RefObject,\n} from \"react\";\nimport { Button, EntityPreview, Popover } from \"@oprt/react\";\nimport type { EngineCallbacks, EngineState, GeoEngine, TileState } from \"./engine-contract\";\nimport { isGeoPoint, normalizeGeoView, sameGeoAnchor, sameGeoFrame } from \"./geometry\";\nimport { mapDataIssues } from \"./renderer-data\";\nimport type {\n GeoAnnotation,\n GeoEntity,\n GeoFrame,\n GeoMapProps,\n GeoMarkerGroup,\n GeoPoint,\n} from \"./types\";\nimport { UtcStamp } from \"./utc-stamp\";\n\n/** One shared empty collection, so a host that passes a new `[]` each render changes nothing. */\nconst NO_ANNOTATIONS: readonly GeoAnnotation[] = [];\nconst NO_POINTS: readonly GeoPoint[] = [];\nconst useBrowserLayoutEffect = typeof document === \"undefined\" ? useEffect : useLayoutEffect;\nconst finitePixel = (point: readonly number[] | null) =>\n point !== null && point.length === 2 && point.every(Number.isFinite);\n\n/** One engine start. Retiring it (dispose, failure, unmount) silences every callback it issued. */\ninterface EngineInstance {\n live: boolean;\n}\n\ninterface MarkerProps {\n group: GeoMarkerGroup;\n records: ReadonlyMap<string, GeoEntity>;\n isSelected: boolean;\n open: boolean;\n /** The location choice made inside this group while it is open. */\n choice: string | null;\n /** The supplied selection, which a choice waits for; only read while the group is open. */\n selectedId: string | null;\n region: RefObject<HTMLDivElement | null>;\n onOpenChange: (groupId: string, open: boolean, singleId: string | null) => void;\n onChoose: (groupId: string, entityId: string, origin: HTMLElement) => void;\n onPin: (groupId: string, entityId: string) => void;\n onCloseAutoFocus: (groupId: string, event: Event) => void;\n}\n\nconst sameIds = (a: readonly string[], b: readonly string[]) =>\n a === b || (a.length === b.length && a.every((item, index) => item === b[index]));\n/** A marker is drawn again for what it shows, not for where it is: a pan only moves its wrapper. */\nconst sameMarker = (previous: MarkerProps, next: MarkerProps) =>\n previous.group.id === next.group.id &&\n sameIds(previous.group.ids, next.group.ids) &&\n previous.records === next.records &&\n previous.isSelected === next.isSelected &&\n previous.open === next.open &&\n previous.choice === next.choice &&\n previous.selectedId === next.selectedId &&\n previous.region === next.region &&\n previous.onOpenChange === next.onOpenChange &&\n previous.onChoose === next.onChoose &&\n previous.onPin === next.onPin &&\n previous.onCloseAutoFocus === next.onCloseAutoFocus;\n\nconst ClusterMarker = memo(function ClusterMarker({\n group,\n records,\n isSelected,\n open,\n choice,\n selectedId,\n region,\n onOpenChange,\n onChoose,\n onPin,\n onCloseAutoFocus,\n}: MarkerProps) {\n const members = group.ids.flatMap((entityId) =>\n records.has(entityId) ? [records.get(entityId)!] : [],\n );\n const selected =\n choice && choice === selectedId && group.ids.includes(choice) ? records.get(choice) : undefined;\n const single = members.length === 1 ? members[0] : undefined;\n const label = single\n ? `${single.record.name} — ${single.record.id}`\n : `${members.length} equipment at this location`;\n return (\n <Popover\n title={selected ? \"Equipment preview\" : \"Equipment at this location\"}\n open={open}\n onOpenChange={(next) => onOpenChange(group.id, next, single?.record.id ?? null)}\n returnFocusRef={region}\n onCloseAutoFocus={(event) => onCloseAutoFocus(group.id, event)}\n content={\n <div className=\"flex max-w-[calc(var(--oprt-space-8)*5)] flex-col gap-2u\">\n {selected ? (\n <>\n <EntityPreview\n entity={{\n ...selected.record,\n facts: [\n ...selected.record.facts.map((fact) => ({\n ...fact,\n id: `entity:${fact.id}`,\n })),\n {\n id: \"geo:position\",\n label: \"Position\",\n value: selected.positionState,\n },\n {\n id: \"geo:observed\",\n label: \"Observed\",\n value: <UtcStamp value={selected.observedAt} />,\n mono: true,\n },\n ],\n }}\n actions={\n <Button size=\"sm\" onClick={() => onPin(group.id, selected.record.id)}>\n Pin in Inspector\n </Button>\n }\n />\n </>\n ) : (\n <>\n <p className=\"m-0 normal-case\">Choose a supplied record to preview its details.</p>\n {members.map((entity) => (\n <Button\n key={entity.record.id}\n size=\"sm\"\n variant=\"secondary\"\n className=\"h-auto! min-h-[var(--oprt-height-sm)] whitespace-normal! break-words text-left normal-case!\"\n onClick={(event) => onChoose(group.id, entity.record.id, event.currentTarget)}\n >\n {entity.record.name} — {entity.record.id}\n </Button>\n ))}\n {choice && (\n <p role=\"status\" className=\"normal-case\">\n Selection requested; waiting for the supplied selection.\n </p>\n )}\n </>\n )}\n </div>\n }\n >\n <Button\n size=\"sm\"\n variant={isSelected ? \"primary\" : \"secondary\"}\n aria-label={label}\n aria-pressed={isSelected}\n className={`whitespace-nowrap normal-case!${isSelected ? \"\" : \" bg-oprt-surface-1!\"}`}\n >\n {single ? single.record.id : members.length}\n </Button>\n </Popover>\n );\n}, sameMarker);\n\n/**\n * Optional renderer. All source, view and selection authority stays with its host.\n *\n * `snapshot`, `entities`, `annotations`, `measurement` and `layers` are compared by identity: the\n * renderer redraws when the host supplies a new collection, and not when it supplies an equal one\n * (a map of 2,000 reports cannot be compared by value on every pan frame). Keep a collection\n * stable between changes, for example in state or memoized.\n */\nexport const GeoMap = memo(function GeoMap(props: GeoMapProps) {\n const id = useId();\n const target = useRef<HTMLDivElement>(null);\n const region = useRef<HTMLDivElement>(null);\n const adapter = useRef<GeoEngine | null>(null);\n const instance = useRef<EngineInstance | null>(null);\n const anchorFrame = useRef<GeoFrame | null>(null);\n // The marker group whose preview or choices are open, for the engine callbacks, which never change\n // identity and so cannot read the state.\n const openGroupRef = useRef<string | null>(null);\n const pinnedGroup = useRef<string | null>(null);\n const choiceFocus = useRef<{\n id: string;\n groupId: string;\n origin: HTMLElement;\n dialog: HTMLElement;\n source: string;\n } | null>(null);\n const { snapshot, entities, selectedId, layers, mode, tileSource } = props;\n const annotations = props.annotations.length ? props.annotations : NO_ANNOTATIONS;\n const measurement = props.measurement.length ? props.measurement : NO_POINTS;\n // The view is a few numbers, not an object to compare: a pan changes these and nothing else.\n const view = normalizeGeoView(props.view);\n const [longitude, latitude] = view.center;\n const { zoom, bearing } = view;\n const tileUrl = tileSource?.url;\n const tileAttribution = tileSource?.attribution;\n const sourceKey = useMemo(\n () => JSON.stringify([snapshot.id, snapshot.revision]),\n [snapshot.id, snapshot.revision],\n );\n // What the engine draws. Its identity changes exactly when one of these inputs does, and it is\n // the stamp that tells a current engine callback from one the next update superseded.\n const state = useMemo<EngineState>(\n () => ({\n snapshot,\n entities,\n selectedId,\n view: { center: [longitude, latitude], zoom, bearing },\n layers,\n annotations,\n measurement,\n mode,\n ...(tileUrl !== undefined && tileAttribution !== undefined\n ? { tileSource: { url: tileUrl, attribution: tileAttribution } }\n : {}),\n }),\n [\n snapshot,\n entities,\n selectedId,\n longitude,\n latitude,\n zoom,\n bearing,\n layers,\n annotations,\n measurement,\n mode,\n tileUrl,\n tileAttribution,\n ],\n );\n // The supplied data is checked once per change of the data, never for a change of the view.\n const issues = useMemo(\n () => mapDataIssues({ snapshot, entities, annotations, measurement }),\n [snapshot, entities, annotations, measurement],\n );\n const positionedIds = useMemo(\n () =>\n new Set(\n entities.filter((entity) => isGeoPoint(entity.position)).map((entity) => entity.record.id),\n ),\n [entities],\n );\n const records = useMemo(\n () => new Map(entities.map((entity) => [entity.record.id, entity])),\n [entities],\n );\n const enabled = !props.disabled && !issues.duplicates && snapshot.availability !== \"unavailable\";\n const latest = useRef({ props, state, enabled, positionedIds });\n latest.current = { props, state, enabled, positionedIds };\n const [attempt, setAttempt] = useState(0);\n const [status, setStatus] = useState<\"loading\" | \"ready\" | \"error\">(\"loading\");\n const [error, setError] = useState(\"\");\n const [frame, setFrame] = useState<GeoFrame | null>(null);\n const [tileState, setTileState] = useState<TileState>(\"loading\");\n const [openGroup, setOpenGroup] = useState<string | null>(null);\n openGroupRef.current = openGroup;\n const [choice, setChoice] = useState<string | null>(null);\n /** Callbacks for one engine start and one state; they go quiet once either is superseded. */\n const makeCallbacks = useCallback(\n (expected: EngineState, owner: EngineInstance): EngineCallbacks => {\n const current = () =>\n owner.live && latest.current.enabled && latest.current.state === expected;\n return {\n onFrame(next) {\n if (!current()) return;\n const ids = latest.current.positionedIds;\n if (\n next.visibleIds.some((entityId) => !ids.has(entityId)) ||\n new Set(next.visibleIds).size !== next.visibleIds.length ||\n next.groups.some(\n (group) =>\n !isGeoPoint(group.point) ||\n !finitePixel(group.pixel) ||\n !group.ids.length ||\n group.ids.some((entityId) => !ids.has(entityId)) ||\n new Set(group.ids).size !== group.ids.length,\n ) ||\n (next.selectedPixel !== null && !finitePixel(next.selectedPixel)) ||\n (next.metersPerPixel !== null &&\n (!Number.isFinite(next.metersPerPixel) || next.metersPerPixel <= 0))\n )\n return;\n // What is open belongs to its marker group, not to the pixel it is drawn at. A frame that\n // only moves the view (Fit map, a zoom, a pan, or the second frame of the renderer settling)\n // moves the marker and what is open from it; it is dismissed when its group is gone or its\n // equipment moved on the ground.\n const open = openGroupRef.current;\n if (open !== null && !sameGeoAnchor(anchorFrame.current, next, open)) {\n choiceFocus.current = null;\n openGroupRef.current = null;\n setOpenGroup(null);\n setChoice(null);\n }\n anchorFrame.current = next;\n setFrame((previous) => (sameGeoFrame(previous, next) ? previous : next));\n latest.current.props.onFrame(next);\n },\n onViewChange(next) {\n if (current()) latest.current.props.onViewChange(normalizeGeoView(next));\n },\n onPoint(point) {\n if (current() && latest.current.props.mode !== \"inspect\" && isGeoPoint(point))\n latest.current.props.onPoint([point[0], point[1]]);\n },\n onTileState(next) {\n if (current()) setTileState(next);\n },\n };\n },\n [],\n );\n\n useEffect(() => {\n // This start owns the callbacks it issues. Retiring it is a write to its own object, not to a\n // counter held in a ref, so the cleanup reads nothing that React might have moved on.\n const owner: EngineInstance = { live: true };\n instance.current = owner;\n anchorFrame.current = null;\n choiceFocus.current = null;\n setFrame(null);\n setOpenGroup(null);\n setChoice(null);\n latest.current.props.onFrame(null);\n if (!enabled || !target.current) return;\n setStatus(\"loading\");\n setError(\"\");\n setTileState(\"loading\");\n void import(\"./engine\")\n .then(({ createGeoEngine }) => {\n if (!owner.live || !latest.current.enabled || !target.current) return;\n const current = latest.current;\n adapter.current = createGeoEngine(\n target.current,\n current.state,\n makeCallbacks(current.state, owner),\n );\n setStatus(\"ready\");\n })\n .catch((cause: unknown) => {\n if (!owner.live) return;\n owner.live = false;\n adapter.current?.dispose();\n adapter.current = null;\n setError(cause instanceof Error ? cause.message : \"Map renderer could not start.\");\n setStatus(\"error\");\n setFrame(null);\n latest.current.props.onFrame(null);\n });\n return () => {\n owner.live = false;\n adapter.current?.dispose();\n adapter.current = null;\n latest.current.props.onFrame(null);\n };\n }, [enabled, attempt, makeCallbacks]);\n\n useEffect(() => {\n const owner = instance.current;\n if (!adapter.current || !enabled || !owner?.live) return;\n try {\n adapter.current.update(state, makeCallbacks(state, owner));\n } catch (cause: unknown) {\n adapter.current.dispose();\n adapter.current = null;\n owner.live = false;\n setError(cause instanceof Error ? cause.message : \"Map renderer could not update.\");\n setStatus(\"error\");\n setFrame(null);\n latest.current.props.onFrame(null);\n }\n }, [state, enabled, makeCallbacks]);\n\n useEffect(() => {\n const leave = (event: Event) => {\n const pending = choiceFocus.current;\n if (pending && !pending.dialog.contains(event.target as Node | null))\n choiceFocus.current = null;\n };\n const blur = (event: FocusEvent) => {\n const pending = choiceFocus.current;\n // A still-connected choice explicitly blurred to nowhere is a departure;\n // removing the focused choice during acceptance is handled after commit.\n if (\n pending &&\n event.target === pending.origin &&\n pending.origin.isConnected &&\n !event.relatedTarget\n )\n choiceFocus.current = null;\n };\n document.addEventListener(\"focusin\", leave, true);\n document.addEventListener(\"pointerdown\", leave, true);\n document.addEventListener(\"focusout\", blur, true);\n return () => {\n choiceFocus.current = null;\n document.removeEventListener(\"focusin\", leave, true);\n document.removeEventListener(\"pointerdown\", leave, true);\n document.removeEventListener(\"focusout\", blur, true);\n };\n }, []);\n useBrowserLayoutEffect(() => {\n const pending = choiceFocus.current;\n if (!pending) return;\n if (\n !enabled ||\n status !== \"ready\" ||\n pending.groupId !== openGroup ||\n pending.id !== choice ||\n pending.source !== sourceKey ||\n !pending.dialog.isConnected\n ) {\n choiceFocus.current = null;\n return;\n }\n if (selectedId !== pending.id) return;\n choiceFocus.current = null;\n const active = document.activeElement;\n if (\n pending.dialog.contains(active) ||\n (active === document.body && !pending.origin.isConnected)\n )\n pending.dialog.focus({ preventScroll: true });\n }, [choice, selectedId, openGroup, enabled, status, sourceKey]);\n\n // The marker handlers read the latest props and write state, so they never change identity and\n // a marker is not drawn again because the map around it was.\n const onMarkerOpenChange = useCallback(\n (groupId: string, open: boolean, singleId: string | null) => {\n setOpenGroup(open ? groupId : null);\n choiceFocus.current = null;\n if (open) {\n setChoice(singleId);\n if (singleId) latest.current.props.onSelectedIdChange(singleId);\n }\n },\n [],\n );\n const onMarkerChoose = useCallback(\n (groupId: string, entityId: string, origin: HTMLElement) => {\n const dialog = origin.closest<HTMLElement>(\"[role=dialog]\");\n choiceFocus.current = dialog\n ? { id: entityId, groupId, origin, dialog, source: sourceKey }\n : null;\n setChoice(entityId);\n latest.current.props.onSelectedIdChange(entityId);\n },\n [sourceKey],\n );\n const onMarkerPin = useCallback((groupId: string, entityId: string) => {\n pinnedGroup.current = groupId;\n setOpenGroup(null);\n latest.current.props.onInspect(entityId);\n }, []);\n const onMarkerCloseAutoFocus = useCallback((groupId: string, event: Event) => {\n if (pinnedGroup.current === groupId) {\n pinnedGroup.current = null;\n event.preventDefault();\n }\n }, []);\n\n const entityLayer = layers.find((layer) => layer.id === \"entities\");\n const groups =\n enabled &&\n status === \"ready\" &&\n mode === \"inspect\" &&\n entityLayer?.visible &&\n entityLayer.opacity > 0\n ? (frame?.groups ?? [])\n : [];\n return (\n <section\n aria-labelledby={`${id}-title`}\n className={`min-w-0 flex flex-col gap-2u font-sans text-oprt-base text-oprt-text ${props.className ?? \"\"}`}\n >\n <h3\n id={`${id}-title`}\n className=\"m-0 text-oprt-base font-medium uppercase tracking-oprt-base\"\n >\n Equipment map\n </h3>\n <p id={`${id}-help`} className=\"sr-only\">\n Focus the viewport and use arrow keys to pan, plus or minus to zoom. Use equipment markers\n for location choices. Equipment records remain available for inspection, including unknown\n positions.\n </p>\n {issues.invalid > 0 && (\n <p role=\"status\">\n {issues.invalid} invalid geometr{issues.invalid === 1 ? \"y\" : \"ies\"} excluded from the\n map. Review supplied coordinates in the table.{\" \"}\n {issues.uncertainty > 0 &&\n `${issues.uncertainty} uncertainty footprint${issues.uncertainty === 1 ? \" is\" : \"s are\"} excluded because of an invalid radius or unsupported projection bounds.`}\n </p>\n )}\n {issues.unknown > 0 && (\n <p>\n {issues.unknown} record{issues.unknown === 1 ? \" has\" : \"s have\"} an unknown position.\n </p>\n )}\n {issues.duplicates ? (\n <p role=\"alert\">\n Duplicate or empty IDs prevent unambiguous map selection. Correct the supplied source; the\n equipment records remain available.\n </p>\n ) : props.disabled ? (\n <p role=\"status\">Map disabled. Use the table to review equipment.</p>\n ) : snapshot.availability === \"unavailable\" ? (\n <p role=\"status\">Map source unavailable. Use the table and source recovery controls.</p>\n ) : null}\n <div\n ref={region}\n role=\"group\"\n aria-label=\"Map viewport\"\n aria-describedby={`${id}-help`}\n tabIndex={enabled && status === \"ready\" ? 0 : -1}\n className=\"relative h-[calc(var(--oprt-space-8)*8)] min-w-0 overflow-hidden border border-oprt-border bg-oprt-surface-2 outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-oprt-border-focus forced-colors:focus-visible:outline-[CanvasText]\"\n >\n <div\n ref={target}\n aria-hidden=\"true\"\n role=\"presentation\"\n className=\"absolute inset-0 [&_.ol-viewport]:touch-none!\"\n />\n {enabled && status === \"loading\" && (\n <p role=\"status\" className=\"absolute inset-x-0 top-0 m-0 bg-oprt-surface-1 p-2u\">\n Loading map renderer…\n </p>\n )}\n {enabled && status === \"error\" && (\n <div className=\"absolute inset-0 flex flex-col items-start gap-2u bg-oprt-surface-1 p-3u\">\n <p role=\"alert\">Map renderer unavailable: {error}</p>\n <Button size=\"sm\" onClick={() => setAttempt((value) => value + 1)}>\n Retry map\n </Button>\n </div>\n )}\n {groups.map((group) => {\n if (!group.ids.every((entityId) => records.has(entityId))) return null;\n const open = openGroup === group.id;\n return (\n <div\n key={group.id}\n className=\"absolute -translate-x-1/2 -translate-y-1/2\"\n style={{ left: group.pixel[0], top: group.pixel[1] }}\n >\n <ClusterMarker\n group={group}\n records={records}\n isSelected={selectedId !== null && group.ids.includes(selectedId)}\n open={open}\n choice={open ? choice : null}\n selectedId={open ? selectedId : null}\n region={region}\n onOpenChange={onMarkerOpenChange}\n onChoose={onMarkerChoose}\n onPin={onMarkerPin}\n onCloseAutoFocus={onMarkerCloseAutoFocus}\n />\n </div>\n );\n })}\n </div>\n {enabled && tileSource && (\n <div className=\"flex flex-wrap items-center gap-2u text-oprt-text-dim\">\n <span className=\"[overflow-wrap:anywhere]\">{tileSource.attribution}</span>\n {tileState === \"loading\" && <span role=\"status\">Loading background tiles…</span>}\n {tileState === \"error\" && (\n <>\n <span role=\"status\">\n Background tiles unavailable. Local vectors and the table remain available.\n </span>\n <Button size=\"sm\" variant=\"tertiary\" onClick={() => setAttempt((value) => value + 1)}>\n Retry tiles\n </Button>\n </>\n )}\n </div>\n )}\n </section>\n );\n});\n","patterns/geospatial-workspace/geometry.ts":"import type { MapAction } from \"@oprt/react\";\nimport type { GeoEntity, GeoFrame, GeoPoint, GeoView } from \"./types\";\n\nexport const MERCATOR_LATITUDE = 85.05112878;\nconst radians = Math.PI / 180;\nconst wrap = (value: number, period: number) => ((value % period) + period) % period;\nexport const normalizeLongitude = (longitude: number) => wrap(longitude + 180, 360) - 180;\nconst finite = (value: unknown, fallback: number): number =>\n typeof value === \"number\" && Number.isFinite(value) ? value : fallback;\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\nconst mercatorY = (latitude: number) =>\n Math.log(Math.tan(Math.PI / 4 + (latitude * radians) / 2)) / (2 * Math.PI);\nconst latitudeFromY = (y: number) =>\n (2 * Math.atan(Math.exp(y * 2 * Math.PI)) - Math.PI / 2) / radians;\n\nexport function isGeoPoint(value: unknown): value is GeoPoint {\n return (\n Array.isArray(value) &&\n value.length === 2 &&\n value.every((axis) => typeof axis === \"number\" && Number.isFinite(axis)) &&\n Math.abs(value[0]) <= 180 &&\n Math.abs(value[1]) <= MERCATOR_LATITUDE\n );\n}\nexport function normalizeGeoView(view: GeoView): GeoView {\n return {\n center: [\n normalizeLongitude(finite(view?.center?.[0], 0)),\n clamp(finite(view?.center?.[1], 0), -MERCATOR_LATITUDE, MERCATOR_LATITUDE),\n ],\n zoom: clamp(finite(view?.zoom, 2), 2, 20),\n bearing: wrap(finite(view?.bearing, 0), 360),\n };\n}\nexport function distanceMeters(a: GeoPoint, b: GeoPoint): number | null {\n if (!isGeoPoint(a) || !isGeoPoint(b)) return null;\n const dLat = (b[1] - a[1]) * radians,\n dLon = normalizeLongitude(b[0] - a[0]) * radians;\n const arc =\n Math.sin(dLat / 2) ** 2 +\n Math.cos(a[1] * radians) * Math.cos(b[1] * radians) * Math.sin(dLon / 2) ** 2;\n return 2 * 6371008.8 * Math.asin(Math.sqrt(clamp(arc, 0, 1)));\n}\nexport function getFitView(entities: readonly GeoEntity[]): GeoView {\n const points = entities.flatMap((entity) =>\n isGeoPoint(entity.position) ? [entity.position] : [],\n );\n if (!points.length) return { center: [0, 0], zoom: 2, bearing: 0 };\n const longitudes = points.map((point) => wrap(point[0], 360)).sort((a, b) => a - b);\n let largestGap = -1,\n start = longitudes[0]!;\n for (let index = 0; index < longitudes.length; index++) {\n const next =\n longitudes[(index + 1) % longitudes.length]! + (index === longitudes.length - 1 ? 360 : 0);\n const gap = next - longitudes[index]!;\n if (gap > largestGap) {\n largestGap = gap;\n start = next;\n }\n }\n const span = 360 - largestGap;\n const ys = points.map((point) => mercatorY(point[1]));\n const low = Math.min(...ys),\n high = Math.max(...ys);\n const fraction = Math.max(span / 360, high - low);\n return normalizeGeoView({\n center: [start + span / 2, latitudeFromY((low + high) / 2)],\n zoom: fraction > 0 ? Math.floor(Math.log2(0.7 / fraction)) : 16,\n bearing: 0,\n });\n}\nexport function moveGeoView(\n view: GeoView,\n action: MapAction,\n entities: readonly GeoEntity[],\n): GeoView {\n const next = normalizeGeoView(view);\n if (action === \"fit\") return getFitView(entities);\n const step = 0.25 / 2 ** next.zoom;\n return normalizeGeoView({\n center: [\n next.center[0] + (action === \"pan-east\" ? 1 : action === \"pan-west\" ? -1 : 0) * step * 360,\n latitudeFromY(\n mercatorY(next.center[1]) +\n (action === \"pan-north\" ? 1 : action === \"pan-south\" ? -1 : 0) * step,\n ),\n ],\n zoom: next.zoom + (action === \"zoom-in\" ? 1 : action === \"zoom-out\" ? -1 : 0),\n bearing: action === \"reset-north\" ? 0 : next.bearing,\n });\n}\n\n/** Approximate spherical footprint. A rejected cap is omitted and counted by the host. */\nexport function getUncertaintyFootprint(\n center: GeoPoint,\n radius: number,\n): readonly GeoPoint[] | null {\n if (!isGeoPoint(center) || !Number.isFinite(radius) || radius < 0) return null;\n if (radius === 0) return [];\n const angularRadius = radius / 6371008.8;\n // Check the complete cap, including its interior, before sampling its boundary.\n if (Math.abs(center[1]) + angularRadius / radians > MERCATOR_LATITUDE) return null;\n const latitude = center[1] * radians;\n const sinLatitude = Math.sin(latitude),\n cosLatitude = Math.cos(latitude);\n const sinRadius = Math.sin(angularRadius),\n cosRadius = Math.cos(angularRadius);\n const points: GeoPoint[] = Array.from({ length: 64 }, (_, index) => {\n const bearing = (2 * Math.PI * index) / 64;\n const nextLatitude = Math.asin(\n clamp(sinLatitude * cosRadius + cosLatitude * sinRadius * Math.cos(bearing), -1, 1),\n );\n const deltaLongitude = Math.atan2(\n Math.sin(bearing) * sinRadius * cosLatitude,\n cosRadius - sinLatitude * Math.sin(nextLatitude),\n );\n return [normalizeLongitude(center[0] + deltaLongitude / radians), nextLatitude / radians];\n });\n return points.every(isGeoPoint) ? points : null;\n}\n\nconst same = (a: number, b: number) => a === b || (Number.isNaN(a) && Number.isNaN(b));\nconst samePair = (a: readonly number[], b: readonly number[]) =>\n same(a[0]!, b[0]!) && same(a[1]!, b[1]!);\nconst sameList = (a: readonly string[], b: readonly string[]) =>\n a === b || (a.length === b.length && a.every((item, index) => item === b[index]));\n\n/**\n * Whether two map frames report the same view, extent, markers and scale. Frames come about every\n * animation frame, so this walks them once instead of serializing them to compare the text.\n */\nexport function sameGeoFrame(a: GeoFrame | null, b: GeoFrame | null): boolean {\n if (a === b) return true;\n if (!a || !b) return false;\n if (\n !samePair(a.view.center, b.view.center) ||\n !same(a.view.zoom, b.view.zoom) ||\n !same(a.view.bearing, b.view.bearing) ||\n !sameList(a.visibleIds, b.visibleIds) ||\n a.groups.length !== b.groups.length ||\n (a.selectedPixel === null) !== (b.selectedPixel === null) ||\n (a.selectedPixel !== null && !samePair(a.selectedPixel, b.selectedPixel!)) ||\n (a.metersPerPixel === null) !== (b.metersPerPixel === null) ||\n (a.metersPerPixel !== null && !same(a.metersPerPixel, b.metersPerPixel!))\n )\n return false;\n return a.groups.every((group, index) => {\n const other = b.groups[index]!;\n return (\n group.id === other.id &&\n sameList(group.ids, other.ids) &&\n samePair(group.point, other.point) &&\n samePair(group.pixel, other.pixel)\n );\n });\n}\n\n/**\n * Whether the marker group `groupId` is still anchored in `after`: the frame draws a group with that\n * id (made from its members) and, where `before` had it too, at the same point on the ground. Its\n * pixel and the view around it are left out on purpose: a marker moves with the map, so whatever is\n * open from the marker (a preview, a list of choices) moves with it. An open marker is dismissed when\n * its group is gone (out of the viewport, regrouped) or when its equipment has moved on the ground.\n */\nexport function sameGeoAnchor(before: GeoFrame | null, after: GeoFrame, groupId: string): boolean {\n const next = after.groups.find((group) => group.id === groupId);\n if (!next) return false;\n const previous = before?.groups.find((group) => group.id === groupId);\n return !previous || samePair(previous.point, next.point);\n}\n","patterns/geospatial-workspace/geospatial-workspace.tsx":"\"use client\";\n\nimport { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from \"react\";\nimport {\n Badge,\n Button,\n CoordinateInput,\n DescriptionList,\n EntityPreview,\n Input,\n Inspector,\n LayerControl,\n MapControls,\n MapLegend,\n MapScale,\n type CoordinateValue,\n type EntityPreviewRecord,\n type MapLegendItem,\n} from \"@oprt/react\";\nimport { formatUtcInstant } from \"../operational-domain\";\nimport { GeoMap } from \"./geo-map\";\nimport { ReportResults } from \"./report-results\";\nimport { UtcStamp } from \"./utc-stamp\";\nimport { distanceMeters, getFitView, isGeoPoint, moveGeoView } from \"./geometry\";\nimport {\n defaultGeoLayers,\n filterGeoEntities,\n formatGeoPosition,\n geoKnownIds,\n getAnnotationDraftError,\n getGeoFrameError,\n getGeoSnapshotError,\n isValidGeoView,\n parseGeoAnnotationResult,\n parseGeoBookmark,\n retainGeoEntity,\n retainedEntityPreview,\n} from \"./workspace-state\";\nimport type {\n GeoAnnotationDraft,\n GeoEntity,\n GeoFrame,\n GeoMapMode,\n GeoPoint,\n GeoView,\n GeospatialWorkspaceProps,\n} from \"./types\";\n\nconst focusStyle =\n \"outline-hidden focus-visible:shadow-oprt-focus forced-colors:focus-visible:outline-solid forced-colors:focus-visible:outline-2 forced-colors:focus-visible:outline-[CanvasText]\";\nconst headingStyle = \"m-0 font-medium uppercase tracking-oprt-sm\";\nconst summaryStyle = `min-h-[var(--oprt-height-sm)] cursor-pointer py-1u text-oprt-base font-medium uppercase tracking-oprt-sm ${focusStyle}`;\nconst legendItems: readonly MapLegendItem[] = [\n {\n id: \"equipment\",\n label: \"Equipment\",\n kind: \"point\",\n variant: \"accent\",\n description: \"A position does not establish condition or data freshness.\",\n },\n {\n id: \"coverage\",\n label: \"Supplied coverage\",\n kind: \"area\",\n variant: \"info\",\n pattern: \"dashed\",\n description: \"A supplied outline does not establish live sensor coverage.\",\n },\n {\n id: \"track\",\n label: \"Reported track\",\n kind: \"line\",\n variant: \"accent\",\n description: \"Only supplied samples; gaps are not reconstructed.\",\n },\n {\n id: \"uncertainty\",\n label: \"Position uncertainty\",\n kind: \"area\",\n variant: \"warning\",\n pattern: \"dashed\",\n description: \"Approximate supplied radius around a report.\",\n },\n {\n id: \"measurement\",\n label: \"Measurement\",\n kind: \"line\",\n variant: \"warning\",\n description: \"Approximate distance between the two reviewed endpoints.\",\n },\n { id: \"annotation\", label: \"Saved annotation\", kind: \"point\", variant: \"warning\" },\n];\nconst blank: CoordinateValue = { latitude: null, longitude: null };\nconst coordinate = (point: GeoPoint): CoordinateValue => ({\n latitude: point[1],\n longitude: point[0],\n});\nfunction point(value: CoordinateValue): GeoPoint | null {\n const candidate = [value.longitude, value.latitude];\n return isGeoPoint(candidate) ? candidate : null;\n}\nfunction missingRecord(id: string): EntityPreviewRecord {\n return {\n id,\n name: \"Unavailable report\",\n facts: [],\n quality: {\n state: \"unavailable\",\n updatedAt: null,\n detail: \"No last-known detail for this identity is available in this session.\",\n },\n };\n}\ntype FocusRequest = { kind: \"inspect\"; id: string; origin: HTMLElement | null } | { kind: \"close\" };\n\n/** Reference workflow. Supplied identities, annotations and bookmarks remain authoritative. */\nexport function GeospatialWorkspace(props: GeospatialWorkspaceProps) {\n const {\n snapshot,\n selectedId,\n onSelectedIdChange,\n bookmarks,\n onBookmarksChange,\n annotations,\n onCreateAnnotation,\n onRemoveAnnotation,\n initialView,\n tileSource,\n } = props;\n const sourceError = useMemo(() => getGeoSnapshotError(snapshot), [snapshot]);\n const entities = useMemo(() => (sourceError ? [] : snapshot.entities), [sourceError, snapshot]);\n const [view, setView] = useState(() =>\n isValidGeoView(initialView) ? initialView : getFitView(entities),\n );\n const [layers, setLayers] = useState(() => defaultGeoLayers.map((layer) => ({ ...layer })));\n const [query, setQuery] = useState(\"\");\n const filtered = useMemo(() => filterGeoEntities(entities, query), [entities, query]);\n const [mapEnabled, setMapEnabled] = useState(true);\n const [frameReport, setFrameReport] = useState<{\n snapshot: typeof snapshot;\n query: string;\n frame: GeoFrame;\n } | null>(null);\n const frame = frameReport?.snapshot === snapshot && mapEnabled ? frameReport.frame : null;\n const [scope, setScope] = useState<\"all\" | \"extent\">(\"all\");\n const [extentNotice, setExtentNotice] = useState(\"\");\n const [mode, setMode] = useState<GeoMapMode>(\"inspect\");\n const [retained, setRetained] = useState<{\n entity: GeoEntity;\n source: { id: string; revision: string; label: string };\n } | null>(null);\n const currentSelected = entities.find((entity) => entity.record.id === selectedId);\n const retainedSelection = retained?.entity.record.id === selectedId ? retained : null;\n const selected = currentSelected ?? retainedSelection?.entity ?? null;\n const reportSource = currentSelected\n ? { id: snapshot.id, revision: snapshot.revision, label: snapshot.source }\n : (retainedSelection?.source ?? null);\n const fromOtherSource = reportSource !== null && reportSource.id !== snapshot.id;\n const selectedInFilter = filtered.some((entity) => entity.record.id === selectedId);\n const preview =\n selectedId === null\n ? null\n : selected\n ? retainedEntityPreview(selected, !currentSelected)\n : missingRecord(selectedId);\n // The extent narrows the list only in that scope, so a map frame leaves \"all records\" alone.\n const extentFrame = scope === \"extent\" ? frame : null;\n const displayed = useMemo(() => {\n if (!extentFrame) return filtered;\n const visible = new Set(extentFrame.visibleIds);\n return filtered.filter((entity) => visible.has(entity.record.id));\n }, [extentFrame, filtered]);\n const knownIds = useMemo(() => geoKnownIds(filtered), [filtered]);\n const resultsRef = useRef<HTMLElement>(null);\n const inspectorRef = useRef<HTMLElement>(null);\n const originRef = useRef<HTMLElement | null>(null);\n const [focusRequest, setFocusRequest] = useState<FocusRequest | null>(null);\n const previousSelection = useRef(selectedId);\n const inputId = useId();\n const [goValue, setGoValue] = useState<CoordinateValue>(() => coordinate(view.center));\n const [goInvalid, setGoInvalid] = useState(false);\n const [goError, setGoError] = useState(\"\");\n const [bookmarkName, setBookmarkName] = useState(\"\");\n const [bookmarkNotice, setBookmarkNotice] = useState(\"\");\n const bookmarkSequence = useRef(0);\n const [measurement, setMeasurement] = useState<readonly GeoPoint[]>([]);\n const [endpoints, setEndpoints] = useState<readonly CoordinateValue[]>([blank, blank]);\n const [endpointInvalid, setEndpointInvalid] = useState([false, false]);\n const [measurementError, setMeasurementError] = useState(\"\");\n const [measurementKey, setMeasurementKey] = useState(0);\n const [annotationValue, setAnnotationValue] = useState<CoordinateValue>(blank);\n const [annotationInvalid, setAnnotationInvalid] = useState(false);\n const [annotationKey, setAnnotationKey] = useState(0);\n const [annotationLabel, setAnnotationLabel] = useState(\"\");\n const [annotationNotice, setAnnotationNotice] = useState(\"\");\n const [annotationPending, setAnnotationPending] = useState(false);\n const pendingAnnotation = useRef<{\n sourceId: string;\n revision: string;\n draft: GeoAnnotationDraft;\n } | null>(null);\n const mounted = useRef(true);\n const latest = useRef({ snapshot, query, sourceError, onCreateAnnotation });\n latest.current = { snapshot, query, sourceError, onCreateAnnotation };\n\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n pendingAnnotation.current = null;\n };\n }, []);\n useEffect(() => {\n if (selectedId === null) setRetained(null);\n else if (currentSelected)\n setRetained({\n entity: retainGeoEntity(currentSelected),\n source: { id: snapshot.id, revision: snapshot.revision, label: snapshot.source },\n });\n else setRetained((old) => (old?.entity.record.id === selectedId ? old : null));\n }, [currentSelected, selectedId, snapshot.id, snapshot.revision, snapshot.source]);\n useLayoutEffect(() => {\n if (focusRequest?.kind === \"inspect\" && selectedId === focusRequest.id) {\n originRef.current = focusRequest.origin;\n inspectorRef.current?.focus({ preventScroll: true });\n } else if (focusRequest?.kind === \"close\" && selectedId === null) {\n const origin = originRef.current;\n (origin?.isConnected && !origin.hasAttribute(\"disabled\")\n ? origin\n : resultsRef.current\n )?.focus({ preventScroll: true });\n originRef.current = null;\n } else if (previousSelection.current !== selectedId) originRef.current = null;\n previousSelection.current = selectedId;\n if (focusRequest) setFocusRequest(null);\n }, [focusRequest, selectedId]);\n useEffect(() => {\n if (scope === \"extent\" && !frame) {\n setScope(\"all\");\n setExtentNotice(\"Map extent is unavailable; showing all records that match the filter.\");\n }\n }, [scope, frame]);\n useEffect(() => {\n const pending = pendingAnnotation.current;\n if (\n pending &&\n (pending.sourceId !== snapshot.id ||\n pending.revision !== snapshot.revision ||\n sourceError ||\n !onCreateAnnotation)\n ) {\n pendingAnnotation.current = null;\n setAnnotationPending(false);\n setAnnotationNotice(\n \"The source or editing capability changed. The draft is retained; the earlier request may still complete at the service.\",\n );\n }\n if (!onCreateAnnotation) setMode((old) => (old === \"annotate\" ? \"inspect\" : old));\n }, [snapshot.id, snapshot.revision, sourceError, onCreateAnnotation]);\n\n const reportFrame = useCallback(\n (next: GeoFrame | null) => {\n if (\n latest.current.snapshot !== snapshot ||\n latest.current.query !== query ||\n latest.current.sourceError\n )\n return;\n const error = next ? getGeoFrameError(next, filtered, knownIds) : null;\n if (error) setExtentNotice(error);\n setFrameReport(next && !error ? { snapshot, query, frame: next } : null);\n },\n [snapshot, query, filtered, knownIds],\n );\n function inspect(id: string) {\n if (sourceError || !entities.some((entity) => entity.record.id === id)) return;\n setFocusRequest({\n kind: \"inspect\",\n id,\n origin: document.activeElement instanceof HTMLElement ? document.activeElement : null,\n });\n onSelectedIdChange(id);\n }\n function selectOnMap(id: string | null) {\n if (!sourceError && (id === null || entities.some((entity) => entity.record.id === id)))\n onSelectedIdChange(id);\n }\n function acceptPoint(next: GeoPoint) {\n if (!isGeoPoint(next) || sourceError) return;\n if (mode === \"measure\") {\n const updated = measurement.length === 1 ? [measurement[0]!, next] : [next];\n setMeasurement(updated);\n setEndpoints([coordinate(updated[0]!), updated[1] ? coordinate(updated[1]) : blank]);\n setEndpointInvalid([false, false]);\n setMeasurementKey((old) => old + 1);\n setMeasurementError(\"\");\n } else if (mode === \"annotate\" && onCreateAnnotation && !annotationPending) {\n setAnnotationValue(coordinate(next));\n setAnnotationInvalid(false);\n setAnnotationKey((old) => old + 1);\n setAnnotationNotice(\"\");\n }\n }\n // The map is given functions that never change, so a frame that re-renders this workspace does\n // not re-render the map as well. They call the latest version of each handler.\n const handlers = useRef({ inspect, selectOnMap, acceptPoint });\n handlers.current = { inspect, selectOnMap, acceptPoint };\n const inspectStable = useCallback((id: string) => handlers.current.inspect(id), []);\n const selectOnMapStable = useCallback(\n (id: string | null) => handlers.current.selectOnMap(id),\n [],\n );\n const acceptPointStable = useCallback((next: GeoPoint) => handlers.current.acceptPoint(next), []);\n const changeView = useCallback((next: GeoView) => {\n if (isValidGeoView(next)) setView(next);\n }, []);\n function saveBookmark() {\n let id = `view-${inputId}-${++bookmarkSequence.current}`;\n while (bookmarks.some((bookmark) => bookmark.id === id))\n id = `view-${inputId}-${++bookmarkSequence.current}`;\n const parsed = parseGeoBookmark({ id, name: bookmarkName.trim(), view, layers });\n if (!parsed.ok) {\n setBookmarkNotice(parsed.error);\n return;\n }\n if (bookmarks.length >= 100) {\n setBookmarkNotice(\n \"Remove a bookmark before adding another; this example supports up to 100 supplied views.\",\n );\n return;\n }\n try {\n onBookmarksChange([...bookmarks, parsed.value]);\n setBookmarkNotice(\n \"Bookmark update requested. The list shows the views accepted by the host.\",\n );\n } catch {\n setBookmarkNotice(\"The bookmark update failed. The current view and name are retained.\");\n }\n }\n async function saveAnnotation() {\n if (pendingAnnotation.current || !onCreateAnnotation || sourceError) return;\n const location = point(annotationValue);\n const draft = { label: annotationLabel.trim(), position: location, sourceId: snapshot.id };\n const error = annotationInvalid\n ? \"Complete valid annotation coordinates before saving.\"\n : getAnnotationDraftError(draft);\n if (error || !location) {\n setAnnotationNotice(error ?? \"Enter a valid annotation position.\");\n return;\n }\n const request = {\n sourceId: snapshot.id,\n revision: snapshot.revision,\n draft: Object.freeze({ ...draft, position: Object.freeze([...location]) as GeoPoint }),\n };\n const existing = [...annotations];\n const adapter = onCreateAnnotation;\n pendingAnnotation.current = request;\n setAnnotationPending(true);\n setAnnotationNotice(\"\");\n const isCurrent = () =>\n mounted.current &&\n pendingAnnotation.current === request &&\n latest.current.snapshot.id === request.sourceId &&\n latest.current.snapshot.revision === request.revision &&\n !latest.current.sourceError &&\n Boolean(latest.current.onCreateAnnotation);\n try {\n const result = await adapter(request.draft);\n if (!isCurrent()) return;\n const parsed = parseGeoAnnotationResult(result, request.draft, existing);\n if (!parsed.ok) setAnnotationNotice(parsed.error);\n else if (parsed.value.status === \"failed\") setAnnotationNotice(parsed.value.message);\n else {\n setAnnotationNotice(\n `Annotation saved: ${parsed.value.annotation.label}. The list reflects supplied annotations.`,\n );\n setMode(\"inspect\");\n }\n } catch {\n if (isCurrent())\n setAnnotationNotice(\n \"The annotation outcome could not be confirmed. The draft is retained; check the source before trying again.\",\n );\n } finally {\n if (pendingAnnotation.current === request) {\n pendingAnnotation.current = null;\n if (mounted.current) setAnnotationPending(false);\n }\n }\n }\n const meters = measurement.length === 2 ? distanceMeters(measurement[0]!, measurement[1]!) : null;\n const duplicateBookmarks = new Set(\n bookmarks\n .filter(\n (bookmark, index) => bookmarks.findIndex((other) => other.id === bookmark.id) !== index,\n )\n .map((bookmark) => bookmark.id),\n );\n const validAnnotations = useMemo(() => {\n const occurrences = new Map<string, number>();\n for (const annotation of annotations)\n occurrences.set(annotation.id, (occurrences.get(annotation.id) ?? 0) + 1);\n return annotations.filter(\n (annotation) =>\n !getAnnotationDraftError({\n label: annotation.label,\n position: annotation.position,\n sourceId: annotation.sourceId,\n }) &&\n typeof annotation.id === \"string\" &&\n annotation.id.trim() !== \"\" &&\n occurrences.get(annotation.id) === 1,\n );\n }, [annotations]);\n // The map compares what it draws by identity, so this list changes only when its content can.\n const sourceAnnotations = useMemo(\n () => validAnnotations.filter((annotation) => annotation.sourceId === snapshot.id),\n [validAnnotations, snapshot.id],\n );\n return (\n <section\n aria-label=\"Operations map\"\n className=\"not-prose flex min-w-0 flex-col gap-3u font-sans text-oprt-base text-oprt-text\"\n >\n <header className=\"flex min-w-0 flex-wrap items-start justify-between gap-2u border-b border-oprt-border pb-2u\">\n <div className=\"flex min-w-0 flex-col gap-1u\">\n <h2 className=\"m-0 text-oprt-xl font-semibold\">{snapshot.title ?? \"Operations map\"}</h2>\n <p className=\"m-0 text-oprt-text-dim [overflow-wrap:anywhere]\">{snapshot.source}</p>\n </div>\n <div className=\"flex flex-wrap items-center gap-2u\">\n {snapshot.observedAt && (\n <time\n dateTime={snapshot.observedAt}\n className=\"font-mono text-oprt-sm text-oprt-text-dim\"\n >\n {formatUtcInstant(snapshot.observedAt)}\n </time>\n )}\n <Badge\n appearance=\"text\"\n variant={snapshot.availability === \"current\" ? \"success\" : \"warning\"}\n >\n {snapshot.availability}\n </Badge>\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n onClick={() => {\n setMapEnabled(!mapEnabled);\n setFrameReport(null);\n }}\n >\n {mapEnabled ? \"Disable map\" : \"Enable map\"}\n </Button>\n </div>\n </header>\n {snapshot.availability !== \"current\" && (\n <p role=\"status\" className=\"m-0 text-oprt-warning\">\n {snapshot.detail}\n </p>\n )}\n {sourceError ? (\n <p role=\"alert\" className=\"m-0 border border-oprt-error p-2u text-oprt-error\">\n {sourceError}\n </p>\n ) : (\n <>\n {initialView && !isValidGeoView(initialView) && (\n <p role=\"status\" className=\"m-0 text-oprt-warning\">\n The initial map view is invalid. A view fitted to the supplied positions is shown.\n </p>\n )}\n <div className=\"flex min-w-0 flex-wrap items-start gap-3u\">\n <div className=\"flex min-w-0 flex-[2_1_32rem] flex-col gap-2u\">\n <GeoMap\n snapshot={snapshot}\n entities={filtered}\n selectedId={selectedId}\n onSelectedIdChange={selectOnMapStable}\n view={view}\n onViewChange={changeView}\n layers={layers}\n annotations={sourceAnnotations}\n measurement={measurement}\n mode={mode}\n onPoint={acceptPointStable}\n onFrame={reportFrame}\n onInspect={inspectStable}\n {...(tileSource ? { tileSource } : {})}\n disabled={!mapEnabled}\n />\n <MapControls\n zoom={view.zoom}\n bearing={view.bearing}\n disabled={!mapEnabled || snapshot.availability === \"unavailable\"}\n onAction={(action) => setView((old) => moveGeoView(old, action, filtered))}\n />\n <div className=\"flex min-w-0 flex-wrap items-start justify-between gap-2u\">\n <MapScale metersPerPixel={frame?.metersPerPixel ?? null} />\n <p className=\"m-0 font-mono text-oprt-text-dim\">\n Center {formatGeoPosition(view.center)}\n </p>\n </div>\n <p className=\"m-0 text-oprt-text-dim\">\n {mode === \"measure\"\n ? \"Measurement mode: choose two points. A third point begins a new measurement.\"\n : mode === \"annotate\"\n ? \"Annotation mode: choose a point, then review and save it in Annotations.\"\n : \"Choose a marker to preview. Pin it to keep its details beside the map.\"}\n </p>\n {mode !== \"inspect\" && (\n <Button size=\"sm\" variant=\"tertiary\" onClick={() => setMode(\"inspect\")}>\n Return to inspection\n </Button>\n )}\n </div>\n {preview ? (\n <Inspector\n ref={inspectorRef}\n title=\"Selected report\"\n tabIndex={-1}\n className={`${focusStyle} min-w-0 flex-[1_1_21rem]`}\n onClose={() => {\n setFocusRequest({ kind: \"close\" });\n onSelectedIdChange(null);\n }}\n >\n <EntityPreview entity={preview} headingLevel={4} />\n {selected && (\n <div className=\"flex flex-col gap-1u border-t border-oprt-border pt-2u\">\n <h4 className={headingStyle}>Geographic report</h4>\n {fromOtherSource && (\n <p className=\"m-0 text-oprt-warning\">\n This retained report belongs to a different source. Locating it on the\n current source is unavailable.\n </p>\n )}\n <DescriptionList\n layout=\"details\"\n items={[\n ...(reportSource\n ? [\n {\n id: \"source\",\n label: \"Report source\",\n value: (\n <>\n {reportSource.label} ·{\" \"}\n <span className=\"font-mono\">\n {reportSource.id} / {reportSource.revision}\n </span>\n </>\n ),\n },\n ]\n : []),\n { id: \"state\", label: \"Position state\", value: selected.positionState },\n {\n id: \"position\",\n label: \"Position\",\n value: formatGeoPosition(selected.position),\n mono: true,\n },\n {\n id: \"observed\",\n label: \"Position observed\",\n value: <UtcStamp value={selected.observedAt} fallback=\"Not supplied\" />,\n mono: true,\n },\n {\n id: \"uncertainty\",\n label: \"Uncertainty\",\n value:\n typeof selected.uncertaintyMeters === \"number\" &&\n Number.isFinite(selected.uncertaintyMeters) &&\n selected.uncertaintyMeters >= 0\n ? `${selected.uncertaintyMeters} m supplied radius`\n : \"Not supplied\",\n mono: true,\n },\n {\n id: \"coverage\",\n label: \"Coverage\",\n value: selected.coverage?.length\n ? \"Supplied geographic outline\"\n : \"Not supplied\",\n },\n {\n id: \"track\",\n label: \"Track\",\n value: `${selected.track?.length ?? 0} supplied samples`,\n },\n ]}\n />\n {selected.track && selected.track.length > 0 && (\n <details>\n <summary className={summaryStyle}>Reported track samples</summary>\n <ol className=\"m-0 flex flex-col gap-1u pl-3u\">\n {selected.track.map((sample, index) => (\n <li key={index}>\n <span className=\"font-mono\">\n <UtcStamp value={sample.observedAt} fallback=\"Time unknown\" />\n </span>\n <p className=\"m-0 font-mono [overflow-wrap:anywhere]\">\n {formatGeoPosition(sample.position)}\n </p>\n </li>\n ))}\n </ol>\n </details>\n )}\n {selected.coverage && selected.coverage.length > 0 && (\n <details>\n <summary className={summaryStyle}>Coverage outline vertices</summary>\n <ol className=\"m-0 flex flex-col gap-1u pl-3u\">\n {selected.coverage.map((location, index) => (\n <li key={index} className=\"font-mono [overflow-wrap:anywhere]\">\n Vertex {index + 1}: {formatGeoPosition(location)}\n </li>\n ))}\n </ol>\n </details>\n )}\n {currentSelected && !selectedInFilter && (\n <p className=\"m-0 text-oprt-warning\">\n Selected report is outside the current filter.\n </p>\n )}\n {currentSelected &&\n selectedInFilter &&\n frame &&\n frameReport?.query === query &&\n !frame.visibleIds.includes(currentSelected.record.id) && (\n <p className=\"m-0 text-oprt-text-dim\">\n Selected report is outside the map extent or has no plottable position.\n </p>\n )}\n </div>\n )}\n <div className=\"border-t border-oprt-border pt-2u\">\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={\n !mapEnabled ||\n snapshot.availability === \"unavailable\" ||\n !selected ||\n !isGeoPoint(selected.position) ||\n fromOtherSource\n }\n onClick={() => {\n if (selected && !fromOtherSource && isGeoPoint(selected.position))\n setView((old) => ({ ...old, center: selected.position as GeoPoint }));\n }}\n >\n Locate selected report\n </Button>\n </div>\n </Inspector>\n ) : (\n <div className=\"flex flex-col gap-2u border border-oprt-border p-2u\">\n <h3 className={headingStyle}>Selected report</h3>\n <p className=\"m-0 text-oprt-text-dim\">\n Inspect a table record, or choose a map marker and pin its details. Reports with\n unknown positions remains in All records.\n </p>\n <MapLegend items={legendItems} />\n </div>\n )}\n </div>\n <details className=\"min-w-0 border border-oprt-border p-2u\">\n <summary className={summaryStyle}>Layers and legend</summary>\n <div className=\"grid min-w-0 grid-cols-[repeat(auto-fit,minmax(min(100%,24rem),1fr))] gap-3u pt-2u\">\n <LayerControl layers={layers} onLayersChange={(next) => setLayers([...next])} />\n <MapLegend items={legendItems} />\n </div>\n </details>\n <section\n ref={resultsRef}\n role=\"region\"\n aria-label=\"Report results\"\n tabIndex={-1}\n className={`flex min-w-0 flex-col gap-2u border border-oprt-border p-2u ${focusStyle}`}\n >\n <h3 className={headingStyle}>Report results</h3>\n <label htmlFor={`${inputId}-filter`} className={headingStyle}>\n Filter reports\n </label>\n <Input\n id={`${inputId}-filter`}\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n placeholder=\"Name, type or ID\"\n />\n <div role=\"group\" aria-label=\"Record scope\" className=\"flex flex-wrap gap-1u\">\n <Button\n size=\"sm\"\n variant=\"secondary\"\n aria-pressed={scope === \"all\"}\n onClick={() => {\n setScope(\"all\");\n setExtentNotice(\"\");\n }}\n >\n All records\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n aria-pressed={scope === \"extent\"}\n disabled={!frame}\n onClick={() => {\n setScope(\"extent\");\n setExtentNotice(\"\");\n }}\n >\n Map extent\n </Button>\n </div>\n <p role=\"status\" className=\"m-0 text-oprt-text-dim\">\n {displayed.length} shown · {filtered.length} matching · {entities.length} supplied.{\" \"}\n {scope === \"extent\" && frame ? \"Scope: map extent.\" : \"Scope: all records.\"} Counts\n include only the supplied collection.\n </p>\n {extentNotice && (\n <p role=\"status\" className=\"m-0 text-oprt-warning\">\n {extentNotice}\n </p>\n )}\n {!frame && (\n <p className=\"m-0 text-oprt-text-dim\">\n Map extent is not available. All records includes unknown and unsupported positions.\n </p>\n )}\n <ReportResults\n entities={displayed}\n onInspect={inspectStable}\n // In the extent scope the map view is the filter: when panning or zooming changes\n // which reports are listed, the table returns to its first page, as for a new query.\n resetKey={`${scope}|${query}|${\n scope === \"extent\" ? `${displayed.length}:${displayed[0]?.record.id ?? \"\"}` : \"\"\n }`}\n />\n {displayed.length === 0 && (\n <p className=\"m-0\">\n {entities.length === 0\n ? \"No reports were supplied.\"\n : \"No reports match this filter and scope.\"}\n </p>\n )}\n </section>\n <details className=\"min-w-0 border border-oprt-border p-2u\">\n <summary className={summaryStyle}>Coordinates and bookmarks</summary>\n <div className=\"grid min-w-0 grid-cols-[repeat(auto-fit,minmax(min(100%,24rem),1fr))] gap-3u pt-2u\">\n <form\n noValidate\n className=\"flex min-w-0 flex-col gap-2u\"\n onSubmit={(event) => {\n event.preventDefault();\n const location = point(goValue);\n if (goInvalid || !location) {\n setGoError(\n \"Enter valid coordinates before moving the map. Supported latitude is ±85.05112878°.\",\n );\n return;\n }\n setGoError(\"\");\n setView((old) => ({ ...old, center: location }));\n }}\n >\n <CoordinateInput\n label=\"Go to coordinates\"\n value={goValue}\n onValueChange={setGoValue}\n onValidationChange={setGoInvalid}\n description=\"WGS84 decimal degrees. Map latitude is limited to ±85.05112878°.\"\n />\n <Button type=\"submit\" size=\"sm\">\n Go to location\n </Button>\n {goError && (\n <p role=\"alert\" className=\"m-0 text-oprt-error\">\n {goError}\n </p>\n )}\n </form>\n <div className=\"flex min-w-0 flex-col gap-2u\">\n <label htmlFor={`${inputId}-bookmark`} className={headingStyle}>\n Bookmark name\n </label>\n <Input\n id={`${inputId}-bookmark`}\n maxLength={80}\n value={bookmarkName}\n onChange={(event) => setBookmarkName(event.target.value)}\n />\n <Button size=\"sm\" onClick={saveBookmark}>\n Save bookmark\n </Button>\n {bookmarkNotice && (\n <p role=\"status\" className=\"m-0\">\n {bookmarkNotice}\n </p>\n )}\n <ul className=\"m-0 flex list-none flex-col gap-2u p-0\">\n {bookmarks.map((bookmark, index) => {\n const parsed = parseGeoBookmark(bookmark);\n const valid = parsed.ok && !duplicateBookmarks.has(bookmark.id);\n const name =\n typeof bookmark.name === \"string\" ? bookmark.name : \"Invalid bookmark\";\n return (\n <li\n key={valid ? `bookmark:${bookmark.id}` : `invalid:${index}`}\n className=\"flex min-w-0 flex-wrap items-start gap-1u\"\n >\n <Button\n size=\"sm\"\n variant=\"secondary\"\n className=\"h-auto! min-h-[var(--oprt-height-sm)] min-w-0 max-w-full whitespace-normal [overflow-wrap:anywhere]\"\n aria-label={`Restore ${name}`}\n disabled={!valid}\n onClick={() => {\n if (parsed.ok && valid) {\n setView(parsed.value.view);\n setLayers([...parsed.value.layers]);\n setBookmarkNotice(`Restored ${parsed.value.name}.`);\n }\n }}\n >\n {name}\n </Button>\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n disabled={duplicateBookmarks.has(bookmark.id)}\n aria-label={`Remove bookmark ${name}`}\n onClick={() => {\n try {\n onBookmarksChange(\n bookmarks.filter((item) => item.id !== bookmark.id),\n );\n setBookmarkNotice(\n \"Bookmark removal requested. The list reflects the supplied views.\",\n );\n } catch {\n setBookmarkNotice(\"The bookmark could not be removed.\");\n }\n }}\n >\n Remove\n </Button>\n {!valid && (\n <p className=\"m-0 text-oprt-error\">\n This bookmark has invalid or ambiguous saved preferences.\n </p>\n )}\n </li>\n );\n })}\n </ul>\n {bookmarks.length === 0 && (\n <p className=\"m-0 text-oprt-text-dim\">No saved views supplied.</p>\n )}\n </div>\n </div>\n </details>\n <details className=\"min-w-0 border border-oprt-border p-2u\">\n <summary className={summaryStyle}>Measurement</summary>\n <div className=\"flex min-w-0 flex-col gap-2u pt-2u\">\n <p className=\"m-0 text-oprt-text-dim\">\n Approximate great-circle distance between two WGS84 points. Terrain, routing and\n height are not included.\n </p>\n <div className=\"flex flex-wrap gap-1u\">\n <Button\n size=\"sm\"\n aria-pressed={mode === \"measure\"}\n onClick={() => setMode(mode === \"measure\" ? \"inspect\" : \"measure\")}\n >\n Measure on map\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n onClick={() => {\n setMeasurement([]);\n setEndpoints([blank, blank]);\n setEndpointInvalid([false, false]);\n setMeasurementKey((old) => old + 1);\n setMeasurementError(\"\");\n setMode(\"inspect\");\n }}\n >\n Clear measurement\n </Button>\n </div>\n <div className=\"grid min-w-0 grid-cols-[repeat(auto-fit,minmax(min(100%,24rem),1fr))] gap-2u\">\n {endpoints.map((value, index) => (\n <CoordinateInput\n key={`${measurementKey}-${index}`}\n label={`Measurement point ${index + 1}`}\n value={value}\n onValueChange={(next) =>\n setEndpoints((old) =>\n old.map((item, position) => (position === index ? next : item)),\n )\n }\n onValidationChange={(invalid) =>\n setEndpointInvalid((old) =>\n old[index] === invalid\n ? old\n : old.map((item, position) => (position === index ? invalid : item)),\n )\n }\n />\n ))}\n </div>\n <Button\n size=\"sm\"\n onClick={() => {\n const first = point(endpoints[0]!);\n const second = point(endpoints[1]!);\n if (endpointInvalid.some(Boolean) || !first || !second) {\n setMeasurementError(\n \"Complete both valid measurement coordinates within the supported map latitude range.\",\n );\n return;\n }\n setMeasurement([first, second]);\n setMeasurementError(\"\");\n }}\n >\n Apply measurement coordinates\n </Button>\n {measurementError && (\n <p role=\"alert\" className=\"m-0 text-oprt-error\">\n {measurementError}\n </p>\n )}\n {measurement.map((location, index) => (\n <p key={index} className=\"m-0 font-mono\">\n Point {index + 1}: {formatGeoPosition(location)}\n </p>\n ))}\n <p role=\"status\" className=\"m-0 font-mono\">\n {meters === null\n ? \"Choose two points to measure.\"\n : `Approximate distance: ${meters < 1000 ? `${Math.round(meters)} m` : `${(meters / 1000).toFixed(2)} km`}`}\n </p>\n </div>\n </details>\n <details className=\"min-w-0 border border-oprt-border p-2u\">\n <summary className={summaryStyle}>Annotations</summary>\n <div className=\"grid min-w-0 grid-cols-[repeat(auto-fit,minmax(min(100%,24rem),1fr))] gap-3u pt-2u\">\n <form\n noValidate\n className=\"flex min-w-0 flex-col gap-2u\"\n onSubmit={(event) => {\n event.preventDefault();\n void saveAnnotation();\n }}\n >\n <p className=\"m-0 text-oprt-text-dim\">\n Source:{\" \"}\n <span className=\"font-mono\">\n {snapshot.id} / {snapshot.revision}\n </span>\n . Review the label and location before saving.\n </p>\n <label htmlFor={`${inputId}-annotation`} className={headingStyle}>\n Annotation label\n </label>\n <Input\n id={`${inputId}-annotation`}\n maxLength={120}\n value={annotationLabel}\n disabled={!onCreateAnnotation || annotationPending}\n onChange={(event) => {\n setAnnotationLabel(event.target.value);\n setAnnotationNotice(\"\");\n }}\n />\n <CoordinateInput\n key={annotationKey}\n label=\"Annotation position\"\n value={annotationValue}\n onValueChange={setAnnotationValue}\n onValidationChange={setAnnotationInvalid}\n disabled={!onCreateAnnotation || annotationPending}\n />\n <div className=\"flex flex-wrap gap-1u\">\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={\n !onCreateAnnotation ||\n annotationPending ||\n !currentSelected ||\n !isGeoPoint(currentSelected.position)\n }\n onClick={() => {\n if (currentSelected && isGeoPoint(currentSelected.position)) {\n setAnnotationValue(coordinate(currentSelected.position));\n setAnnotationInvalid(false);\n setAnnotationKey((old) => old + 1);\n }\n }}\n >\n Use selected position\n </Button>\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={!onCreateAnnotation || annotationPending}\n aria-pressed={mode === \"annotate\"}\n onClick={() => setMode(mode === \"annotate\" ? \"inspect\" : \"annotate\")}\n >\n Annotate on map\n </Button>\n <Button\n size=\"sm\"\n type=\"submit\"\n disabled={!onCreateAnnotation || annotationPending}\n >\n {annotationPending ? \"Saving annotation…\" : \"Save annotation\"}\n </Button>\n </div>\n {!onCreateAnnotation && (\n <p className=\"m-0 text-oprt-text-dim\">\n Annotation creation is unavailable; no create adapter was supplied.\n </p>\n )}\n {annotationNotice && (\n <p role=\"status\" className=\"m-0\">\n {annotationNotice}\n </p>\n )}\n </form>\n <div className=\"flex min-w-0 flex-col gap-2u\">\n <h3 className={headingStyle}>Supplied annotations</h3>\n {validAnnotations.length !== annotations.length && (\n <p role=\"alert\" className=\"m-0 text-oprt-error\">\n Some annotations have invalid or duplicate identities or positions and cannot be\n shown safely.\n </p>\n )}\n {validAnnotations.length > sourceAnnotations.length && (\n <p className=\"m-0 text-oprt-text-dim\">\n Annotations for another source are outside this view.\n </p>\n )}\n <ul className=\"m-0 flex list-none flex-col gap-2u p-0\">\n {sourceAnnotations.map((annotation) => (\n <li\n key={annotation.id}\n className=\"flex min-w-0 flex-col gap-1u border border-oprt-border p-2u\"\n >\n <p className=\"m-0 font-medium [overflow-wrap:anywhere]\">{annotation.label}</p>\n <p className=\"m-0 font-mono [overflow-wrap:anywhere]\">\n {formatGeoPosition(annotation.position)}\n </p>\n <p className=\"m-0 font-mono [overflow-wrap:anywhere]\">\n <UtcStamp value={annotation.createdAt} fallback=\"Time unknown\" />\n </p>\n {onRemoveAnnotation && (\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n aria-label={`Remove annotation ${annotation.label}`}\n onClick={() => {\n try {\n onRemoveAnnotation(annotation.id);\n setAnnotationNotice(\n \"Annotation removal requested. The list reflects the supplied source.\",\n );\n } catch {\n setAnnotationNotice(\"The annotation could not be removed.\");\n }\n }}\n >\n Remove\n </Button>\n )}\n </li>\n ))}\n </ul>\n {sourceAnnotations.length === 0 && (\n <p className=\"m-0 text-oprt-text-dim\">\n No saved annotations supplied for this source.\n </p>\n )}\n </div>\n </div>\n </details>\n </>\n )}\n </section>\n );\n}\n","patterns/geospatial-workspace/index.ts":"export { GeospatialWorkspace as OperationsMap, GeospatialWorkspace } from \"./geospatial-workspace\";\nexport { OperationsMapDemo, GeospatialWorkspaceDemo } from \"./demo\";\nexport { GeoMap } from \"./geo-map\";\nexport {\n initialGeoLayers,\n initialGeoView,\n utilitySnapshot,\n utilityEntities,\n utilitySite,\n} from \"./fixtures\";\nexport type {\n GeospatialWorkspaceProps,\n GeospatialWorkspaceProps as OperationsMapProps,\n GeoMapProps,\n GeoPoint,\n GeoView,\n GeoEntity,\n GeoSnapshot,\n GeoAnnotation,\n GeoAnnotationDraft,\n GeoAnnotationResult,\n GeoBookmark,\n GeoTileSource,\n} from \"./types\";\n","patterns/geospatial-workspace/military-fixture.ts":"import { utilityEntities, utilitySite, fixtureObservedAt } from \"./fixtures\";\nimport type { GeoSnapshot } from \"./types\";\n\nconst records = [\n [\n \"RELAY-01\",\n \"Relay team Alpha\",\n \"Communications\",\n \"Available\",\n \"success\",\n \"North relay\",\n \"Signals section\",\n \"Primary relay established. Battery change scheduled for 12:30 UTC.\",\n ],\n [\n \"GEN-01\",\n \"Relay generator 01\",\n \"Power\",\n \"Inspection due\",\n \"warning\",\n \"North relay\",\n \"Support section\",\n \"Fuel-system inspection due before the next service window.\",\n ],\n [\n \"TEAM-02\",\n \"Survey team Bravo\",\n \"Personnel\",\n \"On station\",\n \"success\",\n \"East service road\",\n \"Engineering section\",\n \"Site survey in progress. Two personnel reported at the east service road.\",\n ],\n [\n \"TEAM-03\",\n \"Survey team Charlie\",\n \"Personnel\",\n \"On station\",\n \"success\",\n \"East service road\",\n \"Engineering section\",\n \"Cable-route survey in progress alongside team Bravo.\",\n ],\n [\n \"CP-01\",\n \"Logistics command post\",\n \"Command post\",\n \"Available\",\n \"success\",\n \"Operations compound\",\n \"Base support\",\n \"Vehicle dispatch and equipment coordination for the current exercise.\",\n ],\n [\n \"OBS-03\",\n \"Gate observation post\",\n \"Observation\",\n \"Available\",\n \"success\",\n \"South access\",\n \"Base support\",\n \"Reported observation sector covers the southern access route.\",\n ],\n [\n \"TRUCK-07\",\n \"Support vehicle 07\",\n \"Ground vehicle\",\n \"Last-known available\",\n \"neutral\",\n \"South service road\",\n \"Transport section\",\n \"Last reported at 11:42 UTC. Current movement has not been confirmed.\",\n ],\n [\n \"RADIO-04\",\n \"Reserve radio kit 04\",\n \"Communications\",\n \"Condition not reported\",\n \"neutral\",\n \"Unassigned\",\n \"Signals section\",\n \"Held in the equipment register; a geographic position has not been reported.\",\n ],\n [\n \"RELAY-09\",\n \"Remote relay team\",\n \"Communications\",\n \"Inspection due\",\n \"warning\",\n \"Eastern relay site\",\n \"Signals section\",\n \"Remote site outside the initial view. Equipment inspection is due at 13:00 UTC.\",\n ],\n] as const;\n\n/** Fictional base-support exercise; positions are local fixture geometry. */\nexport const militaryGeoSnapshot: GeoSnapshot = {\n id: \"camp-meridian-support\",\n title: \"Camp Meridian · base support\",\n revision: \"exercise-12\",\n source: \"Fictional exercise · Base support network\",\n observedAt: fixtureObservedAt,\n availability: \"current\",\n entities: utilityEntities.map((entity, i) => {\n const [id, name, typeLabel, label, variant, area, owner, summary] = records[i]!;\n return {\n ...entity,\n record: {\n ...entity.record,\n id,\n name,\n typeLabel,\n summary,\n condition: { label, variant },\n quality: {\n ...entity.record.quality,\n source: \"Exercise Meridian / base support\",\n detail:\n i === 6\n ? \"Last report 11:42 UTC. Reported position uncertainty: 65 m.\"\n : entity.position === null\n ? \"Listed in the 12:00 UTC register. A position has not been reported.\"\n : \"Reported in the 12:00 UTC exercise snapshot.\",\n },\n facts: [\n { id: \"area\", label: \"Area\", value: area },\n { id: \"owner\", label: \"Assigned to\", value: owner },\n { id: \"reference\", label: \"Reference\", value: id, mono: true },\n ],\n },\n };\n }),\n site: utilitySite.map((site) => ({\n ...site,\n label:\n (\n {\n \"site-boundary\": \"Camp Meridian\",\n \"pump-house\": \"North relay\",\n \"control-room\": \"Gate post\",\n workshop: \"Vehicle workshop\",\n \"power-house\": \"Operations\",\n stores: \"Supply store\",\n \"treatment-a\": \"Training compound A\",\n \"treatment-b\": \"Training compound B\",\n } as Record<string, string>\n )[site.id] ?? site.label,\n })),\n};\n","patterns/geospatial-workspace/renderer-data.ts":"import { getUncertaintyFootprint, isGeoPoint } from \"./geometry\";\nimport type { EngineState } from \"./engine-contract\";\n\n/** The inputs the issues depend on. The view, selection and layers do not change them. */\nexport type MapDataInput = Pick<\n EngineState,\n \"snapshot\" | \"entities\" | \"annotations\" | \"measurement\"\n>;\n\nexport function mapDataIssues(state: MapDataInput): {\n duplicates: boolean;\n invalid: number;\n unknown: number;\n uncertainty: number;\n} {\n const duplicate = (ids: readonly string[]) =>\n ids.some((id) => typeof id !== \"string\" || !id.trim()) || new Set(ids).size !== ids.length;\n const duplicates =\n duplicate(state.entities.map((entity) => entity.record.id)) ||\n duplicate(state.snapshot.entities.map((entity) => entity.record.id)) ||\n duplicate(state.snapshot.site.map((feature) => feature.id)) ||\n duplicate(state.annotations.map((annotation) => annotation.id));\n let invalid = 0,\n unknown = 0,\n uncertainty = 0;\n for (const entity of state.entities) {\n if (entity.position === null) unknown++;\n else if (!isGeoPoint(entity.position)) invalid++;\n if (entity.coverage && (entity.coverage.length < 3 || !entity.coverage.every(isGeoPoint)))\n invalid++;\n if (\n entity.track &&\n (entity.track.length < 2 || !entity.track.every((point) => isGeoPoint(point.position)))\n )\n invalid++;\n if (\n entity.uncertaintyMeters !== undefined &&\n (!Number.isFinite(entity.uncertaintyMeters) ||\n entity.uncertaintyMeters < 0 ||\n (isGeoPoint(entity.position) &&\n getUncertaintyFootprint(entity.position, entity.uncertaintyMeters) === null))\n ) {\n invalid++;\n uncertainty++;\n }\n }\n for (const feature of state.snapshot.site)\n if (\n feature.points.length < (feature.kind === \"road\" ? 2 : 3) ||\n !feature.points.every(isGeoPoint)\n )\n invalid++;\n for (const annotation of state.annotations) if (!isGeoPoint(annotation.position)) invalid++;\n for (const point of state.measurement) if (!isGeoPoint(point)) invalid++;\n return { duplicates, invalid, unknown, uncertainty };\n}\n","patterns/geospatial-workspace/report-results.tsx":"\"use client\";\n\nimport { memo, useMemo, useState } from \"react\";\nimport { Badge, Button, DataTable, type DataTableColumn } from \"@oprt/react\";\nimport { formatGeoPosition } from \"./workspace-state\";\nimport type { GeoEntity } from \"./types\";\n\n/** How many reports are in the table at once. A page of this size re-renders in a frame. */\nexport const REPORT_PAGE_SIZE = 25;\n\nconst rowId = (row: Record<string, unknown>) => String(row.id);\n\ninterface ReportResultsProps {\n /** The reports to list. Keep it the same array until the list itself changes. */\n entities: readonly GeoEntity[];\n /** Stable: the table does not draw again for a new function with the same job. */\n onInspect: (id: string) => void;\n /** A new value returns the table to its first page, as a change of filter does. */\n resetKey: string;\n}\n\n/**\n * The report table, one page at a time. It is its own memoized component so that a map frame,\n * which re-renders the workspace around it about every animation frame, costs it nothing.\n */\nexport const ReportResults = memo(function ReportResults({\n entities,\n onInspect,\n resetKey,\n}: ReportResultsProps) {\n const [requested, setRequested] = useState({ resetKey, page: 1 });\n const pageCount = Math.max(1, Math.ceil(entities.length / REPORT_PAGE_SIZE));\n const page = Math.min(requested.resetKey === resetKey ? requested.page : 1, pageCount);\n const columns = useMemo<DataTableColumn[]>(\n () => [\n { key: \"name\", label: \"Reports\" },\n { key: \"id\", label: \"ID\", as: \"code\" },\n {\n key: \"condition\",\n label: \"Condition\",\n render: (_value, row) => {\n const condition = (row.entity as GeoEntity).record.condition;\n return condition ? (\n <Badge appearance=\"text\" indicator=\"none\" variant={condition.variant ?? \"neutral\"}>\n {condition.label}\n </Badge>\n ) : (\n \"Not supplied\"\n );\n },\n },\n {\n key: \"quality\",\n label: \"Data quality\",\n render: (_value, row) => (\n <Badge appearance=\"text\" indicator=\"none\">\n {(row.entity as GeoEntity).record.quality.state ?? \"unknown\"}\n </Badge>\n ),\n },\n {\n key: \"position\",\n label: \"Position\",\n render: (_value, row) => {\n const entity = row.entity as GeoEntity;\n return (\n <div className=\"flex min-w-0 flex-col gap-1u\">\n <span>{entity.positionState}</span>\n <span className=\"font-mono [overflow-wrap:anywhere]\">\n {formatGeoPosition(entity.position)}\n </span>\n </div>\n );\n },\n },\n {\n key: \"inspect\",\n label: \"Details\",\n render: (_value, row) => (\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n aria-label={`Inspect ${row.name}`}\n onClick={() => onInspect(String(row.id))}\n >\n Inspect\n </Button>\n ),\n },\n ],\n [onInspect],\n );\n const first = (page - 1) * REPORT_PAGE_SIZE;\n const rows = useMemo(\n () =>\n entities.slice(first, first + REPORT_PAGE_SIZE).map((entity) => ({\n id: entity.record.id,\n name: entity.record.name,\n entity,\n })),\n [entities, first],\n );\n const move = (next: number) => {\n if (next >= 1 && next <= pageCount) setRequested({ resetKey, page: next });\n };\n // A button that has just taken the reader to the first or last page stays in place and says it\n // is unavailable (aria-disabled), so keyboard focus is not dropped to the page.\n const pagerButton = (label: string, target: number, unavailable: boolean) => (\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n aria-disabled={unavailable || undefined}\n data-disabled={unavailable ? \"true\" : undefined}\n onClick={() => move(target)}\n >\n {label}\n </Button>\n );\n return (\n <>\n <div className=\"min-w-0 overflow-x-auto\">\n <DataTable\n className=\"min-w-[64rem]\"\n columns={columns}\n data={rows}\n getRowId={rowId}\n caption=\"Supplied reports and geographic positions\"\n {...(pageCount > 1 ? { rowIndexOffset: first, totalRowCount: entities.length } : {})}\n />\n </div>\n {pageCount > 1 && (\n <div role=\"group\" aria-label=\"Report pages\" className=\"flex flex-wrap items-center gap-2u\">\n {pagerButton(\"Previous page\", page - 1, page <= 1)}\n <span className=\"font-mono tabular-nums\" role=\"status\">\n Reports {first + 1}–{Math.min(first + REPORT_PAGE_SIZE, entities.length)} of{\" \"}\n {entities.length}\n </span>\n {pagerButton(\"Next page\", page + 1, page >= pageCount)}\n </div>\n )}\n </>\n );\n});\n","patterns/geospatial-workspace/types.ts":"import type { EntityPreviewRecord, LayerControlItem } from \"@oprt/react\";\n\n/** WGS84 longitude, latitude in decimal degrees. */\nexport type GeoPoint = readonly [number, number];\nexport interface GeoView {\n center: GeoPoint;\n zoom: number;\n bearing: number;\n}\nexport interface GeoEntity {\n record: EntityPreviewRecord;\n position: GeoPoint | null;\n positionState: \"reported\" | \"last-known\" | \"unknown\";\n observedAt: string | null;\n uncertaintyMeters?: number;\n coverage?: readonly GeoPoint[];\n track?: readonly { position: GeoPoint; observedAt: string }[];\n}\nexport interface GeoSiteFeature {\n id: string;\n label: string;\n kind: \"building\" | \"road\" | \"water\" | \"boundary\";\n points: readonly GeoPoint[];\n}\nexport interface GeoSnapshot {\n id: string;\n title?: string;\n revision: string;\n source: string;\n observedAt: string | null;\n availability: \"current\" | \"partial\" | \"offline\" | \"unavailable\";\n detail?: string;\n entities: readonly GeoEntity[];\n site: readonly GeoSiteFeature[];\n}\nexport interface GeoAnnotation {\n id: string;\n label: string;\n position: GeoPoint;\n sourceId: string;\n createdAt: string;\n}\nexport interface GeoAnnotationDraft {\n label: string;\n position: GeoPoint;\n sourceId: string;\n}\nexport type GeoAnnotationResult =\n | { status: \"saved\"; annotation: GeoAnnotation }\n | { status: \"failed\"; message: string };\nexport interface GeoBookmark {\n id: string;\n name: string;\n view: GeoView;\n layers: readonly LayerControlItem[];\n}\nexport interface GeoMarkerGroup {\n id: string;\n ids: readonly string[];\n point: GeoPoint;\n pixel: readonly [number, number];\n}\nexport interface GeoFrame {\n view: GeoView;\n visibleIds: readonly string[];\n groups: readonly GeoMarkerGroup[];\n selectedPixel: readonly [number, number] | null;\n metersPerPixel: number | null;\n}\nexport interface GeoTileSource {\n url: string;\n attribution: string;\n}\nexport type GeoMapMode = \"inspect\" | \"measure\" | \"annotate\";\nexport interface GeoMapProps {\n snapshot: GeoSnapshot;\n entities: readonly GeoEntity[];\n selectedId: string | null;\n onSelectedIdChange: (id: string | null) => void;\n view: GeoView;\n onViewChange: (view: GeoView) => void;\n layers: readonly LayerControlItem[];\n annotations: readonly GeoAnnotation[];\n measurement: readonly GeoPoint[];\n mode: GeoMapMode;\n onPoint: (point: GeoPoint) => void;\n onFrame: (frame: GeoFrame | null) => void;\n onInspect: (id: string) => void;\n tileSource?: GeoTileSource;\n disabled?: boolean;\n className?: string;\n}\nexport interface GeospatialWorkspaceProps {\n snapshot: GeoSnapshot;\n selectedId: string | null;\n onSelectedIdChange: (id: string | null) => void;\n annotations: readonly GeoAnnotation[];\n onCreateAnnotation?: (draft: GeoAnnotationDraft) => Promise<GeoAnnotationResult>;\n onRemoveAnnotation?: (id: string) => void;\n bookmarks: readonly GeoBookmark[];\n onBookmarksChange: (bookmarks: readonly GeoBookmark[]) => void;\n initialView?: GeoView;\n tileSource?: GeoTileSource;\n}\n","patterns/geospatial-workspace/utc-stamp.tsx":"\"use client\";\n\nimport { formatUtcInstant } from \"../operational-domain\";\n\n/**\n * A supplied instant as \"YYYY-MM-DD HH:MM:SS UTC\" in a <time> element. The date and the time with\n * its zone are separate runs, so a narrow column breaks between them and never inside one. Text\n * that is not an exact instant is shown as supplied, so a malformed date is never reinterpreted.\n */\nexport function UtcStamp({\n value,\n fallback = \"Time unknown\",\n}: {\n value: string | null | undefined;\n fallback?: string;\n}) {\n if (!value) return <>{fallback}</>;\n const text = formatUtcInstant(value);\n if (text === value) return <>{value}</>;\n return (\n <time dateTime={new Date(value).toISOString()}>\n <span className=\"inline-block\">{text.slice(0, 10)}</span>{\" \"}\n <span className=\"inline-block\">{text.slice(11)}</span>\n </time>\n );\n}\n","patterns/geospatial-workspace/workspace-state.ts":"import type { LayerControlItem } from \"@oprt/react\";\nimport type {\n GeoAnnotation,\n GeoAnnotationDraft,\n GeoAnnotationResult,\n GeoBookmark,\n GeoEntity,\n GeoView,\n} from \"./types\";\nimport { isGeoPoint } from \"./geometry\";\nexport const defaultGeoLayers: readonly LayerControlItem[] = Object.freeze(\n [\n { id: \"annotations\", label: \"Annotations\", visible: true, opacity: 1 },\n { id: \"measurement\", label: \"Measurement\", visible: true, opacity: 1 },\n { id: \"entities\", label: \"Equipment\", visible: true, opacity: 1 },\n { id: \"tracks\", label: \"Reported tracks\", visible: true, opacity: 0.8 },\n { id: \"uncertainty\", label: \"Position uncertainty\", visible: true, opacity: 0.35 },\n { id: \"coverage\", label: \"Supplied coverage\", visible: true, opacity: 0.3 },\n { id: \"site\", label: \"Site plan\", visible: true, opacity: 0.7 },\n ].map((layer) => Object.freeze(layer)),\n);\nexport type WorkspaceResult<T> = { ok: true; value: T } | { ok: false; error: string };\nfunction object(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction exact(\n value: Record<string, unknown>,\n fields: readonly string[],\n optional: readonly string[] = [],\n): boolean {\n return (\n fields.every((field) => Object.hasOwn(value, field)) &&\n Reflect.ownKeys(value).every(\n (key) => typeof key === \"string\" && (fields.includes(key) || optional.includes(key)),\n )\n );\n}\nfunction text(value: unknown, max = 256): value is string {\n return (\n typeof value === \"string\" &&\n value.trim().length > 0 &&\n value.length <= max &&\n !Array.from(value).some(\n (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127,\n )\n );\n}\nfunction instant(value: unknown): value is string {\n if (\n typeof value !== \"string\" ||\n !/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}(?:\\.\\d{1,3})?(?:Z|[+-]\\d{2}:\\d{2})$/.test(value) ||\n !Number.isFinite(Date.parse(value))\n )\n return false;\n const date = new Date(`${value.slice(0, 10)}T00:00:00Z`);\n return date.toISOString().slice(0, 10) === value.slice(0, 10);\n}\nconst fail = (error: string): { ok: false; error: string } => ({ ok: false, error });\nconst layerIds = defaultGeoLayers.map((layer) => layer.id);\n\n/** Invalid geography stays in the table; ambiguous source identity blocks interaction. */\nexport function getGeoSnapshotError(value: unknown): string | null {\n try {\n if (\n !object(value) ||\n !text(value.id) ||\n !text(value.revision) ||\n !text(value.source) ||\n typeof value.availability !== \"string\" ||\n ![\"current\", \"partial\", \"offline\", \"unavailable\"].includes(value.availability) ||\n !Array.isArray(value.entities) ||\n value.entities.length > 5000 ||\n !Array.isArray(value.site)\n )\n return \"The supplied geographic snapshot is malformed or exceeds 5,000 equipment records.\";\n const ids = new Set<string>();\n for (const item of value.entities) {\n if (\n !object(item) ||\n !object(item.record) ||\n !text(item.record.id) ||\n !text(item.record.name) ||\n !Array.isArray(item.record.facts) ||\n !object(item.record.quality)\n )\n return \"Every equipment record needs a readable unique identity and complete preview content.\";\n if (ids.has(item.record.id))\n return \"Equipment IDs must be unique. Ambiguous records cannot be selected.\";\n ids.add(item.record.id);\n }\n return null;\n } catch {\n return \"The supplied geographic snapshot could not be read.\";\n }\n}\nexport function isValidGeoView(value: unknown): value is GeoView {\n return (\n object(value) &&\n exact(value, [\"center\", \"zoom\", \"bearing\"]) &&\n isGeoPoint(value.center) &&\n typeof value.zoom === \"number\" &&\n Number.isFinite(value.zoom) &&\n value.zoom >= 2 &&\n value.zoom <= 20 &&\n typeof value.bearing === \"number\" &&\n Number.isFinite(value.bearing) &&\n value.bearing >= 0 &&\n value.bearing < 360\n );\n}\n/** The IDs a map frame may mention: the entities that have a plottable position. */\nexport function geoKnownIds(entities: readonly GeoEntity[]): ReadonlySet<string> {\n return new Set(\n entities.filter((entity) => isGeoPoint(entity.position)).map((entity) => entity.record.id),\n );\n}\n/**\n * `known` may be supplied when the caller keeps it between frames; it must be geoKnownIds of the\n * same entities, which are then only counted.\n */\nexport function getGeoFrameError(\n value: unknown,\n entities: readonly GeoEntity[],\n known: ReadonlySet<string> = geoKnownIds(entities),\n): string | null {\n try {\n if (\n !object(value) ||\n !isValidGeoView(value.view) ||\n !Array.isArray(value.visibleIds) ||\n !Array.isArray(value.groups)\n )\n return \"The renderer returned an invalid map frame.\";\n const validIds = (ids: unknown): ids is string[] =>\n Array.isArray(ids) &&\n ids.length <= entities.length &&\n ids.every((id) => typeof id === \"string\" && known.has(id)) &&\n new Set(ids).size === ids.length;\n const pixel = (point: unknown) =>\n Array.isArray(point) &&\n point.length === 2 &&\n point.every((axis) => typeof axis === \"number\" && Number.isFinite(axis));\n if (\n !validIds(value.visibleIds) ||\n (value.selectedPixel !== null && !pixel(value.selectedPixel)) ||\n (value.metersPerPixel !== null &&\n (typeof value.metersPerPixel !== \"number\" ||\n !Number.isFinite(value.metersPerPixel) ||\n value.metersPerPixel <= 0))\n )\n return \"Map extent or scale is unavailable because the renderer returned invalid coordinates or identities.\";\n const groupIds = new Set<string>();\n for (const group of value.groups) {\n if (\n !object(group) ||\n !text(group.id) ||\n groupIds.has(group.id) ||\n !validIds(group.ids) ||\n group.ids.length === 0 ||\n !isGeoPoint(group.point) ||\n !pixel(group.pixel)\n )\n return \"Map groups contain invalid or ambiguous equipment identities.\";\n groupIds.add(group.id);\n }\n return null;\n } catch {\n return \"The map frame could not be read.\";\n }\n}\nexport function filterGeoEntities(entities: readonly GeoEntity[], query: string): GeoEntity[] {\n const term = query.trim().toLowerCase();\n return entities.filter(({ record }) =>\n `${record.id} ${record.name} ${record.typeLabel ?? \"\"}`.toLowerCase().includes(term),\n );\n}\nexport function retainGeoEntity(entity: GeoEntity): GeoEntity {\n return Object.freeze({\n ...entity,\n record: Object.freeze({\n ...entity.record,\n quality: Object.freeze({ ...entity.record.quality }),\n facts: Object.freeze(entity.record.facts.map((fact) => Object.freeze({ ...fact }))),\n ...(entity.record.condition\n ? { condition: Object.freeze({ ...entity.record.condition }) }\n : {}),\n }),\n position: entity.position\n ? (Object.freeze([...entity.position]) as typeof entity.position)\n : null,\n ...(entity.coverage\n ? {\n coverage: Object.freeze(\n entity.coverage.map((point) => Object.freeze([...point]) as typeof point),\n ),\n }\n : {}),\n ...(entity.track\n ? {\n track: Object.freeze(\n entity.track.map((sample) =>\n Object.freeze({\n ...sample,\n position: Object.freeze([...sample.position]) as typeof sample.position,\n }),\n ),\n ),\n }\n : {}),\n });\n}\nexport function retainedEntityPreview(entity: GeoEntity, missing: boolean) {\n return missing\n ? {\n ...entity.record,\n quality: {\n ...entity.record.quality,\n state: \"unavailable\" as const,\n detail: \"Removed from the current snapshot; showing the retained last-known report.\",\n },\n }\n : entity.record;\n}\nexport function formatGeoPosition(position: unknown): string {\n if (position === null) return \"Position unknown\";\n if (!isGeoPoint(position)) return \"Position invalid or outside the supported map latitude range\";\n return `${position[1].toFixed(6)}°, ${position[0].toFixed(6)}° (latitude, longitude)`;\n}\nexport function parseGeoBookmark(value: unknown): WorkspaceResult<GeoBookmark> {\n try {\n if (\n !object(value) ||\n !exact(value, [\"id\", \"name\", \"view\", \"layers\"]) ||\n !text(value.id) ||\n !text(value.name, 80) ||\n !isValidGeoView(value.view) ||\n !Array.isArray(value.layers) ||\n value.layers.length !== layerIds.length\n )\n return fail(\"Bookmark name, view or complete layer preferences are invalid.\");\n const ids = new Set<string>();\n for (const layer of value.layers) {\n if (\n !object(layer) ||\n !exact(layer, [\"id\", \"label\", \"visible\", \"opacity\"], [\"description\", \"disabled\"]) ||\n typeof layer.id !== \"string\" ||\n !layerIds.includes(layer.id) ||\n ids.has(layer.id) ||\n !text(layer.label, 120) ||\n typeof layer.visible !== \"boolean\" ||\n typeof layer.opacity !== \"number\" ||\n !Number.isFinite(layer.opacity) ||\n layer.opacity < 0 ||\n layer.opacity > 1 ||\n (layer.disabled !== undefined && typeof layer.disabled !== \"boolean\") ||\n (layer.description !== undefined && !text(layer.description, 512))\n )\n return fail(\n \"Bookmark layers must contain each supported identity once, with valid visibility and opacity.\",\n );\n ids.add(layer.id);\n }\n const bookmark = value as unknown as GeoBookmark;\n return {\n ok: true,\n value: Object.freeze({\n id: bookmark.id,\n name: bookmark.name,\n view: Object.freeze({\n ...bookmark.view,\n center: Object.freeze([...bookmark.view.center]) as typeof bookmark.view.center,\n }),\n layers: Object.freeze(bookmark.layers.map((layer) => Object.freeze({ ...layer }))),\n }),\n };\n } catch {\n return fail(\"The bookmark could not be read.\");\n }\n}\nexport function getAnnotationDraftError(value: unknown): string | null {\n return object(value) &&\n exact(value, [\"label\", \"position\", \"sourceId\"]) &&\n text(value.label, 120) &&\n text(value.sourceId) &&\n isGeoPoint(value.position)\n ? null\n : \"Enter a label of 1–120 characters and a valid location within ±85.05112878° latitude and ±180° longitude.\";\n}\nexport function parseGeoAnnotationResult(\n value: unknown,\n draft: GeoAnnotationDraft,\n existing: readonly GeoAnnotation[],\n): WorkspaceResult<GeoAnnotationResult> {\n try {\n if (!object(value))\n return fail(\"The annotation service returned an invalid outcome. The draft is retained.\");\n if (\n value.status === \"failed\" &&\n exact(value, [\"status\", \"message\"]) &&\n text(value.message, 2000)\n )\n return { ok: true, value: { status: \"failed\", message: value.message } };\n const annotation = value.annotation;\n if (\n value.status !== \"saved\" ||\n !exact(value, [\"status\", \"annotation\"]) ||\n !object(annotation) ||\n !exact(annotation, [\"id\", \"label\", \"position\", \"sourceId\", \"createdAt\"]) ||\n !text(annotation.id) ||\n existing.some((item) => item.id === annotation.id) ||\n annotation.label !== draft.label ||\n annotation.sourceId !== draft.sourceId ||\n !isGeoPoint(annotation.position) ||\n annotation.position[0] !== draft.position[0] ||\n annotation.position[1] !== draft.position[1] ||\n !instant(annotation.createdAt)\n )\n return fail(\"The saved annotation did not match the submitted draft. The draft is retained.\");\n return {\n ok: true,\n value: Object.freeze({\n status: \"saved\",\n annotation: Object.freeze({\n id: annotation.id,\n label: draft.label,\n sourceId: draft.sourceId,\n position: Object.freeze([...draft.position]) as typeof draft.position,\n createdAt: annotation.createdAt,\n }),\n }),\n };\n } catch {\n return fail(\"The annotation outcome could not be read. The draft is retained.\");\n }\n}\n","patterns/incident-response/demo.tsx":"\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport type { OperationalDomain } from \"../operational-domain\";\nimport { createIncidentSnapshot } from \"./fixtures\";\nimport { IncidentResponse } from \"./incident-response\";\nimport { applyIncidentAction } from \"./incident-state\";\n\nexport function IncidentResponseDemo({\n initialDomain = \"military\",\n}: {\n /** Internal fixture coverage; the public example always opens the support watch. */\n initialDomain?: OperationalDomain;\n}) {\n return (\n <div className=\"flex w-full min-w-0 flex-col gap-2u\">\n <TrainingWorkspace key={initialDomain} domain={initialDomain} />\n <p className=\"m-0 text-oprt-base text-oprt-text-dim\">\n Fictional support cases. Acknowledgments, assignments, handovers, and resolutions are saved\n in this local session.\n </p>\n </div>\n );\n}\n\nfunction TrainingWorkspace({ domain }: { domain: OperationalDomain }) {\n const [snapshot, setSnapshot] = useState(() => createIncidentSnapshot(domain));\n const canonical = useRef(snapshot);\n const [selectedId, setSelectedId] = useState<string | null>(snapshot.cases[0]!.id);\n return (\n <IncidentResponse\n snapshot={snapshot}\n selectedId={selectedId}\n onSelectedIdChange={setSelectedId}\n onAction={async (request) => {\n const next = applyIncidentAction(\n canonical.current,\n request,\n canonical.current.observedAt + 60_000,\n );\n canonical.current = next.snapshot;\n setSnapshot(next.snapshot);\n return next.result;\n }}\n />\n );\n}\n","patterns/incident-response/fixtures.ts":"import type { OperationalDomain } from \"../operational-domain\";\nimport type { IncidentCase, IncidentSnapshot } from \"./types\";\n\nconst time = (value: string) => Date.parse(`2026-10-02T${value}:00Z`);\ntype CaseSeed = Pick<\n IncidentCase,\n \"id\" | \"title\" | \"summary\" | \"priority\" | \"status\" | \"ownerId\" | \"location\" | \"source\"\n> & {\n opened: string;\n evidence: readonly [label: string, value: string, detail: string][];\n report: string;\n update?: string;\n resolution?: string;\n unavailableReason?: string;\n};\n\nconst military: readonly CaseSeed[] = [\n {\n id: \"INC-2041\",\n title: \"North relay link degraded\",\n summary:\n \"Intermittent packet loss on the logistics support net. The secondary path is carrying traffic while the watch assigns fault isolation and records recovery checks.\",\n priority: 1,\n status: \"new\",\n ownerId: null,\n location: \"North relay · Shelter 03\",\n source: \"Signal watch · Net 4\",\n opened: \"08:17\",\n report: \"Checks CHK-418–CHK-420 exceeded the 5% packet-loss watch threshold.\",\n update:\n \"Secondary route verified. Assign a technician to the primary relay and record the next link check at 08:50 UTC before considering closure.\",\n evidence: [\n [\n \"Packet loss\",\n \"18.4%\",\n \"CHK-420: 10-minute sample on the primary relay; watch threshold 5%.\",\n ],\n [\n \"Secondary path\",\n \"Traffic passing\",\n \"Relay 02 delivery check passed. This confirms the fallback path only.\",\n ],\n [\n \"Field report\",\n \"Power steady\",\n \"Shelter technician recorded normal supply and no visible cable damage; primary-link fault remains open.\",\n ],\n ],\n },\n {\n id: \"INC-2038\",\n title: \"Generator service overdue\",\n summary:\n \"The west support shelter has exceeded its planned generator service window. Backup supply passed the morning check.\",\n priority: 2,\n status: \"acknowledged\",\n ownerId: \"mil-rivera\",\n location: \"West support · Shelter 01\",\n source: \"Support maintenance\",\n opened: \"07:45\",\n report: \"Service window exceeded by two hours; maintenance lead informed.\",\n update:\n \"Rivera has the service kit. Confirm the backup transfer and attach the completed service check to this case before resolution.\",\n evidence: [\n [\"Run time\", \"252 h\", \"Planned service interval: 250 hours.\"],\n [\"Backup supply\", \"Available\", \"Transfer check recorded at 07:40 UTC.\"],\n ],\n },\n {\n id: \"INC-2040\",\n title: \"East gate intercom unavailable\",\n summary:\n \"The gate team cannot reach the support desk through the fixed intercom. The handheld fallback is in use.\",\n priority: 2,\n status: \"new\",\n ownerId: \"mil-chen\",\n location: \"East gate · Post 02\",\n source: \"Gate support desk\",\n opened: \"08:12\",\n report: \"Gate attendant reported no audio on the fixed intercom.\",\n update:\n \"Gate team confirmed the fallback. Chen is coordinating a fixed-handset test with the support technician.\",\n evidence: [\n [\"Call test\", \"No audio\", \"Two tests returned a connection with no audio.\"],\n [\"Fallback\", \"Handheld radio\", \"Gate team confirmed contact on the support channel.\"],\n ],\n },\n {\n id: \"INC-2036\",\n title: \"Supply manifest awaiting sync\",\n summary:\n \"Three receiving entries are held locally. The supply clerk has retained the signed delivery sheets.\",\n priority: 3,\n status: \"acknowledged\",\n ownerId: \"mil-brooks\",\n location: \"Supply point · Receiving\",\n source: \"Logistics desk\",\n opened: \"07:28\",\n report: \"Receiving workstation has three pending entries.\",\n update:\n \"Brooks checked all three signed sheets against the local entries. Keep the originals until the receiving archive confirms each record.\",\n evidence: [\n [\"Pending entries\", \"3 records\", \"Last successful sync: 07:21 UTC.\"],\n [\"Paper record\", \"Retained\", \"Signed delivery sheets held by the receiving clerk.\"],\n ],\n },\n {\n id: \"INC-2039\",\n title: \"Backhaul maintenance in progress\",\n summary:\n \"The carrier is replacing a failed interface at the support network exchange. Updates are owned by the network operations team.\",\n priority: 3,\n status: \"acknowledged\",\n ownerId: \"mil-brooks\",\n location: \"Support exchange · Rack B\",\n source: \"Network operations\",\n opened: \"07:58\",\n report: \"Carrier maintenance notice received and linked to the support log.\",\n update:\n \"NET-771 remains with network operations. The next watch should review the carrier update due at 09:00 UTC.\",\n unavailableReason: \"Network operations owns this case. Your watch has read-only access.\",\n evidence: [\n [\"Carrier reference\", \"NET-771\", \"Next update expected at 09:00 UTC.\"],\n [\"Local route\", \"Available\", \"Internal support services remain reachable.\"],\n ],\n },\n {\n id: \"INC-2032\",\n title: \"Weather station reports restored\",\n summary: \"The support weather feed is reporting after the local interface was restarted.\",\n priority: 3,\n status: \"resolved\",\n ownerId: \"mil-rivera\",\n location: \"Support yard · Mast 01\",\n source: \"Support maintenance\",\n opened: \"06:50\",\n report: \"Three scheduled reports were missing from the support log.\",\n resolution:\n \"Interface restarted; three consecutive reports arrived within the expected interval.\",\n evidence: [\n [\"Latest report\", \"Received\", \"The 08:30 UTC observation was delivered.\"],\n [\"Observation period\", \"Complete\", \"Three scheduled reports checked by the support desk.\"],\n ],\n },\n];\n\nconst industrial: readonly CaseSeed[] = [\n {\n id: \"INC-3186\",\n title: \"Cooling loop pressure falling\",\n summary:\n \"Discharge pressure on the west cooling loop is below the operating band. The standby pump is available; the line remains under observation.\",\n priority: 1,\n status: \"new\",\n ownerId: null,\n location: \"West utilities · Pump P-204\",\n source: \"Utilities control room\",\n opened: \"08:17\",\n report: \"Low discharge pressure persisted across three instrument samples.\",\n evidence: [\n [\"Discharge pressure\", \"2.1 bar\", \"Below the supplied operating band of 2.8–3.4 bar.\"],\n [\"Standby pump\", \"Available\", \"P-205 passed its scheduled start check at 07:50 UTC.\"],\n [\n \"Operator round\",\n \"Seal seepage\",\n \"Minor seepage noted at P-204. Area isolated for inspection.\",\n ],\n ],\n },\n {\n id: \"INC-3182\",\n title: \"Compressor vibration elevated\",\n summary:\n \"The finishing line compressor has elevated bearing vibration. Maintenance is reviewing the trend with the line supervisor.\",\n priority: 2,\n status: \"acknowledged\",\n ownerId: \"ind-patel\",\n location: \"Finishing line · AC-12\",\n source: \"Condition monitoring\",\n opened: \"07:45\",\n report: \"Bearing vibration crossed the supplied maintenance threshold.\",\n evidence: [\n [\"Vibration\", \"5.8 mm/s\", \"Supplied maintenance threshold: 4.5 mm/s.\"],\n [\"Bearing temperature\", \"68 °C\", \"Within the supplied 45–75 °C operating band.\"],\n ],\n },\n {\n id: \"INC-3185\",\n title: \"Loading dock reader offline\",\n summary:\n \"The dock 03 badge reader is offline. The logistics team is recording authorized vehicle arrivals at the attended gate.\",\n priority: 2,\n status: \"new\",\n ownerId: \"ind-morgan\",\n location: \"Loading dock · Gate 03\",\n source: \"Site services desk\",\n opened: \"08:12\",\n report: \"The reader stopped reporting at the start of the shift.\",\n evidence: [\n [\"Last heartbeat\", \"08:11 UTC\", \"No response from the reader on the local network.\"],\n [\"Access process\", \"Attended gate\", \"Logistics supervisor confirmed the staffed fallback.\"],\n ],\n },\n {\n id: \"INC-3180\",\n title: \"Batch reports awaiting upload\",\n summary:\n \"Four completed batch reports are held at the packaging workstation pending a connection to the quality archive.\",\n priority: 3,\n status: \"acknowledged\",\n ownerId: \"ind-ellis\",\n location: \"Packaging · Station 07\",\n source: \"Quality systems\",\n opened: \"07:28\",\n report: \"Packaging workstation reported four pending batch records.\",\n evidence: [\n [\"Pending reports\", \"4 batches\", \"The workstation has retained complete local copies.\"],\n [\"Archive check\", \"Queued\", \"Quality coordinator notified at 08:00 UTC.\"],\n ],\n },\n {\n id: \"INC-3183\",\n title: \"Utility meter service visit\",\n summary:\n \"The utility provider is inspecting the incoming meter communications interface. The provider owns the service record.\",\n priority: 3,\n status: \"acknowledged\",\n ownerId: \"ind-ellis\",\n location: \"Substation · Meter M-01\",\n source: \"Utility service desk\",\n opened: \"07:58\",\n report: \"Provider service notice received by site operations.\",\n unavailableReason: \"The utility provider owns this case. Your shift has read-only access.\",\n evidence: [\n [\"Provider reference\", \"UTL-409\", \"Next update expected at 09:00 UTC.\"],\n [\"Local supply\", \"Available\", \"No interruption reported by the control room.\"],\n ],\n },\n {\n id: \"INC-3176\",\n title: \"Tank level indication restored\",\n summary:\n \"The service water tank indication is available after the instrument terminal was reseated.\",\n priority: 3,\n status: \"resolved\",\n ownerId: \"ind-patel\",\n location: \"Water treatment · Tank T-06\",\n source: \"Instrumentation team\",\n opened: \"06:50\",\n report: \"Level indication was missing from the control room display.\",\n resolution: \"Terminal reseated; indication matched the local gauge through three checks.\",\n evidence: [\n [\"Tank level\", \"72%\", \"Confirmed against the local gauge at 08:30 UTC.\"],\n [\n \"Observation period\",\n \"Complete\",\n \"Three instrument readings checked by the shift operator.\",\n ],\n ],\n },\n];\n\n/** Camp Alder is the public fixture; the industrial source remains for engine regression checks. */\nexport function createIncidentSnapshot(domain: OperationalDomain = \"military\"): IncidentSnapshot {\n const isMilitary = domain === \"military\";\n const owners = isMilitary\n ? [\n { id: \"mil-chen\", name: \"Lt Maya Chen\", role: \"Signal watch\" },\n { id: \"mil-rivera\", name: \"Sgt Luis Rivera\", role: \"Support maintenance\" },\n { id: \"mil-brooks\", name: \"Cpl Erin Brooks\", role: \"Logistics desk\" },\n ]\n : [\n { id: \"ind-morgan\", name: \"Alex Morgan\", role: \"Shift operations\" },\n { id: \"ind-patel\", name: \"Priya Patel\", role: \"Mechanical maintenance\" },\n { id: \"ind-ellis\", name: \"Sam Ellis\", role: \"Site services\" },\n ];\n return {\n id: `incident-${domain}-training`,\n domain,\n title: isMilitary ? \"Camp Alder support watch\" : \"Plant response\",\n location: isMilitary ? \"Support incident desk · Morning watch\" : \"Alder Works · Shift A\",\n observedAt: time(\"08:42\"),\n operatorId: owners[0]!.id,\n owners,\n cases: (isMilitary ? military : industrial).map((seed) => ({\n id: seed.id,\n revision: 3,\n title: seed.title,\n summary: seed.summary,\n priority: seed.priority,\n status: seed.status,\n ownerId: seed.ownerId,\n location: seed.location,\n openedAt: time(seed.opened),\n updatedAt: time(\"08:38\"),\n source: seed.source,\n dataState: \"current\",\n evidence: seed.evidence.map(([label, value, detail], index) => ({\n id: `${seed.id}-e${index}`,\n label,\n value,\n detail,\n at: time(index === 0 ? \"08:38\" : index === 1 ? \"08:36\" : \"08:31\"),\n })),\n history: [\n {\n id: `${seed.id}-h1`,\n label: \"Case opened\",\n detail: seed.report,\n at: time(seed.opened),\n actor: seed.source,\n },\n {\n id: `${seed.id}-h2`,\n label:\n seed.status === \"new\"\n ? isMilitary\n ? \"Watch update\"\n : \"Shift update\"\n : \"Case acknowledged\",\n detail:\n seed.status === \"new\"\n ? `Initial evidence collected. Awaiting ${isMilitary ? \"watch\" : \"shift\"} acknowledgment.`\n : \"Owner accepted the case and reviewed the available evidence.\",\n at: time(\"08:24\"),\n actor: seed.ownerId\n ? owners.find((owner) => owner.id === seed.ownerId)!.name\n : seed.source,\n },\n {\n id: `${seed.id}-h3`,\n label: seed.resolution ? \"Case resolved\" : \"Evidence updated\",\n detail: seed.resolution ?? seed.update ?? \"Latest field report added to the case.\",\n at: time(\"08:38\"),\n actor: seed.source,\n },\n ],\n permissions: {\n acknowledge: !seed.unavailableReason,\n assign: !seed.unavailableReason,\n resolve: !seed.unavailableReason,\n note: !seed.unavailableReason,\n },\n ...(seed.resolution ? { resolution: seed.resolution } : {}),\n ...(seed.unavailableReason ? { unavailableReason: seed.unavailableReason } : {}),\n })),\n };\n}\n","patterns/incident-response/incident-response.tsx":"\"use client\";\n\nimport { useId, useLayoutEffect, useRef, useState } from \"react\";\nimport {\n Badge,\n Button,\n EntityPreview,\n Field,\n Input,\n Select,\n SelectOption,\n Textarea,\n} from \"@oprt/react\";\nimport { formatUtcInstant } from \"../operational-domain\";\nimport { prioritizeIncidents, validateIncidentText } from \"./incident-state\";\nimport type {\n IncidentAction,\n IncidentActionResult,\n IncidentCase,\n IncidentResponseProps,\n IncidentSnapshot,\n} from \"./types\";\n\nconst focus =\n \"outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus forced-colors:focus-visible:outline-solid forced-colors:focus-visible:outline-2\";\nconst sectionTitle = \"m-0 text-oprt-base font-semibold uppercase tracking-oprt-caps\";\nconst statusLabel = { new: \"New\", acknowledged: \"Acknowledged\", resolved: \"Resolved\" };\nconst priorityLabel = { 1: \"High\", 2: \"Medium\", 3: \"Low\" };\nconst isoOf = (at: number) => new Date(at).toISOString();\n/** \"YYYY-MM-DD HH:MM:SS UTC\". Case times are minutes, so milliseconds are never shown. */\nconst stamp = (at: number) => formatUtcInstant(isoOf(at), { milliseconds: \"never\" });\n\n/** A time with its date and zone. A narrow column breaks between the date and the time, never inside either. */\nfunction Stamp({ at, className }: { at: number; className?: string }) {\n const text = stamp(at);\n return (\n <time dateTime={isoOf(at)} {...(className ? { className } : {})}>\n <span className=\"inline-block\">{text.slice(0, 10)}</span>{\" \"}\n <span className=\"inline-block\">{text.slice(11)}</span>\n </time>\n );\n}\n\n/**\n * The queue is tight, so a case opened on the day of the snapshot shows only \"HH:MM UTC\". An\n * earlier one carries its date above the time, so a handover across midnight is not mistaken for today.\n */\nfunction QueueTime({ at, today }: { at: number; today: string }) {\n const text = stamp(at);\n const date = text.slice(0, 10);\n return (\n <time dateTime={isoOf(at)} className=\"text-right font-mono text-oprt-text-dim\">\n {date !== today && <span className=\"block\">{date}</span>}\n <span className=\"inline-block\">{text.slice(11, 16)} UTC</span>\n </time>\n );\n}\n\ninterface CaseSession {\n editor: \"assign\" | \"note\" | \"resolve\" | null;\n revision: number;\n text: string;\n ownerId: string;\n fieldError: string | undefined;\n result: IncidentActionResult | null;\n pending: boolean;\n}\n\nfunction createCaseSession(item: IncidentCase, operatorId: string): CaseSession {\n return {\n editor: null,\n revision: item.revision,\n text: \"\",\n ownerId: item.ownerId ?? operatorId,\n fieldError: undefined,\n result: null,\n pending: false,\n };\n}\n\n/** Queue selection and canonical case data belong to the application. */\nexport function IncidentResponse(props: IncidentResponseProps) {\n // A replaced source remounts local filters and drafts, including in-flight feedback.\n return <IncidentWorkspace key={props.snapshot.id} {...props} />;\n}\n\nfunction IncidentWorkspace({\n snapshot,\n selectedId,\n onSelectedIdChange,\n onAction,\n}: IncidentResponseProps) {\n const [scope, setScope] = useState<\"open\" | \"all\">(\"open\");\n const [query, setQuery] = useState(\"\");\n const [sessions, setSessions] = useState(() => new Map<string, CaseSession>());\n const [selectionRequest, setSelectionRequest] = useState<string | null>(null);\n const detailsRef = useRef<HTMLElement>(null);\n const filterId = useId();\n const selected = snapshot.cases.find((item) => item.id === selectedId);\n const open = snapshot.cases.filter((item) => item.status !== \"resolved\").length;\n const visible = prioritizeIncidents(snapshot.cases).filter(\n (item) =>\n (scope === \"all\" || item.status !== \"resolved\") &&\n `${item.id} ${item.title} ${item.location}`\n .toLowerCase()\n .includes(query.trim().toLowerCase()),\n );\n const operator = snapshot.owners.find((owner) => owner.id === snapshot.operatorId);\n const today = isoOf(snapshot.observedAt).slice(0, 10);\n\n function updateSession(item: IncidentCase, patch: Partial<CaseSession>) {\n // Each callback retains its case identity, including after selection changes.\n setSessions((current) =>\n new Map(current).set(item.id, {\n ...(current.get(item.id) ?? createCaseSession(item, snapshot.operatorId)),\n ...patch,\n }),\n );\n }\n\n useLayoutEffect(() => {\n if (selectionRequest && selectedId === selectionRequest) detailsRef.current?.focus();\n if (selectionRequest) setSelectionRequest(null);\n }, [selectedId, selectionRequest]);\n\n return (\n <section\n aria-label=\"Incident response workspace\"\n className=\"not-prose w-full min-w-0 border border-oprt-border bg-oprt-surface-1 font-sans text-oprt-base text-oprt-text\"\n >\n <header className=\"flex flex-wrap items-start justify-between gap-2u border-b border-oprt-border px-3u py-2u\">\n <div className=\"min-w-0\">\n <h2 className=\"m-0 text-oprt-lg font-semibold normal-case tracking-normal\">\n {snapshot.title}\n </h2>\n <p className=\"m-0 mt-0.5u text-oprt-text-dim\">{snapshot.location}</p>\n </div>\n <div className=\"text-right text-oprt-text-dim\">\n <p className=\"m-0\">{operator?.name ?? \"Operator unavailable\"}</p>\n <p className=\"m-0 mt-0.5u font-mono\">\n Snapshot · <Stamp at={snapshot.observedAt} />\n </p>\n </div>\n </header>\n <div className=\"flex min-w-0 flex-wrap\">\n <section\n aria-label=\"Incident queue\"\n className=\"min-w-0 flex-[1_1_14rem] border-b border-oprt-border\"\n >\n <div className=\"flex flex-col gap-2u border-b border-oprt-border p-2u\">\n <div className=\"flex items-baseline justify-between gap-2u\">\n <h3 className={sectionTitle}>Incident queue</h3>\n <span className=\"font-mono text-oprt-text-dim\">{open} open</span>\n </div>\n <div role=\"group\" aria-label=\"Queue scope\" className=\"flex flex-wrap gap-1u\">\n <Button\n size=\"sm\"\n variant={scope === \"open\" ? \"secondary\" : \"ghost\"}\n aria-pressed={scope === \"open\"}\n onClick={() => setScope(\"open\")}\n >\n Open cases\n </Button>\n <Button\n size=\"sm\"\n variant={scope === \"all\" ? \"secondary\" : \"ghost\"}\n aria-pressed={scope === \"all\"}\n onClick={() => setScope(\"all\")}\n >\n All cases · {snapshot.cases.length}\n </Button>\n </div>\n <label className=\"sr-only\" htmlFor={filterId}>\n Find a case\n </label>\n <Input\n id={filterId}\n placeholder=\"Find a case or location\"\n value={query}\n onChange={(event) => setQuery(event.target.value)}\n />\n </div>\n <ol\n className=\"m-0 max-h-[min(40rem,70dvh)] list-none overflow-y-auto p-0\"\n aria-label=\"Cases by priority\"\n >\n {visible.map((item) => {\n const ownerName = item.ownerId\n ? (snapshot.owners.find((owner) => owner.id === item.ownerId)?.name ??\n \"Owner unavailable\")\n : \"Unassigned\";\n const isSelected = item.id === selectedId;\n return (\n <li key={item.id} className=\"border-b border-oprt-border last:border-b-0\">\n <button\n type=\"button\"\n aria-pressed={isSelected}\n // The name opens with the ID and title and then carries every other visible\n // part, so it holds what the button shows (WCAG 2.5.3) in the same order.\n aria-label={`${item.id} · ${item.title}, P${item.priority} · ${priorityLabel[item.priority]}, ${item.location}, ${statusLabel[item.status]} · ${ownerName}, opened ${stamp(item.openedAt)}`}\n onClick={() => {\n setSelectionRequest(item.id);\n onSelectedIdChange(item.id);\n }}\n // The selected case also gets a bar down its edge. The tint alone is about 1.1:1\n // against the panel, and a bar survives the focus ring, which is a box-shadow.\n className={`relative flex w-full min-w-0 flex-col gap-1u border border-transparent p-2u text-left text-oprt-base hover:bg-oprt-surface-2 ${focus} ${isSelected ? \"bg-oprt-surface-3 before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:bg-oprt-accent forced-colors:before:bg-[Highlight]\" : \"bg-transparent\"}`}\n >\n <span className=\"flex w-full flex-wrap items-baseline justify-between gap-1u\">\n <span className=\"font-mono text-oprt-text-dim\">{item.id}</span>\n <Badge\n appearance=\"text\"\n variant={\n item.priority === 1 && item.status !== \"resolved\" ? \"warning\" : \"neutral\"\n }\n >\n P{item.priority} · {priorityLabel[item.priority]}\n </Badge>\n </span>\n <span className=\"text-oprt-md font-semibold normal-case leading-snug\">\n {item.title}\n </span>\n <span className=\"text-oprt-text-dim\">{item.location}</span>\n <span className=\"flex w-full flex-wrap items-center justify-between gap-1u\">\n <span className=\"text-oprt-text-dim\">\n {statusLabel[item.status]} · {ownerName}\n </span>\n <QueueTime at={item.openedAt} today={today} />\n </span>\n </button>\n </li>\n );\n })}\n </ol>\n {visible.length === 0 && (\n <p className=\"m-0 p-3u text-oprt-text-dim\">\n No cases match this queue. Change the filter or choose All cases.\n </p>\n )}\n {selected && !visible.some((item) => item.id === selectedId) && (\n <p className=\"m-0 border-t border-oprt-border p-2u text-oprt-text-dim\">\n The selected case is outside this queue.\n </p>\n )}\n <p className=\"m-0 border-t border-oprt-border p-2u text-oprt-text-dim\">\n Highest priority first · oldest report first\n </p>\n </section>\n <section\n ref={detailsRef}\n aria-label=\"Selected case\"\n tabIndex={-1}\n className={`min-w-0 flex-[2.4_1_25rem] border border-transparent border-l-oprt-border p-3u ${focus}`}\n >\n {selected ? (\n <CaseWorkspace\n key={selected.id}\n snapshot={snapshot}\n item={selected}\n session={\n sessions.get(selected.id) ?? createCaseSession(selected, snapshot.operatorId)\n }\n onSessionChange={(patch) => updateSession(selected, patch)}\n {...(onAction ? { onAction } : {})}\n />\n ) : (\n <div className=\"flex flex-col gap-1u\">\n <h3 className=\"m-0 text-oprt-md font-semibold\">\n {selectedId ? \"Case unavailable\" : \"Select a case\"}\n </h3>\n <p className=\"m-0 text-oprt-text-dim\">\n {selectedId\n ? \"This case is no longer in the supplied queue. Choose another case to continue.\"\n : \"Choose a case from the queue to review its evidence and next action.\"}\n </p>\n </div>\n )}\n </section>\n </div>\n </section>\n );\n}\n\nfunction CaseWorkspace({\n snapshot,\n item,\n session,\n onSessionChange,\n onAction,\n}: {\n snapshot: IncidentSnapshot;\n item: IncidentCase;\n session: CaseSession;\n onSessionChange: (patch: Partial<CaseSession>) => void;\n onAction?: IncidentResponseProps[\"onAction\"];\n}) {\n const { editor, revision, text, ownerId, fieldError, result, pending } = session;\n const actionRef = useRef<HTMLButtonElement>(null);\n const assignOpenerRef = useRef<HTMLButtonElement>(null);\n const noteOpenerRef = useRef<HTMLButtonElement>(null);\n const assignmentRef = useRef<HTMLButtonElement>(null);\n // The control that opened the editor, so closing it puts focus back where the operator was.\n const opener = useRef<\"primary\" | \"assign\" | \"note\">(\"primary\");\n const returnFocus = useRef(false);\n const owner = snapshot.owners.find((person) => person.id === item.ownerId);\n const actionsUnavailable = !onAction\n ? \"Case updates are unavailable in this workspace.\"\n : (item.unavailableReason ??\n (Object.values(item.permissions).some((value) => !value)\n ? \"Your access does not include all case actions.\"\n : undefined));\n const disabled = (action: IncidentAction[\"type\"]) =>\n pending || !onAction || !item.permissions[action] || result?.kind === \"conflict\";\n const primaryAction =\n item.status === \"new\" ? \"acknowledge\" : item.status === \"acknowledged\" ? \"resolve\" : \"note\";\n const primaryLabel =\n item.status === \"new\"\n ? \"Acknowledge case\"\n : item.status === \"acknowledged\"\n ? \"Resolve case\"\n : \"Add handover\";\n\n useLayoutEffect(() => {\n if (editor === \"assign\") assignmentRef.current?.focus();\n if (editor === null && returnFocus.current) {\n const target = { primary: actionRef, assign: assignOpenerRef, note: noteOpenerRef }[\n opener.current\n ].current;\n // A control the case no longer offers (a resolved case has no Assign owner) gives way to\n // the primary action.\n (target?.isConnected && !target.disabled ? target : actionRef.current)?.focus();\n returnFocus.current = false;\n }\n }, [editor, item.status]);\n\n function edit(next: \"assign\" | \"note\" | \"resolve\", from: \"primary\" | \"assign\" | \"note\") {\n opener.current = from;\n onSessionChange({ ...createCaseSession(item, snapshot.operatorId), editor: next });\n }\n\n async function act(action: IncidentAction, expectedRevision: number) {\n if (!onAction || pending) return;\n onSessionChange({ pending: true, result: null });\n try {\n const outcome = await onAction({ caseId: item.id, expectedRevision, action });\n if (outcome.kind === \"accepted\") {\n returnFocus.current = true;\n onSessionChange({ result: outcome, editor: null, text: \"\", fieldError: undefined });\n } else {\n onSessionChange({ result: outcome });\n }\n } catch {\n onSessionChange({\n result: {\n kind: \"rejected\",\n message:\n \"The action outcome could not be confirmed. Refresh the case from the service before retrying.\",\n },\n });\n } finally {\n onSessionChange({ pending: false });\n }\n }\n\n return (\n <div className=\"flex min-w-0 flex-col gap-3u\">\n <EntityPreview\n headingLevel={3}\n entity={{\n id: item.id,\n name: item.title,\n typeLabel: \"Incident case\",\n summary: item.summary,\n facts: [\n {\n id: \"status\",\n label: \"Status\",\n value: (\n <Badge\n appearance=\"text\"\n size=\"md\"\n variant={item.status === \"resolved\" ? \"success\" : \"neutral\"}\n >\n {statusLabel[item.status]}\n </Badge>\n ),\n },\n {\n id: \"priority\",\n label: \"Priority\",\n value: (\n <Badge\n appearance=\"text\"\n size=\"md\"\n variant={\n item.priority === 1 && item.status !== \"resolved\" ? \"warning\" : \"neutral\"\n }\n >\n P{item.priority} · {priorityLabel[item.priority]}\n </Badge>\n ),\n },\n {\n id: \"owner\",\n label: \"Owner\",\n value: owner?.name ?? (item.ownerId ? \"Owner unavailable\" : \"Unassigned\"),\n },\n { id: \"location\", label: \"Location\", value: item.location },\n {\n id: \"opened\",\n label: \"Opened\",\n value: <Stamp at={item.openedAt} />,\n mono: true,\n },\n { id: \"revision\", label: \"Revision\", value: `r${item.revision}`, mono: true },\n ],\n quality: { state: item.dataState, source: item.source, updatedAt: item.updatedAt },\n }}\n />\n\n <div className=\"flex flex-col gap-2u border-y border-oprt-border py-2u\">\n {editor === null ? (\n <div className=\"flex flex-wrap items-center gap-1u\">\n <Button\n ref={actionRef}\n size=\"sm\"\n disabled={disabled(primaryAction)}\n onClick={() => {\n if (primaryAction === \"acknowledge\")\n void act({ type: \"acknowledge\" }, item.revision);\n else edit(primaryAction, \"primary\");\n }}\n >\n {pending ? \"Submitting…\" : primaryLabel}\n </Button>\n {item.status !== \"resolved\" && (\n <Button\n ref={assignOpenerRef}\n size=\"sm\"\n variant=\"tertiary\"\n disabled={disabled(\"assign\")}\n onClick={() => edit(\"assign\", \"assign\")}\n >\n Assign owner\n </Button>\n )}\n {item.status !== \"resolved\" && (\n <Button\n ref={noteOpenerRef}\n size=\"sm\"\n variant=\"ghost\"\n disabled={disabled(\"note\")}\n onClick={() => edit(\"note\", \"note\")}\n >\n Add handover\n </Button>\n )}\n </div>\n ) : (\n <form\n noValidate\n className=\"flex min-w-0 flex-col gap-2u\"\n aria-label={\n editor === \"assign\"\n ? \"Assign case\"\n : editor === \"note\"\n ? \"Add handover\"\n : \"Resolve case\"\n }\n onSubmit={(event) => {\n event.preventDefault();\n if (editor === \"assign\") {\n void act({ type: \"assign\", ownerId }, revision);\n return;\n }\n const error = validateIncidentText(\n text,\n editor === \"note\" ? \"Handover note\" : \"Resolution reason\",\n );\n onSessionChange({ fieldError: error });\n if (!error)\n void act(\n editor === \"note\"\n ? { type: \"note\", note: text }\n : { type: \"resolve\", reason: text },\n revision,\n );\n }}\n >\n {editor === \"assign\" ? (\n <Field label=\"Case owner\" required>\n <Select\n ref={assignmentRef}\n value={ownerId}\n onValueChange={(ownerId) => onSessionChange({ ownerId })}\n disabled={pending}\n size=\"sm\"\n className=\"w-full\"\n required\n >\n {snapshot.owners.map((person) => (\n <SelectOption\n key={person.id}\n value={person.id}\n >{`${person.name} · ${person.role}`}</SelectOption>\n ))}\n </Select>\n </Field>\n ) : (\n <Field\n label={editor === \"note\" ? \"Handover note\" : \"Resolution reason\"}\n required\n help={\n editor === \"note\"\n ? `Record the current condition, outstanding check, and what the next ${snapshot.domain === \"military\" ? \"watch\" : \"shift\"} must follow up.`\n : \"Record what changed and how recovery was verified.\"\n }\n {...(fieldError ? { error: fieldError } : {})}\n >\n <Textarea\n autoFocus\n required\n rows={3}\n maxLength={1000}\n value={text}\n disabled={pending}\n onChange={(event) => {\n onSessionChange({ text: event.target.value, fieldError: undefined });\n }}\n />\n </Field>\n )}\n <div className=\"flex flex-wrap gap-1u\">\n <Button\n type=\"submit\"\n size=\"sm\"\n disabled={\n disabled(editor) ||\n result?.kind === \"conflict\" ||\n (editor === \"assign\" && ownerId === item.ownerId)\n }\n >\n {pending\n ? \"Submitting…\"\n : editor === \"assign\"\n ? \"Save assignment\"\n : editor === \"note\"\n ? \"Save handover\"\n : \"Confirm resolution\"}\n </Button>\n <Button\n size=\"sm\"\n variant=\"ghost\"\n disabled={pending}\n onClick={() => {\n returnFocus.current = true;\n onSessionChange({ editor: null, result: null, text: \"\", fieldError: undefined });\n }}\n >\n Cancel\n </Button>\n </div>\n </form>\n )}\n {actionsUnavailable && <p className=\"m-0 text-oprt-text-dim\">{actionsUnavailable}</p>}\n {result && (\n <div className=\"flex flex-col gap-1u\">\n <p\n role={result.kind === \"accepted\" ? \"status\" : \"alert\"}\n className={`m-0 ${result.kind === \"accepted\" ? \"text-oprt-text-dim\" : \"text-oprt-error\"}`}\n >\n {result.message}\n </p>\n {result.kind === \"conflict\" && (\n <Button\n className=\"self-start\"\n size=\"sm\"\n variant=\"tertiary\"\n onClick={() => {\n onSessionChange({ revision: item.revision, result: null });\n }}\n >\n Review latest case\n </Button>\n )}\n </div>\n )}\n </div>\n\n {item.resolution && (\n <section aria-label=\"Resolution\" className=\"flex flex-col gap-1u\">\n <h4 className={sectionTitle}>Resolution</h4>\n <p className=\"m-0 leading-relaxed\">{item.resolution}</p>\n </section>\n )}\n\n <section aria-label=\"Case evidence\" className=\"flex flex-col gap-2u\">\n <h4 className={sectionTitle}>Evidence</h4>\n <dl className=\"m-0 flex min-w-0 flex-col gap-2u\">\n {item.evidence.map((entry) => (\n <div\n key={entry.id}\n className=\"grid min-w-0 grid-cols-[minmax(0,1fr)_auto] gap-x-2u gap-y-0.5u\"\n >\n <dt className=\"font-medium\">{entry.label}</dt>\n <dd className=\"m-0 text-right font-mono\">{entry.value}</dd>\n <dd className=\"col-span-2 m-0 text-oprt-text-dim\">\n {entry.detail} <Stamp at={entry.at} className=\"font-mono\" />\n </dd>\n </div>\n ))}\n </dl>\n {item.evidence.length === 0 && (\n <p className=\"m-0 text-oprt-text-dim\">No evidence has been supplied for this case.</p>\n )}\n </section>\n\n <section\n aria-label=\"Case history\"\n className=\"flex flex-col gap-2u border-t border-oprt-border pt-3u\"\n >\n <div className=\"flex items-baseline justify-between gap-2u\">\n <h4 className={sectionTitle}>Case history</h4>\n <span className=\"text-oprt-text-dim\">Oldest first</span>\n </div>\n <ol className=\"m-0 flex list-none flex-col gap-3u p-0\">\n {[...item.history]\n .sort((a, b) => a.at - b.at)\n .map((event) => (\n <li key={event.id} className=\"flex min-w-0 flex-wrap gap-x-2u gap-y-0.5u\">\n <Stamp at={event.at} className=\"font-mono text-oprt-text-dim\" />\n <div className=\"min-w-0 flex-[1_1_12rem]\">\n <p className=\"m-0 font-semibold\">{event.label}</p>\n <p className=\"m-0 mt-0.5u [overflow-wrap:anywhere]\">{event.detail}</p>\n <p className=\"m-0 mt-0.5u text-oprt-text-dim\">{event.actor}</p>\n </div>\n </li>\n ))}\n </ol>\n {item.history.length === 0 && (\n <p className=\"m-0 text-oprt-text-dim\">No history has been supplied for this case.</p>\n )}\n </section>\n </div>\n );\n}\n","patterns/incident-response/incident-state.ts":"import type {\n IncidentActionRequest,\n IncidentActionResult,\n IncidentCase,\n IncidentSnapshot,\n} from \"./types\";\n\nexport function prioritizeIncidents(cases: readonly IncidentCase[]): IncidentCase[] {\n return [...cases].sort(\n (a, b) =>\n Number(a.status === \"resolved\") - Number(b.status === \"resolved\") ||\n a.priority - b.priority ||\n a.openedAt - b.openedAt,\n );\n}\n\nexport function validateIncidentText(value: string, label: string): string | undefined {\n if (value.trim().length < 12) return `Add a ${label.toLowerCase()} of at least 12 characters.`;\n if (value.trim().length > 1000) return `${label} must be 1,000 characters or fewer.`;\n return undefined;\n}\n\n/** Local reference host. Services must enforce these rules transactionally at their own boundary. */\nexport function applyIncidentAction(\n snapshot: IncidentSnapshot,\n request: IncidentActionRequest,\n at: number,\n): { snapshot: IncidentSnapshot; result: IncidentActionResult } {\n const refuse = (kind: \"rejected\" | \"conflict\", message: string) => ({\n snapshot,\n result: { kind, message },\n });\n const current = snapshot.cases.find((item) => item.id === request.caseId);\n if (!current) return refuse(\"rejected\", \"This case is no longer available. Select another case.\");\n if (current.revision !== request.expectedRevision)\n return refuse(\n \"conflict\",\n \"This case changed while you were working. Review the latest details before retrying.\",\n );\n if (!current.permissions[request.action.type])\n return refuse(\n \"rejected\",\n current.unavailableReason ?? \"You do not have permission for this action.\",\n );\n if (!Number.isFinite(at) || !Number.isFinite(new Date(at).getTime()) || at < current.updatedAt)\n return refuse(\n \"rejected\",\n \"The action time precedes the latest case update. Refresh the source time.\",\n );\n const actor = snapshot.owners.find((owner) => owner.id === snapshot.operatorId);\n if (!actor)\n return refuse(\n \"rejected\",\n \"Your operator identity is unavailable. Reconnect before updating this case.\",\n );\n if (current.status === \"resolved\" && request.action.type !== \"note\")\n return refuse(\"rejected\", \"This case is already resolved. You can still add a handover note.\");\n\n const next = { ...current, revision: current.revision + 1, updatedAt: at };\n const action = request.action;\n let label = \"\";\n let detail = \"\";\n switch (action.type) {\n case \"acknowledge\":\n if (current.status !== \"new\")\n return refuse(\"rejected\", \"This case has already been acknowledged.\");\n next.status = \"acknowledged\";\n next.ownerId = current.ownerId ?? actor.id;\n label = \"Case acknowledged\";\n detail = `${actor.name} accepted the case for review.`;\n break;\n case \"assign\": {\n const owner = snapshot.owners.find((item) => item.id === action.ownerId);\n if (!owner)\n return refuse(\"rejected\", \"That owner is no longer available. Choose another owner.\");\n if (current.ownerId === owner.id)\n return refuse(\"rejected\", \"This case is already assigned to that owner.\");\n next.ownerId = owner.id;\n label = \"Owner assigned\";\n detail = `${owner.name} · ${owner.role}`;\n break;\n }\n case \"resolve\": {\n if (current.status !== \"acknowledged\")\n return refuse(\"rejected\", \"Acknowledge this case before resolving it.\");\n const error = validateIncidentText(action.reason, \"Resolution reason\");\n if (error) return refuse(\"rejected\", error);\n next.status = \"resolved\";\n next.resolution = action.reason.trim();\n label = \"Case resolved\";\n detail = next.resolution;\n break;\n }\n case \"note\": {\n const error = validateIncidentText(action.note, \"Handover note\");\n if (error) return refuse(\"rejected\", error);\n label = \"Handover note\";\n detail = action.note.trim();\n break;\n }\n }\n next.history = [\n ...current.history,\n { id: `${current.id}-r${next.revision}`, label, detail, at, actor: actor.name },\n ];\n return {\n snapshot: {\n ...snapshot,\n observedAt: Math.max(snapshot.observedAt, at),\n cases: snapshot.cases.map((item) => (item.id === next.id ? next : item)),\n },\n result: { kind: \"accepted\", message: `${label}. ${current.id} updated.` },\n };\n}\n","patterns/incident-response/index.ts":"export { IncidentResponse } from \"./incident-response\";\nexport { IncidentResponseDemo } from \"./demo\";\nexport { createIncidentSnapshot } from \"./fixtures\";\nexport { applyIncidentAction, prioritizeIncidents, validateIncidentText } from \"./incident-state\";\nexport type {\n IncidentStatus,\n IncidentAction,\n IncidentCase,\n IncidentSnapshot,\n IncidentActionRequest,\n IncidentActionResult,\n IncidentResponseProps,\n} from \"./types\";\n","patterns/incident-response/types.ts":"import type { OperationalState } from \"@oprt/react\";\nimport type { OperationalDomain } from \"../operational-domain\";\n\nexport type IncidentStatus = \"new\" | \"acknowledged\" | \"resolved\";\nexport type IncidentAction =\n | { type: \"acknowledge\" }\n | { type: \"assign\"; ownerId: string }\n | { type: \"resolve\"; reason: string }\n | { type: \"note\"; note: string };\n\nexport interface IncidentCase {\n id: string;\n revision: number;\n title: string;\n summary: string;\n priority: 1 | 2 | 3;\n status: IncidentStatus;\n ownerId: string | null;\n location: string;\n openedAt: number;\n updatedAt: number;\n source: string;\n dataState: OperationalState;\n evidence: readonly { id: string; label: string; value: string; detail: string; at: number }[];\n history: readonly { id: string; label: string; detail: string; at: number; actor: string }[];\n permissions: Record<IncidentAction[\"type\"], boolean>;\n unavailableReason?: string;\n resolution?: string;\n}\n\nexport interface IncidentSnapshot {\n /** Changes when the application replaces the entire workspace source. */\n id: string;\n domain: OperationalDomain;\n title: string;\n location: string;\n observedAt: number;\n operatorId: string;\n owners: readonly { id: string; name: string; role: string }[];\n cases: readonly IncidentCase[];\n}\n\nexport interface IncidentActionRequest {\n caseId: string;\n expectedRevision: number;\n action: IncidentAction;\n}\n\nexport type IncidentActionResult = {\n kind: \"accepted\" | \"rejected\" | \"conflict\";\n message: string;\n};\n\nexport interface IncidentResponseProps {\n snapshot: IncidentSnapshot;\n selectedId: string | null;\n onSelectedIdChange: (id: string) => void;\n /** Publish the canonical snapshot separately; this view never applies optimistic case changes. */\n onAction?: (request: IncidentActionRequest) => Promise<IncidentActionResult>;\n}\n","patterns/operational-domain.ts":"/** Fixture context used by reusable application recipes. */\nexport type OperationalDomain = \"military\" | \"industrial\";\n\nexport interface UtcInstantOptions {\n /**\n * Whether the milliseconds are shown. \"auto\" (the default) shows them only when the supplied\n * instant carries a non-zero fraction, because sub-second timing matters only where it exists.\n */\n milliseconds?: \"auto\" | \"always\" | \"never\";\n}\n\nconst INSTANT =\n /^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})(?:\\.(\\d{1,3}))?(Z|[+-]\\d{2}:\\d{2})$/;\n\n/**\n * Reads an ISO 8601 instant as \"YYYY-MM-DD HH:MM:SS UTC\", with \".mmm\" after the seconds where the\n * options ask for it. Text that is not a real instant (an invented date such as 30 February, or\n * free text) is returned as supplied rather than reinterpreted. It never uses Intl, so the server\n * and every browser print the same characters.\n */\nexport function formatUtcInstant(value: string, options: UtcInstantOptions = {}): string {\n const parts = INSTANT.exec(value);\n if (!parts) return value;\n const [year, month, day, hour, minute, second] = parts.slice(1, 7).map(Number) as [\n number,\n number,\n number,\n number,\n number,\n number,\n ];\n const calendar = new Date(Date.UTC(year, month - 1, day));\n // Date rolls an invented day into the next month; compare it back to what was written.\n if (\n calendar.getUTCFullYear() !== year ||\n calendar.getUTCMonth() !== month - 1 ||\n calendar.getUTCDate() !== day ||\n hour > 23 ||\n minute > 59 ||\n second > 59\n )\n return value;\n const fraction = (parts[7] ?? \"\").padEnd(3, \"0\");\n const parsed = new Date(`${value.slice(0, 19)}.${fraction}${parts[8]}`);\n if (Number.isNaN(parsed.getTime())) return value;\n const { milliseconds = \"auto\" } = options;\n const showFraction = milliseconds === \"always\" || (milliseconds === \"auto\" && fraction !== \"000\");\n const iso = parsed.toISOString();\n return `${iso.slice(0, 10)} ${iso.slice(11, showFraction ? 23 : 19)} UTC`;\n}\n\n/**\n * Reads epoch milliseconds as \"YYYY-MM-DD HH:MM:SS UTC\", with the same options as\n * formatUtcInstant. A value that is not a real time reads \"Unknown time\" rather than throwing.\n */\nexport function formatUtcEpoch(ms: number, options: UtcInstantOptions = {}): string {\n const date = new Date(ms);\n if (!Number.isFinite(ms) || Number.isNaN(date.getTime())) return \"Unknown time\";\n return formatUtcInstant(date.toISOString(), options);\n}\n","patterns/planning-scheduling/demo.tsx":"\"use client\";\n\nimport { useState } from \"react\";\nimport type { OperationalDomain } from \"../operational-domain\";\nimport { createScheduleFixture } from \"./fixtures\";\nimport { PlanningScheduling } from \"./planning-scheduling\";\nimport {\n validateChange,\n type ScheduleApplyRequest,\n type ScheduleApplyResult,\n} from \"./schedule-state\";\n\nexport function PlanningSchedulingDemo({\n initialDomain = \"industrial\",\n}: { initialDomain?: OperationalDomain } = {}) {\n return (\n <div className=\"not-prose min-w-0 flex flex-col gap-2u\">\n <LocalSchedule key={initialDomain} domain={initialDomain} />\n <p className=\"m-0 font-sans text-oprt-base text-oprt-text-dim\">\n Fictional maintenance plan. Changes stay in this page session.\n </p>\n </div>\n );\n}\n\n/** Public industrial example; legacy fixtures remain available to regression tests. */\nexport function MaintenancePlanningDemo() {\n return <PlanningSchedulingDemo initialDomain=\"industrial\" />;\n}\n\nfunction LocalSchedule({ domain }: { domain: OperationalDomain }) {\n const [snapshot, setSnapshot] = useState(() => createScheduleFixture(domain));\n const apply = async (request: ScheduleApplyRequest): Promise<ScheduleApplyResult> => {\n if (request.scheduleId !== snapshot.id || request.expectedRevision !== snapshot.revision)\n return {\n status: \"conflict\",\n requestId: request.requestId,\n message: \"The source revision changed. Load the latest revision before applying.\",\n };\n const issues = validateChange(snapshot, request);\n if (issues.length)\n return {\n status: \"rejected\",\n requestId: request.requestId,\n message: issues.map((issue) => issue.message).join(\" \"),\n };\n return {\n status: \"accepted\",\n requestId: request.requestId,\n snapshot: {\n ...snapshot,\n revision: `plan-${String(Number(snapshot.revision.slice(5)) + 1).padStart(2, \"0\")}`,\n jobs: snapshot.jobs.map((job) =>\n job.id === request.jobId\n ? { ...job, resourceId: request.resourceId, start: request.start, end: request.end }\n : job,\n ),\n },\n };\n };\n return (\n <PlanningScheduling\n snapshot={snapshot}\n initialSelectedJobId={domain === \"military\" ? \"WO-1842\" : \"PM-2402\"}\n onApply={apply}\n onAccepted={setSnapshot}\n acceptanceMessage=\"Schedule updated locally. The resource conflict is resolved for this work order.\"\n />\n );\n}\n","patterns/planning-scheduling/fixtures.ts":"import type { OperationalDomain } from \"../operational-domain\";\nimport type { ScheduleJob, ScheduleSnapshot } from \"./schedule-state\";\n\nconst at = (time: string) => Date.parse(`2026-10-06T${time}:00Z`);\nconst job = (\n id: string,\n name: string,\n asset: string,\n owner: string,\n resourceId: string,\n start: string,\n end: string,\n prerequisites: string[],\n summary: string,\n): ScheduleJob => ({\n id,\n name,\n asset,\n owner,\n resourceId,\n eligibleResourceIds:\n resourceId === \"bay-a\" || resourceId === \"bay-b\"\n ? [\"bay-a\", \"bay-b\"]\n : resourceId === \"mechanical-a\" || resourceId === \"mechanical-b\"\n ? [\"mechanical-a\", \"mechanical-b\"]\n : [resourceId],\n start: at(start),\n end: at(end),\n prerequisites,\n summary,\n});\n\nexport function createScheduleFixture(domain: OperationalDomain): ScheduleSnapshot {\n if (domain === \"industrial\")\n return {\n id: \"north-process\",\n revision: \"plan-12\",\n title: \"Plant maintenance\",\n location: \"North Process Works · Unit 4\",\n summary: \"Planned downtime · service windows and crew allocation\",\n start: at(\"06:00\"),\n end: at(\"18:00\"),\n resources: [\n {\n id: \"mechanical-a\",\n name: \"Mechanical crew A\",\n detail: \"Rotating equipment · 4 technicians\",\n },\n {\n id: \"mechanical-b\",\n name: \"Mechanical crew B\",\n detail: \"Mechanical service · 3 technicians\",\n },\n {\n id: \"inspection\",\n name: \"Inspection crew\",\n detail: \"Condition & pressure · 2 inspectors\",\n },\n { id: \"electrical\", name: \"Electrical crew\", detail: \"Electrical · 2 technicians\" },\n { id: \"isolation\", name: \"Stores & isolation\", detail: \"Work packs · 2 coordinators\" },\n ],\n jobs: [\n job(\n \"PM-2401\",\n \"Seal inspection\",\n \"P-204 transfer pump\",\n \"Mara Ellis\",\n \"mechanical-a\",\n \"06:00\",\n \"10:00\",\n [],\n \"Inspect the mechanical seal and document wear before the unit restart.\",\n ),\n job(\n \"PM-2402\",\n \"Bearing replacement\",\n \"P-208 feed pump\",\n \"Noah Voss\",\n \"mechanical-a\",\n \"09:00\",\n \"12:00\",\n [\"PM-2408\"],\n \"Replace the drive-end bearing and record the installation checks before the vibration baseline.\",\n ),\n job(\n \"PM-2403\",\n \"Valve service\",\n \"XV-410 bypass valve\",\n \"Lina Chen\",\n \"mechanical-b\",\n \"06:00\",\n \"09:00\",\n [],\n \"Inspect the actuator travel and replace the stem packing.\",\n ),\n job(\n \"PM-2404\",\n \"Coupling alignment\",\n \"M-212 agitator\",\n \"Lina Chen\",\n \"mechanical-b\",\n \"13:00\",\n \"16:00\",\n [],\n \"Check alignment against the work-pack tolerances and record the final readings.\",\n ),\n job(\n \"PM-2405\",\n \"Vibration baseline\",\n \"P-208 feed pump\",\n \"Evan Patel\",\n \"inspection\",\n \"12:30\",\n \"15:00\",\n [\"PM-2402\"],\n \"Collect the post-maintenance vibration baseline after bearing replacement.\",\n ),\n job(\n \"PM-2406\",\n \"Pressure verification\",\n \"HX-406 exchanger\",\n \"Evan Patel\",\n \"inspection\",\n \"15:00\",\n \"18:00\",\n [],\n \"Witness the planned pressure verification and file the inspection record.\",\n ),\n job(\n \"PM-2407\",\n \"Starter inspection\",\n \"MCC-04 panel\",\n \"Sofia Reed\",\n \"electrical\",\n \"08:00\",\n \"12:00\",\n [],\n \"Inspect the starter contacts under the supplied isolation work pack.\",\n ),\n job(\n \"PM-2408\",\n \"Bearing kit release\",\n \"P-208 work pack\",\n \"Ari Morgan\",\n \"isolation\",\n \"06:00\",\n \"08:00\",\n [],\n \"Verify the bearing kit and release the isolation pack to the assigned mechanical crew.\",\n ),\n job(\n \"PM-2409\",\n \"Spares reconciliation\",\n \"Unit 4 stores\",\n \"Ari Morgan\",\n \"isolation\",\n \"14:00\",\n \"17:00\",\n [],\n \"Reconcile used parts against the shutdown work orders.\",\n ),\n ],\n };\n return {\n id: \"cedar-fleet\",\n revision: \"plan-07\",\n title: \"Fleet availability\",\n location: \"Cedar Support Depot · Maintenance section\",\n summary: \"Tuesday day shift · vehicle maintenance and crew training\",\n start: at(\"06:00\"),\n end: at(\"18:00\"),\n resources: [\n { id: \"bay-a\", name: \"Maintenance bay 1\", detail: \"Vehicle service · 4 technicians\" },\n { id: \"bay-b\", name: \"Maintenance bay 2\", detail: \"Vehicle service · 3 technicians\" },\n { id: \"test\", name: \"Validation pad\", detail: \"Vehicle checks · 2 technicians\" },\n { id: \"training\", name: \"Training cell\", detail: \"Crew instruction · 12 seats\" },\n { id: \"stores\", name: \"Parts & dispatch\", detail: \"Support · 2 coordinators\" },\n ],\n jobs: [\n job(\n \"WO-1841\",\n \"Brake inspection\",\n \"Hawk 21 · utility vehicle\",\n \"Sgt. Ellis\",\n \"bay-a\",\n \"06:00\",\n \"10:00\",\n [],\n \"Inspect the braking system and record service findings before the next driver-training rotation.\",\n ),\n job(\n \"WO-1842\",\n \"Cooling service\",\n \"Hawk 24 · utility vehicle\",\n \"Cpl. Voss\",\n \"bay-a\",\n \"09:00\",\n \"12:00\",\n [\"WO-1848\"],\n \"Replace the coolant hose and perform a leak check before the return-to-service inspection.\",\n ),\n job(\n \"WO-1843\",\n \"Battery service\",\n \"Hawk 18 · utility vehicle\",\n \"Cpl. Chen\",\n \"bay-b\",\n \"06:00\",\n \"09:00\",\n [],\n \"Inspect battery connections and record the load-test result.\",\n ),\n job(\n \"WO-1844\",\n \"Suspension check\",\n \"Hawk 27 · utility vehicle\",\n \"Cpl. Chen\",\n \"bay-b\",\n \"13:00\",\n \"16:00\",\n [],\n \"Inspect the suspension and complete the scheduled service checklist.\",\n ),\n job(\n \"WO-1845\",\n \"Return-to-service check\",\n \"Hawk 24 · utility vehicle\",\n \"Sgt. Patel\",\n \"test\",\n \"12:30\",\n \"15:00\",\n [\"WO-1842\"],\n \"Verify the cooling repair before returning the vehicle to the training pool.\",\n ),\n job(\n \"WO-1846\",\n \"Driver checks\",\n \"Hawk 31 · utility vehicle\",\n \"Sgt. Patel\",\n \"test\",\n \"15:00\",\n \"18:00\",\n [],\n \"Complete routine driver checks with the afternoon training crew.\",\n ),\n job(\n \"WO-1847\",\n \"Crew refresher\",\n \"Support section Bravo\",\n \"Lt. Reed\",\n \"training\",\n \"08:00\",\n \"12:00\",\n [],\n \"Review vehicle care, defect reporting and handover procedures with the support crew.\",\n ),\n job(\n \"WO-1848\",\n \"Service kit release\",\n \"Hawk 24 · parts kit\",\n \"Spc. Morgan\",\n \"stores\",\n \"06:00\",\n \"08:00\",\n [],\n \"Verify the replacement hose and release the service kit to the maintenance team.\",\n ),\n job(\n \"WO-1849\",\n \"Fleet handover\",\n \"Evening support crew\",\n \"Spc. Morgan\",\n \"stores\",\n \"14:00\",\n \"17:00\",\n [],\n \"Reconcile service records and brief the evening support crew on vehicle availability.\",\n ),\n ],\n };\n}\n","patterns/planning-scheduling/index.ts":"export {\n PlanningScheduling,\n PlanningScheduling as MaintenancePlanning,\n type PlanningSchedulingProps,\n type PlanningSchedulingProps as MaintenancePlanningProps,\n} from \"./planning-scheduling\";\nexport { MaintenancePlanningDemo, PlanningSchedulingDemo } from \"./demo\";\nexport { createScheduleFixture } from \"./fixtures\";\nexport {\n conflictsFor,\n layoutLane,\n parseUtcInput,\n utcInput,\n utcTime,\n utcDate,\n utcWindow,\n validateChange,\n} from \"./schedule-state\";\nexport type {\n ScheduleResource,\n ScheduleJob,\n ScheduleSnapshot,\n ScheduleChange,\n ScheduleApplyRequest,\n ScheduleApplyResult,\n ScheduleIssue,\n} from \"./schedule-state\";\n","patterns/planning-scheduling/planning-scheduling.tsx":"\"use client\";\n\nimport { useEffect, useId, useLayoutEffect, useRef, useState } from \"react\";\nimport {\n Badge,\n Button,\n EntityPreview,\n Field,\n Input,\n Inspector,\n Select,\n SelectOption,\n} from \"@oprt/react\";\nimport {\n conflictsFor,\n layoutLane,\n parseUtcInput,\n utcInput,\n utcTime,\n utcWindow,\n validateChange,\n type ScheduleApplyRequest,\n type ScheduleApplyResult,\n type ScheduleChange,\n type ScheduleIssue,\n type ScheduleJob,\n type ScheduleSnapshot,\n} from \"./schedule-state\";\n\nexport type PlanningSchedulingProps = {\n snapshot: ScheduleSnapshot;\n initialSelectedJobId?: string;\n onApply?: (request: ScheduleApplyRequest) => Promise<ScheduleApplyResult>;\n onAccepted?: (snapshot: ScheduleSnapshot) => void;\n acceptanceMessage?: string;\n};\ntype Draft = {\n scheduleId: string;\n jobId: string;\n revision: string;\n resourceId: string;\n start: string;\n end: string;\n};\nconst focusStyle = \"outline-hidden focus-visible:shadow-oprt-focus focus-visible:z-10\";\n/** The selected work order is shown by a 3 px bar (Highlight in forced colors) as well as a tint. */\nconst selectedBar =\n \"before:absolute before:inset-y-0 before:left-0 before:w-[3px] before:bg-oprt-accent forced-colors:before:bg-[Highlight]\";\n/** Layout is measured before paint where there is a document, so a phone never shows the timeline first. */\nconst useBrowserLayoutEffect = typeof document === \"undefined\" ? useEffect : useLayoutEffect;\n/** A number stays with its unit. */\nconst hours = (value: number) => `${value}\\u00A0h`;\n\n/** A controlled schedule. The host owns source revisions and accepted persistence. */\nexport function PlanningScheduling({\n snapshot,\n initialSelectedJobId,\n onApply,\n onAccepted,\n acceptanceMessage = \"Schedule change accepted by the supplied service.\",\n}: PlanningSchedulingProps) {\n const instance = useId();\n const [selectedId, setSelectedId] = useState(\n () =>\n snapshot.jobs.find((job) => job.id === initialSelectedJobId)?.id ??\n snapshot.jobs[0]?.id ??\n \"\",\n );\n // No fallback to another order: a revision that drops the selection says so (see the inspector).\n const selected = snapshot.jobs.find((job) => job.id === selectedId);\n const [viewChoice, setView] = useState<\"schedule\" | \"list\" | null>(null);\n const [narrow, setNarrow] = useState(false);\n const workspace = useRef<HTMLElement>(null);\n const view = viewChoice ?? (narrow ? \"list\" : \"schedule\");\n useBrowserLayoutEffect(() => {\n const element = workspace.current;\n if (!element) return;\n // Measured now, before paint; a box with no width yet (hidden, or no layout) says nothing.\n if (element.clientWidth > 0) setNarrow(element.clientWidth < 640);\n if (typeof ResizeObserver === \"undefined\") return;\n const observer = new ResizeObserver(([entry]) => {\n if (entry) setNarrow(entry.contentRect.width < 640);\n });\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n const [draft, setDraft] = useState<Draft | null>(null);\n const [review, setReview] = useState<ScheduleChange | null>(null);\n const [issues, setIssues] = useState<ScheduleIssue[]>([]);\n const [failure, setFailure] = useState(\"\");\n const [notice, setNotice] = useState(\"\");\n const [pending, setPending] = useState(false);\n const [requestedSelection, setRequestedSelection] = useState<string | null>(null);\n const inspector = useRef<HTMLElement>(null);\n const keepEditing = useRef<HTMLButtonElement>(null);\n const selectionRequested = useRef(false);\n useLayoutEffect(() => {\n if (selectionRequested.current) {\n inspector.current?.focus();\n selectionRequested.current = false;\n }\n }, [selectedId]);\n const source = useRef(snapshot);\n source.current = snapshot;\n const mounted = useRef(true);\n const sequence = useRef(0);\n const editButton = useRef<HTMLButtonElement>(null);\n const formRef = useRef<HTMLFormElement>(null);\n const reviewRef = useRef<HTMLElement>(null);\n const returnToEdit = useRef(false);\n const stale =\n draft !== null && (draft.revision !== snapshot.revision || draft.scheduleId !== snapshot.id);\n useEffect(() => {\n mounted.current = true;\n return () => {\n mounted.current = false;\n sequence.current += 1;\n };\n }, []);\n useEffect(() => {\n sequence.current += 1;\n setPending(false);\n }, [snapshot.id, snapshot.revision]);\n // The form takes focus when an edit starts (or a different order is edited) and when review\n // returns to editing, not on every keystroke: the effect reads two primitives, not the draft.\n const editingJobId = draft?.jobId ?? null;\n const editing = draft !== null;\n useEffect(() => {\n if (editing && !review) formRef.current?.querySelector<HTMLButtonElement>(\"button\")?.focus();\n }, [editing, editingJobId, review]);\n useEffect(() => {\n if (review) reviewRef.current?.focus();\n }, [review]);\n // A tap on another order while a draft is open is answered where the operator can see it.\n useEffect(() => {\n if (!requestedSelection) return;\n keepEditing.current?.focus({ preventScroll: true });\n keepEditing.current?.scrollIntoView?.({ block: \"nearest\" });\n }, [requestedSelection]);\n useEffect(() => {\n if (!draft && returnToEdit.current) {\n editButton.current?.focus();\n returnToEdit.current = false;\n }\n }, [draft]);\n\n const conflictedJobs = snapshot.jobs.filter((job) => conflictsFor(snapshot.jobs, job).length > 0);\n const conflictPairs =\n conflictedJobs.reduce((total, job) => total + conflictsFor(snapshot.jobs, job).length, 0) / 2;\n const firstConflict = conflictedJobs[0];\n const firstConflictPeer = firstConflict\n ? snapshot.jobs.find((job) => job.id === conflictsFor(snapshot.jobs, firstConflict)[0])\n : undefined;\n const resourceName = (id: string) =>\n snapshot.resources.find((resource) => resource.id === id)?.name ?? id;\n const jobLabel = (job: ScheduleJob) =>\n `Select ${job.id}, ${job.name}, ${utcWindow(job)}, ${resourceName(job.resourceId)}${\n conflictsFor(snapshot.jobs, job).length ? \", resource conflict\" : \"\"\n }`;\n const choose = (id: string) => {\n if (draft && id !== selected?.id) {\n setRequestedSelection(id);\n return;\n }\n selectionRequested.current = id !== selectedId;\n if (id === selectedId) inspector.current?.focus();\n setSelectedId(id);\n setNotice(\"\");\n setFailure(\"\");\n };\n const closeEditor = () => {\n sequence.current += 1;\n returnToEdit.current = true;\n setPending(false);\n setDraft(null);\n setReview(null);\n setIssues([]);\n setFailure(\"\");\n setRequestedSelection(null);\n };\n const beginEdit = () => {\n if (!selected) return;\n setDraft({\n scheduleId: snapshot.id,\n jobId: selected.id,\n revision: snapshot.revision,\n resourceId: selected.resourceId,\n start: utcInput(selected.start),\n end: utcInput(selected.end),\n });\n setReview(null);\n setIssues([]);\n setFailure(\"\");\n setNotice(\"\");\n };\n const reviewChange = () => {\n if (!draft || stale) return;\n const proposed = {\n jobId: draft.jobId,\n resourceId: draft.resourceId,\n start: parseUtcInput(draft.start) ?? NaN,\n end: parseUtcInput(draft.end) ?? NaN,\n };\n const nextIssues = validateChange(snapshot, proposed);\n setIssues(nextIssues);\n setFailure(\"\");\n if (nextIssues.length === 0) setReview(proposed);\n };\n const apply = async () => {\n if (!review || !draft || stale || pending || !onApply) return;\n const nextIssues = validateChange(snapshot, review);\n if (nextIssues.length) {\n setReview(null);\n setIssues(nextIssues);\n return;\n }\n const requestSequence = ++sequence.current;\n const request: ScheduleApplyRequest = {\n ...review,\n scheduleId: snapshot.id,\n expectedRevision: draft.revision,\n requestId: `${instance}-${requestSequence}`,\n };\n setPending(true);\n setFailure(\"\");\n try {\n const result = await onApply(request);\n if (\n !mounted.current ||\n sequence.current !== requestSequence ||\n source.current.id !== request.scheduleId ||\n source.current.revision !== request.expectedRevision\n )\n return;\n if (!result || result.requestId !== request.requestId) {\n setFailure(\n \"The service response did not match this request. Your proposed change is retained.\",\n );\n return;\n }\n if (result.status === \"rejected\" || result.status === \"conflict\") {\n setFailure(\n result.message || \"The change was not accepted. Review the source and try again.\",\n );\n return;\n }\n if (result.status !== \"accepted\") {\n setFailure(\"The service returned an unknown outcome. Your proposed change is retained.\");\n return;\n }\n const accepted = result.snapshot;\n const job = accepted?.jobs?.find((item) => item.id === request.jobId);\n if (\n !accepted ||\n accepted.id !== request.scheduleId ||\n typeof accepted.revision !== \"string\" ||\n !accepted.revision ||\n accepted.revision === request.expectedRevision ||\n !Array.isArray(accepted.resources) ||\n !job ||\n job.resourceId !== request.resourceId ||\n job.start !== request.start ||\n job.end !== request.end ||\n validateChange(accepted, request).length > 0\n ) {\n setFailure(\n \"The service returned an inconsistent schedule. Your proposed change is retained.\",\n );\n return;\n }\n onAccepted?.(accepted);\n returnToEdit.current = true;\n setDraft(null);\n setReview(null);\n setNotice(acceptanceMessage);\n } catch {\n if (mounted.current && sequence.current === requestSequence)\n setFailure(\n \"The schedule service could not be reached. Your proposed change is retained; retry or return to editing.\",\n );\n } finally {\n if (mounted.current && sequence.current === requestSequence) setPending(false);\n }\n };\n\n return (\n <section\n ref={workspace}\n aria-label={snapshot.title}\n className=\"not-prose min-w-0 border border-oprt-border bg-oprt-surface-1 font-sans text-oprt-base normal-case text-oprt-text\"\n >\n <header className=\"flex flex-wrap items-start justify-between gap-3u border-b border-oprt-border p-3u\">\n <div className=\"min-w-0 flex flex-col gap-1u\">\n <h2 className=\"m-0 text-oprt-xl font-semibold normal-case tracking-normal\">\n {snapshot.title}\n </h2>\n <p className=\"m-0 text-oprt-text-dim\">{snapshot.location}</p>\n <p className=\"m-0 text-oprt-text-dim\">{snapshot.summary}</p>\n </div>\n <div className=\"flex flex-col gap-1u text-oprt-text-dim\">\n <p className=\"m-0 text-oprt-sm font-medium uppercase tracking-oprt-sm\">Planning window</p>\n <p className=\"m-0 font-mono text-oprt-text\">{utcWindow(snapshot)}</p>\n <p className=\"m-0\">\n {snapshot.jobs.length} work orders · {snapshot.resources.length} resources ·{\" \"}\n <span className=\"font-mono\">{snapshot.revision}</span>\n </p>\n </div>\n </header>\n {/* Beside or under the schedule by the container's width, like the list/timeline switch. A 1 px gap over\n the border color is the divider, so it is right in both arrangements. */}\n <div className=\"flex min-w-0 flex-wrap gap-px bg-oprt-border forced-colors:bg-[CanvasText]\">\n <div className=\"min-w-0 flex-[999_1_36rem] bg-oprt-surface-1\">\n <div className=\"flex flex-wrap items-center justify-between gap-2u border-b border-oprt-border px-3u py-2u\">\n <div className=\"flex flex-wrap items-center gap-2u\">\n <h3 className=\"m-0 text-oprt-md font-medium\">Resource allocation</h3>\n <Badge appearance=\"text\" variant={conflictPairs ? \"warning\" : \"success\"}>\n {conflictPairs\n ? `${conflictPairs} resource conflict${conflictPairs === 1 ? \"\" : \"s\"}`\n : \"No resource conflicts\"}\n </Badge>\n </div>\n {requestedSelection && selected && draft && (\n <p className=\"m-0 text-oprt-warning\">\n Draft open on {selected.id}: keep or discard it to open {requestedSelection}.\n </p>\n )}\n <div role=\"group\" aria-label=\"Schedule view\" className=\"flex gap-1u\">\n <Button\n size=\"sm\"\n variant={view === \"schedule\" ? \"secondary\" : \"ghost\"}\n aria-pressed={view === \"schedule\"}\n onClick={() => setView(\"schedule\")}\n >\n Schedule\n </Button>\n <Button\n size=\"sm\"\n variant={view === \"list\" ? \"secondary\" : \"ghost\"}\n aria-pressed={view === \"list\"}\n onClick={() => setView(\"list\")}\n >\n List\n </Button>\n </div>\n </div>\n <section\n aria-label=\"Conflict review\"\n className=\"flex flex-col gap-1u border-b border-oprt-border px-3u py-2u\"\n >\n <h4 className=\"m-0 text-oprt-base font-medium\">Conflict review</h4>\n {firstConflict && firstConflictPeer ? (\n <div className=\"flex flex-wrap items-center justify-between gap-2u\">\n <p className=\"m-0 max-w-[65ch] text-oprt-text-dim\">\n <span className=\"font-mono text-oprt-text\">{firstConflict.id}</span> and{\" \"}\n <span className=\"font-mono text-oprt-text\">{firstConflictPeer.id}</span> need{\" \"}\n {resourceName(firstConflict.resourceId)} at the same time. Reassign a compatible\n resource or adjust the service window.\n </p>\n <Button size=\"sm\" variant=\"tertiary\" onClick={() => choose(firstConflictPeer.id)}>\n Inspect conflict\n </Button>\n </div>\n ) : (\n <p className=\"m-0 text-oprt-text-dim\">\n Resource windows are clear. Prerequisites and downstream work are checked before\n each change is applied.\n </p>\n )}\n </section>\n {view === \"schedule\" ? (\n <div\n role=\"region\"\n aria-label=\"Resource timeline, scroll horizontally for later hours\"\n tabIndex={0}\n className={`overflow-x-auto ${focusStyle}`}\n >\n <div className=\"min-w-[860px]\">\n <div className=\"grid grid-cols-[150px_minmax(0,1fr)] border-b border-oprt-border bg-oprt-surface-2\">\n <div className=\"px-3u py-2u font-medium text-oprt-text-dim\">\n Resource / assignment\n </div>\n <div className=\"relative h-5u font-mono text-oprt-text-dim\">\n {Array.from({ length: 7 }, (_, index) => (\n <span\n key={index}\n style={{\n left: `${(index / 6) * 100}%`,\n transform: index === 6 ? \"translateX(-100%)\" : \"none\",\n }}\n className=\"absolute top-2u px-1u\"\n >\n {utcTime(snapshot.start + ((snapshot.end - snapshot.start) * index) / 6)}\n {index === 0 ? \"\\u00A0UTC\" : \"\"}\n </span>\n ))}\n </div>\n </div>\n {snapshot.resources.map((resource) => {\n const lane = layoutLane(snapshot, resource.id);\n const height = lane.trackCount * 78 + 16;\n const reservedHours = lane.items.reduce(\n (total, { job }) => total + (job.end - job.start) / 3_600_000,\n 0,\n );\n return (\n <div\n key={resource.id}\n className=\"grid grid-cols-[150px_minmax(0,1fr)] border-b border-oprt-border last:border-b-0\"\n >\n <div className=\"border-r border-oprt-border px-3u py-3u\">\n <p className=\"m-0 font-medium\">{resource.name}</p>\n <p className=\"m-0 mt-1u text-oprt-text-dim\">{resource.detail}</p>\n <p className=\"m-0 mt-1u font-mono text-oprt-text-dim\">\n {hours(reservedHours)} reserved\n </p>\n </div>\n <div className=\"relative\" style={{ height }}>\n <div\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute inset-0 grid grid-cols-6\"\n >\n {Array.from({ length: 6 }, (_, index) => (\n <div\n key={index}\n className=\"border-r border-oprt-border last:border-r-0\"\n />\n ))}\n </div>\n {lane.items.map(({ job, track, left, width }) => (\n <button\n type=\"button\"\n key={job.id}\n aria-label={jobLabel(job)}\n aria-pressed={selected?.id === job.id}\n onClick={() => choose(job.id)}\n style={{\n left: `calc(${left}% + 4px)`,\n width: `calc(${width}% - 8px)`,\n top: track * 78 + 8,\n height: 70,\n }}\n className={`absolute flex min-w-0 flex-col items-start justify-center gap-0.5u border px-1u text-left text-oprt-base font-medium transition-colors hover:bg-oprt-surface-3 ${focusStyle} ${selected?.id === job.id ? `border-oprt-accent bg-oprt-surface-3 ${selectedBar}` : \"border-oprt-border bg-oprt-surface-2\"}`}\n >\n <span className=\"flex w-full justify-between gap-1u font-mono text-oprt-text-dim\">\n <span>{job.id}</span>\n {conflictsFor(snapshot.jobs, job).length > 0 && (\n <span className=\"font-sans text-oprt-warning\">Conflict</span>\n )}\n </span>\n <span className=\"w-full leading-tight\">{job.name}</span>\n <span className=\"font-mono text-oprt-text-dim\">\n {utcTime(job.start)}–{utcTime(job.end)} UTC\n </span>\n </button>\n ))}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n ) : narrow ? (\n <ul aria-label=\"Work order list\" className=\"m-0 list-none p-0\">\n {[...snapshot.jobs]\n .sort((a, b) => a.start - b.start || a.id.localeCompare(b.id))\n .map((job) => (\n <li key={job.id} className=\"border-b border-oprt-border last:border-b-0\">\n <button\n type=\"button\"\n aria-label={jobLabel(job)}\n aria-pressed={selected?.id === job.id}\n onClick={() => choose(job.id)}\n className={`relative flex w-full flex-col gap-1u p-2u text-left text-oprt-base font-medium hover:bg-oprt-surface-3 ${focusStyle} ${selected?.id === job.id ? `bg-oprt-surface-3 ${selectedBar}` : \"\"}`}\n >\n <span className=\"flex w-full flex-wrap justify-between gap-1u\">\n <span className=\"font-mono text-oprt-text-dim\">{job.id}</span>\n <Badge\n appearance=\"text\"\n variant={conflictsFor(snapshot.jobs, job).length ? \"warning\" : \"neutral\"}\n >\n {conflictsFor(snapshot.jobs, job).length ? \"Conflict\" : \"Scheduled\"}\n </Badge>\n </span>\n <span>{job.name}</span>\n <span className=\"text-oprt-text-dim\">{job.asset}</span>\n <span className=\"text-oprt-text-dim\">{resourceName(job.resourceId)}</span>\n <span className=\"font-mono\">\n {utcTime(job.start)}–{utcTime(job.end)} UTC\n </span>\n </button>\n </li>\n ))}\n </ul>\n ) : (\n <div className=\"overflow-x-auto\">\n <table\n aria-label=\"Work order list\"\n className=\"w-full min-w-[640px] border-collapse text-left text-oprt-base\"\n >\n <thead className=\"bg-oprt-surface-2\">\n <tr>\n {[\"Work order\", \"Resource\", \"Window · UTC\", \"Status\"].map((heading) => (\n <th\n key={heading}\n scope=\"col\"\n className=\"border-b border-oprt-border px-2u py-2u font-medium text-oprt-text-dim\"\n >\n {heading}\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {[...snapshot.jobs]\n .sort((a, b) => a.start - b.start || a.id.localeCompare(b.id))\n .map((job) => (\n <tr\n key={job.id}\n className={selected?.id === job.id ? \"bg-oprt-surface-3\" : \"\"}\n >\n <td\n className={`border-b border-oprt-border p-2u ${selected?.id === job.id ? `relative ${selectedBar}` : \"\"}`}\n >\n <button\n type=\"button\"\n aria-label={jobLabel(job)}\n aria-pressed={selected?.id === job.id}\n onClick={() => choose(job.id)}\n className={`flex flex-col gap-0.5u text-left font-medium ${focusStyle}`}\n >\n <span>{job.name}</span>\n <span className=\"font-mono text-oprt-text-dim\">{job.id}</span>\n </button>\n </td>\n <td className=\"border-b border-oprt-border p-2u\">\n {resourceName(job.resourceId)}\n </td>\n <td className=\"border-b border-oprt-border p-2u font-mono\">\n {utcTime(job.start)}–{utcTime(job.end)}\n </td>\n <td className=\"border-b border-oprt-border p-2u\">\n <Badge\n appearance=\"text\"\n variant={\n conflictsFor(snapshot.jobs, job).length ? \"warning\" : \"neutral\"\n }\n >\n {conflictsFor(snapshot.jobs, job).length ? \"Conflict\" : \"Scheduled\"}\n </Badge>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n )}\n <div className=\"border-t border-oprt-border px-3u py-2u\">\n <p className=\"m-0 text-oprt-text-dim\">\n All times are UTC. A resource may start its next work order exactly when the previous\n order ends.\n </p>\n </div>\n </div>\n <Inspector\n ref={inspector}\n tabIndex={-1}\n title=\"Selected work order\"\n className={`min-w-0 flex-[1_1_20rem] border-0 p-3u ${focusStyle}`}\n >\n {selected ? (\n <>\n <EntityPreview\n entity={{\n id: selected.id,\n name: selected.name,\n typeLabel: selected.asset,\n summary: selected.summary,\n condition: {\n label:\n conflictsFor(snapshot.jobs, selected).length > 0\n ? \"Resource conflict\"\n : \"Scheduled\",\n variant:\n conflictsFor(snapshot.jobs, selected).length > 0 ? \"warning\" : \"neutral\",\n },\n facts: [\n { id: \"resource\", label: \"Resource\", value: resourceName(selected.resourceId) },\n { id: \"owner\", label: \"Work lead\", value: selected.owner },\n {\n id: \"window\",\n label: \"Service window\",\n value: <span className=\"font-mono\">{utcWindow(selected)}</span>,\n },\n {\n id: \"duration\",\n label: \"Duration\",\n value: (\n <span className=\"font-mono\">\n {hours((selected.end - selected.start) / 3_600_000)}\n </span>\n ),\n },\n ],\n quality: {\n state: \"current\",\n source: `Supplied plan · ${snapshot.revision}`,\n detail: \"Freshness reflects the supplied snapshot.\",\n },\n }}\n />\n <Dependencies selected={selected} snapshot={snapshot} />\n {requestedSelection && (\n <div className=\"flex flex-col gap-1u border-t border-oprt-border pt-2u\">\n <p role=\"alert\" className=\"m-0\">\n {pending\n ? \"The service is processing this change. Wait for its outcome before opening another work order.\"\n : `Keep this draft or discard it to open ${requestedSelection}.`}\n </p>\n <div className=\"flex flex-wrap gap-1u\">\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n disabled={pending}\n onClick={() => {\n const next = requestedSelection;\n closeEditor();\n setSelectedId(next);\n }}\n >\n Discard change and open work order\n </Button>\n <Button\n ref={keepEditing}\n size=\"sm\"\n variant=\"ghost\"\n onClick={() => setRequestedSelection(null)}\n >\n Keep editing\n </Button>\n </div>\n </div>\n )}\n {notice && (\n <p role=\"status\" className=\"m-0 border-t border-oprt-border pt-2u\">\n {notice}\n </p>\n )}\n {draft ? (\n <div className=\"flex flex-col gap-2u border-t border-oprt-border pt-2u\">\n {stale && (\n <div role=\"alert\" className=\"flex flex-col gap-1u\">\n <p className=\"m-0 text-oprt-warning\">\n The source revision changed. Load the latest revision before editing again.\n </p>\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n className=\"self-start\"\n onClick={closeEditor}\n >\n Load latest revision\n </Button>\n </div>\n )}\n {failure && (\n <p role=\"alert\" className=\"m-0 text-oprt-error\">\n {failure}\n </p>\n )}\n {review ? (\n <section\n ref={reviewRef}\n tabIndex={-1}\n aria-label=\"Review schedule change\"\n className={`flex flex-col gap-2u ${focusStyle}`}\n >\n <h4 className=\"m-0 text-oprt-md font-medium\">Review schedule change</h4>\n <p className=\"m-0 font-mono\">\n {selected.id} · {draft.revision}\n </p>\n <div className=\"flex flex-col gap-1u\">\n <p className=\"m-0 font-medium\">Current</p>\n <p className=\"m-0\">{resourceName(selected.resourceId)}</p>\n <p className=\"m-0 font-mono\">{utcWindow(selected)}</p>\n </div>\n <div className=\"flex flex-col gap-1u\">\n <p className=\"m-0 font-medium\">Proposed</p>\n <p className=\"m-0\">{resourceName(review.resourceId)}</p>\n <p className=\"m-0 font-mono\">{utcWindow(review)}</p>\n </div>\n <p className=\"m-0 text-oprt-text-dim\">\n No resource overlap. Prerequisites and downstream work remain in sequence.\n </p>\n {!onApply && (\n <p className=\"m-0 text-oprt-text-dim\">\n Review only: connect a schedule service to apply changes.\n </p>\n )}\n <div className=\"flex flex-wrap gap-1u\">\n {onApply && (\n <Button disabled={pending || stale} onClick={() => void apply()}>\n {pending ? \"Applying change…\" : \"Apply schedule change\"}\n </Button>\n )}\n <Button\n variant=\"tertiary\"\n disabled={pending}\n onClick={() => {\n setReview(null);\n setFailure(\"\");\n }}\n >\n Back to editing\n </Button>\n </div>\n </section>\n ) : (\n <form\n ref={formRef}\n aria-label=\"Edit work window\"\n className=\"flex flex-col gap-2u\"\n onSubmit={(event) => {\n event.preventDefault();\n reviewChange();\n }}\n noValidate\n >\n <h4 className=\"m-0 text-oprt-md font-medium\">Edit work window</h4>\n <Field label=\"Resource\" controlId={`${instance}-resource`}>\n <Select\n value={draft.resourceId}\n onValueChange={(resourceId) => setDraft({ ...draft, resourceId })}\n disabled={stale}\n className=\"w-full\"\n >\n <>\n {snapshot.resources\n .filter(\n (resource) =>\n !selected.eligibleResourceIds ||\n selected.eligibleResourceIds.includes(resource.id),\n )\n .map((resource) => (\n <SelectOption key={resource.id} value={resource.id}>\n {resource.name}\n </SelectOption>\n ))}\n </>\n </Select>\n </Field>\n <Field label=\"Start · UTC\" controlId={`${instance}-start`}>\n <Input\n type=\"datetime-local\"\n step={60}\n value={draft.start}\n disabled={stale}\n onChange={(event) => setDraft({ ...draft, start: event.target.value })}\n className=\"w-full\"\n />\n </Field>\n <Field label=\"End · UTC\" controlId={`${instance}-end`}>\n <Input\n type=\"datetime-local\"\n step={60}\n value={draft.end}\n disabled={stale}\n onChange={(event) => setDraft({ ...draft, end: event.target.value })}\n className=\"w-full\"\n />\n </Field>\n <p className=\"m-0 text-oprt-text-dim\">\n Enter UTC values within {utcWindow(snapshot)}. Local device time is not\n used.\n </p>\n {issues.length > 0 && (\n <div role=\"alert\" className=\"flex flex-col gap-1u text-oprt-error\">\n <p className=\"m-0 font-medium\">Resolve before review</p>\n <ul className=\"m-0 list-disc space-y-1u pl-2u\">\n {issues.map((issue, index) => (\n <li key={index}>{issue.message}</li>\n ))}\n </ul>\n </div>\n )}\n <div className=\"flex flex-wrap gap-1u\">\n <Button type=\"submit\" disabled={stale}>\n Review change\n </Button>\n <Button variant=\"tertiary\" onClick={closeEditor}>\n Cancel edit\n </Button>\n </div>\n </form>\n )}\n </div>\n ) : (\n <div className=\"border-t border-oprt-border pt-2u\">\n <Button ref={editButton} onClick={beginEdit}>\n Edit work window\n </Button>\n </div>\n )}\n </>\n ) : snapshot.jobs.length && selectedId ? (\n <p role=\"status\" className=\"m-0 text-oprt-text-dim\">\n {selectedId} is no longer in this revision. Choose a work order.\n </p>\n ) : (\n <p className=\"m-0 text-oprt-text-dim\">No work orders in this planning window.</p>\n )}\n </Inspector>\n </div>\n </section>\n );\n}\n\nfunction Dependencies({\n selected,\n snapshot,\n}: {\n selected: ScheduleJob;\n snapshot: ScheduleSnapshot;\n}) {\n const prerequisites = selected.prerequisites.map((id) =>\n snapshot.jobs.find((job) => job.id === id),\n );\n const downstream = snapshot.jobs.filter((job) => job.prerequisites.includes(selected.id));\n return (\n <section\n aria-label=\"Dependencies and impact\"\n className=\"flex flex-col gap-2u border-t border-oprt-border pt-2u\"\n >\n <h4 className=\"m-0 text-oprt-md font-medium\">Dependencies & impact</h4>\n <div className=\"flex flex-col gap-1u\">\n <p className=\"m-0 font-medium text-oprt-text-dim\">Before this work</p>\n {prerequisites.length ? (\n prerequisites.map((job, index) => (\n <p key={selected.prerequisites[index]} className=\"m-0\">\n {job ? (\n <>\n {job.name}\n <span className=\"mt-0.5u block font-mono text-oprt-text-dim\">\n {job.id} · ends {utcTime(job.end)} UTC\n </span>\n </>\n ) : (\n `Missing prerequisite: ${selected.prerequisites[index]}`\n )}\n </p>\n ))\n ) : (\n <p className=\"m-0\">No prerequisites assigned.</p>\n )}\n </div>\n <div className=\"flex flex-col gap-1u\">\n <p className=\"m-0 font-medium text-oprt-text-dim\">Work that depends on this</p>\n {downstream.length ? (\n downstream.map((job) => (\n <p key={job.id} className=\"m-0\">\n {job.name}\n <span className=\"mt-0.5u block font-mono text-oprt-text-dim\">\n {job.id} · starts {utcTime(job.start)} UTC\n </span>\n </p>\n ))\n ) : (\n <p className=\"m-0\">No downstream work assigned.</p>\n )}\n </div>\n </section>\n );\n}\n","patterns/planning-scheduling/schedule-state.ts":"export type ScheduleResource = { id: string; name: string; detail: string };\nexport type ScheduleJob = {\n id: string;\n name: string;\n asset: string;\n owner: string;\n resourceId: string;\n eligibleResourceIds?: readonly string[];\n start: number;\n end: number;\n prerequisites: readonly string[];\n summary: string;\n};\nexport type ScheduleSnapshot = {\n id: string;\n revision: string;\n title: string;\n location: string;\n summary: string;\n start: number;\n end: number;\n resources: readonly ScheduleResource[];\n jobs: readonly ScheduleJob[];\n};\nexport type ScheduleChange = { jobId: string; resourceId: string; start: number; end: number };\nexport type ScheduleApplyRequest = ScheduleChange & {\n scheduleId: string;\n expectedRevision: string;\n requestId: string;\n};\nexport type ScheduleApplyResult =\n | { status: \"accepted\"; requestId: string; snapshot: ScheduleSnapshot }\n | { status: \"rejected\" | \"conflict\"; requestId: string; message: string };\nexport type ScheduleIssue = {\n kind: \"window\" | \"resource\" | \"dependency\";\n message: string;\n jobId?: string;\n};\n\n/** Resource reservations are half-open intervals: [start, end). */\nexport function conflictsFor(jobs: readonly ScheduleJob[], job: ScheduleJob): string[] {\n return jobs\n .filter(\n (other) =>\n other.id !== job.id &&\n other.resourceId === job.resourceId &&\n other.start < job.end &&\n job.start < other.end,\n )\n .map((other) => other.id);\n}\n\nexport function validateChange(\n snapshot: ScheduleSnapshot,\n change: ScheduleChange,\n): ScheduleIssue[] {\n const original = snapshot.jobs.find((job) => job.id === change.jobId);\n if (!original)\n return [\n {\n kind: \"window\",\n message: \"This work order is no longer in the schedule. Load the latest revision.\",\n },\n ];\n if (!Number.isFinite(change.start) || !Number.isFinite(change.end) || change.end <= change.start)\n return [{ kind: \"window\", message: \"Enter a complete window with the end after the start.\" }];\n if (change.start < snapshot.start || change.end > snapshot.end)\n return [{ kind: \"window\", message: \"Keep both times within the displayed planning window.\" }];\n if (!snapshot.resources.some((resource) => resource.id === change.resourceId))\n return [{ kind: \"resource\", message: \"Choose an available resource.\" }];\n if (original.eligibleResourceIds && !original.eligibleResourceIds.includes(change.resourceId))\n return [\n {\n kind: \"resource\",\n message: \"This resource cannot perform the work. Choose a compatible resource.\",\n },\n ];\n const proposed = { ...original, ...change, id: original.id };\n const issues: ScheduleIssue[] = conflictsFor(snapshot.jobs, proposed).map((id) => ({\n kind: \"resource\",\n jobId: id,\n message: `Resource overlaps ${id} · ${snapshot.jobs.find((job) => job.id === id)!.name}. Choose another resource or window.`,\n }));\n for (const id of original.prerequisites) {\n const prerequisite = snapshot.jobs.find((job) => job.id === id);\n if (!prerequisite || prerequisite.end > change.start)\n issues.push({\n kind: \"dependency\",\n jobId: id,\n message: prerequisite\n ? `${id} · ${prerequisite.name} must finish before this work starts.`\n : `Prerequisite ${id} is missing. Restore the source record before applying.`,\n });\n }\n for (const dependent of snapshot.jobs.filter((job) => job.prerequisites.includes(original.id))) {\n if (dependent.start < change.end)\n issues.push({\n kind: \"dependency\",\n jobId: dependent.id,\n message: `${dependent.id} · ${dependent.name} starts before this work would finish. Keep its prerequisite available.`,\n });\n }\n return issues;\n}\n\n/** Track packing preserves exact time positions while making overlaps selectable. */\nexport function layoutLane(snapshot: ScheduleSnapshot, resourceId: string) {\n const ends: number[] = [];\n const duration = snapshot.end - snapshot.start;\n const items = snapshot.jobs\n .filter((job) => job.resourceId === resourceId)\n .sort((a, b) => a.start - b.start || a.id.localeCompare(b.id))\n .map((job) => {\n let track = ends.findIndex((end) => end <= job.start);\n if (track === -1) track = ends.length;\n ends[track] = job.end;\n return {\n job,\n track,\n left: ((job.start - snapshot.start) / duration) * 100,\n width: ((job.end - job.start) / duration) * 100,\n };\n });\n return { items, trackCount: Math.max(1, ends.length) };\n}\n\nexport function parseUtcInput(value: string): number | null {\n if (!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}$/.test(value)) return null;\n const instant = Date.parse(`${value}:00Z`);\n return Number.isFinite(instant) && new Date(instant).toISOString().slice(0, 16) === value\n ? instant\n : null;\n}\nexport const utcInput = (instant: number) => new Date(instant).toISOString().slice(0, 16);\nexport const utcTime = (instant: number) => new Date(instant).toISOString().slice(11, 16);\nexport const utcDate = (instant: number) => new Date(instant).toISOString().slice(0, 10);\nexport const utcWindow = (job: { start: number; end: number }) =>\n `${utcDate(job.start)} ${utcTime(job.start)}–${utcDate(job.start) === utcDate(job.end) ? \"\" : `${utcDate(job.end)} `}${utcTime(job.end)} UTC`;\n","patterns/resource-management/demo.tsx":"\"use client\";\n\nimport { useRef, useState } from \"react\";\nimport { createResourceRecords } from \"./fixtures\";\nimport { AssetManagement } from \"./resource-management\";\nimport { applyResourceSave, type ResourceSaveRequest, type ResourceSaveResult } from \"./state\";\n\nexport function AssetManagementDemo() {\n const [records, setRecords] = useState(() => createResourceRecords(\"industrial\"));\n const current = useRef(records);\n async function save(request: ResourceSaveRequest): Promise<ResourceSaveResult> {\n if (request.domain !== \"industrial\")\n return {\n state: \"rejected\",\n message: \"This request belongs to a different register. Reopen the asset and try again.\",\n };\n const outcome = applyResourceSave(current.current, request, new Date().toISOString());\n if (outcome.state === \"accepted\" && outcome.records) {\n current.current = outcome.records;\n setRecords(outcome.records);\n }\n return outcome;\n }\n return <AssetManagement records={records} onSave={save} />;\n}\n\n/** Compatibility export for applications using the original recipe name. */\nexport const ResourceManagementDemo = AssetManagementDemo;\n","patterns/resource-management/fixtures.ts":"import type { OperationalDomain } from \"../operational-domain\";\n\nexport type ResourceCondition = \"ready\" | \"restricted\" | \"maintenance\";\nexport type AssetServiceRecord = {\n id: string;\n kind: \"inspection\" | \"service\";\n title: string;\n recordedAt: string;\n recordedBy: string;\n result: \"Complete\" | \"Action required\" | \"In progress\";\n detail: string;\n};\nexport type ManagedResource = {\n id: string;\n revision: number;\n name: string;\n category: string;\n owner: string;\n location: string;\n condition: ResourceCondition;\n serviceDue: string;\n note: string;\n updatedAt: string;\n /** Supplied evidence stays separate from editable assignment and service notes. */\n serviceRecords?: readonly AssetServiceRecord[];\n activity: readonly { id: string; at: string; detail: string }[];\n};\n\nexport const conditionLabels: Record<ResourceCondition, string> = {\n ready: \"Ready\",\n restricted: \"Restricted\",\n maintenance: \"Maintenance\",\n};\n\nexport const resourceScenarios = {\n military: {\n title: \"Alder Garrison\",\n department: \"Sustainment company\",\n role: \"Logistics desk · Sgt. Maya Chen\",\n source: \"Alder logistics ledger\",\n date: \"02 Oct 2026\",\n time: \"08:30 UTC\",\n owners: [\"Sgt. Maya Chen\", \"Cpl. Noah Bennett\", \"Sgt. Leila Morgan\", \"Cpl. Evan Shaw\"],\n locations: [\"Vehicle park\", \"Power compound\", \"Supply yard\", \"Workshop 2\"],\n categories: [\n \"Cargo vehicle\",\n \"Power unit\",\n \"Water support\",\n \"Material handling\",\n \"Workshop equipment\",\n ],\n selectedId: \"GEN-018\",\n idPlaceholder: \"MTR-053\",\n },\n industrial: {\n title: \"Northbank Works\",\n department: \"Plant maintenance\",\n role: \"Maintenance desk · Priya Shah\",\n source: \"Northbank asset register\",\n date: \"02 Oct 2026\",\n time: \"08:30 UTC\",\n owners: [\"Priya Shah\", \"Marcus Reed\", \"Elena Ruiz\", \"Theo Walker\"],\n locations: [\"Process hall\", \"Utilities bay\", \"Loading dock\", \"Maintenance shop\"],\n categories: [\n \"Rotating equipment\",\n \"Compressed air\",\n \"Material handling\",\n \"Electrical\",\n \"Process control\",\n ],\n selectedId: \"PMP-204\",\n idPlaceholder: \"PMP-212\",\n },\n} as const;\n\ntype Seed = [string, string, string, string, string, ResourceCondition, string, string];\nconst seeds: Record<OperationalDomain, readonly Seed[]> = {\n military: [\n [\n \"MTR-041\",\n \"Cargo truck 07\",\n \"Cargo vehicle\",\n \"Cpl. Noah Bennett\",\n \"Vehicle park\",\n \"ready\",\n \"2026-10-09\",\n \"Released after the weekly brake and fluid inspection. Assigned to the morning supply detail.\",\n ],\n [\n \"GEN-018\",\n \"Forward power unit\",\n \"Power unit\",\n \"Sgt. Maya Chen\",\n \"Workshop 2\",\n \"maintenance\",\n \"2026-10-02\",\n \"Replace the starter battery, then complete a 30-minute load test before release to the power compound.\",\n ],\n [\n \"WTR-009\",\n \"Water bowser 03\",\n \"Water support\",\n \"Sgt. Leila Morgan\",\n \"Supply yard\",\n \"ready\",\n \"2026-10-06\",\n \"Tank cleaned and seals checked. Potable-water certificate is filed with the supply desk.\",\n ],\n [\n \"FLT-012\",\n \"Rough-terrain forklift\",\n \"Material handling\",\n \"Cpl. Evan Shaw\",\n \"Supply yard\",\n \"restricted\",\n \"2026-10-03\",\n \"Use within the paved supply yard. Replace the left work lamp before night operations.\",\n ],\n [\n \"MTR-046\",\n \"Cargo truck 12\",\n \"Cargo vehicle\",\n \"Cpl. Noah Bennett\",\n \"Vehicle park\",\n \"ready\",\n \"2026-10-12\",\n \"Weekly inspection complete. Next assignment is the afternoon stores collection.\",\n ],\n [\n \"GEN-021\",\n \"Reserve generator\",\n \"Power unit\",\n \"Sgt. Maya Chen\",\n \"Power compound\",\n \"ready\",\n \"2026-10-08\",\n \"Fuel topped up and changeover test complete. Held as reserve for the east accommodation block.\",\n ],\n [\n \"TRL-033\",\n \"Stores trailer 06\",\n \"Cargo vehicle\",\n \"Sgt. Leila Morgan\",\n \"Vehicle park\",\n \"ready\",\n \"2026-10-14\",\n \"Tie-downs and coupling checked. Parked empty pending the next stores movement.\",\n ],\n [\n \"CMP-007\",\n \"Mobile air compressor\",\n \"Workshop equipment\",\n \"Cpl. Evan Shaw\",\n \"Workshop 2\",\n \"maintenance\",\n \"2026-10-02\",\n \"Outlet coupling leaks under pressure. Parts are held at the workshop counter for replacement.\",\n ],\n [\n \"WTR-011\",\n \"Water treatment skid\",\n \"Water support\",\n \"Sgt. Leila Morgan\",\n \"Supply yard\",\n \"ready\",\n \"2026-10-05\",\n \"Filter cartridges replaced on 30 September. Sample results are recorded in the treatment log.\",\n ],\n [\n \"LFT-004\",\n \"Workshop lifting set\",\n \"Workshop equipment\",\n \"Cpl. Evan Shaw\",\n \"Workshop 2\",\n \"ready\",\n \"2026-10-16\",\n \"Slings and shackles checked; inspection tags are present. Return to the marked storage rack after use.\",\n ],\n ],\n industrial: [\n [\n \"PMP-201\",\n \"Cooling water pump A\",\n \"Rotating equipment\",\n \"Priya Shah\",\n \"Process hall\",\n \"ready\",\n \"2026-10-09\",\n \"Duty pump. Seal check and vibration route are complete; the standby changeover is scheduled for Friday.\",\n ],\n [\n \"PMP-204\",\n \"Cooling water pump B\",\n \"Rotating equipment\",\n \"Priya Shah\",\n \"Maintenance shop\",\n \"maintenance\",\n \"2026-10-02\",\n \"Replace the drive-end bearing, align the coupling, then run the acceptance test before returning to standby.\",\n ],\n [\n \"AIR-031\",\n \"Instrument air compressor\",\n \"Compressed air\",\n \"Marcus Reed\",\n \"Utilities bay\",\n \"ready\",\n \"2026-10-07\",\n \"Dryer service complete. Duty pressure remains within the recorded operating range.\",\n ],\n [\n \"FLT-018\",\n \"Electric forklift 04\",\n \"Material handling\",\n \"Theo Walker\",\n \"Loading dock\",\n \"restricted\",\n \"2026-10-03\",\n \"Day shift use only until the rear work lamp is replaced. Charger bay 4 is reserved for this truck.\",\n ],\n [\n \"FAN-112\",\n \"Extraction fan 12\",\n \"Rotating equipment\",\n \"Elena Ruiz\",\n \"Process hall\",\n \"ready\",\n \"2026-10-12\",\n \"Belt tension checked on the last route. Next inspection includes the guard fasteners.\",\n ],\n [\n \"MCC-022\",\n \"Motor control cabinet 2\",\n \"Electrical\",\n \"Elena Ruiz\",\n \"Utilities bay\",\n \"ready\",\n \"2026-10-14\",\n \"Quarterly inspection complete. The updated circuit schedule is stored inside the door.\",\n ],\n [\n \"CNV-006\",\n \"Dispatch conveyor\",\n \"Material handling\",\n \"Theo Walker\",\n \"Loading dock\",\n \"ready\",\n \"2026-10-06\",\n \"Tracking adjusted and stops tested. Cleared for the morning dispatch window.\",\n ],\n [\n \"VLV-087\",\n \"Steam isolation valve\",\n \"Process control\",\n \"Marcus Reed\",\n \"Maintenance shop\",\n \"maintenance\",\n \"2026-10-02\",\n \"Packing replacement in progress. Bench leak test is required before the valve is returned to utilities.\",\n ],\n [\n \"SNS-042\",\n \"Level transmitter 42\",\n \"Process control\",\n \"Elena Ruiz\",\n \"Process hall\",\n \"ready\",\n \"2026-10-08\",\n \"Calibration verified against the workshop reference. Next route includes a cable-gland check.\",\n ],\n [\n \"AIR-034\",\n \"Standby air compressor\",\n \"Compressed air\",\n \"Marcus Reed\",\n \"Utilities bay\",\n \"ready\",\n \"2026-10-16\",\n \"Weekly start test complete. Available for the next duty rotation after the receiver is drained.\",\n ],\n ],\n};\n\nconst inspectionNotes: Record<string, string> = {\n \"PMP-201\": \"Seal inspection and vibration route complete. No defects recorded.\",\n \"PMP-204\":\n \"Drive-end bearing vibration measured above the plant limit. Removed from standby duty.\",\n \"AIR-031\": \"Dryer outlet and duty pressure checked within the recorded operating range.\",\n \"FLT-018\": \"Rear work lamp failed the shift inspection. Day shift restriction recorded.\",\n \"FAN-112\": \"Belt tension and guard condition checked on the weekly route.\",\n \"MCC-022\": \"Quarterly visual inspection complete; circuit schedule verified.\",\n \"CNV-006\": \"Conveyor tracking and emergency stops passed the dispatch inspection.\",\n \"VLV-087\": \"Stem packing leak found during the utilities route. Valve sent for bench service.\",\n \"SNS-042\": \"Calibration verified against the workshop reference.\",\n \"AIR-034\": \"Weekly start test complete; standby availability confirmed.\",\n};\n\nfunction createServiceRecords(\n id: string,\n owner: string,\n condition: ResourceCondition,\n): AssetServiceRecord[] {\n const inspection: AssetServiceRecord = {\n id: id === \"PMP-204\" ? \"INSP-4821\" : `INSP-${id}`,\n kind: \"inspection\",\n title: id === \"PMP-204\" ? \"Bearing vibration inspection\" : \"Equipment condition inspection\",\n recordedAt: \"2026-10-01T15:10:00Z\",\n recordedBy: owner,\n result: condition === \"ready\" ? \"Complete\" : \"Action required\",\n detail: inspectionNotes[id]!,\n };\n if (id !== \"PMP-204\") return [inspection];\n return [\n {\n id: \"WO-2641\",\n kind: \"service\",\n title: \"Drive-end bearing replacement\",\n recordedAt: \"2026-10-02T08:10:00Z\",\n recordedBy: \"Marcus Reed\",\n result: \"In progress\",\n detail:\n \"Replacement bearing issued to the maintenance shop. Alignment and acceptance test remain open.\",\n },\n inspection,\n ];\n}\n\n/** Fresh objects keep every documentation scenario isolated from edits in another. */\nexport function createResourceRecords(domain: OperationalDomain): ManagedResource[] {\n return seeds[domain].map(\n ([id, name, category, owner, location, condition, serviceDue, note], index) => ({\n id,\n name,\n category,\n owner,\n location,\n condition,\n serviceDue,\n note,\n revision: index === 1 ? 4 : 2,\n updatedAt: `2026-10-02T08:${String(20 - index).padStart(2, \"0\")}:00Z`,\n ...(domain === \"industrial\"\n ? { serviceRecords: createServiceRecords(id, owner, condition) }\n : {}),\n activity: [\n {\n id: `${id}-check`,\n at: `2026-10-02T08:${String(20 - index).padStart(2, \"0\")}:00Z`,\n detail: `${owner} confirmed ${conditionLabels[condition].toLowerCase()} condition.`,\n },\n {\n id: `${id}-location`,\n at: \"2026-10-01T15:10:00Z\",\n detail: `Location confirmed: ${location}.`,\n },\n ],\n }),\n );\n}\n","patterns/resource-management/index.ts":"export {\n AssetManagement,\n type AssetManagementProps,\n ResourceManagement,\n type ResourceManagementProps,\n} from \"./resource-management\";\nexport { AssetManagementDemo, ResourceManagementDemo } from \"./demo\";\nexport {\n createResourceRecords,\n type ManagedResource,\n type ResourceCondition,\n type AssetServiceRecord,\n} from \"./fixtures\";\nexport {\n applyResourceSave,\n validateResource,\n type ResourceSaveRequest,\n type ResourceSaveResult,\n type ResourceValues,\n} from \"./state\";\n","patterns/resource-management/resource-form.tsx":"\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport {\n Button,\n ConfirmDialog,\n Dialog,\n Field,\n Input,\n Select,\n SelectOption,\n Textarea,\n} from \"@oprt/react\";\nimport type { OperationalDomain } from \"../operational-domain\";\nimport {\n conditionLabels,\n resourceScenarios,\n type ManagedResource,\n type ResourceCondition,\n} from \"./fixtures\";\nimport {\n validateResource,\n valuesOf,\n type ResourceErrors,\n type ResourceSaveRequest,\n type ResourceSaveResult,\n type ResourceValues,\n} from \"./state\";\n\nexport function ResourceForm({\n domain,\n asset = false,\n record,\n onSave,\n onSaved,\n onClose,\n}: {\n domain: OperationalDomain;\n asset?: boolean;\n record: ManagedResource | null;\n onSave: (request: ResourceSaveRequest) => Promise<ResourceSaveResult>;\n onSaved: (record: ManagedResource) => void;\n onClose: () => void;\n}) {\n const scenario = resourceScenarios[domain];\n const subject = asset ? \"asset\" : \"resource\";\n const label = asset ? \"Asset\" : \"Resource\";\n const [baseline] = useState<ResourceValues>(() =>\n record\n ? valuesOf(record)\n : {\n name: \"\",\n category: scenario.categories[0],\n owner: scenario.owners[0],\n location: scenario.locations[0],\n condition: \"ready\",\n serviceDue: \"2026-10-09\",\n note: \"\",\n },\n );\n const [values, setValues] = useState(baseline);\n const [id, setId] = useState(record?.id ?? \"\");\n const [errors, setErrors] = useState<ResourceErrors>({});\n const [failure, setFailure] = useState(\"\");\n const [saving, setSaving] = useState(false);\n const [confirmDiscard, setConfirmDiscard] = useState(false);\n const live = useRef(true);\n const busy = useRef(false);\n const form = useRef<HTMLFormElement>(null);\n const dirty = JSON.stringify(values) !== JSON.stringify(baseline) || id !== (record?.id ?? \"\");\n\n useEffect(() => {\n live.current = true;\n return () => {\n live.current = false;\n };\n }, []);\n useEffect(() => {\n if (!dirty) return;\n const guard = (event: BeforeUnloadEvent) => {\n event.preventDefault();\n event.returnValue = \"\";\n };\n window.addEventListener(\"beforeunload\", guard);\n return () => window.removeEventListener(\"beforeunload\", guard);\n }, [dirty]);\n\n function change<K extends keyof ResourceValues>(key: K, value: ResourceValues[K]) {\n setValues((previous) => ({ ...previous, [key]: value }));\n setErrors((previous) => {\n const next = { ...previous };\n delete next[key];\n return next;\n });\n setFailure(\"\");\n }\n function close() {\n if (busy.current) return;\n if (dirty) setConfirmDiscard(true);\n else onClose();\n }\n async function save() {\n if (busy.current) return;\n const nextErrors = validateResource(domain, id, values);\n setErrors(nextErrors);\n setFailure(\"\");\n if (Object.keys(nextErrors).length) {\n requestAnimationFrame(() =>\n form.current?.querySelector<HTMLElement>(\"[aria-invalid='true']\")?.focus(),\n );\n return;\n }\n busy.current = true;\n setSaving(true);\n try {\n const result = await onSave({\n domain,\n id,\n expectedRevision: record?.revision ?? null,\n values,\n });\n if (!live.current) return;\n if (result.state !== \"accepted\") setFailure(result.message);\n else if (\n result.record.id !== id ||\n !Number.isSafeInteger(result.record.revision) ||\n result.record.revision <= (record?.revision ?? 0)\n )\n setFailure(\n \"The save response did not confirm this revision. Your draft is still open; retry after checking the register.\",\n );\n else onSaved(result.record);\n } catch {\n if (live.current)\n setFailure(`The ${subject} could not be saved. Your draft is still here; try again.`);\n } finally {\n busy.current = false;\n if (live.current) setSaving(false);\n }\n }\n\n return (\n <>\n <Dialog\n open\n title={record ? `Edit ${record.id}` : `Add ${subject}`}\n description={record ? record.name : `Register equipment at ${scenario.title}.`}\n size=\"lg\"\n onOpenChange={(open) => {\n if (!open) close();\n }}\n className=\"max-h-[90dvh] overflow-y-auto\"\n >\n <form\n ref={form}\n className=\"flex flex-col gap-3u\"\n noValidate\n onSubmit={(event) => {\n event.preventDefault();\n void save();\n }}\n aria-busy={saving}\n >\n {!record && (\n <div className=\"grid grid-cols-1 gap-2u sm:grid-cols-2\">\n <Field\n label={`${label} ID`}\n required\n error={errors.id ?? \"\"}\n help={`Example: ${scenario.idPlaceholder}`}\n >\n <Input\n value={id}\n maxLength={21}\n disabled={saving}\n autoComplete=\"off\"\n onChange={(event) => {\n setId(event.target.value.toUpperCase());\n setErrors((previous) => {\n const next = { ...previous };\n delete next.id;\n return next;\n });\n setFailure(\"\");\n }}\n />\n </Field>\n <Field label={`${label} name`} required error={errors.name ?? \"\"}>\n <Input\n value={values.name}\n maxLength={80}\n disabled={saving}\n onChange={(event) => change(\"name\", event.target.value)}\n />\n </Field>\n <Field label={`${label} type`} required error={errors.category ?? \"\"}>\n <Select\n value={values.category}\n disabled={saving}\n onValueChange={(value) => change(\"category\", value)}\n >\n {scenario.categories.map((category) => (\n <SelectOption key={category} value={category}>\n {category}\n </SelectOption>\n ))}\n </Select>\n </Field>\n </div>\n )}\n <div className=\"grid grid-cols-1 gap-2u sm:grid-cols-2\">\n <Field label=\"Owner\" required error={errors.owner ?? \"\"}>\n <Select\n value={values.owner}\n disabled={saving}\n onValueChange={(value) => change(\"owner\", value)}\n >\n {scenario.owners.map((owner) => (\n <SelectOption key={owner} value={owner}>\n {owner}\n </SelectOption>\n ))}\n </Select>\n </Field>\n <Field label=\"Location\" required error={errors.location ?? \"\"}>\n <Select\n value={values.location}\n disabled={saving}\n onValueChange={(value) => change(\"location\", value)}\n >\n {scenario.locations.map((location) => (\n <SelectOption key={location} value={location}>\n {location}\n </SelectOption>\n ))}\n </Select>\n </Field>\n <Field label=\"Condition\" required error={errors.condition ?? \"\"}>\n <Select\n value={values.condition}\n disabled={saving}\n onValueChange={(value) => change(\"condition\", value as ResourceCondition)}\n >\n {Object.entries(conditionLabels).map(([key, label]) => (\n <SelectOption key={key} value={key}>\n {label}\n </SelectOption>\n ))}\n </Select>\n </Field>\n <Field label=\"Service due\" required error={errors.serviceDue ?? \"\"}>\n <Input\n type=\"date\"\n value={values.serviceDue}\n disabled={saving}\n onChange={(event) => change(\"serviceDue\", event.target.value)}\n />\n </Field>\n </div>\n <Field\n label=\"Service note\"\n required\n error={errors.note ?? \"\"}\n help=\"Include the next action or any limits on use.\"\n >\n <Textarea\n value={values.note}\n rows={3}\n maxLength={500}\n disabled={saving}\n onChange={(event) => change(\"note\", event.target.value)}\n />\n </Field>\n {failure && (\n <p role=\"alert\" className=\"m-0 border border-oprt-border p-2u text-oprt-error\">\n {failure}\n </p>\n )}\n <div className=\"flex flex-wrap items-center justify-end gap-1u border-t border-oprt-border pt-2u\">\n <Button variant=\"tertiary\" disabled={saving} onClick={close}>\n Cancel\n </Button>\n <Button type=\"submit\" disabled={saving || (!!record && !dirty)}>\n {saving ? \"Saving…\" : record ? \"Save changes\" : `Add ${subject}`}\n </Button>\n </div>\n </form>\n </Dialog>\n <ConfirmDialog\n open={confirmDiscard}\n onOpenChange={setConfirmDiscard}\n title=\"Discard unsaved changes?\"\n description={`The register has not been changed. Discard this draft to return to the ${subject} details.`}\n confirmLabel=\"Discard changes\"\n cancelLabel=\"Keep editing\"\n onConfirm={onClose}\n />\n </>\n );\n}\n","patterns/resource-management/resource-management.tsx":"\"use client\";\n\nimport { useLayoutEffect, useRef, useState } from \"react\";\nimport {\n Badge,\n Button,\n CollectionState,\n CopyButton,\n DataGrid,\n EntityPreview,\n Input,\n Inspector,\n Select,\n SelectOption,\n type DataGridColumn,\n} from \"@oprt/react\";\nimport { formatUtcInstant, type OperationalDomain } from \"../operational-domain\";\nimport { requestArtifactDownload, transferCsvCell } from \"../resource-transfer/export\";\nimport {\n conditionLabels,\n resourceScenarios,\n type ManagedResource,\n type ResourceCondition,\n} from \"./fixtures\";\nimport { ResourceForm } from \"./resource-form\";\nimport type { ResourceSaveRequest, ResourceSaveResult } from \"./state\";\n\nexport type ResourceManagementProps = {\n domain: OperationalDomain;\n records: readonly ManagedResource[];\n /** Resolve only when the application has accepted or declined the exact revision. */\n onSave: (request: ResourceSaveRequest) => Promise<ResourceSaveResult>;\n};\nexport type AssetManagementProps = Omit<ResourceManagementProps, \"domain\">;\n\n/** Dedicated industrial register; the host owns the equipment snapshot and save adapter. */\nexport function AssetManagement(props: AssetManagementProps) {\n return <ResourceRegister {...props} domain=\"industrial\" asset />;\n}\n\n/** Compatibility recipe for internal contexts and existing consumers. */\nexport function ResourceManagement(props: ResourceManagementProps) {\n return <ResourceRegister {...props} />;\n}\n\ntype Scope = \"all\" | \"attention\" | \"ready\";\nconst variants: Record<ResourceCondition, \"success\" | \"warning\" | \"neutral\"> = {\n ready: \"success\",\n restricted: \"warning\",\n maintenance: \"neutral\",\n};\n/** The open asset is shown by a tint and a 3 px bar (Highlight in forced colors), never by color alone. */\nconst selectedRowStyle =\n \"[&_tr:has(button[aria-pressed=true])]:bg-oprt-surface-3 [&_tr:has(button[aria-pressed=true])_td:first-child]:relative [&_tr:has(button[aria-pressed=true])_td:first-child]:before:absolute [&_tr:has(button[aria-pressed=true])_td:first-child]:before:inset-y-0 [&_tr:has(button[aria-pressed=true])_td:first-child]:before:left-0 [&_tr:has(button[aria-pressed=true])_td:first-child]:before:w-[3px] [&_tr:has(button[aria-pressed=true])_td:first-child]:before:bg-oprt-accent [&_tr:has(button[aria-pressed=true])_td:first-child]:forced-colors:before:bg-[Highlight]\";\nconst focusStyle =\n \"outline-hidden focus-visible:shadow-oprt-focus focus-visible:outline-solid focus-visible:outline-2 focus-visible:outline-oprt-border-focus\";\n\n/** Mount with a domain key when switching application contexts. */\nfunction ResourceRegister({\n domain,\n records,\n onSave,\n asset = false,\n}: ResourceManagementProps & { asset?: boolean }) {\n const scenario = resourceScenarios[domain];\n const subject = asset ? \"asset\" : \"resource\";\n const label = asset ? \"Asset\" : \"Resource\";\n const [query, setQuery] = useState(\"\");\n const [scope, setScope] = useState<Scope>(\"all\");\n const [location, setLocation] = useState(\"all\");\n const [owner, setOwner] = useState(\"all\");\n const [selectedId, setSelectedId] = useState<string | null>(() =>\n records.some((record) => record.id === scenario.selectedId)\n ? scenario.selectedId\n : (records[0]?.id ?? null),\n );\n const [editor, setEditor] = useState<{ record: ManagedResource | null } | null>(null);\n const [notice, setNotice] = useState(\"\");\n const [exportError, setExportError] = useState(\"\");\n const inspector = useRef<HTMLElement>(null);\n const register = useRef<HTMLElement>(null);\n const selectionOrigin = useRef<HTMLElement | null>(null);\n const focusRequested = useRef(false);\n const selected = records.find((record) => record.id === selectedId);\n const search = query.trim().toLocaleLowerCase();\n const visible = records.filter(\n (record) =>\n (scope === \"all\" ||\n (scope === \"attention\" ? record.condition !== \"ready\" : record.condition === \"ready\")) &&\n (location === \"all\" || record.location === location) &&\n (owner === \"all\" || record.owner === owner) &&\n (!search ||\n [record.id, record.name, record.category, record.owner, record.location].some((value) =>\n value.toLocaleLowerCase().includes(search),\n )),\n );\n const attentionCount = records.filter((record) => record.condition !== \"ready\").length;\n\n useLayoutEffect(() => {\n if (focusRequested.current) {\n inspector.current?.focus();\n focusRequested.current = false;\n }\n }, [selectedId]);\n\n /** A saved or exported confirmation describes the moment it happened, so it ends when the view moves on. */\n function dropConfirmations() {\n setNotice(\"\");\n setExportError(\"\");\n }\n function select(record: ManagedResource, origin?: HTMLElement) {\n selectionOrigin.current =\n origin ?? (document.activeElement instanceof HTMLElement ? document.activeElement : null);\n if (record.id === selectedId) {\n inspector.current?.focus();\n return;\n }\n focusRequested.current = true;\n dropConfirmations();\n setSelectedId(record.id);\n }\n function closeInspection() {\n setSelectedId(null);\n (selectionOrigin.current?.isConnected ? selectionOrigin.current : register.current)?.focus();\n }\n function clear() {\n dropConfirmations();\n setQuery(\"\");\n setScope(\"all\");\n setLocation(\"all\");\n setOwner(\"all\");\n }\n function exportRecords() {\n setExportError(\"\");\n try {\n const header = [\n \"id\",\n \"revision\",\n \"name\",\n \"type\",\n \"owner\",\n \"location\",\n \"condition\",\n \"service_due\",\n \"service_note\",\n ];\n const rows = visible.map((record) => [\n record.id,\n record.revision,\n record.name,\n record.category,\n record.owner,\n record.location,\n conditionLabels[record.condition],\n record.serviceDue,\n record.note,\n ]);\n requestArtifactDownload({\n filename: asset ? \"northbank-asset-register.csv\" : `${domain}-resource-register.csv`,\n mediaType: \"text/csv\",\n content:\n [header, ...rows].map((row) => row.map(transferCsvCell).join(\",\")).join(\"\\r\\n\") + \"\\r\\n\",\n });\n setNotice(\n `CSV prepared for ${visible.length} ${subject}${visible.length === 1 ? \"\" : \"s\"}. Download requested.`,\n );\n } catch {\n setExportError(\"The CSV could not be prepared. Try exporting the current results again.\");\n }\n }\n const columns: DataGridColumn[] = [\n {\n key: \"name\",\n label: asset ? \"Equipment\" : \"Resource\",\n render: (_value, row) => {\n const record = row.record as ManagedResource;\n return (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n aria-label={`Inspect ${record.name}, ${record.id}`}\n aria-pressed={record.id === selectedId}\n className=\"h-auto! min-h-5u w-full justify-start! px-0! text-left normal-case! tracking-normal!\"\n onClick={(event) => select(record, event.currentTarget)}\n >\n <span className=\"flex flex-col gap-0.5u\">\n <span className=\"whitespace-nowrap font-medium\">{record.name}</span>\n <span className=\"font-mono\">{record.id}</span>\n </span>\n </Button>\n );\n },\n },\n {\n key: \"location\",\n label: \"Location\",\n render: (value) => <span className=\"whitespace-nowrap\">{String(value)}</span>,\n },\n {\n key: \"owner\",\n label: \"Owner\",\n render: (value) => <span className=\"whitespace-nowrap\">{String(value)}</span>,\n },\n {\n key: \"condition\",\n label: \"Condition\",\n render: (value) => (\n <Badge appearance=\"text\" indicator=\"none\" variant={variants[value as ResourceCondition]}>\n {conditionLabels[value as ResourceCondition]}\n </Badge>\n ),\n },\n {\n key: \"serviceDue\",\n label: \"Service due\",\n render: (value) => <span className=\"whitespace-nowrap font-mono\">{String(value)}</span>,\n },\n ];\n\n return (\n <section\n ref={register}\n aria-label={`${scenario.title} ${subject} register`}\n tabIndex={-1}\n className={`not-prose flex w-full min-w-0 flex-col gap-3u font-sans text-oprt-base text-oprt-text ${focusStyle}`}\n >\n <header className=\"flex flex-wrap items-start justify-between gap-2u border-b border-oprt-border pb-3u\">\n <div className=\"min-w-0\">\n <h2 className=\"m-0 text-oprt-xl font-semibold normal-case tracking-normal\">\n {label} register\n </h2>\n {asset && (\n <p className=\"m-0 mt-1u text-oprt-text-dim\">\n Equipment ownership, inspections and service records\n </p>\n )}\n <p className=\"m-0 mt-1u text-oprt-text-dim\">\n {scenario.title} <span aria-hidden=\"true\">/</span> {scenario.department}\n </p>\n </div>\n <div className=\"flex flex-wrap gap-1u\">\n <Button size=\"sm\" variant=\"tertiary\" disabled={!visible.length} onClick={exportRecords}>\n Export CSV\n </Button>\n <Button\n size=\"sm\"\n onClick={() => {\n setEditor({ record: null });\n setNotice(\"\");\n }}\n >\n Add {subject}\n </Button>\n </div>\n </header>\n <div className=\"flex flex-wrap items-center justify-between gap-2u\">\n <div role=\"group\" aria-label={`${label} scopes`} className=\"flex flex-wrap gap-1u\">\n {(\n [\n [\"all\", `All ${subject}s`, records.length],\n [\"attention\", \"Needs attention\", attentionCount],\n [\"ready\", \"Ready\", records.length - attentionCount],\n ] as const\n ).map(([key, label, count]) => (\n <Button\n key={key}\n size=\"sm\"\n variant={scope === key ? \"secondary\" : \"ghost\"}\n aria-pressed={scope === key}\n onClick={() => {\n dropConfirmations();\n setScope(key);\n }}\n >\n {label}\n <span className=\"ml-1u font-mono text-oprt-text-dim\">{count}</span>\n </Button>\n ))}\n </div>\n <p className=\"m-0 text-oprt-text-dim\">{scenario.role}</p>\n </div>\n <div className=\"flex flex-wrap items-center gap-1u\">\n <Input\n type=\"search\"\n aria-label={`Search ${subject}s`}\n placeholder=\"Search ID, name or owner\"\n value={query}\n onChange={(event) => {\n dropConfirmations();\n setQuery(event.target.value);\n }}\n className=\"min-w-0 flex-1 basis-[16rem]\"\n />\n <Select\n aria-label={`${label} location`}\n value={location}\n onValueChange={(value) => {\n dropConfirmations();\n setLocation(value);\n }}\n size=\"sm\"\n >\n <SelectOption value=\"all\">All locations</SelectOption>\n {scenario.locations.map((item) => (\n <SelectOption key={item} value={item}>\n {item}\n </SelectOption>\n ))}\n </Select>\n <Select\n aria-label={`${label} owner`}\n value={owner}\n onValueChange={(value) => {\n dropConfirmations();\n setOwner(value);\n }}\n size=\"sm\"\n >\n <SelectOption value=\"all\">All owners</SelectOption>\n {scenario.owners.map((item) => (\n <SelectOption key={item} value={item}>\n {item}\n </SelectOption>\n ))}\n </Select>\n </div>\n {notice && (\n <p role=\"status\" className=\"m-0 border border-oprt-border bg-oprt-surface-2 p-2u\">\n {notice}\n </p>\n )}\n {exportError && (\n <p role=\"alert\" className=\"m-0 text-oprt-error\">\n {exportError}\n </p>\n )}\n <div className=\"flex min-w-0 flex-wrap items-start gap-3u\">\n <div className=\"min-w-0 flex-[999_1_35rem]\">\n <p role=\"status\" aria-label={`${label} count`} className=\"m-0 mb-2u text-oprt-text-dim\">\n {visible.length} of {records.length} {subject}s <span aria-hidden=\"true\">·</span>{\" \"}\n {scenario.date}, {scenario.time}\n </p>\n {!records.length ? (\n <CollectionState\n variant=\"empty\"\n title={`Register your first ${subject}`}\n description=\"Add equipment with an owner, a location and its current condition.\"\n actions={<Button onClick={() => setEditor({ record: null })}>Add {subject}</Button>}\n />\n ) : visible.length === 0 ? (\n <CollectionState\n variant=\"no-results\"\n title={`No ${subject}s match`}\n description=\"Try another identifier or clear the current search, scope and filters.\"\n actions={<Button onClick={clear}>Clear search and scope</Button>}\n />\n ) : (\n <DataGrid\n caption={`${label} register`}\n columns={columns}\n data={visible.map((record) => ({ ...record, record }))}\n getRowId={(row) => String(row.id)}\n pageSize={12}\n dense\n className={selectedRowStyle}\n />\n )}\n </div>\n {selectedId && (\n <Inspector\n ref={inspector}\n tabIndex={-1}\n title={`${label} details`}\n onClose={closeInspection}\n className={`min-w-0 flex-[1_1_24rem] ${focusStyle}`}\n >\n {selected ? (\n <>\n {!visible.some((record) => record.id === selectedId) && (\n <p className=\"m-0 text-oprt-text-dim\">\n Selected {subject} is outside the current results.\n </p>\n )}\n <EntityPreview\n entity={{\n id: selected.id,\n name: selected.name,\n typeLabel: selected.category,\n facts: [\n { id: \"owner\", label: \"Owner\", value: selected.owner },\n { id: \"location\", label: \"Location\", value: selected.location },\n {\n id: \"service\",\n label: \"Service due\",\n value: selected.serviceDue,\n mono: true,\n },\n {\n id: \"revision\",\n label: \"Revision\",\n value: String(selected.revision).padStart(2, \"0\"),\n mono: true,\n },\n ],\n condition: {\n label: conditionLabels[selected.condition],\n variant: variants[selected.condition],\n },\n quality: {\n state: \"current\",\n source: scenario.source,\n updatedAt: Date.parse(selected.updatedAt),\n },\n }}\n actions={\n <>\n <Button\n size=\"sm\"\n onClick={() => {\n setEditor({ record: selected });\n setNotice(\"\");\n }}\n >\n Edit {subject}\n </Button>\n <CopyButton size=\"sm\" value={selected.id} label={`Copy ${subject} ID`} />\n </>\n }\n />\n <div className=\"border-t border-oprt-border pt-2u\">\n <h4 className=\"m-0 font-medium\">Service note</h4>\n <p className=\"m-0 mt-1u leading-relaxed text-oprt-text-dim\">{selected.note}</p>\n </div>\n {asset && (\n <section\n aria-label=\"Inspection and service records\"\n className=\"border-t border-oprt-border pt-2u\"\n >\n <h4 className=\"m-0 font-medium\">Inspection & service records</h4>\n {selected.serviceRecords?.length ? (\n <ol className=\"m-0 mt-2u list-none flex flex-col gap-3u p-0\">\n {selected.serviceRecords.map((record) => (\n <li key={record.id} className=\"flex flex-col gap-1u\">\n <div className=\"flex flex-wrap items-center justify-between gap-1u\">\n <span className=\"font-mono\">{record.id}</span>\n <Badge\n appearance=\"text\"\n indicator=\"none\"\n variant={record.result === \"Complete\" ? \"success\" : \"warning\"}\n >\n {record.result}\n </Badge>\n </div>\n <p className=\"m-0 font-medium\">{record.title}</p>\n <p className=\"m-0 leading-relaxed text-oprt-text-dim\">\n {record.detail}\n </p>\n <p className=\"m-0 text-oprt-text-dim\">\n Recorded by {record.recordedBy}\n </p>\n <time\n dateTime={record.recordedAt}\n className=\"block font-mono text-oprt-text-dim\"\n >\n {formatUtcInstant(record.recordedAt, { milliseconds: \"never\" })}\n </time>\n </li>\n ))}\n </ol>\n ) : (\n <p className=\"m-0 mt-1u text-oprt-text-dim\">\n No inspection or service evidence has been supplied for this asset.\n </p>\n )}\n </section>\n )}\n <div className=\"border-t border-oprt-border pt-2u\">\n <h4 className=\"m-0 font-medium\">\n {asset ? \"Register history\" : \"Recent activity\"}\n </h4>\n <ol className=\"m-0 mt-2u list-none flex flex-col gap-2u p-0\">\n {selected.activity.slice(0, 3).map((event) => (\n <li key={event.id}>\n <p className=\"m-0 text-oprt-text-dim\">{event.detail}</p>\n <time\n dateTime={event.at}\n className=\"font-mono text-oprt-sm text-oprt-text-dim\"\n >\n {formatUtcInstant(event.at, { milliseconds: \"never\" })}\n </time>\n </li>\n ))}\n </ol>\n </div>\n </>\n ) : (\n <CollectionState\n variant=\"empty\"\n title={`${label} no longer available`}\n description={`This ${subject} has left the current register. Choose another ${subject} to inspect its details.`}\n />\n )}\n </Inspector>\n )}\n </div>\n {asset && (\n <p className=\"m-0 text-oprt-text-dim\">\n Fictional Northbank Works register · changes are stored in this session.\n </p>\n )}\n {editor && (\n <ResourceForm\n asset={asset}\n domain={domain}\n record={editor.record}\n onSave={onSave}\n onClose={() => setEditor(null)}\n onSaved={(record) => {\n setEditor(null);\n setSelectedId(record.id);\n setNotice(\n `${record.id} saved. ${record.name} is ${conditionLabels[record.condition].toLowerCase()}.`,\n );\n }}\n />\n )}\n </section>\n );\n}\n","patterns/resource-management/state.ts":"import type { OperationalDomain } from \"../operational-domain\";\nimport { conditionLabels, resourceScenarios, type ManagedResource } from \"./fixtures\";\n\nexport type ResourceValues = Pick<\n ManagedResource,\n \"name\" | \"category\" | \"owner\" | \"location\" | \"condition\" | \"serviceDue\" | \"note\"\n>;\nexport type ResourceSaveRequest = {\n domain: OperationalDomain;\n id: string;\n expectedRevision: number | null;\n values: ResourceValues;\n};\nexport type ResourceSaveResult =\n | { state: \"accepted\"; record: ManagedResource }\n | { state: \"rejected\" | \"conflict\"; message: string };\nexport type ResourceErrors = Partial<Record<keyof ResourceValues | \"id\", string>>;\n\nexport function validateResource(\n domain: OperationalDomain,\n id: string,\n values: ResourceValues,\n): ResourceErrors {\n const errors: ResourceErrors = {};\n const scenario = resourceScenarios[domain];\n if (!/^[A-Z][A-Z0-9]{1,7}-[A-Z0-9]{2,12}$/.test(id))\n errors.id = `Use an identifier such as ${scenario.idPlaceholder}.`;\n if (!values.name.trim() || values.name.trim().length > 80)\n errors.name = `Enter ${domain === \"industrial\" ? \"an asset\" : \"a resource\"} name of 1–80 characters.`;\n if (!(scenario.categories as readonly string[]).includes(values.category))\n errors.category = `Choose ${domain === \"industrial\" ? \"an asset\" : \"a resource\"} type for this register.`;\n if (!(scenario.owners as readonly string[]).includes(values.owner))\n errors.owner = \"Choose an owner from this team.\";\n if (!(scenario.locations as readonly string[]).includes(values.location))\n errors.location = \"Choose a location at this site.\";\n if (!Object.hasOwn(conditionLabels, values.condition)) errors.condition = \"Choose a condition.\";\n const date = new Date(`${values.serviceDue}T00:00:00Z`);\n if (\n !/^\\d{4}-\\d{2}-\\d{2}$/.test(values.serviceDue) ||\n !Number.isFinite(date.getTime()) ||\n date.toISOString().slice(0, 10) !== values.serviceDue\n )\n errors.serviceDue = \"Enter a valid service date.\";\n if (!values.note.trim() || values.note.trim().length > 500)\n errors.note = \"Enter a service note of 1–500 characters.\";\n return errors;\n}\n\n/** An application adapter may replace this local transaction with a service request. */\nexport function applyResourceSave(\n records: readonly ManagedResource[],\n request: ResourceSaveRequest,\n at: string,\n): ResourceSaveResult & { records?: ManagedResource[] } {\n const errors = validateResource(request.domain, request.id, request.values);\n if (Object.keys(errors).length) return { state: \"rejected\", message: Object.values(errors)[0]! };\n const previous = records.find((record) => record.id === request.id);\n if (request.expectedRevision === null && previous)\n return {\n state: \"rejected\",\n message: `${request.id} already exists. Use a different identifier.`,\n };\n if (\n request.expectedRevision !== null &&\n (!previous || previous.revision !== request.expectedRevision)\n )\n return {\n state: \"conflict\",\n message: `This ${request.domain === \"industrial\" ? \"asset\" : \"resource\"} changed after you opened it. Keep your note, close the editor, and reopen the latest record before saving.`,\n };\n const record: ManagedResource = {\n ...request.values,\n name: request.values.name.trim(),\n note: request.values.note.trim(),\n id: request.id,\n revision: (previous?.revision ?? 0) + 1,\n updatedAt: at,\n ...(previous?.serviceRecords ? { serviceRecords: previous.serviceRecords } : {}),\n activity: [\n {\n id: `${request.id}-${(previous?.revision ?? 0) + 1}`,\n at,\n detail: previous\n ? \"Assignment and service details updated.\"\n : `Added to the ${request.domain === \"industrial\" ? \"asset\" : \"resource\"} register.`,\n },\n ...(previous?.activity ?? []),\n ].slice(0, 4),\n };\n return {\n state: \"accepted\",\n record,\n records: previous\n ? records.map((item) => (item.id === record.id ? record : item))\n : [...records, record],\n };\n}\n\nexport function valuesOf(record: ManagedResource): ResourceValues {\n const { name, category, owner, location, condition, serviceDue, note } = record;\n return { name, category, owner, location, condition, serviceDue, note };\n}\n","patterns/resource-transfer/csv.ts":"import type { TransferImportValues, TransferParseResult } from \"./transfer-state\";\n\nexport type ImportField = \"id\" | \"name\" | \"kind\" | \"site\";\nexport type ImportMapping = Partial<Record<ImportField, number>>;\nexport interface CsvTable {\n columns: readonly { index: number; label: string }[];\n rows: readonly { key: string; line: number; cells: readonly string[] }[];\n}\nexport interface ImportRowError {\n field: ImportField;\n line: number;\n message: string;\n}\nexport interface ImportValidationRow {\n key: string;\n line: number;\n cells: readonly string[];\n candidate: TransferImportValues | null;\n errors: readonly ImportRowError[];\n}\nexport interface ImportValidation {\n errors: readonly string[];\n rows: readonly ImportValidationRow[];\n validCount: number;\n invalidCount: number;\n}\nexport const CSV_MAX_BYTES = 1048576;\nexport const CSV_MAX_ROWS = 1000;\nconst fields: readonly ImportField[] = [\"id\", \"name\", \"kind\", \"site\"];\nfunction hasControlCharacters(value: string): boolean {\n return Array.from(value).some((character) => {\n const code = character.charCodeAt(0);\n return code < 32 || (code >= 127 && code <= 159);\n });\n}\n\n/** UTF-8 byte length, without browser/Node globals. Unpaired surrogates encode as replacement characters. */\nexport function utf8ByteLength(text: string): number {\n let bytes = 0;\n for (const character of text) {\n const value = character.codePointAt(0)!;\n bytes += value < 0x80 ? 1 : value < 0x800 ? 2 : value < 0x10000 ? 3 : 4;\n }\n return bytes;\n}\n\n/** A strict, bounded CSV reader: syntax/width failure never returns a partial table. */\nexport function parseEquipmentCsv(text: string, fileId: string): TransferParseResult<CsvTable> {\n const fail = (error: string): TransferParseResult<CsvTable> => ({ ok: false, error });\n if (\n typeof text !== \"string\" ||\n text.length > CSV_MAX_BYTES ||\n utf8ByteLength(text) > CSV_MAX_BYTES\n )\n return fail(\"CSV files must be no larger than 1 MiB of UTF-8 text.\");\n if (typeof fileId !== \"string\" || !fileId.trim() || fileId.length > 256)\n return fail(\"File identity is invalid.\");\n const input = text.startsWith(\"\\uFEFF\") ? text.slice(1) : text;\n if (!input) return fail(\"The CSV needs a header row.\");\n const rows: { line: number; cells: readonly string[] }[] = [];\n let cells: string[] = [],\n cell = \"\",\n quoted = false,\n closed = false,\n line = 1,\n startLine = 1;\n const finishCell = () => {\n cells.push(cell);\n cell = \"\";\n closed = false;\n };\n const finishRow = () => {\n finishCell();\n rows.push({ line: startLine, cells: Object.freeze(cells) });\n cells = [];\n };\n for (let index = 0; index < input.length; index++) {\n const character = input[index]!;\n if (character === \"\\r\" && input[index + 1] !== \"\\n\")\n return fail(`Unsupported line ending on line ${line}; use LF or CRLF.`);\n if (quoted) {\n if (character === '\"') {\n if (input[index + 1] === '\"') {\n cell += '\"';\n index++;\n } else {\n quoted = false;\n closed = true;\n }\n } else {\n cell += character;\n if (character === \"\\n\") line++;\n }\n } else if (character === '\"') {\n if (cell !== \"\" || closed) return fail(`Unexpected quote on line ${line}.`);\n quoted = true;\n } else if (character === \",\") {\n finishCell();\n if (cells.length >= 32) return fail(\"CSV supports at most 32 columns.\");\n } else if (character === \"\\r\" || character === \"\\n\") {\n finishRow();\n if (character === \"\\r\") index++;\n line++;\n startLine = line;\n } else {\n if (closed) return fail(`Unexpected text after a closing quote on line ${line}.`);\n cell += character;\n }\n if (cell.length > 4096) return fail(`Cell on line ${startLine} exceeds 4,096 characters.`);\n if (rows.length > CSV_MAX_ROWS + 1) return fail(\"CSV supports at most 1,000 data rows.\");\n }\n if (quoted) return fail(`Unclosed quote starting on line ${startLine}.`);\n if (cell !== \"\" || closed || cells.length > 0 || !/[\\r\\n]$/.test(input)) finishRow();\n if (rows.length > CSV_MAX_ROWS + 1) return fail(\"CSV supports at most 1,000 data rows.\");\n const header = rows[0];\n if (!header) return fail(\"The CSV needs a header row.\");\n if (rows.some((row) => row.cells.length !== header.cells.length))\n return fail(\"Every CSV row must have the same number of columns as its header.\");\n return {\n ok: true,\n value: Object.freeze({\n columns: Object.freeze(header.cells.map((label, index) => Object.freeze({ index, label }))),\n rows: Object.freeze(\n rows.slice(1).map((row) => Object.freeze({ key: `${fileId}:${row.line}`, ...row })),\n ),\n }),\n };\n}\n\n/** Mapping/domain errors retain all rows; only explicitly chosen valid candidates can be submitted. */\nexport function validateEquipmentImport(\n table: CsvTable,\n mapping: ImportMapping,\n existingIds: readonly string[],\n): ImportValidation {\n const errors: string[] = [],\n used = new Set<number>();\n for (const field of fields) {\n const column = mapping[field];\n if (\n column === undefined ||\n !Number.isInteger(column) ||\n column < 0 ||\n column >= table.columns.length\n )\n errors.push(`Map ${field} to a source column.`);\n else if (used.has(column))\n errors.push(`The ${field} destination must use its own source column.`);\n else used.add(column);\n }\n const existing = new Set(existingIds),\n counts = new Map<string, number>();\n if (errors.length === 0)\n for (const row of table.rows) {\n const id = row.cells[mapping.id!]!;\n counts.set(id, (counts.get(id) ?? 0) + 1);\n }\n const rows: ImportValidationRow[] = table.rows.map((row) => {\n const rowErrors: ImportRowError[] = [];\n const add = (field: ImportField, message: string) =>\n rowErrors.push({ field, line: row.line, message });\n let candidate: TransferImportValues | null = null;\n if (errors.length === 0) {\n const id = row.cells[mapping.id!]!,\n name = row.cells[mapping.name!]!;\n const kind = row.cells[mapping.kind!]!,\n site = row.cells[mapping.site!]!;\n for (const field of fields)\n if (hasControlCharacters(row.cells[mapping[field]!]!))\n add(field, `${field} cannot contain control characters.`);\n if (!id.trim() || id.length > 80) add(\"id\", \"ID must contain 1–80 characters.\");\n if (!name.trim() || name.length > 120) add(\"name\", \"Name must contain 1–120 characters.\");\n if (![\"pump\", \"fan\", \"valve\", \"gauge\"].includes(kind))\n add(\"kind\", \"Kind must be pump, fan, valve or gauge.\");\n if (site !== \"west\" && site !== \"east\") add(\"site\", \"Site must be west or east.\");\n if ((counts.get(id) ?? 0) > 1) add(\"id\", \"ID occurs more than once in this file.\");\n if (existing.has(id)) add(\"id\", \"ID already exists in the supplied inventory.\");\n if (rowErrors.length === 0)\n candidate = Object.freeze({ id, name, kind, site }) as TransferImportValues;\n }\n return Object.freeze({\n ...row,\n candidate,\n errors: Object.freeze(rowErrors.map((error) => Object.freeze(error))),\n });\n });\n const validCount = rows.filter((row) => row.candidate !== null).length;\n return Object.freeze({\n errors: Object.freeze(errors),\n rows: Object.freeze(rows),\n validCount,\n invalidCount: rows.length - validCount,\n });\n}\n","patterns/resource-transfer/export.ts":"import type { TransferArtifact, TransferRequest } from \"./transfer-state\";\nimport type { ImportValidationRow } from \"./csv\";\n\n/** Quote every cell and neutralize spreadsheet formula prefixes, including leading whitespace. */\nexport function transferCsvCell(value: string | number | boolean): string {\n let text = String(value);\n const firstMeaningful = [...text].find(\n (character) => character.charCodeAt(0) > 32 && character.trim() !== \"\",\n );\n if ((firstMeaningful && \"=+@-\".includes(firstMeaningful)) || /^[\\t\\r\\n]/u.test(text))\n text = `'${text}`;\n return `\"${text.replaceAll('\"', '\"\"')}\"`;\n}\n\nexport function prepareTransferExport(\n request: Extract<TransferRequest, { operation: \"export\" }>,\n): TransferArtifact {\n const ids = new Set(request.selection.ids);\n if (\n ids.size === 0 ||\n ids.size !== request.selection.ids.length ||\n request.records.length !== ids.size ||\n new Set(request.records.map((record) => record.id)).size !== ids.size ||\n request.records.some((record) => !ids.has(record.id))\n )\n throw new Error(\"Export records do not match the reviewed selection scope.\");\n const { context, selection } = request;\n const capturedAt = new Date(context.capturedAt).toISOString();\n const asOf =\n context.source.asOf === null ? \"Unknown\" : new Date(context.source.asOf).toISOString();\n const header = [\n \"id\",\n \"record_revision\",\n \"name\",\n \"kind\",\n \"site\",\n \"inspection_required\",\n \"source_id\",\n \"source_revision\",\n \"source_label\",\n \"source_as_of\",\n \"exported_at\",\n \"time_zone\",\n \"selection_scope\",\n \"filter\",\n \"filter_description\",\n ];\n const rows = request.records.map((record) => [\n record.id,\n record.revision,\n record.name,\n record.kind,\n record.site,\n record.inspectionRequired,\n context.source.id,\n context.source.revision,\n context.source.label,\n asOf,\n capturedAt,\n context.timeZone,\n selection.scope,\n JSON.stringify(context.filter),\n context.filterDescription,\n ]);\n return {\n filename: `equipment-export-${capturedAt.replace(/[.:]/g, \"-\")}.csv`,\n mediaType: \"text/csv\",\n content:\n [header, ...rows].map((row) => row.map(transferCsvCell).join(\",\")).join(\"\\r\\n\") + \"\\r\\n\",\n };\n}\n\nexport function prepareImportErrorReport(rows: readonly ImportValidationRow[]): TransferArtifact {\n const output: (string | number)[][] = [\n [\"row_key\", \"source_line\", \"field\", \"message\", \"source_cells\"],\n ];\n for (const row of rows) {\n for (const error of row.errors)\n output.push([row.key, error.line, error.field, error.message, JSON.stringify(row.cells)]);\n }\n return {\n filename: \"equipment-import-errors.csv\",\n mediaType: \"text/csv\",\n content: output.map((row) => row.map(transferCsvCell).join(\",\")).join(\"\\r\\n\") + \"\\r\\n\",\n };\n}\n\n/** Requests a browser download. The browser does not report whether the file reached disk. */\nexport function requestArtifactDownload(artifact: TransferArtifact): void {\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,160}\\.csv$/.test(artifact.filename))\n throw new Error(\"Invalid download filename.\");\n if (artifact.mediaType !== \"text/csv\" || typeof artifact.content !== \"string\")\n throw new Error(\"Unsupported download artifact.\");\n const blob = new Blob([artifact.content], { type: \"text/csv;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n const revoke = URL.revokeObjectURL.bind(URL);\n const link = document.createElement(\"a\");\n link.href = url;\n link.download = artifact.filename;\n link.hidden = true;\n try {\n document.body.appendChild(link);\n link.click();\n } catch (error) {\n revoke(url);\n throw error;\n } finally {\n link.remove();\n }\n setTimeout(() => revoke(url), 1000);\n}\n","patterns/resource-transfer/transfer-state.ts":"import type { BulkSelectionSnapshot } from \"@oprt/react\";\n\nexport interface TransferRecord {\n id: string;\n revision: string;\n name: string;\n kind: \"pump\" | \"fan\" | \"valve\" | \"gauge\";\n site: \"west\" | \"east\";\n inspectionRequired: boolean;\n}\nexport interface TransferSource {\n id: string;\n revision: string;\n label: string;\n asOf: number | null;\n complete: boolean;\n records: readonly TransferRecord[];\n}\nexport interface TransferFilter {\n query: string;\n kind: TransferRecord[\"kind\"] | \"all\";\n site: TransferRecord[\"site\"] | \"all\";\n}\nexport interface TransferContext {\n source: Omit<TransferSource, \"records\">;\n capturedAt: number;\n timeZone: string;\n filter: TransferFilter;\n filterDescription: string;\n}\nexport type TransferImportValues = Pick<TransferRecord, \"id\" | \"name\" | \"kind\" | \"site\">;\ninterface TransferRequestBase {\n requestId: string;\n sessionId: string;\n context: TransferContext;\n selection: BulkSelectionSnapshot;\n}\nexport type TransferRequest = TransferRequestBase &\n (\n | { operation: \"import\"; items: readonly { key: string; values: TransferImportValues }[] }\n | { operation: \"flag-inspection\"; records: readonly TransferRecord[] }\n | { operation: \"export\"; records: readonly TransferRecord[] }\n );\nexport interface TransferArtifact {\n filename: string;\n mediaType: \"text/csv\";\n content: string;\n}\nexport type TransferItemResult =\n | { key: string; status: \"applied\"; record: TransferRecord }\n | { key: string; status: \"failed\"; message: string; retryable: boolean }\n | { key: string; status: \"canceled\" | \"skipped\"; message: string };\nexport type TransferResult =\n | {\n requestId: string;\n operation: \"import\" | \"flag-inspection\";\n status: \"complete\" | \"partial\" | \"failed\" | \"canceled\";\n items: readonly TransferItemResult[];\n message: string;\n sourceRevision: string | null;\n }\n | { requestId: string; operation: \"export\"; status: \"complete\"; artifact: TransferArtifact }\n | { requestId: string; operation: \"export\"; status: \"failed\" | \"canceled\"; message: string };\nexport interface TransferProgress {\n requestId: string;\n state: \"queued\" | \"running\" | \"cancel-requested\";\n completed: number | null;\n total: number | null;\n message?: string;\n}\nexport type TransferCancelResult =\n | { requestId: string; status: \"requested\" }\n | { requestId: string; status: \"rejected\"; message: string };\nexport interface TransferRunOptions {\n signal: AbortSignal;\n onProgress(update: TransferProgress): void;\n}\nexport type TransferParseResult<T> = { ok: true; value: T } | { ok: false; error: string };\n\nconst kinds = [\"pump\", \"fan\", \"valve\", \"gauge\"];\nconst sites = [\"west\", \"east\"];\nfunction hasControlCharacters(value: string): boolean {\n return Array.from(value).some((character) => {\n const code = character.charCodeAt(0);\n return code < 32 || (code >= 127 && code <= 159);\n });\n}\nconst invalid = (error: string): { ok: false; error: string } => ({ ok: false, error });\nconst text = (value: unknown, max = 256): value is string =>\n typeof value === \"string\" &&\n value.trim().length > 0 &&\n value.length <= max &&\n !hasControlCharacters(value);\nconst time = (value: unknown): value is number =>\n typeof value === \"number\" &&\n Number.isSafeInteger(value) &&\n value >= 0 &&\n value <= 8640000000000000;\nfunction object(value: unknown): value is Record<string, unknown> {\n return (\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)\n );\n}\nfunction keys(\n value: Record<string, unknown>,\n expected: readonly string[],\n optional: readonly string[] = [],\n): boolean {\n return (\n expected.every((key) => Object.hasOwn(value, key)) &&\n Reflect.ownKeys(value).every(\n (key) => typeof key === \"string\" && (expected.includes(key) || optional.includes(key)),\n )\n );\n}\nfunction freezeCopy<T>(value: T): T {\n if (Array.isArray(value))\n return Object.freeze(value.map((item: unknown) => freezeCopy(item))) as T;\n if (value !== null && typeof value === \"object\")\n return Object.freeze(\n Object.fromEntries(Object.entries(value).map(([key, item]) => [key, freezeCopy(item)])),\n ) as T;\n return value;\n}\nfunction values(value: unknown): value is TransferImportValues {\n return (\n object(value) &&\n keys(value, [\"id\", \"name\", \"kind\", \"site\"]) &&\n text(value.id, 80) &&\n text(value.name, 120) &&\n typeof value.kind === \"string\" &&\n kinds.includes(value.kind) &&\n typeof value.site === \"string\" &&\n sites.includes(value.site)\n );\n}\nfunction record(value: unknown): value is TransferRecord {\n if (\n !object(value) ||\n !keys(value, [\"id\", \"revision\", \"name\", \"kind\", \"site\", \"inspectionRequired\"])\n )\n return false;\n return (\n values({ id: value.id, name: value.name, kind: value.kind, site: value.site }) &&\n text(value.revision) &&\n typeof value.inspectionRequired === \"boolean\"\n );\n}\nfunction equalRecord(a: TransferRecord, b: TransferRecord): boolean {\n return (\n a.id === b.id &&\n a.revision === b.revision &&\n a.name === b.name &&\n a.kind === b.kind &&\n a.site === b.site &&\n a.inspectionRequired === b.inspectionRequired\n );\n}\nfunction validFilter(value: unknown): value is TransferFilter {\n return (\n object(value) &&\n keys(value, [\"query\", \"kind\", \"site\"]) &&\n typeof value.query === \"string\" &&\n value.query.length <= 120 &&\n !hasControlCharacters(value.query) &&\n typeof value.kind === \"string\" &&\n [\"all\", ...kinds].includes(value.kind) &&\n typeof value.site === \"string\" &&\n [\"all\", ...sites].includes(value.site)\n );\n}\nfunction sameIds(a: readonly string[], b: readonly string[]): boolean {\n return a.length === b.length && a.every((id) => b.includes(id));\n}\nfunction sameFilter(a: TransferFilter, b: TransferFilter): boolean {\n return a.query === b.query && a.kind === b.kind && a.site === b.site;\n}\nfunction validZone(value: unknown): value is string {\n if (!text(value, 128) || /^[+-]/.test(value)) return false;\n try {\n new Intl.DateTimeFormat(\"en\", { timeZone: value }).format(0);\n return true;\n } catch {\n return false;\n }\n}\n\nexport function parseTransferSource(input: unknown): TransferParseResult<TransferSource> {\n try {\n if (\n !object(input) ||\n !keys(input, [\"id\", \"revision\", \"label\", \"asOf\", \"complete\", \"records\"]) ||\n !text(input.id) ||\n !text(input.revision) ||\n !text(input.label, 200) ||\n (input.asOf !== null && !time(input.asOf)) ||\n typeof input.complete !== \"boolean\" ||\n !Array.isArray(input.records) ||\n input.records.length > 1000 ||\n !input.records.every(record)\n )\n return invalid(\n \"Inventory source or equipment records are malformed or exceed the 1,000-record limit.\",\n );\n const ids = input.records.map((item) => (item as TransferRecord).id);\n if (new Set(ids).size !== ids.length) return invalid(\"Inventory record IDs must be unique.\");\n return { ok: true, value: freezeCopy(input as unknown as TransferSource) };\n } catch {\n return invalid(\"Inventory source could not be read.\");\n }\n}\n\n/** Captures a complete reviewed request; no caller object is retained by reference. */\nexport function createTransferRequest(\n input: TransferRequest,\n source: TransferSource,\n canWrite: boolean,\n): TransferParseResult<TransferRequest> {\n try {\n const sourceResult = parseTransferSource(source);\n if (!sourceResult.ok) return sourceResult;\n if (\n !object(input) ||\n !text(input.requestId) ||\n !text(input.sessionId) ||\n ![\"import\", \"flag-inspection\", \"export\"].includes(input.operation)\n )\n return invalid(\"Transfer request identity or operation is invalid.\");\n if (\n !keys(input, [\n \"requestId\",\n \"sessionId\",\n \"context\",\n \"selection\",\n \"operation\",\n input.operation === \"import\" ? \"items\" : \"records\",\n ])\n )\n return invalid(\"Transfer request contains unexpected or missing fields.\");\n const context = input.context;\n if (\n !object(context) ||\n !keys(context, [\"source\", \"capturedAt\", \"timeZone\", \"filter\", \"filterDescription\"]) ||\n !time(context.capturedAt) ||\n !validZone(context.timeZone) ||\n !validFilter(context.filter) ||\n context.filterDescription !== describeTransferFilter(context.filter) ||\n !object(context.source) ||\n !keys(context.source, [\"id\", \"revision\", \"label\", \"asOf\", \"complete\"])\n )\n return invalid(\"Transfer source, time or filter context is invalid.\");\n const metadata = context.source;\n if (\n metadata.id !== source.id ||\n metadata.revision !== source.revision ||\n metadata.label !== source.label ||\n metadata.asOf !== source.asOf ||\n metadata.complete !== source.complete\n )\n return invalid(\"The source changed. Review the current inventory before continuing.\");\n const selection = input.selection;\n if (\n !object(selection) ||\n !keys(selection, [\n \"id\",\n \"scope\",\n \"ids\",\n \"sourceId\",\n \"sourceRevision\",\n \"filterDescription\",\n \"capturedAt\",\n ]) ||\n !text(selection.id) ||\n ![\"visible\", \"selected\", \"all-matching\"].includes(selection.scope) ||\n !Array.isArray(selection.ids) ||\n selection.ids.length === 0 ||\n selection.ids.length > 1000 ||\n !selection.ids.every((id) => text(id)) ||\n new Set(selection.ids).size !== selection.ids.length ||\n selection.sourceId !== source.id ||\n selection.sourceRevision !== source.revision ||\n selection.filterDescription !== context.filterDescription ||\n selection.capturedAt !== context.capturedAt\n )\n return invalid(\n \"The selection snapshot is empty, malformed or belongs to another source or filter.\",\n );\n if (input.operation !== \"export\" && !canWrite)\n return invalid(\"Write permission is required for this transfer.\");\n if (selection.scope === \"all-matching\" && !source.complete)\n return invalid(\"All matching requires a complete supplied inventory.\");\n if (input.operation === \"import\") {\n if (\n selection.scope !== \"selected\" ||\n !Array.isArray(input.items) ||\n input.items.length !== selection.ids.length ||\n !input.items.every(\n (item) =>\n object(item) && keys(item, [\"key\", \"values\"]) && text(item.key) && values(item.values),\n )\n )\n return invalid(\"Import candidates or row selection are invalid.\");\n if (\n new Set(input.items.map((item) => item.key)).size !== input.items.length ||\n !sameIds(\n input.items.map((item) => item.key),\n selection.ids,\n ) ||\n new Set(input.items.map((item) => item.values.id)).size !== input.items.length ||\n input.items.some((item) => source.records.some((current) => current.id === item.values.id))\n )\n return invalid(\n \"Import contains duplicate, existing or incorrectly selected equipment IDs.\",\n );\n } else {\n if (\n !Array.isArray(input.records) ||\n input.records.length !== selection.ids.length ||\n !input.records.every(record) ||\n new Set(input.records.map((item) => item.id)).size !== input.records.length ||\n !sameIds(\n input.records.map((item) => item.id),\n selection.ids,\n )\n )\n return invalid(\"Selected records do not match the reviewed IDs.\");\n if (\n input.records.some((item) => !source.records.some((current) => equalRecord(item, current)))\n )\n return invalid(\"A selected record changed or is no longer in the source. Review again.\");\n if (\n selection.scope === \"all-matching\" &&\n !sameIds(\n selection.ids,\n filterTransferRecords(source.records, context.filter).map((item) => item.id),\n )\n )\n return invalid(\n \"All matching must contain every matching record in the supplied inventory.\",\n );\n }\n return { ok: true, value: freezeCopy(input) };\n } catch {\n return invalid(\"Transfer request could not be read.\");\n }\n}\n\nexport function getTransferAuthorityError(\n request: TransferRequest,\n source: TransferSource,\n filter: TransferFilter,\n canWrite: boolean,\n): string | null {\n if (!validFilter(filter) || !sameFilter(request.context.filter, filter))\n return \"The filters changed. Review the selection again.\";\n const result = createTransferRequest(request, source, canWrite);\n return result.ok ? null : result.error;\n}\n\n/** Missing/unknown results are protocol errors, never implied successful or safe-to-retry items. */\nexport function parseTransferResult(\n input: unknown,\n request: TransferRequest,\n): TransferParseResult<TransferResult> {\n try {\n if (\n !object(input) ||\n input.requestId !== request.requestId ||\n input.operation !== request.operation\n )\n return invalid(\n \"The service returned an unrelated or malformed result; the remote outcome is unknown.\",\n );\n if (request.operation === \"export\") {\n if (input.status === \"complete\") {\n const artifact = input.artifact;\n if (\n !keys(input, [\"requestId\", \"operation\", \"status\", \"artifact\"]) ||\n !object(artifact) ||\n !keys(artifact, [\"filename\", \"mediaType\", \"content\"]) ||\n !text(artifact.filename, 160) ||\n !/^[a-z0-9][a-z0-9_.-]*\\.csv$/i.test(artifact.filename) ||\n artifact.mediaType !== \"text/csv\" ||\n typeof artifact.content !== \"string\" ||\n artifact.content.length > 8388608\n )\n return invalid(\"The service returned an invalid or oversized CSV export artifact.\");\n } else if (\n !keys(input, [\"requestId\", \"operation\", \"status\", \"message\"]) ||\n (input.status !== \"failed\" && input.status !== \"canceled\") ||\n !text(input.message, 2000)\n )\n return invalid(\"The service returned an invalid export outcome.\");\n return { ok: true, value: freezeCopy(input as unknown as TransferResult) };\n }\n if (\n !keys(input, [\"requestId\", \"operation\", \"status\", \"items\", \"message\", \"sourceRevision\"]) ||\n typeof input.status !== \"string\" ||\n ![\"complete\", \"partial\", \"failed\", \"canceled\"].includes(input.status) ||\n !text(input.message, 2000) ||\n !Array.isArray(input.items) ||\n input.items.length !== request.selection.ids.length\n )\n return invalid(\n \"The service did not report an explicit valid outcome for every requested item.\",\n );\n const seen = new Set<string>();\n let accepted = 0,\n canceled = 0;\n for (const item of input.items) {\n if (\n !object(item) ||\n !text(item.key) ||\n seen.has(item.key) ||\n !request.selection.ids.includes(item.key)\n )\n return invalid(\"Service outcomes contain duplicate or unknown item identities.\");\n seen.add(item.key);\n if (item.status === \"applied\") {\n if (\n !keys(item, [\"key\", \"status\", \"record\"]) ||\n !record(item.record) ||\n !item.record.inspectionRequired\n )\n return invalid(\"An applied item is missing a valid flagged equipment record.\");\n const expected =\n request.operation === \"import\"\n ? request.items.find((row) => row.key === item.key)?.values\n : request.records.find((row) => row.id === item.key);\n if (\n !expected ||\n item.record.id !== expected.id ||\n item.record.name !== expected.name ||\n item.record.kind !== expected.kind ||\n item.record.site !== expected.site ||\n (\"revision\" in expected && item.record.revision === expected.revision)\n )\n return invalid(\n \"An applied record does not match its requested item or has an unchanged revision.\",\n );\n accepted++;\n } else if (item.status === \"failed\") {\n if (\n !keys(item, [\"key\", \"status\", \"message\", \"retryable\"]) ||\n !text(item.message, 2000) ||\n typeof item.retryable !== \"boolean\"\n )\n return invalid(\"A failed item needs an explicit message and retryability.\");\n } else if (item.status === \"canceled\" || item.status === \"skipped\") {\n if (!keys(item, [\"key\", \"status\", \"message\"]) || !text(item.message, 2000))\n return invalid(\"An unprocessed item needs an explicit explanation.\");\n if (item.status === \"canceled\") canceled++;\n } else return invalid(\"The service returned an unknown item outcome.\");\n }\n if (\n (accepted > 0 &&\n (!text(input.sourceRevision) ||\n input.sourceRevision === request.context.source.revision)) ||\n (accepted === 0 && input.sourceRevision !== null)\n )\n return invalid(\n \"Applied records require a new source revision; unsuccessful runs cannot advance the source.\",\n );\n if (\n (input.status === \"complete\" && accepted !== input.items.length) ||\n (input.status === \"partial\" && (accepted === 0 || accepted === input.items.length)) ||\n (input.status === \"failed\" && accepted !== 0) ||\n (input.status === \"canceled\" && (canceled === 0 || accepted === input.items.length))\n )\n return invalid(\"The terminal summary does not agree with its item outcomes.\");\n return { ok: true, value: freezeCopy(input as unknown as TransferResult) };\n } catch {\n return invalid(\"The service result could not be read; the remote outcome is unknown.\");\n }\n}\n\nexport function parseTransferProgress(\n input: unknown,\n request: TransferRequest,\n): TransferParseResult<TransferProgress> {\n try {\n if (\n !object(input) ||\n !keys(input, [\"requestId\", \"state\", \"completed\", \"total\"], [\"message\"]) ||\n input.requestId !== request.requestId ||\n typeof input.state !== \"string\" ||\n ![\"queued\", \"running\", \"cancel-requested\"].includes(input.state) ||\n (input.completed !== null &&\n (typeof input.completed !== \"number\" ||\n !Number.isSafeInteger(input.completed) ||\n input.completed < 0)) ||\n (input.total !== null &&\n (typeof input.total !== \"number\" ||\n !Number.isSafeInteger(input.total) ||\n input.total < 0)) ||\n (typeof input.completed === \"number\" &&\n typeof input.total === \"number\" &&\n input.completed > input.total) ||\n (input.message !== undefined && !text(input.message, 2000))\n )\n return invalid(\"Job progress is invalid or belongs to another request.\");\n return { ok: true, value: freezeCopy(input as unknown as TransferProgress) };\n } catch {\n return invalid(\"Job progress could not be read.\");\n }\n}\n\nexport function parseTransferCancelResult(\n input: unknown,\n requestId: string,\n): TransferParseResult<TransferCancelResult> {\n try {\n if (!object(input) || input.requestId !== requestId)\n return invalid(\"The cancellation acknowledgment belongs to another request.\");\n const requested = input.status === \"requested\" && keys(input, [\"requestId\", \"status\"]);\n const rejected =\n input.status === \"rejected\" &&\n keys(input, [\"requestId\", \"status\", \"message\"]) &&\n text(input.message, 2000);\n if (!requested && !rejected)\n return invalid(\n \"The service did not acknowledge this cancellation request. The job outcome is still pending.\",\n );\n return { ok: true, value: freezeCopy(input as unknown as TransferCancelResult) };\n } catch {\n return invalid(\n \"The cancellation response could not be read. The job outcome is still pending.\",\n );\n }\n}\n\nexport function filterTransferRecords(\n records: readonly TransferRecord[],\n filter: TransferFilter,\n): TransferRecord[] {\n if (!validFilter(filter)) return [];\n const query = filter.query.toLocaleLowerCase(\"en-US\");\n return records.filter(\n (item) =>\n (filter.kind === \"all\" || filter.kind === item.kind) &&\n (filter.site === \"all\" || filter.site === item.site) &&\n (item.name.toLocaleLowerCase(\"en-US\").includes(query) ||\n item.id.toLocaleLowerCase(\"en-US\").includes(query)),\n );\n}\nexport function describeTransferFilter(filter: TransferFilter): string {\n if (!validFilter(filter)) return \"Invalid filters\";\n return (\n [\n filter.query ? `Name or ID contains “${filter.query}”` : \"\",\n filter.kind !== \"all\" ? `Kind: ${filter.kind}` : \"\",\n filter.site !== \"all\" ? `Site: ${filter.site}` : \"\",\n ]\n .filter(Boolean)\n .join(\"; \") || \"All equipment\"\n );\n}\nexport function getRetryableTransferKeys(result: TransferResult): string[] {\n return result.operation === \"export\"\n ? []\n : result.items\n .filter((item) => item.status === \"failed\" && item.retryable)\n .map((item) => item.key);\n}\n","PlaybackControls/index.ts":"export { PlaybackControls, type PlaybackControlsProps } from \"./PlaybackControls\";\nexport { PlaybackControlsManifest } from \"./PlaybackControls.manifest\";\n","PlaybackControls/PlaybackControls.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const PlaybackControlsManifest: Record<string, Component> = {\n PlaybackControls: {\n description:\n \"Controlled UTC timestamp transport with a bounded scrubber, play/pause, optional event stepping, playback speed and return to a supplied live reference.\",\n props: {\n start: {\n type: \"number\",\n required: true,\n description:\n \"Inclusive range start in Unix epoch milliseconds. Must be a valid date before end; zero is valid.\",\n },\n end: {\n type: \"number\",\n required: true,\n description:\n \"Inclusive range end in Unix epoch milliseconds. Invalid or single-point ranges disable transport with explanatory text.\",\n },\n value: {\n type: \"number\",\n required: true,\n description:\n \"Controlled cursor in Unix epoch milliseconds. Out-of-range values clamp for display and disable transport; invalid dates display an unknown position.\",\n },\n playing: {\n type: \"boolean\",\n required: true,\n description:\n \"Caller-owned playback state. The component owns no clock and makes no optimistic state changes.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description:\n \"(value: number) => void. Requests a valid in-range timestamp after requesting pause.\",\n },\n onPlayingChange: {\n type: \"function\",\n required: true,\n description:\n \"(playing: boolean) => void. Requests play/pause. Scrubbing and event stepping call this with false before onValueChange.\",\n },\n playbackRate: {\n type: \"number\",\n default: 1,\n description:\n \"Controlled positive finite speed multiplier. A valid supplied rate remains visible, in order, even when absent from playbackRates; invalid rates display Unknown rate.\",\n },\n playbackRates: {\n type: \"array\",\n description:\n \"Readonly number array of offered speed multipliers. Defaults to [0.5, 1, 2, 4]. Non-positive/non-finite rates and duplicates are omitted. Listed in ascending order.\",\n },\n onPlaybackRateChange: {\n type: \"function\",\n description:\n \"(rate: number) => void. Supplying this callback shows the controlled speed selector.\",\n },\n eventTimes: {\n type: \"array\",\n description:\n \"Readonly epoch-millisecond array. Supplying it shows previous/next event buttons. Times are sorted, deduplicated and filtered to valid dates within the range; stepping skips the current time.\",\n },\n liveTime: {\n type: \"number\",\n description:\n \"Application-supplied live reference in epoch milliseconds. May lie outside the review range; the application owns any range change.\",\n },\n followingLive: {\n type: \"boolean\",\n default: false,\n description:\n \"Shows Following live and disables Return to live while the caller follows its reference.\",\n },\n onReturnToLive: {\n type: \"function\",\n description:\n \"() => void. Shows Return to live only when liveTime is a valid date. Requests no cursor, range or playing change itself.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description:\n \"Disables all transport actions and removes them from sequential keyboard navigation.\",\n },\n label: {\n type: \"string\",\n default: \"Playback\",\n description:\n \"Accessible root-group name. Also names the position slider and playback-speed selector.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"MediaFrame\", \"TimeRange\", \"EventTimeline\", \"Chart\"],\n never_inside: [\"Button\"],\n example:\n \"<PlaybackControls start={start} end={end} value={cursor} playing={playing} onValueChange={setCursor} onPlayingChange={setPlaying} eventTimes={eventTimes} playbackRate={rate} onPlaybackRateChange={setRate} liveTime={liveTime} followingLive={followingLive} onReturnToLive={returnToLive} />\",\n },\n};\n","PlaybackControls/PlaybackControls.tsx":"\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport {\n forwardRef,\n useId,\n useImperativeHandle,\n useMemo,\n useRef,\n type HTMLAttributes,\n type KeyboardEvent,\n} from \"react\";\nimport { useLocale } from \"react-aria-components\";\nimport { Button } from \"../Button/Button\";\nimport { Select, SelectOption } from \"../Select/Select\";\nimport { Slider } from \"../Slider/Slider\";\nimport { joinIds } from \"../_internal/fieldIds\";\nimport { formatUtcTimestamp, isValidTimestamp } from \"../_internal/timestamp\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\n\n// The group takes focus only as a fallback, when every control it holds became unavailable.\nconst rootStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\nconst timestampStyles = \"font-mono text-oprt-base tabular-nums break-words\";\nconst defaultRates: readonly number[] = [0.5, 1, 2, 4];\n\n/** Native div attributes reach the root group; `aria-describedby` joins the unavailable message. */\nexport interface PlaybackControlsProps extends Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\" | \"onChange\"\n> {\n start: number;\n end: number;\n value: number;\n playing: boolean;\n onValueChange: (value: number) => void;\n onPlayingChange: (playing: boolean) => void;\n playbackRate?: number;\n playbackRates?: readonly number[];\n onPlaybackRateChange?: (rate: number) => void;\n eventTimes?: readonly number[];\n liveTime?: number;\n followingLive?: boolean;\n onReturnToLive?: () => void;\n disabled?: boolean;\n label?: string;\n className?: string;\n}\n\n// DC-4: a playback position is sub-second, so it shows milliseconds (\"YYYY-MM-DD HH:MM:SS.mmm UTC\").\nconst validTime = (value: number) => isValidTimestamp(value);\nconst formatTime = (value: number) =>\n formatUtcTimestamp(value, { precision: \"milliseconds\" }) ?? \"Unknown\";\nconst validRate = (rate: number) => Number.isFinite(rate) && rate > 0;\n\n/** Readable durations an arrow press may move, in milliseconds: 1 ms up to a day. */\nconst readableSteps: readonly number[] = [\n 1, 10, 50, 100, 500, 1_000, 5_000, 10_000, 30_000, 60_000, 300_000, 600_000, 1_800_000, 3_600_000,\n 21_600_000, 43_200_000, 86_400_000,\n];\n/**\n * How far one arrow press moves (coordinator ruling, Waves 3 and 4): about a two-hundredth of the\n * window, rounded to the nearest readable duration, so a 30-minute window moves 10 s and a\n * 10-second clip 50 ms. Home, End and event stepping keep exact milliseconds.\n */\nfunction keyboardStep(span: number): number {\n const target = span / 200;\n let best = readableSteps[0]!;\n for (const step of readableSteps) {\n if (Math.abs(Math.log(step / target)) < Math.abs(Math.log(best / target))) best = step;\n }\n return best;\n}\n/** The index of the first time after `value` in ascending `times`, by binary search. */\nfunction firstAfter(times: readonly number[], value: number): number {\n let low = 0;\n let high = times.length;\n while (low < high) {\n const middle = (low + high) >>> 1;\n if (times[middle]! <= value) low = middle + 1;\n else high = middle;\n }\n return low;\n}\n/** +1 for a key that moves later, -1 for one that moves earlier, 0 for any other key. */\nfunction arrowDirection(key: string, rightToLeft: boolean): -1 | 0 | 1 {\n if (key === \"ArrowUp\" || key === \"Up\") return 1;\n if (key === \"ArrowDown\" || key === \"Down\") return -1;\n if (key === \"ArrowRight\" || key === \"Right\") return rightToLeft ? -1 : 1;\n if (key === \"ArrowLeft\" || key === \"Left\") return rightToLeft ? 1 : -1;\n return 0;\n}\n\nexport const PlaybackControls = forwardRef<HTMLDivElement, PlaybackControlsProps>(\n (\n {\n start,\n end,\n value,\n playing,\n onValueChange,\n onPlayingChange,\n playbackRate = 1,\n playbackRates = defaultRates,\n onPlaybackRateChange,\n eventTimes,\n liveTime,\n followingLive = false,\n onReturnToLive,\n disabled = false,\n label = \"Playback\",\n className,\n \"aria-describedby\": describedBy,\n onKeyDownCapture,\n onFocusCapture,\n onBlurCapture,\n ...props\n },\n ref,\n ) => {\n const descriptionId = useId();\n const root = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => root.current!, []);\n const previousRef = useRef<HTMLButtonElement>(null);\n const playRef = useRef<HTMLButtonElement>(null);\n const nextRef = useRef<HTMLButtonElement>(null);\n const { direction } = useLocale();\n const validBounds = validTime(start) && validTime(end);\n const validRange = validBounds && start < end;\n const validCursor = validTime(value);\n const inRange = validCursor && value >= start && value <= end;\n const unavailable = !validBounds\n ? \"Supply a valid start and end date in UTC.\"\n : start === end\n ? \"A single timestamp is available. Playback requires a time range.\"\n : start > end\n ? \"Start must be before end.\"\n : !validCursor\n ? \"Invalid playback position. Supply a valid timestamp in UTC.\"\n : !inRange\n ? \"The playback position is outside the playback range.\"\n : \"\";\n const transportDisabled = disabled || !!unavailable;\n // Never hand invalid or degenerate bounds to Slider. The placeholder is\n // disabled and explicitly described when the caller's inputs are invalid.\n const sliderStart = validRange ? start : 0;\n const sliderEnd = validRange ? end : 1;\n const displayValue =\n validRange && validCursor ? Math.min(end, Math.max(start, value)) : sliderStart;\n const positionText =\n validRange && validCursor\n ? formatTime(displayValue)\n : validBounds && start === end\n ? formatTime(start)\n : \"Unknown playback position\";\n // Event times change with the data, not with the position, which moves several times a second\n // during playback: they are prepared once and searched (PlaybackControls-R3).\n const events = useMemo(\n () =>\n [\n ...new Set(\n (eventTimes ?? []).filter((time) => validTime(time) && time >= start && time <= end),\n ),\n ].sort((a, b) => a - b),\n [eventTimes, start, end],\n );\n const after = firstAfter(events, displayValue);\n const next = events[after];\n const previous = events[events[after - 1] === displayValue ? after - 2 : after - 1];\n // A supplied speed that is not offered still shows, in order among the others.\n const rates = [...new Set(playbackRates.filter(validRate))];\n if (validRate(playbackRate) && !rates.includes(playbackRate)) rates.push(playbackRate);\n rates.sort((a, b) => a - b);\n const canReturn = !!onReturnToLive && liveTime !== undefined && validTime(liveTime);\n const requestTime = (time: number) => {\n if (transportDisabled || !validTime(time) || time < start || time > end) return;\n onPlayingChange(false);\n onValueChange(time);\n };\n // The scrubber keeps a 1 ms resolution, so drags, Home, End and supplied values stay exact; an\n // arrow press moves a readable step instead, stopping exactly at either end. Shift with an arrow,\n // Page Up and Page Down keep the slider's own tenth-of-the-window page.\n const stepByKeyboard = (event: KeyboardEvent<HTMLDivElement>) => {\n const target = event.target;\n if (event.shiftKey || !(target instanceof HTMLInputElement) || target.type !== \"range\")\n return;\n const sign = arrowDirection(event.key, direction === \"rtl\");\n if (sign === 0 || transportDisabled || !validRange || !validCursor) return;\n event.preventDefault();\n event.stopPropagation();\n const next = Math.min(end, Math.max(start, displayValue + sign * keyboardStep(end - start)));\n if (next !== displayValue) requestTime(next);\n };\n\n // A button that becomes unavailable when its request is accepted (an event step reaching the\n // first or last event, Return to live once following) hands focus to the other step, else Play\n // or Pause, else the group, so keyboard focus is never stranded (PlaybackControls-R1).\n const restoreFocus = useRestoreFocusOnRemoval<HTMLDivElement, \"previous\" | \"next\" | \"other\">(\n root,\n {\n track: (element) =>\n element === previousRef.current\n ? \"previous\"\n : element === nextRef.current\n ? \"next\"\n : \"other\",\n findTarget: (which) => {\n const usable = (button: HTMLButtonElement | null) =>\n button?.isConnected && !button.disabled ? button : null;\n const sibling =\n which === \"previous\" ? nextRef.current : which === \"next\" ? previousRef.current : null;\n return usable(sibling) ?? usable(playRef.current) ?? root.current;\n },\n },\n );\n\n return (\n <div\n {...props}\n ref={root}\n role=\"group\"\n tabIndex={-1}\n aria-label={label}\n aria-describedby={joinIds(describedBy, unavailable ? descriptionId : undefined)}\n className={clsx(rootStyles(), className)}\n onKeyDownCapture={(event) => {\n onKeyDownCapture?.(event);\n stepByKeyboard(event);\n }}\n onFocusCapture={(event) => {\n onFocusCapture?.(event);\n restoreFocus.onFocusCapture(event);\n }}\n onBlurCapture={(event) => {\n onBlurCapture?.(event);\n restoreFocus.onBlurCapture(event);\n }}\n >\n <div className=\"flex flex-wrap items-center gap-1u\">\n {eventTimes !== undefined && (\n <Button\n ref={previousRef}\n variant=\"tertiary\"\n size=\"sm\"\n disabled={transportDisabled || previous === undefined}\n onClick={() => {\n if (previous !== undefined) requestTime(previous);\n }}\n >\n Previous event\n </Button>\n )}\n <Button\n ref={playRef}\n variant=\"secondary\"\n size=\"sm\"\n disabled={transportDisabled}\n onClick={() => {\n if (!transportDisabled) onPlayingChange(!playing);\n }}\n >\n {playing ? \"Pause\" : \"Play\"}\n </Button>\n {eventTimes !== undefined && (\n <Button\n ref={nextRef}\n variant=\"tertiary\"\n size=\"sm\"\n disabled={transportDisabled || next === undefined}\n onClick={() => {\n if (next !== undefined) requestTime(next);\n }}\n >\n Next event\n </Button>\n )}\n {onPlaybackRateChange && (\n <Select\n aria-label={`${label} speed`}\n size=\"sm\"\n value={validRate(playbackRate) ? String(playbackRate) : \"\"}\n placeholder=\"Unknown rate\"\n disabled={transportDisabled || rates.length === 0}\n onValueChange={(selected) => {\n const rate = Number(selected);\n if (!transportDisabled && validRate(rate) && rates.includes(rate))\n onPlaybackRateChange(rate);\n }}\n >\n {rates.map((rate) => (\n <SelectOption key={rate} value={String(rate)}>{`${rate}×`}</SelectOption>\n ))}\n </Select>\n )}\n {canReturn && (\n <Button\n variant=\"tertiary\"\n size=\"sm\"\n disabled={transportDisabled || followingLive}\n onClick={() => {\n if (!transportDisabled && !followingLive) onReturnToLive?.();\n }}\n >\n Return to live\n </Button>\n )}\n {canReturn && followingLive && (\n <span className=\"text-oprt-base font-medium uppercase tracking-oprt-caps\">\n Following live\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-baseline justify-between gap-1u\">\n <span className=\"text-oprt-base font-medium uppercase tracking-oprt-caps\">\n {label} position\n </span>\n <span className={timestampStyles}>{positionText}</span>\n </div>\n <Slider\n aria-label={`${label} position`}\n aria-describedby={unavailable ? descriptionId : undefined}\n min={sliderStart}\n max={sliderEnd}\n value={displayValue}\n step={1}\n disabled={transportDisabled}\n formatValue={validRange && validCursor ? formatTime : () => positionText}\n onValueChange={requestTime}\n />\n <div className=\"flex flex-wrap justify-between gap-1u text-oprt-text-dim\">\n <span className={timestampStyles}>\n Start: {validTime(start) ? formatTime(start) : \"Unknown\"}\n </span>\n <span className={timestampStyles}>\n End: {validTime(end) ? formatTime(end) : \"Unknown\"}\n </span>\n </div>\n {unavailable && (\n <p id={descriptionId} className=\"m-0 text-oprt-base text-oprt-text-dim\">\n {unavailable}\n </p>\n )}\n </div>\n );\n },\n);\nPlaybackControls.displayName = \"PlaybackControls\";\n","Popover/index.ts":"export { Popover, type PopoverProps } from \"./Popover\";\nexport { PopoverManifest } from \"./Popover.manifest\";\n","Popover/Popover.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const PopoverManifest: Record<string, Component> = {\n Popover: {\n description: \"Named nonmodal interactive details anchored to an explicitly activated trigger.\",\n props: {\n title: {\n type: \"string\",\n description: \"Visible heading that names the dialog; required unless aria-label is set.\",\n },\n \"aria-label\": {\n type: \"string\",\n description: \"Name without a visible heading; required unless title is set.\",\n },\n open: { type: \"boolean\" },\n defaultOpen: { type: \"boolean\", default: false },\n onOpenChange: { type: \"function\" },\n side: { type: \"enum\", values: [\"top\", \"right\", \"bottom\", \"left\"], default: \"bottom\" },\n align: { type: \"enum\", values: [\"start\", \"center\", \"end\"], default: \"start\" },\n disabled: { type: \"boolean\", default: false },\n motion: { type: \"enum\", values: [\"auto\", \"none\"], default: \"auto\" },\n description: {\n type: \"string\",\n description:\n \"Short summary: a tooltip preview on the trigger and a line under the title of the open surface, which it describes. A disabled trigger with a description stays focusable, so focus or a tap shows it.\",\n },\n returnFocusRef: {\n type: \"node\",\n description: \"RefObject<HTMLElement | null> fallback when the trigger is removed.\",\n },\n onCloseAutoFocus: { type: \"function\" },\n className: { type: \"string\" },\n },\n slots: {\n children: { type: \"single focusable trigger element\", required: true },\n content: { type: \"node\", required: true },\n },\n composable_with: [\n \"Button\",\n \"EntityPreview\",\n \"DescriptionList\",\n \"CopyButton\",\n \"Inspector\",\n \"Tooltip\",\n ],\n never_inside: [\"Tooltip\"],\n example:\n '<Popover title=\"Equipment details\" description=\"Equipment operational\" content={<button>Inspect</button>}><Button>Details</Button></Popover>',\n },\n};\n","Popover/Popover.tsx":"\"use client\";\n\nimport { DialogTrigger, Pressable } from \"react-aria-components\";\nimport {\n cloneElement,\n forwardRef,\n useCallback,\n useEffect,\n useId,\n useRef,\n useState,\n version,\n type ComponentPropsWithoutRef,\n type ReactElement,\n type ReactNode,\n type Ref,\n type RefObject,\n} from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button\";\nimport { Tooltip } from \"../Tooltip\";\nimport { PopoverManifest } from \"./Popover.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { MotionContext, useMotion, type MotionMode } from \"../_internal/motion\";\nimport { MotionPopover } from \"../_internal/MotionPopover\";\nimport { POPOVER_WIDTH, SCREEN_MARGIN, toPlacement } from \"../_internal/overlay\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\n// About 30rem, never wider than the viewport less its margins (DC-7); --oprt-popover-max-width,\n// set through className, widens one surface. The height caps at the viewport and scrolls.\nconst surfaceStyles = cva(\n `z-50 border border-oprt-border bg-oprt-surface-1 font-sans text-oprt-base text-oprt-text ${POPOVER_WIDTH} max-h-[calc(100dvh-var(--oprt-space-4))] overflow-y-auto`,\n);\nconst dialogStyles =\n \"outline-hidden p-3u focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-solid focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-oprt-border-focus forced-colors:focus-visible:outline-[CanvasText]\";\n\ntype PopoverName =\n | { title: string; \"aria-label\"?: string }\n | { title?: string; \"aria-label\": string };\nexport type PopoverProps = Omit<\n ComponentPropsWithoutRef<\"div\">,\n \"children\" | \"content\" | \"title\" | \"aria-label\"\n> &\n PopoverName & {\n children: ReactElement;\n content: ReactNode;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n align?: \"start\" | \"center\" | \"end\";\n disabled?: boolean;\n motion?: MotionMode;\n /**\n * Short summary: previewed in a tooltip on hover or keyboard focus of the trigger (suppressed\n * while the details are open), and shown as a line under the title of the open surface, which\n * it describes. A disabled trigger stays focusable, so focus or a tap shows it.\n */\n description?: string;\n returnFocusRef?: RefObject<HTMLElement | null>;\n /** Cancelable native Event; fires only after an accepted close or open unmount. */\n onCloseAutoFocus?: (event: Event) => void;\n };\n\n/** Active surfaces in DOM portal order include nested OPERATOR menus/selectors. */\nfunction topSurface() {\n const surfaces = document.querySelectorAll<HTMLElement>(\n \".oprt-motion-popover:not([data-exiting]):not([inert])\",\n );\n return surfaces[surfaces.length - 1];\n}\n\nexport const Popover = forwardRef<HTMLDivElement, PopoverProps>(\n (\n {\n children,\n content,\n title,\n \"aria-label\": ariaLabel,\n open,\n defaultOpen = false,\n onOpenChange,\n side = \"bottom\",\n align = \"start\",\n disabled = false,\n className,\n motion = \"auto\",\n description,\n returnFocusRef,\n onCloseAutoFocus,\n id: suppliedId,\n ...dialogProps\n },\n ref,\n ) => {\n const [localOpen, setLocalOpen] = useState(defaultOpen);\n const isOpen = open ?? localOpen;\n const [positioning, setPositioning] = useState(true);\n const [tooltipOpen, setTooltipOpen] = useState(false);\n const motionState = useMotion(motion);\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Popover\", PopoverManifest.Popover!.props, { ...dialogProps, side, align });\n }\n const generatedId = useId();\n const id = suppliedId ?? `oprt-popover-${generatedId}`;\n const trigger = useRef<HTMLElement | null>(null);\n const lastTrigger = useRef<HTMLElement | null>(null);\n const dialog = useRef<HTMLDivElement | null>(null);\n const lastDialog = useRef<HTMLDivElement | null>(null);\n const wasOpen = useRef(false);\n const focusFrame = useRef<number>();\n const focusScrollClose = useRef(false);\n const current = useRef({ isOpen, onCloseAutoFocus, returnFocusRef });\n current.current = { isOpen, onCloseAutoFocus, returnFocusRef };\n // React 18 stores refs on the element; React 19 stores them in props.\n const childRef =\n parseInt(version, 10) < 19\n ? (children as ReactElement & { ref?: Ref<HTMLElement> }).ref\n : (children.props as { ref?: Ref<HTMLElement> }).ref;\n // Remember the current and the last attached trigger and dialog, alongside the refs the\n // consumer handed over (the child's own ref and the forwarded one).\n const rememberTrigger = useCallback((node: HTMLElement | null) => {\n trigger.current = node;\n if (node) lastTrigger.current = node;\n }, []);\n const setTrigger = useMergedRefs<HTMLElement>(rememberTrigger, childRef);\n const rememberDialog = useCallback((node: HTMLDivElement | null) => {\n dialog.current = node;\n if (node) lastDialog.current = node;\n }, []);\n const setDialog = useMergedRefs<HTMLDivElement>(rememberDialog, ref);\n const restoreFocus = useCallback(() => {\n const event = new Event(\"popovercloseautofocus\", { cancelable: true });\n current.current.onCloseAutoFocus?.(event);\n if (event.defaultPrevented) return;\n if (focusFrame.current !== undefined) cancelAnimationFrame(focusFrame.current);\n // Native blur can commit a close before the browser focuses an outside\n // control. Wait for that focus sequence before deciding to restore.\n focusFrame.current = requestAnimationFrame(() => {\n focusFrame.current = undefined;\n if (current.current.isOpen && dialog.current?.isConnected) return;\n const active = document.activeElement;\n if (\n active &&\n active !== document.body &&\n active.isConnected &&\n !lastDialog.current?.contains(active)\n )\n return;\n const target = lastTrigger.current?.isConnected\n ? lastTrigger.current\n : current.current.returnFocusRef?.current;\n if (target?.isConnected) target.focus({ preventScroll: true });\n });\n }, []);\n useEffect(() => {\n if (isOpen && !wasOpen.current) {\n if (focusFrame.current !== undefined) cancelAnimationFrame(focusFrame.current);\n setTooltipOpen(false);\n dialog.current?.focus({ preventScroll: true });\n }\n if (!isOpen && wasOpen.current) restoreFocus();\n wasOpen.current = isOpen;\n }, [isOpen, restoreFocus]);\n useEffect(\n () => () => {\n // StrictMode replays effects while the DOM remains connected. Only a\n // detached open surface has actually unmounted and needs restoration.\n if (wasOpen.current && !dialog.current?.isConnected) restoreFocus();\n },\n [restoreFocus],\n );\n\n const requestOpen = useCallback(\n (next: boolean) => {\n if (next && disabled) return;\n if (!next && focusScrollClose.current) return;\n if (open === undefined) setLocalOpen(next);\n onOpenChange?.(next);\n },\n [disabled, open, onOpenChange],\n );\n\n useIsomorphicLayoutEffect(() => {\n // Resuming RAC positioning remeasures its anchor without remounting the\n // focused dialog or writing over the primitive's calculated styles.\n if (!positioning) setPositioning(true);\n }, [positioning]);\n\n useIsomorphicLayoutEffect(() => {\n if (!isOpen) return;\n // Focusing an offscreen trigger can queue a native ancestor scroll after\n // RAC mounts. Moving focus within a tall surface can also scroll an\n // ancestor. Ignore those dispatches until two frames have settled focus.\n // A subsequent wheel, pointer, touch or keyboard gesture ends this grace.\n let settlingFocus = true;\n let explicitScroll = false;\n let anchorRect = trigger.current?.getBoundingClientRect();\n let scrollTask: ReturnType<typeof setTimeout> | undefined;\n let frame: number;\n const settleFocus = () => {\n cancelAnimationFrame(frame);\n settlingFocus = true;\n explicitScroll = false;\n frame = requestAnimationFrame(() => {\n frame = requestAnimationFrame(() => {\n settlingFocus = false;\n anchorRect = trigger.current?.getBoundingClientRect();\n });\n });\n };\n settleFocus();\n const focus = (event: FocusEvent) => {\n if (dialog.current?.contains(event.target as Node)) settleFocus();\n };\n const scroll = () => {\n const nextRect = trigger.current?.getBoundingClientRect();\n // Scroll anchoring can compensate for asynchronously resized content\n // above the trigger. Keep its preview when the screen anchor stays put.\n const anchored =\n !explicitScroll &&\n anchorRect &&\n nextRect &&\n anchorRect.width > 0 &&\n anchorRect.height > 0 &&\n Math.abs(anchorRect.left - nextRect.left) < 0.5 &&\n Math.abs(anchorRect.top - nextRect.top) < 0.5 &&\n Math.abs(anchorRect.width - nextRect.width) < 0.5 &&\n Math.abs(anchorRect.height - nextRect.height) < 0.5;\n if (!settlingFocus && !anchored) {\n focusScrollClose.current = false;\n return;\n }\n focusScrollClose.current = true;\n setPositioning(false);\n // Browsers may run microtasks between native event listeners. Keep the\n // marker through this dispatch, ending it at the next task instead.\n if (scrollTask !== undefined) clearTimeout(scrollTask);\n scrollTask = setTimeout(() => {\n focusScrollClose.current = false;\n scrollTask = undefined;\n }, 0);\n };\n const interact = () => {\n settlingFocus = false;\n explicitScroll = true;\n focusScrollClose.current = false;\n };\n // Layout registration precedes RAC's passive scroll listener on window.\n window.addEventListener(\"scroll\", scroll, true);\n window.addEventListener(\"focusin\", focus, true);\n for (const event of [\"wheel\", \"pointerdown\", \"touchmove\", \"keydown\"])\n window.addEventListener(event, interact, true);\n return () => {\n cancelAnimationFrame(frame);\n if (scrollTask !== undefined) clearTimeout(scrollTask);\n focusScrollClose.current = false;\n window.removeEventListener(\"scroll\", scroll, true);\n window.removeEventListener(\"focusin\", focus, true);\n for (const event of [\"wheel\", \"pointerdown\", \"touchmove\", \"keydown\"])\n window.removeEventListener(event, interact, true);\n };\n }, [isOpen]);\n\n useEffect(() => {\n if (!isOpen) return;\n // RAC isNonModal intentionally disables outside-pointer dismissal. Add it\n // without an underlay so the intended background action still receives input.\n let startedOutside = false;\n const start = (event: PointerEvent) => {\n const surface = dialog.current?.parentElement;\n const target = event.target as Node | null;\n startedOutside =\n event.button === 0 &&\n !!surface &&\n topSurface() === surface &&\n !surface.contains(target) &&\n !trigger.current?.contains(target);\n };\n const end = (event: PointerEvent) => {\n const surface = dialog.current?.parentElement;\n const target = event.target as Node | null;\n if (\n startedOutside &&\n surface &&\n topSurface() === surface &&\n !surface.contains(target) &&\n !trigger.current?.contains(target)\n )\n requestOpen(false);\n startedOutside = false;\n };\n const cancel = () => {\n startedOutside = false;\n };\n document.addEventListener(\"pointerdown\", start, true);\n document.addEventListener(\"pointerup\", end, true);\n document.addEventListener(\"pointercancel\", cancel, true);\n return () => {\n document.removeEventListener(\"pointerdown\", start, true);\n document.removeEventListener(\"pointerup\", end, true);\n document.removeEventListener(\"pointercancel\", cancel, true);\n };\n }, [isOpen, requestOpen]);\n\n const triggerElement = cloneElement(children, {\n ref: setTrigger,\n disabled: disabled || children.props.disabled,\n \"aria-disabled\": disabled || children.props[\"aria-disabled\"],\n \"aria-controls\": isOpen ? id : undefined,\n });\n const pressable = (\n <Pressable isDisabled={disabled}>\n {triggerElement as ComponentPropsWithoutRef<typeof Pressable>[\"children\"]}\n </Pressable>\n );\n return (\n <MotionContext.Provider value={motionState.mode}>\n <DialogTrigger isOpen={isOpen} onOpenChange={requestOpen}>\n <span className=\"contents\" {...motionState.props}>\n {description ? (\n <Tooltip\n content={description}\n open={tooltipOpen && !isOpen}\n onOpenChange={setTooltipOpen}\n >\n {pressable}\n </Tooltip>\n ) : (\n pressable\n )}\n </span>\n <MotionPopover\n motionState={motionState}\n focusManagement=\"manual\"\n isNonModal\n shouldUpdatePosition={positioning}\n placement={toPlacement(side, align)}\n offset={4}\n containerPadding={SCREEN_MARGIN}\n className={clsx(surfaceStyles(), className)}\n shouldCloseOnInteractOutside={(element) => {\n if (trigger.current?.contains(element)) return false;\n const surface = dialog.current?.parentElement;\n return !!surface && topSurface() === surface;\n }}\n >\n <div\n {...dialogProps}\n ref={setDialog}\n id={id}\n role=\"dialog\"\n tabIndex={-1}\n aria-label={ariaLabel}\n aria-labelledby={ariaLabel ? undefined : `${id}-title`}\n // The description line below describes the dialog, after any the consumer set.\n aria-describedby={\n [dialogProps[\"aria-describedby\"], description ? `${id}-description` : undefined]\n .filter(Boolean)\n .join(\" \") || undefined\n }\n data-oprt-popover-dialog=\"\"\n className={dialogStyles}\n >\n <div className=\"flex items-center justify-between gap-3u mb-2u\">\n {title && (\n <h2\n id={`${id}-title`}\n // An unbroken identifier wraps instead of scrolling the surface sideways (DC-7).\n className=\"m-0 min-w-0 font-sans text-oprt-base font-semibold uppercase tracking-oprt-caps oprt-wrap-anywhere\"\n >\n {title}\n </h2>\n )}\n <Button\n variant=\"ghost\"\n size=\"sm\"\n // At the end of the header whether or not a visible title precedes it.\n className=\"ml-auto\"\n onClick={() => {\n focusScrollClose.current = false;\n requestOpen(false);\n }}\n >\n Close\n </Button>\n </div>\n {/* Design call DC-1: the description the trigger previews also opens with the\n details, as a short line under the title, so touch and keyboard users get it. */}\n {description && (\n <p\n id={`${id}-description`}\n className=\"m-0 mb-2u text-oprt-base normal-case text-oprt-text-dim oprt-wrap-anywhere\"\n >\n {description}\n </p>\n )}\n {content}\n </div>\n </MotionPopover>\n </DialogTrigger>\n </MotionContext.Provider>\n );\n },\n);\nPopover.displayName = \"Popover\";\n","Progress/index.ts":"export { Progress, type ProgressProps } from \"./Progress\";\nexport { ProgressManifest } from \"./Progress.manifest\";\n","Progress/Progress.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ProgressManifest: Record<string, Component> = {\n Progress: {\n description:\n \"Determinate or indeterminate progress indicator rendered as a filled bar or a row of block glyphs. It fills the width its container gives it.\",\n props: {\n value: {\n type: \"number\",\n description:\n \"Current progress, clamped from 0 to max. Omit or pass a non-finite value for an indeterminate (amount unknown) state: a full-width hatched fill that slides gently and stays a still hatch with reduced motion and in forced colors, so it never looks like 0% or 100%.\",\n },\n max: {\n type: \"number\",\n default: 100,\n description:\n \"Finite positive upper bound. Zero, negative, and non-finite values fall back to 100.\",\n },\n label: {\n type: \"string\",\n description:\n \"Optional short progress heading, displayed uppercase. Long identifiers wrap beside the value.\",\n },\n showValue: { type: \"boolean\", default: false },\n variant: { type: \"enum\", values: [\"bar\", \"blocks\"], default: \"bar\" },\n size: { type: \"enum\", values: [\"sm\", \"md\"], default: \"md\" },\n },\n composable_with: [\"Card\", \"Field\"],\n never_inside: [\"Progress\"],\n example: \"<Progress value={72} showValue />\",\n },\n};\n","Progress/Progress.tsx":"\"use client\";\n\nimport { ProgressBar } from \"react-aria-components\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ComponentPropsWithoutRef } from \"react\";\nimport clsx from \"clsx\";\nimport { ProgressManifest } from \"./Progress.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\nconst rootStyles = cva(\n \"relative w-full overflow-hidden bg-oprt-surface-2 border border-oprt-border\",\n {\n variants: {\n size: {\n sm: \"h-1.5u\",\n md: \"h-2u\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n// Number of cells rendered in the \"blocks\" variant.\nconst BLOCK_CELLS = 20;\n\n// The fill of a bar with a known value. Forced colors (Windows high contrast) replace every\n// background color with the page color, which would leave the fill the color of the empty track\n// and every bar looking empty; the fill opts out and draws in the text color instead.\nconst SOLID_FILL =\n \"bg-oprt-accent forced-colors:bg-[CanvasText] forced-colors:[forced-color-adjust:none]\";\n\n// The fill of a bar with no known value (design call DC-13): a full-width hatch, so it cannot be\n// mistaken for an empty bar (0%) or a full one (100%). It is drawn with a background image, in the\n// accent color through a custom property that forced colors turn into a system color. Stripes\n// and gaps are 4 px tokens, repeating every --oprt-space-1 along a 135-degree line: the repeat\n// `oprt-motion-slide` moves by default.\nconst HATCH_FILL =\n \"[--oprt-hatch:var(--oprt-accent)] forced-colors:[--oprt-hatch:CanvasText] forced-colors:[forced-color-adjust:none] [background-image:repeating-linear-gradient(135deg,var(--oprt-hatch)_0,var(--oprt-hatch)_var(--oprt-space-0-5),transparent_var(--oprt-space-0-5),transparent_var(--oprt-space-1))]\";\n\n// The hatch moves gently: it slides to the right one repeat at a time, and the loop has no seam.\n// Reduced motion and forced colors leave it still (the utility does this itself; said here too).\nconst SLIDE_STILL = \"motion-reduce:animate-none forced-colors:animate-none\";\n\n// The two glyphs of the blocks hatch. A pair is one repeat of the pattern.\nconst BLOCK_HATCH = [\"▚\", \"▞\"] as const;\n\nexport type ProgressProps = Omit<ComponentPropsWithoutRef<\"div\">, \"value\" | \"size\"> &\n VariantProps<typeof rootStyles> & {\n value?: number;\n max?: number;\n label?: string;\n showValue?: boolean;\n variant?: \"bar\" | \"blocks\";\n };\n\nexport const Progress = forwardRef<HTMLDivElement, ProgressProps>((allProps, ref) => {\n const {\n className,\n value,\n max = 100,\n label,\n showValue = false,\n variant,\n size,\n ...props\n } = allProps;\n // A null or unknown variant or size renders the default one (devValidate reports an unknown one).\n const blocks = variant === \"blocks\";\n const sizeKey = size === \"sm\" ? \"sm\" : \"md\";\n\n const safeMax = Number.isFinite(max) && max > 0 ? max : 100;\n const indeterminate = value == null || !Number.isFinite(value);\n const safeValue = indeterminate ? 0 : Math.min(safeMax, Math.max(0, value));\n const pct = (safeValue / safeMax) * 100;\n const ariaLabel = (props[\"aria-label\"] as string | undefined) ?? label ?? \"Progress\";\n\n // exactOptionalPropertyTypes forbids passing literal `undefined` to an\n // optional prop. Build the ProgressBar props conditionally: the\n // indeterminate case sets isIndeterminate and omits `value` entirely, so\n // React Aria drops aria-valuenow / aria-valuetext.\n const barProps: Record<string, unknown> = { maxValue: safeMax, minValue: 0 };\n if (indeterminate) barProps[\"isIndeterminate\"] = true;\n else barProps[\"value\"] = safeValue;\n\n const filled = Math.round((pct / 100) * BLOCK_CELLS);\n const fillState = indeterminate ? \"indeterminate\" : \"determinate\";\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Progress\", ProgressManifest.Progress!.props, allProps);\n }\n\n return (\n // The wrapper takes the width its container gives it, in a flex row as in a block: a bar has no\n // width of its own, and without this it was 2 px wide beside other content.\n <div className=\"flex w-full min-w-0 flex-col gap-1u font-sans\">\n {(label || showValue) && (\n <div className=\"flex items-baseline justify-between gap-x-2u\">\n {label ? (\n <span className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-sm uppercase tracking-oprt-sm text-oprt-text-dim\">\n {label}\n </span>\n ) : (\n <span aria-hidden=\"true\" />\n )}\n {showValue && (\n <span className=\"m-0 shrink-0 font-mono text-oprt-base text-oprt-text tabular-nums\">\n {indeterminate ? \"—\" : `${Math.round(pct)}%`}\n </span>\n )}\n </div>\n )}\n <ProgressBar\n ref={ref}\n aria-label={ariaLabel}\n className={\n blocks\n ? clsx(\"font-mono text-oprt-base\", className)\n : clsx(rootStyles({ size: sizeKey }), className)\n }\n {...barProps}\n {...(props as object)}\n >\n {blocks ? (\n indeterminate ? (\n // An unknown amount is a hatch of alternating quarter blocks: neither the empty glyph\n // of 0% nor the full one of 100%. The fill is as wide as the twenty cells and clips the\n // row, which slides two cells (one repeat, a tenth of its width) at a time; two more\n // cells wait just before its start and slide in, so each loop ends as the next begins.\n <span\n aria-hidden=\"true\"\n data-oprt-progress-fill={fillState}\n className=\"flex w-fit max-w-full overflow-hidden\"\n >\n <span\n className={clsx(\n \"relative flex tracking-oprt-md oprt-motion-slide-[10%]\",\n SLIDE_STILL,\n )}\n >\n {Array.from({ length: BLOCK_CELLS }, (_, i) => (\n <span key={i} className=\"text-oprt-accent\">\n {BLOCK_HATCH[i % 2]}\n </span>\n ))}\n <span data-oprt-progress-tail=\"\" className=\"absolute right-full top-0 flex\">\n {BLOCK_HATCH.map((glyph) => (\n <span key={glyph} className=\"text-oprt-accent\">\n {glyph}\n </span>\n ))}\n </span>\n </span>\n </span>\n ) : (\n <span\n aria-hidden=\"true\"\n data-oprt-progress-fill={fillState}\n className=\"flex tracking-oprt-md\"\n >\n {Array.from({ length: BLOCK_CELLS }, (_, i) => (\n <span key={i} className={i < filled ? \"text-oprt-accent\" : \"text-oprt-text-dim\"}>\n {i < filled ? \"█\" : \"░\"}\n </span>\n ))}\n </span>\n )\n ) : (\n // RAC has no Indicator part; the fill is a plain element inside the progressbar. A known\n // value slides the fill in from the left; an unknown one covers the track with the hatch,\n // and one repeat more before its start, so the slide never uncovers the track.\n <div\n data-oprt-progress-fill={fillState}\n className={clsx(\n \"h-full\",\n indeterminate\n ? clsx(\n HATCH_FILL,\n \"-ml-[var(--oprt-slide-distance)] w-[calc(100%+var(--oprt-slide-distance))] oprt-motion-slide\",\n SLIDE_STILL,\n )\n : clsx(SOLID_FILL, \"w-full transition-transform duration-oprt-normal\"),\n )}\n {...(indeterminate ? {} : { style: { transform: `translateX(-${100 - pct}%)` } })}\n />\n )}\n </ProgressBar>\n </div>\n );\n});\nProgress.displayName = \"Progress\";\n","PropertyFilter/index.ts":"export { PropertyFilter, type PropertyFilterProps } from \"./PropertyFilter\";\n// From the module without React, so the check stays callable from a Server Component.\nexport {\n getPropertyFilterTokenError,\n type PropertyFilterOperator,\n type PropertyFilterField,\n type PropertyFilterToken,\n type PropertyFilterValue,\n} from \"./tokenError\";\nexport { PropertyFilterManifest } from \"./PropertyFilter.manifest\";\n","PropertyFilter/PropertyFilter.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const PropertyFilterManifest: Record<string, Component> = {\n PropertyFilter: {\n description:\n \"Controlled flat property-filter editor with text or enum values, all/any matching and removable, semantically validated clauses.\",\n props: {\n fields: {\n type: \"array\",\n required: true,\n description:\n \"Readonly PropertyFilterField[] with key, label, operators and optional value/label/disabled options. Operators have key, label and requiresValue.\",\n },\n value: {\n type: \"node\",\n required: true,\n description:\n \"PropertyFilterValue: match all or any, and readonly tokens containing id, field, operator and string or null value.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description:\n \"Requests the complete next value. The parent accepts it through value; declined updates keep tokens and addition drafts.\",\n },\n label: { type: \"string\", default: \"Filters\" },\n disabled: { type: \"boolean\", default: false },\n className: { type: \"string\" },\n },\n composable_with: [\"TagGroup\", \"DataGrid\", \"CollectionState\"],\n never_inside: [\"Button\"],\n example:\n '<PropertyFilter fields={fields} value={filter} onValueChange={setFilter} label=\"Equipment filters\" />',\n },\n};\n","PropertyFilter/PropertyFilter.tsx":"\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useId,\n useImperativeHandle,\n useRef,\n useState,\n type HTMLAttributes,\n} from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Button } from \"../Button/Button\";\nimport { Field } from \"../Field/Field\";\nimport { Input } from \"../Input/Input\";\nimport { Select, SelectOption } from \"../Select/Select\";\nimport { TagGroup } from \"../TagGroup/TagGroup\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\nimport {\n getPropertyFilterTokenError,\n type PropertyFilterField,\n type PropertyFilterToken,\n type PropertyFilterValue,\n} from \"./tokenError\";\n\n// The data types and the clause check live in a module without React, so a Server Component can\n// call the check (PropertyFilter-R5); they are re-exported here for code that imports this file.\nexport {\n getPropertyFilterTokenError,\n type PropertyFilterField,\n type PropertyFilterOperator,\n type PropertyFilterToken,\n type PropertyFilterValue,\n} from \"./tokenError\";\n\nconst filterStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans normal-case text-oprt-base text-oprt-text\",\n);\n// Stage 1 R1 (PropertyFilter-R4): a control takes the space its row gives it, never the window's\n// breakpoint. Each asks for 192 px and grows into what is left, so two sit side by side only when\n// the editor gives each its 192 px, and a narrower editor stacks them at its full width.\nconst controlStyles =\n \"min-w-[min(100%,calc(24*var(--oprt-space-1)))] max-w-full grow basis-[calc(24*var(--oprt-space-1))]\";\n// Select's trigger grows with a wrapped value itself (Select-B2M-1..3); here it fills its Field like\n// the text input beside it.\nconst selectStyles = \"w-full min-w-0 max-w-full\";\n\nexport type PropertyFilterProps = Omit<\n HTMLAttributes<HTMLDivElement>,\n \"children\" | \"defaultValue\" | \"onChange\"\n> & {\n fields: readonly PropertyFilterField[];\n value: PropertyFilterValue;\n onValueChange: (value: PropertyFilterValue) => void;\n label?: string;\n disabled?: boolean;\n className?: string;\n};\n\ntype Draft = { field: string; operator: string; value: string };\n/** A draft error and the control the operator has to fix (PropertyFilter-R2). */\ntype DraftError = { message: string; control: \"field\" | \"operator\" | \"value\" };\n\nfunction tokenLabel(token: PropertyFilterToken, fields: readonly PropertyFilterField[]): string {\n const field = fields.find((candidate) => candidate.key === token.field);\n const operator = field?.operators.find((candidate) => candidate.key === token.operator);\n const value =\n field?.options?.find((option) => option.value === token.value)?.label ?? token.value;\n const label = `${field?.label ?? token.field} ${operator?.label ?? token.operator}${value === null ? \"\" : ` ${value}`}`;\n const error = getPropertyFilterTokenError(token, fields);\n return error ? `Invalid filter: ${label}. ${error}` : label;\n}\n\nexport const PropertyFilter = forwardRef<HTMLDivElement, PropertyFilterProps>(\n (\n {\n fields,\n value,\n onValueChange,\n label = \"Filters\",\n disabled = false,\n className,\n id,\n onFocusCapture,\n onBlurCapture,\n \"aria-labelledby\": labelledBy,\n ...props\n },\n ref,\n ) => {\n // Clause ids are data the application keeps, minted on the client when a clause is added.\n const instanceId = useId();\n const root = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => root.current!, []);\n // Hydration-safe part ids (fieldWiringPlan step 6): after a hydration that disagreed with the\n // server's ids, the base is the one the page carries.\n const ids = usePartIds([\"error\"], {\n id,\n readRendered: () => root.current?.getAttribute(\"data-oprt-ids\"),\n });\n const nextId = useRef(0);\n const addRef = useRef<HTMLButtonElement>(null);\n const clearRef = useRef<HTMLButtonElement>(null);\n const composing = useRef(false);\n const pendingAdd = useRef<{ token: PropertyFilterToken; draft: Draft } | null>(null);\n const [draft, setDraft] = useState<Draft>({ field: \"\", operator: \"\", value: \"\" });\n const [error, setError] = useState<DraftError | null>(null);\n const field = fields.find((candidate) => candidate.key === draft.field);\n const operator = field?.operators.find((candidate) => candidate.key === draft.operator);\n /** The props that mark the control at fault and describe it with the draft error. */\n const fault = (control: DraftError[\"control\"]) =>\n error?.control === control ? { invalid: true, \"aria-describedby\": ids.error } : {};\n\n // Whether the operator moved on: Clear filters lost focus while it could still take it. The\n // shared helper learns this a microtask after the blur, as it must for a control that is\n // removed; Clear filters is only ever disabled, never removed, so it is known at once.\n const clearLeft = useRef(false);\n // When the application accepts Clear filters, on the next render or after a server round trip,\n // the disabled Clear filters gives focus to Add filter (PropertyFilter-R3), unless the operator\n // has moved on. A declined clear keeps Clear filters enabled, and its focus.\n const restore = useRestoreFocusOnRemoval(root, {\n track: (element) => (element === clearRef.current ? undefined : null),\n findTarget: () =>\n !clearLeft.current && !disabled && value.tokens.length === 0 ? addRef.current : null,\n });\n\n useEffect(() => {\n const pending = pendingAdd.current;\n if (\n pending &&\n value.tokens.some(\n (token) =>\n token.id === pending.token.id &&\n token.field === pending.token.field &&\n token.operator === pending.token.operator &&\n token.value === pending.token.value,\n )\n ) {\n pendingAdd.current = null;\n setDraft((current) => (current === pending.draft ? { ...current, value: \"\" } : current));\n }\n }, [value]);\n\n function addFilter() {\n if (disabled || fields.length === 0) return;\n if (!field || !operator) {\n setError({\n message: \"Choose a field and operator before adding a filter.\",\n control: field ? \"operator\" : \"field\",\n });\n return;\n }\n let id: string;\n do {\n id = `filter-${instanceId}-${++nextId.current}`;\n } while (value.tokens.some((token) => token.id === id));\n const token: PropertyFilterToken = {\n id,\n field: field.key,\n operator: operator.key,\n value: operator.requiresValue ? draft.value : null,\n };\n const tokenError = getPropertyFilterTokenError(token, fields);\n if (\n tokenError ||\n (operator.requiresValue &&\n field.options?.some((option) => option.value === draft.value && option.disabled))\n ) {\n setError({\n message: tokenError ?? `Choose an available value for ${field.label}.`,\n control: \"value\",\n });\n return;\n }\n setError(null);\n pendingAdd.current = { token, draft };\n onValueChange({ ...value, tokens: [...value.tokens, token] });\n }\n\n return (\n <div\n {...props}\n ref={root}\n role=\"group\"\n // A caller's label is added to this one: the group names itself by its own id first.\n id={id ?? (labelledBy ? ids.base : undefined)}\n aria-label={label}\n aria-labelledby={labelledBy ? joinIds(ids.base, labelledBy) : undefined}\n data-oprt-ids={ids.base}\n className={clsx(filterStyles(), className)}\n onFocusCapture={(event) => {\n onFocusCapture?.(event);\n if ((event.target as Element) === clearRef.current) clearLeft.current = false;\n restore.onFocusCapture(event);\n }}\n onBlurCapture={(event) => {\n onBlurCapture?.(event);\n const clear = clearRef.current;\n if ((event.target as Element) === clear && clear && !clear.disabled)\n clearLeft.current = true;\n restore.onBlurCapture(event);\n }}\n >\n <div className=\"flex min-w-0 flex-wrap items-end gap-2u\">\n <Field label=\"Field\" className={controlStyles}>\n <Select\n value={draft.field}\n placeholder=\"Choose field\"\n disabled={disabled || fields.length === 0}\n className={selectStyles}\n {...fault(\"field\")}\n onValueChange={(key) => {\n if (disabled) return;\n const next = fields.find((candidate) => candidate.key === key);\n setDraft({ field: key, operator: next?.operators[0]?.key ?? \"\", value: \"\" });\n setError(null);\n }}\n >\n {fields.map((item) => (\n <SelectOption key={item.key} value={item.key}>\n {item.label}\n </SelectOption>\n ))}\n </Select>\n </Field>\n <Field label=\"Operator\" className={controlStyles}>\n <Select\n value={draft.operator}\n placeholder=\"Choose operator\"\n disabled={disabled || !field || !field.operators.length}\n className={selectStyles}\n {...fault(\"operator\")}\n onValueChange={(key) => {\n if (disabled) return;\n const next = field?.operators.find((candidate) => candidate.key === key);\n setDraft((current) => ({\n ...current,\n operator: key,\n value: operator?.requiresValue === next?.requiresValue ? current.value : \"\",\n }));\n setError(null);\n }}\n >\n {(field?.operators ?? []).map((item) => (\n <SelectOption key={item.key} value={item.key}>\n {item.label}\n </SelectOption>\n ))}\n </Select>\n </Field>\n {operator?.requiresValue && (\n <Field label=\"Value\" className={controlStyles}>\n {field?.options ? (\n <Select\n value={draft.value}\n placeholder=\"Choose value\"\n disabled={disabled}\n className={selectStyles}\n {...fault(\"value\")}\n onValueChange={(next) => {\n if (!disabled) {\n setDraft((current) => ({ ...current, value: next }));\n setError(null);\n }\n }}\n >\n {field.options.map((option) => (\n <SelectOption\n key={option.value}\n value={option.value}\n disabled={!!option.disabled}\n >\n {option.label}\n </SelectOption>\n ))}\n </Select>\n ) : (\n <Input\n value={draft.value}\n disabled={disabled}\n className=\"w-full min-w-0\"\n {...fault(\"value\")}\n onChange={(event) => {\n if (!disabled) {\n setDraft((current) => ({ ...current, value: event.target.value }));\n setError(null);\n }\n }}\n onCompositionStart={() => {\n composing.current = true;\n }}\n onCompositionEnd={() => {\n composing.current = false;\n }}\n // Coordinator ruling (PropertyFilter-R1): Enter adds the clause, with the same\n // checks as Add filter, and never submits a form around the editor. Focus stays\n // in the field; Enter during input-method composition belongs to the composition.\n onKeyDown={(event) => {\n if (event.key !== \"Enter\") return;\n if (composing.current || event.nativeEvent.isComposing || event.keyCode === 229)\n return;\n event.preventDefault();\n addFilter();\n }}\n />\n )}\n </Field>\n )}\n <Button\n ref={addRef}\n disabled={disabled || fields.length === 0}\n onClick={addFilter}\n aria-describedby={error ? ids.error : undefined}\n >\n Add filter\n </Button>\n </div>\n {error && (\n <p\n id={ids.error}\n role=\"alert\"\n className=\"m-0 oprt-wrap-anywhere text-oprt-base text-oprt-error\"\n >\n {error.message}\n </p>\n )}\n <div className=\"flex min-w-0 flex-wrap items-end gap-2u\">\n <Field label=\"Match\" className=\"min-w-0 max-w-full\">\n <Select\n value={value.match}\n disabled={disabled}\n className={selectStyles}\n onValueChange={(match) => {\n if (!disabled && (match === \"all\" || match === \"any\"))\n onValueChange({ ...value, match });\n }}\n >\n <SelectOption value=\"all\">All filters</SelectOption>\n <SelectOption value=\"any\">Any filter</SelectOption>\n </Select>\n </Field>\n <Button\n ref={clearRef}\n variant=\"ghost\"\n disabled={disabled || value.tokens.length === 0}\n onClick={() => {\n if (disabled || value.tokens.length === 0) return;\n onValueChange({ ...value, tokens: [] });\n }}\n >\n Clear filters\n </Button>\n </div>\n <TagGroup\n label={`${label}: active filters`}\n items={value.tokens.map((token) => ({ id: token.id, label: tokenLabel(token, fields) }))}\n disabled={disabled}\n emptyMessage=\"No active filters\"\n returnFocusRef={addRef}\n onRemove={(id) => {\n if (!disabled)\n onValueChange({ ...value, tokens: value.tokens.filter((token) => token.id !== id) });\n }}\n />\n </div>\n );\n },\n);\nPropertyFilter.displayName = \"PropertyFilter\";\n","PropertyFilter/tokenError.ts":"// PropertyFilter's data types and its clause check, with no React in them, so a Server Action or a\n// route handler can import getPropertyFilterTokenError from the package root and call it: the\n// build leaves this module unmarked, while PropertyFilter.tsx is a client module.\n\nexport type PropertyFilterOperator = { key: string; label: string; requiresValue: boolean };\nexport type PropertyFilterField = {\n key: string;\n label: string;\n operators: readonly PropertyFilterOperator[];\n options?: readonly { value: string; label: string; disabled?: boolean }[];\n};\nexport type PropertyFilterToken = {\n id: string;\n field: string;\n operator: string;\n value: string | null;\n};\nexport type PropertyFilterValue = { match: \"all\" | \"any\"; tokens: readonly PropertyFilterToken[] };\n\n/** Why a clause does not fit the fields, or null when it does. */\nexport function getPropertyFilterTokenError(\n token: PropertyFilterToken,\n fields: readonly PropertyFilterField[],\n): string | null {\n const field = fields.find((candidate) => candidate.key === token.field);\n if (!field) return `Unknown field: ${token.field}.`;\n const operator = field.operators.find((candidate) => candidate.key === token.operator);\n if (!operator) return `Unknown operator for ${field.label}: ${token.operator}.`;\n if (!operator.requiresValue)\n return token.value === null\n ? null\n : `${field.label} ${operator.label} does not accept a value.`;\n if (typeof token.value !== \"string\" || token.value.trim().length === 0)\n return `Enter a value for ${field.label}.`;\n if (field.options && !field.options.some((option) => option.value === token.value))\n return `Unknown value for ${field.label}: ${token.value}.`;\n return null;\n}\n","RadioGroup/index.ts":"export { RadioGroup, Radio, type RadioGroupProps, type RadioProps } from \"./RadioGroup\";\nexport { RadioGroupManifest, RadioGroupCompositionRules } from \"./RadioGroup.manifest\";\n","RadioGroup/RadioGroup.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const RadioGroupManifest: Record<string, Component> = {\n RadioGroup: {\n description:\n \"Group of mutually exclusive options. Square outline with the shared OPERATOR Check icon when selected.\",\n props: {\n value: { type: \"string\" },\n defaultValue: { type: \"string\" },\n onValueChange: {\n type: \"function\",\n description: \"(value: string) => void. Called when the operator chooses an option.\",\n },\n disabled: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n orientation: { type: \"enum\", values: [\"horizontal\", \"vertical\"], default: \"vertical\" },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n name: { type: \"string\" },\n },\n slots: {\n children: { type: \"Radio\", required: true },\n },\n composable_with: [\"Field\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<Field label=\"CHANNEL\">\\n <RadioGroup defaultValue=\"alpha\">\\n <Radio value=\"alpha\">ALPHA</Radio>\\n <Radio value=\"bravo\">BRAVO</Radio>\\n </RadioGroup>\\n</Field>',\n },\n Radio: {\n description: \"Single radio option. Must be a child of <RadioGroup>.\",\n props: {\n value: { type: \"string\", required: true },\n disabled: { type: \"boolean\", default: false },\n },\n composable_with: [\"RadioGroup\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<Radio value=\"alpha\">ALPHA</Radio>',\n },\n};\n\nexport const RadioGroupCompositionRules = {\n RadioGroup: {\n allowed_children: [\"Radio\"],\n },\n};\n","RadioGroup/RadioGroup.tsx":"\"use client\";\n\nimport { RadioGroup as AriaRadioGroup, Radio as AriaRadio } from \"react-aria-components\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { createContext, forwardRef, useContext, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\nimport { Check } from \"@oprt/icons\";\nimport { useFieldWiring } from \"../_internal/fieldIds\";\nimport { RadioGroupManifest } from \"./RadioGroup.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly, useAriaInvalidRef } from \"../_internal/aria\";\nimport { keyForIds } from \"../_internal/hydration\";\nimport { useMergedRefs, useInputRefObject } from \"../_internal/mergeRefs\";\nimport { FORCED_RADIO } from \"../_internal/controlStyles\";\n\ntype Size = \"sm\" | \"md\" | \"lg\";\n\nconst SizeContext = createContext<Size>(\"md\");\n\nconst groupStyles = cva(\"flex font-sans\", {\n variants: {\n orientation: {\n vertical: \"flex-col gap-2u\",\n // A row that does not fit wraps onto more rows (design call DC-8); arrow keys follow the\n // reading order (RadioGroup-B2M-1).\n horizontal: \"flex-row flex-wrap gap-x-3u gap-y-2u\",\n },\n },\n defaultVariants: { orientation: \"vertical\" },\n});\n\n// Under Radix the indicator box was itself the `role=\"radio\"` element, so it owned\n// the state attributes. Under RAC the role lives on a hidden <input> and every state\n// attribute (data-selected / data-disabled / data-focus-visible) lands on the\n// wrapping <label>, so the box and its inner mark are styled from the label via\n// descendant selectors — the same idiom the Checkbox already used for its marks. The resting box\n// is the control outline (design call DC-3); every box of an invalid group takes the error color\n// (the Toggle-R5 precedent), raised by `[&&]` above the selected accent, which would win a tie.\nconst radioWrapperStyles =\n \"inline-flex items-center min-h-3u min-w-3u gap-2u text-oprt-base font-medium text-oprt-text font-sans cursor-pointer data-[disabled]:opacity-50 data-[disabled]:cursor-not-allowed data-[focus-visible]:[&_.oprt-radio-box]:border-oprt-border-focus data-[focus-visible]:[&_.oprt-radio-box]:shadow-oprt-focus data-[selected]:[&_.oprt-radio-box]:border-oprt-accent [&&]:data-[invalid]:[&_.oprt-radio-box]:border-oprt-error data-[selected]:[&_.oprt-radio-inner]:block\";\n\nconst radioBoxStyles = cva(\n \"oprt-radio-box inline-flex shrink-0 items-center justify-center border bg-oprt-surface-2 transition-colors duration-oprt-fast border-oprt-border-control\",\n {\n variants: {\n size: {\n sm: \"h-2u w-2u\",\n md: \"h-2.5u w-2.5u\",\n lg: \"h-3u w-3u\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport type RadioGroupProps = VariantProps<typeof groupStyles> & {\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n disabled?: boolean;\n required?: boolean;\n name?: string;\n size?: Size;\n className?: string;\n id?: string;\n children: ReactNode;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: boolean | \"true\" | \"false\";\n};\n\nexport const RadioGroup = forwardRef<HTMLDivElement, RadioGroupProps>(\n (\n {\n className,\n orientation,\n size = \"md\",\n id,\n children,\n value,\n defaultValue,\n onValueChange,\n disabled,\n required,\n name,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n },\n ref,\n ) => {\n // `<label htmlFor>` cannot name a div[role=radiogroup], so it takes the Field label's id as\n // aria-labelledby; without it the group has no accessible name. The shared Field rules (Stage 1\n // Field-R2): its own id, names, description and aria-invalid win over the Field's; a required\n // Field makes it required (coordinator ruling on DC-B04-1).\n const {\n id: finalId,\n labelledBy,\n describedBy,\n invalid: isInvalid,\n required: isRequired,\n } = useFieldWiring(\n {\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n required,\n },\n { label: \"labelledby\" },\n );\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"RadioGroup\", RadioGroupManifest.RadioGroup!.props, { orientation, size });\n }\n\n // RAC strips/omits aria-invalid on some components; pin it to the DOM node so\n // the group's validity is still announced.\n const groupRef = useAriaInvalidRef<HTMLDivElement>(ref, isInvalid);\n\n // exactOptionalPropertyTypes forbids passing an explicit `undefined` for an\n // optional prop, so build the props object conditionally.\n const rootProps: Record<string, unknown> = {\n isDisabled: disabled ?? false,\n isRequired,\n isInvalid,\n orientation: orientation ?? \"vertical\",\n ...definedOnly({\n id: finalId,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n name,\n }),\n };\n if (value !== undefined) rootProps[\"value\"] = value;\n if (defaultValue !== undefined) rootProps[\"defaultValue\"] = defaultValue;\n if (onValueChange) rootProps[\"onChange\"] = onValueChange;\n\n return (\n <SizeContext.Provider value={size}>\n <AriaRadioGroup\n // RAC keeps the ids it mounted with (see keyForIds).\n key={keyForIds(finalId, labelledBy)}\n ref={groupRef}\n {...rootProps}\n className={clsx(groupStyles({ orientation }), className)}\n >\n {children}\n </AriaRadioGroup>\n </SizeContext.Provider>\n );\n },\n);\nRadioGroup.displayName = \"RadioGroup\";\n\nexport type RadioProps = {\n value: string;\n disabled?: boolean;\n className?: string;\n id?: string;\n children: ReactNode;\n \"aria-label\"?: string;\n};\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(\n ({ className, children, value, disabled, id, \"aria-label\": ariaLabel }, ref) => {\n const size = useContext(SizeContext);\n\n // The forwarded ref, attached once for as long as it is the same ref (Wave 1, RadioGroup-R4).\n const setNode = useMergedRefs<HTMLInputElement>(ref);\n // React Aria takes the hidden input's ref as an object; the public ref lands on that input,\n // the element with role=\"radio\".\n const inputRef = useInputRefObject(setNode);\n\n const itemProps = definedOnly({ id, \"aria-label\": ariaLabel });\n\n return (\n <AriaRadio\n value={value}\n isDisabled={disabled ?? false}\n inputRef={inputRef}\n {...itemProps}\n className={clsx(radioWrapperStyles, FORCED_RADIO, className)}\n >\n <span className={radioBoxStyles({ size })}>\n <Check\n size={size === \"sm\" ? 12 : size === \"lg\" ? 20 : 16}\n className=\"oprt-radio-inner hidden shrink-0 text-oprt-accent\"\n />\n </span>\n {/* The name wraps beside its box and long identifiers break anywhere (RadioGroup-B2M-2). */}\n <span className=\"min-w-0 oprt-wrap-anywhere normal-case\">{children}</span>\n </AriaRadio>\n );\n },\n);\nRadio.displayName = \"Radio\";\n","Select/index.ts":"export { Select, SelectOption, type SelectProps, type SelectOptionProps } from \"./Select\";\nexport { SelectManifest, SelectCompositionRules } from \"./Select.manifest\";\n","Select/Select.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const SelectManifest: Record<string, Component> = {\n Select: {\n description:\n \"Single-select dropdown. Trigger matches Input geometry for one line and grows when its text wraps; inside a Field it fills the Field's width. Portal'd list with an accent border and the shared OPERATOR Check icon on the selected option; an empty Select opens a list that says so.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer arrival, exit, and open-state chevron rotation; keyboard, assistive activation, and system reduced motion remain immediate.\",\n },\n value: {\n type: \"string\",\n description:\n \"Controlled selected option value. Keep the selected option among the children; a value with no matching option shows the placeholder and submits nothing.\",\n },\n defaultValue: { type: \"string\", description: \"Initial selected option value.\" },\n onValueChange: {\n type: \"function\",\n description: \"(value: string) => void. Called when the operator chooses an option.\",\n },\n open: { type: \"boolean\", description: \"Controlled open state of the list.\" },\n defaultOpen: { type: \"boolean\", default: false, description: \"Initial open state.\" },\n onOpenChange: {\n type: \"function\",\n description: \"(open: boolean) => void. Called when the list opens or closes.\",\n },\n placeholder: { type: \"string\" },\n emptyMessage: {\n type: \"string\",\n default: \"No options available.\",\n description: \"What the list says when there are no options to choose from.\",\n },\n \"aria-label\": {\n type: \"string\",\n description: \"Accessible name when there is no surrounding Field or visible heading.\",\n },\n \"aria-labelledby\": {\n type: \"string\",\n description: \"IDs of visible elements that name the Select; wins over a Field's label.\",\n },\n \"aria-describedby\": {\n type: \"string\",\n description: \"Description IDs; replaces a surrounding Field's help or error.\",\n },\n disabled: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n name: { type: \"string\" },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n invalid: { type: \"boolean\", default: false },\n },\n slots: {\n children: { type: \"SelectOption\", required: true },\n },\n composable_with: [\"Field\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<Field label=\"THEATER\">\\n <Select placeholder=\"CHOOSE...\">\\n <SelectOption value=\"indo-pacific\">INDO-PACIFIC</SelectOption>\\n <SelectOption value=\"european\">EUROPEAN</SelectOption>\\n </Select>\\n</Field>',\n },\n SelectOption: {\n description: \"Single option inside a Select. Must be a child of <Select>.\",\n props: {\n value: { type: \"string\", required: true },\n disabled: { type: \"boolean\", default: false },\n },\n composable_with: [\"Select\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<SelectOption value=\"alpha\">ALPHA</SelectOption>',\n },\n};\n\nexport const SelectCompositionRules = {\n Select: {\n allowed_children: [\"SelectOption\"],\n },\n};\n","Select/Select.tsx":"\"use client\";\n\nimport {\n Select as AriaSelect,\n SelectValue,\n Button as AriaButton,\n ListBox,\n ListBoxItem,\n type Key,\n} from \"react-aria-components\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type ReactNode } from \"react\";\nimport clsx from \"clsx\";\nimport { joinIds, useFieldWiring, usePartIds } from \"../_internal/fieldIds\";\nimport { Check, ChevronDown } from \"@oprt/icons\";\nimport { SelectManifest } from \"./Select.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly, renderInvalidButton, useAriaInvalidRef } from \"../_internal/aria\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport { MotionContext, useMotion, type MotionMode } from \"../_internal/motion\";\nimport { MotionPopover } from \"../_internal/MotionPopover\";\nimport { MENU_ITEM_TEXT, SCREEN_MARGIN } from \"../_internal/overlay\";\nimport {\n FORCED_FOCUS_OUTLINE,\n FORCED_NO_RESTING_OUTLINE,\n LISTBOX_EMPTY,\n LISTBOX_ROW,\n LISTBOX_SURFACE,\n} from \"../_internal/controlStyles\";\n\nconst triggerStyles = cva(\n // A value or placeholder that does not fit wraps and the trigger grows taller, as Button does\n // (the DC-8 ruling): the height is a minimum, and the vertical padding is what is left of it\n // around one line and the 2 px of border, so a one-line trigger keeps exactly its token height.\n \"group/select inline-flex max-w-full items-center justify-between gap-1u border bg-oprt-surface-2 transition-colors duration-oprt-fast data-[focus-visible]:shadow-oprt-focus disabled:cursor-not-allowed disabled:bg-oprt-surface-3 disabled:border-oprt-border-control disabled:text-oprt-text-dim font-sans font-medium normal-case text-oprt-text outline-hidden\",\n {\n variants: {\n invalid: {\n true: \"border-oprt-error data-[focus-visible]:border-oprt-error\",\n // The control outline (design call DC-3); focus in the focus token, as the siblings do.\n false: \"border-oprt-border-control data-[focus-visible]:border-oprt-border-focus\",\n },\n size: {\n sm: \"min-h-oprt-sm px-2u py-[calc((var(--oprt-height-sm)-2px-var(--oprt-line-base))/2)] text-oprt-base\",\n md: \"min-h-oprt-md px-2u py-[calc((var(--oprt-height-md)-2px-var(--oprt-line-md))/2)] text-oprt-md\",\n lg: \"min-h-oprt-lg px-3u py-[calc((var(--oprt-height-lg)-2px-var(--oprt-line-md))/2)] text-oprt-md\",\n },\n },\n defaultVariants: { invalid: false, size: \"md\" },\n },\n);\n\n// The list and its rows are the listbox popup Select shares with ComboBox: the popover width rule\n// (design call DC-7) with the trigger's width as the minimum, and menu-row metrics. RAC state\n// attributes differ from Radix: data-focused (not data-highlighted), data-selected (not\n// data-state=checked).\nconst contentStyles = LISTBOX_SURFACE;\nconst itemStyles = LISTBOX_ROW;\n\nexport type SelectProps = VariantProps<typeof triggerStyles> & {\n motion?: MotionMode;\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n disabled?: boolean;\n required?: boolean;\n name?: string;\n placeholder?: string;\n /** What the list says when there are no options to choose from. */\n emptyMessage?: string;\n className?: string;\n children: ReactNode;\n \"aria-label\"?: string;\n /** Names the Select by another element (a visible heading); wins over a Field's label. */\n \"aria-labelledby\"?: string;\n /** Describes the Select by other elements; replaces a Field's help or error. */\n \"aria-describedby\"?: string;\n id?: string;\n};\n\nexport const Select = forwardRef<HTMLButtonElement, SelectProps>(\n (\n {\n motion = \"auto\",\n placeholder,\n emptyMessage = \"No options available.\",\n className,\n size,\n invalid,\n children,\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n value,\n defaultValue,\n onValueChange,\n open,\n defaultOpen,\n onOpenChange,\n disabled,\n required,\n name,\n },\n ref,\n ) => {\n const motionState = useMotion(motion);\n // The shared Field rules (Stage 1 Field-R2): its own id, names and description win over the\n // Field's; a required Field makes it required (coordinator ruling on DC-B04-1).\n const {\n id: finalId,\n labelledBy,\n describedBy,\n invalid: isInvalid,\n required: isRequired,\n field,\n } = useFieldWiring(\n {\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n invalid,\n required,\n },\n { label: \"labelledby\" },\n );\n // The listbox mounts on open and names itself by the trigger or the Field\n // label, so the trigger must carry ids React still agrees with.\n const hydration = useHydrationRoot(finalId, labelledBy);\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Select\", SelectManifest.Select!.props, { size });\n }\n\n // exactOptionalPropertyTypes forbids passing an explicit `undefined` for an\n // optional prop, so build the props object conditionally.\n const rootProps: Record<string, unknown> = {\n isDisabled: disabled ?? false,\n isRequired,\n isInvalid,\n placeholder,\n // An empty Select opens a list that says so (Select-R3), as an empty menu does (DC-6).\n allowsEmptyCollection: true,\n ...definedOnly({\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": labelledBy,\n }),\n };\n if (value !== undefined) rootProps[\"selectedKey\"] = value;\n if (defaultValue !== undefined) rootProps[\"defaultSelectedKey\"] = defaultValue;\n if (onValueChange) rootProps[\"onSelectionChange\"] = (k: Key) => onValueChange(String(k));\n if (open !== undefined) rootProps[\"isOpen\"] = open;\n if (defaultOpen !== undefined) rootProps[\"defaultOpen\"] = defaultOpen;\n if (onOpenChange) rootProps[\"onOpenChange\"] = onOpenChange;\n if (name !== undefined) rootProps[\"name\"] = name;\n\n // The trigger is a button, which takes no aria-required, and the native select React Aria keeps\n // for forms is hidden from assistive technology, so a required Select says so in its\n // description, by a word only they read (review B7 I2, Select-B7-1). The word comes first, as\n // aria-required would be heard before the help or the error; a caller's own description keeps it.\n const requiredId = usePartIds([\"required\"], { id: finalId }).required;\n const triggerProps = definedOnly({\n id: finalId,\n \"aria-label\": ariaLabel,\n \"aria-describedby\": joinIds(isRequired && requiredId, describedBy),\n });\n // RAC strips aria-invalid from its own components. The trigger renders it itself, so it is in\n // the server markup (renderInvalidButton), and the ref also sets it on the DOM node, which keeps\n // it right on a page whose markup was rendered before the field turned invalid.\n const triggerRef = useAriaInvalidRef<HTMLButtonElement>(ref, isInvalid);\n\n return (\n <MotionContext.Provider value={motionState.mode}>\n <AriaSelect\n key={hydration.key}\n {...hydration.probeProps}\n {...rootProps}\n className=\"oprt-motion\"\n render={(domProps) => <div {...domProps} {...motionState.props} />}\n >\n {isRequired && (\n <span id={requiredId} className=\"sr-only\">\n Required\n </span>\n )}\n <AriaButton\n ref={triggerRef}\n render={renderInvalidButton(isInvalid)}\n {...triggerProps}\n // Forced colors paint the trigger's always-on transparent outline and drop its focus\n // shadow, so focus showed no change there: no outline at rest, a CanvasText outline on\n // keyboard focus (Select-R1).\n className={clsx(\n triggerStyles({ size, invalid: isInvalid }),\n FORCED_NO_RESTING_OUTLINE,\n FORCED_FOCUS_OUTLINE,\n // Inside a Field the trigger fills its width like the other text controls; outside,\n // it keeps its content width (ruling on DC-B04-4, Select-R5). `:where` gives the rule\n // no specificity, so a width class on the Select still wins.\n field && \"[:where(&)]:w-full\",\n className,\n )}\n >\n {/* The value shrinks and breaks anywhere, so an identifier never widens the page. */}\n <SelectValue className=\"min-w-0 oprt-wrap-anywhere text-left data-[placeholder]:text-oprt-text-placeholder group-disabled/select:data-[placeholder]:text-oprt-text-dim\" />\n {/* The arrow takes the trigger's color: full text while it can be used, so an empty\n Select does not read as disabled, and the disabled gray once it cannot. */}\n <ChevronDown\n size={12}\n className=\"oprt-motion-popover-chevron shrink-0\"\n aria-hidden=\"true\"\n />\n </AriaButton>\n <MotionPopover\n motionState={motionState}\n className={contentStyles}\n offset={2}\n containerPadding={SCREEN_MARGIN}\n >\n <ListBox\n className=\"outline-hidden\"\n renderEmptyState={() => <p className={LISTBOX_EMPTY}>{emptyMessage}</p>}\n >\n {children}\n </ListBox>\n </MotionPopover>\n </AriaSelect>\n </MotionContext.Provider>\n );\n },\n);\nSelect.displayName = \"Select\";\n\nexport type SelectOptionProps = {\n value: string;\n disabled?: boolean;\n className?: string;\n children: ReactNode;\n};\n\nexport const SelectOption = forwardRef<HTMLDivElement, SelectOptionProps>(\n ({ className, children, value, disabled, ...props }, ref) => {\n // RAC keys collection items by `id`; the public prop stays `value` so the\n // component API is unchanged for consumers.\n const itemProps: Record<string, unknown> = {};\n if (typeof children === \"string\") itemProps[\"textValue\"] = children;\n\n return (\n <ListBoxItem\n ref={ref}\n id={value}\n isDisabled={disabled ?? false}\n onKeyDown={(event) => {\n // WebKit can retarget Enter's native click to the trigger after RAC\n // selects this option and restores focus, reopening the listbox.\n // Keep RAC selection/navigation and cancel only that plain-Enter default.\n if (\n event.key === \"Enter\" &&\n !event.altKey &&\n !event.ctrlKey &&\n !event.metaKey &&\n !event.shiftKey\n ) {\n event.preventDefault();\n }\n event.continuePropagation();\n }}\n className={clsx(itemStyles, className)}\n {...itemProps}\n {...props}\n >\n {({ isSelected }) => (\n <>\n <span className={MENU_ITEM_TEXT}>{children}</span>\n {isSelected && <Check size={12} className=\"shrink-0\" />}\n </>\n )}\n </ListBoxItem>\n );\n },\n);\nSelectOption.displayName = \"SelectOption\";\n","Sidebar/index.ts":"export {\n Sidebar,\n SidebarLogo,\n SidebarSection,\n SidebarItem,\n SidebarFooter,\n type SidebarProps,\n type SidebarLogoProps,\n type SidebarSectionProps,\n type SidebarItemProps,\n type SidebarFooterProps,\n} from \"./Sidebar\";\nexport { SidebarManifest, SidebarCompositionRules } from \"./Sidebar.manifest\";\n","Sidebar/Sidebar.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const SidebarManifest: Record<string, Component> = {\n Sidebar: {\n description:\n \"Vertical navigation rail, never wider than its container. Use with SidebarLogo, SidebarSection, and SidebarFooter. For the frame of a whole app (the workspace's name, the data connection, the time), pass the Sidebar to AppShell as its navigation instead of laying the frame out by hand. Standing alone it is a navigation landmark named Sidebar; inside AppShell's navigation it is a plain block, and the shell's landmark names the navigation. In a host of bounded height the sections scroll in place between the logo and the footer, which stay put.\",\n props: {},\n slots: {\n children: { type: \"SidebarLogo | SidebarSection | SidebarFooter\", required: true },\n },\n composable_with: [\"SidebarLogo\", \"SidebarSection\", \"SidebarFooter\", \"AppShell\"],\n never_inside: [\"Sidebar\", \"Card\"],\n example:\n '<Sidebar>\\n <SidebarLogo>OPERATOR</SidebarLogo>\\n <SidebarSection label=\"NAVIGATION\">\\n <SidebarItem href=\"/overview\" active>OVERVIEW</SidebarItem>\\n <SidebarItem href=\"/metrics\">METRICS</SidebarItem>\\n </SidebarSection>\\n <SidebarFooter>BUILD 1.0.0</SidebarFooter>\\n</Sidebar>',\n },\n SidebarLogo: {\n description: \"Sidebar brand/header slot — uppercase wordmark row at the top of the rail.\",\n props: {},\n slots: {\n children: { type: \"text\", required: true },\n },\n composable_with: [\"Sidebar\"],\n never_inside: [\"SidebarSection\", \"SidebarFooter\"],\n example: \"<SidebarLogo>OPERATOR</SidebarLogo>\",\n },\n SidebarSection: {\n description:\n \"Grouped block of SidebarItems with an optional uppercase section label; a labeled section is a group named by its label.\",\n props: {\n label: { type: \"string\" },\n },\n slots: {\n children: { type: \"SidebarItem\", required: true },\n },\n composable_with: [\"Sidebar\"],\n never_inside: [\"SidebarLogo\", \"SidebarFooter\"],\n example:\n '<SidebarSection label=\"NAVIGATION\">\\n <SidebarItem href=\"/overview\">OVERVIEW</SidebarItem>\\n</SidebarSection>',\n },\n SidebarItem: {\n description:\n \"Uppercase navigation entry. Renders as an anchor when href is set, otherwise a button; marks the current page when active. Wrap case-sensitive names or IDs in a child with normal-case tracking-normal.\",\n props: {\n active: { type: \"boolean\", default: false },\n href: { type: \"string\" },\n asChild: { type: \"boolean\", default: false },\n },\n slots: {\n icon: { type: \"ReactNode\", required: false },\n children: { type: \"text\", required: true },\n },\n composable_with: [\"SidebarSection\"],\n never_inside: [\"SidebarLogo\", \"SidebarFooter\"],\n example: '<SidebarItem href=\"/overview\" active>OVERVIEW</SidebarItem>',\n },\n SidebarFooter: {\n description: \"Bottom-pinned sidebar slot — version string, account, or status controls.\",\n props: {},\n slots: {\n children: { type: \"any\", required: false },\n },\n composable_with: [\"Sidebar\"],\n never_inside: [\"SidebarLogo\", \"SidebarSection\"],\n example: \"<SidebarFooter>BUILD 1.0.0</SidebarFooter>\",\n },\n};\n\nexport const SidebarCompositionRules = {\n Sidebar: {\n allowed_children: [\"SidebarLogo\", \"SidebarSection\", \"SidebarFooter\"],\n children: {\n SidebarSection: { allowed_children: [\"SidebarItem\"] },\n },\n },\n};\n","Sidebar/Sidebar.tsx":"\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport {\n Children,\n forwardRef,\n isValidElement,\n useId,\n type AnchorHTMLAttributes,\n type HTMLAttributes,\n type ReactNode,\n type Ref,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { useInsideNavigationLandmark } from \"../_internal/navigationLandmark\";\n\nexport type SidebarProps = HTMLAttributes<HTMLElement>;\n\n/**\n * The rail: never wider than its container. In a host of bounded height its item list scrolls in\n * place while the logo and the footer stay put (design call DC-15): the sections between a leading\n * SidebarLogo and a trailing SidebarFooter go in one list that takes the height left and scrolls.\n * The items are links and buttons, so a keyboard reaches the list and it scrolls to the focused one.\n * For the frame of a whole app, pass the rail to AppShell as its `navigation`.\n *\n * Standing alone the rail is a navigation landmark named \"Sidebar\". Inside AppShell's `navigation`\n * the shell's landmark, named for the workspace, already holds it, so the rail is a plain block\n * and the navigation is announced once.\n */\nexport const Sidebar = forwardRef<HTMLElement, SidebarProps>(\n ({ className, children, ...props }, ref) => {\n const insideLandmark = useInsideNavigationLandmark();\n const Root = insideLandmark ? \"div\" : \"nav\";\n const parts = Children.toArray(children);\n let start = 0;\n while (start < parts.length && isPart(parts[start], SidebarLogo)) start += 1;\n let end = parts.length;\n while (end > start && isPart(parts[end - 1], SidebarFooter)) end -= 1;\n const list = parts.slice(start, end);\n return (\n <Root\n // The rail forwards a plain HTMLElement ref, which either element satisfies; the narrower\n // type is only what `div` asks for.\n ref={ref as Ref<HTMLDivElement>}\n aria-label={insideLandmark ? undefined : \"Sidebar\"}\n className={clsx(\n \"flex flex-col w-60 max-w-full h-full bg-oprt-surface-1 border-r border-oprt-border font-sans\",\n className,\n )}\n {...props}\n >\n {parts.slice(0, start)}\n {list.length > 0 && (\n // 1 px of inline padding keeps the items' focus rings inside the scrolling box.\n <div\n data-oprt-sidebar-list=\"\"\n className=\"flex min-h-0 flex-1 flex-col overflow-y-auto px-px\"\n >\n {list}\n </div>\n )}\n {parts.slice(end)}\n </Root>\n );\n },\n);\nSidebar.displayName = \"Sidebar\";\n\nfunction isPart(node: ReactNode, part: unknown): boolean {\n return isValidElement(node) && node.type === part;\n}\n\nexport type SidebarLogoProps = HTMLAttributes<HTMLDivElement>;\n\nexport const SidebarLogo = forwardRef<HTMLDivElement, SidebarLogoProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n className={clsx(\n // A long name wraps and the row grows (the DC-8 rule): the height is a minimum, and the\n // padding is what is left of it around one line and the 1 px border, so a one-line logo is\n // exactly 40 px.\n \"flex shrink-0 items-center px-3u min-h-oprt-lg py-[calc((var(--oprt-height-lg)-1px-var(--oprt-line-lg))/2)] border-b border-oprt-border text-oprt-lg uppercase tracking-oprt-lg text-oprt-text oprt-wrap-anywhere [&_code]:normal-case [&_code]:tracking-normal [&_kbd]:normal-case [&_kbd]:tracking-normal\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarLogo.displayName = \"SidebarLogo\";\n\nexport type SidebarSectionProps = HTMLAttributes<HTMLDivElement> & {\n label?: string;\n};\n\n/** A labeled section is a group named by its label, so its items are announced with it. */\nexport const SidebarSection = forwardRef<HTMLDivElement, SidebarSectionProps>(\n ({ className, label, children, ...props }, ref) => {\n const labelId = useId();\n return (\n <div\n ref={ref}\n className={clsx(\"flex flex-col py-1u\", className)}\n {...(label ? { role: \"group\", \"aria-labelledby\": labelId } : {})}\n {...props}\n >\n {label && (\n <div\n id={labelId}\n className=\"m-0 px-3u py-1u text-oprt-sm uppercase tracking-oprt-sm text-oprt-text-dim oprt-wrap-anywhere\"\n >\n {label}\n </div>\n )}\n {children}\n </div>\n );\n },\n);\nSidebarSection.displayName = \"SidebarSection\";\n\nexport type SidebarItemProps = Omit<AnchorHTMLAttributes<HTMLAnchorElement>, \"href\"> & {\n icon?: ReactNode;\n active?: boolean;\n href?: string;\n asChild?: boolean;\n children: ReactNode;\n};\n\nexport const SidebarItem = forwardRef<HTMLAnchorElement & HTMLButtonElement, SidebarItemProps>(\n ({ className, icon, active = false, href, asChild = false, children, ...props }, ref) => {\n const Comp = asChild ? Slot : href ? \"a\" : \"button\";\n\n const sharedProps: Record<string, unknown> = {\n ref,\n className: clsx(\n // A long label wraps, a token breaks anywhere, and the row grows (the DC-8 rule): the\n // height is a minimum and the padding is what is left of it around one line and the 2 px\n // of border, so a one-line item is exactly 32 px.\n \"flex items-center gap-2u px-3u min-h-oprt-md py-[calc((var(--oprt-height-md)-2px-var(--oprt-line-base))/2)] text-left text-oprt-base font-medium uppercase tracking-oprt-caps oprt-wrap-anywhere [&_code]:normal-case [&_code]:tracking-normal [&_kbd]:normal-case [&_kbd]:tracking-normal text-oprt-text-dim hover:text-oprt-text hover:bg-oprt-surface-2 border border-transparent focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-hidden\",\n active && \"text-oprt-accent bg-oprt-accent-ghost\",\n className,\n ),\n ...props,\n };\n if (active) sharedProps[\"aria-current\"] = \"page\";\n if (!asChild && href) sharedProps[\"href\"] = href;\n if (!asChild && !href) sharedProps[\"type\"] = \"button\";\n\n // When asChild, Slot requires a single React element child, so forward the\n // consumer's element untouched (it owns its own icon/label markup).\n if (asChild) {\n return <Comp {...sharedProps}>{children}</Comp>;\n }\n\n return (\n <Comp {...sharedProps}>\n {icon && (\n <span aria-hidden=\"true\" className=\"inline-flex shrink-0 items-center\">\n {icon}\n </span>\n )}\n {children}\n </Comp>\n );\n },\n);\nSidebarItem.displayName = \"SidebarItem\";\n\nexport type SidebarFooterProps = HTMLAttributes<HTMLDivElement>;\n\nexport const SidebarFooter = forwardRef<HTMLDivElement, SidebarFooterProps>(\n ({ className, ...props }, ref) => (\n <div\n ref={ref}\n // break-words, as CardBody: the footer holds any content, and a token wraps instead of\n // widening the rail.\n className={clsx(\n \"mt-auto shrink-0 min-w-0 break-words px-3u py-2u border-t border-oprt-border\",\n className,\n )}\n {...props}\n />\n ),\n);\nSidebarFooter.displayName = \"SidebarFooter\";\n","Skeleton/index.ts":"export { Skeleton, type SkeletonProps } from \"./Skeleton\";\nexport { SkeletonManifest } from \"./Skeleton.manifest\";\n","Skeleton/Skeleton.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const SkeletonManifest: Record<string, Component> = {\n Skeleton: {\n description:\n \"Loading placeholder. block renders one pulsing bar; text stacks `lines` bars with a shortened last line. Bars fill their container and never widen it, whatever width the caller asks for, and are drawn in a system color in forced-colors mode.\",\n props: {\n variant: { type: \"enum\", values: [\"block\", \"text\"], default: \"block\" },\n width: {\n type: \"union\",\n types: [\"string\", \"number\"],\n description:\n \"Caller dimension applied via style on the block bar (string or number). It is clamped to the container: it can ask for less, never more.\",\n },\n height: {\n type: \"union\",\n types: [\"string\", \"number\"],\n description: \"Caller dimension applied via style on the block bar (string or number).\",\n },\n lines: {\n type: \"number\",\n default: 3,\n description:\n \"Number of stacked bars in the text variant, rounded down with a minimum of 1. Non-finite values fall back to 3.\",\n },\n },\n composable_with: [\"Card\", \"DataTable\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<Skeleton variant=\"text\" lines={3} />',\n },\n};\n","Skeleton/Skeleton.tsx":"\"use client\";\n\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, type CSSProperties, type HTMLAttributes } from \"react\";\nimport clsx from \"clsx\";\nimport { SkeletonManifest } from \"./Skeleton.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\n// Forced colors replace backgrounds with the page color, so the bars are redrawn from a system\n// color there and kept from being adjusted. A caller width is clamped to the container (max-w-full):\n// it can ask for less than the container but never widen it.\nconst barStyles = cva(\n \"bg-oprt-surface-2 animate-pulse motion-reduce:animate-none forced-colors:bg-[color:GrayText] forced-colors:[forced-color-adjust:none]\",\n {\n variants: {\n variant: {\n block: \"w-full max-w-full h-oprt-md\",\n text: \"h-2u w-full\",\n },\n },\n defaultVariants: { variant: \"block\" },\n },\n);\n\nexport type SkeletonProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> &\n VariantProps<typeof barStyles> & {\n width?: string | number;\n height?: string | number;\n lines?: number;\n };\n\nexport const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>((allProps, ref) => {\n const { className, variant = \"block\", width, height, lines = 3, ...props } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Skeleton\", SkeletonManifest.Skeleton!.props, { ...allProps });\n }\n\n if (variant === \"text\") {\n const count = Number.isFinite(lines) ? Math.max(1, Math.floor(lines)) : 3;\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading\"\n className={clsx(\"flex flex-col gap-1u\", className)}\n {...props}\n >\n {Array.from({ length: count }, (_, i) => (\n <div\n key={i}\n aria-hidden=\"true\"\n className={clsx(barStyles({ variant: \"text\" }), i === count - 1 && \"w-2/3\")}\n />\n ))}\n </div>\n );\n }\n\n const style: CSSProperties = {};\n if (width !== undefined) style.width = width;\n if (height !== undefined) style.height = height;\n const hasStyle = width !== undefined || height !== undefined;\n\n return (\n <div\n ref={ref}\n role=\"status\"\n aria-busy=\"true\"\n aria-label=\"Loading\"\n className={clsx(\"flex flex-col\", className)}\n {...props}\n >\n <div\n aria-hidden=\"true\"\n className={barStyles({ variant: \"block\" })}\n {...(hasStyle ? { style } : {})}\n />\n </div>\n );\n});\nSkeleton.displayName = \"Skeleton\";\n","Slider/index.ts":"export { Slider, type SliderProps } from \"./Slider\";\nexport { SliderManifest } from \"./Slider.manifest\";\n","Slider/Slider.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const SliderManifest: Record<string, Component> = {\n Slider: {\n description:\n \"Single-thumb or range slider. Pass value: number for single, [number, number] for range. Track in the control outline color; active range in accent; square thumbs that stay inside the slider at both ends. Inside a Field it is named by the label (range thumbs add lower and upper) and a Field error marks it invalid, drawing its thumbs in the error color.\",\n props: {\n value: {\n type: \"union\",\n types: [\"number\", \"array\"],\n description: \"Single thumb when number; pass [number, number] tuple for range mode.\",\n },\n defaultValue: {\n type: \"union\",\n types: [\"number\", \"array\"],\n description: \"Pass [number, number] tuple for range mode.\",\n },\n onValueChange: {\n type: \"function\",\n description:\n \"(value: number | [number, number]) => void. Called as the value changes; a tuple in range mode.\",\n },\n min: {\n type: \"number\",\n default: 0,\n description: \"Finite lower bound. Non-finite values fall back to 0.\",\n },\n max: {\n type: \"number\",\n default: 100,\n description: \"Finite upper bound greater than min. Invalid values fall back to min + 100.\",\n },\n step: {\n type: \"number\",\n default: 1,\n description: \"Finite positive increment. Invalid values fall back to 1.\",\n },\n disabled: { type: \"boolean\", default: false },\n orientation: { type: \"enum\", values: [\"horizontal\", \"vertical\"], default: \"horizontal\" },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n showValue: {\n type: \"boolean\",\n default: false,\n description:\n \"Render the current value(s) in mono next to the track; the readout moves under the track when the row cannot hold both.\",\n },\n formatValue: {\n type: \"function\",\n description:\n \"(value: number) => string. Formats displayed values and each thumb's aria-valuetext. Applies separately to both values in range mode, including when showValue is false.\",\n },\n name: { type: \"string\" },\n },\n composable_with: [\"Field\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<Slider aria-label=\"Acceptable temperature\" defaultValue={[18, 24]} min={-20} max={50} showValue />',\n },\n};\n","Slider/Slider.tsx":"\"use client\";\n\nimport {\n Slider as AriaSlider,\n SliderTrack,\n SliderThumb,\n SliderFill,\n SliderStateContext,\n type SliderThumbProps,\n} from \"react-aria-components\";\nimport { mergeProps } from \"react-aria\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { Fragment, forwardRef, useContext, useRef, type HTMLAttributes } from \"react\";\nimport clsx from \"clsx\";\nimport { SliderManifest } from \"./Slider.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly } from \"../_internal/aria\";\nimport { joinIds, useFieldWiring, usePartIds } from \"../_internal/fieldIds\";\nimport { keyForIds } from \"../_internal/hydration\";\nimport { NumberText } from \"../_internal/numberText\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\nimport {\n FORCED_SLIDER_FILL,\n FORCED_SLIDER_THUMB,\n FORCED_SLIDER_TRACK,\n} from \"../_internal/controlStyles\";\n\n// The track and the value readout share a row; when the row cannot hold both, the readout wraps\n// under the track (design call DC-8, B2 candidate 2), so neither is squeezed to nothing.\n//\n// Vertical, the slider grows to its wrapper's height (see the wrapper below) rather than taking\n// `h-full` of it: an attribute rule would outrank a consumer's height class, and `h-full` of a\n// wrapper without a height left the track 0px tall (Slider-R2). It is at least as wide as the 24px\n// thumbs, which sit on the track, so they stay inside it.\nconst rootStyles = cva(\n \"relative flex items-center gap-x-2u gap-y-1u min-h-3u select-none touch-none w-full data-[orientation=horizontal]:flex-wrap data-[orientation=vertical]:flex-col data-[orientation=vertical]:flex-grow data-[orientation=vertical]:w-auto data-[orientation=vertical]:min-w-3u data-[disabled]:opacity-50\",\n {\n variants: {\n size: { sm: \"\", md: \"\", lg: \"\" },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n// The track scales with the thumb. Previously a fixed 1u (8px) at every size,\n// which read as a bar rather than a rail and left sm/lg out of proportion.\n//\n// A thumb is centered on its value, so the track is inset by half the 24px target at each end: a\n// thumb at either end then stays inside the slider instead of reaching half a target past it,\n// where a clipping card or cell cut it below the target size (Slider-W0-1). The track keeps a\n// minimum length, so a long readout or a shrink-wrapped host cannot take it to 0px. The unfilled\n// track is the control outline color (design call DC-3, Slider-R6), so the scale's ends show.\nconst trackStyles = cva(\n \"relative flex-grow bg-oprt-border-control data-[orientation=horizontal]:mx-1.5u data-[orientation=horizontal]:min-w-16u data-[orientation=vertical]:my-1.5u\",\n {\n variants: {\n size: {\n sm: \"data-[orientation=horizontal]:h-oprt-track-sm data-[orientation=vertical]:w-oprt-track-sm\",\n md: \"data-[orientation=horizontal]:h-oprt-track-md data-[orientation=vertical]:w-oprt-track-md\",\n lg: \"data-[orientation=horizontal]:h-oprt-track-lg data-[orientation=vertical]:w-oprt-track-lg\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\nconst rangeStyles =\n \"absolute bg-oprt-accent data-[orientation=horizontal]:h-full data-[orientation=vertical]:w-full\";\n\n// The focusable element is the visually hidden <input type=\"range\"> inside the\n// thumb, so the thumb element itself is never :focus-visible. RAC surfaces the\n// input's keyboard focus on the 24px thumb target as data-focus-visible instead.\n// Its named group styles the smaller visual thumb without shrinking the target. The visual thumb is\n// never focused itself, so it carries no outline at rest, which forced colors would paint.\n// An invalid slider draws its thumbs in the error color (coordinator ruling on DC-B04-5); the\n// track is not reddened, so the scale is not read as a danger zone.\nconst thumbStyles = cva(\n \"block bg-oprt-surface-1 border border-oprt-accent group-data-[invalid]/slider:border-oprt-error group-data-[focus-visible]/slider:border-oprt-border-focus group-data-[focus-visible]/slider:shadow-oprt-focus cursor-pointer group-data-[disabled]/slider:cursor-not-allowed\",\n {\n variants: {\n size: {\n sm: \"h-1.5u w-1.5u\",\n md: \"h-2u w-2u\",\n lg: \"h-2.5u w-2.5u\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\n// Spaced from the track by the row's gap; a range breaks at the dash between its values, and a value\n// breaks only where NumberText lets a number break.\nconst valueStyles = \"max-w-full font-mono text-oprt-base text-oprt-text tabular-nums\";\n\ntype Size = NonNullable<VariantProps<typeof thumbStyles>[\"size\"]>;\n\ntype SliderBase = Omit<HTMLAttributes<HTMLSpanElement>, \"defaultValue\" | \"onChange\"> & {\n min?: number;\n max?: number;\n step?: number;\n disabled?: boolean;\n orientation?: \"horizontal\" | \"vertical\";\n size?: Size;\n showValue?: boolean;\n formatValue?: (value: number) => string;\n name?: string;\n};\n\ntype SingleSlider = SliderBase & {\n value?: number;\n defaultValue?: number;\n onValueChange?: (value: number) => void;\n};\n\ntype RangeSlider = SliderBase & {\n value?: [number, number];\n defaultValue?: [number, number];\n onValueChange?: (value: [number, number]) => void;\n};\n\nexport type SliderProps = SingleSlider | RangeSlider;\n\nfunction isRange(value: number | [number, number] | undefined): value is [number, number] {\n return Array.isArray(value);\n}\n\nfunction valueToArray(\n v: number | [number, number] | undefined,\n min: number,\n max: number,\n): number[] | undefined {\n if (v === undefined) return undefined;\n const values = Array.isArray(v) ? [...v] : [v];\n return values\n .map((value) => (Number.isFinite(value) ? Math.min(max, Math.max(min, value)) : min))\n .sort((a, b) => a - b);\n}\n\nfunction ValueThumb({\n formatValue,\n nameIds,\n index = 0,\n ...props\n}: SliderThumbProps & {\n formatValue?: (value: number) => string;\n /** The ids that name this thumb: its label and, in a range, \"(lower)\" or \"(upper)\". */\n nameIds?: string;\n}) {\n const inputRef = useRef<HTMLInputElement>(null);\n const state = useContext(SliderStateContext)!;\n // RAC 1.20 supplies numeric aria-valuetext and does not expose a custom\n // formatter. Preserve its thumb behavior and state identity, and use its\n // public inputRef to keep the actual range input's spoken value in sync.\n // Run after each commit, including when a formatter is removed.\n //\n // RAC also names each thumb by the slider's group as well as its label, and the group's name,\n // taken from its content, holds the values (\"18 ACCEPTABLE TEMP RANGE\"). A thumb is named by its\n // own label, and in a range by which end it is, never by the values (Slider-R3): its label ids\n // when it has them, else only its aria-label.\n useIsomorphicLayoutEffect(() => {\n const input = inputRef.current;\n if (!input) return;\n const valueText = formatValue\n ? formatValue(state.values[index] ?? state.getThumbMinValue(index))\n : state.getThumbValueLabel(index);\n input.setAttribute(\"aria-valuetext\", valueText);\n if (nameIds) input.setAttribute(\"aria-labelledby\", nameIds);\n else input.removeAttribute(\"aria-labelledby\");\n });\n return <SliderThumb {...props} index={index} inputRef={inputRef} />;\n}\n\n/** Which end of a range a thumb is, for its name; a single thumb has none. */\nconst ENDS = [\"lower\", \"upper\"] as const;\n\nexport const Slider = forwardRef<HTMLSpanElement, SliderProps>((props, ref) => {\n const {\n className,\n style,\n min = 0,\n max = 100,\n step = 1,\n disabled,\n orientation = \"horizontal\",\n size = \"md\",\n showValue,\n formatValue,\n name,\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n ...rest\n } = props;\n const value = (rest as { value?: number | [number, number] }).value;\n const defaultValue = (rest as { defaultValue?: number | [number, number] }).defaultValue;\n const onValueChange = (rest as { onValueChange?: (v: number | [number, number]) => void })\n .onValueChange;\n // Every other attribute the type accepts reaches the slider's root (Slider-R5).\n const domProps: Record<string, unknown> = { ...rest };\n delete domProps[\"value\"];\n delete domProps[\"defaultValue\"];\n delete domProps[\"onValueChange\"];\n // A disabled slider is dimmed by opacity, which leaves its readout under the contrast an enabled\n // text needs. The group says it is disabled, so a checker, like assistive technology, treats the\n // dimmed text as the inactive control's, which has no contrast requirement; the look is unchanged.\n if (disabled) domProps[\"aria-disabled\"] = true;\n\n const isRangeMode = isRange(value) || isRange(defaultValue);\n const safeMin = Number.isFinite(min) ? min : 0;\n const safeMax = Number.isFinite(max) && max > safeMin ? max : safeMin + 100;\n const safeStep = Number.isFinite(step) && step > 0 ? step : 1;\n\n // The shared Field rules (Stage 1 Field-R2). RAC's Slider root is a group, which a Field's\n // `<label htmlFor>` cannot name, so it takes the label's id as aria-labelledby; its own\n // aria-labelledby wins (Slider-R5), and a Field error or its own aria-invalid marks it invalid\n // (Slider-R4), which RAC's Slider has no prop for.\n const {\n id: finalId,\n labelledBy,\n describedBy,\n invalid,\n } = useFieldWiring(\n {\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n },\n { label: \"labelledby\" },\n );\n // The \"(lower)\" and \"(upper)\" that name a range's thumbs next to their label, by ids that match\n // the page after a hydration whose ids disagree with the server's.\n const wrapper = useRef<HTMLDivElement>(null);\n const ends = usePartIds(ENDS, {\n readRendered: () => wrapper.current?.getAttribute(\"data-oprt-ids\"),\n });\n\n // RAC always reports an array here because we always hand it an array value.\n const handleChange = (next: number | number[]) => {\n if (!onValueChange) return;\n const arr = Array.isArray(next) ? next : [next];\n if (isRangeMode) {\n const tuple: [number, number] = [arr[0] ?? 0, arr[1] ?? 0];\n (onValueChange as (v: [number, number]) => void)(tuple);\n } else {\n (onValueChange as (v: number) => void)(arr[0] ?? 0);\n }\n };\n\n const ariaValue = valueToArray(value, safeMin, safeMax);\n const ariaDefaultValue =\n valueToArray(defaultValue, safeMin, safeMax) ?? (isRangeMode ? [safeMin, safeMax] : [safeMin]);\n const thumbValues = ariaValue ?? ariaDefaultValue;\n\n const thumbCount = thumbValues.length;\n const endOf = (i: number) => (thumbCount === 1 ? undefined : ENDS[i === 0 ? 0 : 1]);\n // Named by ids (its own aria-labelledby or the Field's label): the thumbs take them and, in a\n // range, \"(lower)\" or \"(upper)\". Named only by an aria-label: the thumbs take it, with the end.\n const thumbLabel = (i: number): string | undefined => {\n if (labelledBy || !ariaLabel) return undefined;\n const end = endOf(i);\n return end ? `${ariaLabel} (${end})` : ariaLabel;\n };\n const thumbNameIds = (i: number): string | undefined => {\n const end = endOf(i);\n return labelledBy ? joinIds(labelledBy, end && ends[end]) : undefined;\n };\n\n // exactOptionalPropertyTypes forbids passing literal `undefined` for an\n // optional prop. Build the root props object conditionally so undefined\n // values are absent rather than explicit.\n //\n // Prop mapping: min -> minValue, max -> maxValue, disabled -> isDisabled,\n // onValueChange -> onChange. `id` is consumed by RAC's useLabel and lands\n // back on the group element, so the Field id still reaches the DOM.\n const rootProps: Record<string, unknown> = {\n ref,\n className: clsx(rootStyles({ size }), className),\n minValue: safeMin,\n maxValue: safeMax,\n step: safeStep,\n orientation,\n onChange: handleChange,\n // The attributes RAC does not pass on itself (title, handlers) reach the root through its\n // render function; RAC's own props win where both set one.\n render: (rendered: HTMLAttributes<HTMLDivElement>) => (\n <div {...mergeProps(domProps as HTMLAttributes<HTMLDivElement>, rendered)} />\n ),\n };\n if (style !== undefined) rootProps[\"style\"] = style;\n if (disabled !== undefined) rootProps[\"isDisabled\"] = disabled;\n if (finalId !== undefined) rootProps[\"id\"] = finalId;\n if (describedBy !== undefined) rootProps[\"aria-describedby\"] = describedBy;\n if (ariaLabel !== undefined) rootProps[\"aria-label\"] = ariaLabel;\n if (labelledBy !== undefined) rootProps[\"aria-labelledby\"] = labelledBy;\n // Drawn by the thumbs, in the error color (coordinator ruling on DC-B04-5).\n if (invalid) rootProps[\"data-invalid\"] = true;\n if (ariaValue !== undefined) {\n rootProps[\"value\"] = ariaValue;\n } else {\n rootProps[\"defaultValue\"] = ariaDefaultValue;\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Slider\", SliderManifest.Slider!.props, { orientation, size });\n }\n\n return (\n // RAC's Slider root is a <div> (Radix's was a <span>), so the wrapper is a\n // <div> too — a <div> inside a <span> would be invalid nesting.\n // A vertical slider takes the height its container gives it, and at least 128px (two 8u\n // blocks), so its track has a length to fill; it sits at the start of a wide container.\n <div\n ref={wrapper}\n data-oprt-ids={ends.base}\n className={\n orientation === \"vertical\"\n ? \"flex h-full min-h-16u flex-col items-start\"\n : \"inline-flex w-full items-center\"\n }\n >\n {labelledBy && thumbCount > 1 && (\n <>\n <span id={ends.lower} hidden>\n (lower)\n </span>\n <span id={ends.upper} hidden>\n (upper)\n </span>\n </>\n )}\n {/* RAC composes the id it mounted with into each thumb's aria-labelledby,\n so remount when the forwarded ids change (see keyForIds). */}\n <AriaSlider key={keyForIds(finalId, labelledBy)} {...rootProps}>\n {({ state }) => (\n <>\n {/* Forced colors would replace the track and its fill, both backgrounds, with the\n page color: they take the shared forced-colors treatment (Slider-R1). */}\n <SliderTrack className={clsx(trackStyles({ size }), FORCED_SLIDER_TRACK)}>\n {/* SliderFill replaces Radix's Range: it spans min -> thumb for a\n single thumb, and lower -> upper thumb in range mode. */}\n <SliderFill className={clsx(rangeStyles, FORCED_SLIDER_FILL)} />\n {thumbValues.map((_, i) => (\n <ValueThumb\n key={i}\n index={i}\n className=\"group/slider flex items-center justify-center h-3u w-3u cursor-pointer data-[disabled]:cursor-not-allowed\"\n isInvalid={invalid}\n data-invalid={invalid || undefined}\n {...definedOnly({\n \"aria-label\": thumbLabel(i),\n // Its label for the server markup, before the thumb's effect names it.\n \"aria-labelledby\": labelledBy,\n nameIds: thumbNameIds(i),\n name,\n formatValue,\n })}\n >\n <span className={clsx(thumbStyles({ size }), FORCED_SLIDER_THUMB)} />\n </ValueThumb>\n ))}\n </SliderTrack>\n {showValue && (\n <span className={valueStyles} aria-hidden=\"true\">\n {/* Each value is read as a number with its unit (R4 and DC-11), so a unit written\n after an ordinary space stays beside its number, and other text still wraps. */}\n {state.values.map((current, index) => (\n <Fragment key={index}>\n {index > 0 && \" – \"}\n <NumberText value={formatValue ? formatValue(current) : current} />\n </Fragment>\n ))}\n </span>\n )}\n </>\n )}\n </AriaSlider>\n </div>\n );\n});\nSlider.displayName = \"Slider\";\n","Sparkline/index.ts":"export { Sparkline, type SparklineProps } from \"./Sparkline\";\nexport { SparklineManifest } from \"./Sparkline.manifest\";\n","Sparkline/Sparkline.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const SparklineManifest: Record<string, Component> = {\n Sparkline: {\n description:\n \"Compact SVG line or area trend with preserved missing-data gaps and a complete accessible sample description.\",\n props: {\n label: {\n type: \"string\",\n required: true,\n description: \"Accessible graphic name. Provide the metric and sampling context.\",\n },\n data: {\n type: \"array\",\n required: true,\n description:\n \"Readonly (number | null)[] in sample order. Null and non-finite values create gaps and are announced as Missing; zero remains zero.\",\n },\n unit: {\n type: \"string\",\n description: \"Unit appended to every finite sample in the accessible description.\",\n },\n seriesId: {\n type: \"string\",\n description:\n \"Optional Chart series ID. Reuses the series' automatic categorical color so a detail trend matches its source plot; omit for the first categorical color. When the series sets its own color, pass the same color too.\",\n },\n color: {\n type: \"number\",\n description:\n \"Optional categorical token 1–8, as a Chart series' color; it takes precedence over seriesId.\",\n },\n variant: { type: \"enum\", values: [\"line\", \"area\"], default: \"line\" },\n },\n composable_with: [\"StatCard\", \"DataTable\", \"DataGrid\", \"Card\"],\n never_inside: [\"Sparkline\", \"Button\"],\n example:\n '<Sparkline label=\"Latency, last seven samples\" data={[22, 24, null, 21, 23, 20, 22]} unit=\"ms\" />',\n },\n};\n","Sparkline/Sparkline.tsx":"\"use client\";\n\nimport { forwardRef, useId, type HTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport {\n extent,\n isFinitePoint,\n linePath,\n scale,\n segments,\n seriesColorClass,\n type CategoryColor,\n} from \"../Chart/geometry\";\nimport { SparklineManifest } from \"./Sparkline.manifest\";\n\nconst sparklineStyles = cva(\"inline-block w-16u max-w-full shrink-0 align-middle font-sans\", {\n variants: { variant: { line: \"\", area: \"\" } },\n defaultVariants: { variant: \"line\" },\n});\n\n// The HTML `color` attribute (a no-op on a div) gives way to the categorical token.\nexport type SparklineProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\" | \"color\"> & {\n label: string;\n data: readonly (number | null)[];\n unit?: string;\n /** Match the stable categorical identity of a Chart series with this ID. */\n seriesId?: string;\n /** The categorical token 1 to 8, as a Chart series' `color`; it wins over `seriesId`. */\n color?: CategoryColor;\n variant?: \"line\" | \"area\";\n};\n\n/** A compact trend; sample order is the x axis and missing values remain gaps. */\nexport const Sparkline = forwardRef<HTMLDivElement, SparklineProps>((allProps, ref) => {\n const { label, data, unit, seriesId, color, variant = \"line\", className, ...props } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Sparkline\", SparklineManifest.Sparkline!.props, { ...allProps });\n }\n const id = useId();\n const points = data.map((y, x) => ({ x, y }));\n const finite = points.filter(isFinitePoint);\n const x = scale(extent(points.map((point) => point.x)), 4, 124);\n const y = scale(\n extent(\n finite.map((point) => point.y),\n variant === \"area\",\n ),\n 28,\n 4,\n );\n const baseline = y(0);\n const last = finite[finite.length - 1];\n const values = data.map((value) =>\n value !== null && Number.isFinite(value) ? `${value}${unit ? ` ${unit}` : \"\"}` : \"Missing\",\n );\n return (\n <div\n {...props}\n ref={ref}\n data-oprt-sparkline={variant}\n className={clsx(sparklineStyles({ variant }), seriesColorClass(seriesId, color), className)}\n >\n <svg\n role=\"img\"\n aria-label={label}\n aria-describedby={`${id}-values`}\n viewBox=\"0 0 128 32\"\n className=\"block h-auto w-full overflow-visible\"\n >\n {variant === \"area\" && finite.length > 0 && (\n <line\n x1={4}\n x2={124}\n y1={baseline}\n y2={baseline}\n aria-hidden=\"true\"\n className=\"text-oprt-chart-grid\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-guide-width)\"\n vectorEffect=\"non-scaling-stroke\"\n />\n )}\n {segments(points).map((segment, index) => {\n const path = linePath(segment, x, y);\n return (\n <g key={index} data-chart-segment=\"\" aria-hidden=\"true\">\n {variant === \"area\" && (\n <path\n d={`${path} L${x(segment[segment.length - 1]!.x)},${baseline} L${x(segment[0]!.x)},${baseline} Z`}\n fill=\"currentColor\"\n fillOpacity=\"var(--oprt-chart-area-opacity)\"\n />\n )}\n <path\n d={path}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"var(--oprt-chart-stroke-width)\"\n strokeLinecap=\"butt\"\n strokeLinejoin=\"miter\"\n strokeMiterlimit={2}\n vectorEffect=\"non-scaling-stroke\"\n />\n {segment.length === 1 && segment[0] !== last && (\n <rect\n x={x(segment[0]!.x) - 1.5}\n y={y(segment[0]!.y) - 1.5}\n width={3}\n height={3}\n fill=\"currentColor\"\n />\n )}\n </g>\n );\n })}\n {last && (\n <rect\n data-sparkline-endpoint=\"\"\n aria-hidden=\"true\"\n x={x(last.x) - 2}\n y={y(last.y) - 2}\n width={4}\n height={4}\n fill=\"currentColor\"\n />\n )}\n </svg>\n <span id={`${id}-values`} className=\"sr-only\">\n {values.length ? values.join(\", \") : \"No data\"}\n </span>\n {finite.length === 0 && (\n <span className=\"text-oprt-base text-oprt-text-dim\">No finite data</span>\n )}\n </div>\n );\n});\nSparkline.displayName = \"Sparkline\";\n","SplitPane/index.ts":"export { SplitPane, type SplitPaneProps } from \"./SplitPane\";\nexport { SplitPaneManifest } from \"./SplitPane.manifest\";\n","SplitPane/SplitPane.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const SplitPaneManifest: Record<string, Component> = {\n SplitPane: {\n description:\n \"Two panels with a keyboard and pointer resize separator and two step buttons on the divider; a horizontal split stacks below a container-width threshold.\",\n props: {\n primary: {\n type: \"node\",\n required: true,\n description: \"Primary panel content, always first in reading order.\",\n },\n secondary: { type: \"node\", required: true, description: \"Secondary panel content.\" },\n size: {\n type: \"number\",\n description:\n \"Controlled primary-panel percentage of available space, excluding the handle. Clamped to minSize/maxSize. Stacked panels share a bounded height by the same split.\",\n },\n defaultSize: {\n type: \"number\",\n default: 50,\n description: \"Initial uncontrolled primary-panel percentage.\",\n },\n onSizeChange: {\n type: \"function\",\n description:\n \"(percent: number) => void. Called for valid keyboard, pointer or step-button adjustments.\",\n },\n minSize: {\n type: \"number\",\n default: 20,\n description:\n \"Minimum primary-panel percentage. Finite values clamp to 0–100; non-finite values use 20.\",\n },\n maxSize: {\n type: \"number\",\n default: 80,\n description:\n \"Maximum primary-panel percentage. Finite values clamp between minSize and 100; non-finite values use 80.\",\n },\n step: {\n type: \"number\",\n default: 1,\n description:\n \"Positive percentage increment for the arrow keys and dragging. Invalid values use 1. The step buttons move 10 points, or step when it is larger.\",\n },\n orientation: {\n type: \"enum\",\n values: [\"horizontal\", \"vertical\"],\n default: \"horizontal\",\n description:\n \"Horizontal puts panels side by side; vertical puts them above and below. Give vertical layouts a bounded height.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n description:\n \"Turns resizing off: the separator leaves the focus order and the step buttons are hidden.\",\n },\n label: {\n type: \"string\",\n default: \"Resize panels\",\n description: \"Accessible name of the separator.\",\n },\n stackBelow: {\n type: \"number\",\n default: 640,\n description:\n \"Container width in CSS pixels below which a horizontal split stacks its panels and hides resizing. A vertical split never stacks. Use 0 to disable responsive stacking.\",\n },\n primaryLabel: {\n type: \"string\",\n default: \"Primary panel\",\n description:\n \"Accessible name of the primary panel while its content scrolls; it is then a focusable region.\",\n },\n secondaryLabel: {\n type: \"string\",\n default: \"Secondary panel\",\n description:\n \"Accessible name of the secondary panel while its content scrolls; it is then a focusable region.\",\n },\n },\n composable_with: [\"Card\", \"DataGrid\", \"Chart\", \"EventTimeline\"],\n never_inside: [\"Button\"],\n example:\n '<SplitPane primary={<section>Telemetry</section>} secondary={<section>Inspector</section>} defaultSize={60} label=\"Resize telemetry and inspector\" />',\n },\n};\n","SplitPane/SplitPane.tsx":"\"use client\";\n\nimport { ChevronDown, ChevronLeft, ChevronRight, ChevronUp } from \"@oprt/icons\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport {\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type FocusEvent,\n type HTMLAttributes,\n type KeyboardEvent,\n type PointerEvent,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { useHydratedId } from \"../_internal/hydration\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { createResizeObserver, useElementWidth } from \"../_internal/useElementWidth\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\nimport { SplitPaneManifest } from \"./SplitPane.manifest\";\n\nconst rootStyles = cva(\"flex min-w-0 min-h-0 font-sans text-oprt-text\", {\n variants: { orientation: { horizontal: \"flex-row\", vertical: \"flex-col\" } },\n defaultVariants: { orientation: \"horizontal\" },\n});\n/** The divider between the panels: the separator fills it and the step buttons sit in its middle. */\nconst dividerStyles = cva(\"relative flex shrink-0 items-center justify-center\", {\n variants: {\n orientation: { horizontal: \"w-3u min-h-3u flex-col\", vertical: \"h-3u min-w-3u flex-row\" },\n },\n defaultVariants: { orientation: \"horizontal\" },\n});\nconst separatorStyles = cva(\n \"absolute inset-0 flex touch-none select-none items-center justify-center border border-transparent bg-oprt-surface-1 focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus\",\n {\n variants: {\n orientation: { horizontal: \"cursor-col-resize\", vertical: \"cursor-row-resize\" },\n disabled: { true: \"cursor-default opacity-50\", false: \"hover:bg-oprt-surface-2\" },\n },\n defaultVariants: { orientation: \"horizontal\", disabled: false },\n },\n);\n// A control you click, so its boundary is the control outline (design call DC-3); unavailable at a\n// limit, it takes the disabled look of design call DC-9: dim text, a muted fill and no hover.\nconst stepButtonStyles =\n \"flex h-oprt-sm w-oprt-sm shrink-0 items-center justify-center border border-oprt-border-control bg-oprt-surface-1 text-oprt-text hover:border-oprt-accent focus-visible:outline-hidden focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus disabled:cursor-not-allowed disabled:bg-oprt-surface-3 disabled:text-oprt-text-dim disabled:hover:border-oprt-border-control\";\n// A panel that scrolls takes keyboard focus, so it shows it, in forced colors too.\nconst paneStyles =\n \"min-w-0 min-h-0 overflow-auto focus-visible:outline-hidden focus-visible:shadow-oprt-focus forced-colors:focus-visible:outline-solid forced-colors:focus-visible:outline-2 forced-colors:focus-visible:-outline-offset-2 forced-colors:focus-visible:outline-[CanvasText]\";\n\n/** How far one press of a step button moves the split, in percentage points (design call DC-16). */\nconst BUTTON_STEP = 10;\n\nexport type SplitPaneProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n primary: ReactNode;\n secondary: ReactNode;\n size?: number;\n defaultSize?: number;\n onSizeChange?: (percent: number) => void;\n minSize?: number;\n maxSize?: number;\n step?: number;\n orientation?: \"horizontal\" | \"vertical\";\n disabled?: boolean;\n label?: string;\n stackBelow?: number;\n /** The primary panel's name while its content scrolls, when it is a focusable region. */\n primaryLabel?: string;\n /** The secondary panel's name while its content scrolls, when it is a focusable region. */\n secondaryLabel?: string;\n};\n\nconst clamp = (value: number, min: number, max: number) => Math.min(max, Math.max(min, value));\n\n/**\n * Whether the panel's content is larger than the panel, so that it scrolls and has to be a named\n * region a keyboard can reach (Stage 1 R6). Measured when the panel or one of its direct children\n * changes size and when a child is added or removed; where there is no ResizeObserver, once.\n */\nfunction usePaneScrolls(pane: RefObject<HTMLElement | null>): boolean {\n const [scrolls, setScrolls] = useState(false);\n useEffect(() => {\n const node = pane.current;\n if (!node) return;\n const measure = () =>\n setScrolls(\n node.scrollWidth > node.clientWidth + 1 || node.scrollHeight > node.clientHeight + 1,\n );\n const resize = createResizeObserver(measure);\n const watched = new Set<Element>();\n const observe = () => {\n if (!resize) return;\n const current = new Set<Element>([node, ...node.children]);\n for (const element of watched)\n if (!current.has(element)) {\n resize.unobserve(element);\n watched.delete(element);\n }\n for (const element of current)\n if (!watched.has(element)) {\n resize.observe(element);\n watched.add(element);\n }\n };\n observe();\n measure();\n const children = new MutationObserver(() => {\n observe();\n measure();\n });\n children.observe(node, { childList: true });\n return () => {\n resize?.disconnect();\n children.disconnect();\n };\n }, [pane]);\n return scrolls;\n}\n\n/** A percentage of the available pane space, excluding the resize handle. */\nexport const SplitPane = forwardRef<HTMLDivElement, SplitPaneProps>(\n (\n {\n primary,\n secondary,\n size,\n defaultSize = 50,\n onSizeChange,\n minSize = 20,\n maxSize = 80,\n step = 1,\n orientation = \"horizontal\",\n disabled = false,\n label = \"Resize panels\",\n stackBelow = 640,\n primaryLabel = \"Primary panel\",\n secondaryLabel = \"Secondary panel\",\n className,\n onFocusCapture,\n onBlurCapture,\n ...props\n },\n forwardedRef,\n ) => {\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"SplitPane\", SplitPaneManifest.SplitPane!.props, { orientation });\n }\n // An unknown orientation behaves as horizontal throughout: layout, keys and aria-orientation.\n const vertical = orientation === \"vertical\";\n const axis = vertical ? \"vertical\" : \"horizontal\";\n const rootRef = useRef<HTMLDivElement | null>(null);\n const primaryRef = useRef<HTMLDivElement | null>(null);\n const secondaryRef = useRef<HTMLDivElement | null>(null);\n const separatorRef = useRef<HTMLDivElement | null>(null);\n // The separator is created again each time the panels stop stacking, after hydration, so the\n // ids it points at are the ones the hydrated markup carries (SplitPane-W0-1). They are read\n // from the root's own children, so a SplitPane nested in a panel never answers for its parent.\n const ownChild = (selector: string) => () =>\n Array.from(rootRef.current?.children ?? []).find((child) => child.matches(selector))?.id;\n const primaryId = useHydratedId(ownChild('[data-pane=\"primary\"]'));\n const helpId = useHydratedId(ownChild(\"[data-oprt-split-help]\"));\n const [internalSize, setInternalSize] = useState(defaultSize);\n const drag = useRef<{\n pointerId: number;\n coordinate: number;\n size: number;\n extent: number;\n } | null>(null);\n const lower = clamp(Number.isFinite(minSize) ? minSize : 20, 0, 100);\n const upper = clamp(Number.isFinite(maxSize) ? maxSize : 80, lower, 100);\n const rawSize = size ?? internalSize;\n const currentSize = clamp(Number.isFinite(rawSize) ? rawSize : 50, lower, upper);\n const increment = Number.isFinite(step) && step > 0 ? step : 1;\n const buttonStep = Math.max(BUTTON_STEP, increment);\n const threshold = Number.isFinite(stackBelow) ? Math.max(0, stackBelow) : 640;\n const setRef = useMergedRefs<HTMLDivElement>(rootRef, forwardedRef);\n // Narrow containers stack a horizontal split; a vertical one already has its panels one above\n // the other (SplitPane-R3). A hidden container reports no width and is measured once it is\n // visible; where there is no ResizeObserver the panels stay side by side.\n const isNarrow = useCallback((width: number) => width < threshold, [threshold]);\n const narrow = useElementWidth<HTMLDivElement, boolean>(rootRef, {\n initial: false,\n select: isNarrow,\n });\n const stacked = !vertical && narrow;\n const primaryScrolls = usePaneScrolls(primaryRef);\n const secondaryScrolls = usePaneScrolls(secondaryRef);\n // When the focused separator or step button goes (the panels stacked) or a step button is\n // disabled at a limit, focus stays in the split pane instead of falling to the page\n // (SplitPane-R4). Only the divider's own controls are tracked: focus inside the panels\n // belongs to their content.\n const restoreFocus = useRestoreFocusOnRemoval<HTMLDivElement, true>(rootRef, {\n track: (element) =>\n element.closest(\"[data-oprt-split-divider]\")?.parentElement === rootRef.current\n ? true\n : null,\n findTarget: () =>\n separatorRef.current?.isConnected ? separatorRef.current : primaryRef.current,\n });\n\n useEffect(() => {\n drag.current = null;\n }, [disabled, stacked, orientation]);\n\n const resize = (next: number) => {\n if (disabled || stacked || !Number.isFinite(next)) return;\n const bounded = Math.round(clamp(next, lower, upper) * 1000) / 1000;\n if (bounded === currentSize) return;\n if (size === undefined) setInternalSize(bounded);\n onSizeChange?.(bounded);\n };\n const keyDown = (event: KeyboardEvent<HTMLDivElement>) => {\n if (disabled || stacked) return;\n const decrease = vertical ? \"ArrowUp\" : \"ArrowLeft\";\n const increase = vertical ? \"ArrowDown\" : \"ArrowRight\";\n let next: number;\n switch (event.key) {\n case decrease:\n next = currentSize - increment;\n break;\n case increase:\n next = currentSize + increment;\n break;\n case \"Home\":\n next = lower;\n break;\n case \"End\":\n next = upper;\n break;\n default:\n return;\n }\n event.preventDefault();\n resize(next);\n };\n const pointerDown = (event: PointerEvent<HTMLDivElement>) => {\n if (disabled || stacked || event.button !== 0 || !rootRef.current) return;\n const rootBounds = rootRef.current.getBoundingClientRect();\n const handleBounds = event.currentTarget.getBoundingClientRect();\n const extent = vertical\n ? rootBounds.height - handleBounds.height\n : rootBounds.width - handleBounds.width;\n if (extent <= 0) return;\n event.preventDefault();\n event.currentTarget.focus();\n event.currentTarget.setPointerCapture?.(event.pointerId);\n drag.current = {\n pointerId: event.pointerId,\n coordinate: vertical ? event.clientY : event.clientX,\n size: currentSize,\n extent,\n };\n };\n const pointerMove = (event: PointerEvent<HTMLDivElement>) => {\n if (!drag.current || drag.current.pointerId !== event.pointerId) return;\n const coordinate = vertical ? event.clientY : event.clientX;\n const next =\n drag.current.size + ((coordinate - drag.current.coordinate) / drag.current.extent) * 100;\n resize(lower + Math.round((next - lower) / increment) * increment);\n };\n const pointerEnd = (event: PointerEvent<HTMLDivElement>) => {\n if (drag.current?.pointerId !== event.pointerId) return;\n drag.current = null;\n if (event.currentTarget.hasPointerCapture?.(event.pointerId))\n event.currentTarget.releasePointerCapture(event.pointerId);\n };\n\n const [Decrease, Increase] = vertical ? [ChevronUp, ChevronDown] : [ChevronLeft, ChevronRight];\n const [decreaseLabel, increaseLabel] = vertical\n ? [\"Move divider up\", \"Move divider down\"]\n : [\"Move divider left\", \"Move divider right\"];\n // A panel whose content scrolls is a named region in the tab order (SplitPane-B2M-1). While\n // stacked the primary panel can also take focus from script, for the separator's sake (R4).\n const region = (scrolls: boolean, name: string) =>\n scrolls ? { role: \"region\", \"aria-label\": name, tabIndex: 0 } : {};\n\n return (\n <div\n {...props}\n ref={setRef}\n data-stacked={stacked || undefined}\n className={clsx(rootStyles({ orientation: stacked ? \"vertical\" : axis }), className)}\n onFocusCapture={(event: FocusEvent<HTMLDivElement>) => {\n onFocusCapture?.(event);\n restoreFocus.onFocusCapture(event);\n }}\n onBlurCapture={(event: FocusEvent<HTMLDivElement>) => {\n onBlurCapture?.(event);\n restoreFocus.onBlurCapture(event);\n }}\n >\n {/* Stacked or side by side, the panels share the space by the saved split. A percentage\n basis resolves to the content's height when the stack has no fixed height, so an\n unbounded stack still grows to its content (SplitPane-R2). */}\n <div\n ref={primaryRef}\n id={primaryId}\n data-pane=\"primary\"\n className={paneStyles}\n style={{ flexBasis: \"0%\", flexGrow: currentSize }}\n {...(primaryScrolls ? region(true, primaryLabel) : stacked ? { tabIndex: -1 } : {})}\n >\n {primary}\n </div>\n {!stacked && (\n <div data-oprt-split-divider=\"\" className={dividerStyles({ orientation: axis })}>\n <div\n ref={separatorRef}\n role=\"separator\"\n tabIndex={disabled ? -1 : 0}\n aria-label={label}\n aria-controls={primaryId}\n aria-describedby={helpId}\n aria-orientation={vertical ? \"horizontal\" : \"vertical\"}\n aria-valuemin={lower}\n aria-valuemax={upper}\n aria-valuenow={currentSize}\n aria-valuetext={`${currentSize}% primary panel`}\n aria-disabled={disabled || undefined}\n className={separatorStyles({ orientation: axis, disabled })}\n onKeyDown={keyDown}\n onPointerDown={pointerDown}\n onPointerMove={pointerMove}\n onPointerUp={pointerEnd}\n onPointerCancel={pointerEnd}\n onLostPointerCapture={() => {\n drag.current = null;\n }}\n >\n {disabled && (\n <span\n aria-hidden=\"true\"\n className={\n vertical\n ? \"w-4u border-t border-oprt-border-strong\"\n : \"h-4u border-l border-oprt-border-strong\"\n }\n />\n )}\n </div>\n {/* The single-pointer alternative to dragging (R9, WCAG 2.5.7; design call DC-16):\n two step buttons in the middle of the divider. The keyboard keeps the separator's\n arrows, Home and End, so the buttons stay out of the tab order; they are named for\n touch screen readers. */}\n {!disabled && (\n <div className={clsx(\"relative flex gap-0.5u\", vertical ? \"flex-row\" : \"flex-col\")}>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={decreaseLabel}\n disabled={currentSize <= lower}\n className={stepButtonStyles}\n onClick={() => resize(currentSize - buttonStep)}\n >\n <Decrease size={16} aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={increaseLabel}\n disabled={currentSize >= upper}\n className={stepButtonStyles}\n onClick={() => resize(currentSize + buttonStep)}\n >\n <Increase size={16} aria-hidden=\"true\" />\n </button>\n </div>\n )}\n </div>\n )}\n <div\n ref={secondaryRef}\n data-pane=\"secondary\"\n className={paneStyles}\n style={{ flexBasis: \"0%\", flexGrow: 100 - currentSize }}\n {...region(secondaryScrolls, secondaryLabel)}\n >\n {secondary}\n </div>\n {/* Hidden: it describes the separator (aria-describedby reads hidden text) without being\n read as page text after the panels, stacked or not (SplitPane-R5). */}\n <span id={helpId} data-oprt-split-help=\"\" hidden>\n Use {vertical ? \"Up and Down\" : \"Left and Right\"} arrow keys to resize. Home and End move\n to the limits.\n </span>\n </div>\n );\n },\n);\nSplitPane.displayName = \"SplitPane\";\n","StatCard/index.ts":"export { StatCard, type StatCardProps } from \"./StatCard\";\nexport { StatCardManifest } from \"./StatCard.manifest\";\n","StatCard/StatCard.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const StatCardManifest: Record<string, Component> = {\n StatCard: {\n description:\n \"Single-metric display: label, mono numeric value, and an optional trend indicator paired with an arrow glyph (non-color cue per WCAG 1.4.1). It keeps a minimum footprint (169 px tall, up to 180 px wide, narrower inside a narrower container) and its text wraps. It measures its own width, so a large number steps down in size to fit it (never below the body text size), and in a row that sizes its items to their content it is 180 px wide.\",\n props: {\n label: {\n type: \"string\",\n required: true,\n description:\n \"Short metric heading displayed uppercase and used as the default accessible name for the metric group. Values, units, and captions retain their casing. Override with aria-label or aria-labelledby when more context is needed.\",\n },\n value: {\n type: \"union\",\n types: [\"string\", \"number\"],\n required: true,\n description:\n \"The metric value (string or number). Rendered in mono for digit alignment. A number is never split between its digits: one with digit groups (1,234,567.890) wraps only after a group separator, and its last group stays with the unit. When they do not fit the card the value steps down in size, never below the body text size (--oprt-type-base); at that size, if they still do not fit together, the unit moves to its own line under the value. Any other text breaks anywhere. Nothing is truncated or abbreviated.\",\n },\n unit: {\n type: \"string\",\n description:\n \"Unit suffix shown beside the value, e.g. 'ms'. Its first word stays beside the last digits at every size above the body text size; only when they cannot fit together even at that size does the unit move to its own line under the value. The rest of a long unit wraps.\",\n },\n delta: {\n type: \"number\",\n description: \"Signed change. Trend is derived from its sign when 'trend' is omitted.\",\n },\n trend: {\n type: \"enum\",\n values: [\"up\", \"down\", \"flat\"],\n description: \"Explicit trend direction. Overrides the sign of 'delta' when provided.\",\n },\n trendIntent: {\n type: \"enum\",\n values: [\"positive\", \"negative\", \"neutral\"],\n description:\n \"Whether the change is good, bad or neither for the application, which sets the trend color independently of its direction: falling errors or latency are 'positive', rising critical alarms are 'negative'. Without it the color follows the direction (up is success, down is error). The arrow and the change text stay as the non-color cue.\",\n },\n deltaLabel: { type: \"string\", description: \"Human-readable change text, e.g. '+12%'.\" },\n caption: { type: \"string\", description: \"Secondary supporting line below the metric.\" },\n },\n composable_with: [\"Card\", \"DataTable\", \"Sidebar\", \"Toolbar\"],\n never_inside: [\"StatCard\", \"Badge\", \"Button\"],\n example:\n '<StatCard label=\"LATENCY\" value={42} unit=\"ms\" delta={-12} deltaLabel=\"-12%\" trendIntent=\"positive\" />',\n },\n};\n","StatCard/StatCard.tsx":"\"use client\";\n\nimport { TrendingUp, TrendingDown, TrendFlat } from \"@oprt/icons\";\nimport { Fragment, forwardRef, useId, type CSSProperties, type HTMLAttributes } from \"react\";\nimport clsx from \"clsx\";\nimport { StatCardManifest } from \"./StatCard.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { numberParts, splitLast, unitWords } from \"../_internal/numberText\";\n\ntype Trend = \"up\" | \"down\" | \"flat\";\ntype TrendIntent = \"positive\" | \"negative\" | \"neutral\";\n\nfunction deriveTrend(delta: number): Trend {\n if (delta > 0) return \"up\";\n if (delta < 0) return \"down\";\n return \"flat\";\n}\n\nconst TREND_COLOR: Record<Trend, string> = {\n up: \"text-oprt-success\",\n down: \"text-oprt-error\",\n flat: \"text-oprt-text-dim\",\n};\n\n// What the change means for the application, independent of its direction (design call DC-12).\nconst INTENT_COLOR: Record<TrendIntent, string> = {\n positive: \"text-oprt-success\",\n negative: \"text-oprt-error\",\n neutral: \"text-oprt-text-dim\",\n};\n\nconst TREND_ICONS = {\n up: TrendingUp,\n down: TrendingDown,\n flat: TrendFlat,\n} as const;\n\nconst has = (table: object, key: unknown): key is string =>\n typeof key === \"string\" && Object.prototype.hasOwnProperty.call(table, key);\n\n// --- Stepping the value down to the card (container query) -------------------------------------------\n//\n// A group that cannot break (the last group, or a first group with its prefix) may be wider than the\n// card, and so may the last group with its unit beside it. Then the type gets smaller, in the card,\n// until they fit: digits are never split. It stops at the body text size, though: below that the\n// digits are unreadable. The card is the container (`@container`), and `cqi` is 1% of its content\n// width. A group is counted in characters and the monospace advance is a constant: the Operator\n// mono family measures 0.60 em a character, plus its tracking (0.06 em in the value, 0.02 em in the\n// unit), and the constants carry 1.5% on top so that rounding never leaves a pixel over.\nconst VALUE_ADVANCE = 0.67; // em of the value a character takes\nconst UNIT_ADVANCE = 0.63; // em of the unit a character takes\n\n/** A character that is not Latin, Arabic or Indic may come from a fallback font, which is wider. */\nfunction glyphWeight(code: number): number {\n if (\n code < 0x250 ||\n (code >= 0x600 && code < 0x700) ||\n (code >= 0x900 && code < 0xa00) ||\n (code >= 0x2000 && code < 0x2100)\n )\n return 1;\n return code >= 0xff00 && code < 0xfff0 ? 1.7 : 1.3;\n}\n\nconst FORMAT_CHARACTER = /\\p{Cf}/u;\n\n/** How long a run is in em: its characters at the monospace advance. A direction mark takes no room. */\nconst lengthOf = (text: string, advance: number): number => {\n let characters = 0;\n for (const character of text)\n if (!FORMAT_CHARACTER.test(character)) characters += glyphWeight(character.codePointAt(0)!);\n return Number((characters * advance).toFixed(3));\n};\n\n// The value's size is the largest at which every group fits the card, between its ceiling (the 2xl\n// token) and its floor (the body text size, `--oprt-type-base`). Two things have to fit:\n// - the widest group before the last one: the card's width over its length (`--oprt-stat-lead`);\n// - the last group with the unit beside it (`--oprt-stat-run` and `--oprt-stat-unit`), at the\n// larger of two sizes: the one that fits the group beside a unit at its own token size, and the\n// one that fits both at a single size when the unit has to shrink with the value (it is never\n// larger).\n// The last group and the unit are one atomic piece of the line, so above the floor they always fit\n// one line. At the floor, if they are still wider than a line, the unit wraps under the value, at\n// the space inside the piece: the final fallback, reached only there. A group that does not fit even\n// at the floor is not split either: it keeps the floor and runs into the card's padding.\n// Each width also keeps 0.8 px per em of length (about half a pixel a character) for engines that\n// round every glyph's advance to a whole pixel, as Chrome on Linux does: without it, a last group\n// and its unit that filled the card to within a pixel or two on a Mac wrapped there at full size.\n// One literal, because the class scanner needs it whole.\nconst STEP_DOWN =\n \"[font-size:max(var(--oprt-type-base),min(var(--oprt-type-2xl),calc((100cqi_-_var(--oprt-stat-lead)_*_0.8px)_/_var(--oprt-stat-lead)),max(calc((100cqi_-_var(--oprt-type-md)_*_var(--oprt-stat-unit)_-_(var(--oprt-stat-run)_+_var(--oprt-stat-unit))_*_0.8px)_/_var(--oprt-stat-run)),calc((100cqi_-_(var(--oprt-stat-run)_+_var(--oprt-stat-unit))_*_0.8px)_/_(var(--oprt-stat-run)_+_var(--oprt-stat-unit))))))]\";\n\nconst VALUE_TEXT = \"m-0 block min-w-0 normal-case font-mono text-oprt-2xl text-oprt-text\";\n// The 2xl type token, with its line height as a ratio (40 / 28) so that it follows the size.\nconst VALUE_NUMBER = `m-0 block min-w-0 normal-case font-mono text-oprt-text tracking-oprt-2xl leading-[1.4286] [font-weight:var(--oprt-weight-2xl)] ${STEP_DOWN}`;\nconst UNIT =\n \"font-mono tracking-oprt-md leading-none text-oprt-text-dim [font-size:min(var(--oprt-type-md),1em)] [font-weight:var(--oprt-weight-md)]\";\n\n/**\n * The value and its unit (R4: a number and its unit stay together, and nothing is truncated).\n *\n * A number breaks only after a group separator. Its last group and the unit beside it are one piece\n * of the line that moves as a whole; if a group is wider than the card the type steps down, to the\n * body text size and no further, and there the unit drops under the value if it still cannot sit\n * beside the last group. Any other text breaks anywhere, and the unit stays beside the last\n * character the reader sees.\n */\nfunction StatValue({ value, unit }: { value: string | number; unit: string | undefined }) {\n const text = String(value);\n const words = unitWords(unit);\n const number = numberParts(text.trim());\n // Other text: the unit follows its last character after a no-break space, in one run that never wraps.\n const unitRun = words && (\n <span data-oprt-stat-unit=\"\" className={UNIT}>\n {`\\u00a0${words.first}`}\n </span>\n );\n const unitRest = words?.rest ? (\n <span className={clsx(UNIT, \"oprt-wrap-anywhere\")}>{` ${words.rest}`}</span>\n ) : null;\n\n if (number) {\n const last = lengthOf(number.tail, VALUE_ADVANCE);\n // The widest group before the last (the first one carries the prefix); the last when it has none.\n const lead =\n number.lead.length > 0\n ? Math.max(...number.lead.map((piece) => lengthOf(piece, VALUE_ADVANCE)))\n : last;\n const style = {\n \"--oprt-stat-lead\": lead,\n \"--oprt-stat-run\": last,\n \"--oprt-stat-unit\": words ? lengthOf(` ${words.first}`, UNIT_ADVANCE) : 0,\n } as CSSProperties;\n return (\n <span data-oprt-stat-value=\"\" className={VALUE_NUMBER} style={style}>\n <span data-oprt-stat-value-text=\"\">\n {number.lead.length > 0 && (\n // The groups before the last, each a run that cannot wrap, with a break after each.\n <span>\n {number.lead.map((piece, index) => (\n <Fragment key={index}>\n <span className=\"whitespace-nowrap\">{piece}</span>\n <wbr />\n </Fragment>\n ))}\n </span>\n )}\n {/* The last group and the unit beside it are one atomic piece of the line, so they move to\n the next line together. Inside it the group cannot break and the space before the unit\n can: that happens only when the pair is wider than a whole line, at the smallest size. */}\n <span data-oprt-stat-run=\"\" className=\"inline-block\">\n <span className=\"whitespace-nowrap\">{number.tail}</span>\n {words && (\n <span data-oprt-stat-unit=\"\" className={UNIT}>\n {` ${words.first}`}\n </span>\n )}\n </span>\n {unitRest}\n </span>\n </span>\n );\n }\n\n const [head, last] = splitLast(text);\n return (\n <span data-oprt-stat-value=\"\" className={VALUE_TEXT}>\n <span data-oprt-stat-value-text=\"\" className=\"oprt-wrap-anywhere\">\n {words ? (\n <>\n {head}\n <span data-oprt-stat-run=\"\" className=\"whitespace-nowrap\">\n {last}\n {unitRun}\n </span>\n {unitRest}\n </>\n ) : (\n text\n )}\n </span>\n </span>\n );\n}\n\nexport type StatCardProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n value: string | number;\n unit?: string;\n delta?: number;\n trend?: Trend;\n /**\n * Whether the change is good, bad or neither, which sets the color of the trend independently of\n * its direction: a fall in errors is `positive`, a rise in alarms is `negative`. Without it the\n * color follows the direction (up is success, down is error).\n */\n trendIntent?: TrendIntent;\n deltaLabel?: string;\n caption?: string;\n};\n\nexport const StatCard = forwardRef<HTMLDivElement, StatCardProps>((allProps, ref) => {\n const {\n className,\n label,\n value,\n unit,\n delta,\n trend,\n trendIntent,\n deltaLabel,\n caption,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n ...props\n } = allProps;\n const labelId = useId();\n const showTrend = delta !== undefined || trend !== undefined;\n // An unknown trend (made up in JS) falls back to the direction of delta, as an omitted one does.\n const explicitTrend = has(TREND_ICONS, trend) ? (trend as Trend) : undefined;\n const resolvedTrend: Trend = explicitTrend ?? (delta !== undefined ? deriveTrend(delta) : \"flat\");\n const TrendIcon = TREND_ICONS[resolvedTrend];\n const trendColor = has(INTENT_COLOR, trendIntent)\n ? INTENT_COLOR[trendIntent as TrendIntent]\n : TREND_COLOR[resolvedTrend];\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"StatCard\", StatCardManifest.StatCard!.props, allProps);\n }\n\n return (\n <div\n ref={ref}\n role=\"group\"\n aria-label={ariaLabel}\n aria-labelledby={ariaLabelledBy ?? (ariaLabel ? undefined : labelId)}\n data-oprt-stat-card=\"\"\n className={clsx(\n // A minimum footprint (180 wide, 169 tall) that grows with content as needed, so a grid of\n // StatCards stays uniform. The width gives way inside a container narrower than 180 px\n // (two cards side by side on a phone); the height does not. Min dimensions are a deliberate\n // component spec, not layout-grid spacing. The card is a size container, so a large value\n // can step its type down to the card's width: it takes the width its cell gives it (R3), and\n // in a shrink-wrapped flex row that is its 180 px minimum. Containment gives a card no\n // intrinsic width of its own, so one in a shrink-wrapped grid would collapse to its padding\n // (measured: 50 px wide, a 0 px value). `contain-intrinsic-inline-size` is the natural width\n // of its content box at that minimum: 180 px less the 2 x 24 px padding and the 2 x 1 px border.\n \"@container [contain-intrinsic-inline-size:130px] flex min-h-[169px] min-w-[min(180px,100%)] flex-col gap-1u bg-oprt-surface-1 border border-oprt-border px-3u py-3u font-sans\",\n className,\n )}\n {...props}\n >\n <span\n id={labelId}\n className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-sm uppercase tracking-oprt-sm text-oprt-text-dim\"\n >\n {label}\n </span>\n\n <StatValue value={value} unit={unit} />\n\n {showTrend && (\n <span\n role=\"img\"\n className={clsx(\"m-0 inline-flex min-w-0 max-w-full items-center gap-1u\", trendColor)}\n aria-label={`trend ${resolvedTrend}${deltaLabel ? \" \" + deltaLabel : \"\"}`}\n >\n <TrendIcon size={16} />\n {deltaLabel !== undefined && (\n <span className=\"min-w-0 oprt-wrap-anywhere font-mono text-oprt-base normal-case\">\n {deltaLabel}\n </span>\n )}\n </span>\n )}\n\n {caption !== undefined && (\n <span className=\"m-0 min-w-0 oprt-wrap-anywhere text-oprt-base normal-case text-oprt-text-dim\">\n {caption}\n </span>\n )}\n </div>\n );\n});\nStatCard.displayName = \"StatCard\";\n","Tabs/index.ts":"export {\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n type TabsProps,\n type TabsListProps,\n type TabsTriggerProps,\n type TabsContentProps,\n} from \"./Tabs\";\nexport { TabsManifest, TabsCompositionRules } from \"./Tabs.manifest\";\n","Tabs/Tabs.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const TabsManifest: Record<string, Component> = {\n Tabs: {\n description:\n \"Tabbed interface root. Underline-accent style. Wraps TabsList and one TabsContent per TabsTrigger.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer/touch feedback. None disables motion; keyboard and system reduced motion are always immediate.\",\n },\n value: { type: \"string\" },\n defaultValue: { type: \"string\" },\n onValueChange: { type: \"function\" },\n orientation: {\n type: \"enum\",\n values: [\"horizontal\", \"vertical\"],\n default: \"horizontal\",\n },\n },\n slots: {\n children: { type: \"TabsList | TabsContent\", required: true },\n },\n composable_with: [\"Card\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<Tabs defaultValue=\"overview\">\\n <TabsList>\\n <TabsTrigger value=\"overview\">OVERVIEW</TabsTrigger>\\n <TabsTrigger value=\"logs\">LOGS</TabsTrigger>\\n </TabsList>\\n <TabsContent value=\"overview\">System nominal.</TabsContent>\\n <TabsContent value=\"logs\">No entries.</TabsContent>\\n</Tabs>',\n },\n TabsList: {\n description:\n \"Row (or column, when vertical) of tab triggers with a rail on the panel side. A row wider than its container stays one row and scrolls sideways inside itself: an edge fade marks each side with hidden tabs, and the selected tab is scrolled into view on open and on selection. Name it with aria-label.\",\n props: {},\n slots: {\n children: { type: \"TabsTrigger\", required: true },\n },\n composable_with: [\"Tabs\"],\n never_inside: [\"TabsTrigger\", \"TabsContent\"],\n example:\n '<TabsList>\\n <TabsTrigger value=\"overview\">OVERVIEW</TabsTrigger>\\n <TabsTrigger value=\"logs\">LOGS</TabsTrigger>\\n</TabsList>',\n },\n TabsTrigger: {\n description:\n \"Single tab trigger. Active tab shows an accent underline and bolder weight. A label wider than the row wraps inside the tab, which grows taller. Must be a child of TabsList.\",\n props: {\n value: { type: \"string\", required: true },\n disabled: { type: \"boolean\", default: false },\n },\n composable_with: [\"TabsList\"],\n never_inside: [\"TabsContent\", \"Button\", \"Badge\"],\n example: '<TabsTrigger value=\"overview\">OVERVIEW</TabsTrigger>',\n },\n TabsContent: {\n description:\n \"Content panel associated with a TabsTrigger by matching value. Must be a child of Tabs.\",\n props: {\n value: { type: \"string\", required: true },\n },\n slots: {\n children: { type: \"any\", required: false },\n },\n composable_with: [\"Tabs\"],\n never_inside: [\"TabsList\", \"TabsTrigger\"],\n example: '<TabsContent value=\"overview\">System nominal.</TabsContent>',\n },\n};\n\nexport const TabsCompositionRules = {\n Tabs: {\n allowed_children: [\"TabsList\", \"TabsContent\"],\n children: {\n TabsList: { allowed_children: [\"TabsTrigger\"] },\n },\n },\n};\n","Tabs/Tabs.tsx":"\"use client\";\n\nimport {\n Tabs as AriaTabs,\n TabList as AriaTabList,\n Tab as AriaTab,\n TabPanel as AriaTabPanel,\n type Key,\n} from \"react-aria-components\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useRef,\n type ComponentPropsWithoutRef,\n type ReactNode,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { TabsManifest } from \"./Tabs.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport {\n useMotion,\n composeMotionHandlers,\n MotionContext,\n type MotionMode,\n} from \"../_internal/motion\";\nimport { useTabsIndicator } from \"./useTabsIndicator\";\nimport { useTabsScroll } from \"./useTabsScroll\";\n\nconst TabsOrientationContext = createContext<\"horizontal\" | \"vertical\">(\"horizontal\");\n\nexport type TabsProps = Omit<\n ComponentPropsWithoutRef<\"div\">,\n \"dir\" | \"value\" | \"defaultValue\" | \"onChange\"\n> & {\n motion?: MotionMode;\n value?: string;\n defaultValue?: string;\n onValueChange?: (value: string) => void;\n orientation?: \"horizontal\" | \"vertical\";\n children: ReactNode;\n};\n\nexport const Tabs = forwardRef<HTMLDivElement, TabsProps>(\n (\n {\n className,\n motion = \"auto\",\n orientation = \"horizontal\",\n value,\n defaultValue,\n onValueChange,\n children,\n ...props\n },\n ref,\n ) => {\n const motionState = useMotion(motion);\n // exactOptionalPropertyTypes: build optional props conditionally so we never\n // forward a literal `undefined` to the React Aria root. Our flat API maps\n // onto RAC's collection names: value -> selectedKey,\n // defaultValue -> defaultSelectedKey, onValueChange -> onSelectionChange.\n const rootProps: Record<string, unknown> = { orientation };\n if (value !== undefined) rootProps[\"selectedKey\"] = value;\n if (defaultValue !== undefined) rootProps[\"defaultSelectedKey\"] = defaultValue;\n if (onValueChange !== undefined) {\n rootProps[\"onSelectionChange\"] = (key: Key) => onValueChange(String(key));\n }\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Tabs\", TabsManifest.Tabs!.props, { orientation });\n }\n\n // RAC remounts each Tab right after hydration, but a TabPanel keeps the id\n // it mounted with, so when hydration ids disagree with the server's the\n // tabs and the hydrated panel point past each other (see useHydrationRoot).\n const hydration = useHydrationRoot();\n\n return (\n <MotionContext.Provider value={motionState.mode}>\n <TabsOrientationContext.Provider value={orientation}>\n <AriaTabs\n key={hydration.key}\n {...hydration.probeProps}\n ref={ref}\n className={clsx(\"oprt-motion min-w-0 font-sans\", className)}\n {...rootProps}\n {...(props as Record<string, unknown>)}\n {...composeMotionHandlers(motionState.props, props)}\n render={(domProps) => (\n <div {...domProps} {...composeMotionHandlers(motionState.props, props)} />\n )}\n >\n {children}\n </AriaTabs>\n </TabsOrientationContext.Provider>\n </MotionContext.Provider>\n );\n },\n);\nTabs.displayName = \"Tabs\";\n\n// RAC's TabList drops `id` from the DOM props it forwards and derives its own\n// id from the tab collection, so `id` is omitted from the public type rather\n// than accepted and silently discarded.\nexport type TabsListProps = Omit<ComponentPropsWithoutRef<\"div\">, \"id\">;\n\n// A horizontal row that does not fit scrolls sideways inside itself (design call DC-14). The list is\n// the scroll area, so React Aria scrolls it as the arrow keys, Home and End move focus. It keeps 1 px\n// of padding for the tabs' focus rings, and its -1 px bottom margin lays its last pixel row over\n// the rail line its row draws, which does not scroll, so the indicator and a selected tab's border\n// still straddle the line. The scroll padding keeps a revealed tab clear of the fades. The\n// scrollbar is hidden: the fades say there is more, and a swipe, a wheel or the keys move the row.\nconst scrollingRow = [\n \"min-w-0 -mb-px overflow-x-auto overflow-y-hidden p-px scroll-px-4u\",\n \"[scrollbar-width:none] [&::-webkit-scrollbar]:hidden\",\n // A fade on each side that hides tabs. A mask uses only alpha, so it suits any background; the\n // color of its opaque stop does not show.\n \"data-[fade=left]:[mask-image:linear-gradient(to_right,transparent,var(--oprt-text)_var(--oprt-space-4))]\",\n \"data-[fade=right]:[mask-image:linear-gradient(to_left,transparent,var(--oprt-text)_var(--oprt-space-4))]\",\n \"data-[fade=both]:[mask-image:linear-gradient(to_right,transparent,var(--oprt-text)_var(--oprt-space-4),var(--oprt-text)_calc(100%_-_var(--oprt-space-4)),transparent)]\",\n];\n\nexport const TabsList = forwardRef<HTMLDivElement, TabsListProps>(\n ({ className, children, ...props }, ref) => {\n const orientation = useContext(TabsOrientationContext);\n const list = useRef<HTMLDivElement | null>(null);\n const setNode = useMergedRefs<HTMLDivElement>(list, ref);\n const vertical = orientation === \"vertical\";\n const geometry = useTabsIndicator(list, vertical);\n const fade = useTabsScroll(list, vertical);\n return (\n <AriaTabList\n ref={setNode}\n className={clsx(\n \"oprt-motion-tabs flex gap-1u\",\n vertical ? \"border-b border-oprt-border\" : scrollingRow,\n className,\n )}\n {...(props as Record<string, unknown>)}\n data-orientation={orientation}\n data-indicator-ready={geometry ? \"true\" : undefined}\n data-fade={fade}\n render={(domProps) => (\n // The row: it draws the rail line under a horizontal list, outside what scrolls. The\n // same element in both orientations, so changing orientation does not remount the tabs.\n // A vertical list draws its own rail, so there the row takes no box (`contents`): the\n // tablist stays its root's flex item, a stretching root runs the rail beside the whole\n // panel, and placement classes on TabsList (sticky, self-start, order) still place it.\n <div\n data-oprt-tabs-row=\"\"\n className={vertical ? \"contents\" : \"min-w-0 border-b border-oprt-border\"}\n >\n <div {...domProps}>\n {domProps.children}\n {geometry && (\n <span\n aria-hidden=\"true\"\n className=\"oprt-motion-indicator\"\n data-immediate={geometry.immediate}\n style={{\n transform: vertical\n ? `translateY(${geometry.y}px) scaleY(${geometry.length})`\n : `translateX(${geometry.x}px) scaleX(${geometry.length})`,\n }}\n />\n )}\n </div>\n </div>\n )}\n >\n {children}\n </AriaTabList>\n );\n },\n);\nTabsList.displayName = \"TabsList\";\n\n// RAC renders a Tab as a <div role=\"tab\">, not a <button>, and uses `id` as the\n// collection key (ours stays `value`). That id also generates the DOM id the\n// panel's aria-labelledby points at, so `id` is omitted from the public type.\nexport type TabsTriggerProps = Omit<ComponentPropsWithoutRef<\"div\">, \"id\"> & {\n value: string;\n disabled?: boolean;\n};\n\nexport const TabsTrigger = forwardRef<HTMLDivElement, TabsTriggerProps>(\n ({ className, value, disabled, ...props }, ref) => (\n <AriaTab\n ref={ref}\n id={value}\n isDisabled={disabled ?? false}\n className={clsx(\n // inline-flex/centering/select-none reproduce the UA <button> defaults (label centered,\n // no text selection) that the div-based RAC Tab does not get for free.\n \"inline-flex items-center justify-center text-center select-none px-2u -mb-px border border-b-2 border-transparent text-oprt-base font-medium uppercase tracking-oprt-caps [&_code]:normal-case [&_code]:tracking-normal [&_kbd]:normal-case [&_kbd]:tracking-normal text-oprt-text-dim hover:text-oprt-text data-[selected]:text-oprt-text data-[selected]:border-b-oprt-accent data-[selected]:font-semibold data-[disabled]:opacity-50 data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus outline-hidden\",\n // A tab keeps its width in a row that scrolls (DC-14), never wider than the row. A label\n // wider than that wraps, breaking a long token anywhere, and the tab grows taller (the DC-8\n // rule): the height is a minimum, and the padding is what is left of it around one line\n // and the 3 px of border, so a one-line tab is exactly 32 px.\n \"shrink-0 max-w-full oprt-wrap-anywhere min-h-oprt-md py-[calc((var(--oprt-height-md)-3px-var(--oprt-line-base))/2)]\",\n // Forced colors drop the focus border color and shadow, and would paint the transparent\n // outline on every tab: only a focused tab draws one, in CanvasText (Tabs-R3).\n \"forced-colors:outline-0 forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:-outline-offset-2 forced-colors:data-[focus-visible]:outline-[CanvasText]\",\n className,\n )}\n {...(props as Record<string, unknown>)}\n />\n ),\n);\nTabsTrigger.displayName = \"TabsTrigger\";\n\n// RAC's TabPanel likewise uses `id` as the collection key tying it to its Tab,\n// and generates the DOM id referenced by that tab's aria-controls, so a\n// caller-supplied `id` cannot be forwarded.\nexport type TabsContentProps = Omit<ComponentPropsWithoutRef<\"div\">, \"id\"> & {\n value: string;\n};\n\nexport const TabsContent = forwardRef<HTMLDivElement, TabsContentProps>(\n ({ className, value, ...props }, ref) => (\n <AriaTabPanel\n ref={ref}\n id={value}\n className={clsx(\n // break-words, as CardBody: an unbroken token wraps inside the panel instead of widening it.\n \"min-w-0 break-words pt-3u border border-transparent text-oprt-md normal-case text-oprt-text data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus outline-hidden\",\n \"forced-colors:outline-0 forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:-outline-offset-2 forced-colors:data-[focus-visible]:outline-[CanvasText]\",\n className,\n )}\n {...(props as Record<string, unknown>)}\n />\n ),\n);\nTabsContent.displayName = \"TabsContent\";\n","Tabs/useTabsIndicator.ts":"import { useRef, useState, type RefObject } from \"react\";\nimport { createResizeObserver } from \"../_internal/useElementWidth\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\ntype Geometry = { x: number; y: number; length: number; immediate: boolean };\n\n/** Decoration only: never delays selection or keeps an outgoing panel alive. */\nexport function useTabsIndicator(list: RefObject<HTMLDivElement | null>, vertical: boolean) {\n const [geometry, setGeometry] = useState<Geometry>();\n const selectedId = useRef<string>();\n useIsomorphicLayoutEffect(() => {\n const node = list.current;\n if (!node) return;\n let alive = true;\n const measure = (selection: boolean) => {\n if (!alive) return;\n const selected = node.querySelector<HTMLElement>('[role=\"tab\"][data-selected]');\n if (!selected) {\n setGeometry(undefined);\n selectedId.current = undefined;\n return;\n }\n // The positioned list is the tabs' offset parent. Layout coordinates\n // remain correct through ancestor transforms and scrolling.\n const length = vertical ? selected.offsetHeight : selected.offsetWidth;\n // The selected border remains the fallback until real geometry is available.\n if (!length) return;\n const x = selected.offsetLeft;\n const y = selected.offsetTop;\n const changedSelection =\n selectedId.current !== undefined && selectedId.current !== selected.id;\n selectedId.current = selected.id;\n setGeometry((previous) => {\n if (previous && previous.x === x && previous.y === y && previous.length === length)\n return previous;\n return { x, y, length, immediate: !selection || !changedSelection };\n });\n };\n // Where there is no ResizeObserver the indicator still follows selection, direction, fonts and\n // window resizes; it only misses a tab changing size on its own.\n const resize = createResizeObserver(() => measure(false));\n const observe = () => {\n if (!resize) return;\n resize.disconnect();\n resize.observe(node);\n for (const tab of node.querySelectorAll('[role=\"tab\"]')) resize.observe(tab);\n };\n observe();\n measure(false);\n const mutation = new MutationObserver((records) => {\n if (records.some((r) => r.type === \"childList\")) observe();\n measure(records.some((r) => r.attributeName === \"data-selected\"));\n });\n mutation.observe(node, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [\"data-selected\", \"dir\"],\n });\n // Direction can be inherited without changing tab size or selection.\n for (let ancestor = node.parentElement; ancestor; ancestor = ancestor.parentElement) {\n mutation.observe(ancestor, { attributes: true, attributeFilter: [\"dir\"] });\n }\n const settle = () => measure(false);\n document.fonts?.addEventListener(\"loadingdone\", settle);\n window.addEventListener(\"resize\", settle);\n return () => {\n alive = false;\n resize?.disconnect();\n mutation.disconnect();\n document.fonts?.removeEventListener(\"loadingdone\", settle);\n window.removeEventListener(\"resize\", settle);\n };\n }, [list, vertical]);\n return geometry;\n}\n","Tabs/useTabsScroll.ts":"import { useState, type RefObject } from \"react\";\nimport { createResizeObserver } from \"../_internal/useElementWidth\";\nimport { useIsomorphicLayoutEffect } from \"../_internal/useIsomorphicLayoutEffect\";\n\n/** The physical sides of a tab row that have tabs hidden behind them. */\nexport type TabsFade = \"left\" | \"right\" | \"both\";\n\n/** Which sides of `row` hide tabs, or undefined when every tab is in view. */\nexport function hiddenSides(row: HTMLElement): TabsFade | undefined {\n const range = row.scrollWidth - row.clientWidth;\n if (range <= 1) return undefined;\n // How far the row has scrolled from its start; scrollLeft runs negative in a right-to-left row.\n const traveled = Math.abs(row.scrollLeft);\n const start = traveled > 1;\n const end = traveled < range - 1;\n const rtl = getComputedStyle(row).direction === \"rtl\";\n const left = rtl ? end : start;\n const right = rtl ? start : end;\n if (left && right) return \"both\";\n if (left) return \"left\";\n return right ? \"right\" : undefined;\n}\n\n/**\n * Scrolls `row` itself, never the page, just enough that `tab` is wholly in view inside the row's\n * scroll padding, which keeps it clear of the fades. A tab wider than that shows its start.\n */\nexport function revealTab(row: HTMLElement, tab: HTMLElement): void {\n const style = getComputedStyle(row);\n const rowBox = row.getBoundingClientRect();\n const tabBox = tab.getBoundingClientRect();\n const inner = rowBox.left + row.clientLeft;\n const visibleLeft = inner + (Number.parseFloat(style.scrollPaddingLeft) || 0);\n const visibleRight = inner + row.clientWidth - (Number.parseFloat(style.scrollPaddingRight) || 0);\n let delta = 0;\n if (tabBox.left < visibleLeft) delta = tabBox.left - visibleLeft;\n else if (tabBox.right > visibleRight)\n delta = Math.min(tabBox.right - visibleRight, tabBox.left - visibleLeft);\n if (delta !== 0) row.scrollLeft += delta;\n}\n\n/**\n * A horizontal tab row that does not fit scrolls sideways inside itself (design call DC-14). This\n * keeps the selected tab in view, when the row opens and whenever the selection changes, and says\n * which sides hide tabs so each can show a fade. React Aria already scrolls the row as the arrow\n * keys, Home and End move focus; this adds the selection by pointer or by the application, and\n * the first view. Until someone scrolls the row by hand (a wheel or a swipe), a change of size,\n * such as a font arriving, keeps the selected tab in view too.\n */\nexport function useTabsScroll(\n list: RefObject<HTMLDivElement | null>,\n vertical: boolean,\n): TabsFade | undefined {\n const [fade, setFade] = useState<TabsFade>();\n useIsomorphicLayoutEffect(() => {\n const row = list.current;\n if (!row || vertical) {\n setFade(undefined);\n return;\n }\n let alive = true;\n let revealed: Element | null = null;\n let pinned = true;\n const update = () => {\n if (alive) setFade(hiddenSides(row));\n };\n const selectedTab = () => row.querySelector<HTMLElement>('[role=\"tab\"][data-selected]');\n const reveal = () => {\n const selected = selectedTab();\n revealed = selected;\n pinned = true;\n if (selected) revealTab(row, selected);\n update();\n };\n const unpin = () => {\n pinned = false;\n };\n // When focus comes into the row from outside, React Aria puts the row back where it last saw\n // it scrolled. A press on a half-hidden tab does that just after it selects the tab, which\n // undid the reveal. Its handler runs after this one (React listens at the root), so the reveal\n // waits a frame. Focus moving between tabs is React Aria's to follow, and is left alone.\n let frame = 0;\n const entered = (event: FocusEvent) => {\n if (event.relatedTarget instanceof Node && row.contains(event.relatedTarget)) return;\n cancelAnimationFrame(frame);\n frame = requestAnimationFrame(() => {\n const selected = selectedTab();\n if (pinned && selected) revealTab(row, selected);\n update();\n });\n };\n const resized = () => {\n const selected = selectedTab();\n if (pinned && selected) revealTab(row, selected);\n update();\n };\n // Where there is no ResizeObserver the row still reveals on selection and fades on scroll.\n const resize = createResizeObserver(resized);\n const watched = new Set<Element>();\n const observe = () => {\n if (!resize) return;\n const current = new Set<Element>([row, ...row.querySelectorAll('[role=\"tab\"]')]);\n for (const element of watched)\n if (!current.has(element)) {\n resize.unobserve(element);\n watched.delete(element);\n }\n for (const element of current)\n if (!watched.has(element)) {\n resize.observe(element);\n watched.add(element);\n }\n };\n observe();\n reveal();\n row.addEventListener(\"scroll\", update, { passive: true });\n row.addEventListener(\"focusin\", entered);\n row.addEventListener(\"wheel\", unpin, { passive: true });\n row.addEventListener(\"touchmove\", unpin, { passive: true });\n const mutation = new MutationObserver((records) => {\n if (records.some((record) => record.type === \"childList\")) observe();\n if (selectedTab() !== revealed) reveal();\n else update();\n });\n mutation.observe(row, {\n subtree: true,\n childList: true,\n attributes: true,\n attributeFilter: [\"data-selected\"],\n });\n return () => {\n alive = false;\n resize?.disconnect();\n mutation.disconnect();\n cancelAnimationFrame(frame);\n row.removeEventListener(\"scroll\", update);\n row.removeEventListener(\"focusin\", entered);\n row.removeEventListener(\"wheel\", unpin);\n row.removeEventListener(\"touchmove\", unpin);\n };\n }, [list, vertical]);\n return fade;\n}\n","TagGroup/index.ts":"export { TagGroup, type TagGroupProps, type TagItem } from \"./TagGroup\";\nexport { TagGroupManifest } from \"./TagGroup.manifest\";\n","TagGroup/TagGroup.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const TagGroupManifest: Record<string, Component> = {\n TagGroup: {\n description:\n \"A labeled collection of tags with optional controlled removal, native keyboard navigation and authored label casing.\",\n props: {\n items: {\n type: \"array\",\n required: true,\n description: \"Readonly TagItem[]: unique string id, label and optional disabled.\",\n },\n label: {\n type: \"string\",\n required: true,\n description: \"Accessible name for the tag collection.\",\n },\n onRemove: {\n type: \"function\",\n description:\n \"Requests removal by id. The parent supplies the updated items; declined removals retain the item and focus.\",\n },\n disabled: { type: \"boolean\", default: false },\n emptyMessage: { type: \"string\", description: \"Optional message when items is empty.\" },\n returnFocusRef: {\n type: \"node\",\n description:\n \"RefObject<HTMLElement | null> focused when the application removes the last tag while focus is in the group, on the next render or after a server round trip. An operator who has moved on keeps their focus.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"PropertyFilter\"],\n never_inside: [\"Button\"],\n example:\n '<TagGroup label=\"Active filters\" items={filters} onRemove={(id) => setFilters(filters.filter((item) => item.id !== id))} returnFocusRef={addButtonRef} />',\n },\n};\n","TagGroup/TagGroup.tsx":"\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n type HTMLAttributes,\n type RefObject,\n} from \"react\";\nimport { Button, TagGroup as AriaTagGroup, TagList, Tag, type Key } from \"react-aria-components\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { X } from \"@oprt/icons\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\n\nconst groupStyles = cva(\"min-w-0 font-sans normal-case text-oprt-base font-medium text-oprt-text\");\nconst tagStyles = cva(\n \"flex min-h-oprt-sm min-w-0 max-w-full items-center gap-1u border border-oprt-border bg-oprt-surface-2 pl-1u outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[disabled]:opacity-50\",\n);\nconst removeStyles = cva(\n \"flex h-oprt-sm w-oprt-sm shrink-0 items-center justify-center border border-transparent bg-transparent text-oprt-text outline-hidden hover:bg-oprt-surface-3 data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus disabled:cursor-not-allowed\",\n);\n\nexport type TagItem = { id: string; label: string; disabled?: boolean };\nexport type TagGroupProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n items: readonly TagItem[];\n label: string;\n onRemove?: (id: string) => void;\n disabled?: boolean;\n emptyMessage?: string;\n returnFocusRef?: RefObject<HTMLElement | null>;\n className?: string;\n};\n\n/** What the focus helper remembers: the id of the tag that holds focus. */\ntype Focused = { id: string };\n\n/** Naming and description props, which React Aria puts on the tag list, where they belong. */\nconst LIST_PROPS = [\"aria-labelledby\", \"aria-describedby\", \"aria-details\"] as const;\n\n// Dev only: once per prop, so a re-render does not repeat it. The message is written after the\n// production check, and the call sits under one, so a production bundle drops both.\nconst reportedNotApplied = new Set<string>();\nfunction reportNotApplied(prop: string) {\n if (process.env.NODE_ENV === \"production\" || reportedNotApplied.has(prop)) return;\n reportedNotApplied.add(prop);\n const suggestion =\n prop === \"aria-label\"\n ? \"TagGroup's `label` names the tag list; an aria-label is not applied. Pass the name as `label`.\"\n : \"TagGroup draws its own tags; dangerouslySetInnerHTML is not applied. Supply the tags as `items`.\";\n console.error(\n JSON.stringify(\n { component: \"TagGroup\", error: \"PROP_NOT_APPLIED\", prop, received: prop, suggestion },\n null,\n 2,\n ),\n );\n}\n\nexport const TagGroup = forwardRef<HTMLDivElement, TagGroupProps>(\n (\n {\n items,\n label,\n onRemove,\n disabled = false,\n emptyMessage,\n returnFocusRef,\n className,\n \"aria-label\": ariaLabel,\n dangerouslySetInnerHTML,\n ...props\n },\n ref,\n ) => {\n const rootRef = useRef<HTMLDivElement>(null);\n useImperativeHandle(ref, () => rootRef.current!, []);\n\n // Removal is a request: the tag stays until the application supplies items without it, on the\n // next render or after a server round trip. When the last tag goes while focus is in the group,\n // focus moves to the supplied return control, however long the application took (TagGroup-R1);\n // an operator who has moved on keeps their focus. A tag is gone once the application's items\n // leave it out: React Aria removes its element in a later render of its own, which does not run\n // this check, so the tag still holds focus here. Between remaining tags, React Aria moves focus.\n const { onFocusCapture, onBlurCapture } = useRestoreFocusOnRemoval<HTMLDivElement, Focused>(\n rootRef,\n {\n track: (element) => {\n const id = element.closest<HTMLElement>('[role=\"row\"][data-key]')?.dataset[\"key\"];\n return id === undefined ? null : { id };\n },\n isGone: (_element, { id }) => !items.some((item) => item.id === id),\n findTarget: () =>\n items.length === 0 &&\n !disabled &&\n onRemove &&\n rootRef.current?.contains(document.activeElement)\n ? (returnFocusRef?.current ?? null)\n : null,\n },\n );\n // The props type is the div's, and is never narrowed (ruling on the B7 review's Minor 4). React\n // Aria passes only some of them to its root; the rest go on through `render`, below. Two cannot\n // apply: `label` names the tag list, and TagGroup draws its own content.\n const notApplied = [\n ariaLabel !== undefined && \"aria-label\",\n dangerouslySetInnerHTML !== undefined && \"dangerouslySetInnerHTML\",\n ]\n .filter(Boolean)\n .join(\" \");\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n for (const prop of notApplied ? notApplied.split(\" \") : []) reportNotApplied(prop);\n }, [notApplied]);\n // Everything goes to React Aria, which places what it knows: id and data-* on the root, the\n // naming and description props on the tag list. The rest is added to the root in `render`.\n const rootProps: HTMLAttributes<HTMLDivElement> = { ...props };\n for (const name of LIST_PROPS) delete rootProps[name];\n\n function remove(keys: Set<Key>) {\n if (disabled || !onRemove) return;\n for (const key of keys) {\n const item = items.find((candidate) => candidate.id === key);\n if (!item || item.disabled) continue;\n onRemove(item.id);\n }\n }\n\n return (\n <AriaTagGroup\n {...(props as Record<string, unknown>)}\n ref={rootRef}\n aria-label={label}\n selectionMode=\"none\"\n className={clsx(groupStyles(), className)}\n {...(onRemove ? { onRemove: remove } : {})}\n // The root is drawn here so every div prop React Aria leaves out (focus, blur and key\n // handlers, title, tabIndex, role) still reaches it, and so focus restoration can listen on\n // it: React Aria's own props win where both set the same one.\n render={(domProps) => (\n <div\n {...rootProps}\n {...domProps}\n onFocusCapture={(event) => {\n rootProps.onFocusCapture?.(event);\n onFocusCapture(event);\n }}\n onBlurCapture={(event) => {\n rootProps.onBlurCapture?.(event);\n onBlurCapture(event);\n }}\n />\n )}\n >\n <TagList\n items={items}\n dependencies={[disabled, !!onRemove]}\n className=\"flex min-w-0 flex-wrap gap-1u outline-hidden data-[focus-visible]:shadow-oprt-focus\"\n renderEmptyState={() =>\n emptyMessage ? <span className=\"text-oprt-text-dim\">{emptyMessage}</span> : null\n }\n >\n {(item) => (\n <Tag\n id={item.id}\n textValue={item.label}\n isDisabled={disabled || !!item.disabled}\n className={tagStyles()}\n >\n <span className=\"min-w-0 whitespace-pre-wrap oprt-wrap-anywhere py-0.5u pr-1u\">\n {item.label}\n </span>\n {onRemove && (\n <Button\n slot=\"remove\"\n isDisabled={disabled || !!item.disabled}\n className={removeStyles()}\n >\n <X size={12} aria-hidden=\"true\" />\n </Button>\n )}\n </Tag>\n )}\n </TagList>\n </AriaTagGroup>\n );\n },\n);\nTagGroup.displayName = \"TagGroup\";\n","Textarea/index.ts":"export { Textarea, type TextareaProps } from \"./Textarea\";\nexport { TextareaManifest } from \"./Textarea.manifest\";\n","Textarea/Textarea.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const TextareaManifest: Record<string, Component> = {\n Textarea: {\n description:\n \"Multi-line text input. Fixed rows (default 4); scroll on overflow. No native resize handle (rounded grip violates radius rule).\",\n props: {\n rows: { type: \"number\", default: 4 },\n value: { type: \"string\" },\n defaultValue: { type: \"string\" },\n placeholder: { type: \"string\" },\n disabled: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n invalid: {\n type: \"boolean\",\n description: \"Override Field's invalid state.\",\n },\n name: { type: \"string\" },\n },\n composable_with: [\"Field\"],\n never_inside: [\"Button\", \"Badge\"],\n example:\n '<Field label=\"MISSION NOTES\" help=\"Free-form text.\">\\n <Textarea placeholder=\"ENTER NOTES...\" />\\n</Field>',\n },\n};\n","Textarea/Textarea.tsx":"\"use client\";\n\nimport { forwardRef, type TextareaHTMLAttributes } from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { useFieldWiring } from \"../_internal/fieldIds\";\nimport { FORCED_FOCUS_WITHIN, FORCED_NO_RESTING_OUTLINE } from \"../_internal/controlStyles\";\n\nconst wrapperStyles = cva(\n // Disabled looks like DC-9's disabled buttons (coordinator ruling on DC-B04-2): dim text, the\n // control outline and a muted fill, with no fade of the whole field.\n \"flex bg-oprt-surface-2 border normal-case transition-colors duration-oprt-fast focus-within:shadow-oprt-focus has-[textarea:disabled]:cursor-not-allowed has-[textarea:disabled]:bg-oprt-surface-3 has-[textarea:disabled]:border-oprt-border-control px-2u py-2u\",\n {\n variants: {\n invalid: {\n true: \"border-oprt-error focus-within:border-oprt-error\",\n // The control outline (design call DC-3); focus in the focus token, as the siblings do.\n false: \"border-oprt-border-control focus-within:border-oprt-border-focus\",\n },\n },\n defaultVariants: { invalid: false },\n },\n);\n\nconst textareaStyles =\n \"w-full bg-transparent border-0 outline-hidden resize-none font-sans font-medium text-oprt-md text-oprt-text placeholder:text-oprt-text-placeholder disabled:placeholder:text-oprt-text-dim disabled:cursor-not-allowed disabled:text-oprt-text-dim\";\n\nexport type TextareaProps = TextareaHTMLAttributes<HTMLTextAreaElement> & {\n invalid?: boolean;\n};\n\nexport const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(\n (\n {\n className,\n invalid,\n rows = 4,\n id,\n required,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n ...props\n },\n ref,\n ) => {\n // The shared Field rules (Stage 1 Field-R2): `invalid`, then aria-invalid, then the Field's\n // error. aria-invalid and aria-describedby are kept out of the spread below, so what is\n // announced is what is drawn (Textarea-R2).\n const wiring = useFieldWiring(\n { id, \"aria-describedby\": ariaDescribedBy, \"aria-invalid\": ariaInvalid, invalid, required },\n { label: \"for\" },\n );\n\n return (\n // In forced colors focus is a 2px CanvasText outline around the field (Textarea-B5-1).\n <div\n className={clsx(wrapperStyles({ invalid: wiring.invalid }), FORCED_FOCUS_WITHIN, className)}\n >\n <textarea\n ref={ref}\n id={wiring.id}\n rows={rows}\n aria-invalid={wiring.ariaInvalid}\n aria-describedby={wiring.describedBy}\n required={wiring.required}\n className={clsx(textareaStyles, FORCED_NO_RESTING_OUTLINE)}\n {...props}\n />\n </div>\n );\n },\n);\nTextarea.displayName = \"Textarea\";\n","TimeField/index.ts":"export { TimeField } from \"./TimeField\";\nexport type { TimeFieldProps } from \"./TimeField\";\nexport { TimeFieldManifest } from \"./TimeField.manifest\";\n","TimeField/TimeField.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const TimeFieldManifest: Record<string, Component> = {\n TimeField: {\n description:\n \"Controlled 24-hour wall-time field with segmented editing and explicit draft validity. It carries no date or time zone.\",\n props: {\n label: { type: \"string\", required: true },\n value: {\n type: \"string\",\n required: true,\n description: \"HH:mm from 00:00 through 23:59, or null.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description: \"Requests a complete valid time or null after clearing.\",\n },\n onDraftValidityChange: {\n type: \"function\",\n description:\n \"Callback (valid, { pending }). Pending identifies uncommitted local edits; pristine required-empty or malformed supplied values are invalid but not pending. Reports initial and changed validity. False for partial, out-of-bounds, or required-empty drafts even while value still contains an earlier valid time. Block Review/Save while false.\",\n },\n min: { type: \"string\", description: \"Inclusive HH:mm lower bound.\" },\n max: {\n type: \"string\",\n description: \"Inclusive HH:mm upper bound. Overnight bounds are not supported.\",\n },\n disabled: { type: \"boolean\", default: false },\n readOnly: { type: \"boolean\", default: false },\n required: { type: \"boolean\", default: false },\n id: {\n type: \"string\",\n description:\n \"ID of the focusable time-control group. Focusing it or its forwarded HTMLDivElement ref enters the first segment.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"FormSection\", \"FormErrorSummary\", \"DateTimePicker\"],\n never_inside: [\"Button\"],\n example:\n '<TimeField id=\"start-time\" label=\"Start time\" value={time} onValueChange={setTime} onDraftValidityChange={setTimeValid} required />',\n },\n};\n","TimeField/TimeField.tsx":"\"use client\";\n\nimport { forwardRef, useContext, useEffect, useId, useMemo, useRef } from \"react\";\nimport {\n TimeField as AriaTimeField,\n TimeFieldStateContext,\n DateInput,\n DateSegment,\n Label,\n Text,\n} from \"react-aria-components\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport type { CalendarProps } from \"../Calendar/Calendar\";\nimport {\n formatTimeOnly,\n parseTimeOnly,\n readTimeSegments,\n timeConfiguration,\n type DateDraftSnapshot,\n} from \"../_internal/date\";\nimport { useHydrationRoot } from \"../_internal/hydration\";\nimport {\n FIELD_BOX_STYLES,\n FIELD_LABEL_STYLES,\n SEGMENT_STYLES,\n SegmentGroupContext,\n firstSegmentFocus,\n useReportSegments,\n useSegmentDraft,\n} from \"../_internal/segmentField\";\nexport type TimeFieldProps = CalendarProps & {\n onDraftValidityChange?: (valid: boolean, details: { pending: boolean }) => void;\n};\nconst fieldStyles = cva(\"flex min-w-0 flex-col gap-1u font-sans text-oprt-base text-oprt-text\");\nfunction DraftObserver({ report }: { report: (snapshot: DateDraftSnapshot) => void }) {\n const state = useContext(TimeFieldStateContext)!;\n useReportSegments(state.segments, readTimeSegments, report);\n return null;\n}\nexport const TimeField = forwardRef<HTMLDivElement, TimeFieldProps>(\n (\n {\n label,\n value,\n onValueChange,\n onDraftValidityChange,\n min,\n max,\n disabled = false,\n readOnly = false,\n required = false,\n id,\n className,\n },\n ref,\n ) => {\n const generated = useId();\n const inputId = id ?? `oprt-time-${generated}`;\n const hydration = useHydrationRoot(inputId);\n const config = useMemo(() => timeConfiguration(value, min, max), [value, min, max]);\n const displayedValue = config.time ? formatTimeOnly(config.time) : null;\n // Inside DateTimePicker: a required group marks these segments required (DateRangePicker-R3).\n const group = useContext(SegmentGroupContext);\n const draft = useSegmentDraft({\n value,\n displayed: displayedValue,\n valid: value === null || !!config.time,\n inactive: disabled || readOnly || config.invalidBounds,\n });\n const time = parseTimeOnly(draft.snapshot.value);\n const valid =\n draft.snapshot.valid &&\n !draft.rejected &&\n (draft.snapshot.empty\n ? !required\n : !!time && time.compare(config.min) >= 0 && time.compare(config.max) <= 0);\n const error =\n config.error ??\n (!valid\n ? draft.snapshot.empty && required\n ? \"A time is required.\"\n : // The bound an edit crossed, named as for a supplied value (TimeField-R1).\n (draft.rejection ?? \"Enter a complete, valid time within the allowed bounds.\")\n : null);\n const callback = useRef(onDraftValidityChange);\n callback.current = onDraftValidityChange;\n const pending = draft.pending;\n useEffect(() => callback.current?.(!error, { pending }), [error, pending]);\n return (\n <div {...draft.editProps}>\n <AriaTimeField\n key={hydration.key}\n {...hydration.probeProps}\n id={inputId}\n value={config.time}\n minValue={config.min}\n maxValue={config.max}\n hourCycle={24}\n granularity=\"minute\"\n isDisabled={disabled || config.invalidBounds}\n isReadOnly={readOnly}\n isRequired={required || !!group?.required}\n isInvalid={!!error}\n validationBehavior=\"aria\"\n className={clsx(fieldStyles(), className)}\n onChange={(next) => {\n if (disabled || readOnly || config.invalidBounds || draft.blurRejected()) return;\n const nextValue = next ? formatTimeOnly(next) : null;\n const nextTime = parseTimeOnly(nextValue);\n if (\n next &&\n (!nextTime || nextTime.compare(config.min) < 0 || nextTime.compare(config.max) > 0)\n ) {\n draft.reject(nextTime ? timeConfiguration(nextValue, min, max).error : null);\n return;\n }\n draft.accept();\n onValueChange(nextValue);\n }}\n >\n <Label className={FIELD_LABEL_STYLES}>\n {label}\n {/* Shown, not read: aria-required says it (DatePicker-R2). */}\n {required && <span aria-hidden=\"true\"> *</span>}\n </Label>\n <DraftObserver report={draft.report} />\n {/* DateInput forwards these native Group props; RAC 1.20 omits them from DateInputProps. */}\n <DateInput ref={ref} {...firstSegmentFocus(disabled)} className={FIELD_BOX_STYLES}>\n {(segment) => <DateSegment segment={segment} className={SEGMENT_STYLES} />}\n </DateInput>\n {error && (\n <Text slot=\"errorMessage\" className=\"m-0 text-oprt-error\">\n {error}\n </Text>\n )}\n </AriaTimeField>\n </div>\n );\n },\n);\nTimeField.displayName = \"TimeField\";\n","TimeRange/index.ts":"export {\n TimeRange,\n type TimeRangeProps,\n type TimeRangeValue,\n type TimeRangePreset,\n} from \"./TimeRange\";\nexport { TimeRangeManifest } from \"./TimeRange.manifest\";\n","TimeRange/TimeRange.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const TimeRangeManifest: Record<string, Component> = {\n TimeRange: {\n description:\n \"Controlled UTC range with separate native date and time fields, OPERATOR Calendar/Clock icons, validated drafts, an Apply action, and optional presets. One line under the fields gives the draft range in 24-hour UTC with milliseconds, and an edit not yet applied is said so.\",\n props: {\n value: {\n type: \"node\",\n required: true,\n description:\n \"TimeRangeValue: { start: number; end: number }, Unix epoch milliseconds. Start must precede end; dates must be in years 0001–9999.\",\n },\n onValueChange: {\n type: \"function\",\n required: true,\n description:\n \"(value: TimeRangeValue) => void. Only called on Apply, Enter or a valid preset; never for invalid drafts.\",\n },\n onDraftValidityChange: {\n type: \"function\",\n description:\n \"(valid: boolean, details: { pending: boolean }) => void. Reports on mount and on every change whether the range in the fields can be used as it stands; valid is false while they hold an error or an edit not yet applied (pending: true). Connect it to Review and Save, as with DateRangePicker.\",\n },\n presets: {\n type: \"array\",\n description:\n \"Readonly TimeRangePreset array. Invalid or out-of-bounds presets are disabled.\",\n items: {\n id: \"string (required, unique)\",\n label: \"string (required)\",\n value: \"TimeRangeValue (required)\",\n },\n },\n min: {\n type: \"number\",\n description:\n \"Inclusive minimum epoch milliseconds. Invalid dates or conflicting bounds prevent commits.\",\n },\n max: { type: \"number\", description: \"Inclusive maximum epoch milliseconds.\" },\n disabled: { type: \"boolean\", default: false },\n label: {\n type: \"string\",\n default: \"Time range\",\n description: \"Accessible name of the range group; each input also has a visible UTC label.\",\n },\n },\n composable_with: [\"EventTimeline\", \"Chart\", \"DataGrid\"],\n never_inside: [\"Button\"],\n example:\n '<TimeRange value={range} onValueChange={setRange} min={windowStart} max={windowEnd} presets={[{ id: \"all\", label: \"Full window\", value: { start: windowStart, end: windowEnd } }]} />',\n },\n};\n","TimeRange/TimeRange.tsx":"\"use client\";\n\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { forwardRef, useEffect, useRef, useState, type HTMLAttributes } from \"react\";\nimport { Input } from \"../Input/Input\";\nimport { Button } from \"../Button/Button\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\nimport { formatUtcTimestamp, isValidTimestamp } from \"../_internal/timestamp\";\n\nconst rangeStyles = cva(\"flex min-w-0 flex-col gap-2u font-sans text-oprt-text\");\n\n/**\n * A field asks for 176 px, room for a native date or time input with milliseconds, and never more\n * than its row, so the fields pair up or stack by the component's own width, not the window's (R1).\n */\nconst fieldStyles =\n \"flex min-w-[min(100%,calc(22*var(--oprt-space-1)))] grow basis-[calc(22*var(--oprt-space-1))] flex-col gap-1u\";\n\n/**\n * The preset that matches the applied range is pressed, and looks it: FilterBar's selected fill\n * (TimeRange-R2). Disabled, it keeps a muted fill, so it still reads as the current range (DC-9).\n * Forced colors repaint fills and borders, so there it is drawn from Highlight (GrayText when\n * disabled), and its focus, which would otherwise vanish, becomes an outline.\n */\nconst pressedPresetStyles = [\n \"aria-[pressed=true]:border-oprt-accent-light aria-[pressed=true]:bg-oprt-accent aria-[pressed=true]:text-oprt-bg\",\n \"aria-[pressed=true]:disabled:border-oprt-border-control aria-[pressed=true]:disabled:bg-oprt-surface-3 aria-[pressed=true]:disabled:text-oprt-text-dim\",\n \"forced-colors:aria-[pressed=true]:[forced-color-adjust:none] forced-colors:aria-[pressed=true]:border-[color:Highlight] forced-colors:aria-[pressed=true]:bg-[color:Highlight] forced-colors:aria-[pressed=true]:text-[color:HighlightText]\",\n \"forced-colors:aria-[pressed=true]:disabled:border-[color:GrayText] forced-colors:aria-[pressed=true]:disabled:bg-[color:GrayText] forced-colors:aria-[pressed=true]:disabled:text-[color:Canvas]\",\n \"forced-colors:aria-[pressed=true]:focus-visible:outline-solid forced-colors:aria-[pressed=true]:focus-visible:outline-2 forced-colors:aria-[pressed=true]:focus-visible:outline-offset-2 forced-colors:aria-[pressed=true]:focus-visible:outline-[color:CanvasText]\",\n].join(\" \");\n\nexport interface TimeRangeValue {\n start: number;\n end: number;\n}\nexport interface TimeRangePreset {\n id: string;\n label: string;\n value: TimeRangeValue;\n}\nexport type TimeRangeProps = Omit<HTMLAttributes<HTMLDivElement>, \"onChange\"> & {\n value: TimeRangeValue;\n onValueChange: (value: TimeRangeValue) => void;\n /**\n * Reports whether the range in the fields can be used as it stands, and whether it is an edit\n * the application has not applied yet: `(valid, { pending })`, on mount and whenever either\n * changes, as DateRangePicker and DateTimePicker do. `valid` is false while the fields hold an\n * error or an unapplied edit.\n */\n onDraftValidityChange?: (valid: boolean, details: { pending: boolean }) => void;\n presets?: readonly TimeRangePreset[];\n min?: number;\n max?: number;\n disabled?: boolean;\n label?: string;\n};\n\ntype Endpoint = { date: string; time: string };\ntype Draft = { start: Endpoint; end: Endpoint };\nconst PARTS = [\"start\", \"end\"] as const;\nconst FIELDS = [\"date\", \"time\"] as const;\n\n// Date and time edits are explicitly interpreted as UTC, never the browser zone. The fields can\n// only express years 0001 through 9999.\nconst dateIsValid = (value: number) => isValidTimestamp(value, { years: \"1-9999\" });\nconst toInput = (value: number) =>\n dateIsValid(value) ? new Date(value).toISOString().slice(0, -1) : \"\";\nconst toEndpoint = (value: number): Endpoint => {\n const [date = \"\", time = \"\"] = toInput(value).split(\"T\");\n return { date, time };\n};\nconst draftOf = (range: TimeRangeValue): Draft => ({\n start: toEndpoint(range.start),\n end: toEndpoint(range.end),\n});\nconst parseInput = (value: string) => {\n if (!/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}(?::\\d{2}(?:\\.\\d{1,3})?)?$/.test(value)) return NaN;\n const parsed = Date.parse(`${value}Z`);\n // Date.parse rolls invalid month-days over; require an exact calendar match.\n return dateIsValid(parsed) && toInput(parsed).slice(0, 16) === value.slice(0, 16) ? parsed : NaN;\n};\nconst parseEndpoint = ({ date, time }: Endpoint) => parseInput(`${date}T${time}`);\nconst validate = ({ start, end }: TimeRangeValue, min?: number, max?: number) => {\n if (\n (min !== undefined && !dateIsValid(min)) ||\n (max !== undefined && !dateIsValid(max)) ||\n (min !== undefined && max !== undefined && min >= max)\n )\n return \"Time bounds must be valid UTC dates with minimum before maximum.\";\n if (!dateIsValid(start) || !dateIsValid(end)) return \"Enter a valid start and end date in UTC.\";\n if (start >= end) return \"Start must be before end.\";\n if ((min !== undefined && start < min) || (max !== undefined && end > max))\n return \"The range must be within the allowed time bounds.\";\n return \"\";\n};\n/** Whether an endpoint in the fields says what the applied value says, however it is typed. */\nconst unchanged = (edited: Endpoint, parsed: number, applied: Endpoint, instant: number) =>\n (edited.date === applied.date && edited.time === applied.time) ||\n (!Number.isNaN(parsed) && dateIsValid(instant) && parsed === Math.floor(instant));\n/**\n * The range in 24-hour UTC, with the milliseconds a sequence window needs (DC-4): \"2026-10-02\n * 14:00:00.000 UTC to 15:00:00.000 UTC\". The end leaves out its date when it is the same day.\n */\nfunction describeRange(start: number, end: number): string | null {\n const from = formatUtcTimestamp(start, { precision: \"milliseconds\" });\n const to = formatUtcTimestamp(end, { precision: \"milliseconds\" });\n if (!from || !to) return null;\n const sameDay = from.slice(0, 10) === to.slice(0, 10);\n return `${from} to ${sameDay ? to.slice(11) : to}`;\n}\n\nexport const TimeRange = forwardRef<HTMLDivElement, TimeRangeProps>(\n (\n {\n value,\n onValueChange,\n onDraftValidityChange,\n presets = [],\n min,\n max,\n disabled = false,\n label = \"Time range\",\n className,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n const setRoot = useMergedRefs<HTMLDivElement>(root, ref);\n // Part ids reconciled with the markup they hydrated (fieldWiringPlan step 6).\n const ids = usePartIds(\n [\"start-date\", \"start-time\", \"end-date\", \"end-time\", \"error\", \"range\", \"status\"],\n { readRendered: () => root.current?.getAttribute(\"data-oprt-ids\") },\n );\n // The draft follows the value in the same render (TimeRange-R4): an edit belongs to the value\n // it was made against, and a new value replaces it at once, so a range and bounds that move\n // together are never checked against each other's old halves.\n const base = `${value.start}|${value.end}`;\n const applied = draftOf(value);\n const [draft, setDraft] = useState<Draft & { base: string }>(() => ({ base, ...applied }));\n const current: Draft = draft.base === base ? draft : applied;\n const parsed = { start: parseEndpoint(current.start), end: parseEndpoint(current.end) };\n const error = validate(parsed, min, max);\n const pending = !(\n unchanged(current.start, parsed.start, applied.start, value.start) &&\n unchanged(current.end, parsed.end, applied.end, value.end)\n );\n const valid = !error && !pending;\n const callback = useRef(onDraftValidityChange);\n callback.current = onDraftValidityChange;\n useEffect(() => callback.current?.(valid, { pending }), [valid, pending]);\n const readout =\n Number.isNaN(parsed.start) || Number.isNaN(parsed.end)\n ? null\n : describeRange(parsed.start, parsed.end);\n const unapplied = !error && pending;\n const edit = (part: (typeof PARTS)[number], field: (typeof FIELDS)[number], next: string) =>\n setDraft({ ...current, base, [part]: { ...current[part], [field]: next } });\n const commit = () => {\n if (!disabled && !error) onValueChange(parsed);\n };\n return (\n <div\n {...props}\n ref={setRoot}\n role=\"group\"\n aria-label={label}\n aria-describedby={joinIds(\n props[\"aria-describedby\"],\n readout && ids.range,\n unapplied && ids.status,\n )}\n data-oprt-ids={ids.base}\n className={clsx(rangeStyles(), className)}\n >\n <div className=\"flex flex-wrap items-end gap-2u\">\n {PARTS.map((part) => (\n <div key={part} className=\"flex min-w-0 grow flex-wrap gap-1u\">\n {FIELDS.map((field) => (\n <div key={field} className={fieldStyles}>\n <label\n htmlFor={ids[`${part}-${field}`]}\n className=\"m-0 text-oprt-base font-medium uppercase tracking-oprt-caps\"\n >\n {part === \"start\" ? \"Start\" : \"End\"} {field} (UTC)\n </label>\n <Input\n id={ids[`${part}-${field}`]}\n type={field}\n step={field === \"time\" ? \"0.001\" : undefined}\n value={current[part][field]}\n min={field === \"date\" && min !== undefined ? toEndpoint(min).date : undefined}\n max={field === \"date\" && max !== undefined ? toEndpoint(max).date : undefined}\n disabled={disabled}\n aria-invalid={!!error}\n aria-describedby={error ? ids.error : undefined}\n className=\"w-full [&_input]:font-mono\"\n onChange={(event) => edit(part, field, event.target.value)}\n onKeyDown={(event) => {\n if (event.key === \"Enter\") {\n event.preventDefault();\n commit();\n }\n }}\n />\n </div>\n ))}\n </div>\n ))}\n <Button disabled={disabled} onClick={commit}>\n Apply range\n </Button>\n </div>\n {readout && (\n <p\n id={ids.range}\n className=\"m-0 font-mono text-oprt-base text-oprt-text-dim oprt-wrap-anywhere\"\n >\n {readout}\n </p>\n )}\n {error && (\n <p id={ids.error} role=\"alert\" className=\"m-0 text-oprt-base text-oprt-error\">\n {error}\n </p>\n )}\n {unapplied && (\n <p id={ids.status} className=\"m-0 text-oprt-base text-oprt-text-dim\">\n The range change has not been applied.\n </p>\n )}\n {presets.length > 0 && (\n <div role=\"group\" aria-label=\"Preset time ranges\" className=\"flex flex-wrap gap-1u\">\n {presets.map((preset) => (\n <Button\n key={preset.id}\n size=\"sm\"\n variant=\"tertiary\"\n className={pressedPresetStyles}\n aria-pressed={preset.value.start === value.start && preset.value.end === value.end}\n disabled={disabled || !!validate(preset.value, min, max)}\n onClick={() => {\n if (!disabled && !validate(preset.value, min, max)) {\n setDraft({ base, ...draftOf(preset.value) });\n onValueChange({ ...preset.value });\n }\n }}\n >\n {preset.label}\n </Button>\n ))}\n </div>\n )}\n </div>\n );\n },\n);\nTimeRange.displayName = \"TimeRange\";\n","Toast/index.ts":"export { ToastProvider, Toast, type ToastProviderProps, type ToastProps } from \"./Toast\";\nexport { ToastManifest, ToastCompositionRules } from \"./Toast.manifest\";\n","Toast/Toast.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ToastManifest: Record<string, Component> = {\n ToastProvider: {\n description:\n \"Wraps the app and renders the notification region, a fixed corner of the window where Toast notifications stack and announce. The region never outgrows the window: a tall stack scrolls inside it, and a new notice is scrolled into view unless the reader has scrolled away from the newest.\",\n props: {\n label: {\n type: \"string\",\n default: \"Notifications\",\n description:\n \"Names the notification region, as '<label> (F8)', F8 being the key that moves focus to it, and is spoken before each announcement. Set it for a translated or more specific name.\",\n },\n swipeDirection: {\n type: \"enum\",\n values: [\"right\", \"left\", \"up\", \"down\"],\n default: \"right\",\n description:\n \"The direction a touch swipe dismisses a toast; a mouse or pen drag selects text instead. With right or left, a vertical touch drag on a toast scrolls the region; with up or down, that drag is the swipe, so the region does not scroll by dragging a toast.\",\n },\n duration: {\n type: \"number\",\n description: \"Milliseconds before a toast closes itself. Defaults to 5000.\",\n },\n },\n slots: {\n children: { type: \"Toast\", required: true },\n },\n never_inside: [\"ToastProvider\", \"Toast\"],\n example:\n '<ToastProvider>\\n <Toast variant=\"success\" title=\"DEPLOYED\" description=\"Build promoted to prod.\" open={open} onOpenChange={setOpen} />\\n</ToastProvider>',\n },\n Toast: {\n description:\n \"A single transient notification with a severity icon, title, optional description, and dismiss control. It closes itself after its duration or when dismissed, by calling onOpenChange(false); the application removes it.\",\n props: {\n variant: {\n type: \"enum\",\n values: [\"info\", \"success\", \"warning\", \"error\"],\n default: \"info\",\n description:\n \"Severity. Drives the icon, the accent color and, unless type is set, how the toast is announced: error and warning interrupt a screen reader, info and success wait.\",\n },\n title: { type: \"string\", required: true },\n description: { type: \"string\" },\n open: {\n type: \"boolean\",\n description:\n \"Controlled open state. A toast that is open and never closed stays on screen: pass onOpenChange and remove the toast, or clear open, when it is called.\",\n },\n defaultOpen: {\n type: \"boolean\",\n default: true,\n description: \"Initial open state when open is not controlled.\",\n },\n onOpenChange: {\n type: \"function\",\n description:\n \"Called with false when the toast closes itself after its duration, or when Dismiss, Escape or a touch swipe closes it.\",\n },\n type: {\n type: \"enum\",\n values: [\"foreground\", \"background\"],\n description:\n \"How the toast is announced: foreground interrupts a screen reader, background waits. Defaults to foreground for error and warning and background for info and success.\",\n },\n duration: { type: \"number\" },\n persistent: {\n type: \"boolean\",\n default: false,\n description:\n \"Disables automatic dismissal (duration Infinity). The toast stays until dismissed.\",\n },\n },\n composable_with: [\"ToastProvider\"],\n never_inside: [\"Toast\"],\n example:\n '<Toast variant=\"error\" title=\"LINK LOST\" description=\"No telemetry for 12s.\" open={open} onOpenChange={setOpen} />',\n },\n};\n\nexport const ToastCompositionRules = {\n ToastProvider: {\n allowed_children: [\"Toast\"],\n },\n};\n","Toast/Toast.tsx":"\"use client\";\n\nimport { Info, CheckCircle, AlertTriangle, XCircle, X } from \"@oprt/icons\";\nimport * as RadixToast from \"@radix-ui/react-toast\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport {\n createContext,\n forwardRef,\n useContext,\n useEffect,\n useRef,\n type ComponentPropsWithoutRef,\n type PointerEvent,\n type ReactNode,\n type RefObject,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { ToastManifest } from \"./Toast.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\nconst ICONS = {\n info: Info,\n success: CheckCircle,\n warning: AlertTriangle,\n error: XCircle,\n} as const;\n\ntype Severity = keyof typeof ICONS;\n\n/** A known severity, and the default one for anything else (null, or a value made up in JS). */\nfunction severityOf(variant: unknown): Severity {\n return typeof variant === \"string\" && Object.prototype.hasOwnProperty.call(ICONS, variant)\n ? (variant as Severity)\n : \"info\";\n}\n\ntype SwipeDirection = \"right\" | \"left\" | \"up\" | \"down\";\n\n// Each toast keeps the axis it is not swiped along free for the browser: with a left or right swipe\n// a vertical touch drag scrolls the region; with an up or down swipe the vertical axis belongs to the\n// swipe, so the region cannot be scrolled by touch on the toast itself. The provider's direction\n// reaches the toasts through this context.\nconst SwipeDirectionContext = createContext<SwipeDirection>(\"right\");\n\n/**\n * Keeps the newest notice in view. The region is capped at the window and scrolls, and each new\n * toast is added at its end: in a full stack, below what the region shows. While the reader is at\n * that end, a new notice scrolls the region to it. Only the region scrolls, never the page, and\n * focus stays where it is. A reader who has scrolled away, to read an older notice, is left there\n * until they come back to the end.\n */\nfunction useNewestInView(region: RefObject<HTMLOListElement | null>) {\n useEffect(() => {\n const list = region.current;\n if (!list) return;\n // Within a pixel: at some zoom levels a scroll position never lands on the end exactly.\n const atEnd = () => list.scrollHeight - list.clientHeight - list.scrollTop <= 1;\n const toEnd = () => {\n list.scrollTop = list.scrollHeight;\n };\n // Whether new notices are followed: true at the start, then wherever the reader scrolls.\n let following = true;\n const onScroll = () => {\n following = atEnd();\n };\n const observer = new MutationObserver((records) => {\n if (records.some((record) => record.addedNodes.length > 0)) {\n if (following) toEnd();\n } else {\n // Removing a notice can bring the end to the reader without a scroll event.\n following = atEnd();\n }\n });\n list.addEventListener(\"scroll\", onScroll, { passive: true });\n observer.observe(list, { childList: true });\n if (list.childElementCount > 0) toEnd();\n return () => {\n observer.disconnect();\n list.removeEventListener(\"scroll\", onScroll);\n };\n }, [region]);\n}\n\n// --- ToastProvider ---\n\nexport type ToastProviderProps = {\n children: ReactNode;\n /**\n * Names the notification region (`<label> (F8)`, F8 being the key that moves focus to it) and\n * is spoken before each announcement.\n */\n label?: string;\n swipeDirection?: SwipeDirection;\n duration?: number;\n};\n\nexport const ToastProvider = forwardRef<HTMLOListElement, ToastProviderProps>((props, ref) => {\n const { children, label = \"Notifications\", swipeDirection = \"right\", duration } = props;\n // exactOptionalPropertyTypes: only forward duration when defined.\n const providerProps: Record<string, unknown> = { label, swipeDirection };\n if (duration !== undefined) providerProps[\"duration\"] = duration;\n const region = useRef<HTMLOListElement>(null);\n const regionRef = useMergedRefs(ref, region);\n useNewestInView(region);\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"ToastProvider\", ToastManifest.ToastProvider!.props, props);\n }\n\n return (\n <RadixToast.Provider {...providerProps}>\n <SwipeDirectionContext.Provider value={swipeDirection}>\n {children}\n </SwipeDirectionContext.Provider>\n {/*\n The region sits in the corner of the window and never outgrows it (R7): a stack taller than\n the window scrolls inside the region, so every Dismiss can be reached, and the scroll does\n not chain to the page behind. 100dvh follows the visible height on phones whose toolbars\n come and go; engines without dvh fall back to 100vh. The newest notice is kept in view.\n */}\n <RadixToast.Viewport\n ref={regionRef}\n label={`${label} ({hotkey})`}\n className=\"fixed bottom-0 right-0 z-50 flex max-h-dvh [@supports_not_(height:100dvh)]:max-h-screen flex-col gap-2u overflow-y-auto overscroll-contain p-4u m-0 w-full max-w-sm list-none\"\n />\n </RadixToast.Provider>\n );\n});\nToastProvider.displayName = \"ToastProvider\";\n\n// --- Toast ---\n\nconst rootStyles = cva(\n \"bg-oprt-surface-1 border border-oprt-border border-l-2 px-3u py-2u flex gap-2u items-start font-sans\",\n {\n variants: {\n variant: {\n info: \"border-l-oprt-info\",\n success: \"border-l-oprt-success\",\n warning: \"border-l-oprt-warning\",\n error: \"border-l-oprt-error\",\n },\n },\n defaultVariants: { variant: \"info\" },\n },\n);\n\nconst iconStyles = cva(\"shrink-0\", {\n variants: {\n variant: {\n info: \"text-oprt-info\",\n success: \"text-oprt-success\",\n warning: \"text-oprt-warning\",\n error: \"text-oprt-error\",\n },\n },\n defaultVariants: { variant: \"info\" },\n});\n\nconst titleStyles = cva(\"m-0 text-oprt-sm uppercase tracking-oprt-sm\", {\n variants: {\n variant: {\n info: \"text-oprt-info\",\n success: \"text-oprt-success\",\n warning: \"text-oprt-warning\",\n error: \"text-oprt-error\",\n },\n },\n defaultVariants: { variant: \"info\" },\n});\n\nexport type ToastProps = Omit<\n ComponentPropsWithoutRef<typeof RadixToast.Root>,\n \"title\" | \"children\"\n> &\n VariantProps<typeof rootStyles> & {\n title: string;\n // `| undefined` so that an application can pass a notice's optional description straight\n // through, which is what the docs show, under exactOptionalPropertyTypes.\n description?: string | undefined;\n persistent?: boolean;\n };\n\nexport const Toast = forwardRef<HTMLLIElement, ToastProps>((allProps, ref) => {\n const {\n className,\n variant,\n title,\n description,\n persistent = false,\n open,\n defaultOpen,\n onOpenChange,\n duration,\n type,\n style,\n onPointerMove,\n ...props\n } = allProps;\n const severity = severityOf(variant);\n const SeverityIcon = ICONS[severity];\n const swipeDirection = useContext(SwipeDirectionContext);\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Toast\", ToastManifest.Toast!.props, allProps);\n }\n\n // exactOptionalPropertyTypes: build the controlled/duration props conditionally.\n const rootProps: Record<string, unknown> = {};\n if (open !== undefined) rootProps[\"open\"] = open;\n if (defaultOpen !== undefined) rootProps[\"defaultOpen\"] = defaultOpen;\n if (onOpenChange !== undefined) rootProps[\"onOpenChange\"] = onOpenChange;\n if (persistent) {\n rootProps[\"duration\"] = Infinity;\n } else if (duration !== undefined) {\n rootProps[\"duration\"] = duration;\n }\n // Politeness follows severity, as Alert's does: an error or a warning interrupts the screen\n // reader, information and success wait their turn. An explicit `type` decides instead.\n rootProps[\"type\"] =\n type ?? (severity === \"error\" || severity === \"warning\" ? \"foreground\" : \"background\");\n\n return (\n <RadixToast.Root\n ref={ref}\n className={clsx(rootStyles({ variant: severity }), className)}\n {...rootProps}\n {...props}\n // Swiping to dismiss is for touch. Radix starts a swipe for any pointer, so a mouse dragged\n // across the text to select a request ID swiped the toast away. Radix's handler runs after\n // this one and skips an event marked as handled, so a mouse or pen move is marked, for Radix\n // only: the drag then just selects. The browser's own event is not canceled, since WebKit\n // stops extending a drag selection when a pointermove is canceled.\n onPointerMove={(event: PointerEvent<HTMLLIElement>) => {\n onPointerMove?.(event);\n if (event.pointerType !== \"touch\") event.defaultPrevented = true;\n }}\n // Radix sets touch-action: none on every toast, which stops a touch drag from scrolling the\n // region. Keep the swipe axis for the swipe and give the other one back; the caller's style\n // still wins.\n style={{\n touchAction: swipeDirection === \"up\" || swipeDirection === \"down\" ? \"pan-x\" : \"pan-y\",\n ...style,\n }}\n >\n <SeverityIcon size={16} className={iconStyles({ variant: severity })} />\n {/*\n The text can be selected and copied (a request ID, an error code), and wraps anywhere, so a\n long identifier never pushes Dismiss out of the toast.\n */}\n <div className=\"flex min-w-0 flex-1 select-text flex-col gap-1u oprt-wrap-anywhere\">\n <RadixToast.Title className={titleStyles({ variant: severity })}>{title}</RadixToast.Title>\n {description && (\n <RadixToast.Description className=\"m-0 text-oprt-base normal-case text-oprt-text\">\n {/*\n The spoken announcement joins text nodes with nothing between them: this keeps the\n title and the description apart.\n */}\n <span className=\"sr-only\">. </span>\n {description}\n </RadixToast.Description>\n )}\n </div>\n <RadixToast.Close\n aria-label=\"Dismiss\"\n className=\"inline-flex items-center justify-center shrink-0 h-oprt-sm w-3u border border-transparent text-oprt-text-dim hover:text-oprt-text focus-visible:border-oprt-border-focus focus-visible:shadow-oprt-focus focus-visible:outline-hidden\"\n >\n <X size={12} />\n </RadixToast.Close>\n </RadixToast.Root>\n );\n});\nToast.displayName = \"Toast\";\n","Toggle/index.ts":"export { Toggle, type ToggleProps } from \"./Toggle\";\nexport { ToggleManifest } from \"./Toggle.manifest\";\n","Toggle/Toggle.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ToggleManifest: Record<string, Component> = {\n Toggle: {\n description:\n \"Binary on/off switch. Square thumb sliding inside a square track. Accent fill when on; an invalid switch draws its track in the error color. In forced-colors mode the on state, the thumb and keyboard focus use system colors.\",\n props: {\n motion: {\n type: \"enum\",\n values: [\"auto\", \"none\"],\n default: \"auto\",\n description:\n \"Tactile pointer/touch feedback. None disables motion; keyboard and system reduced motion are always immediate.\",\n },\n checked: { type: \"boolean\" },\n defaultChecked: { type: \"boolean\", default: false },\n onCheckedChange: { type: \"function\", description: \"Receives the new checked value.\" },\n disabled: { type: \"boolean\", default: false },\n required: {\n type: \"boolean\",\n default: false,\n description:\n \"Announced as required. Like a required Checkbox, it blocks native form submission until the switch is on. The native attribute is added when the page hydrates (the server markup has aria-required), so a form submitted earlier is not blocked: validate on the server too.\",\n },\n size: { type: \"enum\", values: [\"sm\", \"md\", \"lg\"], default: \"md\" },\n name: { type: \"string\" },\n value: { type: \"string\" },\n id: {\n type: \"string\",\n description: \"Id of the switch input. Inside a Field it defaults to the Field's id.\",\n },\n className: { type: \"string\", description: \"Classes applied to the switch's label root.\" },\n \"aria-label\": {\n type: \"string\",\n description:\n \"Accessible name when there is no visible label. Inside a Field the Field's label names the switch.\",\n },\n \"aria-labelledby\": {\n type: \"string\",\n description: \"Id of the element that names the switch.\",\n },\n \"aria-describedby\": {\n type: \"string\",\n description:\n \"Id of the element that describes the switch. A Field supplies its help or error.\",\n },\n \"aria-invalid\": {\n type: \"union\",\n types: [\"boolean\", \"string\"],\n description:\n \"Marks the switch invalid: announced as invalid, with its track in the error color. A Field with an error sets it.\",\n },\n },\n composable_with: [\"Field\", \"Toolbar\"],\n never_inside: [\"Button\", \"Badge\"],\n example: '<Field label=\"ENABLE TELEMETRY\">\\n <Toggle defaultChecked />\\n</Field>',\n },\n};\n","Toggle/Toggle.tsx":"\"use client\";\n\nimport { Switch as AriaSwitch } from \"react-aria-components\";\nimport { cva, type VariantProps } from \"class-variance-authority\";\nimport { forwardRef, useCallback } from \"react\";\nimport clsx from \"clsx\";\nimport { useMotion, type MotionMode } from \"../_internal/motion\";\nimport { useFieldWiring } from \"../_internal/fieldIds\";\nimport { ToggleManifest } from \"./Toggle.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly, switchValidationProps, useAriaInvalidRef } from \"../_internal/aria\";\nimport { keyForIds } from \"../_internal/hydration\";\nimport { useInputRefObject } from \"../_internal/mergeRefs\";\n\n// RAC state attributes replace the Radix ones: data-selected instead of\n// data-state=checked, data-focus-visible instead of the :focus-visible\n// pseudo-class (the role element is now a hidden <input>, so the pseudo-class\n// would never match the styled <label>), data-disabled instead of :disabled.\n// The thumb is a plain span with no state of its own, so its checked styling is\n// driven from the label's named group via descendant selectors. The label reserves\n// a full target while its inner span preserves the compact visual track.\nconst trackStyles = cva(\n [\n \"relative inline-flex items-center border oprt-motion-track cursor-pointer\",\n // The thumb's resting distance from the border, named once because its travel uses it too.\n \"[--toggle-inset:2px] px-[var(--toggle-inset)]\",\n // The resting boundary is the control outline (design call DC-3); on, it is the accent.\n // Invalid draws the error color whether the switch is on or off: Tailwind sorts these state\n // variants by name, so `selected` would win a tie, and `[&&]` raises the invalid rule above it.\n \"bg-oprt-surface-2 border-oprt-border-control\",\n \"group-data-[selected]/toggle:bg-oprt-accent group-data-[selected]/toggle:border-oprt-accent-light\",\n \"group-data-[selected]/toggle:[&_.oprt-toggle-thumb]:bg-oprt-bg\",\n \"group-data-[invalid]/toggle:[&&]:border-oprt-error\",\n \"group-data-[focus-visible]/toggle:border-oprt-border-focus group-data-[focus-visible]/toggle:shadow-oprt-focus\",\n \"group-data-[disabled]/toggle:opacity-50 group-data-[disabled]/toggle:cursor-not-allowed\",\n // Forced colors replace every background with the page color and drop box-shadow, so the\n // thumb, the on fill and the focus ring would all vanish. Each is redrawn from system colors,\n // which forced colors keep, and the focus ring becomes an outline.\n \"forced-colors:[forced-color-adjust:none] forced-colors:bg-[color:Canvas] forced-colors:border-[color:CanvasText]\",\n \"forced-colors:group-data-[selected]/toggle:bg-[color:Highlight] forced-colors:group-data-[selected]/toggle:border-[color:Highlight]\",\n \"forced-colors:group-data-[selected]/toggle:[&_.oprt-toggle-thumb]:bg-[color:HighlightText]\",\n \"forced-colors:group-data-[focus-visible]/toggle:outline-solid forced-colors:group-data-[focus-visible]/toggle:outline-2 forced-colors:group-data-[focus-visible]/toggle:outline-offset-2 forced-colors:group-data-[focus-visible]/toggle:outline-[color:CanvasText]\",\n // Disabled wins over on (`[&&]` raises it above the `selected` rules, which Tailwind writes\n // later): a disabled switch that is on is a Canvas track with GrayText edge and thumb, and the\n // thumb's place still says it is on.\n \"forced-colors:group-data-[disabled]/toggle:[&&]:bg-[color:Canvas] forced-colors:group-data-[disabled]/toggle:[&&]:border-[color:GrayText] forced-colors:[&&]:group-data-[disabled]/toggle:[&_.oprt-toggle-thumb]:bg-[color:GrayText]\",\n ].join(\" \"),\n {\n variants: {\n // The thumb travels the track's width less its two 1px borders and both insets, less its own\n // width, so it ends as far from the border as it starts (14, 18 and 22 px).\n size: {\n sm: \"w-4u h-2u group-data-[selected]/toggle:[&_.oprt-toggle-thumb]:translate-x-[calc(var(--oprt-space-4)-var(--oprt-space-1)*1.5-2px-var(--toggle-inset)*2)]\",\n md: \"w-5u h-2.5u group-data-[selected]/toggle:[&_.oprt-toggle-thumb]:translate-x-[calc(var(--oprt-space-5)-var(--oprt-space-2)-2px-var(--toggle-inset)*2)]\",\n lg: \"w-6u h-3u group-data-[selected]/toggle:[&_.oprt-toggle-thumb]:translate-x-[calc(var(--oprt-space-6)-var(--oprt-space-1)*2.5-2px-var(--toggle-inset)*2)]\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nconst thumbStyles = cva(\n \"oprt-toggle-thumb block oprt-motion-thumb bg-oprt-text-dim forced-colors:bg-[color:CanvasText]\",\n {\n variants: {\n size: {\n sm: \"h-1.5u w-1.5u\",\n md: \"h-2u w-2u\",\n lg: \"h-2.5u w-2.5u\",\n },\n },\n defaultVariants: { size: \"md\" },\n },\n);\n\nexport type ToggleProps = VariantProps<typeof trackStyles> & {\n motion?: MotionMode;\n checked?: boolean;\n defaultChecked?: boolean;\n onCheckedChange?: (checked: boolean) => void;\n disabled?: boolean;\n required?: boolean;\n name?: string;\n value?: string;\n className?: string;\n id?: string;\n \"aria-label\"?: string;\n \"aria-labelledby\"?: string;\n \"aria-describedby\"?: string;\n \"aria-invalid\"?: boolean | \"true\" | \"false\";\n};\n\nexport const Toggle = forwardRef<HTMLInputElement, ToggleProps>((allProps, ref) => {\n const {\n className,\n motion = \"auto\",\n size = \"md\",\n id,\n checked,\n defaultChecked,\n onCheckedChange,\n disabled,\n required,\n name,\n value,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n } = allProps;\n const motionState = useMotion(motion, \"Toggle\");\n // The shared Field rules (Stage 1 Field-R2, B04): its own id, names, description and\n // aria-invalid win over the Field's; a required Field makes it required (coordinator ruling on\n // DC-B04-1, following DC-10).\n const {\n id: finalId,\n labelledBy,\n describedBy,\n invalid: isInvalid,\n required: isRequired,\n } = useFieldWiring(\n {\n id,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": ariaLabelledBy,\n \"aria-describedby\": ariaDescribedBy,\n \"aria-invalid\": ariaInvalid,\n required,\n },\n { label: \"labelledby\" },\n );\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Toggle\", ToggleManifest.Toggle!.props, { ...allProps });\n }\n\n // RAC's SwitchProps deliberately omits isRequired and isInvalid, so neither\n // aria-required nor aria-invalid would reach the input. Radix set both; losing\n // them is a WCAG 3.3.1 / 508 regression. The Switch hands both to its input at\n // runtime, so they are passed untyped (switchValidationProps) and are in the server\n // markup. The ref also writes them onto the DOM node, which keeps them right on a page\n // whose markup was rendered before the field turned invalid.\n //\n // Design call DC-10: a required switch blocks form submission like a required Checkbox does.\n // The server markup keeps its aria-required (React Aria's \"aria\" validation behavior, which\n // renders no native attribute), so the native `required` is added with the other two once the\n // node exists. The browser then runs its own constraint check on submit.\n const setInvalidNode = useAriaInvalidRef<HTMLInputElement>(ref, isInvalid);\n // Returns what the merged ref returns: under React 19 a cleanup, which the input ref object runs\n // when the input goes, instead of calling the consumer's ref with null (Checkbox-R7).\n const setNode = useCallback(\n (node: HTMLInputElement | null) => {\n if (node) {\n if (isRequired) node.setAttribute(\"aria-required\", \"true\");\n else node.removeAttribute(\"aria-required\");\n node.required = isRequired;\n }\n return setInvalidNode(node);\n },\n [setInvalidNode, isRequired],\n );\n // React Aria takes the hidden input's ref as an object; the public ref lands on that input,\n // the element with role=\"switch\".\n const inputRef = useInputRefObject(setNode);\n\n // exactOptionalPropertyTypes forbids passing an explicit `undefined` for an\n // optional prop, so build the props object conditionally.\n const rootProps: Record<string, unknown> = {\n isDisabled: disabled ?? false,\n ...definedOnly({\n id: finalId,\n \"aria-label\": ariaLabel,\n \"aria-labelledby\": labelledBy,\n \"aria-describedby\": describedBy,\n name,\n value,\n }),\n };\n Object.assign(rootProps, switchValidationProps({ invalid: isInvalid, required: isRequired }));\n if (checked !== undefined) rootProps[\"isSelected\"] = checked;\n if (defaultChecked !== undefined) rootProps[\"defaultSelected\"] = defaultChecked;\n if (onCheckedChange) rootProps[\"onChange\"] = onCheckedChange;\n\n return (\n <AriaSwitch\n // RAC keeps the ids it mounted with (see keyForIds).\n key={keyForIds(finalId, labelledBy)}\n {...rootProps}\n {...motionState.props}\n render={(domProps) => <label {...domProps} {...motionState.props} />}\n inputRef={inputRef}\n className={clsx(\n \"oprt-motion group/toggle inline-flex self-start w-fit items-center justify-center min-h-3u cursor-pointer data-[disabled]:cursor-not-allowed\",\n className,\n )}\n >\n <span className={trackStyles({ size: size ?? undefined })}>\n <span className={thumbStyles({ size: size ?? undefined })} />\n </span>\n </AriaSwitch>\n );\n});\nToggle.displayName = \"Toggle\";\n","Toolbar/index.ts":"export {\n Toolbar,\n ToolbarSeparator,\n type ToolbarProps,\n type ToolbarSeparatorProps,\n} from \"./Toolbar\";\nexport { ToolbarManifest, ToolbarCompositionRules } from \"./Toolbar.manifest\";\n","Toolbar/Toolbar.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const ToolbarManifest: Record<string, Component> = {\n Toolbar: {\n description:\n \"Horizontal or vertical action bar with roving-tabindex arrow-key navigation over its focusable children. A horizontal toolbar that does not fit wraps onto more rows and grows taller, so no action is hidden; arrow keys follow reading order across rows. A vertical toolbar stacks.\",\n props: {\n orientation: {\n type: \"enum\",\n values: [\"horizontal\", \"vertical\"],\n default: \"horizontal\",\n },\n ariaLabel: {\n type: \"string\",\n description:\n \"The toolbar's accessible name (native aria-label works too). With aria-labelledby and no label the toolbar is named by that element; a toolbar given neither is called Toolbar.\",\n },\n },\n slots: {\n children: {\n type: \"Button | ButtonGroup | Badge | Toggle | CopyButton | ConfirmDialog | DropdownMenu | Tooltip | ToolbarSeparator\",\n required: true,\n },\n },\n composable_with: [\n \"Button\",\n \"ButtonGroup\",\n \"Badge\",\n \"Toggle\",\n \"CopyButton\",\n \"ConfirmDialog\",\n \"DropdownMenu\",\n \"Tooltip\",\n \"ToolbarSeparator\",\n ],\n never_inside: [\"Toolbar\"],\n example:\n '<Toolbar ariaLabel=\"Track actions\">\\n <Button>ZOOM</Button>\\n <ToolbarSeparator />\\n <Button>RESET</Button>\\n</Toolbar>',\n },\n ToolbarSeparator: {\n description:\n \"Visual divider between Toolbar action groups. It follows its Toolbar: a rule across a vertical toolbar, a rule between the actions of a horizontal one.\",\n props: {\n orientation: {\n type: \"enum\",\n values: [\"horizontal\", \"vertical\"],\n default: \"horizontal\",\n description:\n \"The axis of the toolbar the separator is in; the rule runs across it. It follows its Toolbar when omitted, so pass it only to say otherwise.\",\n },\n },\n composable_with: [\"Toolbar\"],\n never_inside: [\"ToolbarSeparator\"],\n example: \"<ToolbarSeparator />\",\n },\n};\n\nexport const ToolbarCompositionRules = {\n Toolbar: {\n allowed_children: [\n \"Button\",\n \"ButtonGroup\",\n \"Badge\",\n \"Toggle\",\n \"CopyButton\",\n // Each puts one control in the bar and draws its overlay outside it.\n \"ConfirmDialog\",\n \"DropdownMenu\",\n \"Tooltip\",\n \"ToolbarSeparator\",\n ],\n },\n};\n","Toolbar/Toolbar.tsx":"\"use client\";\n\nimport { Toolbar as AriaToolbar, Separator as AriaSeparator } from \"react-aria-components\";\nimport { cva } from \"class-variance-authority\";\nimport { createContext, forwardRef, useContext, type ComponentPropsWithoutRef } from \"react\";\nimport clsx from \"clsx\";\nimport { ToolbarManifest } from \"./Toolbar.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\n\ntype Orientation = \"horizontal\" | \"vertical\";\n\n// A horizontal toolbar that does not fit flows onto more rows and grows taller (design call DC-8):\n// nothing is hidden and the page never widens. The height is a minimum, and the vertical padding is\n// what is left of it around one medium button and the 2px border, so one row of standard buttons is\n// exactly as tall as before (40px) and every further row has the same room above and below.\nconst toolbarStyles = cva(\"flex bg-oprt-surface-1 border border-oprt-border font-sans\", {\n variants: {\n orientation: {\n horizontal:\n \"flex-wrap items-center gap-x-2u gap-y-1u px-2u min-h-oprt-lg py-[calc((var(--oprt-height-lg)-2px-var(--oprt-height-md))/2)]\",\n vertical: \"flex-col items-stretch gap-2u px-2u py-2u w-auto h-auto\",\n },\n },\n defaultVariants: { orientation: \"horizontal\" },\n});\n\n/** The toolbar's axis, so a ToolbarSeparator inside it can follow it when it is not told. */\nconst ToolbarOrientationContext = createContext<Orientation | undefined>(undefined);\n\nexport type ToolbarProps = Omit<ComponentPropsWithoutRef<\"div\">, \"orientation\"> & {\n orientation?: Orientation;\n /** The toolbar's accessible name. `aria-label` works too; `ariaLabel` wins when both are given. */\n ariaLabel?: string;\n};\n\nexport const Toolbar = forwardRef<HTMLDivElement, ToolbarProps>((allProps, ref) => {\n const {\n className,\n orientation = \"horizontal\",\n ariaLabel,\n \"aria-label\": nativeLabel,\n \"aria-labelledby\": labelledBy,\n children,\n id,\n ...props\n } = allProps;\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Toolbar\", ToolbarManifest.Toolbar!.props, { ...allProps });\n }\n\n // A toolbar is named by its label, or by the element it is labeled by. Only a toolbar given\n // neither is called \"Toolbar\": React Aria drops aria-labelledby whenever a label is passed.\n const label = ariaLabel ?? nativeLabel;\n const name = label ?? (labelledBy === undefined ? \"Toolbar\" : undefined);\n const axis = orientation ?? \"horizontal\";\n\n return (\n <ToolbarOrientationContext.Provider value={axis}>\n <AriaToolbar\n ref={ref}\n orientation={axis}\n {...(id !== undefined ? { id } : {})}\n {...(name !== undefined ? { \"aria-label\": name } : {})}\n {...(labelledBy !== undefined ? { \"aria-labelledby\": labelledBy } : {})}\n className={clsx(toolbarStyles({ orientation: axis }), className)}\n {...(props as Record<string, unknown>)}\n >\n {children}\n </AriaToolbar>\n </ToolbarOrientationContext.Provider>\n );\n});\nToolbar.displayName = \"Toolbar\";\n\nconst separatorStyles = cva(\"bg-oprt-border self-stretch\", {\n variants: {\n orientation: {\n horizontal: \"w-px mx-1u\",\n vertical: \"h-px my-1u\",\n },\n },\n defaultVariants: { orientation: \"horizontal\" },\n});\n\nexport type ToolbarSeparatorProps = Omit<ComponentPropsWithoutRef<\"div\">, \"orientation\"> & {\n /**\n * The axis of the toolbar this separator is in, so the rule runs across it. It follows its\n * Toolbar when omitted; pass it to say otherwise.\n */\n orientation?: Orientation;\n};\n\nexport const ToolbarSeparator = forwardRef<HTMLDivElement, ToolbarSeparatorProps>(\n (allProps, ref) => {\n const { className, orientation: given, ...props } = allProps;\n const inherited = useContext(ToolbarOrientationContext);\n const orientation = given ?? inherited ?? \"horizontal\";\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"ToolbarSeparator\", ToolbarManifest.ToolbarSeparator!.props, { ...allProps });\n }\n\n return (\n <AriaSeparator\n ref={ref}\n // The prop names the toolbar's axis; the divider runs across it.\n orientation={orientation === \"horizontal\" ? \"vertical\" : \"horizontal\"}\n // RAC's Separator defaults to <hr>, which carries UA borders and\n // margins. Radix rendered a <div>; keep that so the styles are the only\n // thing painting the rule.\n elementType=\"div\"\n className={clsx(separatorStyles({ orientation }), className)}\n {...(props as Record<string, unknown>)}\n />\n );\n },\n);\nToolbarSeparator.displayName = \"ToolbarSeparator\";\n","Tooltip/index.ts":"export { Tooltip, type TooltipProps } from \"./Tooltip\";\nexport { TooltipManifest } from \"./Tooltip.manifest\";\n","Tooltip/Tooltip.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const TooltipManifest: Record<string, Component> = {\n Tooltip: {\n description:\n \"Supplementary text on hover or keyboard focus of a trigger, wrapping at about 20rem. A disabled Button trigger stays focusable and announced unavailable, so focus or a tap shows the explanation. Built on React Aria Components (TooltipTrigger + Tooltip), portal'd, with a bordered surface.\",\n props: {\n side: { type: \"enum\", values: [\"top\", \"right\", \"bottom\", \"left\"], default: \"top\" },\n align: { type: \"enum\", values: [\"start\", \"center\", \"end\"], default: \"center\" },\n delayDuration: { type: \"number\", default: 200 },\n open: { type: \"boolean\" },\n defaultOpen: { type: \"boolean\" },\n onOpenChange: { type: \"function\" },\n },\n slots: {\n content: { type: \"text | node\", required: true },\n children: {\n type: \"focusable trigger element | text (text renders a text button)\",\n required: true,\n },\n },\n composable_with: [\"Button\", \"Badge\", \"Toolbar\"],\n never_inside: [\"Tooltip\"],\n example: '<Tooltip content=\"GRID 38S MC\"><Button>COORD</Button></Tooltip>',\n },\n};\n","Tooltip/Tooltip.tsx":"\"use client\";\n\nimport {\n TooltipTrigger,\n Tooltip as AriaTooltip,\n OverlayArrow,\n Focusable,\n} from \"react-aria-components\";\nimport {\n cloneElement,\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type PointerEvent as ReactPointerEvent,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport clsx from \"clsx\";\nimport { TooltipManifest } from \"./Tooltip.manifest\";\nimport { devValidate } from \"../_internal/devValidate\";\nimport { definedOnly } from \"../_internal/aria\";\n// Radix modeled position as two axes (`side` + `align`); React Aria collapses both into one\n// placement string, which toPlacement builds. The public API keeps the two props.\nimport { isSingleElement, SCREEN_MARGIN, toPlacement, TOOLTIP_WIDTH } from \"../_internal/overlay\";\nimport { FocusableWhenUnavailableProvider } from \"../_internal/unavailable\";\n\ntype TriggerProps = {\n disabled?: unknown;\n isDisabled?: unknown;\n onPointerUp?: (event: ReactPointerEvent<HTMLElement>) => void;\n};\n\n/**\n * React Aria's Focusable clones a single child element; its `children` type is\n * narrower than our public `children: ReactNode`.\n */\ntype FocusableChild = ComponentProps<typeof Focusable>[\"children\"];\n\n/**\n * Plain-text children (a term, an abbreviation) become a text button: focusable, so the\n * tooltip opens on keyboard focus too, with a dotted underline that says there is more.\n */\nconst PLAIN_TRIGGER =\n \"inline-flex min-h-3u items-center border-0 bg-transparent p-0 [color:inherit] [font:inherit] [letter-spacing:inherit] underline decoration-dotted underline-offset-2 cursor-help outline-hidden focus-visible:shadow-oprt-focus\";\n\nexport type TooltipProps = Omit<ComponentPropsWithoutRef<\"div\">, \"content\"> & {\n /** Tooltip body — text or any node. */\n content: ReactNode;\n /** Trigger element. Wrapped in a React Aria Focusable. */\n children: ReactNode;\n side?: \"top\" | \"right\" | \"bottom\" | \"left\";\n align?: \"start\" | \"center\" | \"end\";\n delayDuration?: number;\n open?: boolean;\n defaultOpen?: boolean;\n onOpenChange?: (open: boolean) => void;\n};\n\nexport const Tooltip = forwardRef<HTMLDivElement, TooltipProps>(\n (\n {\n content,\n children,\n side = \"top\",\n align = \"center\",\n delayDuration = 200,\n open,\n defaultOpen,\n onOpenChange,\n className,\n ...contentProps\n },\n ref,\n ) => {\n // Always controlled here, so a tap on an unavailable trigger can open the explanation too.\n const [localOpen, setLocalOpen] = useState(defaultOpen ?? false);\n const isOpen = open ?? localOpen;\n // The trigger a tap opened the explanation from; a tap anywhere else closes it.\n const tapped = useRef<HTMLElement | null>(null);\n const [tapOpen, setTapOpen] = useState(false);\n const setOpen = useCallback(\n (next: boolean) => {\n if (!next) setTapOpen(false);\n if (open === undefined) setLocalOpen(next);\n onOpenChange?.(next);\n },\n [open, onOpenChange],\n );\n useEffect(() => {\n if (!tapOpen || !isOpen) return;\n const dismiss = (event: PointerEvent) => {\n if (!tapped.current?.contains(event.target as Node | null)) setOpen(false);\n };\n document.addEventListener(\"pointerdown\", dismiss, true);\n return () => document.removeEventListener(\"pointerdown\", dismiss, true);\n }, [tapOpen, isOpen, setOpen]);\n\n if (process.env.NODE_ENV !== \"production\") {\n devValidate(\"Tooltip\", TooltipManifest.Tooltip!.props, { ...contentProps, side, align });\n }\n\n // Nothing to describe.\n if (children === null || children === undefined || typeof children === \"boolean\") return null;\n\n // Design call DC-1: a disabled trigger (a disabled Button, or the Pressable of a disabled\n // Popover) stays focusable and announced unavailable, so the explanation can be reached by\n // keyboard focus and by a tap, not by hover alone. Pressing it keeps the explanation open.\n const element = isSingleElement(children) ? (children as ReactElement<TriggerProps>) : null;\n const unavailable =\n element !== null && (element.props.disabled === true || element.props.isDisabled === true);\n // One element is cloned as it is; plain text (or a number, fragment or list) becomes a text\n // button instead of reaching React Aria, which would throw.\n const trigger = !element ? (\n <button type=\"button\" className={PLAIN_TRIGGER}>\n {children}\n </button>\n ) : unavailable ? (\n cloneElement(element, {\n onPointerUp: (event: ReactPointerEvent<HTMLElement>) => {\n element.props.onPointerUp?.(event);\n // A mouse already shows it on hover; a touch or pen tap shows it here.\n if (event.pointerType === \"mouse\") return;\n tapped.current = event.currentTarget;\n setTapOpen(true);\n setOpen(true);\n },\n })\n ) : (\n element\n );\n // Focusable is React Aria's equivalent of Radix's `asChild` trigger: it clones the single\n // child and merges the trigger props onto it.\n const focusable = <Focusable>{trigger as FocusableChild}</Focusable>;\n\n return (\n <TooltipTrigger\n isOpen={isOpen}\n onOpenChange={setOpen}\n delay={delayDuration}\n // Radix closed the tooltip immediately on pointer leave; React Aria\n // defaults to a 500ms close delay. Keep the old feel.\n closeDelay={0}\n shouldCloseOnPress={!unavailable}\n >\n {unavailable ? (\n <FocusableWhenUnavailableProvider value>{focusable}</FocusableWhenUnavailableProvider>\n ) : (\n focusable\n )}\n <AriaTooltip\n ref={ref}\n placement={toPlacement(side, align)}\n offset={4}\n containerPadding={SCREEN_MARGIN}\n // A readable measure, never wider than the viewport; long and unbroken text wraps (DC-7).\n className={clsx(\n \"z-50 bg-oprt-surface-2 border border-oprt-border px-2u py-1u text-oprt-base normal-case text-oprt-text font-sans oprt-wrap-anywhere\",\n TOOLTIP_WIDTH,\n className,\n )}\n {...definedOnly(contentProps)}\n >\n {content}\n {/* React Aria positions the arrow but does not rotate it; the\n placement lives on the OverlayArrow element (its inline transform\n handles centering, so the rotation goes on the child). */}\n <OverlayArrow className=\"group\">\n <svg\n width={10}\n height={5}\n viewBox=\"0 0 10 5\"\n aria-hidden=\"true\"\n className=\"fill-oprt-border group-data-[placement=bottom]:rotate-180 group-data-[placement=left]:-rotate-90 group-data-[placement=right]:rotate-90\"\n >\n <path d=\"M0 0 L5 5 L10 0 Z\" />\n </svg>\n </OverlayArrow>\n </AriaTooltip>\n </TooltipTrigger>\n );\n },\n);\nTooltip.displayName = \"Tooltip\";\n","TreeView/index.ts":"export { TreeView, type TreeViewProps, type TreeViewItem } from \"./TreeView\";\nexport { TreeViewManifest } from \"./TreeView.manifest\";\n","TreeView/TreeView.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\n\nexport const TreeViewManifest: Record<string, Component> = {\n TreeView: {\n description:\n \"React Aria hierarchical treegrid with stable string IDs, single selection, expansion, arrow-key navigation, and typeahead. Labels wrap and keep a readable width; a hierarchy deeper than the tree is wide scrolls sideways inside the tree, and keyboard focus brings the focused row's label into view. Accepts id, style, aria-labelledby, aria-describedby and data attributes.\",\n props: {\n items: {\n type: \"array\",\n required: true,\n items: {\n id: \"unique string\",\n label: \"string\",\n children: \"optional recursive TreeViewItem[]\",\n disabled: \"optional boolean\",\n },\n },\n label: {\n type: \"string\",\n default: \"Navigation\",\n },\n selectedId: {\n type: \"string\",\n description: \"Controlled selected string ID; null clears the selection.\",\n },\n onSelectionChange: {\n type: \"function\",\n description: \"(id: string | null) => void. Navigation may select as focus moves.\",\n },\n expandedIds: {\n type: \"array\",\n description: \"Controlled expanded string IDs.\",\n },\n defaultExpandedIds: {\n type: \"array\",\n description: \"Initial expanded IDs for local state.\",\n },\n onExpandedChange: {\n type: \"function\",\n description: \"(ids: string[]) => void.\",\n },\n disabled: {\n type: \"boolean\",\n default: false,\n },\n emptyMessage: {\n type: \"string\",\n default: \"No items.\",\n },\n },\n composable_with: [\"AppShell\", \"Inspector\"],\n never_inside: [\"Button\"],\n example:\n '<TreeView label=\"Assets\" items={groups} selectedId={selectedId} onSelectionChange={setSelectedId} />',\n },\n};\n","TreeView/TreeView.tsx":"\"use client\";\n\nimport { forwardRef, useEffect, useRef, useState, type HTMLAttributes } from \"react\";\nimport {\n Tree,\n TreeItem,\n TreeItemContent,\n Button as AriaButton,\n type Key,\n} from \"react-aria-components\";\nimport { ChevronDown, ChevronRight } from \"@oprt/icons\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { useMergedRefs } from \"../_internal/mergeRefs\";\n\n// Forced colors drop the focus border color and shadow, and would paint the transparent outline\n// on everything: only what has keyboard focus draws one, in CanvasText (TreeView-R1).\nconst forcedFocus =\n \"forced-colors:outline-0 forced-colors:data-[focus-visible]:outline-solid forced-colors:data-[focus-visible]:outline-2 forced-colors:data-[focus-visible]:-outline-offset-2 forced-colors:data-[focus-visible]:outline-[CanvasText]\";\n// A hierarchy deeper than the tree is wide scrolls sideways inside the tree (design call DC-17): one\n// column at least as wide as the tree and as wide as the widest row needs, which every row shares.\n// The tree is already a named, focusable region. 1 px of padding keeps the rows' focus rings\n// inside the scrolling box.\nconst treeStyles = cva(\n `grid min-w-0 grid-cols-[minmax(min-content,1fr)] overflow-x-auto p-px border border-transparent font-sans text-oprt-base font-medium normal-case text-oprt-text outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus ${forcedFocus}`,\n);\nconst itemStyles = cva(\n `min-h-3u border border-transparent outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus data-[selected]:bg-oprt-surface-selected data-[selected]:border-oprt-accent forced-colors:data-[selected]:border-[Highlight] data-[disabled]:opacity-50 ${forcedFocus}`,\n);\nexport interface TreeViewItem {\n id: string;\n label: string;\n children?: readonly TreeViewItem[];\n disabled?: boolean;\n}\n/** The attributes React Aria's tree renders on the treegrid. */\ntype TreeViewAttributes = Pick<\n HTMLAttributes<HTMLDivElement>,\n \"id\" | \"style\" | \"aria-labelledby\" | \"aria-describedby\" | \"aria-details\"\n>;\nexport interface TreeViewProps extends TreeViewAttributes {\n items: readonly TreeViewItem[];\n label?: string;\n selectedId?: string | null;\n onSelectionChange?: (id: string | null) => void;\n expandedIds?: readonly string[];\n defaultExpandedIds?: readonly string[];\n onExpandedChange?: (ids: string[]) => void;\n disabled?: boolean;\n emptyMessage?: string;\n className?: string;\n}\n\n/**\n * Trims a row's scroll area to its chevron and label with negative inline scroll margins, so that\n * scrolling the row into view (React Aria does it as focus moves, the browser when focus enters)\n * brings the label into view rather than the nearer end of a row as wide as the whole column. An\n * area wider than the tree keeps its start: the chevron and the beginning of the label.\n */\nfunction aimAtLabel(tree: HTMLElement, row: HTMLElement) {\n const label = row.querySelector(\"[data-oprt-tree-label]\");\n if (!label) return;\n const start = (label.previousElementSibling ?? label).getBoundingClientRect();\n const text = label.getBoundingClientRect();\n const box = row.getBoundingClientRect();\n let left = Math.min(start.left, text.left);\n let right = Math.max(start.right, text.right);\n const room = tree.clientWidth;\n if (room > 0 && right - left > room) {\n if (getComputedStyle(row).direction === \"rtl\") left = right - room;\n else right = left + room;\n }\n row.style.scrollMarginLeft = `${Math.min(0, box.left - left)}px`;\n row.style.scrollMarginRight = `${Math.min(0, right - box.right)}px`;\n}\n\nexport const TreeView = forwardRef<HTMLDivElement, TreeViewProps>(\n (\n {\n items,\n label = \"Navigation\",\n selectedId,\n onSelectionChange,\n expandedIds,\n defaultExpandedIds = [],\n onExpandedChange,\n disabled = false,\n emptyMessage = \"No items.\",\n className,\n ...attributes\n },\n ref,\n ) => {\n const [localSelection, setLocalSelection] = useState<string | null>(null);\n const [localExpanded, setLocalExpanded] = useState<readonly string[]>(defaultExpandedIds);\n const selection = selectedId === undefined ? localSelection : selectedId;\n const treeRef = useRef<HTMLDivElement | null>(null);\n const setRef = useMergedRefs<HTMLDivElement>(treeRef, ref);\n\n // A row spans the tree's full width, so scrolling the focused row into view by its own box,\n // as React Aria does a frame after focus moves, would keep whichever end is nearer and could\n // leave a deep row's label out of sight. When a row takes focus, before that frame, its scroll\n // margins trim it to its chevron and label: the one scroll lands on the label (DC-17).\n useEffect(() => {\n const tree = treeRef.current;\n if (!tree) return;\n const aim = (event: FocusEvent) => {\n const row = (event.target as Element | null)?.closest?.('[role=\"row\"]');\n if (row instanceof HTMLElement && tree.contains(row)) aimAtLabel(tree, row);\n };\n tree.addEventListener(\"focusin\", aim);\n return () => tree.removeEventListener(\"focusin\", aim);\n }, []);\n\n const renderItems = (nodes: readonly TreeViewItem[], depth = 0) =>\n nodes.map((item) => (\n <TreeItem\n key={item.id}\n id={item.id}\n textValue={item.label}\n isDisabled={disabled || item.disabled === true}\n className={itemStyles()}\n >\n <TreeItemContent>\n {({ isExpanded, hasChildItems }) => (\n <div className=\"flex min-h-3u items-center gap-1u p-1u\">\n {Array.from({ length: depth }, (_, index) => (\n <span key={index} aria-hidden=\"true\" className=\"w-1u shrink-0\" />\n ))}\n {hasChildItems ? (\n <AriaButton\n slot=\"chevron\"\n aria-label={isExpanded ? \"Collapse\" : \"Expand\"}\n isDisabled={disabled || item.disabled === true}\n className={`flex h-oprt-sm w-3u shrink-0 items-center justify-center border border-transparent text-oprt-base font-medium outline-hidden data-[focus-visible]:border-oprt-border-focus data-[focus-visible]:shadow-oprt-focus ${forcedFocus}`}\n >\n {isExpanded ? (\n <ChevronDown size={16} aria-hidden=\"true\" />\n ) : (\n <ChevronRight size={16} aria-hidden=\"true\" />\n )}\n </AriaButton>\n ) : (\n <span aria-hidden=\"true\" className=\"w-3u shrink-0\" />\n )}\n {/* A label keeps a readable width, about ten characters, and wraps inside it; past\n that the row grows wider than the tree, which scrolls (DC-17). */}\n <span data-oprt-tree-label=\"\" className=\"min-w-8u oprt-wrap-anywhere\">\n {item.label}\n </span>\n </div>\n )}\n </TreeItemContent>\n {item.children && renderItems(item.children, depth + 1)}\n </TreeItem>\n ));\n return (\n <Tree\n // Optional attributes may arrive as undefined, which React Aria's exact prop types reject.\n {...(attributes as Record<string, unknown>)}\n ref={setRef}\n aria-label={label}\n selectionMode=\"single\"\n disallowEmptySelection\n selectionBehavior=\"replace\"\n selectedKeys={selection ? [selection] : []}\n expandedKeys={expandedIds ?? localExpanded}\n className={clsx(treeStyles(), className)}\n onSelectionChange={(keys) => {\n if (disabled || keys === \"all\") return;\n const next = [...keys][0];\n const id = next === undefined ? null : String(next);\n if (selectedId === undefined) setLocalSelection(id);\n onSelectionChange?.(id);\n }}\n onExpandedChange={(keys: Set<Key>) => {\n if (disabled) return;\n const next = [...keys].map(String);\n if (expandedIds === undefined) setLocalExpanded(next);\n onExpandedChange?.(next);\n }}\n renderEmptyState={() => <p className=\"m-0 p-2u text-oprt-text-dim\">{emptyMessage}</p>}\n >\n {renderItems(items)}\n </Tree>\n );\n },\n);\nTreeView.displayName = \"TreeView\";\n","UploadQueue/index.ts":"export { UploadQueue, type UploadQueueProps, type UploadQueueItem } from \"./UploadQueue\";\nexport { UploadQueueManifest } from \"./UploadQueue.manifest\";\n","UploadQueue/UploadQueue.manifest.ts":"import type { Component } from \"../_internal/manifest-entry\";\nexport const UploadQueueManifest: Record<string, Component> = {\n UploadQueue: {\n description:\n \"Controlled file queue with supplied transfer states, byte progress and explicit recovery actions.\",\n props: {\n label: { type: \"string\", required: true },\n items: {\n type: \"array\",\n required: true,\n description:\n \"Readonly UploadQueueItem[] with unique id, name, sizeBytes, state (ready/queued/uploading/cancel-requested/uploaded/failed/canceled), optional loadedBytes/totalBytes/message and explicit cancelable/retryable/removable capabilities. Ready means local selection; uploaded requires a supplied transport outcome. Any other state shows as Unknown state.\",\n },\n disabled: { type: \"boolean\", default: false },\n onCancel: {\n type: \"function\",\n description:\n \"Requests cancellation by stable ID only for explicitly cancelable queued/uploading items. Does not change supplied state.\",\n },\n onRetry: {\n type: \"function\",\n description: \"Requests retry by stable ID only for explicitly retryable failed items.\",\n },\n onRemove: {\n type: \"function\",\n description:\n \"Requests removal by stable ID only for explicitly removable items. The row remains until items changes; focus moves only when its focused element is removed.\",\n },\n className: { type: \"string\" },\n },\n composable_with: [\"FileUpload\", \"AttachmentPreview\", \"JobStatus\"],\n never_inside: [\"Button\"],\n example:\n '<UploadQueue label=\"Import files\" items={files} onCancel={requestCancel} onRetry={requestRetry} onRemove={requestRemove} />',\n },\n};\n","UploadQueue/UploadQueue.tsx":"\"use client\";\n\nimport {\n forwardRef,\n useEffect,\n useImperativeHandle,\n useRef,\n useState,\n type HTMLAttributes,\n} from \"react\";\nimport { cva } from \"class-variance-authority\";\nimport clsx from \"clsx\";\nimport { Badge } from \"../Badge/Badge\";\nimport { Button } from \"../Button/Button\";\nimport { Progress } from \"../Progress/Progress\";\nimport { ByteCountLabel, NUMBER_UNIT_SPACE } from \"../_internal/bytes\";\nimport { joinIds, usePartIds } from \"../_internal/fieldIds\";\nimport { useRestoreFocusOnRemoval } from \"../_internal/useRestoreFocusOnRemoval\";\nexport type UploadQueueItem = {\n id: string;\n name: string;\n sizeBytes: number;\n state: \"ready\" | \"queued\" | \"uploading\" | \"cancel-requested\" | \"uploaded\" | \"failed\" | \"canceled\";\n loadedBytes?: number;\n totalBytes?: number;\n message?: string;\n cancelable?: boolean;\n retryable?: boolean;\n removable?: boolean;\n};\nexport type UploadQueueProps = Omit<HTMLAttributes<HTMLDivElement>, \"children\"> & {\n label: string;\n items: readonly UploadQueueItem[];\n disabled?: boolean;\n onCancel?: (id: string) => void;\n onRetry?: (id: string) => void;\n onRemove?: (id: string) => void;\n};\nconst queueStyles = cva(\n \"flex min-w-0 flex-col gap-2u font-sans text-oprt-base normal-case text-oprt-text outline-hidden focus-visible:shadow-oprt-focus\",\n);\nconst labels = {\n ready: \"Ready locally\",\n queued: \"Queued\",\n uploading: \"Uploading\",\n \"cancel-requested\": \"Cancel requested\",\n uploaded: \"Uploaded\",\n failed: \"Failed\",\n canceled: \"Canceled\",\n} as const;\nconst variants = {\n ready: \"neutral\",\n queued: \"neutral\",\n uploading: \"info\",\n \"cancel-requested\": \"warning\",\n uploaded: \"success\",\n failed: \"error\",\n canceled: \"neutral\",\n} as const;\ntype KnownState = keyof typeof labels;\nconst KNOWN_STATES = Object.keys(labels) as KnownState[];\nconst isKnownState = (state: unknown): state is KnownState =>\n typeof state === \"string\" && Object.prototype.hasOwnProperty.call(labels, state);\n/** A state the queue does not know (a transport that adds \"paused\") reads as this, never \"undefined\". */\nconst UNKNOWN_STATE = \"Unknown state\";\nconst stateLabel = (state: unknown) => (isKnownState(state) ? labels[state] : UNKNOWN_STATE);\nconst count = (value: number) => Number.isSafeInteger(value) && value >= 0;\n\n// Dev only: once per unknown state, so a re-render does not repeat it.\nconst reportedStates = new Set<string>();\nfunction reportUnknownState(state: unknown) {\n const received = String(state);\n if (process.env.NODE_ENV === \"production\" || reportedStates.has(received)) return;\n reportedStates.add(received);\n console.error(\n JSON.stringify(\n {\n component: \"UploadQueue\",\n error: \"INVALID_PROP_VALUE\",\n prop: \"items[].state\",\n received,\n valid_values: KNOWN_STATES,\n suggestion: `Valid values for \\`items[].state\\`: ${KNOWN_STATES.join(\", \")}. The row shows \"${UNKNOWN_STATE}\" until the state is one of them.`,\n },\n null,\n 2,\n ),\n );\n}\n\n/** Whether any ID is blank or repeated. One pass, so a progress update in a long queue stays cheap. */\nfunction hasInvalidIdentity(items: readonly UploadQueueItem[]): boolean {\n const seen = new Set<string>();\n for (const item of items) {\n if (!item.id.trim() || seen.has(item.id)) return true;\n seen.add(item.id);\n }\n return false;\n}\n\n/** What the live status compares between updates: each row's name, state and message, by ID. */\ntype Row = [id: string, name: string, state: string, message: string];\nconst rowsOf = (items: readonly UploadQueueItem[]): Row[] =>\n items.map((item) => [item.id, item.name, String(item.state), item.message ?? \"\"]);\n/** More changed rows than this in one update are counted by state instead of named one by one. */\nconst NAMED_CHANGES = 3;\n\n/**\n * Coordinator ruling (UploadQueue-R2): the status announces only what changed since the last\n * update, never the whole queue: each added or changed row's name, state and message, each removed\n * row, and \"No files selected.\" once the queue empties. When more rows than NAMED_CHANGES change at\n * once, it counts them by state. Byte ticks change nothing here, so they are not announced.\n */\nfunction describeChanges(before: readonly Row[], after: readonly Row[]): string {\n const previous = new Map(before.map((row) => [row[0], row]));\n const current = new Set(after.map((row) => row[0]));\n const changes: { label: string; sentence: string }[] = [];\n for (const [id, name, state, message] of after) {\n const was = previous.get(id);\n if (was && was[1] === name && was[2] === state && was[3] === message) continue;\n const label = stateLabel(state);\n changes.push({ label, sentence: `${name}: ${label}.${message ? ` ${message}` : \"\"}` });\n }\n for (const [id, name] of before)\n if (!current.has(id)) changes.push({ label: \"Removed\", sentence: `${name}: Removed.` });\n const parts =\n changes.length <= NAMED_CHANGES\n ? changes.map((change) => change.sentence)\n : Array.from(\n changes.reduce(\n (counts, { label }) => counts.set(label, (counts.get(label) ?? 0) + 1),\n new Map<string, number>(),\n ),\n ([label, total]) => `${label}: ${total} ${total === 1 ? \"file\" : \"files\"}.`,\n );\n if (after.length === 0 && before.length > 0) parts.push(\"No files selected.\");\n return parts.join(\" \");\n}\n\nexport const UploadQueue = forwardRef<HTMLDivElement, UploadQueueProps>(\n (\n {\n label,\n items,\n onCancel,\n onRetry,\n onRemove,\n disabled = false,\n className,\n onFocusCapture,\n onBlurCapture,\n \"aria-labelledby\": labelledBy,\n ...props\n },\n ref,\n ) => {\n const root = useRef<HTMLDivElement>(null);\n // Hydration-safe part ids: after a hydration that disagreed with the server's ids, the base is\n // the one the page carries, so a label the application adds later still names the region.\n const ids = usePartIds([\"label\"], {\n id: props.id,\n readRendered: () => root.current?.getAttribute(\"data-oprt-ids\"),\n });\n const rows = useRef(new Map<string, HTMLLIElement>());\n useImperativeHandle(ref, () => root.current!, []);\n const invalidIdentity = hasInvalidIdentity(items);\n // Focus moves when the row that held it is removed, or its focused action is disabled (the\n // queue is disabled, or duplicate IDs block the actions), and only if the user is still there.\n const restoreFocus = useRestoreFocusOnRemoval<HTMLDivElement, { index: number }>(root, {\n track: (element) => {\n const index = items.findIndex((item) => rows.current.get(item.id)?.contains(element));\n return index >= 0 ? { index } : null;\n },\n // The next row's first enabled action, else the row itself, else the queue.\n findTarget: ({ index }) => {\n const next = items[Math.min(index, items.length - 1)];\n const row = next ? rows.current.get(next.id) : null;\n return row?.querySelector<HTMLElement>(\"button:not(:disabled)\") ?? row ?? root.current;\n },\n });\n\n const signature = JSON.stringify(rowsOf(items));\n const announced = useRef<Row[] | null>(null);\n const [announcement, setAnnouncement] = useState(\"\");\n // The sentence last put in the status, and the frame that will put a repeated one back.\n const lastAnnouncement = useRef(\"\");\n const repeatFrame = useRef<number | null>(null);\n useEffect(\n () => () => {\n if (repeatFrame.current !== null) cancelAnimationFrame(repeatFrame.current);\n },\n [],\n );\n useEffect(() => {\n const after = JSON.parse(signature) as Row[];\n const before = announced.current;\n announced.current = after;\n // The first render announces nothing: the rows are on the page to be read.\n if (before === null) return;\n const text = describeChanges(before, after);\n if (!text) return;\n if (repeatFrame.current !== null) {\n cancelAnimationFrame(repeatFrame.current);\n repeatFrame.current = null;\n }\n if (text !== lastAnnouncement.current) {\n lastAnnouncement.current = text;\n setAnnouncement(text);\n return;\n }\n // The same sentence again (\"Uploaded: 4 files.\" for a second batch) would leave the status\n // unchanged, and a screen reader would say nothing (B7 review, Minor 2). Empty it now and put\n // the sentence back on the next frame: two changes, and the sentence is heard again.\n setAnnouncement(\"\");\n repeatFrame.current = requestAnimationFrame(() => {\n repeatFrame.current = null;\n setAnnouncement(text);\n });\n }, [signature]);\n\n const unknownStates = items\n .filter((item) => !isKnownState(item.state))\n .map((item) => item.state);\n const unknownKey = JSON.stringify(unknownStates);\n useEffect(() => {\n if (process.env.NODE_ENV === \"production\") return;\n for (const state of JSON.parse(unknownKey) as unknown[]) reportUnknownState(state);\n }, [unknownKey]);\n\n return (\n <div\n {...props}\n ref={root}\n role=\"region\"\n tabIndex={-1}\n data-oprt-ids={ids.base}\n aria-labelledby={joinIds(ids.label, labelledBy)}\n className={clsx(queueStyles(), className)}\n onFocusCapture={(event) => {\n onFocusCapture?.(event);\n restoreFocus.onFocusCapture(event);\n }}\n onBlurCapture={(event) => {\n onBlurCapture?.(event);\n restoreFocus.onBlurCapture(event);\n }}\n >\n <h3\n id={ids.label}\n className=\"m-0 oprt-wrap-anywhere font-medium uppercase tracking-oprt-sm\"\n >\n {label}\n </h3>\n <p role=\"status\" aria-atomic=\"true\" className=\"sr-only\">\n {announcement}\n </p>\n {invalidIdentity && (\n <p role=\"alert\" className=\"m-0 text-oprt-error\">\n Queue item IDs must be unique and nonempty. Actions are unavailable.\n </p>\n )}\n {!items.length ? (\n <p className=\"m-0 text-oprt-text-dim\">No files selected.</p>\n ) : (\n <ul className=\"m-0 flex min-w-0 list-none flex-col gap-2u p-0\">\n {items.map((item, index) => {\n const transferring = item.state === \"uploading\" || item.state === \"cancel-requested\";\n const invalidProgress =\n (item.loadedBytes !== undefined && !count(item.loadedBytes)) ||\n (item.totalBytes !== undefined && !count(item.totalBytes)) ||\n (item.loadedBytes !== undefined &&\n item.totalBytes !== undefined &&\n item.loadedBytes > item.totalBytes);\n const validLoaded = item.loadedBytes !== undefined && count(item.loadedBytes);\n const blocked = disabled || invalidIdentity;\n return (\n <li\n key={invalidIdentity ? index : item.id}\n ref={(node) => {\n if (node) rows.current.set(item.id, node);\n else rows.current.delete(item.id);\n }}\n tabIndex={-1}\n className=\"flex min-w-0 flex-col gap-1u border border-oprt-border p-2u outline-hidden focus-visible:shadow-oprt-focus\"\n >\n <div className=\"flex min-w-0 flex-wrap items-start justify-between gap-1u\">\n <span className=\"min-w-0 oprt-wrap-anywhere\">{item.name}</span>\n <Badge\n appearance=\"text\"\n variant={isKnownState(item.state) ? variants[item.state] : \"neutral\"}\n >\n {stateLabel(item.state)}\n </Badge>\n </div>\n <p className=\"m-0 text-oprt-text-dim\">\n <ByteCountLabel value={item.sizeBytes} />\n </p>\n {item.message && <p className=\"m-0 oprt-wrap-anywhere\">{item.message}</p>}\n {transferring &&\n (invalidProgress ? (\n <p className=\"m-0 text-oprt-error\">Invalid progress counts.</p>\n ) : item.totalBytes === 0 ? (\n <p className=\"m-0 text-oprt-text-dim\">\n {`0${NUMBER_UNIT_SPACE}bytes total; no percentage available.`}\n </p>\n ) : (\n <>\n {/* Named, not labeled: a visible label repeated the file name in capitals. */}\n <Progress\n aria-label={`Upload progress for ${item.name}`}\n {...(item.totalBytes !== undefined && validLoaded\n ? { value: item.loadedBytes, max: item.totalBytes }\n : {})}\n />\n <p className=\"m-0 font-mono tabular-nums text-oprt-text-dim\">\n {validLoaded\n ? `${item.loadedBytes}${NUMBER_UNIT_SPACE}bytes transferred`\n : \"Transferred byte count unavailable\"}\n {item.totalBytes !== undefined\n ? ` of ${item.totalBytes}${NUMBER_UNIT_SPACE}bytes`\n : \"; total unknown\"}\n .\n </p>\n </>\n ))}\n <div className=\"flex flex-wrap gap-1u\">\n {item.cancelable &&\n onCancel &&\n (item.state === \"queued\" || item.state === \"uploading\") && (\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n disabled={blocked}\n aria-label={`Cancel ${item.name}`}\n onClick={() => {\n if (!blocked) onCancel(item.id);\n }}\n >\n Cancel\n </Button>\n )}\n {item.retryable && onRetry && item.state === \"failed\" && (\n <Button\n size=\"sm\"\n variant=\"secondary\"\n disabled={blocked}\n aria-label={`Retry ${item.name}`}\n onClick={() => {\n if (!blocked) onRetry(item.id);\n }}\n >\n Retry\n </Button>\n )}\n {item.removable && onRemove && (\n <Button\n size=\"sm\"\n variant=\"tertiary\"\n disabled={blocked}\n aria-label={`Remove ${item.name}`}\n onClick={() => {\n if (!blocked) onRemove(item.id);\n }}\n >\n Remove\n </Button>\n )}\n </div>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n );\n },\n);\nUploadQueue.displayName = \"UploadQueue\";\n"},"ranges":{"@internationalized/date":"^3.12.3","@oprt/icons":"^0.1.0","@oprt/react":"^0.1.0","@radix-ui/react-slot":"^1.1.0","@radix-ui/react-toast":"^1.2.15","class-variance-authority":"^0.7.0","clsx":"^2.1.1","ol":"10.10.0","react":">=18","react-aria":"^3.51.0","react-aria-components":"^1.20.0"}}
|