@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.
@@ -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))
@@ -4,6 +4,7 @@ exports.History = History;
4
4
  const tslib_1 = require("tslib");
5
5
  const jsx_runtime_1 = require("react/jsx-runtime");
6
6
  const uikit_1 = require("@gravity-ui/uikit");
7
+ const useSheetKeyboardFit_1 = require("../../../hooks/useSheetKeyboardFit.js");
7
8
  const cn_1 = require("../../../utils/cn.js");
8
9
  const HistoryList_1 = require("./HistoryList.js");
9
10
  const i18n_1 = require("./i18n/index.js");
@@ -19,12 +20,16 @@ function History(props) {
19
20
  const { open = false, onOpenChange, anchorElement, showSheetTitle = true } = props, listProps = tslib_1.__rest(props, ["open", "onOpenChange", "anchorElement", "showSheetTitle"]);
20
21
  const isMobile = (0, uikit_1.useMobile)();
21
22
  const sheetId = (0, uikit_1.useUniqId)();
23
+ const { isKeyboardOpen } = (0, useSheetKeyboardFit_1.useSheetKeyboardFit)(isMobile && open, `.${b('sheet')}`);
22
24
  const handleChatClick = () => {
23
25
  onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false);
24
26
  };
25
27
  const list = (0, jsx_runtime_1.jsx)(HistoryList_1.HistoryList, Object.assign({}, listProps, { onChatClick: handleChatClick }));
26
28
  if (isMobile) {
27
- return ((0, jsx_runtime_1.jsx)(uikit_1.Sheet, { id: sheetId, title: (0, i18n_1.i18n)('sheet-title'), visible: open, onClose: () => onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false), contentClassName: b('sheet-content', { 'without-title': !showSheetTitle }), qa: "history-sheet", allowHideOnContentScroll: true, children: list }));
29
+ return ((0, jsx_runtime_1.jsx)(uikit_1.Sheet, { id: sheetId, title: (0, i18n_1.i18n)('sheet-title'), visible: open, onClose: () => onOpenChange === null || onOpenChange === void 0 ? void 0 : onOpenChange(false), className: b('sheet'), contentClassName: b('sheet-content', {
30
+ 'without-title': !showSheetTitle,
31
+ 'keyboard-open': isKeyboardOpen,
32
+ }), qa: "history-sheet", allowHideOnContentScroll: true, children: list }));
28
33
  }
29
34
  return ((0, jsx_runtime_1.jsx)(uikit_1.Popup, { anchorElement: anchorElement, placement: "bottom-end", open: open, onOpenChange: onOpenChange, children: list }));
30
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"History.js","sourceRoot":"../../../../../src","sources":["components/templates/History/History.tsx"],"names":[],"mappings":";;AA+BA,0BAqCC;;;AApED,6CAAqE;AAErE,6CAAwC;AAExC,kDAAiE;AACjE,0CAA4B;AAE5B,yBAAwB;AAExB,MAAM,CAAC,GAAG,IAAA,UAAK,EAAC,SAAS,CAAC,CAAC;AAgB3B;;;;;GAKG;AACH,SAAgB,OAAO,CAAC,KAAmB;IACvC,MAAM,EAAC,IAAI,GAAG,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,GAAG,IAAI,KAAkB,KAAK,EAAlB,SAAS,kBAAI,KAAK,EAAxF,2DAAgF,CAAQ,CAAC;IAC/F,MAAM,QAAQ,GAAG,IAAA,iBAAS,GAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAA,iBAAS,GAAE,CAAC;IAE5B,MAAM,eAAe,GAAG,GAAG,EAAE;QACzB,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,KAAK,CAAC,CAAC;IAC1B,CAAC,CAAC;IAEF,MAAM,IAAI,GAAG,uBAAC,yBAAW,oBAAK,SAAS,IAAE,WAAW,EAAE,eAAe,IAAI,CAAC;IAE1E,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO,CACH,uBAAC,aAAK,IACF,EAAE,EAAE,OAAO,EACX,KAAK,EAAE,IAAA,WAAI,EAAC,aAAa,CAAC,EAC1B,OAAO,EAAE,IAAI,EACb,OAAO,EAAE,GAAG,EAAE,CAAC,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAG,KAAK,CAAC,EACpC,gBAAgB,EAAE,CAAC,CAAC,eAAe,EAAE,EAAC,eAAe,EAAE,CAAC,cAAc,EAAC,CAAC,EACxE,EAAE,EAAC,eAAe,EAClB,wBAAwB,kBAEvB,IAAI,GACD,CACX,CAAC;IACN,CAAC;IAED,OAAO,CACH,uBAAC,aAAK,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 {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\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 contentClassName={b('sheet-content', {'without-title': !showSheetTitle})}\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"]}
1
+ {"version":3,"file":"History.js","sourceRoot":"../../../../../src","sources":["components/templates/History/History.tsx"],"names":[],"mappings":";;AAgCA,0BA0CC;;;AA1ED,6CAAqE;AAErE,+EAAuE;AACvE,6CAAwC;AAExC,kDAAiE;AACjE,0CAA4B;AAE5B,yBAAwB;AAExB,MAAM,CAAC,GAAG,IAAA,UAAK,EAAC,SAAS,CAAC,CAAC;AAgB3B;;;;;GAKG;AACH,SAAgB,OAAO,CAAC,KAAmB;IACvC,MAAM,EAAC,IAAI,GAAG,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,GAAG,IAAI,KAAkB,KAAK,EAAlB,SAAS,kBAAI,KAAK,EAAxF,2DAAgF,CAAQ,CAAC;IAC/F,MAAM,QAAQ,GAAG,IAAA,iBAAS,GAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAA,iBAAS,GAAE,CAAC;IAC5B,MAAM,EAAC,cAAc,EAAC,GAAG,IAAA,yCAAmB,EAAC,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,uBAAC,yBAAW,oBAAK,SAAS,IAAE,WAAW,EAAE,eAAe,IAAI,CAAC;IAE1E,IAAI,QAAQ,EAAE,CAAC;QACX,OAAO,CACH,uBAAC,aAAK,IACF,EAAE,EAAE,OAAO,EACX,KAAK,EAAE,IAAA,WAAI,EAAC,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,uBAAC,aAAK,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"]}
@@ -10,3 +10,4 @@ export * from "./useToolset.js";
10
10
  export * from "./useToolResultContinuation.js";
11
11
  export * from "./useMascotState.js";
12
12
  export * from "./useKeyboardViewportFit.js";
13
+ export * from "./useSheetKeyboardFit.js";
@@ -13,4 +13,5 @@ tslib_1.__exportStar(require("./useToolset.js"), exports);
13
13
  tslib_1.__exportStar(require("./useToolResultContinuation.js"), exports);
14
14
  tslib_1.__exportStar(require("./useMascotState.js"), exports);
15
15
  tslib_1.__exportStar(require("./useKeyboardViewportFit.js"), exports);
16
+ tslib_1.__exportStar(require("./useSheetKeyboardFit.js"), exports);
16
17
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"../../../src","sources":["hooks/index.ts"],"names":[],"mappings":";;;AAAA,sEAAmC;AACnC,8DAAiC;AACjC,8DAAiC;AACjC,qEAAwC;AACxC,uEAA0C;AAC1C,wEAA2C;AAC3C,0EAA6C;AAC7C,kEAAqC;AACrC,0DAA6B;AAC7B,yEAA4C;AAC5C,8DAAiC;AACjC,sEAAyC","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,sEAAmC;AACnC,8DAAiC;AACjC,8DAAiC;AACjC,qEAAwC;AACxC,uEAA0C;AAC1C,wEAA2C;AAC3C,0EAA6C;AAC7C,kEAAqC;AACrC,0DAA6B;AAC7B,yEAA4C;AAC5C,8DAAiC;AACjC,sEAAyC;AACzC,mEAAsC","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"]}
@@ -104,6 +104,10 @@ function useKeyboardViewportFit(containerRef, enabled = true, options = {}) {
104
104
  if (!container) {
105
105
  return;
106
106
  }
107
+ if (!container.contains(document.activeElement)) {
108
+ setFit((prev) => (prev.isKeyboardOpen ? CLOSED : prev));
109
+ return;
110
+ }
107
111
  const probeBox = (_a = viewportProbeRef === null || viewportProbeRef === void 0 ? void 0 : viewportProbeRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
108
112
  const next = resolveKeyboardViewportFit({
109
113
  viewportHeight: viewport.height,
@@ -134,12 +138,16 @@ function useKeyboardViewportFit(containerRef, enabled = true, options = {}) {
134
138
  update();
135
139
  viewport.addEventListener('resize', schedule);
136
140
  viewport.addEventListener('scroll', schedule);
141
+ document.addEventListener('focusin', schedule);
142
+ document.addEventListener('focusout', schedule);
137
143
  return () => {
138
144
  if (frame) {
139
145
  cancelAnimationFrame(frame);
140
146
  }
141
147
  viewport.removeEventListener('resize', schedule);
142
148
  viewport.removeEventListener('scroll', schedule);
149
+ document.removeEventListener('focusin', schedule);
150
+ document.removeEventListener('focusout', schedule);
143
151
  };
144
152
  }, [containerRef, enabled, viewportProbeRef]);
145
153
  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":"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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 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,323 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SHEET_VISIBLE_HEIGHT_VAR = exports.SHEET_VISIBLE_BOTTOM_VAR = void 0;
4
+ exports.resolveSheetKeyboardFit = resolveSheetKeyboardFit;
5
+ exports.useSheetKeyboardFit = useSheetKeyboardFit;
6
+ const react_1 = require("react");
7
+ const useKeyboardViewportFit_1 = require("./useKeyboardViewportFit.js");
8
+ exports.SHEET_VISIBLE_BOTTOM_VAR = '--g-aikit-sheet-visible-bottom';
9
+ exports.SHEET_VISIBLE_HEIGHT_VAR = '--g-aikit-sheet-visible-height';
10
+ const SHEET_MARGIN_BOX_SELECTOR = '.g-sheet-content-area__margin-box';
11
+ const KEYBOARD_HEIGHT_STORAGE_PREFIX = 'g-aikit-sheet-keyboard-height';
12
+ const PREDICT_GUARD_MS = 500;
13
+ const SYNC_FALLBACK_MS = 400;
14
+ const HANDOVER_WAIT_MS = 1500;
15
+ const HANDOVER_STABLE_FRAMES = 4;
16
+ const SUPPRESS_CLICK_MS = 600;
17
+ const KEYBOARD_CLOSE_MS = 700;
18
+ const CLOSED = { isKeyboardOpen: false };
19
+ function resolveSheetKeyboardFit(metrics, minInset = useKeyboardViewportFit_1.KEYBOARD_MIN_INSET) {
20
+ const { viewportHeight, viewportOffsetTop, scale, layoutHeight } = metrics;
21
+ if (layoutHeight - viewportHeight * scale < minInset) {
22
+ return CLOSED;
23
+ }
24
+ return {
25
+ isKeyboardOpen: true,
26
+ visibleBottom: Math.max(0, Math.floor(viewportOffsetTop + viewportHeight)),
27
+ visibleHeight: Math.max(0, Math.floor(viewportHeight)),
28
+ };
29
+ }
30
+ const NON_TEXT_INPUT_TYPES = new Set([
31
+ 'button',
32
+ 'checkbox',
33
+ 'color',
34
+ 'file',
35
+ 'hidden',
36
+ 'image',
37
+ 'radio',
38
+ 'range',
39
+ 'reset',
40
+ 'submit',
41
+ ]);
42
+ function getIsTextEntryTarget(target) {
43
+ if (target instanceof HTMLTextAreaElement) {
44
+ return true;
45
+ }
46
+ if (target instanceof HTMLInputElement) {
47
+ return !NON_TEXT_INPUT_TYPES.has(target.type);
48
+ }
49
+ return target instanceof HTMLElement && target.isContentEditable;
50
+ }
51
+ function getKeyboardHeightKey() {
52
+ return `${KEYBOARD_HEIGHT_STORAGE_PREFIX}:${window.innerWidth}x${window.innerHeight}`;
53
+ }
54
+ function readKnownKeyboardHeight() {
55
+ try {
56
+ const value = Number(window.localStorage.getItem(getKeyboardHeightKey()));
57
+ return Number.isFinite(value) && value > useKeyboardViewportFit_1.KEYBOARD_MIN_INSET ? value : 0;
58
+ }
59
+ catch (_a) {
60
+ return 0;
61
+ }
62
+ }
63
+ function writeKnownKeyboardHeight(height) {
64
+ try {
65
+ window.localStorage.setItem(getKeyboardHeightKey(), String(Math.round(height)));
66
+ }
67
+ catch (_a) {
68
+ return;
69
+ }
70
+ }
71
+ function createAnchor() {
72
+ const anchor = document.createElement('input');
73
+ anchor.setAttribute('aria-hidden', 'true');
74
+ anchor.tabIndex = -1;
75
+ 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');
76
+ return anchor;
77
+ }
78
+ function writeVar(name, value) {
79
+ const { style } = document.documentElement;
80
+ if (value === undefined) {
81
+ style.removeProperty(name);
82
+ }
83
+ else {
84
+ style.setProperty(name, `${value}px`);
85
+ }
86
+ }
87
+ /**
88
+ * Fits a bottom sheet above the on-screen keyboard raised from a field inside it.
89
+ *
90
+ * The sheet is refitted on the focus of the field, not on the keyboard resize: the height of the
91
+ * keyboard is remembered per screen size, so the sheet rises together with the keyboard instead of
92
+ * jumping once it has finished sliding in. The first time on a screen there is nothing to predict,
93
+ * so the fit is applied when the visual viewport settles, and the height is remembered for later.
94
+ *
95
+ * Two things move the sheet: the cap on its content, which uikit answers by resizing and
96
+ * translating the window from its `ResizeObserver`, and the height of the root, which sets the
97
+ * bottom edge. Both are animated, so they have to start in the same frame - otherwise the window
98
+ * hangs off the top of the screen for as long as the browser holds the observer back (Safari does
99
+ * that for ~150ms while the keyboard slides in). The root height is therefore written from a
100
+ * `ResizeObserver` on the same box uikit watches, which is delivered in the same frame.
101
+ *
102
+ * A field that sits below the line the keyboard will take (a short, filtered sheet at the bottom of
103
+ * the screen) makes Safari pan the visual viewport to reveal it, and the pan drags every fixed box
104
+ * up with it. To keep the field where Safari can see it, the tap is intercepted: an invisible
105
+ * anchor at the top of the screen takes the focus and raises the keyboard, the sheet rises with it,
106
+ * and once the keyboard has settled the focus is handed over to the field, which is above the
107
+ * keyboard by then.
108
+ *
109
+ * @param enabled - disables tracking (e.g. while the sheet is closed or outside mobile mode)
110
+ * @param sheetSelector - selector of the sheet root, used to find the content box uikit observes
111
+ */
112
+ function useSheetKeyboardFit(enabled = true, sheetSelector) {
113
+ const [fit, setFit] = (0, react_1.useState)(CLOSED);
114
+ (0, react_1.useEffect)(() => {
115
+ const viewport = typeof window === 'undefined' ? undefined : window.visualViewport;
116
+ if (!enabled || !viewport) {
117
+ setFit(CLOSED);
118
+ return undefined;
119
+ }
120
+ let tracking = false;
121
+ let guard = 0;
122
+ let syncFallback = 0;
123
+ let desired = CLOSED;
124
+ let releaseTimer = 0;
125
+ let anchor = null;
126
+ let handoverTimer = 0;
127
+ let handoverFrame = 0;
128
+ let suppressClicksUntil = 0;
129
+ const getIsKeyboardShown = () => window.innerHeight - viewport.height >= useKeyboardViewportFit_1.KEYBOARD_MIN_INSET;
130
+ const removeAnchor = () => {
131
+ window.clearTimeout(handoverTimer);
132
+ window.cancelAnimationFrame(handoverFrame);
133
+ handoverTimer = 0;
134
+ handoverFrame = 0;
135
+ anchor === null || anchor === void 0 ? void 0 : anchor.remove();
136
+ anchor = null;
137
+ };
138
+ const writeBottom = () => {
139
+ window.clearTimeout(syncFallback);
140
+ syncFallback = 0;
141
+ writeVar(exports.SHEET_VISIBLE_BOTTOM_VAR, desired.visibleBottom);
142
+ };
143
+ const box = typeof ResizeObserver === 'undefined' || !sheetSelector
144
+ ? null
145
+ : document.querySelector(`${sheetSelector} ${SHEET_MARGIN_BOX_SELECTOR}`);
146
+ let observer = null;
147
+ if (box) {
148
+ observer = new ResizeObserver(writeBottom);
149
+ observer.observe(box);
150
+ }
151
+ const apply = (next) => {
152
+ const isCapChanging = next.isKeyboardOpen !== desired.isKeyboardOpen ||
153
+ next.visibleHeight !== desired.visibleHeight;
154
+ desired = next;
155
+ writeVar(exports.SHEET_VISIBLE_HEIGHT_VAR, next.visibleHeight);
156
+ setFit((prev) => prev.isKeyboardOpen === next.isKeyboardOpen &&
157
+ prev.visibleBottom === next.visibleBottom &&
158
+ prev.visibleHeight === next.visibleHeight
159
+ ? prev
160
+ : next);
161
+ if (!observer || !isCapChanging) {
162
+ writeBottom();
163
+ return;
164
+ }
165
+ window.clearTimeout(syncFallback);
166
+ syncFallback = window.setTimeout(writeBottom, SYNC_FALLBACK_MS);
167
+ };
168
+ const measure = () => {
169
+ const next = resolveSheetKeyboardFit({
170
+ viewportHeight: viewport.height,
171
+ viewportOffsetTop: viewport.offsetTop,
172
+ scale: viewport.scale,
173
+ layoutHeight: window.innerHeight,
174
+ });
175
+ if (next.isKeyboardOpen) {
176
+ // The tallest shrink is the keyboard; smaller readings come from its opening and
177
+ // closing animation and must not overwrite the remembered height with a stray value.
178
+ const measured = window.innerHeight - viewport.height;
179
+ if (measured > readKnownKeyboardHeight()) {
180
+ writeKnownKeyboardHeight(measured);
181
+ }
182
+ }
183
+ apply(next);
184
+ if (!tracking && !next.isKeyboardOpen) {
185
+ window.clearTimeout(releaseTimer);
186
+ releaseTimer = 0;
187
+ viewport.removeEventListener('resize', measure);
188
+ viewport.removeEventListener('scroll', measure);
189
+ }
190
+ };
191
+ const release = () => {
192
+ releaseTimer = 0;
193
+ viewport.removeEventListener('resize', measure);
194
+ viewport.removeEventListener('scroll', measure);
195
+ apply(CLOSED);
196
+ };
197
+ const onFocusIn = (event) => {
198
+ if (tracking || !getIsTextEntryTarget(event.target)) {
199
+ return;
200
+ }
201
+ tracking = true;
202
+ viewport.addEventListener('resize', measure);
203
+ viewport.addEventListener('scroll', measure);
204
+ const known = readKnownKeyboardHeight();
205
+ if (!known) {
206
+ return;
207
+ }
208
+ // The keyboard has not slid in yet; place the sheet where it will end up so it rises
209
+ // with the keyboard, and undo the guess if the keyboard never shows (hardware keyboard).
210
+ const height = Math.max(0, window.innerHeight - known);
211
+ apply({ isKeyboardOpen: true, visibleBottom: height, visibleHeight: height });
212
+ window.clearTimeout(guard);
213
+ guard = window.setTimeout(() => {
214
+ if (window.innerHeight - viewport.height < useKeyboardViewportFit_1.KEYBOARD_MIN_INSET) {
215
+ apply(CLOSED);
216
+ }
217
+ }, PREDICT_GUARD_MS);
218
+ };
219
+ const onFocusOut = (event) => {
220
+ if (!tracking || getIsTextEntryTarget(event.relatedTarget)) {
221
+ return;
222
+ }
223
+ tracking = false;
224
+ window.clearTimeout(guard);
225
+ // The keyboard is still on screen and the visual viewport is still short; undoing the
226
+ // fit now rebuilds the sheet against a viewport that is about to change again. The
227
+ // listeners stay on until `measure` sees the viewport back at full height, with a
228
+ // timer for the cases that never send a resize (a hardware keyboard).
229
+ window.clearTimeout(releaseTimer);
230
+ releaseTimer = window.setTimeout(release, KEYBOARD_CLOSE_MS);
231
+ };
232
+ const handOver = (field) => {
233
+ removeAnchor();
234
+ field.focus({ preventScroll: true });
235
+ if (field instanceof HTMLInputElement || field instanceof HTMLTextAreaElement) {
236
+ const { length } = field.value;
237
+ field.setSelectionRange(length, length);
238
+ }
239
+ suppressClicksUntil = performance.now() + SUPPRESS_CLICK_MS;
240
+ };
241
+ const onPointerDown = (event) => {
242
+ const target = event.target;
243
+ if (!(target instanceof Element) || !sheetSelector || !target.closest(sheetSelector)) {
244
+ return;
245
+ }
246
+ const field = target.closest('input, textarea, [contenteditable]');
247
+ if (!(field instanceof HTMLElement) || !getIsTextEntryTarget(field)) {
248
+ return;
249
+ }
250
+ const known = readKnownKeyboardHeight();
251
+ if (!known || getIsKeyboardShown() || anchor) {
252
+ return;
253
+ }
254
+ if (field.getBoundingClientRect().bottom <= window.innerHeight - known) {
255
+ return;
256
+ }
257
+ event.preventDefault();
258
+ event.stopImmediatePropagation();
259
+ suppressClicksUntil = Number.POSITIVE_INFINITY;
260
+ anchor = createAnchor();
261
+ document.body.appendChild(anchor);
262
+ anchor.focus({ preventScroll: true });
263
+ let stableFrames = 0;
264
+ let lastHeight = 0;
265
+ const settle = () => {
266
+ if (!anchor) {
267
+ return;
268
+ }
269
+ const isSteady = viewport.height === lastHeight;
270
+ lastHeight = viewport.height;
271
+ const isVisible = field.getBoundingClientRect().bottom <= viewport.height + 1;
272
+ stableFrames = isSteady && isVisible ? stableFrames + 1 : 0;
273
+ if (stableFrames >= HANDOVER_STABLE_FRAMES) {
274
+ handOver(field);
275
+ return;
276
+ }
277
+ handoverFrame = window.requestAnimationFrame(settle);
278
+ };
279
+ const onKeyboardShown = () => {
280
+ if (!getIsKeyboardShown()) {
281
+ return;
282
+ }
283
+ viewport.removeEventListener('resize', onKeyboardShown);
284
+ settle();
285
+ };
286
+ viewport.addEventListener('resize', onKeyboardShown);
287
+ handoverTimer = window.setTimeout(() => {
288
+ viewport.removeEventListener('resize', onKeyboardShown);
289
+ handOver(field);
290
+ }, HANDOVER_WAIT_MS);
291
+ };
292
+ const onSyntheticClick = (event) => {
293
+ if (performance.now() > suppressClicksUntil) {
294
+ return;
295
+ }
296
+ event.preventDefault();
297
+ event.stopImmediatePropagation();
298
+ };
299
+ document.addEventListener('focusin', onFocusIn, true);
300
+ document.addEventListener('focusout', onFocusOut, true);
301
+ document.addEventListener('pointerdown', onPointerDown, true);
302
+ document.addEventListener('mousedown', onSyntheticClick, true);
303
+ document.addEventListener('click', onSyntheticClick, true);
304
+ return () => {
305
+ removeAnchor();
306
+ document.removeEventListener('pointerdown', onPointerDown, true);
307
+ document.removeEventListener('mousedown', onSyntheticClick, true);
308
+ document.removeEventListener('click', onSyntheticClick, true);
309
+ window.clearTimeout(guard);
310
+ window.clearTimeout(syncFallback);
311
+ window.clearTimeout(releaseTimer);
312
+ observer === null || observer === void 0 ? void 0 : observer.disconnect();
313
+ document.removeEventListener('focusin', onFocusIn, true);
314
+ document.removeEventListener('focusout', onFocusOut, true);
315
+ viewport.removeEventListener('resize', measure);
316
+ viewport.removeEventListener('scroll', measure);
317
+ writeVar(exports.SHEET_VISIBLE_BOTTOM_VAR, undefined);
318
+ writeVar(exports.SHEET_VISIBLE_HEIGHT_VAR, undefined);
319
+ };
320
+ }, [enabled, sheetSelector]);
321
+ return fit;
322
+ }
323
+ //# 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"]}
@@ -1 +1 @@
1
- {"version":"2.21.1","type":"commonjs","sideEffects":["*.css","*.scss"]}
1
+ {"version":"2.21.2","type":"commonjs","sideEffects":["*.css","*.scss"]}
@@ -1183,6 +1183,10 @@ slide in are animated with exactly those. The container does have to
1183
1183
  be anchored to the top of the viewport: a bottom-anchored one moves its own top as soon as the
1184
1184
  returned limit shrinks it.
1185
1185
 
1186
+ Only a keyboard raised from inside the chat counts. A field that lives above the chat - the
1187
+ search input of the history sheet, an input in a dialog of the host application - raises the same
1188
+ keyboard, and the chat keeps its layout instead of reflowing behind that overlay.
1189
+
1186
1190
  Set `adjustToKeyboard={false}` when the host application already handles the keyboard - for
1187
1191
  example with `interactive-widget=resizes-content` in the viewport meta tag, which shrinks the
1188
1192
  layout viewport itself. Such pages are left alone anyway: nothing of the layout viewport stays