@oprt/react 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.
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 +110 -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 +122 -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 +17811 -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,185 @@
1
+ "use client";
2
+
3
+ // src/DataTable/useTableWindow.ts
4
+ import { useEffect, useMemo, useRef, useState } from "react";
5
+ import { useIsomorphicLayoutEffect } from "../_internal/useIsomorphicLayoutEffect.js";
6
+ var finite = (value, fallback, minimum) => value !== void 0 && Number.isFinite(value) ? Math.max(minimum, value) : fallback;
7
+ var NO_HEIGHTS = /* @__PURE__ */ new Map();
8
+ var MIN_ROW = 24;
9
+ function useTableWindow(rowKeys, options) {
10
+ const { keys, index } = rowKeys;
11
+ const count = keys.length;
12
+ const windowed = options !== void 0;
13
+ const height = finite(options?.height, 320, 24);
14
+ const fixed = options?.rowHeight === void 0 ? void 0 : finite(options.rowHeight, 32, 24);
15
+ const estimate = fixed ?? finite(options?.estimateRowHeight, 40, 24);
16
+ const overscan = Math.floor(finite(options?.overscan, 3, 1));
17
+ const measured = windowed && fixed === void 0;
18
+ const viewport = useRef(null);
19
+ const [heights, setHeights] = useState(NO_HEIGHTS);
20
+ const [anchor, setAnchor] = useState(0);
21
+ const [focusedKey, setFocusedKey] = useState(null);
22
+ const offsets = useMemo(() => {
23
+ if (!measured) return null;
24
+ const result = new Float64Array(keys.length + 1);
25
+ for (let row = 0; row < keys.length; row++)
26
+ result[row + 1] = result[row] + (heights.get(keys[row]) ?? estimate);
27
+ return result;
28
+ }, [measured, keys, heights, estimate]);
29
+ const geometry = useMemo(
30
+ () => ({ count, offsets, estimate, height }),
31
+ [count, offsets, estimate, height]
32
+ );
33
+ const offsetOf = (row) => offsetIn(geometry, row);
34
+ const [start, end] = windowed ? mountedRange(geometry, anchor, overscan) : [0, count];
35
+ const moved = (scrollTop) => {
36
+ const [from, to] = mountedRange(geometry, scrollTop, overscan);
37
+ return from !== start || to !== end;
38
+ };
39
+ let indices = null;
40
+ if (windowed) {
41
+ const focused = focusedKey === null ? void 0 : index.get(focusedKey);
42
+ const kept = focused === void 0 ? [] : [focused - 1, focused, focused + 1].filter(
43
+ (row) => row >= 0 && row < count && (row < start || row >= end)
44
+ );
45
+ indices = kept.filter((row) => row < start);
46
+ for (let row = start; row < end; row++) indices.push(row);
47
+ for (const row of kept) if (row >= end) indices.push(row);
48
+ }
49
+ useIsomorphicLayoutEffect(() => {
50
+ const element = viewport.current;
51
+ if (!windowed || !element) return;
52
+ const [from, to] = mountedRange(geometry, element.scrollTop, overscan);
53
+ if (from !== start || to !== end) setAnchor(element.scrollTop);
54
+ }, [windowed, geometry, overscan, start, end]);
55
+ const nodes = useRef(/* @__PURE__ */ new Map());
56
+ const keyOf = useRef(/* @__PURE__ */ new WeakMap());
57
+ const observer = useRef(null);
58
+ const rowRefs = useRef(/* @__PURE__ */ new Map());
59
+ useEffect(() => {
60
+ if (!measured || typeof ResizeObserver === "undefined") return;
61
+ const resizes = new ResizeObserver((entries) => {
62
+ const sizes = [];
63
+ for (const entry of entries) {
64
+ const key = keyOf.current.get(entry.target);
65
+ const size = entry.borderBoxSize?.[0]?.blockSize ?? entry.target.getBoundingClientRect().height;
66
+ if (key !== void 0 && size >= MIN_ROW) sizes.push([key, size]);
67
+ }
68
+ if (sizes.length > 0) setHeights((current) => withHeights(current, sizes));
69
+ });
70
+ observer.current = resizes;
71
+ for (const node of nodes.current.values()) resizes.observe(node);
72
+ return () => {
73
+ resizes.disconnect();
74
+ observer.current = null;
75
+ };
76
+ }, [measured]);
77
+ useEffect(() => {
78
+ if (!measured || typeof ResizeObserver !== "undefined") return;
79
+ const sizes = [];
80
+ for (const [key, node] of nodes.current) {
81
+ const size = node.getBoundingClientRect().height;
82
+ if (size >= MIN_ROW) sizes.push([key, size]);
83
+ }
84
+ if (sizes.length > 0) setHeights((current) => withHeights(current, sizes));
85
+ }, [measured, start, end]);
86
+ useEffect(() => {
87
+ if (!measured) return;
88
+ for (const key of rowRefs.current.keys())
89
+ if (!index.has(key) && !nodes.current.has(key)) rowRefs.current.delete(key);
90
+ setHeights((current) => {
91
+ let next = null;
92
+ for (const key of current.keys())
93
+ if (!index.has(key)) (next ??= new Map(current)).delete(key);
94
+ return next ?? current;
95
+ });
96
+ }, [measured, index]);
97
+ const rowRef = (key) => {
98
+ if (!measured) return void 0;
99
+ let ref = rowRefs.current.get(key);
100
+ if (!ref) {
101
+ ref = (node) => {
102
+ if (node) {
103
+ nodes.current.set(key, node);
104
+ keyOf.current.set(node, key);
105
+ observer.current?.observe(node);
106
+ return;
107
+ }
108
+ const previous = nodes.current.get(key);
109
+ if (previous) observer.current?.unobserve(previous);
110
+ nodes.current.delete(key);
111
+ rowRefs.current.delete(key);
112
+ };
113
+ rowRefs.current.set(key, ref);
114
+ }
115
+ return ref;
116
+ };
117
+ return {
118
+ viewport,
119
+ height,
120
+ fixed,
121
+ /** The mounted rows in order, or null when every row is mounted (an unwindowed table). */
122
+ indices,
123
+ offsetOf,
124
+ rowRef,
125
+ onScroll: () => {
126
+ const element = viewport.current;
127
+ if (element && moved(element.scrollTop)) setAnchor(element.scrollTop);
128
+ },
129
+ onFocusRow: (key, row) => {
130
+ if (!windowed || key === focusedKey) return;
131
+ setFocusedKey(key);
132
+ const element = viewport.current;
133
+ if (!element) return;
134
+ const rowTop = offsetOf(row);
135
+ const rowBottom = offsetOf(row + 1);
136
+ const header = element.querySelector("thead")?.getBoundingClientRect().height ?? 0;
137
+ const visibleTop = element.scrollTop;
138
+ if (rowTop < visibleTop) element.scrollTop = rowTop;
139
+ else if (rowBottom + header > visibleTop + height)
140
+ element.scrollTop = rowBottom + header - height;
141
+ setAnchor(element.scrollTop);
142
+ },
143
+ clearFocus: () => {
144
+ if (windowed && focusedKey !== null) setFocusedKey(null);
145
+ }
146
+ };
147
+ }
148
+ function offsetIn({ offsets, estimate }, row) {
149
+ return offsets ? offsets[row] : row * estimate;
150
+ }
151
+ function rowAt({ count, offsets, estimate }, position) {
152
+ if (count === 0) return 0;
153
+ if (!offsets) return Math.min(count - 1, Math.max(0, Math.floor(position / estimate)));
154
+ let low = 0;
155
+ let high = count;
156
+ while (low < high) {
157
+ const mid = Math.floor((low + high) / 2);
158
+ if (offsets[mid + 1] <= position) low = mid + 1;
159
+ else high = mid;
160
+ }
161
+ return Math.min(count - 1, low);
162
+ }
163
+ function inView(geometry, scrollTop) {
164
+ const top = Math.min(
165
+ scrollTop,
166
+ Math.max(0, offsetIn(geometry, geometry.count) - geometry.height)
167
+ );
168
+ return [
169
+ rowAt(geometry, top),
170
+ Math.min(geometry.count, rowAt(geometry, top + geometry.height) + 1)
171
+ ];
172
+ }
173
+ function mountedRange(geometry, scrollTop, overscan) {
174
+ const [first, last] = inView(geometry, scrollTop);
175
+ return [Math.max(0, first - overscan), Math.min(geometry.count, last + overscan)];
176
+ }
177
+ function withHeights(current, sizes) {
178
+ let next = null;
179
+ for (const [key, size] of sizes)
180
+ if (current.get(key) !== size) (next ??= new Map(current)).set(key, size);
181
+ return next ?? current;
182
+ }
183
+ export {
184
+ useTableWindow
185
+ };
@@ -0,0 +1,187 @@
1
+ "use client";
2
+
3
+ // src/DatePicker/DatePicker.tsx
4
+ import { forwardRef, useContext, useEffect, useId, useMemo, useRef } from "react";
5
+ import {
6
+ Button,
7
+ Calendar as AriaCalendar,
8
+ DateFieldStateContext,
9
+ DateInput,
10
+ DatePicker as AriaDatePicker,
11
+ DateSegment,
12
+ Dialog,
13
+ Group,
14
+ I18nProvider,
15
+ Label,
16
+ Text,
17
+ useLocale
18
+ } from "react-aria-components";
19
+ import { GregorianCalendar, toCalendar } from "@internationalized/date";
20
+ import { Calendar as CalendarIcon } from "@oprt/icons";
21
+ import { cva } from "class-variance-authority";
22
+ import clsx from "clsx";
23
+ import { CALENDAR_ROOT, CalendarContent } from "../Calendar/Calendar.js";
24
+ import { useMotion } from "../_internal/motion.js";
25
+ import { MotionPopover } from "../_internal/MotionPopover.js";
26
+ import { POPOVER_WIDTH, SCREEN_MARGIN } from "../_internal/overlay.js";
27
+ import { useHydrationRoot } from "../_internal/hydration.js";
28
+ import {
29
+ FIELD_BOX_STYLES,
30
+ FIELD_LABEL_STYLES,
31
+ SEGMENT_STYLES,
32
+ SegmentGroupContext,
33
+ firstSegmentFocus,
34
+ useReportSegments,
35
+ useSegmentDraft
36
+ } from "../_internal/segmentField.js";
37
+ import {
38
+ dateConfiguration,
39
+ dateWithinBounds,
40
+ parseDateOnly,
41
+ readDateSegments
42
+ } from "../_internal/date.js";
43
+ import { jsx, jsxs } from "react/jsx-runtime";
44
+ var fieldStyles = cva("flex min-w-0 flex-col gap-1u font-sans text-oprt-base text-oprt-text");
45
+ function ObservedYear({
46
+ segment,
47
+ report
48
+ }) {
49
+ const state = useContext(DateFieldStateContext);
50
+ useReportSegments(state.segments, readDateSegments, report);
51
+ return /* @__PURE__ */ jsx(DateSegment, { segment, className: SEGMENT_STYLES });
52
+ }
53
+ var DatePicker = forwardRef(
54
+ ({
55
+ label,
56
+ value,
57
+ onValueChange,
58
+ onDraftValidityChange,
59
+ min,
60
+ max,
61
+ disabled = false,
62
+ readOnly = false,
63
+ required = false,
64
+ id,
65
+ className,
66
+ motion = "auto"
67
+ }, ref) => {
68
+ const generated = useId();
69
+ const inputId = id ?? `oprt-date-${generated}`;
70
+ const hydration = useHydrationRoot(inputId);
71
+ const { locale } = useLocale();
72
+ const gregorianLocale = useMemo(
73
+ () => new Intl.Locale(locale, { calendar: "gregory" }).toString(),
74
+ [locale]
75
+ );
76
+ const config = useMemo(() => dateConfiguration(value, min, max), [value, min, max]);
77
+ const displayedValue = config.date?.toString() ?? null;
78
+ const motionState = useMotion(motion);
79
+ const calendarButton = useRef(null);
80
+ const group = useContext(SegmentGroupContext);
81
+ const calendarMin = parseDateOnly(group?.calendar?.min);
82
+ const calendarMax = parseDateOnly(group?.calendar?.max);
83
+ const minValue = calendarMin && calendarMin.compare(config.min) > 0 ? calendarMin : config.min;
84
+ const maxValue = calendarMax && calendarMax.compare(config.max) < 0 ? calendarMax : config.max;
85
+ const draft = useSegmentDraft({
86
+ value,
87
+ displayed: displayedValue,
88
+ valid: value === null || !!config.date,
89
+ inactive: disabled || readOnly || config.invalidBounds
90
+ });
91
+ const draftDate = parseDateOnly(draft.snapshot.value);
92
+ const draftValid = draft.snapshot.valid && !draft.rejected && (draft.snapshot.empty ? !required : !!draftDate && dateWithinBounds(draftDate, config.min, config.max));
93
+ const error = config.error ?? (!draftValid ? draft.snapshot.empty && required ? "A date is required." : (
94
+ // The bound an edit crossed, named as for a supplied value (DatePicker-R6).
95
+ draft.rejection ?? "Enter a complete, valid date within the allowed bounds."
96
+ ) : null);
97
+ const validityCallback = useRef(onDraftValidityChange);
98
+ validityCallback.current = onDraftValidityChange;
99
+ const pending = draft.pending;
100
+ useEffect(() => validityCallback.current?.(!error, { pending }), [error, pending]);
101
+ return /* @__PURE__ */ jsx(I18nProvider, { locale: gregorianLocale, children: /* @__PURE__ */ jsxs(
102
+ AriaDatePicker,
103
+ {
104
+ ...hydration.probeProps,
105
+ id: inputId,
106
+ value: config.date,
107
+ minValue,
108
+ maxValue,
109
+ granularity: "day",
110
+ isDisabled: disabled || config.invalidBounds,
111
+ isReadOnly: readOnly,
112
+ isRequired: required || !!group?.required,
113
+ isInvalid: !!error,
114
+ validationBehavior: "aria",
115
+ className: clsx(fieldStyles(), className),
116
+ onOpenChange: (open) => {
117
+ if (open) draft.releaseBlur();
118
+ },
119
+ onChange: (next) => {
120
+ if (disabled || readOnly || config.invalidBounds || draft.blurRejected()) return;
121
+ const date = next ? toCalendar(next, new GregorianCalendar()) : null;
122
+ if (date && !parseDateOnly(date.toString())) {
123
+ draft.reject();
124
+ return;
125
+ }
126
+ if (date && !dateWithinBounds(date, config.min, config.max)) {
127
+ draft.reject(dateConfiguration(date.toString(), min, max).error);
128
+ return;
129
+ }
130
+ draft.accept();
131
+ onValueChange(date?.toString() ?? null);
132
+ },
133
+ children: [
134
+ /* @__PURE__ */ jsxs(Label, { className: FIELD_LABEL_STYLES, children: [
135
+ label,
136
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " *" })
137
+ ] }),
138
+ /* @__PURE__ */ jsxs(
139
+ Group,
140
+ {
141
+ ref,
142
+ ...firstSegmentFocus(disabled),
143
+ className: `${FIELD_BOX_STYLES} gap-1u`,
144
+ children: [
145
+ /* @__PURE__ */ jsx("div", { className: "contents", ...draft.editProps, children: /* @__PURE__ */ jsx(DateInput, { className: "flex min-w-0 flex-1 items-center", children: (segment) => segment.type === "year" ? /* @__PURE__ */ jsx(ObservedYear, { segment, report: draft.report }) : /* @__PURE__ */ jsx(DateSegment, { segment, className: SEGMENT_STYLES }) }) }),
146
+ /* @__PURE__ */ jsx(
147
+ Button,
148
+ {
149
+ ref: calendarButton,
150
+ "aria-label": "Choose date",
151
+ ...motionState.props,
152
+ 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",
153
+ children: /* @__PURE__ */ jsx(CalendarIcon, { size: 16 })
154
+ }
155
+ )
156
+ ]
157
+ }
158
+ ),
159
+ error && /* @__PURE__ */ jsx(Text, { slot: "errorMessage", className: "m-0 text-oprt-error", children: error }),
160
+ /* @__PURE__ */ jsx(
161
+ MotionPopover,
162
+ {
163
+ motionState,
164
+ returnFocusRef: calendarButton,
165
+ offset: 4,
166
+ containerPadding: SCREEN_MARGIN,
167
+ className: `z-50 ${POPOVER_WIDTH} overflow-y-auto overscroll-contain border border-oprt-border bg-oprt-surface-1 p-2u text-oprt-text`,
168
+ children: /* @__PURE__ */ jsx(Dialog, { className: "outline-hidden", children: /* @__PURE__ */ jsx(
169
+ AriaCalendar,
170
+ {
171
+ createCalendar: () => new GregorianCalendar(),
172
+ className: CALENDAR_ROOT,
173
+ children: /* @__PURE__ */ jsx(CalendarContent, {})
174
+ }
175
+ ) })
176
+ }
177
+ )
178
+ ]
179
+ },
180
+ hydration.key
181
+ ) });
182
+ }
183
+ );
184
+ DatePicker.displayName = "DatePicker";
185
+ export {
186
+ DatePicker
187
+ };
@@ -0,0 +1,40 @@
1
+ // src/DatePicker/DatePicker.manifest.ts
2
+ var DatePickerManifest = {
3
+ DatePicker: {
4
+ description: "Controlled Gregorian date-only picker with segmented editing, calendar overlay and a draft-validity signal for incomplete or rejected edits.",
5
+ props: {
6
+ label: { type: "string", required: true },
7
+ value: {
8
+ type: "string",
9
+ required: true,
10
+ description: "YYYY-MM-DD in years 0001\u20139999, or null. Values contain no time or timezone."
11
+ },
12
+ onValueChange: {
13
+ type: "function",
14
+ required: true,
15
+ description: "Requests a complete valid date or null after clearing. Invalid or coerced dates are not emitted."
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, impossible, out-of-bounds or required-empty edits. Block Review/Save while false, even if value still contains a previous valid date."
20
+ },
21
+ min: { type: "string", description: "Inclusive YYYY-MM-DD lower bound." },
22
+ max: { type: "string", description: "Inclusive YYYY-MM-DD upper bound." },
23
+ disabled: { type: "boolean", default: false },
24
+ readOnly: { type: "boolean", default: false },
25
+ required: { type: "boolean", default: false },
26
+ motion: { type: "enum", values: ["auto", "none"], default: "auto" },
27
+ id: {
28
+ type: "string",
29
+ description: "ID of the focusable date-control group. Focusing it enters the first segment; the forwarded HTMLDivElement ref has the same behavior."
30
+ },
31
+ className: { type: "string" }
32
+ },
33
+ composable_with: ["FormSection", "FormErrorSummary"],
34
+ never_inside: ["Button"],
35
+ example: '<DatePicker id="inspection-date" label="Inspection date" value={date} onValueChange={setDate} onDraftValidityChange={setDateValid} required />'
36
+ }
37
+ };
38
+ export {
39
+ DatePickerManifest
40
+ };
@@ -0,0 +1,7 @@
1
+ // src/DatePicker/index.ts
2
+ import { DatePicker } from "./DatePicker.js";
3
+ import { DatePickerManifest } from "./DatePicker.manifest.js";
4
+ export {
5
+ DatePicker,
6
+ DatePickerManifest
7
+ };
@@ -0,0 +1,156 @@
1
+ "use client";
2
+
3
+ // src/DateRangePicker/DateRangePicker.tsx
4
+ import { forwardRef, useEffect, useRef, useState } from "react";
5
+ import { cva } from "class-variance-authority";
6
+ import clsx from "clsx";
7
+ import { DatePicker } from "../DatePicker/DatePicker.js";
8
+ import { dateConfiguration, parseDateOnly } from "../_internal/date.js";
9
+ import { definedOnly } from "../_internal/aria.js";
10
+ import { joinIds, usePartIds } from "../_internal/fieldIds.js";
11
+ import {
12
+ FIELD_COLUMNS,
13
+ FIELD_LABEL_STYLES,
14
+ SegmentGroupContext,
15
+ firstSegmentFocus
16
+ } from "../_internal/segmentField.js";
17
+ import { useMergedRefs } from "../_internal/mergeRefs.js";
18
+ import { jsx, jsxs } from "react/jsx-runtime";
19
+ var rangeStyles = cva(
20
+ "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"
21
+ );
22
+ var REQUIRED_GROUP = { required: true };
23
+ var keyOf = (value) => value === null ? "null" : JSON.stringify([value.start, value.end]);
24
+ var DateRangePicker = forwardRef(
25
+ ({
26
+ label,
27
+ value,
28
+ onValueChange,
29
+ onDraftValidityChange,
30
+ startLabel = "Start date",
31
+ endLabel = "End date",
32
+ min,
33
+ max,
34
+ disabled = false,
35
+ readOnly = false,
36
+ required = false,
37
+ id,
38
+ className
39
+ }, ref) => {
40
+ const root = useRef(null);
41
+ const setRoot = useMergedRefs(root, ref);
42
+ const ids = usePartIds(["label", "error", "status"], {
43
+ id,
44
+ readRendered: () => root.current?.id
45
+ });
46
+ const base = keyOf(value);
47
+ const [replacement, setReplacement] = useState({ base, generation: 0, requested: null });
48
+ if (replacement.base !== base) {
49
+ setReplacement({
50
+ base,
51
+ generation: replacement.generation + (replacement.requested === base ? 0 : 1),
52
+ requested: null
53
+ });
54
+ }
55
+ const request = (next) => {
56
+ const key = keyOf(next);
57
+ setReplacement((previous) => ({ ...previous, requested: key }));
58
+ onValueChange(next);
59
+ };
60
+ const initial = {
61
+ base,
62
+ start: value?.start ?? null,
63
+ end: value?.end ?? null,
64
+ startValid: true,
65
+ endValid: true,
66
+ startPending: false,
67
+ endPending: false
68
+ };
69
+ const [draft, setDraft] = useState(initial);
70
+ const current = draft.base === base ? draft : initial;
71
+ const start = parseDateOnly(current.start);
72
+ const end = parseDateOnly(current.end);
73
+ const startConfig = dateConfiguration(current.start, min, max);
74
+ const endConfig = dateConfiguration(current.end, min, max);
75
+ const empty = current.start === null && current.end === null;
76
+ const pending = current.startPending || current.endPending || current.start !== (value?.start ?? null) || current.end !== (value?.end ?? null);
77
+ const error = startConfig.error || endConfig.error || (!current.startValid || !current.endValid ? "Complete both date fields before continuing." : empty ? required ? "A date range is required." : null : !start || !end ? "Enter both a start date and an end date." : start.compare(end) > 0 ? "Start date must be on or before end date." : null);
78
+ const callback = useRef(onDraftValidityChange);
79
+ callback.current = onDraftValidityChange;
80
+ useEffect(() => callback.current?.(!error && !pending, { pending }), [error, pending]);
81
+ const setEndpoint = (part, next) => {
82
+ if (disabled || readOnly) return;
83
+ const nextDraft = { ...current, [part]: next };
84
+ setDraft(nextDraft);
85
+ if (!(part === "start" ? current.endValid : current.startValid)) return;
86
+ if (nextDraft.start === null && nextDraft.end === null) {
87
+ request(null);
88
+ return;
89
+ }
90
+ const nextStart = parseDateOnly(nextDraft.start);
91
+ const nextEnd = parseDateOnly(nextDraft.end);
92
+ if (!nextStart || !nextEnd || nextStart.compare(nextEnd) > 0 || dateConfiguration(nextDraft.start, min, max).error || dateConfiguration(nextDraft.end, min, max).error)
93
+ return;
94
+ request({ start: nextDraft.start, end: nextDraft.end });
95
+ };
96
+ const reportEndpoint = (part, valid, pending2) => setDraft((previous) => {
97
+ const active = previous.base === base ? previous : initial;
98
+ const validityKey = part === "start" ? "startValid" : "endValid";
99
+ const pendingKey = part === "start" ? "startPending" : "endPending";
100
+ return active[validityKey] === valid && active[pendingKey] === pending2 ? previous : { ...active, [validityKey]: valid, [pendingKey]: pending2 };
101
+ });
102
+ return /* @__PURE__ */ jsxs(
103
+ "div",
104
+ {
105
+ id: ids.base,
106
+ ref: setRoot,
107
+ role: "group",
108
+ "aria-labelledby": ids.label,
109
+ "aria-describedby": joinIds(error && ids.error, !error && pending && ids.status),
110
+ "aria-invalid": !!error || void 0,
111
+ className: clsx(rangeStyles(), className),
112
+ ...firstSegmentFocus(disabled),
113
+ children: [
114
+ /* @__PURE__ */ jsxs("p", { id: ids.label, className: FIELD_LABEL_STYLES, children: [
115
+ label,
116
+ required && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: " *" })
117
+ ] }),
118
+ /* @__PURE__ */ jsx(SegmentGroupContext.Provider, { value: required ? REQUIRED_GROUP : null, children: /* @__PURE__ */ jsxs("div", { className: FIELD_COLUMNS, children: [
119
+ /* @__PURE__ */ jsx(
120
+ DatePicker,
121
+ {
122
+ className: "min-w-0 flex-1",
123
+ label: startLabel,
124
+ value: current.start,
125
+ onValueChange: (next) => setEndpoint("start", next),
126
+ onDraftValidityChange: (valid, details) => reportEndpoint("start", valid, details.pending),
127
+ disabled,
128
+ readOnly,
129
+ ...definedOnly({ min, max })
130
+ }
131
+ ),
132
+ /* @__PURE__ */ jsx(
133
+ DatePicker,
134
+ {
135
+ className: "min-w-0 flex-1",
136
+ label: endLabel,
137
+ value: current.end,
138
+ onValueChange: (next) => setEndpoint("end", next),
139
+ onDraftValidityChange: (valid, details) => reportEndpoint("end", valid, details.pending),
140
+ disabled,
141
+ readOnly,
142
+ ...definedOnly({ min, max })
143
+ }
144
+ )
145
+ ] }, replacement.generation) }),
146
+ error && /* @__PURE__ */ jsx("p", { id: ids.error, className: "m-0 text-oprt-error", children: error }),
147
+ !error && pending && /* @__PURE__ */ jsx("p", { id: ids.status, className: "m-0 text-oprt-text-dim", children: "The range change has not been applied." })
148
+ ]
149
+ }
150
+ );
151
+ }
152
+ );
153
+ DateRangePicker.displayName = "DateRangePicker";
154
+ export {
155
+ DateRangePicker
156
+ };
@@ -0,0 +1,41 @@
1
+ // src/DateRangePicker/DateRangePicker.manifest.ts
2
+ var DateRangePickerManifest = {
3
+ DateRangePicker: {
4
+ description: "Controlled ordered Gregorian date range with independently editable start and end fields and separate endpoint calendars. Equal-day ranges are valid.",
5
+ props: {
6
+ label: { type: "string", required: true },
7
+ value: {
8
+ type: "node",
9
+ required: true,
10
+ description: "{ start: YYYY-MM-DD, end: YYYY-MM-DD }, or null. Dates carry no time or time zone."
11
+ },
12
+ onValueChange: {
13
+ type: "function",
14
+ required: true,
15
+ description: "Requests complete ordered ranges or null when both endpoints are cleared. Partial and reversed ranges are retained as invalid drafts."
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. False for incomplete, invalid, out-of-bounds, required-empty, or unaccepted range drafts. Block Review/Save while false."
20
+ },
21
+ startLabel: { type: "string", default: "Start date" },
22
+ endLabel: { type: "string", default: "End date" },
23
+ min: { type: "string", description: "Inclusive YYYY-MM-DD lower bound for both endpoints." },
24
+ max: { type: "string", description: "Inclusive YYYY-MM-DD upper bound for both endpoints." },
25
+ disabled: { type: "boolean", default: false },
26
+ readOnly: { type: "boolean", default: false },
27
+ required: { type: "boolean", default: false },
28
+ id: {
29
+ type: "string",
30
+ description: "ID of the focusable group. Focusing it or its forwarded HTMLDivElement ref enters the first start-date segment."
31
+ },
32
+ className: { type: "string" }
33
+ },
34
+ composable_with: ["FormSection", "FormErrorSummary", "DatePicker"],
35
+ never_inside: ["Button"],
36
+ example: '<DateRangePicker id="inspection-window" label="Inspection window" value={range} onValueChange={setRange} onDraftValidityChange={setRangeValid} required />'
37
+ }
38
+ };
39
+ export {
40
+ DateRangePickerManifest
41
+ };
@@ -0,0 +1,7 @@
1
+ // src/DateRangePicker/index.ts
2
+ import { DateRangePicker } from "./DateRangePicker.js";
3
+ import { DateRangePickerManifest } from "./DateRangePicker.manifest.js";
4
+ export {
5
+ DateRangePicker,
6
+ DateRangePickerManifest
7
+ };