@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.
- package/CHANGELOG.md +132 -0
- package/dist/BottomSheet/BottomSheetPanel.d.ts +1 -1
- package/dist/BottomSheet/BottomSheetPanel.d.ts.map +1 -1
- package/dist/BottomSheet/BottomSheetPanel.js +30 -14
- package/dist/BottomSheet/bottomSheetStack.js +1 -1
- package/dist/BottomSheet/useSheetGestures.d.ts +3 -1
- package/dist/BottomSheet/useSheetGestures.d.ts.map +1 -1
- package/dist/BottomSheet/useSheetGestures.js +4 -0
- package/dist/CheckboxList/CheckboxList.d.ts +13 -2
- package/dist/CheckboxList/CheckboxList.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxList.js +10 -4
- package/dist/CheckboxList/CheckboxListContext.d.ts +7 -0
- package/dist/CheckboxList/CheckboxListContext.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListContext.js +2 -1
- package/dist/CheckboxList/CheckboxListItem.d.ts.map +1 -1
- package/dist/CheckboxList/CheckboxListItem.js +14 -2
- package/dist/CheckboxList/requiredGroup.d.ts +22 -0
- package/dist/CheckboxList/requiredGroup.d.ts.map +1 -0
- package/dist/CheckboxList/requiredGroup.js +52 -0
- package/dist/DateInput/PickerWheel.d.ts +1 -1
- package/dist/DateInput/PickerWheel.d.ts.map +1 -1
- package/dist/DateInput/PickerWheel.js +27 -6
- package/dist/DateInput/Wheel.d.ts +2 -2
- package/dist/DateInput/Wheel.d.ts.map +1 -1
- package/dist/DateInput/Wheel.js +20 -6
- package/dist/DateInput/datePickerTouch.d.ts +13 -0
- package/dist/DateInput/datePickerTouch.d.ts.map +1 -0
- package/dist/DateInput/datePickerTouch.js +14 -0
- package/dist/DateInput/wheelOptions.d.ts +22 -2
- package/dist/DateInput/wheelOptions.d.ts.map +1 -1
- package/dist/DateInput/wheelOptions.js +14 -2
- package/dist/DateTimeInput/TouchDateTimeField.d.ts.map +1 -1
- package/dist/DateTimeInput/TouchDateTimeField.js +62 -119
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +6 -1
- package/dist/Overlay/Overlay.d.ts.map +1 -1
- package/dist/Overlay/Overlay.js +7 -2
- package/dist/PowerSearch/PowerSearch.d.ts +4 -2
- package/dist/PowerSearch/PowerSearch.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts +24 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.d.ts.map +1 -1
- package/dist/PowerSearch/PowerSearchBottomSheet.js +172 -50
- package/dist/Resizable/ResizeHandle.d.ts.map +1 -1
- package/dist/Resizable/ResizeHandle.js +15 -15
- package/dist/Resizable/createResizable.d.ts +70 -16
- package/dist/Resizable/createResizable.d.ts.map +1 -1
- package/dist/Resizable/createResizable.js +180 -22
- package/dist/Resizable/separatorAttributes.d.ts +45 -0
- package/dist/Resizable/separatorAttributes.d.ts.map +1 -0
- package/dist/Resizable/separatorAttributes.js +35 -0
- package/dist/Resizable/useResizable.js +2 -2
- package/dist/TimeInput/TimeWheels.d.ts +32 -0
- package/dist/TimeInput/TimeWheels.d.ts.map +1 -0
- package/dist/TimeInput/TimeWheels.js +144 -0
- package/dist/TimeInput/TouchTimeField.d.ts.map +1 -1
- package/dist/TimeInput/TouchTimeField.js +63 -118
- package/dist/TimeInput/timeWheelRules.d.ts +124 -0
- package/dist/TimeInput/timeWheelRules.d.ts.map +1 -0
- package/dist/TimeInput/timeWheelRules.js +277 -0
- package/dist/Tokenizer/TokenizerBottomSheet.js +8 -12
- package/dist/Tooltip/useTooltip.d.ts.map +1 -1
- package/dist/Tooltip/useTooltip.js +6 -0
- package/dist/astryx.css +3 -1
- package/dist/gestures/GestureHost.d.ts.map +1 -1
- package/dist/gestures/GestureHost.js +3 -0
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.d.ts.map +1 -1
- package/dist/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.js +5 -2
- package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts +8 -0
- package/dist/i18n/generated-english/Resizable/createResizable.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/Resizable/createResizable.generated.js +13 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts +8 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.d.ts.map +1 -0
- package/dist/i18n/generated-english/TimeInput/TimeWheels.generated.js +17 -0
- package/dist/i18n/generated-locales/en.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/en.generated.js +5 -2
- package/dist/i18n/generated-locales/pseudo.generated.d.ts.map +1 -1
- package/dist/i18n/generated-locales/pseudo.generated.js +5 -2
- package/dist/keyboard/index.d.ts +17 -0
- package/dist/keyboard/index.d.ts.map +1 -0
- package/dist/keyboard/index.js +19 -0
- package/dist/keyboard/keyboardHeight.d.ts +149 -0
- package/dist/keyboard/keyboardHeight.d.ts.map +1 -0
- package/dist/keyboard/keyboardHeight.js +432 -0
- package/dist/keyboard/keyboardLayoutGuide.d.ts +59 -0
- package/dist/keyboard/keyboardLayoutGuide.d.ts.map +1 -0
- package/dist/keyboard/keyboardLayoutGuide.js +207 -0
- package/dist/keyboard/keyboardModel.d.ts +93 -0
- package/dist/keyboard/keyboardModel.d.ts.map +1 -0
- package/dist/keyboard/keyboardModel.js +220 -0
- package/dist/keyboard/rideKeyboard.d.ts +42 -0
- package/dist/keyboard/rideKeyboard.d.ts.map +1 -0
- package/dist/keyboard/rideKeyboard.js +77 -0
- package/dist/keyboard/useMobileKeyboard.d.ts +61 -0
- package/dist/keyboard/useMobileKeyboard.d.ts.map +1 -0
- package/dist/keyboard/useMobileKeyboard.js +699 -0
- package/dist/motion/android/index.d.ts +16 -0
- package/dist/motion/android/index.d.ts.map +1 -0
- package/dist/motion/android/index.js +18 -0
- package/dist/motion/android/keyboard.d.ts +83 -0
- package/dist/motion/android/keyboard.d.ts.map +1 -0
- package/dist/motion/android/keyboard.js +96 -0
- package/dist/motion/uikit/datePicker.d.ts +58 -0
- package/dist/motion/uikit/datePicker.d.ts.map +1 -0
- package/dist/motion/uikit/datePicker.js +60 -0
- package/dist/motion/uikit/index.d.ts +4 -3
- package/dist/motion/uikit/index.d.ts.map +1 -1
- package/dist/motion/uikit/index.js +4 -3
- package/dist/utils/interactionOverlay.stylex.d.ts +3 -2
- package/dist/utils/interactionOverlay.stylex.d.ts.map +1 -1
- package/dist/utils/interactionOverlay.stylex.js +3 -2
- package/dist/utils/layoutViewport.d.ts +25 -0
- package/dist/utils/layoutViewport.d.ts.map +1 -0
- package/dist/utils/layoutViewport.js +102 -0
- package/dist/utils/pressFeedback.d.ts.map +1 -1
- package/dist/utils/pressFeedback.js +74 -9
- package/dist/utils/pressGesture.d.ts +4 -2
- package/dist/utils/pressGesture.d.ts.map +1 -1
- package/dist/utils/pressGesture.js +3 -1
- package/dist/utils/selectionLock.d.ts +10 -1
- package/dist/utils/selectionLock.d.ts.map +1 -1
- package/dist/utils/selectionLock.js +31 -5
- package/dist/utils/sharedMutationObserver.d.ts +2 -1
- package/dist/utils/sharedMutationObserver.d.ts.map +1 -1
- package/dist/utils/sharedMutationObserver.js +2 -1
- package/dist/utils/timeParser.d.ts +2 -1
- package/dist/utils/timeParser.d.ts.map +1 -1
- package/dist/utils/timeParser.js +29 -3
- package/locales/en.json +21 -9
- package/locales/pseudo.json +15 -6
- package/package.json +5 -5
- package/src/BottomSheet/BottomSheet.doc.mjs +1 -1
- package/src/BottomSheet/BottomSheetPanel.tsx +41 -13
- package/src/BottomSheet/BottomSheetSwitcher.doc.mjs +5 -0
- package/src/BottomSheet/bottomSheetStack.ts +1 -1
- package/src/BottomSheet/useSheetGestures.ts +6 -0
- package/src/CheckboxList/CheckboxList.doc.mjs +7 -0
- package/src/CheckboxList/CheckboxList.tsx +34 -2
- package/src/CheckboxList/CheckboxListContext.tsx +9 -1
- package/src/CheckboxList/CheckboxListItem.tsx +23 -2
- package/src/CheckboxList/requiredGroup.ts +77 -0
- package/src/DateInput/PickerWheel.tsx +37 -4
- package/src/DateInput/Wheel.tsx +30 -3
- package/src/DateInput/datePickerTouch.ts +15 -0
- package/src/DateInput/wheelOptions.ts +31 -2
- package/src/DateTimeInput/TouchDateTimeField.tsx +94 -178
- package/src/HoverCard/useHoverCard.tsx +6 -1
- package/src/Overlay/Overlay.tsx +7 -2
- package/src/PowerSearch/PowerSearch.doc.mjs +2 -2
- package/src/PowerSearch/PowerSearch.tsx +4 -2
- package/src/PowerSearch/PowerSearchBottomSheet.tsx +237 -62
- package/src/Resizable/ResizeHandle.tsx +18 -26
- package/src/Resizable/createResizable.ts +242 -27
- package/src/Resizable/separatorAttributes.ts +73 -0
- package/src/Resizable/useResizable.ts +2 -2
- package/src/TimeInput/TimeWheels.tsx +218 -0
- package/src/TimeInput/TouchTimeField.tsx +93 -157
- package/src/TimeInput/timeWheelRules.ts +352 -0
- package/src/Tokenizer/TokenizerBottomSheet.tsx +8 -12
- package/src/Tooltip/useTooltip.tsx +6 -0
- package/src/gestures/GestureHost.ts +5 -4
- package/src/i18n/generated-english/PowerSearch/PowerSearchBottomSheet.generated.ts +5 -2
- package/src/i18n/generated-english/Resizable/createResizable.generated.ts +14 -0
- package/src/i18n/generated-english/TimeInput/TimeWheels.generated.ts +18 -0
- package/src/i18n/generated-locales/en.generated.ts +6 -4
- package/src/i18n/generated-locales/pseudo.generated.ts +6 -4
- package/src/keyboard/README.md +197 -0
- package/src/keyboard/index.ts +48 -0
- package/src/keyboard/keyboardHeight.ts +543 -0
- package/src/keyboard/keyboardLayoutGuide.ts +266 -0
- package/src/keyboard/keyboardModel.ts +301 -0
- package/src/keyboard/rideKeyboard.ts +100 -0
- package/src/keyboard/useMobileKeyboard.ts +866 -0
- package/src/motion/MEASUREMENTS.md +10 -0
- package/src/motion/README.md +2 -0
- package/src/motion/android/index.ts +18 -0
- package/src/motion/android/keyboard.ts +96 -0
- package/src/motion/uikit/datePicker.ts +60 -0
- package/src/motion/uikit/index.ts +4 -3
- package/src/utils/interactionOverlay.stylex.ts +3 -2
- package/src/utils/layoutViewport.ts +104 -0
- package/src/utils/pressFeedback.ts +99 -21
- package/src/utils/pressGesture.ts +4 -2
- package/src/utils/selectionLock.ts +40 -6
- package/src/utils/sharedMutationObserver.ts +2 -1
- package/src/utils/timeParser.ts +40 -3
- package/dist/BottomSheet/useMobileKeyboard.d.ts +0 -21
- package/dist/BottomSheet/useMobileKeyboard.d.ts.map +0 -1
- package/dist/BottomSheet/useMobileKeyboard.js +0 -413
- 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
|
+
}
|