nonChromiumPlatformUtils.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.97
  5. */
  6. import { e as rectToClientRect } from './floating-ui.js';
  7. /**
  8. * This module provides utils to fix positioning across shadow DOM in non-Chromium browsers
  9. *
  10. * It is based on floating-ui's distributable
  11. */
  12. /**
  13. * 👇 the following are needed to fix shadow DOM positioning 👇️
  14. *
  15. * @param element
  16. */
  17. function getTrueOffsetParent(element) {
  18. if (!isHTMLElement(element) || getComputedStyle(element).position === "fixed") {
  19. return null;
  20. }
  21. return composedOffsetParent(element);
  22. }
  23. /**
  24. * Polyfills the old offsetParent behavior from before the spec was changed:
  25. * https://github.com/w3c/csswg-drafts/issues/159
  26. *
  27. * @param element
  28. */
  29. function composedOffsetParent(element) {
  30. let { offsetParent } = element;
  31. let ancestor = element;
  32. let foundInsideSlot = false;
  33. while (ancestor && ancestor !== offsetParent) {
  34. const { assignedSlot } = ancestor;
  35. if (assignedSlot) {
  36. let newOffsetParent = assignedSlot.offsetParent;
  37. if (getComputedStyle(assignedSlot).display === "contents") {
  38. const hadStyleAttribute = assignedSlot.hasAttribute("style");
  39. const oldDisplay = assignedSlot.style.display;
  40. assignedSlot.style.display = getComputedStyle(ancestor).display;
  41. newOffsetParent = assignedSlot.offsetParent;
  42. assignedSlot.style.display = oldDisplay;
  43. if (!hadStyleAttribute) {
  44. assignedSlot.removeAttribute("style");
  45. }
  46. }
  47. ancestor = assignedSlot;
  48. if (offsetParent !== newOffsetParent) {
  49. offsetParent = newOffsetParent;
  50. foundInsideSlot = true;
  51. }
  52. }
  53. else if (isShadowRoot(ancestor) && ancestor.host && foundInsideSlot) {
  54. break;
  55. }
  56. ancestor = (isShadowRoot(ancestor) && ancestor.host) || ancestor.parentNode;
  57. }
  58. return offsetParent;
  59. }
  60. function getElementRects(_ref) {
  61. const { reference, floating, strategy } = _ref;
  62. return {
  63. reference: getRectRelativeToOffsetParent(reference, getOffsetParent(floating), strategy),
  64. floating: { ...getDimensions(floating), x: 0, y: 0 }
  65. };
  66. }
  67. /**
  68. * ☝️ the following are needed to fix shadow DOM positioning ☝️
  69. */
  70. /**
  71. * 👇 the following are taken directly from floating-ui's ESM distributable to support the exports above 👇️
  72. *
  73. * **Notes**:
  74. * unused functions are removed
  75. * ESLint is disabled
  76. * TS-warnings are suppressed
  77. */
  78. /* eslint-disable */
  79. function isWindow(value) {
  80. return value && value.document && value.location && value.alert && value.setInterval;
  81. }
  82. function getWindow(node) {
  83. if (node == null) {
  84. return window;
  85. }
  86. if (!isWindow(node)) {
  87. const ownerDocument = node.ownerDocument;
  88. return ownerDocument ? ownerDocument.defaultView || window : window;
  89. }
  90. return node;
  91. }
  92. function getComputedStyle(element) {
  93. return getWindow(element).getComputedStyle(element);
  94. }
  95. function getNodeName(node) {
  96. return isWindow(node) ? "" : node ? (node.nodeName || "").toLowerCase() : "";
  97. }
  98. function getUAString() {
  99. // @ts-ignore
  100. const uaData = navigator.userAgentData;
  101. if (uaData != null && uaData.brands) {
  102. return uaData.brands.map((item) => item.brand + "/" + item.version).join(" ");
  103. }
  104. return navigator.userAgent;
  105. }
  106. function isHTMLElement(value) {
  107. return value instanceof getWindow(value).HTMLElement;
  108. }
  109. function isElement(value) {
  110. return value instanceof getWindow(value).Element;
  111. }
  112. function isNode(value) {
  113. return value instanceof getWindow(value).Node;
  114. }
  115. function isShadowRoot(node) {
  116. // Browsers without `ShadowRoot` support
  117. if (typeof ShadowRoot === "undefined") {
  118. return false;
  119. }
  120. const OwnElement = getWindow(node).ShadowRoot;
  121. return node instanceof OwnElement || node instanceof ShadowRoot;
  122. }
  123. function isOverflowElement(element) {
  124. // Firefox wants us to check `-x` and `-y` variations as well
  125. const { overflow, overflowX, overflowY, display } = getComputedStyle(element);
  126. return (/auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX) && !["inline", "contents"].includes(display));
  127. }
  128. function isTableElement(element) {
  129. return ["table", "td", "th"].includes(getNodeName(element));
  130. }
  131. function isContainingBlock(element) {
  132. // TODO: Try and use feature detection here instead
  133. const isFirefox = /firefox/i.test(getUAString());
  134. const css = getComputedStyle(element); // This is non-exhaustive but covers the most common CSS properties that
  135. // create a containing block.
  136. // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block
  137. return (css.transform !== "none" ||
  138. css.perspective !== "none" ||
  139. (isFirefox && css.willChange === "filter") ||
  140. (isFirefox && (css.filter ? css.filter !== "none" : false)) ||
  141. ["transform", "perspective"].some((value) => css.willChange.includes(value)) ||
  142. ["paint", "layout", "strict", "content"].some(
  143. // TS 4.1 compat
  144. (value) => {
  145. const contain = css.contain;
  146. return contain != null ? contain.includes(value) : false;
  147. }));
  148. }
  149. function isLayoutViewport() {
  150. // Not Safari
  151. return !/^((?!chrome|android).)*safari/i.test(getUAString()); // Feature detection for this fails in various ways
  152. // • Always-visible scrollbar or not
  153. // • Width of <html>, etc.
  154. // const vV = win.visualViewport;
  155. // return vV ? Math.abs(win.innerWidth / vV.scale - vV.width) < 0.5 : true;
  156. }
  157. function isLastTraversableNode(node) {
  158. return ["html", "body", "#document"].includes(getNodeName(node));
  159. }
  160. const min = Math.min;
  161. const max = Math.max;
  162. const round = Math.round;
  163. function getBoundingClientRect(element, includeScale, isFixedStrategy) {
  164. var _win$visualViewport$o, _win$visualViewport, _win$visualViewport$o2, _win$visualViewport2;
  165. if (includeScale === void 0) {
  166. includeScale = false;
  167. }
  168. if (isFixedStrategy === void 0) {
  169. isFixedStrategy = false;
  170. }
  171. const clientRect = element.getBoundingClientRect();
  172. let scaleX = 1;
  173. let scaleY = 1;
  174. if (includeScale && isHTMLElement(element)) {
  175. scaleX = element.offsetWidth > 0 ? round(clientRect.width) / element.offsetWidth || 1 : 1;
  176. scaleY = element.offsetHeight > 0 ? round(clientRect.height) / element.offsetHeight || 1 : 1;
  177. }
  178. const win = isElement(element) ? getWindow(element) : window;
  179. const addVisualOffsets = !isLayoutViewport() && isFixedStrategy;
  180. const x = (clientRect.left +
  181. (addVisualOffsets
  182. ? (_win$visualViewport$o =
  183. (_win$visualViewport = win.visualViewport) == null ? void 0 : _win$visualViewport.offsetLeft) != null
  184. ? _win$visualViewport$o
  185. : 0
  186. : 0)) /
  187. scaleX;
  188. const y = (clientRect.top +
  189. (addVisualOffsets
  190. ? (_win$visualViewport$o2 =
  191. (_win$visualViewport2 = win.visualViewport) == null ? void 0 : _win$visualViewport2.offsetTop) != null
  192. ? _win$visualViewport$o2
  193. : 0
  194. : 0)) /
  195. scaleY;
  196. const width = clientRect.width / scaleX;
  197. const height = clientRect.height / scaleY;
  198. return {
  199. width,
  200. height,
  201. top: y,
  202. right: x + width,
  203. bottom: y + height,
  204. left: x,
  205. x,
  206. y
  207. };
  208. }
  209. function getDocumentElement(node) {
  210. return ((isNode(node) ? node.ownerDocument : node.document) || window.document).documentElement;
  211. }
  212. function getNodeScroll(element) {
  213. if (isElement(element)) {
  214. return {
  215. scrollLeft: element.scrollLeft,
  216. scrollTop: element.scrollTop
  217. };
  218. }
  219. return {
  220. scrollLeft: element.pageXOffset,
  221. scrollTop: element.pageYOffset
  222. };
  223. }
  224. function getWindowScrollBarX(element) {
  225. // If <html> has a CSS width greater than the viewport, then this will be
  226. // incorrect for RTL.
  227. // @ts-ignore
  228. return getBoundingClientRect(getDocumentElement(element)).left + getNodeScroll(element).scrollLeft;
  229. }
  230. function isScaled(element) {
  231. // @ts-ignore
  232. const rect = getBoundingClientRect(element);
  233. return round(rect.width) !== element.offsetWidth || round(rect.height) !== element.offsetHeight;
  234. }
  235. function getRectRelativeToOffsetParent(element, offsetParent, strategy) {
  236. const isOffsetParentAnElement = isHTMLElement(offsetParent);
  237. const documentElement = getDocumentElement(offsetParent);
  238. const rect = getBoundingClientRect(element, // @ts-ignore - checked above (TS 4.1 compat)
  239. isOffsetParentAnElement && isScaled(offsetParent), strategy === "fixed");
  240. let scroll = {
  241. scrollLeft: 0,
  242. scrollTop: 0
  243. };
  244. const offsets = {
  245. x: 0,
  246. y: 0
  247. };
  248. if (isOffsetParentAnElement || (!isOffsetParentAnElement && strategy !== "fixed")) {
  249. if (getNodeName(offsetParent) !== "body" || isOverflowElement(documentElement)) {
  250. scroll = getNodeScroll(offsetParent);
  251. }
  252. if (isHTMLElement(offsetParent)) {
  253. // @ts-ignore
  254. const offsetRect = getBoundingClientRect(offsetParent, true);
  255. offsets.x = offsetRect.x + offsetParent.clientLeft;
  256. offsets.y = offsetRect.y + offsetParent.clientTop;
  257. }
  258. else if (documentElement) {
  259. offsets.x = getWindowScrollBarX(documentElement);
  260. }
  261. }
  262. return {
  263. x: rect.left + scroll.scrollLeft - offsets.x,
  264. y: rect.top + scroll.scrollTop - offsets.y,
  265. width: rect.width,
  266. height: rect.height
  267. };
  268. }
  269. function getParentNode(node) {
  270. if (getNodeName(node) === "html") {
  271. return node;
  272. }
  273. return (
  274. // this is a quicker (but less type safe) way to save quite some bytes from the bundle
  275. // @ts-ignore
  276. node.assignedSlot || // step into the shadow DOM of the parent of a slotted node
  277. node.parentNode || // DOM Element detected
  278. (isShadowRoot(node) ? node.host : null) || // ShadowRoot detected
  279. getDocumentElement(node) // fallback
  280. );
  281. }
  282. function getContainingBlock(element) {
  283. let currentNode = getParentNode(element);
  284. if (isShadowRoot(currentNode)) {
  285. currentNode = currentNode.host;
  286. }
  287. while (isHTMLElement(currentNode) && !isLastTraversableNode(currentNode)) {
  288. if (isContainingBlock(currentNode)) {
  289. return currentNode;
  290. }
  291. else {
  292. const parent = currentNode.parentNode;
  293. currentNode = isShadowRoot(parent) ? parent.host : parent;
  294. }
  295. }
  296. return null;
  297. } // Gets the closest ancestor positioned element. Handles some edge cases,
  298. // such as table ancestors and cross browser bugs.
  299. function getOffsetParent(element) {
  300. const window = getWindow(element);
  301. let offsetParent = getTrueOffsetParent(element);
  302. while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === "static") {
  303. offsetParent = getTrueOffsetParent(offsetParent);
  304. }
  305. if (offsetParent &&
  306. (getNodeName(offsetParent) === "html" ||
  307. (getNodeName(offsetParent) === "body" &&
  308. getComputedStyle(offsetParent).position === "static" &&
  309. !isContainingBlock(offsetParent)))) {
  310. return window;
  311. }
  312. return offsetParent || getContainingBlock(element) || window;
  313. }
  314. function getDimensions(element) {
  315. if (isHTMLElement(element)) {
  316. return {
  317. width: element.offsetWidth,
  318. height: element.offsetHeight
  319. };
  320. }
  321. // @ts-ignore
  322. const rect = getBoundingClientRect(element);
  323. return {
  324. width: rect.width,
  325. height: rect.height
  326. };
  327. }
  328. function getViewportRect(element, strategy) {
  329. const win = getWindow(element);
  330. const html = getDocumentElement(element);
  331. const visualViewport = win.visualViewport;
  332. let width = html.clientWidth;
  333. let height = html.clientHeight;
  334. let x = 0;
  335. let y = 0;
  336. if (visualViewport) {
  337. width = visualViewport.width;
  338. height = visualViewport.height;
  339. const layoutViewport = isLayoutViewport();
  340. if (layoutViewport || (!layoutViewport && strategy === "fixed")) {
  341. x = visualViewport.offsetLeft;
  342. y = visualViewport.offsetTop;
  343. }
  344. }
  345. return {
  346. width,
  347. height,
  348. x,
  349. y
  350. };
  351. }
  352. // of the `<html>` and `<body>` rect bounds if horizontally scrollable
  353. function getDocumentRect(element) {
  354. var _element$ownerDocumen;
  355. const html = getDocumentElement(element);
  356. const scroll = getNodeScroll(element);
  357. const body = (_element$ownerDocumen = element.ownerDocument) == null ? void 0 : _element$ownerDocumen.body;
  358. const width = max(html.scrollWidth, html.clientWidth, body ? body.scrollWidth : 0, body ? body.clientWidth : 0);
  359. const height = max(html.scrollHeight, html.clientHeight, body ? body.scrollHeight : 0, body ? body.clientHeight : 0);
  360. let x = -scroll.scrollLeft + getWindowScrollBarX(element);
  361. const y = -scroll.scrollTop;
  362. if (getComputedStyle(body || html).direction === "rtl") {
  363. x += max(html.clientWidth, body ? body.clientWidth : 0) - width;
  364. }
  365. return {
  366. width,
  367. height,
  368. x,
  369. y
  370. };
  371. }
  372. function getNearestOverflowAncestor(node) {
  373. const parentNode = getParentNode(node);
  374. if (isLastTraversableNode(parentNode)) {
  375. // @ts-ignore assume body is always available
  376. return node.ownerDocument.body;
  377. }
  378. if (isHTMLElement(parentNode) && isOverflowElement(parentNode)) {
  379. return parentNode;
  380. }
  381. return getNearestOverflowAncestor(parentNode);
  382. }
  383. function getOverflowAncestors(node, list) {
  384. var _node$ownerDocument;
  385. if (list === void 0) {
  386. list = [];
  387. }
  388. const scrollableAncestor = getNearestOverflowAncestor(node);
  389. const isBody = scrollableAncestor === ((_node$ownerDocument = node.ownerDocument) == null ? void 0 : _node$ownerDocument.body);
  390. const win = getWindow(scrollableAncestor);
  391. const target = isBody
  392. ? [win].concat(win.visualViewport || [], isOverflowElement(scrollableAncestor) ? scrollableAncestor : [])
  393. : scrollableAncestor;
  394. const updatedList = list.concat(target);
  395. return isBody
  396. ? updatedList // @ts-ignore: isBody tells us target will be an HTMLElement here
  397. : updatedList.concat(getOverflowAncestors(target));
  398. }
  399. function contains(parent, child) {
  400. const rootNode = child.getRootNode == null ? void 0 : child.getRootNode(); // First, attempt with faster native method
  401. if (parent.contains(child)) {
  402. return true;
  403. } // then fallback to custom implementation with Shadow DOM support
  404. else if (rootNode && isShadowRoot(rootNode)) {
  405. let next = child;
  406. do {
  407. // use `===` replace node.isSameNode()
  408. if (next && parent === next) {
  409. return true;
  410. } // @ts-ignore: need a better way to handle this...
  411. next = next.parentNode || next.host;
  412. } while (next);
  413. }
  414. return false;
  415. }
  416. function getNearestParentCapableOfEscapingClipping(element, clippingAncestors) {
  417. let currentNode = element;
  418. while (currentNode && !isLastTraversableNode(currentNode) && !clippingAncestors.includes(currentNode)) {
  419. if (isElement(currentNode) && ["absolute", "fixed"].includes(getComputedStyle(currentNode).position)) {
  420. break;
  421. }
  422. const parentNode = getParentNode(currentNode);
  423. currentNode = isShadowRoot(parentNode) ? parentNode.host : parentNode;
  424. }
  425. return currentNode;
  426. }
  427. function getInnerBoundingClientRect(element, strategy) {
  428. const clientRect = getBoundingClientRect(element, false, strategy === "fixed");
  429. const top = clientRect.top + element.clientTop;
  430. const left = clientRect.left + element.clientLeft;
  431. return {
  432. top,
  433. left,
  434. x: left,
  435. y: top,
  436. right: left + element.clientWidth,
  437. bottom: top + element.clientHeight,
  438. width: element.clientWidth,
  439. height: element.clientHeight
  440. };
  441. }
  442. function getClientRectFromClippingAncestor(element, clippingParent, strategy) {
  443. if (clippingParent === "viewport") {
  444. return rectToClientRect(getViewportRect(element, strategy));
  445. }
  446. if (isElement(clippingParent)) {
  447. return getInnerBoundingClientRect(clippingParent, strategy);
  448. }
  449. return rectToClientRect(getDocumentRect(getDocumentElement(element)));
  450. } // A "clipping ancestor" is an overflowable container with the characteristic of
  451. // clipping (or hiding) overflowing elements with a position different from
  452. // `initial`
  453. function getClippingAncestors(element) {
  454. // @ts-ignore
  455. const clippingAncestors = getOverflowAncestors(element);
  456. const nearestEscapableParent = getNearestParentCapableOfEscapingClipping(element, clippingAncestors);
  457. let clipperElement = null;
  458. if (nearestEscapableParent && isHTMLElement(nearestEscapableParent)) {
  459. const offsetParent = getOffsetParent(nearestEscapableParent);
  460. if (isOverflowElement(nearestEscapableParent)) {
  461. clipperElement = nearestEscapableParent;
  462. }
  463. else if (isHTMLElement(offsetParent)) {
  464. clipperElement = offsetParent;
  465. }
  466. }
  467. if (!isElement(clipperElement)) {
  468. return [];
  469. } // @ts-ignore isElement check ensures we return Array<Element>
  470. return clippingAncestors.filter((clippingAncestors) => clipperElement &&
  471. isElement(clippingAncestors) &&
  472. contains(clippingAncestors, clipperElement) &&
  473. getNodeName(clippingAncestors) !== "body");
  474. } // Gets the maximum area that the element is visible in due to any number of
  475. // clipping ancestors
  476. function getClippingRect(_ref) {
  477. let { element, boundary, rootBoundary, strategy } = _ref;
  478. const mainClippingAncestors = boundary === "clippingAncestors" ? getClippingAncestors(element) : [].concat(boundary);
  479. const clippingAncestors = [...mainClippingAncestors, rootBoundary];
  480. const firstClippingAncestor = clippingAncestors[0];
  481. const clippingRect = clippingAncestors.reduce((accRect, clippingAncestor) => {
  482. const rect = getClientRectFromClippingAncestor(element, clippingAncestor, strategy);
  483. accRect.top = max(rect.top, accRect.top);
  484. accRect.right = min(rect.right, accRect.right);
  485. accRect.bottom = min(rect.bottom, accRect.bottom);
  486. accRect.left = max(rect.left, accRect.left);
  487. return accRect;
  488. }, getClientRectFromClippingAncestor(element, firstClippingAncestor, strategy));
  489. return {
  490. width: clippingRect.right - clippingRect.left,
  491. height: clippingRect.bottom - clippingRect.top,
  492. x: clippingRect.left,
  493. y: clippingRect.top
  494. };
  495. }
  496. export { getClippingRect, getElementRects, getOffsetParent };