@oprt/react 0.0.0-stage → 0.2.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.
Files changed (266) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -2
  3. package/dist/ActionStatus/ActionStatus.js +104 -0
  4. package/dist/ActionStatus/ActionStatus.manifest.js +64 -0
  5. package/dist/ActionStatus/index.js +7 -0
  6. package/dist/Alert/Alert.js +86 -0
  7. package/dist/Alert/Alert.manifest.js +36 -0
  8. package/dist/Alert/index.js +7 -0
  9. package/dist/AppShell/AppShell.js +103 -0
  10. package/dist/AppShell/AppShell.manifest.js +66 -0
  11. package/dist/AppShell/index.js +9 -0
  12. package/dist/AttachmentPreview/AttachmentPreview.js +145 -0
  13. package/dist/AttachmentPreview/AttachmentPreview.manifest.js +42 -0
  14. package/dist/AttachmentPreview/index.js +9 -0
  15. package/dist/Avatar/Avatar.js +87 -0
  16. package/dist/Avatar/Avatar.manifest.js +25 -0
  17. package/dist/Avatar/index.js +7 -0
  18. package/dist/Badge/Badge.js +124 -0
  19. package/dist/Badge/Badge.manifest.js +41 -0
  20. package/dist/Badge/index.js +7 -0
  21. package/dist/Breadcrumb/Breadcrumb.js +64 -0
  22. package/dist/Breadcrumb/Breadcrumb.manifest.js +28 -0
  23. package/dist/Breadcrumb/index.js +7 -0
  24. package/dist/BulkActionBar/BulkActionBar.js +145 -0
  25. package/dist/BulkActionBar/BulkActionBar.manifest.js +46 -0
  26. package/dist/BulkActionBar/index.js +9 -0
  27. package/dist/BulkActionBar/selection.js +44 -0
  28. package/dist/BulkResults/BulkResults.js +269 -0
  29. package/dist/BulkResults/BulkResults.manifest.js +45 -0
  30. package/dist/BulkResults/index.js +7 -0
  31. package/dist/Button/Button.js +170 -0
  32. package/dist/Button/Button.manifest.js +36 -0
  33. package/dist/Button/index.js +7 -0
  34. package/dist/ButtonGroup/ButtonGroup.js +60 -0
  35. package/dist/ButtonGroup/ButtonGroup.manifest.js +22 -0
  36. package/dist/ButtonGroup/index.js +7 -0
  37. package/dist/Calendar/Calendar.js +106 -0
  38. package/dist/Calendar/Calendar.manifest.js +32 -0
  39. package/dist/Calendar/index.js +7 -0
  40. package/dist/Card/Card.js +70 -0
  41. package/dist/Card/Card.manifest.js +57 -0
  42. package/dist/Card/index.js +16 -0
  43. package/dist/Chart/Chart.js +1200 -0
  44. package/dist/Chart/Chart.manifest.js +110 -0
  45. package/dist/Chart/ChartControls.js +190 -0
  46. package/dist/Chart/geometry.js +217 -0
  47. package/dist/Chart/index.js +9 -0
  48. package/dist/Checkbox/Checkbox.js +166 -0
  49. package/dist/Checkbox/Checkbox.manifest.js +38 -0
  50. package/dist/Checkbox/index.js +7 -0
  51. package/dist/Collapsible/Collapsible.js +177 -0
  52. package/dist/Collapsible/Collapsible.manifest.js +54 -0
  53. package/dist/Collapsible/index.js +12 -0
  54. package/dist/CollectionState/CollectionState.js +68 -0
  55. package/dist/CollectionState/CollectionState.manifest.js +44 -0
  56. package/dist/CollectionState/index.js +7 -0
  57. package/dist/ComboBox/ComboBox.js +221 -0
  58. package/dist/ComboBox/ComboBox.manifest.js +87 -0
  59. package/dist/ComboBox/index.js +7 -0
  60. package/dist/CommandPalette/CommandPalette.js +276 -0
  61. package/dist/CommandPalette/CommandPalette.manifest.js +24 -0
  62. package/dist/CommandPalette/index.js +7 -0
  63. package/dist/ConfirmDialog/ConfirmDialog.js +84 -0
  64. package/dist/ConfirmDialog/ConfirmDialog.manifest.js +32 -0
  65. package/dist/ConfirmDialog/index.js +7 -0
  66. package/dist/ContextMenu/ContextMenu.js +192 -0
  67. package/dist/ContextMenu/ContextMenu.manifest.js +41 -0
  68. package/dist/ContextMenu/index.js +7 -0
  69. package/dist/CoordinateInput/CoordinateInput.js +141 -0
  70. package/dist/CoordinateInput/CoordinateInput.manifest.js +36 -0
  71. package/dist/CoordinateInput/index.js +9 -0
  72. package/dist/CopyButton/CopyButton.js +129 -0
  73. package/dist/CopyButton/CopyButton.manifest.js +35 -0
  74. package/dist/CopyButton/index.js +7 -0
  75. package/dist/DataGrid/DataGrid.js +459 -0
  76. package/dist/DataGrid/DataGrid.manifest.js +135 -0
  77. package/dist/DataGrid/PageControls.js +41 -0
  78. package/dist/DataGrid/index.js +9 -0
  79. package/dist/DataTable/DataTable.js +375 -0
  80. package/dist/DataTable/DataTable.manifest.js +86 -0
  81. package/dist/DataTable/index.js +9 -0
  82. package/dist/DataTable/sort.js +45 -0
  83. package/dist/DataTable/useScrollsSideways.js +61 -0
  84. package/dist/DataTable/useTableWindow.js +185 -0
  85. package/dist/DatePicker/DatePicker.js +187 -0
  86. package/dist/DatePicker/DatePicker.manifest.js +40 -0
  87. package/dist/DatePicker/index.js +7 -0
  88. package/dist/DateRangePicker/DateRangePicker.js +156 -0
  89. package/dist/DateRangePicker/DateRangePicker.manifest.js +41 -0
  90. package/dist/DateRangePicker/index.js +7 -0
  91. package/dist/DateTimePicker/DateTimePicker.js +213 -0
  92. package/dist/DateTimePicker/DateTimePicker.manifest.js +44 -0
  93. package/dist/DateTimePicker/index.js +7 -0
  94. package/dist/DescriptionList/DescriptionList.js +93 -0
  95. package/dist/DescriptionList/DescriptionList.manifest.js +40 -0
  96. package/dist/DescriptionList/index.js +9 -0
  97. package/dist/Dialog/Dialog.js +214 -0
  98. package/dist/Dialog/Dialog.manifest.js +31 -0
  99. package/dist/Dialog/index.js +7 -0
  100. package/dist/Divider/Divider.js +64 -0
  101. package/dist/Divider/Divider.manifest.js +21 -0
  102. package/dist/Divider/index.js +7 -0
  103. package/dist/DropdownMenu/DropdownMenu.js +170 -0
  104. package/dist/DropdownMenu/DropdownMenu.manifest.js +82 -0
  105. package/dist/DropdownMenu/index.js +16 -0
  106. package/dist/EntityPreview/EntityPreview.js +82 -0
  107. package/dist/EntityPreview/EntityPreview.manifest.js +43 -0
  108. package/dist/EntityPreview/index.js +7 -0
  109. package/dist/EventTimeline/EventTimeline.js +249 -0
  110. package/dist/EventTimeline/EventTimeline.manifest.js +57 -0
  111. package/dist/EventTimeline/index.js +7 -0
  112. package/dist/Field/Field.js +81 -0
  113. package/dist/Field/Field.manifest.js +32 -0
  114. package/dist/Field/index.js +9 -0
  115. package/dist/FileUpload/FileUpload.js +215 -0
  116. package/dist/FileUpload/FileUpload.manifest.js +48 -0
  117. package/dist/FileUpload/index.js +7 -0
  118. package/dist/FilterBar/FilterBar.js +173 -0
  119. package/dist/FilterBar/FilterBar.manifest.js +49 -0
  120. package/dist/FilterBar/index.js +7 -0
  121. package/dist/FormErrorSummary/FormErrorSummary.js +70 -0
  122. package/dist/FormErrorSummary/FormErrorSummary.manifest.js +29 -0
  123. package/dist/FormErrorSummary/index.js +9 -0
  124. package/dist/FormSection/FormSection.js +51 -0
  125. package/dist/FormSection/FormSection.manifest.js +30 -0
  126. package/dist/FormSection/index.js +7 -0
  127. package/dist/InlineEdit/InlineEdit.js +261 -0
  128. package/dist/InlineEdit/InlineEdit.manifest.js +40 -0
  129. package/dist/InlineEdit/index.js +7 -0
  130. package/dist/Input/Input.js +126 -0
  131. package/dist/Input/Input.manifest.js +49 -0
  132. package/dist/Input/index.js +7 -0
  133. package/dist/Inspector/Inspector.js +63 -0
  134. package/dist/Inspector/Inspector.manifest.js +35 -0
  135. package/dist/Inspector/index.js +7 -0
  136. package/dist/JobStatus/JobStatus.js +118 -0
  137. package/dist/JobStatus/JobStatus.manifest.js +53 -0
  138. package/dist/JobStatus/index.js +7 -0
  139. package/dist/LayerControl/LayerControl.js +199 -0
  140. package/dist/LayerControl/LayerControl.manifest.js +34 -0
  141. package/dist/LayerControl/index.js +7 -0
  142. package/dist/Link/Link.js +83 -0
  143. package/dist/Link/Link.manifest.js +37 -0
  144. package/dist/Link/index.js +7 -0
  145. package/dist/MapControls/MapControls.js +122 -0
  146. package/dist/MapControls/MapControls.manifest.js +31 -0
  147. package/dist/MapControls/index.js +7 -0
  148. package/dist/MapLegend/MapLegend.js +89 -0
  149. package/dist/MapLegend/MapLegend.manifest.js +29 -0
  150. package/dist/MapLegend/index.js +7 -0
  151. package/dist/MapScale/MapScale.js +96 -0
  152. package/dist/MapScale/MapScale.manifest.js +31 -0
  153. package/dist/MapScale/index.js +7 -0
  154. package/dist/MediaFrame/MediaFrame.js +113 -0
  155. package/dist/MediaFrame/MediaFrame.manifest.js +57 -0
  156. package/dist/MediaFrame/index.js +7 -0
  157. package/dist/MultiSelect/MultiSelect.js +113 -0
  158. package/dist/MultiSelect/MultiSelect.manifest.js +51 -0
  159. package/dist/MultiSelect/index.js +7 -0
  160. package/dist/NumberInput/NumberInput.js +210 -0
  161. package/dist/NumberInput/NumberInput.manifest.js +56 -0
  162. package/dist/NumberInput/index.js +7 -0
  163. package/dist/OperationalStatus/OperationalStatus.js +89 -0
  164. package/dist/OperationalStatus/OperationalStatus.manifest.js +39 -0
  165. package/dist/OperationalStatus/index.js +9 -0
  166. package/dist/PlaybackControls/PlaybackControls.js +289 -0
  167. package/dist/PlaybackControls/PlaybackControls.manifest.js +85 -0
  168. package/dist/PlaybackControls/index.js +7 -0
  169. package/dist/Popover/Popover.js +302 -0
  170. package/dist/Popover/Popover.manifest.js +50 -0
  171. package/dist/Popover/index.js +7 -0
  172. package/dist/Progress/Progress.js +132 -0
  173. package/dist/Progress/Progress.manifest.js +30 -0
  174. package/dist/Progress/index.js +7 -0
  175. package/dist/PropertyFilter/PropertyFilter.js +306 -0
  176. package/dist/PropertyFilter/PropertyFilter.manifest.js +32 -0
  177. package/dist/PropertyFilter/index.js +11 -0
  178. package/dist/PropertyFilter/tokenError.js +17 -0
  179. package/dist/RadioGroup/RadioGroup.js +143 -0
  180. package/dist/RadioGroup/RadioGroup.manifest.js +44 -0
  181. package/dist/RadioGroup/index.js +9 -0
  182. package/dist/Select/Select.js +219 -0
  183. package/dist/Select/Select.manifest.js +77 -0
  184. package/dist/Select/index.js +9 -0
  185. package/dist/Sidebar/Sidebar.js +146 -0
  186. package/dist/Sidebar/Sidebar.manifest.js +72 -0
  187. package/dist/Sidebar/index.js +18 -0
  188. package/dist/Skeleton/Skeleton.js +76 -0
  189. package/dist/Skeleton/Skeleton.manifest.js +30 -0
  190. package/dist/Skeleton/index.js +7 -0
  191. package/dist/Slider/Slider.js +246 -0
  192. package/dist/Slider/Slider.manifest.js +56 -0
  193. package/dist/Slider/index.js +7 -0
  194. package/dist/Sparkline/Sparkline.js +135 -0
  195. package/dist/Sparkline/Sparkline.manifest.js +37 -0
  196. package/dist/Sparkline/index.js +7 -0
  197. package/dist/SplitPane/SplitPane.js +316 -0
  198. package/dist/SplitPane/SplitPane.manifest.js +79 -0
  199. package/dist/SplitPane/index.js +7 -0
  200. package/dist/StatCard/StatCard.js +163 -0
  201. package/dist/StatCard/StatCard.manifest.js +45 -0
  202. package/dist/StatCard/index.js +7 -0
  203. package/dist/Tabs/Tabs.js +181 -0
  204. package/dist/Tabs/Tabs.manifest.js +72 -0
  205. package/dist/Tabs/index.js +16 -0
  206. package/dist/Tabs/useTabsIndicator.js +71 -0
  207. package/dist/Tabs/useTabsScroll.js +123 -0
  208. package/dist/TagGroup/TagGroup.js +142 -0
  209. package/dist/TagGroup/TagGroup.manifest.js +35 -0
  210. package/dist/TagGroup/index.js +7 -0
  211. package/dist/Textarea/Textarea.js +68 -0
  212. package/dist/Textarea/Textarea.manifest.js +25 -0
  213. package/dist/Textarea/index.js +7 -0
  214. package/dist/TimeField/TimeField.js +118 -0
  215. package/dist/TimeField/TimeField.manifest.js +42 -0
  216. package/dist/TimeField/index.js +7 -0
  217. package/dist/TimeRange/TimeRange.js +184 -0
  218. package/dist/TimeRange/TimeRange.manifest.js +48 -0
  219. package/dist/TimeRange/index.js +9 -0
  220. package/dist/Toast/Toast.js +188 -0
  221. package/dist/Toast/Toast.manifest.js +77 -0
  222. package/dist/Toast/index.js +9 -0
  223. package/dist/Toggle/Toggle.js +154 -0
  224. package/dist/Toggle/Toggle.manifest.js +54 -0
  225. package/dist/Toggle/index.js +7 -0
  226. package/dist/Toolbar/Toolbar.js +86 -0
  227. package/dist/Toolbar/Toolbar.manifest.js +70 -0
  228. package/dist/Toolbar/index.js +12 -0
  229. package/dist/Tooltip/Tooltip.js +123 -0
  230. package/dist/Tooltip/Tooltip.manifest.js +27 -0
  231. package/dist/Tooltip/index.js +7 -0
  232. package/dist/TreeView/TreeView.js +129 -0
  233. package/dist/TreeView/TreeView.manifest.js +56 -0
  234. package/dist/TreeView/index.js +7 -0
  235. package/dist/UploadQueue/UploadQueue.js +296 -0
  236. package/dist/UploadQueue/UploadQueue.manifest.js +34 -0
  237. package/dist/UploadQueue/index.js +7 -0
  238. package/dist/_internal/MotionPopover.js +113 -0
  239. package/dist/_internal/aria.js +40 -0
  240. package/dist/_internal/bytes.js +18 -0
  241. package/dist/_internal/controlStyles.js +26 -0
  242. package/dist/_internal/date.js +179 -0
  243. package/dist/_internal/devValidate.js +51 -0
  244. package/dist/_internal/fieldIds.js +50 -0
  245. package/dist/_internal/hydration.js +74 -0
  246. package/dist/_internal/manifest-entry.js +0 -0
  247. package/dist/_internal/mergeRefs.js +68 -0
  248. package/dist/_internal/motion.js +138 -0
  249. package/dist/_internal/navigationLandmark.js +13 -0
  250. package/dist/_internal/numberText.js +176 -0
  251. package/dist/_internal/overlay.js +53 -0
  252. package/dist/_internal/segmentField.js +125 -0
  253. package/dist/_internal/suggestions.js +20 -0
  254. package/dist/_internal/timestamp.js +25 -0
  255. package/dist/_internal/unavailable.js +13 -0
  256. package/dist/_internal/useActionFocus.js +27 -0
  257. package/dist/_internal/useElementWidth.js +37 -0
  258. package/dist/_internal/useIsMounted.js +18 -0
  259. package/dist/_internal/useIsomorphicLayoutEffect.js +8 -0
  260. package/dist/_internal/useRestoreFocusOnRemoval.js +57 -0
  261. package/dist/index.cjs +17831 -0
  262. package/dist/index.d.cts +2330 -0
  263. package/dist/index.d.ts +2330 -0
  264. package/dist/index.js +407 -0
  265. package/dist/version.js +8 -0
  266. package/package.json +80 -3
@@ -0,0 +1,68 @@
1
+ "use client";
2
+
3
+ // src/Textarea/Textarea.tsx
4
+ import { forwardRef } from "react";
5
+ import { cva } from "class-variance-authority";
6
+ import clsx from "clsx";
7
+ import { useFieldWiring } from "../_internal/fieldIds.js";
8
+ import { FORCED_FOCUS_WITHIN, FORCED_NO_RESTING_OUTLINE } from "../_internal/controlStyles.js";
9
+ import { jsx } from "react/jsx-runtime";
10
+ var wrapperStyles = cva(
11
+ // Disabled looks like DC-9's disabled buttons (coordinator ruling on DC-B04-2): dim text, the
12
+ // control outline and a muted fill, with no fade of the whole field.
13
+ "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",
14
+ {
15
+ variants: {
16
+ invalid: {
17
+ true: "border-oprt-error focus-within:border-oprt-error",
18
+ // The control outline (design call DC-3); focus in the focus token, as the siblings do.
19
+ false: "border-oprt-border-control focus-within:border-oprt-border-focus"
20
+ }
21
+ },
22
+ defaultVariants: { invalid: false }
23
+ }
24
+ );
25
+ var textareaStyles = "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";
26
+ var Textarea = forwardRef(
27
+ ({
28
+ className,
29
+ invalid,
30
+ rows = 4,
31
+ id,
32
+ required,
33
+ "aria-describedby": ariaDescribedBy,
34
+ "aria-invalid": ariaInvalid,
35
+ ...props
36
+ }, ref) => {
37
+ const wiring = useFieldWiring(
38
+ { id, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, invalid, required },
39
+ { label: "for" }
40
+ );
41
+ return (
42
+ // In forced colors focus is a 2px CanvasText outline around the field (Textarea-B5-1).
43
+ /* @__PURE__ */ jsx(
44
+ "div",
45
+ {
46
+ className: clsx(wrapperStyles({ invalid: wiring.invalid }), FORCED_FOCUS_WITHIN, className),
47
+ children: /* @__PURE__ */ jsx(
48
+ "textarea",
49
+ {
50
+ ref,
51
+ id: wiring.id,
52
+ rows,
53
+ "aria-invalid": wiring.ariaInvalid,
54
+ "aria-describedby": wiring.describedBy,
55
+ required: wiring.required,
56
+ className: clsx(textareaStyles, FORCED_NO_RESTING_OUTLINE),
57
+ ...props
58
+ }
59
+ )
60
+ }
61
+ )
62
+ );
63
+ }
64
+ );
65
+ Textarea.displayName = "Textarea";
66
+ export {
67
+ Textarea
68
+ };
@@ -0,0 +1,25 @@
1
+ // src/Textarea/Textarea.manifest.ts
2
+ var TextareaManifest = {
3
+ Textarea: {
4
+ description: "Multi-line text input. Fixed rows (default 4); scroll on overflow. No native resize handle (rounded grip violates radius rule).",
5
+ props: {
6
+ rows: { type: "number", default: 4 },
7
+ value: { type: "string" },
8
+ defaultValue: { type: "string" },
9
+ placeholder: { type: "string" },
10
+ disabled: { type: "boolean", default: false },
11
+ required: { type: "boolean", default: false },
12
+ invalid: {
13
+ type: "boolean",
14
+ description: "Override Field's invalid state."
15
+ },
16
+ name: { type: "string" }
17
+ },
18
+ composable_with: ["Field"],
19
+ never_inside: ["Button", "Badge"],
20
+ example: '<Field label="MISSION NOTES" help="Free-form text.">\n <Textarea placeholder="ENTER NOTES..." />\n</Field>'
21
+ }
22
+ };
23
+ export {
24
+ TextareaManifest
25
+ };
@@ -0,0 +1,7 @@
1
+ // src/Textarea/index.ts
2
+ import { Textarea } from "./Textarea.js";
3
+ import { TextareaManifest } from "./Textarea.manifest.js";
4
+ export {
5
+ Textarea,
6
+ TextareaManifest
7
+ };
@@ -0,0 +1,118 @@
1
+ "use client";
2
+
3
+ // src/TimeField/TimeField.tsx
4
+ import { forwardRef, useContext, useEffect, useId, useMemo, useRef } from "react";
5
+ import {
6
+ TimeField as AriaTimeField,
7
+ TimeFieldStateContext,
8
+ DateInput,
9
+ DateSegment,
10
+ Label,
11
+ Text
12
+ } from "react-aria-components";
13
+ import { cva } from "class-variance-authority";
14
+ import clsx from "clsx";
15
+ import {
16
+ formatTimeOnly,
17
+ parseTimeOnly,
18
+ readTimeSegments,
19
+ timeConfiguration
20
+ } from "../_internal/date.js";
21
+ import { useHydrationRoot } from "../_internal/hydration.js";
22
+ import {
23
+ FIELD_BOX_STYLES,
24
+ FIELD_LABEL_STYLES,
25
+ SEGMENT_STYLES,
26
+ SegmentGroupContext,
27
+ firstSegmentFocus,
28
+ useReportSegments,
29
+ useSegmentDraft
30
+ } from "../_internal/segmentField.js";
31
+ import { jsx, jsxs } from "react/jsx-runtime";
32
+ var fieldStyles = cva("flex min-w-0 flex-col gap-1u font-sans text-oprt-base text-oprt-text");
33
+ function DraftObserver({ report }) {
34
+ const state = useContext(TimeFieldStateContext);
35
+ useReportSegments(state.segments, readTimeSegments, report);
36
+ return null;
37
+ }
38
+ var TimeField = forwardRef(
39
+ ({
40
+ label,
41
+ value,
42
+ onValueChange,
43
+ onDraftValidityChange,
44
+ min,
45
+ max,
46
+ disabled = false,
47
+ readOnly = false,
48
+ required = false,
49
+ id,
50
+ className
51
+ }, ref) => {
52
+ const generated = useId();
53
+ const inputId = id ?? `oprt-time-${generated}`;
54
+ const hydration = useHydrationRoot(inputId);
55
+ const config = useMemo(() => timeConfiguration(value, min, max), [value, min, max]);
56
+ const displayedValue = config.time ? formatTimeOnly(config.time) : null;
57
+ const group = useContext(SegmentGroupContext);
58
+ const draft = useSegmentDraft({
59
+ value,
60
+ displayed: displayedValue,
61
+ valid: value === null || !!config.time,
62
+ inactive: disabled || readOnly || config.invalidBounds
63
+ });
64
+ const time = parseTimeOnly(draft.snapshot.value);
65
+ const valid = draft.snapshot.valid && !draft.rejected && (draft.snapshot.empty ? !required : !!time && time.compare(config.min) >= 0 && time.compare(config.max) <= 0);
66
+ const error = config.error ?? (!valid ? draft.snapshot.empty && required ? "A time is required." : (
67
+ // The bound an edit crossed, named as for a supplied value (TimeField-R1).
68
+ draft.rejection ?? "Enter a complete, valid time within the allowed bounds."
69
+ ) : null);
70
+ const callback = useRef(onDraftValidityChange);
71
+ callback.current = onDraftValidityChange;
72
+ const pending = draft.pending;
73
+ useEffect(() => callback.current?.(!error, { pending }), [error, pending]);
74
+ return /* @__PURE__ */ jsx("div", { ...draft.editProps, children: /* @__PURE__ */ jsxs(
75
+ AriaTimeField,
76
+ {
77
+ ...hydration.probeProps,
78
+ id: inputId,
79
+ value: config.time,
80
+ minValue: config.min,
81
+ maxValue: config.max,
82
+ hourCycle: 24,
83
+ granularity: "minute",
84
+ isDisabled: disabled || config.invalidBounds,
85
+ isReadOnly: readOnly,
86
+ isRequired: required || !!group?.required,
87
+ isInvalid: !!error,
88
+ validationBehavior: "aria",
89
+ className: clsx(fieldStyles(), className),
90
+ onChange: (next) => {
91
+ if (disabled || readOnly || config.invalidBounds || draft.blurRejected()) return;
92
+ const nextValue = next ? formatTimeOnly(next) : null;
93
+ const nextTime = parseTimeOnly(nextValue);
94
+ if (next && (!nextTime || nextTime.compare(config.min) < 0 || nextTime.compare(config.max) > 0)) {
95
+ draft.reject(nextTime ? timeConfiguration(nextValue, min, max).error : null);
96
+ return;
97
+ }
98
+ draft.accept();
99
+ onValueChange(nextValue);
100
+ },
101
+ children: [
102
+ /* @__PURE__ */ jsxs(Label, { className: FIELD_LABEL_STYLES, children: [
103
+ label,
104
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " *" })
105
+ ] }),
106
+ /* @__PURE__ */ jsx(DraftObserver, { report: draft.report }),
107
+ /* @__PURE__ */ jsx(DateInput, { ref, ...firstSegmentFocus(disabled), className: FIELD_BOX_STYLES, children: (segment) => /* @__PURE__ */ jsx(DateSegment, { segment, className: SEGMENT_STYLES }) }),
108
+ error && /* @__PURE__ */ jsx(Text, { slot: "errorMessage", className: "m-0 text-oprt-error", children: error })
109
+ ]
110
+ },
111
+ hydration.key
112
+ ) });
113
+ }
114
+ );
115
+ TimeField.displayName = "TimeField";
116
+ export {
117
+ TimeField
118
+ };
@@ -0,0 +1,42 @@
1
+ // src/TimeField/TimeField.manifest.ts
2
+ var TimeFieldManifest = {
3
+ TimeField: {
4
+ description: "Controlled 24-hour wall-time field with segmented editing and explicit draft validity. It carries no date or time zone.",
5
+ props: {
6
+ label: { type: "string", required: true },
7
+ value: {
8
+ type: "string",
9
+ required: true,
10
+ description: "HH:mm from 00:00 through 23:59, or null."
11
+ },
12
+ onValueChange: {
13
+ type: "function",
14
+ required: true,
15
+ description: "Requests a complete valid time or null after clearing."
16
+ },
17
+ onDraftValidityChange: {
18
+ type: "function",
19
+ description: "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."
20
+ },
21
+ min: { type: "string", description: "Inclusive HH:mm lower bound." },
22
+ max: {
23
+ type: "string",
24
+ description: "Inclusive HH:mm upper bound. Overnight bounds are not supported."
25
+ },
26
+ disabled: { type: "boolean", default: false },
27
+ readOnly: { type: "boolean", default: false },
28
+ required: { type: "boolean", default: false },
29
+ id: {
30
+ type: "string",
31
+ description: "ID of the focusable time-control group. Focusing it or its forwarded HTMLDivElement ref enters the first segment."
32
+ },
33
+ className: { type: "string" }
34
+ },
35
+ composable_with: ["FormSection", "FormErrorSummary", "DateTimePicker"],
36
+ never_inside: ["Button"],
37
+ example: '<TimeField id="start-time" label="Start time" value={time} onValueChange={setTime} onDraftValidityChange={setTimeValid} required />'
38
+ }
39
+ };
40
+ export {
41
+ TimeFieldManifest
42
+ };
@@ -0,0 +1,7 @@
1
+ // src/TimeField/index.ts
2
+ import { TimeField } from "./TimeField.js";
3
+ import { TimeFieldManifest } from "./TimeField.manifest.js";
4
+ export {
5
+ TimeField,
6
+ TimeFieldManifest
7
+ };
@@ -0,0 +1,184 @@
1
+ "use client";
2
+
3
+ // src/TimeRange/TimeRange.tsx
4
+ import { cva } from "class-variance-authority";
5
+ import clsx from "clsx";
6
+ import { forwardRef, useEffect, useRef, useState } from "react";
7
+ import { Input } from "../Input/Input.js";
8
+ import { Button } from "../Button/Button.js";
9
+ import { joinIds, usePartIds } from "../_internal/fieldIds.js";
10
+ import { useMergedRefs } from "../_internal/mergeRefs.js";
11
+ import { formatUtcTimestamp, isValidTimestamp } from "../_internal/timestamp.js";
12
+ import { jsx, jsxs } from "react/jsx-runtime";
13
+ var rangeStyles = cva("flex min-w-0 flex-col gap-2u font-sans text-oprt-text");
14
+ var fieldStyles = "flex min-w-[min(100%,calc(22*var(--oprt-space-1)))] grow basis-[calc(22*var(--oprt-space-1))] flex-col gap-1u";
15
+ var pressedPresetStyles = [
16
+ "aria-[pressed=true]:border-oprt-accent-light aria-[pressed=true]:bg-oprt-accent aria-[pressed=true]:text-oprt-bg",
17
+ "aria-[pressed=true]:disabled:border-oprt-border-control aria-[pressed=true]:disabled:bg-oprt-surface-3 aria-[pressed=true]:disabled:text-oprt-text-dim",
18
+ "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]",
19
+ "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]",
20
+ "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]"
21
+ ].join(" ");
22
+ var PARTS = ["start", "end"];
23
+ var FIELDS = ["date", "time"];
24
+ var dateIsValid = (value) => isValidTimestamp(value, { years: "1-9999" });
25
+ var toInput = (value) => dateIsValid(value) ? new Date(value).toISOString().slice(0, -1) : "";
26
+ var toEndpoint = (value) => {
27
+ const [date = "", time = ""] = toInput(value).split("T");
28
+ return { date, time };
29
+ };
30
+ var draftOf = (range) => ({
31
+ start: toEndpoint(range.start),
32
+ end: toEndpoint(range.end)
33
+ });
34
+ var parseInput = (value) => {
35
+ if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?$/.test(value)) return NaN;
36
+ const parsed = Date.parse(`${value}Z`);
37
+ return dateIsValid(parsed) && toInput(parsed).slice(0, 16) === value.slice(0, 16) ? parsed : NaN;
38
+ };
39
+ var parseEndpoint = ({ date, time }) => parseInput(`${date}T${time}`);
40
+ var validate = ({ start, end }, min, max) => {
41
+ if (min !== void 0 && !dateIsValid(min) || max !== void 0 && !dateIsValid(max) || min !== void 0 && max !== void 0 && min >= max)
42
+ return "Time bounds must be valid UTC dates with minimum before maximum.";
43
+ if (!dateIsValid(start) || !dateIsValid(end)) return "Enter a valid start and end date in UTC.";
44
+ if (start >= end) return "Start must be before end.";
45
+ if (min !== void 0 && start < min || max !== void 0 && end > max)
46
+ return "The range must be within the allowed time bounds.";
47
+ return "";
48
+ };
49
+ var unchanged = (edited, parsed, applied, instant) => edited.date === applied.date && edited.time === applied.time || !Number.isNaN(parsed) && dateIsValid(instant) && parsed === Math.floor(instant);
50
+ function describeRange(start, end) {
51
+ const from = formatUtcTimestamp(start, { precision: "milliseconds" });
52
+ const to = formatUtcTimestamp(end, { precision: "milliseconds" });
53
+ if (!from || !to) return null;
54
+ const sameDay = from.slice(0, 10) === to.slice(0, 10);
55
+ return `${from} to ${sameDay ? to.slice(11) : to}`;
56
+ }
57
+ var TimeRange = forwardRef(
58
+ ({
59
+ value,
60
+ onValueChange,
61
+ onDraftValidityChange,
62
+ presets = [],
63
+ min,
64
+ max,
65
+ disabled = false,
66
+ label = "Time range",
67
+ className,
68
+ ...props
69
+ }, ref) => {
70
+ const root = useRef(null);
71
+ const setRoot = useMergedRefs(root, ref);
72
+ const ids = usePartIds(
73
+ ["start-date", "start-time", "end-date", "end-time", "error", "range", "status"],
74
+ { readRendered: () => root.current?.getAttribute("data-oprt-ids") }
75
+ );
76
+ const base = `${value.start}|${value.end}`;
77
+ const applied = draftOf(value);
78
+ const [draft, setDraft] = useState(() => ({ base, ...applied }));
79
+ const current = draft.base === base ? draft : applied;
80
+ const parsed = { start: parseEndpoint(current.start), end: parseEndpoint(current.end) };
81
+ const error = validate(parsed, min, max);
82
+ const pending = !(unchanged(current.start, parsed.start, applied.start, value.start) && unchanged(current.end, parsed.end, applied.end, value.end));
83
+ const valid = !error && !pending;
84
+ const callback = useRef(onDraftValidityChange);
85
+ callback.current = onDraftValidityChange;
86
+ useEffect(() => callback.current?.(valid, { pending }), [valid, pending]);
87
+ const readout = Number.isNaN(parsed.start) || Number.isNaN(parsed.end) ? null : describeRange(parsed.start, parsed.end);
88
+ const unapplied = !error && pending;
89
+ const edit = (part, field, next) => setDraft({ ...current, base, [part]: { ...current[part], [field]: next } });
90
+ const commit = () => {
91
+ if (!disabled && !error) onValueChange(parsed);
92
+ };
93
+ return /* @__PURE__ */ jsxs(
94
+ "div",
95
+ {
96
+ ...props,
97
+ ref: setRoot,
98
+ role: "group",
99
+ "aria-label": label,
100
+ "aria-describedby": joinIds(
101
+ props["aria-describedby"],
102
+ readout && ids.range,
103
+ unapplied && ids.status
104
+ ),
105
+ "data-oprt-ids": ids.base,
106
+ className: clsx(rangeStyles(), className),
107
+ children: [
108
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-end gap-2u", children: [
109
+ PARTS.map((part) => /* @__PURE__ */ jsx("div", { className: "flex min-w-0 grow flex-wrap gap-1u", children: FIELDS.map((field) => /* @__PURE__ */ jsxs("div", { className: fieldStyles, children: [
110
+ /* @__PURE__ */ jsxs(
111
+ "label",
112
+ {
113
+ htmlFor: ids[`${part}-${field}`],
114
+ className: "m-0 text-oprt-base font-medium uppercase tracking-oprt-caps",
115
+ children: [
116
+ part === "start" ? "Start" : "End",
117
+ " ",
118
+ field,
119
+ " (UTC)"
120
+ ]
121
+ }
122
+ ),
123
+ /* @__PURE__ */ jsx(
124
+ Input,
125
+ {
126
+ id: ids[`${part}-${field}`],
127
+ type: field,
128
+ step: field === "time" ? "0.001" : void 0,
129
+ value: current[part][field],
130
+ min: field === "date" && min !== void 0 ? toEndpoint(min).date : void 0,
131
+ max: field === "date" && max !== void 0 ? toEndpoint(max).date : void 0,
132
+ disabled,
133
+ "aria-invalid": !!error,
134
+ "aria-describedby": error ? ids.error : void 0,
135
+ className: "w-full [&_input]:font-mono",
136
+ onChange: (event) => edit(part, field, event.target.value),
137
+ onKeyDown: (event) => {
138
+ if (event.key === "Enter") {
139
+ event.preventDefault();
140
+ commit();
141
+ }
142
+ }
143
+ }
144
+ )
145
+ ] }, field)) }, part)),
146
+ /* @__PURE__ */ jsx(Button, { disabled, onClick: commit, children: "Apply range" })
147
+ ] }),
148
+ readout && /* @__PURE__ */ jsx(
149
+ "p",
150
+ {
151
+ id: ids.range,
152
+ className: "m-0 font-mono text-oprt-base text-oprt-text-dim oprt-wrap-anywhere",
153
+ children: readout
154
+ }
155
+ ),
156
+ error && /* @__PURE__ */ jsx("p", { id: ids.error, role: "alert", className: "m-0 text-oprt-base text-oprt-error", children: error }),
157
+ unapplied && /* @__PURE__ */ jsx("p", { id: ids.status, className: "m-0 text-oprt-base text-oprt-text-dim", children: "The range change has not been applied." }),
158
+ presets.length > 0 && /* @__PURE__ */ jsx("div", { role: "group", "aria-label": "Preset time ranges", className: "flex flex-wrap gap-1u", children: presets.map((preset) => /* @__PURE__ */ jsx(
159
+ Button,
160
+ {
161
+ size: "sm",
162
+ variant: "tertiary",
163
+ className: pressedPresetStyles,
164
+ "aria-pressed": preset.value.start === value.start && preset.value.end === value.end,
165
+ disabled: disabled || !!validate(preset.value, min, max),
166
+ onClick: () => {
167
+ if (!disabled && !validate(preset.value, min, max)) {
168
+ setDraft({ base, ...draftOf(preset.value) });
169
+ onValueChange({ ...preset.value });
170
+ }
171
+ },
172
+ children: preset.label
173
+ },
174
+ preset.id
175
+ )) })
176
+ ]
177
+ }
178
+ );
179
+ }
180
+ );
181
+ TimeRange.displayName = "TimeRange";
182
+ export {
183
+ TimeRange
184
+ };
@@ -0,0 +1,48 @@
1
+ // src/TimeRange/TimeRange.manifest.ts
2
+ var TimeRangeManifest = {
3
+ TimeRange: {
4
+ 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.",
5
+ props: {
6
+ value: {
7
+ type: "node",
8
+ required: true,
9
+ description: "TimeRangeValue: { start: number; end: number }, Unix epoch milliseconds. Start must precede end; dates must be in years 0001\u20139999."
10
+ },
11
+ onValueChange: {
12
+ type: "function",
13
+ required: true,
14
+ description: "(value: TimeRangeValue) => void. Only called on Apply, Enter or a valid preset; never for invalid drafts."
15
+ },
16
+ onDraftValidityChange: {
17
+ type: "function",
18
+ description: "(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."
19
+ },
20
+ presets: {
21
+ type: "array",
22
+ description: "Readonly TimeRangePreset array. Invalid or out-of-bounds presets are disabled.",
23
+ items: {
24
+ id: "string (required, unique)",
25
+ label: "string (required)",
26
+ value: "TimeRangeValue (required)"
27
+ }
28
+ },
29
+ min: {
30
+ type: "number",
31
+ description: "Inclusive minimum epoch milliseconds. Invalid dates or conflicting bounds prevent commits."
32
+ },
33
+ max: { type: "number", description: "Inclusive maximum epoch milliseconds." },
34
+ disabled: { type: "boolean", default: false },
35
+ label: {
36
+ type: "string",
37
+ default: "Time range",
38
+ description: "Accessible name of the range group; each input also has a visible UTC label."
39
+ }
40
+ },
41
+ composable_with: ["EventTimeline", "Chart", "DataGrid"],
42
+ never_inside: ["Button"],
43
+ example: '<TimeRange value={range} onValueChange={setRange} min={windowStart} max={windowEnd} presets={[{ id: "all", label: "Full window", value: { start: windowStart, end: windowEnd } }]} />'
44
+ }
45
+ };
46
+ export {
47
+ TimeRangeManifest
48
+ };
@@ -0,0 +1,9 @@
1
+ // src/TimeRange/index.ts
2
+ import {
3
+ TimeRange
4
+ } from "./TimeRange.js";
5
+ import { TimeRangeManifest } from "./TimeRange.manifest.js";
6
+ export {
7
+ TimeRange,
8
+ TimeRangeManifest
9
+ };