@astryxdesign/core 0.4.4 → 0.4.5-canary.1dc3e5d
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/CHANGELOG.md +39 -0
- package/dist/BottomSheet/BottomSheet.d.ts +11 -2
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +5 -2
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +56 -13
- package/dist/BottomSheet/index.d.ts +1 -1
- package/dist/BottomSheet/index.d.ts.map +1 -1
- package/dist/BottomSheet/snapOffsets.d.ts +57 -20
- package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
- package/dist/BottomSheet/snapOffsets.js +106 -30
- package/dist/BottomSheet/useSheetGestures.d.ts +13 -8
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +160 -64
- package/dist/Calendar/Calendar.d.ts +17 -0
- package/dist/Calendar/Calendar.d.ts.map +1 -1
- package/dist/Calendar/Calendar.js +34 -3
- package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts +14 -0
- package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts.map +1 -0
- package/dist/Calendar/getStandaloneShortWeekdayNames.js +26 -0
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
- package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
- package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
- package/dist/Calendar/hooks/useCalendarDays.js +14 -10
- package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts +47 -0
- package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts.map +1 -0
- package/dist/Calendar/standaloneShortWeekdayNames.generated.js +40 -0
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +9 -0
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +6 -1
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +6 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +38 -3
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +7 -2
- package/dist/Field/FieldLabel.d.ts +2 -1
- package/dist/Field/FieldLabel.d.ts.map +1 -1
- package/dist/Field/FieldLabel.js +21 -3
- package/dist/Field/InputClearButton.d.ts +2 -1
- package/dist/Field/InputClearButton.d.ts.map +1 -1
- package/dist/Field/InputClearButton.js +6 -1
- package/dist/FormLayout/FormLayout.d.ts +24 -2
- package/dist/FormLayout/FormLayout.d.ts.map +1 -1
- package/dist/FormLayout/FormLayout.js +7 -2
- package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
- package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
- package/dist/FormLayout/FormLayoutContext.js +19 -4
- package/dist/FormLayout/index.d.ts +1 -1
- package/dist/FormLayout/index.d.ts.map +1 -1
- package/dist/Markdown/Markdown.d.ts +8 -0
- package/dist/Markdown/Markdown.d.ts.map +1 -1
- package/dist/Markdown/Markdown.js +31 -3
- package/dist/Markdown/parser.d.ts +14 -2
- package/dist/Markdown/parser.d.ts.map +1 -1
- package/dist/Markdown/parser.js +50 -2
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +6 -1
- package/dist/NumberInput/NumberInput.d.ts.map +1 -1
- package/dist/NumberInput/NumberInput.js +6 -1
- package/dist/Outline/parseOutlineFromMarkdown.d.ts +2 -0
- package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/parseOutlineFromMarkdown.js +5 -31
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +12 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +6 -1
- package/dist/StatusDot/StatusDot.d.ts +43 -3
- package/dist/StatusDot/StatusDot.d.ts.map +1 -1
- package/dist/StatusDot/StatusDot.js +42 -5
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +9 -0
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
- package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
- package/dist/TextArea/TextArea.d.ts.map +1 -1
- package/dist/TextArea/TextArea.js +6 -1
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +6 -1
- package/dist/TimeInput/TimeInput.d.ts.map +1 -1
- package/dist/TimeInput/TimeInput.js +6 -1
- package/dist/hooks/useResolvedRequired.d.ts +19 -0
- package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
- package/dist/hooks/useResolvedRequired.js +40 -0
- package/dist/utils/plainDate.d.ts +6 -0
- package/dist/utils/plainDate.d.ts.map +1 -1
- package/dist/utils/plainDate.js +12 -0
- package/locales/af-ZA.json +958 -0
- package/locales/ar-SA.json +998 -0
- package/locales/ca-ES.json +974 -0
- package/locales/cs-CZ.json +990 -0
- package/locales/da-DK.json +966 -0
- package/locales/de-DE.json +966 -0
- package/locales/el-GR.json +990 -0
- package/locales/en.json +4 -0
- package/locales/es-ES.json +978 -0
- package/locales/fi-FI.json +990 -0
- package/locales/fr-FR.json +928 -0
- package/locales/he-IL.json +990 -0
- package/locales/hu-HU.json +990 -0
- package/locales/it-IT.json +978 -0
- package/locales/ja-JP.json +998 -0
- package/locales/ko-KR.json +990 -0
- package/locales/nl-NL.json +954 -0
- package/locales/no-NO.json +978 -0
- package/locales/pl-PL.json +982 -0
- package/locales/pseudo.json +3 -0
- package/locales/pt-BR.json +982 -0
- package/locales/pt-PT.json +982 -0
- package/locales/ro-RO.json +982 -0
- package/locales/ru-RU.json +990 -0
- package/locales/sr-SP.json +990 -0
- package/locales/sv-SE.json +986 -0
- package/locales/tr-TR.json +990 -0
- package/locales/uk-UA.json +990 -0
- package/locales/vi-VN.json +986 -0
- package/locales/zh-CN.json +998 -0
- package/locales/zh-TW.json +998 -0
- package/package.json +3 -3
- package/src/BottomSheet/BottomSheet.doc.mjs +36 -3
- package/src/BottomSheet/BottomSheet.test.tsx +153 -2
- package/src/BottomSheet/BottomSheet.tsx +16 -2
- package/src/BottomSheet/BottomSheetPanel.tsx +67 -13
- package/src/BottomSheet/index.ts +5 -1
- package/src/BottomSheet/snapOffsets.test.ts +114 -47
- package/src/BottomSheet/snapOffsets.ts +119 -33
- package/src/BottomSheet/useSheetGestures.test.ts +113 -15
- package/src/BottomSheet/useSheetGestures.ts +196 -100
- package/src/Calendar/Calendar.doc.mjs +16 -0
- package/src/Calendar/Calendar.test.tsx +137 -0
- package/src/Calendar/Calendar.tsx +65 -2
- package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +54 -0
- package/src/Calendar/getStandaloneShortWeekdayNames.ts +38 -0
- package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
- package/src/Calendar/hooks/useCalendarDays.ts +13 -10
- package/src/Calendar/standaloneShortWeekdayNames.generated.ts +50 -0
- package/src/CheckboxInput/CheckboxInput.tsx +6 -0
- package/src/ComplexSelector/ComplexSelector.tsx +3 -1
- package/src/DateInput/DateInput.tsx +3 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
- package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
- package/src/DateRangeInput/DateRangeInput.tsx +72 -1
- package/src/DateTimeInput/DateTimeInput.tsx +4 -2
- package/src/Field/Field.doc.mjs +1 -0
- package/src/Field/FieldLabel.tsx +20 -4
- package/src/Field/InputClearButton.test.tsx +25 -0
- package/src/Field/InputClearButton.tsx +4 -1
- package/src/FormLayout/FormLayout.doc.mjs +13 -0
- package/src/FormLayout/FormLayout.test.tsx +181 -4
- package/src/FormLayout/FormLayout.tsx +33 -2
- package/src/FormLayout/FormLayoutContext.ts +22 -7
- package/src/FormLayout/index.ts +1 -1
- package/src/Markdown/Markdown.doc.mjs +3 -0
- package/src/Markdown/Markdown.test.tsx +78 -0
- package/src/Markdown/Markdown.tsx +41 -1
- package/src/Markdown/parser.ts +60 -2
- package/src/MultiSelector/MultiSelector.tsx +3 -1
- package/src/NumberInput/NumberInput.tsx +3 -1
- package/src/Outline/Outline.doc.mjs +2 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
- package/src/RadioList/RadioList.tsx +9 -1
- package/src/Selector/Selector.tsx +3 -1
- package/src/StatusDot/StatusDot.doc.mjs +13 -0
- package/src/StatusDot/StatusDot.test.tsx +115 -8
- package/src/StatusDot/StatusDot.tsx +74 -9
- package/src/Switch/Switch.tsx +6 -0
- package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +20 -2
- package/src/TextArea/TextArea.tsx +3 -1
- package/src/TextInput/TextInput.tsx +3 -1
- package/src/TimeInput/TimeInput.tsx +3 -1
- package/src/hooks/useResolvedRequired.ts +42 -0
- package/src/utils/plainDate.test.ts +50 -0
- package/src/utils/plainDate.ts +12 -0
|
@@ -21,6 +21,8 @@ import {calendarStyles} from './styles';
|
|
|
21
21
|
import {defineTheme} from '../theme/defineTheme';
|
|
22
22
|
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
23
23
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
24
|
+
import {InternationalizationProvider} from '../i18n/InternationalizationProvider';
|
|
25
|
+
import {standaloneShortWeekdayNamesByLocale} from './standaloneShortWeekdayNames.generated';
|
|
24
26
|
|
|
25
27
|
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
26
28
|
const {prose, component} = generateThemeCSS(theme);
|
|
@@ -120,6 +122,45 @@ describe('Calendar', () => {
|
|
|
120
122
|
expect(screen.getByText('Sa')).toBeInTheDocument();
|
|
121
123
|
});
|
|
122
124
|
|
|
125
|
+
it('uses the provider locale for stand-alone short day names', () => {
|
|
126
|
+
const localizedNames = standaloneShortWeekdayNamesByLocale.es;
|
|
127
|
+
const englishNames = standaloneShortWeekdayNamesByLocale.en;
|
|
128
|
+
expect(localizedNames).not.toEqual(englishNames);
|
|
129
|
+
|
|
130
|
+
const {rerender} = render(
|
|
131
|
+
<InternationalizationProvider locale="es-ES">
|
|
132
|
+
<Calendar />
|
|
133
|
+
</InternationalizationProvider>,
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
expect(
|
|
137
|
+
screen.getAllByRole('columnheader').map(header => header.textContent),
|
|
138
|
+
).toEqual(localizedNames);
|
|
139
|
+
|
|
140
|
+
rerender(
|
|
141
|
+
<InternationalizationProvider locale="en">
|
|
142
|
+
<Calendar />
|
|
143
|
+
</InternationalizationProvider>,
|
|
144
|
+
);
|
|
145
|
+
expect(
|
|
146
|
+
screen.getAllByRole('columnheader').map(header => header.textContent),
|
|
147
|
+
).toEqual(englishNames);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('rotates localized day names by numeric weekday index', () => {
|
|
151
|
+
const localizedNames = standaloneShortWeekdayNamesByLocale.es;
|
|
152
|
+
|
|
153
|
+
render(
|
|
154
|
+
<InternationalizationProvider locale="es-ES">
|
|
155
|
+
<Calendar weekStartsOn={1} />
|
|
156
|
+
</InternationalizationProvider>,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
expect(
|
|
160
|
+
screen.getAllByRole('columnheader').map(header => header.textContent),
|
|
161
|
+
).toEqual([...localizedNames.slice(1), localizedNames[0]]);
|
|
162
|
+
});
|
|
163
|
+
|
|
123
164
|
it('displays correct number of day cells', () => {
|
|
124
165
|
render(<Calendar />);
|
|
125
166
|
|
|
@@ -236,6 +277,102 @@ describe('Calendar', () => {
|
|
|
236
277
|
expect(sunday).toBeDisabled();
|
|
237
278
|
});
|
|
238
279
|
|
|
280
|
+
it('caps the end date to maxRangeSpan once a start is picked', async () => {
|
|
281
|
+
const user = userEvent.setup();
|
|
282
|
+
|
|
283
|
+
render(<Calendar mode="range" focusDate="2026-01-01" maxRangeSpan={7} />);
|
|
284
|
+
|
|
285
|
+
// Before a start is picked every day is selectable.
|
|
286
|
+
expect(getDayButton(20)).not.toBeDisabled();
|
|
287
|
+
|
|
288
|
+
// Pick Jan 10 as the start. A 7-day window spans Jan 4–16 (start ± 6).
|
|
289
|
+
await user.click(getDayButton(10));
|
|
290
|
+
|
|
291
|
+
expect(getDayButton(16)).not.toBeDisabled(); // 6 days after — the edge
|
|
292
|
+
expect(getDayButton(17)).toBeDisabled(); // 7 days after — beyond the cap
|
|
293
|
+
expect(getDayButton(4)).not.toBeDisabled(); // 6 days before — symmetric
|
|
294
|
+
expect(getDayButton(3)).toBeDisabled(); // 7 days before — beyond the cap
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
it('enforces minRangeSpan once a start is picked', async () => {
|
|
298
|
+
const user = userEvent.setup();
|
|
299
|
+
|
|
300
|
+
render(<Calendar mode="range" focusDate="2026-01-01" minRangeSpan={3} />);
|
|
301
|
+
|
|
302
|
+
// Pick Jan 10. A 3-day minimum forbids ends closer than 2 days away.
|
|
303
|
+
await user.click(getDayButton(10));
|
|
304
|
+
|
|
305
|
+
// The picked start itself stays enabled — it is the active selection
|
|
306
|
+
// anchor, not an unreachable end.
|
|
307
|
+
expect(getDayButton(10)).not.toBeDisabled();
|
|
308
|
+
expect(getDayButton(11)).toBeDisabled(); // 1 day apart — too short
|
|
309
|
+
expect(getDayButton(12)).not.toBeDisabled(); // 3-day span — allowed
|
|
310
|
+
expect(getDayButton(8)).not.toBeDisabled(); // 3-day span the other way
|
|
311
|
+
expect(getDayButton(9)).toBeDisabled(); // 2-day span — too short
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('clears the in-progress start when the anchor is clicked again', async () => {
|
|
315
|
+
const user = userEvent.setup();
|
|
316
|
+
const handleChange = vi.fn();
|
|
317
|
+
|
|
318
|
+
render(
|
|
319
|
+
<Calendar
|
|
320
|
+
mode="range"
|
|
321
|
+
focusDate="2026-01-01"
|
|
322
|
+
onChange={handleChange}
|
|
323
|
+
minRangeSpan={3}
|
|
324
|
+
/>,
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
// Pick Jan 10 as the start, then click it again.
|
|
328
|
+
await user.click(getDayButton(10));
|
|
329
|
+
await user.click(getDayButton(10));
|
|
330
|
+
|
|
331
|
+
// No zero-length range is committed…
|
|
332
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
333
|
+
|
|
334
|
+
// …and the start is cleared: the days that minRangeSpan disabled around
|
|
335
|
+
// the old anchor are selectable again, so a new start can be placed there.
|
|
336
|
+
expect(getDayButton(11)).not.toBeDisabled();
|
|
337
|
+
expect(getDayButton(9)).not.toBeDisabled();
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
it('does not apply range-span constraints in single mode', async () => {
|
|
341
|
+
const user = userEvent.setup();
|
|
342
|
+
|
|
343
|
+
render(<Calendar focusDate="2026-01-01" maxRangeSpan={7} />);
|
|
344
|
+
|
|
345
|
+
// A picked single date must not disable far-away days.
|
|
346
|
+
await user.click(getDayButton(10));
|
|
347
|
+
expect(getDayButton(25)).not.toBeDisabled();
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
it('keeps a controlled value that is wider than maxRangeSpan (selection-only)', () => {
|
|
351
|
+
// The span constraint governs which days are pickable, not validation of
|
|
352
|
+
// an already-set value — so a 15-day value under a 7-day cap stays
|
|
353
|
+
// rendered and is never silently rewritten.
|
|
354
|
+
render(
|
|
355
|
+
<Calendar
|
|
356
|
+
mode="range"
|
|
357
|
+
value={{start: '2026-01-05', end: '2026-01-20'}}
|
|
358
|
+
focusDate="2026-01-01"
|
|
359
|
+
maxRangeSpan={7}
|
|
360
|
+
/>,
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
// No selection is in progress (both endpoints are set), so no anchor →
|
|
364
|
+
// every day is pickable and both endpoints render selected.
|
|
365
|
+
expect(getDayButton(20)).not.toBeDisabled();
|
|
366
|
+
expect(getDayButton(5).closest('[role="gridcell"]')).toHaveAttribute(
|
|
367
|
+
'aria-selected',
|
|
368
|
+
'true',
|
|
369
|
+
);
|
|
370
|
+
expect(getDayButton(20).closest('[role="gridcell"]')).toHaveAttribute(
|
|
371
|
+
'aria-selected',
|
|
372
|
+
'true',
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
|
|
239
376
|
// ─── Multi-Month ─────────────────────────────────────────────
|
|
240
377
|
|
|
241
378
|
it('renders two months when numberOfMonths={2}', () => {
|
|
@@ -120,6 +120,25 @@ interface CalendarBaseProps extends Omit<
|
|
|
120
120
|
*/
|
|
121
121
|
dateConstraints?: ReadonlyArray<(date: Date) => boolean>;
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* Range mode only. Maximum number of days a selected range may span,
|
|
125
|
+
* counting both endpoints — `maxRangeSpan={7}` allows a 7-day window
|
|
126
|
+
* (start + 6 days). Once a start date is picked, days beyond this distance
|
|
127
|
+
* from it are disabled in either direction; before a start is picked every
|
|
128
|
+
* otherwise-valid day stays selectable. Use for rolling windows like "at
|
|
129
|
+
* most a week from the chosen day". For fixed calendar bounds use min/max.
|
|
130
|
+
*/
|
|
131
|
+
maxRangeSpan?: number;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Range mode only. Minimum number of days a selected range must span,
|
|
135
|
+
* counting both endpoints — `minRangeSpan={2}` forbids a single-day range.
|
|
136
|
+
* Once a start date is picked, days closer than this to it are disabled —
|
|
137
|
+
* except the start itself, which stays selectable as the active anchor.
|
|
138
|
+
* Defaults to 1 (a same-day start and end is allowed).
|
|
139
|
+
*/
|
|
140
|
+
minRangeSpan?: number;
|
|
141
|
+
|
|
123
142
|
/**
|
|
124
143
|
* Controlled focus date (which month is visible).
|
|
125
144
|
* If not provided, defaults to selected date or today.
|
|
@@ -211,6 +230,8 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
211
230
|
min,
|
|
212
231
|
max,
|
|
213
232
|
dateConstraints,
|
|
233
|
+
maxRangeSpan,
|
|
234
|
+
minRangeSpan,
|
|
214
235
|
focusDate: focusDateProp,
|
|
215
236
|
onFocusDateChange,
|
|
216
237
|
hasOutsideDays = true,
|
|
@@ -425,6 +446,23 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
425
446
|
} else {
|
|
426
447
|
// Second click - complete the range
|
|
427
448
|
const startPd = plainDateFromISO(rangeSelectionStart);
|
|
449
|
+
|
|
450
|
+
// Clicking the anchor again clears the in-progress start rather than
|
|
451
|
+
// committing a zero-length range. This is also the escape hatch when
|
|
452
|
+
// `minRangeSpan` disables the days around the anchor: without it the
|
|
453
|
+
// anchor would be the only clickable day left and the start could
|
|
454
|
+
// never be moved. `minRangeSpan` leaves the anchor itself enabled
|
|
455
|
+
// precisely so this toggle stays reachable.
|
|
456
|
+
if (plainDateIsEqual(date, startPd)) {
|
|
457
|
+
setRangeSelectionStart(null);
|
|
458
|
+
announce(
|
|
459
|
+
t('@astryx.calendar.rangeClearedAnnounce', {
|
|
460
|
+
date: plainDateFormat(date, DATE_FORMAT_WITH_WEEKDAY),
|
|
461
|
+
}),
|
|
462
|
+
);
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
|
|
428
466
|
let start: ISODateString;
|
|
429
467
|
let end: ISODateString;
|
|
430
468
|
|
|
@@ -528,6 +566,8 @@ export function Calendar({ref, ...props}: CalendarProps) {
|
|
|
528
566
|
min={min}
|
|
529
567
|
max={max}
|
|
530
568
|
dateConstraints={dateConstraints}
|
|
569
|
+
maxRangeSpan={maxRangeSpan}
|
|
570
|
+
minRangeSpan={minRangeSpan}
|
|
531
571
|
hasOutsideDays={hasOutsideDays}
|
|
532
572
|
hasWeekNumbers={hasWeekNumbers}
|
|
533
573
|
hasVariableRowCount={hasVariableRowCount}
|
|
@@ -567,6 +607,8 @@ interface MonthGridProps {
|
|
|
567
607
|
min?: ISODateString;
|
|
568
608
|
max?: ISODateString;
|
|
569
609
|
dateConstraints?: ReadonlyArray<(date: Date) => boolean>;
|
|
610
|
+
maxRangeSpan?: number;
|
|
611
|
+
minRangeSpan?: number;
|
|
570
612
|
hasOutsideDays: boolean;
|
|
571
613
|
hasWeekNumbers: boolean;
|
|
572
614
|
hasVariableRowCount: boolean;
|
|
@@ -589,6 +631,8 @@ function MonthGrid({
|
|
|
589
631
|
min,
|
|
590
632
|
max,
|
|
591
633
|
dateConstraints,
|
|
634
|
+
maxRangeSpan,
|
|
635
|
+
minRangeSpan,
|
|
592
636
|
hasOutsideDays,
|
|
593
637
|
hasWeekNumbers,
|
|
594
638
|
hasVariableRowCount,
|
|
@@ -610,11 +654,30 @@ function MonthGrid({
|
|
|
610
654
|
weekStartsOn,
|
|
611
655
|
hasVariableRowCount,
|
|
612
656
|
});
|
|
657
|
+
const dayNameHeaders = useMemo(
|
|
658
|
+
() =>
|
|
659
|
+
dayNames.map((name, offset) => ({
|
|
660
|
+
dayOfWeek: ((weekStartsOn + offset) % 7) as DayOfWeek,
|
|
661
|
+
name,
|
|
662
|
+
})),
|
|
663
|
+
[dayNames, weekStartsOn],
|
|
664
|
+
);
|
|
665
|
+
|
|
666
|
+
const rangeAnchor = useMemo(
|
|
667
|
+
() =>
|
|
668
|
+
mode === 'range' && rangeSelectionStart
|
|
669
|
+
? plainDateFromISO(rangeSelectionStart)
|
|
670
|
+
: null,
|
|
671
|
+
[mode, rangeSelectionStart],
|
|
672
|
+
);
|
|
613
673
|
|
|
614
674
|
const {isDateDisabled} = useCalendarConstraints({
|
|
615
675
|
min,
|
|
616
676
|
max,
|
|
617
677
|
dateConstraints,
|
|
678
|
+
maxRangeSpan,
|
|
679
|
+
minRangeSpan,
|
|
680
|
+
rangeAnchor,
|
|
618
681
|
});
|
|
619
682
|
|
|
620
683
|
// Parse selected date for roving tabindex priority
|
|
@@ -833,9 +896,9 @@ function MonthGrid({
|
|
|
833
896
|
)}
|
|
834
897
|
/>
|
|
835
898
|
)}
|
|
836
|
-
{
|
|
899
|
+
{dayNameHeaders.map(({dayOfWeek, name}) => (
|
|
837
900
|
<div
|
|
838
|
-
key={
|
|
901
|
+
key={dayOfWeek}
|
|
839
902
|
role="columnheader"
|
|
840
903
|
{...stylex.props(monthGridStyles.dayName)}>
|
|
841
904
|
{name}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file getStandaloneShortWeekdayNames.test.ts
|
|
5
|
+
* @input Private Calendar weekday locale resolver and generated CLDR data
|
|
6
|
+
* @output Regression coverage for exact, language, extension, and safe fallback
|
|
7
|
+
* @position Tests for getStandaloneShortWeekdayNames.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {describe, expect, it} from 'vitest';
|
|
11
|
+
import {getStandaloneShortWeekdayNames} from './getStandaloneShortWeekdayNames';
|
|
12
|
+
import {standaloneShortWeekdayNamesByLocale} from './standaloneShortWeekdayNames.generated';
|
|
13
|
+
|
|
14
|
+
describe('getStandaloneShortWeekdayNames', () => {
|
|
15
|
+
it('preserves the default English weekday names', () => {
|
|
16
|
+
expect(getStandaloneShortWeekdayNames('en')).toEqual([
|
|
17
|
+
'Su',
|
|
18
|
+
'Mo',
|
|
19
|
+
'Tu',
|
|
20
|
+
'We',
|
|
21
|
+
'Th',
|
|
22
|
+
'Fr',
|
|
23
|
+
'Sa',
|
|
24
|
+
]);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('returns an exact generated locale entry', () => {
|
|
28
|
+
expect(getStandaloneShortWeekdayNames('ar-SA')).toBe(
|
|
29
|
+
standaloneShortWeekdayNamesByLocale['ar-SA'],
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('ignores Unicode extensions when resolving an exact locale', () => {
|
|
34
|
+
const expected = standaloneShortWeekdayNamesByLocale['zh-TW'];
|
|
35
|
+
|
|
36
|
+
expect(getStandaloneShortWeekdayNames('zh-TW')).toBe(expected);
|
|
37
|
+
expect(getStandaloneShortWeekdayNames('zh-TW-u-ca-chinese')).toBe(expected);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('falls back from a regional locale to its base language', () => {
|
|
41
|
+
expect(getStandaloneShortWeekdayNames('es-ES')).toBe(
|
|
42
|
+
standaloneShortWeekdayNamesByLocale.es,
|
|
43
|
+
);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('safely falls back to English for malformed and unsupported locales', () => {
|
|
47
|
+
expect(getStandaloneShortWeekdayNames('not_a_locale')).toBe(
|
|
48
|
+
standaloneShortWeekdayNamesByLocale.en,
|
|
49
|
+
);
|
|
50
|
+
expect(getStandaloneShortWeekdayNames('xx-ZZ')).toBe(
|
|
51
|
+
standaloneShortWeekdayNamesByLocale.en,
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file getStandaloneShortWeekdayNames.ts
|
|
5
|
+
* @input Generated CLDR stand-alone-short weekday table
|
|
6
|
+
* @output Private locale resolver for Calendar weekday headers
|
|
7
|
+
* @position Calendar-internal pure utility; not exported from the package
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
standaloneShortWeekdayNamesByLocale,
|
|
12
|
+
type StandaloneShortWeekdayNames,
|
|
13
|
+
} from './standaloneShortWeekdayNames.generated';
|
|
14
|
+
|
|
15
|
+
const weekdayNamesByLocale: Readonly<
|
|
16
|
+
Record<string, StandaloneShortWeekdayNames>
|
|
17
|
+
> = standaloneShortWeekdayNamesByLocale;
|
|
18
|
+
const englishWeekdayNames = weekdayNamesByLocale.en;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve generated weekday reference data as exact locale -> language -> English.
|
|
22
|
+
* Unicode extensions are ignored through Intl.Locale.baseName. Invalid tags and
|
|
23
|
+
* valid but unsupported languages safely use English.
|
|
24
|
+
*/
|
|
25
|
+
export function getStandaloneShortWeekdayNames(
|
|
26
|
+
locale: string,
|
|
27
|
+
): StandaloneShortWeekdayNames {
|
|
28
|
+
try {
|
|
29
|
+
const parsed = new Intl.Locale(locale);
|
|
30
|
+
return (
|
|
31
|
+
weekdayNamesByLocale[parsed.baseName] ??
|
|
32
|
+
weekdayNamesByLocale[parsed.language] ??
|
|
33
|
+
englishWeekdayNames
|
|
34
|
+
);
|
|
35
|
+
} catch {
|
|
36
|
+
return englishWeekdayNames;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
plainDateToDate,
|
|
21
21
|
plainDateIsBefore,
|
|
22
22
|
plainDateIsAfter,
|
|
23
|
+
plainDateDiffDays,
|
|
23
24
|
} from '../../utils/plainDate';
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -35,6 +36,25 @@ export interface UseCalendarConstraintsOptions {
|
|
|
35
36
|
* Date is disabled if ANY function returns false.
|
|
36
37
|
*/
|
|
37
38
|
dateConstraints?: ReadonlyArray<(date: Date) => boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Maximum number of days a range may span, counting both endpoints
|
|
41
|
+
* (a value of 7 allows a 7-day window). In range mode, once a start is
|
|
42
|
+
* picked, dates further than this from it are disabled. No effect until a
|
|
43
|
+
* range anchor exists.
|
|
44
|
+
*/
|
|
45
|
+
maxRangeSpan?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Minimum number of days a range must span, counting both endpoints
|
|
48
|
+
* (a value of 2 forbids a single-day range). In range mode, once a start
|
|
49
|
+
* is picked, dates closer than this to it are disabled.
|
|
50
|
+
*/
|
|
51
|
+
minRangeSpan?: number;
|
|
52
|
+
/**
|
|
53
|
+
* The in-progress range start (first click, awaiting the second). Span
|
|
54
|
+
* constraints are measured from this date. Null when no selection is
|
|
55
|
+
* underway.
|
|
56
|
+
*/
|
|
57
|
+
rangeAnchor?: PlainDate | null;
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
/**
|
|
@@ -53,13 +73,15 @@ export interface UseCalendarConstraintsReturn {
|
|
|
53
73
|
* Hook for managing calendar date validation constraints.
|
|
54
74
|
*
|
|
55
75
|
* Provides a function to check if a date is disabled based on
|
|
56
|
-
* min/max bounds and custom constraint functions.
|
|
76
|
+
* min/max bounds, range-span bounds, and custom constraint functions.
|
|
57
77
|
*
|
|
58
78
|
* @example
|
|
59
79
|
* ```
|
|
60
80
|
* const {isDateDisabled} = useCalendarConstraints({
|
|
61
81
|
* min: '2026-01-01',
|
|
62
82
|
* max: '2026-12-31',
|
|
83
|
+
* maxRangeSpan: 7, // once a start is picked, cap the window at 7 days
|
|
84
|
+
* rangeAnchor, // the in-progress start (null before the first click)
|
|
63
85
|
* dateConstraints: [
|
|
64
86
|
* (date) => date.getDay() !== 0, // No Sundays (receives native Date)
|
|
65
87
|
* ],
|
|
@@ -74,7 +96,8 @@ export interface UseCalendarConstraintsReturn {
|
|
|
74
96
|
export function useCalendarConstraints(
|
|
75
97
|
options: UseCalendarConstraintsOptions,
|
|
76
98
|
): UseCalendarConstraintsReturn {
|
|
77
|
-
const {min, max, dateConstraints} =
|
|
99
|
+
const {min, max, dateConstraints, maxRangeSpan, minRangeSpan, rangeAnchor} =
|
|
100
|
+
options;
|
|
78
101
|
|
|
79
102
|
// Parse min/max dates
|
|
80
103
|
const minDate = useMemo(() => (min ? plainDateFromISO(min) : null), [min]);
|
|
@@ -93,6 +116,27 @@ export function useCalendarConstraints(
|
|
|
93
116
|
return true;
|
|
94
117
|
}
|
|
95
118
|
|
|
119
|
+
// Range-span bounds, measured from the in-progress start. Spans count
|
|
120
|
+
// both endpoints (a span of 7 spans a 7-day window), so the reachable
|
|
121
|
+
// distance from the anchor is `span - 1` days in either direction. Only
|
|
122
|
+
// active once a start is picked — before that, every day stays pickable.
|
|
123
|
+
if (rangeAnchor) {
|
|
124
|
+
const distance = Math.abs(plainDateDiffDays(rangeAnchor, date));
|
|
125
|
+
if (maxRangeSpan != null && distance > maxRangeSpan - 1) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
// The anchor itself (distance 0) is never disabled by minRangeSpan —
|
|
129
|
+
// it is the picked start, and disabling it would render the active
|
|
130
|
+
// selection start as aria-disabled to keyboard and screen-reader users.
|
|
131
|
+
if (
|
|
132
|
+
minRangeSpan != null &&
|
|
133
|
+
distance > 0 &&
|
|
134
|
+
distance < minRangeSpan - 1
|
|
135
|
+
) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
96
140
|
// Check custom constraints (convert to Date for public API compatibility)
|
|
97
141
|
if (dateConstraints) {
|
|
98
142
|
for (const constraint of dateConstraints) {
|
|
@@ -104,7 +148,14 @@ export function useCalendarConstraints(
|
|
|
104
148
|
|
|
105
149
|
return false;
|
|
106
150
|
},
|
|
107
|
-
[
|
|
151
|
+
[
|
|
152
|
+
minDate,
|
|
153
|
+
maxDate,
|
|
154
|
+
dateConstraints,
|
|
155
|
+
maxRangeSpan,
|
|
156
|
+
minRangeSpan,
|
|
157
|
+
rangeAnchor,
|
|
158
|
+
],
|
|
108
159
|
);
|
|
109
160
|
|
|
110
161
|
return {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @file useCalendarDays.ts
|
|
7
|
-
* @input Uses React useMemo, PlainDate utilities
|
|
7
|
+
* @input Uses React context via use(), useMemo, PlainDate utilities, generated CLDR weekday data
|
|
8
8
|
* @output Exports useCalendarDays hook for generating calendar day grids
|
|
9
9
|
* @position Calendar-specific hook; used by Calendar
|
|
10
10
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* - /packages/core/src/Calendar/hooks/index.ts
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import {useMemo} from 'react';
|
|
15
|
+
import {use, useMemo} from 'react';
|
|
16
16
|
import type {DayOfWeek, ISODateString} from '../../utils/dateTypes';
|
|
17
17
|
import {
|
|
18
18
|
type PlainDate,
|
|
@@ -21,6 +21,8 @@ import {
|
|
|
21
21
|
plainDateDayOfWeek,
|
|
22
22
|
plainDateAddDays,
|
|
23
23
|
} from '../../utils/plainDate';
|
|
24
|
+
import {InternationalizationContext} from '../../i18n/InternationalizationContext';
|
|
25
|
+
import {getStandaloneShortWeekdayNames} from '../getStandaloneShortWeekdayNames';
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Represents a single day in the calendar grid.
|
|
@@ -84,6 +86,7 @@ export function useCalendarDays(
|
|
|
84
86
|
options: UseCalendarDaysOptions,
|
|
85
87
|
): UseCalendarDaysReturn {
|
|
86
88
|
const {year, month, weekStartsOn = 0, hasVariableRowCount = false} = options;
|
|
89
|
+
const {locale} = use(InternationalizationContext);
|
|
87
90
|
|
|
88
91
|
// Calculate grid structure
|
|
89
92
|
const gridInfo = useMemo(() => {
|
|
@@ -108,15 +111,15 @@ export function useCalendarDays(
|
|
|
108
111
|
};
|
|
109
112
|
}, [year, month, weekStartsOn, hasVariableRowCount]);
|
|
110
113
|
|
|
111
|
-
//
|
|
114
|
+
// Generated strings avoid Date anchors, so weekday headers cannot shift with
|
|
115
|
+
// the runtime timezone. Keep numeric indices as the rotation source of truth.
|
|
112
116
|
const dayNames = useMemo(() => {
|
|
113
|
-
const names =
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}, [weekStartsOn]);
|
|
117
|
+
const names = getStandaloneShortWeekdayNames(locale);
|
|
118
|
+
return Array.from(
|
|
119
|
+
{length: 7},
|
|
120
|
+
(_, index) => names[(index + weekStartsOn) % 7],
|
|
121
|
+
);
|
|
122
|
+
}, [locale, weekStartsOn]);
|
|
120
123
|
|
|
121
124
|
// Generate days array
|
|
122
125
|
const days = useMemo(() => {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @generated by scripts/generate-cldr-weekdays.mjs from Unicode CLDR 48.2.0.
|
|
5
|
+
* Do not edit by hand. Run `pnpm generate:cldr-weekdays` instead.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type StandaloneShortWeekdayNames = readonly [
|
|
9
|
+
string,
|
|
10
|
+
string,
|
|
11
|
+
string,
|
|
12
|
+
string,
|
|
13
|
+
string,
|
|
14
|
+
string,
|
|
15
|
+
string,
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const standaloneShortWeekdayNamesByLocale = {
|
|
19
|
+
af: ['So.', 'Ma.', 'Di.', 'Wo.', 'Do.', 'Vr.', 'Sa.'],
|
|
20
|
+
ar: ['أحد', 'إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'],
|
|
21
|
+
'ar-SA': ['أحد', 'إثنين', 'ثلاثاء', 'أربعاء', 'خميس', 'جمعة', 'سبت'],
|
|
22
|
+
ca: ['dg.', 'dl.', 'dt.', 'dc.', 'dj.', 'dv.', 'ds.'],
|
|
23
|
+
cs: ['ne', 'po', 'út', 'st', 'čt', 'pá', 'so'],
|
|
24
|
+
da: ['sø.', 'ma.', 'ti.', 'on.', 'to.', 'fr.', 'lø.'],
|
|
25
|
+
de: ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'],
|
|
26
|
+
el: ['Κυ', 'Δε', 'Τρ', 'Τε', 'Πέ', 'Πα', 'Σά'],
|
|
27
|
+
en: ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
|
|
28
|
+
es: ['DO', 'LU', 'MA', 'MI', 'JU', 'VI', 'SA'],
|
|
29
|
+
fi: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'],
|
|
30
|
+
fr: ['di', 'lu', 'ma', 'me', 'je', 've', 'sa'],
|
|
31
|
+
he: ['א׳', 'ב׳', 'ג׳', 'ד׳', 'ה׳', 'ו׳', 'ש׳'],
|
|
32
|
+
hu: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'],
|
|
33
|
+
it: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'],
|
|
34
|
+
ja: ['日', '月', '火', '水', '木', '金', '土'],
|
|
35
|
+
ko: ['일', '월', '화', '수', '목', '금', '토'],
|
|
36
|
+
nl: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'],
|
|
37
|
+
no: ['sø.', 'ma.', 'ti.', 'on.', 'to.', 'fr.', 'lø.'],
|
|
38
|
+
pl: ['nie', 'pon', 'wto', 'śro', 'czw', 'pią', 'sob'],
|
|
39
|
+
pt: ['dom.', 'seg.', 'ter.', 'qua.', 'qui.', 'sex.', 'sáb.'],
|
|
40
|
+
'pt-PT': ['dom.', 'seg.', 'ter.', 'qua.', 'qui.', 'sex.', 'sáb.'],
|
|
41
|
+
ro: ['du.', 'lu.', 'ma.', 'mi.', 'joi', 'vi.', 'sâ.'],
|
|
42
|
+
ru: ['вс', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
|
|
43
|
+
sr: ['не', 'по', 'ут', 'ср', 'че', 'пе', 'су'],
|
|
44
|
+
sv: ['sö', 'må', 'ti', 'on', 'to', 'fr', 'lö'],
|
|
45
|
+
tr: ['Pa', 'Pt', 'Sa', 'Ça', 'Pe', 'Cu', 'Ct'],
|
|
46
|
+
uk: ['нд', 'пн', 'вт', 'ср', 'чт', 'пт', 'сб'],
|
|
47
|
+
vi: ['CN', 'T2', 'T3', 'T4', 'T5', 'T6', 'T7'],
|
|
48
|
+
zh: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
|
49
|
+
'zh-TW': ['日', '一', '二', '三', '四', '五', '六'],
|
|
50
|
+
} as const satisfies Readonly<Record<string, StandaloneShortWeekdayNames>>;
|
|
@@ -47,6 +47,7 @@ import {useTooltip} from '../Tooltip';
|
|
|
47
47
|
import {mergeProps, mergeRefs} from '../utils';
|
|
48
48
|
import {indicatorScope} from '../Indicator/indicator.markers.stylex';
|
|
49
49
|
import {useIndicatorFocusRing} from '../hooks/useIndicatorFocusRing';
|
|
50
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
50
51
|
import {useIndicator} from '../Indicator';
|
|
51
52
|
import {themeProps} from '../utils/themeProps';
|
|
52
53
|
import {CheckboxListContext} from '../CheckboxList/CheckboxListContext';
|
|
@@ -301,6 +302,10 @@ export function CheckboxInput({
|
|
|
301
302
|
const id = useId();
|
|
302
303
|
const descriptionID = useId();
|
|
303
304
|
const statusMessageID = useId();
|
|
305
|
+
// Announce the effective required state (form default included) while the
|
|
306
|
+
// native `required` stays bound to the explicit `isRequired` so a layout
|
|
307
|
+
// default never switches on browser validation.
|
|
308
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
304
309
|
|
|
305
310
|
const [, startTransition] = useTransition();
|
|
306
311
|
const [optimisticValue, setOptimisticValue] = useOptimistic(value);
|
|
@@ -421,6 +426,7 @@ export function CheckboxInput({
|
|
|
421
426
|
form={isFocusableDisabled ? '' : undefined}
|
|
422
427
|
readOnly={isReadOnly}
|
|
423
428
|
required={isRequired}
|
|
429
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
424
430
|
onChange={e => {
|
|
425
431
|
if (isDisabled || isBusy || isReadOnly) {
|
|
426
432
|
return;
|
|
@@ -35,6 +35,7 @@ import {useTranslator} from '../i18n';
|
|
|
35
35
|
import {layerAnimations} from '../Layer/layerAnimations.stylex';
|
|
36
36
|
import type {LayerAlignment, LayerPlacement} from '../Layer/useLayer';
|
|
37
37
|
import {usePopover} from '../Popover/usePopover';
|
|
38
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
38
39
|
import {
|
|
39
40
|
colorVars,
|
|
40
41
|
durationVars,
|
|
@@ -344,6 +345,7 @@ export function ComplexSelector<Value>({
|
|
|
344
345
|
...props
|
|
345
346
|
}: ComplexSelectorProps<Value>) {
|
|
346
347
|
const t = useTranslator();
|
|
348
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
347
349
|
const placeholder = placeholderFromProps ?? t('@astryx.selector.placeholder');
|
|
348
350
|
const effectiveStatusVariant =
|
|
349
351
|
variant === 'ghost' && statusVariant === 'attached'
|
|
@@ -497,7 +499,7 @@ export function ComplexSelector<Value>({
|
|
|
497
499
|
aria-controls={contentId}
|
|
498
500
|
aria-describedby={ariaDescribedBy}
|
|
499
501
|
aria-labelledby={labelId}
|
|
500
|
-
aria-required={
|
|
502
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
501
503
|
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
502
504
|
aria-busy={isBusy || undefined}
|
|
503
505
|
disabled={isDisabled}
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
} from '../Calendar';
|
|
59
59
|
import {useCalendarConstraints} from '../Calendar/hooks';
|
|
60
60
|
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
61
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
61
62
|
import {usePopover} from '../Popover';
|
|
62
63
|
import {useTooltip} from '../Tooltip';
|
|
63
64
|
import {getInputARIA, isImeKeyEvent, parseDateInput} from '../utils';
|
|
@@ -399,6 +400,7 @@ export function DateInput({
|
|
|
399
400
|
...rest
|
|
400
401
|
}: DateInputProps) {
|
|
401
402
|
const t = useTranslator();
|
|
403
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
402
404
|
const placeholder =
|
|
403
405
|
placeholderFromProps ?? t('@astryx.dateInput.placeholder');
|
|
404
406
|
const size = useSize(sizeProp, 'md');
|
|
@@ -737,7 +739,7 @@ export function DateInput({
|
|
|
737
739
|
readOnly={showsDisabledMessage || undefined}
|
|
738
740
|
aria-labelledby={ariaLabelledBy}
|
|
739
741
|
aria-describedby={ariaDescribedBy}
|
|
740
|
-
aria-required={
|
|
742
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
741
743
|
aria-invalid={
|
|
742
744
|
status?.type === 'error' || !isInputValid ? 'true' : undefined
|
|
743
745
|
}
|