@astryxdesign/core 0.7.8-canary.ffd29c5 → 0.7.8

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 (189) hide show
  1. package/CHANGELOG.md +132 -0
  2. package/dist/BottomSheet/BottomSheetPanel.d.ts +1 -1
  3. package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
  4. package/dist/BottomSheet/BottomSheetPanel.js +30 -14
  5. package/dist/BottomSheet/bottomSheetStack.js +1 -1
  6. package/dist/BottomSheet/useSheetGestures.d.ts +3 -1
  7. package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
  8. package/dist/BottomSheet/useSheetGestures.js +4 -0
  9. package/dist/CheckboxList/CheckboxList.d.ts +13 -2
  10. package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
  11. package/dist/CheckboxList/CheckboxList.js +10 -4
  12. package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
  13. package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
  14. package/dist/CheckboxList/CheckboxListContext.js +2 -1
  15. package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
  16. package/dist/CheckboxList/CheckboxListItem.js +14 -2
  17. package/dist/CheckboxList/requiredGroup.d.ts +22 -0
  18. package/dist/CheckboxList/requiredGroup.d.ts.map +1 -0
  19. package/dist/CheckboxList/requiredGroup.js +52 -0
  20. package/dist/DateInput/PickerWheel.d.ts +1 -1
  21. package/dist/DateInput/PickerWheel.d.ts.map +1 -1
  22. package/dist/DateInput/PickerWheel.js +27 -6
  23. package/dist/DateInput/Wheel.d.ts +2 -2
  24. package/dist/DateInput/Wheel.d.ts.map +1 -1
  25. package/dist/DateInput/Wheel.js +20 -6
  26. package/dist/DateInput/datePickerTouch.d.ts +13 -0
  27. package/dist/DateInput/datePickerTouch.d.ts.map +1 -0
  28. package/dist/DateInput/datePickerTouch.js +14 -0
  29. package/dist/DateInput/wheelOptions.d.ts +22 -2
  30. package/dist/DateInput/wheelOptions.d.ts.map +1 -1
  31. package/dist/DateInput/wheelOptions.js +14 -2
  32. package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
  33. package/dist/DateTimeInput/TouchDateTimeField.js +62 -119
  34. package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
  35. package/dist/HoverCard/useHoverCard.js +6 -1
  36. package/dist/Overlay/Overlay.d.ts.map +1 -1
  37. package/dist/Overlay/Overlay.js +7 -2
  38. package/dist/PowerSearch/PowerSearch.d.ts +4 -2
  39. package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
  40. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +24 -1
  41. package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -1
  42. package/dist/PowerSearch/PowerSearchBottomSheet.js +172 -50
  43. package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
  44. package/dist/Resizable/ResizeHandle.js +15 -15
  45. package/dist/Resizable/createResizable.d.ts +70 -16
  46. package/dist/Resizable/createResizable.d.ts.map +1 -1
  47. package/dist/Resizable/createResizable.js +180 -22
  48. package/dist/Resizable/separatorAttributes.d.ts +45 -0
  49. package/dist/Resizable/separatorAttributes.d.ts.map +1 -0
  50. package/dist/Resizable/separatorAttributes.js +35 -0
  51. package/dist/Resizable/useResizable.js +2 -2
  52. package/dist/TimeInput/TimeWheels.d.ts +32 -0
  53. package/dist/TimeInput/TimeWheels.d.ts.map +1 -0
  54. package/dist/TimeInput/TimeWheels.js +144 -0
  55. package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
  56. package/dist/TimeInput/TouchTimeField.js +63 -118
  57. package/dist/TimeInput/timeWheelRules.d.ts +124 -0
  58. package/dist/TimeInput/timeWheelRules.d.ts.map +1 -0
  59. package/dist/TimeInput/timeWheelRules.js +277 -0
  60. package/dist/Tokenizer/TokenizerBottomSheet.js +8 -12
  61. package/dist/Tooltip/useTooltip.d.ts.map +1 -1
  62. package/dist/Tooltip/useTooltip.js +6 -0
  63. package/dist/astryx.css +3 -1
  64. package/dist/gestures/GestureHost.d.ts.map +1 -1
  65. package/dist/gestures/GestureHost.js +3 -0
  66. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -1
  67. package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +5 -2
  68. package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts +8 -0
  69. package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts.map +1 -0
  70. package/dist/i18n/generated-english/Resizable/createResizable.generated.js +13 -0
  71. package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts +8 -0
  72. package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts.map +1 -0
  73. package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.js +17 -0
  74. package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
  75. package/dist/i18n/generated-locales/en.generated.js +5 -2
  76. package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
  77. package/dist/i18n/generated-locales/pseudo.generated.js +5 -2
  78. package/dist/keyboard/index.d.ts +17 -0
  79. package/dist/keyboard/index.d.ts.map +1 -0
  80. package/dist/keyboard/index.js +19 -0
  81. package/dist/keyboard/keyboardHeight.d.ts +149 -0
  82. package/dist/keyboard/keyboardHeight.d.ts.map +1 -0
  83. package/dist/keyboard/keyboardHeight.js +432 -0
  84. package/dist/keyboard/keyboardLayoutGuide.d.ts +59 -0
  85. package/dist/keyboard/keyboardLayoutGuide.d.ts.map +1 -0
  86. package/dist/keyboard/keyboardLayoutGuide.js +207 -0
  87. package/dist/keyboard/keyboardModel.d.ts +93 -0
  88. package/dist/keyboard/keyboardModel.d.ts.map +1 -0
  89. package/dist/keyboard/keyboardModel.js +220 -0
  90. package/dist/keyboard/rideKeyboard.d.ts +42 -0
  91. package/dist/keyboard/rideKeyboard.d.ts.map +1 -0
  92. package/dist/keyboard/rideKeyboard.js +77 -0
  93. package/dist/keyboard/useMobileKeyboard.d.ts +61 -0
  94. package/dist/keyboard/useMobileKeyboard.d.ts.map +1 -0
  95. package/dist/keyboard/useMobileKeyboard.js +699 -0
  96. package/dist/motion/android/index.d.ts +16 -0
  97. package/dist/motion/android/index.d.ts.map +1 -0
  98. package/dist/motion/android/index.js +18 -0
  99. package/dist/motion/android/keyboard.d.ts +83 -0
  100. package/dist/motion/android/keyboard.d.ts.map +1 -0
  101. package/dist/motion/android/keyboard.js +96 -0
  102. package/dist/motion/uikit/datePicker.d.ts +58 -0
  103. package/dist/motion/uikit/datePicker.d.ts.map +1 -0
  104. package/dist/motion/uikit/datePicker.js +60 -0
  105. package/dist/motion/uikit/index.d.ts +4 -3
  106. package/dist/motion/uikit/index.d.ts.map +1 -1
  107. package/dist/motion/uikit/index.js +4 -3
  108. package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
  109. package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
  110. package/dist/utils/interactionOverlay.stylex.js +3 -2
  111. package/dist/utils/layoutViewport.d.ts +25 -0
  112. package/dist/utils/layoutViewport.d.ts.map +1 -0
  113. package/dist/utils/layoutViewport.js +102 -0
  114. package/dist/utils/pressFeedback.d.ts.map +1 -1
  115. package/dist/utils/pressFeedback.js +74 -9
  116. package/dist/utils/pressGesture.d.ts +4 -2
  117. package/dist/utils/pressGesture.d.ts.map +1 -1
  118. package/dist/utils/pressGesture.js +3 -1
  119. package/dist/utils/selectionLock.d.ts +10 -1
  120. package/dist/utils/selectionLock.d.ts.map +1 -1
  121. package/dist/utils/selectionLock.js +31 -5
  122. package/dist/utils/sharedMutationObserver.d.ts +2 -1
  123. package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
  124. package/dist/utils/sharedMutationObserver.js +2 -1
  125. package/dist/utils/timeParser.d.ts +2 -1
  126. package/dist/utils/timeParser.d.ts.map +1 -1
  127. package/dist/utils/timeParser.js +29 -3
  128. package/locales/en.json +21 -9
  129. package/locales/pseudo.json +15 -6
  130. package/package.json +5 -5
  131. package/src/BottomSheet/BottomSheet.doc.mjs +1 -1
  132. package/src/BottomSheet/BottomSheetPanel.tsx +41 -13
  133. package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +5 -0
  134. package/src/BottomSheet/bottomSheetStack.ts +1 -1
  135. package/src/BottomSheet/useSheetGestures.ts +6 -0
  136. package/src/CheckboxList/CheckboxList.doc.mjs +7 -0
  137. package/src/CheckboxList/CheckboxList.tsx +34 -2
  138. package/src/CheckboxList/CheckboxListContext.tsx +9 -1
  139. package/src/CheckboxList/CheckboxListItem.tsx +23 -2
  140. package/src/CheckboxList/requiredGroup.ts +77 -0
  141. package/src/DateInput/PickerWheel.tsx +37 -4
  142. package/src/DateInput/Wheel.tsx +30 -3
  143. package/src/DateInput/datePickerTouch.ts +15 -0
  144. package/src/DateInput/wheelOptions.ts +31 -2
  145. package/src/DateTimeInput/TouchDateTimeField.tsx +94 -178
  146. package/src/HoverCard/useHoverCard.tsx +6 -1
  147. package/src/Overlay/Overlay.tsx +7 -2
  148. package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
  149. package/src/PowerSearch/PowerSearch.tsx +4 -2
  150. package/src/PowerSearch/PowerSearchBottomSheet.tsx +237 -62
  151. package/src/Resizable/ResizeHandle.tsx +18 -26
  152. package/src/Resizable/createResizable.ts +242 -27
  153. package/src/Resizable/separatorAttributes.ts +73 -0
  154. package/src/Resizable/useResizable.ts +2 -2
  155. package/src/TimeInput/TimeWheels.tsx +218 -0
  156. package/src/TimeInput/TouchTimeField.tsx +93 -157
  157. package/src/TimeInput/timeWheelRules.ts +352 -0
  158. package/src/Tokenizer/TokenizerBottomSheet.tsx +8 -12
  159. package/src/Tooltip/useTooltip.tsx +6 -0
  160. package/src/gestures/GestureHost.ts +5 -4
  161. package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +5 -2
  162. package/src/i18n/generated-english/Resizable/createResizable.generated.ts +14 -0
  163. package/src/i18n/generated-english/TimeInput/TimeWheels.generated.ts +18 -0
  164. package/src/i18n/generated-locales/en.generated.ts +6 -4
  165. package/src/i18n/generated-locales/pseudo.generated.ts +6 -4
  166. package/src/keyboard/README.md +197 -0
  167. package/src/keyboard/index.ts +48 -0
  168. package/src/keyboard/keyboardHeight.ts +543 -0
  169. package/src/keyboard/keyboardLayoutGuide.ts +266 -0
  170. package/src/keyboard/keyboardModel.ts +301 -0
  171. package/src/keyboard/rideKeyboard.ts +100 -0
  172. package/src/keyboard/useMobileKeyboard.ts +866 -0
  173. package/src/motion/MEASUREMENTS.md +10 -0
  174. package/src/motion/README.md +2 -0
  175. package/src/motion/android/index.ts +18 -0
  176. package/src/motion/android/keyboard.ts +96 -0
  177. package/src/motion/uikit/datePicker.ts +60 -0
  178. package/src/motion/uikit/index.ts +4 -3
  179. package/src/utils/interactionOverlay.stylex.ts +3 -2
  180. package/src/utils/layoutViewport.ts +104 -0
  181. package/src/utils/pressFeedback.ts +99 -21
  182. package/src/utils/pressGesture.ts +4 -2
  183. package/src/utils/selectionLock.ts +40 -6
  184. package/src/utils/sharedMutationObserver.ts +2 -1
  185. package/src/utils/timeParser.ts +40 -3
  186. package/dist/BottomSheet/useMobileKeyboard.d.ts +0 -21
  187. package/dist/BottomSheet/useMobileKeyboard.d.ts.map +0 -1
  188. package/dist/BottomSheet/useMobileKeyboard.js +0 -413
  189. package/src/BottomSheet/useMobileKeyboard.ts +0 -542
@@ -0,0 +1,207 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file keyboardLayoutGuide.ts
5
+ * @input The keyboard module's reports (subscribeKeyboardHeight) and
6
+ * createKeyboardModel
7
+ * @output getKeyboardLayoutGuide (a window's one guide), bringsUpKeyboard and
8
+ * the KeyboardLayoutGuide type
9
+ * @position The web's UIKeyboardLayoutGuide: where the software keyboard
10
+ * covers the layout viewport and how it moves. Touch presentations read it
11
+ * to keep content above the keyboard. It is internal to core.
12
+ *
13
+ * SYNC: When modified, update /packages/core/src/keyboard/README.md
14
+ */
15
+
16
+ import { getKeyboardHeight, isTextEntryControl, subscribeKeyboardHeight } from "./keyboardHeight.js";
17
+ import { createKeyboardModel } from "./keyboardModel.js";
18
+
19
+ /**
20
+ * Whose keyboard schedule the guide reads: `chromium` where the browser has
21
+ * the VirtualKeyboard API, else null. Without the profile, no surface rides
22
+ * the guide. Chrome reports the keyboard as each slide starts. The profile is
23
+ * a capability, never the user agent. motion/android/keyboard.ts holds the
24
+ * measured timing.
25
+ */
26
+ export function keyboardProfileOf(win) {
27
+ return 'virtualKeyboard' in win.navigator ? 'chromium' : null;
28
+ }
29
+
30
+ /** A window's keyboard layout guide. */
31
+
32
+ // Extra time after a predicted transition's end before checking it.
33
+ const VERIFY_SLACK_MS = 150;
34
+ // How long after a field gains focus Chrome may still report the keyboard
35
+ // gone for a moment (measured: back within 50 ms of the move).
36
+ const FOCUS_HANDOFF_MS = 150;
37
+
38
+ // Controls whose picker Chrome on Android opens over the page while the
39
+ // keyboard goes away; on iOS the picker takes the keyboard's place.
40
+ const PICKER_INPUT_TYPES = new Set(['date', 'datetime-local', 'month', 'time', 'week']);
41
+
42
+ /**
43
+ * Whether focusing `target` brings up a software keyboard (or another input
44
+ * view in its place, as a select's picker does).
45
+ */
46
+ export function bringsUpKeyboard(target) {
47
+ const element = target instanceof Element ? target : null;
48
+ return isTextEntryControl(element) || element instanceof HTMLSelectElement && !element.disabled;
49
+ }
50
+
51
+ // Whether `target` is a select, or a date or time field, which Chrome on
52
+ // Android edits in a picker over the page.
53
+ function opensPickerOverPage(target) {
54
+ const element = target;
55
+ if (element == null || element.nodeType !== 1) {
56
+ return false;
57
+ }
58
+ return element.tagName === 'SELECT' || element.tagName === 'INPUT' && PICKER_INPUT_TYPES.has(element.type);
59
+ }
60
+ const guides = new WeakMap();
61
+
62
+ /**
63
+ * The window's keyboard layout guide, created on first use.
64
+ *
65
+ * One guide serves each window. It subscribes to the keyboard module only
66
+ * while something subscribes to it. It owns no listener and measures nothing.
67
+ * It feeds the model the module's reports, the keyboard height and the focus
68
+ * changes that start a hide. It hands each transition to its subscribers.
69
+ * What it adds is timing: when a show started, a hide predicted from the
70
+ * blur, and the motion in between. Layout follows the keyboard height
71
+ * itself, and the guide's transitions only time the optional ride on top.
72
+ *
73
+ * The model reads Chrome's measured schedule: Chrome reports each slide just
74
+ * after it starts. A surface rides the guide only where the VirtualKeyboard
75
+ * API reports the keyboard. Focus can move from one field to another through
76
+ * something that brings no keyboard, such as a popover's container. Chrome
77
+ * then reports the keyboard gone for a moment and then back. So a hide
78
+ * reported while a field that just got focus still has it waits until the
79
+ * move settles.
80
+ *
81
+ * @example
82
+ * ```
83
+ * const guide = getKeyboardLayoutGuide(window);
84
+ * const unsubscribe = guide.subscribe(t => {
85
+ * rideKeyboard(animator, {y: -t.to}, t);
86
+ * });
87
+ * ```
88
+ */
89
+ export function getKeyboardLayoutGuide(win) {
90
+ let guide = guides.get(win);
91
+ if (guide == null) {
92
+ guide = createGuide(win);
93
+ guides.set(win, guide);
94
+ }
95
+ return guide;
96
+ }
97
+ function createGuide(win) {
98
+ const listeners = new Set();
99
+ let profile = keyboardProfileOf(win);
100
+ let model = createKeyboardModel(getKeyboardHeight(win));
101
+ let verifyTimer = null;
102
+ let detach = null;
103
+ const publish = transition => {
104
+ if (transition == null) {
105
+ return;
106
+ }
107
+ if (transition.predicted) {
108
+ scheduleVerify(transition);
109
+ }
110
+ for (const listener of [...listeners]) {
111
+ listener(transition);
112
+ }
113
+ };
114
+ const scheduleVerify = transition => {
115
+ if (verifyTimer != null) {
116
+ clearTimeout(verifyTimer);
117
+ }
118
+ const due = transition.start + (transition.curve?.durationMs ?? 0) + VERIFY_SLACK_MS;
119
+ verifyTimer = setTimeout(() => {
120
+ verifyTimer = null;
121
+ publish(model.verify(getKeyboardHeight(win), win.performance.now()));
122
+ }, Math.max(0, due - win.performance.now()));
123
+ };
124
+ const attach = () => {
125
+ profile = keyboardProfileOf(win);
126
+ model = createKeyboardModel(getKeyboardHeight(win));
127
+ // Whether focusing `target` keeps an input view where the keyboard is. On
128
+ // iOS, a select's picker or a date or time field's picker takes the
129
+ // keyboard's place. Chrome on Android opens it over the page, and the
130
+ // keyboard goes away.
131
+ const keepsInputView = target => bringsUpKeyboard(target) && !(profile === 'chromium' && opensPickerOverPage(target));
132
+ // When a field that brings up a keyboard last gained focus.
133
+ let keyboardFocusAt = -Infinity;
134
+ let handoffTimer = null;
135
+ const report = (height, time) => {
136
+ if (handoffTimer != null) {
137
+ clearTimeout(handoffTimer);
138
+ handoffTimer = null;
139
+ }
140
+ const settled = keyboardFocusAt + FOCUS_HANDOFF_MS;
141
+ if (profile === 'chromium' && height < 0.5 && model.inset >= 0.5 && time < settled && keepsInputView(win.document.activeElement)) {
142
+ // Focus just moved to another field: Chrome reports the keyboard
143
+ // gone before it reports it back. Hold the hide until the move
144
+ // settles, then believe whatever the keyboard is doing.
145
+ handoffTimer = setTimeout(() => {
146
+ handoffTimer = null;
147
+ publish(model.report(getKeyboardHeight(win), settled));
148
+ }, settled - time);
149
+ return null;
150
+ }
151
+ return model.report(height, time);
152
+ };
153
+ const onReport = event => {
154
+ if (event.type === 'viewport') {
155
+ publish(report(event.height, event.timeStamp));
156
+ } else if (event.type === 'focusin') {
157
+ if (keepsInputView(event.target)) {
158
+ keyboardFocusAt = event.timeStamp;
159
+ publish(model.focused(event.timeStamp));
160
+ }
161
+ } else if (keepsInputView(event.target) && !keepsInputView(event.relatedTarget)) {
162
+ publish(model.blurred(event.timeStamp));
163
+ }
164
+ };
165
+ const unsubscribe = subscribeKeyboardHeight(win, onReport, {
166
+ focus: true
167
+ });
168
+ return () => {
169
+ unsubscribe();
170
+ if (handoffTimer != null) {
171
+ clearTimeout(handoffTimer);
172
+ handoffTimer = null;
173
+ }
174
+ if (verifyTimer != null) {
175
+ clearTimeout(verifyTimer);
176
+ verifyTimer = null;
177
+ }
178
+ };
179
+ };
180
+ return {
181
+ get profile() {
182
+ return profile;
183
+ },
184
+ get inset() {
185
+ return model.inset;
186
+ },
187
+ get transition() {
188
+ return model.transition;
189
+ },
190
+ presentation(time = win.performance.now()) {
191
+ return model.presentation(time);
192
+ },
193
+ subscribe(listener) {
194
+ listeners.add(listener);
195
+ if (detach == null) {
196
+ detach = attach();
197
+ }
198
+ return () => {
199
+ listeners.delete(listener);
200
+ if (listeners.size === 0 && detach != null) {
201
+ detach();
202
+ detach = null;
203
+ }
204
+ };
205
+ }
206
+ };
207
+ }
@@ -0,0 +1,93 @@
1
+ /** Which browser's keyboard reports the guide reads. */
2
+ export type KeyboardProfile = 'chromium';
3
+ /** An eased slide: the curve a transition plays (Gboard going up or down). */
4
+ export interface KeyboardCurve {
5
+ /** Length, ms. */
6
+ readonly durationMs: number;
7
+ /** Cubic-bézier control points. */
8
+ readonly points: readonly [number, number, number, number];
9
+ }
10
+ /** How the keyboard's cover changes. */
11
+ export type KeyboardTransitionKind = 'show' | 'hide' | 'change';
12
+ /** One change of the keyboard's cover, as the guide reports it. */
13
+ export interface KeyboardTransition {
14
+ readonly kind: KeyboardTransitionKind;
15
+ /** Inset presented at `start`, CSS px. */
16
+ readonly from: number;
17
+ /** Inset the keyboard comes to rest at, CSS px. */
18
+ readonly to: number;
19
+ /**
20
+ * When the keyboard starts to move, ms on the document timeline
21
+ * (`performance.now()`). For a slide Chrome reported, the guide dates it
22
+ * back to Gboard's start. For a hide after a blur, it is shortly ahead.
23
+ */
24
+ readonly start: number;
25
+ /** The inset's velocity at `start`, px/s (0 unless it interrupts one). */
26
+ readonly velocity: number;
27
+ /** False when the inset jumps at `start` (another keyboard while one is up). */
28
+ readonly animated: boolean;
29
+ /** True until the visual viewport confirms a transition the guide inferred. */
30
+ readonly predicted: boolean;
31
+ /**
32
+ * The eased slide the keyboard moves on (Gboard in Chrome on Android), or
33
+ * null when the inset jumps. A surface riding the keyboard plays it from
34
+ * `start`, as rideKeyboard does. It is the keyboard's own motion, with no
35
+ * motion role.
36
+ */
37
+ readonly curve: KeyboardCurve | null;
38
+ }
39
+ /** The guide's clock-free core: inputs carry their own times. */
40
+ export interface KeyboardModel {
41
+ /** The inset the keyboard rests at (or is heading to), CSS px. */
42
+ readonly inset: number;
43
+ /** The latest transition. */
44
+ readonly transition: KeyboardTransition;
45
+ /** The inset presented at `time`, CSS px. */
46
+ presentation(time: number): number;
47
+ /** The presented inset's velocity at `time`, px/s. */
48
+ velocity(time: number): number;
49
+ /**
50
+ * The visual viewport reports an inset (null when unknown) at `time` (the
51
+ * event's timestamp). Returns the transition the report starts, if any.
52
+ */
53
+ report(inset: number | null, time: number): KeyboardTransition | null;
54
+ /**
55
+ * A control that brings up a keyboard lost focus to nothing that brings
56
+ * up one.
57
+ */
58
+ blurred(time: number): KeyboardTransition | null;
59
+ /** A control that brings up a keyboard gained focus. */
60
+ focused(time: number): KeyboardTransition | null;
61
+ /**
62
+ * Checks a predicted transition against the viewport once it should have
63
+ * ended; moves to what the viewport says when they disagree.
64
+ */
65
+ verify(inset: number | null, time: number): KeyboardTransition | null;
66
+ }
67
+ /**
68
+ * Creates the guide's core. Tests drive it with explicit times, and the
69
+ * window's guide feeds it events.
70
+ *
71
+ * Chrome reports each keyboard slide through the visual viewport just after
72
+ * it starts (README.md, "What the page sees"). Content rides Gboard's own
73
+ * slides, fitted on Chromium 158 with Gboard (motion/android/keyboard.ts).
74
+ *
75
+ * - Show: from the report, dated back `androidKeyboard.showReportLagMs`, on
76
+ * `showCurve` over `showMs`, joining the slide where it is. The report's
77
+ * first painted frame trails Gboard's first moved frame by 14 to 35 ms. An
78
+ * `<input>` that brings an accessory bar first gives a smaller report
79
+ * before the keyboard's. It rides to the last keyboard's height instead, as
80
+ * a prediction, and the keyboard's report confirms it.
81
+ * - Hide: on `hideCurve` over `hideMs`. It starts
82
+ * `androidKeyboard.hideAfterBlurMs` after a blur that brings no keyboard,
83
+ * as a prediction, because Gboard starts down at the blur. When nothing
84
+ * predicted it, as with Back, it starts from Chrome's report, dated back
85
+ * `hideReportLagMs`, the lag measured on blurs.
86
+ * - A keyboard that changes while up, such as another keyboard, lays out at
87
+ * once.
88
+ *
89
+ * A prediction the viewport has not confirmed by its end gives way to what
90
+ * the viewport says (`verify`).
91
+ */
92
+ export declare function createKeyboardModel(initialInset?: number): KeyboardModel;
93
+ //# sourceMappingURL=keyboardModel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboardModel.d.ts","sourceRoot":"","sources":["../../src/keyboard/keyboardModel.ts"],"names":[],"mappings":"AAkBA,wDAAwD;AACxD,MAAM,MAAM,eAAe,GAAG,UAAU,CAAC;AAEzC,8EAA8E;AAC9E,MAAM,WAAW,aAAa;IAC5B,kBAAkB;IAClB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,mCAAmC;IACnC,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5D;AAED,wCAAwC;AACxC,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEhE,mEAAmE;AACnE,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,0CAA0C;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;CACtC;AAED,iEAAiE;AACjE,MAAM,WAAW,aAAa;IAC5B,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,6CAA6C;IAC7C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IACnC,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC;IAC/B;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACtE;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACjD,wDAAwD;IACxD,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACjD;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAAC;CACvE;AAUD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,SAAI,GAAG,aAAa,CAkLnE"}
@@ -0,0 +1,220 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file keyboardModel.ts
5
+ * @input `keyboard` from the Android table: Gboard's slides and Chrome's
6
+ * report timings
7
+ * @output createKeyboardModel, the keyboard layout guide's clock-free core,
8
+ * and the KeyboardTransition and KeyboardModel types
9
+ * @position The state of the keyboard layout guide (keyboardLayoutGuide.ts).
10
+ * It infers the keyboard's transition from the visual viewport's reports
11
+ * and focus changes, each input with its own time. It is internal to core.
12
+ *
13
+ * SYNC: When modified, update /packages/core/src/keyboard/README.md
14
+ */
15
+
16
+ import { keyboard as androidKeyboard } from "../motion/android/keyboard.js";
17
+ import { cubicBezier } from "../motion/bezier.js";
18
+
19
+ /** Which browser's keyboard reports the guide reads. */
20
+
21
+ /** An eased slide: the curve a transition plays (Gboard going up or down). */
22
+
23
+ /** How the keyboard's cover changes. */
24
+
25
+ /** One change of the keyboard's cover, as the guide reports it. */
26
+
27
+ /** The guide's clock-free core: inputs carry their own times. */
28
+
29
+ const EPSILON = 0.5;
30
+
31
+ /** The inset and its velocity, seconds after a transition's start. */
32
+
33
+ /**
34
+ * Creates the guide's core. Tests drive it with explicit times, and the
35
+ * window's guide feeds it events.
36
+ *
37
+ * Chrome reports each keyboard slide through the visual viewport just after
38
+ * it starts (README.md, "What the page sees"). Content rides Gboard's own
39
+ * slides, fitted on Chromium 158 with Gboard (motion/android/keyboard.ts).
40
+ *
41
+ * - Show: from the report, dated back `androidKeyboard.showReportLagMs`, on
42
+ * `showCurve` over `showMs`, joining the slide where it is. The report's
43
+ * first painted frame trails Gboard's first moved frame by 14 to 35 ms. An
44
+ * `<input>` that brings an accessory bar first gives a smaller report
45
+ * before the keyboard's. It rides to the last keyboard's height instead, as
46
+ * a prediction, and the keyboard's report confirms it.
47
+ * - Hide: on `hideCurve` over `hideMs`. It starts
48
+ * `androidKeyboard.hideAfterBlurMs` after a blur that brings no keyboard,
49
+ * as a prediction, because Gboard starts down at the blur. When nothing
50
+ * predicted it, as with Back, it starts from Chrome's report, dated back
51
+ * `hideReportLagMs`, the lag measured on blurs.
52
+ * - A keyboard that changes while up, such as another keyboard, lays out at
53
+ * once.
54
+ *
55
+ * A prediction the viewport has not confirmed by its end gives way to what
56
+ * the viewport says (`verify`).
57
+ */
58
+ export function createKeyboardModel(initialInset = 0) {
59
+ let transition = {
60
+ kind: 'change',
61
+ from: initialInset,
62
+ to: initialInset,
63
+ start: -Infinity,
64
+ velocity: 0,
65
+ animated: false,
66
+ predicted: false,
67
+ curve: null
68
+ };
69
+ // The inset over seconds since the transition's start.
70
+ let curve = null;
71
+ // An eased slide's value and velocity.
72
+ const easedCurve = (from, to, slide) => {
73
+ const span = slide.durationMs / 1000;
74
+ const ease = cubicBezier(slide.points);
75
+ const value = t => from + (to - from) * ease(Math.min(1, Math.max(0, t / span)));
76
+ return {
77
+ value,
78
+ velocity: t => {
79
+ if (t >= span) {
80
+ return 0;
81
+ }
82
+ const a = Math.max(0, t - 0.001);
83
+ const b = Math.min(span, t + 0.001);
84
+ return (value(b) - value(a)) / (b - a);
85
+ }
86
+ };
87
+ };
88
+ // Gboard's slides, as Chrome draws them: the table's show and hide curves.
89
+ const showSlide = {
90
+ durationMs: androidKeyboard.showMs,
91
+ points: androidKeyboard.showCurve
92
+ };
93
+ const hideSlide = {
94
+ durationMs: androidKeyboard.hideMs,
95
+ points: androidKeyboard.hideCurve
96
+ };
97
+ // How long the current transition moves for.
98
+ const lengthOf = () => transition.curve != null ? transition.curve.durationMs : 0;
99
+ // The inset of the last keyboard that was up, for a hide that turns back.
100
+ let shownInset = initialInset;
101
+ const presentation = time => {
102
+ // A jump has happened at its start; a curve starts from `from`.
103
+ if (time < transition.start) {
104
+ return transition.from;
105
+ }
106
+ if (curve == null || time >= transition.start + lengthOf()) {
107
+ return transition.to;
108
+ }
109
+ return curve.value((time - transition.start) / 1000);
110
+ };
111
+ const velocity = time => {
112
+ if (time <= transition.start) {
113
+ return transition.velocity;
114
+ }
115
+ if (curve == null || time >= transition.start + lengthOf()) {
116
+ return 0;
117
+ }
118
+ return curve.velocity((time - transition.start) / 1000);
119
+ };
120
+ const begin = (kind, to, start, {
121
+ animated = true,
122
+ predicted = false
123
+ } = {}) => {
124
+ // Never date a transition before the one it interrupts.
125
+ const at = Math.max(start, transition.start);
126
+ const from = presentation(at);
127
+ const v = animated ? velocity(at) : 0;
128
+ const slide = animated ? to > from ? showSlide : hideSlide : null;
129
+ transition = {
130
+ kind,
131
+ from,
132
+ to,
133
+ start: at,
134
+ velocity: v,
135
+ animated,
136
+ predicted,
137
+ curve: slide
138
+ };
139
+ curve = slide != null && Math.abs(to - from) + Math.abs(v) > 0 ? easedCurve(from, to, slide) : null;
140
+ if (to > EPSILON) {
141
+ shownInset = to;
142
+ }
143
+ return transition;
144
+ };
145
+ const confirm = () => {
146
+ if (transition.predicted) {
147
+ transition = {
148
+ ...transition,
149
+ predicted: false
150
+ };
151
+ }
152
+ return null;
153
+ };
154
+ return {
155
+ get inset() {
156
+ return transition.to;
157
+ },
158
+ get transition() {
159
+ return transition;
160
+ },
161
+ presentation,
162
+ velocity,
163
+ report(inset, time) {
164
+ if (inset == null) {
165
+ return null;
166
+ }
167
+ const target = transition.to;
168
+ if (Math.abs(inset - target) < EPSILON) {
169
+ return confirm();
170
+ }
171
+ if (inset <= EPSILON) {
172
+ // A hide nothing predicted (Back): Chrome reports it just after
173
+ // Gboard starts down, so it rides from that moment.
174
+ return begin('hide', 0, time - androidKeyboard.hideReportLagMs);
175
+ }
176
+ if (target <= EPSILON) {
177
+ // Chrome reports the show just after Gboard starts up: ride from
178
+ // that moment, joining the slide where it is. A smaller first
179
+ // report is the accessory bar, the keyboard's own following: ride
180
+ // to the last keyboard's height and let its report confirm it.
181
+ const predicted = shownInset > inset + EPSILON;
182
+ return begin('show', predicted ? shownInset : inset, time - androidKeyboard.showReportLagMs, {
183
+ predicted
184
+ });
185
+ }
186
+ // The keyboard changed while up (another keyboard, or the keyboard's
187
+ // report after the accessory bar's): Chrome lays the page out at once.
188
+ return begin('change', inset, time, {
189
+ animated: false
190
+ });
191
+ },
192
+ blurred(time) {
193
+ if (transition.to <= EPSILON) {
194
+ return null;
195
+ }
196
+ return begin('hide', 0, time + androidKeyboard.hideAfterBlurMs, {
197
+ predicted: true
198
+ });
199
+ },
200
+ focused(time) {
201
+ if (transition.kind !== 'hide' || !transition.predicted) {
202
+ return null;
203
+ }
204
+ // Focus came back before the predicted hide finished: the keyboard
205
+ // turns back to the inset it had.
206
+ return begin('show', shownInset, Math.max(time, transition.start), {
207
+ predicted: true
208
+ });
209
+ },
210
+ verify(inset, time) {
211
+ if (!transition.predicted || inset == null || time < transition.start + lengthOf()) {
212
+ return null;
213
+ }
214
+ if (Math.abs(inset - transition.to) < EPSILON) {
215
+ return confirm();
216
+ }
217
+ return begin(inset > transition.to ? 'show' : 'hide', inset, time);
218
+ }
219
+ };
220
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @file rideKeyboard.ts
3
+ * @input An animator, target values and a keyboard transition, with its
4
+ * start and the curve the guide publishes with it, and ride options
5
+ * @output rideKeyboard, which moves an element with the keyboard on the
6
+ * keyboard's own motion
7
+ * @position Keyboard module, internal to core. A guide subscriber calls it
8
+ * for each transition, as BottomSheet's hook does.
9
+ *
10
+ * SYNC: When modified, update /packages/core/src/keyboard/README.md
11
+ */
12
+ import type { Animator, MotionHandle, MotionValues } from '../motion/animator.js';
13
+ import type { KeyboardTransition } from './keyboardModel.js';
14
+ /** How a ride treats Reduce Motion. */
15
+ export interface RideOptions {
16
+ /**
17
+ * Under Reduce Motion, lands the element at once and fades it in. Without
18
+ * it, the animator's own Reduce Motion plan applies.
19
+ */
20
+ readonly entrance?: boolean;
21
+ }
22
+ /**
23
+ * Moves `animator`'s element to `to` with the keyboard: on its curve from
24
+ * `transition.start`, or at once when the keyboard jumps.
25
+ *
26
+ * Android's keyboard moves on Gboard's slides up and down
27
+ * (motion/android/keyboard.ts). The transition publishes the slide
28
+ * (`curve`), and the ride plays it. Every ride starts at the transition's
29
+ * start, so content that joins a slide late lands where the keyboard is. A
30
+ * transition that is not animated lands at once, such as a keyboard changing
31
+ * while up. So does a slide that has ended by the call. These values are the
32
+ * keyboard's own motion, with no motion role. The system moves the keyboard
33
+ * on them whatever the page does, so any other curve would leave the element
34
+ * out of step.
35
+ *
36
+ * @example
37
+ * ```
38
+ * guide.subscribe(t => rideKeyboard(dock.animator, {y: -lift(t.to)}, t));
39
+ * ```
40
+ */
41
+ export declare function rideKeyboard(animator: Animator, to: MotionValues, transition: KeyboardTransition, { entrance }?: RideOptions): MotionHandle | null;
42
+ //# sourceMappingURL=rideKeyboard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rideKeyboard.d.ts","sourceRoot":"","sources":["../../src/keyboard/rideKeyboard.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EACV,QAAQ,EAER,YAAY,EAEZ,YAAY,EACb,MAAM,oBAAoB,CAAC;AAE5B,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,iBAAiB,CAAC;AAExD,uCAAuC;AACvC,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,QAAQ,EAClB,EAAE,EAAE,YAAY,EAChB,UAAU,EAAE,kBAAkB,EAC9B,EAAC,QAAgB,EAAC,GAAE,WAAgB,GACnC,YAAY,GAAG,IAAI,CA0CrB"}
@@ -0,0 +1,77 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file rideKeyboard.ts
5
+ * @input An animator, target values and a keyboard transition, with its
6
+ * start and the curve the guide publishes with it, and ride options
7
+ * @output rideKeyboard, which moves an element with the keyboard on the
8
+ * keyboard's own motion
9
+ * @position Keyboard module, internal to core. A guide subscriber calls it
10
+ * for each transition, as BottomSheet's hook does.
11
+ *
12
+ * SYNC: When modified, update /packages/core/src/keyboard/README.md
13
+ */
14
+
15
+ import { cubicBezier } from "../motion/bezier.js";
16
+
17
+ /** How a ride treats Reduce Motion. */
18
+
19
+ /**
20
+ * Moves `animator`'s element to `to` with the keyboard: on its curve from
21
+ * `transition.start`, or at once when the keyboard jumps.
22
+ *
23
+ * Android's keyboard moves on Gboard's slides up and down
24
+ * (motion/android/keyboard.ts). The transition publishes the slide
25
+ * (`curve`), and the ride plays it. Every ride starts at the transition's
26
+ * start, so content that joins a slide late lands where the keyboard is. A
27
+ * transition that is not animated lands at once, such as a keyboard changing
28
+ * while up. So does a slide that has ended by the call. These values are the
29
+ * keyboard's own motion, with no motion role. The system moves the keyboard
30
+ * on them whatever the page does, so any other curve would leave the element
31
+ * out of step.
32
+ *
33
+ * @example
34
+ * ```
35
+ * guide.subscribe(t => rideKeyboard(dock.animator, {y: -lift(t.to)}, t));
36
+ * ```
37
+ */
38
+ export function rideKeyboard(animator, to, transition, {
39
+ entrance = false
40
+ } = {}) {
41
+ // A frame after a busy task still draws an animation at that task's
42
+ // start. The ride sets a slide that has already ended, so no frame shows the
43
+ // element short of its target.
44
+ if (!transition.animated || transition.curve == null || performance.now() >= transition.start + transition.curve.durationMs) {
45
+ animator.set(to);
46
+ return null;
47
+ }
48
+ // Under Reduce Motion nothing travels. The animator's default cross-fades
49
+ // the element to its new place on a touch screen, and elsewhere moves it at
50
+ // once. An entrance lands it at once and fades it in.
51
+ const options = entrance ? {
52
+ at: transition.start,
53
+ crossfadeAs: 'entrance'
54
+ } : {
55
+ at: transition.start
56
+ };
57
+ const span = transition.curve.durationMs / 1000;
58
+ const ease = cubicBezier(transition.curve.points);
59
+ const curves = {};
60
+ for (const [property, target] of Object.entries(to)) {
61
+ const from = animator.get(property, transition.start);
62
+ const at = t => from + (target - from) * ease(Math.min(1, Math.max(0, t / span)));
63
+ curves[property] = {
64
+ duration: span,
65
+ at,
66
+ velocity: t => {
67
+ if (t >= span) {
68
+ return 0;
69
+ }
70
+ const a = Math.max(0, t - 0.001);
71
+ const b = Math.min(span, t + 0.001);
72
+ return (at(b) - at(a)) / (b - a);
73
+ }
74
+ };
75
+ }
76
+ return animator.play(curves, options);
77
+ }