@astryxdesign/core 0.7.7-canary.f9ac85f → 0.7.7
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 +104 -0
- package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts +13 -0
- package/dist/AppShell/useCloseMobileDrawerOnActivate.d.ts.map +1 -0
- package/dist/AppShell/useCloseMobileDrawerOnActivate.js +45 -0
- package/dist/BottomSheet/BottomSheet.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheet.js +79 -9
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +1 -1
- package/dist/BottomSheet/bottomSheetStack.d.ts.map +1 -1
- package/dist/BottomSheet/bottomSheetStack.js +5 -4
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts +2 -1
- package/dist/Breadcrumbs/BreadcrumbItem.d.ts.map +1 -1
- package/dist/Breadcrumbs/BreadcrumbItem.js +7 -3
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +30 -2
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +7 -1
- package/dist/CheckboxList/CheckboxListContext.d.ts +6 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +10 -2
- package/dist/DateInput/DateInput.d.ts.map +1 -1
- package/dist/DateInput/DateInput.js +5 -2
- package/dist/DateInput/MonthScroller.d.ts.map +1 -1
- package/dist/DateInput/MonthScroller.js +13 -27
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +22 -41
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +5 -2
- package/dist/Dialog/Dialog.d.ts.map +1 -1
- package/dist/Dialog/Dialog.js +4 -3
- package/dist/Drawer/Drawer.js +0 -1
- package/dist/Drawer/useDrawerDialogPresence.d.ts.map +1 -1
- package/dist/Drawer/useDrawerDialogPresence.js +5 -1
- package/dist/Field/PanelSearchInput.d.ts +9 -1
- package/dist/Field/PanelSearchInput.d.ts.map +1 -1
- package/dist/Field/PanelSearchInput.js +3 -1
- package/dist/InfoTip/InfoTip.d.ts +2 -2
- package/dist/InfoTip/InfoTip.js +4 -4
- package/dist/Layer/layerAnimations.stylex.d.ts +7 -8
- package/dist/Layer/layerAnimations.stylex.d.ts.map +1 -1
- package/dist/Layer/layerAnimations.stylex.js +25 -20
- package/dist/Layer/usePressPin.d.ts +3 -5
- package/dist/Layer/usePressPin.d.ts.map +1 -1
- package/dist/Layer/usePressPin.js +4 -6
- package/dist/MobileNav/MobileNav.js +0 -1
- package/dist/MultiSelector/hooks.d.ts.map +1 -1
- package/dist/MultiSelector/hooks.js +2 -4
- package/dist/Outline/Outline.d.ts +2 -4
- package/dist/Outline/Outline.d.ts.map +1 -1
- package/dist/Outline/Outline.js +4 -52
- package/dist/Outline/useOutlineFromMarkdown.d.ts.map +1 -1
- package/dist/Outline/useOutlineFromMarkdown.js +1 -0
- package/dist/Outline/useScrollSpy.js +3 -3
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearch.js +134 -179
- package/dist/RadioList/RadioList.d.ts.map +1 -1
- package/dist/RadioList/RadioList.js +5 -0
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +118 -260
- package/dist/Resizable/resizeGesture.d.ts +91 -0
- package/dist/Resizable/resizeGesture.d.ts.map +1 -0
- package/dist/Resizable/resizeGesture.js +251 -0
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +3 -9
- package/dist/Token/Token.d.ts +2 -2
- package/dist/Token/Token.d.ts.map +1 -1
- package/dist/Token/Token.js +12 -4
- package/dist/Token/TokenNamingContext.d.ts +14 -0
- package/dist/Token/TokenNamingContext.d.ts.map +1 -0
- package/dist/Token/TokenNamingContext.js +23 -0
- package/dist/Tokenizer/Tokenizer.d.ts +46 -59
- package/dist/Tokenizer/Tokenizer.d.ts.map +1 -1
- package/dist/Tokenizer/Tokenizer.js +35 -58
- package/dist/Tokenizer/TokenizerBottomSheet.d.ts +25 -28
- package/dist/Tokenizer/TokenizerBottomSheet.d.ts.map +1 -1
- package/dist/Tokenizer/TokenizerBottomSheet.js +157 -54
- package/dist/Tokenizer/TokenizerItemRemovableContext.d.ts.map +1 -1
- package/dist/Tokenizer/TokenizerItemRemovableContext.js +5 -3
- package/dist/Tooltip/Tooltip.d.ts +25 -30
- package/dist/Tooltip/Tooltip.d.ts.map +1 -1
- package/dist/Tooltip/Tooltip.js +24 -5
- package/dist/Tooltip/index.d.ts +1 -1
- package/dist/Tooltip/index.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.d.ts +28 -29
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +20 -8
- package/dist/TopNav/TopNavItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavItem.js +4 -7
- package/dist/TopNav/TopNavMegaMenuItem.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenuItem.js +4 -7
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +4 -7
- package/dist/astryx.css +5 -5
- package/dist/gestures/GestureHost.d.ts +27 -1
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +90 -8
- package/dist/hooks/useHighlightedOptionScroll.d.ts +11 -1
- package/dist/hooks/useHighlightedOptionScroll.d.ts.map +1 -1
- package/dist/hooks/useHighlightedOptionScroll.js +103 -5
- package/dist/hooks/useMenuPress.d.ts.map +1 -1
- package/dist/hooks/useMenuPress.js +4 -2
- package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearch.generated.js +1 -0
- package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.js +7 -1
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +1 -0
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +1 -0
- package/dist/internal/SheetConfirmation.d.ts +11 -0
- package/dist/internal/SheetConfirmation.d.ts.map +1 -0
- package/dist/internal/SheetConfirmation.js +92 -0
- package/dist/internal/sheetDraft.d.ts +35 -0
- package/dist/internal/sheetDraft.d.ts.map +1 -0
- package/dist/internal/sheetDraft.js +65 -0
- package/dist/internal/touch-sheet.d.ts +21 -0
- package/dist/internal/touch-sheet.d.ts.map +1 -0
- package/dist/internal/touch-sheet.js +19 -0
- package/dist/internal/useDismissSearchOnTouchDrag.d.ts +24 -0
- package/dist/internal/useDismissSearchOnTouchDrag.d.ts.map +1 -0
- package/dist/internal/useDismissSearchOnTouchDrag.js +56 -0
- package/dist/motion/reducedMotion.d.ts +24 -0
- package/dist/motion/reducedMotion.d.ts.map +1 -1
- package/dist/motion/reducedMotion.js +78 -1
- package/dist/utils/clickGuard.d.ts +8 -1
- package/dist/utils/clickGuard.d.ts.map +1 -1
- package/dist/utils/clickGuard.js +24 -1
- package/locales/en.json +4 -0
- package/locales/pseudo.json +3 -0
- package/package.json +9 -9
- package/src/AppShell/useCloseMobileDrawerOnActivate.ts +52 -0
- package/src/BottomSheet/BottomSheet.tsx +95 -10
- package/src/BottomSheet/BottomSheetPanel.tsx +7 -2
- package/src/BottomSheet/bottomSheetStack.ts +8 -4
- package/src/Breadcrumbs/BreadcrumbItem.tsx +13 -3
- package/src/Button/Button.tsx +37 -1
- package/src/CheckboxList/CheckboxList.doc.mjs +4 -2
- package/src/CheckboxList/CheckboxList.tsx +6 -0
- package/src/CheckboxList/CheckboxListContext.tsx +6 -0
- package/src/CheckboxList/CheckboxListItem.tsx +11 -2
- package/src/DateInput/DateInput.tsx +8 -2
- package/src/DateInput/MonthScroller.tsx +18 -22
- package/src/DateInput/Wheel.tsx +30 -32
- package/src/DateTimeInput/DateTimeInput.tsx +8 -2
- package/src/Dialog/Dialog.tsx +19 -4
- package/src/Drawer/Drawer.tsx +1 -1
- package/src/Drawer/useDrawerDialogPresence.ts +5 -1
- package/src/Field/PanelSearchInput.tsx +11 -0
- package/src/InfoTip/InfoTip.doc.mjs +3 -3
- package/src/InfoTip/InfoTip.tsx +5 -5
- package/src/Layer/layerAnimations.stylex.ts +24 -16
- package/src/Layer/usePressPin.ts +5 -11
- package/src/MobileNav/MobileNav.tsx +1 -1
- package/src/MultiSelector/hooks.ts +2 -4
- package/src/Outline/Outline.doc.mjs +177 -51
- package/src/Outline/Outline.tsx +5 -57
- package/src/Outline/useOutlineFromMarkdown.doc.mjs +157 -0
- package/src/Outline/useOutlineFromMarkdown.ts +1 -0
- package/src/Outline/useScrollSpy.ts +3 -3
- package/src/PowerSearch/PowerSearch.doc.mjs +3 -3
- package/src/PowerSearch/PowerSearch.tsx +196 -183
- package/src/RadioList/RadioList.doc.mjs +4 -2
- package/src/RadioList/RadioList.tsx +4 -0
- package/src/Resizable/ResizeHandle.tsx +133 -328
- package/src/Resizable/resizeGesture.ts +368 -0
- package/src/SideNav/SideNavItem.tsx +3 -7
- package/src/Token/Token.tsx +13 -4
- package/src/Token/TokenNamingContext.ts +28 -0
- package/src/Tokenizer/Tokenizer.doc.mjs +16 -0
- package/src/Tokenizer/Tokenizer.tsx +81 -86
- package/src/Tokenizer/TokenizerBottomSheet.tsx +219 -93
- package/src/Tokenizer/TokenizerItemRemovableContext.ts +5 -3
- package/src/Tooltip/Tooltip.doc.mjs +123 -54
- package/src/Tooltip/Tooltip.tsx +36 -39
- package/src/Tooltip/index.ts +0 -1
- package/src/Tooltip/useTooltip.doc.mjs +147 -33
- package/src/Tooltip/useTooltip.tsx +39 -32
- package/src/TopNav/TopNavItem.tsx +6 -5
- package/src/TopNav/TopNavMegaMenuItem.tsx +6 -5
- package/src/TopNav/TopNavMenu.tsx +6 -5
- package/src/gestures/GestureHost.ts +105 -10
- package/src/hooks/useHighlightedOptionScroll.ts +113 -9
- package/src/hooks/useMenuPress.ts +8 -2
- package/src/i18n/generated-english/PowerSearch/PowerSearch.generated.ts +1 -0
- package/src/i18n/generated-english/Tokenizer/TokenizerBottomSheet.generated.ts +7 -1
- package/src/i18n/generated-locales/en.generated.ts +1 -0
- package/src/i18n/generated-locales/pseudo.generated.ts +1 -0
- package/src/internal/SheetConfirmation.tsx +101 -0
- package/src/internal/sheetDraft.ts +80 -0
- package/src/internal/touch-sheet.ts +21 -0
- package/src/internal/useDismissSearchOnTouchDrag.ts +69 -0
- package/src/motion/reducedMotion.ts +79 -1
- package/src/utils/clickGuard.ts +37 -1
- package/dist/internal/tokenizer-presentation.d.ts +0 -21
- package/dist/internal/tokenizer-presentation.d.ts.map +0 -1
- package/dist/internal/tokenizer-presentation.js +0 -21
- package/src/internal/tokenizer-presentation.ts +0 -25
package/src/Button/Button.tsx
CHANGED
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
+
isValidElement,
|
|
22
23
|
use,
|
|
23
24
|
useEffect,
|
|
24
25
|
useRef,
|
|
@@ -44,6 +45,7 @@ import {
|
|
|
44
45
|
focusVars,
|
|
45
46
|
} from '../theme/tokens.stylex';
|
|
46
47
|
import {Spinner} from '../Spinner/Spinner';
|
|
48
|
+
import {Icon} from '../Icon/Icon';
|
|
47
49
|
import {VisuallyHidden} from '../VisuallyHidden/VisuallyHidden';
|
|
48
50
|
import {IconDefaultSizeProvider} from '../Icon/IconDefaultSizeContext';
|
|
49
51
|
import {iconBoxSizeStyles, type IconSize} from '../Icon/IconSize.stylex';
|
|
@@ -559,6 +561,21 @@ const groupStyles = stylex.create({
|
|
|
559
561
|
},
|
|
560
562
|
});
|
|
561
563
|
|
|
564
|
+
/**
|
|
565
|
+
* Whether `node` adds nothing to a content name: nothing renders, or an Icon
|
|
566
|
+
* with no label. Any other node can add text.
|
|
567
|
+
*/
|
|
568
|
+
function addsNoName(node: ReactNode): boolean {
|
|
569
|
+
if (!isRenderable(node)) {
|
|
570
|
+
return true;
|
|
571
|
+
}
|
|
572
|
+
if (!isValidElement<{label?: unknown}>(node) || node.type !== Icon) {
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
const {label} = node.props;
|
|
576
|
+
return typeof label !== 'string' || label.trim() === '';
|
|
577
|
+
}
|
|
578
|
+
|
|
562
579
|
/**
|
|
563
580
|
* A versatile button component with multiple variants.
|
|
564
581
|
*
|
|
@@ -836,10 +853,29 @@ export function Button({
|
|
|
836
853
|
(children != null && children !== label);
|
|
837
854
|
const ariaLabelProp = needsAriaLabel ? {'aria-label': label} : null;
|
|
838
855
|
|
|
856
|
+
// The Button's accessible name when props decide it: the derived
|
|
857
|
+
// aria-label, else a caller aria-label, else the label text alone. It is
|
|
858
|
+
// not known here when a caller aria-labelledby names the Button from other
|
|
859
|
+
// elements, or when `icon` or `endContent` can add text to its content
|
|
860
|
+
// name (a count, for example). A decorative Icon in either slot adds none.
|
|
861
|
+
const accessibleName =
|
|
862
|
+
props['aria-labelledby'] != null
|
|
863
|
+
? undefined
|
|
864
|
+
: needsAriaLabel
|
|
865
|
+
? label
|
|
866
|
+
: (props['aria-label'] ??
|
|
867
|
+
(addsNoName(icon) && addsNoName(endContent) ? label : undefined));
|
|
868
|
+
// A tooltip that repeats the name adds nothing to the description, so a
|
|
869
|
+
// screen reader does not read the same words twice.
|
|
870
|
+
const isTooltipName =
|
|
871
|
+
tooltip != null &&
|
|
872
|
+
accessibleName != null &&
|
|
873
|
+
tooltip.trim() === accessibleName.trim();
|
|
874
|
+
|
|
839
875
|
// When a tooltip is attached via the hook, point aria-describedby at the
|
|
840
876
|
// tooltip content (composing with any consumer-provided value).
|
|
841
877
|
const describedByProp =
|
|
842
|
-
tooltip != null
|
|
878
|
+
tooltip != null && !isTooltipName
|
|
843
879
|
? {
|
|
844
880
|
'aria-describedby':
|
|
845
881
|
[props['aria-describedby'], tooltipHook.describedBy]
|
|
@@ -178,12 +178,14 @@ export const docs = {
|
|
|
178
178
|
{
|
|
179
179
|
name: 'containerClassName',
|
|
180
180
|
type: 'string',
|
|
181
|
-
description:
|
|
181
|
+
description:
|
|
182
|
+
'Class name for the field container. Same targeting as `containerXstyle`.',
|
|
182
183
|
},
|
|
183
184
|
{
|
|
184
185
|
name: 'containerStyle',
|
|
185
186
|
type: 'CSSProperties',
|
|
186
|
-
description:
|
|
187
|
+
description:
|
|
188
|
+
'Inline styles for the field container. Same targeting as `containerXstyle`.',
|
|
187
189
|
},
|
|
188
190
|
{
|
|
189
191
|
name: 'controlXstyle',
|
|
@@ -246,6 +246,7 @@ export function CheckboxList({
|
|
|
246
246
|
? disabledMessageTooltip.describedBy
|
|
247
247
|
: undefined,
|
|
248
248
|
isReadOnly,
|
|
249
|
+
isInvalid: status?.type === 'error',
|
|
249
250
|
loadingValues,
|
|
250
251
|
}),
|
|
251
252
|
[
|
|
@@ -256,6 +257,7 @@ export function CheckboxList({
|
|
|
256
257
|
showsDisabledMessage,
|
|
257
258
|
disabledMessageTooltip.describedBy,
|
|
258
259
|
isReadOnly,
|
|
260
|
+
status?.type,
|
|
259
261
|
loadingValues,
|
|
260
262
|
],
|
|
261
263
|
);
|
|
@@ -298,6 +300,10 @@ export function CheckboxList({
|
|
|
298
300
|
controlStyle,
|
|
299
301
|
)}
|
|
300
302
|
role="group"
|
|
303
|
+
// Screen readers announce the whole group as unavailable. Its dimmed
|
|
304
|
+
// label then labels a disabled control. An enabled list keeps a
|
|
305
|
+
// caller's own value.
|
|
306
|
+
aria-disabled={isDisabled || rest['aria-disabled']}
|
|
301
307
|
// A caller aria-labelledby names the group together with its label.
|
|
302
308
|
aria-labelledby={joinAriaIDs(labelID, ariaLabelledByProp)}
|
|
303
309
|
aria-describedby={
|
|
@@ -37,6 +37,12 @@ export interface CheckboxListContextValue {
|
|
|
37
37
|
*/
|
|
38
38
|
disabledMessageID?: string;
|
|
39
39
|
isReadOnly: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* True when the list has an error `status`. Each checkbox is then
|
|
42
|
+
* `aria-invalid`. Some screen readers skip a group's invalid state when a
|
|
43
|
+
* checkbox takes focus.
|
|
44
|
+
*/
|
|
45
|
+
isInvalid?: boolean;
|
|
40
46
|
/**
|
|
41
47
|
* Values of the items whose `changeAction` is pending. Each matching item
|
|
42
48
|
* renders an in-checkbox spinner and blocks re-toggling; the list is empty
|
|
@@ -24,6 +24,7 @@ import {colorVars} from '../theme/tokens.stylex';
|
|
|
24
24
|
import type {BaseProps} from '../BaseProps';
|
|
25
25
|
import {CheckboxInput} from '../CheckboxInput/CheckboxInput';
|
|
26
26
|
import type {CheckboxInputProps} from '../CheckboxInput/CheckboxInput';
|
|
27
|
+
import type {InputStatus} from '../Field/types';
|
|
27
28
|
import {ListItem} from '../List/ListItem';
|
|
28
29
|
import {ListContext} from '../List/ListContext';
|
|
29
30
|
import {CheckboxListContext} from './CheckboxListContext';
|
|
@@ -149,6 +150,8 @@ export interface CheckboxListItemProps extends BaseProps<HTMLLIElement> {
|
|
|
149
150
|
// Component
|
|
150
151
|
// =============================================================================
|
|
151
152
|
|
|
153
|
+
const ERROR_STATUS: InputStatus = {type: 'error'};
|
|
154
|
+
|
|
152
155
|
/**
|
|
153
156
|
* The row's visible description is the checkbox's accessible description. Item
|
|
154
157
|
* renders that element and owns its id, and publishes the id through
|
|
@@ -167,11 +170,17 @@ function DescribedCheckboxInput(
|
|
|
167
170
|
props: Omit<CheckboxInputProps, 'aria-describedby'>,
|
|
168
171
|
) {
|
|
169
172
|
const describedBy = use(ItemDescriptionContext);
|
|
170
|
-
const
|
|
173
|
+
const listContext = use(CheckboxListContext);
|
|
171
174
|
return (
|
|
172
175
|
<CheckboxInput
|
|
173
176
|
{...props}
|
|
174
|
-
aria-describedby={joinAriaIDs(
|
|
177
|
+
aria-describedby={joinAriaIDs(
|
|
178
|
+
describedBy ?? undefined,
|
|
179
|
+
listContext?.disabledMessageID,
|
|
180
|
+
)}
|
|
181
|
+
// The status has no message, so CheckboxInput renders nothing. The list
|
|
182
|
+
// renders the message.
|
|
183
|
+
status={listContext?.isInvalid ? ERROR_STATUS : undefined}
|
|
175
184
|
/>
|
|
176
185
|
);
|
|
177
186
|
}
|
|
@@ -884,12 +884,18 @@ function PointerDateField({
|
|
|
884
884
|
if (!isEffectivelyDisabled) {
|
|
885
885
|
popover.show({skipAutoFocus: true});
|
|
886
886
|
}
|
|
887
|
-
} else if (
|
|
887
|
+
} else if (
|
|
888
|
+
e.key === 'Enter' &&
|
|
889
|
+
(pendingInput !== null || isEffectivelyDisabled)
|
|
890
|
+
) {
|
|
891
|
+
// Typed text holds Enter back, or a form would submit the value from
|
|
892
|
+
// before the commit. A disabled or busy field must not submit its
|
|
893
|
+
// form either. Otherwise Enter keeps its default, as in any text field.
|
|
888
894
|
e.preventDefault();
|
|
889
895
|
commitPendingInput();
|
|
890
896
|
}
|
|
891
897
|
},
|
|
892
|
-
[popover, commitPendingInput, isEffectivelyDisabled],
|
|
898
|
+
[popover, commitPendingInput, isEffectivelyDisabled, pendingInput],
|
|
893
899
|
);
|
|
894
900
|
|
|
895
901
|
const inputWrapper = (
|
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
useRef,
|
|
39
39
|
useState,
|
|
40
40
|
} from 'react';
|
|
41
|
+
import {flushSync} from 'react-dom';
|
|
41
42
|
import * as stylex from '@stylexjs/stylex';
|
|
42
43
|
import {useCalendarDays} from '../Calendar';
|
|
43
44
|
import {useDirection, useLocale} from '../i18n';
|
|
@@ -62,7 +63,11 @@ import {
|
|
|
62
63
|
plainDateToISO,
|
|
63
64
|
DATE_FORMAT_WITH_WEEKDAY,
|
|
64
65
|
} from '../utils';
|
|
65
|
-
import {
|
|
66
|
+
import {
|
|
67
|
+
playReducedMotionDip,
|
|
68
|
+
reducedMotionFade,
|
|
69
|
+
type ReducedMotionDip,
|
|
70
|
+
} from '../motion/reducedMotion';
|
|
66
71
|
import {dateInputTouchSizes, dateInputTouchGeometry} from './tokens.stylex';
|
|
67
72
|
import {useOwnScrollGesture} from './useOwnScrollGesture';
|
|
68
73
|
import {useScrollSettle} from './useScrollSettle';
|
|
@@ -434,17 +439,12 @@ export function MonthScroller({
|
|
|
434
439
|
|
|
435
440
|
// The Reduced Motion dip of a step on a touch screen, owned here so a
|
|
436
441
|
// second step or the unmount clears it.
|
|
437
|
-
const dipRef = useRef<
|
|
442
|
+
const dipRef = useRef<ReducedMotionDip | null>(null);
|
|
438
443
|
const stopDip = useCallback(() => {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
window.clearTimeout(dip.timer);
|
|
442
|
-
dip.animation.cancel();
|
|
443
|
-
dipRef.current = null;
|
|
444
|
-
}
|
|
444
|
+
dipRef.current?.cancel();
|
|
445
|
+
dipRef.current = null;
|
|
445
446
|
}, []);
|
|
446
|
-
// Synchronizes with the dip's
|
|
447
|
-
// scroller.
|
|
447
|
+
// Synchronizes with the dip's animations: they do not outlive the scroller.
|
|
448
448
|
useEffect(() => stopDip, [stopDip]);
|
|
449
449
|
|
|
450
450
|
const scrollToMonth = useCallback(
|
|
@@ -505,18 +505,14 @@ export function MonthScroller({
|
|
|
505
505
|
land();
|
|
506
506
|
return;
|
|
507
507
|
}
|
|
508
|
-
// A
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
)
|
|
515
|
-
|
|
516
|
-
dipRef.current = null;
|
|
517
|
-
land();
|
|
518
|
-
}, fade.duration / 2),
|
|
519
|
-
};
|
|
508
|
+
// A newer step replaces this one: only its scroll lands.
|
|
509
|
+
dipRef.current = playReducedMotionDip(scroller, fade, () => {
|
|
510
|
+
dipRef.current = null;
|
|
511
|
+
// The fade-in starts as soon as this returns. So render now: the
|
|
512
|
+
// scroll lands in the layout effect, before the fade-in.
|
|
513
|
+
// eslint-disable-next-line @eslint-react/dom-no-flush-sync -- the month must change before the dip's fade-in starts
|
|
514
|
+
flushSync(land);
|
|
515
|
+
});
|
|
520
516
|
return;
|
|
521
517
|
}
|
|
522
518
|
steeredRowRef.current = row;
|
package/src/DateInput/Wheel.tsx
CHANGED
|
@@ -49,7 +49,11 @@ import {
|
|
|
49
49
|
durationVars,
|
|
50
50
|
} from '../theme/tokens.stylex';
|
|
51
51
|
import {focusOutlineStyles} from '../utils';
|
|
52
|
-
import {
|
|
52
|
+
import {
|
|
53
|
+
playReducedMotionDip,
|
|
54
|
+
reducedMotionFade,
|
|
55
|
+
type ReducedMotionDip,
|
|
56
|
+
} from '../motion/reducedMotion';
|
|
53
57
|
import {dateInputTouchSizes, dateInputTouchGeometry} from './tokens.stylex';
|
|
54
58
|
import {useOwnScrollGesture} from './useOwnScrollGesture';
|
|
55
59
|
import {usePointerDragScroll} from './usePointerDragScroll';
|
|
@@ -331,19 +335,19 @@ export function Wheel({
|
|
|
331
335
|
// once on a fine one.
|
|
332
336
|
const lastPointerRef = useRef<string | null>(null);
|
|
333
337
|
|
|
334
|
-
// A reduced-motion dip whose rows have not landed yet:
|
|
335
|
-
//
|
|
336
|
-
//
|
|
337
|
-
const pendingFadeRef = useRef<{
|
|
338
|
+
// A reduced-motion dip whose rows have not landed yet: the row they land
|
|
339
|
+
// on. While one is pending the rows stay put for it, so they never move at
|
|
340
|
+
// full opacity, and a further step retargets it.
|
|
341
|
+
const pendingFadeRef = useRef<{top: number} | null>(null);
|
|
338
342
|
// The running dip, so a step during its fade-in starts the next one where
|
|
339
343
|
// the column is.
|
|
340
|
-
const dipRef = useRef<
|
|
344
|
+
const dipRef = useRef<ReducedMotionDip | null>(null);
|
|
345
|
+
// Synchronizes with the dip's animations: an unmounted wheel lands nothing.
|
|
341
346
|
useEffect(
|
|
342
347
|
() => () => {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
}
|
|
348
|
+
pendingFadeRef.current = null;
|
|
349
|
+
dipRef.current?.cancel();
|
|
350
|
+
dipRef.current = null;
|
|
347
351
|
},
|
|
348
352
|
[],
|
|
349
353
|
);
|
|
@@ -368,19 +372,15 @@ export function Wheel({
|
|
|
368
372
|
? reducedMotionFade(scroller, {touchRelease})
|
|
369
373
|
: null;
|
|
370
374
|
const pending = pendingFadeRef.current;
|
|
375
|
+
// A change that lands now replaces a pending landing. Its dip goes on
|
|
376
|
+
// and fades back in.
|
|
371
377
|
if (fadeTiming == null) {
|
|
372
|
-
|
|
373
|
-
window.clearTimeout(pending.timer);
|
|
374
|
-
pendingFadeRef.current = null;
|
|
375
|
-
}
|
|
378
|
+
pendingFadeRef.current = null;
|
|
376
379
|
scroller.scrollTo({top, behavior});
|
|
377
380
|
return;
|
|
378
381
|
}
|
|
379
382
|
if (fadeTiming.duration === 0 || typeof scroller.animate !== 'function') {
|
|
380
|
-
|
|
381
|
-
window.clearTimeout(pending.timer);
|
|
382
|
-
pendingFadeRef.current = null;
|
|
383
|
-
}
|
|
383
|
+
pendingFadeRef.current = null;
|
|
384
384
|
scroller.scrollTo({top, behavior: 'auto'});
|
|
385
385
|
return;
|
|
386
386
|
}
|
|
@@ -389,26 +389,24 @@ export function Wheel({
|
|
|
389
389
|
pending.top = top;
|
|
390
390
|
return;
|
|
391
391
|
}
|
|
392
|
-
const {duration, easing} = fadeTiming;
|
|
393
392
|
// A step during the last dip's fade-in starts the new dip at the
|
|
394
393
|
// column's current opacity, not at full opacity. A leaving element fades
|
|
395
394
|
// out from where it is.
|
|
396
395
|
const shown = Number.parseFloat(getComputedStyle(scroller).opacity);
|
|
397
396
|
dipRef.current?.cancel();
|
|
398
|
-
|
|
399
|
-
[
|
|
400
|
-
{opacity: Number.isFinite(shown) ? shown : 1, easing},
|
|
401
|
-
{opacity: 0, easing},
|
|
402
|
-
{opacity: 1},
|
|
403
|
-
],
|
|
404
|
-
{duration, easing: 'linear'},
|
|
405
|
-
);
|
|
406
|
-
const landing = {top, timer: 0};
|
|
407
|
-
landing.timer = window.setTimeout(() => {
|
|
408
|
-
pendingFadeRef.current = null;
|
|
409
|
-
scroller.scrollTo({top: landing.top, behavior: 'auto'});
|
|
410
|
-
}, duration / 2);
|
|
397
|
+
const landing = {top};
|
|
411
398
|
pendingFadeRef.current = landing;
|
|
399
|
+
dipRef.current = playReducedMotionDip(
|
|
400
|
+
scroller,
|
|
401
|
+
fadeTiming,
|
|
402
|
+
() => {
|
|
403
|
+
if (pendingFadeRef.current === landing) {
|
|
404
|
+
pendingFadeRef.current = null;
|
|
405
|
+
scroller.scrollTo({top: landing.top, behavior: 'auto'});
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
Number.isFinite(shown) ? shown : 1,
|
|
409
|
+
);
|
|
412
410
|
},
|
|
413
411
|
[itemBlockSize],
|
|
414
412
|
);
|
|
@@ -1143,12 +1143,18 @@ function PointerDateTimeField({
|
|
|
1143
1143
|
if (!isEffectivelyDisabled) {
|
|
1144
1144
|
popover.show({skipAutoFocus: true});
|
|
1145
1145
|
}
|
|
1146
|
-
} else if (
|
|
1146
|
+
} else if (
|
|
1147
|
+
e.key === 'Enter' &&
|
|
1148
|
+
(datePendingInput !== null || isEffectivelyDisabled)
|
|
1149
|
+
) {
|
|
1150
|
+
// Typed text holds Enter back, or a form would submit the value from
|
|
1151
|
+
// before the commit. A disabled or busy field must not submit its
|
|
1152
|
+
// form either. Otherwise Enter keeps its default, as in any text field.
|
|
1147
1153
|
e.preventDefault();
|
|
1148
1154
|
commitDatePendingInput();
|
|
1149
1155
|
}
|
|
1150
1156
|
},
|
|
1151
|
-
[popover, commitDatePendingInput, isEffectivelyDisabled],
|
|
1157
|
+
[popover, commitDatePendingInput, isEffectivelyDisabled, datePendingInput],
|
|
1152
1158
|
);
|
|
1153
1159
|
|
|
1154
1160
|
// --- Time-option popover (#2727) ---
|
package/src/Dialog/Dialog.tsx
CHANGED
|
@@ -189,8 +189,22 @@ const styles = stylex.create({
|
|
|
189
189
|
position: 'fixed',
|
|
190
190
|
margin: 'auto',
|
|
191
191
|
padding: 0,
|
|
192
|
-
|
|
193
|
-
|
|
192
|
+
// The backdrop does not blur the page. Forced colors override the
|
|
193
|
+
// backdrop's colour, so a 1px border keeps the modal separate from the
|
|
194
|
+
// page. It is a border, not an outline: the focus ring
|
|
195
|
+
// (focusOutlineProps) owns the outline longhands on this element.
|
|
196
|
+
borderWidth: {
|
|
197
|
+
default: 0,
|
|
198
|
+
'@media (forced-colors: active)': 1,
|
|
199
|
+
},
|
|
200
|
+
borderStyle: {
|
|
201
|
+
default: 'none',
|
|
202
|
+
'@media (forced-colors: active)': 'solid',
|
|
203
|
+
},
|
|
204
|
+
borderColor: {
|
|
205
|
+
default: null,
|
|
206
|
+
'@media (forced-colors: active)': 'CanvasText',
|
|
207
|
+
},
|
|
194
208
|
backgroundColor: colorVars['--color-background-surface'],
|
|
195
209
|
'--_dialog-radius': radiusVars['--radius-container'],
|
|
196
210
|
borderRadius: 'var(--_dialog-radius)',
|
|
@@ -236,11 +250,10 @@ const styles = stylex.create({
|
|
|
236
250
|
openSettled: {
|
|
237
251
|
animationName: 'none',
|
|
238
252
|
},
|
|
239
|
-
//
|
|
253
|
+
// No backdrop-filter: a full-viewport blur is costly to repaint.
|
|
240
254
|
backdrop: {
|
|
241
255
|
'::backdrop': {
|
|
242
256
|
backgroundColor: colorVars['--color-overlay'],
|
|
243
|
-
backdropFilter: 'blur(2px)',
|
|
244
257
|
},
|
|
245
258
|
},
|
|
246
259
|
fullscreen: {
|
|
@@ -249,6 +262,8 @@ const styles = stylex.create({
|
|
|
249
262
|
maxWidth: '100dvw',
|
|
250
263
|
maxHeight: '100dvh',
|
|
251
264
|
borderRadius: 0,
|
|
265
|
+
// Nothing of the page shows around a full-screen dialog: no edge.
|
|
266
|
+
borderWidth: 0,
|
|
252
267
|
margin: 0,
|
|
253
268
|
inset: 0,
|
|
254
269
|
},
|
package/src/Drawer/Drawer.tsx
CHANGED
|
@@ -222,8 +222,8 @@ const styles = stylex.create({
|
|
|
222
222
|
// Scrim via the browser's ::backdrop pseudo-element (top layer).
|
|
223
223
|
scrim: {
|
|
224
224
|
'::backdrop': {
|
|
225
|
+
// No backdrop-filter: a full-viewport blur is costly to repaint.
|
|
225
226
|
backgroundColor: colorVars['--color-overlay'],
|
|
226
|
-
backdropFilter: 'blur(2px)',
|
|
227
227
|
opacity: 0,
|
|
228
228
|
transitionProperty: 'opacity',
|
|
229
229
|
transitionDuration: durationVars['--duration-medium'],
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
} from 'react';
|
|
34
34
|
import {flushSync} from 'react-dom';
|
|
35
35
|
import {notifyTopLayerEntry} from '../Layer/modalOutlet';
|
|
36
|
+
import {runPopoverOperation} from '../Layer/popoverOperations';
|
|
36
37
|
|
|
37
38
|
/** Slack past the computed transition before the backstop gives up waiting. */
|
|
38
39
|
const EXIT_BACKSTOP_BUFFER_MS = 50;
|
|
@@ -71,7 +72,10 @@ function showDrawerHost(dialog: HTMLDialogElement, isModal: boolean): void {
|
|
|
71
72
|
if (isModal) {
|
|
72
73
|
dialog.showModal();
|
|
73
74
|
} else if (typeof dialog.showPopover === 'function') {
|
|
74
|
-
dialog.
|
|
75
|
+
// The dialog's show focuses its first control. A tooltip on that control
|
|
76
|
+
// then asks to show. The browser refuses a show inside another show.
|
|
77
|
+
// Counting this show makes the tooltip wait until it ends.
|
|
78
|
+
runPopoverOperation(dialog.ownerDocument, () => dialog.showPopover());
|
|
75
79
|
// The drawer is now the newest top-layer entry: global hosts shown before
|
|
76
80
|
// it (the toast viewport) re-enter above it, as they do above a modal.
|
|
77
81
|
notifyTopLayerEntry();
|
|
@@ -58,6 +58,7 @@ import {
|
|
|
58
58
|
} from '../theme/tokens.stylex';
|
|
59
59
|
import type {BaseProps} from '../BaseProps';
|
|
60
60
|
import {textEntryTraitAttributes} from './textEntryTraits';
|
|
61
|
+
import {inputWrapperStyles} from './inputStyles.stylex';
|
|
61
62
|
|
|
62
63
|
const styles = stylex.create({
|
|
63
64
|
// Gutter only. Matches the option list's own padding so the field below
|
|
@@ -185,6 +186,14 @@ export interface PanelSearchInputProps extends Omit<
|
|
|
185
186
|
* the field to the same height as its rows.
|
|
186
187
|
*/
|
|
187
188
|
fieldXstyle?: StyleXStyles;
|
|
189
|
+
/**
|
|
190
|
+
* Draw the field as the shared input box — the input border, accent while
|
|
191
|
+
* focused, surface and radius — instead of the borderless panel row. A
|
|
192
|
+
* touch sheet is a full-width surface, not a bordered panel, so its field
|
|
193
|
+
* reads as a form input.
|
|
194
|
+
* @default false
|
|
195
|
+
*/
|
|
196
|
+
hasInputBox?: boolean;
|
|
188
197
|
}
|
|
189
198
|
|
|
190
199
|
/**
|
|
@@ -208,6 +217,7 @@ export function PanelSearchInput({
|
|
|
208
217
|
onBlur,
|
|
209
218
|
onContainerKeyDown,
|
|
210
219
|
fieldXstyle,
|
|
220
|
+
hasInputBox = false,
|
|
211
221
|
xstyle,
|
|
212
222
|
className,
|
|
213
223
|
style,
|
|
@@ -268,6 +278,7 @@ export function PanelSearchInput({
|
|
|
268
278
|
{...stylex.props(
|
|
269
279
|
styles.field,
|
|
270
280
|
isKeyboardFocus && styles.fieldKeyboardFocus,
|
|
281
|
+
hasInputBox && inputWrapperStyles.base,
|
|
271
282
|
fieldXstyle,
|
|
272
283
|
)}>
|
|
273
284
|
<Icon icon="search" size="sm" color="secondary" xstyle={styles.icon} />
|
|
@@ -66,7 +66,7 @@ export const docs = {
|
|
|
66
66
|
{
|
|
67
67
|
guidance: true,
|
|
68
68
|
description:
|
|
69
|
-
'Rely on InfoTip rather than a hand-rolled info button on touch: it
|
|
69
|
+
'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
guidance: false,
|
|
@@ -131,7 +131,7 @@ export const docsZh = {
|
|
|
131
131
|
{
|
|
132
132
|
guidance: true,
|
|
133
133
|
description:
|
|
134
|
-
'Rely on InfoTip rather than a hand-rolled info button on touch: it
|
|
134
|
+
'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
|
|
135
135
|
},
|
|
136
136
|
{
|
|
137
137
|
guidance: false,
|
|
@@ -168,7 +168,7 @@ export const docsDense = {
|
|
|
168
168
|
{
|
|
169
169
|
guidance: true,
|
|
170
170
|
description:
|
|
171
|
-
'Rely on InfoTip rather than a hand-rolled info button on touch: it
|
|
171
|
+
'Rely on InfoTip rather than a hand-rolled info button on touch: it pins its help on a press, so a tap, a click, Enter, or Space pins the tooltip even though the trigger is a button, and a tap outside dismisses it.',
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
guidance: false,
|
package/src/InfoTip/InfoTip.tsx
CHANGED
|
@@ -23,7 +23,7 @@ import * as stylex from '@stylexjs/stylex';
|
|
|
23
23
|
import type {BaseProps} from '../BaseProps';
|
|
24
24
|
import {useMergedRefs} from '../hooks/useMergedRefs';
|
|
25
25
|
import {Icon, type IconSize} from '../Icon/Icon';
|
|
26
|
-
import {
|
|
26
|
+
import {TooltipWithTriggerBehavior} from '../Tooltip/Tooltip';
|
|
27
27
|
import {
|
|
28
28
|
registerEnglishFallback,
|
|
29
29
|
useComponentTranslator,
|
|
@@ -122,8 +122,8 @@ const styles = stylex.create({
|
|
|
122
122
|
* labels, values, and metrics for permission notes, metric definitions, and
|
|
123
123
|
* field help.
|
|
124
124
|
*
|
|
125
|
-
* InfoTip is
|
|
126
|
-
*
|
|
125
|
+
* InfoTip is a press-to-pin Tooltip on a named button: Tooltip opens, pins,
|
|
126
|
+
* announces, and closes the help. The info icon resolves from
|
|
127
127
|
* the icon registry, so themes can replace it.
|
|
128
128
|
*
|
|
129
129
|
* @example
|
|
@@ -157,7 +157,7 @@ export function InfoTip({
|
|
|
157
157
|
// stack takes Escape, so one press never closes both the help and an
|
|
158
158
|
// enclosing Dialog.
|
|
159
159
|
return (
|
|
160
|
-
<
|
|
160
|
+
<TooltipWithTriggerBehavior
|
|
161
161
|
content={content}
|
|
162
162
|
// The tooltip is the button's only purpose. A click, a tap, Enter, or
|
|
163
163
|
// Space pins it open, and the press stops at the button. So an InfoTip's
|
|
@@ -179,7 +179,7 @@ export function InfoTip({
|
|
|
179
179
|
)}>
|
|
180
180
|
<Icon icon="info" size={size} />
|
|
181
181
|
</button>
|
|
182
|
-
</
|
|
182
|
+
</TooltipWithTriggerBehavior>
|
|
183
183
|
);
|
|
184
184
|
}
|
|
185
185
|
|