@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
@@ -23,6 +23,7 @@ import {spacingVars, colorVars, radiusVars} from '../../../theme/tokens.stylex';
23
23
  import {Icon} from '../../../Icon';
24
24
  import {resolveContextActions} from '../../tableContextMenu';
25
25
  import {useTranslator} from '../../../i18n';
26
+ import {rtlStyles} from '../../../utils';
26
27
  import type {
27
28
  TablePlugin,
28
29
  TableColumn,
@@ -92,8 +93,20 @@ const expansionStyles = stylex.create({
92
93
  color: colorVars['--color-icon-primary'],
93
94
  },
94
95
  },
96
+ // The RTL mirror is folded into each state's transform rather than living
97
+ // on a parent span, matching TreeListItem's chevron (both are `transform`,
98
+ // so on one element the later value would win).
95
99
  chevronExpanded: {
96
- transform: 'rotate(90deg)',
100
+ transform: {
101
+ default: 'rotate(90deg)',
102
+ ':is([dir="rtl"] *)': 'scaleX(-1) rotate(90deg)',
103
+ },
104
+ },
105
+ chevronCollapsed: {
106
+ transform: {
107
+ default: 'rotate(0deg)',
108
+ ':is([dir="rtl"] *)': 'scaleX(-1) rotate(0deg)',
109
+ },
97
110
  },
98
111
  expandedRow: {
99
112
  backgroundColor: colorVars['--color-background-muted'],
@@ -121,7 +134,9 @@ function ExpansionChevron({
121
134
  type="button"
122
135
  {...stylex.props(
123
136
  expansionStyles.chevronButton,
124
- isExpanded && expansionStyles.chevronExpanded,
137
+ isExpanded
138
+ ? expansionStyles.chevronExpanded
139
+ : expansionStyles.chevronCollapsed,
125
140
  )}
126
141
  onClick={e => {
127
142
  e.stopPropagation();
@@ -249,6 +264,9 @@ export function useTableRowExpansion<T extends Record<string, unknown>>(
249
264
  icon={isExpanded ? 'chevronDown' : 'chevronRight'}
250
265
  size="xsm"
251
266
  aria-hidden
267
+ // chevronDown needs no mirroring; chevronRight (collapsed,
268
+ // pointing toward the reveal direction) does.
269
+ xstyle={!isExpanded && rtlStyles.mirror}
252
270
  />
253
271
  ),
254
272
  onSelect: () => onToggle(key),
@@ -52,6 +52,7 @@ import type {SizeValue} from '../utils/types';
52
52
  import {useInputContainer} from '../hooks/useInputContainer';
53
53
  import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
54
54
  import {useAnnounce} from '../hooks/useAnnounce';
55
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
55
56
  import {useSize} from '../SizeContext/SizeContext';
56
57
  import {themeProps} from '../utils/themeProps';
57
58
  import {characterCount} from '../utils/characters';
@@ -394,6 +395,7 @@ export function TextArea({
394
395
  }: TextAreaProps) {
395
396
  const size = useSize(sizeProp, 'md');
396
397
  const t = useTranslator();
398
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
397
399
  const announce = useAnnounce();
398
400
  const id = useId();
399
401
  const descriptionID = useId();
@@ -598,7 +600,7 @@ export function TextArea({
598
600
  autoFocus={hasAutoFocus}
599
601
  data-autofocus={hasAutoFocus || undefined}
600
602
  aria-describedby={ariaDescribedBy}
601
- aria-required={isRequired && !isOptional ? 'true' : undefined}
603
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
602
604
  aria-invalid={
603
605
  status?.type === 'error' ||
604
606
  (maxLength != null && valueLength > maxLength)
@@ -101,6 +101,7 @@ import {mergeProps, mergeRefs} from '../utils';
101
101
  import {useSize} from '../SizeContext/SizeContext';
102
102
  import {useInputContainer} from '../hooks/useInputContainer';
103
103
  import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
104
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
104
105
  import {useInputGroup} from '../InputGroup/InputGroupContext';
105
106
  import type {BaseProps} from '../BaseProps';
106
107
  import type {SizeValue} from '../utils/types';
@@ -301,6 +302,7 @@ export function TextInput({
301
302
  ...rest
302
303
  }: TextInputProps) {
303
304
  const t = useTranslator();
305
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
304
306
  const size = useSize(sizeProp, 'md');
305
307
 
306
308
  const id = useId();
@@ -445,7 +447,7 @@ export function TextInput({
445
447
  autoFocus={hasAutoFocus}
446
448
  data-autofocus={hasAutoFocus || undefined}
447
449
  aria-describedby={ariaDescribedBy}
448
- aria-required={isRequired === true ? 'true' : undefined}
450
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
449
451
  aria-invalid={status?.type === 'error' ? 'true' : undefined}
450
452
  aria-busy={isBusy || undefined}
451
453
  aria-labelledby={ariaLabelledBy}
@@ -67,6 +67,7 @@ import {useSize} from '../SizeContext/SizeContext';
67
67
  import {useAnnounce} from '../hooks/useAnnounce';
68
68
  import {useInputContainer} from '../hooks/useInputContainer';
69
69
  import {useInputStatusIcon} from '../hooks/useInputStatusIcon';
70
+ import {useResolvedRequired} from '../hooks/useResolvedRequired';
70
71
  import {useInputGroup} from '../InputGroup/InputGroupContext';
71
72
  import {groupStyles} from '../InputGroup/groupStyles';
72
73
  import {useTooltip} from '../Tooltip';
@@ -347,6 +348,7 @@ export function TimeInput({
347
348
  ref,
348
349
  }: TimeInputProps) {
349
350
  const t = useTranslator();
351
+ const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
350
352
  const placeholder =
351
353
  placeholderFromProps ?? t('@astryx.timeInput.placeholder');
352
354
  const size = useSize(sizeProp, 'md');
@@ -664,7 +666,7 @@ export function TimeInput({
664
666
  autoFocus={hasAutoFocus}
665
667
  data-autofocus={hasAutoFocus || undefined}
666
668
  aria-describedby={ariaDescribedBy}
667
- aria-required={isRequired === true ? 'true' : undefined}
669
+ aria-required={isEffectivelyRequired ? 'true' : undefined}
668
670
  aria-invalid={
669
671
  status?.type === 'error' || !isInputValid ? 'true' : undefined
670
672
  }
@@ -0,0 +1,167 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file scrollbarGutter.test.ts
5
+ * @input Uses vitest and a stubbed viewport + element box (jsdom does no layout)
6
+ * @output Unit tests for holdScrollbarGutter
7
+ * @position Testing; validates scrollbarGutter.ts implementation
8
+ *
9
+ * SYNC: When scrollbarGutter.ts changes, update tests to match new behavior
10
+ */
11
+
12
+ import {afterEach, describe, expect, it} from 'vitest';
13
+ import {holdScrollbarGutter} from './scrollbarGutter';
14
+
15
+ /**
16
+ * jsdom does no layout, so both halves of the measurement are stubbed: the
17
+ * viewport (`innerWidth` vs `documentElement.clientWidth`) and the element's
18
+ * own box, which `widths` reads from and a test flips to simulate the lock
19
+ * widening it.
20
+ */
21
+ function stub({
22
+ innerWidth,
23
+ clientWidth,
24
+ widths,
25
+ }: {
26
+ innerWidth: number;
27
+ clientWidth: number;
28
+ widths?: () => number;
29
+ }) {
30
+ Object.defineProperty(window, 'innerWidth', {
31
+ value: innerWidth,
32
+ configurable: true,
33
+ writable: true,
34
+ });
35
+ Object.defineProperty(document.documentElement, 'clientWidth', {
36
+ value: clientWidth,
37
+ configurable: true,
38
+ });
39
+ if (widths) {
40
+ document.body.getBoundingClientRect = () => ({width: widths()}) as DOMRect;
41
+ }
42
+ }
43
+
44
+ const rootGutter = () => document.documentElement.style.scrollbarGutter;
45
+
46
+ describe('holdScrollbarGutter', () => {
47
+ afterEach(() => {
48
+ document.body.style.cssText = '';
49
+ document.documentElement.style.cssText = '';
50
+ // @ts-expect-error -- drop the stubs so jsdom's own values come back
51
+ delete document.documentElement.clientWidth;
52
+ // @ts-expect-error -- restore the prototype's implementation
53
+ delete document.body.getBoundingClientRect;
54
+ });
55
+
56
+ it('holds the gutter open when a space-taking scrollbar is about to be hidden', () => {
57
+ // A 1024px window over a 1009px layout viewport = a 15px classic scrollbar.
58
+ stub({innerWidth: 1024, clientWidth: 1009});
59
+
60
+ const hold = holdScrollbarGutter(document.body);
61
+
62
+ expect(rootGutter()).toBe('stable');
63
+
64
+ hold.settle();
65
+ hold.release();
66
+
67
+ expect(rootGutter()).toBe('');
68
+ });
69
+
70
+ it('leaves an overlay scrollbar alone — there is no gutter to hold', () => {
71
+ stub({innerWidth: 1024, clientWidth: 1024});
72
+
73
+ const hold = holdScrollbarGutter(document.body);
74
+ hold.settle();
75
+
76
+ expect(rootGutter()).toBe('');
77
+ expect(document.body.style.paddingRight).toBe('');
78
+
79
+ hold.release();
80
+ });
81
+
82
+ it('does not pad when holding the gutter kept the element still', () => {
83
+ stub({innerWidth: 1024, clientWidth: 1009, widths: () => 1009});
84
+
85
+ const hold = holdScrollbarGutter(document.body);
86
+ hold.settle();
87
+
88
+ expect(rootGutter()).toBe('stable');
89
+ expect(document.body.style.paddingRight).toBe('');
90
+
91
+ hold.release();
92
+ });
93
+
94
+ it('falls back to padding when the element grew anyway', () => {
95
+ // What an engine without scrollbar-gutter support does: the gutter request
96
+ // is ignored, the scrollbar goes away, and the element widens by 15px.
97
+ let width = 1009;
98
+ stub({innerWidth: 1024, clientWidth: 1009, widths: () => width});
99
+
100
+ const hold = holdScrollbarGutter(document.body);
101
+ width = 1024;
102
+ hold.settle();
103
+
104
+ expect(document.body.style.paddingRight).toBe('15px');
105
+
106
+ hold.release();
107
+
108
+ expect(document.body.style.paddingRight).toBe('');
109
+ });
110
+
111
+ it("adds to the page's own padding instead of replacing it", () => {
112
+ let width = 1009;
113
+ stub({innerWidth: 1024, clientWidth: 1009, widths: () => width});
114
+ document.body.style.paddingRight = '24px';
115
+
116
+ const hold = holdScrollbarGutter(document.body);
117
+ width = 1024;
118
+ hold.settle();
119
+
120
+ expect(document.body.style.paddingRight).toBe('39px');
121
+
122
+ hold.release();
123
+
124
+ expect(document.body.style.paddingRight).toBe('24px');
125
+ });
126
+
127
+ it('restores the page\u2019s own scrollbar-gutter rather than clearing it', () => {
128
+ stub({innerWidth: 1024, clientWidth: 1009});
129
+ document.documentElement.style.scrollbarGutter = 'stable both-edges';
130
+
131
+ const hold = holdScrollbarGutter(document.body);
132
+
133
+ expect(rootGutter()).toBe('stable');
134
+
135
+ hold.settle();
136
+ hold.release();
137
+
138
+ expect(rootGutter()).toBe('stable both-edges');
139
+ });
140
+
141
+ it('settles once, however many times it is called', () => {
142
+ let width = 1009;
143
+ stub({innerWidth: 1024, clientWidth: 1009, widths: () => width});
144
+
145
+ const hold = holdScrollbarGutter(document.body);
146
+ width = 1024;
147
+ hold.settle();
148
+ hold.settle();
149
+ hold.settle();
150
+
151
+ expect(document.body.style.paddingRight).toBe('15px');
152
+
153
+ hold.release();
154
+ });
155
+
156
+ it('does nothing where there is no layout to measure', () => {
157
+ stub({innerWidth: 1024, clientWidth: 0});
158
+
159
+ const hold = holdScrollbarGutter(document.body);
160
+ hold.settle();
161
+
162
+ expect(rootGutter()).toBe('');
163
+ expect(document.body.style.paddingRight).toBe('');
164
+
165
+ hold.release();
166
+ });
167
+ });
@@ -0,0 +1,121 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file scrollbarGutter.ts
5
+ * @input The element a scroll lock pins, plus live layout measurements
6
+ * @output Exports holdScrollbarGutter and the ScrollbarGutterHold it returns
7
+ * @position Internal hook utility; used by useScrollLock and MobileNav so that
8
+ * locking background scroll does not resize the layout viewport.
9
+ *
10
+ * Locking scroll hides the document's scrollbar. Where that scrollbar is a
11
+ * classic (space-taking) one — Windows/Linux desktop, and macOS set to
12
+ * "Always" show scroll bars — hiding it widens the layout viewport by the
13
+ * scrollbar's width and the whole page jumps sideways behind the overlay.
14
+ *
15
+ * The fix is `scrollbar-gutter: stable`, which holds the gutter open once the
16
+ * scrollbar goes away. Being a real layout change it holds `position: fixed`
17
+ * chrome (sticky headers, toast viewports) as well as in-flow content, which
18
+ * padding fundamentally cannot: fixed elements resolve against the viewport,
19
+ * not against the padded element.
20
+ *
21
+ * Padding is kept only as a fallback for engines without `scrollbar-gutter`
22
+ * (Safari shipped it in 18.2), and it is applied by measuring whether the
23
+ * element actually moved rather than by assuming it did.
24
+ */
25
+
26
+ const NOOP: ScrollbarGutterHold = {
27
+ settle() {},
28
+ release() {},
29
+ };
30
+
31
+ export interface ScrollbarGutterHold {
32
+ /**
33
+ * Call once the lock's own styles are applied: measures whether the element
34
+ * grew anyway and, only then, pads the difference away.
35
+ */
36
+ settle(): void;
37
+ /** Call on unlock: restores everything this hold changed. */
38
+ release(): void;
39
+ }
40
+
41
+ /**
42
+ * Keeps `element`'s content box the width it is right now, across a scroll
43
+ * lock that is about to hide the document's scrollbar.
44
+ *
45
+ * Call it *before* applying the lock's styles, then {@link
46
+ * ScrollbarGutterHold.settle} immediately after, and {@link
47
+ * ScrollbarGutterHold.release} on unlock:
48
+ *
49
+ * ```
50
+ * const hold = holdScrollbarGutter(document.body);
51
+ * body.style.position = 'fixed';
52
+ * hold.settle();
53
+ * ```
54
+ *
55
+ * Overlay scrollbars (mobile, default macOS) take no layout space, so there is
56
+ * nothing to hold and nothing is touched.
57
+ */
58
+ export function holdScrollbarGutter(element: HTMLElement): ScrollbarGutterHold {
59
+ if (typeof window === 'undefined' || typeof document === 'undefined') {
60
+ return NOOP;
61
+ }
62
+
63
+ const root = document.documentElement;
64
+ const viewportWidth = root.clientWidth;
65
+
66
+ // 0 means "no layout to measure" (jsdom, and any non-visual environment),
67
+ // not "the scrollbar is as wide as the window".
68
+ if (viewportWidth === 0) {
69
+ return NOOP;
70
+ }
71
+
72
+ const previousGutter = root.style.scrollbarGutter;
73
+ const previousPadding = element.style.paddingRight;
74
+ const widthBefore = element.getBoundingClientRect().width;
75
+
76
+ let heldGutter = false;
77
+ let padded = false;
78
+ let settled = false;
79
+
80
+ // Only when a space-taking scrollbar is actually there. Holding the gutter
81
+ // open on a page that never had one would carve out 15px that was never
82
+ // reserved — the same shift, in the other direction.
83
+ if (window.innerWidth > viewportWidth) {
84
+ root.style.scrollbarGutter = 'stable';
85
+ heldGutter = true;
86
+ }
87
+
88
+ return {
89
+ settle() {
90
+ if (settled) {
91
+ return;
92
+ }
93
+ settled = true;
94
+
95
+ // What the lock actually did to this element, rather than what we
96
+ // assumed it would do. 0 on any engine where the gutter held — and on a
97
+ // page that already sets `scrollbar-gutter: stable` itself, where the
98
+ // scrollbar was never taking the space back.
99
+ const grew = element.getBoundingClientRect().width - widthBefore;
100
+ if (grew <= 0) {
101
+ return;
102
+ }
103
+
104
+ const existing =
105
+ Number.parseFloat(window.getComputedStyle(element).paddingRight) || 0;
106
+ element.style.paddingRight = `${existing + grew}px`;
107
+ padded = true;
108
+ },
109
+
110
+ release() {
111
+ if (padded) {
112
+ element.style.paddingRight = previousPadding;
113
+ padded = false;
114
+ }
115
+ if (heldGutter) {
116
+ root.style.scrollbarGutter = previousGutter;
117
+ heldGutter = false;
118
+ }
119
+ },
120
+ };
121
+ }
@@ -0,0 +1,42 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file useResolvedRequired.ts
7
+ * @input Uses React, FormLayoutContext
8
+ * @output Exports useResolvedRequired
9
+ * @position Resolves a field's effective aria-required against the form default
10
+ *
11
+ * SYNC: When modified, update these files to stay in sync:
12
+ * - /packages/core/src/FormLayout/FormLayoutContext.ts (defaultOptionality source)
13
+ * - /packages/core/src/FormLayout/FormLayout.tsx (prop doc: aria-required behavior)
14
+ */
15
+
16
+ import {use} from 'react';
17
+ import {FormLayoutContext} from '../FormLayout/FormLayoutContext';
18
+
19
+ /**
20
+ * Resolve a field's effective *required* state for `aria-required`, honoring a
21
+ * surrounding `FormLayout`'s `defaultOptionality`.
22
+ *
23
+ * Under `defaultOptionality="required"` a field is required unless it opts out
24
+ * with `isOptional`. The visible indicator is suppressed for the unmarked
25
+ * majority, so those fields must still expose `aria-required` — otherwise a
26
+ * sighted user reads them as required (form-wide default, no indicator) while a
27
+ * screen reader hears "not required". This closes that mismatch.
28
+ *
29
+ * Semantics only: this drives `aria-required`, never the native `required`
30
+ * attribute, so a layout-level default can't silently switch on browser
31
+ * validation bubbles. `isOptional` takes precedence, matching `FieldLabel`.
32
+ */
33
+ export function useResolvedRequired({
34
+ isRequired = false,
35
+ isOptional = false,
36
+ }: {
37
+ isRequired?: boolean;
38
+ isOptional?: boolean;
39
+ }): boolean {
40
+ const {defaultOptionality} = use(FormLayoutContext);
41
+ return !isOptional && (isRequired || defaultOptionality === 'required');
42
+ }
@@ -4,7 +4,7 @@
4
4
  export const docs = {
5
5
  name: 'useScrollLock',
6
6
  displayName: 'useScrollLock',
7
- keywords: ['scroll', 'lock', 'modal', 'dialog', 'body', 'prevent', 'background', 'ios', 'safari', 'fixed'],
7
+ keywords: ['scroll', 'lock', 'modal', 'dialog', 'body', 'prevent', 'background', 'ios', 'safari', 'fixed', 'scrollbar', 'gutter', 'layout shift', 'reflow'],
8
8
  params: [
9
9
  {
10
10
  name: 'isLocked',
@@ -16,7 +16,7 @@ export const docs = {
16
16
  returns: [],
17
17
  usage: {
18
18
  description:
19
- 'Locks body scroll when active by pinning the body with position: fixed. This prevents background scrolling behind modals and dialogs, which is necessary for iOS Safari where overscroll-behavior: contain does not work. Restores the original scroll position when unlocked.',
19
+ 'Locks body scroll when active by pinning the body with position: fixed. This prevents background scrolling behind modals and dialogs, which is necessary for iOS Safari where overscroll-behavior: contain does not work. Restores the original scroll position when unlocked. Pinning hides the document scrollbar, so where that scrollbar takes layout space (desktop) the hook holds its gutter open with scrollbar-gutter: stable for the duration of the lock — the page, including any position: fixed chrome, does not shift sideways.',
20
20
  bestPractices: [
21
21
  { guidance: true, description: 'Use when opening full-screen modals or dialogs to prevent background content from scrolling.' },
22
22
  { guidance: true, description: 'Pass the same boolean that controls dialog visibility (e.g., isOpen) as the isLocked parameter.' },
@@ -32,13 +32,13 @@ export const docs = {
32
32
  /** @type {import('@astryxdesign/cli/authoring').HookTranslationDoc} */
33
33
  export const docsDense = {
34
34
  description:
35
- 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked.',
35
+ 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked. Holds the hidden scrollbar\'s gutter open (scrollbar-gutter: stable) so the page — incl. position: fixed chrome — does not shift sideways.',
36
36
  paramDescriptions: {
37
37
  isLocked: 'whether body scroll should be locked.',
38
38
  },
39
39
  usage: {
40
40
  description:
41
- 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked.',
41
+ 'Locks body scroll when active by pinning body w/ position: fixed. Prevents background scrolling behind modals + dialogs, necessary for iOS Safari where overscroll-behavior: contain does not work. Restores original scroll position when unlocked. Holds the hidden scrollbar\'s gutter open (scrollbar-gutter: stable) so the page — incl. position: fixed chrome — does not shift sideways.',
42
42
  bestPractices: [
43
43
  { guidance: true, description: 'Use when opening full-screen modals / dialogs to prevent background content from scrolling.' },
44
44
  { guidance: true, description: 'Pass same boolean that controls dialog visibility (e.g. isOpen) as isLocked parameter.' },
@@ -17,6 +17,9 @@ describe('useScrollLock', () => {
17
17
  afterEach(() => {
18
18
  cleanup();
19
19
  document.body.style.cssText = '';
20
+ document.documentElement.style.cssText = '';
21
+ // @ts-expect-error -- drop the viewport stub so jsdom's own value comes back
22
+ delete document.documentElement.clientWidth;
20
23
  vi.restoreAllMocks();
21
24
  });
22
25
 
@@ -69,4 +72,74 @@ describe('useScrollLock', () => {
69
72
  expect(document.body.style.left).toBe('');
70
73
  expect(document.body.style.right).toBe('');
71
74
  });
75
+
76
+ it('holds the page still across the scrollbar it hides', () => {
77
+ // A 1024px window over a 1009px layout viewport = a 15px classic
78
+ // scrollbar. Pinning the body hides it, which would widen the page by
79
+ // those 15px and reflow everything sideways.
80
+ Object.defineProperty(window, 'innerWidth', {
81
+ value: 1024,
82
+ configurable: true,
83
+ writable: true,
84
+ });
85
+ Object.defineProperty(document.documentElement, 'clientWidth', {
86
+ value: 1009,
87
+ configurable: true,
88
+ });
89
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
90
+
91
+ const lock = renderHook(() => useScrollLock(true));
92
+
93
+ expect(document.documentElement.style.scrollbarGutter).toBe('stable');
94
+
95
+ lock.unmount();
96
+
97
+ expect(document.documentElement.style.scrollbarGutter).toBe('');
98
+ });
99
+
100
+ it('leaves the page alone when the scrollbar is an overlay one', () => {
101
+ Object.defineProperty(window, 'innerWidth', {
102
+ value: 1024,
103
+ configurable: true,
104
+ writable: true,
105
+ });
106
+ Object.defineProperty(document.documentElement, 'clientWidth', {
107
+ value: 1024,
108
+ configurable: true,
109
+ });
110
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
111
+
112
+ const lock = renderHook(() => useScrollLock(true));
113
+
114
+ expect(document.documentElement.style.scrollbarGutter).toBe('');
115
+ expect(document.body.style.paddingRight).toBe('');
116
+
117
+ lock.unmount();
118
+ });
119
+
120
+ it('holds the gutter for the outermost overlay only, and gives it back once', () => {
121
+ Object.defineProperty(window, 'innerWidth', {
122
+ value: 1024,
123
+ configurable: true,
124
+ writable: true,
125
+ });
126
+ Object.defineProperty(document.documentElement, 'clientWidth', {
127
+ value: 1009,
128
+ configurable: true,
129
+ });
130
+ vi.spyOn(window, 'scrollTo').mockImplementation(() => {});
131
+
132
+ const first = renderHook(() => useScrollLock(true));
133
+ const second = renderHook(() => useScrollLock(true));
134
+
135
+ expect(document.documentElement.style.scrollbarGutter).toBe('stable');
136
+
137
+ first.unmount();
138
+
139
+ expect(document.documentElement.style.scrollbarGutter).toBe('stable');
140
+
141
+ second.unmount();
142
+
143
+ expect(document.documentElement.style.scrollbarGutter).toBe('');
144
+ });
72
145
  });
@@ -13,9 +13,11 @@
13
13
  *
14
14
  * SYNC: When modified, update:
15
15
  * - /packages/core/src/hooks/index.ts
16
+ * - /packages/core/src/hooks/scrollbarGutter.ts
16
17
  */
17
18
 
18
19
  import {useEffect} from 'react';
20
+ import {holdScrollbarGutter, type ScrollbarGutterHold} from './scrollbarGutter';
19
21
 
20
22
  interface ScrollLockSnapshot {
21
23
  scrollX: number;
@@ -25,6 +27,7 @@ interface ScrollLockSnapshot {
25
27
  top: string;
26
28
  left: string;
27
29
  right: string;
30
+ gutter: ScrollbarGutterHold;
28
31
  }
29
32
 
30
33
  let lockCount = 0;
@@ -38,6 +41,10 @@ let originalBodyState: ScrollLockSnapshot | null = null;
38
41
  * does not prevent body scroll behind modals. Restores scroll position
39
42
  * on unlock.
40
43
  *
44
+ * Pinning also hides the document's scrollbar, so the gutter that scrollbar
45
+ * occupied is held open for the duration of the lock — without it the page
46
+ * reflows sideways by ~15px the moment an overlay opens.
47
+ *
41
48
  * @example
42
49
  * ```
43
50
  * useScrollLock(isOpen);
@@ -55,6 +62,9 @@ export function useScrollLock(isLocked: boolean): void {
55
62
  const scrollX = window.scrollX;
56
63
  const scrollY = window.scrollY;
57
64
 
65
+ // Taken before the pinning styles below hide the scrollbar.
66
+ const gutter = holdScrollbarGutter(body);
67
+
58
68
  originalBodyState = {
59
69
  scrollX,
60
70
  scrollY,
@@ -63,6 +73,7 @@ export function useScrollLock(isLocked: boolean): void {
63
73
  top: body.style.top,
64
74
  left: body.style.left,
65
75
  right: body.style.right,
76
+ gutter,
66
77
  };
67
78
 
68
79
  body.style.overflow = 'hidden';
@@ -70,6 +81,8 @@ export function useScrollLock(isLocked: boolean): void {
70
81
  body.style.top = `-${scrollY}px`;
71
82
  body.style.left = '0';
72
83
  body.style.right = '0';
84
+
85
+ gutter.settle();
73
86
  }
74
87
 
75
88
  lockCount += 1;
@@ -89,6 +102,7 @@ export function useScrollLock(isLocked: boolean): void {
89
102
  body.style.top = state.top;
90
103
  body.style.left = state.left;
91
104
  body.style.right = state.right;
105
+ state.gutter.release();
92
106
  window.scrollTo(state.scrollX, state.scrollY);
93
107
  };
94
108
  }, [isLocked]);
@@ -85,6 +85,9 @@ const STRUCTURAL_VARS = new Set([
85
85
  '--table-row-overlay',
86
86
  '--separator-display',
87
87
  '--astryx-section-padding',
88
+ // Private counterpart of the public token above: one ancestor Section's
89
+ // padding, propagated down the tree. Structural, never authored by a theme.
90
+ '--_section-padding-propagated',
88
91
  ]);
89
92
 
90
93
  /**
@@ -164,9 +167,6 @@ function discoverComponents(): ComponentInfo[] {
164
167
  allVars.add(v);
165
168
  }
166
169
  }
167
- if (allVars.size === 0) {
168
- continue;
169
- }
170
170
 
171
171
  // Only check component directories (those with a doc file named after the
172
172
  // directory). Match against the on-disk listing rather than existsSync so
@@ -188,6 +188,16 @@ function discoverComponents(): ComponentInfo[] {
188
188
  /* skip */
189
189
  }
190
190
 
191
+ // A directory earns a check by declaring a var OR by documenting a
192
+ // derived[] entry. Bailing on the var count alone (as this did) hid every
193
+ // component that is themeable purely through an expansion strategy —
194
+ // `{property: 'padding', expand: 'container'}` names no var, so such a
195
+ // component declares nothing and its registry↔doc consistency check
196
+ // silently never ran.
197
+ if (allVars.size === 0 && docDerived.length === 0) {
198
+ continue;
199
+ }
200
+
191
201
  results.push({
192
202
  dir,
193
203
  sourceVars: [...allVars],
@@ -212,6 +222,7 @@ const DIR_TO_REGISTRY_KEY: Record<string, string> = {
212
222
  DropdownMenu: 'dropdown-menu',
213
223
  Field: 'field',
214
224
  HoverCard: 'hovercard',
225
+ NumberInput: 'number-input',
215
226
  Popover: 'popover',
216
227
  ProgressBar: 'progressbar-mark',
217
228
  Section: 'section',