@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.
Files changed (275) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/BottomSheet/BottomSheet.d.ts +11 -2
  3. package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheet.js +4 -0
  5. package/dist/BottomSheet/BottomSheetPanel.d.ts +5 -2
  6. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  7. package/dist/BottomSheet/BottomSheetPanel.js +65 -15
  8. package/dist/BottomSheet/index.d.ts +1 -1
  9. package/dist/BottomSheet/index.d.ts.map +1 -1
  10. package/dist/BottomSheet/snapOffsets.d.ts +57 -20
  11. package/dist/BottomSheet/snapOffsets.d.ts.map +1 -1
  12. package/dist/BottomSheet/snapOffsets.js +106 -30
  13. package/dist/BottomSheet/useSheetGestures.d.ts +13 -8
  14. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  15. package/dist/BottomSheet/useSheetGestures.js +160 -64
  16. package/dist/Calendar/Calendar.d.ts +17 -0
  17. package/dist/Calendar/Calendar.d.ts.map +1 -1
  18. package/dist/Calendar/Calendar.js +34 -3
  19. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts +14 -0
  20. package/dist/Calendar/getStandaloneShortWeekdayNames.d.ts.map +1 -0
  21. package/dist/Calendar/getStandaloneShortWeekdayNames.js +26 -0
  22. package/dist/Calendar/hooks/useCalendarConstraints.d.ts +22 -1
  23. package/dist/Calendar/hooks/useCalendarConstraints.d.ts.map +1 -1
  24. package/dist/Calendar/hooks/useCalendarConstraints.js +26 -4
  25. package/dist/Calendar/hooks/useCalendarDays.d.ts.map +1 -1
  26. package/dist/Calendar/hooks/useCalendarDays.js +14 -10
  27. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts +47 -0
  28. package/dist/Calendar/standaloneShortWeekdayNames.generated.d.ts.map +1 -0
  29. package/dist/Calendar/standaloneShortWeekdayNames.generated.js +40 -0
  30. package/dist/Center/Center.d.ts +23 -1
  31. package/dist/Center/Center.d.ts.map +1 -1
  32. package/dist/Center/Center.js +12 -6
  33. package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
  34. package/dist/CheckboxInput/CheckboxInput.js +9 -0
  35. package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
  36. package/dist/ComplexSelector/ComplexSelector.js +6 -1
  37. package/dist/DateInput/DateInput.d.ts.map +1 -1
  38. package/dist/DateInput/DateInput.js +6 -1
  39. package/dist/DateRangeInput/DateRangeInput.d.ts +23 -1
  40. package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
  41. package/dist/DateRangeInput/DateRangeInput.js +38 -3
  42. package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
  43. package/dist/DateTimeInput/DateTimeInput.js +7 -2
  44. package/dist/Dialog/Dialog.d.ts.map +1 -1
  45. package/dist/Dialog/Dialog.js +3 -3
  46. package/dist/Field/FieldLabel.d.ts +2 -1
  47. package/dist/Field/FieldLabel.d.ts.map +1 -1
  48. package/dist/Field/FieldLabel.js +21 -3
  49. package/dist/Field/InputClearButton.d.ts +2 -1
  50. package/dist/Field/InputClearButton.d.ts.map +1 -1
  51. package/dist/Field/InputClearButton.js +6 -1
  52. package/dist/FormLayout/FormLayout.d.ts +24 -2
  53. package/dist/FormLayout/FormLayout.d.ts.map +1 -1
  54. package/dist/FormLayout/FormLayout.js +7 -2
  55. package/dist/FormLayout/FormLayoutContext.d.ts +15 -2
  56. package/dist/FormLayout/FormLayoutContext.d.ts.map +1 -1
  57. package/dist/FormLayout/FormLayoutContext.js +19 -4
  58. package/dist/FormLayout/index.d.ts +1 -1
  59. package/dist/FormLayout/index.d.ts.map +1 -1
  60. package/dist/Item/Item.d.ts +9 -1
  61. package/dist/Item/Item.d.ts.map +1 -1
  62. package/dist/Item/Item.js +45 -7
  63. package/dist/Layer/useLayer.d.ts.map +1 -1
  64. package/dist/Layer/useLayer.js +3 -2
  65. package/dist/Layout/container.stylex.d.ts +4 -2
  66. package/dist/Layout/container.stylex.d.ts.map +1 -1
  67. package/dist/Layout/container.stylex.js +19 -11
  68. package/dist/Layout/index.d.ts +1 -0
  69. package/dist/Layout/index.d.ts.map +1 -1
  70. package/dist/Layout/index.js +4 -0
  71. package/dist/Layout/padding.stylex.d.ts +315 -12
  72. package/dist/Layout/padding.stylex.d.ts.map +1 -1
  73. package/dist/Layout/padding.stylex.js +389 -12
  74. package/dist/Lightbox/Lightbox.d.ts.map +1 -1
  75. package/dist/Lightbox/Lightbox.js +2 -1
  76. package/dist/Markdown/Markdown.d.ts +8 -0
  77. package/dist/Markdown/Markdown.d.ts.map +1 -1
  78. package/dist/Markdown/Markdown.js +31 -3
  79. package/dist/Markdown/parser.d.ts +14 -2
  80. package/dist/Markdown/parser.d.ts.map +1 -1
  81. package/dist/Markdown/parser.js +50 -2
  82. package/dist/MobileNav/MobileNav.d.ts +1 -0
  83. package/dist/MobileNav/MobileNav.d.ts.map +1 -1
  84. package/dist/MobileNav/MobileNav.js +24 -6
  85. package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
  86. package/dist/MultiSelector/MultiSelector.js +6 -1
  87. package/dist/NumberInput/NumberInput.d.ts.map +1 -1
  88. package/dist/NumberInput/NumberInput.js +36 -3
  89. package/dist/Outline/parseOutlineFromMarkdown.d.ts +2 -0
  90. package/dist/Outline/parseOutlineFromMarkdown.d.ts.map +1 -1
  91. package/dist/Outline/parseOutlineFromMarkdown.js +5 -31
  92. package/dist/RadioList/RadioList.d.ts.map +1 -1
  93. package/dist/RadioList/RadioList.js +12 -1
  94. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  95. package/dist/Resizable/ResizeHandle.js +3 -3
  96. package/dist/Section/Section.d.ts +36 -1
  97. package/dist/Section/Section.d.ts.map +1 -1
  98. package/dist/Section/Section.js +12 -2
  99. package/dist/Selector/Selector.d.ts +26 -1
  100. package/dist/Selector/Selector.d.ts.map +1 -1
  101. package/dist/Selector/Selector.js +93 -15
  102. package/dist/Selector/SelectorOption.d.ts +14 -1
  103. package/dist/Selector/SelectorOption.d.ts.map +1 -1
  104. package/dist/Selector/SelectorOption.js +6 -0
  105. package/dist/Selector/SelectorRowLayoutContext.d.ts +8 -0
  106. package/dist/Selector/SelectorRowLayoutContext.d.ts.map +1 -0
  107. package/dist/Selector/SelectorRowLayoutContext.js +27 -0
  108. package/dist/Selector/types.d.ts +1 -0
  109. package/dist/Selector/types.d.ts.map +1 -1
  110. package/dist/Slider/Slider.d.ts.map +1 -1
  111. package/dist/Slider/Slider.js +2 -2
  112. package/dist/Stack/Stack.d.ts +23 -1
  113. package/dist/Stack/Stack.d.ts.map +1 -1
  114. package/dist/Stack/Stack.js +12 -6
  115. package/dist/StatusDot/StatusDot.d.ts +43 -3
  116. package/dist/StatusDot/StatusDot.d.ts.map +1 -1
  117. package/dist/StatusDot/StatusDot.js +42 -5
  118. package/dist/Switch/Switch.d.ts.map +1 -1
  119. package/dist/Switch/Switch.js +9 -0
  120. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.d.ts.map +1 -1
  121. package/dist/Table/plugins/rowExpansion/useTableRowExpansion.js +7 -2
  122. package/dist/TextArea/TextArea.d.ts.map +1 -1
  123. package/dist/TextArea/TextArea.js +6 -1
  124. package/dist/TextInput/TextInput.d.ts.map +1 -1
  125. package/dist/TextInput/TextInput.js +6 -1
  126. package/dist/TimeInput/TimeInput.d.ts.map +1 -1
  127. package/dist/TimeInput/TimeInput.js +6 -1
  128. package/dist/astryx.css +40 -21
  129. package/dist/hooks/scrollbarGutter.d.ts +28 -0
  130. package/dist/hooks/scrollbarGutter.d.ts.map +1 -0
  131. package/dist/hooks/scrollbarGutter.js +103 -0
  132. package/dist/hooks/useResolvedRequired.d.ts +19 -0
  133. package/dist/hooks/useResolvedRequired.d.ts.map +1 -0
  134. package/dist/hooks/useResolvedRequired.js +40 -0
  135. package/dist/hooks/useScrollLock.d.ts +4 -0
  136. package/dist/hooks/useScrollLock.d.ts.map +1 -1
  137. package/dist/hooks/useScrollLock.js +13 -1
  138. package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
  139. package/dist/theme/derivedVarRegistry.js +7 -0
  140. package/dist/theme/generateThemeRules.d.ts.map +1 -1
  141. package/dist/theme/generateThemeRules.js +37 -4
  142. package/dist/utils/plainDate.d.ts +6 -0
  143. package/dist/utils/plainDate.d.ts.map +1 -1
  144. package/dist/utils/plainDate.js +12 -0
  145. package/locales/af-ZA.json +958 -0
  146. package/locales/ar-SA.json +998 -0
  147. package/locales/ca-ES.json +974 -0
  148. package/locales/cs-CZ.json +990 -0
  149. package/locales/da-DK.json +966 -0
  150. package/locales/de-DE.json +966 -0
  151. package/locales/el-GR.json +990 -0
  152. package/locales/en.json +8 -0
  153. package/locales/es-ES.json +978 -0
  154. package/locales/fi-FI.json +990 -0
  155. package/locales/fr-FR.json +928 -0
  156. package/locales/he-IL.json +990 -0
  157. package/locales/hu-HU.json +990 -0
  158. package/locales/it-IT.json +978 -0
  159. package/locales/ja-JP.json +998 -0
  160. package/locales/ko-KR.json +990 -0
  161. package/locales/nl-NL.json +954 -0
  162. package/locales/no-NO.json +978 -0
  163. package/locales/pl-PL.json +982 -0
  164. package/locales/pseudo.json +6 -0
  165. package/locales/pt-BR.json +982 -0
  166. package/locales/pt-PT.json +982 -0
  167. package/locales/ro-RO.json +982 -0
  168. package/locales/ru-RU.json +990 -0
  169. package/locales/sr-SP.json +990 -0
  170. package/locales/sv-SE.json +986 -0
  171. package/locales/tr-TR.json +990 -0
  172. package/locales/uk-UA.json +990 -0
  173. package/locales/vi-VN.json +986 -0
  174. package/locales/zh-CN.json +998 -0
  175. package/locales/zh-TW.json +998 -0
  176. package/package.json +3 -3
  177. package/src/BottomSheet/BottomSheet.doc.mjs +36 -3
  178. package/src/BottomSheet/BottomSheet.test.tsx +177 -2
  179. package/src/BottomSheet/BottomSheet.tsx +16 -2
  180. package/src/BottomSheet/BottomSheetPanel.test.tsx +41 -0
  181. package/src/BottomSheet/BottomSheetPanel.tsx +95 -15
  182. package/src/BottomSheet/index.ts +5 -1
  183. package/src/BottomSheet/snapOffsets.test.ts +114 -47
  184. package/src/BottomSheet/snapOffsets.ts +119 -33
  185. package/src/BottomSheet/useSheetGestures.test.ts +113 -15
  186. package/src/BottomSheet/useSheetGestures.ts +196 -100
  187. package/src/Calendar/Calendar.doc.mjs +16 -0
  188. package/src/Calendar/Calendar.test.tsx +137 -0
  189. package/src/Calendar/Calendar.tsx +65 -2
  190. package/src/Calendar/getStandaloneShortWeekdayNames.test.ts +54 -0
  191. package/src/Calendar/getStandaloneShortWeekdayNames.ts +38 -0
  192. package/src/Calendar/hooks/useCalendarConstraints.ts +54 -3
  193. package/src/Calendar/hooks/useCalendarDays.ts +13 -10
  194. package/src/Calendar/standaloneShortWeekdayNames.generated.ts +50 -0
  195. package/src/Center/Center.doc.mjs +48 -0
  196. package/src/Center/Center.test.tsx +158 -0
  197. package/src/Center/Center.tsx +50 -9
  198. package/src/CheckboxInput/CheckboxInput.tsx +6 -0
  199. package/src/ComplexSelector/ComplexSelector.tsx +3 -1
  200. package/src/DateInput/DateInput.tsx +3 -1
  201. package/src/DateRangeInput/DateRangeInput.doc.mjs +16 -0
  202. package/src/DateRangeInput/DateRangeInput.test.tsx +81 -1
  203. package/src/DateRangeInput/DateRangeInput.tsx +72 -1
  204. package/src/DateTimeInput/DateTimeInput.tsx +4 -2
  205. package/src/Dialog/Dialog.test.tsx +24 -0
  206. package/src/Dialog/Dialog.tsx +3 -0
  207. package/src/Field/Field.doc.mjs +1 -0
  208. package/src/Field/FieldLabel.tsx +20 -4
  209. package/src/Field/InputClearButton.test.tsx +25 -0
  210. package/src/Field/InputClearButton.tsx +4 -1
  211. package/src/FormLayout/FormLayout.doc.mjs +13 -0
  212. package/src/FormLayout/FormLayout.test.tsx +181 -4
  213. package/src/FormLayout/FormLayout.tsx +33 -2
  214. package/src/FormLayout/FormLayoutContext.ts +22 -7
  215. package/src/FormLayout/index.ts +1 -1
  216. package/src/Item/Item.doc.mjs +1 -0
  217. package/src/Item/Item.test.tsx +50 -0
  218. package/src/Item/Item.tsx +40 -1
  219. package/src/Layer/useLayer.tsx +13 -2
  220. package/src/Layout/Layout.doc.mjs +18 -0
  221. package/src/Layout/container.stylex.ts +11 -3
  222. package/src/Layout/index.ts +4 -0
  223. package/src/Layout/overlayPaddingReset.test.tsx +222 -0
  224. package/src/Layout/padding.stylex.ts +192 -12
  225. package/src/Lightbox/Lightbox.tsx +2 -1
  226. package/src/Markdown/Markdown.doc.mjs +3 -0
  227. package/src/Markdown/Markdown.test.tsx +78 -0
  228. package/src/Markdown/Markdown.tsx +41 -1
  229. package/src/Markdown/parser.ts +60 -2
  230. package/src/MobileNav/MobileNav.tsx +61 -5
  231. package/src/MobileNav/MobileNavEntryAnimation.test.tsx +212 -0
  232. package/src/MobileNav/MobileNavScrollbarGutter.test.tsx +109 -0
  233. package/src/MultiSelector/MultiSelector.tsx +3 -1
  234. package/src/NumberInput/NumberInput.doc.mjs +24 -0
  235. package/src/NumberInput/NumberInput.test.tsx +138 -0
  236. package/src/NumberInput/NumberInput.tsx +43 -5
  237. package/src/Outline/Outline.doc.mjs +2 -0
  238. package/src/Outline/parseOutlineFromMarkdown.ts +10 -40
  239. package/src/RadioList/RadioList.tsx +9 -1
  240. package/src/Resizable/ResizeHandle.test.tsx +22 -0
  241. package/src/Resizable/ResizeHandle.tsx +9 -3
  242. package/src/Section/Section.doc.mjs +55 -0
  243. package/src/Section/Section.test.tsx +201 -4
  244. package/src/Section/Section.tsx +69 -0
  245. package/src/Selector/Selector.doc.mjs +16 -2
  246. package/src/Selector/Selector.test.tsx +524 -0
  247. package/src/Selector/Selector.tsx +161 -17
  248. package/src/Selector/SelectorOption.doc.mjs +7 -0
  249. package/src/Selector/SelectorOption.tsx +21 -0
  250. package/src/Selector/SelectorRowLayoutContext.ts +32 -0
  251. package/src/Selector/types.ts +4 -4
  252. package/src/Slider/Slider.tsx +10 -0
  253. package/src/Stack/Stack.doc.mjs +72 -0
  254. package/src/Stack/Stack.test.tsx +156 -0
  255. package/src/Stack/Stack.tsx +50 -8
  256. package/src/StatusDot/StatusDot.doc.mjs +13 -0
  257. package/src/StatusDot/StatusDot.test.tsx +115 -8
  258. package/src/StatusDot/StatusDot.tsx +74 -9
  259. package/src/Switch/Switch.tsx +6 -0
  260. package/src/Table/plugins/rowExpansion/useTableRowExpansion.tsx +20 -2
  261. package/src/TextArea/TextArea.tsx +3 -1
  262. package/src/TextInput/TextInput.tsx +3 -1
  263. package/src/TimeInput/TimeInput.tsx +3 -1
  264. package/src/hooks/scrollbarGutter.test.ts +167 -0
  265. package/src/hooks/scrollbarGutter.ts +121 -0
  266. package/src/hooks/useResolvedRequired.ts +42 -0
  267. package/src/hooks/useScrollLock.doc.mjs +4 -4
  268. package/src/hooks/useScrollLock.test.ts +73 -0
  269. package/src/hooks/useScrollLock.ts +14 -0
  270. package/src/theme/derivedVarRegistry.test.ts +14 -3
  271. package/src/theme/derivedVarRegistry.ts +4 -0
  272. package/src/theme/generateThemeRules.test.ts +79 -0
  273. package/src/theme/generateThemeRules.ts +49 -4
  274. package/src/utils/plainDate.test.ts +50 -0
  275. 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={isRequired ? 'true' : undefined}
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
- marginBlock: `calc(-1 * ${spacingVars['--spacing-1']})`,
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: radiusVars['--radius-element'],
110
- borderEndEndRadius: radiusVars['--radius-element'],
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={isRequired === true ? 'true' : undefined}
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 and OutlineItem type
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 {parseMarkdown, type InlineNode} from '../Markdown/parser';
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={isRequired || undefined}
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: `translate(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px), -50%)`,
226
- ':is([dir="rtl"] *)': `translate(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) + 6.5px), -50%)`,
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: `translate(-50%, calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px))`,
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
  };