@db-ux/react-core-components 2.0.9 → 2.0.10-popover-d7e8b9a

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.
@@ -2,7 +2,7 @@
2
2
  import * as React from "react";
3
3
  import { filterPassingProps, getRootProps } from "../../utils/react";
4
4
  import { useState, useRef, useEffect, forwardRef } from "react";
5
- import { cls, delay, getBoolean, getBooleanAsString, getHideProp, getSearchInput, handleDataOutside, hasVoiceOver, stringPropVisible, uuid, } from "../../utils";
5
+ import { cls, delay, getBoolean, getBooleanAsString, getHideProp, getSearchInput, hasVoiceOver, stringPropVisible, uuid, } from "../../utils";
6
6
  import { DEFAULT_CLOSE_BUTTON, DEFAULT_INVALID_MESSAGE, DEFAULT_INVALID_MESSAGE_ID_SUFFIX, DEFAULT_LABEL, DEFAULT_LABEL_ID_SUFFIX, DEFAULT_MESSAGE, DEFAULT_MESSAGE_ID_SUFFIX, DEFAULT_PLACEHOLDER_ID_SUFFIX, DEFAULT_REMOVE, DEFAULT_SELECT_ID_SUFFIX, DEFAULT_SELECTED, DEFAULT_VALID_MESSAGE, DEFAULT_VALID_MESSAGE_ID_SUFFIX, } from "../../shared/constants";
7
7
  import DBCustomSelectList from "../custom-select-list/custom-select-list";
8
8
  import DBCustomSelectListItem from "../custom-select-list-item/custom-select-list-item";
@@ -13,6 +13,8 @@ import DBButton from "../button/button";
13
13
  import DBTooltip from "../tooltip/tooltip";
14
14
  import DBInput from "../input/input";
15
15
  import { DocumentClickListener } from "../../utils/document-click-listener";
16
+ import { DocumentScrollListener } from "../../utils/document-scroll-listener";
17
+ import { handleFixedDropdown } from "../../utils/floating-components";
16
18
  function DBCustomSelectFn(props, component) {
17
19
  var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
18
20
  props = Object.assign({ clearSelectionText: "Clear selection", showClearSelection: true }, props);
@@ -45,6 +47,15 @@ function DBCustomSelectFn(props, component) {
45
47
  const [_hasNoOptions, set_hasNoOptions] = useState(() => false);
46
48
  const [_documentClickListenerCallbackId, set_documentClickListenerCallbackId,] = useState(() => undefined);
47
49
  const [_internalChangeTimestamp, set_internalChangeTimestamp] = useState(() => 0);
50
+ const [_documentScrollListenerCallbackId, set_documentScrollListenerCallbackId,] = useState(() => undefined);
51
+ const [_observer, set_observer] = useState(() => undefined);
52
+ function handleDocumentScroll(event) {
53
+ var _a, _b;
54
+ if (((_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.contains) &&
55
+ ((_b = event === null || event === void 0 ? void 0 : event.target) === null || _b === void 0 ? void 0 : _b.contains(detailsRef.current))) {
56
+ handleAutoPlacement();
57
+ }
58
+ }
48
59
  function hasValidState() {
49
60
  var _a;
50
61
  return !!((_a = props.validMessage) !== null && _a !== void 0 ? _a : props.validation === "valid");
@@ -92,7 +103,9 @@ function DBCustomSelectFn(props, component) {
92
103
  }
93
104
  if (event.target.open) {
94
105
  set_documentClickListenerCallbackId(new DocumentClickListener().addCallback((event) => handleDocumentClose(event)));
106
+ set_documentScrollListenerCallbackId(new DocumentScrollListener().addCallback((event) => handleDocumentScroll(event)));
95
107
  handleAutoPlacement();
108
+ _observer === null || _observer === void 0 ? void 0 : _observer.observe(detailsRef.current);
96
109
  if (!event.target.dataset.test) {
97
110
  // We need this workaround for snapshot testing
98
111
  handleOpenByKeyboardFocus();
@@ -102,6 +115,10 @@ function DBCustomSelectFn(props, component) {
102
115
  if (_documentClickListenerCallbackId) {
103
116
  new DocumentClickListener().removeCallback(_documentClickListenerCallbackId);
104
117
  }
118
+ if (_documentScrollListenerCallbackId) {
119
+ new DocumentScrollListener().removeCallback(_documentScrollListenerCallbackId);
120
+ }
121
+ _observer === null || _observer === void 0 ? void 0 : _observer.unobserve(detailsRef.current);
105
122
  }
106
123
  }
107
124
  function getNativeSelectValue() {
@@ -156,8 +173,10 @@ function DBCustomSelectFn(props, component) {
156
173
  if (detailsRef.current) {
157
174
  const dropdown = detailsRef.current.querySelector("article");
158
175
  if (dropdown) {
176
+ // This is a workaround for Angular
159
177
  delay(() => {
160
- handleDataOutside(dropdown);
178
+ var _a;
179
+ handleFixedDropdown(dropdown, detailsRef.current, (_a = props.placement) !== null && _a !== void 0 ? _a : "bottom");
161
180
  }, 1);
162
181
  }
163
182
  }
@@ -400,6 +419,14 @@ function DBCustomSelectFn(props, component) {
400
419
  set_selectedLabelsId(mId + "-selected-labels");
401
420
  set_infoTextId(mId + "-info");
402
421
  set_invalidMessage(props.invalidMessage || DEFAULT_INVALID_MESSAGE);
422
+ set_observer(new IntersectionObserver((payload) => {
423
+ if (detailsRef.current) {
424
+ const entry = payload.find(({ target }) => target === detailsRef.current);
425
+ if (entry && !entry.isIntersecting && detailsRef.current.open) {
426
+ detailsRef.current.open = false;
427
+ }
428
+ }
429
+ }));
403
430
  }, []);
404
431
  useEffect(() => {
405
432
  if (detailsRef.current) {
@@ -1,4 +1,4 @@
1
- import { BaseFormProps, CloseEventState, CustomFormProps, FormMessageProps, FormState, FromValidState, GlobalProps, GlobalState, IconProps, PlacementVerticalType, PopoverState, RequiredProps, ShowIconProps, ShowLabelProps, ValidationType, WidthType } from '../../shared/model';
1
+ import { BaseFormProps, CloseEventState, CustomFormProps, DocumentScrollState, FormMessageProps, FormState, FromValidState, GlobalProps, GlobalState, IconProps, PlacementVerticalType, RequiredProps, ShowIconProps, ShowLabelProps, ValidationType, WidthType } from '../../shared/model';
2
2
  import { DBCustomSelectFormFieldDefaultProps } from '../custom-select-form-field/model';
3
3
  import { CustomSelectDropdownWidthType } from '../custom-select-dropdown/model';
4
4
  import { DBCustomSelectListItemExtraProps } from '../custom-select-list-item/model';
@@ -189,5 +189,6 @@ export type DBCustomSelectDefaultState = {
189
189
  getSelectAllLabel: () => string;
190
190
  selectAllChecked: boolean;
191
191
  selectAllIndeterminate: boolean;
192
+ handleAutoPlacement: () => void;
192
193
  };
193
- export type DBCustomSelectState = DBCustomSelectDefaultState & GlobalState & FormState & FromValidState & CloseEventState & PopoverState;
194
+ export type DBCustomSelectState = DBCustomSelectDefaultState & GlobalState & FormState & FromValidState & CloseEventState & DocumentScrollState;
@@ -1,10 +1,8 @@
1
1
  import { GlobalProps, GlobalState, IconProps, TextProps } from '../../shared/model';
2
- export declare const IconVariantList: readonly ["default", "inverted", "filled"];
3
- export type IconVariantType = (typeof IconVariantList)[number];
4
2
  export declare const IconWeightList: readonly ["16", "20", "24", "32", "48", "64"];
5
3
  export type IconWeightType = (typeof IconWeightList)[number];
6
4
  export type DBIconDefaultProps = {
7
- variant?: IconVariantType;
5
+ variant?: string;
8
6
  weight?: IconWeightType;
9
7
  };
10
8
  export type DBIconProps = DBIconDefaultProps & GlobalProps & IconProps & TextProps;
@@ -1,2 +1 @@
1
- export const IconVariantList = ['default', 'inverted', 'filled'];
2
1
  export const IconWeightList = ['16', '20', '24', '32', '48', '64'];
@@ -13,6 +13,5 @@ export type DBPopoverProps = DBPopoverDefaultProps & GlobalProps & SpacingProps
13
13
  export type DBPopoverDefaultState = {
14
14
  isExpanded?: boolean;
15
15
  getTrigger: () => Element | null;
16
- handleLeave: (event: any) => void;
17
16
  };
18
17
  export type DBPopoverState = DBPopoverDefaultState & GlobalState & PopoverState & InitializedState;
@@ -2,29 +2,66 @@
2
2
  import * as React from "react";
3
3
  import { filterPassingProps, getRootProps } from "../../utils/react";
4
4
  import { useState, useRef, useEffect, forwardRef } from "react";
5
- import { cls, getBooleanAsString, handleDataOutside } from "../../utils";
5
+ import { cls, delay as utilsDelay, getBooleanAsString } from "../../utils";
6
+ import { handleFixedPopover } from "../../utils/floating-components";
7
+ import { DocumentScrollListener } from "../../utils/document-scroll-listener";
6
8
  function DBPopoverFn(props, component) {
7
9
  var _a;
8
10
  const _ref = component || useRef(component);
9
11
  const [initialized, setInitialized] = useState(() => false);
10
12
  const [isExpanded, setIsExpanded] = useState(() => false);
13
+ const [_documentScrollListenerCallbackId, set_documentScrollListenerCallbackId,] = useState(() => undefined);
14
+ const [_observer, set_observer] = useState(() => undefined);
15
+ function handleEscape(event) {
16
+ if (!event || event.key === "Escape") {
17
+ // TODO: Recursive for any child
18
+ for (const child of Array.from(_ref.current.children)) {
19
+ child.blur();
20
+ }
21
+ }
22
+ }
11
23
  function handleAutoPlacement() {
12
- setIsExpanded(true);
13
24
  if (!_ref.current)
14
25
  return;
15
26
  const article = _ref.current.querySelector("article");
16
- if (!article)
17
- return;
18
- handleDataOutside(article);
27
+ if (article) {
28
+ // This is a workaround for angular
29
+ utilsDelay(() => {
30
+ var _a;
31
+ handleFixedPopover(article, _ref.current, (_a = props.placement) !== null && _a !== void 0 ? _a : "bottom");
32
+ }, 1);
33
+ }
34
+ }
35
+ function handleDocumentScroll(event) {
36
+ var _a, _b;
37
+ if (((_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.contains) && ((_b = event === null || event === void 0 ? void 0 : event.target) === null || _b === void 0 ? void 0 : _b.contains(_ref.current))) {
38
+ handleAutoPlacement();
39
+ }
40
+ }
41
+ function handleEnter() {
42
+ setIsExpanded(true);
43
+ set_documentScrollListenerCallbackId(new DocumentScrollListener().addCallback((event) => handleDocumentScroll(event)));
44
+ handleAutoPlacement();
45
+ const child = getTrigger();
46
+ if (child) {
47
+ _observer === null || _observer === void 0 ? void 0 : _observer.observe(child);
48
+ }
19
49
  }
20
50
  function handleLeave(event) {
21
- const element = event.target;
22
- const parent = element.parentNode;
51
+ const element = event === null || event === void 0 ? void 0 : event.target;
52
+ const parent = element === null || element === void 0 ? void 0 : element.parentNode;
23
53
  if (!parent ||
24
54
  (element.parentNode.querySelector(":focus") !== element &&
25
55
  element.parentNode.querySelector(":focus-within") !== element &&
26
56
  element.parentNode.querySelector(":hover") !== element)) {
27
57
  setIsExpanded(false);
58
+ if (_documentScrollListenerCallbackId) {
59
+ new DocumentScrollListener().removeCallback(_documentScrollListenerCallbackId);
60
+ }
61
+ const child = getTrigger();
62
+ if (child) {
63
+ _observer === null || _observer === void 0 ? void 0 : _observer.unobserve(child);
64
+ }
28
65
  }
29
66
  }
30
67
  function getTrigger() {
@@ -51,11 +88,25 @@ function DBPopoverFn(props, component) {
51
88
  }, []);
52
89
  useEffect(() => {
53
90
  if (_ref.current && initialized) {
91
+ setInitialized(false);
54
92
  const child = getTrigger();
55
93
  if (child) {
56
94
  child.ariaHasPopup = "true";
57
95
  }
58
- setInitialized(false);
96
+ handleAutoPlacement();
97
+ _ref.current.addEventListener("keydown", (event) => handleEscape(event));
98
+ ["mouseenter", "focusin"].forEach((event) => {
99
+ _ref.current.addEventListener(event, () => handleEnter());
100
+ });
101
+ ["mouseleave", "focusout"].forEach((event) => {
102
+ _ref.current.addEventListener(event, () => handleLeave());
103
+ });
104
+ set_observer(new IntersectionObserver((payload) => {
105
+ const entry = payload.find(({ target }) => target === getTrigger());
106
+ if (entry && !entry.isIntersecting) {
107
+ handleEscape(false);
108
+ }
109
+ }));
59
110
  }
60
111
  }, [_ref.current, initialized]);
61
112
  useEffect(() => {
@@ -66,7 +117,7 @@ function DBPopoverFn(props, component) {
66
117
  }
67
118
  }
68
119
  }, [_ref.current, isExpanded]);
69
- return (React.createElement("div", Object.assign({ ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font"]), { id: props.id }, getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font"]), { className: cls("db-popover", props.className), onFocus: (event) => handleAutoPlacement(), onBlur: (event) => handleLeave(event), onMouseEnter: (event) => handleAutoPlacement(), onMouseLeave: (event) => handleLeave(event) }),
120
+ return (React.createElement("div", Object.assign({ ref: _ref }, filterPassingProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font"]), { id: props.id }, getRootProps(props, ["data-icon-variant", "data-icon-variant-before", "data-icon-variant-after", "data-icon-weight", "data-icon-weight-before", "data-icon-weight-after", "data-interactive", "data-force-mobile", "data-color", "data-container-color", "data-bg-color", "data-on-bg-color", "data-color-scheme", "data-font-size", "data-headline-size", "data-divider", "data-focus", "data-font"]), { className: cls("db-popover", props.className) }),
70
121
  React.createElement(React.Fragment, null, props.trigger),
71
122
  React.createElement("article", { className: "db-popover-content", "data-spacing": props.spacing, "data-gap": getBooleanAsString(props.gap), "data-animation": getBooleanAsString((_a = props.animation) !== null && _a !== void 0 ? _a : true), "data-open": getBooleanAsString(props.open), "data-delay": props.delay, "data-width": props.width, "data-placement": props.placement }, props.children)));
72
123
  }
@@ -1,7 +1,8 @@
1
1
  import { ChangeEventProps, ChangeEventState, ClickEventProps, ClickEventState, FocusEventProps, FocusEventState, FormMessageProps, FormProps, FormSizeProps, FormState, FromValidState, GlobalProps, GlobalState, IconProps, InitializedState, InputEventProps, InputEventState, ShowIconProps } from '../../shared/model';
2
2
  export type DBSelectDefaultProps = {
3
3
  /**
4
- * Enable multiple select -> use DBCustomSelect/db-custom-select for a better look
4
+ * @deprecated
5
+ * Enables multiple select, but it isn't styled, please use DBCustomSelect/db-custom-select instead
5
6
  */
6
7
  multiple?: boolean;
7
8
  /**
@@ -1,7 +1,9 @@
1
- import { ClickEventState, EmphasisProps, GlobalProps, GlobalState, InitializedState, PlacementProps, PopoverProps, PopoverState } from '../../shared/model';
1
+ import { ClickEventState, DocumentScrollState, EmphasisProps, GlobalProps, GlobalState, InitializedState, PlacementProps, PopoverProps, PopoverState } from '../../shared/model';
2
2
  export type DBTooltipDefaultProps = {
3
3
  showArrow?: boolean | string;
4
4
  };
5
5
  export type DBTooltipProps = DBTooltipDefaultProps & GlobalProps & EmphasisProps & PlacementProps & PopoverProps;
6
- export type DBTooltipDefaultState = {};
7
- export type DBTooltipState = DBTooltipDefaultState & GlobalState & ClickEventState<HTMLElement> & PopoverState & InitializedState;
6
+ export type DBTooltipDefaultState = {
7
+ getParent: () => HTMLElement;
8
+ };
9
+ export type DBTooltipState = DBTooltipDefaultState & GlobalState & ClickEventState<HTMLElement> & PopoverState & InitializedState & DocumentScrollState;
@@ -2,19 +2,62 @@
2
2
  import * as React from "react";
3
3
  import { filterPassingProps, getRootProps } from "../../utils/react";
4
4
  import { useState, useRef, useEffect, forwardRef } from "react";
5
- import { cls, getBooleanAsString, handleDataOutside, uuid } from "../../utils";
5
+ import { cls, delay as utilsDelay, getBooleanAsString, uuid, } from "../../utils";
6
6
  import { DEFAULT_ID } from "../../shared/constants";
7
+ import { handleFixedPopover } from "../../utils/floating-components";
8
+ import { DocumentScrollListener } from "../../utils/document-scroll-listener";
7
9
  function DBTooltipFn(props, component) {
8
10
  var _a, _b;
9
11
  const _ref = component || useRef(component);
10
12
  const [_id, set_id] = useState(() => DEFAULT_ID);
11
13
  const [initialized, setInitialized] = useState(() => false);
14
+ const [_documentScrollListenerCallbackId, set_documentScrollListenerCallbackId,] = useState(() => undefined);
15
+ const [_observer, set_observer] = useState(() => undefined);
12
16
  function handleClick(event) {
13
17
  event.stopPropagation();
14
18
  }
15
- function handleAutoPlacement() {
16
- if (_ref.current)
17
- handleDataOutside(_ref.current);
19
+ function handleEscape(event) {
20
+ if ((!event || event.key === "Escape") &&
21
+ _ref.current &&
22
+ getComputedStyle(_ref.current).visibility === "visible") {
23
+ getParent().blur();
24
+ }
25
+ }
26
+ function getParent() {
27
+ let parent = _ref.current.parentElement;
28
+ if (parent && parent.localName.includes("tooltip")) {
29
+ // Angular workaround
30
+ parent = parent.parentElement;
31
+ }
32
+ return parent;
33
+ }
34
+ function handleAutoPlacement(parent) {
35
+ if (!parent)
36
+ return;
37
+ if (_ref.current) {
38
+ // This is a workaround for angular
39
+ utilsDelay(() => {
40
+ var _a;
41
+ handleFixedPopover(_ref.current, parent, (_a = props.placement) !== null && _a !== void 0 ? _a : "bottom");
42
+ }, 1);
43
+ }
44
+ }
45
+ function handleDocumentScroll(event, parent) {
46
+ var _a, _b;
47
+ if (((_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.contains) && ((_b = event === null || event === void 0 ? void 0 : event.target) === null || _b === void 0 ? void 0 : _b.contains(_ref.current))) {
48
+ handleAutoPlacement(parent);
49
+ }
50
+ }
51
+ function handleLeave() {
52
+ if (_documentScrollListenerCallbackId) {
53
+ new DocumentScrollListener().removeCallback(_documentScrollListenerCallbackId);
54
+ }
55
+ _observer === null || _observer === void 0 ? void 0 : _observer.unobserve(getParent());
56
+ }
57
+ function handleEnter(parent) {
58
+ set_documentScrollListenerCallbackId(new DocumentScrollListener().addCallback((event) => handleDocumentScroll(event, parent)));
59
+ handleAutoPlacement(parent);
60
+ _observer === null || _observer === void 0 ? void 0 : _observer.observe(getParent());
18
61
  }
19
62
  useEffect(() => {
20
63
  set_id(props.id || "tooltip-" + uuid());
@@ -22,18 +65,24 @@ function DBTooltipFn(props, component) {
22
65
  }, []);
23
66
  useEffect(() => {
24
67
  if (_ref.current && initialized && _id) {
25
- let parent = _ref.current.parentElement;
26
- if (parent && parent.localName.includes("tooltip")) {
27
- // Angular workaround
28
- parent = parent.parentElement;
29
- }
68
+ const parent = getParent();
30
69
  if (parent) {
31
- ["mouseenter", "focus"].forEach((event) => {
32
- parent.addEventListener(event, () => handleAutoPlacement());
70
+ ["mouseenter", "focusin"].forEach((event) => {
71
+ parent.addEventListener(event, () => handleEnter(parent));
72
+ });
73
+ parent.addEventListener("keydown", (event) => handleEscape(event));
74
+ ["mouseleave", "focusout"].forEach((event) => {
75
+ parent.addEventListener(event, () => handleLeave());
33
76
  });
34
77
  parent.setAttribute("data-has-tooltip", "true");
35
78
  parent.setAttribute("aria-describedby", _id);
36
79
  }
80
+ set_observer(new IntersectionObserver((payload) => {
81
+ const entry = payload.find(({ target }) => target === getParent());
82
+ if (entry && !entry.isIntersecting) {
83
+ handleEscape(false);
84
+ }
85
+ }));
37
86
  setInitialized(false);
38
87
  }
39
88
  }, [_ref.current, initialized]);
@@ -150,9 +150,6 @@ export type PopoverProps = {
150
150
  */
151
151
  width?: PopoverWidthType;
152
152
  };
153
- export type PopoverState = {
154
- handleAutoPlacement: () => void;
155
- };
156
153
  export type NameProps = {
157
154
  /**
158
155
  * The name attribute gives the name of the element to group it.
@@ -493,3 +490,14 @@ export type ValueLabelType = {
493
490
  value: string;
494
491
  label?: string;
495
492
  };
493
+ export type DocumentScrollState = {
494
+ _documentScrollListenerCallbackId?: string;
495
+ handleDocumentScroll: (event: any, parent?: HTMLElement) => void;
496
+ _observer?: IntersectionObserver;
497
+ };
498
+ export type PopoverState = {
499
+ handleEscape: (event: any) => void;
500
+ handleAutoPlacement: (parent?: HTMLElement) => void;
501
+ handleEnter: (parent?: HTMLElement) => void;
502
+ handleLeave: (event?: any) => void;
503
+ } & DocumentScrollState;
@@ -0,0 +1,9 @@
1
+ export declare class DocumentScrollListener {
2
+ private static callbacks;
3
+ private static _instance;
4
+ private static runCallbacks;
5
+ private ticking;
6
+ constructor();
7
+ addCallback(callback: (event: any) => void): string;
8
+ removeCallback(id: string): void;
9
+ }
@@ -0,0 +1,38 @@
1
+ import { uuid } from './index';
2
+ export class DocumentScrollListener {
3
+ static runCallbacks(event) {
4
+ for (const callback of Object.values(DocumentScrollListener.callbacks)) {
5
+ if (typeof callback === 'function') {
6
+ callback(event);
7
+ }
8
+ }
9
+ }
10
+ constructor() {
11
+ this.ticking = false;
12
+ if (DocumentScrollListener._instance) {
13
+ return DocumentScrollListener._instance;
14
+ }
15
+ DocumentScrollListener._instance = this;
16
+ if (self.document) {
17
+ self.document.addEventListener('scroll', event => {
18
+ if (!this.ticking) {
19
+ window.requestAnimationFrame(() => {
20
+ DocumentScrollListener.runCallbacks(event);
21
+ this.ticking = false;
22
+ });
23
+ this.ticking = true;
24
+ }
25
+ }, true);
26
+ }
27
+ }
28
+ addCallback(callback) {
29
+ const callbackID = uuid();
30
+ DocumentScrollListener.callbacks[callbackID] = callback;
31
+ return callbackID;
32
+ }
33
+ removeCallback(id) {
34
+ delete DocumentScrollListener.callbacks[id];
35
+ }
36
+ }
37
+ DocumentScrollListener.callbacks = {};
38
+ DocumentScrollListener._instance = null;
@@ -0,0 +1,7 @@
1
+ export interface DBDataOutsidePair {
2
+ vx?: 'left' | 'right';
3
+ vy?: 'top' | 'bottom';
4
+ }
5
+ export declare const handleDataOutside: (el: HTMLElement) => DBDataOutsidePair;
6
+ export declare const handleFixedDropdown: (element: HTMLElement, parent: HTMLElement, placement: string) => void;
7
+ export declare const handleFixedPopover: (element: HTMLElement, parent: HTMLElement, placement: string) => void;
@@ -0,0 +1,290 @@
1
+ // TODO: We should reevaluate this as soon as CSS Anchor Positioning is supported in all relevant browsers
2
+ const isInView = (el) => {
3
+ var _a;
4
+ const { top, bottom, left, right } = el.getBoundingClientRect();
5
+ const { innerHeight, innerWidth } = window;
6
+ let outTop = top < 0;
7
+ let outBottom = bottom > innerHeight;
8
+ let outLeft = left < 0;
9
+ let outRight = right > innerWidth;
10
+ // We need to check if it was already outside
11
+ const outsideY = el.dataset['outsideVy'];
12
+ const outsideX = el.dataset['outsideVx'];
13
+ const parentRect = (_a = el === null || el === void 0 ? void 0 : el.parentElement) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
14
+ if (parentRect) {
15
+ if (outsideY) {
16
+ const position = el.dataset['outsideVy'];
17
+ if (position === 'top') {
18
+ outTop = parentRect.top - (bottom - parentRect.bottom) < 0;
19
+ }
20
+ else {
21
+ outBottom = parentRect.bottom + (parentRect.top - top) > innerHeight;
22
+ }
23
+ }
24
+ if (outsideX) {
25
+ const position = el.dataset['outsideVx'];
26
+ if (position === 'left') {
27
+ outLeft = parentRect.left - (right - parentRect.right) < 0;
28
+ }
29
+ else {
30
+ outRight = parentRect.right + (parentRect.left - left) > innerWidth;
31
+ }
32
+ }
33
+ }
34
+ return {
35
+ outTop,
36
+ outBottom,
37
+ outLeft,
38
+ outRight
39
+ };
40
+ };
41
+ export const handleDataOutside = (el) => {
42
+ const { outTop, outBottom, outLeft, outRight } = isInView(el);
43
+ let dataOutsidePair = {};
44
+ if (outTop || outBottom) {
45
+ dataOutsidePair = {
46
+ vy: outTop ? 'top' : 'bottom'
47
+ };
48
+ el.dataset['outsideVy'] = dataOutsidePair.vy;
49
+ }
50
+ else {
51
+ delete el.dataset['outsideVy'];
52
+ }
53
+ if (outLeft || outRight) {
54
+ dataOutsidePair = Object.assign(Object.assign({}, dataOutsidePair), { vx: outRight ? 'right' : 'left' });
55
+ el.dataset['outsideVx'] = dataOutsidePair.vx;
56
+ }
57
+ else {
58
+ delete el.dataset['outsideVx'];
59
+ }
60
+ return dataOutsidePair;
61
+ };
62
+ export const handleFixedDropdown = (element, parent, placement) => {
63
+ // We skip this if we are in mobile it's already fixed
64
+ if (getComputedStyle(element).zIndex === '9999')
65
+ return;
66
+ const { top, bottom, childHeight, childWidth, width, right, left, correctedPlacement } = getFloatingProps(element, parent, placement);
67
+ const fullWidth = element.dataset['width'] === 'full';
68
+ if (fullWidth) {
69
+ element.style.inlineSize = `${width}px`;
70
+ }
71
+ if (correctedPlacement === 'top' || correctedPlacement === 'bottom' || correctedPlacement === 'top-start' || correctedPlacement === 'bottom-start') {
72
+ element.style.insetInlineStart = `${left}px`;
73
+ }
74
+ else if (correctedPlacement === 'top-end' || correctedPlacement === 'bottom-end') {
75
+ element.style.insetInlineStart = `${right - childWidth}px`;
76
+ }
77
+ if (correctedPlacement === null || correctedPlacement === void 0 ? void 0 : correctedPlacement.startsWith('top')) {
78
+ element.style.insetBlockStart = `${top - childHeight}px`;
79
+ }
80
+ else if (correctedPlacement === null || correctedPlacement === void 0 ? void 0 : correctedPlacement.startsWith('bottom')) {
81
+ element.style.insetBlockStart = `${bottom}px`;
82
+ }
83
+ element.style.position = 'fixed';
84
+ };
85
+ const getFloatingProps = (element, parent, placement) => {
86
+ const childRect = element.getBoundingClientRect();
87
+ const { top, height, bottom, right, left, width } = parent.getBoundingClientRect();
88
+ const { innerHeight, innerWidth } = window;
89
+ let childHeight = childRect.height;
90
+ let childWidth = childRect.width;
91
+ if (placement === 'bottom' || placement === 'top') {
92
+ childWidth = childWidth / 2;
93
+ }
94
+ if (placement === 'left' || placement === 'right') {
95
+ childHeight = childHeight / 2;
96
+ }
97
+ const outsideBottom = bottom + childHeight > innerHeight;
98
+ const outsideTop = top - childHeight < 0;
99
+ const outsideLeft = left - childWidth < 0;
100
+ const outsideRight = right + childWidth > innerWidth;
101
+ let correctedPlacement = placement;
102
+ if (placement.startsWith('bottom')) {
103
+ if (outsideBottom) {
104
+ correctedPlacement = placement === null || placement === void 0 ? void 0 : placement.replace('bottom', 'top');
105
+ if (outsideLeft && outsideRight) {
106
+ correctedPlacement = 'top';
107
+ }
108
+ else if (outsideLeft) {
109
+ correctedPlacement = 'top-start';
110
+ }
111
+ else if (outsideRight) {
112
+ correctedPlacement = 'top-end';
113
+ }
114
+ }
115
+ else {
116
+ if (outsideLeft && outsideRight) {
117
+ correctedPlacement = 'bottom';
118
+ }
119
+ else if (outsideLeft) {
120
+ correctedPlacement = 'bottom-start';
121
+ }
122
+ else if (outsideRight) {
123
+ correctedPlacement = 'bottom-end';
124
+ }
125
+ }
126
+ }
127
+ else if (placement.startsWith('top')) {
128
+ if (outsideTop) {
129
+ correctedPlacement = placement === null || placement === void 0 ? void 0 : placement.replace('top', 'bottom');
130
+ if (outsideLeft && outsideRight) {
131
+ correctedPlacement = 'bottom';
132
+ }
133
+ else if (outsideLeft) {
134
+ correctedPlacement = 'bottom-start';
135
+ }
136
+ else if (outsideRight) {
137
+ correctedPlacement = 'bottom-end';
138
+ }
139
+ }
140
+ else {
141
+ if (outsideLeft && outsideRight) {
142
+ correctedPlacement = 'top';
143
+ }
144
+ else if (outsideLeft) {
145
+ correctedPlacement = 'top-start';
146
+ }
147
+ else if (outsideRight) {
148
+ correctedPlacement = 'top-end';
149
+ }
150
+ }
151
+ }
152
+ else if (placement.startsWith('left')) {
153
+ if (outsideLeft) {
154
+ correctedPlacement = placement === null || placement === void 0 ? void 0 : placement.replace('left', 'right');
155
+ if (outsideBottom && outsideTop) {
156
+ correctedPlacement = 'right';
157
+ }
158
+ else if (outsideBottom) {
159
+ correctedPlacement = 'right-end';
160
+ }
161
+ else if (outsideTop) {
162
+ correctedPlacement = 'right-start';
163
+ }
164
+ }
165
+ else {
166
+ if (outsideBottom && outsideTop) {
167
+ correctedPlacement = 'left';
168
+ }
169
+ else if (outsideBottom) {
170
+ correctedPlacement = 'left-end';
171
+ }
172
+ else if (outsideTop) {
173
+ correctedPlacement = 'left-start';
174
+ }
175
+ }
176
+ }
177
+ else if (correctedPlacement.startsWith('right')) {
178
+ if (outsideRight) {
179
+ correctedPlacement = placement === null || placement === void 0 ? void 0 : placement.replace('right', 'left');
180
+ if (outsideBottom && outsideTop) {
181
+ correctedPlacement = 'left';
182
+ }
183
+ else if (outsideBottom) {
184
+ correctedPlacement = 'left-end';
185
+ }
186
+ else if (outsideTop) {
187
+ correctedPlacement = 'left-start';
188
+ }
189
+ }
190
+ else {
191
+ if (outsideBottom && outsideTop) {
192
+ correctedPlacement = 'right';
193
+ }
194
+ else if (outsideBottom) {
195
+ correctedPlacement = 'right-end';
196
+ }
197
+ else if (outsideTop) {
198
+ correctedPlacement = 'right-start';
199
+ }
200
+ }
201
+ }
202
+ return {
203
+ top,
204
+ bottom,
205
+ right,
206
+ height,
207
+ width,
208
+ left,
209
+ childHeight: childRect.height,
210
+ childWidth: childRect.width,
211
+ correctedPlacement,
212
+ innerWidth,
213
+ innerHeight
214
+ };
215
+ };
216
+ export const handleFixedPopover = (element, parent, placement) => {
217
+ var _a;
218
+ const distance = (_a = getComputedStyle(element).getPropertyValue('--db-popover-distance')) !== null && _a !== void 0 ? _a : '0px';
219
+ const { top, height, width, childHeight, childWidth, right, left, bottom, correctedPlacement, innerWidth, innerHeight } = getFloatingProps(element, parent, placement);
220
+ // Tooltip arrow position
221
+ if (childWidth > width && (correctedPlacement.startsWith('bottom') || correctedPlacement.startsWith('top'))) {
222
+ const diff = width / 2 / childWidth * 100;
223
+ if (correctedPlacement.endsWith('start')) {
224
+ element.style.setProperty('--db-tooltip-arrow-inline-start', `${diff}%`);
225
+ }
226
+ else if (correctedPlacement.endsWith('end')) {
227
+ element.style.setProperty('--db-tooltip-arrow-inline-start', `${100 - diff}%`);
228
+ }
229
+ }
230
+ if (childHeight > height && (correctedPlacement.startsWith('left') || correctedPlacement.startsWith('bottom'))) {
231
+ const diff = height / 2 / childHeight * 100;
232
+ if (correctedPlacement.endsWith('start')) {
233
+ element.style.setProperty('--db-tooltip-arrow-block-start', `${diff}%`);
234
+ }
235
+ else if (correctedPlacement.endsWith('end')) {
236
+ element.style.setProperty('--db-tooltip-arrow-block-start', `${100 - diff}%`);
237
+ }
238
+ }
239
+ // Popover position
240
+ if (correctedPlacement === 'right' || correctedPlacement === 'left') {
241
+ // center horizontally
242
+ element.style.insetBlockStart = `${top + height / 2}px`;
243
+ }
244
+ else if (correctedPlacement === 'right-start' || correctedPlacement === 'left-start') {
245
+ const end = top + childHeight;
246
+ element.style.insetBlockStart = `${top}px`;
247
+ element.style.insetBlockEnd = `${end > innerHeight ? innerHeight : end}px`;
248
+ }
249
+ else if (correctedPlacement === 'right-end' || correctedPlacement === 'left-end') {
250
+ const start = bottom - childHeight;
251
+ element.style.insetBlockStart = `${start < 0 ? 0 : start}px`;
252
+ element.style.insetBlockEnd = `${bottom}px`;
253
+ }
254
+ else if (correctedPlacement === 'top' || correctedPlacement === 'bottom') {
255
+ // center vertically
256
+ element.style.insetInlineStart = `${left + width / 2}px`;
257
+ }
258
+ else if (correctedPlacement === 'top-start' || correctedPlacement === 'bottom-start') {
259
+ const end = left + childWidth;
260
+ element.style.insetInlineStart = `${left}px`;
261
+ element.style.insetInlineEnd = `${end > innerWidth ? innerWidth : end}px`;
262
+ }
263
+ else if (correctedPlacement === 'top-end' || correctedPlacement === 'bottom-end') {
264
+ const start = left - childWidth;
265
+ element.style.insetInlineStart = `${right - childWidth}px`;
266
+ element.style.insetInlineEnd = `${start < 0 ? 0 : start}px`;
267
+ }
268
+ if (correctedPlacement === null || correctedPlacement === void 0 ? void 0 : correctedPlacement.startsWith('right')) {
269
+ const end = right + childWidth;
270
+ element.style.insetInlineStart = `calc(${right}px + ${distance})`;
271
+ element.style.insetInlineEnd = `calc(${end > innerWidth ? innerWidth : end}px + ${distance})`;
272
+ }
273
+ else if (correctedPlacement === null || correctedPlacement === void 0 ? void 0 : correctedPlacement.startsWith('left')) {
274
+ const start = left - childWidth;
275
+ element.style.insetInlineStart = `calc(${start < 0 ? 0 : start}px - ${distance})`;
276
+ element.style.insetInlineEnd = `calc(${right}px - ${distance})`;
277
+ }
278
+ else if (correctedPlacement === null || correctedPlacement === void 0 ? void 0 : correctedPlacement.startsWith('top')) {
279
+ const start = top - childHeight;
280
+ element.style.insetBlockStart = `calc(${start < 0 ? 0 : start}px - ${distance})`;
281
+ element.style.insetBlockEnd = `calc(${bottom}px - ${distance})`;
282
+ }
283
+ else if (correctedPlacement === null || correctedPlacement === void 0 ? void 0 : correctedPlacement.startsWith('bottom')) {
284
+ const end = bottom + childHeight;
285
+ element.style.insetBlockStart = `calc(${bottom}px + ${distance})`;
286
+ element.style.insetBlockEnd = `calc(${end > innerHeight ? innerHeight : end}px + ${distance})`;
287
+ }
288
+ element.style.position = 'fixed';
289
+ element.setAttribute('data-corrected-placement', correctedPlacement);
290
+ };
@@ -7,19 +7,6 @@ export type ClassNameArg = string | {
7
7
  [key: string]: boolean | undefined;
8
8
  } | undefined;
9
9
  export declare const cls: (...args: ClassNameArg[]) => string;
10
- export declare const visibleInVX: (el: Element) => boolean;
11
- export declare const visibleInVY: (el: Element) => boolean;
12
- export declare const isInView: (el: Element) => {
13
- outTop: boolean;
14
- outBottom: boolean;
15
- outLeft: boolean;
16
- outRight: boolean;
17
- };
18
- export interface DBDataOutsidePair {
19
- vx?: 'left' | 'right';
20
- vy?: 'top' | 'bottom';
21
- }
22
- export declare const handleDataOutside: (el: Element) => DBDataOutsidePair;
23
10
  export declare const isArrayOfStrings: (value: unknown) => value is string[];
24
11
  export declare const hasVoiceOver: () => boolean;
25
12
  export declare const delay: (fn: () => void, ms: number) => Promise<unknown>;
@@ -37,76 +37,6 @@ export const cls = (...args) => {
37
37
  }
38
38
  return result.trim();
39
39
  };
40
- export const visibleInVX = (el) => {
41
- const { left, right } = el.getBoundingClientRect();
42
- const { innerWidth } = window;
43
- return left >= 0 && right <= innerWidth;
44
- };
45
- export const visibleInVY = (el) => {
46
- const { top, bottom } = el.getBoundingClientRect();
47
- const { innerHeight } = window;
48
- return top >= 0 && bottom <= innerHeight;
49
- };
50
- export const isInView = (el) => {
51
- var _a;
52
- const { top, bottom, left, right } = el.getBoundingClientRect();
53
- const { innerHeight, innerWidth } = window;
54
- let outTop = top < 0;
55
- let outBottom = bottom > innerHeight;
56
- let outLeft = left < 0;
57
- let outRight = right > innerWidth;
58
- // We need to check if it was already outside
59
- const outsideY = el.hasAttribute('data-outside-vy');
60
- const outsideX = el.hasAttribute('data-outside-vx');
61
- const parentRect = (_a = el === null || el === void 0 ? void 0 : el.parentElement) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect();
62
- if (parentRect) {
63
- if (outsideY) {
64
- const position = el.getAttribute('data-outside-vy');
65
- if (position === 'top') {
66
- outTop = parentRect.top - (bottom - parentRect.bottom) < 0;
67
- }
68
- else {
69
- outBottom = parentRect.bottom + (parentRect.top - top) > innerHeight;
70
- }
71
- }
72
- if (outsideX) {
73
- const position = el.getAttribute('data-outside-vx');
74
- if (position === 'left') {
75
- outLeft = parentRect.left - (right - parentRect.right) < 0;
76
- }
77
- else {
78
- outRight = parentRect.right + (parentRect.left - left) > innerWidth;
79
- }
80
- }
81
- }
82
- return {
83
- outTop,
84
- outBottom,
85
- outLeft,
86
- outRight
87
- };
88
- };
89
- export const handleDataOutside = (el) => {
90
- const { outTop, outBottom, outLeft, outRight } = isInView(el);
91
- let dataOutsidePair = {};
92
- if (outTop || outBottom) {
93
- dataOutsidePair = {
94
- vy: outTop ? 'top' : 'bottom'
95
- };
96
- el.setAttribute('data-outside-vy', dataOutsidePair.vy);
97
- }
98
- else {
99
- el.removeAttribute('data-outside-vy');
100
- }
101
- if (outLeft || outRight) {
102
- dataOutsidePair = Object.assign(Object.assign({}, dataOutsidePair), { vx: outRight ? 'right' : 'left' });
103
- el.setAttribute('data-outside-vx', dataOutsidePair.vx);
104
- }
105
- else {
106
- el.removeAttribute('data-outside-vx');
107
- }
108
- return dataOutsidePair;
109
- };
110
40
  export const isArrayOfStrings = (value) => Array.isArray(value) && value.every(item => typeof item === 'string');
111
41
  const appleOs = ['Mac', 'iPhone', 'iPad', 'iPod'];
112
42
  export const hasVoiceOver = () => typeof window !== 'undefined' && appleOs.some(os => window.navigator.userAgent.includes(os));
@@ -15,7 +15,7 @@ export declare class NavigationItemSafeTriangle {
15
15
  private initialized;
16
16
  private mouseX;
17
17
  private mouseY;
18
- constructor(element: HTMLElement | null, subNavigation: Element | null);
18
+ constructor(element: HTMLElement | null, subNavigation: HTMLElement | null);
19
19
  private init;
20
20
  enableFollow(): void;
21
21
  disableFollow(): void;
@@ -1,4 +1,4 @@
1
- import { handleDataOutside } from './index';
1
+ import { handleDataOutside } from './floating-components';
2
2
  export const isEventTargetNavigationItem = (event) => {
3
3
  var _a, _b;
4
4
  const { target } = event;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@db-ux/react-core-components",
3
- "version": "2.0.9",
3
+ "version": "2.0.10-popover-d7e8b9a",
4
4
  "description": "React components for @db-ux/core-components",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,7 +38,7 @@
38
38
  },
39
39
  "sideEffects": false,
40
40
  "dependencies": {
41
- "@db-ux/core-components": "2.0.9",
42
- "@db-ux/core-foundations": "2.0.9"
41
+ "@db-ux/core-components": "2.0.10-popover-d7e8b9a",
42
+ "@db-ux/core-foundations": "2.0.10-popover-d7e8b9a"
43
43
  }
44
44
  }