@astryxdesign/core 0.7.7-canary.ae97fa8 → 0.7.7-canary.b377b21
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/AlertDialog/AlertDialog.d.ts +4 -1
- package/dist/AlertDialog/AlertDialog.d.ts.map +1 -1
- package/dist/AlertDialog/AlertDialog.js +36 -6
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +22 -3
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +3 -1
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +17 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +13 -2
- package/dist/ComplexSelector/ComplexSelector.d.ts +4 -1
- package/dist/ComplexSelector/ComplexSelector.d.ts.map +1 -1
- package/dist/ComplexSelector/ComplexSelector.js +14 -4
- package/dist/ContextMenu/ContextMenu.js +2 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +7 -31
- package/dist/Field/Field.js +2 -2
- package/dist/HoverCard/HoverCard.d.ts +4 -3
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.d.ts +5 -4
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +17 -31
- package/dist/Icon/Icon.d.ts +5 -1
- package/dist/Icon/Icon.d.ts.map +1 -1
- package/dist/Icon/Icon.js +5 -2
- package/dist/Layer/usePressPin.d.ts +17 -5
- package/dist/Layer/usePressPin.d.ts.map +1 -1
- package/dist/Layer/usePressPin.js +62 -12
- package/dist/Lightbox/Lightbox.d.ts.map +1 -1
- package/dist/Lightbox/Lightbox.js +18 -0
- package/dist/PowerSearch/PowerSearch.d.ts +36 -3
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +433 -127
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +100 -0
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -0
- package/dist/PowerSearch/PowerSearchBottomSheet.js +1913 -0
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts +32 -1
- package/dist/PowerSearch/PowerSearchEditPopover.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchEditPopover.js +17 -6
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts +38 -3
- package/dist/PowerSearch/PowerSearchValueEditor.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchValueEditor.js +170 -61
- package/dist/PowerSearch/formatFilterValue.d.ts +7 -0
- package/dist/PowerSearch/formatFilterValue.d.ts.map +1 -1
- package/dist/PowerSearch/formatFilterValue.js +11 -1
- package/dist/PowerSearch/formatTokenValue.d.ts +28 -0
- package/dist/PowerSearch/formatTokenValue.d.ts.map +1 -0
- package/dist/PowerSearch/formatTokenValue.js +160 -0
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts +65 -0
- package/dist/PowerSearch/powerSearchSheetUtils.d.ts.map +1 -0
- package/dist/PowerSearch/powerSearchSheetUtils.js +168 -0
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +3 -2
- package/dist/RadioList/RadioListItem.d.ts +1 -1
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -3
- package/dist/Resizable/ResizeHandle.d.ts +2 -1
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +99 -77
- package/dist/SegmentedControl/useSegmentedSelectionMotion.d.ts.map +1 -1
- package/dist/SegmentedControl/useSegmentedSelectionMotion.js +3 -3
- package/dist/Slider/sliderTouchController.js +2 -2
- package/dist/Spinner/Spinner.d.ts +1 -1
- package/dist/Spinner/Spinner.d.ts.map +1 -1
- package/dist/Spinner/Spinner.js +60 -58
- package/dist/Switch/useSwitchTouch.js +4 -4
- package/dist/Table/BaseTable.d.ts.map +1 -1
- package/dist/Table/BaseTable.js +29 -8
- package/dist/TextInput/TextInput.d.ts +2 -2
- package/dist/TextInput/TextInput.d.ts.map +1 -1
- package/dist/TextInput/TextInput.js +10 -7
- package/dist/Toast/Toast.d.ts +11 -1
- package/dist/Toast/Toast.d.ts.map +1 -1
- package/dist/Toast/Toast.js +25 -15
- package/dist/Toast/ToastViewport.d.ts.map +1 -1
- package/dist/Toast/ToastViewport.js +94 -34
- package/dist/Toast/types.d.ts +9 -3
- package/dist/Toast/types.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +19 -1
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +34 -53
- package/dist/Typeahead/TypeaheadItem.d.ts +2 -1
- package/dist/Typeahead/TypeaheadItem.d.ts.map +1 -1
- package/dist/animator/createAnimator.d.ts +98 -0
- package/dist/animator/createAnimator.d.ts.map +1 -0
- package/dist/animator/createAnimator.js +259 -0
- package/dist/astryx.css +35 -6
- package/dist/gestures/PressTracker.d.ts.map +1 -1
- package/dist/gestures/PressTracker.js +4 -3
- package/dist/hooks/actionGroupTracker.d.ts +4 -1
- package/dist/hooks/actionGroupTracker.d.ts.map +1 -1
- package/dist/hooks/actionGroupTracker.js +13 -3
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -0
- package/dist/hooks/pressGroupContext.d.ts +6 -0
- package/dist/hooks/pressGroupContext.d.ts.map +1 -0
- package/dist/hooks/pressGroupContext.js +20 -0
- package/dist/hooks/useActionGroupTracking.d.ts +53 -0
- package/dist/hooks/useActionGroupTracking.d.ts.map +1 -0
- package/dist/hooks/useActionGroupTracking.js +174 -0
- package/dist/hooks/useAnimator.d.ts +41 -0
- package/dist/hooks/useAnimator.d.ts.map +1 -0
- package/dist/hooks/useAnimator.js +70 -0
- package/dist/hooks/useAttachment.d.ts +1 -1
- package/dist/hooks/useAttachment.js +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts +1 -1
- package/dist/hooks/useInputStatusIcon.d.ts.map +1 -1
- package/dist/hooks/useInputStatusIcon.js +2 -2
- package/dist/hooks/useLongPress.d.ts +6 -2
- package/dist/hooks/useLongPress.d.ts.map +1 -1
- package/dist/hooks/useLongPress.js +95 -15
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts +8 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +42 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.js +6 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts +8 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/PowerSearch/formatTokenValue.generated.js +14 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +30 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +30 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/internal/indicatorPress.stylex.d.ts +20 -0
- package/dist/internal/indicatorPress.stylex.d.ts.map +1 -0
- package/dist/internal/indicatorPress.stylex.js +26 -0
- package/dist/motion/animator.d.ts +14 -14
- package/dist/motion/animator.d.ts.map +1 -1
- package/dist/motion/animator.js +41 -18
- package/dist/motion/uikit/alert.d.ts +41 -0
- package/dist/motion/uikit/alert.d.ts.map +1 -0
- package/dist/motion/uikit/alert.js +43 -0
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts +15 -0
- package/dist/privateAttributes/pressGroupItem.stylex.d.ts.map +1 -0
- package/dist/privateAttributes/pressGroupItem.stylex.js +23 -0
- package/dist/utils/focusReturn.d.ts +15 -0
- package/dist/utils/focusReturn.d.ts.map +1 -1
- package/dist/utils/focusReturn.js +28 -0
- package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +3 -2
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +19 -6
- package/dist/utils/triggerFocus.d.ts +36 -0
- package/dist/utils/triggerFocus.d.ts.map +1 -0
- package/dist/utils/triggerFocus.js +65 -0
- package/locales/en.json +120 -0
- package/locales/pseudo.json +90 -0
- package/package.json +9 -4
- package/src/AlertDialog/AlertDialog.doc.mjs +2 -2
- package/src/AlertDialog/AlertDialog.tsx +37 -5
- package/src/BottomSheet/BottomSheetPanel.tsx +33 -3
- package/src/BottomSheet/useSheetGestures.ts +3 -1
- package/src/Button/Button.tsx +26 -4
- package/src/CheckboxInput/CheckboxInput.doc.mjs +18 -0
- package/src/CheckboxInput/CheckboxInput.tsx +36 -9
- package/src/ComplexSelector/ComplexSelector.tsx +20 -5
- package/src/ContextMenu/ContextMenu.tsx +2 -2
- package/src/Dialog/Dialog.tsx +10 -31
- package/src/Field/Field.tsx +2 -2
- package/src/HoverCard/HoverCard.doc.mjs +4 -2
- package/src/HoverCard/HoverCard.tsx +4 -3
- package/src/HoverCard/useHoverCard.doc.mjs +178 -36
- package/src/HoverCard/useHoverCard.tsx +46 -52
- package/src/Icon/Icon.doc.mjs +49 -8
- package/src/Icon/Icon.tsx +15 -7
- package/src/Layer/usePressPin.ts +81 -15
- package/src/Lightbox/Lightbox.tsx +18 -0
- package/src/PowerSearch/PowerSearch.doc.mjs +54 -5
- package/src/PowerSearch/PowerSearch.tsx +609 -155
- package/src/PowerSearch/PowerSearchBottomSheet.tsx +2515 -0
- package/src/PowerSearch/PowerSearchEditPopover.tsx +28 -6
- package/src/PowerSearch/PowerSearchValueEditor.tsx +203 -38
- package/src/PowerSearch/formatFilterValue.ts +18 -1
- package/src/PowerSearch/formatTokenValue.ts +215 -0
- package/src/PowerSearch/powerSearchSheetUtils.ts +224 -0
- package/src/RadioList/RadioList.tsx +3 -2
- package/src/RadioList/RadioListItem.tsx +5 -3
- package/src/Resizable/Resizable.doc.mjs +5 -4
- package/src/Resizable/ResizeHandle.tsx +167 -109
- package/src/SegmentedControl/useSegmentedSelectionMotion.ts +6 -3
- package/src/Slider/sliderTouchController.ts +2 -2
- package/src/Spinner/Spinner.tsx +77 -63
- package/src/Switch/useSwitchTouch.ts +4 -4
- package/src/Table/BaseTable.tsx +46 -12
- package/src/TextInput/TextInput.tsx +11 -10
- package/src/Toast/Toast.doc.mjs +4 -3
- package/src/Toast/Toast.tsx +52 -32
- package/src/Toast/ToastViewport.tsx +121 -35
- package/src/Toast/types.ts +9 -3
- package/src/Tooltip/useTooltip.doc.mjs +24 -0
- package/src/Tooltip/useTooltip.tsx +61 -56
- package/src/Typeahead/TypeaheadItem.doc.mjs +6 -3
- package/src/Typeahead/TypeaheadItem.tsx +2 -1
- package/src/animator/createAnimator.doc.mjs +153 -0
- package/src/animator/createAnimator.ts +324 -0
- package/src/gestures/PressTracker.ts +4 -3
- package/src/gestures/README.md +7 -1
- package/src/hooks/actionGroupTracker.ts +20 -5
- package/src/hooks/index.ts +2 -0
- package/src/hooks/pressGroupContext.ts +21 -0
- package/src/hooks/useActionGroupTracking.ts +224 -0
- package/src/hooks/useAnimator.doc.mjs +109 -0
- package/src/hooks/useAnimator.ts +67 -0
- package/src/hooks/useAttachment.ts +1 -1
- package/src/hooks/useInputStatusIcon.tsx +3 -2
- package/src/hooks/useLongPress.doc.mjs +2 -2
- package/src/hooks/useLongPress.ts +136 -15
- package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +43 -0
- package/src/i18n/generated-english/PowerSearch/PowerSearchValueEditor.generated.ts +6 -0
- package/src/i18n/generated-english/PowerSearch/formatTokenValue.generated.ts +15 -0
- package/src/i18n/generated-locales/en.generated.ts +41 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +42 -0
- package/src/index.ts +13 -0
- package/src/internal/indicatorPress.stylex.ts +24 -0
- package/src/motion/README.md +8 -5
- package/src/motion/animator.ts +61 -29
- package/src/motion/uikit/alert.ts +43 -0
- package/src/privateAttributes/pressGroupItem.stylex.ts +23 -0
- package/src/utils/focusReturn.ts +30 -0
- package/src/utils/interactionOverlay.stylex.ts +3 -2
- package/src/utils/pressFeedback.ts +19 -6
- package/src/utils/triggerFocus.ts +80 -0
|
@@ -8,29 +8,12 @@
|
|
|
8
8
|
* @output Styled drag handle with WAI-ARIA separator role and keyboard support
|
|
9
9
|
* @position Between resizable panels; consumed directly by builders
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Pill grip indicator can sit on either side of the divider (or centered)
|
|
14
|
-
* via pillPlacement. Default 'auto' places the pill on the panel side and
|
|
15
|
-
* flips when the panel collapses to 0px so it stays accessible.
|
|
16
|
-
*
|
|
17
|
-
* Pill placement uses a single stylex dynamic style that accepts a direction
|
|
18
|
-
* multiplier (-1 or 1). The pill element has its own themeProps
|
|
19
|
-
* ('resize-handle-pill') so themes can target size/shape directly.
|
|
20
|
-
*
|
|
21
|
-
* While the panel is collapsed, aria-valuenow is clamped to aria-valuemin
|
|
22
|
-
* (a value below the minimum is invalid per WCAG 4.1.2) and a localized
|
|
23
|
-
* "Collapsed" aria-valuetext announces the real state.
|
|
24
|
-
*
|
|
25
|
-
* The handle's input (the pointer drag, the keys, the double-click) is
|
|
26
|
-
* resizeGesture.ts. ResizeHandle forwards its React events to it, over the
|
|
27
|
-
* region its `resizable` prop describes. A drag takes pointer capture on the grab zone.
|
|
28
|
-
* The gesture's move, up and cancel events then go there, as in Slider, even
|
|
29
|
-
* over an embedded frame.
|
|
11
|
+
* A drag takes pointer capture on the element it starts on, so moves over an
|
|
12
|
+
* embedded frame still arrive.
|
|
30
13
|
*/
|
|
31
14
|
|
|
32
15
|
import {useEffect, useRef, useState} from 'react';
|
|
33
|
-
import type {ReactNode} from 'react';
|
|
16
|
+
import type {HTMLAttributes, ReactNode} from 'react';
|
|
34
17
|
import * as stylex from '@stylexjs/stylex';
|
|
35
18
|
import type {BaseProps} from '../BaseProps';
|
|
36
19
|
import {
|
|
@@ -81,30 +64,6 @@ function resolveEffectiveSide(
|
|
|
81
64
|
return panelSide;
|
|
82
65
|
}
|
|
83
66
|
|
|
84
|
-
/**
|
|
85
|
-
* Hit-area inline bias, expressed as a fraction of the pill's per-side offset.
|
|
86
|
-
*
|
|
87
|
-
* The visible pill is placed by `pillOffsetX`/`pillOffsetY`: anchored at the
|
|
88
|
-
* divider's inline-start edge and pushed one grip-width-plus-gap toward the
|
|
89
|
-
* panel side via a PHYSICAL translate (dir −1 = start, +1 = end). Because that
|
|
90
|
-
* offset is physical, the pill sits on the same physical side of the divider in
|
|
91
|
-
* both LTR and RTL. The grab zone must sit OVER that pill, so it has to use the
|
|
92
|
-
* exact same anchor + physical-offset construction as the pill (just wider) —
|
|
93
|
-
* anchoring the hit area at the divider centre (`insetInlineStart: 50%`) and
|
|
94
|
-
* biasing with a percentage translate mixes a logical anchor (which flips under
|
|
95
|
-
* RTL) with a physical translate (which doesn't), leaving the grab zone stranded
|
|
96
|
-
* to one side under RTL. Returning the pill's `dir` here lets the hit area reuse
|
|
97
|
-
* `pillOffsetX`/`pillOffsetY` so the two elements are positioned identically.
|
|
98
|
-
*/
|
|
99
|
-
function hitAreaBiasDir(
|
|
100
|
-
effectiveSide: 'start' | 'end' | 'center',
|
|
101
|
-
): number | null {
|
|
102
|
-
if (effectiveSide === 'center') {
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
return effectiveSide === 'start' ? -1 : 1;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
67
|
const styles = stylex.create({
|
|
109
68
|
handle: {
|
|
110
69
|
position: 'relative',
|
|
@@ -137,6 +96,12 @@ const styles = stylex.create({
|
|
|
137
96
|
insetInlineEnd: 0,
|
|
138
97
|
height: 'var(--resize-handle-hit-area, 16px)',
|
|
139
98
|
},
|
|
99
|
+
overlayHorizontalOnShortPane: {
|
|
100
|
+
width: 'min(var(--resize-handle-hit-area, 16px), 50%)',
|
|
101
|
+
},
|
|
102
|
+
overlayVerticalOnShortPane: {
|
|
103
|
+
height: 'min(var(--resize-handle-hit-area, 16px), 50%)',
|
|
104
|
+
},
|
|
140
105
|
horizontal: {
|
|
141
106
|
width: 1,
|
|
142
107
|
height: '100%',
|
|
@@ -196,19 +161,27 @@ const styles = stylex.create({
|
|
|
196
161
|
':is(:disabled,[aria-disabled="true"])': 'default',
|
|
197
162
|
},
|
|
198
163
|
},
|
|
199
|
-
//
|
|
200
|
-
// the
|
|
201
|
-
// the call site (correct in LTR and RTL); this
|
|
164
|
+
// The grab zone sits centred on the Handle whatever `pillPlacement` says,
|
|
165
|
+
// which only places the pill. Inline centering comes from
|
|
166
|
+
// rtlStyles.centerInline at the call site (correct in LTR and RTL); this
|
|
167
|
+
// block owns only the block axis.
|
|
202
168
|
hitAreaCenteredY: {
|
|
203
169
|
insetBlockStart: '50%',
|
|
204
170
|
transform: 'translateY(-50%)',
|
|
205
171
|
},
|
|
172
|
+
hitAreaWithinHandleX: {
|
|
173
|
+
width: `min(${spacingVars['--spacing-4']}, 100%)`,
|
|
174
|
+
},
|
|
175
|
+
hitAreaWithinHandleY: {
|
|
176
|
+
height: `min(${spacingVars['--spacing-4']}, 100%)`,
|
|
177
|
+
},
|
|
206
178
|
|
|
207
179
|
// Pill base — themes target .astryx-resize-handle-pill for size/shape.
|
|
208
180
|
pill: {
|
|
209
181
|
position: 'absolute',
|
|
210
182
|
zIndex: 2,
|
|
211
|
-
|
|
183
|
+
touchAction: 'none',
|
|
184
|
+
userSelect: 'none',
|
|
212
185
|
borderRadius: radiusVars['--radius-full'],
|
|
213
186
|
backgroundColor: colorVars['--color-border'],
|
|
214
187
|
transitionProperty: 'opacity, background-color, transform',
|
|
@@ -216,6 +189,18 @@ const styles = stylex.create({
|
|
|
216
189
|
transitionTimingFunction: easeVars['--ease-standard'],
|
|
217
190
|
top: '50%',
|
|
218
191
|
},
|
|
192
|
+
pillGrabHorizontal: {
|
|
193
|
+
cursor: {
|
|
194
|
+
default: 'col-resize',
|
|
195
|
+
':is(:disabled,[aria-disabled="true"])': 'default',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
pillGrabVertical: {
|
|
199
|
+
cursor: {
|
|
200
|
+
default: 'row-resize',
|
|
201
|
+
':is(:disabled,[aria-disabled="true"])': 'default',
|
|
202
|
+
},
|
|
203
|
+
},
|
|
219
204
|
pillHorizontal: {
|
|
220
205
|
width: 3,
|
|
221
206
|
height: spacingVars['--spacing-8'],
|
|
@@ -224,59 +209,93 @@ const styles = stylex.create({
|
|
|
224
209
|
width: spacingVars['--spacing-8'],
|
|
225
210
|
height: 3,
|
|
226
211
|
},
|
|
227
|
-
|
|
212
|
+
// A hidden pill paints nothing, so it takes no pointer either.
|
|
213
|
+
pillHidden: {opacity: 0, pointerEvents: 'none'},
|
|
228
214
|
pillVisible: {opacity: 1},
|
|
229
215
|
pillHover: {
|
|
230
216
|
opacity: 1,
|
|
217
|
+
pointerEvents: 'auto',
|
|
231
218
|
backgroundColor: colorVars['--color-border'],
|
|
232
219
|
},
|
|
233
220
|
pillActive: {
|
|
234
221
|
opacity: 1,
|
|
222
|
+
pointerEvents: 'auto',
|
|
235
223
|
backgroundColor: colorVars['--color-border-emphasized'],
|
|
236
224
|
},
|
|
225
|
+
// A pill shown on hover can sit outside the Grab zone. This invisible
|
|
226
|
+
// extension spans the gap toward the Handle, so the pointer keeps the hover
|
|
227
|
+
// on its way to the pill. It reaches no further than the pill, and it takes
|
|
228
|
+
// the pointer only while the pill does. The pill's offset is a physical
|
|
229
|
+
// translate, so the side toward the Handle is physical too.
|
|
230
|
+
pillBridgeRight: {
|
|
231
|
+
'::before': {
|
|
232
|
+
content: '""',
|
|
233
|
+
position: 'absolute',
|
|
234
|
+
insetBlock: 0,
|
|
235
|
+
insetInlineStart: {
|
|
236
|
+
default: 0,
|
|
237
|
+
':is([dir="rtl"] *)': `calc(-1 * ${spacingVars['--spacing-1']} - 1px)`,
|
|
238
|
+
},
|
|
239
|
+
insetInlineEnd: {
|
|
240
|
+
default: `calc(-1 * ${spacingVars['--spacing-1']} - 1px)`,
|
|
241
|
+
':is([dir="rtl"] *)': 0,
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
pillBridgeLeft: {
|
|
246
|
+
'::before': {
|
|
247
|
+
content: '""',
|
|
248
|
+
position: 'absolute',
|
|
249
|
+
insetBlock: 0,
|
|
250
|
+
insetInlineStart: {
|
|
251
|
+
default: `calc(-1 * ${spacingVars['--spacing-1']} - 1px)`,
|
|
252
|
+
':is([dir="rtl"] *)': 0,
|
|
253
|
+
},
|
|
254
|
+
insetInlineEnd: {
|
|
255
|
+
default: 0,
|
|
256
|
+
':is([dir="rtl"] *)': `calc(-1 * ${spacingVars['--spacing-1']} - 1px)`,
|
|
257
|
+
},
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
pillBridgeDown: {
|
|
261
|
+
'::before': {
|
|
262
|
+
content: '""',
|
|
263
|
+
position: 'absolute',
|
|
264
|
+
insetInline: 0,
|
|
265
|
+
insetBlockStart: 0,
|
|
266
|
+
insetBlockEnd: `calc(-1 * ${spacingVars['--spacing-1']} - 1px)`,
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
pillBridgeUp: {
|
|
270
|
+
'::before': {
|
|
271
|
+
content: '""',
|
|
272
|
+
position: 'absolute',
|
|
273
|
+
insetInline: 0,
|
|
274
|
+
insetBlockStart: `calc(-1 * ${spacingVars['--spacing-1']} - 1px)`,
|
|
275
|
+
insetBlockEnd: 0,
|
|
276
|
+
},
|
|
277
|
+
},
|
|
237
278
|
});
|
|
238
279
|
|
|
239
280
|
// Dynamic styles — avoids inline style overrides.
|
|
240
281
|
// Each axis gets its own function since StyleX requires static structure.
|
|
241
282
|
const dynamicStyles = stylex.create({
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
// physical translate `dir * (gripWidth + gap)`, so they share the pill's
|
|
247
|
-
// near edge. The hit area is wider, so it also shifts by half the width
|
|
248
|
-
// difference `(16px − 3px) / 2 = 6.5px` to align the two CENTRES. That
|
|
249
|
-
// centring shift is along the inline axis (a box grows from its inline-start
|
|
250
|
-
// edge toward inline-end), so it must flip physical sign under RTL — unlike
|
|
251
|
-
// the `dir * (...)` travel, which is physical and identical in both
|
|
252
|
-
// directions because the pill's own offset is physical. Mixing the previous
|
|
253
|
-
// divider-relative `50%` anchor with a percentage translate stranded the grab
|
|
254
|
-
// zone to one side under RTL; this construction keeps it on the pill.
|
|
255
|
-
//
|
|
256
|
-
// Only the offset axis is translated. Unlike the pill — a small box anchored
|
|
257
|
-
// at the divider's midpoint, so it needs a −50% self-shift to centre — the
|
|
258
|
-
// grab zone is STRETCHED along the handle by hitAreaHorizontal/Vertical's
|
|
259
|
-
// 0/0 insets, so it is already in place on that axis. A percentage there
|
|
260
|
-
// would displace it by half the handle's own length, growing with the panel.
|
|
261
|
-
hitAreaOffsetX: (dir: number) => ({
|
|
262
|
-
insetInlineStart: 0,
|
|
263
|
-
transform: {
|
|
264
|
-
default: `translateX(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px))`,
|
|
265
|
-
':is([dir="rtl"] *)': `translateX(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) + 6.5px))`,
|
|
266
|
-
},
|
|
283
|
+
// An inline Handle's zone is centred on its line, so half of it reaches into
|
|
284
|
+
// the pane.
|
|
285
|
+
hitAreaLengthX: (pane: number) => ({
|
|
286
|
+
width: `min(${spacingVars['--spacing-4']}, ${pane}px)`,
|
|
267
287
|
}),
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
transform: `translateY(calc(${dir} * (3px + ${spacingVars['--spacing-1']}) - 6.5px))`,
|
|
288
|
+
hitAreaLengthY: (pane: number) => ({
|
|
289
|
+
height: `min(${spacingVars['--spacing-4']}, ${pane}px)`,
|
|
271
290
|
}),
|
|
272
|
-
pillOffsetX: (dir:
|
|
291
|
+
pillOffsetX: (dir: -1 | 1) => ({
|
|
273
292
|
insetInlineStart: 0,
|
|
274
293
|
transform: `translate(calc(${dir} * (100% + ${spacingVars['--spacing-1']})), -50%)`,
|
|
275
294
|
}),
|
|
276
295
|
// Vertical offset: no rotation — use explicit landscape dimensions.
|
|
277
296
|
// Rotation + offset creates confusing coordinate math since translate
|
|
278
297
|
// operates in pre-rotation local space.
|
|
279
|
-
pillOffsetY: (dir:
|
|
298
|
+
pillOffsetY: (dir: -1 | 1) => ({
|
|
280
299
|
top: 0,
|
|
281
300
|
transform: `translate(-50%, calc(${dir} * (100% + ${spacingVars['--spacing-1']})))`,
|
|
282
301
|
}),
|
|
@@ -333,7 +352,8 @@ export interface ResizeHandleProps extends Omit<
|
|
|
333
352
|
isAlwaysVisible?: boolean;
|
|
334
353
|
|
|
335
354
|
/**
|
|
336
|
-
* Which side of the divider line the pill sits on.
|
|
355
|
+
* Which side of the divider line the pill sits on. It moves only the pill:
|
|
356
|
+
* the grab zone stays centred on the divider.
|
|
337
357
|
* - `'auto'` — panel side by default, flips when panel is collapsed to 0px
|
|
338
358
|
* - `'start'` — left (horizontal) or top (vertical)
|
|
339
359
|
* - `'end'` — right (horizontal) or bottom (vertical)
|
|
@@ -468,9 +488,12 @@ export function ResizeHandle({
|
|
|
468
488
|
isReversed,
|
|
469
489
|
isCollapsed,
|
|
470
490
|
);
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
|
|
491
|
+
// An inline zone reaches at most half of the pane the Handle resizes. An
|
|
492
|
+
// overlay Handle takes at most half of it. A collapsed pane, or one at 0px,
|
|
493
|
+
// has nothing to protect and keeps the whole zone.
|
|
494
|
+
const paneSize = resizable?._size;
|
|
495
|
+
const paneLength =
|
|
496
|
+
isCollapsed || paneSize == null || paneSize <= 0 ? undefined : paneSize;
|
|
474
497
|
const isInteracting = isHovered || isFocused;
|
|
475
498
|
|
|
476
499
|
// A handle whose axis disagrees with its region drags along one axis while
|
|
@@ -533,6 +556,35 @@ export function ResizeHandle({
|
|
|
533
556
|
? t('@astryx.resizable.collapsed')
|
|
534
557
|
: undefined;
|
|
535
558
|
|
|
559
|
+
// The Grip pill takes the pointer as the Grab zone does, so a pill drawn
|
|
560
|
+
// outside the zone still resizes.
|
|
561
|
+
const grabHandlers: Pick<
|
|
562
|
+
HTMLAttributes<HTMLDivElement>,
|
|
563
|
+
| 'onPointerDown'
|
|
564
|
+
| 'onPointerMove'
|
|
565
|
+
| 'onPointerUp'
|
|
566
|
+
| 'onPointerCancel'
|
|
567
|
+
| 'onLostPointerCapture'
|
|
568
|
+
| 'onPointerEnter'
|
|
569
|
+
| 'onPointerLeave'
|
|
570
|
+
> = {
|
|
571
|
+
onPointerDown: e => {
|
|
572
|
+
if (!isDisabled) {
|
|
573
|
+
gesture?.pointerDown(e);
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
onPointerMove: e => gesture?.pointerMove(e),
|
|
577
|
+
onPointerUp: e => gesture?.pointerUp(e),
|
|
578
|
+
onPointerCancel: e => gesture?.pointerCancel(e),
|
|
579
|
+
onLostPointerCapture: e => gesture?.pointerCancel(e),
|
|
580
|
+
onPointerEnter: () => setIsHovered(true),
|
|
581
|
+
onPointerLeave: () => {
|
|
582
|
+
if (!isDragging) {
|
|
583
|
+
setIsHovered(false);
|
|
584
|
+
}
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
|
|
536
588
|
return (
|
|
537
589
|
<div
|
|
538
590
|
ref={useMergedRefs(ref, handleRef)}
|
|
@@ -560,6 +612,11 @@ export function ResizeHandle({
|
|
|
560
612
|
isOverlay && styles.overlay,
|
|
561
613
|
isOverlay &&
|
|
562
614
|
(isHorizontal ? styles.overlayHorizontal : styles.overlayVertical),
|
|
615
|
+
isOverlay &&
|
|
616
|
+
paneLength != null &&
|
|
617
|
+
(isHorizontal
|
|
618
|
+
? styles.overlayHorizontalOnShortPane
|
|
619
|
+
: styles.overlayVerticalOnShortPane),
|
|
563
620
|
!isOverlay && (isHorizontal ? styles.horizontal : styles.vertical),
|
|
564
621
|
!isOverlay &&
|
|
565
622
|
!hasDivider &&
|
|
@@ -594,34 +651,21 @@ export function ResizeHandle({
|
|
|
594
651
|
{...stylex.props(
|
|
595
652
|
styles.hitArea,
|
|
596
653
|
isHorizontal ? styles.hitAreaHorizontal : styles.hitAreaVertical,
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
654
|
+
isHorizontal
|
|
655
|
+
? rtlStyles.centerInline('0px')
|
|
656
|
+
: styles.hitAreaCenteredY,
|
|
657
|
+
isOverlay &&
|
|
658
|
+
(isHorizontal
|
|
659
|
+
? styles.hitAreaWithinHandleX
|
|
660
|
+
: styles.hitAreaWithinHandleY),
|
|
661
|
+
!isOverlay &&
|
|
662
|
+
paneLength != null &&
|
|
663
|
+
(isHorizontal
|
|
664
|
+
? dynamicStyles.hitAreaLengthX(paneLength)
|
|
665
|
+
: dynamicStyles.hitAreaLengthY(paneLength)),
|
|
608
666
|
isDisabled && styles.disabled,
|
|
609
667
|
)}
|
|
610
|
-
|
|
611
|
-
if (!isDisabled) {
|
|
612
|
-
gesture?.pointerDown(e);
|
|
613
|
-
}
|
|
614
|
-
}}
|
|
615
|
-
onPointerMove={e => gesture?.pointerMove(e)}
|
|
616
|
-
onPointerUp={e => gesture?.pointerUp(e)}
|
|
617
|
-
onPointerCancel={e => gesture?.pointerCancel(e)}
|
|
618
|
-
onLostPointerCapture={e => gesture?.pointerCancel(e)}
|
|
619
|
-
onPointerEnter={() => setIsHovered(true)}
|
|
620
|
-
onPointerLeave={() => {
|
|
621
|
-
if (!isDragging) {
|
|
622
|
-
setIsHovered(false);
|
|
623
|
-
}
|
|
624
|
-
}}
|
|
668
|
+
{...grabHandlers}
|
|
625
669
|
/>
|
|
626
670
|
{/* Pill grip indicator — themed via .astryx-resize-handle-pill */}
|
|
627
671
|
{children ?? (
|
|
@@ -640,11 +684,25 @@ export function ResizeHandle({
|
|
|
640
684
|
: dynamicStyles.pillOffsetY(
|
|
641
685
|
effectiveSide === 'start' ? -1 : 1,
|
|
642
686
|
),
|
|
687
|
+
isHorizontal
|
|
688
|
+
? styles.pillGrabHorizontal
|
|
689
|
+
: styles.pillGrabVertical,
|
|
643
690
|
isAlwaysVisible ? styles.pillVisible : styles.pillHidden,
|
|
691
|
+
!isAlwaysVisible &&
|
|
692
|
+
effectiveSide !== 'center' &&
|
|
693
|
+
(isHorizontal
|
|
694
|
+
? effectiveSide === 'start'
|
|
695
|
+
? styles.pillBridgeRight
|
|
696
|
+
: styles.pillBridgeLeft
|
|
697
|
+
: effectiveSide === 'start'
|
|
698
|
+
? styles.pillBridgeDown
|
|
699
|
+
: styles.pillBridgeUp),
|
|
644
700
|
isInteracting && !isDragging && styles.pillHover,
|
|
645
701
|
isDragging && styles.pillActive,
|
|
702
|
+
isDisabled && styles.disabled,
|
|
646
703
|
),
|
|
647
704
|
)}
|
|
705
|
+
{...grabHandlers}
|
|
648
706
|
/>
|
|
649
707
|
)}
|
|
650
708
|
</div>
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
type RefObject,
|
|
26
26
|
} from 'react';
|
|
27
27
|
import {
|
|
28
|
-
|
|
28
|
+
createMotionAnimator,
|
|
29
29
|
type AnimateOptions,
|
|
30
30
|
type Animator,
|
|
31
31
|
type MotionCurve,
|
|
@@ -539,7 +539,7 @@ export class SegmentedSelectionMotion {
|
|
|
539
539
|
private readonly after: Animator;
|
|
540
540
|
|
|
541
541
|
constructor(
|
|
542
|
-
private readonly animator: Animator =
|
|
542
|
+
private readonly animator: Animator = createMotionAnimator(null, {
|
|
543
543
|
pseudoElement: '::before',
|
|
544
544
|
}),
|
|
545
545
|
options: SegmentedSelectionMotionOptions = {},
|
|
@@ -552,7 +552,10 @@ export class SegmentedSelectionMotion {
|
|
|
552
552
|
this.now = options.now ?? defaultNow;
|
|
553
553
|
this.after =
|
|
554
554
|
options.afterAnimator ??
|
|
555
|
-
|
|
555
|
+
createMotionAnimator(null, {
|
|
556
|
+
pseudoElement: '::after',
|
|
557
|
+
initial: {opacity: 0},
|
|
558
|
+
});
|
|
556
559
|
}
|
|
557
560
|
|
|
558
561
|
/** Attaches the radiogroup whose pseudo-elements are the lens (null detaches). */
|
|
@@ -29,7 +29,7 @@ import {TapGestureRecognizer} from '../gestures/TapGestureRecognizer';
|
|
|
29
29
|
import type {PointerKind} from '../gestures/types';
|
|
30
30
|
import {isRtlElement} from '../hooks/isRtlElement';
|
|
31
31
|
import {
|
|
32
|
-
|
|
32
|
+
createMotionAnimator,
|
|
33
33
|
type Animator,
|
|
34
34
|
type MotionCurve,
|
|
35
35
|
type MotionHandle,
|
|
@@ -1186,7 +1186,7 @@ export class SliderTouchController {
|
|
|
1186
1186
|
private animatorFor(element: HTMLElement): Animator {
|
|
1187
1187
|
let animator = this.animators.get(element);
|
|
1188
1188
|
if (animator == null) {
|
|
1189
|
-
animator =
|
|
1189
|
+
animator = createMotionAnimator(element);
|
|
1190
1190
|
this.animators.set(element, animator);
|
|
1191
1191
|
}
|
|
1192
1192
|
return animator;
|
package/src/Spinner/Spinner.tsx
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* SYNC: When modified, update these files to stay in sync:
|
|
12
12
|
* - /packages/core/src/Spinner/Spinner.doc.mjs
|
|
13
|
-
* - /packages/core/src/Spinner/Spinner.test.tsx
|
|
13
|
+
* - /packages/core/src/Spinner/Spinner.test.tsx and __tests__/SpinnerMotion.a11y.chromium.spec.ts (rotor timing)
|
|
14
14
|
* - /packages/core/src/Spinner/index.ts
|
|
15
15
|
* - /apps/storybook/stories/Spinner.stories.tsx
|
|
16
16
|
* - /packages/cli/assets/templates/blocks/components/Spinner/ (showcase blocks)
|
|
@@ -153,60 +153,57 @@ function registerSpinnerVars(): void {
|
|
|
153
153
|
registerSpinnerVars();
|
|
154
154
|
|
|
155
155
|
/**
|
|
156
|
-
* Pin every ring's
|
|
157
|
-
*
|
|
156
|
+
* Pin every ring's rotation to the document timeline's origin instead of its
|
|
157
|
+
* own start time, so spinners mounted seconds apart stay in phase.
|
|
158
158
|
*
|
|
159
|
-
* Setting `startTime` is exact where arithmetic on a clock read is not
|
|
159
|
+
* Setting `startTime` is exact where arithmetic on a clock read is not. A
|
|
160
160
|
* negative `animation-delay` computed at mount is only as good as the gap
|
|
161
|
-
* between
|
|
162
|
-
*
|
|
163
|
-
* literal rotation; the same drift risk applies to the dash offset now).
|
|
161
|
+
* between the clock read and the frame the animation starts in. At 10x CPU
|
|
162
|
+
* throttling that gap measured 116 degrees of drift.
|
|
164
163
|
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
164
|
+
* Rotors are collected and pinned in one frame. `getAnimations()` resolves
|
|
165
|
+
* style, and `startTime` dirties it again. So pinning them one at a time makes
|
|
166
|
+
* each mount re-force what the previous one invalidated: 53 style recalcs for
|
|
167
|
+
* 38 spinners, against 19 batched.
|
|
169
168
|
*/
|
|
170
|
-
const
|
|
169
|
+
const pendingRotors = new Set<HTMLSpanElement>();
|
|
171
170
|
let flushScheduled = false;
|
|
172
171
|
|
|
173
|
-
function
|
|
172
|
+
function pinRotorsToTimelineOrigin(): void {
|
|
174
173
|
flushScheduled = false;
|
|
175
174
|
const animations: Animation[] = [];
|
|
176
|
-
for (const
|
|
177
|
-
|
|
178
|
-
// this ref sits on (#6253), so it needs subtree:true to be found here.
|
|
179
|
-
animations.push(...svg.getAnimations({subtree: true}));
|
|
175
|
+
for (const rotor of pendingRotors) {
|
|
176
|
+
animations.push(...rotor.getAnimations());
|
|
180
177
|
}
|
|
181
|
-
|
|
178
|
+
pendingRotors.clear();
|
|
182
179
|
for (const animation of animations) {
|
|
183
180
|
animation.startTime = 0;
|
|
184
181
|
}
|
|
185
182
|
}
|
|
186
183
|
|
|
187
184
|
/**
|
|
188
|
-
* Ref callback for the
|
|
185
|
+
* Ref callback for the rotor: the one place a mounted ring touches the DOM.
|
|
189
186
|
*
|
|
190
187
|
* It reads nothing back — the geometry is resolved by the cascade, not in JS.
|
|
191
188
|
*/
|
|
192
189
|
function syncRotationPhase(
|
|
193
|
-
|
|
190
|
+
rotor: HTMLSpanElement | null,
|
|
194
191
|
): (() => void) | undefined {
|
|
195
|
-
if (
|
|
192
|
+
if (rotor == null) {
|
|
196
193
|
return undefined;
|
|
197
194
|
}
|
|
198
195
|
// jsdom implements no Web Animations, and this runs in every consumer's
|
|
199
196
|
// component tests.
|
|
200
|
-
if (typeof
|
|
197
|
+
if (typeof rotor.getAnimations !== 'function') {
|
|
201
198
|
return undefined;
|
|
202
199
|
}
|
|
203
|
-
|
|
200
|
+
pendingRotors.add(rotor);
|
|
204
201
|
if (!flushScheduled) {
|
|
205
202
|
flushScheduled = true;
|
|
206
|
-
requestAnimationFrame(
|
|
203
|
+
requestAnimationFrame(pinRotorsToTimelineOrigin);
|
|
207
204
|
}
|
|
208
205
|
return () => {
|
|
209
|
-
|
|
206
|
+
pendingRotors.delete(rotor);
|
|
210
207
|
};
|
|
211
208
|
}
|
|
212
209
|
|
|
@@ -215,19 +212,13 @@ function syncRotationPhase(
|
|
|
215
212
|
// =============================================================================
|
|
216
213
|
|
|
217
214
|
/**
|
|
218
|
-
*
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* angle, which reads as a visible wobble on iOS Safari independent of
|
|
222
|
-
* compositing (#6253) — confirmed by a real-device test against this exact
|
|
223
|
-
* fix, since `willChange`/layer promotion alone (the earlier fix for #3617)
|
|
224
|
-
* only smooths the rotation's motion, not the per-frame stroke rasterization.
|
|
225
|
-
* A static circle with an animated `stroke-dashoffset` never rotates, so
|
|
226
|
-
* WebKit never re-rasterizes the cap at an intermediate angle at all.
|
|
215
|
+
* A `transform` animation runs on the compositor, so the ring keeps turning
|
|
216
|
+
* while a script runs. A `stroke-dashoffset` animation would repaint the arc
|
|
217
|
+
* on the main thread every frame, so it would stop while a script runs.
|
|
227
218
|
*/
|
|
228
|
-
const
|
|
229
|
-
'0%': {
|
|
230
|
-
'100%': {
|
|
219
|
+
const rotation = stylex.keyframes({
|
|
220
|
+
'0%': {transform: 'rotate(0deg)'},
|
|
221
|
+
'100%': {transform: 'rotate(360deg)'},
|
|
231
222
|
});
|
|
232
223
|
|
|
233
224
|
// =============================================================================
|
|
@@ -283,6 +274,8 @@ const styles = stylex.create({
|
|
|
283
274
|
ring: {
|
|
284
275
|
backfaceVisibility: 'hidden',
|
|
285
276
|
display: 'block',
|
|
277
|
+
// The track and the rotor share the one grid cell, stacked and centred.
|
|
278
|
+
gridArea: '1 / 1',
|
|
286
279
|
// The frame is the box, read from the same composed var the span is sized
|
|
287
280
|
// from — not from the size constant, which a theme cannot reach, and not
|
|
288
281
|
// from a percentage, which needs the grid area to be a definite size in
|
|
@@ -300,6 +293,27 @@ const styles = stylex.create({
|
|
|
300
293
|
// which put the ring 1.5-3.3px off across the four sizes.
|
|
301
294
|
overflow: 'visible',
|
|
302
295
|
},
|
|
296
|
+
// The rotor is the frame's size, so it turns about the ring's centre.
|
|
297
|
+
rotor: {
|
|
298
|
+
display: 'block',
|
|
299
|
+
gridArea: '1 / 1',
|
|
300
|
+
width: `var(${BOX_SIZE})`,
|
|
301
|
+
height: `var(${BOX_SIZE})`,
|
|
302
|
+
// Mobile Safari keeps a re-timed animation off the main thread only on an
|
|
303
|
+
// element with its own layer. So the rotor declares one before the start
|
|
304
|
+
// time is set (see `pinRotorsToTimelineOrigin`).
|
|
305
|
+
willChange: 'transform',
|
|
306
|
+
// Reduced motion slows the turn but never stops it: a still ring reads as
|
|
307
|
+
// broken. The status role and its name still say that work is busy.
|
|
308
|
+
animationDuration: {
|
|
309
|
+
// The loop reads the duration before a theme's tempo scales it.
|
|
310
|
+
default: `var(--_astryx-pre-tempo-duration-slow-min, ${durationVars['--duration-slow-min']})`,
|
|
311
|
+
'@media (prefers-reduced-motion: reduce)': '3s',
|
|
312
|
+
},
|
|
313
|
+
animationIterationCount: 'infinite',
|
|
314
|
+
animationName: rotation,
|
|
315
|
+
animationTimingFunction: 'linear',
|
|
316
|
+
},
|
|
303
317
|
circle: {
|
|
304
318
|
fill: 'none',
|
|
305
319
|
// The ring is centred on the box (cx/cy 50%), so the arc's start offset
|
|
@@ -336,18 +350,6 @@ const styles = stylex.create({
|
|
|
336
350
|
// `0` the way the registered length pair guards against, so it needs no
|
|
337
351
|
// registration.
|
|
338
352
|
arc: {
|
|
339
|
-
// Slow the motion dramatically under reduced-motion rather than freezing
|
|
340
|
-
// it (a frozen spinner reads as broken), matching ProgressBar's approach.
|
|
341
|
-
// The role="status" + "Loading" label still convey busy state (obs-6).
|
|
342
|
-
animationDuration: {
|
|
343
|
-
// The loop reads the duration before a theme's tempo scales it
|
|
344
|
-
// (spec:AST-108 FR4).
|
|
345
|
-
default: `var(--_astryx-pre-tempo-duration-slow-min, ${durationVars['--duration-slow-min']})`,
|
|
346
|
-
'@media (prefers-reduced-motion: reduce)': '3s',
|
|
347
|
-
},
|
|
348
|
-
animationIterationCount: 'infinite',
|
|
349
|
-
animationName: dashMotion,
|
|
350
|
-
animationTimingFunction: 'linear',
|
|
351
353
|
stroke: 'var(--spinner-color)',
|
|
352
354
|
transform: 'rotate(-90deg)',
|
|
353
355
|
strokeDasharray: `calc(var(${RESOLVED_DIAMETER}) * ${PI} * var(--spinner-arc-fraction)) calc(var(${RESOLVED_DIAMETER}) * ${PI} * (1 - var(--spinner-arc-fraction)))`,
|
|
@@ -569,10 +571,12 @@ export function Spinner({
|
|
|
569
571
|
height: `var(${BOX_SIZE}, ${frameSize}px)`,
|
|
570
572
|
},
|
|
571
573
|
)}>
|
|
574
|
+
{/* The track is zero-sized by attribute. With no stylesheet, the
|
|
575
|
+
rotor's arc alone fills the box, instead of two discs side by side.
|
|
576
|
+
`ring` sizes both to the box. */}
|
|
572
577
|
<svg
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
height={frameSize}
|
|
578
|
+
width={0}
|
|
579
|
+
height={0}
|
|
576
580
|
aria-hidden="true"
|
|
577
581
|
{...stylex.props(styles.ring)}>
|
|
578
582
|
<circle
|
|
@@ -586,18 +590,28 @@ export function Spinner({
|
|
|
586
590
|
trackOpacityStyles[shade],
|
|
587
591
|
)}
|
|
588
592
|
/>
|
|
589
|
-
<circle
|
|
590
|
-
cx="50%"
|
|
591
|
-
cy="50%"
|
|
592
|
-
r={diameter / 2}
|
|
593
|
-
strokeWidth={border}
|
|
594
|
-
// The size's own dash, for the render with no stylesheet. The rule
|
|
595
|
-
// above composes the same lengths from the resolved diameter, so a
|
|
596
|
-
// themed ring keeps this fraction of arc rather than this length.
|
|
597
|
-
strokeDasharray={`${arcLength} ${circumference - arcLength}`}
|
|
598
|
-
{...stylex.props(styles.circle, styles.arc)}
|
|
599
|
-
/>
|
|
600
593
|
</svg>
|
|
594
|
+
<span
|
|
595
|
+
ref={syncRotationPhase}
|
|
596
|
+
aria-hidden="true"
|
|
597
|
+
{...stylex.props(styles.rotor)}>
|
|
598
|
+
<svg
|
|
599
|
+
width={frameSize}
|
|
600
|
+
height={frameSize}
|
|
601
|
+
{...stylex.props(styles.ring)}>
|
|
602
|
+
<circle
|
|
603
|
+
cx="50%"
|
|
604
|
+
cy="50%"
|
|
605
|
+
r={diameter / 2}
|
|
606
|
+
strokeWidth={border}
|
|
607
|
+
// The size's own dash, for the render with no stylesheet. The rule
|
|
608
|
+
// above composes the same lengths from the resolved diameter, so a
|
|
609
|
+
// themed ring keeps this fraction of arc rather than this length.
|
|
610
|
+
strokeDasharray={`${arcLength} ${circumference - arcLength}`}
|
|
611
|
+
{...stylex.props(styles.circle, styles.arc)}
|
|
612
|
+
/>
|
|
613
|
+
</svg>
|
|
614
|
+
</span>
|
|
601
615
|
</span>
|
|
602
616
|
);
|
|
603
617
|
|