blixify-ui-web 1.2.168 → 1.2.169
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/lib/components/chat/chatAnchor/index.d.ts +16 -0
- package/lib/components/chat/chatAnchor/index.d.ts.map +1 -1
- package/lib/components/chat/chatAnchor/index.js +269 -57
- package/lib/components/chat/chatAnchor/index.js.map +1 -1
- package/lib/components/chat/chatBase/index.d.ts.map +1 -1
- package/lib/components/chat/chatBase/index.js +9 -5
- package/lib/components/chat/chatBase/index.js.map +1 -1
- package/lib/tail.css +1 -1
- package/package.json +1 -1
|
@@ -15,6 +15,7 @@ interface Props extends ChatProps {
|
|
|
15
15
|
resizableHeight?: boolean;
|
|
16
16
|
defaultHeight?: number;
|
|
17
17
|
heightStorageKey?: string;
|
|
18
|
+
keyboardOpen?: boolean;
|
|
18
19
|
}
|
|
19
20
|
interface AnchorPosition {
|
|
20
21
|
right: number;
|
|
@@ -28,7 +29,15 @@ export declare class ChatAnchor extends Component<Props> {
|
|
|
28
29
|
position: AnchorPosition | null;
|
|
29
30
|
width: number | null;
|
|
30
31
|
height: number | null;
|
|
32
|
+
keyboardVisible: boolean;
|
|
33
|
+
viewportHeight: number | null;
|
|
34
|
+
viewportTop: number;
|
|
35
|
+
viewportBottom: number;
|
|
31
36
|
};
|
|
37
|
+
keyboardClosedHeight: number;
|
|
38
|
+
viewportWidth: number;
|
|
39
|
+
visualViewport: VisualViewport | null;
|
|
40
|
+
viewportTrackingActive: boolean;
|
|
32
41
|
anchorRef: React.RefObject<HTMLDivElement | null>;
|
|
33
42
|
buttonRef: React.RefObject<HTMLDivElement | null>;
|
|
34
43
|
panelRef: React.RefObject<HTMLDivElement | null>;
|
|
@@ -63,6 +72,7 @@ export declare class ChatAnchor extends Component<Props> {
|
|
|
63
72
|
handleMaxWidth: () => number;
|
|
64
73
|
handleMaxHeight: () => number;
|
|
65
74
|
handleClampWidth: (width: number) => number;
|
|
75
|
+
handleMinHeight: () => number;
|
|
66
76
|
handleClampHeight: (height: number) => number;
|
|
67
77
|
handleIsResizable: () => boolean;
|
|
68
78
|
handleIsResizableHeight: () => boolean;
|
|
@@ -83,15 +93,21 @@ export declare class ChatAnchor extends Component<Props> {
|
|
|
83
93
|
handleRemoveResizeListeners: () => void;
|
|
84
94
|
handleResizeVertStart: (event: React.PointerEvent<HTMLDivElement>) => void;
|
|
85
95
|
handleResizeVertMove: (event: PointerEvent) => void;
|
|
96
|
+
handleResizeHeightKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;
|
|
86
97
|
handleResizeVertEnd: () => void;
|
|
87
98
|
handleRemoveResizeVertListeners: () => void;
|
|
88
99
|
handleAnchorClick: () => void;
|
|
89
100
|
handleIsDraggable: () => boolean;
|
|
90
101
|
componentDidUpdate: (prevProps: Props, prevState: {
|
|
91
102
|
chatCard: boolean;
|
|
103
|
+
isMobileWidth: boolean;
|
|
92
104
|
}) => void;
|
|
93
105
|
handleReclampPosition: () => void;
|
|
94
106
|
handleResize: () => void;
|
|
107
|
+
handleRemoveViewportListeners: () => void;
|
|
108
|
+
handleSyncViewportListeners: () => void;
|
|
109
|
+
handleChatFocusChange: (event: FocusEvent) => void;
|
|
110
|
+
handleViewportChange: () => void;
|
|
95
111
|
renderChatAnchorIcon: () => React.JSX.Element;
|
|
96
112
|
render(): React.JSX.Element;
|
|
97
113
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/chat/chatAnchor/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAY,MAAM,OAAO,CAAC;AACnD,OAAO,EAAY,KAAK,IAAI,SAAS,EAAE,MAAM,aAAa,CAAC;AAE3D,UAAU,KAAM,SAAQ,SAAS;IAC/B,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;IACpC,SAAS,CAAC,EAAE,KAAK,CAAC,SAAS,GAAG,MAAM,CAAC;IACrC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/chat/chatAnchor/index.tsx"],"names":[],"mappings":"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"}
|
|
@@ -82,10 +82,14 @@ var WIDTH_VIEWPORT_GUTTER = 16;
|
|
|
82
82
|
// gutter. The floor is taller than the width floor because a chat still needs
|
|
83
83
|
// its header and input row to stay usable.
|
|
84
84
|
var MIN_USABLE_HEIGHT = 120;
|
|
85
|
+
var MIN_MOBILE_HEIGHT_VIEWPORT_RATIO = 0.5;
|
|
85
86
|
var HEIGHT_VIEWPORT_GUTTER = 16;
|
|
86
87
|
// INFO: a drag shorter than this is a click — a tap on a touch screen always
|
|
87
88
|
// travels a pixel or two, and it must still open the chat
|
|
88
89
|
var DRAG_THRESHOLD = 4;
|
|
90
|
+
var KEYBOARD_VIEWPORT_THRESHOLD = 100;
|
|
91
|
+
var SAFE_AREA_TOP = "var(--safe-area-inset-top, env(safe-area-inset-top, 0px))";
|
|
92
|
+
var SAFE_AREA_BOTTOM = "var(--safe-area-inset-bottom, env(safe-area-inset-bottom, 0px))";
|
|
89
93
|
var ChatAnchor = /** @class */ (function (_super) {
|
|
90
94
|
__extends(ChatAnchor, _super);
|
|
91
95
|
function ChatAnchor() {
|
|
@@ -96,7 +100,15 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
96
100
|
position: null,
|
|
97
101
|
width: null,
|
|
98
102
|
height: null,
|
|
99
|
-
|
|
103
|
+
keyboardVisible: false,
|
|
104
|
+
viewportHeight: null,
|
|
105
|
+
viewportTop: 0,
|
|
106
|
+
viewportBottom: 0,
|
|
107
|
+
};
|
|
108
|
+
_this.keyboardClosedHeight = 0;
|
|
109
|
+
_this.viewportWidth = 0;
|
|
110
|
+
_this.visualViewport = null;
|
|
111
|
+
_this.viewportTrackingActive = false;
|
|
100
112
|
_this.anchorRef = react_2.default.createRef();
|
|
101
113
|
_this.buttonRef = react_2.default.createRef();
|
|
102
114
|
// INFO: the panel's own box, not the anchor's — the anchor also holds the
|
|
@@ -125,7 +137,10 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
125
137
|
return _this.props.defaultHeight;
|
|
126
138
|
if (typeof window === "undefined")
|
|
127
139
|
return DEFAULT_HEIGHT_FALLBACK;
|
|
128
|
-
|
|
140
|
+
var viewportHeight = window.innerWidth < 640
|
|
141
|
+
? _this.keyboardClosedHeight || window.innerHeight
|
|
142
|
+
: window.innerHeight;
|
|
143
|
+
return Math.round(viewportHeight * DEFAULT_HEIGHT_VIEWPORT_RATIO);
|
|
129
144
|
};
|
|
130
145
|
// INFO: the panel grows leftward from the bottom-right anchor, so the widest
|
|
131
146
|
// it can be without clipping off the left edge is the distance from its own
|
|
@@ -152,6 +167,10 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
152
167
|
var _a, _b;
|
|
153
168
|
if (typeof window === "undefined")
|
|
154
169
|
return Number.POSITIVE_INFINITY;
|
|
170
|
+
// INFO: mobile height resizing is pinned to the viewport bottom, independent
|
|
171
|
+
// of the floating button's saved position. It can grow to the full screen.
|
|
172
|
+
if (window.innerWidth < 640 && _this.props.resizableHeight)
|
|
173
|
+
return _this.keyboardClosedHeight || window.innerHeight;
|
|
155
174
|
var rect = (_b = ((_a = _this.panelRef.current) !== null && _a !== void 0 ? _a : _this.anchorRef.current)) === null || _b === void 0 ? void 0 : _b.getBoundingClientRect();
|
|
156
175
|
var bottomEdge = rect ? rect.bottom : window.innerHeight;
|
|
157
176
|
return bottomEdge - HEIGHT_VIEWPORT_GUTTER;
|
|
@@ -167,16 +186,26 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
167
186
|
var cap = Math.max(MIN_USABLE_WIDTH, _this.handleMaxWidth());
|
|
168
187
|
return Math.min(Math.max(MIN_USABLE_WIDTH, width), cap);
|
|
169
188
|
};
|
|
189
|
+
_this.handleMinHeight = function () {
|
|
190
|
+
return typeof window !== "undefined" && window.innerWidth < 640
|
|
191
|
+
? Math.round((_this.keyboardClosedHeight || window.innerHeight) *
|
|
192
|
+
MIN_MOBILE_HEIGHT_VIEWPORT_RATIO)
|
|
193
|
+
: MIN_USABLE_HEIGHT;
|
|
194
|
+
};
|
|
170
195
|
_this.handleClampHeight = function (height) {
|
|
171
196
|
// INFO: a bad pointer event (no clientY) can produce NaN — never let it
|
|
172
197
|
// reach state or localStorage; fall back to the default height instead
|
|
173
198
|
if (Number.isNaN(height))
|
|
174
199
|
return _this.handleDefaultHeight();
|
|
175
200
|
// INFO: floor keeps the header and input usable, cap keeps the panel on
|
|
176
|
-
// screen.
|
|
177
|
-
//
|
|
178
|
-
var
|
|
179
|
-
|
|
201
|
+
// screen. Mobile stays between half and all of the current viewport;
|
|
202
|
+
// desktop keeps its existing pixel floor.
|
|
203
|
+
var isMobile = typeof window !== "undefined" && window.innerWidth < 640;
|
|
204
|
+
var minimum = _this.handleMinHeight();
|
|
205
|
+
var cap = isMobile
|
|
206
|
+
? _this.handleMaxHeight()
|
|
207
|
+
: Math.max(minimum, _this.handleMaxHeight());
|
|
208
|
+
return Math.min(Math.max(minimum, height), cap);
|
|
180
209
|
};
|
|
181
210
|
_this.handleIsResizable = function () {
|
|
182
211
|
return _this.props.resizableWidth === true &&
|
|
@@ -184,7 +213,7 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
184
213
|
};
|
|
185
214
|
_this.handleIsResizableHeight = function () {
|
|
186
215
|
return _this.props.resizableHeight === true &&
|
|
187
|
-
!(_this.
|
|
216
|
+
!(_this.state.isMobileWidth && _this.state.keyboardVisible);
|
|
188
217
|
};
|
|
189
218
|
// INFO: keeps the anchor on screen when a position saved on a wide window is
|
|
190
219
|
// restored on a narrow one, when the window is resized under it, and when the
|
|
@@ -292,30 +321,26 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
292
321
|
_this.handleRestoreHeight = function () {
|
|
293
322
|
if (!_this.props.resizableHeight)
|
|
294
323
|
return;
|
|
324
|
+
// INFO: defer the default if mounted while a native keyboard is already
|
|
325
|
+
// visible; the closed viewport is not known until it is dismissed.
|
|
326
|
+
var height = _this.props.keyboardOpen && window.innerWidth < 640
|
|
327
|
+
? null
|
|
328
|
+
: _this.handleDefaultHeight();
|
|
295
329
|
try {
|
|
296
330
|
var stored = window.localStorage.getItem(_this.handleHeightStorageKey());
|
|
297
|
-
if (
|
|
298
|
-
|
|
299
|
-
// consumer's chosen height rather than the sizeClassName fallback
|
|
300
|
-
_this.setState({
|
|
301
|
-
height: _this.handleClampHeight(_this.handleDefaultHeight()),
|
|
302
|
-
});
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
var parsed = Number(stored);
|
|
306
|
-
if (Number.isNaN(parsed)) {
|
|
307
|
-
// INFO: a corrupted value falls back to the default height, not nothing
|
|
308
|
-
_this.setState({
|
|
309
|
-
height: _this.handleClampHeight(_this.handleDefaultHeight()),
|
|
310
|
-
});
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
_this.setState({ height: _this.handleClampHeight(parsed) });
|
|
331
|
+
if (stored && Number.isFinite(Number(stored)))
|
|
332
|
+
height = Number(stored);
|
|
314
333
|
}
|
|
315
334
|
catch (_a) {
|
|
316
|
-
// INFO:
|
|
317
|
-
// panel just keeps its default height
|
|
335
|
+
// INFO: blocked storage still uses the default height.
|
|
318
336
|
}
|
|
337
|
+
// INFO: a mobile mount can happen with the keyboard already open. Preserve
|
|
338
|
+
// the stored preference and apply viewport limits only when rendering it.
|
|
339
|
+
_this.setState({
|
|
340
|
+
height: height === null || window.innerWidth < 640
|
|
341
|
+
? height
|
|
342
|
+
: _this.handleClampHeight(height),
|
|
343
|
+
});
|
|
319
344
|
};
|
|
320
345
|
_this.handlePersistHeight = function (height) {
|
|
321
346
|
try {
|
|
@@ -443,7 +468,7 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
443
468
|
return;
|
|
444
469
|
// INFO: height is seeded on mount whenever resize is on, so state.height is
|
|
445
470
|
// the panel's current height — the drag continues from what the user sees
|
|
446
|
-
var startHeight = (_a = _this.state.height) !== null && _a !== void 0 ? _a : _this.handleDefaultHeight();
|
|
471
|
+
var startHeight = _this.handleClampHeight((_a = _this.state.height) !== null && _a !== void 0 ? _a : _this.handleDefaultHeight());
|
|
447
472
|
event.preventDefault();
|
|
448
473
|
event.stopPropagation();
|
|
449
474
|
_this.resizeVertRef = {
|
|
@@ -458,7 +483,7 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
458
483
|
};
|
|
459
484
|
_this.handleResizeVertMove = function (event) {
|
|
460
485
|
var resize = _this.resizeVertRef;
|
|
461
|
-
if (!resize)
|
|
486
|
+
if (!resize || !_this.handleIsResizableHeight())
|
|
462
487
|
return;
|
|
463
488
|
var deltaY = event.clientY - resize.pointerY;
|
|
464
489
|
if (!resize.moved && Math.abs(deltaY) < DRAG_THRESHOLD)
|
|
@@ -469,11 +494,23 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
469
494
|
resize.latest = _this.handleClampHeight(resize.startHeight - deltaY);
|
|
470
495
|
_this.setState({ height: resize.latest });
|
|
471
496
|
};
|
|
497
|
+
_this.handleResizeHeightKeyDown = function (event) {
|
|
498
|
+
var _a;
|
|
499
|
+
if (!_this.handleIsResizableHeight())
|
|
500
|
+
return;
|
|
501
|
+
if (event.key !== "ArrowUp" && event.key !== "ArrowDown")
|
|
502
|
+
return;
|
|
503
|
+
event.preventDefault();
|
|
504
|
+
var height = _this.handleClampHeight(((_a = _this.state.height) !== null && _a !== void 0 ? _a : _this.handleDefaultHeight()) +
|
|
505
|
+
(event.key === "ArrowUp" ? 24 : -24));
|
|
506
|
+
_this.setState({ height: height });
|
|
507
|
+
_this.handlePersistHeight(height);
|
|
508
|
+
};
|
|
472
509
|
_this.handleResizeVertEnd = function () {
|
|
473
510
|
var resize = _this.resizeVertRef;
|
|
474
511
|
_this.resizeVertRef = null;
|
|
475
512
|
_this.handleRemoveResizeVertListeners();
|
|
476
|
-
if (!(resize === null || resize === void 0 ? void 0 : resize.moved))
|
|
513
|
+
if (!(resize === null || resize === void 0 ? void 0 : resize.moved) || !_this.handleIsResizableHeight())
|
|
477
514
|
return;
|
|
478
515
|
if (resize.latest != null)
|
|
479
516
|
_this.handlePersistHeight(resize.latest);
|
|
@@ -490,18 +527,33 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
490
527
|
}
|
|
491
528
|
_this.props.onClick();
|
|
492
529
|
};
|
|
493
|
-
// INFO: no dragging while the panel is
|
|
530
|
+
// INFO: no button dragging while the panel is open on mobile — there is no
|
|
494
531
|
// free-floating button to move at that point
|
|
495
532
|
_this.handleIsDraggable = function () {
|
|
496
533
|
return _this.props.draggable !== false &&
|
|
497
534
|
!(_this.props.open && _this.state.isMobileWidth);
|
|
498
535
|
};
|
|
499
536
|
_this.componentDidUpdate = function (prevProps, prevState) {
|
|
537
|
+
var _a;
|
|
500
538
|
if (_this.props.open !== prevProps.open && _this.props.open) {
|
|
501
539
|
_this.setState({
|
|
502
540
|
chatCard: false,
|
|
503
541
|
});
|
|
504
542
|
}
|
|
543
|
+
if (_this.props.open !== prevProps.open ||
|
|
544
|
+
_this.props.keyboardOpen !== prevProps.keyboardOpen ||
|
|
545
|
+
_this.state.isMobileWidth !== prevState.isMobileWidth) {
|
|
546
|
+
_this.handleSyncViewportListeners();
|
|
547
|
+
}
|
|
548
|
+
if (_this.props.resizableHeight &&
|
|
549
|
+
!_this.state.isMobileWidth &&
|
|
550
|
+
(_this.state.isMobileWidth !== prevState.isMobileWidth ||
|
|
551
|
+
_this.props.open !== prevProps.open ||
|
|
552
|
+
_this.props.resizableHeight !== prevProps.resizableHeight)) {
|
|
553
|
+
var height = _this.handleClampHeight((_a = _this.state.height) !== null && _a !== void 0 ? _a : _this.handleDefaultHeight());
|
|
554
|
+
if (height !== _this.state.height)
|
|
555
|
+
_this.setState({ height: height });
|
|
556
|
+
}
|
|
505
557
|
// INFO: opening, closing or dismissing the greeting changes the anchor's
|
|
506
558
|
// size, so a position that fit the old size can leave the new one off
|
|
507
559
|
// screen — re-clamp it against the layout that was just committed
|
|
@@ -520,17 +572,137 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
520
572
|
// INFO: kept too, or the next visit restores the off-screen position
|
|
521
573
|
_this.handlePersistPosition(next);
|
|
522
574
|
};
|
|
575
|
+
// INFO: only real window resizes need floating-position and size measurements.
|
|
523
576
|
_this.handleResize = function () {
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
577
|
+
var _a, _b;
|
|
578
|
+
var isMobileWidth = window.innerWidth < 640;
|
|
579
|
+
if (!_this.props.open || !isMobileWidth) {
|
|
580
|
+
_this.keyboardClosedHeight = Math.min(window.innerHeight, (_b = (_a = window.visualViewport) === null || _a === void 0 ? void 0 : _a.height) !== null && _b !== void 0 ? _b : window.innerHeight);
|
|
581
|
+
_this.viewportWidth = window.innerWidth;
|
|
582
|
+
}
|
|
583
|
+
_this.setState(function (previous) {
|
|
584
|
+
var _a, _b;
|
|
585
|
+
var position = previous.position
|
|
527
586
|
? _this.handleClampPosition(previous.position)
|
|
528
|
-
: null
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
587
|
+
: null;
|
|
588
|
+
var width = previous.width ? _this.handleClampWidth(previous.width) : null;
|
|
589
|
+
var height = !isMobileWidth && previous.height
|
|
590
|
+
? _this.handleClampHeight(previous.height)
|
|
591
|
+
: previous.height;
|
|
592
|
+
if (previous.isMobileWidth === isMobileWidth &&
|
|
593
|
+
((_a = previous.position) === null || _a === void 0 ? void 0 : _a.right) === (position === null || position === void 0 ? void 0 : position.right) &&
|
|
594
|
+
((_b = previous.position) === null || _b === void 0 ? void 0 : _b.bottom) === (position === null || position === void 0 ? void 0 : position.bottom) &&
|
|
595
|
+
previous.width === width &&
|
|
596
|
+
previous.height === height)
|
|
597
|
+
return null;
|
|
598
|
+
return { isMobileWidth: isMobileWidth, position: position, width: width, height: height };
|
|
599
|
+
});
|
|
600
|
+
_this.handleSyncViewportListeners();
|
|
601
|
+
};
|
|
602
|
+
_this.handleRemoveViewportListeners = function () {
|
|
603
|
+
var _a, _b;
|
|
604
|
+
if (!_this.viewportTrackingActive)
|
|
605
|
+
return;
|
|
606
|
+
(_a = _this.visualViewport) === null || _a === void 0 ? void 0 : _a.removeEventListener("resize", _this.handleViewportChange);
|
|
607
|
+
(_b = _this.visualViewport) === null || _b === void 0 ? void 0 : _b.removeEventListener("scroll", _this.handleViewportChange);
|
|
608
|
+
document.removeEventListener("focusin", _this.handleChatFocusChange);
|
|
609
|
+
document.removeEventListener("focusout", _this.handleChatFocusChange);
|
|
610
|
+
_this.visualViewport = null;
|
|
611
|
+
_this.viewportTrackingActive = false;
|
|
612
|
+
};
|
|
613
|
+
_this.handleSyncViewportListeners = function () {
|
|
614
|
+
var _a, _b;
|
|
615
|
+
// INFO: fullscreen and resizable mobile panels both need keyboard tracking.
|
|
616
|
+
var shouldTrack = _this.props.open && window.innerWidth < 640;
|
|
617
|
+
if (shouldTrack && !_this.viewportTrackingActive) {
|
|
618
|
+
_this.viewportTrackingActive = true;
|
|
619
|
+
_this.visualViewport = window.visualViewport;
|
|
620
|
+
(_a = _this.visualViewport) === null || _a === void 0 ? void 0 : _a.addEventListener("resize", _this.handleViewportChange);
|
|
621
|
+
(_b = _this.visualViewport) === null || _b === void 0 ? void 0 : _b.addEventListener("scroll", _this.handleViewportChange);
|
|
622
|
+
document.addEventListener("focusin", _this.handleChatFocusChange);
|
|
623
|
+
document.addEventListener("focusout", _this.handleChatFocusChange);
|
|
624
|
+
}
|
|
625
|
+
else if (!shouldTrack && _this.viewportTrackingActive) {
|
|
626
|
+
_this.handleRemoveViewportListeners();
|
|
627
|
+
_this.setState({
|
|
628
|
+
keyboardVisible: false,
|
|
629
|
+
viewportHeight: null,
|
|
630
|
+
viewportTop: 0,
|
|
631
|
+
viewportBottom: 0,
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
if (shouldTrack)
|
|
635
|
+
_this.handleViewportChange();
|
|
636
|
+
};
|
|
637
|
+
_this.handleChatFocusChange = function (event) {
|
|
638
|
+
var _a;
|
|
639
|
+
// INFO: an input elsewhere on the page must not trigger chat layout work.
|
|
640
|
+
// Focusout from this chat still runs so keyboard dismissal remains latched.
|
|
641
|
+
if (event.target instanceof Node &&
|
|
642
|
+
((_a = _this.anchorRef.current) === null || _a === void 0 ? void 0 : _a.contains(event.target)))
|
|
643
|
+
_this.handleViewportChange();
|
|
644
|
+
};
|
|
645
|
+
_this.handleViewportChange = function () {
|
|
646
|
+
var _a, _b, _c, _d, _e;
|
|
647
|
+
if (!_this.viewportTrackingActive || !_this.props.open || window.innerWidth >= 640)
|
|
648
|
+
return;
|
|
649
|
+
var viewport = window.visualViewport;
|
|
650
|
+
// INFO: pinch zoom changes the visual viewport too; it is not a keyboard.
|
|
651
|
+
if (viewport && Math.abs(viewport.scale - 1) > 0.01)
|
|
652
|
+
return;
|
|
653
|
+
var isMobileWidth = window.innerWidth < 640;
|
|
654
|
+
var viewportHeight = Math.min(window.innerHeight, (_a = viewport === null || viewport === void 0 ? void 0 : viewport.height) !== null && _a !== void 0 ? _a : window.innerHeight);
|
|
655
|
+
var viewportTop = Math.max(0, (_b = viewport === null || viewport === void 0 ? void 0 : viewport.offsetTop) !== null && _b !== void 0 ? _b : 0);
|
|
656
|
+
var viewportBottom = Math.max(0, window.innerHeight - viewportHeight - viewportTop);
|
|
657
|
+
if (!_this.keyboardClosedHeight || _this.viewportWidth !== window.innerWidth) {
|
|
658
|
+
_this.keyboardClosedHeight = viewportHeight;
|
|
659
|
+
_this.viewportWidth = window.innerWidth;
|
|
660
|
+
}
|
|
661
|
+
var active = document.activeElement;
|
|
662
|
+
var inputFocused = active instanceof HTMLElement &&
|
|
663
|
+
((_c = _this.anchorRef.current) === null || _c === void 0 ? void 0 : _c.contains(active)) &&
|
|
664
|
+
(active.matches("input:not([readonly]):not([disabled]), textarea:not([readonly]):not([disabled])") || active.isContentEditable);
|
|
665
|
+
// INFO: latch detection while the viewport stays reduced, including the
|
|
666
|
+
// interval after blur but before the keyboard's closing animation finishes.
|
|
667
|
+
var detectedKeyboard = isMobileWidth &&
|
|
668
|
+
(inputFocused || _this.state.keyboardVisible) &&
|
|
669
|
+
_this.keyboardClosedHeight - viewportHeight >
|
|
670
|
+
Math.max(KEYBOARD_VIEWPORT_THRESHOLD, _this.keyboardClosedHeight * 0.15);
|
|
671
|
+
var keyboardVisible = isMobileWidth && ((_d = _this.props.keyboardOpen) !== null && _d !== void 0 ? _d : !!detectedKeyboard);
|
|
672
|
+
if (!keyboardVisible)
|
|
673
|
+
_this.keyboardClosedHeight = viewportHeight;
|
|
674
|
+
// INFO: a keyboard interrupting a drag must not save a temporary height.
|
|
675
|
+
var interruptedHeight = keyboardVisible
|
|
676
|
+
? (_e = _this.resizeVertRef) === null || _e === void 0 ? void 0 : _e.startHeight
|
|
677
|
+
: undefined;
|
|
678
|
+
if (keyboardVisible) {
|
|
679
|
+
_this.resizeVertRef = null;
|
|
680
|
+
_this.handleRemoveResizeVertListeners();
|
|
681
|
+
}
|
|
682
|
+
// INFO: focus and viewport scroll can repeat identical metrics. Avoid both
|
|
683
|
+
// geometry measurements and state updates when nothing actually changed.
|
|
684
|
+
if (_this.state.keyboardVisible === keyboardVisible &&
|
|
685
|
+
_this.state.viewportHeight === viewportHeight &&
|
|
686
|
+
_this.state.viewportTop === viewportTop &&
|
|
687
|
+
_this.state.viewportBottom === viewportBottom &&
|
|
688
|
+
interruptedHeight === undefined)
|
|
689
|
+
return;
|
|
690
|
+
_this.setState(function (previous) {
|
|
691
|
+
var height = interruptedHeight !== null && interruptedHeight !== void 0 ? interruptedHeight : previous.height;
|
|
692
|
+
if (previous.keyboardVisible === keyboardVisible &&
|
|
693
|
+
previous.viewportHeight === viewportHeight &&
|
|
694
|
+
previous.viewportTop === viewportTop &&
|
|
695
|
+
previous.viewportBottom === viewportBottom &&
|
|
696
|
+
previous.height === height)
|
|
697
|
+
return null;
|
|
698
|
+
return {
|
|
699
|
+
keyboardVisible: keyboardVisible,
|
|
700
|
+
viewportHeight: viewportHeight,
|
|
701
|
+
viewportTop: viewportTop,
|
|
702
|
+
viewportBottom: viewportBottom,
|
|
703
|
+
height: height,
|
|
704
|
+
};
|
|
705
|
+
});
|
|
534
706
|
};
|
|
535
707
|
_this.renderChatAnchorIcon = function () {
|
|
536
708
|
var _a = _this.props, open = _a.open, openIcon = _a.openIcon, closeIcon = _a.closeIcon;
|
|
@@ -556,22 +728,27 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
556
728
|
};
|
|
557
729
|
ChatAnchor.prototype.componentWillUnmount = function () {
|
|
558
730
|
window.removeEventListener("resize", this.handleResize);
|
|
731
|
+
this.handleRemoveViewportListeners();
|
|
559
732
|
this.handleRemoveDragListeners();
|
|
560
733
|
this.handleRemoveResizeListeners();
|
|
561
734
|
this.handleRemoveResizeVertListeners();
|
|
562
735
|
};
|
|
563
736
|
ChatAnchor.prototype.render = function () {
|
|
564
737
|
var _this = this;
|
|
565
|
-
var _a, _b;
|
|
566
|
-
var
|
|
738
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
739
|
+
var isMobileOpen = this.props.open && this.state.isMobileWidth;
|
|
740
|
+
var useMobileResize = isMobileOpen && this.props.resizableHeight === true;
|
|
741
|
+
var useAbsolutePosition = isMobileOpen && !useMobileResize;
|
|
567
742
|
// INFO: a dragged position is an inline style — Tailwind only emits the
|
|
568
743
|
// classes it can see at build time, so a runtime bottom-[Npx] never exists
|
|
569
|
-
var draggedPosition = !
|
|
744
|
+
var draggedPosition = !isMobileOpen && this.state.position ? this.state.position : null;
|
|
570
745
|
var positionOffset = useAbsolutePosition
|
|
571
746
|
? "h-[calc(100dvh)] w-screen top-0 left-0 overflow-hidden"
|
|
572
|
-
:
|
|
573
|
-
? ""
|
|
574
|
-
:
|
|
747
|
+
: useMobileResize
|
|
748
|
+
? "bottom-0 left-0 w-screen max-h-[100dvh]"
|
|
749
|
+
: draggedPosition
|
|
750
|
+
? ""
|
|
751
|
+
: ((_a = this.props.positionOffset) !== null && _a !== void 0 ? _a : exports.DEFAULT_POSITION_OFFSET);
|
|
575
752
|
var chatAnchorVisible = this.props.open ? "hidden sm:block" : "block";
|
|
576
753
|
var icon = this.props.open ? this.props.closeIcon : this.props.openIcon;
|
|
577
754
|
// INFO: only feed a pixel width when resize is on and the panel is not
|
|
@@ -580,33 +757,68 @@ var ChatAnchor = /** @class */ (function (_super) {
|
|
|
580
757
|
? this.state.width
|
|
581
758
|
: undefined;
|
|
582
759
|
// INFO: same for height — a full-screen mobile panel is the whole viewport
|
|
583
|
-
var heightPx = this.
|
|
584
|
-
?
|
|
760
|
+
var heightPx = this.props.resizableHeight
|
|
761
|
+
? useMobileResize
|
|
762
|
+
? this.handleClampHeight((_b = this.state.height) !== null && _b !== void 0 ? _b : this.handleDefaultHeight())
|
|
763
|
+
: (_c = this.state.height) !== null && _c !== void 0 ? _c : undefined
|
|
585
764
|
: undefined;
|
|
765
|
+
var availableHeight = (_d = this.state.viewportHeight) !== null && _d !== void 0 ? _d : this.handleDefaultHeight();
|
|
766
|
+
var mobileHeight = Math.min(heightPx !== null && heightPx !== void 0 ? heightPx : this.handleDefaultHeight(), availableHeight);
|
|
767
|
+
var mobileTopInset = mobileHeight >= availableHeight - 1 ? SAFE_AREA_TOP : "0px";
|
|
768
|
+
var mobileBottomInset = this.state.keyboardVisible ? "0px" : SAFE_AREA_BOTTOM;
|
|
586
769
|
// INFO: the handle only makes sense while the resizable panel is open and
|
|
587
770
|
// not full-screen on mobile — it needs a panel edge to sit against. Gated
|
|
588
771
|
// on the same useAbsolutePosition the layout uses so the two never disagree.
|
|
589
|
-
var showResizeHandle = this.props.open && this.props.resizableWidth && !
|
|
590
|
-
var showResizeVertHandle = this.props.open && this.
|
|
772
|
+
var showResizeHandle = this.props.open && this.props.resizableWidth && !isMobileOpen;
|
|
773
|
+
var showResizeVertHandle = this.props.open && this.handleIsResizableHeight() && !useAbsolutePosition;
|
|
591
774
|
return (react_2.default.createElement("div", { ref: this.anchorRef,
|
|
592
775
|
// INFO: no child spacing when full screen — the hidden bubble row's
|
|
593
776
|
// space-y margin made the panel 16px taller than the screen, so the
|
|
594
777
|
// container scrolled on top of the message list
|
|
595
|
-
className: "z-40 flex flex-col items-end fixed ".concat(
|
|
778
|
+
className: "z-40 flex flex-col items-end fixed ".concat(isMobileOpen ? "" : "space-y-4", " ").concat(positionOffset), style: isMobileOpen
|
|
779
|
+
? useMobileResize
|
|
780
|
+
? { bottom: this.state.viewportBottom }
|
|
781
|
+
: {
|
|
782
|
+
top: this.state.viewportTop,
|
|
783
|
+
height: (_e = this.state.viewportHeight) !== null && _e !== void 0 ? _e : undefined,
|
|
784
|
+
paddingTop: SAFE_AREA_TOP,
|
|
785
|
+
paddingBottom: mobileBottomInset,
|
|
786
|
+
}
|
|
787
|
+
: draggedPosition !== null && draggedPosition !== void 0 ? draggedPosition : undefined },
|
|
596
788
|
react_2.default.createElement(react_1.Transition, { show: this.props.open, as: react_2.Fragment, enter: "transform transition duration-[400ms]", enterFrom: "opacity-0 scale-50", leave: "transform duration-200 transition ease-in-out", leaveTo: "opacity-0 scale-95 " },
|
|
597
|
-
react_2.default.createElement("div", { ref: this.panelRef, className: "relative w-full h-full"
|
|
789
|
+
react_2.default.createElement("div", { ref: this.panelRef, className: "relative w-full h-full ".concat(useMobileResize
|
|
790
|
+
? "pt-8 max-h-[100dvh] rounded-t-2xl overflow-hidden ".concat(this.props.darkMode ? "bg-gray-900" : "bg-white")
|
|
791
|
+
: ""), style: useMobileResize
|
|
792
|
+
? {
|
|
793
|
+
height: mobileHeight,
|
|
794
|
+
paddingTop: "calc(2rem + ".concat(mobileTopInset, ")"),
|
|
795
|
+
paddingBottom: mobileBottomInset,
|
|
796
|
+
paddingLeft: "var(--safe-area-inset-left, env(safe-area-inset-left, 0px))",
|
|
797
|
+
paddingRight: "var(--safe-area-inset-right, env(safe-area-inset-right, 0px))",
|
|
798
|
+
}
|
|
799
|
+
: undefined },
|
|
598
800
|
showResizeHandle && (react_2.default.createElement("div", { "data-testid": "chat-anchor-resize-handle", onPointerDown: this.handleResizeStart, className: "absolute left-0 top-0 h-full w-2 -ml-1 z-10 flex items-center justify-center touch-none select-none cursor-ew-resize group" },
|
|
599
801
|
react_2.default.createElement("div", { className: "h-10 w-1 rounded-full bg-gray-300 group-hover:bg-primary-500 transition-colors" }))),
|
|
600
|
-
showResizeVertHandle && (react_2.default.createElement("div", { "data-testid": "chat-anchor-resize-handle-vertical", onPointerDown: this.handleResizeVertStart,
|
|
802
|
+
showResizeVertHandle && (react_2.default.createElement("div", { "data-testid": "chat-anchor-resize-handle-vertical", onPointerDown: this.handleResizeVertStart, style: useMobileResize ? { top: mobileTopInset } : undefined,
|
|
601
803
|
// INFO: sits a hair right of the left width handle so the two do
|
|
602
804
|
// not fight for the top-left corner; z-20 keeps it above the
|
|
603
805
|
// width strip where they still overlap
|
|
604
|
-
|
|
806
|
+
role: "separator", "aria-label": "Resize chat height", "aria-orientation": "horizontal", "aria-valuemin": this.handleMinHeight(), "aria-valuemax": this.handleMaxHeight(), "aria-valuenow": heightPx, tabIndex: 0, onKeyDown: this.handleResizeHeightKeyDown, className: "absolute top-0 z-20 flex items-center justify-center touch-none select-none cursor-ns-resize group ".concat(useMobileResize
|
|
807
|
+
? "left-0 h-8 ".concat(this.props.hideAdditionalAction ? "right-10" : "right-0")
|
|
808
|
+
: "left-3 right-0 h-2 -mt-1") },
|
|
605
809
|
react_2.default.createElement("div", { className: "h-1 w-10 rounded-full bg-gray-300 group-hover:bg-primary-500 transition-colors" }))),
|
|
606
|
-
react_2.default.createElement(
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
810
|
+
useMobileResize && this.props.hideAdditionalAction && (react_2.default.createElement("button", { type: "button", "aria-label": "Close chat", style: { top: mobileTopInset }, onClick: (_f = this.props.onClose) !== null && _f !== void 0 ? _f : this.props.onClick, className: "absolute top-0 right-0 z-20 h-8 w-10 flex items-center justify-center text-gray-500" },
|
|
811
|
+
react_2.default.createElement(solid_1.XMarkIcon, { className: "h-5 w-5" }))),
|
|
812
|
+
react_2.default.createElement(chatBase_1.ChatBase, __assign({}, this.props, { onClose: useMobileResize
|
|
813
|
+
? (_g = this.props.onClose) !== null && _g !== void 0 ? _g : this.props.onClick
|
|
814
|
+
: this.props.onClose, disableMobileResize: useMobileResize ? false : this.props.disableMobileResize, sizeClassName: useMobileResize ? "h-full w-full min-h-0" : this.props.sizeClassName, widthPx: widthPx, heightPx: useMobileResize ? undefined : heightPx,
|
|
815
|
+
// INFO: clip custom headers to the mobile panel's rounded top corners.
|
|
816
|
+
className: "".concat((_h = this.props.className) !== null && _h !== void 0 ? _h : "", " ").concat(useMobileResize
|
|
817
|
+
? "!rounded-t-2xl !rounded-b-none overflow-hidden"
|
|
818
|
+
: useAbsolutePosition
|
|
819
|
+
? "!rounded-none"
|
|
820
|
+
: "") })))),
|
|
821
|
+
react_2.default.createElement("div", { className: isMobileOpen ? "hidden sm:flex flex-row" : "flex flex-row" },
|
|
610
822
|
react_2.default.createElement("div", { ref: this.buttonRef, onClick: this.handleAnchorClick, onPointerDown: this.handleDragStart, className: "".concat(chatAnchorVisible, " ").concat(icon ? "" : "p-3", " ").concat(this.handleIsDraggable()
|
|
611
823
|
? "touch-none cursor-grab active:cursor-grabbing"
|
|
612
824
|
: "hover:cursor-pointer", " rounded-full select-none h-fit w-fit bg-primary-500 shadow-lg hover:bg-primary-600") }, this.renderChatAnchorIcon()),
|