@astryxdesign/core 0.7.6 → 0.7.7-canary.0eb44a2
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/dist/AppShell/AppShell.d.ts.map +1 -1
- package/dist/AppShell/AppShell.js +23 -3
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +16 -9
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +2 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +3 -2
- package/dist/Chat/ChatComposerInput.d.ts +9 -0
- package/dist/Chat/ChatComposerInput.d.ts.map +1 -1
- package/dist/Chat/ChatComposerInput.js +2 -0
- package/dist/Chat/ChatMessage.js +2 -2
- package/dist/Chat/chatMessage.vars.stylex.d.ts +8 -8
- package/dist/Chat/chatMessage.vars.stylex.js +8 -8
- package/dist/CommandPalette/CommandPaletteInput.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteInput.js +8 -1
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +72 -2
- package/dist/DateInput/Wheel.d.ts +17 -1
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +179 -19
- package/dist/DateRangeInput/DateRangeInput.d.ts.map +1 -1
- package/dist/DateRangeInput/DateRangeInput.js +63 -49
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +37 -1
- package/dist/Drawer/Drawer.d.ts.map +1 -1
- package/dist/Drawer/Drawer.js +6 -0
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
- package/dist/Drawer/useDrawerDialogPresence.js +4 -0
- package/dist/DropdownMenu/DropdownMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenu.js +20 -4
- package/dist/DropdownMenu/useMenuOverflow.d.ts +13 -3
- package/dist/DropdownMenu/useMenuOverflow.d.ts.map +1 -1
- package/dist/DropdownMenu/useMenuOverflow.js +31 -8
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +8 -1
- package/dist/Field/textEntryTraits.d.ts +6 -6
- package/dist/Field/textEntryTraits.js +6 -6
- package/dist/Layer/layerClickBoundary.d.ts +7 -1
- package/dist/Layer/layerClickBoundary.d.ts.map +1 -1
- package/dist/Layer/layerClickBoundary.js +2 -2
- package/dist/Layer/usePressPin.d.ts +95 -0
- package/dist/Layer/usePressPin.d.ts.map +1 -0
- package/dist/Layer/usePressPin.js +365 -0
- package/dist/Layer/useTouchTrigger.d.ts +23 -1
- package/dist/Layer/useTouchTrigger.d.ts.map +1 -1
- package/dist/Layer/useTouchTrigger.js +45 -7
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +2 -1
- package/dist/Link/Link.d.ts.map +1 -1
- package/dist/Link/Link.js +1 -0
- package/dist/Popover/usePopover.d.ts.map +1 -1
- package/dist/Popover/usePopover.js +56 -7
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +38 -30
- package/dist/ProgressBar/ProgressBar.d.ts +1 -1
- package/dist/ProgressBar/ProgressBar.d.ts.map +1 -1
- package/dist/ProgressBar/ProgressBar.js +52 -14
- package/dist/ProgressBar/ProgressBarTouch.d.ts +29 -0
- package/dist/ProgressBar/ProgressBarTouch.d.ts.map +1 -0
- package/dist/ProgressBar/ProgressBarTouch.js +91 -0
- package/dist/SegmentedControl/SegmentedControlItem.d.ts.map +1 -1
- package/dist/SegmentedControl/SegmentedControlItem.js +2 -1
- package/dist/Selector/Selector.d.ts.map +1 -1
- package/dist/Selector/Selector.js +25 -13
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +404 -131
- package/dist/Slider/sliderTouchController.d.ts +189 -0
- package/dist/Slider/sliderTouchController.d.ts.map +1 -0
- package/dist/Slider/sliderTouchController.js +1017 -0
- package/dist/Slider/useSliderTouch.d.ts +22 -0
- package/dist/Slider/useSliderTouch.d.ts.map +1 -0
- package/dist/Slider/useSliderTouch.js +56 -0
- package/dist/Switch/Switch.d.ts +1 -1
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +200 -46
- package/dist/Switch/switchFill.d.ts +31 -0
- package/dist/Switch/switchFill.d.ts.map +1 -0
- package/dist/Switch/switchFill.js +88 -0
- package/dist/Switch/switchTouch.d.ts +81 -0
- package/dist/Switch/switchTouch.d.ts.map +1 -0
- package/dist/Switch/switchTouch.js +133 -0
- package/dist/Switch/useSwitchTouch.d.ts +285 -0
- package/dist/Switch/useSwitchTouch.d.ts.map +1 -0
- package/dist/Switch/useSwitchTouch.js +998 -0
- package/dist/TabList/Tab.d.ts.map +1 -1
- package/dist/TabList/Tab.js +4 -3
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +12 -5
- package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts +3 -0
- package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -0
- package/dist/Tokenizer/TokenizerItemRemovableContext.js +17 -0
- package/dist/Tooltip/Tooltip.d.ts +30 -6
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +5 -0
- package/dist/Tooltip/index.d.ts +1 -1
- package/dist/Tooltip/index.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +31 -5
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +59 -18
- package/dist/Typeahead/BaseTypeahead.d.ts.map +1 -1
- package/dist/Typeahead/BaseTypeahead.js +19 -9
- package/dist/Typeahead/Typeahead.d.ts.map +1 -1
- package/dist/Typeahead/Typeahead.js +26 -15
- package/dist/astryx.css +91 -8
- package/dist/gestures/GestureArena.d.ts +21 -2
- package/dist/gestures/GestureArena.d.ts.map +1 -1
- package/dist/gestures/GestureArena.js +23 -3
- package/dist/gestures/GestureHost.d.ts +123 -3
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +797 -15
- package/dist/gestures/GestureRecognizer.d.ts +15 -1
- package/dist/gestures/GestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/GestureRecognizer.js +12 -0
- package/dist/gestures/PanGestureRecognizer.d.ts +8 -0
- package/dist/gestures/PanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PinchGestureRecognizer.d.ts +5 -1
- package/dist/gestures/PinchGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/PinchGestureRecognizer.js +10 -0
- package/dist/gestures/PressTracker.d.ts +6 -0
- package/dist/gestures/PressTracker.d.ts.map +1 -1
- package/dist/gestures/PressTracker.js +3 -0
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts +3 -3
- package/dist/gestures/ScreenEdgePanGestureRecognizer.d.ts.map +1 -1
- package/dist/gestures/ScreenEdgePanGestureRecognizer.js +7 -5
- package/dist/gestures/nativeScroll.d.ts +16 -2
- package/dist/gestures/nativeScroll.d.ts.map +1 -1
- package/dist/gestures/nativeScroll.js +39 -8
- package/dist/gestures/recognizerInternals.d.ts +4 -2
- package/dist/gestures/recognizerInternals.d.ts.map +1 -1
- package/dist/gestures/types.d.ts +17 -0
- package/dist/gestures/types.d.ts.map +1 -1
- package/dist/gestures/types.js +7 -0
- package/dist/gestures/usePressTracker.d.ts.map +1 -1
- package/dist/gestures/usePressTracker.js +1 -0
- package/dist/gestures/wheelSegmenter.d.ts +56 -0
- package/dist/gestures/wheelSegmenter.d.ts.map +1 -0
- package/dist/gestures/wheelSegmenter.js +122 -0
- package/dist/hooks/useAttachment.d.ts +16 -8
- package/dist/hooks/useAttachment.d.ts.map +1 -1
- package/dist/hooks/useAttachment.js +35 -15
- package/dist/hooks/useFocusTrap.d.ts +15 -0
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +13 -2
- package/dist/hooks/usePager.d.ts +261 -0
- package/dist/hooks/usePager.d.ts.map +1 -0
- package/dist/hooks/usePager.js +1169 -0
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +140 -19
- package/dist/motion/paging.d.ts +69 -0
- package/dist/motion/paging.d.ts.map +1 -0
- package/dist/motion/paging.js +148 -0
- package/dist/motion/uikit/index.d.ts +8 -4
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +8 -4
- package/dist/motion/uikit/paging.d.ts +171 -0
- package/dist/motion/uikit/paging.d.ts.map +1 -0
- package/dist/motion/uikit/paging.js +192 -0
- package/dist/motion/uikit/progressBar.d.ts +79 -0
- package/dist/motion/uikit/progressBar.d.ts.map +1 -0
- package/dist/motion/uikit/progressBar.js +102 -0
- package/dist/motion/uikit/scroll.d.ts +0 -19
- package/dist/motion/uikit/scroll.d.ts.map +1 -1
- package/dist/motion/uikit/scroll.js +2 -21
- package/dist/motion/uikit/slider.d.ts +97 -0
- package/dist/motion/uikit/slider.d.ts.map +1 -0
- package/dist/motion/uikit/slider.js +111 -0
- package/dist/motion/uikit/springs.d.ts +59 -30
- package/dist/motion/uikit/springs.d.ts.map +1 -1
- package/dist/motion/uikit/springs.js +64 -34
- package/dist/motion/uikit/switch.d.ts +166 -0
- package/dist/motion/uikit/switch.d.ts.map +1 -0
- package/dist/motion/uikit/switch.js +186 -0
- package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts +18 -0
- package/dist/privateAttributes/pressHoldsDrag.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/pressHoldsDrag.stylex.js +28 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +5 -0
- package/dist/utils/selectionLock.d.ts +43 -0
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +394 -6
- package/package.json +5 -5
- package/src/AppShell/AppShell.doc.mjs +10 -0
- package/src/AppShell/AppShell.tsx +48 -2
- package/src/Avatar/Avatar.tsx +3 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +16 -9
- package/src/Breadcrumbs/BreadcrumbItem.tsx +4 -1
- package/src/Button/Button.tsx +3 -2
- package/src/Chat/Chat.doc.mjs +1 -0
- package/src/Chat/ChatComposerInput.doc.mjs +11 -0
- package/src/Chat/ChatComposerInput.tsx +11 -0
- package/src/Chat/ChatMessage.tsx +2 -2
- package/src/Chat/chatMessage.vars.stylex.ts +8 -8
- package/src/CommandPalette/CommandPaletteInput.tsx +6 -0
- package/src/DateInput/MonthScroller.tsx +63 -5
- package/src/DateInput/Wheel.tsx +174 -14
- package/src/DateRangeInput/DateRangeInput.doc.mjs +3 -2
- package/src/DateRangeInput/DateRangeInput.tsx +117 -71
- package/src/Dialog/Dialog.tsx +37 -1
- package/src/Drawer/Drawer.tsx +6 -0
- package/src/Drawer/useDrawerDialogPresence.ts +4 -0
- package/src/DropdownMenu/DropdownMenu.tsx +28 -6
- package/src/DropdownMenu/useMenuOverflow.ts +44 -8
- package/src/Field/PanelSearchInput.tsx +6 -0
- package/src/Field/textEntryTraits.ts +6 -6
- package/src/Layer/layerClickBoundary.ts +2 -2
- package/src/Layer/usePressPin.ts +516 -0
- package/src/Layer/useTouchTrigger.ts +56 -6
- package/src/Lightbox/Lightbox.tsx +2 -1
- package/src/Link/Link.tsx +3 -0
- package/src/Popover/usePopover.tsx +71 -9
- package/src/PowerSearch/PowerSearch.tsx +38 -31
- package/src/ProgressBar/ProgressBar.doc.mjs +7 -5
- package/src/ProgressBar/ProgressBar.tsx +147 -16
- package/src/ProgressBar/ProgressBarTouch.ts +109 -0
- package/src/SegmentedControl/SegmentedControlItem.tsx +2 -1
- package/src/Selector/Selector.tsx +29 -2
- package/src/Slider/Slider.doc.mjs +11 -7
- package/src/Slider/Slider.tsx +539 -132
- package/src/Slider/sliderTouchController.ts +1209 -0
- package/src/Slider/useSliderTouch.ts +78 -0
- package/src/Switch/Switch.doc.mjs +3 -3
- package/src/Switch/Switch.tsx +412 -86
- package/src/Switch/switchFill.ts +106 -0
- package/src/Switch/switchTouch.ts +194 -0
- package/src/Switch/useSwitchTouch.ts +1121 -0
- package/src/TabList/Tab.tsx +5 -3
- package/src/Tokenizer/Tokenizer.tsx +15 -5
- package/src/Tokenizer/TokenizerItemRemovableContext.ts +22 -0
- package/src/Tooltip/Tooltip.doc.mjs +21 -6
- package/src/Tooltip/Tooltip.tsx +42 -5
- package/src/Tooltip/index.ts +1 -0
- package/src/Tooltip/useTooltip.doc.mjs +4 -2
- package/src/Tooltip/useTooltip.tsx +84 -21
- package/src/Typeahead/BaseTypeahead.tsx +16 -8
- package/src/Typeahead/Typeahead.tsx +29 -15
- package/src/gestures/GestureArena.ts +45 -4
- package/src/gestures/GestureHost.ts +958 -15
- package/src/gestures/GestureRecognizer.ts +28 -0
- package/src/gestures/PanGestureRecognizer.ts +8 -0
- package/src/gestures/PinchGestureRecognizer.ts +16 -1
- package/src/gestures/PressTracker.ts +10 -0
- package/src/gestures/README.md +82 -3
- package/src/gestures/ScreenEdgePanGestureRecognizer.ts +7 -5
- package/src/gestures/nativeScroll.ts +49 -9
- package/src/gestures/recognizerInternals.ts +9 -2
- package/src/gestures/types.ts +18 -0
- package/src/gestures/usePressTracker.ts +2 -0
- package/src/gestures/wheelSegmenter.ts +143 -0
- package/src/hooks/useAttachment.ts +56 -14
- package/src/hooks/useFocusTrap.ts +26 -2
- package/src/hooks/usePager.ts +1446 -0
- package/src/motion/MEASUREMENTS.md +36 -27
- package/src/motion/README.md +7 -3
- package/src/motion/animator.ts +193 -25
- package/src/motion/paging.ts +161 -0
- package/src/motion/uikit/index.ts +7 -4
- package/src/motion/uikit/paging.ts +193 -0
- package/src/motion/uikit/progressBar.ts +102 -0
- package/src/motion/uikit/scroll.ts +2 -18
- package/src/motion/uikit/slider.ts +105 -0
- package/src/motion/uikit/springs.ts +53 -32
- package/src/motion/uikit/switch.ts +174 -0
- package/src/privateAttributes/pressHoldsDrag.stylex.ts +28 -0
- package/src/utils/pressFeedback.ts +5 -0
- package/src/utils/selectionLock.ts +501 -6
- package/dist/motion/selectPage.d.ts +0 -26
- package/dist/motion/selectPage.d.ts.map +0 -1
- package/dist/motion/selectPage.js +0 -55
- package/src/motion/selectPage.ts +0 -55
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
/**
|
|
4
4
|
* @file selectionLock.ts
|
|
5
5
|
* @input The elements a held touch contact presses, and the touch's target
|
|
6
|
-
* @output lockSelection
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* @output lockSelection and locksSelectionFor give a held finger's lock.
|
|
7
|
+
* lockDragSelection gives a claimed mouse or pen drag's hold, which keeps
|
|
8
|
+
* the page's selection. startsOnSelectableText and selectionRangesOf are
|
|
9
|
+
* what a mouse or pen press reads.
|
|
10
|
+
* @position Recognizers whose hold does something bring lockSelection; the
|
|
11
|
+
* gesture host imports the drag hold and the press reads.
|
|
11
12
|
*
|
|
12
13
|
* SYNC: When modified, update GestureHost.ts and utils/pressFeedback.ts; the
|
|
13
14
|
* iOS query matches utils/touchPresentation.ts.
|
|
@@ -27,7 +28,8 @@
|
|
|
27
28
|
//
|
|
28
29
|
// A mouse never takes the lock, so its released behavior stays (AST-017).
|
|
29
30
|
|
|
30
|
-
import { containsAlongFlatTree } from "./flatTree.js";
|
|
31
|
+
import { containsAlongFlatTree, parentAcrossShadow } from "./flatTree.js";
|
|
32
|
+
import { subscribeInput } from "./inputStream.js";
|
|
31
33
|
import { isIosWebKit } from "./touchQueries.js";
|
|
32
34
|
|
|
33
35
|
/** What a held finger must not start on a control or a cell. */
|
|
@@ -77,6 +79,392 @@ export function lockSelection(elements, target) {
|
|
|
77
79
|
}
|
|
78
80
|
};
|
|
79
81
|
}
|
|
82
|
+
return lockStyles(set);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** How a drag's selection hold ends: at a lift, or at a cancel. */
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Refuse a new selection while a mouse or pen drag that a recognizer claimed
|
|
89
|
+
* runs, until the returned function runs. `kept` is the page's selection
|
|
90
|
+
* when the press began, and it stays. Anything that replaces it while the
|
|
91
|
+
* drag runs is put back to it. That includes the press's own collapse and a
|
|
92
|
+
* selection the drag started before its claim. So the drag takes no new
|
|
93
|
+
* selection and never clears an existing one. `elements` refuse a
|
|
94
|
+
* selection with inline styles, on every engine. The document refuses one
|
|
95
|
+
* too, because WebKit extends a drag's selection over text outside the
|
|
96
|
+
* elements without another `selectstart`. So a `selectstart` anywhere is
|
|
97
|
+
* refused, and the selection is put back. A `selectstart` does not leave its
|
|
98
|
+
* shadow tree. So the document refuses only one that starts outside every
|
|
99
|
+
* shadow tree, and a claiming element inside one refuses its own. The
|
|
100
|
+
* press's own `mouseup` and `click` come after its `pointerup` in the same
|
|
101
|
+
* task, and WebKit settles the drag's selection there. So the document's
|
|
102
|
+
* hold ends a task after a lift. A cancel ends it at once, because nothing
|
|
103
|
+
* follows a cancel. `onReleased` runs once the document's hold has ended.
|
|
104
|
+
* The page listeners exist only during a press. Idempotent.
|
|
105
|
+
*/
|
|
106
|
+
export function lockDragSelection(elements, view, kept, onReleased) {
|
|
107
|
+
const doc = view?.document ?? null;
|
|
108
|
+
const roots = shadowRootsOfRanges(kept);
|
|
109
|
+
const restore = () => {
|
|
110
|
+
const selection = view?.getSelection();
|
|
111
|
+
// The focused field keeps its own selection (a drag never starts in one).
|
|
112
|
+
if (selection == null || isTextEntry(deepActiveElement(doc))) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
const held = heldRanges(selection, roots);
|
|
116
|
+
if (kept.length === 0) {
|
|
117
|
+
if (held.some(range => !range.collapsed)) {
|
|
118
|
+
selection.removeAllRanges();
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// A render can remove what the kept selection held: then there is
|
|
123
|
+
// nothing to put back.
|
|
124
|
+
if (sameRanges(held, kept) || !kept.every(isValidRange)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
selectRanges(selection, kept);
|
|
128
|
+
};
|
|
129
|
+
restore();
|
|
130
|
+
const set = new Set(elements);
|
|
131
|
+
const refuse = event => {
|
|
132
|
+
if (!isTextEntry(event.target)) {
|
|
133
|
+
event.preventDefault();
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
for (const element of set) {
|
|
137
|
+
element.addEventListener('selectstart', refuse);
|
|
138
|
+
}
|
|
139
|
+
const stopDocument = view == null ? [] : [subscribeInput(view, 'selectstart', refuse, {
|
|
140
|
+
cancels: true
|
|
141
|
+
}), subscribeInput(view, 'selectionchange', restore)];
|
|
142
|
+
const unlockStyles = lockStyles(set);
|
|
143
|
+
let stage = 'held';
|
|
144
|
+
let timer = null;
|
|
145
|
+
const releaseDocument = () => {
|
|
146
|
+
if (stage === 'done') {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
stage = 'done';
|
|
150
|
+
if (timer != null) {
|
|
151
|
+
view?.clearTimeout(timer);
|
|
152
|
+
timer = null;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
restore();
|
|
156
|
+
} finally {
|
|
157
|
+
for (const stop of stopDocument) {
|
|
158
|
+
stop();
|
|
159
|
+
}
|
|
160
|
+
onReleased?.();
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
return ({
|
|
164
|
+
isCancel = false
|
|
165
|
+
} = {}) => {
|
|
166
|
+
if (stage === 'held') {
|
|
167
|
+
stage = 'lifted';
|
|
168
|
+
for (const element of set) {
|
|
169
|
+
element.removeEventListener('selectstart', refuse);
|
|
170
|
+
}
|
|
171
|
+
unlockStyles();
|
|
172
|
+
// A lift's own mouseup and click follow in its task and must still
|
|
173
|
+
// meet the hold; nothing follows a cancel, which ends it at once.
|
|
174
|
+
if (view == null || isCancel) {
|
|
175
|
+
releaseDocument();
|
|
176
|
+
} else {
|
|
177
|
+
timer = view.setTimeout(releaseDocument, 0);
|
|
178
|
+
}
|
|
179
|
+
} else if (stage === 'lifted' && isCancel) {
|
|
180
|
+
releaseDocument();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The page's selection now, its ranges copied; none for a collapsed or empty
|
|
187
|
+
* one. A drag's hold keeps what this read when the press began.
|
|
188
|
+
*/
|
|
189
|
+
export function selectionRangesOf(view) {
|
|
190
|
+
const selection = view?.getSelection();
|
|
191
|
+
if (selection == null) {
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
const composed = composedRangesOf(selection, []);
|
|
195
|
+
if (composed != null) {
|
|
196
|
+
return composed.filter(range => !range.collapsed).map(liveWhereItCan);
|
|
197
|
+
}
|
|
198
|
+
if (selection.isCollapsed) {
|
|
199
|
+
return [];
|
|
200
|
+
}
|
|
201
|
+
const ranges = [];
|
|
202
|
+
for (let index = 0; index < selection.rangeCount; index++) {
|
|
203
|
+
ranges.push(selection.getRangeAt(index).cloneRange());
|
|
204
|
+
}
|
|
205
|
+
return ranges;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Whether a press at (`x`, `y`) lands on text a drag would select. That is a
|
|
210
|
+
* text field, or a character of non-empty text whose element allows a
|
|
211
|
+
* selection. A native button's label, slotted or not, is not selectable text,
|
|
212
|
+
* because the engine starts no selection from a drag inside a button. A point
|
|
213
|
+
* beside text, which a caret query snaps to the nearest character, lands on
|
|
214
|
+
* none. Where the engine has no caret query, nothing is selectable text.
|
|
215
|
+
*/
|
|
216
|
+
export function startsOnSelectableText(doc, x, y, target) {
|
|
217
|
+
if (isTextEntry(target)) {
|
|
218
|
+
return true;
|
|
219
|
+
}
|
|
220
|
+
const caret = caretAt(doc, x, y, [...ownShadowRoot(target), ...openShadowRootsOf(target)]);
|
|
221
|
+
if (caret == null) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const {
|
|
225
|
+
node,
|
|
226
|
+
offset
|
|
227
|
+
} = caret;
|
|
228
|
+
if (node.nodeType !== 3 || (node.textContent ?? '').trim() === '') {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
// Text directly under a shadow root takes its host's style.
|
|
232
|
+
const element = node.parentElement ?? (node.parentNode?.nodeType === 11 ? node.parentNode.host : null);
|
|
233
|
+
if (element == null || !allowsSelection(element) || isInButton(node)) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
const length = (node.textContent ?? '').length;
|
|
237
|
+
const range = doc.createRange();
|
|
238
|
+
if (typeof range.getClientRects !== 'function') {
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
for (const start of [offset - 1, offset]) {
|
|
242
|
+
if (start < 0 || start >= length) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
range.setStart(node, start);
|
|
246
|
+
range.setEnd(node, start + 1);
|
|
247
|
+
for (const rect of Array.from(range.getClientRects())) {
|
|
248
|
+
if (x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom) {
|
|
249
|
+
return true;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Whether `node` is inside a native button, along the flat tree. */
|
|
257
|
+
function isInButton(node) {
|
|
258
|
+
for (let at = node; at != null; at = parentAcrossShadow(at)) {
|
|
259
|
+
if (at.nodeType === 1 && at.localName === 'button') {
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** The caret position the engine puts at a point, by either query. */
|
|
267
|
+
function caretAt(doc, x, y, shadowRoots) {
|
|
268
|
+
const queries = doc;
|
|
269
|
+
if (typeof queries.caretPositionFromPoint === 'function') {
|
|
270
|
+
// Without the roots, a point over shadow text answers at its host.
|
|
271
|
+
const position = shadowRoots.length > 0 ? queries.caretPositionFromPoint(x, y, {
|
|
272
|
+
shadowRoots: [...shadowRoots]
|
|
273
|
+
}) : queries.caretPositionFromPoint(x, y);
|
|
274
|
+
return position == null ? null : {
|
|
275
|
+
node: position.offsetNode,
|
|
276
|
+
offset: position.offset
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
if (typeof queries.caretRangeFromPoint === 'function') {
|
|
280
|
+
const range = queries.caretRangeFromPoint(x, y);
|
|
281
|
+
return range == null ? null : {
|
|
282
|
+
node: range.startContainer,
|
|
283
|
+
offset: range.startOffset
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** Whether an element's text can be selected (`user-select` is not none). */
|
|
290
|
+
function allowsSelection(element) {
|
|
291
|
+
const style = element.ownerDocument.defaultView?.getComputedStyle(element);
|
|
292
|
+
if (style == null) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
return style.getPropertyValue('user-select') !== 'none' && style.getPropertyValue('-webkit-user-select') !== 'none';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The ranges `selection` holds, with boundaries inside `shadowRoots` where the
|
|
300
|
+
* engine reads composed ranges.
|
|
301
|
+
*/
|
|
302
|
+
function heldRanges(selection, shadowRoots) {
|
|
303
|
+
const composed = composedRangesOf(selection, shadowRoots);
|
|
304
|
+
if (composed != null) {
|
|
305
|
+
return composed;
|
|
306
|
+
}
|
|
307
|
+
const ranges = [];
|
|
308
|
+
for (let index = 0; index < selection.rangeCount; index++) {
|
|
309
|
+
ranges.push(selection.getRangeAt(index));
|
|
310
|
+
}
|
|
311
|
+
return ranges;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* A live copy of `range` when both boundaries share a root, so it follows
|
|
316
|
+
* DOM changes. A range across roots stays static: no live range holds it.
|
|
317
|
+
*/
|
|
318
|
+
function liveWhereItCan(range) {
|
|
319
|
+
const live = range.startContainer.ownerDocument?.createRange();
|
|
320
|
+
if (live == null || range.startContainer.getRootNode() !== range.endContainer.getRootNode()) {
|
|
321
|
+
return range;
|
|
322
|
+
}
|
|
323
|
+
live.setStart(range.startContainer, range.startOffset);
|
|
324
|
+
live.setEnd(range.endContainer, range.endOffset);
|
|
325
|
+
return live;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** Whether both of `range`'s boundaries still exist in the document. */
|
|
329
|
+
function isValidRange(range) {
|
|
330
|
+
return isValidBoundary(range.startContainer, range.startOffset) && isValidBoundary(range.endContainer, range.endOffset);
|
|
331
|
+
}
|
|
332
|
+
function isValidBoundary(node, offset) {
|
|
333
|
+
const length = node.nodeType === 3 || node.nodeType === 4 || node.nodeType === 8 ? node.length : node.childNodes.length;
|
|
334
|
+
return node.isConnected && offset >= 0 && offset <= length;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Whether two range lists have the same boundaries, in order. */
|
|
338
|
+
function sameRanges(held, kept) {
|
|
339
|
+
return held.length === kept.length && kept.every((range, index) => {
|
|
340
|
+
const other = held[index];
|
|
341
|
+
return other.startContainer === range.startContainer && other.startOffset === range.startOffset && other.endContainer === range.endContainer && other.endOffset === range.endOffset;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Put `ranges` back as the selection. One range goes through
|
|
347
|
+
* `setBaseAndExtent`, which also reaches inside a shadow root.
|
|
348
|
+
*/
|
|
349
|
+
function selectRanges(selection, ranges) {
|
|
350
|
+
const [only] = ranges;
|
|
351
|
+
if (ranges.length === 1 && typeof selection.setBaseAndExtent === 'function') {
|
|
352
|
+
selection.setBaseAndExtent(only.startContainer, only.startOffset, only.endContainer, only.endOffset);
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
selection.removeAllRanges();
|
|
356
|
+
for (const range of ranges) {
|
|
357
|
+
const copy = range.startContainer.ownerDocument?.createRange();
|
|
358
|
+
if (copy == null) {
|
|
359
|
+
continue;
|
|
360
|
+
}
|
|
361
|
+
copy.setStart(range.startContainer, range.startOffset);
|
|
362
|
+
copy.setEnd(range.endContainer, range.endOffset);
|
|
363
|
+
selection.addRange(copy);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* The selection's composed ranges, reaching into each open shadow root a
|
|
369
|
+
* boundary lands on; null where the engine has no `getComposedRanges`.
|
|
370
|
+
* Without the roots, a selection inside a shadow root reads as one around
|
|
371
|
+
* its host.
|
|
372
|
+
*/
|
|
373
|
+
function composedRangesOf(selection, known) {
|
|
374
|
+
const query = selection.getComposedRanges;
|
|
375
|
+
if (typeof query !== 'function') {
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
378
|
+
// Engines take the roots as a dictionary, or, in older releases, as
|
|
379
|
+
// arguments.
|
|
380
|
+
const read = roots => {
|
|
381
|
+
try {
|
|
382
|
+
return query.call(selection, {
|
|
383
|
+
shadowRoots: roots
|
|
384
|
+
});
|
|
385
|
+
} catch {
|
|
386
|
+
try {
|
|
387
|
+
return query.call(selection, ...roots);
|
|
388
|
+
} catch {
|
|
389
|
+
return null;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
const roots = [...known];
|
|
394
|
+
for (;;) {
|
|
395
|
+
const ranges = read([...roots]);
|
|
396
|
+
if (ranges == null) {
|
|
397
|
+
return null;
|
|
398
|
+
}
|
|
399
|
+
const found = new Set();
|
|
400
|
+
for (const range of ranges) {
|
|
401
|
+
for (const [node, offset] of [[range.startContainer, range.startOffset], [range.endContainer, range.endOffset]]) {
|
|
402
|
+
for (const child of [node.childNodes[offset], node.childNodes[offset - 1]]) {
|
|
403
|
+
const root = child?.nodeType === 1 ? child.shadowRoot : null;
|
|
404
|
+
if (root != null && !roots.includes(root)) {
|
|
405
|
+
found.add(root);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
if (found.size === 0) {
|
|
411
|
+
return ranges;
|
|
412
|
+
}
|
|
413
|
+
roots.push(...found);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** The open shadow roots on the root chains of `ranges`' boundaries. */
|
|
418
|
+
function shadowRootsOfRanges(ranges) {
|
|
419
|
+
const roots = new Set();
|
|
420
|
+
for (const range of ranges) {
|
|
421
|
+
for (const root of [...openShadowRootsOf(range.startContainer), ...openShadowRootsOf(range.endContainer)]) {
|
|
422
|
+
roots.add(root);
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
return [...roots];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* The open shadow root `target` hosts, if any. A press on text directly under
|
|
430
|
+
* a shadow root targets its host.
|
|
431
|
+
*/
|
|
432
|
+
function ownShadowRoot(target) {
|
|
433
|
+
const root = target?.nodeType === 1 ? target.shadowRoot : null;
|
|
434
|
+
return root == null ? [] : [root];
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** The open shadow roots on `target`'s root chain, innermost first. */
|
|
438
|
+
function openShadowRootsOf(target) {
|
|
439
|
+
const roots = [];
|
|
440
|
+
let root = target != null && typeof target.nodeType === 'number' ? target.getRootNode() : null;
|
|
441
|
+
while (root != null && root.nodeType === 11) {
|
|
442
|
+
const host = root.host;
|
|
443
|
+
if (host == null) {
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
if (root.mode === 'open') {
|
|
447
|
+
roots.push(root);
|
|
448
|
+
}
|
|
449
|
+
root = host.getRootNode();
|
|
450
|
+
}
|
|
451
|
+
return roots;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** The focused element, inside open shadow roots too. */
|
|
455
|
+
function deepActiveElement(doc) {
|
|
456
|
+
let active = doc?.activeElement ?? null;
|
|
457
|
+
while (active?.shadowRoot?.activeElement != null) {
|
|
458
|
+
active = active.shadowRoot.activeElement;
|
|
459
|
+
}
|
|
460
|
+
return active;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* The selection-lock styles on `set`, refcounted per element; idempotent
|
|
465
|
+
* release.
|
|
466
|
+
*/
|
|
467
|
+
function lockStyles(set) {
|
|
80
468
|
const locked = [];
|
|
81
469
|
for (const element of set) {
|
|
82
470
|
const style = styleOf(element);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/core",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.7-canary.0eb44a2",
|
|
4
4
|
"displayName": "Astryx Core",
|
|
5
5
|
"description": "The component library. Accessible, themeable React components with built-in spacing, dark mode, and StyleX styling.",
|
|
6
6
|
"author": "Meta Open Source",
|
|
@@ -860,6 +860,9 @@
|
|
|
860
860
|
"react-dom": ">=19.0.0"
|
|
861
861
|
},
|
|
862
862
|
"devDependencies": {
|
|
863
|
+
"@astryxdesign/a11y-spec": "0.7.7-canary.0eb44a2",
|
|
864
|
+
"@astryxdesign/cli": "0.7.7-canary.0eb44a2",
|
|
865
|
+
"@astryxdesign/interaction-harness": "0.7.7-canary.0eb44a2",
|
|
863
866
|
"@babel/cli": "^7.29.7",
|
|
864
867
|
"@babel/core": "^7.29.7",
|
|
865
868
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -870,10 +873,7 @@
|
|
|
870
873
|
"@testing-library/jest-dom": "^6.6.0",
|
|
871
874
|
"@testing-library/react": "^16.3.2",
|
|
872
875
|
"@types/babel__core": "^7.20.5",
|
|
873
|
-
"tailwindcss": "^4.3.2"
|
|
874
|
-
"@astryxdesign/cli": "0.7.6",
|
|
875
|
-
"@astryxdesign/a11y-spec": "0.0.0",
|
|
876
|
-
"@astryxdesign/interaction-harness": "0.0.0"
|
|
876
|
+
"tailwindcss": "^4.3.2"
|
|
877
877
|
},
|
|
878
878
|
"dependencies": {
|
|
879
879
|
"intl-messageformat": "^11.2.9"
|
|
@@ -45,6 +45,11 @@ export const docs = {
|
|
|
45
45
|
description:
|
|
46
46
|
'Start the page heading inside `children`. AppShell owns the skip link, the banner landmark and the main landmark, but it renders no heading, so the first heading in the content area is the page h1.',
|
|
47
47
|
},
|
|
48
|
+
{
|
|
49
|
+
guidance: true,
|
|
50
|
+
description:
|
|
51
|
+
'On phones, opt the page into the full screen with `viewport-fit=cover` in the viewport meta tag (`width=device-width, initial-scale=1, viewport-fit=cover`). AppShell then extends its header under the status bar and out to the screen sides, and keeps the header\'s items and the end of the content inside the device\'s safe area. An installed web app can also draw under the status bar with `<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">`. In landscape only the header is inset: an inline SideNav and the content\'s sides still reach under a notch, so keep content padding there. A shell with no header region or phone top bar keeps no top inset; keep top padding there.',
|
|
52
|
+
},
|
|
48
53
|
{
|
|
49
54
|
guidance: false,
|
|
50
55
|
description:
|
|
@@ -343,6 +348,11 @@ export const docsDense = {
|
|
|
343
348
|
description:
|
|
344
349
|
'Give every nav slot an accessible name: TopNav and SideNav are separate navigation landmarks, so pass `label` to each.',
|
|
345
350
|
},
|
|
351
|
+
{
|
|
352
|
+
guidance: true,
|
|
353
|
+
description:
|
|
354
|
+
'Phones: add `viewport-fit=cover` to the viewport meta; the header then reaches under the status bar while its items and the content end stay in the safe area. Landscape: only the header is inset (not an inline SideNav or the content sides). No header region or phone top bar: no top inset, so keep top padding. Installed web apps: `apple-mobile-web-app-status-bar-style` `black-translucent`.',
|
|
355
|
+
},
|
|
346
356
|
{
|
|
347
357
|
guidance: true,
|
|
348
358
|
description:
|
|
@@ -471,6 +471,38 @@ const styles = stylex.create({
|
|
|
471
471
|
top: 0,
|
|
472
472
|
zIndex: 1,
|
|
473
473
|
},
|
|
474
|
+
// The insets are zero unless the page sets `viewport-fit=cover`. The header
|
|
475
|
+
// background goes under the status bar and to the screen sides, as a UIKit
|
|
476
|
+
// bar does.
|
|
477
|
+
headerSafeAreaTop: {
|
|
478
|
+
paddingBlockStart: 'env(safe-area-inset-top, 0px)',
|
|
479
|
+
},
|
|
480
|
+
// The insets are physical sides, so a right-to-left context swaps them on
|
|
481
|
+
// the inline sides.
|
|
482
|
+
headerSafeAreaSides: {
|
|
483
|
+
paddingInlineStart: {
|
|
484
|
+
default: 'env(safe-area-inset-left, 0px)',
|
|
485
|
+
':is([dir="rtl"] *)': 'env(safe-area-inset-right, 0px)',
|
|
486
|
+
},
|
|
487
|
+
paddingInlineEnd: {
|
|
488
|
+
default: 'env(safe-area-inset-right, 0px)',
|
|
489
|
+
':is([dir="rtl"] *)': 'env(safe-area-inset-left, 0px)',
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
// In `auto`, the banner's wrapper and the phone top bar both stick. Below a
|
|
493
|
+
// banner, the bar sticks under the top inset, so its items stay clear of the
|
|
494
|
+
// status bar.
|
|
495
|
+
headerStickyBelowTopInset: {
|
|
496
|
+
top: 'env(safe-area-inset-top, 0px)',
|
|
497
|
+
},
|
|
498
|
+
// Main adds the bottom inset to the padding that LayoutContent publishes for
|
|
499
|
+
// `contentPadding`. The published value excludes the inset, so a bleed child
|
|
500
|
+
// subtracts only `contentPadding`. Main is border-box, so a child that fills
|
|
501
|
+
// it ends above the inset.
|
|
502
|
+
mainSafeAreaEnd: {
|
|
503
|
+
paddingBlockEnd:
|
|
504
|
+
'calc(var(--container-padding-block-end, 0px) + env(safe-area-inset-bottom, 0px))',
|
|
505
|
+
},
|
|
474
506
|
// Sticky sideNav for auto height mode — sticks within the wrapper.
|
|
475
507
|
// This div replaces the panel as the direct flex child of the middle
|
|
476
508
|
// (horizontal) container, so it needs the same flex properties that
|
|
@@ -891,7 +923,12 @@ export function AppShell({
|
|
|
891
923
|
role="banner"
|
|
892
924
|
{...mergeProps(
|
|
893
925
|
themeProps('app-shell-header', {variant}),
|
|
894
|
-
stylex.props(
|
|
926
|
+
stylex.props(
|
|
927
|
+
headerAreaStyle,
|
|
928
|
+
isAuto && styles.headerSticky,
|
|
929
|
+
styles.headerSafeAreaTop,
|
|
930
|
+
styles.headerSafeAreaSides,
|
|
931
|
+
),
|
|
895
932
|
)}>
|
|
896
933
|
{headerInner}
|
|
897
934
|
</div>
|
|
@@ -1184,6 +1221,7 @@ export function AppShell({
|
|
|
1184
1221
|
drawsMainFocusRing
|
|
1185
1222
|
? [focusOutlineStyles.focusVisible, styles.mainScrollRegionInset]
|
|
1186
1223
|
: styles.mainFocusTarget,
|
|
1224
|
+
styles.mainSafeAreaEnd,
|
|
1187
1225
|
]}>
|
|
1188
1226
|
{children}
|
|
1189
1227
|
</LayoutContent>
|
|
@@ -1230,7 +1268,15 @@ export function AppShell({
|
|
|
1230
1268
|
role={headerContent == null ? 'banner' : undefined}
|
|
1231
1269
|
{...mergeProps(
|
|
1232
1270
|
themeProps('app-shell-header', {variant}),
|
|
1233
|
-
stylex.props(
|
|
1271
|
+
stylex.props(
|
|
1272
|
+
headerAreaStyle,
|
|
1273
|
+
isAuto && styles.headerSticky,
|
|
1274
|
+
styles.headerSafeAreaSides,
|
|
1275
|
+
// Under a banner, the banner's wrapper pads the top inset. This top
|
|
1276
|
+
// bar takes only the side insets.
|
|
1277
|
+
headerContent == null && styles.headerSafeAreaTop,
|
|
1278
|
+
isAuto && headerContent != null && styles.headerStickyBelowTopInset,
|
|
1279
|
+
),
|
|
1234
1280
|
)}>
|
|
1235
1281
|
<LayoutHeader padding={0} hasDivider={navHasDividers}>
|
|
1236
1282
|
<div
|
package/src/Avatar/Avatar.tsx
CHANGED
|
@@ -824,7 +824,9 @@ export function Avatar({
|
|
|
824
824
|
</div>
|
|
825
825
|
)}
|
|
826
826
|
</div>
|
|
827
|
-
{status
|
|
827
|
+
{/* The status slot renders for a truthy value only. A falsy one, 0
|
|
828
|
+
included, renders nothing, not a stray "0". */}
|
|
829
|
+
{Boolean(status) && (
|
|
828
830
|
<div
|
|
829
831
|
{...stylex.props(
|
|
830
832
|
styles.status,
|
|
@@ -675,10 +675,23 @@ export function BottomSheetPanel({
|
|
|
675
675
|
if (motion == null) {
|
|
676
676
|
return;
|
|
677
677
|
}
|
|
678
|
-
|
|
678
|
+
// React can run this effect again for the same motion, for example when
|
|
679
|
+
// a Suspense boundary shows this content again. The passive effect below
|
|
680
|
+
// does not run again then, so the motion it started stays started, and a
|
|
681
|
+
// completion completes at once instead of waiting for that effect.
|
|
682
|
+
if (startedMotionRef.current !== motion) {
|
|
683
|
+
startedMotionRef.current = null;
|
|
684
|
+
}
|
|
679
685
|
pendingMotionCompleteRef.current = null;
|
|
686
|
+
const complete = () => {
|
|
687
|
+
if (startedMotionRef.current === motion) {
|
|
688
|
+
onMotionCompleteRef.current?.(motion);
|
|
689
|
+
} else {
|
|
690
|
+
pendingMotionCompleteRef.current = motion;
|
|
691
|
+
}
|
|
692
|
+
};
|
|
680
693
|
if (motion === 'entering' && reactivatedEntranceRef.current) {
|
|
681
|
-
|
|
694
|
+
complete();
|
|
682
695
|
return;
|
|
683
696
|
}
|
|
684
697
|
// What ends a motion: the fade is opacity, and the entrance, alignment and
|
|
@@ -690,13 +703,7 @@ export function BottomSheetPanel({
|
|
|
690
703
|
return waitForTransition(
|
|
691
704
|
elementRef.current,
|
|
692
705
|
motion === 'fading' || crossFadeMotion ? 'opacity' : 'transform',
|
|
693
|
-
|
|
694
|
-
if (startedMotionRef.current === motion) {
|
|
695
|
-
onMotionCompleteRef.current?.(motion);
|
|
696
|
-
} else {
|
|
697
|
-
pendingMotionCompleteRef.current = motion;
|
|
698
|
-
}
|
|
699
|
-
},
|
|
706
|
+
complete,
|
|
700
707
|
);
|
|
701
708
|
}, [motion, isCrossFade]);
|
|
702
709
|
// The snap is transform-only; the layout height reconciles when it lands.
|
|
@@ -73,6 +73,7 @@ import type {DropdownMenuOption} from '../DropdownMenu/DropdownMenu';
|
|
|
73
73
|
|
|
74
74
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
75
75
|
import {useInteractionModalityTracking} from '../utils/interactionModality';
|
|
76
|
+
import {isRenderable} from '../utils/isRenderable';
|
|
76
77
|
// =============================================================================
|
|
77
78
|
// Props
|
|
78
79
|
// =============================================================================
|
|
@@ -373,7 +374,9 @@ export function BreadcrumbItem({
|
|
|
373
374
|
|
|
374
375
|
const content = (
|
|
375
376
|
<>
|
|
376
|
-
{startIcon &&
|
|
377
|
+
{isRenderable(startIcon) && (
|
|
378
|
+
<span {...stylex.props(itemStyles.icon)}>{startIcon}</span>
|
|
379
|
+
)}
|
|
377
380
|
{children}
|
|
378
381
|
</>
|
|
379
382
|
);
|
package/src/Button/Button.tsx
CHANGED
|
@@ -71,6 +71,7 @@ import {
|
|
|
71
71
|
useComponentTranslator,
|
|
72
72
|
} from '../i18n/useComponentTranslator';
|
|
73
73
|
import english from '../i18n/generated-english/Button/Button.generated';
|
|
74
|
+
import {isRenderable} from '../utils/isRenderable';
|
|
74
75
|
|
|
75
76
|
registerEnglishFallback(english);
|
|
76
77
|
|
|
@@ -803,7 +804,7 @@ export function Button({
|
|
|
803
804
|
: loadingStyles.hiddenContent),
|
|
804
805
|
)}
|
|
805
806
|
aria-hidden={isLoadingState || undefined}>
|
|
806
|
-
{icon && (
|
|
807
|
+
{isRenderable(icon) && (
|
|
807
808
|
<span
|
|
808
809
|
{...stylex.props(styles.iconWrapper, iconBoxSizeStyles[iconSize])}>
|
|
809
810
|
<IconDefaultSizeProvider value={iconSize}>
|
|
@@ -814,7 +815,7 @@ export function Button({
|
|
|
814
815
|
{isIconOnly ? null : (
|
|
815
816
|
<span {...stylex.props(styles.labelText)}>{children ?? label}</span>
|
|
816
817
|
)}
|
|
817
|
-
{!isIconOnly && endContent && (
|
|
818
|
+
{!isIconOnly && isRenderable(endContent) && (
|
|
818
819
|
<span {...stylex.props(styles.endContentWrapper)}>{endContent}</span>
|
|
819
820
|
)}
|
|
820
821
|
</span>
|
package/src/Chat/Chat.doc.mjs
CHANGED
|
@@ -35,6 +35,7 @@ export const docs = {
|
|
|
35
35
|
vars: [
|
|
36
36
|
{name: '--_chat-composer-radius', description: 'Border radius of the composer body. Inner elements derive their radius concentrically.', default: 'var(--radius-chat)', private: true},
|
|
37
37
|
{name: '--_chat-composer-padding', description: 'Padding of the composer body. Used in the concentric radius calculation.', default: 'var(--spacing-3)', private: true},
|
|
38
|
+
{name: '--_chat-avatar-offset', description: 'Top offset of a message avatar: the name row spacing when a bubble in the message shows a name, else 0px. Each message sets it from its own content. Not a theming lever.', default: '0px', private: true},
|
|
38
39
|
{name: '--_button-radius', description: 'Concentric button radius inside the composer.', default: 'max(var(--radius-element), calc(var(--_chat-composer-radius) - var(--_chat-composer-padding)))', private: true, derived: true, formula: 'max(var(--radius-element), calc(var(--_chat-composer-radius) - var(--_chat-composer-padding)))'},
|
|
39
40
|
],
|
|
40
41
|
derived: [
|