@runtypelabs/persona 3.31.0 → 3.33.0
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/README.md +2 -0
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-quh7NmYD.d.cts → types-CthJFfNx.d.cts} +7 -0
- package/dist/animations/{types-quh7NmYD.d.ts → types-CthJFfNx.d.ts} +7 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/codegen.cjs +4 -4
- package/dist/codegen.js +3 -3
- package/dist/index.cjs +43 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +288 -8
- package/dist/index.d.ts +288 -8
- package/dist/index.global.js +76 -79
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +43 -43
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +78 -2
- package/dist/smart-dom-reader.d.ts +78 -2
- package/dist/theme-editor.cjs +30 -30
- package/dist/theme-editor.d.cts +92 -5
- package/dist/theme-editor.d.ts +92 -5
- package/dist/theme-editor.js +30 -30
- package/dist/webmcp-polyfill.js +4 -0
- package/package.json +4 -3
- package/src/ask-user-question-tool.test.ts +148 -0
- package/src/ask-user-question-tool.ts +138 -0
- package/src/client.ts +43 -9
- package/src/components/messages.ts +10 -0
- package/src/components/suggestions.ts +49 -6
- package/src/index-core.ts +15 -0
- package/src/index-global.ts +39 -0
- package/src/runtime/host-layout.test.ts +158 -3
- package/src/runtime/host-layout.ts +95 -1
- package/src/session.ts +188 -32
- package/src/session.webmcp.test.ts +48 -0
- package/src/suggest-replies-tool.test.ts +445 -0
- package/src/suggest-replies-tool.ts +152 -0
- package/src/theme-editor/index.ts +2 -0
- package/src/theme-editor/webmcp/index.ts +2 -0
- package/src/theme-editor/webmcp/types.ts +16 -3
- package/src/types.ts +79 -2
- package/src/ui.suggest-replies.test.ts +237 -0
- package/src/ui.ts +57 -13
- package/src/utils/dock.test.ts +23 -1
- package/src/utils/dock.ts +2 -0
- package/src/version.ts +5 -2
- package/src/webmcp-bridge.test.ts +60 -0
- package/src/webmcp-bridge.ts +34 -1
- package/src/webmcp-polyfill.ts +16 -0
package/src/index-global.ts
CHANGED
|
@@ -31,3 +31,42 @@ export {
|
|
|
31
31
|
listRegisteredStreamAnimations,
|
|
32
32
|
} from "./utils/stream-animation";
|
|
33
33
|
export type { StreamAnimationPlugin, StreamAnimationContext } from "./types";
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Deferred WebMCP polyfill loading.
|
|
37
|
+
//
|
|
38
|
+
// This bundle is built with `@mcp-b/webmcp-polyfill` external — the bridge's
|
|
39
|
+
// default `import("@mcp-b/webmcp-polyfill")` is a bare specifier no browser
|
|
40
|
+
// can resolve, so register a loader that imports the self-contained
|
|
41
|
+
// `webmcp-polyfill.js` chunk from a sibling URL instead. Mirrors how
|
|
42
|
+
// `install.ts` derives `launcher.global.js` from a `jsUrl` override.
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
|
|
45
|
+
import { setWebMcpPolyfillLoader } from "./webmcp-bridge";
|
|
46
|
+
|
|
47
|
+
// Capture at module-evaluation time — `document.currentScript` is null once
|
|
48
|
+
// execution leaves the script's initial synchronous run.
|
|
49
|
+
const widgetScriptSrc: string | null =
|
|
50
|
+
typeof document !== "undefined"
|
|
51
|
+
? ((document.currentScript as HTMLScriptElement | null)?.src ?? null)
|
|
52
|
+
: null;
|
|
53
|
+
|
|
54
|
+
setWebMcpPolyfillLoader(() => {
|
|
55
|
+
const chunkUrl = widgetScriptSrc?.replace(
|
|
56
|
+
/index\.global\.js($|\?)/,
|
|
57
|
+
"webmcp-polyfill.js$1",
|
|
58
|
+
);
|
|
59
|
+
if (!chunkUrl || chunkUrl === widgetScriptSrc) {
|
|
60
|
+
return Promise.reject(
|
|
61
|
+
new Error(
|
|
62
|
+
"Could not derive the webmcp-polyfill.js URL from the widget script URL " +
|
|
63
|
+
`(${widgetScriptSrc ?? "unavailable"}). Self-hosted deployments that ` +
|
|
64
|
+
"rename index.global.js should install @mcp-b/webmcp-polyfill on the " +
|
|
65
|
+
"page themselves before enabling config.webmcp.",
|
|
66
|
+
),
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
// Runtime-only dynamic import; the specifier is a computed URL, so esbuild
|
|
70
|
+
// leaves it untouched (and must not try to bundle it).
|
|
71
|
+
return import(/* @vite-ignore */ chunkUrl);
|
|
72
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
|
|
5
5
|
import { createWidgetHostLayout } from "./host-layout";
|
|
6
6
|
|
|
@@ -194,6 +194,161 @@ describe("createWidgetHostLayout docked", () => {
|
|
|
194
194
|
layout.destroy();
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
it("clamps the dock slot to the viewport guard and pins resize/emerge sticky", () => {
|
|
198
|
+
for (const reveal of ["resize", "emerge"] as const) {
|
|
199
|
+
const parent = document.createElement("div");
|
|
200
|
+
document.body.appendChild(parent);
|
|
201
|
+
const target = document.createElement("div");
|
|
202
|
+
parent.appendChild(target);
|
|
203
|
+
|
|
204
|
+
const layout = createWidgetHostLayout(target, {
|
|
205
|
+
launcher: {
|
|
206
|
+
mountMode: "docked",
|
|
207
|
+
autoExpand: false,
|
|
208
|
+
dock: { width: "320px", reveal },
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
213
|
+
expect(dockSlot?.style.maxHeight, reveal).not.toBe("");
|
|
214
|
+
expect(dockSlot?.style.position, reveal).toBe("sticky");
|
|
215
|
+
expect(dockSlot?.style.top, reveal).toBe("0px");
|
|
216
|
+
|
|
217
|
+
layout.destroy();
|
|
218
|
+
document.body.innerHTML = "";
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("clamps push and overlay dock slots without sticky (transform/absolute contexts)", () => {
|
|
223
|
+
// push: the slot lives inside the translated track, where a transformed
|
|
224
|
+
// ancestor defeats sticky — cap only. overlay: keeps absolute positioning.
|
|
225
|
+
const cases = [
|
|
226
|
+
{ reveal: "push" as const, position: "relative" },
|
|
227
|
+
{ reveal: "overlay" as const, position: "absolute" },
|
|
228
|
+
];
|
|
229
|
+
for (const { reveal, position } of cases) {
|
|
230
|
+
const parent = document.createElement("div");
|
|
231
|
+
document.body.appendChild(parent);
|
|
232
|
+
const target = document.createElement("div");
|
|
233
|
+
parent.appendChild(target);
|
|
234
|
+
|
|
235
|
+
const layout = createWidgetHostLayout(target, {
|
|
236
|
+
launcher: {
|
|
237
|
+
mountMode: "docked",
|
|
238
|
+
autoExpand: false,
|
|
239
|
+
dock: { width: "320px", reveal },
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
244
|
+
expect(dockSlot?.style.maxHeight, reveal).not.toBe("");
|
|
245
|
+
expect(dockSlot?.style.position, reveal).toBe(position);
|
|
246
|
+
|
|
247
|
+
layout.destroy();
|
|
248
|
+
document.body.innerHTML = "";
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("honors a custom dock.maxHeight and the false opt-out", () => {
|
|
253
|
+
const parent = document.createElement("div");
|
|
254
|
+
document.body.appendChild(parent);
|
|
255
|
+
const target = document.createElement("div");
|
|
256
|
+
parent.appendChild(target);
|
|
257
|
+
|
|
258
|
+
const dockConfig = {
|
|
259
|
+
mountMode: "docked" as const,
|
|
260
|
+
autoExpand: false,
|
|
261
|
+
dock: { width: "320px", maxHeight: "600px" },
|
|
262
|
+
};
|
|
263
|
+
const layout = createWidgetHostLayout(target, { launcher: dockConfig });
|
|
264
|
+
|
|
265
|
+
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
266
|
+
expect(dockSlot?.style.maxHeight).toBe("600px");
|
|
267
|
+
|
|
268
|
+
layout.updateConfig({
|
|
269
|
+
launcher: { ...dockConfig, dock: { width: "320px", maxHeight: false } },
|
|
270
|
+
});
|
|
271
|
+
expect(dockSlot?.style.maxHeight).toBe("");
|
|
272
|
+
expect(dockSlot?.style.position).toBe("relative");
|
|
273
|
+
expect(dockSlot?.style.top).toBe("");
|
|
274
|
+
|
|
275
|
+
layout.destroy();
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
describe("height-chain warning", () => {
|
|
279
|
+
const withOffsetHeight = (impl: (el: HTMLElement) => number, fn: () => void): void => {
|
|
280
|
+
const original = Object.getOwnPropertyDescriptor(HTMLElement.prototype, "offsetHeight");
|
|
281
|
+
Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
|
|
282
|
+
configurable: true,
|
|
283
|
+
get(this: HTMLElement) {
|
|
284
|
+
return impl(this);
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
try {
|
|
288
|
+
fn();
|
|
289
|
+
} finally {
|
|
290
|
+
if (original) {
|
|
291
|
+
Object.defineProperty(HTMLElement.prototype, "offsetHeight", original);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const mountDocked = () => {
|
|
297
|
+
const parent = document.createElement("div");
|
|
298
|
+
document.body.appendChild(parent);
|
|
299
|
+
const target = document.createElement("div");
|
|
300
|
+
parent.appendChild(target);
|
|
301
|
+
return createWidgetHostLayout(target, {
|
|
302
|
+
launcher: {
|
|
303
|
+
mountMode: "docked",
|
|
304
|
+
autoExpand: true,
|
|
305
|
+
dock: { width: "320px" },
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
afterEach(() => {
|
|
311
|
+
vi.restoreAllMocks();
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("warns once when a percentage height does not resolve against the shell's parent", () => {
|
|
315
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
316
|
+
// Fixed-height probe measures (environment works); 100% probe collapses
|
|
317
|
+
// to 0 (no ancestor provides a definite height).
|
|
318
|
+
withOffsetHeight((el) => (el.style.height === "1px" ? 1 : 0), () => {
|
|
319
|
+
const layout = mountDocked();
|
|
320
|
+
layout.syncWidgetState({ open: false, launcherEnabled: true });
|
|
321
|
+
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
322
|
+
layout.destroy();
|
|
323
|
+
});
|
|
324
|
+
const heightWarnings = warn.mock.calls.filter((c) =>
|
|
325
|
+
String(c[0]).includes("definite height")
|
|
326
|
+
);
|
|
327
|
+
expect(heightWarnings).toHaveLength(1);
|
|
328
|
+
expect(String(heightWarnings[0][0])).toContain("100dvh");
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("does not warn when the height chain resolves", () => {
|
|
332
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
333
|
+
withOffsetHeight(() => 1, () => {
|
|
334
|
+
const layout = mountDocked();
|
|
335
|
+
layout.destroy();
|
|
336
|
+
});
|
|
337
|
+
expect(
|
|
338
|
+
warn.mock.calls.filter((c) => String(c[0]).includes("definite height"))
|
|
339
|
+
).toHaveLength(0);
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("does not warn when the environment cannot measure layout (jsdom default)", () => {
|
|
343
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
344
|
+
const layout = mountDocked();
|
|
345
|
+
layout.destroy();
|
|
346
|
+
expect(
|
|
347
|
+
warn.mock.calls.filter((c) => String(c[0]).includes("definite height"))
|
|
348
|
+
).toHaveLength(0);
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
|
|
197
352
|
const withInnerWidth = (width: number, fn: () => void): void => {
|
|
198
353
|
const prev = window.innerWidth;
|
|
199
354
|
try {
|
|
@@ -277,7 +432,7 @@ describe("createWidgetHostLayout docked", () => {
|
|
|
277
432
|
|
|
278
433
|
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
279
434
|
layout.syncWidgetState({ open: true, launcherEnabled: true });
|
|
280
|
-
expect(dockSlot?.style.position).toBe("
|
|
435
|
+
expect(dockSlot?.style.position).toBe("sticky");
|
|
281
436
|
expect(layout.shell?.dataset.personaDockMobileFullscreen).toBeUndefined();
|
|
282
437
|
|
|
283
438
|
layout.destroy();
|
|
@@ -325,7 +480,7 @@ describe("createWidgetHostLayout docked", () => {
|
|
|
325
480
|
|
|
326
481
|
const dockSlot = layout.shell?.querySelector<HTMLElement>('[data-persona-dock-role="panel"]');
|
|
327
482
|
layout.syncWidgetState({ open: false, launcherEnabled: true });
|
|
328
|
-
expect(dockSlot?.style.position).toBe("
|
|
483
|
+
expect(dockSlot?.style.position).toBe("sticky");
|
|
329
484
|
|
|
330
485
|
layout.destroy();
|
|
331
486
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AgentWidgetConfig,
|
|
3
|
+
AgentWidgetDockConfig,
|
|
3
4
|
AgentWidgetStateSnapshot,
|
|
4
5
|
} from "../types";
|
|
5
6
|
import { isDockedMountMode, resolveDockConfig } from "../utils/dock";
|
|
@@ -26,6 +27,83 @@ const parseDockWidthToPx = (width: string, shellClientWidth: number): number =>
|
|
|
26
27
|
return 420;
|
|
27
28
|
};
|
|
28
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Viewport-overflow guard. The docked shell sizes itself with `height: 100%`,
|
|
32
|
+
* which only resolves when an ancestor provides a definite height; without one
|
|
33
|
+
* the dock column would grow with the conversation and scroll off the page.
|
|
34
|
+
* Clamping the slot keeps the panel viewport-sized (messages scroll
|
|
35
|
+
* internally) even when the page's height chain is missing. `100vh` is set
|
|
36
|
+
* first as a fallback for engines without `dvh` support — an invalid value
|
|
37
|
+
* leaves the previous one in place.
|
|
38
|
+
*/
|
|
39
|
+
const applyDockSlotMaxHeight = (
|
|
40
|
+
dockSlot: HTMLElement,
|
|
41
|
+
maxHeight: string | false
|
|
42
|
+
): void => {
|
|
43
|
+
if (maxHeight === false) {
|
|
44
|
+
dockSlot.style.maxHeight = "";
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
dockSlot.style.maxHeight = "100vh";
|
|
48
|
+
dockSlot.style.maxHeight = maxHeight;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Sticky keeps the resize/emerge dock column pinned to the top of the viewport
|
|
53
|
+
* when the surrounding page is taller than the screen (e.g. a missing height
|
|
54
|
+
* chain or a deliberately scrolling page). With a properly sized shell it
|
|
55
|
+
* behaves exactly like the previous `position: relative`. Not used for push:
|
|
56
|
+
* there the slot lives inside the translated push track, and that transformed
|
|
57
|
+
* ancestor defeats sticky (verified in Chromium) — push gets the max-height
|
|
58
|
+
* cap only, like overlay.
|
|
59
|
+
*/
|
|
60
|
+
const applyInFlowDockSlotPosition = (
|
|
61
|
+
dockSlot: HTMLElement,
|
|
62
|
+
maxHeight: string | false
|
|
63
|
+
): void => {
|
|
64
|
+
if (maxHeight === false) {
|
|
65
|
+
dockSlot.style.position = "relative";
|
|
66
|
+
dockSlot.style.top = "";
|
|
67
|
+
} else {
|
|
68
|
+
dockSlot.style.position = "sticky";
|
|
69
|
+
dockSlot.style.top = "0";
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Warns once per docked mount when no ancestor of the dock target provides a
|
|
75
|
+
* definite height, which is the common misconfiguration behind a dock panel
|
|
76
|
+
* that grows past the viewport. Probes with a throwaway `height: 100%` child
|
|
77
|
+
* of the shell's parent; a fixed-height reference probe first checks the
|
|
78
|
+
* environment can measure at all (display: none subtrees and jsdom measure
|
|
79
|
+
* everything as 0, and must not produce a false warning).
|
|
80
|
+
*/
|
|
81
|
+
const warnIfDockHeightChainUnresolved = (
|
|
82
|
+
shell: HTMLElement,
|
|
83
|
+
dock: Required<AgentWidgetDockConfig>
|
|
84
|
+
): void => {
|
|
85
|
+
const parent = shell.parentElement;
|
|
86
|
+
if (!parent) return;
|
|
87
|
+
const probe = shell.ownerDocument.createElement("div");
|
|
88
|
+
probe.style.cssText =
|
|
89
|
+
"width:0;height:1px;margin:0;padding:0;border:0;visibility:hidden;";
|
|
90
|
+
parent.appendChild(probe);
|
|
91
|
+
const measurable = probe.offsetHeight > 0;
|
|
92
|
+
probe.style.height = "100%";
|
|
93
|
+
const resolved = probe.offsetHeight > 0;
|
|
94
|
+
probe.remove();
|
|
95
|
+
if (!measurable || resolved) return;
|
|
96
|
+
console.warn(
|
|
97
|
+
"[AgentWidget] Docked mode: no ancestor of the dock target provides a definite height, " +
|
|
98
|
+
"so the dock panel cannot size to your layout." +
|
|
99
|
+
(dock.maxHeight === false
|
|
100
|
+
? " The viewport guard is disabled (dock.maxHeight: false), so the panel will grow with the conversation and overflow the viewport."
|
|
101
|
+
: ` Falling back to clamping the panel to ${dock.maxHeight} (configurable via launcher.dock.maxHeight).`) +
|
|
102
|
+
" To size the panel from your layout instead, give the height chain a definite height " +
|
|
103
|
+
"(e.g. `html, body { height: 100% }`) down to the dock target's parent."
|
|
104
|
+
);
|
|
105
|
+
};
|
|
106
|
+
|
|
29
107
|
const setDirectHostStyles = (host: HTMLElement, config?: AgentWidgetConfig): void => {
|
|
30
108
|
const launcherEnabled = config?.launcher?.enabled ?? true;
|
|
31
109
|
host.className = "persona-host";
|
|
@@ -53,6 +131,7 @@ const clearMobileFullscreenDockSlotStyles = (dockSlot: HTMLElement): void => {
|
|
|
53
131
|
dockSlot.style.width = "";
|
|
54
132
|
dockSlot.style.height = "";
|
|
55
133
|
dockSlot.style.maxWidth = "";
|
|
134
|
+
dockSlot.style.maxHeight = "";
|
|
56
135
|
dockSlot.style.minWidth = "";
|
|
57
136
|
clearOverlayDockSlotStyles(dockSlot);
|
|
58
137
|
};
|
|
@@ -227,6 +306,7 @@ const applyDockStyles = (
|
|
|
227
306
|
|
|
228
307
|
shell.removeAttribute("data-persona-dock-mobile-fullscreen");
|
|
229
308
|
clearMobileFullscreenDockSlotStyles(dockSlot);
|
|
309
|
+
applyDockSlotMaxHeight(dockSlot, dock.maxHeight);
|
|
230
310
|
|
|
231
311
|
if (dock.reveal === "overlay") {
|
|
232
312
|
shell.style.display = "flex";
|
|
@@ -315,6 +395,7 @@ const applyDockStyles = (
|
|
|
315
395
|
dockSlot.style.minWidth = dock.width;
|
|
316
396
|
dockSlot.style.maxWidth = dock.width;
|
|
317
397
|
dockSlot.style.position = "relative";
|
|
398
|
+
dockSlot.style.top = "";
|
|
318
399
|
dockSlot.style.overflow = "hidden";
|
|
319
400
|
dockSlot.style.transition = "none";
|
|
320
401
|
dockSlot.style.pointerEvents = expanded ? "auto" : "none";
|
|
@@ -348,7 +429,7 @@ const applyDockStyles = (
|
|
|
348
429
|
dockSlot.style.maxWidth = width;
|
|
349
430
|
dockSlot.style.minWidth = width;
|
|
350
431
|
dockSlot.style.minHeight = "0";
|
|
351
|
-
dockSlot.
|
|
432
|
+
applyInFlowDockSlotPosition(dockSlot, dock.maxHeight);
|
|
352
433
|
dockSlot.style.overflow =
|
|
353
434
|
isEmerge ? "hidden" : collapsedClosed ? "hidden" : "visible";
|
|
354
435
|
dockSlot.style.transition = dockTransition;
|
|
@@ -429,9 +510,22 @@ const createDockedLayout = (target: HTMLElement, config?: AgentWidgetConfig): Wi
|
|
|
429
510
|
resizeObserver.observe(shell);
|
|
430
511
|
};
|
|
431
512
|
|
|
513
|
+
let heightChainChecked = false;
|
|
514
|
+
|
|
432
515
|
const layout = (): void => {
|
|
433
516
|
applyDockStyles(shell, pushTrack, contentSlot, dockSlot, host, config, expanded);
|
|
434
517
|
syncPushResizeObserver();
|
|
518
|
+
// Check the height chain once, the first time the panel is actually shown
|
|
519
|
+
// in a layout that depends on it (mobile fullscreen is fixed-position and
|
|
520
|
+
// doesn't — keep checking until a dependent layout comes around).
|
|
521
|
+
if (
|
|
522
|
+
expanded &&
|
|
523
|
+
!heightChainChecked &&
|
|
524
|
+
shell.dataset.personaDockMobileFullscreen !== "true"
|
|
525
|
+
) {
|
|
526
|
+
heightChainChecked = true;
|
|
527
|
+
warnIfDockHeightChainUnresolved(shell, resolveDockConfig(config));
|
|
528
|
+
}
|
|
435
529
|
};
|
|
436
530
|
|
|
437
531
|
const ownerWindow = shell.ownerDocument.defaultView;
|