@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,149 @@
1
+ export declare const KEYBOARD_EPSILON = 1;
2
+ /**
3
+ * Whether this window measures the keyboard: a top-level document with a
4
+ * visual viewport. Capabilities decide it, never the user agent or the
5
+ * pointer. The module measures a browser with its own keyboard geometry API,
6
+ * such as Chrome on Android with the VirtualKeyboard API, the same way.
7
+ * Its visual viewport shrinks for the keyboard while the layout viewport keeps
8
+ * its height, as everywhere else. A desktop browser's visual viewport never
9
+ * shrinks for a keyboard, so it reads 0. Where the app sets
10
+ * `interactive-widget=resizes-content`, Chrome shrinks the layout viewport
11
+ * with the keyboard. The height is then 0 too, and nothing rides, because the
12
+ * browser has made the room itself.
13
+ */
14
+ export declare function isKeyboardMeasurable(win: Window): boolean;
15
+ /**
16
+ * The on-screen keyboard's height over the layout viewport, CSS px. It is 0
17
+ * when none is shown, also while pinch-zoomed, and wherever
18
+ * `isKeyboardMeasurable` is false.
19
+ */
20
+ export declare function measureKeyboardHeight(win: Window): number;
21
+ /** A report from the keyboard module, in the order the window raised them. */
22
+ export type KeyboardHeightEvent = {
23
+ /** The visual viewport or the window changed size or position. */
24
+ type: 'viewport';
25
+ /** The keyboard height after this report. */
26
+ height: number;
27
+ /** The report's event time (the document timeline). */
28
+ timeStamp: number;
29
+ } | {
30
+ /** Focus moved into (`focusin`) or out of (`focusout`) `target`. */
31
+ type: 'focusin' | 'focusout';
32
+ height: number;
33
+ timeStamp: number;
34
+ target: EventTarget | null;
35
+ relatedTarget: EventTarget | null;
36
+ };
37
+ export declare function isNodeTarget(target: EventTarget | null): boolean;
38
+ /**
39
+ * Calls `listener` with every report of `win`'s keyboard until the caller
40
+ * runs the returned function. It reports the viewport's changes, and focus
41
+ * moves too when the caller sets `focus`. All subscribers of a window share
42
+ * one set of listeners, and its focus listeners exist only while a subscriber
43
+ * asks for focus moves.
44
+ *
45
+ * @example
46
+ * ```
47
+ * const stop = subscribeKeyboardHeight(window, event => {
48
+ * if (event.type === 'viewport') layout(event.height);
49
+ * });
50
+ * ```
51
+ */
52
+ export declare function subscribeKeyboardHeight(win: Window, listener: (event: KeyboardHeightEvent) => void, { focus }?: {
53
+ focus?: boolean;
54
+ }): () => void;
55
+ /**
56
+ * `win`'s keyboard height. While the module listens, it is the height
57
+ * measured at the last report. Every reader of a keyboard change, such as
58
+ * each component's hook, shares that one layout read. Every change of the
59
+ * visual viewport or the window is a report, so a reader between two reports
60
+ * sees the current height. With nothing listening yet, the call measures the
61
+ * height now.
62
+ */
63
+ export declare function getKeyboardHeight(win: Window): number;
64
+ /**
65
+ * Whether focusing `element` brings up a keyboard for typing text. A control
66
+ * with `inputmode="none"` brings up none.
67
+ */
68
+ export declare function isTextEntryControl(element: Element | null): element is HTMLElement;
69
+ /** The text-entry control inside `container` that `target` is or labels. */
70
+ export declare function findTextEntryControl(target: EventTarget | null, container: HTMLElement): HTMLElement | null;
71
+ /** The caller's rules for refuseFocusReveal. */
72
+ export interface RefuseFocusRevealOptions {
73
+ /** Whether to claim at all just now, checked on every blur. */
74
+ isActive?: () => boolean;
75
+ /** The control a focus move is heading to, when this caller claims it. */
76
+ claim: (destination: EventTarget | null) => HTMLElement | null;
77
+ /** Focus left `origin` for nothing (the keyboard's Done): park it, if at all. */
78
+ park?: (origin: EventTarget | null) => void;
79
+ }
80
+ /**
81
+ * Delivers the focus moves `claim` accepts with `preventScroll`, so the
82
+ * browser never reveals their destination itself. It stops when the caller
83
+ * runs the returned function.
84
+ *
85
+ * The browser dispatches `blur` in the capture phase ahead of its own focus
86
+ * step, and `blur` names the destination in `relatedTarget`. Focusing it there
87
+ * with preventScroll settles the change. The browser's step then finds the
88
+ * element already active, so it dispatches nothing further and reveals
89
+ * nothing.
90
+ *
91
+ * @example
92
+ * ```
93
+ * const stop = refuseFocusReveal(document, {
94
+ * claim: target => findTextEntryControl(target, body),
95
+ * park: () => sheet.focus({preventScroll: true}),
96
+ * });
97
+ * ```
98
+ */
99
+ export declare function refuseFocusReveal(doc: Document, { isActive, claim, park }: RefuseFocusRevealOptions): () => void;
100
+ /**
101
+ * Test-only: how many callers refuse the focus reveal in `doc`, through its
102
+ * one shared subscription. Not part of the runtime API.
103
+ *
104
+ * @internal
105
+ */
106
+ export declare function __focusTakeoverCallersForTest(doc: Document): number;
107
+ /** The caller's rules for watchRevealPan. */
108
+ export interface WatchRevealPanOptions {
109
+ /** Whether the focused field is the caller's to keep in view. */
110
+ owns: (focused: HTMLElement) => boolean;
111
+ /** Called on every touch start, which ends the watch for the report. */
112
+ onTouch?: () => void;
113
+ }
114
+ /** A watch for the browser's own reveal pan (see watchRevealPan). */
115
+ export interface RevealPanWatch {
116
+ /** A report changed the keyboard's height: remember where everything sat. */
117
+ report: () => void;
118
+ /** Whether the visual viewport has since moved down as a reveal pan. */
119
+ isRevealPan: (height: number) => boolean;
120
+ /**
121
+ * Pans the visual viewport back to where it sat at the report, without
122
+ * scrolling the document. It scrolls `anchor` into view, an element fixed at
123
+ * the layout viewport's top, or else a probe fixed where the viewport sat.
124
+ */
125
+ putBack: (anchor?: Element | null) => void;
126
+ /** Ends the watch; the shared touch subscription goes with the last watch. */
127
+ stop: () => void;
128
+ }
129
+ /**
130
+ * Some browsers reveal a focused field themselves, after the keyboard's
131
+ * report, by panning the visual viewport to it while the keyboard still
132
+ * covers it. Firefox for Android pans 54 to 143 ms after the report. Chrome
133
+ * on Android animates a pan of the keyboard's whole height from about 100 to
134
+ * 170 ms after it. The page gets no frame until that pan ends. Such a pan
135
+ * moves the visual viewport down within the
136
+ * layout viewport. Since the report there is no touch, document scroll or
137
+ * zoom, and a text field the caller owns has focus. The watch reads the
138
+ * behavior, never the user agent. The caller lands its field itself and
139
+ * puts the pan back. Every keyboard-aware surface can use it.
140
+ *
141
+ * @example
142
+ * ```
143
+ * const watch = watchRevealPan(window, {owns: field => region.contains(field)});
144
+ * // On a report that changes the height: watch.report();
145
+ * // On any later report: if (watch.isRevealPan(height)) watch.putBack();
146
+ * ```
147
+ */
148
+ export declare function watchRevealPan(win: Window, { owns, onTouch }: WatchRevealPanOptions): RevealPanWatch;
149
+ //# sourceMappingURL=keyboardHeight.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboardHeight.d.ts","sourceRoot":"","sources":["../../src/keyboard/keyboardHeight.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,432 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ 'use client';
4
+
5
+ /**
6
+ * @file keyboardHeight.ts
7
+ * @input The visual viewport, the window's input stream (focus, blur,
8
+ * resize, scroll and touch), and the layout viewport's height
9
+ * @output The on-screen keyboard module: measureKeyboardHeight,
10
+ * isKeyboardMeasurable, subscribeKeyboardHeight, getKeyboardHeight,
11
+ * isTextEntryControl, findTextEntryControl, refuseFocusReveal and
12
+ * watchRevealPan
13
+ * @position Core's one source for the on-screen keyboard. It is internal to
14
+ * core and never exported from the package.
15
+ *
16
+ * SYNC: When modified, update /packages/core/src/keyboard/README.md
17
+ */
18
+ import { subscribeInput } from "../utils/inputStream.js";
19
+ import { layoutViewportHeight } from "../utils/layoutViewport.js";
20
+
21
+ // A shortfall smaller than this is rounding, not a keyboard.
22
+ export const KEYBOARD_EPSILON = 1;
23
+ const NON_TEXT_INPUT_TYPES = new Set(['button', 'checkbox', 'color', 'file', 'hidden', 'image', 'radio', 'range', 'reset', 'submit']);
24
+
25
+ /**
26
+ * Whether this window measures the keyboard: a top-level document with a
27
+ * visual viewport. Capabilities decide it, never the user agent or the
28
+ * pointer. The module measures a browser with its own keyboard geometry API,
29
+ * such as Chrome on Android with the VirtualKeyboard API, the same way.
30
+ * Its visual viewport shrinks for the keyboard while the layout viewport keeps
31
+ * its height, as everywhere else. A desktop browser's visual viewport never
32
+ * shrinks for a keyboard, so it reads 0. Where the app sets
33
+ * `interactive-widget=resizes-content`, Chrome shrinks the layout viewport
34
+ * with the keyboard. The height is then 0 too, and nothing rides, because the
35
+ * browser has made the room itself.
36
+ */
37
+ export function isKeyboardMeasurable(win) {
38
+ return win.visualViewport != null && win.self === win.top;
39
+ }
40
+
41
+ /**
42
+ * The on-screen keyboard's height over the layout viewport, CSS px. It is 0
43
+ * when none is shown, also while pinch-zoomed, and wherever
44
+ * `isKeyboardMeasurable` is false.
45
+ */
46
+ export function measureKeyboardHeight(win) {
47
+ const viewport = win.visualViewport;
48
+ if (viewport == null || !isKeyboardMeasurable(win)) {
49
+ return 0;
50
+ }
51
+ const height = layoutViewportHeight(win) - viewport.height * (viewport.scale ?? 1);
52
+ return height > KEYBOARD_EPSILON ? Math.round(height * 100) / 100 : 0;
53
+ }
54
+
55
+ /** A report from the keyboard module, in the order the window raised them. */
56
+
57
+ const stores = new WeakMap();
58
+
59
+ // A window's capture phase also hears its document's events. A window's own
60
+ // event has no node as its target.
61
+ export function isNodeTarget(target) {
62
+ return target != null && typeof target.nodeType === 'number';
63
+ }
64
+ function storeOf(win) {
65
+ let store = stores.get(win);
66
+ if (store == null) {
67
+ store = {
68
+ height: 0,
69
+ listeners: new Set(),
70
+ focusListeners: new Set(),
71
+ detach: null,
72
+ detachFocus: null
73
+ };
74
+ stores.set(win, store);
75
+ }
76
+ return store;
77
+ }
78
+ function attach(win, store) {
79
+ const viewport = win.visualViewport;
80
+ store.height = measureKeyboardHeight(win);
81
+ const onViewport = event => {
82
+ store.height = measureKeyboardHeight(win);
83
+ const report = {
84
+ type: 'viewport',
85
+ height: store.height,
86
+ timeStamp: event.timeStamp
87
+ };
88
+ for (const listener of [...store.listeners]) {
89
+ listener(report);
90
+ }
91
+ };
92
+ viewport?.addEventListener('resize', onViewport);
93
+ viewport?.addEventListener('scroll', onViewport);
94
+ // Only the window's own resize reports.
95
+ const stopResize = subscribeInput(win, 'resize', event => {
96
+ if (!isNodeTarget(event.target)) {
97
+ onViewport(event);
98
+ }
99
+ });
100
+ return () => {
101
+ viewport?.removeEventListener('resize', onViewport);
102
+ viewport?.removeEventListener('scroll', onViewport);
103
+ stopResize();
104
+ };
105
+ }
106
+
107
+ // Focus moves are heard only while a subscriber asks for them. A viewport
108
+ // reader, such as a Tall BottomSheet's hook under any pointer, adds no focus
109
+ // listener.
110
+ function attachFocus(win, store) {
111
+ const onFocus = event => {
112
+ const report = {
113
+ type: event.type === 'focusin' ? 'focusin' : 'focusout',
114
+ height: store.height,
115
+ timeStamp: event.timeStamp,
116
+ target: event.target,
117
+ relatedTarget: event.relatedTarget
118
+ };
119
+ for (const listener of [...store.focusListeners]) {
120
+ listener(report);
121
+ }
122
+ };
123
+ const stopFocusIn = subscribeInput(win, 'focusin', onFocus);
124
+ const stopFocusOut = subscribeInput(win, 'focusout', onFocus);
125
+ return () => {
126
+ stopFocusIn();
127
+ stopFocusOut();
128
+ };
129
+ }
130
+
131
+ /**
132
+ * Calls `listener` with every report of `win`'s keyboard until the caller
133
+ * runs the returned function. It reports the viewport's changes, and focus
134
+ * moves too when the caller sets `focus`. All subscribers of a window share
135
+ * one set of listeners, and its focus listeners exist only while a subscriber
136
+ * asks for focus moves.
137
+ *
138
+ * @example
139
+ * ```
140
+ * const stop = subscribeKeyboardHeight(window, event => {
141
+ * if (event.type === 'viewport') layout(event.height);
142
+ * });
143
+ * ```
144
+ */
145
+ export function subscribeKeyboardHeight(win, listener, {
146
+ focus = false
147
+ } = {}) {
148
+ const store = storeOf(win);
149
+ store.listeners.add(listener);
150
+ if (store.detach == null) {
151
+ store.detach = attach(win, store);
152
+ }
153
+ if (focus) {
154
+ store.focusListeners.add(listener);
155
+ if (store.detachFocus == null) {
156
+ store.detachFocus = attachFocus(win, store);
157
+ }
158
+ }
159
+ return () => {
160
+ store.listeners.delete(listener);
161
+ if (focus) {
162
+ store.focusListeners.delete(listener);
163
+ if (store.focusListeners.size === 0 && store.detachFocus != null) {
164
+ store.detachFocus();
165
+ store.detachFocus = null;
166
+ }
167
+ }
168
+ if (store.listeners.size === 0 && store.detach != null) {
169
+ store.detach();
170
+ store.detach = null;
171
+ }
172
+ };
173
+ }
174
+
175
+ /**
176
+ * `win`'s keyboard height. While the module listens, it is the height
177
+ * measured at the last report. Every reader of a keyboard change, such as
178
+ * each component's hook, shares that one layout read. Every change of the
179
+ * visual viewport or the window is a report, so a reader between two reports
180
+ * sees the current height. With nothing listening yet, the call measures the
181
+ * height now.
182
+ */
183
+ export function getKeyboardHeight(win) {
184
+ const store = stores.get(win);
185
+ return store?.detach != null ? store.height : measureKeyboardHeight(win);
186
+ }
187
+
188
+ /**
189
+ * Whether focusing `element` brings up a keyboard for typing text. A control
190
+ * with `inputmode="none"` brings up none.
191
+ */
192
+ export function isTextEntryControl(element) {
193
+ if (element instanceof HTMLElement && element.getAttribute('inputmode') === 'none') {
194
+ return false;
195
+ }
196
+ if (element instanceof HTMLTextAreaElement) {
197
+ return !element.disabled && !element.readOnly;
198
+ }
199
+ if (element instanceof HTMLInputElement) {
200
+ return !element.disabled && !element.readOnly && !NON_TEXT_INPUT_TYPES.has((element.getAttribute('type') ?? 'text').toLowerCase());
201
+ }
202
+ return element instanceof HTMLElement && element.matches('[contenteditable]:not([contenteditable="false"])');
203
+ }
204
+
205
+ /** The text-entry control inside `container` that `target` is or labels. */
206
+ export function findTextEntryControl(target, container) {
207
+ if (!(target instanceof Element)) {
208
+ return null;
209
+ }
210
+ const direct = target.closest('input, textarea, [contenteditable]:not([contenteditable="false"])');
211
+ if (container.contains(direct) && isTextEntryControl(direct)) {
212
+ return direct;
213
+ }
214
+ const label = target.closest('label');
215
+ const control = label instanceof HTMLLabelElement ? label.control : null;
216
+ return container.contains(control) && isTextEntryControl(control) ? control : null;
217
+ }
218
+
219
+ /** The caller's rules for refuseFocusReveal. */
220
+
221
+ // One shared `blur` subscription per document serves every caller of
222
+ // refuseFocusReveal in it, so listeners do not grow with the number of
223
+ // instances. The first caller adds it, and the last removes it.
224
+ const focusTakeovers = new WeakMap();
225
+
226
+ /**
227
+ * Delivers the focus moves `claim` accepts with `preventScroll`, so the
228
+ * browser never reveals their destination itself. It stops when the caller
229
+ * runs the returned function.
230
+ *
231
+ * The browser dispatches `blur` in the capture phase ahead of its own focus
232
+ * step, and `blur` names the destination in `relatedTarget`. Focusing it there
233
+ * with preventScroll settles the change. The browser's step then finds the
234
+ * element already active, so it dispatches nothing further and reveals
235
+ * nothing.
236
+ *
237
+ * @example
238
+ * ```
239
+ * const stop = refuseFocusReveal(document, {
240
+ * claim: target => findTextEntryControl(target, body),
241
+ * park: () => sheet.focus({preventScroll: true}),
242
+ * });
243
+ * ```
244
+ */
245
+ export function refuseFocusReveal(doc, {
246
+ isActive,
247
+ claim,
248
+ park
249
+ }) {
250
+ const onBlur = event => {
251
+ if (isActive != null && !isActive()) {
252
+ return;
253
+ }
254
+ const destination = claim(event.relatedTarget);
255
+ if (destination != null) {
256
+ if (destination !== doc.activeElement) {
257
+ destination.focus({
258
+ preventScroll: true
259
+ });
260
+ }
261
+ return;
262
+ }
263
+ if (event.relatedTarget == null) {
264
+ park?.(event.target);
265
+ }
266
+ };
267
+ let shared = focusTakeovers.get(doc);
268
+ if (shared == null) {
269
+ const view = doc.defaultView;
270
+ if (view == null) {
271
+ return () => {};
272
+ }
273
+ const callers = new Set();
274
+ // Callers hear each blur in the order they came, as separate listeners
275
+ // would; one that stopped during this blur no longer hears it.
276
+ const listener = event => {
277
+ for (const caller of [...callers]) {
278
+ if (callers.has(caller)) {
279
+ caller(event);
280
+ }
281
+ }
282
+ };
283
+ // `blur` does not bubble: only the capture phase hears every one, and it
284
+ // runs before the browser's own focus step. The window's own blur moves
285
+ // no focus inside the document.
286
+ const stop = subscribeInput(view, 'blur', event => {
287
+ if (isNodeTarget(event.target)) {
288
+ listener(event);
289
+ }
290
+ });
291
+ shared = {
292
+ stop,
293
+ callers
294
+ };
295
+ focusTakeovers.set(doc, shared);
296
+ }
297
+ shared.callers.add(onBlur);
298
+ return () => {
299
+ const current = focusTakeovers.get(doc);
300
+ if (current == null || !current.callers.delete(onBlur)) {
301
+ return;
302
+ }
303
+ if (current.callers.size === 0) {
304
+ current.stop();
305
+ focusTakeovers.delete(doc);
306
+ }
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Test-only: how many callers refuse the focus reveal in `doc`, through its
312
+ * one shared subscription. Not part of the runtime API.
313
+ *
314
+ * @internal
315
+ */
316
+ export function __focusTakeoverCallersForTest(doc) {
317
+ return focusTakeovers.get(doc)?.callers.size ?? 0;
318
+ }
319
+
320
+ /** The caller's rules for watchRevealPan. */
321
+
322
+ /** A watch for the browser's own reveal pan (see watchRevealPan). */
323
+
324
+ // One shared touch subscription per document serves every reveal-pan watch
325
+ // in it, so no instance adds a listener. The first watch adds it, and the
326
+ // last removes it.
327
+ const touchStartWatches = new WeakMap();
328
+ function watchTouchStarts(doc, onTouchStart) {
329
+ let shared = touchStartWatches.get(doc);
330
+ if (shared == null) {
331
+ const view = doc.defaultView;
332
+ if (view == null) {
333
+ return () => {};
334
+ }
335
+ const watches = new Set();
336
+ const stop = subscribeInput(view, 'touchstart', () => {
337
+ for (const watch of [...watches]) {
338
+ watch();
339
+ }
340
+ });
341
+ shared = {
342
+ stop,
343
+ watches
344
+ };
345
+ touchStartWatches.set(doc, shared);
346
+ }
347
+ shared.watches.add(onTouchStart);
348
+ return () => {
349
+ const current = touchStartWatches.get(doc);
350
+ if (current == null || !current.watches.delete(onTouchStart)) {
351
+ return;
352
+ }
353
+ if (current.watches.size === 0) {
354
+ current.stop();
355
+ touchStartWatches.delete(doc);
356
+ }
357
+ };
358
+ }
359
+
360
+ /**
361
+ * Some browsers reveal a focused field themselves, after the keyboard's
362
+ * report, by panning the visual viewport to it while the keyboard still
363
+ * covers it. Firefox for Android pans 54 to 143 ms after the report. Chrome
364
+ * on Android animates a pan of the keyboard's whole height from about 100 to
365
+ * 170 ms after it. The page gets no frame until that pan ends. Such a pan
366
+ * moves the visual viewport down within the
367
+ * layout viewport. Since the report there is no touch, document scroll or
368
+ * zoom, and a text field the caller owns has focus. The watch reads the
369
+ * behavior, never the user agent. The caller lands its field itself and
370
+ * puts the pan back. Every keyboard-aware surface can use it.
371
+ *
372
+ * @example
373
+ * ```
374
+ * const watch = watchRevealPan(window, {owns: field => region.contains(field)});
375
+ * // On a report that changes the height: watch.report();
376
+ * // On any later report: if (watch.isRevealPan(height)) watch.putBack();
377
+ * ```
378
+ */
379
+ export function watchRevealPan(win, {
380
+ owns,
381
+ onTouch
382
+ }) {
383
+ const doc = win.document;
384
+ let atReport = null;
385
+ let touchedSinceReport = false;
386
+ const stopTouchStarts = watchTouchStarts(doc, () => {
387
+ touchedSinceReport = true;
388
+ onTouch?.();
389
+ });
390
+ return {
391
+ report: () => {
392
+ const viewport = win.visualViewport;
393
+ atReport = viewport == null ? null : {
394
+ x: win.scrollX,
395
+ y: win.scrollY,
396
+ top: viewport.offsetTop,
397
+ scale: viewport.scale
398
+ };
399
+ touchedSinceReport = false;
400
+ },
401
+ isRevealPan: height => {
402
+ const viewport = win.visualViewport;
403
+ const focused = doc.activeElement;
404
+ return viewport != null && atReport != null && !touchedSinceReport && height > 0 && viewport.offsetTop > atReport.top && viewport.scale === atReport.scale && win.scrollX === atReport.x && win.scrollY === atReport.y && focused instanceof HTMLElement && isTextEntryControl(focused) && owns(focused);
405
+ },
406
+ putBack: anchor => {
407
+ const options = {
408
+ block: 'start',
409
+ inline: 'nearest',
410
+ behavior: 'instant'
411
+ };
412
+ if (anchor != null) {
413
+ anchor.scrollIntoView(options);
414
+ return;
415
+ }
416
+ // A fixed element sits in the layout viewport, which the pan does not
417
+ // move: scrolling its top into view pans the visual viewport back.
418
+ const probe = doc.createElement('div');
419
+ probe.style.position = 'fixed';
420
+ probe.style.left = '0';
421
+ probe.style.top = `${atReport?.top ?? 0}px`;
422
+ probe.style.width = '1px';
423
+ probe.style.height = '1px';
424
+ probe.style.visibility = 'hidden';
425
+ probe.style.pointerEvents = 'none';
426
+ (doc.body ?? doc.documentElement).append(probe);
427
+ probe.scrollIntoView(options);
428
+ probe.remove();
429
+ },
430
+ stop: stopTouchStarts
431
+ };
432
+ }
@@ -0,0 +1,59 @@
1
+ import { type KeyboardProfile, type KeyboardTransition } from './keyboardModel.js';
2
+ /**
3
+ * Whose keyboard schedule the guide reads: `chromium` where the browser has
4
+ * the VirtualKeyboard API, else null. Without the profile, no surface rides
5
+ * the guide. Chrome reports the keyboard as each slide starts. The profile is
6
+ * a capability, never the user agent. motion/android/keyboard.ts holds the
7
+ * measured timing.
8
+ */
9
+ export declare function keyboardProfileOf(win: Window): KeyboardProfile | null;
10
+ /** A window's keyboard layout guide. */
11
+ export interface KeyboardLayoutGuide {
12
+ /** The inset the keyboard rests at (or is heading to), CSS px. */
13
+ readonly inset: number;
14
+ /** The latest transition. */
15
+ readonly transition: KeyboardTransition;
16
+ /** The inset presented at `time` (default now), CSS px. */
17
+ presentation(time?: number): number;
18
+ /**
19
+ * Calls `listener` with every transition, inside the event that revealed
20
+ * it, until the caller runs the returned function.
21
+ */
22
+ subscribe(listener: (transition: KeyboardTransition) => void): () => void;
23
+ /** Whose keyboard schedule the guide reads (keyboardProfileOf). */
24
+ readonly profile: KeyboardProfile | null;
25
+ }
26
+ /**
27
+ * Whether focusing `target` brings up a software keyboard (or another input
28
+ * view in its place, as a select's picker does).
29
+ */
30
+ export declare function bringsUpKeyboard(target: EventTarget | null): boolean;
31
+ /**
32
+ * The window's keyboard layout guide, created on first use.
33
+ *
34
+ * One guide serves each window. It subscribes to the keyboard module only
35
+ * while something subscribes to it. It owns no listener and measures nothing.
36
+ * It feeds the model the module's reports, the keyboard height and the focus
37
+ * changes that start a hide. It hands each transition to its subscribers.
38
+ * What it adds is timing: when a show started, a hide predicted from the
39
+ * blur, and the motion in between. Layout follows the keyboard height
40
+ * itself, and the guide's transitions only time the optional ride on top.
41
+ *
42
+ * The model reads Chrome's measured schedule: Chrome reports each slide just
43
+ * after it starts. A surface rides the guide only where the VirtualKeyboard
44
+ * API reports the keyboard. Focus can move from one field to another through
45
+ * something that brings no keyboard, such as a popover's container. Chrome
46
+ * then reports the keyboard gone for a moment and then back. So a hide
47
+ * reported while a field that just got focus still has it waits until the
48
+ * move settles.
49
+ *
50
+ * @example
51
+ * ```
52
+ * const guide = getKeyboardLayoutGuide(window);
53
+ * const unsubscribe = guide.subscribe(t => {
54
+ * rideKeyboard(animator, {y: -t.to}, t);
55
+ * });
56
+ * ```
57
+ */
58
+ export declare function getKeyboardLayoutGuide(win: Window): KeyboardLayoutGuide;
59
+ //# sourceMappingURL=keyboardLayoutGuide.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyboardLayoutGuide.d.ts","sourceRoot":"","sources":["../../src/keyboard/keyboardLayoutGuide.ts"],"names":[],"mappings":"AAqBA,OAAO,EAGL,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAErE;AAED,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,6BAA6B;IAC7B,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC;IACxC,2DAA2D;IAC3D,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACpC;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,CAAC,UAAU,EAAE,kBAAkB,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC1E,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI,CAAC;CAC1C;AAkBD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAMpE;AAkBD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAOvE"}