@gravity-ui/aikit 2.21.1 → 2.21.2
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/build/cjs/components/templates/History/History.css +21 -0
- package/build/cjs/components/templates/History/History.js +6 -1
- package/build/cjs/components/templates/History/History.js.map +1 -1
- package/build/cjs/hooks/index.d.ts +1 -0
- package/build/cjs/hooks/index.js +1 -0
- package/build/cjs/hooks/index.js.map +1 -1
- package/build/cjs/hooks/useKeyboardViewportFit.js +8 -0
- package/build/cjs/hooks/useKeyboardViewportFit.js.map +1 -1
- package/build/cjs/hooks/useSheetKeyboardFit.d.ts +40 -0
- package/build/cjs/hooks/useSheetKeyboardFit.js +323 -0
- package/build/cjs/hooks/useSheetKeyboardFit.js.map +1 -0
- package/build/cjs/package.json +1 -1
- package/build/docs/components/pages/ChatContainer.md +4 -0
- package/build/docs/components/templates/History.md +13 -0
- package/build/esm/components/templates/History/History.css +21 -0
- package/build/esm/components/templates/History/History.js +6 -1
- package/build/esm/components/templates/History/History.js.map +1 -1
- package/build/esm/hooks/index.d.ts +1 -0
- package/build/esm/hooks/index.js +1 -0
- package/build/esm/hooks/index.js.map +1 -1
- package/build/esm/hooks/useKeyboardViewportFit.js +8 -0
- package/build/esm/hooks/useKeyboardViewportFit.js.map +1 -1
- package/build/esm/hooks/useSheetKeyboardFit.d.ts +40 -0
- package/build/esm/hooks/useSheetKeyboardFit.js +318 -0
- package/build/esm/hooks/useSheetKeyboardFit.js.map +1 -0
- package/build/esm/package.json +1 -1
- package/package.json +1 -1
|
@@ -389,6 +389,19 @@ To bring the desktop metrics back into the sheet:
|
|
|
389
389
|
}
|
|
390
390
|
```
|
|
391
391
|
|
|
392
|
+
### On-screen keyboard
|
|
393
|
+
|
|
394
|
+
The search field raises the on-screen keyboard, and mobile browsers keep the layout viewport at
|
|
395
|
+
full height while it is open - iOS Safari always, Chrome and Firefox under the
|
|
396
|
+
`interactive-widget=resizes-visual` default. A uikit `Sheet` is measured against that viewport and
|
|
397
|
+
pins its window to the bottom of it, so the sheet ends up below the keyboard with only the search
|
|
398
|
+
field left on screen. On the focus of the search field the sheet is fitted to the area the keyboard
|
|
399
|
+
is about to leave: its content is capped to `--g-aikit-sheet-visible-height` and its root is cut to
|
|
400
|
+
`--g-aikit-sheet-visible-bottom`, both published on the document element, so the sheet rises
|
|
401
|
+
together with the keyboard instead of jumping after it. The height of the keyboard is remembered
|
|
402
|
+
per screen size; the first time on a screen the fit waits for the visual viewport to settle. The
|
|
403
|
+
search field stays put while the chat list scrolls under it.
|
|
404
|
+
|
|
392
405
|
```css
|
|
393
406
|
/* Example: Custom dimensions */
|
|
394
407
|
.custom-chat-history {
|
|
@@ -93,6 +93,27 @@
|
|
|
93
93
|
justify-content: center;
|
|
94
94
|
padding: calc(var(--g-spacing-base) * 2) calc(var(--g-spacing-base) * 4);
|
|
95
95
|
}
|
|
96
|
+
.g-aikit-history__sheet {
|
|
97
|
+
height: var(--g-aikit-sheet-visible-bottom, 100%);
|
|
98
|
+
transition: height 0.3s ease;
|
|
99
|
+
}
|
|
100
|
+
.g-aikit-history__sheet-content.g-aikit-history__sheet-content_keyboard-open {
|
|
101
|
+
display: flex;
|
|
102
|
+
flex-direction: column;
|
|
103
|
+
max-height: calc(var(--g-aikit-sheet-visible-height, 100%) * 0.9 - var(--_--top-height, 20px));
|
|
104
|
+
}
|
|
105
|
+
.g-aikit-history__sheet-content.g-aikit-history__sheet-content_keyboard-open > * {
|
|
106
|
+
flex-shrink: 0;
|
|
107
|
+
}
|
|
108
|
+
.g-aikit-history__sheet-content.g-aikit-history__sheet-content_keyboard-open .g-aikit-history__container {
|
|
109
|
+
flex: 1;
|
|
110
|
+
min-height: 0;
|
|
111
|
+
}
|
|
112
|
+
.g-aikit-history__sheet-content.g-aikit-history__sheet-content_keyboard-open .g-aikit-history__list-wrapper {
|
|
113
|
+
flex: 1;
|
|
114
|
+
min-height: 0;
|
|
115
|
+
overflow: auto;
|
|
116
|
+
}
|
|
96
117
|
.g-aikit-history__sheet-content {
|
|
97
118
|
--g-sheet-content-padding: 0 max(var(--g-aikit-history-sheet-inline-padding), env(safe-area-inset-right, 0px))
|
|
98
119
|
calc(var(--g-aikit-history-sheet-bottom-padding) + env(safe-area-inset-bottom, 0px))
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { __rest } from "tslib";
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { Popup, Sheet, useMobile, useUniqId } from '@gravity-ui/uikit';
|
|
4
|
+
import { useSheetKeyboardFit } from "../../../hooks/useSheetKeyboardFit.js";
|
|
4
5
|
import { block } from "../../../utils/cn.js";
|
|
5
6
|
import { HistoryList } from "./HistoryList.js";
|
|
6
7
|
import { i18n } from "./i18n/index.js";
|
|
@@ -16,12 +17,16 @@ export function History(props) {
|
|
|
16
17
|
const { open = false, onOpenChange, anchorElement, showSheetTitle = true } = props, listProps = __rest(props, ["open", "onOpenChange", "anchorElement", "showSheetTitle"]);
|
|
17
18
|
const isMobile = useMobile();
|
|
18
19
|
const sheetId = useUniqId();
|
|
20
|
+
const { isKeyboardOpen } = useSheetKeyboardFit(isMobile && open, `.${b('sheet')}`);
|
|
19
21
|
const handleChatClick = () => {
|
|
20
22
|
onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false);
|
|
21
23
|
};
|
|
22
24
|
const list = _jsx(HistoryList, Object.assign({}, listProps, { onChatClick: handleChatClick }));
|
|
23
25
|
if (isMobile) {
|
|
24
|
-
return (_jsx(Sheet, { id: sheetId, title: i18n('sheet-title'), visible: open, onClose: () => onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false),
|
|
26
|
+
return (_jsx(Sheet, { id: sheetId, title: i18n('sheet-title'), visible: open, onClose: () => onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false), className: b('sheet'), contentClassName: b('sheet-content', {
|
|
27
|
+
'without-title': !showSheetTitle,
|
|
28
|
+
'keyboard-open': isKeyboardOpen,
|
|
29
|
+
}), qa: "history-sheet", allowHideOnContentScroll: true, children: list }));
|
|
25
30
|
}
|
|
26
31
|
return (_jsx(Popup, { anchorElement: anchorElement, placement: "bottom-end", open: open, onOpenChange: onOpenChange, children: list }));
|
|
27
32
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"History.js","sourceRoot":"../../../../../src","sources":["components/templates/History/History.tsx"],"names":[],"mappings":";;AAAA,OAAO,EAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAErE,OAAO,EAAC,KAAK,EAAC,6BAA0B;AAExC,OAAO,EAAC,WAAW,EAAwB,yBAAsB;AACjE,OAAO,EAAC,IAAI,EAAC,wBAAe;AAE5B,uBAAwB;AAExB,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;AAgB3B;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,KAAmB;IACvC,MAAM,EAAC,IAAI,GAAG,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,GAAG,IAAI,KAAkB,KAAK,EAAlB,SAAS,UAAI,KAAK,EAAxF,2DAAgF,CAAQ,CAAC;IAC/F,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"History.js","sourceRoot":"../../../../../src","sources":["components/templates/History/History.tsx"],"names":[],"mappings":";;AAAA,OAAO,EAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,SAAS,EAAC,MAAM,mBAAmB,CAAC;AAErE,OAAO,EAAC,mBAAmB,EAAC,8CAA2C;AACvE,OAAO,EAAC,KAAK,EAAC,6BAA0B;AAExC,OAAO,EAAC,WAAW,EAAwB,yBAAsB;AACjE,OAAO,EAAC,IAAI,EAAC,wBAAe;AAE5B,uBAAwB;AAExB,MAAM,CAAC,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;AAgB3B;;;;;GAKG;AACH,MAAM,UAAU,OAAO,CAAC,KAAmB;IACvC,MAAM,EAAC,IAAI,GAAG,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,GAAG,IAAI,KAAkB,KAAK,EAAlB,SAAS,UAAI,KAAK,EAAxF,2DAAgF,CAAQ,CAAC;IAC/F,MAAM,QAAQ,GAAG,SAAS,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;IAC5B,MAAM,EAAC,cAAc,EAAC,GAAG,mBAAmB,CAAC,QAAQ,IAAI,IAAI,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAEjF,MAAM,eAAe,GAAG,GAAG,EAAE;QACzB,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,KAAK,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,KAAC,WAAW,oBAAK,SAAS,IAAE,WAAW,EAAE,eAAe,IAAI,CAAC;IAE1E,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO,CACH,KAAC,KAAK,IACF,EAAE,EAAE,OAAO,EACX,KAAK,EAAE,IAAI,CAAC,aAAa,CAAC,EAC1B,OAAO,EAAE,IAAI,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,KAAK,CAAC,EACpC,SAAS,EAAE,CAAC,CAAC,OAAO,CAAC,EACrB,gBAAgB,EAAE,CAAC,CAAC,eAAe,EAAE;gBACjC,eAAe,EAAE,CAAC,cAAc;gBAChC,eAAe,EAAE,cAAc;aAClC,CAAC,EACF,EAAE,EAAC,eAAe,EAClB,wBAAwB,kBAEvB,IAAI,GACD,CACX,CAAC;IACN,CAAC;IAED,OAAO,CACH,KAAC,KAAK,IACF,aAAa,EAAE,aAAa,EAC5B,SAAS,EAAC,YAAY,EACtB,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,YAAY,YAEzB,IAAI,GACD,CACX,CAAC;AACN,CAAC","sourcesContent":["import {Popup, Sheet, useMobile, useUniqId} from '@gravity-ui/uikit';\n\nimport {useSheetKeyboardFit} from '../../../hooks/useSheetKeyboardFit';\nimport {block} from '../../../utils/cn';\n\nimport {HistoryList, type HistoryListProps} from './HistoryList';\nimport {i18n} from './i18n';\n\nimport './History.scss';\n\nconst b = block('history');\n\n/**\n * Props for the History component\n */\nexport interface HistoryProps extends Omit<HistoryListProps, 'onChatClick'> {\n /** Control popup open state */\n open?: boolean;\n /** Callback when popup open state changes */\n onOpenChange?: (open: boolean) => void;\n /** Anchor element for the popup */\n anchorElement: HTMLElement | null;\n /** Show the mobile sheet title (default: `true`); it stays in `aria-label` either way */\n showSheetTitle?: boolean;\n}\n\n/**\n * History component - wraps HistoryList in a Popup, and in a bottom Sheet in mobile mode\n *\n * @param props - Component props\n * @returns React component\n */\nexport function History(props: HistoryProps) {\n const {open = false, onOpenChange, anchorElement, showSheetTitle = true, ...listProps} = props;\n const isMobile = useMobile();\n const sheetId = useUniqId();\n const {isKeyboardOpen} = useSheetKeyboardFit(isMobile && open, `.${b('sheet')}`);\n\n const handleChatClick = () => {\n onOpenChange?.(false);\n };\n\n const list = <HistoryList {...listProps} onChatClick={handleChatClick} />;\n\n if (isMobile) {\n return (\n <Sheet\n id={sheetId}\n title={i18n('sheet-title')}\n visible={open}\n onClose={() => onOpenChange?.(false)}\n className={b('sheet')}\n contentClassName={b('sheet-content', {\n 'without-title': !showSheetTitle,\n 'keyboard-open': isKeyboardOpen,\n })}\n qa=\"history-sheet\"\n allowHideOnContentScroll\n >\n {list}\n </Sheet>\n );\n }\n\n return (\n <Popup\n anchorElement={anchorElement}\n placement=\"bottom-end\"\n open={open}\n onOpenChange={onOpenChange}\n >\n {list}\n </Popup>\n );\n}\n"]}
|
package/build/esm/hooks/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":"AAAA,4CAAmC;AACnC,oCAAiC;AACjC,oCAAiC;AACjC,2CAAwC;AACxC,6CAA0C;AAC1C,8CAA2C;AAC3C,gDAA6C;AAC7C,wCAAqC;AACrC,gCAA6B;AAC7B,+CAA4C;AAC5C,oCAAiC;AACjC,4CAAyC","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useVirtualStickToBottom';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\nexport * from './useToolset';\nexport * from './useToolResultContinuation';\nexport * from './useMascotState';\nexport * from './useKeyboardViewportFit';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":"AAAA,4CAAmC;AACnC,oCAAiC;AACjC,oCAAiC;AACjC,2CAAwC;AACxC,6CAA0C;AAC1C,8CAA2C;AAC3C,gDAA6C;AAC7C,wCAAqC;AACrC,gCAA6B;AAC7B,+CAA4C;AAC5C,oCAAiC;AACjC,4CAAyC;AACzC,yCAAsC","sourcesContent":["export * from './useDateFormatter';\nexport * from './useToolMessage';\nexport * from './useSmartScroll';\nexport * from './useScrollPreservation';\nexport * from './useVirtualStickToBottom';\nexport * from './useAutoCollapseOnSuccess';\nexport * from './useAutoCollapseOnCancelled';\nexport * from './useFileUploadStore';\nexport * from './useToolset';\nexport * from './useToolResultContinuation';\nexport * from './useMascotState';\nexport * from './useKeyboardViewportFit';\nexport * from './useSheetKeyboardFit';\n"]}
|
|
@@ -98,6 +98,10 @@ export function useKeyboardViewportFit(containerRef, enabled = true, options = {
|
|
|
98
98
|
if (!container) {
|
|
99
99
|
return;
|
|
100
100
|
}
|
|
101
|
+
if (!container.contains(document.activeElement)) {
|
|
102
|
+
setFit((prev) => (prev.isKeyboardOpen ? CLOSED : prev));
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
101
105
|
const probeBox = (_a = viewportProbeRef === null || viewportProbeRef === void 0 ? void 0 : viewportProbeRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
|
|
102
106
|
const next = resolveKeyboardViewportFit({
|
|
103
107
|
viewportHeight: viewport.height,
|
|
@@ -128,12 +132,16 @@ export function useKeyboardViewportFit(containerRef, enabled = true, options = {
|
|
|
128
132
|
update();
|
|
129
133
|
viewport.addEventListener('resize', schedule);
|
|
130
134
|
viewport.addEventListener('scroll', schedule);
|
|
135
|
+
document.addEventListener('focusin', schedule);
|
|
136
|
+
document.addEventListener('focusout', schedule);
|
|
131
137
|
return () => {
|
|
132
138
|
if (frame) {
|
|
133
139
|
cancelAnimationFrame(frame);
|
|
134
140
|
}
|
|
135
141
|
viewport.removeEventListener('resize', schedule);
|
|
136
142
|
viewport.removeEventListener('scroll', schedule);
|
|
143
|
+
document.removeEventListener('focusin', schedule);
|
|
144
|
+
document.removeEventListener('focusout', schedule);
|
|
137
145
|
};
|
|
138
146
|
}, [containerRef, enabled, viewportProbeRef]);
|
|
139
147
|
return fit;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useKeyboardViewportFit.js","sourceRoot":"../../../src","sources":["hooks/useKeyboardViewportFit.ts"],"names":[],"mappings":"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","sourcesContent":["import {type RefObject, useEffect, useState} from 'react';\n\n/**\n * Minimum difference between the layout and the visual viewport that is treated as an opened\n * on-screen keyboard. Small differences come from browser UI (URL bar, toolbars) and must not\n * shrink the container.\n */\nexport const KEYBOARD_MIN_INSET = 80;\n\n/**\n * Largest shortfall of `visualViewport.height` against a measured reference that is still\n * attributed to the browser's own bottom chrome. Anything larger is the keyboard itself and\n * must not be corrected away.\n */\nexport const VIEWPORT_HEIGHT_TOLERANCE = 100;\n\nexport interface KeyboardViewportMetrics {\n /** `visualViewport.height` - height of the area not covered by the keyboard. */\n viewportHeight: number;\n /** `visualViewport.offsetTop` - offset of the visual viewport inside the layout viewport. */\n viewportOffsetTop: number;\n /** `visualViewport.scale` - pinch-zoom factor, `1` when the page is not zoomed. */\n scale: number;\n /** `window.innerHeight` - layout viewport height, unaffected by the keyboard on iOS Safari. */\n layoutHeight: number;\n /** Top of the container in layout viewport coordinates (`getBoundingClientRect().top`). */\n containerTop: number;\n /**\n * Top of an element pinned to the top of the layout viewport (`position: fixed; top: 0`), read\n * with `getBoundingClientRect()`. Client rectangles are relative to the layout viewport in most\n * browsers and to the visual viewport in Safari, and this reading tells the two apart: it stays\n * at zero in the first case and drops to minus the viewport offset in the second. `0` by\n * default, which keeps the layout viewport reading.\n */\n viewportOriginTop?: number;\n /**\n * Height of a reference element sized to the dynamic viewport (`height: 100dvh`), read from\n * the DOM. iOS Safari reports a visible area short by the height of its bottom toolbar.\n */\n measuredViewportHeight?: number;\n}\n\nexport interface KeyboardViewportFit {\n /** Whether the on-screen keyboard currently covers part of the layout viewport. */\n isKeyboardOpen: boolean;\n /**\n * Height limit that keeps the bottom of the container right above the keyboard.\n * `undefined` while the keyboard is closed - the container keeps its natural height.\n */\n maxHeight?: number;\n}\n\nconst CLOSED: KeyboardViewportFit = {isKeyboardOpen: false};\n\n/**\n * `visualViewport.height` can come back short by the height of the browser bottom chrome, which\n * makes the container shrink while no keyboard is open. A reference element sized to the dynamic\n * viewport measures the same area without that shortfall, so the larger of the two wins - but only\n * while the difference stays inside the tolerance, otherwise the keyboard itself would be\n * corrected away. The correction is one-way: it can only raise the visible height.\n */\nfunction resolveVisibleHeight(\n reportedHeight: number,\n measuredHeight: number | undefined,\n scale: number,\n tolerance: number,\n): number {\n // A DOM rect is in CSS pixels while `visualViewport.height` shrinks with pinch zoom, so the\n // two are only comparable on an unzoomed page.\n if (measuredHeight === undefined || scale !== 1) {\n return reportedHeight;\n }\n\n const difference = measuredHeight - reportedHeight;\n\n return difference > 0 && difference < tolerance ? measuredHeight : reportedHeight;\n}\n\n/**\n * Height limit for a container whose bottom would otherwise end up under the on-screen keyboard.\n *\n * Browsers keep the layout viewport at full height while the keyboard is open (iOS Safari always,\n * Chrome and Firefox since the `interactive-widget=resizes-visual` default), so `100vh` / `100dvh` /\n * `height: 100%` keep the original height and the bottom of the chat (prompt input, disclaimer)\n * stays hidden. The visible area is reported by `visualViewport` instead, and the limit is its\n * bottom edge measured from the top of the container.\n */\nexport function resolveKeyboardViewportFit(\n metrics: KeyboardViewportMetrics,\n minInset: number = KEYBOARD_MIN_INSET,\n): KeyboardViewportFit {\n const {\n viewportHeight,\n viewportOffsetTop,\n scale,\n layoutHeight,\n containerTop,\n viewportOriginTop = 0,\n measuredViewportHeight,\n } = metrics;\n\n const visibleHeight = resolveVisibleHeight(\n viewportHeight,\n measuredViewportHeight,\n scale,\n VIEWPORT_HEIGHT_TOLERANCE,\n );\n\n // Pinch zoom shrinks `visualViewport.height` just like the keyboard does, so the height is\n // scaled back to layout pixels first: what remains is the part of the layout viewport that the\n // user cannot reach by panning, which is the keyboard. A page that opted into\n // `interactive-widget=resizes-content` shrinks the layout viewport itself and lands here with a\n // zero inset - the browser has already done the work and the container is left alone.\n if (layoutHeight - visibleHeight * scale < minInset) {\n return CLOSED;\n }\n\n // Where the visible area ends, in the same coordinates the container was measured in. Reading\n // the origin from the DOM keeps this right in a browser that reports rectangles against the\n // visual viewport, where adding the offset back would count it twice.\n const visibleBottom = viewportOffsetTop + viewportOriginTop + visibleHeight;\n\n return {\n isKeyboardOpen: true,\n maxHeight: Math.max(0, Math.floor(visibleBottom - containerTop)),\n };\n}\n\nexport interface ViewportOriginMetrics {\n /** Top of the probe as `getBoundingClientRect` reports it. */\n probeTop: number;\n /** `visualViewport.offsetTop` at the same moment. */\n viewportOffsetTop: number;\n}\n\n/**\n * Top of the viewport the container is measured against, read from the probe.\n *\n * A probe pinned to the top of the layout viewport can only report two things: zero, where client\n * rectangles are measured against the layout viewport, and minus the viewport offset, where they\n * are measured against the visual viewport as in Safari. Anything else means the probe is not\n * where it was asked to be: a `transform`, a `filter`, a `will-change` or a `contain: paint` on\n * any ancestor - and panels that slide in are animated with exactly those - makes `position: fixed`\n * count from that ancestor instead of the viewport. Such a reading is dropped, and the limit falls\n * back to the formula without a probe rather than following the ancestor's own offset.\n */\nexport function resolveViewportOriginTop({\n probeTop,\n viewportOffsetTop,\n}: ViewportOriginMetrics): number {\n const isPinnedToViewport = Math.abs(probeTop) < 1 || Math.abs(probeTop + viewportOffsetTop) < 1;\n\n return isPinnedToViewport ? probeTop : 0;\n}\n\nexport interface KeyboardViewportFitOptions {\n /**\n * Element pinned to the top of the layout viewport and sized to the dynamic viewport\n * (`position: fixed; top: 0; height: 100dvh`). Its rectangle answers two questions the\n * `visualViewport` numbers alone cannot: which viewport client rectangles are measured\n * against, and how tall the browser itself considers the visible area to be. See\n * {@link VIEWPORT_HEIGHT_TOLERANCE}.\n */\n viewportProbeRef?: RefObject<HTMLElement | null>;\n}\n\n/**\n * Tracks the on-screen keyboard through `visualViewport` and returns the height limit that keeps\n * the referenced container inside the visible area. The container has to be anchored to the top of\n * the viewport: a bottom-anchored one moves its own top as soon as the limit shrinks it.\n *\n * @param containerRef - element to fit into the visual viewport\n * @param enabled - disables tracking (e.g. outside mobile mode)\n * @param options - optional dynamic viewport probe, see {@link KeyboardViewportFitOptions}\n */\nexport function useKeyboardViewportFit(\n containerRef: RefObject<HTMLElement>,\n enabled = true,\n options: KeyboardViewportFitOptions = {},\n): KeyboardViewportFit {\n const {viewportProbeRef} = options;\n const [fit, setFit] = useState<KeyboardViewportFit>(CLOSED);\n\n useEffect(() => {\n const viewport = typeof window === 'undefined' ? undefined : window.visualViewport;\n if (!enabled || !viewport) {\n setFit(CLOSED);\n return undefined;\n }\n\n let frame = 0;\n\n const update = () => {\n frame = 0;\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n const probeBox = viewportProbeRef?.current?.getBoundingClientRect();\n\n const next = resolveKeyboardViewportFit({\n viewportHeight: viewport.height,\n viewportOffsetTop: viewport.offsetTop,\n scale: viewport.scale,\n layoutHeight: window.innerHeight,\n // The limit only depends on the top of the container, which the limit itself does\n // not move - so applying it cannot feed back into the next measurement.\n containerTop: container.getBoundingClientRect().top,\n viewportOriginTop:\n probeBox &&\n resolveViewportOriginTop({\n probeTop: probeBox.top,\n viewportOffsetTop: viewport.offsetTop,\n }),\n measuredViewportHeight: probeBox?.height,\n });\n\n setFit((prev) =>\n prev.isKeyboardOpen === next.isKeyboardOpen && prev.maxHeight === next.maxHeight\n ? prev\n : next,\n );\n };\n\n // iOS reports intermediate viewport sizes during the keyboard animation, so updates are\n // collapsed into a single frame.\n const schedule = () => {\n if (!frame) {\n frame = requestAnimationFrame(update);\n }\n };\n\n update();\n viewport.addEventListener('resize', schedule);\n viewport.addEventListener('scroll', schedule);\n\n return () => {\n if (frame) {\n cancelAnimationFrame(frame);\n }\n viewport.removeEventListener('resize', schedule);\n viewport.removeEventListener('scroll', schedule);\n };\n }, [containerRef, enabled, viewportProbeRef]);\n\n return fit;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"useKeyboardViewportFit.js","sourceRoot":"../../../src","sources":["hooks/useKeyboardViewportFit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,SAAS,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAC;AAE1D;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAsC7C,MAAM,MAAM,GAAwB,EAAC,cAAc,EAAE,KAAK,EAAC,CAAC;AAE5D;;;;;;GAMG;AACH,SAAS,oBAAoB,CACzB,cAAsB,EACtB,cAAkC,EAClC,KAAa,EACb,SAAiB;IAEjB,4FAA4F;IAC5F,+CAA+C;IAC/C,IAAI,cAAc,KAAK,SAAS,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;QAC9C,OAAO,cAAc,CAAC;IAC1B,CAAC;IAED,MAAM,UAAU,GAAG,cAAc,GAAG,cAAc,CAAC;IAEnD,OAAO,UAAU,GAAG,CAAC,IAAI,UAAU,GAAG,SAAS,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,cAAc,CAAC;AACtF,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CACtC,OAAgC,EAChC,WAAmB,kBAAkB;IAErC,MAAM,EACF,cAAc,EACd,iBAAiB,EACjB,KAAK,EACL,YAAY,EACZ,YAAY,EACZ,iBAAiB,GAAG,CAAC,EACrB,sBAAsB,GACzB,GAAG,OAAO,CAAC;IAEZ,MAAM,aAAa,GAAG,oBAAoB,CACtC,cAAc,EACd,sBAAsB,EACtB,KAAK,EACL,yBAAyB,CAC5B,CAAC;IAEF,2FAA2F;IAC3F,+FAA+F;IAC/F,8EAA8E;IAC9E,gGAAgG;IAChG,sFAAsF;IACtF,IAAI,YAAY,GAAG,aAAa,GAAG,KAAK,GAAG,QAAQ,EAAE,CAAC;QAClD,OAAO,MAAM,CAAC;IAClB,CAAC;IAED,8FAA8F;IAC9F,4FAA4F;IAC5F,sEAAsE;IACtE,MAAM,aAAa,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,aAAa,CAAC;IAE5E,OAAO;QACH,cAAc,EAAE,IAAI;QACpB,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,YAAY,CAAC,CAAC;KACnE,CAAC;AACN,CAAC;AASD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,wBAAwB,CAAC,EACrC,QAAQ,EACR,iBAAiB,GACG;IACpB,MAAM,kBAAkB,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IAEhG,OAAO,kBAAkB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC;AAaD;;;;;;;;GAQG;AACH,MAAM,UAAU,sBAAsB,CAClC,YAAoC,EACpC,OAAO,GAAG,IAAI,EACd,UAAsC,EAAE;IAExC,MAAM,EAAC,gBAAgB,EAAC,GAAG,OAAO,CAAC;IACnC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,QAAQ,CAAsB,MAAM,CAAC,CAAC;IAE5D,SAAS,CAAC,GAAG,EAAE;QACX,MAAM,QAAQ,GAAG,OAAO,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC;QACnF,IAAI,CAAC,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC;YACxB,MAAM,CAAC,MAAM,CAAC,CAAC;YACf,OAAO,SAAS,CAAC;QACrB,CAAC;QAED,IAAI,KAAK,GAAG,CAAC,CAAC;QAEd,MAAM,MAAM,GAAG,GAAG,EAAE;;YAChB,KAAK,GAAG,CAAC,CAAC;YACV,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;YACvC,IAAI,CAAC,SAAS,EAAE,CAAC;gBACb,OAAO;YACX,CAAC;YAED,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;gBAC9C,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;gBACxD,OAAO;YACX,CAAC;YAED,MAAM,QAAQ,GAAG,MAAA,gBAAgB,aAAhB,gBAAgB,uBAAhB,gBAAgB,CAAE,OAAO,0CAAE,qBAAqB,EAAE,CAAC;YAEpE,MAAM,IAAI,GAAG,0BAA0B,CAAC;gBACpC,cAAc,EAAE,QAAQ,CAAC,MAAM;gBAC/B,iBAAiB,EAAE,QAAQ,CAAC,SAAS;gBACrC,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,YAAY,EAAE,MAAM,CAAC,WAAW;gBAChC,kFAAkF;gBAClF,wEAAwE;gBACxE,YAAY,EAAE,SAAS,CAAC,qBAAqB,EAAE,CAAC,GAAG;gBACnD,iBAAiB,EACb,QAAQ;oBACR,wBAAwB,CAAC;wBACrB,QAAQ,EAAE,QAAQ,CAAC,GAAG;wBACtB,iBAAiB,EAAE,QAAQ,CAAC,SAAS;qBACxC,CAAC;gBACN,sBAAsB,EAAE,QAAQ,aAAR,QAAQ,uBAAR,QAAQ,CAAE,MAAM;aAC3C,CAAC,CAAC;YAEH,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,IAAI,CAAC,cAAc,KAAK,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,SAAS;gBAC5E,CAAC,CAAC,IAAI;gBACN,CAAC,CAAC,IAAI,CACb,CAAC;QACN,CAAC,CAAC;QAEF,wFAAwF;QACxF,iCAAiC;QACjC,MAAM,QAAQ,GAAG,GAAG,EAAE;YAClB,IAAI,CAAC,KAAK,EAAE,CAAC;gBACT,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;YAC1C,CAAC;QACL,CAAC,CAAC;QAEF,MAAM,EAAE,CAAC;QACT,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9C,QAAQ,CAAC,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC9C,QAAQ,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC/C,QAAQ,CAAC,gBAAgB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QAEhD,OAAO,GAAG,EAAE;YACR,IAAI,KAAK,EAAE,CAAC;gBACR,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAChC,CAAC;YACD,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACjD,QAAQ,CAAC,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;YACjD,QAAQ,CAAC,mBAAmB,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;YAClD,QAAQ,CAAC,mBAAmB,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACvD,CAAC,CAAC;IACN,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,EAAE,gBAAgB,CAAC,CAAC,CAAC;IAE9C,OAAO,GAAG,CAAC;AACf,CAAC","sourcesContent":["import {type RefObject, useEffect, useState} from 'react';\n\n/**\n * Minimum difference between the layout and the visual viewport that is treated as an opened\n * on-screen keyboard. Small differences come from browser UI (URL bar, toolbars) and must not\n * shrink the container.\n */\nexport const KEYBOARD_MIN_INSET = 80;\n\n/**\n * Largest shortfall of `visualViewport.height` against a measured reference that is still\n * attributed to the browser's own bottom chrome. Anything larger is the keyboard itself and\n * must not be corrected away.\n */\nexport const VIEWPORT_HEIGHT_TOLERANCE = 100;\n\nexport interface KeyboardViewportMetrics {\n /** `visualViewport.height` - height of the area not covered by the keyboard. */\n viewportHeight: number;\n /** `visualViewport.offsetTop` - offset of the visual viewport inside the layout viewport. */\n viewportOffsetTop: number;\n /** `visualViewport.scale` - pinch-zoom factor, `1` when the page is not zoomed. */\n scale: number;\n /** `window.innerHeight` - layout viewport height, unaffected by the keyboard on iOS Safari. */\n layoutHeight: number;\n /** Top of the container in layout viewport coordinates (`getBoundingClientRect().top`). */\n containerTop: number;\n /**\n * Top of an element pinned to the top of the layout viewport (`position: fixed; top: 0`), read\n * with `getBoundingClientRect()`. Client rectangles are relative to the layout viewport in most\n * browsers and to the visual viewport in Safari, and this reading tells the two apart: it stays\n * at zero in the first case and drops to minus the viewport offset in the second. `0` by\n * default, which keeps the layout viewport reading.\n */\n viewportOriginTop?: number;\n /**\n * Height of a reference element sized to the dynamic viewport (`height: 100dvh`), read from\n * the DOM. iOS Safari reports a visible area short by the height of its bottom toolbar.\n */\n measuredViewportHeight?: number;\n}\n\nexport interface KeyboardViewportFit {\n /** Whether the on-screen keyboard currently covers part of the layout viewport. */\n isKeyboardOpen: boolean;\n /**\n * Height limit that keeps the bottom of the container right above the keyboard.\n * `undefined` while the keyboard is closed - the container keeps its natural height.\n */\n maxHeight?: number;\n}\n\nconst CLOSED: KeyboardViewportFit = {isKeyboardOpen: false};\n\n/**\n * `visualViewport.height` can come back short by the height of the browser bottom chrome, which\n * makes the container shrink while no keyboard is open. A reference element sized to the dynamic\n * viewport measures the same area without that shortfall, so the larger of the two wins - but only\n * while the difference stays inside the tolerance, otherwise the keyboard itself would be\n * corrected away. The correction is one-way: it can only raise the visible height.\n */\nfunction resolveVisibleHeight(\n reportedHeight: number,\n measuredHeight: number | undefined,\n scale: number,\n tolerance: number,\n): number {\n // A DOM rect is in CSS pixels while `visualViewport.height` shrinks with pinch zoom, so the\n // two are only comparable on an unzoomed page.\n if (measuredHeight === undefined || scale !== 1) {\n return reportedHeight;\n }\n\n const difference = measuredHeight - reportedHeight;\n\n return difference > 0 && difference < tolerance ? measuredHeight : reportedHeight;\n}\n\n/**\n * Height limit for a container whose bottom would otherwise end up under the on-screen keyboard.\n *\n * Browsers keep the layout viewport at full height while the keyboard is open (iOS Safari always,\n * Chrome and Firefox since the `interactive-widget=resizes-visual` default), so `100vh` / `100dvh` /\n * `height: 100%` keep the original height and the bottom of the chat (prompt input, disclaimer)\n * stays hidden. The visible area is reported by `visualViewport` instead, and the limit is its\n * bottom edge measured from the top of the container.\n */\nexport function resolveKeyboardViewportFit(\n metrics: KeyboardViewportMetrics,\n minInset: number = KEYBOARD_MIN_INSET,\n): KeyboardViewportFit {\n const {\n viewportHeight,\n viewportOffsetTop,\n scale,\n layoutHeight,\n containerTop,\n viewportOriginTop = 0,\n measuredViewportHeight,\n } = metrics;\n\n const visibleHeight = resolveVisibleHeight(\n viewportHeight,\n measuredViewportHeight,\n scale,\n VIEWPORT_HEIGHT_TOLERANCE,\n );\n\n // Pinch zoom shrinks `visualViewport.height` just like the keyboard does, so the height is\n // scaled back to layout pixels first: what remains is the part of the layout viewport that the\n // user cannot reach by panning, which is the keyboard. A page that opted into\n // `interactive-widget=resizes-content` shrinks the layout viewport itself and lands here with a\n // zero inset - the browser has already done the work and the container is left alone.\n if (layoutHeight - visibleHeight * scale < minInset) {\n return CLOSED;\n }\n\n // Where the visible area ends, in the same coordinates the container was measured in. Reading\n // the origin from the DOM keeps this right in a browser that reports rectangles against the\n // visual viewport, where adding the offset back would count it twice.\n const visibleBottom = viewportOffsetTop + viewportOriginTop + visibleHeight;\n\n return {\n isKeyboardOpen: true,\n maxHeight: Math.max(0, Math.floor(visibleBottom - containerTop)),\n };\n}\n\nexport interface ViewportOriginMetrics {\n /** Top of the probe as `getBoundingClientRect` reports it. */\n probeTop: number;\n /** `visualViewport.offsetTop` at the same moment. */\n viewportOffsetTop: number;\n}\n\n/**\n * Top of the viewport the container is measured against, read from the probe.\n *\n * A probe pinned to the top of the layout viewport can only report two things: zero, where client\n * rectangles are measured against the layout viewport, and minus the viewport offset, where they\n * are measured against the visual viewport as in Safari. Anything else means the probe is not\n * where it was asked to be: a `transform`, a `filter`, a `will-change` or a `contain: paint` on\n * any ancestor - and panels that slide in are animated with exactly those - makes `position: fixed`\n * count from that ancestor instead of the viewport. Such a reading is dropped, and the limit falls\n * back to the formula without a probe rather than following the ancestor's own offset.\n */\nexport function resolveViewportOriginTop({\n probeTop,\n viewportOffsetTop,\n}: ViewportOriginMetrics): number {\n const isPinnedToViewport = Math.abs(probeTop) < 1 || Math.abs(probeTop + viewportOffsetTop) < 1;\n\n return isPinnedToViewport ? probeTop : 0;\n}\n\nexport interface KeyboardViewportFitOptions {\n /**\n * Element pinned to the top of the layout viewport and sized to the dynamic viewport\n * (`position: fixed; top: 0; height: 100dvh`). Its rectangle answers two questions the\n * `visualViewport` numbers alone cannot: which viewport client rectangles are measured\n * against, and how tall the browser itself considers the visible area to be. See\n * {@link VIEWPORT_HEIGHT_TOLERANCE}.\n */\n viewportProbeRef?: RefObject<HTMLElement | null>;\n}\n\n/**\n * Tracks the on-screen keyboard through `visualViewport` and returns the height limit that keeps\n * the referenced container inside the visible area. The container has to be anchored to the top of\n * the viewport: a bottom-anchored one moves its own top as soon as the limit shrinks it.\n *\n * @param containerRef - element to fit into the visual viewport\n * @param enabled - disables tracking (e.g. outside mobile mode)\n * @param options - optional dynamic viewport probe, see {@link KeyboardViewportFitOptions}\n */\nexport function useKeyboardViewportFit(\n containerRef: RefObject<HTMLElement>,\n enabled = true,\n options: KeyboardViewportFitOptions = {},\n): KeyboardViewportFit {\n const {viewportProbeRef} = options;\n const [fit, setFit] = useState<KeyboardViewportFit>(CLOSED);\n\n useEffect(() => {\n const viewport = typeof window === 'undefined' ? undefined : window.visualViewport;\n if (!enabled || !viewport) {\n setFit(CLOSED);\n return undefined;\n }\n\n let frame = 0;\n\n const update = () => {\n frame = 0;\n const container = containerRef.current;\n if (!container) {\n return;\n }\n\n if (!container.contains(document.activeElement)) {\n setFit((prev) => (prev.isKeyboardOpen ? CLOSED : prev));\n return;\n }\n\n const probeBox = viewportProbeRef?.current?.getBoundingClientRect();\n\n const next = resolveKeyboardViewportFit({\n viewportHeight: viewport.height,\n viewportOffsetTop: viewport.offsetTop,\n scale: viewport.scale,\n layoutHeight: window.innerHeight,\n // The limit only depends on the top of the container, which the limit itself does\n // not move - so applying it cannot feed back into the next measurement.\n containerTop: container.getBoundingClientRect().top,\n viewportOriginTop:\n probeBox &&\n resolveViewportOriginTop({\n probeTop: probeBox.top,\n viewportOffsetTop: viewport.offsetTop,\n }),\n measuredViewportHeight: probeBox?.height,\n });\n\n setFit((prev) =>\n prev.isKeyboardOpen === next.isKeyboardOpen && prev.maxHeight === next.maxHeight\n ? prev\n : next,\n );\n };\n\n // iOS reports intermediate viewport sizes during the keyboard animation, so updates are\n // collapsed into a single frame.\n const schedule = () => {\n if (!frame) {\n frame = requestAnimationFrame(update);\n }\n };\n\n update();\n viewport.addEventListener('resize', schedule);\n viewport.addEventListener('scroll', schedule);\n document.addEventListener('focusin', schedule);\n document.addEventListener('focusout', schedule);\n\n return () => {\n if (frame) {\n cancelAnimationFrame(frame);\n }\n viewport.removeEventListener('resize', schedule);\n viewport.removeEventListener('scroll', schedule);\n document.removeEventListener('focusin', schedule);\n document.removeEventListener('focusout', schedule);\n };\n }, [containerRef, enabled, viewportProbeRef]);\n\n return fit;\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export declare const SHEET_VISIBLE_BOTTOM_VAR = "--g-aikit-sheet-visible-bottom";
|
|
2
|
+
export declare const SHEET_VISIBLE_HEIGHT_VAR = "--g-aikit-sheet-visible-height";
|
|
3
|
+
export interface SheetKeyboardMetrics {
|
|
4
|
+
viewportHeight: number;
|
|
5
|
+
viewportOffsetTop: number;
|
|
6
|
+
scale: number;
|
|
7
|
+
layoutHeight: number;
|
|
8
|
+
}
|
|
9
|
+
export interface SheetKeyboardFit {
|
|
10
|
+
isKeyboardOpen: boolean;
|
|
11
|
+
visibleBottom?: number;
|
|
12
|
+
visibleHeight?: number;
|
|
13
|
+
}
|
|
14
|
+
export declare function resolveSheetKeyboardFit(metrics: SheetKeyboardMetrics, minInset?: number): SheetKeyboardFit;
|
|
15
|
+
/**
|
|
16
|
+
* Fits a bottom sheet above the on-screen keyboard raised from a field inside it.
|
|
17
|
+
*
|
|
18
|
+
* The sheet is refitted on the focus of the field, not on the keyboard resize: the height of the
|
|
19
|
+
* keyboard is remembered per screen size, so the sheet rises together with the keyboard instead of
|
|
20
|
+
* jumping once it has finished sliding in. The first time on a screen there is nothing to predict,
|
|
21
|
+
* so the fit is applied when the visual viewport settles, and the height is remembered for later.
|
|
22
|
+
*
|
|
23
|
+
* Two things move the sheet: the cap on its content, which uikit answers by resizing and
|
|
24
|
+
* translating the window from its `ResizeObserver`, and the height of the root, which sets the
|
|
25
|
+
* bottom edge. Both are animated, so they have to start in the same frame - otherwise the window
|
|
26
|
+
* hangs off the top of the screen for as long as the browser holds the observer back (Safari does
|
|
27
|
+
* that for ~150ms while the keyboard slides in). The root height is therefore written from a
|
|
28
|
+
* `ResizeObserver` on the same box uikit watches, which is delivered in the same frame.
|
|
29
|
+
*
|
|
30
|
+
* A field that sits below the line the keyboard will take (a short, filtered sheet at the bottom of
|
|
31
|
+
* the screen) makes Safari pan the visual viewport to reveal it, and the pan drags every fixed box
|
|
32
|
+
* up with it. To keep the field where Safari can see it, the tap is intercepted: an invisible
|
|
33
|
+
* anchor at the top of the screen takes the focus and raises the keyboard, the sheet rises with it,
|
|
34
|
+
* and once the keyboard has settled the focus is handed over to the field, which is above the
|
|
35
|
+
* keyboard by then.
|
|
36
|
+
*
|
|
37
|
+
* @param enabled - disables tracking (e.g. while the sheet is closed or outside mobile mode)
|
|
38
|
+
* @param sheetSelector - selector of the sheet root, used to find the content box uikit observes
|
|
39
|
+
*/
|
|
40
|
+
export declare function useSheetKeyboardFit(enabled?: boolean, sheetSelector?: string): SheetKeyboardFit;
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { KEYBOARD_MIN_INSET } from "./useKeyboardViewportFit.js";
|
|
3
|
+
export const SHEET_VISIBLE_BOTTOM_VAR = '--g-aikit-sheet-visible-bottom';
|
|
4
|
+
export const SHEET_VISIBLE_HEIGHT_VAR = '--g-aikit-sheet-visible-height';
|
|
5
|
+
const SHEET_MARGIN_BOX_SELECTOR = '.g-sheet-content-area__margin-box';
|
|
6
|
+
const KEYBOARD_HEIGHT_STORAGE_PREFIX = 'g-aikit-sheet-keyboard-height';
|
|
7
|
+
const PREDICT_GUARD_MS = 500;
|
|
8
|
+
const SYNC_FALLBACK_MS = 400;
|
|
9
|
+
const HANDOVER_WAIT_MS = 1500;
|
|
10
|
+
const HANDOVER_STABLE_FRAMES = 4;
|
|
11
|
+
const SUPPRESS_CLICK_MS = 600;
|
|
12
|
+
const KEYBOARD_CLOSE_MS = 700;
|
|
13
|
+
const CLOSED = { isKeyboardOpen: false };
|
|
14
|
+
export function resolveSheetKeyboardFit(metrics, minInset = KEYBOARD_MIN_INSET) {
|
|
15
|
+
const { viewportHeight, viewportOffsetTop, scale, layoutHeight } = metrics;
|
|
16
|
+
if (layoutHeight - viewportHeight * scale < minInset) {
|
|
17
|
+
return CLOSED;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
isKeyboardOpen: true,
|
|
21
|
+
visibleBottom: Math.max(0, Math.floor(viewportOffsetTop + viewportHeight)),
|
|
22
|
+
visibleHeight: Math.max(0, Math.floor(viewportHeight)),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const NON_TEXT_INPUT_TYPES = new Set([
|
|
26
|
+
'button',
|
|
27
|
+
'checkbox',
|
|
28
|
+
'color',
|
|
29
|
+
'file',
|
|
30
|
+
'hidden',
|
|
31
|
+
'image',
|
|
32
|
+
'radio',
|
|
33
|
+
'range',
|
|
34
|
+
'reset',
|
|
35
|
+
'submit',
|
|
36
|
+
]);
|
|
37
|
+
function getIsTextEntryTarget(target) {
|
|
38
|
+
if (target instanceof HTMLTextAreaElement) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
if (target instanceof HTMLInputElement) {
|
|
42
|
+
return !NON_TEXT_INPUT_TYPES.has(target.type);
|
|
43
|
+
}
|
|
44
|
+
return target instanceof HTMLElement && target.isContentEditable;
|
|
45
|
+
}
|
|
46
|
+
function getKeyboardHeightKey() {
|
|
47
|
+
return `${KEYBOARD_HEIGHT_STORAGE_PREFIX}:${window.innerWidth}x${window.innerHeight}`;
|
|
48
|
+
}
|
|
49
|
+
function readKnownKeyboardHeight() {
|
|
50
|
+
try {
|
|
51
|
+
const value = Number(window.localStorage.getItem(getKeyboardHeightKey()));
|
|
52
|
+
return Number.isFinite(value) && value > KEYBOARD_MIN_INSET ? value : 0;
|
|
53
|
+
}
|
|
54
|
+
catch (_a) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function writeKnownKeyboardHeight(height) {
|
|
59
|
+
try {
|
|
60
|
+
window.localStorage.setItem(getKeyboardHeightKey(), String(Math.round(height)));
|
|
61
|
+
}
|
|
62
|
+
catch (_a) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function createAnchor() {
|
|
67
|
+
const anchor = document.createElement('input');
|
|
68
|
+
anchor.setAttribute('aria-hidden', 'true');
|
|
69
|
+
anchor.tabIndex = -1;
|
|
70
|
+
anchor.setAttribute('style', 'position:fixed;top:0;left:0;z-index:-1;width:1px;height:1px;font-size:16px;opacity:0;pointer-events:none;border:0;padding:0');
|
|
71
|
+
return anchor;
|
|
72
|
+
}
|
|
73
|
+
function writeVar(name, value) {
|
|
74
|
+
const { style } = document.documentElement;
|
|
75
|
+
if (value === undefined) {
|
|
76
|
+
style.removeProperty(name);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
style.setProperty(name, `${value}px`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Fits a bottom sheet above the on-screen keyboard raised from a field inside it.
|
|
84
|
+
*
|
|
85
|
+
* The sheet is refitted on the focus of the field, not on the keyboard resize: the height of the
|
|
86
|
+
* keyboard is remembered per screen size, so the sheet rises together with the keyboard instead of
|
|
87
|
+
* jumping once it has finished sliding in. The first time on a screen there is nothing to predict,
|
|
88
|
+
* so the fit is applied when the visual viewport settles, and the height is remembered for later.
|
|
89
|
+
*
|
|
90
|
+
* Two things move the sheet: the cap on its content, which uikit answers by resizing and
|
|
91
|
+
* translating the window from its `ResizeObserver`, and the height of the root, which sets the
|
|
92
|
+
* bottom edge. Both are animated, so they have to start in the same frame - otherwise the window
|
|
93
|
+
* hangs off the top of the screen for as long as the browser holds the observer back (Safari does
|
|
94
|
+
* that for ~150ms while the keyboard slides in). The root height is therefore written from a
|
|
95
|
+
* `ResizeObserver` on the same box uikit watches, which is delivered in the same frame.
|
|
96
|
+
*
|
|
97
|
+
* A field that sits below the line the keyboard will take (a short, filtered sheet at the bottom of
|
|
98
|
+
* the screen) makes Safari pan the visual viewport to reveal it, and the pan drags every fixed box
|
|
99
|
+
* up with it. To keep the field where Safari can see it, the tap is intercepted: an invisible
|
|
100
|
+
* anchor at the top of the screen takes the focus and raises the keyboard, the sheet rises with it,
|
|
101
|
+
* and once the keyboard has settled the focus is handed over to the field, which is above the
|
|
102
|
+
* keyboard by then.
|
|
103
|
+
*
|
|
104
|
+
* @param enabled - disables tracking (e.g. while the sheet is closed or outside mobile mode)
|
|
105
|
+
* @param sheetSelector - selector of the sheet root, used to find the content box uikit observes
|
|
106
|
+
*/
|
|
107
|
+
export function useSheetKeyboardFit(enabled = true, sheetSelector) {
|
|
108
|
+
const [fit, setFit] = useState(CLOSED);
|
|
109
|
+
useEffect(() => {
|
|
110
|
+
const viewport = typeof window === 'undefined' ? undefined : window.visualViewport;
|
|
111
|
+
if (!enabled || !viewport) {
|
|
112
|
+
setFit(CLOSED);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
let tracking = false;
|
|
116
|
+
let guard = 0;
|
|
117
|
+
let syncFallback = 0;
|
|
118
|
+
let desired = CLOSED;
|
|
119
|
+
let releaseTimer = 0;
|
|
120
|
+
let anchor = null;
|
|
121
|
+
let handoverTimer = 0;
|
|
122
|
+
let handoverFrame = 0;
|
|
123
|
+
let suppressClicksUntil = 0;
|
|
124
|
+
const getIsKeyboardShown = () => window.innerHeight - viewport.height >= KEYBOARD_MIN_INSET;
|
|
125
|
+
const removeAnchor = () => {
|
|
126
|
+
window.clearTimeout(handoverTimer);
|
|
127
|
+
window.cancelAnimationFrame(handoverFrame);
|
|
128
|
+
handoverTimer = 0;
|
|
129
|
+
handoverFrame = 0;
|
|
130
|
+
anchor === null || anchor === void 0 ? void 0 : anchor.remove();
|
|
131
|
+
anchor = null;
|
|
132
|
+
};
|
|
133
|
+
const writeBottom = () => {
|
|
134
|
+
window.clearTimeout(syncFallback);
|
|
135
|
+
syncFallback = 0;
|
|
136
|
+
writeVar(SHEET_VISIBLE_BOTTOM_VAR, desired.visibleBottom);
|
|
137
|
+
};
|
|
138
|
+
const box = typeof ResizeObserver === 'undefined' || !sheetSelector
|
|
139
|
+
? null
|
|
140
|
+
: document.querySelector(`${sheetSelector} ${SHEET_MARGIN_BOX_SELECTOR}`);
|
|
141
|
+
let observer = null;
|
|
142
|
+
if (box) {
|
|
143
|
+
observer = new ResizeObserver(writeBottom);
|
|
144
|
+
observer.observe(box);
|
|
145
|
+
}
|
|
146
|
+
const apply = (next) => {
|
|
147
|
+
const isCapChanging = next.isKeyboardOpen !== desired.isKeyboardOpen ||
|
|
148
|
+
next.visibleHeight !== desired.visibleHeight;
|
|
149
|
+
desired = next;
|
|
150
|
+
writeVar(SHEET_VISIBLE_HEIGHT_VAR, next.visibleHeight);
|
|
151
|
+
setFit((prev) => prev.isKeyboardOpen === next.isKeyboardOpen &&
|
|
152
|
+
prev.visibleBottom === next.visibleBottom &&
|
|
153
|
+
prev.visibleHeight === next.visibleHeight
|
|
154
|
+
? prev
|
|
155
|
+
: next);
|
|
156
|
+
if (!observer || !isCapChanging) {
|
|
157
|
+
writeBottom();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
window.clearTimeout(syncFallback);
|
|
161
|
+
syncFallback = window.setTimeout(writeBottom, SYNC_FALLBACK_MS);
|
|
162
|
+
};
|
|
163
|
+
const measure = () => {
|
|
164
|
+
const next = resolveSheetKeyboardFit({
|
|
165
|
+
viewportHeight: viewport.height,
|
|
166
|
+
viewportOffsetTop: viewport.offsetTop,
|
|
167
|
+
scale: viewport.scale,
|
|
168
|
+
layoutHeight: window.innerHeight,
|
|
169
|
+
});
|
|
170
|
+
if (next.isKeyboardOpen) {
|
|
171
|
+
// The tallest shrink is the keyboard; smaller readings come from its opening and
|
|
172
|
+
// closing animation and must not overwrite the remembered height with a stray value.
|
|
173
|
+
const measured = window.innerHeight - viewport.height;
|
|
174
|
+
if (measured > readKnownKeyboardHeight()) {
|
|
175
|
+
writeKnownKeyboardHeight(measured);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
apply(next);
|
|
179
|
+
if (!tracking && !next.isKeyboardOpen) {
|
|
180
|
+
window.clearTimeout(releaseTimer);
|
|
181
|
+
releaseTimer = 0;
|
|
182
|
+
viewport.removeEventListener('resize', measure);
|
|
183
|
+
viewport.removeEventListener('scroll', measure);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const release = () => {
|
|
187
|
+
releaseTimer = 0;
|
|
188
|
+
viewport.removeEventListener('resize', measure);
|
|
189
|
+
viewport.removeEventListener('scroll', measure);
|
|
190
|
+
apply(CLOSED);
|
|
191
|
+
};
|
|
192
|
+
const onFocusIn = (event) => {
|
|
193
|
+
if (tracking || !getIsTextEntryTarget(event.target)) {
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
tracking = true;
|
|
197
|
+
viewport.addEventListener('resize', measure);
|
|
198
|
+
viewport.addEventListener('scroll', measure);
|
|
199
|
+
const known = readKnownKeyboardHeight();
|
|
200
|
+
if (!known) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
// The keyboard has not slid in yet; place the sheet where it will end up so it rises
|
|
204
|
+
// with the keyboard, and undo the guess if the keyboard never shows (hardware keyboard).
|
|
205
|
+
const height = Math.max(0, window.innerHeight - known);
|
|
206
|
+
apply({ isKeyboardOpen: true, visibleBottom: height, visibleHeight: height });
|
|
207
|
+
window.clearTimeout(guard);
|
|
208
|
+
guard = window.setTimeout(() => {
|
|
209
|
+
if (window.innerHeight - viewport.height < KEYBOARD_MIN_INSET) {
|
|
210
|
+
apply(CLOSED);
|
|
211
|
+
}
|
|
212
|
+
}, PREDICT_GUARD_MS);
|
|
213
|
+
};
|
|
214
|
+
const onFocusOut = (event) => {
|
|
215
|
+
if (!tracking || getIsTextEntryTarget(event.relatedTarget)) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
tracking = false;
|
|
219
|
+
window.clearTimeout(guard);
|
|
220
|
+
// The keyboard is still on screen and the visual viewport is still short; undoing the
|
|
221
|
+
// fit now rebuilds the sheet against a viewport that is about to change again. The
|
|
222
|
+
// listeners stay on until `measure` sees the viewport back at full height, with a
|
|
223
|
+
// timer for the cases that never send a resize (a hardware keyboard).
|
|
224
|
+
window.clearTimeout(releaseTimer);
|
|
225
|
+
releaseTimer = window.setTimeout(release, KEYBOARD_CLOSE_MS);
|
|
226
|
+
};
|
|
227
|
+
const handOver = (field) => {
|
|
228
|
+
removeAnchor();
|
|
229
|
+
field.focus({ preventScroll: true });
|
|
230
|
+
if (field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement) {
|
|
231
|
+
const { length } = field.value;
|
|
232
|
+
field.setSelectionRange(length, length);
|
|
233
|
+
}
|
|
234
|
+
suppressClicksUntil = performance.now() + SUPPRESS_CLICK_MS;
|
|
235
|
+
};
|
|
236
|
+
const onPointerDown = (event) => {
|
|
237
|
+
const target = event.target;
|
|
238
|
+
if (!(target instanceof Element) || !sheetSelector || !target.closest(sheetSelector)) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const field = target.closest('input, textarea, [contenteditable]');
|
|
242
|
+
if (!(field instanceof HTMLElement) || !getIsTextEntryTarget(field)) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
const known = readKnownKeyboardHeight();
|
|
246
|
+
if (!known || getIsKeyboardShown() || anchor) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (field.getBoundingClientRect().bottom <= window.innerHeight - known) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
event.preventDefault();
|
|
253
|
+
event.stopImmediatePropagation();
|
|
254
|
+
suppressClicksUntil = Number.POSITIVE_INFINITY;
|
|
255
|
+
anchor = createAnchor();
|
|
256
|
+
document.body.appendChild(anchor);
|
|
257
|
+
anchor.focus({ preventScroll: true });
|
|
258
|
+
let stableFrames = 0;
|
|
259
|
+
let lastHeight = 0;
|
|
260
|
+
const settle = () => {
|
|
261
|
+
if (!anchor) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const isSteady = viewport.height === lastHeight;
|
|
265
|
+
lastHeight = viewport.height;
|
|
266
|
+
const isVisible = field.getBoundingClientRect().bottom <= viewport.height + 1;
|
|
267
|
+
stableFrames = isSteady && isVisible ? stableFrames + 1 : 0;
|
|
268
|
+
if (stableFrames >= HANDOVER_STABLE_FRAMES) {
|
|
269
|
+
handOver(field);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
handoverFrame = window.requestAnimationFrame(settle);
|
|
273
|
+
};
|
|
274
|
+
const onKeyboardShown = () => {
|
|
275
|
+
if (!getIsKeyboardShown()) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
viewport.removeEventListener('resize', onKeyboardShown);
|
|
279
|
+
settle();
|
|
280
|
+
};
|
|
281
|
+
viewport.addEventListener('resize', onKeyboardShown);
|
|
282
|
+
handoverTimer = window.setTimeout(() => {
|
|
283
|
+
viewport.removeEventListener('resize', onKeyboardShown);
|
|
284
|
+
handOver(field);
|
|
285
|
+
}, HANDOVER_WAIT_MS);
|
|
286
|
+
};
|
|
287
|
+
const onSyntheticClick = (event) => {
|
|
288
|
+
if (performance.now() > suppressClicksUntil) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
event.stopImmediatePropagation();
|
|
293
|
+
};
|
|
294
|
+
document.addEventListener('focusin', onFocusIn, true);
|
|
295
|
+
document.addEventListener('focusout', onFocusOut, true);
|
|
296
|
+
document.addEventListener('pointerdown', onPointerDown, true);
|
|
297
|
+
document.addEventListener('mousedown', onSyntheticClick, true);
|
|
298
|
+
document.addEventListener('click', onSyntheticClick, true);
|
|
299
|
+
return () => {
|
|
300
|
+
removeAnchor();
|
|
301
|
+
document.removeEventListener('pointerdown', onPointerDown, true);
|
|
302
|
+
document.removeEventListener('mousedown', onSyntheticClick, true);
|
|
303
|
+
document.removeEventListener('click', onSyntheticClick, true);
|
|
304
|
+
window.clearTimeout(guard);
|
|
305
|
+
window.clearTimeout(syncFallback);
|
|
306
|
+
window.clearTimeout(releaseTimer);
|
|
307
|
+
observer === null || observer === void 0 ? void 0 : observer.disconnect();
|
|
308
|
+
document.removeEventListener('focusin', onFocusIn, true);
|
|
309
|
+
document.removeEventListener('focusout', onFocusOut, true);
|
|
310
|
+
viewport.removeEventListener('resize', measure);
|
|
311
|
+
viewport.removeEventListener('scroll', measure);
|
|
312
|
+
writeVar(SHEET_VISIBLE_BOTTOM_VAR, undefined);
|
|
313
|
+
writeVar(SHEET_VISIBLE_HEIGHT_VAR, undefined);
|
|
314
|
+
};
|
|
315
|
+
}, [enabled, sheetSelector]);
|
|
316
|
+
return fit;
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=useSheetKeyboardFit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useSheetKeyboardFit.js","sourceRoot":"../../../src","sources":["hooks/useSheetKeyboardFit.ts"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useState} from 'react';\n\nimport {KEYBOARD_MIN_INSET} from './useKeyboardViewportFit';\n\nexport const SHEET_VISIBLE_BOTTOM_VAR = '--g-aikit-sheet-visible-bottom';\n\nexport const SHEET_VISIBLE_HEIGHT_VAR = '--g-aikit-sheet-visible-height';\n\nconst SHEET_MARGIN_BOX_SELECTOR = '.g-sheet-content-area__margin-box';\n\nconst KEYBOARD_HEIGHT_STORAGE_PREFIX = 'g-aikit-sheet-keyboard-height';\n\nconst PREDICT_GUARD_MS = 500;\n\nconst SYNC_FALLBACK_MS = 400;\n\nconst HANDOVER_WAIT_MS = 1500;\n\nconst HANDOVER_STABLE_FRAMES = 4;\n\nconst SUPPRESS_CLICK_MS = 600;\n\nconst KEYBOARD_CLOSE_MS = 700;\n\nexport interface SheetKeyboardMetrics {\n viewportHeight: number;\n viewportOffsetTop: number;\n scale: number;\n layoutHeight: number;\n}\n\nexport interface SheetKeyboardFit {\n isKeyboardOpen: boolean;\n visibleBottom?: number;\n visibleHeight?: number;\n}\n\nconst CLOSED: SheetKeyboardFit = {isKeyboardOpen: false};\n\nexport function resolveSheetKeyboardFit(\n metrics: SheetKeyboardMetrics,\n minInset: number = KEYBOARD_MIN_INSET,\n): SheetKeyboardFit {\n const {viewportHeight, viewportOffsetTop, scale, layoutHeight} = metrics;\n\n if (layoutHeight - viewportHeight * scale < minInset) {\n return CLOSED;\n }\n\n return {\n isKeyboardOpen: true,\n visibleBottom: Math.max(0, Math.floor(viewportOffsetTop + viewportHeight)),\n visibleHeight: Math.max(0, Math.floor(viewportHeight)),\n };\n}\n\nconst NON_TEXT_INPUT_TYPES = new Set([\n 'button',\n 'checkbox',\n 'color',\n 'file',\n 'hidden',\n 'image',\n 'radio',\n 'range',\n 'reset',\n 'submit',\n]);\n\nfunction getIsTextEntryTarget(target: EventTarget | null): boolean {\n if (target instanceof HTMLTextAreaElement) {\n return true;\n }\n\n if (target instanceof HTMLInputElement) {\n return !NON_TEXT_INPUT_TYPES.has(target.type);\n }\n\n return target instanceof HTMLElement && target.isContentEditable;\n}\n\nfunction getKeyboardHeightKey(): string {\n return `${KEYBOARD_HEIGHT_STORAGE_PREFIX}:${window.innerWidth}x${window.innerHeight}`;\n}\n\nfunction readKnownKeyboardHeight(): number {\n try {\n const value = Number(window.localStorage.getItem(getKeyboardHeightKey()));\n\n return Number.isFinite(value) && value > KEYBOARD_MIN_INSET ? value : 0;\n } catch {\n return 0;\n }\n}\n\nfunction writeKnownKeyboardHeight(height: number) {\n try {\n window.localStorage.setItem(getKeyboardHeightKey(), String(Math.round(height)));\n } catch {\n return;\n }\n}\n\nfunction createAnchor(): HTMLInputElement {\n const anchor = document.createElement('input');\n\n anchor.setAttribute('aria-hidden', 'true');\n anchor.tabIndex = -1;\n anchor.setAttribute(\n 'style',\n 'position:fixed;top:0;left:0;z-index:-1;width:1px;height:1px;font-size:16px;opacity:0;pointer-events:none;border:0;padding:0',\n );\n\n return anchor;\n}\n\nfunction writeVar(name: string, value: number | undefined) {\n const {style} = document.documentElement;\n\n if (value === undefined) {\n style.removeProperty(name);\n } else {\n style.setProperty(name, `${value}px`);\n }\n}\n\n/**\n * Fits a bottom sheet above the on-screen keyboard raised from a field inside it.\n *\n * The sheet is refitted on the focus of the field, not on the keyboard resize: the height of the\n * keyboard is remembered per screen size, so the sheet rises together with the keyboard instead of\n * jumping once it has finished sliding in. The first time on a screen there is nothing to predict,\n * so the fit is applied when the visual viewport settles, and the height is remembered for later.\n *\n * Two things move the sheet: the cap on its content, which uikit answers by resizing and\n * translating the window from its `ResizeObserver`, and the height of the root, which sets the\n * bottom edge. Both are animated, so they have to start in the same frame - otherwise the window\n * hangs off the top of the screen for as long as the browser holds the observer back (Safari does\n * that for ~150ms while the keyboard slides in). The root height is therefore written from a\n * `ResizeObserver` on the same box uikit watches, which is delivered in the same frame.\n *\n * A field that sits below the line the keyboard will take (a short, filtered sheet at the bottom of\n * the screen) makes Safari pan the visual viewport to reveal it, and the pan drags every fixed box\n * up with it. To keep the field where Safari can see it, the tap is intercepted: an invisible\n * anchor at the top of the screen takes the focus and raises the keyboard, the sheet rises with it,\n * and once the keyboard has settled the focus is handed over to the field, which is above the\n * keyboard by then.\n *\n * @param enabled - disables tracking (e.g. while the sheet is closed or outside mobile mode)\n * @param sheetSelector - selector of the sheet root, used to find the content box uikit observes\n */\nexport function useSheetKeyboardFit(enabled = true, sheetSelector?: string): SheetKeyboardFit {\n const [fit, setFit] = useState<SheetKeyboardFit>(CLOSED);\n\n useEffect(() => {\n const viewport = typeof window === 'undefined' ? undefined : window.visualViewport;\n if (!enabled || !viewport) {\n setFit(CLOSED);\n return undefined;\n }\n\n let tracking = false;\n let guard = 0;\n let syncFallback = 0;\n let desired: SheetKeyboardFit = CLOSED;\n let releaseTimer = 0;\n let anchor: HTMLInputElement | null = null;\n let handoverTimer = 0;\n let handoverFrame = 0;\n let suppressClicksUntil = 0;\n\n const getIsKeyboardShown = () => window.innerHeight - viewport.height >= KEYBOARD_MIN_INSET;\n\n const removeAnchor = () => {\n window.clearTimeout(handoverTimer);\n window.cancelAnimationFrame(handoverFrame);\n handoverTimer = 0;\n handoverFrame = 0;\n anchor?.remove();\n anchor = null;\n };\n\n const writeBottom = () => {\n window.clearTimeout(syncFallback);\n syncFallback = 0;\n writeVar(SHEET_VISIBLE_BOTTOM_VAR, desired.visibleBottom);\n };\n\n const box =\n typeof ResizeObserver === 'undefined' || !sheetSelector\n ? null\n : document.querySelector(`${sheetSelector} ${SHEET_MARGIN_BOX_SELECTOR}`);\n let observer: ResizeObserver | null = null;\n if (box) {\n observer = new ResizeObserver(writeBottom);\n observer.observe(box);\n }\n\n const apply = (next: SheetKeyboardFit) => {\n const isCapChanging =\n next.isKeyboardOpen !== desired.isKeyboardOpen ||\n next.visibleHeight !== desired.visibleHeight;\n\n desired = next;\n writeVar(SHEET_VISIBLE_HEIGHT_VAR, next.visibleHeight);\n setFit((prev) =>\n prev.isKeyboardOpen === next.isKeyboardOpen &&\n prev.visibleBottom === next.visibleBottom &&\n prev.visibleHeight === next.visibleHeight\n ? prev\n : next,\n );\n\n if (!observer || !isCapChanging) {\n writeBottom();\n return;\n }\n\n window.clearTimeout(syncFallback);\n syncFallback = window.setTimeout(writeBottom, SYNC_FALLBACK_MS);\n };\n\n const measure = () => {\n const next = resolveSheetKeyboardFit({\n viewportHeight: viewport.height,\n viewportOffsetTop: viewport.offsetTop,\n scale: viewport.scale,\n layoutHeight: window.innerHeight,\n });\n\n if (next.isKeyboardOpen) {\n // The tallest shrink is the keyboard; smaller readings come from its opening and\n // closing animation and must not overwrite the remembered height with a stray value.\n const measured = window.innerHeight - viewport.height;\n if (measured > readKnownKeyboardHeight()) {\n writeKnownKeyboardHeight(measured);\n }\n }\n\n apply(next);\n\n if (!tracking && !next.isKeyboardOpen) {\n window.clearTimeout(releaseTimer);\n releaseTimer = 0;\n viewport.removeEventListener('resize', measure);\n viewport.removeEventListener('scroll', measure);\n }\n };\n\n const release = () => {\n releaseTimer = 0;\n viewport.removeEventListener('resize', measure);\n viewport.removeEventListener('scroll', measure);\n apply(CLOSED);\n };\n\n const onFocusIn = (event: FocusEvent) => {\n if (tracking || !getIsTextEntryTarget(event.target)) {\n return;\n }\n\n tracking = true;\n viewport.addEventListener('resize', measure);\n viewport.addEventListener('scroll', measure);\n\n const known = readKnownKeyboardHeight();\n if (!known) {\n return;\n }\n\n // The keyboard has not slid in yet; place the sheet where it will end up so it rises\n // with the keyboard, and undo the guess if the keyboard never shows (hardware keyboard).\n const height = Math.max(0, window.innerHeight - known);\n apply({isKeyboardOpen: true, visibleBottom: height, visibleHeight: height});\n\n window.clearTimeout(guard);\n guard = window.setTimeout(() => {\n if (window.innerHeight - viewport.height < KEYBOARD_MIN_INSET) {\n apply(CLOSED);\n }\n }, PREDICT_GUARD_MS);\n };\n\n const onFocusOut = (event: FocusEvent) => {\n if (!tracking || getIsTextEntryTarget(event.relatedTarget)) {\n return;\n }\n\n tracking = false;\n window.clearTimeout(guard);\n\n // The keyboard is still on screen and the visual viewport is still short; undoing the\n // fit now rebuilds the sheet against a viewport that is about to change again. The\n // listeners stay on until `measure` sees the viewport back at full height, with a\n // timer for the cases that never send a resize (a hardware keyboard).\n window.clearTimeout(releaseTimer);\n releaseTimer = window.setTimeout(release, KEYBOARD_CLOSE_MS);\n };\n\n const handOver = (field: HTMLElement) => {\n removeAnchor();\n field.focus({preventScroll: true});\n\n if (field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement) {\n const {length} = field.value;\n field.setSelectionRange(length, length);\n }\n\n suppressClicksUntil = performance.now() + SUPPRESS_CLICK_MS;\n };\n\n const onPointerDown = (event: Event) => {\n const target = event.target;\n if (!(target instanceof Element) || !sheetSelector || !target.closest(sheetSelector)) {\n return;\n }\n\n const field = target.closest('input, textarea, [contenteditable]');\n if (!(field instanceof HTMLElement) || !getIsTextEntryTarget(field)) {\n return;\n }\n\n const known = readKnownKeyboardHeight();\n if (!known || getIsKeyboardShown() || anchor) {\n return;\n }\n\n if (field.getBoundingClientRect().bottom <= window.innerHeight - known) {\n return;\n }\n\n event.preventDefault();\n event.stopImmediatePropagation();\n suppressClicksUntil = Number.POSITIVE_INFINITY;\n\n anchor = createAnchor();\n document.body.appendChild(anchor);\n anchor.focus({preventScroll: true});\n\n let stableFrames = 0;\n let lastHeight = 0;\n\n const settle = () => {\n if (!anchor) {\n return;\n }\n\n const isSteady = viewport.height === lastHeight;\n lastHeight = viewport.height;\n const isVisible = field.getBoundingClientRect().bottom <= viewport.height + 1;\n stableFrames = isSteady && isVisible ? stableFrames + 1 : 0;\n\n if (stableFrames >= HANDOVER_STABLE_FRAMES) {\n handOver(field);\n return;\n }\n\n handoverFrame = window.requestAnimationFrame(settle);\n };\n\n const onKeyboardShown = () => {\n if (!getIsKeyboardShown()) {\n return;\n }\n\n viewport.removeEventListener('resize', onKeyboardShown);\n settle();\n };\n\n viewport.addEventListener('resize', onKeyboardShown);\n handoverTimer = window.setTimeout(() => {\n viewport.removeEventListener('resize', onKeyboardShown);\n handOver(field);\n }, HANDOVER_WAIT_MS);\n };\n\n const onSyntheticClick = (event: Event) => {\n if (performance.now() > suppressClicksUntil) {\n return;\n }\n\n event.preventDefault();\n event.stopImmediatePropagation();\n };\n\n document.addEventListener('focusin', onFocusIn, true);\n document.addEventListener('focusout', onFocusOut, true);\n document.addEventListener('pointerdown', onPointerDown, true);\n document.addEventListener('mousedown', onSyntheticClick, true);\n document.addEventListener('click', onSyntheticClick, true);\n\n return () => {\n removeAnchor();\n document.removeEventListener('pointerdown', onPointerDown, true);\n document.removeEventListener('mousedown', onSyntheticClick, true);\n document.removeEventListener('click', onSyntheticClick, true);\n window.clearTimeout(guard);\n window.clearTimeout(syncFallback);\n window.clearTimeout(releaseTimer);\n observer?.disconnect();\n document.removeEventListener('focusin', onFocusIn, true);\n document.removeEventListener('focusout', onFocusOut, true);\n viewport.removeEventListener('resize', measure);\n viewport.removeEventListener('scroll', measure);\n writeVar(SHEET_VISIBLE_BOTTOM_VAR, undefined);\n writeVar(SHEET_VISIBLE_HEIGHT_VAR, undefined);\n };\n }, [enabled, sheetSelector]);\n\n return fit;\n}\n"]}
|
package/build/esm/package.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"2.21.
|
|
1
|
+
{"version":"2.21.2","type":"module","sideEffects":["*.css","*.scss"]}
|