@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
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
const english: Readonly<Record<string, string>> = {
|
|
10
|
+
"@astryx.alertDialog.cancel": "Cancel",
|
|
10
11
|
"@astryx.textInput.clearLabel": "Clear {label}",
|
|
11
12
|
"@astryx.tokenizer.add": "Add",
|
|
12
13
|
"@astryx.tokenizer.addValue": "Add {value}",
|
|
@@ -16,11 +17,16 @@ const english: Readonly<Record<string, string>> = {
|
|
|
16
17
|
"@astryx.tokenizer.clearAllTitle": "Clear all selected items?",
|
|
17
18
|
"@astryx.tokenizer.clearSelection": "Clear selection",
|
|
18
19
|
"@astryx.tokenizer.create": "Create \"{value}\"",
|
|
19
|
-
"@astryx.tokenizer.
|
|
20
|
+
"@astryx.tokenizer.discardChanges": "Discard changes",
|
|
21
|
+
"@astryx.tokenizer.discardChangesDescription": "Your changes to {label} haven't been saved.",
|
|
22
|
+
"@astryx.tokenizer.discardChangesTitle": "Discard changes?",
|
|
23
|
+
"@astryx.tokenizer.keepEditing": "Keep editing",
|
|
20
24
|
"@astryx.tokenizer.noResultsDescription": "Try a different search.",
|
|
21
25
|
"@astryx.tokenizer.nothingToClear": "Nothing selected",
|
|
26
|
+
"@astryx.tokenizer.save": "Save",
|
|
22
27
|
"@astryx.tokenizer.search": "Search {label}",
|
|
23
28
|
"@astryx.tokenizer.searchPlaceholder": "Search…",
|
|
29
|
+
"@astryx.tokenizer.selectionLimit": "Select up to {count, plural, one {# item} other {# items}}",
|
|
24
30
|
"@astryx.tokenizer.tokenAdded": "Added {label}",
|
|
25
31
|
"@astryx.tokenizer.tokenRemoved": "Removed {label}",
|
|
26
32
|
"@astryx.typeahead.emptySearchResults": "No results found",
|
|
@@ -315,6 +315,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
315
315
|
'@astryx.powersearch.label': 'Search',
|
|
316
316
|
'@astryx.powersearch.placeholder': 'Search…',
|
|
317
317
|
'@astryx.powersearch.allFiltersRemoved': 'All filters removed',
|
|
318
|
+
'@astryx.powersearch.editableFiltersRemoved': 'Editable filters removed',
|
|
318
319
|
'@astryx.resizable.collapsed': 'Collapsed',
|
|
319
320
|
'@astryx.resizable.handle.label': 'Resize handle',
|
|
320
321
|
'@astryx.selector.placeholder': 'Select…',
|
|
@@ -322,6 +322,7 @@ const runtimeCatalog: Readonly<Record<string, string>> = {
|
|
|
322
322
|
'@astryx.powersearch.label': '⟦Šéàřçĥ⟧',
|
|
323
323
|
'@astryx.powersearch.placeholder': '⟦Šéàřçĥ…⟧',
|
|
324
324
|
'@astryx.powersearch.allFiltersRemoved': '⟦Àłł ƒíłţéřš řéɱóṽéð⟧',
|
|
325
|
+
'@astryx.powersearch.editableFiltersRemoved': '⟦Éðíţàƀłé ƒíłţéřš řéɱóṽéð⟧',
|
|
325
326
|
'@astryx.resizable.collapsed': '⟦Çółłàþšéð⟧',
|
|
326
327
|
'@astryx.resizable.handle.label': '⟦Řéšížé ĥàñðłé⟧',
|
|
327
328
|
'@astryx.selector.placeholder': '⟦Šéłéçţ…⟧',
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file SheetConfirmation.tsx
|
|
7
|
+
* @input Dialog, Layout, Heading, Text, Button; caller-owned copy
|
|
8
|
+
* @output SheetConfirmation — the confirmation a touch sheet opens over
|
|
9
|
+
* itself for Clear all and for discarding a changed draft
|
|
10
|
+
* @position Core-internal, shared by the Tokenizer sheet and the PowerSearch
|
|
11
|
+
* sheets; not a public export
|
|
12
|
+
*
|
|
13
|
+
* AlertDialog fixes its purpose to form and its safe action to ghost. A touch
|
|
14
|
+
* sheet's confirmation allows scrim dismissal. Its two full-width large
|
|
15
|
+
* actions are stacked and separated: the destructive action first in reading
|
|
16
|
+
* order, the safe action focused first. The alert-dialog theme target stays.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import {useId} from 'react';
|
|
20
|
+
import * as stylex from '@stylexjs/stylex';
|
|
21
|
+
import {Button} from '../Button/Button';
|
|
22
|
+
import {Dialog} from '../Dialog/Dialog';
|
|
23
|
+
import {Heading} from '../Heading/Heading';
|
|
24
|
+
import {Layout} from '../Layout/Layout';
|
|
25
|
+
import {LayoutContent} from '../Layout/LayoutContent';
|
|
26
|
+
import {LayoutFooter} from '../Layout/LayoutFooter';
|
|
27
|
+
import {Stack} from '../Stack/Stack';
|
|
28
|
+
import {Text} from '../Text/Text';
|
|
29
|
+
import {themeProps} from '../utils/themeProps';
|
|
30
|
+
|
|
31
|
+
export interface SheetConfirmationProps {
|
|
32
|
+
isOpen: boolean;
|
|
33
|
+
onOpenChange: (isOpen: boolean) => void;
|
|
34
|
+
title: string;
|
|
35
|
+
description: string;
|
|
36
|
+
cancelLabel: string;
|
|
37
|
+
actionLabel: string;
|
|
38
|
+
onAction: () => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const styles = stylex.create({
|
|
42
|
+
action: {width: '100%'},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export function SheetConfirmation({
|
|
46
|
+
isOpen,
|
|
47
|
+
onOpenChange,
|
|
48
|
+
title,
|
|
49
|
+
description,
|
|
50
|
+
cancelLabel,
|
|
51
|
+
actionLabel,
|
|
52
|
+
onAction,
|
|
53
|
+
}: SheetConfirmationProps) {
|
|
54
|
+
const titleId = useId();
|
|
55
|
+
const descriptionId = useId();
|
|
56
|
+
return (
|
|
57
|
+
<Dialog
|
|
58
|
+
{...themeProps('alert-dialog')}
|
|
59
|
+
isOpen={isOpen}
|
|
60
|
+
onOpenChange={onOpenChange}
|
|
61
|
+
purpose="info"
|
|
62
|
+
role="alertdialog"
|
|
63
|
+
aria-labelledby={titleId}
|
|
64
|
+
aria-describedby={descriptionId}
|
|
65
|
+
width={400}>
|
|
66
|
+
<Layout
|
|
67
|
+
content={
|
|
68
|
+
<LayoutContent>
|
|
69
|
+
<Heading level={2} id={titleId}>
|
|
70
|
+
{title}
|
|
71
|
+
</Heading>
|
|
72
|
+
<Text type="body" color="secondary" id={descriptionId}>
|
|
73
|
+
{description}
|
|
74
|
+
</Text>
|
|
75
|
+
</LayoutContent>
|
|
76
|
+
}
|
|
77
|
+
footer={
|
|
78
|
+
<LayoutFooter>
|
|
79
|
+
<Stack direction="vertical" gap={4} hAlign="stretch">
|
|
80
|
+
<Button
|
|
81
|
+
label={actionLabel}
|
|
82
|
+
variant="destructive"
|
|
83
|
+
size="lg"
|
|
84
|
+
onClick={onAction}
|
|
85
|
+
xstyle={styles.action}
|
|
86
|
+
/>
|
|
87
|
+
<Button
|
|
88
|
+
label={cancelLabel}
|
|
89
|
+
variant="secondary"
|
|
90
|
+
size="lg"
|
|
91
|
+
onClick={() => onOpenChange(false)}
|
|
92
|
+
data-autofocus
|
|
93
|
+
xstyle={styles.action}
|
|
94
|
+
/>
|
|
95
|
+
</Stack>
|
|
96
|
+
</LayoutFooter>
|
|
97
|
+
}
|
|
98
|
+
/>
|
|
99
|
+
</Dialog>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
/**
|
|
3
|
+
* @file sheetDraft.ts
|
|
4
|
+
* @input A sheet's selection at open, its draft, and the last edit made
|
|
5
|
+
* @output SheetDraftChange, hasSameSelection, nameSheetDraftChange — the
|
|
6
|
+
* rules a touch sheet's Save and dismissal share
|
|
7
|
+
* @position Core-internal, shared by the Tokenizer sheet and the Lab sheets
|
|
8
|
+
* through `internal/touch-sheet`; not a public export
|
|
9
|
+
*
|
|
10
|
+
* A touch sheet edits a draft and reports it once, on Save, with one change:
|
|
11
|
+
* the last edit that still holds, else the nearest net add or remove. A
|
|
12
|
+
* dismissal asks before discarding only while the draft differs from the
|
|
13
|
+
* selection at open, as a set: a row unchecked and checked again is no change.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface SheetDraftItem {
|
|
17
|
+
id: string | number;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** An edit of a sheet's draft, and the shape Save reports. */
|
|
21
|
+
export type SheetDraftChange<T> = {
|
|
22
|
+
item: T;
|
|
23
|
+
type: 'add' | 'create' | 'remove';
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** Whether two selections hold the same items, in any order. */
|
|
27
|
+
export function hasSameSelection<T extends SheetDraftItem>(
|
|
28
|
+
a: ReadonlyArray<T>,
|
|
29
|
+
b: ReadonlyArray<T>,
|
|
30
|
+
): boolean {
|
|
31
|
+
if (a.length !== b.length) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const ids = new Set(a.map(item => item.id));
|
|
35
|
+
return b.every(item => ids.has(item.id));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The change Save reports for a draft that differs from the selection at
|
|
40
|
+
* open, or `null` when nothing differs. The last edit names it when it still
|
|
41
|
+
* holds: its item is in the draft and was not selected at open, or was
|
|
42
|
+
* selected at open and is gone. Otherwise, after a removal, a `remove` names
|
|
43
|
+
* an item that is gone; otherwise an `add` (`create` when the last edit
|
|
44
|
+
* created that item) names an item that is new; otherwise a `remove` names
|
|
45
|
+
* an item that is gone.
|
|
46
|
+
*/
|
|
47
|
+
export function nameSheetDraftChange<T extends SheetDraftItem>(
|
|
48
|
+
valueAtOpen: ReadonlyArray<T>,
|
|
49
|
+
draftValue: ReadonlyArray<T>,
|
|
50
|
+
lastDraftChange: SheetDraftChange<T> | null,
|
|
51
|
+
): SheetDraftChange<T> | null {
|
|
52
|
+
const wasSelected = (id: T['id']) => valueAtOpen.some(item => item.id === id);
|
|
53
|
+
const isSelected = (id: T['id']) => draftValue.some(item => item.id === id);
|
|
54
|
+
const added = draftValue.find(item => !wasSelected(item.id));
|
|
55
|
+
const removed = valueAtOpen.find(item => !isSelected(item.id));
|
|
56
|
+
const lastChangeIsNet =
|
|
57
|
+
lastDraftChange != null &&
|
|
58
|
+
(lastDraftChange.type === 'remove'
|
|
59
|
+
? wasSelected(lastDraftChange.item.id) &&
|
|
60
|
+
!isSelected(lastDraftChange.item.id)
|
|
61
|
+
: isSelected(lastDraftChange.item.id) &&
|
|
62
|
+
!wasSelected(lastDraftChange.item.id));
|
|
63
|
+
if (lastChangeIsNet) {
|
|
64
|
+
return lastDraftChange;
|
|
65
|
+
}
|
|
66
|
+
if (lastDraftChange?.type === 'remove' && removed != null) {
|
|
67
|
+
return {item: removed, type: 'remove'};
|
|
68
|
+
}
|
|
69
|
+
if (added != null) {
|
|
70
|
+
const type: SheetDraftChange<T>['type'] =
|
|
71
|
+
lastDraftChange?.item.id === added.id && lastDraftChange.type === 'create'
|
|
72
|
+
? 'create'
|
|
73
|
+
: 'add';
|
|
74
|
+
return {item: added, type};
|
|
75
|
+
}
|
|
76
|
+
if (removed != null) {
|
|
77
|
+
return {item: removed, type: 'remove'};
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
/**
|
|
3
|
+
* @file touch-sheet.ts
|
|
4
|
+
* @input The touch sheets' shared confirmation, search-drag dismissal and
|
|
5
|
+
* draft rules
|
|
6
|
+
* @output `@astryxdesign/core/internal/touch-sheet`: SheetConfirmation,
|
|
7
|
+
* useDismissSearchOnTouchDrag, hasSameSelection and nameSheetDraftChange,
|
|
8
|
+
* re-exported by name for Lab's sheets
|
|
9
|
+
* @position An internal entry reserved for Astryx's own packages:
|
|
10
|
+
* undocumented, outside the public API and its compatibility promise,
|
|
11
|
+
* versioned in lockstep with Core, and allowed only in Core and Lab by the
|
|
12
|
+
* `@astryx/no-core-internal-import` lint rule. Core components import the
|
|
13
|
+
* same modules by relative path. Named re-exports only.
|
|
14
|
+
*
|
|
15
|
+
* SYNC: When modified, update scripts/sync-exports.js.
|
|
16
|
+
*/
|
|
17
|
+
export {SheetConfirmation} from './SheetConfirmation';
|
|
18
|
+
export type {SheetConfirmationProps} from './SheetConfirmation';
|
|
19
|
+
export {useDismissSearchOnTouchDrag} from './useDismissSearchOnTouchDrag';
|
|
20
|
+
export {hasSameSelection, nameSheetDraftChange} from './sheetDraft';
|
|
21
|
+
export type {SheetDraftChange} from './sheetDraft';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
'use client';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @file useDismissSearchOnTouchDrag.ts
|
|
7
|
+
* @input Caller-owned search inputs, a focus target, and touch events from a
|
|
8
|
+
* separate results list
|
|
9
|
+
* @output useDismissSearchOnTouchDrag — touch handlers for the results list
|
|
10
|
+
* @position Core-internal, shared by the touch sheets; not a public export
|
|
11
|
+
*
|
|
12
|
+
* A direct vertical touch drag on the results moves focus off a focused
|
|
13
|
+
* search input. The on-screen keyboard then leaves and the rows come into
|
|
14
|
+
* view; the query stays. Focus goes to the caller's target (the sheet's
|
|
15
|
+
* panel), never to the page body behind an open modal. Scroll events are not
|
|
16
|
+
* observed: filtering can clamp scrollTop and fire scroll while someone is
|
|
17
|
+
* typing. Small moves, taps and multi-touch gestures leave focus alone.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import {useRef, type TouchEvent} from 'react';
|
|
21
|
+
|
|
22
|
+
const DRAG_THRESHOLD_PX = 8;
|
|
23
|
+
|
|
24
|
+
type SearchInput = HTMLInputElement | null | undefined;
|
|
25
|
+
|
|
26
|
+
export function useDismissSearchOnTouchDrag(
|
|
27
|
+
getSearchInputs: () => ReadonlyArray<SearchInput>,
|
|
28
|
+
getFocusTarget?: () => HTMLElement | null | undefined,
|
|
29
|
+
) {
|
|
30
|
+
const startYRef = useRef<number | null>(null);
|
|
31
|
+
const reset = () => {
|
|
32
|
+
startYRef.current = null;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
onTouchStart: (event: TouchEvent<HTMLElement>) => {
|
|
37
|
+
startYRef.current =
|
|
38
|
+
event.touches.length === 1 ? event.touches[0].clientY : null;
|
|
39
|
+
},
|
|
40
|
+
onTouchMove: (event: TouchEvent<HTMLElement>) => {
|
|
41
|
+
if (event.touches.length !== 1) {
|
|
42
|
+
reset();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const startY = startYRef.current;
|
|
46
|
+
if (
|
|
47
|
+
startY == null ||
|
|
48
|
+
Math.abs(event.touches[0].clientY - startY) < DRAG_THRESHOLD_PX
|
|
49
|
+
) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
reset();
|
|
53
|
+
const focusedSearch = getSearchInputs().find(
|
|
54
|
+
input => input != null && input === document.activeElement,
|
|
55
|
+
);
|
|
56
|
+
if (focusedSearch == null) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const target = getFocusTarget?.();
|
|
60
|
+
if (target != null) {
|
|
61
|
+
target.focus({preventScroll: true});
|
|
62
|
+
} else {
|
|
63
|
+
focusedSearch.blur();
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
onTouchEnd: reset,
|
|
67
|
+
onTouchCancel: reset,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @file reducedMotion.ts
|
|
5
5
|
* @input The Reduced Motion preference, the primary pointer, the motion tables
|
|
6
6
|
* @output reducedMotionFade (the fade the animator would play, for motion it
|
|
7
|
-
* does not drive) and the
|
|
7
|
+
* does not drive), playReducedMotionDip, and the queries a transition spells
|
|
8
8
|
* @position The one Reduced Motion policy (design:reduced-motion) for code
|
|
9
9
|
* beside the animator: a raw Web Animations fade, a CSS transition
|
|
10
10
|
*
|
|
@@ -102,3 +102,81 @@ export function reducedMotionFade(
|
|
|
102
102
|
easing: `cubic-bezier(${curve.join(', ')})`,
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
|
|
106
|
+
/** A running Reduced Motion dip. */
|
|
107
|
+
export interface ReducedMotionDip {
|
|
108
|
+
/** Stops the dip. A change that has not landed yet never lands. */
|
|
109
|
+
cancel(): void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Plays `fade` on `element` as a dip, and runs `land` at its middle: the
|
|
114
|
+
* fade-out, the change, then the fade-in. The fade-out holds at 0 until `land`
|
|
115
|
+
* has run, and the fade-in starts only after it. So the change never shows at
|
|
116
|
+
* full opacity, however late the main thread runs. A timer at the middle can
|
|
117
|
+
* fire after the fade-in has begun.
|
|
118
|
+
*
|
|
119
|
+
* `land` must apply the change before it returns: the fade-in starts right
|
|
120
|
+
* after it. `from` is the opacity the fade-out starts at: a leaving element
|
|
121
|
+
* fades out from where it is.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```
|
|
125
|
+
* const dip = playReducedMotionDip(scroller, fade, () => scroller.scrollTo({top}));
|
|
126
|
+
* // A newer change replaces it:
|
|
127
|
+
* dip.cancel();
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export function playReducedMotionDip(
|
|
131
|
+
element: Element,
|
|
132
|
+
fade: ReducedMotionFade,
|
|
133
|
+
land: () => void,
|
|
134
|
+
from = 1,
|
|
135
|
+
): ReducedMotionDip {
|
|
136
|
+
const half = fade.duration / 2;
|
|
137
|
+
let isCancelled = false;
|
|
138
|
+
let fadeIn: Animation | null = null;
|
|
139
|
+
const fadeOut = element.animate(
|
|
140
|
+
[{opacity: from, easing: fade.easing}, {opacity: 0}],
|
|
141
|
+
{duration: half, fill: 'forwards'},
|
|
142
|
+
);
|
|
143
|
+
fadeOut.finished.then(
|
|
144
|
+
() => {
|
|
145
|
+
if (isCancelled) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
land();
|
|
149
|
+
// The fade-in holds 0 until it starts: after a long stall, its start can
|
|
150
|
+
// come after the frame that runs this.
|
|
151
|
+
fadeIn = element.animate(
|
|
152
|
+
[{opacity: 0, easing: fade.easing}, {opacity: 1}],
|
|
153
|
+
{duration: half, fill: 'backwards'},
|
|
154
|
+
);
|
|
155
|
+
// The fade-in keeps the dip's own schedule: it starts where the fade-out
|
|
156
|
+
// ended, so the dip keeps its length. The clock is read after the
|
|
157
|
+
// landing, because a frame's timestamp does not count the landing's own
|
|
158
|
+
// work. After a long stall, half of the fade-in still plays.
|
|
159
|
+
const fadeOutEnd = endTime(fadeOut, half);
|
|
160
|
+
if (fadeOutEnd != null) {
|
|
161
|
+
fadeIn.startTime = Math.max(fadeOutEnd, performance.now() - half / 2);
|
|
162
|
+
}
|
|
163
|
+
// The fade-in now holds the opacity, so the held 0 can go.
|
|
164
|
+
fadeOut.cancel();
|
|
165
|
+
},
|
|
166
|
+
// A cancelled fade-out rejects `finished`: nothing lands.
|
|
167
|
+
() => {},
|
|
168
|
+
);
|
|
169
|
+
return {
|
|
170
|
+
cancel() {
|
|
171
|
+
isCancelled = true;
|
|
172
|
+
fadeOut.cancel();
|
|
173
|
+
fadeIn?.cancel();
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** When `animation` was due to end, on its timeline (ms), if it has started. */
|
|
179
|
+
function endTime(animation: Animation, duration: number): number | null {
|
|
180
|
+
const start = animation.startTime;
|
|
181
|
+
return typeof start === 'number' ? start + duration : null;
|
|
182
|
+
}
|
package/src/utils/clickGuard.ts
CHANGED
|
@@ -26,6 +26,11 @@
|
|
|
26
26
|
// `touchend`, aimed at whatever is under the finger by then. They go with
|
|
27
27
|
// the click, the next `pointerdown` or the window, as the guard does.
|
|
28
28
|
//
|
|
29
|
+
// The menu press model's arm at a finger's release also cancels that touch's
|
|
30
|
+
// `touchend`, so the browser creates no click and no compatibility mouse
|
|
31
|
+
// events. A busy main thread can otherwise hold that click past the window,
|
|
32
|
+
// when a drill-in has put another row under the finger.
|
|
33
|
+
//
|
|
29
34
|
// Some clicks always pass:
|
|
30
35
|
// - a click with `detail` 0: a keyboard's, a screen reader's, or one that the
|
|
31
36
|
// page dispatches;
|
|
@@ -56,6 +61,13 @@ export interface ClickGuardOptions {
|
|
|
56
61
|
* @default false
|
|
57
62
|
*/
|
|
58
63
|
readonly takesLateMouse?: boolean;
|
|
64
|
+
/**
|
|
65
|
+
* The menu press model's arm at a finger's release: the guard also cancels
|
|
66
|
+
* that touch's `touchend`, so the browser creates no click for it. A
|
|
67
|
+
* cancelled `touchend` disarms the guard.
|
|
68
|
+
* @default false
|
|
69
|
+
*/
|
|
70
|
+
readonly cancelsTouchEnd?: boolean;
|
|
59
71
|
}
|
|
60
72
|
|
|
61
73
|
interface Guard {
|
|
@@ -89,7 +101,11 @@ function listenForClicks(view: Window): () => void {
|
|
|
89
101
|
/** Swallow the browser's next click on `doc` (see the file header). */
|
|
90
102
|
export function armClickGuard(
|
|
91
103
|
doc: Document,
|
|
92
|
-
{
|
|
104
|
+
{
|
|
105
|
+
staysInstalled = false,
|
|
106
|
+
takesLateMouse = false,
|
|
107
|
+
cancelsTouchEnd = false,
|
|
108
|
+
}: ClickGuardOptions = {},
|
|
93
109
|
): void {
|
|
94
110
|
const view = doc.defaultView;
|
|
95
111
|
if (view == null) {
|
|
@@ -116,6 +132,26 @@ export function armClickGuard(
|
|
|
116
132
|
);
|
|
117
133
|
}
|
|
118
134
|
}
|
|
135
|
+
if (cancelsTouchEnd) {
|
|
136
|
+
// A touch sends its pointerup before its touchend, so the next touchend is
|
|
137
|
+
// that touch's own. The next pointerdown disarms the guard first.
|
|
138
|
+
const stopTouchEnd = subscribeInput(
|
|
139
|
+
view,
|
|
140
|
+
'touchend',
|
|
141
|
+
event => {
|
|
142
|
+
stopTouchEnd();
|
|
143
|
+
// A touchend that the browser took for a scroll is not cancelable.
|
|
144
|
+
// The guard stays armed for a click that may follow it.
|
|
145
|
+
if (event.cancelable) {
|
|
146
|
+
event.preventDefault();
|
|
147
|
+
// No click follows a cancelled touchend.
|
|
148
|
+
disarmClickGuard(doc);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{cancels: true},
|
|
152
|
+
);
|
|
153
|
+
guard.unsubscribers.push(stopTouchEnd);
|
|
154
|
+
}
|
|
119
155
|
guard.timer = setTimeout(() => disarmClickGuard(doc), CLICK_GUARD_WINDOW_MS);
|
|
120
156
|
}
|
|
121
157
|
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file tokenizer-presentation.ts
|
|
3
|
-
* @input Tokenizer's sheet-capable signature
|
|
4
|
-
* @output `@astryxdesign/core/internal/tokenizer-presentation`: the Tokenizer
|
|
5
|
-
* with its `presentation` options reachable, for Astryx's own packages
|
|
6
|
-
* @position An internal entry reserved for Astryx's own packages:
|
|
7
|
-
* undocumented, outside the public API and its compatibility promise
|
|
8
|
-
* (spec:AST-017 FR21), versioned in lockstep with Core, and allowed only in
|
|
9
|
-
* Core and Lab by the `@astryx/no-core-internal-import` lint rule
|
|
10
|
-
* (architecture:react-component-runtime INV13). The sheet presentation is
|
|
11
|
-
* implemented but not yet exposed: the public `TokenizerProps` offers no
|
|
12
|
-
* `experimental.presentation` while its user experience is being finished
|
|
13
|
-
* (`component:Tokenizer` DEC-3, implementation gap), and every public field
|
|
14
|
-
* renders the popover presentation. Named re-exports only.
|
|
15
|
-
*
|
|
16
|
-
* SYNC: When modified, update tokenizer-presentation.test.ts and
|
|
17
|
-
* scripts/sync-exports.js.
|
|
18
|
-
*/
|
|
19
|
-
export { TokenizerWithPresentation } from '../Tokenizer/Tokenizer.js';
|
|
20
|
-
export type { TokenizerPresentationOptions, TokenizerWithPresentationProps, } from '../Tokenizer/Tokenizer.js';
|
|
21
|
-
//# sourceMappingURL=tokenizer-presentation.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tokenizer-presentation.d.ts","sourceRoot":"","sources":["../../src/internal/tokenizer-presentation.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAC,yBAAyB,EAAC,MAAM,wBAAwB,CAAC;AACjE,YAAY,EACV,4BAA4B,EAC5B,8BAA8B,GAC/B,MAAM,wBAAwB,CAAC"}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file tokenizer-presentation.ts
|
|
5
|
-
* @input Tokenizer's sheet-capable signature
|
|
6
|
-
* @output `@astryxdesign/core/internal/tokenizer-presentation`: the Tokenizer
|
|
7
|
-
* with its `presentation` options reachable, for Astryx's own packages
|
|
8
|
-
* @position An internal entry reserved for Astryx's own packages:
|
|
9
|
-
* undocumented, outside the public API and its compatibility promise
|
|
10
|
-
* (spec:AST-017 FR21), versioned in lockstep with Core, and allowed only in
|
|
11
|
-
* Core and Lab by the `@astryx/no-core-internal-import` lint rule
|
|
12
|
-
* (architecture:react-component-runtime INV13). The sheet presentation is
|
|
13
|
-
* implemented but not yet exposed: the public `TokenizerProps` offers no
|
|
14
|
-
* `experimental.presentation` while its user experience is being finished
|
|
15
|
-
* (`component:Tokenizer` DEC-3, implementation gap), and every public field
|
|
16
|
-
* renders the popover presentation. Named re-exports only.
|
|
17
|
-
*
|
|
18
|
-
* SYNC: When modified, update tokenizer-presentation.test.ts and
|
|
19
|
-
* scripts/sync-exports.js.
|
|
20
|
-
*/
|
|
21
|
-
export { TokenizerWithPresentation } from "../Tokenizer/Tokenizer.js";
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @file tokenizer-presentation.ts
|
|
5
|
-
* @input Tokenizer's sheet-capable signature
|
|
6
|
-
* @output `@astryxdesign/core/internal/tokenizer-presentation`: the Tokenizer
|
|
7
|
-
* with its `presentation` options reachable, for Astryx's own packages
|
|
8
|
-
* @position An internal entry reserved for Astryx's own packages:
|
|
9
|
-
* undocumented, outside the public API and its compatibility promise
|
|
10
|
-
* (spec:AST-017 FR21), versioned in lockstep with Core, and allowed only in
|
|
11
|
-
* Core and Lab by the `@astryx/no-core-internal-import` lint rule
|
|
12
|
-
* (architecture:react-component-runtime INV13). The sheet presentation is
|
|
13
|
-
* implemented but not yet exposed: the public `TokenizerProps` offers no
|
|
14
|
-
* `experimental.presentation` while its user experience is being finished
|
|
15
|
-
* (`component:Tokenizer` DEC-3, implementation gap), and every public field
|
|
16
|
-
* renders the popover presentation. Named re-exports only.
|
|
17
|
-
*
|
|
18
|
-
* SYNC: When modified, update tokenizer-presentation.test.ts and
|
|
19
|
-
* scripts/sync-exports.js.
|
|
20
|
-
*/
|
|
21
|
-
export {TokenizerWithPresentation} from '../Tokenizer/Tokenizer';
|
|
22
|
-
export type {
|
|
23
|
-
TokenizerPresentationOptions,
|
|
24
|
-
TokenizerWithPresentationProps,
|
|
25
|
-
} from '../Tokenizer/Tokenizer';
|