@astryxdesign/core 0.5.1 → 0.5.2-canary.00477a5
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 +19 -0
- package/dist/BottomSheet/BottomSheet.d.ts +3 -1
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +4 -3
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts +2 -2
- package/dist/BottomSheet/BottomSheetEdgeTint.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetEdgeTint.js +7 -10
- package/dist/BottomSheet/BottomSheetSwitcher.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetSwitcher.js +1 -1
- package/dist/Button/Button.d.ts +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +11 -4
- package/dist/Carousel/Carousel.d.ts.map +1 -1
- package/dist/Carousel/Carousel.js +2 -2
- package/dist/Chat/ChatToolCalls.d.ts.map +1 -1
- package/dist/Chat/ChatToolCalls.js +34 -17
- package/dist/ContextMenu/ContextMenu.d.ts +14 -3
- package/dist/ContextMenu/ContextMenu.d.ts.map +1 -1
- package/dist/ContextMenu/ContextMenu.js +149 -26
- package/dist/ContextMenu/index.d.ts +1 -0
- package/dist/ContextMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.d.ts +24 -5
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +340 -26
- package/dist/DropdownMenu/DropdownMenuContext.d.ts +1 -1
- package/dist/DropdownMenu/DropdownMenuContext.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuItem.js +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts +6 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +43 -14
- package/dist/DropdownMenu/MenuBottomSheet.d.ts +20 -0
- package/dist/DropdownMenu/MenuBottomSheet.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheet.js +36 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts +10 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.d.ts.map +1 -0
- package/dist/DropdownMenu/MenuBottomSheetActionList.js +111 -0
- package/dist/DropdownMenu/index.d.ts +2 -1
- package/dist/DropdownMenu/index.d.ts.map +1 -1
- package/dist/DropdownMenu/menuWidth.d.ts +14 -0
- package/dist/DropdownMenu/menuWidth.d.ts.map +1 -0
- package/dist/DropdownMenu/menuWidth.js +35 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts +10 -0
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -0
- package/dist/DropdownMenu/useMenuOverflow.js +60 -0
- package/dist/MoreMenu/MoreMenu.d.ts +8 -2
- package/dist/MoreMenu/MoreMenu.d.ts.map +1 -1
- package/dist/MoreMenu/MoreMenu.js +2 -0
- package/dist/MultiSelector/MultiSelector.d.ts +11 -1
- package/dist/MultiSelector/MultiSelector.d.ts.map +1 -1
- package/dist/MultiSelector/MultiSelector.js +97 -68
- package/dist/MultiSelector/index.d.ts +1 -1
- package/dist/MultiSelector/index.d.ts.map +1 -1
- package/dist/Popover/Popover.d.ts +10 -6
- package/dist/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.js +141 -16
- package/dist/Popover/usePopover.d.ts +4 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +52 -5
- package/dist/Selector/Selector.d.ts +10 -0
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +106 -80
- package/dist/Selector/SelectorBottomSheet.d.ts +21 -0
- package/dist/Selector/SelectorBottomSheet.d.ts.map +1 -0
- package/dist/Selector/SelectorBottomSheet.js +83 -0
- package/dist/Selector/index.d.ts +1 -1
- package/dist/Selector/index.d.ts.map +1 -1
- package/dist/Selector/selectorPresentation.stylex.d.ts +16 -0
- package/dist/Selector/selectorPresentation.stylex.d.ts.map +1 -0
- package/dist/Selector/selectorPresentation.stylex.js +21 -0
- package/dist/Selector/useSelectorPresentation.d.ts +32 -0
- package/dist/Selector/useSelectorPresentation.d.ts.map +1 -0
- package/dist/Selector/useSelectorPresentation.js +90 -0
- package/dist/Toast/useToastGesture.d.ts.map +1 -1
- package/dist/Toast/useToastGesture.js +46 -9
- package/dist/astryx.css +19 -1
- package/dist/hooks/useAdaptivePresentation.d.ts +5 -0
- package/dist/hooks/useAdaptivePresentation.d.ts.map +1 -0
- package/dist/hooks/useAdaptivePresentation.js +19 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts +7 -0
- package/dist/hooks/useFocusReturnVisibility.d.ts.map +1 -0
- package/dist/hooks/useFocusReturnVisibility.js +35 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +12 -0
- package/dist/i18n/InternationalizationContext.d.ts +2 -0
- package/dist/i18n/InternationalizationContext.d.ts.map +1 -1
- package/dist/i18n/InternationalizationContext.js +3 -1
- package/dist/i18n/InternationalizationProvider.d.ts.map +1 -1
- package/dist/i18n/InternationalizationProvider.js +3 -1
- package/dist/i18n/resolve.d.ts +1 -1
- package/dist/i18n/resolve.d.ts.map +1 -1
- package/dist/i18n/resolve.js +37 -38
- package/dist/i18n/useTranslator.d.ts.map +1 -1
- package/dist/i18n/useTranslator.js +2 -3
- package/locales/af-ZA.json +0 -4
- package/locales/ar-SA.json +0 -4
- package/locales/ca-ES.json +0 -4
- package/locales/cs-CZ.json +0 -4
- package/locales/da-DK.json +0 -4
- package/locales/de-DE.json +0 -4
- package/locales/el-GR.json +0 -4
- package/locales/en.json +20 -0
- package/locales/es-ES.json +0 -4
- package/locales/fi-FI.json +0 -4
- package/locales/fr-FR.json +0 -4
- package/locales/he-IL.json +0 -4
- package/locales/hu-HU.json +0 -4
- package/locales/it-IT.json +0 -4
- package/locales/ja-JP.json +0 -4
- package/locales/ko-KR.json +0 -4
- package/locales/nl-NL.json +0 -4
- package/locales/no-NO.json +0 -4
- package/locales/pl-PL.json +0 -4
- package/locales/pseudo.json +15 -0
- package/locales/pt-BR.json +0 -4
- package/locales/pt-PT.json +0 -4
- package/locales/ro-RO.json +0 -4
- package/locales/ru-RU.json +0 -4
- package/locales/sr-SP.json +0 -4
- package/locales/sv-SE.json +0 -4
- package/locales/tr-TR.json +0 -4
- package/locales/uk-UA.json +0 -4
- package/locales/vi-VN.json +0 -4
- package/locales/zh-CN.json +0 -4
- package/locales/zh-TW.json +0 -4
- package/package.json +3 -3
- package/src/BottomSheet/BottomSheet.doc.mjs +37 -0
- package/src/BottomSheet/BottomSheet.spec.md +177 -0
- package/src/BottomSheet/BottomSheet.test.tsx +29 -0
- package/src/BottomSheet/BottomSheet.tsx +7 -4
- package/src/BottomSheet/BottomSheetEdgeTint.test.tsx +4 -6
- package/src/BottomSheet/BottomSheetEdgeTint.tsx +7 -10
- package/src/BottomSheet/BottomSheetSwitcher.tsx +1 -2
- package/src/Button/Button.doc.mjs +56 -0
- package/src/Button/Button.test.tsx +10 -0
- package/src/Button/Button.tsx +13 -5
- package/src/ButtonGroup/ButtonGroup.doc.mjs +47 -0
- package/src/Calendar/Calendar.doc.mjs +1 -1
- package/src/Carousel/Carousel.test.tsx +83 -0
- package/src/Carousel/Carousel.tsx +8 -2
- package/src/Chat/ChatToolCalls.test.tsx +44 -0
- package/src/Chat/ChatToolCalls.tsx +36 -15
- package/src/CheckboxInput/CheckboxInput.doc.mjs +1 -1
- package/src/CheckboxList/CheckboxList.doc.mjs +173 -32
- package/src/CheckboxList/CheckboxList.spec.md +214 -0
- package/src/CommandPalette/CommandPalette.doc.mjs +72 -0
- package/src/CommandPalette/CommandPalette.spec.md +223 -0
- package/src/ComplexSelector/ComplexSelector.doc.mjs +44 -2
- package/src/ComplexSelector/ComplexSelector.spec.md +189 -0
- package/src/ContextMenu/ContextMenu.doc.mjs +13 -1
- package/src/ContextMenu/ContextMenu.test.tsx +131 -1
- package/src/ContextMenu/ContextMenu.tsx +199 -32
- package/src/ContextMenu/index.ts +1 -0
- package/src/DateInput/DateInput.doc.mjs +1 -1
- package/src/DateRangeInput/DateRangeInput.doc.mjs +1 -1
- package/src/DateTimeInput/DateTimeInput.doc.mjs +1 -1
- package/src/Divider/Divider.doc.mjs +24 -0
- package/src/Divider/Divider.spec.md +150 -0
- package/src/DropdownMenu/DropdownMenu.doc.mjs +201 -40
- package/src/DropdownMenu/DropdownMenu.test.tsx +518 -7
- package/src/DropdownMenu/DropdownMenu.tsx +459 -28
- package/src/DropdownMenu/DropdownMenuContext.tsx +1 -1
- package/src/DropdownMenu/DropdownMenuItem.tsx +2 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.doc.mjs +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +124 -0
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +72 -17
- package/src/DropdownMenu/MenuBottomSheet.tsx +46 -0
- package/src/DropdownMenu/MenuBottomSheetActionList.tsx +141 -0
- package/src/DropdownMenu/index.ts +2 -0
- package/src/DropdownMenu/menuWidth.ts +51 -0
- package/src/DropdownMenu/useMenuOverflow.ts +77 -0
- package/src/Field/Field.doc.mjs +1 -1
- package/src/FieldStatus/FieldStatus.doc.mjs +22 -1
- package/src/FieldStatus/FieldStatus.spec.md +212 -0
- package/src/FileInput/FileInput.doc.mjs +1 -1
- package/src/Grid/Grid.doc.mjs +19 -0
- package/src/Grid/Grid.spec.md +161 -0
- package/src/Icon/Icon.doc.mjs +196 -48
- package/src/Icon/Icon.spec.md +191 -0
- package/src/IconButton/IconButton.doc.mjs +38 -0
- package/src/Indicator/Indicator.doc.mjs +1 -1
- package/src/InputGroup/InputGroup.doc.mjs +1 -1
- package/src/Kbd/Kbd.doc.mjs +18 -0
- package/src/Kbd/Kbd.spec.md +141 -0
- package/src/Layout/Layout.doc.mjs +32 -0
- package/src/Layout/Layout.spec.md +199 -0
- package/src/Lightbox/Lightbox.doc.mjs +149 -33
- package/src/Lightbox/Lightbox.spec.md +197 -0
- package/src/Markdown/Markdown.doc.mjs +60 -0
- package/src/Markdown/Markdown.spec.md +200 -0
- package/src/MobileNav/MobileNav.doc.mjs +140 -32
- package/src/MobileNav/MobileNav.spec.md +208 -0
- package/src/MoreMenu/MoreMenu.doc.mjs +17 -0
- package/src/MoreMenu/MoreMenu.test.tsx +47 -0
- package/src/MoreMenu/MoreMenu.tsx +13 -1
- package/src/MultiSelector/MultiSelector.doc.mjs +129 -1
- package/src/MultiSelector/MultiSelector.spec.md +266 -0
- package/src/MultiSelector/MultiSelector.test.tsx +127 -0
- package/src/MultiSelector/MultiSelector.tsx +121 -73
- package/src/MultiSelector/index.ts +1 -0
- package/src/NavIcon/NavIcon.doc.mjs +17 -0
- package/src/NavIcon/NavIcon.spec.md +133 -0
- package/src/NavMenu/NavHeadingMenu.spec.md +205 -0
- package/src/NavMenu/NavMenu.doc.mjs +37 -0
- package/src/NumberInput/NumberInput.doc.mjs +1 -1
- package/src/Outline/Outline.doc.mjs +35 -0
- package/src/Outline/Outline.spec.md +161 -0
- package/src/OverflowList/OverflowList.doc.mjs +24 -0
- package/src/OverflowList/OverflowList.spec.md +156 -0
- package/src/Pagination/Pagination.doc.mjs +74 -0
- package/src/Pagination/Pagination.spec.md +225 -0
- package/src/Popover/Popover.doc.mjs +11 -11
- package/src/Popover/Popover.test.tsx +397 -2
- package/src/Popover/Popover.tsx +228 -21
- package/src/Popover/usePopover.tsx +92 -15
- package/src/PowerSearch/PowerSearch.doc.mjs +1 -1
- package/src/ProgressBar/ProgressBar.doc.mjs +198 -38
- package/src/ProgressBar/ProgressBar.spec.md +260 -0
- package/src/RadioList/RadioList.doc.mjs +1 -1
- package/src/Resizable/Resizable.doc.mjs +59 -16
- package/src/Resizable/Resizable.spec.md +194 -0
- package/src/Section/Section.doc.mjs +17 -0
- package/src/Section/Section.spec.md +138 -0
- package/src/SegmentedControl/SegmentedControl.doc.mjs +83 -0
- package/src/SegmentedControl/SegmentedControl.spec.md +151 -0
- package/src/Selector/Selector.doc.mjs +13 -1
- package/src/Selector/Selector.test.tsx +135 -0
- package/src/Selector/Selector.tsx +141 -82
- package/src/Selector/SelectorBottomSheet.tsx +97 -0
- package/src/Selector/index.ts +1 -0
- package/src/Selector/selectorPresentation.stylex.ts +20 -0
- package/src/Selector/useSelectorPresentation.ts +147 -0
- package/src/Skeleton/Skeleton.doc.mjs +12 -0
- package/src/Skeleton/Skeleton.spec.md +126 -0
- package/src/Slider/Slider.doc.mjs +186 -32
- package/src/Slider/Slider.spec.md +223 -0
- package/src/Stack/Stack.doc.mjs +22 -0
- package/src/Stack/Stack.spec.md +154 -0
- package/src/StatusDot/StatusDot.doc.mjs +17 -0
- package/src/StatusDot/StatusDot.spec.md +137 -0
- package/src/Switch/Switch.doc.mjs +41 -1
- package/src/Switch/Switch.spec.md +174 -0
- package/src/Text/Text.doc.mjs +190 -43
- package/src/Text/Text.spec.md +205 -0
- package/src/TextArea/TextArea.doc.mjs +256 -46
- package/src/TextArea/TextArea.spec.md +204 -0
- package/src/TextInput/TextInput.doc.mjs +1 -1
- package/src/TimeInput/TimeInput.doc.mjs +1 -1
- package/src/Toast/ToastViewport.test.tsx +118 -4
- package/src/Toast/useToastGesture.ts +61 -8
- package/src/ToggleButton/ToggleButton.doc.mjs +56 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +1 -1
- package/src/Toolbar/Toolbar.doc.mjs +19 -0
- package/src/Toolbar/Toolbar.spec.md +173 -0
- package/src/Tooltip/Tooltip.doc.mjs +17 -0
- package/src/Tooltip/Tooltip.spec.md +141 -0
- package/src/TreeList/TreeList.doc.mjs +58 -0
- package/src/TreeList/TreeList.spec.md +220 -0
- package/src/Typeahead/Typeahead.doc.mjs +84 -2
- package/src/Typeahead/Typeahead.spec.md +250 -0
- package/src/hooks/useAdaptivePresentation.ts +35 -0
- package/src/hooks/useFocusReturnVisibility.ts +45 -0
- package/src/hooks/useFocusTrap.test.tsx +40 -0
- package/src/hooks/useFocusTrap.ts +13 -0
- package/src/i18n/InternationalizationContext.ts +3 -0
- package/src/i18n/InternationalizationProvider.tsx +8 -1
- package/src/i18n/__tests__/resolve.test.ts +23 -1
- package/src/i18n/resolve.ts +46 -49
- package/src/i18n/useTranslator.ts +2 -7
- package/src/theme/themingTargets.test.ts +91 -25
|
@@ -28,7 +28,6 @@ import React, {
|
|
|
28
28
|
type ReactNode,
|
|
29
29
|
} from 'react';
|
|
30
30
|
import * as stylex from '@stylexjs/stylex';
|
|
31
|
-
import {usePopoverInternal} from '../Popover/usePopover';
|
|
32
31
|
import {useTooltip} from '../Tooltip';
|
|
33
32
|
import {Icon, renderIconSlot, type IconType} from '../Icon';
|
|
34
33
|
import type {IconName} from '../Icon';
|
|
@@ -57,7 +56,6 @@ import {
|
|
|
57
56
|
typographyVars,
|
|
58
57
|
fontWeightVars,
|
|
59
58
|
typeScaleVars,
|
|
60
|
-
borderVars,
|
|
61
59
|
} from '../theme/tokens.stylex';
|
|
62
60
|
import type {
|
|
63
61
|
MultiSelectorOptionType,
|
|
@@ -86,6 +84,10 @@ import {groupStyles} from '../InputGroup/groupStyles';
|
|
|
86
84
|
import {useInputGroup} from '../InputGroup/InputGroupContext';
|
|
87
85
|
import {VisuallyHidden} from '../VisuallyHidden';
|
|
88
86
|
import {useTranslator} from '../i18n';
|
|
87
|
+
import type {AdaptivePresentation} from '../hooks/useAdaptivePresentation';
|
|
88
|
+
import {SelectorBottomSheet} from '../Selector/SelectorBottomSheet';
|
|
89
|
+
import {useSelectorPresentation} from '../Selector/useSelectorPresentation';
|
|
90
|
+
import {selectorPresentationStyles} from '../Selector/selectorPresentation.stylex';
|
|
89
91
|
|
|
90
92
|
// Sentinel value for the select-all item in keyboard navigation
|
|
91
93
|
const SELECT_ALL_VALUE = '__xds_select_all__';
|
|
@@ -245,6 +247,9 @@ const styles = stylex.create({
|
|
|
245
247
|
overflowY: 'auto',
|
|
246
248
|
padding: spacingVars['--spacing-1'],
|
|
247
249
|
},
|
|
250
|
+
listbox: {
|
|
251
|
+
outline: 'none',
|
|
252
|
+
},
|
|
248
253
|
|
|
249
254
|
// Popover container (for anchor positioning)
|
|
250
255
|
popover: {
|
|
@@ -415,6 +420,8 @@ export type MultiSelectorSize = 'sm' | 'md' | 'lg';
|
|
|
415
420
|
|
|
416
421
|
export type MultiSelectorVariant = 'input' | 'ghost';
|
|
417
422
|
|
|
423
|
+
export type MultiSelectorPresentation = AdaptivePresentation;
|
|
424
|
+
|
|
418
425
|
export type MultiSelectorStatusType = 'warning' | 'error' | 'success';
|
|
419
426
|
|
|
420
427
|
export type {MultiSelectorStatus};
|
|
@@ -658,6 +665,15 @@ export interface MultiSelectorProps<
|
|
|
658
665
|
*/
|
|
659
666
|
indicatorPosition?: IndicatorPosition;
|
|
660
667
|
|
|
668
|
+
/**
|
|
669
|
+
* How the option list is presented.
|
|
670
|
+
* - 'popover': anchored to the trigger
|
|
671
|
+
* - 'bottom-sheet': modal sheet suited to compact touch screens
|
|
672
|
+
* - 'adaptive': bottom sheet on compact coarse-pointer screens, otherwise popover
|
|
673
|
+
* @default 'popover'
|
|
674
|
+
*/
|
|
675
|
+
presentation?: MultiSelectorPresentation;
|
|
676
|
+
|
|
661
677
|
/**
|
|
662
678
|
* Whether the dropdown starts open on mount.
|
|
663
679
|
* Useful for showcases and previews.
|
|
@@ -747,6 +763,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
747
763
|
maxBadges = 3,
|
|
748
764
|
renderOption,
|
|
749
765
|
indicatorPosition = 'start',
|
|
766
|
+
presentation = 'popover',
|
|
750
767
|
isDefaultOpen = false,
|
|
751
768
|
'data-testid': testId,
|
|
752
769
|
htmlName,
|
|
@@ -754,6 +771,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
754
771
|
xstyle,
|
|
755
772
|
className,
|
|
756
773
|
style,
|
|
774
|
+
onFocus,
|
|
757
775
|
}: MultiSelectorProps<T>) {
|
|
758
776
|
const t = useTranslator();
|
|
759
777
|
const isEffectivelyRequired = useResolvedRequired({isRequired, isOptional});
|
|
@@ -780,6 +798,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
780
798
|
const searchId = useId();
|
|
781
799
|
const triggerRef = useRef<HTMLButtonElement>(null);
|
|
782
800
|
const searchRef = useRef<HTMLInputElement>(null);
|
|
801
|
+
const listboxRef = useRef<HTMLDivElement>(null);
|
|
783
802
|
const inputGroup = useInputGroup();
|
|
784
803
|
|
|
785
804
|
const [searchQuery, setSearchQuery] = useState('');
|
|
@@ -935,27 +954,31 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
935
954
|
// Clear any lingering result count when the popover closes so stale status
|
|
936
955
|
// text does not linger in the a11y tree.
|
|
937
956
|
announce('');
|
|
938
|
-
triggerRef.current?.focus();
|
|
939
957
|
}, [announce]);
|
|
940
958
|
|
|
941
|
-
const
|
|
942
|
-
|
|
959
|
+
const surface = useSelectorPresentation({
|
|
960
|
+
presentation,
|
|
943
961
|
onHide: handleLayerHide,
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
962
|
+
triggerRef,
|
|
963
|
+
popoverOptions: {
|
|
964
|
+
hasLightDismiss: true,
|
|
965
|
+
hasCloseButton: false,
|
|
966
|
+
hasAutoFocus: false,
|
|
967
|
+
// The popup's own role="listbox" is the exposed semantics; the trigger
|
|
968
|
+
// keeps DOM focus, so wrapping it in a modal dialog would misrepresent it.
|
|
969
|
+
role: 'none',
|
|
970
|
+
// The theme target belongs on the SURFACE that paints the popup, which
|
|
971
|
+
// `usePopover` owns — not on the scrolling list inside it.
|
|
972
|
+
surfaceTarget: 'multi-selector-popup',
|
|
973
|
+
},
|
|
952
974
|
});
|
|
975
|
+
const {popover} = surface;
|
|
953
976
|
const keepOpenProps = useKeepLayerOpenProps(popover.id, popover.isOpen);
|
|
954
977
|
|
|
955
978
|
// Open dropdown on mount when isDefaultOpen is true
|
|
956
979
|
useEffect(() => {
|
|
957
980
|
if (isDefaultOpen) {
|
|
958
|
-
|
|
981
|
+
surface.show();
|
|
959
982
|
}
|
|
960
983
|
// eslint-disable-next-line @eslint-react/exhaustive-deps -- mount-only: isDefaultOpen is not reactive
|
|
961
984
|
}, []);
|
|
@@ -999,7 +1022,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
999
1022
|
const announcedEmptyRef = useRef<string | null>(null);
|
|
1000
1023
|
useEffect(() => {
|
|
1001
1024
|
const isPanelEmpty =
|
|
1002
|
-
|
|
1025
|
+
surface.isOpen &&
|
|
1003
1026
|
!isLoading &&
|
|
1004
1027
|
searchQuery === '' &&
|
|
1005
1028
|
selectableItems.length === 0;
|
|
@@ -1013,7 +1036,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1013
1036
|
announcedEmptyRef.current = emptyAnnouncement;
|
|
1014
1037
|
announce(emptyAnnouncement);
|
|
1015
1038
|
}, [
|
|
1016
|
-
|
|
1039
|
+
surface.isOpen,
|
|
1017
1040
|
isLoading,
|
|
1018
1041
|
searchQuery,
|
|
1019
1042
|
selectableItems.length,
|
|
@@ -1158,24 +1181,24 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1158
1181
|
onKeyDown,
|
|
1159
1182
|
onItemMouseEnter,
|
|
1160
1183
|
} = useMultiCombobox({
|
|
1161
|
-
wasJustDismissed:
|
|
1184
|
+
wasJustDismissed: surface.wasJustDismissed,
|
|
1162
1185
|
selectableItems: sortedItems,
|
|
1163
1186
|
isDisabled,
|
|
1164
|
-
isOpen:
|
|
1187
|
+
isOpen: surface.isOpen,
|
|
1165
1188
|
hasSearch,
|
|
1166
1189
|
onOpen: useCallback(() => {
|
|
1167
1190
|
// Snapshot which items are selected at open time — sort is frozen until close
|
|
1168
1191
|
setSelectedAtOpen(new Set(optimisticValue));
|
|
1169
1192
|
|
|
1170
|
-
|
|
1193
|
+
surface.show();
|
|
1171
1194
|
if (hasSearch) {
|
|
1172
1195
|
// Focus search after popover opens
|
|
1173
1196
|
requestAnimationFrame(() => {
|
|
1174
1197
|
searchRef.current?.focus();
|
|
1175
1198
|
});
|
|
1176
1199
|
}
|
|
1177
|
-
}, [
|
|
1178
|
-
onClose:
|
|
1200
|
+
}, [surface, hasSearch, optimisticValue]),
|
|
1201
|
+
onClose: surface.hide,
|
|
1179
1202
|
onToggle: handleNavigableToggle,
|
|
1180
1203
|
onClear: hasClear ? clearValues : undefined,
|
|
1181
1204
|
hasValue,
|
|
@@ -1187,13 +1210,13 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1187
1210
|
// cursor walks off-screen once navigation passes the visible window. Mirrors
|
|
1188
1211
|
// CommandPaletteItem's scrollIntoView({block: 'nearest'}) behavior.
|
|
1189
1212
|
useEffect(() => {
|
|
1190
|
-
if (!
|
|
1213
|
+
if (!surface.isOpen || highlightedIndex < 0) {
|
|
1191
1214
|
return;
|
|
1192
1215
|
}
|
|
1193
1216
|
document
|
|
1194
1217
|
.getElementById(getItemId(highlightedIndex))
|
|
1195
1218
|
?.scrollIntoView?.({block: 'nearest'});
|
|
1196
|
-
}, [
|
|
1219
|
+
}, [surface.isOpen, highlightedIndex, getItemId]);
|
|
1197
1220
|
|
|
1198
1221
|
// Build trigger display content
|
|
1199
1222
|
const selectedItems = useMemo(() => {
|
|
@@ -1288,11 +1311,11 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1288
1311
|
// not the trigger — must be the combobox reporting the highlighted
|
|
1289
1312
|
// option via aria-activedescendant (comboboxes-4).
|
|
1290
1313
|
role="combobox"
|
|
1291
|
-
aria-expanded={
|
|
1314
|
+
aria-expanded={surface.isOpen}
|
|
1292
1315
|
aria-controls={listboxId}
|
|
1293
1316
|
aria-autocomplete="list"
|
|
1294
1317
|
aria-activedescendant={
|
|
1295
|
-
|
|
1318
|
+
surface.isOpen && highlightedIndex >= 0
|
|
1296
1319
|
? getItemId(highlightedIndex)
|
|
1297
1320
|
: undefined
|
|
1298
1321
|
}
|
|
@@ -1355,7 +1378,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1355
1378
|
searchPlaceholder,
|
|
1356
1379
|
handleSearchChange,
|
|
1357
1380
|
onKeyDown,
|
|
1358
|
-
|
|
1381
|
+
surface.isOpen,
|
|
1359
1382
|
highlightedIndex,
|
|
1360
1383
|
getItemId,
|
|
1361
1384
|
t,
|
|
@@ -1608,6 +1631,64 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1608
1631
|
const showStatusTooltip =
|
|
1609
1632
|
status != null && effectiveStatusVariant === 'tooltip' && !!status.message;
|
|
1610
1633
|
|
|
1634
|
+
const panelContent = hasSearch ? (
|
|
1635
|
+
<div>
|
|
1636
|
+
{renderSearch()}
|
|
1637
|
+
<Divider />
|
|
1638
|
+
<div {...stylex.props(styles.dropdown)}>
|
|
1639
|
+
<div
|
|
1640
|
+
ref={listboxRef}
|
|
1641
|
+
id={listboxId}
|
|
1642
|
+
role="listbox"
|
|
1643
|
+
aria-multiselectable="true"
|
|
1644
|
+
aria-labelledby={triggerId}
|
|
1645
|
+
{...stylex.props(styles.listbox)}>
|
|
1646
|
+
{renderOptions()}
|
|
1647
|
+
</div>
|
|
1648
|
+
</div>
|
|
1649
|
+
</div>
|
|
1650
|
+
) : (
|
|
1651
|
+
<div {...stylex.props(styles.dropdown)}>
|
|
1652
|
+
<div
|
|
1653
|
+
ref={listboxRef}
|
|
1654
|
+
id={listboxId}
|
|
1655
|
+
role="listbox"
|
|
1656
|
+
aria-multiselectable="true"
|
|
1657
|
+
aria-labelledby={triggerId}
|
|
1658
|
+
aria-activedescendant={
|
|
1659
|
+
surface.isOpen && highlightedIndex >= 0
|
|
1660
|
+
? getItemId(highlightedIndex)
|
|
1661
|
+
: undefined
|
|
1662
|
+
}
|
|
1663
|
+
tabIndex={surface.activePresentation === 'bottom-sheet' ? 0 : undefined}
|
|
1664
|
+
onKeyDown={
|
|
1665
|
+
surface.activePresentation === 'bottom-sheet' ? onKeyDown : undefined
|
|
1666
|
+
}
|
|
1667
|
+
{...stylex.props(styles.listbox)}>
|
|
1668
|
+
{renderOptions()}
|
|
1669
|
+
</div>
|
|
1670
|
+
</div>
|
|
1671
|
+
);
|
|
1672
|
+
|
|
1673
|
+
const selectionSurface =
|
|
1674
|
+
surface.activePresentation === 'bottom-sheet' ? (
|
|
1675
|
+
<SelectorBottomSheet
|
|
1676
|
+
isOpen={surface.isSheetOpen}
|
|
1677
|
+
onOpenChange={surface.onSheetOpenChange}
|
|
1678
|
+
finalFocusRef={triggerRef}
|
|
1679
|
+
initialFocusRef={hasSearch ? searchRef : listboxRef}
|
|
1680
|
+
label={label}>
|
|
1681
|
+
{panelContent}
|
|
1682
|
+
</SelectorBottomSheet>
|
|
1683
|
+
) : (
|
|
1684
|
+
popover.render(panelContent, {
|
|
1685
|
+
placement: 'below',
|
|
1686
|
+
alignment: 'start',
|
|
1687
|
+
offset: spacingVars['--spacing-1'],
|
|
1688
|
+
xstyle: styles.popover,
|
|
1689
|
+
})
|
|
1690
|
+
);
|
|
1691
|
+
|
|
1611
1692
|
const multiSelectorContent = (
|
|
1612
1693
|
<>
|
|
1613
1694
|
<div
|
|
@@ -1634,6 +1715,8 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1634
1715
|
variant === 'ghost' && styles.triggerGhost,
|
|
1635
1716
|
variant === 'ghost' && interactionOverlayStyles.backgroundImage,
|
|
1636
1717
|
variant === 'ghost' && focusOutlineStyles.focusWithin,
|
|
1718
|
+
surface.isTriggerFocusRingSuppressed &&
|
|
1719
|
+
selectorPresentationStyles.pointerRestoredFocus,
|
|
1637
1720
|
isDisabled && inputWrapperStyles.disabled,
|
|
1638
1721
|
variant === 'ghost' && isDisabled && styles.triggerGhostDisabled,
|
|
1639
1722
|
optimisticValue.length === 0 && styles.triggerPlaceholder,
|
|
@@ -1663,11 +1746,13 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1663
1746
|
// focus + aria-activedescendant, comboboxes-4), so the trigger is a
|
|
1664
1747
|
// plain button that opens the listbox — not a second combobox.
|
|
1665
1748
|
role={hasSearch ? undefined : 'combobox'}
|
|
1666
|
-
aria-haspopup=
|
|
1667
|
-
|
|
1749
|
+
aria-haspopup={
|
|
1750
|
+
surface.activePresentation === 'bottom-sheet' ? 'dialog' : 'listbox'
|
|
1751
|
+
}
|
|
1752
|
+
aria-expanded={surface.isOpen}
|
|
1668
1753
|
aria-controls={listboxId}
|
|
1669
1754
|
aria-activedescendant={
|
|
1670
|
-
!hasSearch &&
|
|
1755
|
+
!hasSearch && surface.isOpen && highlightedIndex >= 0
|
|
1671
1756
|
? getItemId(highlightedIndex)
|
|
1672
1757
|
: undefined
|
|
1673
1758
|
}
|
|
@@ -1682,6 +1767,10 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1682
1767
|
disabled={isDisabled && !showsDisabledMessage}
|
|
1683
1768
|
aria-disabled={showsDisabledMessage ? 'true' : undefined}
|
|
1684
1769
|
onKeyDown={onKeyDown}
|
|
1770
|
+
onFocus={event => {
|
|
1771
|
+
onFocus?.(event);
|
|
1772
|
+
surface.onTriggerFocus(event);
|
|
1773
|
+
}}
|
|
1685
1774
|
tabIndex={isDisabled && !showsDisabledMessage ? -1 : 0}
|
|
1686
1775
|
{...stylex.props(styles.trigger)}>
|
|
1687
1776
|
<span {...stylex.props(styles.triggerContent)}>
|
|
@@ -1754,7 +1843,7 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1754
1843
|
xstyle={[
|
|
1755
1844
|
styles.triggerIcon,
|
|
1756
1845
|
styles.triggerIconRotation,
|
|
1757
|
-
|
|
1846
|
+
surface.isOpen && styles.triggerIconOpen,
|
|
1758
1847
|
]}
|
|
1759
1848
|
// Stable theme target on the chevron glyph itself, so a theme can
|
|
1760
1849
|
// restyle just this icon (color, size, hover) — and its
|
|
@@ -1762,54 +1851,13 @@ export function MultiSelector<T extends MultiSelectorOptionType>({
|
|
|
1762
1851
|
// @layer astryx-theme win over the icon's own base color/size,
|
|
1763
1852
|
// which a button-level target could not reach.
|
|
1764
1853
|
{...themeProps('multi-selector-indicator-icon', {
|
|
1765
|
-
state:
|
|
1854
|
+
state: surface.isOpen ? 'expanded' : 'collapsed',
|
|
1766
1855
|
})}
|
|
1767
1856
|
/>
|
|
1768
1857
|
)}
|
|
1769
1858
|
</div>
|
|
1770
1859
|
|
|
1771
|
-
{
|
|
1772
|
-
hasSearch ? (
|
|
1773
|
-
// With a search row the panel splits: the header stays put while the
|
|
1774
|
-
// options scroll under it, so the field does not slide out of reach
|
|
1775
|
-
// in a long list. Without one the panel is a single scroll container,
|
|
1776
|
-
// exactly as before.
|
|
1777
|
-
<div>
|
|
1778
|
-
{renderSearch()}
|
|
1779
|
-
{/*
|
|
1780
|
-
Separates the header from the options and spans the panel: the
|
|
1781
|
-
search row and the option list each hold their own inline
|
|
1782
|
-
padding, the line does not, so it reads as the panel's own edge.
|
|
1783
|
-
*/}
|
|
1784
|
-
<Divider />
|
|
1785
|
-
<div {...stylex.props(styles.dropdown)}>
|
|
1786
|
-
<div
|
|
1787
|
-
id={listboxId}
|
|
1788
|
-
role="listbox"
|
|
1789
|
-
aria-multiselectable="true"
|
|
1790
|
-
aria-labelledby={triggerId}>
|
|
1791
|
-
{renderOptions()}
|
|
1792
|
-
</div>
|
|
1793
|
-
</div>
|
|
1794
|
-
</div>
|
|
1795
|
-
) : (
|
|
1796
|
-
<div {...stylex.props(styles.dropdown)}>
|
|
1797
|
-
<div
|
|
1798
|
-
id={listboxId}
|
|
1799
|
-
role="listbox"
|
|
1800
|
-
aria-multiselectable="true"
|
|
1801
|
-
aria-labelledby={triggerId}>
|
|
1802
|
-
{renderOptions()}
|
|
1803
|
-
</div>
|
|
1804
|
-
</div>
|
|
1805
|
-
),
|
|
1806
|
-
{
|
|
1807
|
-
placement: 'below',
|
|
1808
|
-
alignment: 'start',
|
|
1809
|
-
offset: spacingVars['--spacing-1'],
|
|
1810
|
-
xstyle: styles.popover,
|
|
1811
|
-
},
|
|
1812
|
-
)}
|
|
1860
|
+
{selectionSurface}
|
|
1813
1861
|
|
|
1814
1862
|
{showStatusTooltip && statusTooltip.renderTooltip(status?.message ?? '')}
|
|
1815
1863
|
|
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
2
|
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentAnatomyElement[]} */
|
|
4
|
+
const anatomy = [
|
|
5
|
+
{
|
|
6
|
+
name: 'Container',
|
|
7
|
+
required: true,
|
|
8
|
+
description: 'Circular painted container for the supplied icon.',
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: 'Icon',
|
|
12
|
+
required: true,
|
|
13
|
+
description: 'Caller-supplied visual content rendered inside the container.',
|
|
14
|
+
},
|
|
15
|
+
];
|
|
16
|
+
|
|
3
17
|
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
18
|
|
|
5
19
|
export const docs = {
|
|
@@ -29,6 +43,7 @@ export const docs = {
|
|
|
29
43
|
],
|
|
30
44
|
},
|
|
31
45
|
usage: {
|
|
46
|
+
anatomy,
|
|
32
47
|
description:
|
|
33
48
|
'NavIcon is a circular icon container with an accent-colored background. Use it in navigation headers such as TopNavHeading and PageNavHeader to visually identify a section or application.',
|
|
34
49
|
bestPractices: [
|
|
@@ -61,6 +76,7 @@ export const docsZh = {
|
|
|
61
76
|
],
|
|
62
77
|
},
|
|
63
78
|
usage: {
|
|
79
|
+
anatomy,
|
|
64
80
|
description:
|
|
65
81
|
'NavIcon is a circular icon container with an accent-colored background. Use it in navigation headers such as TopNavHeading and PageNavHeader to visually identify a section or application.',
|
|
66
82
|
bestPractices: [
|
|
@@ -76,6 +92,7 @@ export const docsDense = {
|
|
|
76
92
|
description:
|
|
77
93
|
'Circular icon container w/ accent background for navigation headers.',
|
|
78
94
|
usage: {
|
|
95
|
+
anatomy,
|
|
79
96
|
description:
|
|
80
97
|
'NavIcon is a circular icon container with an accent-colored background. Use it in navigation headers such as TopNavHeading and PageNavHeader to visually identify a section or application.',
|
|
81
98
|
bestPractices: [
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
---
|
|
2
|
+
schema_version: 1
|
|
3
|
+
template_version: 3
|
|
4
|
+
kind: component
|
|
5
|
+
id: component:NavIcon
|
|
6
|
+
authority: draft
|
|
7
|
+
archive_reason: null
|
|
8
|
+
superseded_by: null
|
|
9
|
+
approved_by: null
|
|
10
|
+
approved_at: null
|
|
11
|
+
owners: [cixzhang]
|
|
12
|
+
review_triggers: [theming]
|
|
13
|
+
verified_by:
|
|
14
|
+
[packages/core/src/NavIcon/NavIcon.test.tsx, scripts/check-knowledge.mjs]
|
|
15
|
+
families: []
|
|
16
|
+
design_specs: []
|
|
17
|
+
architecture:
|
|
18
|
+
[architecture:component-theming-surface, architecture:public-component-api]
|
|
19
|
+
contributing: []
|
|
20
|
+
system_specs: []
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# NavIcon component contract
|
|
24
|
+
|
|
25
|
+
## Intent
|
|
26
|
+
|
|
27
|
+
NavIcon renders caller-supplied icon content inside a circular navigation
|
|
28
|
+
container. This draft records the current consumer anatomy and theming ownership
|
|
29
|
+
without changing runtime behavior or target compatibility.
|
|
30
|
+
|
|
31
|
+
## Compatibility and migration
|
|
32
|
+
|
|
33
|
+
- Released default preserved: `yes`
|
|
34
|
+
- Compatibility class: additive documentation only; runtime, DOM, styling,
|
|
35
|
+
targets, aliases, and public API remain unchanged
|
|
36
|
+
- Controlled/uncontrolled behavior: not applicable
|
|
37
|
+
- Migration decision: none
|
|
38
|
+
|
|
39
|
+
Consumer migration instructions belong in consumer docs and release notes.
|
|
40
|
+
|
|
41
|
+
## Ownership boundary
|
|
42
|
+
|
|
43
|
+
**Owns**
|
|
44
|
+
|
|
45
|
+
- The circular container and its current `nav-icon` theming target.
|
|
46
|
+
|
|
47
|
+
**Does not own / non-goals**
|
|
48
|
+
|
|
49
|
+
- The artwork or component supplied through `icon` — owned by the caller.
|
|
50
|
+
- Interaction semantics — NavIcon remains a display-only container.
|
|
51
|
+
|
|
52
|
+
## Public concepts
|
|
53
|
+
|
|
54
|
+
No new public concept is introduced. Consumer props and usage remain documented
|
|
55
|
+
in `NavIcon.doc.mjs`.
|
|
56
|
+
|
|
57
|
+
## Behavioral and layout contract
|
|
58
|
+
|
|
59
|
+
| ID | Candidate invariant | Basis | Draft review state |
|
|
60
|
+
| --- | ------------------------------------------------------------------------------------------------ | ------------------------------- | -------------------------------------------------- |
|
|
61
|
+
| FR1 | The current render contains a circular container and the caller-supplied icon content. | Current source, docs, and tests | Verified current behavior; no new behavior decided |
|
|
62
|
+
| FR2 | The container carries `nav-icon`; the deprecated `navicon` alias remains compatibility metadata. | Current source, docs, and tests | Verified current behavior; no target change |
|
|
63
|
+
|
|
64
|
+
### Allowed variation
|
|
65
|
+
|
|
66
|
+
- Caller-supplied icon artwork and implementation may vary without becoming
|
|
67
|
+
NavIcon-owned anatomy.
|
|
68
|
+
|
|
69
|
+
### Representative states
|
|
70
|
+
|
|
71
|
+
- The documented anatomy is the same for every caller-supplied icon.
|
|
72
|
+
|
|
73
|
+
### Transformation and precedence order
|
|
74
|
+
|
|
75
|
+
- No new ordering rule is introduced.
|
|
76
|
+
|
|
77
|
+
### Performance and resources
|
|
78
|
+
|
|
79
|
+
- No new performance or resource rule is introduced.
|
|
80
|
+
|
|
81
|
+
## Accessibility contract
|
|
82
|
+
|
|
83
|
+
This draft does not change or extend NavIcon's existing accessibility behavior.
|
|
84
|
+
|
|
85
|
+
## Design relationships
|
|
86
|
+
|
|
87
|
+
| Anatomy or state | Design requirement | Representation authority | Hierarchy role | Component contract |
|
|
88
|
+
| ---------------- | ---------------------------------------------- | ------------------------------ | ----------------- | ------------------ |
|
|
89
|
+
| Container | Presents the current circular painted surface. | Current source and public docs | Supporting | FR1, FR2 |
|
|
90
|
+
| Icon | Presents caller-supplied visual content. | Caller-supplied content | Context-dependent | FR1 |
|
|
91
|
+
|
|
92
|
+
The deprecated `navicon` alias is compatibility metadata, not a separate anatomy
|
|
93
|
+
part or current target.
|
|
94
|
+
|
|
95
|
+
### Theming anatomy
|
|
96
|
+
|
|
97
|
+
<!-- anatomy-theming:v1 -->
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"Container": {"target": "nav-icon"},
|
|
102
|
+
"Icon": {"inherits": "nav-icon"}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Family and system relationships
|
|
107
|
+
|
|
108
|
+
This draft references the current shared owners without restating their rules:
|
|
109
|
+
`architecture:component-theming-surface` and
|
|
110
|
+
`architecture:public-component-api`. The deprecated `navicon` alias remains
|
|
111
|
+
compatibility metadata rather than separate anatomy.
|
|
112
|
+
|
|
113
|
+
## Verification map
|
|
114
|
+
|
|
115
|
+
| Contract | Verification | Representative states | Mutation or failure expectation | Audit section |
|
|
116
|
+
| ------------------- | ------------------------------------ | ------------------------------------- | ------------------------------------------------------------------------------------- | ----------------------- |
|
|
117
|
+
| FR1 | `NavIcon.test.tsx` content suite | Caller-supplied icon | Removing the container or supplied content fails existing render and ref assertions. | `audit:NavIcon/anatomy` |
|
|
118
|
+
| FR2 | `NavIcon.test.tsx` target-name suite | Current and deprecated target classes | Removing either emitted compatibility class fails the existing target assertion. | `audit:NavIcon/theming` |
|
|
119
|
+
| Theming anatomy map | `scripts/check-knowledge.mjs` | Canonical anatomy and current target | Missing, extra, prefixed, stale, or alias-backed mappings fail repository validation. | `audit:NavIcon/theming` |
|
|
120
|
+
|
|
121
|
+
## Decision log
|
|
122
|
+
|
|
123
|
+
None. This draft records current facts and introduces no component-local design
|
|
124
|
+
decision.
|
|
125
|
+
|
|
126
|
+
## Open questions
|
|
127
|
+
|
|
128
|
+
None.
|
|
129
|
+
|
|
130
|
+
## Content boundary
|
|
131
|
+
|
|
132
|
+
This file does not duplicate consumer prop tables, examples, implementation
|
|
133
|
+
steps, or system rules. It links to their owners.
|