@scattered-light/loomiere-base-ui 0.6.1 → 0.7.1
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/README.md +135 -5
- package/dist/chunk-JUUPOOQQ.js +3723 -0
- package/dist/chunk-JUUPOOQQ.js.map +1 -0
- package/dist/chunk-M7KJASGK.js +802 -0
- package/dist/chunk-M7KJASGK.js.map +1 -0
- package/dist/chunk-Y2CPBMFJ.js +147 -0
- package/dist/chunk-Y2CPBMFJ.js.map +1 -0
- package/dist/custom/collection/index.d.ts +540 -0
- package/dist/custom/collection/index.js +1489 -0
- package/dist/custom/collection/index.js.map +1 -0
- package/dist/custom/index.d.ts +5 -0
- package/dist/custom/index.js +4 -0
- package/dist/custom/index.js.map +1 -0
- package/dist/design-system/components/Badge/README.md +35 -0
- package/dist/design-system/components/Badge/preview.html +19 -0
- package/dist/design-system/components/Collection/README.md +14 -0
- package/dist/design-system/components/CollectionPagination/README.md +15 -0
- package/dist/design-system/components/CollectionToolbar/README.md +29 -0
- package/dist/design-system/components/DataList/README.md +39 -0
- package/dist/design-system/components/DataList/preview.html +475 -0
- package/dist/design-system/components/DataTable/README.md +41 -0
- package/dist/design-system/components/DataTable/preview.html +475 -0
- package/dist/design-system/components/DatePicker/README.md +55 -0
- package/dist/design-system/components/DatePicker/preview.html +475 -0
- package/dist/design-system/components/EditableList/README.md +33 -0
- package/dist/design-system/components/EditableList/preview.html +475 -0
- package/dist/design-system/components/EmptyState/README.md +60 -0
- package/dist/design-system/components/EmptyState/preview.html +475 -0
- package/dist/design-system/components/ListDetail/README.md +43 -0
- package/dist/design-system/components/ListDetail/preview.html +475 -0
- package/dist/design-system/components/MoneyField/README.md +52 -0
- package/dist/design-system/components/MoneyField/preview.html +475 -0
- package/dist/design-system/components/Splitter/README.md +42 -0
- package/dist/design-system/components/Splitter/preview.html +475 -0
- package/dist/design-system/components/Table/README.md +2 -1
- package/dist/design-system/components/Table/preview.html +1 -1
- package/dist/design-system/components/bundle.js +7 -5
- package/dist/design-system/components/index.d.ts +3 -21
- package/dist/design-system/components/styles.css +2349 -186
- package/dist/index-ChJXlxzq.d.ts +809 -0
- package/dist/index.d.ts +3 -21
- package/dist/index.js +176 -891
- package/dist/index.js.map +1 -1
- package/dist/styles/components/Accordion.css +1 -1
- package/dist/styles/components/AlertDialog.css +1 -1
- package/dist/styles/components/Autocomplete.css +1 -1
- package/dist/styles/components/Avatar.css +1 -1
- package/dist/styles/components/Button.css +1 -1
- package/dist/styles/components/Checkbox.css +1 -1
- package/dist/styles/components/CheckboxGroup.css +1 -1
- package/dist/styles/components/Collapsible.css +1 -1
- package/dist/styles/components/Combobox.css +1 -1
- package/dist/styles/components/ContextMenu.css +1 -1
- package/dist/styles/components/Dialog.css +1 -1
- package/dist/styles/components/Drawer.css +1 -1
- package/dist/styles/components/Field.css +1 -1
- package/dist/styles/components/Fieldset.css +1 -1
- package/dist/styles/components/Form.css +1 -1
- package/dist/styles/components/Input.css +1 -1
- package/dist/styles/components/Menu.css +1 -1
- package/dist/styles/components/Menubar.css +1 -1
- package/dist/styles/components/Meter.css +1 -1
- package/dist/styles/components/NavigationMenu.css +1 -1
- package/dist/styles/components/NumberField.css +1 -1
- package/dist/styles/components/OTPField.css +1 -1
- package/dist/styles/components/Popover.css +1 -1
- package/dist/styles/components/PreviewCard.css +1 -1
- package/dist/styles/components/Progress.css +1 -1
- package/dist/styles/components/Radio.css +1 -1
- package/dist/styles/components/ScrollArea.css +1 -1
- package/dist/styles/components/Select.css +1 -1
- package/dist/styles/components/Separator.css +1 -1
- package/dist/styles/components/Slider.css +1 -1
- package/dist/styles/components/Switch.css +1 -1
- package/dist/styles/components/Tabs.css +1 -1
- package/dist/styles/components/Toast.css +1 -1
- package/dist/styles/components/Toggle.css +1 -1
- package/dist/styles/components/ToggleGroup.css +1 -1
- package/dist/styles/components/Toolbar.css +1 -1
- package/dist/styles/components/Tooltip.css +1 -1
- package/dist/styles/custom/Badge.css +104 -0
- package/dist/styles/custom/Collection.css +15 -0
- package/dist/styles/custom/CollectionPagination.css +24 -0
- package/dist/styles/custom/CollectionToolbar.css +140 -0
- package/dist/styles/custom/DataList.css +123 -0
- package/dist/styles/custom/DataTable.css +120 -0
- package/dist/styles/custom/DatePicker.css +572 -0
- package/dist/styles/custom/EditableList.css +98 -0
- package/dist/styles/custom/EmptyState.css +255 -0
- package/dist/styles/custom/ListDetail.css +126 -0
- package/dist/styles/custom/MoneyField.css +344 -0
- package/dist/styles/custom/Splitter.css +207 -0
- package/dist/styles/{components → custom}/Table.css +31 -4
- package/dist/styles/index.css +12 -2
- package/dist/styles/reset/accessibility.css +1 -1
- package/dist/styles/reset/reset-accessibility.css +1 -1
- package/dist/styles/reset/reset-tailwind.css +1 -1
- package/dist/styles/reset/reset.css +1 -1
- package/dist/styles/tailwind.css +12 -2
- package/package.json +35 -7
|
@@ -0,0 +1,3723 @@
|
|
|
1
|
+
import { cnState, styledPart, buttonPart, cn, Button, Combobox, Select, useUnderWidth, balanceOf, formatMoney, minorToDecimal } from './chunk-M7KJASGK.js';
|
|
2
|
+
import * as React10 from 'react';
|
|
3
|
+
import { Field as Field$1 } from '@base-ui/react/field';
|
|
4
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
import { NumberField as NumberField$1 } from '@base-ui/react/number-field';
|
|
6
|
+
import { Popover as Popover$1 } from '@base-ui/react/popover';
|
|
7
|
+
import { Drawer as Drawer$1 } from '@base-ui/react/drawer';
|
|
8
|
+
import { useFieldRootContext } from '@base-ui/react/internals/field-root-context';
|
|
9
|
+
import { useLabelableContext } from '@base-ui/react/internals/labelable-provider';
|
|
10
|
+
import { createPortal } from 'react-dom';
|
|
11
|
+
|
|
12
|
+
var Root = React10.forwardRef(
|
|
13
|
+
function FieldRoot({ className, ...props }, ref) {
|
|
14
|
+
return /* @__PURE__ */ jsx(
|
|
15
|
+
Field$1.Root,
|
|
16
|
+
{
|
|
17
|
+
ref,
|
|
18
|
+
...props,
|
|
19
|
+
className: (state) => cnState("FieldRoot", className, state)
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
);
|
|
24
|
+
var Label = React10.forwardRef(
|
|
25
|
+
function FieldLabel({ className, ...props }, ref) {
|
|
26
|
+
return /* @__PURE__ */ jsx(
|
|
27
|
+
Field$1.Label,
|
|
28
|
+
{
|
|
29
|
+
ref,
|
|
30
|
+
...props,
|
|
31
|
+
className: (state) => cnState("FieldLabel", className, state)
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
var Description = React10.forwardRef(function FieldDescription({ className, ...props }, ref) {
|
|
37
|
+
return /* @__PURE__ */ jsx(
|
|
38
|
+
Field$1.Description,
|
|
39
|
+
{
|
|
40
|
+
ref,
|
|
41
|
+
...props,
|
|
42
|
+
className: (state) => cnState("FieldDescription", className, state)
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
var Error2 = React10.forwardRef(
|
|
47
|
+
function FieldError({ className, ...props }, ref) {
|
|
48
|
+
return /* @__PURE__ */ jsx(
|
|
49
|
+
Field$1.Error,
|
|
50
|
+
{
|
|
51
|
+
ref,
|
|
52
|
+
...props,
|
|
53
|
+
className: (state) => cnState("FieldError", className, state)
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
var Control = React10.forwardRef(
|
|
59
|
+
function FieldControl({ className, ...props }, ref) {
|
|
60
|
+
return /* @__PURE__ */ jsx(
|
|
61
|
+
Field$1.Control,
|
|
62
|
+
{
|
|
63
|
+
ref,
|
|
64
|
+
...props,
|
|
65
|
+
className: (state) => cnState("FieldControl", className, state)
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
var Validity = Field$1.Validity;
|
|
71
|
+
var Item = React10.forwardRef(
|
|
72
|
+
function FieldItem({ className, ...props }, ref) {
|
|
73
|
+
return /* @__PURE__ */ jsx(
|
|
74
|
+
Field$1.Item,
|
|
75
|
+
{
|
|
76
|
+
ref,
|
|
77
|
+
...props,
|
|
78
|
+
className: (state) => cnState("", className, state)
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
);
|
|
83
|
+
var Field = {
|
|
84
|
+
Root,
|
|
85
|
+
Label,
|
|
86
|
+
Description,
|
|
87
|
+
Error: Error2,
|
|
88
|
+
Control,
|
|
89
|
+
Validity,
|
|
90
|
+
Item
|
|
91
|
+
};
|
|
92
|
+
var Root2 = styledPart(
|
|
93
|
+
NumberField$1.Root,
|
|
94
|
+
"NumberFieldRoot",
|
|
95
|
+
"NumberFieldRoot"
|
|
96
|
+
);
|
|
97
|
+
var Group = styledPart(
|
|
98
|
+
NumberField$1.Group,
|
|
99
|
+
"NumberFieldGroup",
|
|
100
|
+
"NumberFieldGroup"
|
|
101
|
+
);
|
|
102
|
+
var Input = styledPart(
|
|
103
|
+
NumberField$1.Input,
|
|
104
|
+
"NumberFieldInput",
|
|
105
|
+
"NumberFieldInput"
|
|
106
|
+
);
|
|
107
|
+
var Increment = styledPart(
|
|
108
|
+
NumberField$1.Increment,
|
|
109
|
+
"NumberFieldIncrement",
|
|
110
|
+
"NumberFieldIncrement"
|
|
111
|
+
);
|
|
112
|
+
var Decrement = styledPart(
|
|
113
|
+
NumberField$1.Decrement,
|
|
114
|
+
"NumberFieldDecrement",
|
|
115
|
+
"NumberFieldDecrement"
|
|
116
|
+
);
|
|
117
|
+
var ScrubArea = styledPart(
|
|
118
|
+
NumberField$1.ScrubArea,
|
|
119
|
+
"NumberFieldScrubArea",
|
|
120
|
+
"NumberFieldScrubArea"
|
|
121
|
+
);
|
|
122
|
+
function NumberFieldLabel({ className, htmlFor, ...props }) {
|
|
123
|
+
return /* @__PURE__ */ jsx("label", { htmlFor, className: cn("NumberFieldLabel", className), ...props });
|
|
124
|
+
}
|
|
125
|
+
var NumberField = {
|
|
126
|
+
Root: Root2,
|
|
127
|
+
Group,
|
|
128
|
+
Input,
|
|
129
|
+
Increment,
|
|
130
|
+
Decrement,
|
|
131
|
+
ScrubArea,
|
|
132
|
+
ScrubAreaCursor: NumberField$1.ScrubAreaCursor,
|
|
133
|
+
Label: NumberFieldLabel
|
|
134
|
+
};
|
|
135
|
+
var Trigger = buttonPart(
|
|
136
|
+
Popover$1.Trigger,
|
|
137
|
+
"PopoverTrigger",
|
|
138
|
+
{ partClass: "PopoverTrigger", optIn: true }
|
|
139
|
+
);
|
|
140
|
+
var Backdrop = styledPart(
|
|
141
|
+
Popover$1.Backdrop,
|
|
142
|
+
"PopoverBackdrop",
|
|
143
|
+
"PopoverBackdrop"
|
|
144
|
+
);
|
|
145
|
+
var Positioner = styledPart(
|
|
146
|
+
Popover$1.Positioner,
|
|
147
|
+
"PopoverPositioner",
|
|
148
|
+
"PopoverPositioner"
|
|
149
|
+
);
|
|
150
|
+
var Popup = styledPart(
|
|
151
|
+
Popover$1.Popup,
|
|
152
|
+
"PopoverPopup",
|
|
153
|
+
"PopoverPopup"
|
|
154
|
+
);
|
|
155
|
+
var Title = styledPart(
|
|
156
|
+
Popover$1.Title,
|
|
157
|
+
"PopoverTitle",
|
|
158
|
+
"PopoverTitle"
|
|
159
|
+
);
|
|
160
|
+
var Description2 = styledPart(
|
|
161
|
+
Popover$1.Description,
|
|
162
|
+
"PopoverDescription",
|
|
163
|
+
"PopoverDescription"
|
|
164
|
+
);
|
|
165
|
+
var Close = styledPart(
|
|
166
|
+
Popover$1.Close,
|
|
167
|
+
"PopoverClose",
|
|
168
|
+
"PopoverClose"
|
|
169
|
+
);
|
|
170
|
+
var Arrow = styledPart(
|
|
171
|
+
Popover$1.Arrow,
|
|
172
|
+
"PopoverArrow",
|
|
173
|
+
"PopoverArrow"
|
|
174
|
+
);
|
|
175
|
+
var Popover = {
|
|
176
|
+
Root: Popover$1.Root,
|
|
177
|
+
Trigger,
|
|
178
|
+
Portal: Popover$1.Portal,
|
|
179
|
+
Backdrop,
|
|
180
|
+
Positioner,
|
|
181
|
+
Popup,
|
|
182
|
+
Title,
|
|
183
|
+
Description: Description2,
|
|
184
|
+
Close,
|
|
185
|
+
Arrow,
|
|
186
|
+
Viewport: Popover$1.Viewport,
|
|
187
|
+
Handle: Popover$1.Handle,
|
|
188
|
+
createHandle: Popover$1.createHandle
|
|
189
|
+
};
|
|
190
|
+
var Trigger2 = buttonPart(
|
|
191
|
+
Drawer$1.Trigger,
|
|
192
|
+
"DrawerTrigger"
|
|
193
|
+
);
|
|
194
|
+
var Backdrop2 = styledPart(
|
|
195
|
+
Drawer$1.Backdrop,
|
|
196
|
+
"DrawerBackdrop",
|
|
197
|
+
"DrawerBackdrop"
|
|
198
|
+
);
|
|
199
|
+
var Viewport = styledPart(
|
|
200
|
+
Drawer$1.Viewport,
|
|
201
|
+
"DrawerViewport",
|
|
202
|
+
"DrawerViewport"
|
|
203
|
+
);
|
|
204
|
+
var Popup2 = styledPart(
|
|
205
|
+
Drawer$1.Popup,
|
|
206
|
+
"DrawerPopup",
|
|
207
|
+
"DrawerPopup"
|
|
208
|
+
);
|
|
209
|
+
var Title2 = styledPart(
|
|
210
|
+
Drawer$1.Title,
|
|
211
|
+
"DrawerTitle",
|
|
212
|
+
"DrawerTitle"
|
|
213
|
+
);
|
|
214
|
+
var Description3 = styledPart(
|
|
215
|
+
Drawer$1.Description,
|
|
216
|
+
"DrawerDescription",
|
|
217
|
+
"DrawerDescription"
|
|
218
|
+
);
|
|
219
|
+
var Content = styledPart(
|
|
220
|
+
Drawer$1.Content,
|
|
221
|
+
"DrawerContent",
|
|
222
|
+
"DrawerContent"
|
|
223
|
+
);
|
|
224
|
+
var Close2 = buttonPart(
|
|
225
|
+
Drawer$1.Close,
|
|
226
|
+
"DrawerClose"
|
|
227
|
+
);
|
|
228
|
+
var Drawer = {
|
|
229
|
+
Root: Drawer$1.Root,
|
|
230
|
+
Trigger: Trigger2,
|
|
231
|
+
Portal: Drawer$1.Portal,
|
|
232
|
+
Backdrop: Backdrop2,
|
|
233
|
+
Viewport,
|
|
234
|
+
Popup: Popup2,
|
|
235
|
+
Title: Title2,
|
|
236
|
+
Description: Description3,
|
|
237
|
+
Content,
|
|
238
|
+
Close: Close2,
|
|
239
|
+
Indent: Drawer$1.Indent,
|
|
240
|
+
IndentBackground: Drawer$1.IndentBackground,
|
|
241
|
+
SwipeArea: Drawer$1.SwipeArea,
|
|
242
|
+
Provider: Drawer$1.Provider,
|
|
243
|
+
VirtualKeyboardProvider: Drawer$1.VirtualKeyboardProvider,
|
|
244
|
+
Handle: Drawer$1.Handle,
|
|
245
|
+
createHandle: Drawer$1.createHandle
|
|
246
|
+
};
|
|
247
|
+
var Badge = React10.forwardRef(function Badge2({ className, emphasis, status, palette, size, dot, children, ...props }, ref) {
|
|
248
|
+
return /* @__PURE__ */ jsx(
|
|
249
|
+
"span",
|
|
250
|
+
{
|
|
251
|
+
ref,
|
|
252
|
+
"data-emphasis": emphasis,
|
|
253
|
+
"data-status": status,
|
|
254
|
+
"data-color": palette,
|
|
255
|
+
"data-size": size,
|
|
256
|
+
"data-dot": dot ? "" : void 0,
|
|
257
|
+
className: cn("Badge", className),
|
|
258
|
+
...props,
|
|
259
|
+
children
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
// src/custom/money-field/money-utils.ts
|
|
265
|
+
var digitCache = /* @__PURE__ */ new Map();
|
|
266
|
+
var CURRENCY_MARKS = [
|
|
267
|
+
"$",
|
|
268
|
+
"\u20AC",
|
|
269
|
+
"\xA3",
|
|
270
|
+
"\xA5",
|
|
271
|
+
"\uFFE5",
|
|
272
|
+
"\u20A9",
|
|
273
|
+
"\u20B9",
|
|
274
|
+
"\u20BD",
|
|
275
|
+
"\u20BA",
|
|
276
|
+
"\u20B4",
|
|
277
|
+
"\u20A6",
|
|
278
|
+
"\u0E3F",
|
|
279
|
+
"\u20AB",
|
|
280
|
+
"\u20B1",
|
|
281
|
+
"\u20AA",
|
|
282
|
+
"\u20A1",
|
|
283
|
+
"\u20B2",
|
|
284
|
+
"\u20B8",
|
|
285
|
+
"\u20BC",
|
|
286
|
+
"\u20BE",
|
|
287
|
+
"\xA2",
|
|
288
|
+
"\xA4"
|
|
289
|
+
];
|
|
290
|
+
var MAX_MINOR_UNITS = Number(10n ** 15n - 1n);
|
|
291
|
+
var ASCII_DIGITS = {};
|
|
292
|
+
for (const [start, base] of [
|
|
293
|
+
[1632, 0],
|
|
294
|
+
[1776, 0],
|
|
295
|
+
[65296, 0]
|
|
296
|
+
]) {
|
|
297
|
+
for (let i = 0; i < 10; i += 1) ASCII_DIGITS[String.fromCharCode(start + i)] = String(base + i);
|
|
298
|
+
}
|
|
299
|
+
function normalizeDigits(text) {
|
|
300
|
+
return Array.from(text, (char) => ASCII_DIGITS[char] ?? char).join("");
|
|
301
|
+
}
|
|
302
|
+
function isValidCurrency(code) {
|
|
303
|
+
if (!/^[A-Za-z]{3}$/.test(code)) return false;
|
|
304
|
+
const upper = code.toUpperCase();
|
|
305
|
+
const intlWithSupport = Intl;
|
|
306
|
+
const supported = typeof intlWithSupport.supportedValuesOf === "function" ? intlWithSupport.supportedValuesOf("currency") : null;
|
|
307
|
+
if (supported?.includes(upper)) return true;
|
|
308
|
+
try {
|
|
309
|
+
const format = new Intl.NumberFormat("en", { style: "currency", currency: upper });
|
|
310
|
+
const digits = format.resolvedOptions().maximumFractionDigits;
|
|
311
|
+
const symbol = format.formatToParts(1).find((part) => part.type === "currency")?.value;
|
|
312
|
+
if (supported && symbol === upper && digits === 2) return false;
|
|
313
|
+
return true;
|
|
314
|
+
} catch {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function getCurrencyDigits(currency, override) {
|
|
319
|
+
if (override != null) {
|
|
320
|
+
if (!Number.isInteger(override) || override < 0 || override > 20) {
|
|
321
|
+
throw new Error(`fractionDigits must be an integer from 0 to 20, got ${override}`);
|
|
322
|
+
}
|
|
323
|
+
return override;
|
|
324
|
+
}
|
|
325
|
+
const code = currency.toUpperCase();
|
|
326
|
+
const cached = digitCache.get(code);
|
|
327
|
+
if (cached != null) return cached;
|
|
328
|
+
if (!isValidCurrency(code)) {
|
|
329
|
+
throw new Error(`Unknown currency "${currency}". Expected an ISO 4217 code Intl recognizes.`);
|
|
330
|
+
}
|
|
331
|
+
const digits = new Intl.NumberFormat("en", { style: "currency", currency: code }).resolvedOptions().maximumFractionDigits;
|
|
332
|
+
const resolved = digits ?? 0;
|
|
333
|
+
digitCache.set(code, resolved);
|
|
334
|
+
return resolved;
|
|
335
|
+
}
|
|
336
|
+
function numberToDecimal(value) {
|
|
337
|
+
if (!Number.isFinite(value)) {
|
|
338
|
+
throw new Error(`Amount must be a finite number, got ${value}`);
|
|
339
|
+
}
|
|
340
|
+
const negative = value < 0;
|
|
341
|
+
const text = Math.abs(value).toString().toLowerCase();
|
|
342
|
+
const expanded = text.includes("e") ? expandExponential(text) : text;
|
|
343
|
+
return negative ? `-${expanded}` : expanded;
|
|
344
|
+
}
|
|
345
|
+
function expandExponential(text) {
|
|
346
|
+
const [coeff, expStr] = text.split("e");
|
|
347
|
+
const exp = Number(expStr);
|
|
348
|
+
const [head, tail = ""] = coeff.split(".");
|
|
349
|
+
const digits = `${head}${tail}`.replace(/^0+/, "") || "0";
|
|
350
|
+
const point = head.length + exp;
|
|
351
|
+
if (point <= 0) return `0.${"0".repeat(-point)}${digits}`;
|
|
352
|
+
if (point >= digits.length) return digits + "0".repeat(point - digits.length);
|
|
353
|
+
return `${digits.slice(0, point)}.${digits.slice(point)}`;
|
|
354
|
+
}
|
|
355
|
+
function roundHalfExpand(unsigned, digits) {
|
|
356
|
+
if (!/^\d+(\.\d+)?$/.test(unsigned)) {
|
|
357
|
+
throw new Error(`Not a decimal amount: ${unsigned}`);
|
|
358
|
+
}
|
|
359
|
+
const [wholeRaw, frac = ""] = unsigned.split(".");
|
|
360
|
+
const whole = wholeRaw.replace(/^0+(?=\d)/, "") || "0";
|
|
361
|
+
if (frac.length <= digits) {
|
|
362
|
+
if (digits === 0) return whole;
|
|
363
|
+
return `${whole}.${frac.padEnd(digits, "0")}`;
|
|
364
|
+
}
|
|
365
|
+
const keep = frac.slice(0, digits);
|
|
366
|
+
const roundUp = frac.charCodeAt(digits) - 48 >= 5;
|
|
367
|
+
if (!roundUp) {
|
|
368
|
+
if (digits === 0) return whole;
|
|
369
|
+
return `${whole}.${keep}`;
|
|
370
|
+
}
|
|
371
|
+
const body = `${whole}${keep}`.replace(/^0+(?=\d)/, "") || "0";
|
|
372
|
+
const bumped = (BigInt(body) + 1n).toString();
|
|
373
|
+
if (digits === 0) return bumped;
|
|
374
|
+
const padded = bumped.padStart(digits + 1, "0");
|
|
375
|
+
return `${padded.slice(0, -digits)}.${padded.slice(-digits)}`;
|
|
376
|
+
}
|
|
377
|
+
function minorFromRounded(rounded, negative) {
|
|
378
|
+
const digits = rounded.replace(".", "").replace(/^0+(?=\d)/, "") || "0";
|
|
379
|
+
if (BigInt(digits) > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
380
|
+
throw new Error(
|
|
381
|
+
`Amount exceeds Number.MAX_SAFE_INTEGER minor units (${Number.MAX_SAFE_INTEGER})`
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const minor = Number(digits);
|
|
385
|
+
return negative ? -minor : minor;
|
|
386
|
+
}
|
|
387
|
+
function signedParts(decimal) {
|
|
388
|
+
const trimmed = decimal.trim();
|
|
389
|
+
if (!/^[+-]?\d+(\.\d+)?$/.test(trimmed)) {
|
|
390
|
+
throw new Error(`Not a decimal amount: ${decimal}`);
|
|
391
|
+
}
|
|
392
|
+
return { negative: trimmed.startsWith("-"), unsigned: trimmed.replace(/^[+-]/, "") };
|
|
393
|
+
}
|
|
394
|
+
function toMinorUnits(major, currency, override) {
|
|
395
|
+
const digits = getCurrencyDigits(currency, override);
|
|
396
|
+
const text = numberToDecimal(major);
|
|
397
|
+
const { negative, unsigned } = signedParts(text);
|
|
398
|
+
return minorFromRounded(roundHalfExpand(unsigned, digits), negative);
|
|
399
|
+
}
|
|
400
|
+
function toDecimalString(minor, currency, override) {
|
|
401
|
+
if (!Number.isSafeInteger(minor)) {
|
|
402
|
+
throw new Error(`Minor units must be a safe integer, got ${minor}`);
|
|
403
|
+
}
|
|
404
|
+
const digits = getCurrencyDigits(currency, override);
|
|
405
|
+
const negative = minor < 0;
|
|
406
|
+
const abs = Math.abs(minor).toString();
|
|
407
|
+
if (digits === 0) return `${negative ? "-" : ""}${abs}`;
|
|
408
|
+
const padded = abs.padStart(digits + 1, "0");
|
|
409
|
+
return `${negative ? "-" : ""}${padded.slice(0, -digits)}.${padded.slice(-digits)}`;
|
|
410
|
+
}
|
|
411
|
+
function parseDecimalString(decimal, currency, override) {
|
|
412
|
+
const digits = getCurrencyDigits(currency, override);
|
|
413
|
+
const { negative, unsigned } = signedParts(decimal);
|
|
414
|
+
return minorFromRounded(roundHalfExpand(unsigned, digits), negative);
|
|
415
|
+
}
|
|
416
|
+
function fromMinorUnits(minor, currency, override) {
|
|
417
|
+
return Number(toDecimalString(minor, currency, override));
|
|
418
|
+
}
|
|
419
|
+
function formatMoney2(minor, currency, options) {
|
|
420
|
+
const digits = getCurrencyDigits(currency, options?.fractionDigits);
|
|
421
|
+
const decimal = toDecimalString(minor, currency, options?.fractionDigits);
|
|
422
|
+
const display = options?.display ?? "narrowSymbol";
|
|
423
|
+
const format = display === "none" ? new Intl.NumberFormat(options?.locale, {
|
|
424
|
+
style: "decimal",
|
|
425
|
+
useGrouping: true,
|
|
426
|
+
minimumFractionDigits: digits,
|
|
427
|
+
maximumFractionDigits: digits
|
|
428
|
+
}) : new Intl.NumberFormat(options?.locale, {
|
|
429
|
+
style: "currency",
|
|
430
|
+
currency: currency.toUpperCase(),
|
|
431
|
+
currencyDisplay: display,
|
|
432
|
+
minimumFractionDigits: digits,
|
|
433
|
+
maximumFractionDigits: digits
|
|
434
|
+
});
|
|
435
|
+
return formatExact(format, decimal);
|
|
436
|
+
}
|
|
437
|
+
function formatExact(format, decimal) {
|
|
438
|
+
return format.format(decimal);
|
|
439
|
+
}
|
|
440
|
+
function affixSide(locale, currency) {
|
|
441
|
+
const parts = new Intl.NumberFormat(locale, {
|
|
442
|
+
style: "currency",
|
|
443
|
+
currency: currency.toUpperCase()
|
|
444
|
+
}).formatToParts(1);
|
|
445
|
+
const currencyIndex = parts.findIndex((part) => part.type === "currency");
|
|
446
|
+
const integerIndex = parts.findIndex((part) => part.type === "integer");
|
|
447
|
+
return currencyIndex < integerIndex ? "start" : "end";
|
|
448
|
+
}
|
|
449
|
+
function currencySymbol(locale, currency, display) {
|
|
450
|
+
if (display === "none") return "";
|
|
451
|
+
if (display === "code") return currency.toUpperCase();
|
|
452
|
+
const part = new Intl.NumberFormat(locale, {
|
|
453
|
+
style: "currency",
|
|
454
|
+
currency: currency.toUpperCase(),
|
|
455
|
+
currencyDisplay: display
|
|
456
|
+
}).formatToParts(1).find((item) => item.type === "currency");
|
|
457
|
+
return part?.value ?? currency.toUpperCase();
|
|
458
|
+
}
|
|
459
|
+
function currencyName(locale, currency) {
|
|
460
|
+
try {
|
|
461
|
+
return new Intl.DisplayNames(locale, { type: "currency" }).of(currency.toUpperCase()) ?? currency.toUpperCase();
|
|
462
|
+
} catch {
|
|
463
|
+
return currency.toUpperCase();
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
function groupSeparator(locale) {
|
|
467
|
+
return new Intl.NumberFormat(locale, { useGrouping: true }).formatToParts(11111).find((part) => part.type === "group")?.value ?? "";
|
|
468
|
+
}
|
|
469
|
+
function symbolsFor(locale, currency) {
|
|
470
|
+
if (!isValidCurrency(currency)) return [];
|
|
471
|
+
const found = [];
|
|
472
|
+
for (const display of ["symbol", "narrowSymbol"]) {
|
|
473
|
+
const part = new Intl.NumberFormat(locale, {
|
|
474
|
+
style: "currency",
|
|
475
|
+
currency: currency.toUpperCase(),
|
|
476
|
+
currencyDisplay: display
|
|
477
|
+
}).formatToParts(1).find((item) => item.type === "currency")?.value;
|
|
478
|
+
if (part) found.push(part);
|
|
479
|
+
}
|
|
480
|
+
return found;
|
|
481
|
+
}
|
|
482
|
+
function normalizePaste(raw, locale, currency, options) {
|
|
483
|
+
let text = raw.trim();
|
|
484
|
+
let negative = false;
|
|
485
|
+
if (/^\(.*\)$/.test(text)) {
|
|
486
|
+
if (!options?.allowNegative) return { text: null, currency: null };
|
|
487
|
+
text = text.slice(1, -1).trim();
|
|
488
|
+
negative = true;
|
|
489
|
+
}
|
|
490
|
+
text = stripLettersBesideMarks(text);
|
|
491
|
+
const allowed = new Set((options?.currencies ?? []).map((code) => code.toUpperCase()));
|
|
492
|
+
let named = null;
|
|
493
|
+
for (const match of text.matchAll(/\b([A-Za-z]{3})\b/g)) {
|
|
494
|
+
const code = match[1].toUpperCase();
|
|
495
|
+
if (!isValidCurrency(code)) continue;
|
|
496
|
+
if (!named && allowed.has(code) && code !== currency.toUpperCase()) named = code;
|
|
497
|
+
text = text.replace(match[0], " ");
|
|
498
|
+
}
|
|
499
|
+
const symbols = new Set(CURRENCY_MARKS);
|
|
500
|
+
for (const code of [currency, named, ...options?.currencies ?? []]) {
|
|
501
|
+
if (code) symbolsFor(locale, code).forEach((symbol) => symbols.add(symbol));
|
|
502
|
+
}
|
|
503
|
+
const ordered = [...symbols].filter(Boolean).sort((a, b) => b.length - a.length);
|
|
504
|
+
for (const symbol of ordered) text = text.split(symbol).join("");
|
|
505
|
+
const group = groupSeparator(locale);
|
|
506
|
+
text = Array.from(text).filter((char) => {
|
|
507
|
+
if (!/\s/u.test(char)) return true;
|
|
508
|
+
return group !== "" && group.includes(char);
|
|
509
|
+
}).join("").trim();
|
|
510
|
+
if (/[eE]/.test(text) || !/[\d٠-٩۰-۹0-9]/.test(text)) {
|
|
511
|
+
return { text: null, currency: named };
|
|
512
|
+
}
|
|
513
|
+
if (negative && !text.startsWith("-")) text = `-${text}`;
|
|
514
|
+
return { text, currency: named };
|
|
515
|
+
}
|
|
516
|
+
var MARK_CLASS = CURRENCY_MARKS.map((mark) => mark.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("");
|
|
517
|
+
function stripLettersBesideMarks(text) {
|
|
518
|
+
const beside = new RegExp(`[A-Za-z]+(?=\\s*[${MARK_CLASS}])|(?<=[${MARK_CLASS}]\\s*)[A-Za-z]+`, "gu");
|
|
519
|
+
return text.replace(beside, "");
|
|
520
|
+
}
|
|
521
|
+
function roundingChangesValue(raw, locale, digits) {
|
|
522
|
+
const decimal = new Intl.NumberFormat(locale).formatToParts(1.1).find((part) => part.type === "decimal")?.value ?? ".";
|
|
523
|
+
const group = groupSeparator(locale);
|
|
524
|
+
let text = normalizeDigits(raw.trim()).replace(/^[+-]/, "");
|
|
525
|
+
if (group) text = text.split(group).join("");
|
|
526
|
+
if (decimal !== ".") text = text.replace(decimal, ".");
|
|
527
|
+
if (!/^\d+(\.\d+)?$/.test(text)) return false;
|
|
528
|
+
const fraction = text.split(".")[1] ?? "";
|
|
529
|
+
if (fraction.length <= digits) return false;
|
|
530
|
+
const [whole, frac = ""] = text.split(".");
|
|
531
|
+
const cut = digits === 0 ? whole : `${whole}.${frac.slice(0, digits)}`;
|
|
532
|
+
return roundHalfExpand(text, digits) !== roundHalfExpand(cut, digits);
|
|
533
|
+
}
|
|
534
|
+
var MoneyFieldContext = React10.createContext(null);
|
|
535
|
+
function useMoneyField() {
|
|
536
|
+
const value = React10.useContext(MoneyFieldContext);
|
|
537
|
+
if (!value) throw new Error("MoneyField parts must be rendered inside MoneyField.Root");
|
|
538
|
+
return value;
|
|
539
|
+
}
|
|
540
|
+
var CURRENCY_SEARCH_THRESHOLD = 12;
|
|
541
|
+
function CurrencyItemLabel({ code, locale }) {
|
|
542
|
+
return /* @__PURE__ */ jsxs("span", { className: "MoneyFieldCurrencyItem", children: [
|
|
543
|
+
/* @__PURE__ */ jsxs("span", { children: [
|
|
544
|
+
code,
|
|
545
|
+
" \xB7 ",
|
|
546
|
+
currencyName(locale, code)
|
|
547
|
+
] }),
|
|
548
|
+
/* @__PURE__ */ jsx("span", { className: "MoneyFieldCurrencySymbol", children: currencySymbol(locale, code, "narrowSymbol") })
|
|
549
|
+
] });
|
|
550
|
+
}
|
|
551
|
+
function Chevron() {
|
|
552
|
+
return /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined MoneyFieldCurrencyIcon", children: "expand_more" });
|
|
553
|
+
}
|
|
554
|
+
function CurrencySelect() {
|
|
555
|
+
const {
|
|
556
|
+
currency,
|
|
557
|
+
currencies,
|
|
558
|
+
locale,
|
|
559
|
+
currencyLabel,
|
|
560
|
+
disabled,
|
|
561
|
+
readOnly,
|
|
562
|
+
searchable,
|
|
563
|
+
onCurrencyChange
|
|
564
|
+
} = useMoneyField();
|
|
565
|
+
const codes = currencies ?? [];
|
|
566
|
+
const locked = disabled || readOnly;
|
|
567
|
+
const search = searchable === true || searchable !== false && codes.length > CURRENCY_SEARCH_THRESHOLD;
|
|
568
|
+
const triggerName = `${currencyLabel}, ${currency}`;
|
|
569
|
+
if (search) {
|
|
570
|
+
return /* @__PURE__ */ jsx(Field.Root, { className: "MoneyFieldCurrencyField", children: /* @__PURE__ */ jsxs(
|
|
571
|
+
Combobox.Root,
|
|
572
|
+
{
|
|
573
|
+
items: codes,
|
|
574
|
+
value: currency,
|
|
575
|
+
onValueChange: (next) => {
|
|
576
|
+
if (typeof next === "string") onCurrencyChange(next);
|
|
577
|
+
},
|
|
578
|
+
itemToStringLabel: (code) => `${code} ${currencyName(locale, code)}`,
|
|
579
|
+
disabled: locked,
|
|
580
|
+
children: [
|
|
581
|
+
/* @__PURE__ */ jsx("span", { className: "MoneyFieldCurrency", children: /* @__PURE__ */ jsxs(Combobox.Trigger, { className: "MoneyFieldCurrencyTrigger", "aria-label": triggerName, disabled: locked, children: [
|
|
582
|
+
/* @__PURE__ */ jsx("span", { className: "MoneyFieldCurrencyCode", children: currency }),
|
|
583
|
+
/* @__PURE__ */ jsx(Chevron, {})
|
|
584
|
+
] }) }),
|
|
585
|
+
/* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, { sideOffset: 4, align: "start", children: /* @__PURE__ */ jsxs(Combobox.Popup, { className: "MoneyFieldCurrencyPopup", children: [
|
|
586
|
+
/* @__PURE__ */ jsx(Combobox.Input, { "aria-label": `Filter ${currencyLabel}`, placeholder: "Code or name" }),
|
|
587
|
+
/* @__PURE__ */ jsx(Combobox.Empty, { children: "No currencies" }),
|
|
588
|
+
/* @__PURE__ */ jsx(Combobox.List, { children: (code) => /* @__PURE__ */ jsx(Combobox.Item, { value: code, children: /* @__PURE__ */ jsx(CurrencyItemLabel, { code, locale }) }, code) })
|
|
589
|
+
] }) }) })
|
|
590
|
+
]
|
|
591
|
+
}
|
|
592
|
+
) });
|
|
593
|
+
}
|
|
594
|
+
return /* @__PURE__ */ jsx(Field.Root, { className: "MoneyFieldCurrencyField", children: /* @__PURE__ */ jsxs(
|
|
595
|
+
Select.Root,
|
|
596
|
+
{
|
|
597
|
+
items: codes.map((code) => ({ value: code, label: code })),
|
|
598
|
+
value: currency,
|
|
599
|
+
onValueChange: (next) => {
|
|
600
|
+
if (next) onCurrencyChange(next);
|
|
601
|
+
},
|
|
602
|
+
disabled: locked,
|
|
603
|
+
children: [
|
|
604
|
+
/* @__PURE__ */ jsx("span", { className: "MoneyFieldCurrency", children: /* @__PURE__ */ jsxs(Select.Trigger, { className: "MoneyFieldCurrencyTrigger", "aria-label": triggerName, disabled: locked, children: [
|
|
605
|
+
/* @__PURE__ */ jsx(Select.Value, { className: "MoneyFieldCurrencyCode" }),
|
|
606
|
+
/* @__PURE__ */ jsx(Select.Icon, { children: /* @__PURE__ */ jsx(Chevron, {}) })
|
|
607
|
+
] }) }),
|
|
608
|
+
/* @__PURE__ */ jsx(Select.Portal, { children: /* @__PURE__ */ jsx(Select.Positioner, { sideOffset: 4, align: "start", children: /* @__PURE__ */ jsx(Select.Popup, { className: "MoneyFieldCurrencyPopup", children: /* @__PURE__ */ jsx(Select.List, { children: codes.map((code) => /* @__PURE__ */ jsx(Select.Item, { value: code, children: /* @__PURE__ */ jsx(Select.ItemText, { children: /* @__PURE__ */ jsx(CurrencyItemLabel, { code, locale }) }) }, code)) }) }) }) })
|
|
609
|
+
]
|
|
610
|
+
}
|
|
611
|
+
) });
|
|
612
|
+
}
|
|
613
|
+
function documentLocale() {
|
|
614
|
+
if (typeof document !== "undefined" && document.documentElement.lang) return document.documentElement.lang;
|
|
615
|
+
if (typeof navigator !== "undefined" && navigator.language) return navigator.language;
|
|
616
|
+
return "en";
|
|
617
|
+
}
|
|
618
|
+
function spokenAmount(minor, currency, fractionDigits) {
|
|
619
|
+
return toDecimalString(minor, currency, fractionDigits).replace(/(\.\d*?)0+$/, "$1").replace(/\.$/, "");
|
|
620
|
+
}
|
|
621
|
+
function assignRef(ref, value) {
|
|
622
|
+
if (typeof ref === "function") ref(value);
|
|
623
|
+
else if (ref) ref.current = value;
|
|
624
|
+
}
|
|
625
|
+
function describedBy(existing, ...ids) {
|
|
626
|
+
const parts = /* @__PURE__ */ new Set();
|
|
627
|
+
for (const value of [existing, ...ids]) {
|
|
628
|
+
if (!value) continue;
|
|
629
|
+
for (const id of value.split(/\s+/)) if (id) parts.add(id);
|
|
630
|
+
}
|
|
631
|
+
return parts.size ? [...parts].join(" ") : void 0;
|
|
632
|
+
}
|
|
633
|
+
var MoneyFieldRoot = React10.forwardRef(function MoneyFieldRoot2({
|
|
634
|
+
value: valueProp,
|
|
635
|
+
defaultValue = null,
|
|
636
|
+
onValueChange,
|
|
637
|
+
onValueCommitted,
|
|
638
|
+
currency: currencyProp,
|
|
639
|
+
defaultCurrency,
|
|
640
|
+
currencies,
|
|
641
|
+
onCurrencyChange: onCurrencyChangeProp,
|
|
642
|
+
locale: localeProp,
|
|
643
|
+
fractionDigits,
|
|
644
|
+
trailingZeros = "always",
|
|
645
|
+
allowNegative = false,
|
|
646
|
+
min,
|
|
647
|
+
max,
|
|
648
|
+
steps,
|
|
649
|
+
symbolDisplay = "narrowSymbol",
|
|
650
|
+
align = "start",
|
|
651
|
+
name,
|
|
652
|
+
submitAs = "decimal",
|
|
653
|
+
currencyName: currencyNameProp,
|
|
654
|
+
currencyLabel = "Currency",
|
|
655
|
+
searchable = "auto",
|
|
656
|
+
size = "md",
|
|
657
|
+
allowWheelScrub = false,
|
|
658
|
+
className,
|
|
659
|
+
children,
|
|
660
|
+
disabled = false,
|
|
661
|
+
readOnly = false,
|
|
662
|
+
required = false,
|
|
663
|
+
id,
|
|
664
|
+
...rest
|
|
665
|
+
}, ref) {
|
|
666
|
+
const locale = localeProp || documentLocale();
|
|
667
|
+
const fallback = currencyProp ?? defaultCurrency ?? currencies?.[0];
|
|
668
|
+
if (process.env.NODE_ENV !== "production") {
|
|
669
|
+
if (!fallback) {
|
|
670
|
+
throw new Error("MoneyField needs `currency`, `defaultCurrency`, or a non-empty `currencies` list.");
|
|
671
|
+
}
|
|
672
|
+
for (const code of [fallback, ...currencies ?? []]) {
|
|
673
|
+
if (!isValidCurrency(code)) {
|
|
674
|
+
throw new Error(`Unknown currency "${code}". Expected an ISO 4217 code Intl recognizes.`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
const [currencyState, setCurrencyState] = React10.useState(fallback ?? "USD");
|
|
679
|
+
const currency = (currencyProp ?? currencyState).toUpperCase();
|
|
680
|
+
const digits = getCurrencyDigits(currency, fractionDigits);
|
|
681
|
+
const [uncontrolled, setUncontrolled] = React10.useState(defaultValue);
|
|
682
|
+
const minor = valueProp !== void 0 ? valueProp : uncontrolled;
|
|
683
|
+
const major = minor == null ? null : fromMinorUnits(minor, currency, fractionDigits);
|
|
684
|
+
const currencyRef = React10.useRef(currency);
|
|
685
|
+
const minorRef = React10.useRef(minor);
|
|
686
|
+
const majorRef = React10.useRef(major);
|
|
687
|
+
if (currencyRef.current === currency) {
|
|
688
|
+
minorRef.current = minor;
|
|
689
|
+
majorRef.current = major;
|
|
690
|
+
}
|
|
691
|
+
const inputRef = React10.useRef(null);
|
|
692
|
+
const formattingRef = React10.useRef(false);
|
|
693
|
+
const rawRef = React10.useRef("");
|
|
694
|
+
const descriptionId = React10.useId();
|
|
695
|
+
const liveId = React10.useId();
|
|
696
|
+
const [live, setLive] = React10.useState("");
|
|
697
|
+
const [focused, setFocused] = React10.useState(false);
|
|
698
|
+
const [blurCount, setBlurCount] = React10.useState(0);
|
|
699
|
+
const [pendingSign, setPendingSign] = React10.useState(1);
|
|
700
|
+
const pendingSignRef = React10.useRef(pendingSign);
|
|
701
|
+
pendingSignRef.current = pendingSign;
|
|
702
|
+
const [draftMajor, setDraftMajor] = React10.useState(void 0);
|
|
703
|
+
const showPicker = (currencies?.length ?? 0) >= 2;
|
|
704
|
+
const symbol = currencySymbol(locale, currency, symbolDisplay);
|
|
705
|
+
const side = affixSide(locale, currency);
|
|
706
|
+
const showSymbol = symbolDisplay !== "none" && symbol !== "";
|
|
707
|
+
const minMinor = min ?? (allowNegative ? void 0 : 0);
|
|
708
|
+
const maxMinor = Math.min(max ?? MAX_MINOR_UNITS, MAX_MINOR_UNITS);
|
|
709
|
+
const minorPerMajor = 10 ** digits;
|
|
710
|
+
const stepMinor = steps?.step ?? minorPerMajor;
|
|
711
|
+
const smallMinor = steps?.small ?? 1;
|
|
712
|
+
const largeMinor = steps?.large ?? 100 * minorPerMajor;
|
|
713
|
+
const majorStep = fromMinorUnits(stepMinor, currency, fractionDigits);
|
|
714
|
+
const step = majorStep === 1 ? "any" : majorStep;
|
|
715
|
+
const publish = React10.useCallback(
|
|
716
|
+
(nextMinor, reason, commit) => {
|
|
717
|
+
const code = currencyRef.current;
|
|
718
|
+
const decimal = nextMinor == null ? null : toDecimalString(nextMinor, code, fractionDigits);
|
|
719
|
+
minorRef.current = nextMinor;
|
|
720
|
+
majorRef.current = nextMinor == null ? null : fromMinorUnits(nextMinor, code, fractionDigits);
|
|
721
|
+
if (valueProp === void 0) setUncontrolled(nextMinor);
|
|
722
|
+
onValueChange?.(nextMinor, { currency: code, decimal, reason });
|
|
723
|
+
if (commit) onValueCommitted?.(nextMinor, { currency: code, decimal });
|
|
724
|
+
},
|
|
725
|
+
[fractionDigits, onValueChange, onValueCommitted, valueProp]
|
|
726
|
+
);
|
|
727
|
+
const changeCurrency = React10.useCallback(
|
|
728
|
+
(next) => {
|
|
729
|
+
const code = next.toUpperCase();
|
|
730
|
+
if (code === currencyRef.current) return;
|
|
731
|
+
if (currencyProp === void 0) setCurrencyState(code);
|
|
732
|
+
onCurrencyChangeProp?.(code);
|
|
733
|
+
},
|
|
734
|
+
[currencyProp, onCurrencyChangeProp]
|
|
735
|
+
);
|
|
736
|
+
React10.useLayoutEffect(() => {
|
|
737
|
+
if (currencyRef.current === currency) return;
|
|
738
|
+
const previousMajor = majorRef.current;
|
|
739
|
+
const previousMinor = minorRef.current;
|
|
740
|
+
currencyRef.current = currency;
|
|
741
|
+
setDraftMajor(void 0);
|
|
742
|
+
const nameOf = currencyName(locale, currency);
|
|
743
|
+
if (valueProp !== void 0) {
|
|
744
|
+
minorRef.current = valueProp;
|
|
745
|
+
majorRef.current = valueProp == null ? null : fromMinorUnits(valueProp, currency, fractionDigits);
|
|
746
|
+
setLive(`Currency changed to ${nameOf}.`);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
if (previousMajor == null || previousMinor == null) {
|
|
750
|
+
setLive(`Currency changed to ${nameOf}.`);
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
const nextMinor = toMinorUnits(previousMajor, currency, fractionDigits);
|
|
754
|
+
if (nextMinor !== previousMinor) {
|
|
755
|
+
publish(nextMinor, "none", true);
|
|
756
|
+
setLive(`Currency changed to ${nameOf}. Amount rounded to ${spokenAmount(nextMinor, currency, fractionDigits)}.`);
|
|
757
|
+
} else {
|
|
758
|
+
setLive(`Currency changed to ${nameOf}.`);
|
|
759
|
+
}
|
|
760
|
+
}, [currency, fractionDigits, locale, publish, valueProp]);
|
|
761
|
+
const onPasteCapture = React10.useCallback(
|
|
762
|
+
(event) => {
|
|
763
|
+
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
764
|
+
if (!input || input.type === "number" || input.type === "hidden" || readOnly || disabled) return;
|
|
765
|
+
const raw = event.clipboardData?.getData("text/plain") ?? "";
|
|
766
|
+
const normalized = normalizePaste(raw, locale, currencyRef.current, { allowNegative, currencies });
|
|
767
|
+
if (normalized.currency) changeCurrency(normalized.currency);
|
|
768
|
+
if (normalized.text == null) {
|
|
769
|
+
event.preventDefault();
|
|
770
|
+
event.stopPropagation();
|
|
771
|
+
if (!allowNegative && /^\(.*\)$/.test(raw.trim())) {
|
|
772
|
+
setLive("Negative amounts aren't allowed here.");
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (normalized.text === raw) return;
|
|
777
|
+
event.preventDefault();
|
|
778
|
+
event.stopPropagation();
|
|
779
|
+
const start = input.selectionStart ?? input.value.length;
|
|
780
|
+
const end = input.selectionEnd ?? start;
|
|
781
|
+
const next = input.value.slice(0, start) + normalized.text + input.value.slice(end);
|
|
782
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
783
|
+
setter?.call(input, next);
|
|
784
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
785
|
+
},
|
|
786
|
+
[allowNegative, changeCurrency, currencies, disabled, locale, readOnly]
|
|
787
|
+
);
|
|
788
|
+
const onKeyDownCapture = React10.useCallback((event) => {
|
|
789
|
+
if (event.key === "e" || event.key === "E") {
|
|
790
|
+
event.preventDefault();
|
|
791
|
+
event.stopPropagation();
|
|
792
|
+
}
|
|
793
|
+
}, []);
|
|
794
|
+
const onInputCapture = React10.useCallback((event) => {
|
|
795
|
+
const input = event.target instanceof HTMLInputElement ? event.target : null;
|
|
796
|
+
if (!input || input.type === "number" || input.type === "hidden" || formattingRef.current) return;
|
|
797
|
+
rawRef.current = input.value;
|
|
798
|
+
setLive("");
|
|
799
|
+
}, []);
|
|
800
|
+
const onGroupBlur = React10.useCallback(
|
|
801
|
+
(event) => {
|
|
802
|
+
if (event.currentTarget.contains(event.relatedTarget)) return;
|
|
803
|
+
const raw = inputRef.current?.value || rawRef.current;
|
|
804
|
+
const code = currencyRef.current;
|
|
805
|
+
const current = minorRef.current;
|
|
806
|
+
const places = getCurrencyDigits(code, fractionDigits);
|
|
807
|
+
if (current != null && raw && roundingChangesValue(raw, locale, places)) {
|
|
808
|
+
setLive(`Rounded to ${formatMoney2(current, code, { locale, display: symbolDisplay, fractionDigits })}.`);
|
|
809
|
+
}
|
|
810
|
+
setBlurCount((n) => n + 1);
|
|
811
|
+
},
|
|
812
|
+
[fractionDigits, locale, symbolDisplay]
|
|
813
|
+
);
|
|
814
|
+
const whole = trailingZeros === "auto" && (minor == null || digits > 0 && minor % minorPerMajor === 0);
|
|
815
|
+
const amountFormat = new Intl.NumberFormat(locale, {
|
|
816
|
+
style: "decimal",
|
|
817
|
+
useGrouping: true,
|
|
818
|
+
minimumFractionDigits: whole || digits === 0 ? 0 : digits,
|
|
819
|
+
maximumFractionDigits: digits
|
|
820
|
+
});
|
|
821
|
+
const formattedAmount = minor == null ? "" : amountFormat.format(
|
|
822
|
+
toDecimalString(minor, currency, fractionDigits)
|
|
823
|
+
);
|
|
824
|
+
React10.useLayoutEffect(() => {
|
|
825
|
+
const input = inputRef.current;
|
|
826
|
+
if (!input || input.ownerDocument.activeElement === input || input.value === formattedAmount) return;
|
|
827
|
+
const setter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
828
|
+
formattingRef.current = true;
|
|
829
|
+
setter?.call(input, formattedAmount);
|
|
830
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
831
|
+
formattingRef.current = false;
|
|
832
|
+
}, [blurCount, formattedAmount]);
|
|
833
|
+
const messages = React10.useMemo(
|
|
834
|
+
() => ({
|
|
835
|
+
valueMissing: "Enter an amount",
|
|
836
|
+
rangeUnderflow: minMinor == null ? "Enter an amount" : `Enter an amount of at least ${formatMoney2(minMinor, currency, { locale, display: symbolDisplay, fractionDigits })}`,
|
|
837
|
+
rangeOverflow: `Enter an amount of ${formatMoney2(maxMinor, currency, { locale, display: symbolDisplay, fractionDigits })} or less`
|
|
838
|
+
}),
|
|
839
|
+
[currency, fractionDigits, locale, maxMinor, minMinor, symbolDisplay]
|
|
840
|
+
);
|
|
841
|
+
const context = React10.useMemo(
|
|
842
|
+
() => ({
|
|
843
|
+
currency,
|
|
844
|
+
currencies,
|
|
845
|
+
locale,
|
|
846
|
+
affix: side,
|
|
847
|
+
symbol,
|
|
848
|
+
symbolDisplay,
|
|
849
|
+
showPicker,
|
|
850
|
+
showSymbol,
|
|
851
|
+
digits,
|
|
852
|
+
currencyLabel,
|
|
853
|
+
disabled,
|
|
854
|
+
readOnly,
|
|
855
|
+
allowNegative,
|
|
856
|
+
minor,
|
|
857
|
+
searchable,
|
|
858
|
+
descriptionId,
|
|
859
|
+
liveId,
|
|
860
|
+
pendingSign,
|
|
861
|
+
messages,
|
|
862
|
+
inputRef,
|
|
863
|
+
onCurrencyChange: changeCurrency,
|
|
864
|
+
onToggleSign: () => {
|
|
865
|
+
if (disabled || readOnly) return;
|
|
866
|
+
if (minorRef.current == null) {
|
|
867
|
+
setPendingSign((sign) => sign < 0 ? 1 : -1);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
setPendingSign(1);
|
|
871
|
+
publish(-minorRef.current, "none", true);
|
|
872
|
+
},
|
|
873
|
+
onPasteCapture,
|
|
874
|
+
onKeyDownCapture,
|
|
875
|
+
onInputCapture,
|
|
876
|
+
onGroupBlur,
|
|
877
|
+
focusInput: () => inputRef.current?.focus()
|
|
878
|
+
}),
|
|
879
|
+
[
|
|
880
|
+
allowNegative,
|
|
881
|
+
changeCurrency,
|
|
882
|
+
currency,
|
|
883
|
+
currencies,
|
|
884
|
+
currencyLabel,
|
|
885
|
+
descriptionId,
|
|
886
|
+
digits,
|
|
887
|
+
liveId,
|
|
888
|
+
messages,
|
|
889
|
+
pendingSign,
|
|
890
|
+
disabled,
|
|
891
|
+
locale,
|
|
892
|
+
minor,
|
|
893
|
+
onGroupBlur,
|
|
894
|
+
onInputCapture,
|
|
895
|
+
onKeyDownCapture,
|
|
896
|
+
onPasteCapture,
|
|
897
|
+
publish,
|
|
898
|
+
readOnly,
|
|
899
|
+
searchable,
|
|
900
|
+
showPicker,
|
|
901
|
+
showSymbol,
|
|
902
|
+
side,
|
|
903
|
+
symbol,
|
|
904
|
+
symbolDisplay
|
|
905
|
+
]
|
|
906
|
+
);
|
|
907
|
+
const submitted = minor == null ? "" : submitAs === "minor" ? String(minor) : toDecimalString(minor, currency, fractionDigits);
|
|
908
|
+
return /* @__PURE__ */ jsx(MoneyFieldContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(
|
|
909
|
+
NumberField.Root,
|
|
910
|
+
{
|
|
911
|
+
ref,
|
|
912
|
+
id,
|
|
913
|
+
value: draftMajor !== void 0 ? draftMajor : major,
|
|
914
|
+
onValueChange: (next, details) => {
|
|
915
|
+
if (formattingRef.current) return;
|
|
916
|
+
const typing = details.reason === "input-change" || details.reason === "input-paste";
|
|
917
|
+
let parsed = next;
|
|
918
|
+
if (pendingSignRef.current < 0 && parsed != null && parsed > 0) parsed = -parsed;
|
|
919
|
+
if (typing) setDraftMajor(parsed);
|
|
920
|
+
else {
|
|
921
|
+
setDraftMajor(void 0);
|
|
922
|
+
if (pendingSignRef.current < 0) setPendingSign(1);
|
|
923
|
+
}
|
|
924
|
+
const nextMinor = parsed == null ? null : toMinorUnits(parsed, currencyRef.current, fractionDigits);
|
|
925
|
+
if (nextMinor === minorRef.current && details.reason !== "input-clear") return;
|
|
926
|
+
publish(nextMinor, details.reason, !typing);
|
|
927
|
+
},
|
|
928
|
+
locale,
|
|
929
|
+
format: {
|
|
930
|
+
style: "decimal",
|
|
931
|
+
useGrouping: true,
|
|
932
|
+
minimumFractionDigits: whole || digits === 0 ? 0 : digits,
|
|
933
|
+
maximumFractionDigits: digits
|
|
934
|
+
},
|
|
935
|
+
min: minMinor == null ? void 0 : fromMinorUnits(minMinor, currency, fractionDigits),
|
|
936
|
+
max: fromMinorUnits(maxMinor, currency, fractionDigits),
|
|
937
|
+
step,
|
|
938
|
+
smallStep: fromMinorUnits(smallMinor, currency, fractionDigits),
|
|
939
|
+
largeStep: fromMinorUnits(largeMinor, currency, fractionDigits),
|
|
940
|
+
...rest,
|
|
941
|
+
allowOutOfRange: true,
|
|
942
|
+
allowWheelScrub,
|
|
943
|
+
disabled,
|
|
944
|
+
readOnly,
|
|
945
|
+
required,
|
|
946
|
+
"data-currency": currency,
|
|
947
|
+
"data-affix": showPicker || showSymbol ? side : void 0,
|
|
948
|
+
"data-affix-kind": showPicker ? "picker" : showSymbol ? "symbol" : void 0,
|
|
949
|
+
"data-negative": minor != null && minor < 0 ? "" : void 0,
|
|
950
|
+
"data-align": align,
|
|
951
|
+
"data-size": size,
|
|
952
|
+
"data-disabled": disabled ? "" : void 0,
|
|
953
|
+
"data-readonly": readOnly ? "" : void 0,
|
|
954
|
+
"data-focused": focused ? "" : void 0,
|
|
955
|
+
className: (state) => cn("MoneyField", typeof className === "function" ? className(state) : className),
|
|
956
|
+
onFocus: () => setFocused(true),
|
|
957
|
+
onBlur: (event) => {
|
|
958
|
+
if (!event.currentTarget.contains(event.relatedTarget)) setFocused(false);
|
|
959
|
+
},
|
|
960
|
+
children: [
|
|
961
|
+
children,
|
|
962
|
+
/* @__PURE__ */ jsx(Field.Description, { id: descriptionId, className: "visually-hidden", children: currencyName(locale, currency) }),
|
|
963
|
+
/* @__PURE__ */ jsx(Field.Description, { id: liveId, className: "visually-hidden", "aria-live": "polite", children: live }),
|
|
964
|
+
name ? /* @__PURE__ */ jsx("input", { type: "hidden", name, value: submitted }) : null,
|
|
965
|
+
showPicker && currencyNameProp ? /* @__PURE__ */ jsx("input", { type: "hidden", name: currencyNameProp, value: currency }) : null
|
|
966
|
+
]
|
|
967
|
+
}
|
|
968
|
+
) });
|
|
969
|
+
});
|
|
970
|
+
var MoneyFieldGroup = React10.forwardRef(
|
|
971
|
+
function MoneyFieldGroup2({ className, onPasteCapture, onKeyDownCapture, onInputCapture, onBlur, ...props }, ref) {
|
|
972
|
+
const money = useMoneyField();
|
|
973
|
+
return /* @__PURE__ */ jsx(
|
|
974
|
+
NumberField.Group,
|
|
975
|
+
{
|
|
976
|
+
ref,
|
|
977
|
+
className: (state) => cn("MoneyFieldGroup", typeof className === "function" ? className(state) : className),
|
|
978
|
+
onPasteCapture: (event) => {
|
|
979
|
+
onPasteCapture?.(event);
|
|
980
|
+
if (!event.defaultPrevented) money.onPasteCapture(event);
|
|
981
|
+
},
|
|
982
|
+
onKeyDownCapture: (event) => {
|
|
983
|
+
onKeyDownCapture?.(event);
|
|
984
|
+
if (!event.defaultPrevented) money.onKeyDownCapture(event);
|
|
985
|
+
},
|
|
986
|
+
onInputCapture: (event) => {
|
|
987
|
+
onInputCapture?.(event);
|
|
988
|
+
money.onInputCapture(event);
|
|
989
|
+
},
|
|
990
|
+
onBlur: (event) => {
|
|
991
|
+
onBlur?.(event);
|
|
992
|
+
money.onGroupBlur(event);
|
|
993
|
+
},
|
|
994
|
+
...props
|
|
995
|
+
}
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
);
|
|
999
|
+
var MoneyFieldInput = React10.forwardRef(
|
|
1000
|
+
function MoneyFieldInput2({ className, ...props }, ref) {
|
|
1001
|
+
const money = useMoneyField();
|
|
1002
|
+
return /* @__PURE__ */ jsx(
|
|
1003
|
+
NumberField.Input,
|
|
1004
|
+
{
|
|
1005
|
+
ref: (node) => {
|
|
1006
|
+
money.inputRef.current = node;
|
|
1007
|
+
assignRef(ref, node);
|
|
1008
|
+
},
|
|
1009
|
+
className: (state) => cn("MoneyFieldInput", typeof className === "function" ? className(state) : className),
|
|
1010
|
+
inputMode: money.digits > 0 ? "decimal" : "numeric",
|
|
1011
|
+
...props,
|
|
1012
|
+
"aria-describedby": describedBy(
|
|
1013
|
+
typeof props["aria-describedby"] === "string" ? props["aria-describedby"] : void 0,
|
|
1014
|
+
money.descriptionId,
|
|
1015
|
+
money.liveId
|
|
1016
|
+
)
|
|
1017
|
+
}
|
|
1018
|
+
);
|
|
1019
|
+
}
|
|
1020
|
+
);
|
|
1021
|
+
var MoneyFieldAffix = React10.forwardRef(
|
|
1022
|
+
function MoneyFieldAffix2({ className, ...props }, ref) {
|
|
1023
|
+
const money = useMoneyField();
|
|
1024
|
+
if (money.showPicker) return /* @__PURE__ */ jsx(CurrencySelect, {});
|
|
1025
|
+
if (!money.showSymbol) return null;
|
|
1026
|
+
return /* @__PURE__ */ jsx(
|
|
1027
|
+
"span",
|
|
1028
|
+
{
|
|
1029
|
+
ref,
|
|
1030
|
+
className: cn("MoneyFieldAffix", className),
|
|
1031
|
+
"aria-hidden": "true",
|
|
1032
|
+
onMouseDown: (event) => event.preventDefault(),
|
|
1033
|
+
onClick: money.focusInput,
|
|
1034
|
+
...props,
|
|
1035
|
+
children: money.symbol
|
|
1036
|
+
}
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
);
|
|
1040
|
+
function SignToggle() {
|
|
1041
|
+
const money = useMoneyField();
|
|
1042
|
+
if (!money.allowNegative) return null;
|
|
1043
|
+
const negative = money.minor != null ? money.minor < 0 : money.pendingSign < 0;
|
|
1044
|
+
const label = money.minor == null && negative ? "The next amount will be negative" : negative ? "Make amount positive" : "Make amount negative";
|
|
1045
|
+
return /* @__PURE__ */ jsx(
|
|
1046
|
+
Button,
|
|
1047
|
+
{
|
|
1048
|
+
type: "button",
|
|
1049
|
+
emphasis: "quiet",
|
|
1050
|
+
className: "MoneyFieldSign",
|
|
1051
|
+
"aria-label": label,
|
|
1052
|
+
disabled: money.disabled || money.readOnly,
|
|
1053
|
+
onClick: money.onToggleSign,
|
|
1054
|
+
children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: negative ? "remove" : "add" })
|
|
1055
|
+
}
|
|
1056
|
+
);
|
|
1057
|
+
}
|
|
1058
|
+
var MoneyFieldError = React10.forwardRef(
|
|
1059
|
+
function MoneyFieldError2({ match, children, ...props }, ref) {
|
|
1060
|
+
const money = useMoneyField();
|
|
1061
|
+
const fallback = match === "valueMissing" ? money.messages.valueMissing : match === "rangeUnderflow" ? money.messages.rangeUnderflow : match === "rangeOverflow" ? money.messages.rangeOverflow : null;
|
|
1062
|
+
return /* @__PURE__ */ jsx(Field.Error, { ref, match, ...props, children: children ?? fallback });
|
|
1063
|
+
}
|
|
1064
|
+
);
|
|
1065
|
+
function ownsError(children, match) {
|
|
1066
|
+
let found = false;
|
|
1067
|
+
React10.Children.forEach(children, (child) => {
|
|
1068
|
+
if (!React10.isValidElement(child) || child.type !== MoneyFieldError) return;
|
|
1069
|
+
const childMatch = child.props.match;
|
|
1070
|
+
if (childMatch == null || childMatch === true || childMatch === match) found = true;
|
|
1071
|
+
});
|
|
1072
|
+
return found;
|
|
1073
|
+
}
|
|
1074
|
+
var MoneyField = Object.assign(
|
|
1075
|
+
React10.forwardRef(function MoneyField2({ placeholder, steppers = false, children, ...props }, ref) {
|
|
1076
|
+
return /* @__PURE__ */ jsxs(MoneyFieldRoot, { ref, "data-steppers": steppers ? "" : void 0, ...props, children: [
|
|
1077
|
+
/* @__PURE__ */ jsxs(MoneyFieldGroup, { children: [
|
|
1078
|
+
/* @__PURE__ */ jsx(AffixSlot, { side: "start" }),
|
|
1079
|
+
/* @__PURE__ */ jsx(MoneyFieldInput, { placeholder }),
|
|
1080
|
+
/* @__PURE__ */ jsx(SignToggle, {}),
|
|
1081
|
+
steppers ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1082
|
+
/* @__PURE__ */ jsx(NumberField.Increment, { "aria-label": "Increase", children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "add" }) }),
|
|
1083
|
+
/* @__PURE__ */ jsx(NumberField.Decrement, { "aria-label": "Decrease", children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "remove" }) })
|
|
1084
|
+
] }) : null,
|
|
1085
|
+
/* @__PURE__ */ jsx(AffixSlot, { side: "end" })
|
|
1086
|
+
] }),
|
|
1087
|
+
children,
|
|
1088
|
+
ownsError(children, "valueMissing") ? null : /* @__PURE__ */ jsx(MoneyFieldError, { match: "valueMissing" }),
|
|
1089
|
+
ownsError(children, "rangeUnderflow") ? null : /* @__PURE__ */ jsx(MoneyFieldError, { match: "rangeUnderflow" }),
|
|
1090
|
+
ownsError(children, "rangeOverflow") ? null : /* @__PURE__ */ jsx(MoneyFieldError, { match: "rangeOverflow" })
|
|
1091
|
+
] });
|
|
1092
|
+
}),
|
|
1093
|
+
{
|
|
1094
|
+
Root: MoneyFieldRoot,
|
|
1095
|
+
Group: MoneyFieldGroup,
|
|
1096
|
+
Input: MoneyFieldInput,
|
|
1097
|
+
Affix: MoneyFieldAffix,
|
|
1098
|
+
CurrencySelect,
|
|
1099
|
+
Increment: NumberField.Increment,
|
|
1100
|
+
Decrement: NumberField.Decrement,
|
|
1101
|
+
Error: MoneyFieldError
|
|
1102
|
+
}
|
|
1103
|
+
);
|
|
1104
|
+
function AffixSlot({ side }) {
|
|
1105
|
+
const money = useMoneyField();
|
|
1106
|
+
if (money.affix !== side) return null;
|
|
1107
|
+
if (!money.showPicker && !money.showSymbol) return null;
|
|
1108
|
+
return /* @__PURE__ */ jsx(MoneyFieldAffix, {});
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
// src/custom/date-picker/date-utils.ts
|
|
1112
|
+
var ISO = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
1113
|
+
function parseDate(value) {
|
|
1114
|
+
const match = ISO.exec(value);
|
|
1115
|
+
if (!match) return null;
|
|
1116
|
+
const y = Number(match[1]);
|
|
1117
|
+
const m = Number(match[2]);
|
|
1118
|
+
const d = Number(match[3]);
|
|
1119
|
+
if (m < 1 || m > 12 || d < 1 || d > daysInMonth(y, m)) return null;
|
|
1120
|
+
return { y, m, d };
|
|
1121
|
+
}
|
|
1122
|
+
function formatDate(y, m, d) {
|
|
1123
|
+
return `${String(y).padStart(4, "0")}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}`;
|
|
1124
|
+
}
|
|
1125
|
+
function daysInMonth(year, month) {
|
|
1126
|
+
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
1127
|
+
}
|
|
1128
|
+
function utc(value) {
|
|
1129
|
+
const parts = parseDate(value);
|
|
1130
|
+
if (!parts) throw new Error(`Not an ISO date: ${value}`);
|
|
1131
|
+
return new Date(Date.UTC(parts.y, parts.m - 1, parts.d));
|
|
1132
|
+
}
|
|
1133
|
+
function fromUtc(date) {
|
|
1134
|
+
return formatDate(date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate());
|
|
1135
|
+
}
|
|
1136
|
+
function todayDate(now = /* @__PURE__ */ new Date()) {
|
|
1137
|
+
return formatDate(now.getFullYear(), now.getMonth() + 1, now.getDate());
|
|
1138
|
+
}
|
|
1139
|
+
function compare(a, b) {
|
|
1140
|
+
if (a === b) return 0;
|
|
1141
|
+
return a < b ? -1 : 1;
|
|
1142
|
+
}
|
|
1143
|
+
function addDays(value, days) {
|
|
1144
|
+
const date = utc(value);
|
|
1145
|
+
date.setUTCDate(date.getUTCDate() + days);
|
|
1146
|
+
return fromUtc(date);
|
|
1147
|
+
}
|
|
1148
|
+
function addMonths(value, months) {
|
|
1149
|
+
const parts = parseDate(value);
|
|
1150
|
+
if (!parts) throw new Error(`Not an ISO date: ${value}`);
|
|
1151
|
+
const index = parts.y * 12 + (parts.m - 1) + months;
|
|
1152
|
+
const y = Math.floor(index / 12);
|
|
1153
|
+
const m = index - y * 12 + 1;
|
|
1154
|
+
return formatDate(y, m, Math.min(parts.d, daysInMonth(y, m)));
|
|
1155
|
+
}
|
|
1156
|
+
function addYears(value, years) {
|
|
1157
|
+
return addMonths(value, years * 12);
|
|
1158
|
+
}
|
|
1159
|
+
function startOfMonth(value) {
|
|
1160
|
+
const parts = parseDate(value);
|
|
1161
|
+
if (!parts) throw new Error(`Not an ISO date: ${value}`);
|
|
1162
|
+
return formatDate(parts.y, parts.m, 1);
|
|
1163
|
+
}
|
|
1164
|
+
function endOfMonth(value) {
|
|
1165
|
+
const parts = parseDate(value);
|
|
1166
|
+
if (!parts) throw new Error(`Not an ISO date: ${value}`);
|
|
1167
|
+
return formatDate(parts.y, parts.m, daysInMonth(parts.y, parts.m));
|
|
1168
|
+
}
|
|
1169
|
+
function startOfWeek(value, weekStartsOn) {
|
|
1170
|
+
const day = utc(value).getUTCDay();
|
|
1171
|
+
const delta = (day - weekStartsOn + 7) % 7;
|
|
1172
|
+
return addDays(value, -delta);
|
|
1173
|
+
}
|
|
1174
|
+
function endOfWeek(value, weekStartsOn) {
|
|
1175
|
+
return addDays(startOfWeek(value, weekStartsOn), 6);
|
|
1176
|
+
}
|
|
1177
|
+
function isoWeek(value) {
|
|
1178
|
+
const date = utc(value);
|
|
1179
|
+
const day = date.getUTCDay() || 7;
|
|
1180
|
+
date.setUTCDate(date.getUTCDate() + 4 - day);
|
|
1181
|
+
const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
|
|
1182
|
+
return Math.ceil(((date.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
1183
|
+
}
|
|
1184
|
+
function clampDate(value, min, max) {
|
|
1185
|
+
if (min && compare(value, min) < 0) return min;
|
|
1186
|
+
if (max && compare(value, max) > 0) return max;
|
|
1187
|
+
return value;
|
|
1188
|
+
}
|
|
1189
|
+
function isWithin(value, min, max) {
|
|
1190
|
+
if (min && compare(value, min) < 0) return false;
|
|
1191
|
+
if (max && compare(value, max) > 0) return false;
|
|
1192
|
+
return true;
|
|
1193
|
+
}
|
|
1194
|
+
function inclusiveDays(start, end) {
|
|
1195
|
+
const [a, b] = compare(start, end) <= 0 ? [start, end] : [end, start];
|
|
1196
|
+
return Math.round((utc(b).getTime() - utc(a).getTime()) / 864e5) + 1;
|
|
1197
|
+
}
|
|
1198
|
+
function orderRange(a, b) {
|
|
1199
|
+
return compare(a, b) <= 0 ? { start: a, end: b } : { start: b, end: a };
|
|
1200
|
+
}
|
|
1201
|
+
function lengthBlockReason(start, day, minLength, maxLength) {
|
|
1202
|
+
const len = inclusiveDays(start, day);
|
|
1203
|
+
if (maxLength != null && len > maxLength) return `Ranges can be up to ${maxLength} days`;
|
|
1204
|
+
if (minLength != null && len < minLength) return `Ranges must be at least ${minLength} days`;
|
|
1205
|
+
return null;
|
|
1206
|
+
}
|
|
1207
|
+
var SUNDAY_FIRST = /* @__PURE__ */ new Set(["US", "CA", "MX", "BR", "JP", "IL", "PH", "AU", "NZ", "IN", "KR", "TW", "TH", "ZA", "SA"]);
|
|
1208
|
+
function localeRegion(locale) {
|
|
1209
|
+
const tag = locale.trim().replace(/_/g, "-");
|
|
1210
|
+
if (!tag.includes("-")) return /^en$/i.test(tag) ? "US" : null;
|
|
1211
|
+
try {
|
|
1212
|
+
return new Intl.Locale(tag).region ?? null;
|
|
1213
|
+
} catch {
|
|
1214
|
+
const part = tag.split("-").find((piece) => /^[A-Za-z]{2}$/.test(piece));
|
|
1215
|
+
return part ? part.toUpperCase() : null;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
function defaultWeekStartsOn(locale) {
|
|
1219
|
+
try {
|
|
1220
|
+
const loc = new Intl.Locale(locale);
|
|
1221
|
+
const info = typeof loc.getWeekInfo === "function" ? loc.getWeekInfo() : loc.weekInfo;
|
|
1222
|
+
const first = info?.firstDay;
|
|
1223
|
+
if (typeof first === "number" && first >= 1 && first <= 7) return first === 7 ? 0 : first;
|
|
1224
|
+
} catch {
|
|
1225
|
+
}
|
|
1226
|
+
const region = localeRegion(locale);
|
|
1227
|
+
return region != null && SUNDAY_FIRST.has(region) ? 0 : 1;
|
|
1228
|
+
}
|
|
1229
|
+
function monthCells(month, weekStartsOn) {
|
|
1230
|
+
const first = startOfMonth(month);
|
|
1231
|
+
const gridStart = startOfWeek(first, weekStartsOn);
|
|
1232
|
+
const monthNumber = parseDate(first).m;
|
|
1233
|
+
return Array.from({ length: 42 }, (_, i) => {
|
|
1234
|
+
const date = addDays(gridStart, i);
|
|
1235
|
+
return { date, outside: parseDate(date).m !== monthNumber };
|
|
1236
|
+
});
|
|
1237
|
+
}
|
|
1238
|
+
function atNoon(value) {
|
|
1239
|
+
const parts = parseDate(value);
|
|
1240
|
+
if (!parts) throw new Error(`Not an ISO date: ${value}`);
|
|
1241
|
+
return new Date(Date.UTC(parts.y, parts.m - 1, parts.d, 12));
|
|
1242
|
+
}
|
|
1243
|
+
function formatDay(value, locale) {
|
|
1244
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1245
|
+
month: "short",
|
|
1246
|
+
day: "numeric",
|
|
1247
|
+
year: "numeric",
|
|
1248
|
+
timeZone: "UTC"
|
|
1249
|
+
}).format(atNoon(value));
|
|
1250
|
+
}
|
|
1251
|
+
function formatFull(value, locale) {
|
|
1252
|
+
return new Intl.DateTimeFormat(locale, {
|
|
1253
|
+
weekday: "long",
|
|
1254
|
+
month: "long",
|
|
1255
|
+
day: "numeric",
|
|
1256
|
+
year: "numeric",
|
|
1257
|
+
timeZone: "UTC"
|
|
1258
|
+
}).format(atNoon(value));
|
|
1259
|
+
}
|
|
1260
|
+
function formatMonth(value, locale) {
|
|
1261
|
+
return new Intl.DateTimeFormat(locale, { month: "long", year: "numeric", timeZone: "UTC" }).format(atNoon(value));
|
|
1262
|
+
}
|
|
1263
|
+
function formatMonthShort(value, locale) {
|
|
1264
|
+
return new Intl.DateTimeFormat(locale, { month: "short", timeZone: "UTC" }).format(atNoon(value));
|
|
1265
|
+
}
|
|
1266
|
+
function formatRangeLabel(start, end, locale) {
|
|
1267
|
+
const fmt = new Intl.DateTimeFormat(locale, {
|
|
1268
|
+
month: "short",
|
|
1269
|
+
day: "numeric",
|
|
1270
|
+
year: "numeric",
|
|
1271
|
+
timeZone: "UTC"
|
|
1272
|
+
});
|
|
1273
|
+
if (typeof fmt.formatRange === "function") return fmt.formatRange(atNoon(start), atNoon(end));
|
|
1274
|
+
return `${formatDay(start, locale)} \u2013 ${formatDay(end, locale)}`;
|
|
1275
|
+
}
|
|
1276
|
+
function weekdayLabels(locale, weekStartsOn) {
|
|
1277
|
+
const sunday = "2026-01-04";
|
|
1278
|
+
return Array.from({ length: 7 }, (_, i) => {
|
|
1279
|
+
const date = atNoon(addDays(sunday, (weekStartsOn + i) % 7));
|
|
1280
|
+
return {
|
|
1281
|
+
narrow: new Intl.DateTimeFormat(locale, { weekday: "narrow", timeZone: "UTC" }).format(date),
|
|
1282
|
+
long: new Intl.DateTimeFormat(locale, { weekday: "long", timeZone: "UTC" }).format(date)
|
|
1283
|
+
};
|
|
1284
|
+
});
|
|
1285
|
+
}
|
|
1286
|
+
function weekRange(todayValue, weekStartsOn, offsetWeeks) {
|
|
1287
|
+
const start = addDays(startOfWeek(todayValue, weekStartsOn), offsetWeeks * 7);
|
|
1288
|
+
return { start, end: addDays(start, 6) };
|
|
1289
|
+
}
|
|
1290
|
+
function monthRange(todayValue, offset) {
|
|
1291
|
+
const month = addMonths(startOfMonth(todayValue), offset);
|
|
1292
|
+
return { start: month, end: endOfMonth(month) };
|
|
1293
|
+
}
|
|
1294
|
+
var today = { id: "today", label: "Today", value: (day) => day };
|
|
1295
|
+
var yesterday = { id: "yesterday", label: "Yesterday", value: (day) => addDays(day, -1) };
|
|
1296
|
+
var tomorrow = { id: "tomorrow", label: "Tomorrow", value: (day) => addDays(day, 1) };
|
|
1297
|
+
function thisWeek(weekStartsOn = 1) {
|
|
1298
|
+
return { id: "thisWeek", label: "This week", value: (day) => weekRange(day, weekStartsOn, 0) };
|
|
1299
|
+
}
|
|
1300
|
+
function lastWeek(weekStartsOn = 1) {
|
|
1301
|
+
return { id: "lastWeek", label: "Last week", value: (day) => weekRange(day, weekStartsOn, -1) };
|
|
1302
|
+
}
|
|
1303
|
+
function nextWeek(weekStartsOn = 1) {
|
|
1304
|
+
return { id: "nextWeek", label: "Next week", value: (day) => weekRange(day, weekStartsOn, 1) };
|
|
1305
|
+
}
|
|
1306
|
+
var last7Days = { id: "last7Days", label: "Last 7 days", value: (day) => ({ start: addDays(day, -6), end: day }) };
|
|
1307
|
+
var last30Days = { id: "last30Days", label: "Last 30 days", value: (day) => ({ start: addDays(day, -29), end: day }) };
|
|
1308
|
+
var thisMonth = { id: "thisMonth", label: "This month", value: (day) => monthRange(day, 0) };
|
|
1309
|
+
var lastMonth = { id: "lastMonth", label: "Last month", value: (day) => monthRange(day, -1) };
|
|
1310
|
+
var nextMonth = { id: "nextMonth", label: "Next month", value: (day) => monthRange(day, 1) };
|
|
1311
|
+
var thisQuarter = {
|
|
1312
|
+
id: "thisQuarter",
|
|
1313
|
+
label: "This quarter",
|
|
1314
|
+
value: (day) => {
|
|
1315
|
+
const parts = parseDate(day);
|
|
1316
|
+
const startMonth = Math.floor((parts.m - 1) / 3) * 3 + 1;
|
|
1317
|
+
const start = formatDate(parts.y, startMonth, 1);
|
|
1318
|
+
return { start, end: endOfMonth(addMonths(start, 2)) };
|
|
1319
|
+
}
|
|
1320
|
+
};
|
|
1321
|
+
var yearToDate = {
|
|
1322
|
+
id: "yearToDate",
|
|
1323
|
+
label: "Year to date",
|
|
1324
|
+
value: (day) => ({ start: formatDate(parseDate(day).y, 1, 1), end: day })
|
|
1325
|
+
};
|
|
1326
|
+
function builtinPresets(weekStartsOn) {
|
|
1327
|
+
return [
|
|
1328
|
+
today,
|
|
1329
|
+
yesterday,
|
|
1330
|
+
tomorrow,
|
|
1331
|
+
thisWeek(weekStartsOn),
|
|
1332
|
+
lastWeek(weekStartsOn),
|
|
1333
|
+
nextWeek(weekStartsOn),
|
|
1334
|
+
last7Days,
|
|
1335
|
+
last30Days,
|
|
1336
|
+
thisMonth,
|
|
1337
|
+
lastMonth,
|
|
1338
|
+
nextMonth,
|
|
1339
|
+
thisQuarter,
|
|
1340
|
+
yearToDate
|
|
1341
|
+
];
|
|
1342
|
+
}
|
|
1343
|
+
var SINGLE_PRESET_IDS = ["today", "yesterday", "tomorrow"];
|
|
1344
|
+
function defaultPresets(mode, weekStartsOn) {
|
|
1345
|
+
const all = builtinPresets(weekStartsOn);
|
|
1346
|
+
if (mode === "single") return all.filter((preset) => SINGLE_PRESET_IDS.includes(preset.id));
|
|
1347
|
+
return all.filter((preset) => !SINGLE_PRESET_IDS.includes(preset.id) || preset.id === "today");
|
|
1348
|
+
}
|
|
1349
|
+
function resolvePreset(preset, todayValue) {
|
|
1350
|
+
return typeof preset.value === "function" ? preset.value(todayValue) : preset.value;
|
|
1351
|
+
}
|
|
1352
|
+
function isRangeValue(value) {
|
|
1353
|
+
return typeof value === "object" && value !== null && "start" in value;
|
|
1354
|
+
}
|
|
1355
|
+
function sameSelection(a, b) {
|
|
1356
|
+
if (a == null || b == null) return a == null && b == null;
|
|
1357
|
+
const left = isRangeValue(a) ? a : { start: a, end: a };
|
|
1358
|
+
const right = isRangeValue(b) ? b : { start: b, end: b };
|
|
1359
|
+
return left.start === right.start && left.end === right.end;
|
|
1360
|
+
}
|
|
1361
|
+
function presetInBounds(value, min, max, isDateDisabled) {
|
|
1362
|
+
const dates = isRangeValue(value) ? value.start && value.end ? eachDay(value.start, value.end) : [] : [value];
|
|
1363
|
+
if (!dates.length) return false;
|
|
1364
|
+
return dates.every((date) => isWithin(date, min, max) && !isDateDisabled?.(date));
|
|
1365
|
+
}
|
|
1366
|
+
function eachDay(start, end) {
|
|
1367
|
+
const ordered = orderRange(start, end);
|
|
1368
|
+
const days = [];
|
|
1369
|
+
for (let date = ordered.start; compare(date, ordered.end) <= 0; date = addDays(date, 1)) days.push(date);
|
|
1370
|
+
return days;
|
|
1371
|
+
}
|
|
1372
|
+
function moveFocusDate(date, key, opts) {
|
|
1373
|
+
const dir = opts.rtl ? -1 : 1;
|
|
1374
|
+
switch (key) {
|
|
1375
|
+
case "ArrowLeft":
|
|
1376
|
+
return addDays(date, -dir);
|
|
1377
|
+
case "ArrowRight":
|
|
1378
|
+
return addDays(date, dir);
|
|
1379
|
+
case "ArrowUp":
|
|
1380
|
+
return addDays(date, -7);
|
|
1381
|
+
case "ArrowDown":
|
|
1382
|
+
return addDays(date, 7);
|
|
1383
|
+
case "Home":
|
|
1384
|
+
return startOfWeek(date, opts.weekStartsOn);
|
|
1385
|
+
case "End":
|
|
1386
|
+
return endOfWeek(date, opts.weekStartsOn);
|
|
1387
|
+
case "PageUp":
|
|
1388
|
+
return opts.shift ? addYears(date, -1) : addMonths(date, -1);
|
|
1389
|
+
case "PageDown":
|
|
1390
|
+
return opts.shift ? addYears(date, 1) : addMonths(date, 1);
|
|
1391
|
+
default:
|
|
1392
|
+
return null;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
function documentLocale2() {
|
|
1396
|
+
if (typeof document !== "undefined" && document.documentElement.lang) return document.documentElement.lang;
|
|
1397
|
+
return "en-US";
|
|
1398
|
+
}
|
|
1399
|
+
function useCalendarGrid({
|
|
1400
|
+
focused,
|
|
1401
|
+
onFocusedChange,
|
|
1402
|
+
month,
|
|
1403
|
+
onMonthChange,
|
|
1404
|
+
months,
|
|
1405
|
+
weekStartsOn,
|
|
1406
|
+
rtl,
|
|
1407
|
+
min,
|
|
1408
|
+
max,
|
|
1409
|
+
onClear,
|
|
1410
|
+
onPick
|
|
1411
|
+
}) {
|
|
1412
|
+
const pendingFocus = React10.useRef(false);
|
|
1413
|
+
const onDayKeyDown = (event) => {
|
|
1414
|
+
if (!focused) return;
|
|
1415
|
+
if (event.key === "Delete" || event.key === "Backspace") {
|
|
1416
|
+
event.preventDefault();
|
|
1417
|
+
onClear?.();
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
1421
|
+
event.preventDefault();
|
|
1422
|
+
onPick?.(focused);
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
const raw = moveFocusDate(focused, event.key, { weekStartsOn, rtl, shift: event.shiftKey });
|
|
1426
|
+
if (!raw) return;
|
|
1427
|
+
event.preventDefault();
|
|
1428
|
+
const next = clampDate(raw, min, max);
|
|
1429
|
+
if (next === focused) return;
|
|
1430
|
+
pendingFocus.current = true;
|
|
1431
|
+
onFocusedChange(next);
|
|
1432
|
+
const first = startOfMonth(month);
|
|
1433
|
+
const last = endOfMonth(addMonths(first, months - 1));
|
|
1434
|
+
if (compare(next, first) >= 0 && compare(next, last) <= 0) return;
|
|
1435
|
+
const page = compare(next, last) > 0 ? startOfMonth(addMonths(next, -(months - 1))) : startOfMonth(next);
|
|
1436
|
+
if (page !== first) onMonthChange(page);
|
|
1437
|
+
};
|
|
1438
|
+
return { onDayKeyDown, pendingFocus };
|
|
1439
|
+
}
|
|
1440
|
+
function useChooserGrid({ columns, count, index, onIndexChange, onPick, onBack, rtl }) {
|
|
1441
|
+
const onKeyDown = (event) => {
|
|
1442
|
+
if (event.key === "Escape") {
|
|
1443
|
+
event.preventDefault();
|
|
1444
|
+
event.stopPropagation();
|
|
1445
|
+
onBack?.();
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
const dir = rtl ? -1 : 1;
|
|
1449
|
+
let next = index;
|
|
1450
|
+
switch (event.key) {
|
|
1451
|
+
case "ArrowRight":
|
|
1452
|
+
next = index + dir;
|
|
1453
|
+
break;
|
|
1454
|
+
case "ArrowLeft":
|
|
1455
|
+
next = index - dir;
|
|
1456
|
+
break;
|
|
1457
|
+
case "ArrowDown":
|
|
1458
|
+
next = index + columns;
|
|
1459
|
+
break;
|
|
1460
|
+
case "ArrowUp":
|
|
1461
|
+
next = index - columns;
|
|
1462
|
+
break;
|
|
1463
|
+
case "Home":
|
|
1464
|
+
next = 0;
|
|
1465
|
+
break;
|
|
1466
|
+
case "End":
|
|
1467
|
+
next = count - 1;
|
|
1468
|
+
break;
|
|
1469
|
+
case "Enter":
|
|
1470
|
+
case " ":
|
|
1471
|
+
event.preventDefault();
|
|
1472
|
+
if (event.currentTarget.getAttribute("aria-disabled") === "true") return;
|
|
1473
|
+
onPick(index);
|
|
1474
|
+
return;
|
|
1475
|
+
default:
|
|
1476
|
+
return;
|
|
1477
|
+
}
|
|
1478
|
+
event.preventDefault();
|
|
1479
|
+
if (next < 0 || next >= count) return;
|
|
1480
|
+
onIndexChange(next);
|
|
1481
|
+
};
|
|
1482
|
+
return { onKeyDown };
|
|
1483
|
+
}
|
|
1484
|
+
function isRange(value) {
|
|
1485
|
+
return typeof value === "object" && value !== null && "start" in value;
|
|
1486
|
+
}
|
|
1487
|
+
function emptyFor(mode) {
|
|
1488
|
+
return mode === "range" ? { start: null, end: null } : null;
|
|
1489
|
+
}
|
|
1490
|
+
var Calendar = React10.forwardRef(function Calendar2({
|
|
1491
|
+
mode = "single",
|
|
1492
|
+
value,
|
|
1493
|
+
defaultValue,
|
|
1494
|
+
onValueChange,
|
|
1495
|
+
month,
|
|
1496
|
+
defaultMonth,
|
|
1497
|
+
onMonthChange,
|
|
1498
|
+
months = 1,
|
|
1499
|
+
min,
|
|
1500
|
+
max,
|
|
1501
|
+
isDateDisabled,
|
|
1502
|
+
minLength,
|
|
1503
|
+
maxLength,
|
|
1504
|
+
weekStartsOn,
|
|
1505
|
+
locale,
|
|
1506
|
+
showOutsideDays = true,
|
|
1507
|
+
showWeekNumbers = false,
|
|
1508
|
+
focusSelected = false,
|
|
1509
|
+
chooserBackRef,
|
|
1510
|
+
onAnnounce,
|
|
1511
|
+
className
|
|
1512
|
+
}, ref) {
|
|
1513
|
+
const rootId = React10.useId();
|
|
1514
|
+
const localeName = locale || documentLocale2();
|
|
1515
|
+
const weekStart = weekStartsOn ?? defaultWeekStartsOn(localeName);
|
|
1516
|
+
const rootRef = React10.useRef(null);
|
|
1517
|
+
React10.useImperativeHandle(ref, () => rootRef.current);
|
|
1518
|
+
const [dir, setDir] = React10.useState(false);
|
|
1519
|
+
React10.useEffect(() => {
|
|
1520
|
+
const el = rootRef.current;
|
|
1521
|
+
if (!el) return;
|
|
1522
|
+
const update = () => setDir(getComputedStyle(el).direction === "rtl");
|
|
1523
|
+
update();
|
|
1524
|
+
}, []);
|
|
1525
|
+
const [uncontrolledValue, setUncontrolledValue] = React10.useState(defaultValue ?? emptyFor(mode));
|
|
1526
|
+
const selected = value !== void 0 ? value : uncontrolledValue;
|
|
1527
|
+
const range = isRange(selected) ? selected : null;
|
|
1528
|
+
const single = !isRange(selected) ? selected : null;
|
|
1529
|
+
const setSelected = (next) => {
|
|
1530
|
+
if (value === void 0) setUncontrolledValue(next);
|
|
1531
|
+
onValueChange?.(next);
|
|
1532
|
+
};
|
|
1533
|
+
const todayValue = todayDate();
|
|
1534
|
+
const selectedSeed = range?.start || single || null;
|
|
1535
|
+
function clampMonth(seed) {
|
|
1536
|
+
let first = startOfMonth(seed);
|
|
1537
|
+
if (max && compare(first, max) > 0) first = startOfMonth(max);
|
|
1538
|
+
if (min && compare(endOfMonth(addMonths(first, months - 1)), min) < 0) first = startOfMonth(min);
|
|
1539
|
+
if (max && compare(first, max) > 0) first = startOfMonth(max);
|
|
1540
|
+
return first;
|
|
1541
|
+
}
|
|
1542
|
+
function enabledOnOpen(date) {
|
|
1543
|
+
return isWithin(date, min, max) && !isDateDisabled?.(date);
|
|
1544
|
+
}
|
|
1545
|
+
const initialMonth = month != null ? startOfMonth(month) : selectedSeed ? startOfMonth(selectedSeed) : clampMonth(startOfMonth(defaultMonth ?? todayValue));
|
|
1546
|
+
const [uncontrolledMonth, setUncontrolledMonth] = React10.useState(startOfMonth(initialMonth));
|
|
1547
|
+
const rawVisible = startOfMonth(month ?? uncontrolledMonth);
|
|
1548
|
+
const visible = selectedSeed ? rawVisible : clampMonth(rawVisible);
|
|
1549
|
+
const setVisible = (next) => {
|
|
1550
|
+
const first = startOfMonth(next);
|
|
1551
|
+
if (month === void 0) setUncontrolledMonth(first);
|
|
1552
|
+
onMonthChange?.(first);
|
|
1553
|
+
const count = months;
|
|
1554
|
+
const names = Array.from({ length: count }, (_, i) => formatMonth(addMonths(first, i), localeName));
|
|
1555
|
+
onAnnounce?.(names.join(", "));
|
|
1556
|
+
};
|
|
1557
|
+
const [focused, setFocused] = React10.useState(range?.start || single || null);
|
|
1558
|
+
const [preview, setPreview] = React10.useState(null);
|
|
1559
|
+
const [view, setView] = React10.useState("days");
|
|
1560
|
+
const [yearCursor, setYearCursor] = React10.useState(() => parseDate(visible).y);
|
|
1561
|
+
const [chooserMonth, setChooserMonth] = React10.useState(0);
|
|
1562
|
+
const dayRefs = React10.useRef(/* @__PURE__ */ new Map());
|
|
1563
|
+
const titleRefs = React10.useRef([]);
|
|
1564
|
+
const titleIndex = React10.useRef(0);
|
|
1565
|
+
const restoreTitle = React10.useRef(false);
|
|
1566
|
+
const { onDayKeyDown, pendingFocus } = useCalendarGrid({
|
|
1567
|
+
focused,
|
|
1568
|
+
onFocusedChange: (date) => {
|
|
1569
|
+
setFocused(date);
|
|
1570
|
+
if (range?.start && !range.end) setPreview(date);
|
|
1571
|
+
},
|
|
1572
|
+
month: visible,
|
|
1573
|
+
onMonthChange: setVisible,
|
|
1574
|
+
months,
|
|
1575
|
+
weekStartsOn: weekStart,
|
|
1576
|
+
rtl: dir,
|
|
1577
|
+
min,
|
|
1578
|
+
max,
|
|
1579
|
+
onPick: (date) => pick(date),
|
|
1580
|
+
onClear: () => {
|
|
1581
|
+
setPreview(null);
|
|
1582
|
+
setSelected(emptyFor(mode));
|
|
1583
|
+
onAnnounce?.("Selection cleared.");
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1586
|
+
const opened = React10.useRef(false);
|
|
1587
|
+
const focusOnOpen = React10.useRef(null);
|
|
1588
|
+
React10.useLayoutEffect(() => {
|
|
1589
|
+
if (!focusSelected) {
|
|
1590
|
+
opened.current = false;
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1593
|
+
if (opened.current) return;
|
|
1594
|
+
opened.current = true;
|
|
1595
|
+
const target = selectedSeed ? selectedSeed : enabledOnOpen(todayValue) ? todayValue : firstEnabledDay(clampMonth(max && compare(todayValue, max) > 0 ? max : min ?? todayValue));
|
|
1596
|
+
if (!target) return;
|
|
1597
|
+
setFocused(target);
|
|
1598
|
+
const first = startOfMonth(visible);
|
|
1599
|
+
const last = endOfMonth(addMonths(first, months - 1));
|
|
1600
|
+
if (compare(target, first) < 0 || compare(target, last) > 0) setVisible(startOfMonth(target));
|
|
1601
|
+
focusOnOpen.current = target;
|
|
1602
|
+
}, [focusSelected]);
|
|
1603
|
+
React10.useLayoutEffect(() => {
|
|
1604
|
+
const target = focusOnOpen.current;
|
|
1605
|
+
if (!target) return;
|
|
1606
|
+
const button = dayRefs.current.get(target);
|
|
1607
|
+
if (!button) return;
|
|
1608
|
+
focusOnOpen.current = null;
|
|
1609
|
+
button.focus();
|
|
1610
|
+
});
|
|
1611
|
+
function firstEnabledDay(from) {
|
|
1612
|
+
for (let i = 0; i < months; i++) {
|
|
1613
|
+
for (const cell of monthCells(addMonths(from, i), weekStart)) {
|
|
1614
|
+
if (!cell.outside && enabledOnOpen(cell.date)) return cell.date;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
return null;
|
|
1618
|
+
}
|
|
1619
|
+
const backToDays = React10.useCallback(() => {
|
|
1620
|
+
setView("days");
|
|
1621
|
+
restoreTitle.current = true;
|
|
1622
|
+
}, []);
|
|
1623
|
+
React10.useEffect(() => {
|
|
1624
|
+
if (!chooserBackRef) return;
|
|
1625
|
+
chooserBackRef.current = view === "days" ? null : backToDays;
|
|
1626
|
+
return () => {
|
|
1627
|
+
chooserBackRef.current = null;
|
|
1628
|
+
};
|
|
1629
|
+
}, [view, chooserBackRef, backToDays]);
|
|
1630
|
+
React10.useLayoutEffect(() => {
|
|
1631
|
+
if (view !== "days" || !restoreTitle.current) return;
|
|
1632
|
+
restoreTitle.current = false;
|
|
1633
|
+
titleRefs.current[titleIndex.current]?.focus();
|
|
1634
|
+
}, [view]);
|
|
1635
|
+
React10.useEffect(() => {
|
|
1636
|
+
if (!pendingFocus.current || !focused) return;
|
|
1637
|
+
pendingFocus.current = false;
|
|
1638
|
+
dayRefs.current.get(focused)?.focus();
|
|
1639
|
+
}, [focused, visible, pendingFocus]);
|
|
1640
|
+
const pendingStart = range?.start && !range.end ? range.start : null;
|
|
1641
|
+
const pick = (date) => {
|
|
1642
|
+
if (dayBlocked(date) && !(pendingStart && date === pendingStart && (minLength == null || minLength <= 1))) return;
|
|
1643
|
+
if (mode === "single") {
|
|
1644
|
+
setSelected(date);
|
|
1645
|
+
setFocused(date);
|
|
1646
|
+
onAnnounce?.(formatFull(date, localeName));
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
if (!range?.start || range.end) {
|
|
1650
|
+
setSelected({ start: date, end: null });
|
|
1651
|
+
setPreview(null);
|
|
1652
|
+
setFocused(date);
|
|
1653
|
+
onAnnounce?.(`Start date ${formatFull(date, localeName)}. Pick an end date.`);
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
if (date === range.start) {
|
|
1657
|
+
setSelected({ start: date, end: date });
|
|
1658
|
+
setPreview(null);
|
|
1659
|
+
onAnnounce?.(`${formatFull(date, localeName)}, one day.`);
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
const ordered = orderRange(range.start, date);
|
|
1663
|
+
setSelected(ordered);
|
|
1664
|
+
setPreview(null);
|
|
1665
|
+
onAnnounce?.(`${formatFull(ordered.start, localeName)} to ${formatFull(ordered.end, localeName)}, ${inclusiveDays(ordered.start, ordered.end)} days.`);
|
|
1666
|
+
};
|
|
1667
|
+
function dayBlocked(date) {
|
|
1668
|
+
if (!isWithin(date, min, max)) return true;
|
|
1669
|
+
if (isDateDisabled?.(date)) return true;
|
|
1670
|
+
if (pendingStart && lengthBlockReason(pendingStart, date, minLength, maxLength)) return true;
|
|
1671
|
+
return false;
|
|
1672
|
+
}
|
|
1673
|
+
function blockReason(date) {
|
|
1674
|
+
if (min && compare(date, min) < 0) return `Before ${formatFull(min, localeName)}`;
|
|
1675
|
+
if (max && compare(date, max) > 0) return `After ${formatFull(max, localeName)}`;
|
|
1676
|
+
if (isDateDisabled?.(date)) return "Unavailable";
|
|
1677
|
+
if (pendingStart) return lengthBlockReason(pendingStart, date, minLength, maxLength);
|
|
1678
|
+
return null;
|
|
1679
|
+
}
|
|
1680
|
+
const labels = weekdayLabels(localeName, weekStart);
|
|
1681
|
+
const monthList = Array.from({ length: months }, (_, i) => addMonths(visible, i));
|
|
1682
|
+
const shown = new Set(
|
|
1683
|
+
monthList.flatMap((item) => monthCells(item, weekStart).filter((cell) => !cell.outside).map((cell) => cell.date))
|
|
1684
|
+
);
|
|
1685
|
+
const canPage = (delta) => {
|
|
1686
|
+
const next = addMonths(visible, delta);
|
|
1687
|
+
const edge = delta < 0 ? endOfMonth(next) : startOfMonth(addMonths(next, months - 1));
|
|
1688
|
+
if (delta < 0 && min && compare(edge, min) < 0) return false;
|
|
1689
|
+
if (delta > 0 && max && compare(startOfMonth(next), max) > 0) return false;
|
|
1690
|
+
return true;
|
|
1691
|
+
};
|
|
1692
|
+
const pageBy = (delta) => {
|
|
1693
|
+
if (!canPage(delta)) return;
|
|
1694
|
+
setVisible(addMonths(visible, delta));
|
|
1695
|
+
const base = focused ?? todayValue;
|
|
1696
|
+
setFocused(clampDate(addMonths(base, delta), min, max));
|
|
1697
|
+
pendingFocus.current = true;
|
|
1698
|
+
};
|
|
1699
|
+
const visibleDays = monthList.flatMap(
|
|
1700
|
+
(item) => monthCells(item, weekStart).filter((cell) => !(cell.outside && !showOutsideDays || cell.outside && shown.has(cell.date))).map((cell) => cell.date)
|
|
1701
|
+
);
|
|
1702
|
+
const visibleSet = new Set(visibleDays);
|
|
1703
|
+
const inView = (date) => Boolean(date && visibleSet.has(date));
|
|
1704
|
+
const tabDate = (inView(focused) ? focused : null) || (inView(single) ? single : null) || (inView(range?.start) ? range.start : null) || (inView(range?.end) ? range.end : null) || (inView(todayValue) ? todayValue : null) || visibleDays.find((date) => !dayBlocked(date)) || visibleDays[0] || null;
|
|
1705
|
+
const previewEnd = pendingStart && preview && preview !== pendingStart ? preview : null;
|
|
1706
|
+
const band = previewEnd ? orderRange(pendingStart, previewEnd) : range?.start && range.end ? orderRange(range.start, range.end) : null;
|
|
1707
|
+
const chevron = (which) => {
|
|
1708
|
+
const back = which === "prev";
|
|
1709
|
+
const pointLeft = dir ? !back : back;
|
|
1710
|
+
return /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: pointLeft ? "chevron_left" : "chevron_right" });
|
|
1711
|
+
};
|
|
1712
|
+
return /* @__PURE__ */ jsx("div", { ref: rootRef, className: cn("Calendar", className), "data-months": months, children: view === "days" ? /* @__PURE__ */ jsxs("div", { className: "DatePickerMonths", children: [
|
|
1713
|
+
/* @__PURE__ */ jsxs("div", { className: "DatePickerNav", children: [
|
|
1714
|
+
/* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", "aria-label": "Previous month", disabled: !canPage(-1), onClick: () => pageBy(-1), children: chevron("prev") }),
|
|
1715
|
+
monthList.map((item, index) => /* @__PURE__ */ jsx(
|
|
1716
|
+
"button",
|
|
1717
|
+
{
|
|
1718
|
+
type: "button",
|
|
1719
|
+
className: "DatePickerMonthTitle",
|
|
1720
|
+
id: `${rootId}-month-${index}`,
|
|
1721
|
+
"data-index": index,
|
|
1722
|
+
ref: (node) => {
|
|
1723
|
+
titleRefs.current[index] = node;
|
|
1724
|
+
},
|
|
1725
|
+
onClick: () => {
|
|
1726
|
+
titleIndex.current = index;
|
|
1727
|
+
setChooserMonth(parseDate(item).m - 1);
|
|
1728
|
+
setYearCursor(parseDate(item).y);
|
|
1729
|
+
setView("months");
|
|
1730
|
+
},
|
|
1731
|
+
children: formatMonth(item, localeName)
|
|
1732
|
+
},
|
|
1733
|
+
item
|
|
1734
|
+
)),
|
|
1735
|
+
/* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", "aria-label": "Next month", disabled: !canPage(1), onClick: () => pageBy(1), children: chevron("next") })
|
|
1736
|
+
] }),
|
|
1737
|
+
monthList.map((item, index) => /* @__PURE__ */ jsx(
|
|
1738
|
+
Month,
|
|
1739
|
+
{
|
|
1740
|
+
month: item,
|
|
1741
|
+
titleId: `${rootId}-month-${index}`,
|
|
1742
|
+
monthIndex: index,
|
|
1743
|
+
localeName,
|
|
1744
|
+
labels,
|
|
1745
|
+
showWeekNumbers,
|
|
1746
|
+
showOutsideDays,
|
|
1747
|
+
shown,
|
|
1748
|
+
weekStart,
|
|
1749
|
+
mode,
|
|
1750
|
+
focused,
|
|
1751
|
+
tabDate,
|
|
1752
|
+
single,
|
|
1753
|
+
rangeStart: range?.start ?? null,
|
|
1754
|
+
rangeEnd: range?.end ?? null,
|
|
1755
|
+
band,
|
|
1756
|
+
previewing: Boolean(previewEnd),
|
|
1757
|
+
todayValue,
|
|
1758
|
+
dayBlocked,
|
|
1759
|
+
blockReason,
|
|
1760
|
+
onPick: pick,
|
|
1761
|
+
onFocusDay: setFocused,
|
|
1762
|
+
onPreview: (date) => pendingStart && setPreview(date),
|
|
1763
|
+
onPreviewEnd: () => setPreview(null),
|
|
1764
|
+
onKeyDown: onDayKeyDown,
|
|
1765
|
+
dayRefs
|
|
1766
|
+
},
|
|
1767
|
+
item
|
|
1768
|
+
))
|
|
1769
|
+
] }) : view === "months" ? /* @__PURE__ */ jsx(
|
|
1770
|
+
MonthChooser,
|
|
1771
|
+
{
|
|
1772
|
+
year: yearCursor,
|
|
1773
|
+
localeName,
|
|
1774
|
+
min,
|
|
1775
|
+
max,
|
|
1776
|
+
rtl: dir,
|
|
1777
|
+
onYear: (delta) => setYearCursor((y) => y + delta),
|
|
1778
|
+
initialIndex: chooserMonth,
|
|
1779
|
+
onPick: (monthIndex) => {
|
|
1780
|
+
setVisible(formatDate(yearCursor, monthIndex + 1, 1));
|
|
1781
|
+
backToDays();
|
|
1782
|
+
},
|
|
1783
|
+
onBack: backToDays,
|
|
1784
|
+
onOpenYears: () => setView("years")
|
|
1785
|
+
}
|
|
1786
|
+
) : /* @__PURE__ */ jsx(
|
|
1787
|
+
YearChooser,
|
|
1788
|
+
{
|
|
1789
|
+
year: yearCursor,
|
|
1790
|
+
min,
|
|
1791
|
+
max,
|
|
1792
|
+
rtl: dir,
|
|
1793
|
+
onShift: (delta) => setYearCursor((y) => y + delta),
|
|
1794
|
+
onPick: (y) => {
|
|
1795
|
+
setYearCursor(y);
|
|
1796
|
+
setView("months");
|
|
1797
|
+
},
|
|
1798
|
+
onBack: backToDays
|
|
1799
|
+
}
|
|
1800
|
+
) });
|
|
1801
|
+
});
|
|
1802
|
+
function Month(props) {
|
|
1803
|
+
const cells = monthCells(props.month, props.weekStart);
|
|
1804
|
+
const weeks = [];
|
|
1805
|
+
for (let i = 0; i < cells.length; i += 7) weeks.push(cells.slice(i, i + 7));
|
|
1806
|
+
return /* @__PURE__ */ jsx("section", { className: "DatePickerMonth", "data-index": props.monthIndex, children: /* @__PURE__ */ jsxs(
|
|
1807
|
+
"div",
|
|
1808
|
+
{
|
|
1809
|
+
role: "grid",
|
|
1810
|
+
tabIndex: -1,
|
|
1811
|
+
"aria-labelledby": props.titleId,
|
|
1812
|
+
"aria-multiselectable": props.mode === "range" ? true : void 0,
|
|
1813
|
+
className: "CalendarGrid",
|
|
1814
|
+
onMouseLeave: props.onPreviewEnd,
|
|
1815
|
+
children: [
|
|
1816
|
+
/* @__PURE__ */ jsxs("div", { role: "row", className: "CalendarWeek", children: [
|
|
1817
|
+
props.showWeekNumbers ? /* @__PURE__ */ jsx("span", { role: "columnheader", className: "CalendarWeekNumber" }) : null,
|
|
1818
|
+
props.labels.map((label) => /* @__PURE__ */ jsx("span", { role: "columnheader", className: "CalendarWeekday", children: /* @__PURE__ */ jsx("abbr", { title: label.long, children: label.narrow }) }, label.long))
|
|
1819
|
+
] }),
|
|
1820
|
+
weeks.map((week) => /* @__PURE__ */ jsxs("div", { role: "row", className: "CalendarWeek", children: [
|
|
1821
|
+
props.showWeekNumbers ? /* @__PURE__ */ jsx("span", { role: "rowheader", className: "CalendarWeekNumber", children: isoWeek(week.find((cell) => !cell.outside)?.date ?? week[3].date) }) : null,
|
|
1822
|
+
week.map((cell) => {
|
|
1823
|
+
const hiddenDuplicate = cell.outside && props.shown.has(cell.date);
|
|
1824
|
+
if (cell.outside && !props.showOutsideDays || hiddenDuplicate) {
|
|
1825
|
+
return /* @__PURE__ */ jsx("span", { role: "gridcell", className: "CalendarDay CalendarDay--empty" }, cell.date);
|
|
1826
|
+
}
|
|
1827
|
+
const blocked = props.dayBlocked(cell.date);
|
|
1828
|
+
const reason = blocked ? props.blockReason(cell.date) : null;
|
|
1829
|
+
const selectedSingle = props.single === cell.date;
|
|
1830
|
+
const isStart = props.rangeStart === cell.date;
|
|
1831
|
+
const isEnd = props.rangeEnd === cell.date;
|
|
1832
|
+
const inBand = props.band != null && compare(cell.date, props.band.start) > 0 && compare(cell.date, props.band.end) < 0;
|
|
1833
|
+
const bandStart = props.band?.start === cell.date;
|
|
1834
|
+
const bandEnd = props.band?.end === cell.date && props.band.start !== props.band.end;
|
|
1835
|
+
const marked = selectedSingle || isStart || isEnd || bandStart || bandEnd;
|
|
1836
|
+
const ariaSelected = selectedSingle || isStart || isEnd;
|
|
1837
|
+
const parts = [
|
|
1838
|
+
formatFull(cell.date, props.localeName),
|
|
1839
|
+
cell.date === props.todayValue ? "today" : "",
|
|
1840
|
+
selectedSingle ? "selected" : "",
|
|
1841
|
+
isStart ? "range start" : "",
|
|
1842
|
+
isEnd ? "range end" : "",
|
|
1843
|
+
inBand ? "in range" : "",
|
|
1844
|
+
reason ?? ""
|
|
1845
|
+
].filter(Boolean);
|
|
1846
|
+
return /* @__PURE__ */ jsx(
|
|
1847
|
+
"button",
|
|
1848
|
+
{
|
|
1849
|
+
type: "button",
|
|
1850
|
+
role: "gridcell",
|
|
1851
|
+
ref: (node) => {
|
|
1852
|
+
if (node) props.dayRefs.current.set(cell.date, node);
|
|
1853
|
+
else props.dayRefs.current.delete(cell.date);
|
|
1854
|
+
},
|
|
1855
|
+
className: "CalendarDay",
|
|
1856
|
+
tabIndex: cell.date === props.tabDate ? 0 : -1,
|
|
1857
|
+
"aria-selected": ariaSelected || void 0,
|
|
1858
|
+
"aria-current": cell.date === props.todayValue ? "date" : void 0,
|
|
1859
|
+
"aria-disabled": blocked || void 0,
|
|
1860
|
+
"aria-label": parts.join(", "),
|
|
1861
|
+
"data-selected": marked || void 0,
|
|
1862
|
+
"data-range-start": bandStart ? "" : void 0,
|
|
1863
|
+
"data-range-end": bandEnd ? "" : void 0,
|
|
1864
|
+
"data-in-range": inBand ? "" : void 0,
|
|
1865
|
+
"data-preview": props.previewing && props.band && compare(cell.date, props.band.start) >= 0 && compare(cell.date, props.band.end) <= 0 ? "" : void 0,
|
|
1866
|
+
"data-today": cell.date === props.todayValue ? "" : void 0,
|
|
1867
|
+
"data-outside-month": cell.outside ? "" : void 0,
|
|
1868
|
+
"data-disabled": blocked ? "" : void 0,
|
|
1869
|
+
"data-focused": props.focused === cell.date ? "" : void 0,
|
|
1870
|
+
onClick: () => props.onPick(cell.date),
|
|
1871
|
+
onMouseEnter: () => props.onPreview(cell.date),
|
|
1872
|
+
onFocus: () => {
|
|
1873
|
+
props.onFocusDay(cell.date);
|
|
1874
|
+
props.onPreview(cell.date);
|
|
1875
|
+
},
|
|
1876
|
+
onKeyDown: props.onKeyDown,
|
|
1877
|
+
children: /* @__PURE__ */ jsx("span", { className: "CalendarDayNumber", children: parseDate(cell.date).d })
|
|
1878
|
+
},
|
|
1879
|
+
cell.date
|
|
1880
|
+
);
|
|
1881
|
+
})
|
|
1882
|
+
] }, week[0].date))
|
|
1883
|
+
]
|
|
1884
|
+
}
|
|
1885
|
+
) });
|
|
1886
|
+
}
|
|
1887
|
+
function MonthChooser({
|
|
1888
|
+
year,
|
|
1889
|
+
localeName,
|
|
1890
|
+
min,
|
|
1891
|
+
max,
|
|
1892
|
+
rtl,
|
|
1893
|
+
initialIndex,
|
|
1894
|
+
onYear,
|
|
1895
|
+
onPick,
|
|
1896
|
+
onBack,
|
|
1897
|
+
onOpenYears
|
|
1898
|
+
}) {
|
|
1899
|
+
const [index, setIndex] = React10.useState(initialIndex);
|
|
1900
|
+
const cells = React10.useRef([]);
|
|
1901
|
+
const { onKeyDown } = useChooserGrid({
|
|
1902
|
+
columns: 3,
|
|
1903
|
+
count: 12,
|
|
1904
|
+
index,
|
|
1905
|
+
onIndexChange: setIndex,
|
|
1906
|
+
onPick,
|
|
1907
|
+
onBack,
|
|
1908
|
+
rtl
|
|
1909
|
+
});
|
|
1910
|
+
React10.useLayoutEffect(() => {
|
|
1911
|
+
cells.current[index]?.focus();
|
|
1912
|
+
}, [index]);
|
|
1913
|
+
const minYear = min ? parseDate(min).y : null;
|
|
1914
|
+
const maxYear = max ? parseDate(max).y : null;
|
|
1915
|
+
return /* @__PURE__ */ jsxs("div", { className: "CalendarChooser", children: [
|
|
1916
|
+
/* @__PURE__ */ jsxs("div", { className: "DatePickerMonthBar", children: [
|
|
1917
|
+
/* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", "aria-label": "Previous year", disabled: minYear != null && year <= minYear, onClick: () => onYear(-1), children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: rtl ? "chevron_right" : "chevron_left" }) }),
|
|
1918
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "DatePickerMonthTitle", onClick: onOpenYears, children: year }),
|
|
1919
|
+
/* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", "aria-label": "Next year", disabled: maxYear != null && year >= maxYear, onClick: () => onYear(1), children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: rtl ? "chevron_left" : "chevron_right" }) })
|
|
1920
|
+
] }),
|
|
1921
|
+
/* @__PURE__ */ jsx("div", { role: "grid", tabIndex: -1, className: "CalendarChooserGrid", "aria-label": `${year} months`, children: Array.from({ length: 12 }, (_, i) => {
|
|
1922
|
+
const date = formatDate(year, i + 1, 1);
|
|
1923
|
+
const disabled = min != null && compare(endOfMonth(date), min) < 0 || max != null && compare(date, max) > 0;
|
|
1924
|
+
return /* @__PURE__ */ jsx(
|
|
1925
|
+
"button",
|
|
1926
|
+
{
|
|
1927
|
+
type: "button",
|
|
1928
|
+
role: "gridcell",
|
|
1929
|
+
className: "CalendarChoice",
|
|
1930
|
+
ref: (node) => {
|
|
1931
|
+
cells.current[i] = node;
|
|
1932
|
+
},
|
|
1933
|
+
tabIndex: index === i ? 0 : -1,
|
|
1934
|
+
"aria-disabled": disabled || void 0,
|
|
1935
|
+
onClick: () => !disabled && onPick(i),
|
|
1936
|
+
onKeyDown,
|
|
1937
|
+
onFocus: () => setIndex(i),
|
|
1938
|
+
children: formatMonthShort(date, localeName)
|
|
1939
|
+
},
|
|
1940
|
+
date
|
|
1941
|
+
);
|
|
1942
|
+
}) })
|
|
1943
|
+
] });
|
|
1944
|
+
}
|
|
1945
|
+
function YearChooser({
|
|
1946
|
+
year,
|
|
1947
|
+
min,
|
|
1948
|
+
max,
|
|
1949
|
+
rtl,
|
|
1950
|
+
onShift,
|
|
1951
|
+
onPick,
|
|
1952
|
+
onBack
|
|
1953
|
+
}) {
|
|
1954
|
+
const minYear = min ? parseDate(min).y : null;
|
|
1955
|
+
const maxYear = max ? parseDate(max).y : null;
|
|
1956
|
+
const pageStart = Math.floor(year / 12) * 12;
|
|
1957
|
+
const pageEnd = pageStart + 11;
|
|
1958
|
+
const from = minYear != null ? Math.max(pageStart, minYear) : pageStart;
|
|
1959
|
+
const to = maxYear != null ? Math.min(pageEnd, maxYear) : pageEnd;
|
|
1960
|
+
const years = to >= from ? Array.from({ length: to - from + 1 }, (_, i) => from + i) : [year];
|
|
1961
|
+
const cells = React10.useRef([]);
|
|
1962
|
+
const [index, setIndex] = React10.useState(Math.max(0, years.indexOf(year)));
|
|
1963
|
+
React10.useLayoutEffect(() => {
|
|
1964
|
+
cells.current[index]?.focus();
|
|
1965
|
+
}, [index]);
|
|
1966
|
+
const { onKeyDown } = useChooserGrid({
|
|
1967
|
+
columns: 3,
|
|
1968
|
+
count: years.length,
|
|
1969
|
+
index,
|
|
1970
|
+
onIndexChange: setIndex,
|
|
1971
|
+
onPick: (i) => onPick(years[i]),
|
|
1972
|
+
onBack,
|
|
1973
|
+
rtl
|
|
1974
|
+
});
|
|
1975
|
+
const prevDisabled = minYear != null && pageStart <= minYear;
|
|
1976
|
+
const nextDisabled = maxYear != null && pageEnd >= maxYear;
|
|
1977
|
+
return /* @__PURE__ */ jsxs("div", { className: "CalendarChooser", children: [
|
|
1978
|
+
/* @__PURE__ */ jsxs("div", { className: "DatePickerMonthBar", children: [
|
|
1979
|
+
/* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", "aria-label": "Previous years", disabled: prevDisabled, onClick: () => !prevDisabled && onShift(-12), children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: rtl ? "chevron_right" : "chevron_left" }) }),
|
|
1980
|
+
/* @__PURE__ */ jsxs("span", { className: "DatePickerMonthTitle", children: [
|
|
1981
|
+
years[0],
|
|
1982
|
+
" \u2013 ",
|
|
1983
|
+
years[years.length - 1]
|
|
1984
|
+
] }),
|
|
1985
|
+
/* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", "aria-label": "Next years", disabled: nextDisabled, onClick: () => !nextDisabled && onShift(12), children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: rtl ? "chevron_left" : "chevron_right" }) })
|
|
1986
|
+
] }),
|
|
1987
|
+
/* @__PURE__ */ jsx("div", { role: "grid", tabIndex: -1, className: "CalendarChooserGrid", "aria-label": "Years", children: years.map((y, i) => {
|
|
1988
|
+
const disabled = minYear != null && y < minYear || maxYear != null && y > maxYear;
|
|
1989
|
+
return /* @__PURE__ */ jsx(
|
|
1990
|
+
"button",
|
|
1991
|
+
{
|
|
1992
|
+
type: "button",
|
|
1993
|
+
role: "gridcell",
|
|
1994
|
+
className: "CalendarChoice",
|
|
1995
|
+
ref: (node) => {
|
|
1996
|
+
cells.current[i] = node;
|
|
1997
|
+
},
|
|
1998
|
+
tabIndex: index === i ? 0 : -1,
|
|
1999
|
+
"aria-disabled": disabled || void 0,
|
|
2000
|
+
onClick: () => !disabled && onPick(y),
|
|
2001
|
+
onKeyDown,
|
|
2002
|
+
onFocus: () => setIndex(i),
|
|
2003
|
+
children: y
|
|
2004
|
+
},
|
|
2005
|
+
y
|
|
2006
|
+
);
|
|
2007
|
+
}) })
|
|
2008
|
+
] });
|
|
2009
|
+
}
|
|
2010
|
+
function DatePresets({
|
|
2011
|
+
presets,
|
|
2012
|
+
value,
|
|
2013
|
+
onSelect,
|
|
2014
|
+
min,
|
|
2015
|
+
max,
|
|
2016
|
+
isDateDisabled,
|
|
2017
|
+
locale = "en-US",
|
|
2018
|
+
layout = "column",
|
|
2019
|
+
selectOnNavigate = false,
|
|
2020
|
+
label = "Quick selections",
|
|
2021
|
+
className
|
|
2022
|
+
}) {
|
|
2023
|
+
const todayValue = todayDate();
|
|
2024
|
+
const resolved = presets.map((preset) => ({ preset, value: resolvePreset(preset, todayValue) }));
|
|
2025
|
+
const enabled = resolved.map((item) => presetInBounds(item.value, min, max, isDateDisabled));
|
|
2026
|
+
const checked = resolved.findIndex((item, i) => enabled[i] && sameSelection(item.value, value));
|
|
2027
|
+
const [cursor, setCursor] = React10.useState(checked >= 0 ? checked : enabled.findIndex(Boolean));
|
|
2028
|
+
const buttons = React10.useRef([]);
|
|
2029
|
+
React10.useEffect(() => {
|
|
2030
|
+
if (checked >= 0) setCursor(checked);
|
|
2031
|
+
}, [checked]);
|
|
2032
|
+
const move = (next) => {
|
|
2033
|
+
setCursor(next);
|
|
2034
|
+
buttons.current[next]?.focus();
|
|
2035
|
+
const item = resolved[next];
|
|
2036
|
+
if (selectOnNavigate && item && enabled[next]) onSelect(item.preset, item.value);
|
|
2037
|
+
};
|
|
2038
|
+
const onKeyDown = (event) => {
|
|
2039
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
2040
|
+
event.preventDefault();
|
|
2041
|
+
const item = resolved[cursor];
|
|
2042
|
+
if (item && enabled[cursor]) onSelect(item.preset, item.value);
|
|
2043
|
+
return;
|
|
2044
|
+
}
|
|
2045
|
+
const dir = event.key === "ArrowDown" || event.key === "ArrowRight" ? 1 : event.key === "ArrowUp" || event.key === "ArrowLeft" ? -1 : 0;
|
|
2046
|
+
if (!dir && event.key !== "Home" && event.key !== "End") return;
|
|
2047
|
+
event.preventDefault();
|
|
2048
|
+
const step = (from, delta) => {
|
|
2049
|
+
for (let i = 1; i <= resolved.length; i++) {
|
|
2050
|
+
const next2 = (from + delta * i + resolved.length) % resolved.length;
|
|
2051
|
+
if (enabled[next2]) return next2;
|
|
2052
|
+
}
|
|
2053
|
+
return from;
|
|
2054
|
+
};
|
|
2055
|
+
const next = event.key === "Home" ? enabled.findIndex(Boolean) : event.key === "End" ? enabled.lastIndexOf(true) : step(cursor, dir);
|
|
2056
|
+
if (next >= 0) move(next);
|
|
2057
|
+
};
|
|
2058
|
+
return /* @__PURE__ */ jsx(
|
|
2059
|
+
"div",
|
|
2060
|
+
{
|
|
2061
|
+
role: "radiogroup",
|
|
2062
|
+
tabIndex: -1,
|
|
2063
|
+
"aria-label": label,
|
|
2064
|
+
className: cn("DatePickerPresets", className),
|
|
2065
|
+
"data-layout": layout,
|
|
2066
|
+
onKeyDown,
|
|
2067
|
+
children: resolved.map((item, index) => {
|
|
2068
|
+
const hint = item.preset.hint ?? (isRangeValue(item.value) && item.value.start && item.value.end ? formatRangeLabel(item.value.start, item.value.end, locale) : void 0);
|
|
2069
|
+
return /* @__PURE__ */ jsxs(
|
|
2070
|
+
"button",
|
|
2071
|
+
{
|
|
2072
|
+
type: "button",
|
|
2073
|
+
role: "radio",
|
|
2074
|
+
className: "DatePickerPreset",
|
|
2075
|
+
"aria-checked": index === checked,
|
|
2076
|
+
"aria-disabled": !enabled[index] || void 0,
|
|
2077
|
+
"data-checked": index === checked ? "" : void 0,
|
|
2078
|
+
"data-disabled": !enabled[index] ? "" : void 0,
|
|
2079
|
+
ref: (node) => {
|
|
2080
|
+
buttons.current[index] = node;
|
|
2081
|
+
},
|
|
2082
|
+
tabIndex: index === cursor ? 0 : -1,
|
|
2083
|
+
onClick: () => {
|
|
2084
|
+
if (!enabled[index]) return;
|
|
2085
|
+
setCursor(index);
|
|
2086
|
+
onSelect(item.preset, item.value);
|
|
2087
|
+
},
|
|
2088
|
+
children: [
|
|
2089
|
+
/* @__PURE__ */ jsx("span", { className: "DatePickerPresetLabel", children: item.preset.label }),
|
|
2090
|
+
hint ? /* @__PURE__ */ jsx("span", { className: "DatePickerPresetHint", children: hint }) : null
|
|
2091
|
+
]
|
|
2092
|
+
},
|
|
2093
|
+
item.preset.id
|
|
2094
|
+
);
|
|
2095
|
+
})
|
|
2096
|
+
}
|
|
2097
|
+
);
|
|
2098
|
+
}
|
|
2099
|
+
var EMPTY_RANGE = { start: null, end: null };
|
|
2100
|
+
function useNarrowViewport() {
|
|
2101
|
+
const [query, setQuery] = React10.useState("(max-width: 48em)");
|
|
2102
|
+
const [narrow, setNarrow] = React10.useState(false);
|
|
2103
|
+
React10.useEffect(() => {
|
|
2104
|
+
const raw = getComputedStyle(document.documentElement).getPropertyValue("--sl-breakpoint-md").trim();
|
|
2105
|
+
if (raw) setQuery(`(max-width: ${raw})`);
|
|
2106
|
+
}, []);
|
|
2107
|
+
React10.useEffect(() => {
|
|
2108
|
+
const media = window.matchMedia(query);
|
|
2109
|
+
const update = () => setNarrow(media.matches);
|
|
2110
|
+
update();
|
|
2111
|
+
media.addEventListener("change", update);
|
|
2112
|
+
return () => media.removeEventListener("change", update);
|
|
2113
|
+
}, [query]);
|
|
2114
|
+
return narrow;
|
|
2115
|
+
}
|
|
2116
|
+
function hasValue(mode, value) {
|
|
2117
|
+
if (mode === "range") return Boolean(isRangeValue(value) && (value.start || value.end));
|
|
2118
|
+
return Boolean(value);
|
|
2119
|
+
}
|
|
2120
|
+
function halfRange(value) {
|
|
2121
|
+
return isRangeValue(value) && Boolean(value.start) && !value.end;
|
|
2122
|
+
}
|
|
2123
|
+
var TWO_MONTHS_REM = 40;
|
|
2124
|
+
var ONE_MONTH_REM = 32;
|
|
2125
|
+
var COARSE_ONE_REM = 48;
|
|
2126
|
+
function monthFor(value, count) {
|
|
2127
|
+
const start = typeof value === "string" ? value : isRangeValue(value) ? value.start : null;
|
|
2128
|
+
if (!start || !parseDate(start)) return null;
|
|
2129
|
+
const end = isRangeValue(value) ? value.end : null;
|
|
2130
|
+
const startMonth = startOfMonth(start);
|
|
2131
|
+
if (!end || !parseDate(end)) return startMonth;
|
|
2132
|
+
const endMonth = startOfMonth(end);
|
|
2133
|
+
const startParts = parseDate(startMonth);
|
|
2134
|
+
const endParts = parseDate(endMonth);
|
|
2135
|
+
const span = (endParts.y - startParts.y) * 12 + (endParts.m - startParts.m);
|
|
2136
|
+
if (span < count) return startMonth;
|
|
2137
|
+
return addMonths(endMonth, -(count - 1));
|
|
2138
|
+
}
|
|
2139
|
+
function explicitBox(node) {
|
|
2140
|
+
const root = parseFloat(getComputedStyle(document.documentElement).fontSize) || 16;
|
|
2141
|
+
let current = node?.parentElement ?? null;
|
|
2142
|
+
while (current && current !== document.documentElement) {
|
|
2143
|
+
const raw = current.style.width || current.style.maxWidth || current.style.inlineSize || current.style.maxInlineSize;
|
|
2144
|
+
if (raw.endsWith("px")) return parseFloat(raw);
|
|
2145
|
+
if (raw.endsWith("rem")) return parseFloat(raw) * root;
|
|
2146
|
+
current = current.parentElement;
|
|
2147
|
+
}
|
|
2148
|
+
return void 0;
|
|
2149
|
+
}
|
|
2150
|
+
var DatePicker = React10.forwardRef(function DatePicker2(props, ref) {
|
|
2151
|
+
const {
|
|
2152
|
+
mode = "single",
|
|
2153
|
+
min,
|
|
2154
|
+
max,
|
|
2155
|
+
isDateDisabled,
|
|
2156
|
+
minLength,
|
|
2157
|
+
maxLength,
|
|
2158
|
+
presets = true,
|
|
2159
|
+
months = "auto",
|
|
2160
|
+
weekStartsOn,
|
|
2161
|
+
locale,
|
|
2162
|
+
showOutsideDays,
|
|
2163
|
+
showWeekNumbers,
|
|
2164
|
+
commit,
|
|
2165
|
+
clearable = true,
|
|
2166
|
+
placeholder,
|
|
2167
|
+
name,
|
|
2168
|
+
nameStart,
|
|
2169
|
+
nameEnd,
|
|
2170
|
+
required,
|
|
2171
|
+
disabled,
|
|
2172
|
+
readOnly,
|
|
2173
|
+
formatValue,
|
|
2174
|
+
open,
|
|
2175
|
+
defaultOpen,
|
|
2176
|
+
onOpenChange,
|
|
2177
|
+
popup = "auto",
|
|
2178
|
+
defaultMonth,
|
|
2179
|
+
label,
|
|
2180
|
+
id,
|
|
2181
|
+
className
|
|
2182
|
+
} = props;
|
|
2183
|
+
const localeName = locale || documentLocale2();
|
|
2184
|
+
const weekStart = weekStartsOn ?? defaultWeekStartsOn(localeName);
|
|
2185
|
+
const commitMode = commit ?? (mode === "range" ? "apply" : "immediate");
|
|
2186
|
+
const narrow = useNarrowViewport();
|
|
2187
|
+
const useDrawer = popup === "drawer" || popup === "auto" && narrow;
|
|
2188
|
+
const fieldState = useFieldRootContext();
|
|
2189
|
+
const { labelId } = useLabelableContext();
|
|
2190
|
+
const invalid = fieldState.state.valid === false;
|
|
2191
|
+
const controlled = props.value !== void 0;
|
|
2192
|
+
const [uncontrolled, setUncontrolled] = React10.useState(
|
|
2193
|
+
props.defaultValue ?? (mode === "range" ? EMPTY_RANGE : null)
|
|
2194
|
+
);
|
|
2195
|
+
const committed = (controlled ? props.value : uncontrolled) ?? (mode === "range" ? EMPTY_RANGE : null);
|
|
2196
|
+
const [draft, setDraft] = React10.useState(committed);
|
|
2197
|
+
const [uncontrolledOpen, setUncontrolledOpen] = React10.useState(defaultOpen ?? false);
|
|
2198
|
+
const isOpen = open ?? uncontrolledOpen;
|
|
2199
|
+
const [announcement, setAnnouncement] = React10.useState("");
|
|
2200
|
+
const fieldRef = React10.useRef(null);
|
|
2201
|
+
const panelRef = React10.useRef(null);
|
|
2202
|
+
const chooserBack = React10.useRef(null);
|
|
2203
|
+
const [panelWidth, setPanelWidth] = React10.useState(0);
|
|
2204
|
+
const [rtl, setRtl] = React10.useState(false);
|
|
2205
|
+
React10.useLayoutEffect(() => {
|
|
2206
|
+
const el = fieldRef.current;
|
|
2207
|
+
if (el) setRtl(getComputedStyle(el).direction === "rtl");
|
|
2208
|
+
}, [isOpen]);
|
|
2209
|
+
React10.useLayoutEffect(() => {
|
|
2210
|
+
if (!isOpen) return;
|
|
2211
|
+
let observer = null;
|
|
2212
|
+
let frame = 0;
|
|
2213
|
+
const attach = () => {
|
|
2214
|
+
const node = panelRef.current;
|
|
2215
|
+
if (!node) {
|
|
2216
|
+
frame = requestAnimationFrame(attach);
|
|
2217
|
+
return;
|
|
2218
|
+
}
|
|
2219
|
+
const read = () => setPanelWidth(node.clientWidth);
|
|
2220
|
+
read();
|
|
2221
|
+
observer = new ResizeObserver(read);
|
|
2222
|
+
observer.observe(node);
|
|
2223
|
+
};
|
|
2224
|
+
attach();
|
|
2225
|
+
return () => {
|
|
2226
|
+
cancelAnimationFrame(frame);
|
|
2227
|
+
observer?.disconnect();
|
|
2228
|
+
};
|
|
2229
|
+
}, [isOpen]);
|
|
2230
|
+
const rootPx = typeof document !== "undefined" ? parseFloat(getComputedStyle(document.documentElement).fontSize) || 16 : 16;
|
|
2231
|
+
const coarse = typeof window !== "undefined" && window.matchMedia("(pointer: coarse)").matches;
|
|
2232
|
+
const requested = months === "auto" ? mode === "range" ? 2 : 1 : months;
|
|
2233
|
+
let monthCount = requested;
|
|
2234
|
+
if (useDrawer) monthCount = 1;
|
|
2235
|
+
else if (panelWidth > 0) {
|
|
2236
|
+
const one = panelWidth < ONE_MONTH_REM * rootPx || coarse && panelWidth < COARSE_ONE_REM * rootPx;
|
|
2237
|
+
if (one) monthCount = 1;
|
|
2238
|
+
else if (panelWidth < TWO_MONTHS_REM * rootPx) monthCount = requested === 1 ? 1 : 2;
|
|
2239
|
+
}
|
|
2240
|
+
if (monthCount > requested) monthCount = requested;
|
|
2241
|
+
const rowLayout = useDrawer || panelWidth > 0 && panelWidth < TWO_MONTHS_REM * rootPx;
|
|
2242
|
+
const compact = useDrawer || panelWidth > 0 && panelWidth < ONE_MONTH_REM * rootPx;
|
|
2243
|
+
const touch = useDrawer || coarse || compact;
|
|
2244
|
+
const boxCap = isOpen && typeof document !== "undefined" ? explicitBox(fieldRef.current) : void 0;
|
|
2245
|
+
const [calendarMonth, setCalendarMonth] = React10.useState(
|
|
2246
|
+
() => startOfMonth(monthFor(committed, months === "auto" ? mode === "range" ? 2 : 1 : months) ?? defaultMonth ?? todayDate())
|
|
2247
|
+
);
|
|
2248
|
+
const generatedId = React10.useId();
|
|
2249
|
+
const triggerId = id ?? generatedId;
|
|
2250
|
+
const setOpen = (next) => {
|
|
2251
|
+
if (open === void 0) setUncontrolledOpen(next);
|
|
2252
|
+
onOpenChange?.(next);
|
|
2253
|
+
};
|
|
2254
|
+
const write = (next) => {
|
|
2255
|
+
if (!controlled) setUncontrolled(next);
|
|
2256
|
+
if (mode === "range") props.onValueChange?.(isRangeValue(next) ? next : EMPTY_RANGE);
|
|
2257
|
+
else props.onValueChange?.(typeof next === "string" ? next : null);
|
|
2258
|
+
};
|
|
2259
|
+
const display = formatValue ? formatValue(mode === "range" ? isRangeValue(committed) ? committed : EMPTY_RANGE : typeof committed === "string" ? committed : null, localeName) : labelFor(mode, committed, localeName, placeholder);
|
|
2260
|
+
const presetList = presets === false ? [] : presets === true ? defaultPresets(mode, weekStart) : presets.filter((preset) => mode === "single" ? !isRangeValue(typeof preset.value === "function" ? preset.value(todayDate()) : preset.value) : true);
|
|
2261
|
+
const committedKey = typeof committed === "string" ? committed : isRangeValue(committed) ? `${committed.start ?? ""}|${committed.end ?? ""}` : "";
|
|
2262
|
+
React10.useEffect(() => {
|
|
2263
|
+
if (!isOpen) return;
|
|
2264
|
+
const next = monthFor(committed, monthCount);
|
|
2265
|
+
if (next) setCalendarMonth(next);
|
|
2266
|
+
}, [isOpen, committedKey, monthCount]);
|
|
2267
|
+
const onDraft = (next) => {
|
|
2268
|
+
setDraft(next);
|
|
2269
|
+
if (commitMode === "immediate") {
|
|
2270
|
+
write(next);
|
|
2271
|
+
const done = mode === "single" ? typeof next === "string" : isRangeValue(next) && next.start && next.end;
|
|
2272
|
+
if (done) setOpen(false);
|
|
2273
|
+
}
|
|
2274
|
+
};
|
|
2275
|
+
const clearDraft = () => {
|
|
2276
|
+
const empty = mode === "range" ? EMPTY_RANGE : null;
|
|
2277
|
+
setDraft(empty);
|
|
2278
|
+
setAnnouncement("Selection cleared.");
|
|
2279
|
+
};
|
|
2280
|
+
const clearCommitted = () => {
|
|
2281
|
+
const empty = mode === "range" ? EMPTY_RANGE : null;
|
|
2282
|
+
setDraft(empty);
|
|
2283
|
+
write(empty);
|
|
2284
|
+
setAnnouncement(mode === "range" ? "Dates cleared." : "Date cleared.");
|
|
2285
|
+
document.getElementById(triggerId)?.focus();
|
|
2286
|
+
};
|
|
2287
|
+
const apply = () => {
|
|
2288
|
+
write(draft);
|
|
2289
|
+
setOpen(false);
|
|
2290
|
+
};
|
|
2291
|
+
const cancel = () => {
|
|
2292
|
+
setDraft(committed);
|
|
2293
|
+
setOpen(false);
|
|
2294
|
+
};
|
|
2295
|
+
const onPopupChange = (next, details) => {
|
|
2296
|
+
if (next && readOnly) {
|
|
2297
|
+
details?.cancel?.();
|
|
2298
|
+
return;
|
|
2299
|
+
}
|
|
2300
|
+
if (!next && details?.reason === "escape-key" && chooserBack.current) {
|
|
2301
|
+
details.cancel?.();
|
|
2302
|
+
chooserBack.current();
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
if (!next && halfRange(draft) && details?.reason === "escape-key") {
|
|
2306
|
+
details.cancel?.();
|
|
2307
|
+
clearDraft();
|
|
2308
|
+
setAnnouncement("Start date cleared. Pick a start date, or press Escape to close.");
|
|
2309
|
+
return;
|
|
2310
|
+
}
|
|
2311
|
+
if (!next) setDraft(committed);
|
|
2312
|
+
if (next) setDraft(committed);
|
|
2313
|
+
setOpen(next);
|
|
2314
|
+
};
|
|
2315
|
+
const reveal = (value) => {
|
|
2316
|
+
const next = monthFor(value, monthCount);
|
|
2317
|
+
if (next) setCalendarMonth(next);
|
|
2318
|
+
};
|
|
2319
|
+
const panel = /* @__PURE__ */ jsxs(
|
|
2320
|
+
"div",
|
|
2321
|
+
{
|
|
2322
|
+
ref: panelRef,
|
|
2323
|
+
className: "DatePickerPanel",
|
|
2324
|
+
"data-months": monthCount,
|
|
2325
|
+
"data-mode": mode,
|
|
2326
|
+
"data-popup": useDrawer ? "drawer" : "popover",
|
|
2327
|
+
"data-layout": rowLayout ? "row" : "column",
|
|
2328
|
+
"data-compact": compact ? "" : void 0,
|
|
2329
|
+
"data-touch": touch ? "" : void 0,
|
|
2330
|
+
children: [
|
|
2331
|
+
presetList.length ? /* @__PURE__ */ jsx(
|
|
2332
|
+
DatePresets,
|
|
2333
|
+
{
|
|
2334
|
+
presets: presetList,
|
|
2335
|
+
value: draft,
|
|
2336
|
+
min,
|
|
2337
|
+
max,
|
|
2338
|
+
isDateDisabled,
|
|
2339
|
+
locale: localeName,
|
|
2340
|
+
layout: rowLayout ? "row" : "column",
|
|
2341
|
+
selectOnNavigate: commitMode === "apply",
|
|
2342
|
+
onSelect: (_preset, next) => {
|
|
2343
|
+
const value = mode === "range" && typeof next === "string" ? { start: next, end: next } : next;
|
|
2344
|
+
onDraft(value);
|
|
2345
|
+
reveal(value);
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
) : null,
|
|
2349
|
+
/* @__PURE__ */ jsx(
|
|
2350
|
+
Calendar,
|
|
2351
|
+
{
|
|
2352
|
+
mode,
|
|
2353
|
+
value: draft,
|
|
2354
|
+
onValueChange: onDraft,
|
|
2355
|
+
month: calendarMonth,
|
|
2356
|
+
defaultMonth,
|
|
2357
|
+
onMonthChange: setCalendarMonth,
|
|
2358
|
+
months: monthCount,
|
|
2359
|
+
chooserBackRef: chooserBack,
|
|
2360
|
+
min,
|
|
2361
|
+
max,
|
|
2362
|
+
isDateDisabled,
|
|
2363
|
+
minLength,
|
|
2364
|
+
maxLength,
|
|
2365
|
+
weekStartsOn: weekStart,
|
|
2366
|
+
locale: localeName,
|
|
2367
|
+
showOutsideDays,
|
|
2368
|
+
showWeekNumbers,
|
|
2369
|
+
focusSelected: isOpen,
|
|
2370
|
+
onAnnounce: setAnnouncement
|
|
2371
|
+
}
|
|
2372
|
+
),
|
|
2373
|
+
/* @__PURE__ */ jsxs("div", { className: "DatePickerFooter", children: [
|
|
2374
|
+
/* @__PURE__ */ jsx("p", { className: "DatePickerSummary", children: summary(mode, draft, localeName) }),
|
|
2375
|
+
/* @__PURE__ */ jsxs("div", { className: "DatePickerActions", children: [
|
|
2376
|
+
clearable ? /* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", onClick: clearDraft, children: "Clear" }) : null,
|
|
2377
|
+
commitMode === "apply" ? /* @__PURE__ */ jsx(Button, { type: "button", emphasis: "standard", onClick: cancel, children: "Cancel" }) : null,
|
|
2378
|
+
commitMode === "apply" ? /* @__PURE__ */ jsx(Button, { type: "button", emphasis: "bold", className: "DatePickerApply", onClick: apply, children: "Apply" }) : null
|
|
2379
|
+
] })
|
|
2380
|
+
] }),
|
|
2381
|
+
/* @__PURE__ */ jsx("div", { className: "visually-hidden", "aria-live": "polite", children: announcement })
|
|
2382
|
+
]
|
|
2383
|
+
}
|
|
2384
|
+
);
|
|
2385
|
+
const valueId = `${triggerId}-value`;
|
|
2386
|
+
const named = labelId ? { "aria-labelledby": labelId } : label ? { "aria-label": label } : {};
|
|
2387
|
+
const described = labelId || label ? { "aria-describedby": valueId } : {};
|
|
2388
|
+
const popupName = labelId ? { "aria-labelledby": labelId } : { "aria-label": label || placeholder || (mode === "range" ? "Choose dates" : "Choose a date") };
|
|
2389
|
+
const face = /* @__PURE__ */ jsx(TriggerFace, { id: valueId, display, placeholder, empty: !hasValue(mode, committed) });
|
|
2390
|
+
const triggerA11y = {
|
|
2391
|
+
"aria-haspopup": "dialog",
|
|
2392
|
+
disabled,
|
|
2393
|
+
"aria-invalid": invalid || void 0,
|
|
2394
|
+
"aria-readonly": readOnly || void 0,
|
|
2395
|
+
"data-invalid": invalid ? "" : void 0,
|
|
2396
|
+
...named,
|
|
2397
|
+
...described
|
|
2398
|
+
};
|
|
2399
|
+
const trigger = /* @__PURE__ */ jsxs("div", { ref: fieldRef, className: cn("DatePickerField", className), children: [
|
|
2400
|
+
useDrawer ? /* @__PURE__ */ jsx(Drawer.Trigger, { id: triggerId, ref, className: "DatePickerTrigger", ...triggerA11y, children: face }) : /* @__PURE__ */ jsx(Popover.Trigger, { id: triggerId, ref, className: "DatePickerTrigger", ...triggerA11y, children: face }),
|
|
2401
|
+
clearable && hasValue(mode, committed) && !disabled && !readOnly ? /* @__PURE__ */ jsx(Button, { type: "button", emphasis: "quiet", className: "DatePickerClear", "aria-label": mode === "range" ? "Clear dates" : "Clear date", onClick: clearCommitted, children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", children: "close" }) }) : null,
|
|
2402
|
+
mode === "single" ? /* @__PURE__ */ jsx(HiddenValue, { name, value: typeof committed === "string" ? committed : "", required, triggerId }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2403
|
+
/* @__PURE__ */ jsx(HiddenValue, { name: nameStart ?? (name ? `${name}[start]` : void 0), value: isRangeValue(committed) ? committed.start ?? "" : "", required, triggerId }),
|
|
2404
|
+
/* @__PURE__ */ jsx(
|
|
2405
|
+
"input",
|
|
2406
|
+
{
|
|
2407
|
+
className: "visually-hidden",
|
|
2408
|
+
name: nameEnd ?? (name ? `${name}[end]` : void 0),
|
|
2409
|
+
value: isRangeValue(committed) ? committed.end ?? "" : "",
|
|
2410
|
+
tabIndex: -1,
|
|
2411
|
+
"aria-hidden": true,
|
|
2412
|
+
autoComplete: "off",
|
|
2413
|
+
onChange: () => {
|
|
2414
|
+
},
|
|
2415
|
+
onFocus: () => document.getElementById(triggerId)?.focus()
|
|
2416
|
+
}
|
|
2417
|
+
)
|
|
2418
|
+
] })
|
|
2419
|
+
] });
|
|
2420
|
+
const popupStyle = boxCap ? { maxInlineSize: boxCap } : void 0;
|
|
2421
|
+
if (useDrawer) {
|
|
2422
|
+
return /* @__PURE__ */ jsxs(Drawer.Root, { swipeDirection: "down", open: isOpen, onOpenChange: onPopupChange, modal: true, children: [
|
|
2423
|
+
trigger,
|
|
2424
|
+
/* @__PURE__ */ jsxs(Drawer.Portal, { children: [
|
|
2425
|
+
/* @__PURE__ */ jsx(Drawer.Backdrop, {}),
|
|
2426
|
+
/* @__PURE__ */ jsx(Drawer.Viewport, { children: /* @__PURE__ */ jsx(Drawer.Popup, { ...popupName, "data-months": monthCount, dir: rtl ? "rtl" : "ltr", style: popupStyle, children: /* @__PURE__ */ jsxs(Drawer.Content, { children: [
|
|
2427
|
+
panel,
|
|
2428
|
+
/* @__PURE__ */ jsx(Drawer.Close, { className: "visually-hidden", tabIndex: -1, children: "Close" })
|
|
2429
|
+
] }) }) })
|
|
2430
|
+
] })
|
|
2431
|
+
] });
|
|
2432
|
+
}
|
|
2433
|
+
return /* @__PURE__ */ jsxs(Popover.Root, { open: isOpen, onOpenChange: onPopupChange, modal: "trap-focus", children: [
|
|
2434
|
+
trigger,
|
|
2435
|
+
/* @__PURE__ */ jsx(Popover.Portal, { children: /* @__PURE__ */ jsx(Popover.Positioner, { sideOffset: 8, children: /* @__PURE__ */ jsxs(Popover.Popup, { ...popupName, className: "DatePickerPopup", "data-months": monthCount, dir: rtl ? "rtl" : "ltr", initialFocus: false, style: popupStyle, children: [
|
|
2436
|
+
panel,
|
|
2437
|
+
/* @__PURE__ */ jsx(Popover.Close, { className: "visually-hidden", tabIndex: -1, children: "Close" })
|
|
2438
|
+
] }) }) })
|
|
2439
|
+
] });
|
|
2440
|
+
});
|
|
2441
|
+
function TriggerFace({ id, display, placeholder, empty }) {
|
|
2442
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2443
|
+
/* @__PURE__ */ jsx("span", { className: "material-symbols-outlined DatePickerIcon", "aria-hidden": "true", children: "calendar_today" }),
|
|
2444
|
+
/* @__PURE__ */ jsx("span", { id, className: "DatePickerValue", "data-placeholder": empty ? "" : void 0, children: empty ? placeholder || display : display })
|
|
2445
|
+
] });
|
|
2446
|
+
}
|
|
2447
|
+
function HiddenValue({ name, value, required, triggerId }) {
|
|
2448
|
+
return /* @__PURE__ */ jsx(
|
|
2449
|
+
Field.Control,
|
|
2450
|
+
{
|
|
2451
|
+
className: "visually-hidden",
|
|
2452
|
+
name,
|
|
2453
|
+
value,
|
|
2454
|
+
required,
|
|
2455
|
+
tabIndex: -1,
|
|
2456
|
+
"aria-hidden": true,
|
|
2457
|
+
autoComplete: "off",
|
|
2458
|
+
onFocus: () => {
|
|
2459
|
+
document.getElementById(triggerId)?.focus();
|
|
2460
|
+
}
|
|
2461
|
+
}
|
|
2462
|
+
);
|
|
2463
|
+
}
|
|
2464
|
+
function labelFor(mode, value, locale, placeholder) {
|
|
2465
|
+
if (mode === "single") {
|
|
2466
|
+
return typeof value === "string" ? formatDay(value, locale) : placeholder || "Choose a date";
|
|
2467
|
+
}
|
|
2468
|
+
if (isRangeValue(value) && value.start && value.end) return formatRangeLabel(value.start, value.end, locale);
|
|
2469
|
+
if (isRangeValue(value) && value.start) return `${formatDay(value.start, locale)} \u2013`;
|
|
2470
|
+
return placeholder || "Choose dates";
|
|
2471
|
+
}
|
|
2472
|
+
function summary(mode, value, locale) {
|
|
2473
|
+
if (mode === "range" && isRangeValue(value) && value.start && value.end) {
|
|
2474
|
+
return `${formatRangeLabel(value.start, value.end, locale)} \xB7 ${inclusiveDays(value.start, value.end)} days`;
|
|
2475
|
+
}
|
|
2476
|
+
if (mode === "range" && isRangeValue(value) && value.start) return `Start date ${formatDay(value.start, locale)}. Pick an end date.`;
|
|
2477
|
+
if (mode === "single" && typeof value === "string") return formatDay(value, locale);
|
|
2478
|
+
return "No date selected";
|
|
2479
|
+
}
|
|
2480
|
+
|
|
2481
|
+
// src/custom/split/layout.ts
|
|
2482
|
+
var ZERO = { unit: "px", value: 0 };
|
|
2483
|
+
function parseSize(input) {
|
|
2484
|
+
if (input == null || input === "") return null;
|
|
2485
|
+
if (typeof input === "number") return Number.isFinite(input) ? { unit: "percent", value: input } : null;
|
|
2486
|
+
const text = input.trim();
|
|
2487
|
+
const px = /^(-?\d+(?:\.\d+)?)px$/.exec(text);
|
|
2488
|
+
if (px) return { unit: "px", value: Number(px[1]) };
|
|
2489
|
+
const percent = /^(-?\d+(?:\.\d+)?)%$/.exec(text);
|
|
2490
|
+
if (percent) return { unit: "percent", value: Number(percent[1]) };
|
|
2491
|
+
return null;
|
|
2492
|
+
}
|
|
2493
|
+
function serializeSize(size) {
|
|
2494
|
+
const n = Math.round(size.value * 100) / 100;
|
|
2495
|
+
const text = Object.is(n, -0) ? "0" : String(n);
|
|
2496
|
+
return size.unit === "px" ? `${text}px` : `${text}%`;
|
|
2497
|
+
}
|
|
2498
|
+
function toPx(size, root) {
|
|
2499
|
+
return size.unit === "px" ? size.value : size.value / 100 * root;
|
|
2500
|
+
}
|
|
2501
|
+
function fromPx(px, unit, root) {
|
|
2502
|
+
const value = unit === "px" || root === 0 ? px : px / root * 100;
|
|
2503
|
+
return { unit, value: Math.round(value * 100) / 100 };
|
|
2504
|
+
}
|
|
2505
|
+
function clamp(n, min, max) {
|
|
2506
|
+
return Math.min(max, Math.max(min, n));
|
|
2507
|
+
}
|
|
2508
|
+
function copy(panel) {
|
|
2509
|
+
return { ...panel, size: { ...panel.size }, defaultSize: { ...panel.defaultSize }, min: { ...panel.min }, max: panel.max ? { ...panel.max } : null, collapsedSize: { ...panel.collapsedSize }, restore: panel.restore ? { ...panel.restore } : null };
|
|
2510
|
+
}
|
|
2511
|
+
function createPanels(specs) {
|
|
2512
|
+
if (specs.length < 2) throw new Error("Splitter needs at least two panels.");
|
|
2513
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2514
|
+
for (const spec of specs) {
|
|
2515
|
+
if (!spec.id) throw new Error("Splitter panel needs an id.");
|
|
2516
|
+
if (ids.has(spec.id)) throw new Error(`Splitter panel id "${spec.id}" is used twice.`);
|
|
2517
|
+
ids.add(spec.id);
|
|
2518
|
+
}
|
|
2519
|
+
const missing = specs.flatMap((spec, index) => spec.defaultSize == null ? [index] : []);
|
|
2520
|
+
const fillAt = missing.length ? missing[missing.length - 1] : specs.length - 1;
|
|
2521
|
+
return specs.map((spec, index) => {
|
|
2522
|
+
const fill = index === fillAt;
|
|
2523
|
+
const parsed = parseSize(spec.defaultSize) ?? { unit: "percent", value: Math.round(100 / specs.length) };
|
|
2524
|
+
const size = fill && spec.defaultSize == null ? { unit: "percent", value: 0 } : parsed;
|
|
2525
|
+
const collapsed = spec.defaultCollapsed ?? false;
|
|
2526
|
+
return {
|
|
2527
|
+
id: spec.id,
|
|
2528
|
+
fill,
|
|
2529
|
+
size,
|
|
2530
|
+
defaultSize: { ...size },
|
|
2531
|
+
min: parseSize(spec.minSize) ?? ZERO,
|
|
2532
|
+
max: parseSize(spec.maxSize),
|
|
2533
|
+
collapsible: spec.collapsible ?? false,
|
|
2534
|
+
collapsedSize: parseSize(spec.collapsedSize) ?? ZERO,
|
|
2535
|
+
collapsed,
|
|
2536
|
+
restore: collapsed ? { ...size } : null,
|
|
2537
|
+
autoCollapsed: false,
|
|
2538
|
+
suspended: spec.suspended ?? false
|
|
2539
|
+
};
|
|
2540
|
+
});
|
|
2541
|
+
}
|
|
2542
|
+
function syncPanels(current, specs) {
|
|
2543
|
+
const created = createPanels(specs);
|
|
2544
|
+
let changed = created.length !== current.length;
|
|
2545
|
+
const next = created.map((panel) => {
|
|
2546
|
+
const prev = current.find((item) => item.id === panel.id);
|
|
2547
|
+
if (!prev) {
|
|
2548
|
+
changed = true;
|
|
2549
|
+
return panel;
|
|
2550
|
+
}
|
|
2551
|
+
const size = within(prev.size, panel.min, panel.max) ? prev.size : panel.size;
|
|
2552
|
+
const collapsed = panel.collapsible ? prev.collapsed : false;
|
|
2553
|
+
const autoCollapsed = Boolean(collapsed && prev.autoCollapsed);
|
|
2554
|
+
const restore = collapsed ? { ...prev.restore ?? size } : null;
|
|
2555
|
+
const same = size.value === prev.size.value && size.unit === prev.size.unit && collapsed === prev.collapsed && autoCollapsed === prev.autoCollapsed && panel.suspended === prev.suspended && panel.collapsible === prev.collapsible && panel.min.value === prev.min.value && panel.min.unit === prev.min.unit && (panel.max?.value ?? null) === (prev.max?.value ?? null) && (panel.max?.unit ?? null) === (prev.max?.unit ?? null);
|
|
2556
|
+
if (!same) changed = true;
|
|
2557
|
+
return { ...panel, size: { ...size }, collapsed, autoCollapsed, restore };
|
|
2558
|
+
});
|
|
2559
|
+
return changed ? next : current;
|
|
2560
|
+
}
|
|
2561
|
+
function limits(panel, root) {
|
|
2562
|
+
const min = toPx(panel.min, root);
|
|
2563
|
+
const max = panel.max ? toPx(panel.max, root) : root;
|
|
2564
|
+
return { min, max: Math.max(min, max) };
|
|
2565
|
+
}
|
|
2566
|
+
function taken(panel) {
|
|
2567
|
+
return panel.collapsed || panel.suspended;
|
|
2568
|
+
}
|
|
2569
|
+
function measure(panels, root, handles = 0) {
|
|
2570
|
+
const safeRoot = Math.max(0, root - (Number.isFinite(handles) ? handles : 0));
|
|
2571
|
+
const fill = Math.max(0, panels.findIndex((panel) => panel.fill));
|
|
2572
|
+
const px = panels.map((panel) => {
|
|
2573
|
+
if (panel.fill && !taken(panel)) return 0;
|
|
2574
|
+
if (taken(panel)) return toPx(panel.collapsedSize, safeRoot);
|
|
2575
|
+
const { min, max } = limits(panel, safeRoot);
|
|
2576
|
+
return clamp(toPx(panel.size, safeRoot), min, max);
|
|
2577
|
+
});
|
|
2578
|
+
const fillMin = toPx(panels[fill].min, safeRoot);
|
|
2579
|
+
let used = px.reduce((sum2, value) => sum2 + value, 0);
|
|
2580
|
+
if (safeRoot - used < fillMin) {
|
|
2581
|
+
const deficit = fillMin - (safeRoot - used);
|
|
2582
|
+
const flexible = panels.flatMap((panel, index) => {
|
|
2583
|
+
if (panel.fill || taken(panel)) return [];
|
|
2584
|
+
const room = px[index] - toPx(panel.min, safeRoot);
|
|
2585
|
+
return room > 0 ? [{ index, room }] : [];
|
|
2586
|
+
});
|
|
2587
|
+
const roomSum = flexible.reduce((sum2, item) => sum2 + item.room, 0);
|
|
2588
|
+
const take = Math.min(deficit, roomSum);
|
|
2589
|
+
let left = take;
|
|
2590
|
+
flexible.forEach((item, index) => {
|
|
2591
|
+
const share = index === flexible.length - 1 ? left : item.room / roomSum * take;
|
|
2592
|
+
px[item.index] -= share;
|
|
2593
|
+
left -= share;
|
|
2594
|
+
});
|
|
2595
|
+
used = px.reduce((sum2, value) => sum2 + value, 0);
|
|
2596
|
+
}
|
|
2597
|
+
const rounded = px.map((value, index) => index === fill ? 0 : Math.round(value));
|
|
2598
|
+
const sum = rounded.reduce((total, value) => total + value, 0);
|
|
2599
|
+
const fillMax = panels[fill].max ? toPx(panels[fill].max, safeRoot) : safeRoot;
|
|
2600
|
+
rounded[fill] = clamp(Math.round(safeRoot) - sum, 0, Math.round(fillMax));
|
|
2601
|
+
return rounded;
|
|
2602
|
+
}
|
|
2603
|
+
function settle(panels, root) {
|
|
2604
|
+
const next = panels.map(copy);
|
|
2605
|
+
const overflow = () => {
|
|
2606
|
+
const sum = next.reduce((total, panel) => {
|
|
2607
|
+
if (taken(panel)) return total + toPx(panel.collapsedSize, root);
|
|
2608
|
+
return total + toPx(panel.min, root);
|
|
2609
|
+
}, 0);
|
|
2610
|
+
return sum - root;
|
|
2611
|
+
};
|
|
2612
|
+
let changed = false;
|
|
2613
|
+
if (overflow() > 0.5) {
|
|
2614
|
+
for (let pass = 0; pass < 2 && overflow() > 0.5; pass += 1) {
|
|
2615
|
+
for (let index = next.length - 1; index >= 0 && overflow() > 0.5; index -= 1) {
|
|
2616
|
+
const panel = next[index];
|
|
2617
|
+
if (pass === 0 && panel.fill) continue;
|
|
2618
|
+
if (!panel.collapsible || panel.collapsed || panel.suspended) continue;
|
|
2619
|
+
next[index] = { ...panel, collapsed: true, autoCollapsed: true, restore: { ...panel.size } };
|
|
2620
|
+
changed = true;
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
} else {
|
|
2624
|
+
for (let index = 0; index < next.length; index += 1) {
|
|
2625
|
+
const panel = next[index];
|
|
2626
|
+
if (!panel.autoCollapsed || !panel.collapsed || panel.suspended) continue;
|
|
2627
|
+
const gain = toPx(panel.min, root) - toPx(panel.collapsedSize, root);
|
|
2628
|
+
if (overflow() + gain > 0.5) continue;
|
|
2629
|
+
next[index] = {
|
|
2630
|
+
...panel,
|
|
2631
|
+
collapsed: false,
|
|
2632
|
+
autoCollapsed: false,
|
|
2633
|
+
size: { ...panel.restore ?? panel.defaultSize },
|
|
2634
|
+
restore: null
|
|
2635
|
+
};
|
|
2636
|
+
changed = true;
|
|
2637
|
+
}
|
|
2638
|
+
}
|
|
2639
|
+
return changed ? next : panels;
|
|
2640
|
+
}
|
|
2641
|
+
function handleTarget(panels, handleIndex) {
|
|
2642
|
+
const left = handleIndex;
|
|
2643
|
+
const right = handleIndex + 1;
|
|
2644
|
+
if (!panels[left]?.fill) return left;
|
|
2645
|
+
return right;
|
|
2646
|
+
}
|
|
2647
|
+
function resizeByDelta(panels, root, handleIndex, deltaPx) {
|
|
2648
|
+
if (!Number.isFinite(root) || root <= 0) return panels;
|
|
2649
|
+
const target = handleTarget(panels, handleIndex);
|
|
2650
|
+
const fill = panels.findIndex((panel) => panel.fill);
|
|
2651
|
+
if (target < 0 || fill < 0 || panels[target].fill) return panels;
|
|
2652
|
+
const pixels = measure(panels, root);
|
|
2653
|
+
const current = pixels[target];
|
|
2654
|
+
const sign = target === handleIndex ? 1 : -1;
|
|
2655
|
+
const { min, max } = limits(panels[target], root);
|
|
2656
|
+
const fillMin = toPx(panels[fill].min, root);
|
|
2657
|
+
const fillMax = panels[fill].max ? toPx(panels[fill].max, root) : root;
|
|
2658
|
+
let delta = sign * deltaPx;
|
|
2659
|
+
delta = clamp(delta, Math.max(min - current, pixels[fill] - fillMax), Math.min(max - current, pixels[fill] - fillMin));
|
|
2660
|
+
const next = panels.map(copy);
|
|
2661
|
+
next[target] = {
|
|
2662
|
+
...next[target],
|
|
2663
|
+
collapsed: false,
|
|
2664
|
+
autoCollapsed: false,
|
|
2665
|
+
size: fromPx(current + delta, next[target].size.unit, root)
|
|
2666
|
+
};
|
|
2667
|
+
return next;
|
|
2668
|
+
}
|
|
2669
|
+
function toggleCollapsed(panels, handleIndex) {
|
|
2670
|
+
const target = handleTarget(panels, handleIndex);
|
|
2671
|
+
const panel = panels[target];
|
|
2672
|
+
if (!panel || panel.fill || !panel.collapsible) return null;
|
|
2673
|
+
const next = panels.map(copy);
|
|
2674
|
+
if (panel.collapsed) {
|
|
2675
|
+
next[target] = { ...next[target], collapsed: false, autoCollapsed: false, size: { ...panel.restore ?? panel.defaultSize }, restore: null };
|
|
2676
|
+
} else {
|
|
2677
|
+
next[target] = { ...next[target], collapsed: true, autoCollapsed: false, restore: { ...panel.size } };
|
|
2678
|
+
}
|
|
2679
|
+
return next;
|
|
2680
|
+
}
|
|
2681
|
+
function resetToDefault(panels, handleIndex) {
|
|
2682
|
+
const target = handleTarget(panels, handleIndex);
|
|
2683
|
+
const panel = panels[target];
|
|
2684
|
+
if (!panel || panel.fill) return panels;
|
|
2685
|
+
const next = panels.map(copy);
|
|
2686
|
+
next[target] = { ...next[target], collapsed: false, autoCollapsed: false, size: { ...panel.defaultSize }, restore: null };
|
|
2687
|
+
return next;
|
|
2688
|
+
}
|
|
2689
|
+
function applyKey(panels, root, handleIndex, key, options) {
|
|
2690
|
+
if (key === "Enter") return toggleCollapsed(panels, handleIndex);
|
|
2691
|
+
let axisKey = key;
|
|
2692
|
+
if (options.rtl && options.orientation === "horizontal") {
|
|
2693
|
+
if (key === "ArrowLeft") axisKey = "ArrowRight";
|
|
2694
|
+
else if (key === "ArrowRight") axisKey = "ArrowLeft";
|
|
2695
|
+
}
|
|
2696
|
+
const horizontal = options.orientation === "horizontal";
|
|
2697
|
+
const along = horizontal ? axisKey === "ArrowLeft" || axisKey === "ArrowRight" : axisKey === "ArrowUp" || axisKey === "ArrowDown";
|
|
2698
|
+
const edge = axisKey === "Home" || axisKey === "End";
|
|
2699
|
+
if (!along && !edge) return null;
|
|
2700
|
+
const target = handleTarget(panels, handleIndex);
|
|
2701
|
+
const panel = panels[target];
|
|
2702
|
+
if (!panel || panel.fill) return null;
|
|
2703
|
+
const sign = target === handleIndex ? 1 : -1;
|
|
2704
|
+
const pixels = measure(panels, root);
|
|
2705
|
+
const { min, max } = limits(panel, root);
|
|
2706
|
+
if (edge) {
|
|
2707
|
+
const goal = axisKey === "Home" ? min : max;
|
|
2708
|
+
return resizeByDelta(panels, root, handleIndex, (goal - pixels[target]) / sign);
|
|
2709
|
+
}
|
|
2710
|
+
const step = (options.shift ? options.largeStep : options.step) / 100 * root;
|
|
2711
|
+
const towardEnd = axisKey === "ArrowRight" || axisKey === "ArrowDown";
|
|
2712
|
+
return resizeByDelta(panels, root, handleIndex, towardEnd ? step : -step);
|
|
2713
|
+
}
|
|
2714
|
+
function sameUnit(size, bound) {
|
|
2715
|
+
return bound == null || bound.unit === size.unit;
|
|
2716
|
+
}
|
|
2717
|
+
function within(size, min, max) {
|
|
2718
|
+
if (!sameUnit(size, min) || !sameUnit(size, max)) return false;
|
|
2719
|
+
if (size.value < min.value) return false;
|
|
2720
|
+
if (max && size.value > max.value) return false;
|
|
2721
|
+
return true;
|
|
2722
|
+
}
|
|
2723
|
+
function applyStored(raw, panels, partial = "reject") {
|
|
2724
|
+
if (!raw) return null;
|
|
2725
|
+
let data;
|
|
2726
|
+
try {
|
|
2727
|
+
data = JSON.parse(raw);
|
|
2728
|
+
} catch {
|
|
2729
|
+
return null;
|
|
2730
|
+
}
|
|
2731
|
+
if (!data || typeof data !== "object" || Array.isArray(data)) return null;
|
|
2732
|
+
const record = data;
|
|
2733
|
+
const next = panels.map(copy);
|
|
2734
|
+
for (const panel of next) {
|
|
2735
|
+
if (panel.fill) continue;
|
|
2736
|
+
const entry = record[panel.id];
|
|
2737
|
+
if (entry == null) {
|
|
2738
|
+
if (partial === "ignore") continue;
|
|
2739
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2740
|
+
console.warn(`Splitter layout has no entry for panel "${panel.id}". Pass a size for every panel, or leave layout unset.`);
|
|
2741
|
+
}
|
|
2742
|
+
return null;
|
|
2743
|
+
}
|
|
2744
|
+
if (typeof entry !== "object" || Array.isArray(entry)) return null;
|
|
2745
|
+
const sizeText2 = entry.size;
|
|
2746
|
+
const collapsed = entry.collapsed;
|
|
2747
|
+
if (typeof sizeText2 !== "string" || typeof collapsed !== "boolean") return null;
|
|
2748
|
+
const size = parseSize(sizeText2);
|
|
2749
|
+
if (!size || !within(size, panel.min, panel.max)) return null;
|
|
2750
|
+
panel.size = size;
|
|
2751
|
+
panel.collapsed = collapsed;
|
|
2752
|
+
panel.autoCollapsed = false;
|
|
2753
|
+
panel.restore = collapsed ? { ...size } : null;
|
|
2754
|
+
}
|
|
2755
|
+
return next;
|
|
2756
|
+
}
|
|
2757
|
+
function layoutOf(panels) {
|
|
2758
|
+
const layout = {};
|
|
2759
|
+
for (const panel of panels) {
|
|
2760
|
+
layout[panel.id] = {
|
|
2761
|
+
size: serializeSize(panel.size),
|
|
2762
|
+
collapsed: panel.autoCollapsed ? false : panel.collapsed
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
return layout;
|
|
2766
|
+
}
|
|
2767
|
+
function sizeText(panel) {
|
|
2768
|
+
return serializeSize(panel.size);
|
|
2769
|
+
}
|
|
2770
|
+
function cssSize(panel) {
|
|
2771
|
+
const basis = taken(panel) ? panel.collapsedSize : panel.size;
|
|
2772
|
+
if (panel.fill && !taken(panel)) return "auto";
|
|
2773
|
+
const size = serializeSize(basis);
|
|
2774
|
+
const max = panel.max;
|
|
2775
|
+
const same = basis.unit === panel.min.unit && (max == null || max.unit === basis.unit);
|
|
2776
|
+
if (!same) return size;
|
|
2777
|
+
if (max) return `clamp(${serializeSize(panel.min)}, ${size}, ${serializeSize(max)})`;
|
|
2778
|
+
if (panel.min.value > 0) return `max(${serializeSize(panel.min)}, ${size})`;
|
|
2779
|
+
return size;
|
|
2780
|
+
}
|
|
2781
|
+
function ariaPercents(panel, pixels, root) {
|
|
2782
|
+
const span = root <= 0 ? 1 : root;
|
|
2783
|
+
const { min, max } = limits(panel, span);
|
|
2784
|
+
return {
|
|
2785
|
+
now: clamp(Math.round(pixels / span * 100), 0, 100),
|
|
2786
|
+
min: clamp(Math.round(min / span * 100), 0, 100),
|
|
2787
|
+
max: clamp(Math.round(max / span * 100), 0, 100)
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
function valueText(panel, pixels) {
|
|
2791
|
+
if (panel.collapsed) return "Collapsed";
|
|
2792
|
+
if (panel.size.unit === "px") return `${Math.round(pixels)} pixels`;
|
|
2793
|
+
return `${serializeSize(panel.size).replace("%", "")} percent`;
|
|
2794
|
+
}
|
|
2795
|
+
var SplitterContext = React10.createContext(null);
|
|
2796
|
+
function useSplitter() {
|
|
2797
|
+
const value = React10.useContext(SplitterContext);
|
|
2798
|
+
if (!value) throw new Error("Splitter parts must be inside Splitter.Root.");
|
|
2799
|
+
return value;
|
|
2800
|
+
}
|
|
2801
|
+
function flattenParts(children) {
|
|
2802
|
+
const out = [];
|
|
2803
|
+
const visit = (node) => {
|
|
2804
|
+
React10.Children.forEach(node, (child) => {
|
|
2805
|
+
if (child == null || typeof child === "boolean") return;
|
|
2806
|
+
if (typeof child === "string" || typeof child === "number") {
|
|
2807
|
+
if (String(child).trim() === "") return;
|
|
2808
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2809
|
+
throw new Error("Splitter.Root only accepts Splitter.Panel and Splitter.Handle. Put text outside the splitter.");
|
|
2810
|
+
}
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
if (!React10.isValidElement(child)) return;
|
|
2814
|
+
if (child.type === React10.Fragment) {
|
|
2815
|
+
visit(child.props.children);
|
|
2816
|
+
return;
|
|
2817
|
+
}
|
|
2818
|
+
if (child.type !== SplitterPanel && child.type !== SplitterHandle) {
|
|
2819
|
+
if (process.env.NODE_ENV !== "production") {
|
|
2820
|
+
throw new Error(
|
|
2821
|
+
"Splitter.Root only accepts Splitter.Panel and Splitter.Handle as children. Render the panel directly, or return it from a Fragment. A wrapper component is not a panel."
|
|
2822
|
+
);
|
|
2823
|
+
}
|
|
2824
|
+
return;
|
|
2825
|
+
}
|
|
2826
|
+
out.push(child);
|
|
2827
|
+
});
|
|
2828
|
+
};
|
|
2829
|
+
visit(children);
|
|
2830
|
+
return out;
|
|
2831
|
+
}
|
|
2832
|
+
function specsFrom(parts) {
|
|
2833
|
+
const specs = [];
|
|
2834
|
+
for (const child of parts) {
|
|
2835
|
+
if (child.type !== SplitterPanel) continue;
|
|
2836
|
+
const props = child.props;
|
|
2837
|
+
specs.push({
|
|
2838
|
+
id: props.id,
|
|
2839
|
+
defaultSize: props.defaultSize,
|
|
2840
|
+
minSize: props.minSize,
|
|
2841
|
+
maxSize: props.maxSize,
|
|
2842
|
+
collapsible: props.collapsible,
|
|
2843
|
+
collapsedSize: props.collapsedSize,
|
|
2844
|
+
defaultCollapsed: props.defaultCollapsed,
|
|
2845
|
+
suspended: props.suspended
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
return specs;
|
|
2849
|
+
}
|
|
2850
|
+
function specKeyOf(specs) {
|
|
2851
|
+
return specs.map(
|
|
2852
|
+
(spec) => [spec.id, spec.defaultSize ?? "", spec.minSize ?? "", spec.maxSize ?? "", spec.collapsible ? "1" : "0", spec.collapsedSize ?? "", spec.defaultCollapsed ? "1" : "0", spec.suspended ? "1" : "0"].join("\0")
|
|
2853
|
+
).join("\n");
|
|
2854
|
+
}
|
|
2855
|
+
var KEYS = /* @__PURE__ */ new Set(["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "Enter"]);
|
|
2856
|
+
function readStep(root) {
|
|
2857
|
+
if (!root) return { step: 5, largeStep: 10 };
|
|
2858
|
+
const style = getComputedStyle(root);
|
|
2859
|
+
const step = parseFloat(style.getPropertyValue("--sl-splitter-key-step"));
|
|
2860
|
+
const large = parseFloat(style.getPropertyValue("--sl-splitter-key-step-large"));
|
|
2861
|
+
return {
|
|
2862
|
+
step: Number.isFinite(step) ? step : 5,
|
|
2863
|
+
largeStep: Number.isFinite(large) ? large : 10
|
|
2864
|
+
};
|
|
2865
|
+
}
|
|
2866
|
+
var SplitterRoot = React10.forwardRef(function SplitterRoot2({ orientation = "horizontal", storageKey, layout, onLayoutChange, className, children, ...props }, ref) {
|
|
2867
|
+
const uid = React10.useId();
|
|
2868
|
+
const parts = flattenParts(children);
|
|
2869
|
+
const specs = specsFrom(parts);
|
|
2870
|
+
const specKey = specKeyOf(specs);
|
|
2871
|
+
const [panels, setPanels] = React10.useState(() => createPanels(specs));
|
|
2872
|
+
const [metrics, setMetrics] = React10.useState({ root: 0, handles: 0 });
|
|
2873
|
+
const rootRef = React10.useRef(null);
|
|
2874
|
+
const panelsRef = React10.useRef(panels);
|
|
2875
|
+
const sizeRef = React10.useRef(0);
|
|
2876
|
+
const dragging = React10.useRef(false);
|
|
2877
|
+
const livePixels = React10.useRef(null);
|
|
2878
|
+
const specsRef = React10.useRef(specs);
|
|
2879
|
+
specsRef.current = specs;
|
|
2880
|
+
const controlled = layout !== void 0;
|
|
2881
|
+
React10.useLayoutEffect(() => {
|
|
2882
|
+
setPanels((current) => syncPanels(current, specsRef.current));
|
|
2883
|
+
}, [specKey]);
|
|
2884
|
+
const contentSize = Math.max(0, metrics.root - metrics.handles);
|
|
2885
|
+
const base = controlled ? applyStored(JSON.stringify(layout), panels) ?? panels : panels;
|
|
2886
|
+
const shown = contentSize > 0 ? settle(base, contentSize) : base;
|
|
2887
|
+
panelsRef.current = shown;
|
|
2888
|
+
sizeRef.current = contentSize;
|
|
2889
|
+
const commit = React10.useCallback(
|
|
2890
|
+
(next) => {
|
|
2891
|
+
const stored = layoutOf(next);
|
|
2892
|
+
onLayoutChange?.(stored);
|
|
2893
|
+
if (controlled) return;
|
|
2894
|
+
setPanels(next);
|
|
2895
|
+
if (!storageKey) return;
|
|
2896
|
+
try {
|
|
2897
|
+
localStorage.setItem(storageKey, JSON.stringify(stored));
|
|
2898
|
+
} catch {
|
|
2899
|
+
}
|
|
2900
|
+
},
|
|
2901
|
+
[controlled, onLayoutChange, storageKey]
|
|
2902
|
+
);
|
|
2903
|
+
React10.useEffect(() => {
|
|
2904
|
+
if (controlled || !storageKey) return;
|
|
2905
|
+
try {
|
|
2906
|
+
const next = applyStored(localStorage.getItem(storageKey), panelsRef.current, "ignore");
|
|
2907
|
+
if (next) setPanels(next);
|
|
2908
|
+
} catch {
|
|
2909
|
+
}
|
|
2910
|
+
}, [storageKey, controlled]);
|
|
2911
|
+
React10.useLayoutEffect(() => {
|
|
2912
|
+
const root = rootRef.current;
|
|
2913
|
+
if (!root) return;
|
|
2914
|
+
const apply = () => {
|
|
2915
|
+
const bars = [...root.children].filter((node) => node.classList?.contains("SplitterHandle"));
|
|
2916
|
+
const handleSum = bars.reduce((sum, bar) => sum + (orientation === "horizontal" ? bar.offsetWidth : bar.offsetHeight), 0);
|
|
2917
|
+
const nextRoot = orientation === "horizontal" ? root.clientWidth : root.clientHeight;
|
|
2918
|
+
setMetrics((current) => current.root === nextRoot && current.handles === handleSum ? current : { root: nextRoot, handles: handleSum });
|
|
2919
|
+
};
|
|
2920
|
+
apply();
|
|
2921
|
+
const observer = new ResizeObserver(apply);
|
|
2922
|
+
observer.observe(root);
|
|
2923
|
+
return () => observer.disconnect();
|
|
2924
|
+
}, [orientation]);
|
|
2925
|
+
const pixels = measure(shown, metrics.root, metrics.handles);
|
|
2926
|
+
const applyDom = React10.useCallback((next, size, handleSum) => {
|
|
2927
|
+
const root = rootRef.current;
|
|
2928
|
+
if (!root) return;
|
|
2929
|
+
const nextPixels = measure(next, size, handleSum);
|
|
2930
|
+
livePixels.current = nextPixels;
|
|
2931
|
+
const elements = [...root.children].filter((node) => node.classList?.contains("SplitterPanel"));
|
|
2932
|
+
elements.forEach((element, index) => {
|
|
2933
|
+
element.style.setProperty("--sl-splitter-panel-size", `${nextPixels[index] ?? 0}px`);
|
|
2934
|
+
});
|
|
2935
|
+
const handles = [...root.children].filter((node) => node.classList?.contains("SplitterHandle"));
|
|
2936
|
+
const content2 = Math.max(0, size - handleSum);
|
|
2937
|
+
handles.forEach((handle, index) => {
|
|
2938
|
+
const targetIndex = handleTarget(next, index);
|
|
2939
|
+
const target = next[targetIndex];
|
|
2940
|
+
if (!target) return;
|
|
2941
|
+
const values = ariaPercents(target, nextPixels[targetIndex] ?? 0, content2);
|
|
2942
|
+
handle.setAttribute("aria-valuenow", String(values.now));
|
|
2943
|
+
handle.setAttribute("aria-valuetext", valueText(target, nextPixels[targetIndex] ?? 0));
|
|
2944
|
+
});
|
|
2945
|
+
}, []);
|
|
2946
|
+
const onDragStart = React10.useCallback(
|
|
2947
|
+
(index, event) => {
|
|
2948
|
+
if (event.button !== 0) return;
|
|
2949
|
+
event.preventDefault();
|
|
2950
|
+
event.stopPropagation();
|
|
2951
|
+
const handle = event.currentTarget;
|
|
2952
|
+
const root = rootRef.current;
|
|
2953
|
+
const size = metrics.root;
|
|
2954
|
+
if (!root || sizeRef.current <= 0) return;
|
|
2955
|
+
try {
|
|
2956
|
+
handle.setPointerCapture(event.pointerId);
|
|
2957
|
+
} catch {
|
|
2958
|
+
}
|
|
2959
|
+
const start = orientation === "horizontal" ? event.clientX : event.clientY;
|
|
2960
|
+
const startPanels = panelsRef.current;
|
|
2961
|
+
const previousUserSelect = document.body.style.userSelect;
|
|
2962
|
+
document.body.style.userSelect = "none";
|
|
2963
|
+
root.setAttribute("data-dragging", "");
|
|
2964
|
+
handle.setAttribute("data-dragging", "");
|
|
2965
|
+
dragging.current = true;
|
|
2966
|
+
let frame = 0;
|
|
2967
|
+
let latest = startPanels;
|
|
2968
|
+
let ended = false;
|
|
2969
|
+
const move = (pointer) => {
|
|
2970
|
+
const point = orientation === "horizontal" ? pointer.clientX : pointer.clientY;
|
|
2971
|
+
const delta = point - start;
|
|
2972
|
+
cancelAnimationFrame(frame);
|
|
2973
|
+
frame = requestAnimationFrame(() => {
|
|
2974
|
+
latest = resizeByDelta(startPanels, sizeRef.current, index, delta);
|
|
2975
|
+
applyDom(latest, size, metrics.handles);
|
|
2976
|
+
});
|
|
2977
|
+
};
|
|
2978
|
+
const finish = (restore) => {
|
|
2979
|
+
if (ended) return;
|
|
2980
|
+
ended = true;
|
|
2981
|
+
cancelAnimationFrame(frame);
|
|
2982
|
+
root.removeAttribute("data-dragging");
|
|
2983
|
+
handle.removeAttribute("data-dragging");
|
|
2984
|
+
document.body.style.userSelect = previousUserSelect;
|
|
2985
|
+
handle.removeEventListener("pointermove", move);
|
|
2986
|
+
handle.removeEventListener("pointerup", end);
|
|
2987
|
+
handle.removeEventListener("pointercancel", end);
|
|
2988
|
+
handle.removeEventListener("lostpointercapture", end);
|
|
2989
|
+
window.removeEventListener("blur", end);
|
|
2990
|
+
window.removeEventListener("keydown", onEscape, true);
|
|
2991
|
+
dragging.current = false;
|
|
2992
|
+
livePixels.current = null;
|
|
2993
|
+
if (restore) {
|
|
2994
|
+
applyDom(startPanels, size, metrics.handles);
|
|
2995
|
+
return;
|
|
2996
|
+
}
|
|
2997
|
+
commit(latest);
|
|
2998
|
+
};
|
|
2999
|
+
const end = () => finish(false);
|
|
3000
|
+
const onEscape = (key) => {
|
|
3001
|
+
if (key.key !== "Escape") return;
|
|
3002
|
+
key.preventDefault();
|
|
3003
|
+
key.stopPropagation();
|
|
3004
|
+
finish(true);
|
|
3005
|
+
};
|
|
3006
|
+
handle.addEventListener("pointermove", move);
|
|
3007
|
+
handle.addEventListener("pointerup", end);
|
|
3008
|
+
handle.addEventListener("pointercancel", end);
|
|
3009
|
+
handle.addEventListener("lostpointercapture", end);
|
|
3010
|
+
window.addEventListener("blur", end);
|
|
3011
|
+
window.addEventListener("keydown", onEscape, true);
|
|
3012
|
+
},
|
|
3013
|
+
[applyDom, commit, metrics.handles, metrics.root, orientation]
|
|
3014
|
+
);
|
|
3015
|
+
const onKey = React10.useCallback(
|
|
3016
|
+
(index, event) => {
|
|
3017
|
+
if (!KEYS.has(event.key)) return;
|
|
3018
|
+
const root = rootRef.current;
|
|
3019
|
+
const rtl = root ? getComputedStyle(root).direction === "rtl" : false;
|
|
3020
|
+
const next = applyKey(panelsRef.current, sizeRef.current, index, event.key, {
|
|
3021
|
+
orientation,
|
|
3022
|
+
shift: event.shiftKey,
|
|
3023
|
+
rtl,
|
|
3024
|
+
...readStep(root)
|
|
3025
|
+
});
|
|
3026
|
+
if (!next) return;
|
|
3027
|
+
event.preventDefault();
|
|
3028
|
+
commit(next);
|
|
3029
|
+
},
|
|
3030
|
+
[commit, orientation]
|
|
3031
|
+
);
|
|
3032
|
+
const onReset = React10.useCallback(
|
|
3033
|
+
(index) => {
|
|
3034
|
+
commit(resetToDefault(panelsRef.current, index));
|
|
3035
|
+
},
|
|
3036
|
+
[commit]
|
|
3037
|
+
);
|
|
3038
|
+
let handleIndex = 0;
|
|
3039
|
+
const content = parts.map((child) => {
|
|
3040
|
+
if (child.type !== SplitterHandle) return child;
|
|
3041
|
+
const index = handleIndex;
|
|
3042
|
+
handleIndex += 1;
|
|
3043
|
+
return React10.cloneElement(child, { index, key: child.key ?? `handle-${index}` });
|
|
3044
|
+
});
|
|
3045
|
+
const domId = React10.useCallback((id) => `${uid}-${id}`, [uid]);
|
|
3046
|
+
const context = React10.useMemo(
|
|
3047
|
+
() => ({ orientation, domId, panels: shown, pixels, contentSize, dragging, livePixels, onKey, onDragStart, onReset }),
|
|
3048
|
+
[orientation, domId, shown, pixels, contentSize, onKey, onDragStart, onReset]
|
|
3049
|
+
);
|
|
3050
|
+
return /* @__PURE__ */ jsx(SplitterContext.Provider, { value: context, children: /* @__PURE__ */ jsx(
|
|
3051
|
+
"div",
|
|
3052
|
+
{
|
|
3053
|
+
...props,
|
|
3054
|
+
ref: (node) => {
|
|
3055
|
+
rootRef.current = node;
|
|
3056
|
+
if (typeof ref === "function") ref(node);
|
|
3057
|
+
else if (ref) ref.current = node;
|
|
3058
|
+
},
|
|
3059
|
+
className: cn("Splitter", className),
|
|
3060
|
+
"data-orientation": orientation,
|
|
3061
|
+
children: content
|
|
3062
|
+
}
|
|
3063
|
+
) });
|
|
3064
|
+
});
|
|
3065
|
+
function withoutSize(props) {
|
|
3066
|
+
const dom = { ...props };
|
|
3067
|
+
delete dom.defaultSize;
|
|
3068
|
+
delete dom.minSize;
|
|
3069
|
+
delete dom.maxSize;
|
|
3070
|
+
delete dom.collapsible;
|
|
3071
|
+
delete dom.collapsedSize;
|
|
3072
|
+
delete dom.defaultCollapsed;
|
|
3073
|
+
delete dom.suspended;
|
|
3074
|
+
return dom;
|
|
3075
|
+
}
|
|
3076
|
+
var SplitterPanel = React10.forwardRef(function SplitterPanel2({ id, className, style, ...props }, ref) {
|
|
3077
|
+
const splitter = useSplitter();
|
|
3078
|
+
const index = splitter.panels.findIndex((panel2) => panel2.id === id);
|
|
3079
|
+
const panel = splitter.panels[index];
|
|
3080
|
+
const measured = splitter.contentSize > 0;
|
|
3081
|
+
const live = splitter.dragging.current ? splitter.livePixels.current : null;
|
|
3082
|
+
const pixels = live?.[index] ?? splitter.pixels[index] ?? 0;
|
|
3083
|
+
const collapsed = panel?.collapsed ?? false;
|
|
3084
|
+
const suspended = panel?.suspended ?? false;
|
|
3085
|
+
const hidden = (collapsed || suspended) && (measured ? pixels === 0 : (panel?.collapsedSize.value ?? 0) === 0);
|
|
3086
|
+
const size = measured ? `${pixels}px` : panel ? cssSize(panel) : "auto";
|
|
3087
|
+
return /* @__PURE__ */ jsx(
|
|
3088
|
+
"div",
|
|
3089
|
+
{
|
|
3090
|
+
...withoutSize(props),
|
|
3091
|
+
ref,
|
|
3092
|
+
id: splitter.domId(id),
|
|
3093
|
+
className: cn("SplitterPanel", className),
|
|
3094
|
+
"data-orientation": splitter.orientation,
|
|
3095
|
+
"data-collapsed": collapsed ? "" : void 0,
|
|
3096
|
+
"data-fill": panel?.fill ? "" : void 0,
|
|
3097
|
+
hidden: hidden || void 0,
|
|
3098
|
+
style: { ...style, ["--sl-splitter-panel-size"]: size }
|
|
3099
|
+
}
|
|
3100
|
+
);
|
|
3101
|
+
});
|
|
3102
|
+
var SplitterHandle = React10.forwardRef(function SplitterHandle2({ className, disabled, grip, index = 0, ...props }, ref) {
|
|
3103
|
+
const splitter = useSplitter();
|
|
3104
|
+
const targetIndex = handleTarget(splitter.panels, index);
|
|
3105
|
+
const target = splitter.panels[targetIndex];
|
|
3106
|
+
const live = splitter.dragging.current ? splitter.livePixels.current : null;
|
|
3107
|
+
const pixels = live?.[targetIndex] ?? splitter.pixels[targetIndex] ?? 0;
|
|
3108
|
+
const values = target ? ariaPercents(target, pixels, splitter.contentSize || 1) : { now: 0, min: 0, max: 100 };
|
|
3109
|
+
const collapsed = target?.collapsed ?? false;
|
|
3110
|
+
const bar = splitter.orientation === "horizontal" ? "vertical" : "horizontal";
|
|
3111
|
+
return (
|
|
3112
|
+
// The separator role does not list expanded. Collapsed still has to be announced
|
|
3113
|
+
// when valuetext is not spoken. Only set it on a panel that can collapse.
|
|
3114
|
+
// eslint-disable-next-line jsx-a11y/role-supports-aria-props
|
|
3115
|
+
/* @__PURE__ */ jsx(
|
|
3116
|
+
"div",
|
|
3117
|
+
{
|
|
3118
|
+
...props,
|
|
3119
|
+
ref,
|
|
3120
|
+
className: cn("SplitterHandle", className),
|
|
3121
|
+
role: "separator",
|
|
3122
|
+
tabIndex: disabled ? -1 : 0,
|
|
3123
|
+
"aria-orientation": bar,
|
|
3124
|
+
"aria-controls": target ? splitter.domId(target.id) : void 0,
|
|
3125
|
+
"aria-valuenow": values.now,
|
|
3126
|
+
"aria-valuemin": values.min,
|
|
3127
|
+
"aria-valuemax": values.max,
|
|
3128
|
+
"aria-valuetext": target ? valueText(target, pixels) : void 0,
|
|
3129
|
+
"aria-expanded": target?.collapsible ? !collapsed : void 0,
|
|
3130
|
+
"aria-disabled": disabled || void 0,
|
|
3131
|
+
"data-orientation": splitter.orientation,
|
|
3132
|
+
"data-collapsed": collapsed ? "" : void 0,
|
|
3133
|
+
"data-disabled": disabled ? "" : void 0,
|
|
3134
|
+
"data-grip": grip ? "" : void 0,
|
|
3135
|
+
onPointerDown: disabled ? void 0 : (event) => splitter.onDragStart(index, event),
|
|
3136
|
+
onKeyDown: disabled ? void 0 : (event) => splitter.onKey(index, event),
|
|
3137
|
+
onDoubleClick: disabled ? void 0 : () => splitter.onReset(index)
|
|
3138
|
+
}
|
|
3139
|
+
)
|
|
3140
|
+
);
|
|
3141
|
+
});
|
|
3142
|
+
var Splitter = {
|
|
3143
|
+
Root: SplitterRoot,
|
|
3144
|
+
Panel: SplitterPanel,
|
|
3145
|
+
Handle: SplitterHandle
|
|
3146
|
+
};
|
|
3147
|
+
var ListDetailContext = React10.createContext(null);
|
|
3148
|
+
function useListDetail() {
|
|
3149
|
+
const value = React10.useContext(ListDetailContext);
|
|
3150
|
+
if (!value) throw new Error("useListDetail must be inside ListDetail.Root.");
|
|
3151
|
+
return { layout: value.layout, close: () => value.requestClose("close-button") };
|
|
3152
|
+
}
|
|
3153
|
+
var FALLBACK = { detail: "45%", min: "35%", max: "65%", minWidth: 720 };
|
|
3154
|
+
function cssSize2(element, name, fallback) {
|
|
3155
|
+
const value = getComputedStyle(element).getPropertyValue(name).trim();
|
|
3156
|
+
return value || fallback;
|
|
3157
|
+
}
|
|
3158
|
+
function cssPx(element, name, fallback) {
|
|
3159
|
+
const value = parseFloat(getComputedStyle(element).getPropertyValue(name));
|
|
3160
|
+
return Number.isFinite(value) ? value : fallback;
|
|
3161
|
+
}
|
|
3162
|
+
function inOpenPopup(target, root) {
|
|
3163
|
+
if (!(target instanceof Element)) return false;
|
|
3164
|
+
const dialog = target.closest('[role="dialog"]');
|
|
3165
|
+
if (dialog && (!root || !root.contains(dialog))) return true;
|
|
3166
|
+
const popup = target.closest("[data-open]");
|
|
3167
|
+
if (!popup || popup === root) return false;
|
|
3168
|
+
if (root?.contains(popup) && popup.classList.contains("ListDetailDetail")) return false;
|
|
3169
|
+
return true;
|
|
3170
|
+
}
|
|
3171
|
+
var ListDetailRoot = React10.forwardRef(function ListDetailRoot2({
|
|
3172
|
+
open,
|
|
3173
|
+
onOpenChange,
|
|
3174
|
+
narrow = "drawer",
|
|
3175
|
+
splitMinWidth,
|
|
3176
|
+
storageKey,
|
|
3177
|
+
defaultDetailSize,
|
|
3178
|
+
minDetailSize,
|
|
3179
|
+
maxDetailSize,
|
|
3180
|
+
handleLabel = "Resize details",
|
|
3181
|
+
listLabel,
|
|
3182
|
+
className,
|
|
3183
|
+
children,
|
|
3184
|
+
onKeyDown,
|
|
3185
|
+
...props
|
|
3186
|
+
}, ref) {
|
|
3187
|
+
const rootRef = React10.useRef(null);
|
|
3188
|
+
const listRef = React10.useRef(null);
|
|
3189
|
+
const headingRef = React10.useRef(null);
|
|
3190
|
+
const pendingFocus = React10.useRef(false);
|
|
3191
|
+
const wasOpen = React10.useRef(false);
|
|
3192
|
+
const previousFocus = React10.useRef(null);
|
|
3193
|
+
const restoreFrame = React10.useRef(0);
|
|
3194
|
+
const closeLocked = React10.useRef(false);
|
|
3195
|
+
const layoutRef = React10.useRef(null);
|
|
3196
|
+
const titleId = React10.useId();
|
|
3197
|
+
const [wide, setWide] = React10.useState(null);
|
|
3198
|
+
const [endSide, setEndSide] = React10.useState("right");
|
|
3199
|
+
const [pointerLocked, setPointerLocked] = React10.useState(false);
|
|
3200
|
+
const [header, setHeader] = React10.useState(null);
|
|
3201
|
+
const [sizes, setSizes] = React10.useState(FALLBACK);
|
|
3202
|
+
const [detailContainer, setDetailContainer] = React10.useState(null);
|
|
3203
|
+
const [hosts, setHosts] = React10.useState({ split: null, drawer: null, page: null });
|
|
3204
|
+
const measured = wide !== null;
|
|
3205
|
+
const layout = wide === false ? narrow : "split";
|
|
3206
|
+
const splitOpen = measured && wide === true && open;
|
|
3207
|
+
let listChild = null;
|
|
3208
|
+
let detailChild = null;
|
|
3209
|
+
const visit = (node) => {
|
|
3210
|
+
React10.Children.forEach(node, (child) => {
|
|
3211
|
+
if (!React10.isValidElement(child)) return;
|
|
3212
|
+
if (child.type === React10.Fragment) {
|
|
3213
|
+
visit(child.props.children);
|
|
3214
|
+
return;
|
|
3215
|
+
}
|
|
3216
|
+
if (child.type === ListDetailList) listChild = child;
|
|
3217
|
+
else if (child.type === ListDetailDetail) detailChild = child;
|
|
3218
|
+
});
|
|
3219
|
+
};
|
|
3220
|
+
visit(children);
|
|
3221
|
+
const requestClose = React10.useCallback(
|
|
3222
|
+
(reason) => {
|
|
3223
|
+
if (!open || closeLocked.current) return;
|
|
3224
|
+
onOpenChange?.(false, reason);
|
|
3225
|
+
},
|
|
3226
|
+
[onOpenChange, open]
|
|
3227
|
+
);
|
|
3228
|
+
React10.useEffect(() => {
|
|
3229
|
+
const node = document.createElement("div");
|
|
3230
|
+
node.className = "ListDetailSlot";
|
|
3231
|
+
setDetailContainer(node);
|
|
3232
|
+
return () => node.remove();
|
|
3233
|
+
}, []);
|
|
3234
|
+
React10.useLayoutEffect(() => {
|
|
3235
|
+
const root = rootRef.current;
|
|
3236
|
+
if (!root) return;
|
|
3237
|
+
const apply = () => {
|
|
3238
|
+
const min = splitMinWidth ?? cssPx(root, "--sl-list-detail-split-min-width", FALLBACK.minWidth);
|
|
3239
|
+
setWide(root.clientWidth >= min);
|
|
3240
|
+
setEndSide(getComputedStyle(root).direction === "rtl" ? "left" : "right");
|
|
3241
|
+
const next = {
|
|
3242
|
+
minWidth: min,
|
|
3243
|
+
detail: defaultDetailSize ?? cssSize2(root, "--sl-list-detail-detail-default-size", FALLBACK.detail),
|
|
3244
|
+
min: minDetailSize ?? cssSize2(root, "--sl-list-detail-detail-min-size", FALLBACK.min),
|
|
3245
|
+
max: maxDetailSize ?? cssSize2(root, "--sl-list-detail-detail-max-size", FALLBACK.max)
|
|
3246
|
+
};
|
|
3247
|
+
setSizes((current) => current.detail === next.detail && current.min === next.min && current.max === next.max && current.minWidth === next.minWidth ? current : next);
|
|
3248
|
+
};
|
|
3249
|
+
apply();
|
|
3250
|
+
const observer = new ResizeObserver(apply);
|
|
3251
|
+
observer.observe(root);
|
|
3252
|
+
return () => observer.disconnect();
|
|
3253
|
+
}, [defaultDetailSize, maxDetailSize, minDetailSize, splitMinWidth]);
|
|
3254
|
+
const placeSplit = React10.useCallback((node) => {
|
|
3255
|
+
setHosts((current) => current.split === node ? current : { ...current, split: node });
|
|
3256
|
+
}, []);
|
|
3257
|
+
const placeDrawer = React10.useCallback((node) => {
|
|
3258
|
+
setHosts((current) => current.drawer === node ? current : { ...current, drawer: node });
|
|
3259
|
+
}, []);
|
|
3260
|
+
const placePage = React10.useCallback((node) => {
|
|
3261
|
+
setHosts((current) => current.page === node ? current : { ...current, page: node });
|
|
3262
|
+
}, []);
|
|
3263
|
+
React10.useLayoutEffect(() => {
|
|
3264
|
+
if (!detailContainer) return;
|
|
3265
|
+
const host = layout === "drawer" && open ? hosts.drawer : layout === "page" && open ? hosts.page : hosts.split;
|
|
3266
|
+
if (host && detailContainer.parentNode !== host) host.appendChild(detailContainer);
|
|
3267
|
+
const previous = layoutRef.current;
|
|
3268
|
+
const changed = previous != null && previous !== layout;
|
|
3269
|
+
layoutRef.current = measured ? layout : null;
|
|
3270
|
+
if (!open || !changed || !measured) return;
|
|
3271
|
+
const active = document.activeElement;
|
|
3272
|
+
const inside = detailContainer.contains(active);
|
|
3273
|
+
const onBody = active === document.body || active === document.documentElement;
|
|
3274
|
+
if (!inside && !onBody) return;
|
|
3275
|
+
if (layout === "drawer") {
|
|
3276
|
+
pendingFocus.current = true;
|
|
3277
|
+
return;
|
|
3278
|
+
}
|
|
3279
|
+
headingRef.current?.focus();
|
|
3280
|
+
}, [detailContainer, hosts, layout, measured, open]);
|
|
3281
|
+
React10.useEffect(() => {
|
|
3282
|
+
if (open && !wasOpen.current) {
|
|
3283
|
+
cancelAnimationFrame(restoreFrame.current);
|
|
3284
|
+
previousFocus.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
3285
|
+
if (layout !== "drawer" && headingRef.current) {
|
|
3286
|
+
headingRef.current.focus();
|
|
3287
|
+
pendingFocus.current = false;
|
|
3288
|
+
} else pendingFocus.current = true;
|
|
3289
|
+
}
|
|
3290
|
+
if (!open && wasOpen.current) {
|
|
3291
|
+
const back = previousFocus.current;
|
|
3292
|
+
const list = listRef.current;
|
|
3293
|
+
cancelAnimationFrame(restoreFrame.current);
|
|
3294
|
+
restoreFrame.current = requestAnimationFrame(() => {
|
|
3295
|
+
if (back?.isConnected) back.focus();
|
|
3296
|
+
else list?.focus();
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
wasOpen.current = open;
|
|
3300
|
+
}, [layout, open]);
|
|
3301
|
+
const context = React10.useMemo(
|
|
3302
|
+
() => ({
|
|
3303
|
+
open,
|
|
3304
|
+
layout,
|
|
3305
|
+
listLabel,
|
|
3306
|
+
titleId,
|
|
3307
|
+
detailContainer,
|
|
3308
|
+
listRef,
|
|
3309
|
+
headingRef,
|
|
3310
|
+
pendingFocus,
|
|
3311
|
+
closeLocked,
|
|
3312
|
+
setPointerLocked,
|
|
3313
|
+
setHeader,
|
|
3314
|
+
requestClose
|
|
3315
|
+
}),
|
|
3316
|
+
[detailContainer, layout, listLabel, open, requestClose, titleId]
|
|
3317
|
+
);
|
|
3318
|
+
const pageShowing = measured && layout === "page" && open;
|
|
3319
|
+
return /* @__PURE__ */ jsx(ListDetailContext.Provider, { value: context, children: /* @__PURE__ */ jsxs(
|
|
3320
|
+
"div",
|
|
3321
|
+
{
|
|
3322
|
+
...props,
|
|
3323
|
+
ref: (node) => {
|
|
3324
|
+
rootRef.current = node;
|
|
3325
|
+
if (typeof ref === "function") ref(node);
|
|
3326
|
+
else if (ref) ref.current = node;
|
|
3327
|
+
},
|
|
3328
|
+
className: cn("ListDetail", className),
|
|
3329
|
+
"data-layout": measured ? layout : void 0,
|
|
3330
|
+
"data-open": open ? "" : void 0,
|
|
3331
|
+
onKeyDown: (event) => {
|
|
3332
|
+
onKeyDown?.(event);
|
|
3333
|
+
if (event.key !== "Escape" || event.defaultPrevented || !open || layout === "drawer") return;
|
|
3334
|
+
if (inOpenPopup(event.target, rootRef.current)) return;
|
|
3335
|
+
if (closeLocked.current) return;
|
|
3336
|
+
event.preventDefault();
|
|
3337
|
+
requestClose("escape");
|
|
3338
|
+
},
|
|
3339
|
+
children: [
|
|
3340
|
+
/* @__PURE__ */ jsxs(
|
|
3341
|
+
Splitter.Root,
|
|
3342
|
+
{
|
|
3343
|
+
className: "ListDetailSplit",
|
|
3344
|
+
orientation: "horizontal",
|
|
3345
|
+
storageKey,
|
|
3346
|
+
hidden: pageShowing ? true : void 0,
|
|
3347
|
+
children: [
|
|
3348
|
+
/* @__PURE__ */ jsx(Splitter.Panel, { id: "list", className: "ListDetailListPanel", children: /* @__PURE__ */ jsx("div", { className: "ListDetailHost", children: listChild }) }),
|
|
3349
|
+
splitOpen ? /* @__PURE__ */ jsx(Splitter.Handle, { "aria-label": handleLabel }) : null,
|
|
3350
|
+
/* @__PURE__ */ jsx(
|
|
3351
|
+
Splitter.Panel,
|
|
3352
|
+
{
|
|
3353
|
+
id: "detail",
|
|
3354
|
+
className: "ListDetailDetailPanel",
|
|
3355
|
+
defaultSize: sizes.detail,
|
|
3356
|
+
minSize: sizes.min,
|
|
3357
|
+
maxSize: sizes.max,
|
|
3358
|
+
collapsedSize: "0px",
|
|
3359
|
+
suspended: !splitOpen,
|
|
3360
|
+
children: /* @__PURE__ */ jsx("div", { ref: placeSplit, className: "ListDetailHost" })
|
|
3361
|
+
}
|
|
3362
|
+
)
|
|
3363
|
+
]
|
|
3364
|
+
}
|
|
3365
|
+
),
|
|
3366
|
+
measured && layout === "drawer" ? /* @__PURE__ */ jsx(
|
|
3367
|
+
Drawer.Root,
|
|
3368
|
+
{
|
|
3369
|
+
open,
|
|
3370
|
+
modal: true,
|
|
3371
|
+
swipeDirection: endSide,
|
|
3372
|
+
disablePointerDismissal: pointerLocked,
|
|
3373
|
+
onOpenChange: (next, details) => {
|
|
3374
|
+
if (next) return;
|
|
3375
|
+
if (closeLocked.current) {
|
|
3376
|
+
details.cancel();
|
|
3377
|
+
return;
|
|
3378
|
+
}
|
|
3379
|
+
requestClose(details.reason === "escape-key" ? "escape" : "close-button");
|
|
3380
|
+
},
|
|
3381
|
+
onOpenChangeComplete: (next) => {
|
|
3382
|
+
if (!next || !pendingFocus.current) return;
|
|
3383
|
+
headingRef.current?.focus();
|
|
3384
|
+
pendingFocus.current = false;
|
|
3385
|
+
},
|
|
3386
|
+
children: /* @__PURE__ */ jsxs(Drawer.Portal, { children: [
|
|
3387
|
+
/* @__PURE__ */ jsx(Drawer.Backdrop, {}),
|
|
3388
|
+
/* @__PURE__ */ jsx(Drawer.Viewport, { children: /* @__PURE__ */ jsxs(Drawer.Popup, { children: [
|
|
3389
|
+
header ? /* @__PURE__ */ jsxs("header", { className: "ListDetailHeader", children: [
|
|
3390
|
+
/* @__PURE__ */ jsx(
|
|
3391
|
+
Drawer.Title,
|
|
3392
|
+
{
|
|
3393
|
+
id: titleId,
|
|
3394
|
+
render: React10.createElement(`h${header.headingLevel}`),
|
|
3395
|
+
ref: (node) => {
|
|
3396
|
+
headingRef.current = node;
|
|
3397
|
+
},
|
|
3398
|
+
tabIndex: -1,
|
|
3399
|
+
children: header.title
|
|
3400
|
+
}
|
|
3401
|
+
),
|
|
3402
|
+
header.actions ? /* @__PURE__ */ jsx("div", { className: "ListDetailActions", children: header.actions }) : null,
|
|
3403
|
+
/* @__PURE__ */ jsx(Drawer.Close, { "aria-label": header.closeLabel, disabled: header.closeDisabled, "data-emphasis": "quiet", children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "close" }) })
|
|
3404
|
+
] }) : null,
|
|
3405
|
+
/* @__PURE__ */ jsx("div", { ref: placeDrawer, className: "ListDetailHost" })
|
|
3406
|
+
] }) })
|
|
3407
|
+
] })
|
|
3408
|
+
}
|
|
3409
|
+
) : null,
|
|
3410
|
+
pageShowing ? /* @__PURE__ */ jsx("div", { ref: placePage, className: "ListDetailHost ListDetailPage" }) : null,
|
|
3411
|
+
detailChild
|
|
3412
|
+
]
|
|
3413
|
+
}
|
|
3414
|
+
) });
|
|
3415
|
+
});
|
|
3416
|
+
var ListDetailList = React10.forwardRef(function ListDetailList2({ className, children, ...props }, ref) {
|
|
3417
|
+
const detail = React10.useContext(ListDetailContext);
|
|
3418
|
+
return /* @__PURE__ */ jsx(
|
|
3419
|
+
"div",
|
|
3420
|
+
{
|
|
3421
|
+
...props,
|
|
3422
|
+
ref: (node) => {
|
|
3423
|
+
if (detail) detail.listRef.current = node;
|
|
3424
|
+
if (typeof ref === "function") ref(node);
|
|
3425
|
+
else if (ref) ref.current = node;
|
|
3426
|
+
},
|
|
3427
|
+
className: cn("ListDetailList", className),
|
|
3428
|
+
role: detail?.listLabel ? "region" : void 0,
|
|
3429
|
+
"aria-label": detail?.listLabel,
|
|
3430
|
+
tabIndex: -1,
|
|
3431
|
+
children
|
|
3432
|
+
}
|
|
3433
|
+
);
|
|
3434
|
+
});
|
|
3435
|
+
var ListDetailDetail = React10.forwardRef(function ListDetailDetail2({
|
|
3436
|
+
title,
|
|
3437
|
+
actions,
|
|
3438
|
+
closeDisabled = false,
|
|
3439
|
+
closeLabel = "Close",
|
|
3440
|
+
backLabel = "Back",
|
|
3441
|
+
headingLevel = 2,
|
|
3442
|
+
className,
|
|
3443
|
+
children,
|
|
3444
|
+
...props
|
|
3445
|
+
}, ref) {
|
|
3446
|
+
const detail = React10.useContext(ListDetailContext);
|
|
3447
|
+
const setLocked = detail?.setPointerLocked;
|
|
3448
|
+
const setHeader = detail?.setHeader;
|
|
3449
|
+
const closeLocked = detail?.closeLocked;
|
|
3450
|
+
React10.useLayoutEffect(() => {
|
|
3451
|
+
if (closeLocked) closeLocked.current = closeDisabled;
|
|
3452
|
+
setLocked?.(closeDisabled);
|
|
3453
|
+
}, [closeDisabled, closeLocked, setLocked]);
|
|
3454
|
+
React10.useLayoutEffect(() => {
|
|
3455
|
+
setHeader?.((current) => {
|
|
3456
|
+
if (current && current.title === title && current.actions === actions && current.closeDisabled === closeDisabled && current.closeLabel === closeLabel && current.headingLevel === headingLevel) {
|
|
3457
|
+
return current;
|
|
3458
|
+
}
|
|
3459
|
+
return { title, actions, closeDisabled, closeLabel, headingLevel };
|
|
3460
|
+
});
|
|
3461
|
+
}, [actions, closeDisabled, closeLabel, headingLevel, setHeader, title]);
|
|
3462
|
+
React10.useLayoutEffect(() => {
|
|
3463
|
+
if (!detail || detail.layout === "drawer" || !detail.pendingFocus.current || !detail.headingRef.current) return;
|
|
3464
|
+
detail.headingRef.current.focus();
|
|
3465
|
+
detail.pendingFocus.current = false;
|
|
3466
|
+
});
|
|
3467
|
+
if (!detail?.open || !detail.detailContainer) return null;
|
|
3468
|
+
const Title3 = `h${headingLevel}`;
|
|
3469
|
+
const { layout, titleId, headingRef, requestClose, detailContainer } = detail;
|
|
3470
|
+
return createPortal(
|
|
3471
|
+
/* @__PURE__ */ jsxs("section", { ...props, ref, className: cn("ListDetailDetail", className), "data-layout": layout, "aria-labelledby": titleId, children: [
|
|
3472
|
+
layout === "drawer" ? null : /* @__PURE__ */ jsxs("header", { className: "ListDetailHeader", children: [
|
|
3473
|
+
layout === "page" ? /* @__PURE__ */ jsxs(Button, { emphasis: "quiet", disabled: closeDisabled, onClick: () => requestClose("back"), children: [
|
|
3474
|
+
/* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "arrow_back" }),
|
|
3475
|
+
backLabel
|
|
3476
|
+
] }) : null,
|
|
3477
|
+
/* @__PURE__ */ jsx(
|
|
3478
|
+
Title3,
|
|
3479
|
+
{
|
|
3480
|
+
id: titleId,
|
|
3481
|
+
className: "ListDetailTitle",
|
|
3482
|
+
ref: (node) => {
|
|
3483
|
+
headingRef.current = node;
|
|
3484
|
+
},
|
|
3485
|
+
tabIndex: -1,
|
|
3486
|
+
children: title
|
|
3487
|
+
}
|
|
3488
|
+
),
|
|
3489
|
+
actions ? /* @__PURE__ */ jsx("div", { className: "ListDetailActions", children: actions }) : null,
|
|
3490
|
+
layout === "page" ? null : /* @__PURE__ */ jsx(Button, { emphasis: "quiet", "aria-label": closeLabel, disabled: closeDisabled, onClick: () => requestClose("close-button"), children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "close" }) })
|
|
3491
|
+
] }),
|
|
3492
|
+
/* @__PURE__ */ jsx("div", { className: "ListDetailBody", children })
|
|
3493
|
+
] }),
|
|
3494
|
+
detailContainer
|
|
3495
|
+
);
|
|
3496
|
+
});
|
|
3497
|
+
var ListDetail = {
|
|
3498
|
+
Root: ListDetailRoot,
|
|
3499
|
+
List: ListDetailList,
|
|
3500
|
+
Detail: ListDetailDetail
|
|
3501
|
+
};
|
|
3502
|
+
var EditableListContext = React10.createContext(null);
|
|
3503
|
+
function slug(value) {
|
|
3504
|
+
return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "field";
|
|
3505
|
+
}
|
|
3506
|
+
function useEditable() {
|
|
3507
|
+
const value = React10.useContext(EditableListContext);
|
|
3508
|
+
if (!value) throw new Error("EditableList parts must be inside EditableList.Root");
|
|
3509
|
+
return value;
|
|
3510
|
+
}
|
|
3511
|
+
function EditableListRoot({
|
|
3512
|
+
value,
|
|
3513
|
+
defaultValue,
|
|
3514
|
+
onValueChange,
|
|
3515
|
+
getRowId,
|
|
3516
|
+
createRow,
|
|
3517
|
+
minRows,
|
|
3518
|
+
maxRows,
|
|
3519
|
+
label,
|
|
3520
|
+
itemLabel,
|
|
3521
|
+
name,
|
|
3522
|
+
fields,
|
|
3523
|
+
locale,
|
|
3524
|
+
rowError,
|
|
3525
|
+
children,
|
|
3526
|
+
className
|
|
3527
|
+
}) {
|
|
3528
|
+
const prefix = React10.useId();
|
|
3529
|
+
const fullId = `${prefix}-full`;
|
|
3530
|
+
const errorId = `${prefix}-error`;
|
|
3531
|
+
const [uncontrolled, setUncontrolled] = React10.useState(defaultValue ?? []);
|
|
3532
|
+
const rows = value ?? uncontrolled;
|
|
3533
|
+
const setRows = (next) => {
|
|
3534
|
+
onValueChange?.(next);
|
|
3535
|
+
if (value === void 0) setUncontrolled(next);
|
|
3536
|
+
};
|
|
3537
|
+
const rowName = itemLabel ?? label;
|
|
3538
|
+
const [columns, setColumns] = React10.useState([]);
|
|
3539
|
+
const [removed, setRemoved] = React10.useState("");
|
|
3540
|
+
const [balanced, setBalanced] = React10.useState(false);
|
|
3541
|
+
const [focusAt, setFocusAt] = React10.useState(null);
|
|
3542
|
+
const stack = useUnderWidth("--sl-collection-editable-list-stack-width");
|
|
3543
|
+
const updateRow = (index, patch) => {
|
|
3544
|
+
setRows(rows.map((row, rowIndex) => rowIndex === index ? { ...row, ...patch } : row));
|
|
3545
|
+
};
|
|
3546
|
+
const removeRow = (index) => {
|
|
3547
|
+
if (minRows != null && rows.length <= minRows) return;
|
|
3548
|
+
const next = rows.filter((_, rowIndex) => rowIndex !== index);
|
|
3549
|
+
setRemoved("");
|
|
3550
|
+
setRows(next);
|
|
3551
|
+
window.requestAnimationFrame(() => setRemoved(`${rowName} ${index + 1} removed`));
|
|
3552
|
+
setFocusAt(next.length === 0 ? "add" : Math.min(index, next.length - 1));
|
|
3553
|
+
};
|
|
3554
|
+
const addRow = () => {
|
|
3555
|
+
if (maxRows != null && rows.length >= maxRows) return;
|
|
3556
|
+
setRows([...rows, createRow()]);
|
|
3557
|
+
setFocusAt(rows.length);
|
|
3558
|
+
};
|
|
3559
|
+
const context = {
|
|
3560
|
+
rows,
|
|
3561
|
+
label,
|
|
3562
|
+
getRowId,
|
|
3563
|
+
itemLabel: rowName,
|
|
3564
|
+
locale: locale || (typeof document === "undefined" ? "en" : document.documentElement.lang) || "en",
|
|
3565
|
+
prefix,
|
|
3566
|
+
fullId,
|
|
3567
|
+
name,
|
|
3568
|
+
minRows,
|
|
3569
|
+
maxRows,
|
|
3570
|
+
updateRow,
|
|
3571
|
+
removeRow,
|
|
3572
|
+
addRow,
|
|
3573
|
+
columns,
|
|
3574
|
+
setColumns,
|
|
3575
|
+
errorId,
|
|
3576
|
+
rowError,
|
|
3577
|
+
balanced,
|
|
3578
|
+
setBalanced,
|
|
3579
|
+
focusAt,
|
|
3580
|
+
clearFocus: () => setFocusAt(null)
|
|
3581
|
+
};
|
|
3582
|
+
return /* @__PURE__ */ jsx(EditableListContext.Provider, { value: context, children: /* @__PURE__ */ jsxs("div", { ref: stack.ref, className: cn("EditableList", className), "data-label": label, "data-balanced": balanced ? "" : void 0, "data-stacked": stack.under ? "" : void 0, children: [
|
|
3583
|
+
/* @__PURE__ */ jsx("table", { className: "Table EditableListTable", "aria-describedby": `${fullId} ${errorId}`, children }),
|
|
3584
|
+
/* @__PURE__ */ jsx("div", { className: "visually-hidden", "aria-live": "polite", children: removed }),
|
|
3585
|
+
name ? rows.flatMap(
|
|
3586
|
+
(row, index) => Object.entries(row).filter(([field, fieldValue]) => {
|
|
3587
|
+
if (fields) return fields.includes(field);
|
|
3588
|
+
if (field === "id") return false;
|
|
3589
|
+
return fieldValue == null || ["string", "number", "boolean"].includes(typeof fieldValue);
|
|
3590
|
+
}).map(([field, fieldValue]) => /* @__PURE__ */ jsx("input", { type: "hidden", name: `${name}[${index}].${field}`, value: fieldValue == null ? "" : String(fieldValue) }, `${getRowId(row)}-${field}`))
|
|
3591
|
+
) : null,
|
|
3592
|
+
/* @__PURE__ */ jsx(FocusBridge, {})
|
|
3593
|
+
] }) });
|
|
3594
|
+
}
|
|
3595
|
+
function FocusBridge() {
|
|
3596
|
+
const list = useEditable();
|
|
3597
|
+
const ref = React10.useRef(null);
|
|
3598
|
+
React10.useLayoutEffect(() => {
|
|
3599
|
+
if (list.focusAt == null) return;
|
|
3600
|
+
const root = ref.current?.closest(".EditableList");
|
|
3601
|
+
if (!root) return;
|
|
3602
|
+
const target = list.focusAt === "add" ? root.querySelector(".EditableListAdd button") : root.querySelectorAll("tbody tr")[list.focusAt]?.querySelector("input, select, textarea, button");
|
|
3603
|
+
target?.focus();
|
|
3604
|
+
list.clearFocus();
|
|
3605
|
+
});
|
|
3606
|
+
return /* @__PURE__ */ jsx("div", { ref, hidden: true });
|
|
3607
|
+
}
|
|
3608
|
+
function EditableListHeader({ columns }) {
|
|
3609
|
+
const list = useEditable();
|
|
3610
|
+
React10.useEffect(() => {
|
|
3611
|
+
list.setColumns(columns);
|
|
3612
|
+
}, [columns.join("|")]);
|
|
3613
|
+
return /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
3614
|
+
columns.map((column) => /* @__PURE__ */ jsx("th", { scope: "col", id: `${list.prefix}-${slug(column)}`, children: column }, column)),
|
|
3615
|
+
/* @__PURE__ */ jsx("th", { scope: "col", children: /* @__PURE__ */ jsx("span", { className: "visually-hidden", children: "Remove" }) })
|
|
3616
|
+
] }) });
|
|
3617
|
+
}
|
|
3618
|
+
function EditableListRows({ children }) {
|
|
3619
|
+
const list = useEditable();
|
|
3620
|
+
return /* @__PURE__ */ jsx("tbody", { children: list.rows.map((row, index) => /* @__PURE__ */ jsx(RowScope, { index, children: children(row, index, { index, update: (patch) => list.updateRow(index, patch), remove: () => list.removeRow(index) }) }, list.getRowId(row))) });
|
|
3621
|
+
}
|
|
3622
|
+
var RowIndex = React10.createContext(0);
|
|
3623
|
+
function RowScope({ index, children }) {
|
|
3624
|
+
return /* @__PURE__ */ jsx(RowIndex.Provider, { value: index, children });
|
|
3625
|
+
}
|
|
3626
|
+
function EditableListRow({ children }) {
|
|
3627
|
+
const list = useEditable();
|
|
3628
|
+
const index = React10.useContext(RowIndex);
|
|
3629
|
+
const message = list.rowError?.(list.rows[index]) ?? null;
|
|
3630
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3631
|
+
/* @__PURE__ */ jsx("tr", { "data-invalid": message ? "" : void 0, "data-row": index + 1, children }),
|
|
3632
|
+
message ? /* @__PURE__ */ jsx("tr", { children: /* @__PURE__ */ jsx("td", { colSpan: Math.max(list.columns.length, 1), children: /* @__PURE__ */ jsx(Field.Error, { id: `${list.prefix}-row-${index}-error`, children: message }) }) }) : null
|
|
3633
|
+
] });
|
|
3634
|
+
}
|
|
3635
|
+
function useEditableListField(column) {
|
|
3636
|
+
const list = useEditable();
|
|
3637
|
+
const index = React10.useContext(RowIndex);
|
|
3638
|
+
const id = `${list.prefix}-${slug(column)}-${index}`;
|
|
3639
|
+
return { id, "aria-labelledby": `${list.prefix}-${slug(column)} ${list.prefix}-row-${index}` };
|
|
3640
|
+
}
|
|
3641
|
+
function EditableListCell({ column, align, children }) {
|
|
3642
|
+
const list = useEditable();
|
|
3643
|
+
const index = React10.useContext(RowIndex);
|
|
3644
|
+
const field = useEditableListField(column);
|
|
3645
|
+
const first = list.columns[0] === column;
|
|
3646
|
+
return /* @__PURE__ */ jsxs("td", { "data-align": align, children: [
|
|
3647
|
+
first ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3648
|
+
/* @__PURE__ */ jsxs("span", { className: "EditableListStackedTitle", children: [
|
|
3649
|
+
list.itemLabel,
|
|
3650
|
+
" ",
|
|
3651
|
+
index + 1
|
|
3652
|
+
] }),
|
|
3653
|
+
/* @__PURE__ */ jsxs("span", { className: "visually-hidden", id: `${list.prefix}-row-${index}`, children: [
|
|
3654
|
+
list.itemLabel,
|
|
3655
|
+
" ",
|
|
3656
|
+
index + 1
|
|
3657
|
+
] })
|
|
3658
|
+
] }) : null,
|
|
3659
|
+
/* @__PURE__ */ jsx("label", { className: "EditableListFieldLabel", htmlFor: field.id, children: column }),
|
|
3660
|
+
children
|
|
3661
|
+
] });
|
|
3662
|
+
}
|
|
3663
|
+
function EditableListRemoveButton() {
|
|
3664
|
+
const list = useEditable();
|
|
3665
|
+
const index = React10.useContext(RowIndex);
|
|
3666
|
+
const hidden = list.minRows != null && list.rows.length <= list.minRows;
|
|
3667
|
+
if (hidden) return /* @__PURE__ */ jsx("td", {});
|
|
3668
|
+
return /* @__PURE__ */ jsx("td", { children: /* @__PURE__ */ jsx(Button, { emphasis: "quiet", "data-icon-only": true, "aria-label": `Remove ${list.itemLabel} ${index + 1}`, onClick: () => list.removeRow(index), children: /* @__PURE__ */ jsx("span", { className: "material-symbols-outlined", "aria-hidden": "true", children: "close" }) }) });
|
|
3669
|
+
}
|
|
3670
|
+
function EditableListAddButton({ children }) {
|
|
3671
|
+
const list = useEditable();
|
|
3672
|
+
const atMax = list.maxRows != null && list.rows.length >= list.maxRows;
|
|
3673
|
+
return /* @__PURE__ */ jsxs("div", { className: "EditableListAdd", children: [
|
|
3674
|
+
/* @__PURE__ */ jsx(Button, { emphasis: "standard", disabled: atMax, focusableWhenDisabled: atMax, onClick: list.addRow, "aria-describedby": atMax ? list.fullId : void 0, children }),
|
|
3675
|
+
atMax ? /* @__PURE__ */ jsx("p", { id: list.fullId, children: "The list is full." }) : null
|
|
3676
|
+
] });
|
|
3677
|
+
}
|
|
3678
|
+
function EditableListError({ children }) {
|
|
3679
|
+
const list = useEditable();
|
|
3680
|
+
return /* @__PURE__ */ jsx("p", { id: list.errorId, className: "EditableListError", role: "alert", children });
|
|
3681
|
+
}
|
|
3682
|
+
function EditableListBalance({ control, currency, sum }) {
|
|
3683
|
+
const list = useEditable();
|
|
3684
|
+
const math = balanceOf(sum(list.rows), control, currency);
|
|
3685
|
+
const digits = math.digits;
|
|
3686
|
+
const allocated = formatMoney(minorToDecimal(math.allocated, digits), { locale: list.locale, currency });
|
|
3687
|
+
const target = formatMoney(minorToDecimal(math.control, digits), { locale: list.locale, currency });
|
|
3688
|
+
const delta = formatMoney(minorToDecimal(math.delta < 0n ? -math.delta : math.delta, digits), { locale: list.locale, currency });
|
|
3689
|
+
const word = math.tone === "balanced" ? "balanced" : math.tone === "under" ? `${delta} left` : `${delta} over`;
|
|
3690
|
+
const [announced, setAnnounced] = React10.useState(word);
|
|
3691
|
+
React10.useEffect(() => {
|
|
3692
|
+
list.setBalanced(math.tone === "balanced");
|
|
3693
|
+
}, [math.tone]);
|
|
3694
|
+
React10.useEffect(() => {
|
|
3695
|
+
const timer = window.setTimeout(() => setAnnounced(word), 200);
|
|
3696
|
+
return () => window.clearTimeout(timer);
|
|
3697
|
+
}, [word]);
|
|
3698
|
+
return /* @__PURE__ */ jsxs("p", { className: "EditableListBalance", "data-color": math.tone === "balanced" ? "success" : math.tone === "over" ? "error" : "warning", "data-balanced": math.tone === "balanced" ? "" : void 0, children: [
|
|
3699
|
+
"Allocated ",
|
|
3700
|
+
allocated,
|
|
3701
|
+
" of ",
|
|
3702
|
+
target,
|
|
3703
|
+
" \xB7 ",
|
|
3704
|
+
word,
|
|
3705
|
+
/* @__PURE__ */ jsx("span", { className: "visually-hidden", "aria-live": "polite", children: announced })
|
|
3706
|
+
] });
|
|
3707
|
+
}
|
|
3708
|
+
var EditableList = {
|
|
3709
|
+
useField: useEditableListField,
|
|
3710
|
+
Root: EditableListRoot,
|
|
3711
|
+
Header: EditableListHeader,
|
|
3712
|
+
Rows: EditableListRows,
|
|
3713
|
+
Row: EditableListRow,
|
|
3714
|
+
Cell: EditableListCell,
|
|
3715
|
+
RemoveButton: EditableListRemoveButton,
|
|
3716
|
+
AddButton: EditableListAddButton,
|
|
3717
|
+
Error: EditableListError,
|
|
3718
|
+
Balance: EditableListBalance
|
|
3719
|
+
};
|
|
3720
|
+
|
|
3721
|
+
export { Badge, CURRENCY_SEARCH_THRESHOLD, Calendar, CurrencySelect, DatePicker, DatePresets, Drawer, EditableList, Field, ListDetail, ListDetailDetail, ListDetailList, ListDetailRoot, MAX_MINOR_UNITS, MoneyField, MoneyFieldAffix, MoneyFieldGroup, MoneyFieldInput, MoneyFieldRoot, NumberField, Popover, Splitter, SplitterHandle, SplitterPanel, SplitterRoot, addDays, addMonths, affixSide, applyKey, applyStored, ariaPercents, builtinPresets, clampDate, createPanels, cssSize, currencyName, currencySymbol, daysInMonth, defaultPresets, defaultWeekStartsOn, endOfWeek, formatDay, formatFull, formatMoney2 as formatMoney, fromMinorUnits, getCurrencyDigits, handleTarget, inclusiveDays, isValidCurrency, isoWeek, last30Days, last7Days, lastMonth, lastWeek, layoutOf, lengthBlockReason, measure, monthCells, moveFocusDate, nextMonth, nextWeek, normalizePaste, orderRange, parseDate, parseDecimalString, parseSize, presetInBounds, resetToDefault, resizeByDelta, resolvePreset, roundHalfExpand, roundingChangesValue, sameSelection, serializeSize, settle, sizeText, startOfWeek, syncPanels, thisMonth, thisQuarter, thisWeek, toDecimalString, toMinorUnits, today, todayDate, toggleCollapsed, tomorrow, useListDetail, valueText, yearToDate, yesterday };
|
|
3722
|
+
//# sourceMappingURL=chunk-JUUPOOQQ.js.map
|
|
3723
|
+
//# sourceMappingURL=chunk-JUUPOOQQ.js.map
|