@astryxdesign/core 0.4.4 → 0.4.5-canary.1fdecb1
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 +40 -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 +65 -15
- 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/Center/Center.d.ts +23 -1
- package/dist/Center/Center.d.ts.map +1 -1
- package/dist/Center/Center.js +12 -6
- 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/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +3 -3
- 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/Item/Item.d.ts +9 -1
- package/dist/Item/Item.d.ts.map +1 -1
- package/dist/Item/Item.js +45 -7
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +3 -2
- package/dist/Layout/container.stylex.d.ts +4 -2
- package/dist/Layout/container.stylex.d.ts.map +1 -1
- package/dist/Layout/container.stylex.js +19 -11
- package/dist/Layout/index.d.ts +1 -0
- package/dist/Layout/index.d.ts.map +1 -1
- package/dist/Layout/index.js +4 -0
- package/dist/Layout/padding.stylex.d.ts +315 -12
- package/dist/Layout/padding.stylex.d.ts.map +1 -1
- package/dist/Layout/padding.stylex.js +389 -12
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +2 -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/MobileNav/MobileNav.d.ts +1 -0
- package/dist/MobileNav/MobileNav.d.ts.map +1 -1
- package/dist/MobileNav/MobileNav.js +24 -6
- 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 +36 -3
- 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/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +3 -3
- package/dist/Section/Section.d.ts +36 -1
- package/dist/Section/Section.d.ts.map +1 -1
- package/dist/Section/Section.js +12 -2
- package/dist/Selector/Selector.d.ts +26 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +93 -15
- package/dist/Selector/SelectorOption.d.ts +14 -1
- package/dist/Selector/SelectorOption.d.ts.map +1 -1
- package/dist/Selector/SelectorOption.js +6 -0
- package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
- package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
- package/dist/Selector/SelectorRowLayoutContext.js +27 -0
- package/dist/Selector/types.d.ts +1 -0
- package/dist/Selector/types.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +2 -2
- package/dist/Stack/Stack.d.ts +23 -1
- package/dist/Stack/Stack.d.ts.map +1 -1
- package/dist/Stack/Stack.js +12 -6
- 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/astryx.css +40 -21
- package/dist/hooks/scrollbarGutter.d.ts +28 -0
- package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
- package/dist/hooks/scrollbarGutter.js +103 -0
- 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/hooks/useScrollLock.d.ts +4 -0
- package/dist/hooks/useScrollLock.d.ts.map +1 -1
- package/dist/hooks/useScrollLock.js +13 -1
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +7 -0
- package/dist/theme/generateThemeRules.d.ts.map +1 -1
- package/dist/theme/generateThemeRules.js +37 -4
- 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 +8 -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 +6 -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 +177 -2
- package/src/BottomSheet/BottomSheet.tsx +16 -2
- package/src/BottomSheet/BottomSheetPanel.test.tsx +41 -0
- package/src/BottomSheet/BottomSheetPanel.tsx +95 -15
- 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/Center/Center.doc.mjs +48 -0
- package/src/Center/Center.test.tsx +158 -0
- package/src/Center/Center.tsx +50 -9
- 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/Dialog/Dialog.test.tsx +24 -0
- package/src/Dialog/Dialog.tsx +3 -0
- 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/Item/Item.doc.mjs +1 -0
- package/src/Item/Item.test.tsx +50 -0
- package/src/Item/Item.tsx +40 -1
- package/src/Layer/useLayer.tsx +13 -2
- package/src/Layout/Layout.doc.mjs +18 -0
- package/src/Layout/container.stylex.ts +11 -3
- package/src/Layout/index.ts +4 -0
- package/src/Layout/overlayPaddingReset.test.tsx +222 -0
- package/src/Layout/padding.stylex.ts +192 -12
- package/src/Lightbox/Lightbox.tsx +2 -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/MobileNav/MobileNav.tsx +61 -5
- package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
- package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
- package/src/MultiSelector/MultiSelector.tsx +3 -1
- package/src/NumberInput/NumberInput.doc.mjs +24 -0
- package/src/NumberInput/NumberInput.test.tsx +138 -0
- package/src/NumberInput/NumberInput.tsx +43 -5
- package/src/Outline/Outline.doc.mjs +2 -0
- package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
- package/src/RadioList/RadioList.tsx +9 -1
- package/src/Resizable/ResizeHandle.test.tsx +22 -0
- package/src/Resizable/ResizeHandle.tsx +9 -3
- package/src/Section/Section.doc.mjs +55 -0
- package/src/Section/Section.test.tsx +201 -4
- package/src/Section/Section.tsx +69 -0
- package/src/Selector/Selector.doc.mjs +16 -2
- package/src/Selector/Selector.test.tsx +524 -0
- package/src/Selector/Selector.tsx +161 -17
- package/src/Selector/SelectorOption.doc.mjs +7 -0
- package/src/Selector/SelectorOption.tsx +21 -0
- package/src/Selector/SelectorRowLayoutContext.ts +32 -0
- package/src/Selector/types.ts +4 -4
- package/src/Slider/Slider.tsx +10 -0
- package/src/Stack/Stack.doc.mjs +72 -0
- package/src/Stack/Stack.test.tsx +156 -0
- package/src/Stack/Stack.tsx +50 -8
- 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/scrollbarGutter.test.ts +167 -0
- package/src/hooks/scrollbarGutter.ts +121 -0
- package/src/hooks/useResolvedRequired.ts +42 -0
- package/src/hooks/useScrollLock.doc.mjs +4 -4
- package/src/hooks/useScrollLock.test.ts +73 -0
- package/src/hooks/useScrollLock.ts +14 -0
- package/src/theme/derivedVarRegistry.test.ts +14 -3
- package/src/theme/derivedVarRegistry.ts +4 -0
- package/src/theme/generateThemeRules.test.ts +79 -0
- package/src/theme/generateThemeRules.ts +49 -4
- package/src/utils/plainDate.test.ts +50 -0
- package/src/utils/plainDate.ts +12 -0
|
@@ -13,6 +13,22 @@ import {describe, it, expect, vi} from 'vitest';
|
|
|
13
13
|
import {render, screen} from '@testing-library/react';
|
|
14
14
|
import {Center} from './Center';
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* The functional class output, as an order-insensitive set. StyleX's dev
|
|
18
|
+
* runtime also emits readable debug classes naming the style object a
|
|
19
|
+
* declaration came from ("padding__paddingBlockStyles.2"); those record
|
|
20
|
+
* provenance rather than applied CSS and survive even when the declaration
|
|
21
|
+
* they name loses a merge, so they are dropped here.
|
|
22
|
+
*/
|
|
23
|
+
function classSet(el: HTMLElement): Set<string> {
|
|
24
|
+
return new Set(
|
|
25
|
+
el.className
|
|
26
|
+
.split(' ')
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.filter(c => !c.includes('__') && !c.includes('.')),
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
16
32
|
describe('Center', () => {
|
|
17
33
|
it('renders and centers children (both axes by default)', () => {
|
|
18
34
|
render(
|
|
@@ -207,4 +223,146 @@ describe('Center', () => {
|
|
|
207
223
|
const element = screen.getByTestId('center');
|
|
208
224
|
expect(element.tagName).toBe('DIV');
|
|
209
225
|
});
|
|
226
|
+
|
|
227
|
+
it('applies a class when paddingBlockStart is set on its own', () => {
|
|
228
|
+
const {rerender} = render(
|
|
229
|
+
<Center data-testid="center">
|
|
230
|
+
<div>Content</div>
|
|
231
|
+
</Center>,
|
|
232
|
+
);
|
|
233
|
+
const baseline = screen.getByTestId('center').className;
|
|
234
|
+
rerender(
|
|
235
|
+
<Center paddingBlockStart={2} data-testid="center">
|
|
236
|
+
<div>Content</div>
|
|
237
|
+
</Center>,
|
|
238
|
+
);
|
|
239
|
+
expect(screen.getByTestId('center').className).not.toBe(baseline);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('lets paddingBlockStart/paddingBlockEnd override only their own edge', () => {
|
|
243
|
+
// padding={4} + paddingBlockStart={2} must equal spelling every edge out:
|
|
244
|
+
// both inline edges and the block-end edge stay at 4.
|
|
245
|
+
const {rerender} = render(
|
|
246
|
+
<Center padding={4} paddingBlockStart={2} data-testid="center">
|
|
247
|
+
<div>Content</div>
|
|
248
|
+
</Center>,
|
|
249
|
+
);
|
|
250
|
+
const perEdge = classSet(screen.getByTestId('center'));
|
|
251
|
+
rerender(
|
|
252
|
+
<Center
|
|
253
|
+
paddingInline={4}
|
|
254
|
+
paddingBlockStart={2}
|
|
255
|
+
paddingBlockEnd={4}
|
|
256
|
+
data-testid="center">
|
|
257
|
+
<div>Content</div>
|
|
258
|
+
</Center>,
|
|
259
|
+
);
|
|
260
|
+
expect(perEdge).toEqual(classSet(screen.getByTestId('center')));
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('gives paddingBlockEnd precedence over paddingBlock', () => {
|
|
264
|
+
const {rerender} = render(
|
|
265
|
+
<Center paddingBlock={6} paddingBlockEnd={0} data-testid="center">
|
|
266
|
+
<div>Content</div>
|
|
267
|
+
</Center>,
|
|
268
|
+
);
|
|
269
|
+
const overridden = classSet(screen.getByTestId('center'));
|
|
270
|
+
rerender(
|
|
271
|
+
<Center paddingBlockStart={6} paddingBlockEnd={0} data-testid="center">
|
|
272
|
+
<div>Content</div>
|
|
273
|
+
</Center>,
|
|
274
|
+
);
|
|
275
|
+
expect(overridden).toEqual(classSet(screen.getByTestId('center')));
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('leaves padding/paddingBlock output unchanged when no edge prop is set', () => {
|
|
279
|
+
const {rerender} = render(
|
|
280
|
+
<Center padding={3} data-testid="center">
|
|
281
|
+
<div>Content</div>
|
|
282
|
+
</Center>,
|
|
283
|
+
);
|
|
284
|
+
const uniform = classSet(screen.getByTestId('center'));
|
|
285
|
+
rerender(
|
|
286
|
+
<Center paddingInline={3} paddingBlock={3} data-testid="center">
|
|
287
|
+
<div>Content</div>
|
|
288
|
+
</Center>,
|
|
289
|
+
);
|
|
290
|
+
expect(uniform).toEqual(classSet(screen.getByTestId('center')));
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('applies a class when paddingInlineStart is set on its own', () => {
|
|
294
|
+
const {rerender} = render(
|
|
295
|
+
<Center data-testid="center">
|
|
296
|
+
<div>Content</div>
|
|
297
|
+
</Center>,
|
|
298
|
+
);
|
|
299
|
+
const baseline = screen.getByTestId('center').className;
|
|
300
|
+
rerender(
|
|
301
|
+
<Center paddingInlineStart={2} data-testid="center">
|
|
302
|
+
<div>Content</div>
|
|
303
|
+
</Center>,
|
|
304
|
+
);
|
|
305
|
+
expect(screen.getByTestId('center').className).not.toBe(baseline);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it('lets paddingInlineStart/paddingInlineEnd override only their own edge', () => {
|
|
309
|
+
const {rerender} = render(
|
|
310
|
+
<Center padding={4} paddingInlineStart={2} data-testid="center">
|
|
311
|
+
<div>Content</div>
|
|
312
|
+
</Center>,
|
|
313
|
+
);
|
|
314
|
+
const perEdge = classSet(screen.getByTestId('center'));
|
|
315
|
+
rerender(
|
|
316
|
+
<Center
|
|
317
|
+
paddingInlineStart={2}
|
|
318
|
+
paddingInlineEnd={4}
|
|
319
|
+
paddingBlock={4}
|
|
320
|
+
data-testid="center">
|
|
321
|
+
<div>Content</div>
|
|
322
|
+
</Center>,
|
|
323
|
+
);
|
|
324
|
+
expect(perEdge).toEqual(classSet(screen.getByTestId('center')));
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
it('gives paddingInlineEnd precedence over paddingInline', () => {
|
|
328
|
+
const {rerender} = render(
|
|
329
|
+
<Center paddingInline={6} paddingInlineEnd={0} data-testid="center">
|
|
330
|
+
<div>Content</div>
|
|
331
|
+
</Center>,
|
|
332
|
+
);
|
|
333
|
+
const overridden = classSet(screen.getByTestId('center'));
|
|
334
|
+
rerender(
|
|
335
|
+
<Center paddingInlineStart={6} paddingInlineEnd={0} data-testid="center">
|
|
336
|
+
<div>Content</div>
|
|
337
|
+
</Center>,
|
|
338
|
+
);
|
|
339
|
+
expect(overridden).toEqual(classSet(screen.getByTestId('center')));
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it('resolves all four edges independently', () => {
|
|
343
|
+
// One prop per edge, each a different step: the four-edge spelling and the
|
|
344
|
+
// shorthand-plus-overrides spelling must agree.
|
|
345
|
+
const {rerender} = render(
|
|
346
|
+
<Center
|
|
347
|
+
paddingInlineStart={1}
|
|
348
|
+
paddingInlineEnd={2}
|
|
349
|
+
paddingBlockStart={3}
|
|
350
|
+
paddingBlockEnd={4}
|
|
351
|
+
data-testid="center">
|
|
352
|
+
<div>Content</div>
|
|
353
|
+
</Center>,
|
|
354
|
+
);
|
|
355
|
+
const explicit = classSet(screen.getByTestId('center'));
|
|
356
|
+
rerender(
|
|
357
|
+
<Center
|
|
358
|
+
padding={4}
|
|
359
|
+
paddingInlineStart={1}
|
|
360
|
+
paddingInlineEnd={2}
|
|
361
|
+
paddingBlockStart={3}
|
|
362
|
+
data-testid="center">
|
|
363
|
+
<div>Content</div>
|
|
364
|
+
</Center>,
|
|
365
|
+
);
|
|
366
|
+
expect(explicit).toEqual(classSet(screen.getByTestId('center')));
|
|
367
|
+
});
|
|
210
368
|
});
|
package/src/Center/Center.tsx
CHANGED
|
@@ -18,8 +18,10 @@ import type {BaseProps} from '../BaseProps';
|
|
|
18
18
|
import * as stylex from '@stylexjs/stylex';
|
|
19
19
|
import type {SizeValue, SpacingStep} from '../utils/types';
|
|
20
20
|
import {
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
paddingInlineStartStyles,
|
|
22
|
+
paddingInlineEndStyles,
|
|
23
|
+
paddingBlockStartStyles,
|
|
24
|
+
paddingBlockEndStyles,
|
|
23
25
|
} from '../Layout/padding.stylex';
|
|
24
26
|
import {mergeProps} from '../utils';
|
|
25
27
|
import {themeProps} from '../utils/themeProps';
|
|
@@ -106,12 +108,38 @@ export interface CenterProps extends BaseProps<HTMLDivElement> {
|
|
|
106
108
|
*/
|
|
107
109
|
paddingInline?: SpacingStep;
|
|
108
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Inline-start padding, using the spacing scale. Logical: the left edge in
|
|
113
|
+
* LTR, the right edge in RTL.
|
|
114
|
+
* Overrides `paddingInline` and `padding` on that edge only.
|
|
115
|
+
*/
|
|
116
|
+
paddingInlineStart?: SpacingStep;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Inline-end padding, using the spacing scale. Logical: the right edge in
|
|
120
|
+
* LTR, the left edge in RTL.
|
|
121
|
+
* Overrides `paddingInline` and `padding` on that edge only.
|
|
122
|
+
*/
|
|
123
|
+
paddingInlineEnd?: SpacingStep;
|
|
124
|
+
|
|
109
125
|
/**
|
|
110
126
|
* Block (vertical) padding, using the spacing scale.
|
|
111
127
|
* Overrides `padding` on the block axis when both are set.
|
|
112
128
|
*/
|
|
113
129
|
paddingBlock?: SpacingStep;
|
|
114
130
|
|
|
131
|
+
/**
|
|
132
|
+
* Block-start (top) padding, using the spacing scale.
|
|
133
|
+
* Overrides `paddingBlock` and `padding` on that edge only.
|
|
134
|
+
*/
|
|
135
|
+
paddingBlockStart?: SpacingStep;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Block-end (bottom) padding, using the spacing scale.
|
|
139
|
+
* Overrides `paddingBlock` and `padding` on that edge only.
|
|
140
|
+
*/
|
|
141
|
+
paddingBlockEnd?: SpacingStep;
|
|
142
|
+
|
|
115
143
|
/**
|
|
116
144
|
* Whether to make the container inline-flex (useful for text/icons).
|
|
117
145
|
* @default false
|
|
@@ -145,7 +173,11 @@ export function Center({
|
|
|
145
173
|
minHeight,
|
|
146
174
|
padding,
|
|
147
175
|
paddingInline,
|
|
176
|
+
paddingInlineStart,
|
|
177
|
+
paddingInlineEnd,
|
|
148
178
|
paddingBlock,
|
|
179
|
+
paddingBlockStart,
|
|
180
|
+
paddingBlockEnd,
|
|
149
181
|
isInline = false,
|
|
150
182
|
children,
|
|
151
183
|
xstyle,
|
|
@@ -154,10 +186,14 @@ export function Center({
|
|
|
154
186
|
ref,
|
|
155
187
|
...props
|
|
156
188
|
}: CenterProps) {
|
|
157
|
-
// Resolve padding to per-
|
|
158
|
-
//
|
|
159
|
-
const
|
|
160
|
-
|
|
189
|
+
// Resolve padding to per-edge values. Most specific wins, per edge:
|
|
190
|
+
// edge prop -> axis prop -> `padding`.
|
|
191
|
+
const resolvedPaddingInlineStart =
|
|
192
|
+
paddingInlineStart ?? paddingInline ?? padding;
|
|
193
|
+
const resolvedPaddingInlineEnd = paddingInlineEnd ?? paddingInline ?? padding;
|
|
194
|
+
const resolvedPaddingBlockStart =
|
|
195
|
+
paddingBlockStart ?? paddingBlock ?? padding;
|
|
196
|
+
const resolvedPaddingBlockEnd = paddingBlockEnd ?? paddingBlock ?? padding;
|
|
161
197
|
|
|
162
198
|
const stylexProps = mergeProps(
|
|
163
199
|
themeProps('center', {axis}),
|
|
@@ -171,9 +207,14 @@ export function Center({
|
|
|
171
207
|
maxWidth ?? null,
|
|
172
208
|
minHeight ?? null,
|
|
173
209
|
),
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
210
|
+
resolvedPaddingInlineStart != null &&
|
|
211
|
+
paddingInlineStartStyles[resolvedPaddingInlineStart],
|
|
212
|
+
resolvedPaddingInlineEnd != null &&
|
|
213
|
+
paddingInlineEndStyles[resolvedPaddingInlineEnd],
|
|
214
|
+
resolvedPaddingBlockStart != null &&
|
|
215
|
+
paddingBlockStartStyles[resolvedPaddingBlockStart],
|
|
216
|
+
resolvedPaddingBlockEnd != null &&
|
|
217
|
+
paddingBlockEndStyles[resolvedPaddingBlockEnd],
|
|
177
218
|
xstyle,
|
|
178
219
|
),
|
|
179
220
|
className,
|
|
@@ -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
|
}
|
|
@@ -98,6 +98,18 @@ export const docs = {
|
|
|
98
98
|
type: 'Array<(date: Date) => boolean>',
|
|
99
99
|
description: 'Custom constraint functions to disable specific dates.',
|
|
100
100
|
},
|
|
101
|
+
{
|
|
102
|
+
name: 'maxRangeSpan',
|
|
103
|
+
type: 'number',
|
|
104
|
+
description:
|
|
105
|
+
'Maximum days a selected range may span, counting both endpoints (`7` = a 7-day window, start + 6). Once a start is picked, days beyond this distance are disabled so the range cannot stretch past the cap. Rolling window relative to the start — for fixed calendar bounds use `min`/`max`. Constrains selection only; it never rewrites a `value` already wider than the cap (flag that with `status`).',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: 'minRangeSpan',
|
|
109
|
+
type: 'number',
|
|
110
|
+
description:
|
|
111
|
+
'Minimum days a selected range must span, counting both endpoints (`2` forbids a single-day range). Once a start is picked, days closer than this are disabled. Defaults to 1 (same-day start and end allowed).',
|
|
112
|
+
},
|
|
101
113
|
{
|
|
102
114
|
name: 'presets',
|
|
103
115
|
type: 'Array<DateRangePreset>',
|
|
@@ -313,6 +325,10 @@ export const docsDense = {
|
|
|
313
325
|
min: 'min selectable date: ISODateString template literal type (YYYY-MM-DD); use string literal or cast `as ISODateString`',
|
|
314
326
|
max: 'max selectable date: ISODateString template literal type (YYYY-MM-DD); use string literal or cast `as ISODateString`',
|
|
315
327
|
dateConstraints: 'custom constraint fns to disable dates',
|
|
328
|
+
maxRangeSpan:
|
|
329
|
+
'max days a range may span, both endpoints counted (7 = a 7-day window); caps the window from the picked start. Selection-only; does not rewrite an over-wide value',
|
|
330
|
+
minRangeSpan:
|
|
331
|
+
'min days a range must span, both endpoints counted (2 forbids a single-day range); default 1',
|
|
316
332
|
presets: 'preset ranges as quick-select options',
|
|
317
333
|
hasClear: 'clear button when range is set (default true)',
|
|
318
334
|
placeholder: 'placeholder when empty',
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* SYNC: When DateRangeInput.tsx changes, update tests to match new behavior
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {describe, it, expect, vi, beforeEach} from 'vitest';
|
|
12
|
+
import {describe, it, expect, vi, beforeEach, afterEach} from 'vitest';
|
|
13
13
|
import {render, screen, fireEvent, waitFor} from '@testing-library/react';
|
|
14
14
|
import userEvent from '@testing-library/user-event';
|
|
15
15
|
|
|
@@ -712,3 +712,83 @@ describe('DateRangeInput disabled theme state', () => {
|
|
|
712
712
|
expect(root).not.toHaveAttribute('data-disabled');
|
|
713
713
|
});
|
|
714
714
|
});
|
|
715
|
+
|
|
716
|
+
describe('DateRangeInput range-span forwarding', () => {
|
|
717
|
+
// Pin "today" so the popover opens on a known month and the day buttons we
|
|
718
|
+
// query are guaranteed to render.
|
|
719
|
+
beforeEach(() => {
|
|
720
|
+
vi.useFakeTimers();
|
|
721
|
+
vi.setSystemTime(new Date('2026-01-05T12:00:00Z'));
|
|
722
|
+
});
|
|
723
|
+
afterEach(() => {
|
|
724
|
+
vi.useRealTimers();
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// The calendar renders in the top layer; jsdom keeps day buttons in the DOM
|
|
728
|
+
// but role queries skip them, so reach them by their machine-readable
|
|
729
|
+
// data-date (ISO) attribute — the same approach Calendar's own tests use.
|
|
730
|
+
const dayButton = (iso: string): HTMLButtonElement | null =>
|
|
731
|
+
document.querySelector<HTMLButtonElement>(`button[data-date="${iso}"]`);
|
|
732
|
+
|
|
733
|
+
it('forwards maxRangeSpan so the window caps after a start is picked', () => {
|
|
734
|
+
render(
|
|
735
|
+
<DateRangeInput
|
|
736
|
+
label="Reporting period"
|
|
737
|
+
value={null}
|
|
738
|
+
onChange={() => {}}
|
|
739
|
+
maxRangeSpan={7}
|
|
740
|
+
/>,
|
|
741
|
+
);
|
|
742
|
+
|
|
743
|
+
fireEvent.click(getButton('Open calendar'));
|
|
744
|
+
|
|
745
|
+
// Before a start is picked, a far-off day is selectable.
|
|
746
|
+
expect(dayButton('2026-01-20')).not.toBeDisabled();
|
|
747
|
+
|
|
748
|
+
fireEvent.click(dayButton('2026-01-10') as HTMLButtonElement);
|
|
749
|
+
|
|
750
|
+
// A 7-day window spans start ± 6 days: Jan 16 is the edge, Jan 17 is out.
|
|
751
|
+
expect(dayButton('2026-01-16')).not.toBeDisabled();
|
|
752
|
+
expect(dayButton('2026-01-17')).toBeDisabled();
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
it('disables a preset whose range violates the span cap', () => {
|
|
756
|
+
const presets = [
|
|
757
|
+
{
|
|
758
|
+
label: 'Last 3 days',
|
|
759
|
+
getRange: (): DateRange => ({
|
|
760
|
+
start: '2026-01-08',
|
|
761
|
+
end: '2026-01-10',
|
|
762
|
+
}),
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
label: 'Last 30 days',
|
|
766
|
+
getRange: (): DateRange => ({
|
|
767
|
+
start: '2025-12-12',
|
|
768
|
+
end: '2026-01-10',
|
|
769
|
+
}),
|
|
770
|
+
},
|
|
771
|
+
];
|
|
772
|
+
const handleChange = vi.fn();
|
|
773
|
+
render(
|
|
774
|
+
<DateRangeInput
|
|
775
|
+
label="Reporting period"
|
|
776
|
+
value={null}
|
|
777
|
+
onChange={handleChange}
|
|
778
|
+
maxRangeSpan={7}
|
|
779
|
+
presets={presets}
|
|
780
|
+
/>,
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
fireEvent.click(getButton('Open calendar'));
|
|
784
|
+
|
|
785
|
+
// The 3-day preset fits the 7-day cap; the 30-day preset can't be committed.
|
|
786
|
+
const withinCap = getButton('Last 3 days');
|
|
787
|
+
const overCap = getButton('Last 30 days');
|
|
788
|
+
expect(withinCap).not.toBeDisabled();
|
|
789
|
+
expect(overCap).toBeDisabled();
|
|
790
|
+
|
|
791
|
+
fireEvent.click(overCap);
|
|
792
|
+
expect(handleChange).not.toHaveBeenCalled();
|
|
793
|
+
});
|
|
794
|
+
});
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
plainDateFromISO,
|
|
23
23
|
plainDateToday,
|
|
24
24
|
plainDateFormat,
|
|
25
|
+
plainDateDiffDays,
|
|
25
26
|
DATE_FORMAT_SHORT,
|
|
26
27
|
DATE_FORMAT_SHORT_WITH_YEAR,
|
|
27
28
|
} from '../utils/plainDate';
|
|
@@ -60,6 +61,7 @@ import type {BaseProps} from '../BaseProps';
|
|
|
60
61
|
import type {SizeValue} from '../utils/types';
|
|
61
62
|
import {useSize} from '../SizeContext/SizeContext';
|
|
62
63
|
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
64
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
63
65
|
import {themeProps} from '../utils/themeProps';
|
|
64
66
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
65
67
|
import {stableClassName} from '../naming';
|
|
@@ -161,6 +163,11 @@ const styles = stylex.create({
|
|
|
161
163
|
backgroundColor: colorVars['--color-accent-muted'],
|
|
162
164
|
color: colorVars['--color-accent'],
|
|
163
165
|
},
|
|
166
|
+
presetButtonDisabled: {
|
|
167
|
+
color: colorVars['--color-text-disabled'],
|
|
168
|
+
cursor: 'not-allowed',
|
|
169
|
+
backgroundColor: 'transparent',
|
|
170
|
+
},
|
|
164
171
|
});
|
|
165
172
|
|
|
166
173
|
const sizeStyles = stylex.create({
|
|
@@ -201,6 +208,34 @@ function isRangeEqual(a: DateRange | null, b: DateRange | null): boolean {
|
|
|
201
208
|
return a.start === b.start && a.end === b.end;
|
|
202
209
|
}
|
|
203
210
|
|
|
211
|
+
// A preset that would land outside the span bounds is disabled rather than
|
|
212
|
+
// allowed to override them: the cap is authoritative, so an out-of-window
|
|
213
|
+
// preset stays visible (discoverable) but non-committable, mirroring how the
|
|
214
|
+
// calendar disables out-of-window days. Spans count both endpoints.
|
|
215
|
+
function isRangeWithinSpan(
|
|
216
|
+
range: DateRange,
|
|
217
|
+
maxRangeSpan: number | undefined,
|
|
218
|
+
minRangeSpan: number | undefined,
|
|
219
|
+
): boolean {
|
|
220
|
+
if (maxRangeSpan == null && minRangeSpan == null) {
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
const span =
|
|
224
|
+
Math.abs(
|
|
225
|
+
plainDateDiffDays(
|
|
226
|
+
plainDateFromISO(range.start),
|
|
227
|
+
plainDateFromISO(range.end),
|
|
228
|
+
),
|
|
229
|
+
) + 1;
|
|
230
|
+
if (maxRangeSpan != null && span > maxRangeSpan) {
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
if (minRangeSpan != null && span < minRangeSpan) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
204
239
|
export interface DateRangeInputProps extends Omit<
|
|
205
240
|
BaseProps,
|
|
206
241
|
'onChange' | 'defaultValue'
|
|
@@ -303,6 +338,30 @@ export interface DateRangeInputProps extends Omit<
|
|
|
303
338
|
*/
|
|
304
339
|
dateConstraints?: ReadonlyArray<(date: Date) => boolean>;
|
|
305
340
|
|
|
341
|
+
/**
|
|
342
|
+
* Maximum number of days the selected range may span, counting both
|
|
343
|
+
* endpoints — `maxRangeSpan={7}` allows a 7-day window (start + 6 days).
|
|
344
|
+
* Once a start date is picked, days beyond this distance from it are
|
|
345
|
+
* disabled, so the user can't stretch the range past the cap. Use for
|
|
346
|
+
* rolling windows like "at most a week from the chosen day"; for fixed
|
|
347
|
+
* calendar bounds use `min`/`max`.
|
|
348
|
+
*
|
|
349
|
+
* This constrains selection only — it never rewrites a `value` that is
|
|
350
|
+
* already wider than the cap. Surface such a value with `status` if you
|
|
351
|
+
* need to flag it. A `preset` whose range violates the cap is disabled
|
|
352
|
+
* (shown but not committable) rather than allowed to override it.
|
|
353
|
+
*/
|
|
354
|
+
maxRangeSpan?: number;
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Minimum number of days the selected range must span, counting both
|
|
358
|
+
* endpoints — `minRangeSpan={2}` forbids a single-day range. Once a start
|
|
359
|
+
* date is picked, days closer than this to it are disabled — except the
|
|
360
|
+
* start itself, which stays selectable as the active anchor. Defaults to 1
|
|
361
|
+
* (a same-day start and end is allowed).
|
|
362
|
+
*/
|
|
363
|
+
minRangeSpan?: number;
|
|
364
|
+
|
|
306
365
|
/**
|
|
307
366
|
* Preset date ranges shown as quick-select options beside the calendar.
|
|
308
367
|
*/
|
|
@@ -396,6 +455,8 @@ export function DateRangeInput({
|
|
|
396
455
|
min,
|
|
397
456
|
max,
|
|
398
457
|
dateConstraints,
|
|
458
|
+
maxRangeSpan,
|
|
459
|
+
minRangeSpan,
|
|
399
460
|
presets,
|
|
400
461
|
hasClear = true,
|
|
401
462
|
placeholder: placeholderFromProps,
|
|
@@ -413,6 +474,7 @@ export function DateRangeInput({
|
|
|
413
474
|
...rest
|
|
414
475
|
}: DateRangeInputProps) {
|
|
415
476
|
const t = useTranslator();
|
|
477
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
416
478
|
const placeholder =
|
|
417
479
|
placeholderFromProps ?? t('@astryx.dateRangeInput.placeholder');
|
|
418
480
|
const size = useSize(sizeProp, 'md');
|
|
@@ -609,7 +671,7 @@ export function DateRangeInput({
|
|
|
609
671
|
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
610
672
|
aria-label={triggerAriaLabel}
|
|
611
673
|
aria-describedby={ariaDescribedBy}
|
|
612
|
-
aria-required={
|
|
674
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
613
675
|
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
614
676
|
aria-busy={isBusy || undefined}
|
|
615
677
|
aria-expanded={popover.isOpen}
|
|
@@ -642,6 +704,11 @@ export function DateRangeInput({
|
|
|
642
704
|
{presets.map(preset => {
|
|
643
705
|
const presetRange = preset.getRange();
|
|
644
706
|
const isActive = isRangeEqual(value, presetRange);
|
|
707
|
+
const isPresetDisabled = !isRangeWithinSpan(
|
|
708
|
+
presetRange,
|
|
709
|
+
maxRangeSpan,
|
|
710
|
+
minRangeSpan,
|
|
711
|
+
);
|
|
645
712
|
return (
|
|
646
713
|
<button
|
|
647
714
|
key={preset.label}
|
|
@@ -652,11 +719,13 @@ export function DateRangeInput({
|
|
|
652
719
|
// marked with aria-current (not aria-selected, a listbox
|
|
653
720
|
// concept that contradicted the Tab interaction) (forms-5).
|
|
654
721
|
aria-current={isActive ? 'true' : undefined}
|
|
722
|
+
disabled={isPresetDisabled}
|
|
655
723
|
onClick={() => handlePresetClick(preset)}
|
|
656
724
|
{...stylex.props(
|
|
657
725
|
focusOutlineStyles.focusVisible,
|
|
658
726
|
styles.presetButton,
|
|
659
727
|
isActive && styles.presetButtonActive,
|
|
728
|
+
isPresetDisabled && styles.presetButtonDisabled,
|
|
660
729
|
)}>
|
|
661
730
|
{preset.label}
|
|
662
731
|
</button>
|
|
@@ -671,6 +740,8 @@ export function DateRangeInput({
|
|
|
671
740
|
min={min}
|
|
672
741
|
max={max}
|
|
673
742
|
dateConstraints={dateConstraints}
|
|
743
|
+
maxRangeSpan={maxRangeSpan}
|
|
744
|
+
minRangeSpan={minRangeSpan}
|
|
674
745
|
numberOfMonths={numberOfMonths}
|
|
675
746
|
weekStartsOn={weekStartsOn}
|
|
676
747
|
/>
|
|
@@ -84,6 +84,7 @@ import {
|
|
|
84
84
|
import type {BaseProps} from '../BaseProps';
|
|
85
85
|
import type {SizeValue} from '../utils/types';
|
|
86
86
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
87
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
87
88
|
import {useSize} from '../SizeContext/SizeContext';
|
|
88
89
|
import {themeProps} from '../utils/themeProps';
|
|
89
90
|
import {focusOutlineStyles} from '../utils/focusOutline.stylex';
|
|
@@ -448,6 +449,7 @@ export function DateTimeInput({
|
|
|
448
449
|
...rest
|
|
449
450
|
}: DateTimeInputProps) {
|
|
450
451
|
const t = useTranslator();
|
|
452
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
451
453
|
// Speaks arrow-key stepping results through the persistent live regions:
|
|
452
454
|
// stepping programmatically rewrites a plain textbox's value, which screen
|
|
453
455
|
// readers do not announce on their own (WCAG 4.1.2).
|
|
@@ -1009,7 +1011,7 @@ export function DateTimeInput({
|
|
|
1009
1011
|
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
1010
1012
|
readOnly={showsDisabledMessage || undefined}
|
|
1011
1013
|
aria-describedby={ariaDescribedBy}
|
|
1012
|
-
aria-required={
|
|
1014
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
1013
1015
|
aria-invalid={
|
|
1014
1016
|
status?.type === 'error' || !isDateInputValid ? 'true' : undefined
|
|
1015
1017
|
}
|
|
@@ -1098,7 +1100,7 @@ export function DateTimeInput({
|
|
|
1098
1100
|
timeLabel ?? t('@astryx.dateTimeInput.timeSuffix', {label})
|
|
1099
1101
|
}
|
|
1100
1102
|
aria-describedby={ariaDescribedBy}
|
|
1101
|
-
aria-required={
|
|
1103
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
1102
1104
|
aria-invalid={
|
|
1103
1105
|
status?.type === 'error' || !isTimeInputValid ? 'true' : undefined
|
|
1104
1106
|
}
|
|
@@ -461,4 +461,28 @@ describe('Dialog', () => {
|
|
|
461
461
|
before.remove();
|
|
462
462
|
});
|
|
463
463
|
});
|
|
464
|
+
|
|
465
|
+
describe('container padding isolation', () => {
|
|
466
|
+
it('resets container padding custom properties on the root dialog element', () => {
|
|
467
|
+
render(
|
|
468
|
+
<Dialog isOpen={true} onOpenChange={() => {}}>
|
|
469
|
+
Content
|
|
470
|
+
</Dialog>,
|
|
471
|
+
);
|
|
472
|
+
const dialog = screen.getByRole('dialog');
|
|
473
|
+
const computed = window.getComputedStyle(dialog);
|
|
474
|
+
expect(
|
|
475
|
+
computed.getPropertyValue('--container-padding-inline-start'),
|
|
476
|
+
).toBe('0px');
|
|
477
|
+
expect(computed.getPropertyValue('--container-padding-inline-end')).toBe(
|
|
478
|
+
'0px',
|
|
479
|
+
);
|
|
480
|
+
expect(computed.getPropertyValue('--container-padding-block-start')).toBe(
|
|
481
|
+
'0px',
|
|
482
|
+
);
|
|
483
|
+
expect(computed.getPropertyValue('--container-padding-block-end')).toBe(
|
|
484
|
+
'0px',
|
|
485
|
+
);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
464
488
|
});
|