browser-debugger-cli 0.8.0 → 0.10.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 +7 -1
- package/dist/cdp/schema.d.ts +4 -1
- package/dist/cdp/schema.js +48 -7
- package/dist/commands/cdp.js +3 -2
- package/dist/commands/cleanup.d.ts +11 -0
- package/dist/commands/cleanup.js +161 -57
- package/dist/commands/console.d.ts +20 -1
- package/dist/commands/console.js +57 -17
- package/dist/commands/details.js +3 -2
- package/dist/commands/dom/DomElementResolver.d.ts +10 -3
- package/dist/commands/dom/DomElementResolver.js +35 -17
- package/dist/commands/dom/a11y.d.ts +10 -0
- package/dist/commands/dom/a11y.js +29 -6
- package/dist/commands/dom/eval.d.ts +3 -1
- package/dist/commands/dom/eval.js +29 -4
- package/dist/commands/dom/form.js +16 -62
- package/dist/commands/dom/formInteraction.js +189 -119
- package/dist/commands/dom/formSummary.d.ts +49 -0
- package/dist/commands/dom/formSummary.js +180 -0
- package/dist/commands/dom/frames.d.ts +2 -1
- package/dist/commands/dom/frames.js +17 -2
- package/dist/commands/dom/get.d.ts +6 -5
- package/dist/commands/dom/get.js +92 -82
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
- package/dist/commands/dom/helpers/keyAttributes.js +54 -0
- package/dist/commands/dom/helpers/query.d.ts +44 -17
- package/dist/commands/dom/helpers/query.js +300 -106
- package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
- package/dist/commands/dom/helpers/runElementCommand.js +98 -30
- package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
- package/dist/commands/dom/helpers/screenshot.js +239 -51
- package/dist/commands/dom/index.d.ts +4 -1
- package/dist/commands/dom/index.js +22 -7
- package/dist/commands/dom/inspect.d.ts +15 -0
- package/dist/commands/dom/inspect.js +82 -0
- package/dist/commands/dom/layout.d.ts +14 -0
- package/dist/commands/dom/layout.js +54 -0
- package/dist/commands/dom/listeners.d.ts +5 -1
- package/dist/commands/dom/listeners.js +15 -5
- package/dist/commands/dom/query.js +2 -3
- package/dist/commands/dom/screenshot.d.ts +12 -2
- package/dist/commands/dom/screenshot.js +27 -3
- package/dist/commands/dom/semanticUtils.d.ts +16 -10
- package/dist/commands/dom/semanticUtils.js +53 -16
- package/dist/commands/dom/wait.d.ts +13 -0
- package/dist/commands/dom/wait.js +83 -0
- package/dist/commands/helpJson.js +2 -2
- package/dist/commands/network/list.js +17 -13
- package/dist/commands/optionBehaviors.js +154 -21
- package/dist/commands/page.d.ts +3 -2
- package/dist/commands/page.js +100 -5
- package/dist/commands/peek.js +4 -11
- package/dist/commands/sessions.d.ts +8 -0
- package/dist/commands/sessions.js +19 -0
- package/dist/commands/shared/CommandRunner.js +4 -4
- package/dist/commands/shared/commonOptions.d.ts +4 -0
- package/dist/commands/shared/commonOptions.js +9 -0
- package/dist/commands/shared/dataFetcher.js +2 -2
- package/dist/commands/shared/followMode.d.ts +21 -1
- package/dist/commands/shared/followMode.js +29 -2
- package/dist/commands/shared/handleValidationError.d.ts +2 -2
- package/dist/commands/shared/handleValidationError.js +12 -3
- package/dist/commands/shared/optionTypes.d.ts +61 -5
- package/dist/commands/shared/startHelpers.d.ts +66 -0
- package/dist/commands/shared/startHelpers.js +103 -13
- package/dist/commands/shared/validation.d.ts +14 -2
- package/dist/commands/shared/validation.js +20 -3
- package/dist/commands/start.d.ts +63 -0
- package/dist/commands/start.js +115 -15
- package/dist/commands/status.js +29 -7
- package/dist/commands/stop.js +7 -6
- package/dist/commands/tail.js +4 -11
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands.js +2 -0
- package/dist/connection/chromeIdentity.d.ts +65 -0
- package/dist/connection/chromeIdentity.js +143 -0
- package/dist/connection/launcher/profilePreferences.d.ts +47 -0
- package/dist/connection/launcher/profilePreferences.js +151 -0
- package/dist/connection/launcher.d.ts +21 -2
- package/dist/connection/launcher.js +42 -16
- package/dist/connection/portReservation.d.ts +14 -4
- package/dist/connection/portReservation.js +21 -6
- package/dist/connection/startupExit.d.ts +8 -0
- package/dist/connection/startupExit.js +15 -6
- package/dist/constants.d.ts +6 -2
- package/dist/constants.js +9 -2
- package/dist/daemon/SessionController.js +23 -7
- package/dist/daemon/errors.d.ts +1 -1
- package/dist/daemon/errors.js +1 -1
- package/dist/daemon/launcher.d.ts +10 -2
- package/dist/daemon/launcher.js +8 -7
- package/dist/daemon/server/SocketServer.js +1 -2
- package/dist/daemon/session/Session.d.ts +20 -0
- package/dist/daemon/session/Session.js +80 -9
- package/dist/daemon/session/chromeConnection.d.ts +9 -0
- package/dist/daemon/session/chromeConnection.js +45 -8
- package/dist/daemon/session/commandRegistry.d.ts +14 -1
- package/dist/daemon/session/commandRegistry.js +113 -67
- package/dist/daemon/session/interactions.d.ts +48 -9
- package/dist/daemon/session/interactions.js +46 -9
- package/dist/daemon/session/triggeredRequests.d.ts +67 -0
- package/dist/daemon/session/triggeredRequests.js +157 -0
- package/dist/daemon/session/types.d.ts +5 -1
- package/dist/daemon.js +10630 -3601
- package/dist/errors/messages.d.ts +456 -24
- package/dist/errors/messages.js +862 -67
- package/dist/index.js +6915 -3401
- package/dist/ipc/client.d.ts +21 -1
- package/dist/ipc/client.js +35 -3
- package/dist/ipc/protocol/commands.d.ts +145 -5
- package/dist/ipc/protocol/commands.js +4 -0
- package/dist/ipc/protocol/domTypes.d.ts +291 -7
- package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
- package/dist/ipc/protocol/inspectTypes.js +10 -0
- package/dist/ipc/session/lifecycle.d.ts +8 -1
- package/dist/ipc/session/queries.d.ts +5 -1
- package/dist/ipc/session/types.d.ts +5 -0
- package/dist/ipc/transport/index.d.ts +2 -1
- package/dist/ipc/transport/index.js +2 -2
- package/dist/runtime/dom/actionEffects.d.ts +185 -0
- package/dist/runtime/dom/actionEffects.js +402 -0
- package/dist/runtime/dom/actionEffectsScripts.d.ts +90 -0
- package/dist/runtime/dom/actionEffectsScripts.js +426 -0
- package/dist/runtime/dom/elementGeometry.d.ts +170 -0
- package/dist/runtime/dom/elementGeometry.js +553 -0
- package/dist/runtime/dom/elementInfo.d.ts +103 -0
- package/dist/runtime/dom/elementInfo.js +256 -0
- package/dist/runtime/dom/evalHelpers.d.ts +51 -6
- package/dist/runtime/dom/evalHelpers.js +136 -26
- package/dist/runtime/dom/eventListeners.d.ts +2 -1
- package/dist/runtime/dom/eventListeners.js +184 -47
- package/dist/runtime/dom/formDiscovery.d.ts +1 -1
- package/dist/runtime/dom/formDiscovery.js +116 -16
- package/dist/runtime/dom/formFillHelpers/fill.d.ts +9 -0
- package/dist/runtime/dom/formFillHelpers/fill.js +178 -14
- package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
- package/dist/runtime/dom/formFillHelpers/index.js +2 -2
- package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
- package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
- package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
- package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
- package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
- package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
- package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
- package/dist/runtime/dom/formSubmitHelpers.js +89 -15
- package/dist/runtime/dom/frameLayout.d.ts +60 -0
- package/dist/runtime/dom/frameLayout.js +140 -0
- package/dist/runtime/dom/frameOrigin.d.ts +50 -0
- package/dist/runtime/dom/frameOrigin.js +62 -0
- package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
- package/dist/runtime/dom/frameScopedConnection.js +252 -0
- package/dist/runtime/dom/frameSelection.d.ts +12 -1
- package/dist/runtime/dom/frameSelection.js +22 -3
- package/dist/runtime/dom/frames.d.ts +61 -5
- package/dist/runtime/dom/frames.js +329 -75
- package/dist/runtime/dom/inspect.d.ts +28 -0
- package/dist/runtime/dom/inspect.js +557 -0
- package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
- package/dist/runtime/dom/inspectAllStyles.js +385 -0
- package/dist/runtime/dom/inspectCascade.d.ts +94 -0
- package/dist/runtime/dom/inspectCascade.js +371 -0
- package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
- package/dist/runtime/dom/inspectCascadeModel.js +232 -0
- package/dist/runtime/dom/inspectHints.d.ts +62 -0
- package/dist/runtime/dom/inspectHints.js +305 -0
- package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
- package/dist/runtime/dom/inspectLayoutModel.js +346 -0
- package/dist/runtime/dom/inspectModel.d.ts +74 -0
- package/dist/runtime/dom/inspectModel.js +184 -0
- package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
- package/dist/runtime/dom/inspectPaintModel.js +461 -0
- package/dist/runtime/dom/inspectRules.d.ts +37 -0
- package/dist/runtime/dom/inspectRules.js +101 -0
- package/dist/runtime/dom/inspectScripts.d.ts +132 -0
- package/dist/runtime/dom/inspectScripts.js +263 -0
- package/dist/runtime/dom/inspectTree.d.ts +40 -0
- package/dist/runtime/dom/inspectTree.js +134 -0
- package/dist/runtime/dom/inspectVariables.d.ts +33 -0
- package/dist/runtime/dom/inspectVariables.js +94 -0
- package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
- package/dist/runtime/dom/inspectWhyModel.js +134 -0
- package/dist/runtime/dom/layout.d.ts +71 -0
- package/dist/runtime/dom/layout.js +340 -0
- package/dist/runtime/dom/listenerPageScripts.d.ts +72 -0
- package/dist/runtime/dom/listenerPageScripts.js +365 -0
- package/dist/runtime/dom/listenerSummary.d.ts +136 -11
- package/dist/runtime/dom/listenerSummary.js +361 -22
- package/dist/runtime/dom/pageActivity.d.ts +41 -0
- package/dist/runtime/dom/pageActivity.js +123 -0
- package/dist/runtime/dom/reactEventHelpers.d.ts +63 -2
- package/dist/runtime/dom/reactEventHelpers.js +220 -41
- package/dist/runtime/dom/targetNode.d.ts +80 -27
- package/dist/runtime/dom/targetNode.js +249 -33
- package/dist/runtime/dom/wait.d.ts +25 -0
- package/dist/runtime/dom/wait.js +199 -0
- package/dist/runtime/dom/waitCondition.d.ts +71 -0
- package/dist/runtime/dom/waitCondition.js +75 -0
- package/dist/runtime/page/emulation.d.ts +71 -0
- package/dist/runtime/page/emulation.js +117 -0
- package/dist/runtime/page/loadingState.d.ts +36 -0
- package/dist/runtime/page/loadingState.js +86 -0
- package/dist/runtime/page/navigation.d.ts +46 -2
- package/dist/runtime/page/navigation.js +69 -33
- package/dist/session/QueryCacheManager.d.ts +11 -1
- package/dist/session/QueryCacheManager.js +25 -3
- package/dist/session/chromeOwners.d.ts +34 -0
- package/dist/session/chromeOwners.js +51 -0
- package/dist/session/cleanup/staleSession.d.ts +11 -1
- package/dist/session/cleanup/staleSession.js +17 -6
- package/dist/session/cleanup/userCommands.js +2 -4
- package/dist/session/metadata.d.ts +5 -1
- package/dist/session/metadata.js +2 -1
- package/dist/session/paths.d.ts +77 -3
- package/dist/session/paths.js +111 -5
- package/dist/session/port.d.ts +31 -7
- package/dist/session/port.js +50 -43
- package/dist/session/portClaims.d.ts +66 -0
- package/dist/session/portClaims.js +284 -0
- package/dist/session/sessionList.d.ts +58 -0
- package/dist/session/sessionList.js +199 -0
- package/dist/session/sessionName.d.ts +46 -0
- package/dist/session/sessionName.js +97 -0
- package/dist/telemetry/a11y.d.ts +18 -3
- package/dist/telemetry/a11y.js +170 -29
- package/dist/telemetry/console.d.ts +1 -0
- package/dist/telemetry/console.js +100 -5
- package/dist/telemetry/network.js +3 -1
- package/dist/telemetry/requestKinds.d.ts +32 -0
- package/dist/telemetry/requestKinds.js +61 -0
- package/dist/telemetry/requestState.d.ts +31 -0
- package/dist/telemetry/requestState.js +38 -0
- package/dist/types.d.ts +112 -3
- package/dist/ui/formatters/a11y.js +3 -0
- package/dist/ui/formatters/console/chronological.d.ts +8 -0
- package/dist/ui/formatters/console/chronological.js +17 -4
- package/dist/ui/formatters/console/json.js +3 -4
- package/dist/ui/formatters/console/shared.d.ts +12 -0
- package/dist/ui/formatters/console.d.ts +2 -2
- package/dist/ui/formatters/console.js +1 -1
- package/dist/ui/formatters/details.d.ts +8 -0
- package/dist/ui/formatters/details.js +61 -4
- package/dist/ui/formatters/dom.d.ts +27 -14
- package/dist/ui/formatters/dom.js +88 -59
- package/dist/ui/formatters/form.js +29 -18
- package/dist/ui/formatters/inspect.d.ts +39 -0
- package/dist/ui/formatters/inspect.js +596 -0
- package/dist/ui/formatters/keyAttributes.d.ts +19 -0
- package/dist/ui/formatters/keyAttributes.js +84 -0
- package/dist/ui/formatters/layout.d.ts +31 -0
- package/dist/ui/formatters/layout.js +53 -0
- package/dist/ui/formatters/listeners.d.ts +3 -2
- package/dist/ui/formatters/listeners.js +73 -9
- package/dist/ui/formatters/networkHeaders.d.ts +13 -0
- package/dist/ui/formatters/networkHeaders.js +36 -3
- package/dist/ui/formatters/networkList.d.ts +29 -1
- package/dist/ui/formatters/networkList.js +86 -20
- package/dist/ui/formatters/preview.js +2 -1
- package/dist/ui/formatters/requestStatus.d.ts +1 -17
- package/dist/ui/formatters/requestStatus.js +2 -30
- package/dist/ui/formatters/sessions.d.ts +12 -0
- package/dist/ui/formatters/sessions.js +40 -0
- package/dist/ui/formatters/status.d.ts +21 -2
- package/dist/ui/formatters/status.js +47 -10
- package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
- package/dist/ui/formatters/triggeredRequests.js +65 -0
- package/dist/ui/formatting.d.ts +19 -0
- package/dist/ui/formatting.js +31 -36
- package/dist/ui/messages/chrome.d.ts +9 -0
- package/dist/ui/messages/chrome.js +17 -5
- package/dist/ui/messages/commands.d.ts +504 -14
- package/dist/ui/messages/commands.js +835 -21
- package/dist/ui/messages/consoleMessages.d.ts +10 -0
- package/dist/ui/messages/consoleMessages.js +17 -0
- package/dist/ui/messages/hints.js +2 -1
- package/dist/ui/messages/networkMessages.d.ts +14 -0
- package/dist/ui/messages/networkMessages.js +18 -0
- package/dist/ui/messages/preview.js +5 -4
- package/dist/ui/messages/session.d.ts +30 -21
- package/dist/ui/messages/session.js +48 -26
- package/dist/ui/messages/sessionCommand.d.ts +43 -0
- package/dist/ui/messages/sessionCommand.js +52 -0
- package/dist/utils/async.d.ts +17 -0
- package/dist/utils/async.js +36 -0
- package/dist/utils/color.d.ts +84 -0
- package/dist/utils/color.js +376 -0
- package/dist/utils/cssValues.d.ts +109 -0
- package/dist/utils/cssValues.js +236 -0
- package/dist/utils/http.d.ts +22 -1
- package/dist/utils/http.js +28 -9
- package/dist/utils/selectorFilters.d.ts +48 -8
- package/dist/utils/selectorFilters.js +296 -53
- package/dist/utils/shellDetection.d.ts +8 -2
- package/dist/utils/shellDetection.js +120 -33
- package/dist/utils/suggestions.d.ts +26 -0
- package/dist/utils/suggestions.js +73 -0
- package/dist/utils/taskMappings.js +10 -0
- package/dist/utils/url.d.ts +12 -2
- package/dist/utils/url.js +69 -7
- package/package.json +1 -1
- package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
- package/dist/ui/formatters/sessionFormatters.js +0 -121
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where elements are relative to the viewport of the top-level page.
|
|
3
|
+
*
|
|
4
|
+
* The page-side scripts measure an element in top-level viewport coordinates
|
|
5
|
+
* (adding the offsets of the same-origin iframes it is in) together with what
|
|
6
|
+
* clips it: the viewports of those iframes and ancestors that cut off
|
|
7
|
+
* overflowing content (scroll containers, `overflow: hidden`). Classifying the
|
|
8
|
+
* measurements is done here, outside the page, so `dom query` and `dom layout`
|
|
9
|
+
* agree and the rules can be tested without a browser.
|
|
10
|
+
*/
|
|
11
|
+
import { ELEMENT_DESCRIPTION_JS } from './elementInfo.js';
|
|
12
|
+
import { LAYOUT_REASONS, scrollLockedReason } from '../../ui/messages/commands.js';
|
|
13
|
+
/** Why page scroll cannot bring an element that is not fully in view into view. */
|
|
14
|
+
export const OFF_SCREEN_REASONS = {
|
|
15
|
+
fixed: 'fixed position, page scroll does not move it',
|
|
16
|
+
sticky: 'sticky position, page scroll moves it only until it sticks',
|
|
17
|
+
outOfRange: "beyond the page's scroll range",
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Page-side size of a window's viewport without scrollbars (the window size
|
|
21
|
+
* for quirks-mode documents, whose root element is as large as the content).
|
|
22
|
+
*/
|
|
23
|
+
export const VIEWPORT_SIZE_JS = `(view) => {
|
|
24
|
+
const root = view.document.documentElement;
|
|
25
|
+
const standards = view.document.compatMode === 'CSS1Compat' && root;
|
|
26
|
+
return {
|
|
27
|
+
width: standards ? root.clientWidth : view.innerWidth,
|
|
28
|
+
height: standards ? root.clientHeight : view.innerHeight
|
|
29
|
+
};
|
|
30
|
+
}`;
|
|
31
|
+
/**
|
|
32
|
+
* Page-side distances a window's page can scroll in each direction from its
|
|
33
|
+
* current position. Right-to-left pages scroll to negative `scrollX`. Content
|
|
34
|
+
* that a scrolling body cuts off is reported through the body as a clipper.
|
|
35
|
+
*/
|
|
36
|
+
export const SCROLL_RANGE_JS = `(view) => {
|
|
37
|
+
const doc = view.document;
|
|
38
|
+
const scroller = doc.scrollingElement || doc.documentElement;
|
|
39
|
+
const maxX = Math.max(0, scroller.scrollWidth - scroller.clientWidth);
|
|
40
|
+
const maxY = Math.max(0, scroller.scrollHeight - scroller.clientHeight);
|
|
41
|
+
const rtl = view.scrollX < 0 || view.getComputedStyle(doc.documentElement).direction === 'rtl';
|
|
42
|
+
const left = rtl ? maxX + view.scrollX : view.scrollX;
|
|
43
|
+
return { left: Math.max(0, left), up: Math.max(0, view.scrollY), right: Math.max(0, maxX - left), down: Math.max(0, maxY - view.scrollY) };
|
|
44
|
+
}`;
|
|
45
|
+
/**
|
|
46
|
+
* Page-side reason the page of a window cannot be scrolled although its
|
|
47
|
+
* content may extend below the fold: the scrolling element is no taller than
|
|
48
|
+
* the viewport while the body or root element is `position: fixed` or has
|
|
49
|
+
* `overflow: hidden` (or `clip`), which is how dialogs lock page scrolling,
|
|
50
|
+
* with the first visible dialog of the page (`dialog[open]`,
|
|
51
|
+
* `[aria-modal=true]`, `[role=dialog]`, `[role=alertdialog]`) as the likely
|
|
52
|
+
* reason. Null when the page can scroll or nothing locks it.
|
|
53
|
+
*/
|
|
54
|
+
const SCROLL_LOCK_JS = `(view, describe) => {
|
|
55
|
+
const doc = view.document;
|
|
56
|
+
const scroller = doc.scrollingElement || doc.documentElement;
|
|
57
|
+
if (scroller.scrollHeight > scroller.clientHeight + 1) return null;
|
|
58
|
+
const lockOf = (node, name) => {
|
|
59
|
+
if (!node) return null;
|
|
60
|
+
const style = view.getComputedStyle(node);
|
|
61
|
+
const parts = [];
|
|
62
|
+
if (style.position === 'fixed') parts.push('position: fixed');
|
|
63
|
+
if (style.overflowY === 'hidden' || style.overflowY === 'clip') parts.push('overflow: ' + style.overflowY);
|
|
64
|
+
return parts.length > 0 ? parts.join(', ') + ' on ' + name : null;
|
|
65
|
+
};
|
|
66
|
+
const by = lockOf(doc.body, 'body') || lockOf(doc.documentElement, 'html');
|
|
67
|
+
if (!by) return null;
|
|
68
|
+
const shown = (el) => {
|
|
69
|
+
const r = el.getBoundingClientRect();
|
|
70
|
+
return r.width > 0 && r.height > 0 && (!el.checkVisibility || el.checkVisibility({ visibilityProperty: true }));
|
|
71
|
+
};
|
|
72
|
+
const dialogs = doc.querySelectorAll('dialog[open], [aria-modal="true"], [role="dialog"], [role="alertdialog"]');
|
|
73
|
+
const dialog = Array.from(dialogs).find(shown);
|
|
74
|
+
return { by: by, dialog: dialog ? describe(dialog) : null };
|
|
75
|
+
}`;
|
|
76
|
+
/**
|
|
77
|
+
* Page-side offset of an iframe's content (inside its border and padding)
|
|
78
|
+
* within the viewport of the document holding the iframe.
|
|
79
|
+
*/
|
|
80
|
+
export const FRAME_OFFSET_JS = `(frame) => {
|
|
81
|
+
const rect = frame.getBoundingClientRect();
|
|
82
|
+
const style = frame.ownerDocument.defaultView.getComputedStyle(frame);
|
|
83
|
+
return {
|
|
84
|
+
x: rect.left + frame.clientLeft + parseFloat(style.paddingLeft),
|
|
85
|
+
y: rect.top + frame.clientTop + parseFloat(style.paddingTop)
|
|
86
|
+
};
|
|
87
|
+
}`;
|
|
88
|
+
/** Page-side parent in the flat tree: a slotted node's slot, then its parent, then a shadow root's host. */
|
|
89
|
+
const FLAT_PARENT_JS = `(n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null`;
|
|
90
|
+
/**
|
|
91
|
+
* Page-side test of computed styles: true when they only scale or move the
|
|
92
|
+
* box (no `rotate`, and a 2D `transform` matrix without rotation or skew), so
|
|
93
|
+
* its bounding box is the box itself, scaled.
|
|
94
|
+
*/
|
|
95
|
+
export const SCALES_ONLY_JS = `(style) => {
|
|
96
|
+
if (style.rotate && style.rotate !== 'none') return false;
|
|
97
|
+
if (!style.transform || style.transform === 'none') return true;
|
|
98
|
+
const matrix = /^matrix\\(([^)]*)\\)$/.exec(style.transform);
|
|
99
|
+
if (!matrix) return false;
|
|
100
|
+
const values = matrix[1].split(',').map(parseFloat);
|
|
101
|
+
return Math.abs(values[1]) < 1e-6 && Math.abs(values[2]) < 1e-6;
|
|
102
|
+
}`;
|
|
103
|
+
/**
|
|
104
|
+
* Page-side padding box of a clipping container in its document's viewport
|
|
105
|
+
* coordinates. It is scaled by the container's rendered to layout size ratio
|
|
106
|
+
* (`getBoundingClientRect()` against `offsetWidth`/`offsetHeight`), so CSS
|
|
107
|
+
* `zoom` and `transform: scale()` on it or around it clip where they show;
|
|
108
|
+
* when it or an ancestor is rotated or skewed the bounding box is no longer
|
|
109
|
+
* its box, and the unscaled size is used. `clientWidth`/`clientHeight` are
|
|
110
|
+
* whole pixels: a container they report as 0 is measured from its rendered
|
|
111
|
+
* size minus borders, so only one with no area at all counts as collapsed.
|
|
112
|
+
*/
|
|
113
|
+
const CLIP_BOX_JS = `(p) => {
|
|
114
|
+
const scalesOnly = ${SCALES_ONLY_JS};
|
|
115
|
+
const parentOf = ${FLAT_PARENT_JS};
|
|
116
|
+
const styleOf = (n) => n.ownerDocument.defaultView.getComputedStyle(n);
|
|
117
|
+
let plain = true;
|
|
118
|
+
for (let n = p; n && plain; n = parentOf(n)) plain = scalesOnly(styleOf(n));
|
|
119
|
+
const style = styleOf(p);
|
|
120
|
+
const r = p.getBoundingClientRect();
|
|
121
|
+
const scaleX = plain && p.offsetWidth > 0 ? r.width / p.offsetWidth : 1;
|
|
122
|
+
const scaleY = plain && p.offsetHeight > 0 ? r.height / p.offsetHeight : 1;
|
|
123
|
+
const inner = (client, rendered, borders, scale) =>
|
|
124
|
+
client > 0 ? client * scale : Math.max(0, rendered - borders * scale);
|
|
125
|
+
const width = inner(p.clientWidth, plain ? r.width : p.offsetWidth, parseFloat(style.borderLeftWidth) + parseFloat(style.borderRightWidth), scaleX);
|
|
126
|
+
const height = inner(p.clientHeight, plain ? r.height : p.offsetHeight, parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth), scaleY);
|
|
127
|
+
const left = r.left + p.clientLeft * scaleX;
|
|
128
|
+
const top = r.top + p.clientTop * scaleY;
|
|
129
|
+
return { left: left, top: top, right: left + width, bottom: top + height };
|
|
130
|
+
}`;
|
|
131
|
+
/**
|
|
132
|
+
* Page-side overlay scrollbars of a clipping container: whether it scrolls
|
|
133
|
+
* along an axis with a scrollbar that takes no space (macOS, mobile), which
|
|
134
|
+
* shows over its right (vertical) or bottom (horizontal) edge after a scroll.
|
|
135
|
+
*/
|
|
136
|
+
const OVERLAY_SCROLLBARS_JS = `(p) => {
|
|
137
|
+
const style = p.ownerDocument.defaultView.getComputedStyle(p);
|
|
138
|
+
const scrolls = (overflow, content, client) => /auto|scroll/.test(overflow) && content > client;
|
|
139
|
+
const spare = (outer, client, a, b) => outer - client - parseFloat(a) - parseFloat(b) < 1;
|
|
140
|
+
return {
|
|
141
|
+
right: scrolls(style.overflowY, p.scrollHeight, p.clientHeight) && spare(p.offsetWidth, p.clientWidth, style.borderLeftWidth, style.borderRightWidth),
|
|
142
|
+
bottom: scrolls(style.overflowX, p.scrollWidth, p.clientWidth) && spare(p.offsetHeight, p.clientHeight, style.borderTopWidth, style.borderBottomWidth)
|
|
143
|
+
};
|
|
144
|
+
}`;
|
|
145
|
+
/**
|
|
146
|
+
* Page-side test of whether a container lets a `position: fixed` descendant
|
|
147
|
+
* escape to the viewport: false when it is transformed (or filtered,
|
|
148
|
+
* contained, …) and so holds it like an absolutely positioned one.
|
|
149
|
+
*/
|
|
150
|
+
const HOLDS_FIXED_JS = `(style) =>
|
|
151
|
+
style.transform !== 'none' || style.filter !== 'none' || style.perspective !== 'none' ||
|
|
152
|
+
/transform|filter|perspective/.test(style.willChange) || /paint|layout|strict|content/.test(style.contain)`;
|
|
153
|
+
/**
|
|
154
|
+
* Page-side test of whether a `position: fixed` node is fixed to its
|
|
155
|
+
* document's viewport (no ancestor holds it, {@link HOLDS_FIXED_JS}).
|
|
156
|
+
*/
|
|
157
|
+
const FIXED_TO_VIEWPORT_JS = `(n) => {
|
|
158
|
+
const holdsFixed = ${HOLDS_FIXED_JS};
|
|
159
|
+
const parentOf = (node) => node.parentElement || (node.parentNode && node.parentNode.host) || null;
|
|
160
|
+
for (let p = parentOf(n); p && p !== n.ownerDocument.documentElement; p = parentOf(p)) {
|
|
161
|
+
if (holdsFixed(p.ownerDocument.defaultView.getComputedStyle(p))) return false;
|
|
162
|
+
}
|
|
163
|
+
return true;
|
|
164
|
+
}`;
|
|
165
|
+
/**
|
|
166
|
+
* Page-side clip of a node by its ancestors (looked up through open shadow
|
|
167
|
+
* roots): the padding boxes ({@link CLIP_BOX_JS}) of those that cut off
|
|
168
|
+
* overflowing content and hold the node in their containing-block chain. An
|
|
169
|
+
* absolutely positioned node skips static ancestors (that are not
|
|
170
|
+
* transformed) up to its containing block, a fixed one is not clipped at all
|
|
171
|
+
* unless a transformed (or filtered, contained, …) ancestor holds it like an
|
|
172
|
+
* absolute one, and inline ancestors and `display: contents` ones have no box
|
|
173
|
+
* to clip with. The root element is left out (its overflow belongs to the
|
|
174
|
+
* viewport), and so is the body's overflow unless the root element's
|
|
175
|
+
* overflow is not `visible` (then the body keeps its own overflow and, e.g.
|
|
176
|
+
* as the page's scroller, clips like any container).
|
|
177
|
+
* Returns the clip, whether overlay scrollbars of the containers show along
|
|
178
|
+
* its right and bottom edges ({@link OVERLAY_SCROLLBARS_JS}), the innermost
|
|
179
|
+
* ancestor cutting off part of `rect` (null when none does), why the
|
|
180
|
+
* innermost clipping ancestor with no area (a collapsed
|
|
181
|
+
* `height: 0; overflow: hidden` accordion) hides it, e.g.
|
|
182
|
+
* `clipped by div#acc: zero height` (null when none has), and whether the
|
|
183
|
+
* node is fixed to the viewport (it or a container in its containing-block
|
|
184
|
+
* chain is `position: fixed`; `fixedBy` is that node).
|
|
185
|
+
*/
|
|
186
|
+
const ANCESTOR_CLIP_JS = `(node, rect, describe) => {
|
|
187
|
+
const clipBox = ${CLIP_BOX_JS};
|
|
188
|
+
const overlayScrollbars = ${OVERLAY_SCROLLBARS_JS};
|
|
189
|
+
const holdsFixed = ${HOLDS_FIXED_JS};
|
|
190
|
+
const fixedToViewport = ${FIXED_TO_VIEWPORT_JS};
|
|
191
|
+
const reasons = ${JSON.stringify(LAYOUT_REASONS)};
|
|
192
|
+
const styleOf = (n) => n.ownerDocument.defaultView.getComputedStyle(n);
|
|
193
|
+
const parentOf = (n) => n.parentElement || (n.parentNode && n.parentNode.host) || null;
|
|
194
|
+
const doc = node.ownerDocument;
|
|
195
|
+
const rootStyle = styleOf(doc.documentElement);
|
|
196
|
+
const bodyClips = rootStyle.overflowX !== 'visible' || rootStyle.overflowY !== 'visible';
|
|
197
|
+
const result = { clip: null, overlay: { right: false, bottom: false }, clipper: null, collapsed: null, fixed: false, fixedBy: null };
|
|
198
|
+
const add = (p) => {
|
|
199
|
+
const box = clipBox(p);
|
|
200
|
+
const contains = rect.left >= box.left && rect.top >= box.top && rect.right <= box.right && rect.bottom <= box.bottom;
|
|
201
|
+
if (!result.clipper && !contains) result.clipper = describe(p);
|
|
202
|
+
const empty = box.bottom <= box.top ? reasons.zeroHeight : box.right <= box.left ? reasons.zeroWidth : null;
|
|
203
|
+
if (!result.collapsed && empty) result.collapsed = reasons.clippedBy + describe(p) + ': ' + empty;
|
|
204
|
+
const clip = result.clip || box;
|
|
205
|
+
const bars = overlayScrollbars(p);
|
|
206
|
+
result.overlay = {
|
|
207
|
+
right: box.right < clip.right ? bars.right : box.right > clip.right ? result.overlay.right : result.overlay.right || bars.right,
|
|
208
|
+
bottom: box.bottom < clip.bottom ? bars.bottom : box.bottom > clip.bottom ? result.overlay.bottom : result.overlay.bottom || bars.bottom
|
|
209
|
+
};
|
|
210
|
+
result.clip = { left: Math.max(clip.left, box.left), top: Math.max(clip.top, box.top), right: Math.min(clip.right, box.right), bottom: Math.min(clip.bottom, box.bottom) };
|
|
211
|
+
};
|
|
212
|
+
let position = styleOf(node).position;
|
|
213
|
+
if (position === 'fixed') {
|
|
214
|
+
if (fixedToViewport(node)) return Object.assign(result, { fixed: true, fixedBy: node });
|
|
215
|
+
position = 'absolute';
|
|
216
|
+
}
|
|
217
|
+
for (let p = parentOf(node); p && p !== doc.documentElement; p = parentOf(p)) {
|
|
218
|
+
const style = styleOf(p);
|
|
219
|
+
if (position === 'absolute' && style.position === 'static' && !holdsFixed(style)) continue;
|
|
220
|
+
const boxed = style.display !== 'inline' && style.display !== 'contents' && (bodyClips || p !== doc.body);
|
|
221
|
+
if (boxed && (style.overflowX !== 'visible' || style.overflowY !== 'visible')) add(p);
|
|
222
|
+
position = style.position;
|
|
223
|
+
if (position === 'fixed') {
|
|
224
|
+
if (fixedToViewport(p)) return Object.assign(result, { fixed: true, fixedBy: p });
|
|
225
|
+
position = 'absolute';
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return result;
|
|
229
|
+
}`;
|
|
230
|
+
/** Page-side test of a `clip-path` that cuts everything away: `inset()` with percentages leaving no area. */
|
|
231
|
+
export const CLIP_PATH_CUTS_ALL_JS = `(clipPath) => {
|
|
232
|
+
const inset = /^inset\\(([^)]*)\\)/.exec(clipPath || '');
|
|
233
|
+
const values = inset ? inset[1].split(' round ')[0].trim().split(/\\s+/) : [];
|
|
234
|
+
if (values.length === 0 || values.some((v) => !/%$/.test(v))) return false;
|
|
235
|
+
const [top, right = top, bottom = top, left = right] = values.map(parseFloat);
|
|
236
|
+
return top + bottom >= 100 || left + right >= 100;
|
|
237
|
+
}`;
|
|
238
|
+
/**
|
|
239
|
+
* Page-side test of a `clip` that cuts everything away: `rect()` with no area
|
|
240
|
+
* on an absolutely positioned or fixed element (the "visually hidden"
|
|
241
|
+
* pattern; `clip` applies to no other element).
|
|
242
|
+
*/
|
|
243
|
+
export const CLIP_RECT_CUTS_ALL_JS = `(position, clip) => {
|
|
244
|
+
if ((position !== 'absolute' && position !== 'fixed') || !/^rect\\(/.test(clip || '')) return false;
|
|
245
|
+
const edges = (clip.match(/-?[\\d.]+/g) || []).map(Number);
|
|
246
|
+
return edges.length === 4 && (edges[1] <= edges[3] || edges[2] <= edges[0]);
|
|
247
|
+
}`;
|
|
248
|
+
/**
|
|
249
|
+
* Page-side reason an element that is rendered still cannot be seen: it or an
|
|
250
|
+
* ancestor in the flat tree (slots, shadow hosts, iframes) is fully
|
|
251
|
+
* transparent (`opacity: 0`), or cuts everything away
|
|
252
|
+
* ({@link CLIP_PATH_CUTS_ALL_JS}, {@link CLIP_RECT_CUTS_ALL_JS}). Other
|
|
253
|
+
* `clip-path` shapes are not evaluated. Null when none applies.
|
|
254
|
+
*/
|
|
255
|
+
const INVISIBLE_REASON_JS = `(el, describe) => {
|
|
256
|
+
const flatParent = ${FLAT_PARENT_JS};
|
|
257
|
+
const clipPathCutsAll = ${CLIP_PATH_CUTS_ALL_JS};
|
|
258
|
+
const clipRectCutsAll = ${CLIP_RECT_CUTS_ALL_JS};
|
|
259
|
+
const reasons = ${JSON.stringify(LAYOUT_REASONS)};
|
|
260
|
+
const parentOf = (n) => flatParent(n) || n.ownerDocument.defaultView.frameElement || null;
|
|
261
|
+
for (let n = el; n; n = parentOf(n)) {
|
|
262
|
+
const style = n.ownerDocument.defaultView.getComputedStyle(n);
|
|
263
|
+
const cause = parseFloat(style.opacity) === 0 ? reasons.transparent
|
|
264
|
+
: clipPathCutsAll(style.clipPath) ? 'clip-path: ' + style.clipPath
|
|
265
|
+
: clipRectCutsAll(style.position, style.clip) ? 'clip: ' + style.clip
|
|
266
|
+
: null;
|
|
267
|
+
if (cause) return n === el ? cause : cause + reasons.on + describe(n);
|
|
268
|
+
}
|
|
269
|
+
return null;
|
|
270
|
+
}`;
|
|
271
|
+
/**
|
|
272
|
+
* Page-side measurement of an element ({@link ElementGeometry}): its box in
|
|
273
|
+
* top-level viewport coordinates, its clip by ancestors
|
|
274
|
+
* ({@link ANCESTOR_CLIP_JS}) and by the viewports of its iframes, why it
|
|
275
|
+
* cannot be seen at all (not rendered, or inside a clipping container
|
|
276
|
+
* collapsed to zero size), why a rendered one is still invisible
|
|
277
|
+
* ({@link INVISIBLE_REASON_JS}), whether it is inert (an `inert` element
|
|
278
|
+
* around it, through shadow roots) or fixed to the top-level viewport, and how
|
|
279
|
+
* far the top-level page can scroll ({@link SCROLL_RANGE_JS}) or what locks
|
|
280
|
+
* its scrolling ({@link SCROLL_LOCK_JS}). Content of a body fixed to lock the
|
|
281
|
+
* page is not counted as fixed: it is in-flow content the lock holds in place.
|
|
282
|
+
*
|
|
283
|
+
* Rendering is decided by `checkVisibility()`, so content Chrome skips
|
|
284
|
+
* (inside a closed `<details>`, under `content-visibility: hidden`) is hidden
|
|
285
|
+
* although it still has a box. Content skipped by `content-visibility: auto`
|
|
286
|
+
* is not: it is rendered once scrolled near the viewport.
|
|
287
|
+
*/
|
|
288
|
+
export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
289
|
+
const viewportSize = ${VIEWPORT_SIZE_JS};
|
|
290
|
+
const frameOffset = ${FRAME_OFFSET_JS};
|
|
291
|
+
const ancestorClip = ${ANCESTOR_CLIP_JS};
|
|
292
|
+
const scrollRange = ${SCROLL_RANGE_JS};
|
|
293
|
+
const scrollLock = ${SCROLL_LOCK_JS};
|
|
294
|
+
const invisibleReason = ${INVISIBLE_REASON_JS};
|
|
295
|
+
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
296
|
+
const reasons = ${JSON.stringify(LAYOUT_REASONS)};
|
|
297
|
+
const styleOf = (node) => node.ownerDocument.defaultView.getComputedStyle(node);
|
|
298
|
+
const parentOf = (node) => node.parentElement || (node.parentNode && node.parentNode.host) || null;
|
|
299
|
+
const shift = (r, x, y) => r && { left: r.left + x, top: r.top + y, right: r.right + x, bottom: r.bottom + y };
|
|
300
|
+
const intersect = (a, b) => {
|
|
301
|
+
if (!a || !b) return a || b;
|
|
302
|
+
const left = Math.max(a.left, b.left);
|
|
303
|
+
const top = Math.max(a.top, b.top);
|
|
304
|
+
return { left: left, top: top, right: Math.max(left, Math.min(a.right, b.right)), bottom: Math.max(top, Math.min(a.bottom, b.bottom)) };
|
|
305
|
+
};
|
|
306
|
+
const skippedReason = (node) => {
|
|
307
|
+
if (typeof node.checkVisibility !== 'function' || node.checkVisibility()) return null;
|
|
308
|
+
for (let child = node, p = parentOf(node); p; child = p, p = parentOf(p)) {
|
|
309
|
+
if (p.tagName === 'DETAILS' && !p.open && p.querySelector(':scope > summary') !== child) return 'inside a closed <details>';
|
|
310
|
+
if (styleOf(p).contentVisibility === 'hidden') return 'content-visibility: hidden on ' + describe(p);
|
|
311
|
+
}
|
|
312
|
+
return 'not rendered';
|
|
313
|
+
};
|
|
314
|
+
const hiddenReason = (style, box) => {
|
|
315
|
+
if (style.display === 'none') return 'display: none';
|
|
316
|
+
if (style.display === 'contents') return 'display: contents (no box of its own)';
|
|
317
|
+
if (el.getClientRects().length === 0) {
|
|
318
|
+
return el.tagName === 'OPTION' ? reasons.option : 'not rendered (an ancestor has display: none)';
|
|
319
|
+
}
|
|
320
|
+
const skipped = skippedReason(el);
|
|
321
|
+
if (skipped) return skipped;
|
|
322
|
+
if (style.visibility !== 'visible') return 'visibility: ' + style.visibility;
|
|
323
|
+
if (box.width === 0 || box.height === 0) return 'zero size';
|
|
324
|
+
return null;
|
|
325
|
+
};
|
|
326
|
+
const isSticky = () => {
|
|
327
|
+
for (let n = el; n; n = parentOf(n)) {
|
|
328
|
+
if (styleOf(n).position === 'sticky') return true;
|
|
329
|
+
}
|
|
330
|
+
return false;
|
|
331
|
+
};
|
|
332
|
+
const isInert = () => {
|
|
333
|
+
for (let n = el; n; n = n.parentElement || (n.getRootNode() && n.getRootNode().host) || null) {
|
|
334
|
+
if (n.hasAttribute('inert')) return true;
|
|
335
|
+
}
|
|
336
|
+
return false;
|
|
337
|
+
};
|
|
338
|
+
const box = el.getBoundingClientRect();
|
|
339
|
+
let rect = { left: box.left, top: box.top, right: box.right, bottom: box.bottom };
|
|
340
|
+
const own = ancestorClip(el, rect, describe);
|
|
341
|
+
let clip = own.clip;
|
|
342
|
+
let overlay = own.overlay;
|
|
343
|
+
let clipper = own.clipper;
|
|
344
|
+
let fixed = own.fixed;
|
|
345
|
+
let fixedBy = own.fixedBy;
|
|
346
|
+
let hidden = hiddenReason(styleOf(el), box) || own.collapsed;
|
|
347
|
+
let x = 0;
|
|
348
|
+
let y = 0;
|
|
349
|
+
for (let view = el.ownerDocument.defaultView; view && view.frameElement; view = view.parent) {
|
|
350
|
+
const frame = view.frameElement;
|
|
351
|
+
const size = viewportSize(view);
|
|
352
|
+
if (!clipper && (rect.left < 0 || rect.top < 0 || rect.right > size.width || rect.bottom > size.height)) clipper = describe(frame);
|
|
353
|
+
const unframed = clip;
|
|
354
|
+
clip = intersect(clip, { left: 0, top: 0, right: size.width, bottom: size.height });
|
|
355
|
+
overlay = { right: !!unframed && unframed.right <= clip.right && overlay.right, bottom: !!unframed && unframed.bottom <= clip.bottom && overlay.bottom };
|
|
356
|
+
const offset = frameOffset(frame);
|
|
357
|
+
rect = shift(rect, offset.x, offset.y);
|
|
358
|
+
const outer = ancestorClip(frame, rect, describe);
|
|
359
|
+
const inner = shift(clip, offset.x, offset.y);
|
|
360
|
+
clip = intersect(inner, outer.clip);
|
|
361
|
+
overlay = { right: clip.right < inner.right ? outer.overlay.right : overlay.right, bottom: clip.bottom < inner.bottom ? outer.overlay.bottom : overlay.bottom };
|
|
362
|
+
clipper = clipper || outer.clipper;
|
|
363
|
+
fixed = outer.fixed;
|
|
364
|
+
fixedBy = outer.fixedBy;
|
|
365
|
+
hidden = hidden || outer.collapsed;
|
|
366
|
+
x += offset.x;
|
|
367
|
+
y += offset.y;
|
|
368
|
+
if (!hidden && (frame.getClientRects().length === 0 || styleOf(frame).visibility !== 'visible' || skippedReason(frame))) hidden = 'inside a hidden iframe';
|
|
369
|
+
}
|
|
370
|
+
let top = el.ownerDocument.defaultView;
|
|
371
|
+
while (top.frameElement) top = top.parent;
|
|
372
|
+
const toBox = (r) => r && { x: r.left, y: r.top, width: r.right - r.left, height: r.bottom - r.top };
|
|
373
|
+
const invisible = hidden ? null : invisibleReason(el, describe);
|
|
374
|
+
const lock = scrollLock(top, describe);
|
|
375
|
+
if (lock && fixedBy === top.document.body) fixed = false;
|
|
376
|
+
return { rect: toBox(rect), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
|
|
377
|
+
}`;
|
|
378
|
+
/**
|
|
379
|
+
* Edges of a box.
|
|
380
|
+
*
|
|
381
|
+
* @param box - Position and size
|
|
382
|
+
* @returns Its edges
|
|
383
|
+
*/
|
|
384
|
+
function edgesOf(box) {
|
|
385
|
+
return { left: box.x, top: box.y, right: box.x + box.width, bottom: box.y + box.height };
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Area of the overlap of two boxes.
|
|
389
|
+
*
|
|
390
|
+
* @param a - First box
|
|
391
|
+
* @param b - Second box
|
|
392
|
+
* @returns Overlap, or null when they do not overlap
|
|
393
|
+
*/
|
|
394
|
+
function overlap(a, b) {
|
|
395
|
+
const result = {
|
|
396
|
+
left: Math.max(a.left, b.left),
|
|
397
|
+
top: Math.max(a.top, b.top),
|
|
398
|
+
right: Math.min(a.right, b.right),
|
|
399
|
+
bottom: Math.min(a.bottom, b.bottom),
|
|
400
|
+
};
|
|
401
|
+
return result.right > result.left && result.bottom > result.top ? result : null;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* The direction in which a box lies entirely outside another, vertical first
|
|
405
|
+
* (pages mostly scroll vertically).
|
|
406
|
+
*
|
|
407
|
+
* @param rect - Element box
|
|
408
|
+
* @param view - Visible area
|
|
409
|
+
* @returns Direction, or null when they overlap on both axes
|
|
410
|
+
*/
|
|
411
|
+
function directionOutside(rect, view) {
|
|
412
|
+
if (rect.bottom <= view.top)
|
|
413
|
+
return 'above';
|
|
414
|
+
if (rect.top >= view.bottom)
|
|
415
|
+
return 'below';
|
|
416
|
+
if (rect.right <= view.left)
|
|
417
|
+
return 'left';
|
|
418
|
+
if (rect.left >= view.right)
|
|
419
|
+
return 'right';
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
/** Rounding slack (px) when checking that a span is fully in view */
|
|
423
|
+
const IN_VIEW_SLACK = 1;
|
|
424
|
+
/**
|
|
425
|
+
* Length of a span that is inside the viewport after a scroll.
|
|
426
|
+
*
|
|
427
|
+
* @param start - Start of the span in viewport coordinates
|
|
428
|
+
* @param size - Length of the span
|
|
429
|
+
* @param viewSize - Length of the viewport
|
|
430
|
+
* @param scroll - Scroll along the axis (negative: back)
|
|
431
|
+
* @returns Visible length (0 when out of view)
|
|
432
|
+
*/
|
|
433
|
+
function visibleLength(start, size, viewSize, scroll) {
|
|
434
|
+
return Math.max(0, Math.min(start - scroll + size, viewSize) - Math.max(start - scroll, 0));
|
|
435
|
+
}
|
|
436
|
+
/**
|
|
437
|
+
* Where along one axis a scroll should bring a span's start (in the current
|
|
438
|
+
* viewport coordinates): centred, or just inside the edge cutting it off; a
|
|
439
|
+
* span larger than the viewport gets its start aligned either way.
|
|
440
|
+
*
|
|
441
|
+
* @param start - Start of the span in viewport coordinates
|
|
442
|
+
* @param size - Length of the span
|
|
443
|
+
* @param viewSize - Length of the viewport
|
|
444
|
+
* @param aim - Centre it, or the smallest scroll showing all of it
|
|
445
|
+
* @returns Scroll that would do it (negative: back), before limiting it to the page
|
|
446
|
+
*/
|
|
447
|
+
export function scrollTarget(start, size, viewSize, aim) {
|
|
448
|
+
if (size > viewSize)
|
|
449
|
+
return start;
|
|
450
|
+
if (aim === 'centre')
|
|
451
|
+
return start + size / 2 - viewSize / 2;
|
|
452
|
+
return start < 0 ? start : start + size - viewSize;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Page scroll along one axis that brings a span into view ({@link ScrollAim}).
|
|
456
|
+
* Limited to how far the page can scroll; when that is not far enough to
|
|
457
|
+
* show the span fully, no scroll helps.
|
|
458
|
+
*
|
|
459
|
+
* @param start - Start of the span in viewport coordinates
|
|
460
|
+
* @param size - Length of the span
|
|
461
|
+
* @param viewSize - Length of the viewport
|
|
462
|
+
* @param range - How far the page can scroll back (up or left) and forward (down or right)
|
|
463
|
+
* @param aim - Centre it, or the smallest scroll showing all of it
|
|
464
|
+
* @returns Pixels to scroll (negative: back), 0 when it is fully in view, null
|
|
465
|
+
* when the page cannot scroll far enough
|
|
466
|
+
*/
|
|
467
|
+
function axisScroll(start, size, viewSize, range, aim) {
|
|
468
|
+
const fully = Math.min(size, viewSize) - IN_VIEW_SLACK;
|
|
469
|
+
if (visibleLength(start, size, viewSize, 0) >= fully)
|
|
470
|
+
return 0;
|
|
471
|
+
const target = scrollTarget(start, size, viewSize, aim);
|
|
472
|
+
const scroll = Math.round(Math.min(range.forward, Math.max(-range.back, target))) || 0;
|
|
473
|
+
return visibleLength(start, size, viewSize, scroll) >= fully ? scroll : null;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Classify where an element is relative to the top-level viewport.
|
|
477
|
+
*
|
|
478
|
+
* Visible parts are what lies inside the viewport and the element's clip
|
|
479
|
+
* (iframes, overflow containers). An element with no visible part is `above`,
|
|
480
|
+
* `below`, `left` or `right` of the viewport (or of its clip, e.g. scrolled
|
|
481
|
+
* out of a list), and `hidden` when it is not rendered at all. Being inert
|
|
482
|
+
* does not change the position: inert elements are shown.
|
|
483
|
+
*
|
|
484
|
+
* @param geometry - Page-side measurements
|
|
485
|
+
* @param viewport - Top-level viewport size
|
|
486
|
+
* @returns Position, with the visible share, hidden reason, clipping
|
|
487
|
+
* ancestor or page scroll when relevant
|
|
488
|
+
*/
|
|
489
|
+
export function classifyViewportPosition(geometry, viewport) {
|
|
490
|
+
if (geometry.hidden)
|
|
491
|
+
return { inViewport: 'hidden', hiddenReason: geometry.hidden };
|
|
492
|
+
const rect = edgesOf(geometry.rect);
|
|
493
|
+
const screen = edgesOf({ x: 0, y: 0, ...viewport });
|
|
494
|
+
const view = geometry.clip ? overlap(screen, edgesOf(geometry.clip)) : screen;
|
|
495
|
+
const seen = view && overlap(rect, view);
|
|
496
|
+
if (!seen) {
|
|
497
|
+
const direction = directionOutside(rect, screen) ??
|
|
498
|
+
(geometry.clip && directionOutside(rect, edgesOf(geometry.clip)));
|
|
499
|
+
if (!direction) {
|
|
500
|
+
return {
|
|
501
|
+
inViewport: 'hidden',
|
|
502
|
+
hiddenReason: `clipped by ${geometry.clipper ?? 'an ancestor'}`,
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
return { inViewport: direction, ...outOfViewAdvice(geometry, viewport, 'centre') };
|
|
506
|
+
}
|
|
507
|
+
const area = (seen.right - seen.left) * (seen.bottom - seen.top);
|
|
508
|
+
const share = (area / (geometry.rect.width * geometry.rect.height)) * 100;
|
|
509
|
+
if (share >= 99.5)
|
|
510
|
+
return { inViewport: 'visible' };
|
|
511
|
+
const percentVisible = Math.min(99, Math.max(1, Math.round(share)));
|
|
512
|
+
return {
|
|
513
|
+
inViewport: 'partly',
|
|
514
|
+
percentVisible,
|
|
515
|
+
...outOfViewAdvice(geometry, viewport, 'minimal'),
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* What brings a not fully visible element into view: the ancestor or iframe
|
|
520
|
+
* cutting it off when there is one (page scroll alone would not help),
|
|
521
|
+
* otherwise the page scroll, unless the page scroll does not move it (fixed)
|
|
522
|
+
* or not all the way (sticky: the scroll needed cannot be told), or cannot go far enough (it is beyond the scrollable area, or the page does
|
|
523
|
+
* not scroll at all, e.g. locked by a dialog).
|
|
524
|
+
*
|
|
525
|
+
* @param geometry - Page-side measurements
|
|
526
|
+
* @param viewport - Top-level viewport size
|
|
527
|
+
* @param aim - Centre it (out of view), or the smallest scroll showing all of it (partly in view)
|
|
528
|
+
* @returns `clippedBy`, `scrollBy` or `offScreenReason`, or nothing when no
|
|
529
|
+
* scroll is needed
|
|
530
|
+
*/
|
|
531
|
+
function outOfViewAdvice(geometry, viewport, aim) {
|
|
532
|
+
if (geometry.clipper)
|
|
533
|
+
return { clippedBy: geometry.clipper };
|
|
534
|
+
if (geometry.fixed)
|
|
535
|
+
return { offScreenReason: OFF_SCREEN_REASONS.fixed };
|
|
536
|
+
if (geometry.sticky)
|
|
537
|
+
return { offScreenReason: OFF_SCREEN_REASONS.sticky };
|
|
538
|
+
const { rect, pageScroll } = geometry;
|
|
539
|
+
const horizontal = { back: pageScroll.left, forward: pageScroll.right };
|
|
540
|
+
const vertical = { back: pageScroll.up, forward: pageScroll.down };
|
|
541
|
+
const x = axisScroll(rect.x, rect.width, viewport.width, horizontal, aim);
|
|
542
|
+
const y = axisScroll(rect.y, rect.height, viewport.height, vertical, aim);
|
|
543
|
+
if (x === null || y === null) {
|
|
544
|
+
const { scrollLock } = geometry;
|
|
545
|
+
return {
|
|
546
|
+
offScreenReason: scrollLock
|
|
547
|
+
? scrollLockedReason(scrollLock.by, scrollLock.dialog)
|
|
548
|
+
: OFF_SCREEN_REASONS.outOfRange,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
return x === 0 && y === 0 ? {} : { scrollBy: { x, y } };
|
|
552
|
+
}
|
|
553
|
+
//# sourceMappingURL=elementGeometry.js.map
|