@momentum-design/components 0.118.5 → 0.118.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/index.js +198 -198
- package/dist/browser/index.js.map +4 -4
- package/dist/components/popover/index.js +2 -0
- package/dist/components/popover/popover.component.d.ts +44 -8
- package/dist/components/popover/popover.component.js +53 -11
- package/dist/components/popover/popover.portal.component.d.ts +29 -0
- package/dist/components/popover/popover.portal.component.js +37 -0
- package/dist/components/popover/popover.utils.d.ts +15 -0
- package/dist/components/popover/popover.utils.js +32 -3
- package/dist/custom-elements.json +432 -412
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +2 -2
- package/dist/react/popover/index.d.ts +43 -7
- package/dist/react/popover/index.js +43 -7
- package/package.json +1 -1
@@ -1,5 +1,7 @@
|
|
1
1
|
import Popover from './popover.component';
|
2
|
+
import { PopoverPortal, TAG_NAME as POPOVER_PORTAL_TAG_NAME } from './popover.portal.component';
|
2
3
|
import { TAG_NAME } from './popover.constants';
|
3
4
|
import '../button';
|
4
5
|
Popover.register(TAG_NAME);
|
6
|
+
PopoverPortal.register(POPOVER_PORTAL_TAG_NAME);
|
5
7
|
export default Popover;
|
@@ -3,20 +3,56 @@ import { Component } from '../../models';
|
|
3
3
|
import type { PopoverColor, PopoverPlacement, PopoverStrategy, PopoverTrigger } from './popover.types';
|
4
4
|
declare const Popover_base: import("../../utils/mixins/index.types").Constructor<Component & import("../../utils/mixins/BackdropMixin").BackdropMixinInterface> & import("../../utils/mixins/index.types").Constructor<Component & import("../../utils/mixins/PreventScrollMixin").PreventScrollMixinInterface> & import("../../utils/mixins/index.types").Constructor<Component & import("../../utils/mixins/FocusTrapMixin").FocusTrapClassInterface> & typeof Component;
|
5
5
|
/**
|
6
|
-
* Popover
|
7
|
-
*
|
6
|
+
* Popover is genric overlay which can be trigered by any actinable element.
|
7
|
+
*
|
8
|
+
* It can be used for tooltips, dropdowns, menus or any showing any other contextual content.
|
9
|
+
*
|
8
10
|
* The popover automatically positions itself based on available space and
|
9
|
-
* supports dynamic height adjustments with scrollable content when needed
|
11
|
+
* supports dynamic height adjustments with scrollable content when needed.
|
12
|
+
* It uses [Floating UI](https://floating-ui.com/) for maintaining the position of the popover.
|
13
|
+
*
|
14
|
+
* ## Limitations
|
15
|
+
*
|
16
|
+
* ### On trigger for multiple popovers
|
17
|
+
*
|
18
|
+
* A component (button, etc.) can trigger more than one popover, but only one of them should change the
|
19
|
+
* aria-expanded and aria-haspopup attributes on the trigger.
|
20
|
+
*
|
21
|
+
* To prevent unexpected attribute changes on the trigger `disable-aria-expanded` attribute must be set on all linked
|
22
|
+
* Popoers except one.
|
10
23
|
*
|
11
|
-
*
|
12
|
-
*
|
13
|
-
*
|
24
|
+
* ### React Popover with append-to attribute
|
25
|
+
*
|
26
|
+
* React mounts the popover based on the virtual DOM, but when the append-to attribute is set, the popover removes itself
|
27
|
+
* and mounts to the specified element. React will not know about the move and will not know about the
|
28
|
+
* newly created mdc-popoverportal element either. This throws a `NotFoundError` error when the Popover is directly
|
29
|
+
* added/removed by React, for example:
|
30
|
+
*
|
31
|
+
* ```tsx
|
32
|
+
* const SomeComponent = () => {
|
33
|
+
* const [isOpen, setIsOpen] = useState(false);
|
34
|
+
* return (<div>
|
35
|
+
* {isOpen && <Popover append-to="some-element-id">...</mdc-popover>}
|
36
|
+
* </div>);
|
37
|
+
* }
|
38
|
+
* ```
|
39
|
+
* As a workaround Popover need to wrap with any other element/component, for example:
|
40
|
+
* ```tsx
|
41
|
+
* const SomeComponent = () => {
|
42
|
+
* const [isOpen, setIsOpen] = useState(false);
|
43
|
+
* return (<div>
|
44
|
+
* {isOpen && <div>
|
45
|
+
* <Popover append-to="some-element-id">...</mdc-popover>
|
46
|
+
* <div>}
|
47
|
+
* </div>);
|
48
|
+
* }
|
49
|
+
* ```
|
50
|
+
* Note the wrapper <div> around the Popover component (React.Fragment does not work).
|
14
51
|
*
|
15
52
|
* @dependency mdc-button
|
16
53
|
*
|
17
54
|
* @tagname mdc-popover
|
18
55
|
*
|
19
|
-
*
|
20
56
|
* @event shown - (React: onShown) This event is dispatched when the popover is shown
|
21
57
|
* @event hidden - (React: onHidden) This event is dispatched when the popover is hidden
|
22
58
|
* @event created - (React: onCreated) This event is dispatched when the popover is created (added to the DOM)
|
@@ -312,9 +348,9 @@ declare class Popover extends Popover_base {
|
|
312
348
|
*/
|
313
349
|
get triggerElement(): HTMLElement | null;
|
314
350
|
constructor();
|
351
|
+
private parseTrigger;
|
315
352
|
protected firstUpdated(changedProperties: PropertyValues): Promise<void>;
|
316
353
|
connectedCallback(): void;
|
317
|
-
private parseTrigger;
|
318
354
|
disconnectedCallback(): Promise<void>;
|
319
355
|
/**
|
320
356
|
* Sets up the trigger related event listeners, based on the trigger type.
|
@@ -22,20 +22,56 @@ import { popoverStack } from './popover.stack';
|
|
22
22
|
import styles from './popover.styles';
|
23
23
|
import { PopoverUtils } from './popover.utils';
|
24
24
|
/**
|
25
|
-
* Popover
|
26
|
-
*
|
25
|
+
* Popover is genric overlay which can be trigered by any actinable element.
|
26
|
+
*
|
27
|
+
* It can be used for tooltips, dropdowns, menus or any showing any other contextual content.
|
28
|
+
*
|
27
29
|
* The popover automatically positions itself based on available space and
|
28
|
-
* supports dynamic height adjustments with scrollable content when needed
|
30
|
+
* supports dynamic height adjustments with scrollable content when needed.
|
31
|
+
* It uses [Floating UI](https://floating-ui.com/) for maintaining the position of the popover.
|
32
|
+
*
|
33
|
+
* ## Limitations
|
34
|
+
*
|
35
|
+
* ### On trigger for multiple popovers
|
36
|
+
*
|
37
|
+
* A component (button, etc.) can trigger more than one popover, but only one of them should change the
|
38
|
+
* aria-expanded and aria-haspopup attributes on the trigger.
|
39
|
+
*
|
40
|
+
* To prevent unexpected attribute changes on the trigger `disable-aria-expanded` attribute must be set on all linked
|
41
|
+
* Popoers except one.
|
42
|
+
*
|
43
|
+
* ### React Popover with append-to attribute
|
44
|
+
*
|
45
|
+
* React mounts the popover based on the virtual DOM, but when the append-to attribute is set, the popover removes itself
|
46
|
+
* and mounts to the specified element. React will not know about the move and will not know about the
|
47
|
+
* newly created mdc-popoverportal element either. This throws a `NotFoundError` error when the Popover is directly
|
48
|
+
* added/removed by React, for example:
|
29
49
|
*
|
30
|
-
*
|
31
|
-
*
|
32
|
-
*
|
50
|
+
* ```tsx
|
51
|
+
* const SomeComponent = () => {
|
52
|
+
* const [isOpen, setIsOpen] = useState(false);
|
53
|
+
* return (<div>
|
54
|
+
* {isOpen && <Popover append-to="some-element-id">...</mdc-popover>}
|
55
|
+
* </div>);
|
56
|
+
* }
|
57
|
+
* ```
|
58
|
+
* As a workaround Popover need to wrap with any other element/component, for example:
|
59
|
+
* ```tsx
|
60
|
+
* const SomeComponent = () => {
|
61
|
+
* const [isOpen, setIsOpen] = useState(false);
|
62
|
+
* return (<div>
|
63
|
+
* {isOpen && <div>
|
64
|
+
* <Popover append-to="some-element-id">...</mdc-popover>
|
65
|
+
* <div>}
|
66
|
+
* </div>);
|
67
|
+
* }
|
68
|
+
* ```
|
69
|
+
* Note the wrapper <div> around the Popover component (React.Fragment does not work).
|
33
70
|
*
|
34
71
|
* @dependency mdc-button
|
35
72
|
*
|
36
73
|
* @tagname mdc-popover
|
37
74
|
*
|
38
|
-
*
|
39
75
|
* @event shown - (React: onShown) This event is dispatched when the popover is shown
|
40
76
|
* @event hidden - (React: onHidden) This event is dispatched when the popover is hidden
|
41
77
|
* @event created - (React: onCreated) This event is dispatched when the popover is created (added to the DOM)
|
@@ -632,12 +668,12 @@ class Popover extends BackdropMixin(PreventScrollMixin(FocusTrapMixin(Component)
|
|
632
668
|
}
|
633
669
|
async firstUpdated(changedProperties) {
|
634
670
|
super.firstUpdated(changedProperties);
|
635
|
-
this.style.zIndex = `${this.zIndex}`;
|
636
|
-
this.utils.setupAppendTo();
|
637
671
|
PopoverEventManager.onCreatedPopover(this);
|
638
672
|
}
|
639
673
|
connectedCallback() {
|
640
674
|
super.connectedCallback();
|
675
|
+
this.style.zIndex = `${this.zIndex}`;
|
676
|
+
this.utils.setupAppendTo();
|
641
677
|
this.setupTriggerListeners();
|
642
678
|
}
|
643
679
|
async disconnectedCallback() {
|
@@ -656,6 +692,7 @@ class Popover extends BackdropMixin(PreventScrollMixin(FocusTrapMixin(Component)
|
|
656
692
|
this.connectedTooltip.shouldSuppressOpening = false;
|
657
693
|
}
|
658
694
|
}
|
695
|
+
this.utils.cleanupAppendTo();
|
659
696
|
PopoverEventManager.onDestroyedPopover(this);
|
660
697
|
popoverStack.remove(this);
|
661
698
|
}
|
@@ -683,8 +720,13 @@ class Popover extends BackdropMixin(PreventScrollMixin(FocusTrapMixin(Component)
|
|
683
720
|
if (changedProperties.has('zIndex')) {
|
684
721
|
this.setAttribute('z-index', `${this.zIndex}`);
|
685
722
|
}
|
686
|
-
if (changedProperties.has('
|
687
|
-
this.
|
723
|
+
if (changedProperties.has('appendTo')) {
|
724
|
+
if (this.appendTo) {
|
725
|
+
this.utils.setupAppendTo();
|
726
|
+
}
|
727
|
+
else {
|
728
|
+
this.utils.cleanupAppendTo();
|
729
|
+
}
|
688
730
|
}
|
689
731
|
if (changedProperties.has('interactive') ||
|
690
732
|
changedProperties.has('aria-label') ||
|
@@ -0,0 +1,29 @@
|
|
1
|
+
import { Component } from '../../models';
|
2
|
+
export declare const TAG_NAME: "mdc-popoverportal";
|
3
|
+
/**
|
4
|
+
* PopoverPortal in a placeholder component
|
5
|
+
*
|
6
|
+
* When the popover appended to another container, this component is used to mark the original place of the
|
7
|
+
* popover in the DOM. When the portal removed from the DOM, we remove the popover from the container as well.
|
8
|
+
*
|
9
|
+
* We need this behavior to support on hover menus. Without the portal:
|
10
|
+
* - Each time when then consumer renders the menu we append a new instance of the popover to the container which
|
11
|
+
* cause memory leak.
|
12
|
+
* - Trigger component will open all popovers at once, because all of them has the same triggerID and all
|
13
|
+
* listeners attached to the document.
|
14
|
+
*
|
15
|
+
* Portal component make sure the popover clean up when it was normally (without append-to) removed from the DOM.
|
16
|
+
* This is especially important when the popover is used in a framework like React or Angular, where virtual does not
|
17
|
+
* know about the popover moved to another place in the DOM.
|
18
|
+
*
|
19
|
+
* @internal
|
20
|
+
*/
|
21
|
+
export declare class PopoverPortal extends Component {
|
22
|
+
onDisconnect: Function | undefined;
|
23
|
+
connectedCallback(): void;
|
24
|
+
/**
|
25
|
+
* When the portal removed from the DOM, we remove the popover from the container as well.
|
26
|
+
* @internal
|
27
|
+
*/
|
28
|
+
disconnectedCallback(): void;
|
29
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { Component } from '../../models';
|
2
|
+
import utils from '../../utils/tag-name';
|
3
|
+
export const TAG_NAME = utils.constructTagName('popoverportal');
|
4
|
+
/**
|
5
|
+
* PopoverPortal in a placeholder component
|
6
|
+
*
|
7
|
+
* When the popover appended to another container, this component is used to mark the original place of the
|
8
|
+
* popover in the DOM. When the portal removed from the DOM, we remove the popover from the container as well.
|
9
|
+
*
|
10
|
+
* We need this behavior to support on hover menus. Without the portal:
|
11
|
+
* - Each time when then consumer renders the menu we append a new instance of the popover to the container which
|
12
|
+
* cause memory leak.
|
13
|
+
* - Trigger component will open all popovers at once, because all of them has the same triggerID and all
|
14
|
+
* listeners attached to the document.
|
15
|
+
*
|
16
|
+
* Portal component make sure the popover clean up when it was normally (without append-to) removed from the DOM.
|
17
|
+
* This is especially important when the popover is used in a framework like React or Angular, where virtual does not
|
18
|
+
* know about the popover moved to another place in the DOM.
|
19
|
+
*
|
20
|
+
* @internal
|
21
|
+
*/
|
22
|
+
export class PopoverPortal extends Component {
|
23
|
+
connectedCallback() {
|
24
|
+
super.connectedCallback();
|
25
|
+
// We don't want the portal to be focusable or visible for screen readers
|
26
|
+
this.ariaHidden = 'true';
|
27
|
+
}
|
28
|
+
/**
|
29
|
+
* When the portal removed from the DOM, we remove the popover from the container as well.
|
30
|
+
* @internal
|
31
|
+
*/
|
32
|
+
disconnectedCallback() {
|
33
|
+
var _a;
|
34
|
+
super.disconnectedCallback();
|
35
|
+
(_a = this.onDisconnect) === null || _a === void 0 ? void 0 : _a.call(this);
|
36
|
+
}
|
37
|
+
}
|
@@ -2,6 +2,17 @@ import type Popover from './popover.component';
|
|
2
2
|
export declare class PopoverUtils {
|
3
3
|
/** @internal */
|
4
4
|
private popover;
|
5
|
+
/**
|
6
|
+
* The portal element used when the popover is appended to another container.
|
7
|
+
* @internal
|
8
|
+
*/
|
9
|
+
private portalElement;
|
10
|
+
/**
|
11
|
+
* Flag to indicate if the popover was diconnected because it was appended to another container, or
|
12
|
+
* it was actually removed from the DOM.
|
13
|
+
* @internal
|
14
|
+
*/
|
15
|
+
private disconnectAfterAppendTo;
|
5
16
|
/** @internal */
|
6
17
|
private arrowPixelChange;
|
7
18
|
constructor(popover: Popover);
|
@@ -24,6 +35,10 @@ export declare class PopoverUtils {
|
|
24
35
|
* DOM element by its ID, and appends this popover as a child of that element.
|
25
36
|
*/
|
26
37
|
setupAppendTo(): void;
|
38
|
+
/**
|
39
|
+
* Remove portal component to when the popover appended to somewhere else and removed from the DOM
|
40
|
+
*/
|
41
|
+
cleanupAppendTo(): void;
|
27
42
|
/**
|
28
43
|
* Sets up the aria labels
|
29
44
|
*/
|
@@ -1,6 +1,18 @@
|
|
1
1
|
import { ROLE } from '../../utils/roles';
|
2
|
+
import { TAG_NAME as POPOVER_PORTAL_TAG_NAME } from './popover.portal.component';
|
2
3
|
export class PopoverUtils {
|
3
4
|
constructor(popover) {
|
5
|
+
/**
|
6
|
+
* The portal element used when the popover is appended to another container.
|
7
|
+
* @internal
|
8
|
+
*/
|
9
|
+
this.portalElement = null;
|
10
|
+
/**
|
11
|
+
* Flag to indicate if the popover was diconnected because it was appended to another container, or
|
12
|
+
* it was actually removed from the DOM.
|
13
|
+
* @internal
|
14
|
+
*/
|
15
|
+
this.disconnectAfterAppendTo = false;
|
4
16
|
/** @internal */
|
5
17
|
this.arrowPixelChange = false;
|
6
18
|
this.popover = popover;
|
@@ -78,13 +90,30 @@ export class PopoverUtils {
|
|
78
90
|
* DOM element by its ID, and appends this popover as a child of that element.
|
79
91
|
*/
|
80
92
|
setupAppendTo() {
|
93
|
+
var _a, _b;
|
81
94
|
if (this.popover.appendTo) {
|
82
|
-
const
|
83
|
-
if (
|
84
|
-
|
95
|
+
const appendToEl = document.getElementById(this.popover.appendTo);
|
96
|
+
if (appendToEl && !Array.from(appendToEl.children).includes(this.popover)) {
|
97
|
+
this.disconnectAfterAppendTo = true;
|
98
|
+
this.portalElement = document.createElement(POPOVER_PORTAL_TAG_NAME);
|
99
|
+
this.portalElement.onDisconnect = () => {
|
100
|
+
this.popover.remove();
|
101
|
+
this.portalElement = null;
|
102
|
+
};
|
103
|
+
(_b = (_a = this.popover.parentElement) === null || _a === void 0 ? void 0 : _a.appendChild) === null || _b === void 0 ? void 0 : _b.call(_a, this.portalElement);
|
104
|
+
appendToEl.appendChild(this.popover);
|
85
105
|
}
|
86
106
|
}
|
87
107
|
}
|
108
|
+
/**
|
109
|
+
* Remove portal component to when the popover appended to somewhere else and removed from the DOM
|
110
|
+
*/
|
111
|
+
cleanupAppendTo() {
|
112
|
+
if (!this.disconnectAfterAppendTo && this.portalElement) {
|
113
|
+
this.portalElement.remove();
|
114
|
+
}
|
115
|
+
this.disconnectAfterAppendTo = false;
|
116
|
+
}
|
88
117
|
/**
|
89
118
|
* Sets up the aria labels
|
90
119
|
*/
|