@transferwise/components 46.167.0 → 46.168.0

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.
Files changed (173) hide show
  1. package/README.md +1 -1
  2. package/build/Link/Link.js.map +1 -1
  3. package/build/Link/Link.mjs.map +1 -1
  4. package/build/Loader/Loader.js.map +1 -1
  5. package/build/Loader/Loader.mjs.map +1 -1
  6. package/build/Prompt/PrimitivePrompt/PrimitivePrompt.js +2 -1
  7. package/build/Prompt/PrimitivePrompt/PrimitivePrompt.js.map +1 -1
  8. package/build/Prompt/PrimitivePrompt/PrimitivePrompt.mjs +2 -1
  9. package/build/Prompt/PrimitivePrompt/PrimitivePrompt.mjs.map +1 -1
  10. package/build/Prompt/Snackbar/Snackbar.js +205 -0
  11. package/build/Prompt/Snackbar/Snackbar.js.map +1 -0
  12. package/build/Prompt/Snackbar/Snackbar.messages.js +14 -0
  13. package/build/Prompt/Snackbar/Snackbar.messages.js.map +1 -0
  14. package/build/Prompt/Snackbar/Snackbar.messages.mjs +10 -0
  15. package/build/Prompt/Snackbar/Snackbar.messages.mjs.map +1 -0
  16. package/build/Prompt/Snackbar/Snackbar.mjs +203 -0
  17. package/build/Prompt/Snackbar/Snackbar.mjs.map +1 -0
  18. package/build/Prompt/Snackbar/SnackbarContext.js.map +1 -0
  19. package/build/Prompt/Snackbar/SnackbarContext.mjs.map +1 -0
  20. package/build/Prompt/Snackbar/SnackbarPortal.js +12 -0
  21. package/build/Prompt/Snackbar/SnackbarPortal.js.map +1 -0
  22. package/build/Prompt/Snackbar/SnackbarPortal.mjs +8 -0
  23. package/build/Prompt/Snackbar/SnackbarPortal.mjs.map +1 -0
  24. package/build/Prompt/Snackbar/SnackbarProvider.js +42 -0
  25. package/build/Prompt/Snackbar/SnackbarProvider.js.map +1 -0
  26. package/build/Prompt/Snackbar/SnackbarProvider.mjs +38 -0
  27. package/build/Prompt/Snackbar/SnackbarProvider.mjs.map +1 -0
  28. package/build/Prompt/Snackbar/constants.js +17 -0
  29. package/build/Prompt/Snackbar/constants.js.map +1 -0
  30. package/build/Prompt/Snackbar/constants.mjs +11 -0
  31. package/build/Prompt/Snackbar/constants.mjs.map +1 -0
  32. package/build/Prompt/Snackbar/useSnackbar.js.map +1 -0
  33. package/build/Prompt/Snackbar/useSnackbar.mjs.map +1 -0
  34. package/build/Prompt/Snackbar/useSnackbarAutoDismiss.js +66 -0
  35. package/build/Prompt/Snackbar/useSnackbarAutoDismiss.js.map +1 -0
  36. package/build/Prompt/Snackbar/useSnackbarAutoDismiss.mjs +62 -0
  37. package/build/Prompt/Snackbar/useSnackbarAutoDismiss.mjs.map +1 -0
  38. package/build/Prompt/Snackbar/useSnackbarManager.js +153 -0
  39. package/build/Prompt/Snackbar/useSnackbarManager.js.map +1 -0
  40. package/build/Prompt/Snackbar/useSnackbarManager.mjs +149 -0
  41. package/build/Prompt/Snackbar/useSnackbarManager.mjs.map +1 -0
  42. package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.js +135 -0
  43. package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.js.map +1 -0
  44. package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.mjs +131 -0
  45. package/build/Prompt/Snackbar/useSnackbarSwipeDismiss.mjs.map +1 -0
  46. package/build/Section/Section.js.map +1 -1
  47. package/build/Section/Section.mjs.map +1 -1
  48. package/build/_deprecated/Select/Select.js +1 -1
  49. package/build/_deprecated/Select/Select.js.map +1 -1
  50. package/build/_deprecated/Select/Select.mjs +1 -1
  51. package/build/_deprecated/Select/Select.mjs.map +1 -1
  52. package/build/common/BottomSheet/BottomSheet.js +1 -3
  53. package/build/common/BottomSheet/BottomSheet.js.map +1 -1
  54. package/build/common/BottomSheet/BottomSheet.mjs +1 -3
  55. package/build/common/BottomSheet/BottomSheet.mjs.map +1 -1
  56. package/build/i18n/en.json +1 -0
  57. package/build/i18n/en.json.js +1 -0
  58. package/build/i18n/en.json.js.map +1 -1
  59. package/build/i18n/en.json.mjs +1 -0
  60. package/build/i18n/en.json.mjs.map +1 -1
  61. package/build/index.js +3 -3
  62. package/build/index.mjs +3 -3
  63. package/build/main.css +137 -75
  64. package/build/styles/IconButton/IconButton.css +3 -4
  65. package/build/styles/Prompt/Snackbar/Snackbar.css +134 -0
  66. package/build/styles/main.css +137 -75
  67. package/build/types/Link/Link.d.ts +1 -1
  68. package/build/types/Loader/Loader.d.ts +1 -1
  69. package/build/types/Prompt/PrimitivePrompt/PrimitivePrompt.d.ts +11 -0
  70. package/build/types/Prompt/PrimitivePrompt/PrimitivePrompt.d.ts.map +1 -1
  71. package/build/types/Prompt/Snackbar/Snackbar.d.ts +62 -0
  72. package/build/types/Prompt/Snackbar/Snackbar.d.ts.map +1 -0
  73. package/build/types/Prompt/Snackbar/Snackbar.messages.d.ts +9 -0
  74. package/build/types/Prompt/Snackbar/Snackbar.messages.d.ts.map +1 -0
  75. package/build/types/Prompt/Snackbar/Snackbar.types.d.ts +12 -0
  76. package/build/types/Prompt/Snackbar/Snackbar.types.d.ts.map +1 -0
  77. package/build/types/{Snackbar → Prompt/Snackbar}/SnackbarContext.d.ts +2 -2
  78. package/build/types/Prompt/Snackbar/SnackbarContext.d.ts.map +1 -0
  79. package/build/types/Prompt/Snackbar/SnackbarPortal.d.ts +4 -0
  80. package/build/types/Prompt/Snackbar/SnackbarPortal.d.ts.map +1 -0
  81. package/build/types/Prompt/Snackbar/SnackbarProvider.d.ts +13 -0
  82. package/build/types/Prompt/Snackbar/SnackbarProvider.d.ts.map +1 -0
  83. package/build/types/Prompt/Snackbar/constants.d.ts +9 -0
  84. package/build/types/Prompt/Snackbar/constants.d.ts.map +1 -0
  85. package/build/types/{Snackbar → Prompt/Snackbar}/index.d.ts +1 -1
  86. package/build/types/Prompt/Snackbar/index.d.ts.map +1 -0
  87. package/build/types/Prompt/Snackbar/useSnackbar.d.ts +3 -0
  88. package/build/types/Prompt/Snackbar/useSnackbar.d.ts.map +1 -0
  89. package/build/types/Prompt/Snackbar/useSnackbarAutoDismiss.d.ts +13 -0
  90. package/build/types/Prompt/Snackbar/useSnackbarAutoDismiss.d.ts.map +1 -0
  91. package/build/types/Prompt/Snackbar/useSnackbarManager.d.ts +20 -0
  92. package/build/types/Prompt/Snackbar/useSnackbarManager.d.ts.map +1 -0
  93. package/build/types/Prompt/Snackbar/useSnackbarSwipeDismiss.d.ts +22 -0
  94. package/build/types/Prompt/Snackbar/useSnackbarSwipeDismiss.d.ts.map +1 -0
  95. package/build/types/Section/Section.d.ts +1 -1
  96. package/build/types/_deprecated/Select/Select.d.ts +1 -1
  97. package/build/types/common/BottomSheet/BottomSheet.d.ts +1 -3
  98. package/build/types/common/BottomSheet/BottomSheet.d.ts.map +1 -1
  99. package/build/types/index.d.ts +5 -5
  100. package/build/types/index.d.ts.map +1 -1
  101. package/build/types/test-utils/index.d.ts +2 -0
  102. package/build/types/test-utils/index.d.ts.map +1 -1
  103. package/package.json +2 -2
  104. package/src/IconButton/IconButton.css +3 -4
  105. package/src/IconButton/IconButton.less +14 -1
  106. package/src/Link/Link.tsx +1 -1
  107. package/src/ListItem/AdditionalInfo/_stories/ListItemAdditionalInfo.story.tsx +1 -1
  108. package/src/ListItem/AvatarView/_stories/ListItemAvatarView.story.tsx +3 -3
  109. package/src/ListItem/_stories/ListItem.disabled.story.tsx +1 -1
  110. package/src/ListItem/_stories/ListItem.scenarios.story.tsx +2 -2
  111. package/src/Loader/Loader.tsx +1 -1
  112. package/src/Prompt/PrimitivePrompt/PrimitivePrompt.test.tsx +24 -1
  113. package/src/Prompt/PrimitivePrompt/PrimitivePrompt.tsx +8 -2
  114. package/src/Prompt/Snackbar/Snackbar.css +134 -0
  115. package/src/Prompt/Snackbar/Snackbar.less +159 -0
  116. package/src/Prompt/Snackbar/Snackbar.messages.ts +9 -0
  117. package/src/Prompt/Snackbar/Snackbar.test.tsx +1079 -0
  118. package/src/Prompt/Snackbar/Snackbar.tsx +245 -0
  119. package/src/Prompt/Snackbar/Snackbar.types.ts +17 -0
  120. package/src/{Snackbar → Prompt/Snackbar}/SnackbarContext.ts +2 -2
  121. package/src/Prompt/Snackbar/SnackbarPortal.tsx +8 -0
  122. package/src/Prompt/Snackbar/SnackbarProvider.test.tsx +783 -0
  123. package/src/Prompt/Snackbar/SnackbarProvider.tsx +41 -0
  124. package/src/Prompt/Snackbar/_stories/Snackbar.accessibility.docs.mdx +58 -0
  125. package/src/{Snackbar → Prompt/Snackbar}/_stories/Snackbar.rtl.test.story.tsx +17 -9
  126. package/src/Prompt/Snackbar/_stories/Snackbar.story.tsx +416 -0
  127. package/src/Prompt/Snackbar/_stories/Snackbar.test.story.tsx +263 -0
  128. package/src/Prompt/Snackbar/constants.ts +10 -0
  129. package/src/{Snackbar → Prompt/Snackbar}/index.ts +1 -1
  130. package/src/Prompt/Snackbar/useSnackbarAutoDismiss.ts +74 -0
  131. package/src/Prompt/Snackbar/useSnackbarManager.ts +169 -0
  132. package/src/Prompt/Snackbar/useSnackbarSwipeDismiss.ts +169 -0
  133. package/src/Section/Section.tsx +1 -1
  134. package/src/_deprecated/Select/Select.tsx +1 -1
  135. package/src/_deprecated/Select/_stories/Select.story.tsx +1 -1
  136. package/src/common/BottomSheet/BottomSheet.tsx +1 -3
  137. package/src/i18n/en.json +1 -0
  138. package/src/index.ts +5 -5
  139. package/src/main.css +137 -75
  140. package/src/main.less +1 -1
  141. package/build/Snackbar/Snackbar.js +0 -128
  142. package/build/Snackbar/Snackbar.js.map +0 -1
  143. package/build/Snackbar/Snackbar.mjs +0 -123
  144. package/build/Snackbar/Snackbar.mjs.map +0 -1
  145. package/build/Snackbar/SnackbarContext.js.map +0 -1
  146. package/build/Snackbar/SnackbarContext.mjs.map +0 -1
  147. package/build/Snackbar/SnackbarProvider.js +0 -46
  148. package/build/Snackbar/SnackbarProvider.js.map +0 -1
  149. package/build/Snackbar/SnackbarProvider.mjs +0 -42
  150. package/build/Snackbar/SnackbarProvider.mjs.map +0 -1
  151. package/build/Snackbar/useSnackbar.js.map +0 -1
  152. package/build/Snackbar/useSnackbar.mjs.map +0 -1
  153. package/build/styles/Snackbar/Snackbar.css +0 -71
  154. package/build/types/Snackbar/Snackbar.d.ts +0 -16
  155. package/build/types/Snackbar/Snackbar.d.ts.map +0 -1
  156. package/build/types/Snackbar/SnackbarContext.d.ts.map +0 -1
  157. package/build/types/Snackbar/SnackbarProvider.d.ts +0 -6
  158. package/build/types/Snackbar/SnackbarProvider.d.ts.map +0 -1
  159. package/build/types/Snackbar/index.d.ts.map +0 -1
  160. package/build/types/Snackbar/useSnackbar.d.ts +0 -3
  161. package/build/types/Snackbar/useSnackbar.d.ts.map +0 -1
  162. package/src/Snackbar/Snackbar.css +0 -71
  163. package/src/Snackbar/Snackbar.less +0 -80
  164. package/src/Snackbar/Snackbar.test.tsx +0 -206
  165. package/src/Snackbar/Snackbar.tsx +0 -129
  166. package/src/Snackbar/SnackbarProvider.tsx +0 -33
  167. package/src/Snackbar/_stories/Snackbar.story.tsx +0 -89
  168. package/src/Snackbar/_stories/Snackbar.test.story.tsx +0 -139
  169. /package/build/{Snackbar → Prompt/Snackbar}/SnackbarContext.js +0 -0
  170. /package/build/{Snackbar → Prompt/Snackbar}/SnackbarContext.mjs +0 -0
  171. /package/build/{Snackbar → Prompt/Snackbar}/useSnackbar.js +0 -0
  172. /package/build/{Snackbar → Prompt/Snackbar}/useSnackbar.mjs +0 -0
  173. /package/src/{Snackbar → Prompt/Snackbar}/useSnackbar.ts +0 -0
@@ -0,0 +1,153 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+
7
+ const emptyState = {
8
+ active: null,
9
+ activeHasRendered: false,
10
+ announcementKey: 0,
11
+ pending: null,
12
+ visible: false
13
+ };
14
+ /**
15
+ * Coalesces creates before the first commit, then keeps active content until its exit finishes.
16
+ * Only the latest pending replacement is retained. Exited promotes pending or returns to idle.
17
+ * Instance IDs prevent stale exit callbacks from affecting a newer Snackbar.
18
+ */
19
+ const snackbarManagerReducer = (state, action) => {
20
+ switch (action.type) {
21
+ case 'show':
22
+ if (!state.active || !state.activeHasRendered) {
23
+ // Batched creates collapse to the latest instance before anything has entered.
24
+ return {
25
+ active: action.instance,
26
+ activeHasRendered: false,
27
+ announcementKey: state.announcementKey + 1,
28
+ pending: null,
29
+ visible: true
30
+ };
31
+ }
32
+ // Keep the active content until it exits; newer creates replace only the pending instance.
33
+ return {
34
+ ...state,
35
+ pending: action.instance,
36
+ visible: false
37
+ };
38
+ case 'rendered':
39
+ if (state.active?.id !== action.instanceId || state.activeHasRendered) {
40
+ return state;
41
+ }
42
+ return {
43
+ ...state,
44
+ activeHasRendered: true
45
+ };
46
+ case 'dismiss':
47
+ case 'clear':
48
+ if (!state.active) {
49
+ return state;
50
+ }
51
+ if (!state.activeHasRendered) {
52
+ return {
53
+ ...emptyState,
54
+ announcementKey: state.announcementKey
55
+ };
56
+ }
57
+ // Dismissal lets a queued replacement show next; clearing cancels it as well.
58
+ return {
59
+ ...state,
60
+ pending: action.type === 'clear' ? null : state.pending,
61
+ visible: false
62
+ };
63
+ case 'exited':
64
+ // Ignore callbacks from an instance that has already been replaced.
65
+ if (state.active?.id !== action.instanceId) {
66
+ return state;
67
+ }
68
+ if (state.pending) {
69
+ return {
70
+ active: state.pending,
71
+ activeHasRendered: false,
72
+ announcementKey: state.announcementKey + 1,
73
+ pending: null,
74
+ visible: true
75
+ };
76
+ }
77
+ return {
78
+ ...emptyState,
79
+ announcementKey: state.announcementKey
80
+ };
81
+ default:
82
+ return state;
83
+ }
84
+ };
85
+ /**
86
+ * Owns latest-wins replacement through reducer actions, separate from rendering and animation.
87
+ * Stable create/dismiss callbacks request state changes; the surface's onExited callback completes
88
+ * dismissal. The provider uses this state to mount/unmount the portal without a notification queue.
89
+ */
90
+ const useSnackbarManager = () => {
91
+ const [state, dispatch] = React.useReducer(snackbarManagerReducer, emptyState);
92
+ const nextInstanceId = React.useRef(0);
93
+ const createSnackbar = React.useCallback(props => {
94
+ const {
95
+ text
96
+ } = props;
97
+ if (text == null || typeof text === 'boolean' || typeof text === 'string' && text.trim() === '') {
98
+ // A clear request must dismiss active and cancel pending, never create a blank replacement.
99
+ dispatch({
100
+ type: 'clear'
101
+ });
102
+ return;
103
+ }
104
+ nextInstanceId.current += 1;
105
+ dispatch({
106
+ type: 'show',
107
+ instance: {
108
+ id: nextInstanceId.current,
109
+ props
110
+ }
111
+ });
112
+ }, []);
113
+ const dismissSnackbar = React.useCallback(() => dispatch({
114
+ type: 'dismiss'
115
+ }), []);
116
+ const handleExited = React.useCallback(() => {
117
+ if (!state.active) {
118
+ return;
119
+ }
120
+ try {
121
+ state.active.props.onDismiss?.();
122
+ } catch (error) {
123
+ // This runs in a transition commit; an escaping error could unmount the provider.
124
+ // eslint-disable-next-line no-console
125
+ console.error('Snackbar onDismiss callback failed:', error);
126
+ } finally {
127
+ dispatch({
128
+ type: 'exited',
129
+ instanceId: state.active.id
130
+ });
131
+ }
132
+ }, [state.active]);
133
+ React.useEffect(() => {
134
+ if (state.active && state.visible && !state.activeHasRendered) {
135
+ // Later creates can now wait for this instance's exit instead of coalescing before render.
136
+ dispatch({
137
+ type: 'rendered',
138
+ instanceId: state.active.id
139
+ });
140
+ }
141
+ }, [state.active, state.activeHasRendered, state.visible]);
142
+ return {
143
+ active: state.active,
144
+ announcementKey: state.announcementKey,
145
+ createSnackbar,
146
+ dismissSnackbar,
147
+ handleExited,
148
+ visible: state.visible
149
+ };
150
+ };
151
+
152
+ exports.default = useSnackbarManager;
153
+ //# sourceMappingURL=useSnackbarManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSnackbarManager.js","sources":["../../../src/Prompt/Snackbar/useSnackbarManager.ts"],"sourcesContent":["import { useCallback, useEffect, useReducer, useRef } from 'react';\n\nimport type { SnackbarCreationProps } from './Snackbar.types';\n\ntype SnackbarInstance = {\n id: number;\n props: SnackbarCreationProps;\n};\n\ntype SnackbarManagerState = {\n /** The instance currently entering, visible or exiting. `null` means the provider is idle. */\n active: SnackbarInstance | null;\n /** Marks the active provider render as committed, not its entry animation as finished. */\n activeHasRendered: boolean;\n /** Changes when a new instance becomes active so repeated message text can be announced. */\n announcementKey: number;\n /** The latest replacement waiting for active to exit. `null` means no replacement is waiting. */\n pending: SnackbarInstance | null;\n /** Whether active should enter/stay visible; false requests its exit or indicates idle state. */\n visible: boolean;\n};\n\ntype SnackbarManagerAction =\n | { type: 'show'; instance: SnackbarInstance }\n | {\n /** Ends pre-render coalescing; subsequent creates must wait for the committed instance to exit. */\n type: 'rendered';\n instanceId: number;\n }\n | { type: 'dismiss' }\n | {\n /** Like `dismiss`, but also cancels a queued replacement. Sent when `createSnackbar` gets empty text. */\n type: 'clear';\n }\n | { type: 'exited'; instanceId: number };\n\nconst emptyState: SnackbarManagerState = {\n active: null,\n activeHasRendered: false,\n announcementKey: 0,\n pending: null,\n visible: false,\n};\n\n/**\n * Coalesces creates before the first commit, then keeps active content until its exit finishes.\n * Only the latest pending replacement is retained. Exited promotes pending or returns to idle.\n * Instance IDs prevent stale exit callbacks from affecting a newer Snackbar.\n */\nconst snackbarManagerReducer = (\n state: SnackbarManagerState,\n action: SnackbarManagerAction,\n): SnackbarManagerState => {\n switch (action.type) {\n case 'show':\n if (!state.active || !state.activeHasRendered) {\n // Batched creates collapse to the latest instance before anything has entered.\n return {\n active: action.instance,\n activeHasRendered: false,\n announcementKey: state.announcementKey + 1,\n pending: null,\n visible: true,\n };\n }\n\n // Keep the active content until it exits; newer creates replace only the pending instance.\n return { ...state, pending: action.instance, visible: false };\n case 'rendered':\n if (state.active?.id !== action.instanceId || state.activeHasRendered) {\n return state;\n }\n\n return { ...state, activeHasRendered: true };\n case 'dismiss':\n case 'clear':\n if (!state.active) {\n return state;\n }\n\n if (!state.activeHasRendered) {\n return { ...emptyState, announcementKey: state.announcementKey };\n }\n\n // Dismissal lets a queued replacement show next; clearing cancels it as well.\n return { ...state, pending: action.type === 'clear' ? null : state.pending, visible: false };\n case 'exited':\n // Ignore callbacks from an instance that has already been replaced.\n if (state.active?.id !== action.instanceId) {\n return state;\n }\n\n if (state.pending) {\n return {\n active: state.pending,\n activeHasRendered: false,\n announcementKey: state.announcementKey + 1,\n pending: null,\n visible: true,\n };\n }\n\n return { ...emptyState, announcementKey: state.announcementKey };\n default:\n return state;\n }\n};\n\n/**\n * Owns latest-wins replacement through reducer actions, separate from rendering and animation.\n * Stable create/dismiss callbacks request state changes; the surface's onExited callback completes\n * dismissal. The provider uses this state to mount/unmount the portal without a notification queue.\n */\nconst useSnackbarManager = () => {\n const [state, dispatch] = useReducer(snackbarManagerReducer, emptyState);\n const nextInstanceId = useRef(0);\n\n const createSnackbar = useCallback((props: SnackbarCreationProps) => {\n const { text } = props;\n if (\n text == null ||\n typeof text === 'boolean' ||\n (typeof text === 'string' && text.trim() === '')\n ) {\n // A clear request must dismiss active and cancel pending, never create a blank replacement.\n dispatch({ type: 'clear' });\n return;\n }\n\n nextInstanceId.current += 1;\n dispatch({ type: 'show', instance: { id: nextInstanceId.current, props } });\n }, []);\n\n const dismissSnackbar = useCallback(() => dispatch({ type: 'dismiss' }), []);\n\n const handleExited = useCallback(() => {\n if (!state.active) {\n return;\n }\n\n try {\n state.active.props.onDismiss?.();\n } catch (error) {\n // This runs in a transition commit; an escaping error could unmount the provider.\n // eslint-disable-next-line no-console\n console.error('Snackbar onDismiss callback failed:', error);\n } finally {\n dispatch({ type: 'exited', instanceId: state.active.id });\n }\n }, [state.active]);\n\n useEffect(() => {\n if (state.active && state.visible && !state.activeHasRendered) {\n // Later creates can now wait for this instance's exit instead of coalescing before render.\n dispatch({ type: 'rendered', instanceId: state.active.id });\n }\n }, [state.active, state.activeHasRendered, state.visible]);\n\n return {\n active: state.active,\n announcementKey: state.announcementKey,\n createSnackbar,\n dismissSnackbar,\n handleExited,\n visible: state.visible,\n };\n};\n\nexport default useSnackbarManager;\n"],"names":["emptyState","active","activeHasRendered","announcementKey","pending","visible","snackbarManagerReducer","state","action","type","instance","id","instanceId","useSnackbarManager","dispatch","useReducer","nextInstanceId","useRef","createSnackbar","useCallback","props","text","trim","current","dismissSnackbar","handleExited","onDismiss","error","console","useEffect"],"mappings":"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"}
@@ -0,0 +1,149 @@
1
+ import { useReducer, useRef, useCallback, useEffect } from 'react';
2
+
3
+ const emptyState = {
4
+ active: null,
5
+ activeHasRendered: false,
6
+ announcementKey: 0,
7
+ pending: null,
8
+ visible: false
9
+ };
10
+ /**
11
+ * Coalesces creates before the first commit, then keeps active content until its exit finishes.
12
+ * Only the latest pending replacement is retained. Exited promotes pending or returns to idle.
13
+ * Instance IDs prevent stale exit callbacks from affecting a newer Snackbar.
14
+ */
15
+ const snackbarManagerReducer = (state, action) => {
16
+ switch (action.type) {
17
+ case 'show':
18
+ if (!state.active || !state.activeHasRendered) {
19
+ // Batched creates collapse to the latest instance before anything has entered.
20
+ return {
21
+ active: action.instance,
22
+ activeHasRendered: false,
23
+ announcementKey: state.announcementKey + 1,
24
+ pending: null,
25
+ visible: true
26
+ };
27
+ }
28
+ // Keep the active content until it exits; newer creates replace only the pending instance.
29
+ return {
30
+ ...state,
31
+ pending: action.instance,
32
+ visible: false
33
+ };
34
+ case 'rendered':
35
+ if (state.active?.id !== action.instanceId || state.activeHasRendered) {
36
+ return state;
37
+ }
38
+ return {
39
+ ...state,
40
+ activeHasRendered: true
41
+ };
42
+ case 'dismiss':
43
+ case 'clear':
44
+ if (!state.active) {
45
+ return state;
46
+ }
47
+ if (!state.activeHasRendered) {
48
+ return {
49
+ ...emptyState,
50
+ announcementKey: state.announcementKey
51
+ };
52
+ }
53
+ // Dismissal lets a queued replacement show next; clearing cancels it as well.
54
+ return {
55
+ ...state,
56
+ pending: action.type === 'clear' ? null : state.pending,
57
+ visible: false
58
+ };
59
+ case 'exited':
60
+ // Ignore callbacks from an instance that has already been replaced.
61
+ if (state.active?.id !== action.instanceId) {
62
+ return state;
63
+ }
64
+ if (state.pending) {
65
+ return {
66
+ active: state.pending,
67
+ activeHasRendered: false,
68
+ announcementKey: state.announcementKey + 1,
69
+ pending: null,
70
+ visible: true
71
+ };
72
+ }
73
+ return {
74
+ ...emptyState,
75
+ announcementKey: state.announcementKey
76
+ };
77
+ default:
78
+ return state;
79
+ }
80
+ };
81
+ /**
82
+ * Owns latest-wins replacement through reducer actions, separate from rendering and animation.
83
+ * Stable create/dismiss callbacks request state changes; the surface's onExited callback completes
84
+ * dismissal. The provider uses this state to mount/unmount the portal without a notification queue.
85
+ */
86
+ const useSnackbarManager = () => {
87
+ const [state, dispatch] = useReducer(snackbarManagerReducer, emptyState);
88
+ const nextInstanceId = useRef(0);
89
+ const createSnackbar = useCallback(props => {
90
+ const {
91
+ text
92
+ } = props;
93
+ if (text == null || typeof text === 'boolean' || typeof text === 'string' && text.trim() === '') {
94
+ // A clear request must dismiss active and cancel pending, never create a blank replacement.
95
+ dispatch({
96
+ type: 'clear'
97
+ });
98
+ return;
99
+ }
100
+ nextInstanceId.current += 1;
101
+ dispatch({
102
+ type: 'show',
103
+ instance: {
104
+ id: nextInstanceId.current,
105
+ props
106
+ }
107
+ });
108
+ }, []);
109
+ const dismissSnackbar = useCallback(() => dispatch({
110
+ type: 'dismiss'
111
+ }), []);
112
+ const handleExited = useCallback(() => {
113
+ if (!state.active) {
114
+ return;
115
+ }
116
+ try {
117
+ state.active.props.onDismiss?.();
118
+ } catch (error) {
119
+ // This runs in a transition commit; an escaping error could unmount the provider.
120
+ // eslint-disable-next-line no-console
121
+ console.error('Snackbar onDismiss callback failed:', error);
122
+ } finally {
123
+ dispatch({
124
+ type: 'exited',
125
+ instanceId: state.active.id
126
+ });
127
+ }
128
+ }, [state.active]);
129
+ useEffect(() => {
130
+ if (state.active && state.visible && !state.activeHasRendered) {
131
+ // Later creates can now wait for this instance's exit instead of coalescing before render.
132
+ dispatch({
133
+ type: 'rendered',
134
+ instanceId: state.active.id
135
+ });
136
+ }
137
+ }, [state.active, state.activeHasRendered, state.visible]);
138
+ return {
139
+ active: state.active,
140
+ announcementKey: state.announcementKey,
141
+ createSnackbar,
142
+ dismissSnackbar,
143
+ handleExited,
144
+ visible: state.visible
145
+ };
146
+ };
147
+
148
+ export { useSnackbarManager as default };
149
+ //# sourceMappingURL=useSnackbarManager.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSnackbarManager.mjs","sources":["../../../src/Prompt/Snackbar/useSnackbarManager.ts"],"sourcesContent":["import { useCallback, useEffect, useReducer, useRef } from 'react';\n\nimport type { SnackbarCreationProps } from './Snackbar.types';\n\ntype SnackbarInstance = {\n id: number;\n props: SnackbarCreationProps;\n};\n\ntype SnackbarManagerState = {\n /** The instance currently entering, visible or exiting. `null` means the provider is idle. */\n active: SnackbarInstance | null;\n /** Marks the active provider render as committed, not its entry animation as finished. */\n activeHasRendered: boolean;\n /** Changes when a new instance becomes active so repeated message text can be announced. */\n announcementKey: number;\n /** The latest replacement waiting for active to exit. `null` means no replacement is waiting. */\n pending: SnackbarInstance | null;\n /** Whether active should enter/stay visible; false requests its exit or indicates idle state. */\n visible: boolean;\n};\n\ntype SnackbarManagerAction =\n | { type: 'show'; instance: SnackbarInstance }\n | {\n /** Ends pre-render coalescing; subsequent creates must wait for the committed instance to exit. */\n type: 'rendered';\n instanceId: number;\n }\n | { type: 'dismiss' }\n | {\n /** Like `dismiss`, but also cancels a queued replacement. Sent when `createSnackbar` gets empty text. */\n type: 'clear';\n }\n | { type: 'exited'; instanceId: number };\n\nconst emptyState: SnackbarManagerState = {\n active: null,\n activeHasRendered: false,\n announcementKey: 0,\n pending: null,\n visible: false,\n};\n\n/**\n * Coalesces creates before the first commit, then keeps active content until its exit finishes.\n * Only the latest pending replacement is retained. Exited promotes pending or returns to idle.\n * Instance IDs prevent stale exit callbacks from affecting a newer Snackbar.\n */\nconst snackbarManagerReducer = (\n state: SnackbarManagerState,\n action: SnackbarManagerAction,\n): SnackbarManagerState => {\n switch (action.type) {\n case 'show':\n if (!state.active || !state.activeHasRendered) {\n // Batched creates collapse to the latest instance before anything has entered.\n return {\n active: action.instance,\n activeHasRendered: false,\n announcementKey: state.announcementKey + 1,\n pending: null,\n visible: true,\n };\n }\n\n // Keep the active content until it exits; newer creates replace only the pending instance.\n return { ...state, pending: action.instance, visible: false };\n case 'rendered':\n if (state.active?.id !== action.instanceId || state.activeHasRendered) {\n return state;\n }\n\n return { ...state, activeHasRendered: true };\n case 'dismiss':\n case 'clear':\n if (!state.active) {\n return state;\n }\n\n if (!state.activeHasRendered) {\n return { ...emptyState, announcementKey: state.announcementKey };\n }\n\n // Dismissal lets a queued replacement show next; clearing cancels it as well.\n return { ...state, pending: action.type === 'clear' ? null : state.pending, visible: false };\n case 'exited':\n // Ignore callbacks from an instance that has already been replaced.\n if (state.active?.id !== action.instanceId) {\n return state;\n }\n\n if (state.pending) {\n return {\n active: state.pending,\n activeHasRendered: false,\n announcementKey: state.announcementKey + 1,\n pending: null,\n visible: true,\n };\n }\n\n return { ...emptyState, announcementKey: state.announcementKey };\n default:\n return state;\n }\n};\n\n/**\n * Owns latest-wins replacement through reducer actions, separate from rendering and animation.\n * Stable create/dismiss callbacks request state changes; the surface's onExited callback completes\n * dismissal. The provider uses this state to mount/unmount the portal without a notification queue.\n */\nconst useSnackbarManager = () => {\n const [state, dispatch] = useReducer(snackbarManagerReducer, emptyState);\n const nextInstanceId = useRef(0);\n\n const createSnackbar = useCallback((props: SnackbarCreationProps) => {\n const { text } = props;\n if (\n text == null ||\n typeof text === 'boolean' ||\n (typeof text === 'string' && text.trim() === '')\n ) {\n // A clear request must dismiss active and cancel pending, never create a blank replacement.\n dispatch({ type: 'clear' });\n return;\n }\n\n nextInstanceId.current += 1;\n dispatch({ type: 'show', instance: { id: nextInstanceId.current, props } });\n }, []);\n\n const dismissSnackbar = useCallback(() => dispatch({ type: 'dismiss' }), []);\n\n const handleExited = useCallback(() => {\n if (!state.active) {\n return;\n }\n\n try {\n state.active.props.onDismiss?.();\n } catch (error) {\n // This runs in a transition commit; an escaping error could unmount the provider.\n // eslint-disable-next-line no-console\n console.error('Snackbar onDismiss callback failed:', error);\n } finally {\n dispatch({ type: 'exited', instanceId: state.active.id });\n }\n }, [state.active]);\n\n useEffect(() => {\n if (state.active && state.visible && !state.activeHasRendered) {\n // Later creates can now wait for this instance's exit instead of coalescing before render.\n dispatch({ type: 'rendered', instanceId: state.active.id });\n }\n }, [state.active, state.activeHasRendered, state.visible]);\n\n return {\n active: state.active,\n announcementKey: state.announcementKey,\n createSnackbar,\n dismissSnackbar,\n handleExited,\n visible: state.visible,\n };\n};\n\nexport default useSnackbarManager;\n"],"names":["emptyState","active","activeHasRendered","announcementKey","pending","visible","snackbarManagerReducer","state","action","type","instance","id","instanceId","useSnackbarManager","dispatch","useReducer","nextInstanceId","useRef","createSnackbar","useCallback","props","text","trim","current","dismissSnackbar","handleExited","onDismiss","error","console","useEffect"],"mappings":"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"}
@@ -0,0 +1,135 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var React = require('react');
6
+ var constants = require('./constants.js');
7
+
8
+ const DISMISS_CLASS = 'wds-Snackbar--swipe-dismiss';
9
+ const SETTLE_CLASS = 'wds-Snackbar--swipe-settle';
10
+ /**
11
+ * Captures one primary pointer and follows downward, mostly vertical movement without re-rendering.
12
+ * Release dismisses at half the surface height; shorter/cancelled gestures settle back into place.
13
+ * The outer wrapper moves independently of entry/exit motion, and a committed swipe consumes its
14
+ * follow-up click so dragging from the action cannot activate it.
15
+ */
16
+ const useSnackbarSwipeDismiss = ({
17
+ onDismissRequest,
18
+ onSwipeChange,
19
+ visible
20
+ }) => {
21
+ const containerRef = React.useRef(null);
22
+ const pointerRef = React.useRef(null);
23
+ const suppressClick = React.useRef(false);
24
+ const clearSwipePosition = React.useCallback(() => {
25
+ const pointer = pointerRef.current;
26
+ pointerRef.current = null;
27
+ if (pointer) {
28
+ if (pointer.target.hasPointerCapture?.(pointer.id)) {
29
+ pointer.target.releasePointerCapture(pointer.id);
30
+ }
31
+ onSwipeChange(false);
32
+ }
33
+ suppressClick.current = false;
34
+ containerRef.current?.classList.remove(DISMISS_CLASS, SETTLE_CLASS);
35
+ containerRef.current?.style.removeProperty('transform');
36
+ }, [onSwipeChange]);
37
+ const handlePointerDown = event => {
38
+ if (event.button !== 0 || !event.isPrimary || pointerRef.current) {
39
+ return;
40
+ }
41
+ clearSwipePosition();
42
+ const target = event.target;
43
+ target.setPointerCapture?.(event.pointerId);
44
+ pointerRef.current = {
45
+ id: event.pointerId,
46
+ target,
47
+ startX: event.clientX,
48
+ startY: event.clientY,
49
+ threshold: event.currentTarget.offsetHeight * constants.SNACKBAR_SWIPE_DISMISS_THRESHOLD
50
+ };
51
+ onSwipeChange(true);
52
+ };
53
+ const handlePointerMove = event => {
54
+ const pointer = pointerRef.current;
55
+ if (pointer?.id !== event.pointerId) {
56
+ return;
57
+ }
58
+ const offsetY = event.clientY - pointer.startY;
59
+ const offsetX = event.clientX - pointer.startX;
60
+ if (offsetY > 0 && offsetY > Math.abs(offsetX)) {
61
+ containerRef.current?.style.setProperty('transform', `translateY(${offsetY}px)`);
62
+ } else {
63
+ containerRef.current?.style.removeProperty('transform');
64
+ }
65
+ };
66
+ const handlePointerUp = event => {
67
+ const pointer = pointerRef.current;
68
+ if (pointer?.id !== event.pointerId) {
69
+ return;
70
+ }
71
+ const offsetY = event.clientY - pointer.startY;
72
+ const offsetX = event.clientX - pointer.startX;
73
+ const container = containerRef.current;
74
+ const bounds = pointer.target.closest('a, button')?.getBoundingClientRect();
75
+ clearSwipePosition();
76
+ if (offsetY >= pointer.threshold && offsetY > Math.abs(offsetX)) {
77
+ if (container) {
78
+ // Include wrapper padding and the safe area, not only the surface, to clear the viewport.
79
+ container.classList.add(DISMISS_CLASS);
80
+ container.style.setProperty('transform', `translateY(${Math.max(offsetY, container.offsetHeight)}px)`);
81
+ }
82
+ suppressClick.current = true;
83
+ onDismissRequest();
84
+ } else {
85
+ // Capture retargets clicks to the pressed control even when released outside it.
86
+ suppressClick.current = !!bounds && (offsetX !== 0 || offsetY !== 0) && (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom);
87
+ container?.classList.add(SETTLE_CLASS);
88
+ }
89
+ };
90
+ const handlePointerCancel = React.useCallback(event => {
91
+ if (pointerRef.current?.id !== event.pointerId) {
92
+ return;
93
+ }
94
+ clearSwipePosition();
95
+ containerRef.current?.classList.add(SETTLE_CLASS);
96
+ }, [clearSwipePosition]);
97
+ React.useEffect(() => {
98
+ if (visible) {
99
+ clearSwipePosition();
100
+ } else if (pointerRef.current) {
101
+ // An exit starting mid-gesture (timeout, Escape, replacement) ends it without dismissing again.
102
+ handlePointerCancel({
103
+ pointerId: pointerRef.current.id
104
+ });
105
+ }
106
+ }, [visible, clearSwipePosition, handlePointerCancel]);
107
+ React.useEffect(() => {
108
+ // Removed descendants report capture loss on their document, not the Snackbar surface.
109
+ const ownerDocument = containerRef.current?.ownerDocument;
110
+ ownerDocument?.addEventListener('lostpointercapture', handlePointerCancel);
111
+ return () => {
112
+ ownerDocument?.removeEventListener('lostpointercapture', handlePointerCancel);
113
+ clearSwipePosition();
114
+ };
115
+ }, [clearSwipePosition, handlePointerCancel]);
116
+ const handleClickCapture = event => {
117
+ if (suppressClick.current && event.detail > 0) {
118
+ suppressClick.current = false;
119
+ event.preventDefault();
120
+ event.stopPropagation();
121
+ }
122
+ };
123
+ return {
124
+ clearSwipePosition,
125
+ containerRef,
126
+ handleClickCapture,
127
+ handlePointerCancel,
128
+ handlePointerDown,
129
+ handlePointerMove,
130
+ handlePointerUp
131
+ };
132
+ };
133
+
134
+ exports.default = useSnackbarSwipeDismiss;
135
+ //# sourceMappingURL=useSnackbarSwipeDismiss.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSnackbarSwipeDismiss.js","sources":["../../../src/Prompt/Snackbar/useSnackbarSwipeDismiss.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\nimport { SNACKBAR_SWIPE_DISMISS_THRESHOLD } from './constants';\n\nconst DISMISS_CLASS = 'wds-Snackbar--swipe-dismiss';\nconst SETTLE_CLASS = 'wds-Snackbar--swipe-settle';\n\ntype SwipePointer = {\n id: number;\n target: Element;\n startX: number;\n startY: number;\n threshold: number;\n};\n\n/**\n * Captures one primary pointer and follows downward, mostly vertical movement without re-rendering.\n * Release dismisses at half the surface height; shorter/cancelled gestures settle back into place.\n * The outer wrapper moves independently of entry/exit motion, and a committed swipe consumes its\n * follow-up click so dragging from the action cannot activate it.\n */\nconst useSnackbarSwipeDismiss = ({\n onDismissRequest,\n onSwipeChange,\n visible,\n}: {\n onDismissRequest: () => void;\n /** Reports when a gesture starts and ends so auto-dismiss can pause beneath the pointer. */\n onSwipeChange: (swiping: boolean) => void;\n visible: boolean;\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const pointerRef = useRef<SwipePointer | null>(null);\n const suppressClick = useRef(false);\n\n const clearSwipePosition = useCallback(() => {\n const pointer = pointerRef.current;\n pointerRef.current = null;\n if (pointer) {\n if (pointer.target.hasPointerCapture?.(pointer.id)) {\n pointer.target.releasePointerCapture(pointer.id);\n }\n onSwipeChange(false);\n }\n suppressClick.current = false;\n containerRef.current?.classList.remove(DISMISS_CLASS, SETTLE_CLASS);\n containerRef.current?.style.removeProperty('transform');\n }, [onSwipeChange]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0 || !event.isPrimary || pointerRef.current) {\n return;\n }\n\n clearSwipePosition();\n const target = event.target as Element;\n target.setPointerCapture?.(event.pointerId);\n pointerRef.current = {\n id: event.pointerId,\n target,\n startX: event.clientX,\n startY: event.clientY,\n threshold: event.currentTarget.offsetHeight * SNACKBAR_SWIPE_DISMISS_THRESHOLD,\n };\n onSwipeChange(true);\n };\n\n const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n const pointer = pointerRef.current;\n if (pointer?.id !== event.pointerId) {\n return;\n }\n\n const offsetY = event.clientY - pointer.startY;\n const offsetX = event.clientX - pointer.startX;\n if (offsetY > 0 && offsetY > Math.abs(offsetX)) {\n containerRef.current?.style.setProperty('transform', `translateY(${offsetY}px)`);\n } else {\n containerRef.current?.style.removeProperty('transform');\n }\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {\n const pointer = pointerRef.current;\n if (pointer?.id !== event.pointerId) {\n return;\n }\n\n const offsetY = event.clientY - pointer.startY;\n const offsetX = event.clientX - pointer.startX;\n const container = containerRef.current;\n const bounds = pointer.target.closest('a, button')?.getBoundingClientRect();\n clearSwipePosition();\n\n if (offsetY >= pointer.threshold && offsetY > Math.abs(offsetX)) {\n if (container) {\n // Include wrapper padding and the safe area, not only the surface, to clear the viewport.\n container.classList.add(DISMISS_CLASS);\n container.style.setProperty(\n 'transform',\n `translateY(${Math.max(offsetY, container.offsetHeight)}px)`,\n );\n }\n suppressClick.current = true;\n onDismissRequest();\n } else {\n // Capture retargets clicks to the pressed control even when released outside it.\n suppressClick.current =\n !!bounds &&\n (offsetX !== 0 || offsetY !== 0) &&\n (event.clientX < bounds.left ||\n event.clientX > bounds.right ||\n event.clientY < bounds.top ||\n event.clientY > bounds.bottom);\n container?.classList.add(SETTLE_CLASS);\n }\n };\n\n const handlePointerCancel = useCallback(\n (event: Pick<PointerEvent, 'pointerId'>) => {\n if (pointerRef.current?.id !== event.pointerId) {\n return;\n }\n\n clearSwipePosition();\n containerRef.current?.classList.add(SETTLE_CLASS);\n },\n [clearSwipePosition],\n );\n\n useEffect(() => {\n if (visible) {\n clearSwipePosition();\n } else if (pointerRef.current) {\n // An exit starting mid-gesture (timeout, Escape, replacement) ends it without dismissing again.\n handlePointerCancel({ pointerId: pointerRef.current.id });\n }\n }, [visible, clearSwipePosition, handlePointerCancel]);\n\n useEffect(() => {\n // Removed descendants report capture loss on their document, not the Snackbar surface.\n const ownerDocument = containerRef.current?.ownerDocument;\n ownerDocument?.addEventListener('lostpointercapture', handlePointerCancel);\n return () => {\n ownerDocument?.removeEventListener('lostpointercapture', handlePointerCancel);\n clearSwipePosition();\n };\n }, [clearSwipePosition, handlePointerCancel]);\n\n const handleClickCapture = (event: React.MouseEvent<HTMLDivElement>) => {\n if (suppressClick.current && event.detail > 0) {\n suppressClick.current = false;\n event.preventDefault();\n event.stopPropagation();\n }\n };\n\n return {\n clearSwipePosition,\n containerRef,\n handleClickCapture,\n handlePointerCancel,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n };\n};\n\nexport default useSnackbarSwipeDismiss;\n"],"names":["DISMISS_CLASS","SETTLE_CLASS","useSnackbarSwipeDismiss","onDismissRequest","onSwipeChange","visible","containerRef","useRef","pointerRef","suppressClick","clearSwipePosition","useCallback","pointer","current","target","hasPointerCapture","id","releasePointerCapture","classList","remove","style","removeProperty","handlePointerDown","event","button","isPrimary","setPointerCapture","pointerId","startX","clientX","startY","clientY","threshold","currentTarget","offsetHeight","SNACKBAR_SWIPE_DISMISS_THRESHOLD","handlePointerMove","offsetY","offsetX","Math","abs","setProperty","handlePointerUp","container","bounds","closest","getBoundingClientRect","add","max","left","right","top","bottom","handlePointerCancel","useEffect","ownerDocument","addEventListener","removeEventListener","handleClickCapture","detail","preventDefault","stopPropagation"],"mappings":"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"}
@@ -0,0 +1,131 @@
1
+ import { useRef, useCallback, useEffect } from 'react';
2
+ import { SNACKBAR_SWIPE_DISMISS_THRESHOLD } from './constants.mjs';
3
+
4
+ const DISMISS_CLASS = 'wds-Snackbar--swipe-dismiss';
5
+ const SETTLE_CLASS = 'wds-Snackbar--swipe-settle';
6
+ /**
7
+ * Captures one primary pointer and follows downward, mostly vertical movement without re-rendering.
8
+ * Release dismisses at half the surface height; shorter/cancelled gestures settle back into place.
9
+ * The outer wrapper moves independently of entry/exit motion, and a committed swipe consumes its
10
+ * follow-up click so dragging from the action cannot activate it.
11
+ */
12
+ const useSnackbarSwipeDismiss = ({
13
+ onDismissRequest,
14
+ onSwipeChange,
15
+ visible
16
+ }) => {
17
+ const containerRef = useRef(null);
18
+ const pointerRef = useRef(null);
19
+ const suppressClick = useRef(false);
20
+ const clearSwipePosition = useCallback(() => {
21
+ const pointer = pointerRef.current;
22
+ pointerRef.current = null;
23
+ if (pointer) {
24
+ if (pointer.target.hasPointerCapture?.(pointer.id)) {
25
+ pointer.target.releasePointerCapture(pointer.id);
26
+ }
27
+ onSwipeChange(false);
28
+ }
29
+ suppressClick.current = false;
30
+ containerRef.current?.classList.remove(DISMISS_CLASS, SETTLE_CLASS);
31
+ containerRef.current?.style.removeProperty('transform');
32
+ }, [onSwipeChange]);
33
+ const handlePointerDown = event => {
34
+ if (event.button !== 0 || !event.isPrimary || pointerRef.current) {
35
+ return;
36
+ }
37
+ clearSwipePosition();
38
+ const target = event.target;
39
+ target.setPointerCapture?.(event.pointerId);
40
+ pointerRef.current = {
41
+ id: event.pointerId,
42
+ target,
43
+ startX: event.clientX,
44
+ startY: event.clientY,
45
+ threshold: event.currentTarget.offsetHeight * SNACKBAR_SWIPE_DISMISS_THRESHOLD
46
+ };
47
+ onSwipeChange(true);
48
+ };
49
+ const handlePointerMove = event => {
50
+ const pointer = pointerRef.current;
51
+ if (pointer?.id !== event.pointerId) {
52
+ return;
53
+ }
54
+ const offsetY = event.clientY - pointer.startY;
55
+ const offsetX = event.clientX - pointer.startX;
56
+ if (offsetY > 0 && offsetY > Math.abs(offsetX)) {
57
+ containerRef.current?.style.setProperty('transform', `translateY(${offsetY}px)`);
58
+ } else {
59
+ containerRef.current?.style.removeProperty('transform');
60
+ }
61
+ };
62
+ const handlePointerUp = event => {
63
+ const pointer = pointerRef.current;
64
+ if (pointer?.id !== event.pointerId) {
65
+ return;
66
+ }
67
+ const offsetY = event.clientY - pointer.startY;
68
+ const offsetX = event.clientX - pointer.startX;
69
+ const container = containerRef.current;
70
+ const bounds = pointer.target.closest('a, button')?.getBoundingClientRect();
71
+ clearSwipePosition();
72
+ if (offsetY >= pointer.threshold && offsetY > Math.abs(offsetX)) {
73
+ if (container) {
74
+ // Include wrapper padding and the safe area, not only the surface, to clear the viewport.
75
+ container.classList.add(DISMISS_CLASS);
76
+ container.style.setProperty('transform', `translateY(${Math.max(offsetY, container.offsetHeight)}px)`);
77
+ }
78
+ suppressClick.current = true;
79
+ onDismissRequest();
80
+ } else {
81
+ // Capture retargets clicks to the pressed control even when released outside it.
82
+ suppressClick.current = !!bounds && (offsetX !== 0 || offsetY !== 0) && (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom);
83
+ container?.classList.add(SETTLE_CLASS);
84
+ }
85
+ };
86
+ const handlePointerCancel = useCallback(event => {
87
+ if (pointerRef.current?.id !== event.pointerId) {
88
+ return;
89
+ }
90
+ clearSwipePosition();
91
+ containerRef.current?.classList.add(SETTLE_CLASS);
92
+ }, [clearSwipePosition]);
93
+ useEffect(() => {
94
+ if (visible) {
95
+ clearSwipePosition();
96
+ } else if (pointerRef.current) {
97
+ // An exit starting mid-gesture (timeout, Escape, replacement) ends it without dismissing again.
98
+ handlePointerCancel({
99
+ pointerId: pointerRef.current.id
100
+ });
101
+ }
102
+ }, [visible, clearSwipePosition, handlePointerCancel]);
103
+ useEffect(() => {
104
+ // Removed descendants report capture loss on their document, not the Snackbar surface.
105
+ const ownerDocument = containerRef.current?.ownerDocument;
106
+ ownerDocument?.addEventListener('lostpointercapture', handlePointerCancel);
107
+ return () => {
108
+ ownerDocument?.removeEventListener('lostpointercapture', handlePointerCancel);
109
+ clearSwipePosition();
110
+ };
111
+ }, [clearSwipePosition, handlePointerCancel]);
112
+ const handleClickCapture = event => {
113
+ if (suppressClick.current && event.detail > 0) {
114
+ suppressClick.current = false;
115
+ event.preventDefault();
116
+ event.stopPropagation();
117
+ }
118
+ };
119
+ return {
120
+ clearSwipePosition,
121
+ containerRef,
122
+ handleClickCapture,
123
+ handlePointerCancel,
124
+ handlePointerDown,
125
+ handlePointerMove,
126
+ handlePointerUp
127
+ };
128
+ };
129
+
130
+ export { useSnackbarSwipeDismiss as default };
131
+ //# sourceMappingURL=useSnackbarSwipeDismiss.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSnackbarSwipeDismiss.mjs","sources":["../../../src/Prompt/Snackbar/useSnackbarSwipeDismiss.ts"],"sourcesContent":["import { useCallback, useEffect, useRef } from 'react';\n\nimport { SNACKBAR_SWIPE_DISMISS_THRESHOLD } from './constants';\n\nconst DISMISS_CLASS = 'wds-Snackbar--swipe-dismiss';\nconst SETTLE_CLASS = 'wds-Snackbar--swipe-settle';\n\ntype SwipePointer = {\n id: number;\n target: Element;\n startX: number;\n startY: number;\n threshold: number;\n};\n\n/**\n * Captures one primary pointer and follows downward, mostly vertical movement without re-rendering.\n * Release dismisses at half the surface height; shorter/cancelled gestures settle back into place.\n * The outer wrapper moves independently of entry/exit motion, and a committed swipe consumes its\n * follow-up click so dragging from the action cannot activate it.\n */\nconst useSnackbarSwipeDismiss = ({\n onDismissRequest,\n onSwipeChange,\n visible,\n}: {\n onDismissRequest: () => void;\n /** Reports when a gesture starts and ends so auto-dismiss can pause beneath the pointer. */\n onSwipeChange: (swiping: boolean) => void;\n visible: boolean;\n}) => {\n const containerRef = useRef<HTMLDivElement>(null);\n const pointerRef = useRef<SwipePointer | null>(null);\n const suppressClick = useRef(false);\n\n const clearSwipePosition = useCallback(() => {\n const pointer = pointerRef.current;\n pointerRef.current = null;\n if (pointer) {\n if (pointer.target.hasPointerCapture?.(pointer.id)) {\n pointer.target.releasePointerCapture(pointer.id);\n }\n onSwipeChange(false);\n }\n suppressClick.current = false;\n containerRef.current?.classList.remove(DISMISS_CLASS, SETTLE_CLASS);\n containerRef.current?.style.removeProperty('transform');\n }, [onSwipeChange]);\n\n const handlePointerDown = (event: React.PointerEvent<HTMLDivElement>) => {\n if (event.button !== 0 || !event.isPrimary || pointerRef.current) {\n return;\n }\n\n clearSwipePosition();\n const target = event.target as Element;\n target.setPointerCapture?.(event.pointerId);\n pointerRef.current = {\n id: event.pointerId,\n target,\n startX: event.clientX,\n startY: event.clientY,\n threshold: event.currentTarget.offsetHeight * SNACKBAR_SWIPE_DISMISS_THRESHOLD,\n };\n onSwipeChange(true);\n };\n\n const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n const pointer = pointerRef.current;\n if (pointer?.id !== event.pointerId) {\n return;\n }\n\n const offsetY = event.clientY - pointer.startY;\n const offsetX = event.clientX - pointer.startX;\n if (offsetY > 0 && offsetY > Math.abs(offsetX)) {\n containerRef.current?.style.setProperty('transform', `translateY(${offsetY}px)`);\n } else {\n containerRef.current?.style.removeProperty('transform');\n }\n };\n\n const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {\n const pointer = pointerRef.current;\n if (pointer?.id !== event.pointerId) {\n return;\n }\n\n const offsetY = event.clientY - pointer.startY;\n const offsetX = event.clientX - pointer.startX;\n const container = containerRef.current;\n const bounds = pointer.target.closest('a, button')?.getBoundingClientRect();\n clearSwipePosition();\n\n if (offsetY >= pointer.threshold && offsetY > Math.abs(offsetX)) {\n if (container) {\n // Include wrapper padding and the safe area, not only the surface, to clear the viewport.\n container.classList.add(DISMISS_CLASS);\n container.style.setProperty(\n 'transform',\n `translateY(${Math.max(offsetY, container.offsetHeight)}px)`,\n );\n }\n suppressClick.current = true;\n onDismissRequest();\n } else {\n // Capture retargets clicks to the pressed control even when released outside it.\n suppressClick.current =\n !!bounds &&\n (offsetX !== 0 || offsetY !== 0) &&\n (event.clientX < bounds.left ||\n event.clientX > bounds.right ||\n event.clientY < bounds.top ||\n event.clientY > bounds.bottom);\n container?.classList.add(SETTLE_CLASS);\n }\n };\n\n const handlePointerCancel = useCallback(\n (event: Pick<PointerEvent, 'pointerId'>) => {\n if (pointerRef.current?.id !== event.pointerId) {\n return;\n }\n\n clearSwipePosition();\n containerRef.current?.classList.add(SETTLE_CLASS);\n },\n [clearSwipePosition],\n );\n\n useEffect(() => {\n if (visible) {\n clearSwipePosition();\n } else if (pointerRef.current) {\n // An exit starting mid-gesture (timeout, Escape, replacement) ends it without dismissing again.\n handlePointerCancel({ pointerId: pointerRef.current.id });\n }\n }, [visible, clearSwipePosition, handlePointerCancel]);\n\n useEffect(() => {\n // Removed descendants report capture loss on their document, not the Snackbar surface.\n const ownerDocument = containerRef.current?.ownerDocument;\n ownerDocument?.addEventListener('lostpointercapture', handlePointerCancel);\n return () => {\n ownerDocument?.removeEventListener('lostpointercapture', handlePointerCancel);\n clearSwipePosition();\n };\n }, [clearSwipePosition, handlePointerCancel]);\n\n const handleClickCapture = (event: React.MouseEvent<HTMLDivElement>) => {\n if (suppressClick.current && event.detail > 0) {\n suppressClick.current = false;\n event.preventDefault();\n event.stopPropagation();\n }\n };\n\n return {\n clearSwipePosition,\n containerRef,\n handleClickCapture,\n handlePointerCancel,\n handlePointerDown,\n handlePointerMove,\n handlePointerUp,\n };\n};\n\nexport default useSnackbarSwipeDismiss;\n"],"names":["DISMISS_CLASS","SETTLE_CLASS","useSnackbarSwipeDismiss","onDismissRequest","onSwipeChange","visible","containerRef","useRef","pointerRef","suppressClick","clearSwipePosition","useCallback","pointer","current","target","hasPointerCapture","id","releasePointerCapture","classList","remove","style","removeProperty","handlePointerDown","event","button","isPrimary","setPointerCapture","pointerId","startX","clientX","startY","clientY","threshold","currentTarget","offsetHeight","SNACKBAR_SWIPE_DISMISS_THRESHOLD","handlePointerMove","offsetY","offsetX","Math","abs","setProperty","handlePointerUp","container","bounds","closest","getBoundingClientRect","add","max","left","right","top","bottom","handlePointerCancel","useEffect","ownerDocument","addEventListener","removeEventListener","handleClickCapture","detail","preventDefault","stopPropagation"],"mappings":"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"}