@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
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file MobileNavScrollbarGutter.test.tsx
|
|
5
|
+
* @input Uses vitest, @testing-library/react, MobileNav, a stubbed viewport
|
|
6
|
+
* @output Unit tests for the scrollbar gutter MobileNav reserves while open
|
|
7
|
+
* @position Testing; guards MobileNav.tsx against the open-drawer page shift
|
|
8
|
+
*
|
|
9
|
+
* MobileNav clips the document to lock background scroll, which hides a
|
|
10
|
+
* classic scrollbar and widens the layout viewport by its width — the page
|
|
11
|
+
* behind the drawer jumps sideways. The clip has to be paired with holding
|
|
12
|
+
* that gutter open.
|
|
13
|
+
*
|
|
14
|
+
* SYNC: When MobileNav.tsx changes, update tests to match new behavior
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {describe, it, expect, beforeAll, afterEach} from 'vitest';
|
|
18
|
+
import {render, cleanup} from '@testing-library/react';
|
|
19
|
+
import {MobileNav} from './MobileNav';
|
|
20
|
+
|
|
21
|
+
beforeAll(() => {
|
|
22
|
+
// jsdom doesn't implement showModal/close on <dialog>
|
|
23
|
+
HTMLDialogElement.prototype.showModal =
|
|
24
|
+
HTMLDialogElement.prototype.showModal ||
|
|
25
|
+
function (this: HTMLDialogElement) {
|
|
26
|
+
this.setAttribute('open', '');
|
|
27
|
+
};
|
|
28
|
+
HTMLDialogElement.prototype.close =
|
|
29
|
+
HTMLDialogElement.prototype.close ||
|
|
30
|
+
function (this: HTMLDialogElement) {
|
|
31
|
+
this.removeAttribute('open');
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
/** jsdom does no layout, so both halves of the measurement are stubbed. */
|
|
36
|
+
function stubViewport(innerWidth: number, clientWidth: number) {
|
|
37
|
+
Object.defineProperty(window, 'innerWidth', {
|
|
38
|
+
value: innerWidth,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
});
|
|
42
|
+
Object.defineProperty(document.documentElement, 'clientWidth', {
|
|
43
|
+
value: clientWidth,
|
|
44
|
+
configurable: true,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const rootGutter = () => document.documentElement.style.scrollbarGutter;
|
|
49
|
+
|
|
50
|
+
describe('MobileNav scrollbar gutter', () => {
|
|
51
|
+
afterEach(() => {
|
|
52
|
+
cleanup();
|
|
53
|
+
document.documentElement.style.cssText = '';
|
|
54
|
+
// @ts-expect-error -- drop the viewport stub so jsdom's own value comes back
|
|
55
|
+
delete document.documentElement.clientWidth;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('holds the gutter open while the drawer is open', () => {
|
|
59
|
+
stubViewport(1024, 1009);
|
|
60
|
+
|
|
61
|
+
const view = render(
|
|
62
|
+
<MobileNav isOpen={true} onOpenChange={() => {}}>
|
|
63
|
+
<span>Nav content</span>
|
|
64
|
+
</MobileNav>,
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(document.documentElement.style.overflow).toBe('clip');
|
|
68
|
+
expect(rootGutter()).toBe('stable');
|
|
69
|
+
|
|
70
|
+
view.unmount();
|
|
71
|
+
|
|
72
|
+
expect(document.documentElement.style.overflow).toBe('');
|
|
73
|
+
expect(rootGutter()).toBe('');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('gives the gutter back when the drawer closes', () => {
|
|
77
|
+
stubViewport(1024, 1009);
|
|
78
|
+
|
|
79
|
+
const view = render(
|
|
80
|
+
<MobileNav isOpen={true} onOpenChange={() => {}}>
|
|
81
|
+
<span>Nav content</span>
|
|
82
|
+
</MobileNav>,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(rootGutter()).toBe('stable');
|
|
86
|
+
|
|
87
|
+
view.rerender(
|
|
88
|
+
<MobileNav isOpen={false} onOpenChange={() => {}}>
|
|
89
|
+
<span>Nav content</span>
|
|
90
|
+
</MobileNav>,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
expect(rootGutter()).toBe('');
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('holds nothing when the scrollbar is an overlay one', () => {
|
|
97
|
+
stubViewport(1024, 1024);
|
|
98
|
+
|
|
99
|
+
render(
|
|
100
|
+
<MobileNav isOpen={true} onOpenChange={() => {}}>
|
|
101
|
+
<span>Nav content</span>
|
|
102
|
+
</MobileNav>,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(document.documentElement.style.overflow).toBe('clip');
|
|
106
|
+
expect(rootGutter()).toBe('');
|
|
107
|
+
expect(document.documentElement.style.paddingRight).toBe('');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -73,6 +73,7 @@ import {
|
|
|
73
73
|
import {useMultiCombobox} from './hooks';
|
|
74
74
|
import {getInputARIA, isImeKeyEvent, mergeProps} from '../utils';
|
|
75
75
|
import {useAnnounce} from '../hooks/useAnnounce';
|
|
76
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
76
77
|
import type {BaseProps} from '../BaseProps';
|
|
77
78
|
import type {SizeValue} from '../utils/types';
|
|
78
79
|
import {useSize} from '../SizeContext/SizeContext';
|
|
@@ -706,6 +707,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
706
707
|
style,
|
|
707
708
|
}: MultiSelectorProps<T>) {
|
|
708
709
|
const t = useTranslator();
|
|
710
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
709
711
|
const placeholder =
|
|
710
712
|
placeholderFromProps ?? t('@astryx.multiSelector.selectPlaceholder');
|
|
711
713
|
const selectAllLabel =
|
|
@@ -1526,7 +1528,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1526
1528
|
}
|
|
1527
1529
|
aria-describedby={ariaDescribedBy}
|
|
1528
1530
|
aria-labelledby={ariaLabelledBy}
|
|
1529
|
-
aria-required={
|
|
1531
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
1530
1532
|
aria-invalid={status?.type === 'error' ? 'true' : undefined}
|
|
1531
1533
|
aria-busy={isBusy || undefined}
|
|
1532
1534
|
// With a disabledMessage the trigger keeps focusability via
|
|
@@ -219,6 +219,19 @@ export const docs = {
|
|
|
219
219
|
states: ['disabled', 'readonly'],
|
|
220
220
|
},
|
|
221
221
|
],
|
|
222
|
+
derived: [
|
|
223
|
+
// `padding` in any spelling — the shorthand, `paddingBlock`, or a lone
|
|
224
|
+
// `paddingBlockStart` — is parsed by the shared container expansion and
|
|
225
|
+
// emitted as normalized per-side `--astryx-number-input-padding-*`
|
|
226
|
+
// tokens. The wrapper and the number-stepper column both read those, so
|
|
227
|
+
// the column stays flush with the field edges under a themed padding.
|
|
228
|
+
{property: 'padding', expand: 'container'},
|
|
229
|
+
// Scoped to this component's own subtree: the stepper column's outer
|
|
230
|
+
// corners follow the field radius, so a themed `number-input`
|
|
231
|
+
// borderRadius has to reach the var the column reads, not just the
|
|
232
|
+
// wrapper's own `border-radius`.
|
|
233
|
+
{property: 'borderRadius', vars: ['--_field-radius']},
|
|
234
|
+
],
|
|
222
235
|
},
|
|
223
236
|
usage: {
|
|
224
237
|
description:
|
|
@@ -475,6 +488,17 @@ export const docsZh = {
|
|
|
475
488
|
states: ['disabled', 'readonly'],
|
|
476
489
|
},
|
|
477
490
|
],
|
|
491
|
+
derived: [
|
|
492
|
+
// 任何写法的 `padding`(简写、`paddingBlock`,或单独的
|
|
493
|
+
// `paddingBlockStart`)都由共享的 container 展开解析,并输出为规范化的
|
|
494
|
+
// 每侧 `--astryx-number-input-padding-*` 令牌。容器与数字步进器列都读取
|
|
495
|
+
// 这些令牌,因此在主题化内边距下步进器仍与字段边缘齐平。
|
|
496
|
+
{property: 'padding', expand: 'container'},
|
|
497
|
+
// 作用域限于该组件自身的子树:步进器列的外角跟随字段圆角,因此主题化的
|
|
498
|
+
// `number-input` borderRadius 必须传到步进器读取的变量,而不只是容器
|
|
499
|
+
// 自身的 `border-radius`。
|
|
500
|
+
{property: 'borderRadius', vars: ['--_field-radius']},
|
|
501
|
+
],
|
|
478
502
|
},
|
|
479
503
|
usage: {
|
|
480
504
|
description:
|
|
@@ -15,6 +15,8 @@ import userEvent from '@testing-library/user-event';
|
|
|
15
15
|
import {TestIcon} from '../__tests__/TestIcon';
|
|
16
16
|
import {InputGroup} from '../InputGroup';
|
|
17
17
|
import {NumberInput} from './NumberInput';
|
|
18
|
+
import {defineTheme} from '../theme/defineTheme';
|
|
19
|
+
import {generateThemeCSS} from '../theme/generateThemeRules';
|
|
18
20
|
import {__resetLiveRegionsForTest} from '../hooks/useAnnounce';
|
|
19
21
|
|
|
20
22
|
// FieldStatus announces status messages through the persistent useAnnounce
|
|
@@ -1745,3 +1747,139 @@ describe('NumberInput readonly theme state', () => {
|
|
|
1745
1747
|
expect(root).not.toHaveAttribute('data-readonly');
|
|
1746
1748
|
});
|
|
1747
1749
|
});
|
|
1750
|
+
|
|
1751
|
+
describe('NumberInput stepper padding coupling', () => {
|
|
1752
|
+
function generateThemeTestCSS(theme: Parameters<typeof generateThemeCSS>[0]) {
|
|
1753
|
+
const {prose, component} = generateThemeCSS(theme);
|
|
1754
|
+
return [prose, component].filter(Boolean).join('\n\n');
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// The chains the wrapper and the stepper column both read. Spelled out here
|
|
1758
|
+
// rather than imported so the test pins the actual token names a theme sets:
|
|
1759
|
+
// if the chain is renamed or a level is dropped, this fails.
|
|
1760
|
+
const BLOCK_START =
|
|
1761
|
+
'var(--astryx-number-input-padding-block-start, var(--astryx-number-input-padding, var(--spacing-1)))';
|
|
1762
|
+
const BLOCK_END =
|
|
1763
|
+
'var(--astryx-number-input-padding-block-end, var(--astryx-number-input-padding, var(--spacing-1)))';
|
|
1764
|
+
|
|
1765
|
+
// jsdom re-serializes a var() chain without the spaces after its commas.
|
|
1766
|
+
const noSpace = (value: string) => value.replace(/\s+/g, '');
|
|
1767
|
+
|
|
1768
|
+
it('reads its padding from the public number-input padding tokens', () => {
|
|
1769
|
+
const {container} = render(
|
|
1770
|
+
<NumberInput
|
|
1771
|
+
label="Quantity"
|
|
1772
|
+
value={5}
|
|
1773
|
+
onChange={() => {}}
|
|
1774
|
+
hasNumberSteppers
|
|
1775
|
+
/>,
|
|
1776
|
+
);
|
|
1777
|
+
const root = container.querySelector('.astryx-number-input') as HTMLElement;
|
|
1778
|
+
// Read as physical properties: StyleX compiles the block-axis logical
|
|
1779
|
+
// properties to `padding-top`/`padding-bottom` (identical in every
|
|
1780
|
+
// horizontal writing mode). Per side, not through the shared field base's
|
|
1781
|
+
// `paddingBlock` shorthand, so each edge can be cancelled on its own.
|
|
1782
|
+
const wrapper = getComputedStyle(root);
|
|
1783
|
+
expect(noSpace(wrapper.paddingTop)).toBe(noSpace(BLOCK_START));
|
|
1784
|
+
expect(noSpace(wrapper.paddingBottom)).toBe(noSpace(BLOCK_END));
|
|
1785
|
+
|
|
1786
|
+
// The column cancels the wrapper's block padding by reading the same
|
|
1787
|
+
// tokens — not a hardcoded default — so it stays flush when a theme
|
|
1788
|
+
// changes the padding. The column is the stepper buttons' direct parent.
|
|
1789
|
+
const steppers = container.querySelector(
|
|
1790
|
+
'button[aria-label="Increment Quantity"]',
|
|
1791
|
+
)?.parentElement as HTMLElement;
|
|
1792
|
+
const column = getComputedStyle(steppers);
|
|
1793
|
+
expect(noSpace(column.marginTop)).toBe(
|
|
1794
|
+
noSpace(`calc(-1 * ${BLOCK_START})`),
|
|
1795
|
+
);
|
|
1796
|
+
expect(noSpace(column.marginBottom)).toBe(
|
|
1797
|
+
noSpace(`calc(-1 * ${BLOCK_END})`),
|
|
1798
|
+
);
|
|
1799
|
+
});
|
|
1800
|
+
|
|
1801
|
+
// jsdom cannot resolve the @layer cascade, so the generated CSS is the
|
|
1802
|
+
// proof. Each case is a spelling of the SAME declaration: the column has to
|
|
1803
|
+
// track all of them, not just the one the component happened to name.
|
|
1804
|
+
describe('a themed padding reaches the steppers in every spelling', () => {
|
|
1805
|
+
it.each([
|
|
1806
|
+
[
|
|
1807
|
+
'the padding shorthand, two values',
|
|
1808
|
+
{padding: '14px 20px'},
|
|
1809
|
+
[
|
|
1810
|
+
'--astryx-number-input-padding-block-start: 14px',
|
|
1811
|
+
'--astryx-number-input-padding-block-end: 14px',
|
|
1812
|
+
'--astryx-number-input-padding-inline: 20px',
|
|
1813
|
+
],
|
|
1814
|
+
],
|
|
1815
|
+
[
|
|
1816
|
+
'the padding shorthand, one value',
|
|
1817
|
+
{padding: '10px'},
|
|
1818
|
+
['--astryx-number-input-padding: 10px'],
|
|
1819
|
+
],
|
|
1820
|
+
[
|
|
1821
|
+
'the block/inline longhands',
|
|
1822
|
+
{paddingBlock: '10px', paddingInline: '20px'},
|
|
1823
|
+
[
|
|
1824
|
+
'--astryx-number-input-padding-block-start: 10px',
|
|
1825
|
+
'--astryx-number-input-padding-block-end: 10px',
|
|
1826
|
+
'--astryx-number-input-padding-inline: 20px',
|
|
1827
|
+
],
|
|
1828
|
+
],
|
|
1829
|
+
[
|
|
1830
|
+
'a single edge longhand',
|
|
1831
|
+
{paddingBlockStart: '12px'},
|
|
1832
|
+
['--astryx-number-input-padding-block-start: 12px'],
|
|
1833
|
+
],
|
|
1834
|
+
[
|
|
1835
|
+
// A single var carrying "4px 12px" would make the column's
|
|
1836
|
+
// calc(-1 * …) invalid and drop the margin outright; the expansion
|
|
1837
|
+
// splits the two values into their own edges instead.
|
|
1838
|
+
'an asymmetric block padding',
|
|
1839
|
+
{paddingBlock: '4px 12px'},
|
|
1840
|
+
[
|
|
1841
|
+
'--astryx-number-input-padding-block-start: 4px',
|
|
1842
|
+
'--astryx-number-input-padding-block-end: 12px',
|
|
1843
|
+
],
|
|
1844
|
+
],
|
|
1845
|
+
[
|
|
1846
|
+
// `paddingTop` is `paddingBlockStart` in every horizontal writing
|
|
1847
|
+
// mode, so it normalizes onto the same tokens. The physical inline
|
|
1848
|
+
// pair is deliberately not mapped — see generateThemeRules.
|
|
1849
|
+
'the physical block longhands',
|
|
1850
|
+
{paddingTop: '14px', paddingBottom: '6px'},
|
|
1851
|
+
[
|
|
1852
|
+
'--astryx-number-input-padding-block-start: 14px',
|
|
1853
|
+
'--astryx-number-input-padding-block-end: 6px',
|
|
1854
|
+
],
|
|
1855
|
+
],
|
|
1856
|
+
])('%s', (_label, base, expected) => {
|
|
1857
|
+
const theme = defineTheme({
|
|
1858
|
+
name: 'number-input-padding-spelling-test',
|
|
1859
|
+
components: {'number-input': {base}},
|
|
1860
|
+
});
|
|
1861
|
+
const css = generateThemeTestCSS(theme);
|
|
1862
|
+
for (const declaration of expected) {
|
|
1863
|
+
expect(css).toContain(declaration);
|
|
1864
|
+
}
|
|
1865
|
+
// The expansion consumes the padding: a raw declaration left on the
|
|
1866
|
+
// wrapper is padding the column cannot see, which is the gap itself.
|
|
1867
|
+
expect(css).not.toMatch(
|
|
1868
|
+
/^\s*padding(-block|-inline|-top|-bottom)?(-start|-end)?:/m,
|
|
1869
|
+
);
|
|
1870
|
+
});
|
|
1871
|
+
});
|
|
1872
|
+
|
|
1873
|
+
it('carries a themed border radius to the stepper column corners', () => {
|
|
1874
|
+
// The column's outer corners read --_field-radius. Emitting only
|
|
1875
|
+
// `border-radius` on the wrapper rounds the field and leaves the steppers
|
|
1876
|
+
// at the default radius, which is visible wherever the two differ.
|
|
1877
|
+
const theme = defineTheme({
|
|
1878
|
+
name: 'number-input-radius-test',
|
|
1879
|
+
components: {'number-input': {base: {borderRadius: '2px'}}},
|
|
1880
|
+
});
|
|
1881
|
+
const css = generateThemeTestCSS(theme);
|
|
1882
|
+
expect(css).toContain('--_field-radius: 2px');
|
|
1883
|
+
expect(css).toContain('border-radius: 2px');
|
|
1884
|
+
});
|
|
1885
|
+
});
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
colorVars,
|
|
32
32
|
sizeVars,
|
|
33
33
|
spacingVars,
|
|
34
|
-
radiusVars,
|
|
35
34
|
typographyVars,
|
|
36
35
|
typeScaleVars,
|
|
37
36
|
borderVars,
|
|
@@ -53,11 +52,44 @@ import {getInputARIA} from '../utils';
|
|
|
53
52
|
import {useSize} from '../SizeContext/SizeContext';
|
|
54
53
|
import {useInputContainer} from '../hooks/useInputContainer';
|
|
55
54
|
import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
|
|
55
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
56
56
|
import {useInputGroup} from '../InputGroup/InputGroupContext';
|
|
57
57
|
|
|
58
|
+
// Public padding tokens for the `number-input` theme target. A theme writes an
|
|
59
|
+
// ordinary `padding` (in ANY spelling — the shorthand, `paddingBlock`, or a
|
|
60
|
+
// single `paddingBlockStart`) and the pipeline's `container` expansion parses
|
|
61
|
+
// it and emits these normalized per-side tokens; the wrapper and the stepper
|
|
62
|
+
// column both read them, so the column tracks whatever the theme sets instead
|
|
63
|
+
// of assuming the default. Routing through the shared expansion rather than a
|
|
64
|
+
// hand-rolled property→var mapping is what makes every spelling work: a
|
|
65
|
+
// mapping only fires for the exact property name it names.
|
|
66
|
+
//
|
|
67
|
+
// Read order per level: `var(--astryx-…, <next level>)`, terminating at the
|
|
68
|
+
// shared field defaults (NOT the container default --spacing-4, which is a
|
|
69
|
+
// layout metric and would resize every themed field). Built as chained const
|
|
70
|
+
// strings — no function calls — so StyleX can statically analyze them; same
|
|
71
|
+
// shape as the card/section/dialog chains in Layout/container.stylex.ts.
|
|
72
|
+
const FIELD_PAD_BLOCK = spacingVars['--spacing-1'];
|
|
73
|
+
const FIELD_PAD_INLINE = spacingVars['--spacing-2'];
|
|
74
|
+
const padBlockAll = `var(--astryx-number-input-padding, ${FIELD_PAD_BLOCK})`;
|
|
75
|
+
const padInlineAll = `var(--astryx-number-input-padding, ${FIELD_PAD_INLINE})`;
|
|
76
|
+
const padInline = `var(--astryx-number-input-padding-inline, ${padInlineAll})`;
|
|
77
|
+
const padInlineStart = `var(--astryx-number-input-padding-inline-start, ${padInline})`;
|
|
78
|
+
const padInlineEnd = `var(--astryx-number-input-padding-inline-end, ${padInline})`;
|
|
79
|
+
const padBlockStart = `var(--astryx-number-input-padding-block-start, ${padBlockAll})`;
|
|
80
|
+
const padBlockEnd = `var(--astryx-number-input-padding-block-end, ${padBlockAll})`;
|
|
81
|
+
|
|
58
82
|
const styles = stylex.create({
|
|
59
83
|
wrapper: {
|
|
60
84
|
zIndex: 1,
|
|
85
|
+
// Applied per side rather than through the shared field base's
|
|
86
|
+
// `paddingBlock`/`paddingInline` shorthands, because the stepper column
|
|
87
|
+
// has to cancel the block padding edge by edge — an asymmetric
|
|
88
|
+
// `paddingBlock: 4px 12px` needs two different negative margins.
|
|
89
|
+
paddingBlockStart: padBlockStart,
|
|
90
|
+
paddingBlockEnd: padBlockEnd,
|
|
91
|
+
paddingInlineStart: padInlineStart,
|
|
92
|
+
paddingInlineEnd: padInlineEnd,
|
|
61
93
|
},
|
|
62
94
|
wrapperWithNumberSteppers: {
|
|
63
95
|
paddingInlineEnd: 0,
|
|
@@ -101,13 +133,18 @@ const styles = stylex.create({
|
|
|
101
133
|
flexDirection: 'column',
|
|
102
134
|
flexShrink: 0,
|
|
103
135
|
width: spacingVars['--spacing-4'],
|
|
104
|
-
|
|
136
|
+
// Cancel the wrapper's block padding edge by edge so the column spans the
|
|
137
|
+
// field's full height. Reading the same tokens the wrapper applies is what
|
|
138
|
+
// keeps it flush under a themed padding — including an asymmetric one,
|
|
139
|
+
// where a single `marginBlock` would be wrong at one end.
|
|
140
|
+
marginBlockStart: `calc(-1 * ${padBlockStart})`,
|
|
141
|
+
marginBlockEnd: `calc(-1 * ${padBlockEnd})`,
|
|
105
142
|
borderInlineStartWidth: borderVars['--border-width'],
|
|
106
143
|
borderInlineStartStyle: 'solid',
|
|
107
144
|
borderInlineStartColor: colorVars['--color-border-emphasized'],
|
|
108
145
|
overflow: 'hidden',
|
|
109
|
-
borderStartEndRadius:
|
|
110
|
-
borderEndEndRadius:
|
|
146
|
+
borderStartEndRadius: 'var(--_field-radius)',
|
|
147
|
+
borderEndEndRadius: 'var(--_field-radius)',
|
|
111
148
|
},
|
|
112
149
|
numberStepperButton: {
|
|
113
150
|
boxSizing: 'border-box',
|
|
@@ -559,6 +596,7 @@ export function NumberInput({
|
|
|
559
596
|
...rest
|
|
560
597
|
}: NumberInputProps) {
|
|
561
598
|
const t = useTranslator();
|
|
599
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
562
600
|
const size = useSize(sizeProp, 'md');
|
|
563
601
|
const id = useId();
|
|
564
602
|
const inputLabelID = useId();
|
|
@@ -927,7 +965,7 @@ export function NumberInput({
|
|
|
927
965
|
value == null || !formatValue ? undefined : formattedValue
|
|
928
966
|
}
|
|
929
967
|
aria-describedby={ariaDescribedBy}
|
|
930
|
-
aria-required={
|
|
968
|
+
aria-required={isEffectivelyRequired ? 'true' : undefined}
|
|
931
969
|
aria-invalid={
|
|
932
970
|
status?.type === 'error' || !isInputValid ? 'true' : undefined
|
|
933
971
|
}
|
|
@@ -166,6 +166,8 @@ import {Outline, useOutlineFromMarkdown} from '@astryxdesign/core/Outline';
|
|
|
166
166
|
|
|
167
167
|
function MarkdownOutline({markdown}) {
|
|
168
168
|
// Derives {id, label, level} items from headings in the source.
|
|
169
|
+
// The Markdown component renders the same generated ids on its
|
|
170
|
+
// headings, so outline links scroll without any extra wiring.
|
|
169
171
|
const items = useOutlineFromMarkdown(markdown);
|
|
170
172
|
return <Outline items={items} />;
|
|
171
173
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file parseOutlineFromMarkdown.ts
|
|
5
|
-
* @input Uses Markdown parser internals
|
|
5
|
+
* @input Uses Markdown parser internals (parseMarkdown + heading slug
|
|
6
|
+
* helpers) and OutlineItem type
|
|
6
7
|
* @output Exports parseOutlineFromMarkdown for extracting heading outlines from Markdown
|
|
7
8
|
* @position Pure utility; consumed by useOutlineFromMarkdown and public exports
|
|
8
9
|
*
|
|
@@ -11,52 +12,21 @@
|
|
|
11
12
|
* - /packages/core/src/Outline/index.ts
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
parseMarkdown,
|
|
17
|
+
inlineText,
|
|
18
|
+
slugify,
|
|
19
|
+
uniqueSlug,
|
|
20
|
+
} from '../Markdown/parser';
|
|
15
21
|
import type {OutlineItem} from './types';
|
|
16
22
|
|
|
17
|
-
function inlineText(nodes: InlineNode[]): string {
|
|
18
|
-
return nodes
|
|
19
|
-
.map(node => {
|
|
20
|
-
switch (node.type) {
|
|
21
|
-
case 'text':
|
|
22
|
-
case 'code':
|
|
23
|
-
return node.content;
|
|
24
|
-
case 'bold':
|
|
25
|
-
case 'italic':
|
|
26
|
-
case 'strikethrough':
|
|
27
|
-
case 'link':
|
|
28
|
-
return inlineText(node.children);
|
|
29
|
-
case 'image':
|
|
30
|
-
return node.alt;
|
|
31
|
-
case 'citation':
|
|
32
|
-
case 'break':
|
|
33
|
-
return '';
|
|
34
|
-
}
|
|
35
|
-
})
|
|
36
|
-
.join('');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function slugify(value: string): string {
|
|
40
|
-
return value
|
|
41
|
-
.trim()
|
|
42
|
-
.toLowerCase()
|
|
43
|
-
.replace(/['"]/g, '')
|
|
44
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
45
|
-
.replace(/^-+|-+$/g, '');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function uniqueSlug(baseSlug: string, counts: Map<string, number>): string {
|
|
49
|
-
const fallbackSlug = baseSlug || 'section';
|
|
50
|
-
const count = counts.get(fallbackSlug) ?? 0;
|
|
51
|
-
counts.set(fallbackSlug, count + 1);
|
|
52
|
-
return count === 0 ? fallbackSlug : `${fallbackSlug}-${count}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
23
|
/**
|
|
56
24
|
* Extract heading items from a Markdown string.
|
|
57
25
|
*
|
|
58
26
|
* Uses Markdown's parser so fenced code blocks, tables, lists, and inline
|
|
59
27
|
* formatting are interpreted consistently with rendered Markdown output.
|
|
28
|
+
* Ids come from the parser's shared slug helpers, so they always match the
|
|
29
|
+
* `id` attributes Markdown renders on its headings.
|
|
60
30
|
*/
|
|
61
31
|
export function parseOutlineFromMarkdown(markdown: string): OutlineItem[] {
|
|
62
32
|
const counts = new Map<string, number>();
|
|
@@ -29,6 +29,7 @@ import {spacingVars} from '../theme/tokens.stylex';
|
|
|
29
29
|
import {Field} from '../Field/Field';
|
|
30
30
|
import type {InputStatus} from '../Field/types';
|
|
31
31
|
import {useTooltip} from '../Tooltip';
|
|
32
|
+
import {useResolvedRequired} from '../hooks/useResolvedRequired';
|
|
32
33
|
import {mergeProps} from '../utils';
|
|
33
34
|
import type {BaseProps} from '../BaseProps';
|
|
34
35
|
import type {SizeValue} from '../utils/types';
|
|
@@ -218,6 +219,13 @@ export function RadioList({
|
|
|
218
219
|
|
|
219
220
|
const groupRef = useRef<HTMLDivElement>(null);
|
|
220
221
|
|
|
222
|
+
// The radiogroup exposes the *effective* required state so a form-wide
|
|
223
|
+
// `defaultOptionality="required"` is announced even when the group carries no
|
|
224
|
+
// visible indicator. Individual radios keep their native `required` bound to
|
|
225
|
+
// the explicit `isRequired` (via context), so a layout default never switches
|
|
226
|
+
// on browser validation for the group.
|
|
227
|
+
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
228
|
+
|
|
221
229
|
// Disabled-reason tooltip. Applies to the whole-group disabled state. Disabled
|
|
222
230
|
// controls swallow pointer events, so the tooltip listeners attach to the
|
|
223
231
|
// radiogroup container and the radios stay perceivable via aria-disabled
|
|
@@ -387,7 +395,7 @@ export function RadioList({
|
|
|
387
395
|
.join(' ') || undefined
|
|
388
396
|
}
|
|
389
397
|
aria-invalid={status?.type === 'error' ? true : undefined}
|
|
390
|
-
aria-required={
|
|
398
|
+
aria-required={isEffectivelyRequired || undefined}
|
|
391
399
|
{...mergeProps(
|
|
392
400
|
themeProps('radio-list', {orientation, size}),
|
|
393
401
|
stylex.props(
|
|
@@ -344,6 +344,28 @@ describe('ResizeHandle', () => {
|
|
|
344
344
|
expect(hitArea.className).not.toContain('hitAreaOffsetX');
|
|
345
345
|
});
|
|
346
346
|
|
|
347
|
+
// The grab zone is stretched along the handle by its 0/0 insets, so anything
|
|
348
|
+
// that moves it across the handle displaces the whole zone off the divider —
|
|
349
|
+
// a percentage does it by half the handle's length, so the taller the panel
|
|
350
|
+
// the larger the dead region, and nothing about it is visible on screen.
|
|
351
|
+
it.each([
|
|
352
|
+
['horizontal' as const, 'translateX'],
|
|
353
|
+
['vertical' as const, 'translateY'],
|
|
354
|
+
])('offsets the %s grab zone along the pill axis only', (direction, axis) => {
|
|
355
|
+
render(<Harness handleProps={{direction, pillPlacement: 'start'}} />);
|
|
356
|
+
const hitArea = getSeparator().firstElementChild as HTMLElement;
|
|
357
|
+
const translates = (hitArea.getAttribute('style') ?? '')
|
|
358
|
+
.split(';')
|
|
359
|
+
.map(decl => decl.split(/:(.*)/s)[1]?.trim() ?? '')
|
|
360
|
+
.filter(value => value.startsWith('translate'));
|
|
361
|
+
|
|
362
|
+
// Both the LTR and RTL declarations of the horizontal offset.
|
|
363
|
+
expect(translates.length).toBeGreaterThan(0);
|
|
364
|
+
for (const translate of translates) {
|
|
365
|
+
expect(translate.startsWith(`${axis}(`)).toBe(true);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
|
|
347
369
|
// --- Prop composition (ordering choice: handler sits after {...props}) ---
|
|
348
370
|
|
|
349
371
|
it('runs a consumer onKeyDown alongside keyboard resizing', () => {
|
|
@@ -219,16 +219,22 @@ const dynamicStyles = stylex.create({
|
|
|
219
219
|
// directions because the pill's own offset is physical. Mixing the previous
|
|
220
220
|
// divider-relative `50%` anchor with a percentage translate stranded the grab
|
|
221
221
|
// zone to one side under RTL; this construction keeps it on the pill.
|
|
222
|
+
//
|
|
223
|
+
// Only the offset axis is translated. Unlike the pill — a small box anchored
|
|
224
|
+
// at the divider's midpoint, so it needs a −50% self-shift to centre — the
|
|
225
|
+
// grab zone is STRETCHED along the handle by hitAreaHorizontal/Vertical's
|
|
226
|
+
// 0/0 insets, so it is already in place on that axis. A percentage there
|
|
227
|
+
// would displace it by half the handle's own length, growing with the panel.
|
|
222
228
|
hitAreaOffsetX: (dir: number) => ({
|
|
223
229
|
insetInlineStart: 0,
|
|
224
230
|
transform: {
|
|
225
|
-
default: `
|
|
226
|
-
':is([dir="rtl"] *)': `
|
|
231
|
+
default: `translateX(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px))`,
|
|
232
|
+
':is([dir="rtl"] *)': `translateX(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) + 6.5px))`,
|
|
227
233
|
},
|
|
228
234
|
}),
|
|
229
235
|
hitAreaOffsetY: (dir: number) => ({
|
|
230
236
|
insetBlockStart: 0,
|
|
231
|
-
transform: `
|
|
237
|
+
transform: `translateY(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px))`,
|
|
232
238
|
}),
|
|
233
239
|
pillOffsetX: (dir: number) => ({
|
|
234
240
|
insetInlineStart: 0,
|
|
@@ -53,11 +53,36 @@ export const docs = {
|
|
|
53
53
|
description: 'Internal padding using the spacing scale (0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10). Use padding={0} for edge-to-edge content.',
|
|
54
54
|
default: '4',
|
|
55
55
|
},
|
|
56
|
+
{
|
|
57
|
+
name: 'paddingInline',
|
|
58
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
59
|
+
description: 'Inline (horizontal) padding override. Overrides only the inline-axis padding while preserving block padding from `padding` or the container theme default. Accepts the spacing scale (0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10).',
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: 'paddingInlineStart',
|
|
63
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
64
|
+
description: 'Inline-start padding override (left in LTR, right in RTL). Overrides paddingInline and padding on that edge only.',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'paddingInlineEnd',
|
|
68
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
69
|
+
description: 'Inline-end padding override (right in LTR, left in RTL). Overrides paddingInline and padding on that edge only.',
|
|
70
|
+
},
|
|
56
71
|
{
|
|
57
72
|
name: 'paddingBlock',
|
|
58
73
|
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
59
74
|
description: 'Block (vertical) padding override. Overrides only the block-axis padding while preserving inline padding from `padding` or the container theme default. Accepts the spacing scale (0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10).',
|
|
60
75
|
},
|
|
76
|
+
{
|
|
77
|
+
name: 'paddingBlockStart',
|
|
78
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
79
|
+
description: 'Block-start (top) padding, using the spacing scale. Overrides paddingBlock and padding on that edge only.',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'paddingBlockEnd',
|
|
83
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
84
|
+
description: 'Block-end (bottom) padding, using the spacing scale. Overrides paddingBlock and padding on that edge only.',
|
|
85
|
+
},
|
|
61
86
|
{
|
|
62
87
|
name: 'xstyle',
|
|
63
88
|
type: 'StyleXStyles',
|
|
@@ -135,11 +160,36 @@ export const docsZh = {
|
|
|
135
160
|
description: '使用间距比例的内部内边距(0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10)。使用 padding={0} 实现全宽内容。',
|
|
136
161
|
default: '4',
|
|
137
162
|
},
|
|
163
|
+
{
|
|
164
|
+
name: 'paddingInline',
|
|
165
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
166
|
+
description: '行内(水平)方向内边距覆盖。仅覆盖行内轴内边距,同时保留来自 padding 或容器主题默认值的块内边距。使用间距比例(0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10)。',
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: 'paddingInlineStart',
|
|
170
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
171
|
+
description: '行内起始内边距覆盖(LTR 中为左侧,RTL 中为右侧)。仅在该边上覆盖 paddingInline 和 padding。',
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: 'paddingInlineEnd',
|
|
175
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
176
|
+
description: '行内结束内边距覆盖(LTR 中为右侧,RTL 中为左侧)。仅在该边上覆盖 paddingInline 和 padding。',
|
|
177
|
+
},
|
|
138
178
|
{
|
|
139
179
|
name: 'paddingBlock',
|
|
140
180
|
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
141
181
|
description: '块(垂直)方向内边距覆盖。仅覆盖块轴内边距,同时保留来自 padding 或容器主题默认值的行内内边距。使用间距比例(0, 0.5, 1, 1.5, 2, 3, 4, 5, 6, 8, 10)。',
|
|
142
182
|
},
|
|
183
|
+
{
|
|
184
|
+
name: 'paddingBlockStart',
|
|
185
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
186
|
+
description: '块起始(顶部)内边距,使用间距刻度。仅在该边上覆盖 paddingBlock 和 padding。',
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: 'paddingBlockEnd',
|
|
190
|
+
type: '0 | 0.5 | 1 | 1.5 | 2 | 3 | 4 | 5 | 6 | 8 | 10',
|
|
191
|
+
description: '块结束(底部)内边距,使用间距刻度。仅在该边上覆盖 paddingBlock 和 padding。',
|
|
192
|
+
},
|
|
143
193
|
{
|
|
144
194
|
name: 'xstyle',
|
|
145
195
|
type: 'StyleXStyles',
|
|
@@ -192,7 +242,12 @@ export const docsDense = {
|
|
|
192
242
|
children: 'Content rendered inside section.',
|
|
193
243
|
dividers: 'Which sides of section have divider borders.',
|
|
194
244
|
padding: 'Internal padding via spacing scale; 0 for edge-to-edge content.',
|
|
245
|
+
paddingInline: 'Inline-axis padding override; preserves block padding from padding/theme.',
|
|
246
|
+
paddingInlineStart: 'Inline-start (left in LTR) padding override; wins over paddingInline/padding on that edge.',
|
|
247
|
+
paddingInlineEnd: 'Inline-end (right in LTR) padding override; wins over paddingInline/padding on that edge.',
|
|
195
248
|
paddingBlock: 'Block-axis padding override; preserves inline padding from padding/theme.',
|
|
249
|
+
paddingBlockStart: 'Block-start (top) padding override; wins over paddingBlock/padding on that edge.',
|
|
250
|
+
paddingBlockEnd: 'Block-end (bottom) padding override; wins over paddingBlock/padding on that edge.',
|
|
196
251
|
xstyle: 'StyleX styles for layout customization; must be stylex.create() value.',
|
|
197
252
|
},
|
|
198
253
|
};
|