react-panel-layout 0.6.0 → 0.6.1
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/{FloatingPanelFrame-SgYLc6Ud.js → FloatingPanelFrame-3eU9AwPo.js} +2 -2
- package/dist/{FloatingPanelFrame-SgYLc6Ud.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
- package/dist/FloatingWindow-CUXnEtrb.js +827 -0
- package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
- package/dist/GridLayout-DKTg_N61.cjs +2 -0
- package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DKTg_N61.cjs.map} +1 -1
- package/dist/{GridLayout-BltqeCPK.js → GridLayout-UWNxXw77.js} +34 -35
- package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-UWNxXw77.js.map} +1 -1
- package/dist/{HorizontalDivider-WF1k_qND.js → HorizontalDivider-DdxzfV0l.js} +3 -3
- package/dist/{HorizontalDivider-WF1k_qND.js.map → HorizontalDivider-DdxzfV0l.js.map} +1 -1
- package/dist/{HorizontalDivider-B5Z-KZLk.cjs → HorizontalDivider-_pgV4Mcv.cjs} +2 -2
- package/dist/{HorizontalDivider-B5Z-KZLk.cjs.map → HorizontalDivider-_pgV4Mcv.cjs.map} +1 -1
- package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-BqUzNtf2.js} +5 -5
- package/dist/{PanelSystem-Dr1TBhxM.js.map → PanelSystem-BqUzNtf2.js.map} +1 -1
- package/dist/{PanelSystem-Bs8bQwQF.cjs → PanelSystem-D603LKKv.cjs} +2 -2
- package/dist/{PanelSystem-Bs8bQwQF.cjs.map → PanelSystem-D603LKKv.cjs.map} +1 -1
- package/dist/ResizeHandle-CBcAS918.cjs +2 -0
- package/dist/{ResizeHandle-CScipO5l.cjs.map → ResizeHandle-CBcAS918.cjs.map} +1 -1
- package/dist/{ResizeHandle-CdA_JYfN.js → ResizeHandle-CXjc1meV.js} +28 -29
- package/dist/{ResizeHandle-CdA_JYfN.js.map → ResizeHandle-CXjc1meV.js.map} +1 -1
- package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
- package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
- package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
- package/dist/components/window/Drawer.d.ts +3 -1
- package/dist/components/window/DrawerLayers.d.ts +1 -1
- package/dist/components/window/drawerStyles.d.ts +69 -0
- package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
- package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
- package/dist/config.cjs +1 -1
- package/dist/config.js +3 -3
- package/dist/constants/styles.d.ts +17 -0
- package/dist/dialog/index.d.ts +69 -0
- package/dist/floating.js +1 -1
- package/dist/grid.cjs +1 -1
- package/dist/grid.js +2 -2
- package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
- package/dist/hooks/gesture/types.d.ts +48 -5
- package/dist/hooks/gesture/utils.d.ts +19 -0
- package/dist/hooks/useAnimationFrame.d.ts +2 -0
- package/dist/hooks/useOperationContinuity.d.ts +64 -0
- package/dist/hooks/useResizeObserver.d.ts +33 -1
- package/dist/hooks/useSharedElementTransition.d.ts +112 -0
- package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
- package/dist/index.cjs +1 -1
- package/dist/index.js +7 -7
- package/dist/modules/dialog/AlertDialog.d.ts +9 -0
- package/dist/modules/dialog/DialogContainer.d.ts +37 -0
- package/dist/modules/dialog/Modal.d.ts +26 -0
- package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
- package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
- package/dist/modules/dialog/types.d.ts +183 -0
- package/dist/modules/dialog/useDialog.d.ts +39 -0
- package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
- package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
- package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
- package/dist/modules/drawer/types.d.ts +74 -0
- package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
- package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
- package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
- package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
- package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
- package/dist/panels.cjs +1 -1
- package/dist/panels.js +1 -1
- package/dist/pivot.cjs +1 -1
- package/dist/pivot.js +1 -1
- package/dist/resizer.cjs +1 -1
- package/dist/resizer.js +2 -2
- package/dist/stack.cjs +1 -1
- package/dist/stack.cjs.map +1 -1
- package/dist/stack.js +503 -762
- package/dist/stack.js.map +1 -1
- package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
- package/dist/sticky-header.cjs +1 -1
- package/dist/sticky-header.cjs.map +1 -1
- package/dist/sticky-header.js +59 -51
- package/dist/sticky-header.js.map +1 -1
- package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
- package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
- package/dist/styles-qf6ptVLD.cjs.map +1 -1
- package/dist/types.d.ts +16 -0
- package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
- package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
- package/dist/window/index.d.ts +2 -0
- package/dist/window.cjs +1 -1
- package/dist/window.cjs.map +1 -1
- package/dist/window.js +114 -103
- package/dist/window.js.map +1 -1
- package/package.json +6 -1
- package/src/components/gesture/SwipeSafeZone.tsx +69 -0
- package/src/components/window/Drawer.tsx +249 -162
- package/src/components/window/DrawerLayers.tsx +13 -3
- package/src/components/window/drawerStyles.spec.ts +263 -0
- package/src/components/window/drawerStyles.ts +228 -0
- package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
- package/src/components/window/drawerSwipeConfig.ts +112 -0
- package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
- package/src/components/window/useDrawerSwipeTransform.ts +129 -0
- package/src/constants/styles.ts +19 -0
- package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
- package/src/demo/pages/Dialog/card/index.tsx +22 -0
- package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
- package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
- package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
- package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
- package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
- package/src/demo/pages/Dialog/modal/index.tsx +17 -0
- package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
- package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
- package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
- package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
- package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
- package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
- package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
- package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
- package/src/demo/routes.tsx +22 -1
- package/src/dialog/index.ts +85 -0
- package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
- package/src/hooks/gesture/testing/createGestureSimulator.ts +112 -37
- package/src/hooks/gesture/types.ts +83 -6
- package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
- package/src/hooks/gesture/useNativeGestureGuard.spec.ts +91 -31
- package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
- package/src/hooks/gesture/utils.ts +91 -0
- package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
- package/src/hooks/useAnimatedVisibility.ts +28 -2
- package/src/hooks/useAnimationFrame.ts +8 -0
- package/src/hooks/useOperationContinuity.spec.ts +387 -0
- package/src/hooks/useOperationContinuity.ts +135 -0
- package/src/hooks/useResizeObserver.spec.tsx +277 -0
- package/src/hooks/useResizeObserver.tsx +108 -39
- package/src/hooks/useScrollContainer.ts +4 -10
- package/src/hooks/useSharedElementTransition.ts +333 -0
- package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
- package/src/hooks/useSwipeContentTransform.ts +166 -28
- package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
- package/src/modules/dialog/AlertDialog.tsx +221 -0
- package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
- package/src/modules/dialog/DialogContainer.tsx +188 -0
- package/src/modules/dialog/Modal.spec.tsx +220 -0
- package/src/modules/dialog/Modal.tsx +182 -0
- package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
- package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
- package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
- package/src/modules/dialog/types.ts +186 -0
- package/src/modules/dialog/useDialog.spec.tsx +447 -0
- package/src/modules/dialog/useDialog.ts +214 -0
- package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
- package/src/modules/dialog/useDialogContainer.ts +150 -0
- package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
- package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
- package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
- package/src/modules/dialog/useDialogTransform.ts +407 -0
- package/src/modules/drawer/types.ts +102 -0
- package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
- package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
- package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
- package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
- package/src/modules/pivot/SwipePivotContent.spec.tsx +55 -25
- package/src/modules/pivot/SwipePivotContent.tsx +2 -2
- package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
- package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
- package/src/modules/pivot/scaleInputState.spec.ts +11 -2
- package/src/modules/pivot/usePivot.spec.ts +17 -3
- package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
- package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
- package/src/modules/stack/SwipeStackContent.tsx +43 -33
- package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
- package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
- package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
- package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
- package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
- package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
- package/src/modules/stack/useStackAnimationState.ts +18 -13
- package/src/modules/stack/useStackNavigation.spec.ts +198 -3
- package/src/modules/stack/useStackNavigation.tsx +113 -56
- package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
- package/src/modules/stack/useStackSwipeInput.ts +1 -1
- package/src/sticky-header/StickyArea.tsx +29 -57
- package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
- package/src/sticky-header/calculateStickyMetrics.ts +50 -0
- package/src/types.ts +18 -0
- package/src/window/index.ts +2 -0
- package/dist/FloatingWindow-BpdOpg_L.js +0 -400
- package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
- package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
- package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
- package/dist/GridLayout-B4VRsC0r.cjs +0 -2
- package/dist/ResizeHandle-CScipO5l.cjs +0 -2
- package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
- package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
- package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
- package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
- package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
- package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
- package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
- package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
- package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
- package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
- package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
- package/dist/useEffectEvent-huSsGUnl.cjs.map +0 -1
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Dialog module type definitions
|
|
3
|
+
*/
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import type { Position } from "../../types";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transition mode for dialog animations
|
|
9
|
+
* - "none": No animation
|
|
10
|
+
* - "css": CSS-based fade/scale animation
|
|
11
|
+
* - "swipe": Swipeable with multi-phase animation
|
|
12
|
+
*/
|
|
13
|
+
export type DialogTransitionMode = "none" | "css" | "swipe";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Direction from which the dialog opens.
|
|
17
|
+
* The close animation uses the same direction (swipe down to close if opened from bottom).
|
|
18
|
+
*/
|
|
19
|
+
export type DialogOpenDirection = "center" | "top" | "bottom" | "left" | "right";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Base props for DialogContainer component
|
|
23
|
+
*/
|
|
24
|
+
export type DialogContainerProps = {
|
|
25
|
+
/** Whether the dialog is visible */
|
|
26
|
+
visible: boolean;
|
|
27
|
+
/** Callback when dialog should close */
|
|
28
|
+
onClose: () => void;
|
|
29
|
+
/** Dialog content */
|
|
30
|
+
children: React.ReactNode;
|
|
31
|
+
/** Position: 'center' for screen center, or Position for absolute coordinates */
|
|
32
|
+
position?: "center" | Position;
|
|
33
|
+
/** Whether clicking backdrop closes the dialog */
|
|
34
|
+
dismissible?: boolean;
|
|
35
|
+
/** Whether pressing Escape closes the dialog */
|
|
36
|
+
closeOnEscape?: boolean;
|
|
37
|
+
/** Whether to prevent body scroll when open */
|
|
38
|
+
preventBodyScroll?: boolean;
|
|
39
|
+
/** Whether to return focus to previous element on close */
|
|
40
|
+
returnFocus?: boolean;
|
|
41
|
+
/** Aria label for the dialog */
|
|
42
|
+
ariaLabel?: string;
|
|
43
|
+
/** ID of element that labels the dialog */
|
|
44
|
+
ariaLabelledBy?: string;
|
|
45
|
+
/** ID of element that describes the dialog */
|
|
46
|
+
ariaDescribedBy?: string;
|
|
47
|
+
/** Transition mode */
|
|
48
|
+
transitionMode?: DialogTransitionMode;
|
|
49
|
+
/** Transition duration (CSS value, e.g. '200ms') */
|
|
50
|
+
transitionDuration?: string;
|
|
51
|
+
/** Transition easing (CSS value, e.g. 'ease-out') */
|
|
52
|
+
transitionEasing?: string;
|
|
53
|
+
/** Whether the dialog can be dismissed by swiping. @default false for "css", true for "swipe" */
|
|
54
|
+
swipeDismissible?: boolean;
|
|
55
|
+
/** Direction the dialog opens from (used for swipe mode). @default "center" */
|
|
56
|
+
openDirection?: DialogOpenDirection;
|
|
57
|
+
/** Whether to use viewTransition API for close animation (when available). @default false */
|
|
58
|
+
useViewTransition?: boolean;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Header configuration for Modal
|
|
63
|
+
*/
|
|
64
|
+
export type ModalHeader = {
|
|
65
|
+
/** Title displayed in the header */
|
|
66
|
+
title: string;
|
|
67
|
+
/** Whether to show close button (default: true) */
|
|
68
|
+
showCloseButton?: boolean;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Props for Modal component
|
|
73
|
+
*/
|
|
74
|
+
export type ModalProps = Omit<DialogContainerProps, "position"> & {
|
|
75
|
+
/** Header configuration */
|
|
76
|
+
header?: ModalHeader;
|
|
77
|
+
/** Modal width */
|
|
78
|
+
width?: string | number;
|
|
79
|
+
/** Modal height */
|
|
80
|
+
height?: string | number;
|
|
81
|
+
/** Max width (default: 90vw) */
|
|
82
|
+
maxWidth?: string | number;
|
|
83
|
+
/** Max height (default: 85vh) */
|
|
84
|
+
maxHeight?: string | number;
|
|
85
|
+
/** Whether to use FloatingPanelFrame chrome (default: true) */
|
|
86
|
+
chrome?: boolean;
|
|
87
|
+
/** Custom content style */
|
|
88
|
+
contentStyle?: React.CSSProperties;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Options for alert dialog
|
|
93
|
+
*/
|
|
94
|
+
export type AlertOptions = {
|
|
95
|
+
/** Optional title */
|
|
96
|
+
title?: string;
|
|
97
|
+
/** Message to display */
|
|
98
|
+
message: string;
|
|
99
|
+
/** OK button label (default: 'OK') */
|
|
100
|
+
okLabel?: string;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Options for confirm dialog
|
|
105
|
+
*/
|
|
106
|
+
export type ConfirmOptions = AlertOptions & {
|
|
107
|
+
/** Confirm button label (default: 'OK') */
|
|
108
|
+
confirmLabel?: string;
|
|
109
|
+
/** Cancel button label (default: 'Cancel') */
|
|
110
|
+
cancelLabel?: string;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Options for prompt dialog
|
|
115
|
+
*/
|
|
116
|
+
export type PromptOptions = ConfirmOptions & {
|
|
117
|
+
/** Default input value */
|
|
118
|
+
defaultValue?: string;
|
|
119
|
+
/** Placeholder text for input */
|
|
120
|
+
placeholder?: string;
|
|
121
|
+
/** Input type (default: 'text') */
|
|
122
|
+
inputType?: "text" | "password" | "email" | "number";
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Internal state for dialog queue
|
|
127
|
+
*/
|
|
128
|
+
export type DialogQueueItem =
|
|
129
|
+
| { type: "alert"; options: AlertOptions; resolve: (value: void) => void }
|
|
130
|
+
| { type: "confirm"; options: ConfirmOptions; resolve: (value: boolean) => void }
|
|
131
|
+
| { type: "prompt"; options: PromptOptions; resolve: (value: string | null) => void };
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Return type of useDialog hook
|
|
135
|
+
*/
|
|
136
|
+
export type UseDialogReturn = {
|
|
137
|
+
/** Show an alert dialog */
|
|
138
|
+
alert: (options: AlertOptions | string) => Promise<void>;
|
|
139
|
+
/** Show a confirm dialog */
|
|
140
|
+
confirm: (options: ConfirmOptions | string) => Promise<boolean>;
|
|
141
|
+
/** Show a prompt dialog */
|
|
142
|
+
prompt: (options: PromptOptions | string) => Promise<string | null>;
|
|
143
|
+
/** Outlet component to render dialogs */
|
|
144
|
+
Outlet: React.FC;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Props for useDialog hook
|
|
149
|
+
*/
|
|
150
|
+
export type UseDialogProps = {
|
|
151
|
+
/**
|
|
152
|
+
* Custom component to render alert/confirm/prompt dialogs.
|
|
153
|
+
* When provided, replaces the default AlertDialog component.
|
|
154
|
+
*/
|
|
155
|
+
alertDialogComponent?: React.ComponentType<AlertDialogProps>;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Props for AlertDialog component (internal)
|
|
160
|
+
*/
|
|
161
|
+
export type AlertDialogProps = {
|
|
162
|
+
/** Dialog type */
|
|
163
|
+
type: "alert" | "confirm" | "prompt";
|
|
164
|
+
/** Whether the dialog is visible */
|
|
165
|
+
visible: boolean;
|
|
166
|
+
/** Title */
|
|
167
|
+
title?: string;
|
|
168
|
+
/** Message */
|
|
169
|
+
message: string;
|
|
170
|
+
/** OK/Confirm button label */
|
|
171
|
+
confirmLabel?: string;
|
|
172
|
+
/** Cancel button label */
|
|
173
|
+
cancelLabel?: string;
|
|
174
|
+
/** Placeholder for prompt input */
|
|
175
|
+
placeholder?: string;
|
|
176
|
+
/** Default value for prompt input */
|
|
177
|
+
defaultValue?: string;
|
|
178
|
+
/** Input type for prompt */
|
|
179
|
+
inputType?: "text" | "password" | "email" | "number";
|
|
180
|
+
/** Callback for confirm/OK action */
|
|
181
|
+
onConfirm: (value?: string) => void;
|
|
182
|
+
/** Callback for cancel action */
|
|
183
|
+
onCancel: () => void;
|
|
184
|
+
/** Whether the dialog can be dismissed by swiping. @default false for alert, true for confirm/prompt */
|
|
185
|
+
swipeDismissible?: boolean;
|
|
186
|
+
};
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Tests for useDialog hook
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { useDialog } from "./useDialog";
|
|
7
|
+
|
|
8
|
+
type CallTracker = {
|
|
9
|
+
calls: ReadonlyArray<ReadonlyArray<unknown>>;
|
|
10
|
+
fn: (...args: ReadonlyArray<unknown>) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const createCallTracker = (): CallTracker => {
|
|
14
|
+
const calls: Array<ReadonlyArray<unknown>> = [];
|
|
15
|
+
const fn = (...args: ReadonlyArray<unknown>): void => {
|
|
16
|
+
calls.push(args);
|
|
17
|
+
};
|
|
18
|
+
return { calls, fn };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
describe("useDialog", () => {
|
|
22
|
+
const originalShowModal = HTMLDialogElement.prototype.showModal;
|
|
23
|
+
const originalClose = HTMLDialogElement.prototype.close;
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
HTMLDialogElement.prototype.showModal = function (this: HTMLDialogElement) {
|
|
27
|
+
this.setAttribute("open", "");
|
|
28
|
+
};
|
|
29
|
+
HTMLDialogElement.prototype.close = function (this: HTMLDialogElement) {
|
|
30
|
+
this.removeAttribute("open");
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(() => {
|
|
35
|
+
HTMLDialogElement.prototype.showModal = originalShowModal;
|
|
36
|
+
HTMLDialogElement.prototype.close = originalClose;
|
|
37
|
+
document.body.style.overflow = "";
|
|
38
|
+
document.body.style.paddingRight = "";
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const TestComponent: React.FC<{
|
|
42
|
+
onAlert?: () => void;
|
|
43
|
+
onConfirm?: (result: boolean) => void;
|
|
44
|
+
onPrompt?: (result: string | null) => void;
|
|
45
|
+
}> = ({ onAlert, onConfirm, onPrompt }) => {
|
|
46
|
+
const { alert, confirm, prompt, Outlet } = useDialog();
|
|
47
|
+
|
|
48
|
+
const handleAlert = async () => {
|
|
49
|
+
await alert("Test alert message");
|
|
50
|
+
onAlert?.();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleConfirm = async () => {
|
|
54
|
+
const result = await confirm("Test confirm message");
|
|
55
|
+
onConfirm?.(result);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const handlePrompt = async () => {
|
|
59
|
+
const result = await prompt("Test prompt message");
|
|
60
|
+
onPrompt?.(result);
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<div>
|
|
65
|
+
<button data-testid="alert-btn" onClick={handleAlert}>
|
|
66
|
+
Alert
|
|
67
|
+
</button>
|
|
68
|
+
<button data-testid="confirm-btn" onClick={handleConfirm}>
|
|
69
|
+
Confirm
|
|
70
|
+
</button>
|
|
71
|
+
<button data-testid="prompt-btn" onClick={handlePrompt}>
|
|
72
|
+
Prompt
|
|
73
|
+
</button>
|
|
74
|
+
<Outlet />
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
describe("alert", () => {
|
|
80
|
+
it("should show alert dialog with message", async () => {
|
|
81
|
+
render(<TestComponent />);
|
|
82
|
+
|
|
83
|
+
fireEvent.click(screen.getByTestId("alert-btn"));
|
|
84
|
+
|
|
85
|
+
await waitFor(() => {
|
|
86
|
+
expect(screen.getByText("Test alert message")).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should resolve when OK is clicked", async () => {
|
|
91
|
+
const onAlert = createCallTracker();
|
|
92
|
+
render(<TestComponent onAlert={onAlert.fn} />);
|
|
93
|
+
|
|
94
|
+
fireEvent.click(screen.getByTestId("alert-btn"));
|
|
95
|
+
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
expect(screen.getByRole("button", { name: "OK" })).toBeInTheDocument();
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
101
|
+
|
|
102
|
+
await waitFor(() => {
|
|
103
|
+
expect(onAlert.calls).toHaveLength(1);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should accept string as shorthand", async () => {
|
|
108
|
+
const TestStringAlert: React.FC = () => {
|
|
109
|
+
const { alert, Outlet } = useDialog();
|
|
110
|
+
return (
|
|
111
|
+
<div>
|
|
112
|
+
<button onClick={() => alert("Simple message")}>Alert</button>
|
|
113
|
+
<Outlet />
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
render(<TestStringAlert />);
|
|
119
|
+
fireEvent.click(screen.getByRole("button", { name: "Alert" }));
|
|
120
|
+
|
|
121
|
+
await waitFor(() => {
|
|
122
|
+
expect(screen.getByText("Simple message")).toBeInTheDocument();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
describe("confirm", () => {
|
|
128
|
+
it("should show confirm dialog with message", async () => {
|
|
129
|
+
render(<TestComponent />);
|
|
130
|
+
|
|
131
|
+
fireEvent.click(screen.getByTestId("confirm-btn"));
|
|
132
|
+
|
|
133
|
+
await waitFor(() => {
|
|
134
|
+
expect(screen.getByText("Test confirm message")).toBeInTheDocument();
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("should resolve with true when OK is clicked", async () => {
|
|
139
|
+
const onConfirm = createCallTracker();
|
|
140
|
+
render(<TestComponent onConfirm={onConfirm.fn} />);
|
|
141
|
+
|
|
142
|
+
fireEvent.click(screen.getByTestId("confirm-btn"));
|
|
143
|
+
|
|
144
|
+
await waitFor(() => {
|
|
145
|
+
expect(screen.getByRole("button", { name: "OK" })).toBeInTheDocument();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
149
|
+
|
|
150
|
+
await waitFor(() => {
|
|
151
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
152
|
+
expect(onConfirm.calls[0]?.[0]).toBe(true);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should resolve with false when Cancel is clicked", async () => {
|
|
157
|
+
const onConfirm = createCallTracker();
|
|
158
|
+
render(<TestComponent onConfirm={onConfirm.fn} />);
|
|
159
|
+
|
|
160
|
+
fireEvent.click(screen.getByTestId("confirm-btn"));
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument();
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
167
|
+
|
|
168
|
+
await waitFor(() => {
|
|
169
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
170
|
+
expect(onConfirm.calls[0]?.[0]).toBe(false);
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("should resolve with false when backdrop is clicked", async () => {
|
|
175
|
+
const onConfirm = createCallTracker();
|
|
176
|
+
render(<TestComponent onConfirm={onConfirm.fn} />);
|
|
177
|
+
|
|
178
|
+
fireEvent.click(screen.getByTestId("confirm-btn"));
|
|
179
|
+
|
|
180
|
+
await waitFor(() => {
|
|
181
|
+
expect(screen.getByText("Test confirm message")).toBeInTheDocument();
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const dialog = document.querySelector("dialog");
|
|
185
|
+
fireEvent.click(dialog!);
|
|
186
|
+
|
|
187
|
+
await waitFor(() => {
|
|
188
|
+
expect(onConfirm.calls).toHaveLength(1);
|
|
189
|
+
expect(onConfirm.calls[0]?.[0]).toBe(false);
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
describe("prompt", () => {
|
|
195
|
+
it("should show prompt dialog with message and input", async () => {
|
|
196
|
+
render(<TestComponent />);
|
|
197
|
+
|
|
198
|
+
fireEvent.click(screen.getByTestId("prompt-btn"));
|
|
199
|
+
|
|
200
|
+
await waitFor(() => {
|
|
201
|
+
expect(screen.getByText("Test prompt message")).toBeInTheDocument();
|
|
202
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should resolve with input value when OK is clicked", async () => {
|
|
207
|
+
const onPrompt = createCallTracker();
|
|
208
|
+
render(<TestComponent onPrompt={onPrompt.fn} />);
|
|
209
|
+
|
|
210
|
+
fireEvent.click(screen.getByTestId("prompt-btn"));
|
|
211
|
+
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const input = screen.getByRole("textbox");
|
|
217
|
+
fireEvent.change(input, { target: { value: "Test input" } });
|
|
218
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
219
|
+
|
|
220
|
+
await waitFor(() => {
|
|
221
|
+
expect(onPrompt.calls).toHaveLength(1);
|
|
222
|
+
expect(onPrompt.calls[0]?.[0]).toBe("Test input");
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("should resolve with null when Cancel is clicked", async () => {
|
|
227
|
+
const onPrompt = createCallTracker();
|
|
228
|
+
render(<TestComponent onPrompt={onPrompt.fn} />);
|
|
229
|
+
|
|
230
|
+
fireEvent.click(screen.getByTestId("prompt-btn"));
|
|
231
|
+
|
|
232
|
+
await waitFor(() => {
|
|
233
|
+
expect(screen.getByRole("button", { name: "Cancel" })).toBeInTheDocument();
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
237
|
+
|
|
238
|
+
await waitFor(() => {
|
|
239
|
+
expect(onPrompt.calls).toHaveLength(1);
|
|
240
|
+
expect(onPrompt.calls[0]?.[0]).toBe(null);
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it("should resolve with null when backdrop is clicked", async () => {
|
|
245
|
+
const onPrompt = createCallTracker();
|
|
246
|
+
render(<TestComponent onPrompt={onPrompt.fn} />);
|
|
247
|
+
|
|
248
|
+
fireEvent.click(screen.getByTestId("prompt-btn"));
|
|
249
|
+
|
|
250
|
+
await waitFor(() => {
|
|
251
|
+
expect(screen.getByRole("textbox")).toBeInTheDocument();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const dialog = document.querySelector("dialog");
|
|
255
|
+
fireEvent.click(dialog!);
|
|
256
|
+
|
|
257
|
+
await waitFor(() => {
|
|
258
|
+
expect(onPrompt.calls).toHaveLength(1);
|
|
259
|
+
expect(onPrompt.calls[0]?.[0]).toBe(null);
|
|
260
|
+
});
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("queue behavior", () => {
|
|
265
|
+
it("should queue multiple dialogs and show one at a time", async () => {
|
|
266
|
+
const results: string[] = [];
|
|
267
|
+
|
|
268
|
+
const QueueTest: React.FC = () => {
|
|
269
|
+
const { alert, Outlet } = useDialog();
|
|
270
|
+
|
|
271
|
+
const handleClick = async () => {
|
|
272
|
+
await alert("First");
|
|
273
|
+
results.push("first done");
|
|
274
|
+
await alert("Second");
|
|
275
|
+
results.push("second done");
|
|
276
|
+
await alert("Third");
|
|
277
|
+
results.push("third done");
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
return (
|
|
281
|
+
<div>
|
|
282
|
+
<button onClick={handleClick}>Show All</button>
|
|
283
|
+
<Outlet />
|
|
284
|
+
</div>
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
render(<QueueTest />);
|
|
289
|
+
|
|
290
|
+
fireEvent.click(screen.getByRole("button", { name: "Show All" }));
|
|
291
|
+
|
|
292
|
+
// First dialog
|
|
293
|
+
await waitFor(() => {
|
|
294
|
+
expect(screen.getByText("First")).toBeInTheDocument();
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
298
|
+
|
|
299
|
+
// Second dialog
|
|
300
|
+
await waitFor(() => {
|
|
301
|
+
expect(screen.getByText("Second")).toBeInTheDocument();
|
|
302
|
+
});
|
|
303
|
+
expect(results).toContain("first done");
|
|
304
|
+
|
|
305
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
306
|
+
|
|
307
|
+
// Third dialog
|
|
308
|
+
await waitFor(() => {
|
|
309
|
+
expect(screen.getByText("Third")).toBeInTheDocument();
|
|
310
|
+
});
|
|
311
|
+
expect(results).toContain("second done");
|
|
312
|
+
|
|
313
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
314
|
+
|
|
315
|
+
await waitFor(() => {
|
|
316
|
+
expect(results).toContain("third done");
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should handle multiple dialogs triggered simultaneously", async () => {
|
|
321
|
+
const results: (boolean | string | null)[] = [];
|
|
322
|
+
|
|
323
|
+
const SimultaneousTest: React.FC = () => {
|
|
324
|
+
const { confirm, prompt, Outlet } = useDialog();
|
|
325
|
+
|
|
326
|
+
const handleClick = () => {
|
|
327
|
+
confirm("First?").then((r) => results.push(r));
|
|
328
|
+
prompt("Second?").then((r) => results.push(r));
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
return (
|
|
332
|
+
<div>
|
|
333
|
+
<button onClick={handleClick}>Show All</button>
|
|
334
|
+
<Outlet />
|
|
335
|
+
</div>
|
|
336
|
+
);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
render(<SimultaneousTest />);
|
|
340
|
+
|
|
341
|
+
fireEvent.click(screen.getByRole("button", { name: "Show All" }));
|
|
342
|
+
|
|
343
|
+
// First dialog (confirm)
|
|
344
|
+
await waitFor(() => {
|
|
345
|
+
expect(screen.getByText("First?")).toBeInTheDocument();
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
fireEvent.click(screen.getByRole("button", { name: "OK" }));
|
|
349
|
+
|
|
350
|
+
// Second dialog (prompt)
|
|
351
|
+
await waitFor(() => {
|
|
352
|
+
expect(screen.getByText("Second?")).toBeInTheDocument();
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
expect(results[0]).toBe(true);
|
|
356
|
+
|
|
357
|
+
fireEvent.click(screen.getByRole("button", { name: "Cancel" }));
|
|
358
|
+
|
|
359
|
+
await waitFor(() => {
|
|
360
|
+
expect(results[1]).toBeNull();
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("options", () => {
|
|
366
|
+
it("should use custom title", async () => {
|
|
367
|
+
const TestTitle: React.FC = () => {
|
|
368
|
+
const { alert, Outlet } = useDialog();
|
|
369
|
+
return (
|
|
370
|
+
<div>
|
|
371
|
+
<button onClick={() => alert({ title: "Custom Title", message: "Message" })}>Alert</button>
|
|
372
|
+
<Outlet />
|
|
373
|
+
</div>
|
|
374
|
+
);
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
render(<TestTitle />);
|
|
378
|
+
fireEvent.click(screen.getByRole("button", { name: "Alert" }));
|
|
379
|
+
|
|
380
|
+
await waitFor(() => {
|
|
381
|
+
expect(screen.getByText("Custom Title")).toBeInTheDocument();
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("should use custom button labels", async () => {
|
|
386
|
+
const TestLabels: React.FC = () => {
|
|
387
|
+
const { confirm, Outlet } = useDialog();
|
|
388
|
+
return (
|
|
389
|
+
<div>
|
|
390
|
+
<button
|
|
391
|
+
onClick={() =>
|
|
392
|
+
confirm({
|
|
393
|
+
message: "Confirm?",
|
|
394
|
+
confirmLabel: "Yes",
|
|
395
|
+
cancelLabel: "No",
|
|
396
|
+
})
|
|
397
|
+
}
|
|
398
|
+
>
|
|
399
|
+
Confirm
|
|
400
|
+
</button>
|
|
401
|
+
<Outlet />
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
render(<TestLabels />);
|
|
407
|
+
fireEvent.click(screen.getByRole("button", { name: "Confirm" }));
|
|
408
|
+
|
|
409
|
+
await waitFor(() => {
|
|
410
|
+
expect(screen.getByRole("button", { name: "Yes" })).toBeInTheDocument();
|
|
411
|
+
expect(screen.getByRole("button", { name: "No" })).toBeInTheDocument();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("should use prompt options", async () => {
|
|
416
|
+
const TestPromptOptions: React.FC = () => {
|
|
417
|
+
const { prompt, Outlet } = useDialog();
|
|
418
|
+
return (
|
|
419
|
+
<div>
|
|
420
|
+
<button
|
|
421
|
+
onClick={() =>
|
|
422
|
+
prompt({
|
|
423
|
+
message: "Enter:",
|
|
424
|
+
defaultValue: "default",
|
|
425
|
+
placeholder: "placeholder",
|
|
426
|
+
inputType: "password",
|
|
427
|
+
})
|
|
428
|
+
}
|
|
429
|
+
>
|
|
430
|
+
Prompt
|
|
431
|
+
</button>
|
|
432
|
+
<Outlet />
|
|
433
|
+
</div>
|
|
434
|
+
);
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
render(<TestPromptOptions />);
|
|
438
|
+
fireEvent.click(screen.getByRole("button", { name: "Prompt" }));
|
|
439
|
+
|
|
440
|
+
await waitFor(() => {
|
|
441
|
+
const input = screen.getByPlaceholderText("placeholder") as HTMLInputElement;
|
|
442
|
+
expect(input.value).toBe("default");
|
|
443
|
+
expect(input.type).toBe("password");
|
|
444
|
+
});
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
});
|