@db-ux/react-core-components 2.0.8 → 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.
@@ -4,6 +4,7 @@ import { filterPassingProps, getRootProps } from "../../utils/react";
4
4
  import { useState, useRef, useEffect, forwardRef } from "react";
5
5
  import { cls, getBoolean, getHideProp, uuid } from "../../utils";
6
6
  function DBRadioFn(props, component) {
7
+ var _a;
7
8
  const _ref = component || useRef(component);
8
9
  const [initialized, setInitialized] = useState(() => false);
9
10
  const [_id, set_id] = useState(() => undefined);
@@ -36,7 +37,7 @@ function DBRadioFn(props, component) {
36
37
  }
37
38
  }, [initialized, _ref.current, props.checked]);
38
39
  return (React.createElement("label", Object.assign({ "data-size": props.size, "data-hide-label": getHideProp(props.showLabel) }, 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-radio", props.className), htmlFor: _id }),
39
- React.createElement("input", Object.assign({ type: "radio", "aria-invalid": props.validation === "invalid", "data-custom-validity": props.validation, 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: _id, name: props.name, checked: getBoolean(props.checked, "checked"), disabled: getBoolean(props.disabled, "disabled"), "aria-describedby": props.describedbyid, value: props.value, required: getBoolean(props.required, "required"), onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event) })),
40
+ React.createElement("input", Object.assign({ type: "radio", "aria-invalid": props.validation === "invalid", "data-custom-validity": props.validation, 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: _id, name: props.name, checked: getBoolean(props.checked, "checked"), disabled: getBoolean(props.disabled, "disabled"), "aria-describedby": (_a = props.describedbyid) !== null && _a !== void 0 ? _a : props.ariaDescribedBy, value: props.value, required: getBoolean(props.required, "required"), onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event) })),
40
41
  props.label ? React.createElement(React.Fragment, null, props.label) : React.createElement(React.Fragment, null, props.children)));
41
42
  }
42
43
  const DBRadio = forwardRef(DBRadioFn);
@@ -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
  /**
@@ -6,7 +6,7 @@ import { cls, delay, getBoolean, getHideProp, hasVoiceOver, stringPropVisible, u
6
6
  import { DEFAULT_INVALID_MESSAGE, DEFAULT_INVALID_MESSAGE_ID_SUFFIX, DEFAULT_LABEL, DEFAULT_MESSAGE_ID_SUFFIX, DEFAULT_PLACEHOLDER_ID_SUFFIX, DEFAULT_VALID_MESSAGE, DEFAULT_VALID_MESSAGE_ID_SUFFIX, } from "../../shared/constants";
7
7
  import DBInfotext from "../infotext/infotext";
8
8
  function DBSelectFn(props, component) {
9
- var _a, _b, _c;
9
+ var _a, _b, _c, _d;
10
10
  const _ref = component || useRef(component);
11
11
  const [_id, set_id] = useState(() => undefined);
12
12
  const [_messageId, set_messageId] = useState(() => undefined);
@@ -52,8 +52,8 @@ function DBSelectFn(props, component) {
52
52
  }
53
53
  }
54
54
  function handleClick(event) {
55
- event.stopPropagation();
56
55
  if (props.onClick) {
56
+ event.stopPropagation();
57
57
  props.onClick(event);
58
58
  }
59
59
  }
@@ -126,15 +126,15 @@ function DBSelectFn(props, component) {
126
126
  }, [props.value]);
127
127
  return (React.createElement("div", Object.assign({}, 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-select", props.className), "data-variant": props.variant, "data-hide-label": getHideProp(props.showLabel), "data-icon": props.icon, "data-hide-icon": getHideProp(props.showIcon) }),
128
128
  React.createElement("label", { htmlFor: _id }, (_a = props.label) !== null && _a !== void 0 ? _a : DEFAULT_LABEL),
129
- React.createElement("select", Object.assign({ "aria-invalid": props.validation === "invalid", "data-custom-validity": props.validation, 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"]), { required: getBoolean(props.required, "required"), disabled: getBoolean(props.disabled, "disabled"), id: _id, name: props.name, size: props.size, value: props.value, autoComplete: props.autocomplete, multiple: props.multiple, onInput: (event) => handleInput(event), onClick: (event) => handleClick(event), onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event), "aria-describedby": _descByIds }),
129
+ React.createElement("select", Object.assign({ "aria-invalid": props.validation === "invalid", "data-custom-validity": props.validation, 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"]), { required: getBoolean(props.required, "required"), disabled: getBoolean(props.disabled, "disabled"), id: _id, name: props.name, size: props.size, value: props.value, autoComplete: props.autocomplete, multiple: props.multiple, onInput: (event) => handleInput(event), onClick: (event) => handleClick(event), onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event), "aria-describedby": (_b = props.ariaDescribedBy) !== null && _b !== void 0 ? _b : _descByIds }),
130
130
  React.createElement("option", { hidden: true }),
131
- props.options ? (React.createElement(React.Fragment, null, (_b = props.options) === null || _b === void 0 ? void 0 : _b.map((option) => {
131
+ props.options ? (React.createElement(React.Fragment, null, (_c = props.options) === null || _c === void 0 ? void 0 : _c.map((option) => {
132
132
  var _a;
133
133
  return (React.createElement(React.Fragment, { key: uuid() },
134
134
  option.options ? (React.createElement("optgroup", { label: getOptionLabel(option) }, (_a = option.options) === null || _a === void 0 ? void 0 : _a.map((optgroupOption) => (React.createElement("option", { key: optgroupOption.value.toString(), value: optgroupOption.value, disabled: optgroupOption.disabled }, getOptionLabel(optgroupOption)))))) : null,
135
135
  !option.options ? (React.createElement("option", { value: option.value, disabled: option.disabled }, getOptionLabel(option))) : null));
136
136
  }))) : (React.createElement(React.Fragment, null, props.children))),
137
- React.createElement("span", { id: _placeholderId }, (_c = props.placeholder) !== null && _c !== void 0 ? _c : props.label),
137
+ React.createElement("span", { id: _placeholderId }, (_d = props.placeholder) !== null && _d !== void 0 ? _d : props.label),
138
138
  stringPropVisible(props.message, props.showMessage) ? (React.createElement(DBInfotext, { size: "small", icon: props.messageIcon, id: _messageId }, props.message)) : null,
139
139
  hasValidState() ? (React.createElement(DBInfotext, { size: "small", semantic: "successful", id: _validMessageId }, props.validMessage || DEFAULT_VALID_MESSAGE)) : null,
140
140
  React.createElement(DBInfotext, { size: "small", semantic: "critical", id: _invalidMessageId }, _invalidMessage),
@@ -4,6 +4,7 @@ import { filterPassingProps, getRootProps } from "../../utils/react";
4
4
  import { useState, useRef, useEffect, forwardRef } from "react";
5
5
  import { cls, getBoolean, getBooleanAsString, getHideProp, uuid, } from "../../utils";
6
6
  function DBSwitchFn(props, component) {
7
+ var _a;
7
8
  const _ref = component || useRef(component);
8
9
  const [_id, set_id] = useState(() => undefined);
9
10
  const [_checked, set_checked] = useState(() => { var _a; return (_a = props["defaultChecked"]) !== null && _a !== void 0 ? _a : false; });
@@ -38,7 +39,7 @@ function DBSwitchFn(props, component) {
38
39
  }
39
40
  }, [props.checked]);
40
41
  return (React.createElement("label", Object.assign({ "data-visual-aid": getBooleanAsString(props.visualAid), "data-size": props.size, "data-hide-label": getHideProp(props.showLabel), "data-emphasis": props.emphasis, htmlFor: _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-switch", props.className) }),
41
- React.createElement("input", Object.assign({ type: "checkbox", role: "switch", id: _id, "aria-checked": getBooleanAsString(_checked), 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"]), { checked: getBoolean(props.checked, "checked"), value: props.value, disabled: getBoolean(props.disabled, "disabled"), "aria-describedby": props.describedbyid, "aria-invalid": props.validation === "invalid", "data-custom-validity": props.validation, name: props.name, required: getBoolean(props.required, "required"), "data-aid-icon": props.icon, "data-aid-icon-after": props.iconAfter, onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event) })),
42
+ React.createElement("input", Object.assign({ type: "checkbox", role: "switch", id: _id, "aria-checked": getBooleanAsString(_checked), 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"]), { checked: getBoolean(props.checked, "checked"), value: props.value, disabled: getBoolean(props.disabled, "disabled"), "aria-describedby": (_a = props.describedbyid) !== null && _a !== void 0 ? _a : props.ariaDescribedBy, "aria-invalid": props.validation === "invalid", "data-custom-validity": props.validation, name: props.name, required: getBoolean(props.required, "required"), "data-aid-icon": props.icon, "data-aid-icon-after": props.iconAfter, onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event) })),
42
43
  props.label ? React.createElement(React.Fragment, null, props.label) : React.createElement(React.Fragment, null, props.children)));
43
44
  }
44
45
  const DBSwitch = forwardRef(DBSwitchFn);
@@ -6,7 +6,7 @@ import DBInfotext from "../infotext/infotext";
6
6
  import { cls, delay, getBoolean, getHideProp, getNumber, hasVoiceOver, stringPropVisible, uuid, } from "../../utils";
7
7
  import { DEFAULT_INVALID_MESSAGE, DEFAULT_INVALID_MESSAGE_ID_SUFFIX, DEFAULT_LABEL, DEFAULT_MESSAGE_ID_SUFFIX, DEFAULT_PLACEHOLDER, DEFAULT_ROWS, DEFAULT_VALID_MESSAGE, DEFAULT_VALID_MESSAGE_ID_SUFFIX, } from "../../shared/constants";
8
8
  function DBTextareaFn(props, component) {
9
- var _a, _b;
9
+ var _a, _b, _c;
10
10
  const _ref = component || useRef(component);
11
11
  const [_id, set_id] = useState(() => undefined);
12
12
  const [_messageId, set_messageId] = useState(() => undefined);
@@ -107,7 +107,7 @@ function DBTextareaFn(props, component) {
107
107
  return (React.createElement("div", Object.assign({}, 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-textarea", props.className), "data-variant": props.variant, "data-hide-label": getHideProp(props.showLabel) }),
108
108
  React.createElement("label", { htmlFor: _id }, (_a = props.label) !== null && _a !== void 0 ? _a : DEFAULT_LABEL),
109
109
  React.createElement("textarea", Object.assign({ "aria-invalid": props.validation === "invalid", "data-custom-validity": props.validation, 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: _id, "data-resize": props.resize, disabled: getBoolean(props.disabled, "disabled"), required: getBoolean(props.required, "required"), readOnly: getBoolean(props.readOnly, "readOnly") ||
110
- getBoolean(props.readonly, "readonly"), form: props.form, maxLength: getNumber(props.maxLength, props.maxlength), minLength: getNumber(props.minLength, props.minlength), name: props.name, wrap: props.wrap, spellCheck: props.spellCheck, autoComplete: props.autocomplete, onInput: (event) => handleInput(event), onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event), value: props.value, "aria-describedby": _descByIds, placeholder: (_b = props.placeholder) !== null && _b !== void 0 ? _b : DEFAULT_PLACEHOLDER, rows: getNumber(props.rows, DEFAULT_ROWS), cols: getNumber(props.cols) })),
110
+ getBoolean(props.readonly, "readonly"), form: props.form, maxLength: getNumber(props.maxLength, props.maxlength), minLength: getNumber(props.minLength, props.minlength), name: props.name, wrap: props.wrap, spellCheck: props.spellCheck, autoComplete: props.autocomplete, onInput: (event) => handleInput(event), onChange: (event) => handleChange(event), onBlur: (event) => handleBlur(event), onFocus: (event) => handleFocus(event), value: props.value, "aria-describedby": (_b = props.ariaDescribedBy) !== null && _b !== void 0 ? _b : _descByIds, placeholder: (_c = props.placeholder) !== null && _c !== void 0 ? _c : DEFAULT_PLACEHOLDER, rows: getNumber(props.rows, DEFAULT_ROWS), cols: getNumber(props.cols) })),
111
111
  stringPropVisible(props.message, props.showMessage) ? (React.createElement(DBInfotext, { size: "small", icon: props.messageIcon, id: _messageId }, props.message)) : null,
112
112
  hasValidState() ? (React.createElement(DBInfotext, { size: "small", semantic: "successful", id: _validMessageId }, props.validMessage || DEFAULT_VALID_MESSAGE)) : null,
113
113
  React.createElement(DBInfotext, { size: "small", semantic: "critical", id: _invalidMessageId }, _invalidMessage),
@@ -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]);
@@ -14,6 +14,7 @@ export type GlobalProps = {
14
14
  */
15
15
  class?: string | any;
16
16
  /**
17
+ * @deprecated
17
18
  * [`aria-describedby`](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-describedby) is used to link to the elements that describe the element with the set attribute.
18
19
  */
19
20
  describedbyid?: string;
@@ -149,9 +150,6 @@ export type PopoverProps = {
149
150
  */
150
151
  width?: PopoverWidthType;
151
152
  };
152
- export type PopoverState = {
153
- handleAutoPlacement: () => void;
154
- };
155
153
  export type NameProps = {
156
154
  /**
157
155
  * The name attribute gives the name of the element to group it.
@@ -492,3 +490,14 @@ export type ValueLabelType = {
492
490
  value: string;
493
491
  label?: string;
494
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;
@@ -1,19 +1,19 @@
1
1
  import { uuid } from './index';
2
2
  export class DocumentClickListener {
3
3
  static runCallbacks(event) {
4
- Object.values(DocumentClickListener.callbacks).forEach(callback => {
4
+ for (const callback of Object.values(DocumentClickListener.callbacks)) {
5
5
  if (typeof callback === 'function') {
6
6
  callback(event);
7
7
  }
8
- });
8
+ }
9
9
  }
10
10
  constructor() {
11
11
  if (DocumentClickListener._instance) {
12
12
  return DocumentClickListener._instance;
13
13
  }
14
14
  DocumentClickListener._instance = this;
15
- if (document) {
16
- document.addEventListener('click', event => DocumentClickListener.runCallbacks(event));
15
+ if (self.document) {
16
+ self.document.addEventListener('click', event => DocumentClickListener.runCallbacks(event));
17
17
  }
18
18
  }
19
19
  addCallback(callback) {
@@ -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
+ };