@brightspace-ui/core 3.88.4 → 3.89.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/dropdown/demo/dropdown-menu.html +7 -0
- package/components/dropdown/demo/dropdown-tabs.html +7 -0
- package/components/dropdown/demo/dropdown.html +7 -2
- package/components/dropdown/dropdown-content.js +32 -15
- package/components/dropdown/dropdown-menu.js +291 -116
- package/components/dropdown/dropdown-opener-mixin.js +21 -6
- package/components/dropdown/dropdown-popover-mixin.js +366 -0
- package/components/dropdown/dropdown-tabs.js +112 -48
- package/components/popover/demo/popover.html +2 -2
- package/components/popover/popover-mixin.js +120 -100
- package/custom-elements.json +17 -36
- package/helpers/flags.js +1 -0
- package/package.json +1 -1
@@ -0,0 +1,366 @@
|
|
1
|
+
import { css, html } from 'lit';
|
2
|
+
import { classMap } from 'lit/directives/class-map.js';
|
3
|
+
import { findComposedAncestor } from '../../helpers/dom.js';
|
4
|
+
import { getFlag } from '../../helpers/flags.js';
|
5
|
+
import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
|
6
|
+
import { PopoverMixin } from '../popover/popover-mixin.js';
|
7
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
8
|
+
|
9
|
+
export const usePopoverMixin = getFlag('GAUD-7472-dropdown-popover', false);
|
10
|
+
|
11
|
+
export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElement(PopoverMixin(superclass)) {
|
12
|
+
|
13
|
+
static get properties() {
|
14
|
+
return {
|
15
|
+
/**
|
16
|
+
* Optionally align dropdown to either start or end. If not set, the dropdown will attempt be centred.
|
17
|
+
* @type {'start'|'end'}
|
18
|
+
*/
|
19
|
+
align: { type: String },
|
20
|
+
/**
|
21
|
+
* Override max-height. Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed.
|
22
|
+
* @type {number}
|
23
|
+
*/
|
24
|
+
maxHeight: { type: Number, attribute: 'max-height' },
|
25
|
+
/**
|
26
|
+
* Override default max-width (undefined). Specify a number that would be the px value.
|
27
|
+
* @type {number}
|
28
|
+
*/
|
29
|
+
maxWidth: { type: Number, attribute: 'max-width' },
|
30
|
+
/**
|
31
|
+
* Override default height used for required space when `no-auto-fit` is true. Specify a number that would be the px value. Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed.
|
32
|
+
* @type {number}
|
33
|
+
*/
|
34
|
+
minHeight: { type: Number, attribute: 'min-height' },
|
35
|
+
/**
|
36
|
+
* Override default min-width (undefined). Specify a number that would be the px value.
|
37
|
+
* @type {number}
|
38
|
+
*/
|
39
|
+
minWidth: { type: Number, attribute: 'min-width' },
|
40
|
+
/**
|
41
|
+
* Override the breakpoint at which mobile styling is used. Defaults to 616px.
|
42
|
+
* @type {number}
|
43
|
+
*/
|
44
|
+
mobileBreakpointOverride: { type: Number, attribute: 'mobile-breakpoint' },
|
45
|
+
/**
|
46
|
+
* Mobile dropdown style.
|
47
|
+
* @type {'left'|'right'|'bottom'}
|
48
|
+
*/
|
49
|
+
mobileTray: { type: String, attribute: 'mobile-tray' },
|
50
|
+
/**
|
51
|
+
* Opt out of automatically closing on focus or click outside of the dropdown content
|
52
|
+
* @type {boolean}
|
53
|
+
*/
|
54
|
+
noAutoClose: { type: Boolean, attribute: 'no-auto-close' },
|
55
|
+
/**
|
56
|
+
* Opt out of auto-sizing
|
57
|
+
* @type {boolean}
|
58
|
+
*/
|
59
|
+
noAutoFit: { type: Boolean, attribute: 'no-auto-fit' },
|
60
|
+
/**
|
61
|
+
* Opt out of focus being automatically moved to the first focusable element in the dropdown when opened
|
62
|
+
* @type {boolean}
|
63
|
+
*/
|
64
|
+
noAutoFocus: { type: Boolean, attribute: 'no-auto-focus' },
|
65
|
+
/**
|
66
|
+
* Opt-out of showing a close button in the footer of tray-style mobile dropdowns.
|
67
|
+
* @type {boolean}
|
68
|
+
*/
|
69
|
+
noMobileCloseButton: { type: Boolean, attribute: 'no-mobile-close-button' },
|
70
|
+
/**
|
71
|
+
* Render with no padding
|
72
|
+
* @type {boolean}
|
73
|
+
*/
|
74
|
+
noPadding: { type: Boolean, attribute: 'no-padding' },
|
75
|
+
/**
|
76
|
+
* Render the footer with no padding (if it has content)
|
77
|
+
* @type {boolean}
|
78
|
+
*/
|
79
|
+
noPaddingFooter: { type: Boolean, attribute: 'no-padding-footer' },
|
80
|
+
/**
|
81
|
+
* Render the header with no padding (if it has content)
|
82
|
+
* @type {boolean}
|
83
|
+
*/
|
84
|
+
noPaddingHeader: { type: Boolean, attribute: 'no-padding-header' },
|
85
|
+
/**
|
86
|
+
* Render without a pointer
|
87
|
+
* @type {boolean}
|
88
|
+
*/
|
89
|
+
noPointer: { type: Boolean, attribute: 'no-pointer' },
|
90
|
+
/**
|
91
|
+
* Whether the dropdown is open or not
|
92
|
+
* @type {boolean}
|
93
|
+
*/
|
94
|
+
opened: { type: Boolean, reflect: true },
|
95
|
+
/**
|
96
|
+
* Optionally render a d2l-focus-trap around the dropdown content
|
97
|
+
* @type {boolean}
|
98
|
+
*/
|
99
|
+
trapFocus: { type: Boolean, attribute: 'trap-focus' },
|
100
|
+
/**
|
101
|
+
* Provide custom offset, positive or negative
|
102
|
+
* @type {string}
|
103
|
+
*/
|
104
|
+
verticalOffset: { type: String, attribute: 'vertical-offset' },
|
105
|
+
_blockEndScroll: { state: true },
|
106
|
+
_blockStartScroll: { state: true },
|
107
|
+
_dropdownContent: { type: Boolean, attribute: 'dropdown-content', reflect: true },
|
108
|
+
_hasFooterSlotContent: { state: true },
|
109
|
+
_hasHeaderSlotContent: { state: true }
|
110
|
+
};
|
111
|
+
}
|
112
|
+
|
113
|
+
static get styles() {
|
114
|
+
return [super.styles, css`
|
115
|
+
.dropdown-content-layout {
|
116
|
+
align-items: flex-start;
|
117
|
+
display: flex;
|
118
|
+
flex-direction: column;
|
119
|
+
}
|
120
|
+
.dropdown-content {
|
121
|
+
box-sizing: border-box;
|
122
|
+
flex: auto;
|
123
|
+
max-width: 100%;
|
124
|
+
overflow-y: auto;
|
125
|
+
padding: 1rem;
|
126
|
+
}
|
127
|
+
.dropdown-header,
|
128
|
+
.dropdown-footer {
|
129
|
+
box-sizing: border-box;
|
130
|
+
flex: none;
|
131
|
+
max-width: 100%;
|
132
|
+
min-height: 5px;
|
133
|
+
padding: 1rem;
|
134
|
+
position: relative;
|
135
|
+
width: 100%;
|
136
|
+
z-index: 2;
|
137
|
+
}
|
138
|
+
.dropdown-header {
|
139
|
+
border-block-end: 1px solid var(--d2l-popover-border-color, var(--d2l-popover-default-border-color));
|
140
|
+
border-start-end-radius: var(--d2l-popover-border-radius, var(--d2l-popover-default-border-radius));
|
141
|
+
border-start-start-radius: var(--d2l-popover-border-radius, var(--d2l-popover-default-border-radius));
|
142
|
+
}
|
143
|
+
.dropdown-footer {
|
144
|
+
border-block-start: 1px solid var(--d2l-popover-border-color, var(--d2l-popover-default-border-color));
|
145
|
+
border-end-end-radius: var(--d2l-popover-border-radius, var(--d2l-popover-default-border-radius));
|
146
|
+
border-end-start-radius: var(--d2l-popover-border-radius, var(--d2l-popover-default-border-radius));
|
147
|
+
}
|
148
|
+
.dropdown-no-header {
|
149
|
+
border-block-end: none;
|
150
|
+
padding: 0;
|
151
|
+
}
|
152
|
+
.dropdown-no-footer {
|
153
|
+
border-block-start: none;
|
154
|
+
padding: 0;
|
155
|
+
}
|
156
|
+
.dropdown-no-padding {
|
157
|
+
padding: 0;
|
158
|
+
}
|
159
|
+
.dropdown-header-scroll {
|
160
|
+
box-shadow: 0 3px 3px 0 var(--d2l-popover-shadow-color, var(--d2l-popover-default-shadow-color));
|
161
|
+
}
|
162
|
+
.dropdown-footer-scroll {
|
163
|
+
box-shadow: 0 -3px 3px 0 var(--d2l-popover-shadow-color, var(--d2l-popover-default-shadow-color));
|
164
|
+
}
|
165
|
+
|
166
|
+
:host([_mobile][_mobile-tray-location="inline-start"][opened]) .dropdown-content-layout,
|
167
|
+
:host([_mobile][_mobile-tray-location="inline-end"][opened]) .dropdown-content-layout {
|
168
|
+
height: 100vh;
|
169
|
+
}
|
170
|
+
`];
|
171
|
+
}
|
172
|
+
|
173
|
+
constructor() {
|
174
|
+
super();
|
175
|
+
this.opened = false;
|
176
|
+
this.noAutoClose = false;
|
177
|
+
this.noAutoFit = false;
|
178
|
+
this.noAutoFocus = false;
|
179
|
+
this.noPadding = false;
|
180
|
+
this.noPaddingFooter = false;
|
181
|
+
this.noPaddingHeader = false;
|
182
|
+
this.noPointer = false;
|
183
|
+
this.trapFocus = false;
|
184
|
+
|
185
|
+
this._blockEndScroll = false;
|
186
|
+
this._blockStartScroll = false;
|
187
|
+
this._dropdownContent = true;
|
188
|
+
this._hasFooterSlotContent = false;
|
189
|
+
this._hasHeaderSlotContent = false;
|
190
|
+
}
|
191
|
+
|
192
|
+
firstUpdated(changedProperties) {
|
193
|
+
super.firstUpdated(changedProperties);
|
194
|
+
this.#contentElement = this.shadowRoot?.querySelector('.dropdown-content');
|
195
|
+
this.addEventListener('d2l-popover-open', this.#handlePopoverOpen);
|
196
|
+
this.addEventListener('d2l-popover-close', this.#handlePopoverClose);
|
197
|
+
this.addEventListener('d2l-popover-position', this.#handlePopoverPosition);
|
198
|
+
}
|
199
|
+
|
200
|
+
render() {
|
201
|
+
|
202
|
+
const fillHeight = this._mobile && (this._mobileTrayLocation === 'inline-start' || this._mobileTrayLocation === 'inline-end');
|
203
|
+
const contentLayoutStyles = {
|
204
|
+
maxHeight: (!fillHeight && this._contentHeight) ? `${this._contentHeight}px` : undefined
|
205
|
+
};
|
206
|
+
const contentClasses = {
|
207
|
+
'dropdown-content': true,
|
208
|
+
'dropdown-no-padding': this.noPadding
|
209
|
+
};
|
210
|
+
const headerClasses = {
|
211
|
+
'dropdown-header': true,
|
212
|
+
'dropdown-no-header': !this._hasHeaderSlotContent,
|
213
|
+
'dropdown-no-padding': this.noPaddingHeader,
|
214
|
+
'dropdown-header-scroll': this._blockStartScroll
|
215
|
+
};
|
216
|
+
const footerClasses = {
|
217
|
+
'dropdown-footer': true,
|
218
|
+
'dropdown-no-footer': !(this._hasFooterSlotContent || (this._mobile && this._mobileTrayLocation && !this.noMobileCloseButton)),
|
219
|
+
'dropdown-no-padding': this.noPaddingFooter,
|
220
|
+
'dropdown-footer-scroll': this._blockEndScroll
|
221
|
+
};
|
222
|
+
|
223
|
+
const closeButtonStyles = this.#getMobileCloseButtonStyles();
|
224
|
+
|
225
|
+
const content = html`
|
226
|
+
<div class="dropdown-content-layout" style="${styleMap(contentLayoutStyles)}">
|
227
|
+
<div class="${classMap(headerClasses)}">
|
228
|
+
<slot name="header" @slotchange="${this.#handleHeaderSlotChange}"></slot>
|
229
|
+
</div>
|
230
|
+
<div class="${classMap(contentClasses)}" @scroll="${this.#toggleScrollStyles}">
|
231
|
+
<slot></slot>
|
232
|
+
</div>
|
233
|
+
<div class="${classMap(footerClasses)}">
|
234
|
+
<slot name="footer" @slotchange="${this.#handleFooterSlotChange}"></slot>
|
235
|
+
<d2l-button style=${styleMap(closeButtonStyles)} @click=${this.close}>
|
236
|
+
${this.localize('components.dropdown.close')}
|
237
|
+
</d2l-button>
|
238
|
+
</div>
|
239
|
+
</div>
|
240
|
+
`;
|
241
|
+
|
242
|
+
return this.renderPopover(content);
|
243
|
+
}
|
244
|
+
|
245
|
+
willUpdate(changedProperties) {
|
246
|
+
if (changedProperties.has('align') || changedProperties.has('maxHeight') || changedProperties.has('maxWidth') || changedProperties.has('minHeight') || changedProperties.has('minWidth') || changedProperties.has('mobileBreakpointOverride') || changedProperties.has('mobileTray') || changedProperties.has('noAutoClose') || changedProperties.has('noAutoFit') || changedProperties.has('noAutoFocus') || changedProperties.has('noPointer') || changedProperties.has('trapFocus') || changedProperties.has('verticalOffset')) {
|
247
|
+
super.configure({
|
248
|
+
maxHeight: this.maxHeight,
|
249
|
+
maxWidth: this.maxWidth,
|
250
|
+
minHeight: this.minHeight,
|
251
|
+
minWidth: this.minWidth,
|
252
|
+
mobileBreakpoint: this.mobileBreakpointOverride,
|
253
|
+
mobileTrayLocation: this.#adaptMobileTrayLocation(this.mobileTray),
|
254
|
+
noAutoClose: this.noAutoClose,
|
255
|
+
noAutoFit: this.noAutoFit,
|
256
|
+
noAutoFocus: this.noAutoFocus,
|
257
|
+
noPointer: this.noPointer,
|
258
|
+
offset: (this.verticalOffset !== undefined ? Number.parseInt(this.verticalOffset) : undefined),
|
259
|
+
position: { location: 'block-end', span: this.#adaptPositionSpan(this.align) },
|
260
|
+
trapFocus: this.trapFocus
|
261
|
+
});
|
262
|
+
}
|
263
|
+
|
264
|
+
if (changedProperties.has('opened')) {
|
265
|
+
if (this.opened) this.open(true);
|
266
|
+
else if (changedProperties.get('opened')) this.close();
|
267
|
+
}
|
268
|
+
}
|
269
|
+
|
270
|
+
async open(applyFocus = true) {
|
271
|
+
const opener = this.#getOpener();
|
272
|
+
super.open(opener, applyFocus);
|
273
|
+
}
|
274
|
+
|
275
|
+
toggleOpen(applyFocus = true) {
|
276
|
+
const opener = this.#getOpener();
|
277
|
+
super.toggleOpen(opener, applyFocus);
|
278
|
+
}
|
279
|
+
|
280
|
+
#contentElement;
|
281
|
+
|
282
|
+
#adaptMobileTrayLocation(val) {
|
283
|
+
switch (val) {
|
284
|
+
case 'bottom': return 'block-end';
|
285
|
+
case 'left': return 'inline-start';
|
286
|
+
case 'right': return 'inline-end';
|
287
|
+
default: return undefined;
|
288
|
+
}
|
289
|
+
}
|
290
|
+
|
291
|
+
#adaptPositionSpan(val) {
|
292
|
+
switch (val) {
|
293
|
+
case 'start': return 'end';
|
294
|
+
case 'end': return 'start';
|
295
|
+
default: return 'all';
|
296
|
+
}
|
297
|
+
}
|
298
|
+
|
299
|
+
#getMobileCloseButtonStyles() {
|
300
|
+
if (!this._mobile || !this._mobileTrayLocation) {
|
301
|
+
return { display: 'none' };
|
302
|
+
}
|
303
|
+
|
304
|
+
let footerWidth;
|
305
|
+
if (this.noPaddingFooter) {
|
306
|
+
footerWidth = 'calc(100% - 24px)';
|
307
|
+
} else if (this._hasFooterSlotContent) {
|
308
|
+
footerWidth = '100%';
|
309
|
+
} else {
|
310
|
+
footerWidth = 'calc(100% + 16px)';
|
311
|
+
}
|
312
|
+
|
313
|
+
return {
|
314
|
+
display: !this.noMobileCloseButton ? 'inline-block' : 'none',
|
315
|
+
marginBlock: this._hasFooterSlotContent ? '0' : '-20px -20px',
|
316
|
+
marginInline: this._hasFooterSlotContent ? '0' : '-20px 0',
|
317
|
+
padding: this._hasFooterSlotContent && !this.noPaddingFooter ? '12px 0 0 0' : '12px',
|
318
|
+
width: footerWidth
|
319
|
+
};
|
320
|
+
}
|
321
|
+
|
322
|
+
#getOpener() {
|
323
|
+
return findComposedAncestor(this, elem => elem.dropdownOpener);
|
324
|
+
}
|
325
|
+
|
326
|
+
#handleFooterSlotChange(e) {
|
327
|
+
this._hasFooterSlotContent = e.target.assignedNodes().length !== 0;
|
328
|
+
}
|
329
|
+
|
330
|
+
#handleHeaderSlotChange(e) {
|
331
|
+
this._hasHeaderSlotContent = e.target.assignedNodes().length !== 0;
|
332
|
+
}
|
333
|
+
|
334
|
+
#handlePopoverClose() {
|
335
|
+
setTimeout(() => {
|
336
|
+
this.opened = false;
|
337
|
+
|
338
|
+
/** Dispatched when the dropdown is closed */
|
339
|
+
this.dispatchEvent(new CustomEvent('d2l-dropdown-close', { bubbles: true, composed: true }));
|
340
|
+
});
|
341
|
+
}
|
342
|
+
|
343
|
+
#handlePopoverOpen() {
|
344
|
+
this.opened = true;
|
345
|
+
|
346
|
+
if (!this.noAutoFit && this.#contentElement) {
|
347
|
+
this.#contentElement.scrollTop ??= 0;
|
348
|
+
}
|
349
|
+
|
350
|
+
/** Dispatched when the dropdown is opened */
|
351
|
+
this.dispatchEvent(new CustomEvent('d2l-dropdown-open', { bubbles: true, composed: true }));
|
352
|
+
}
|
353
|
+
|
354
|
+
#handlePopoverPosition() {
|
355
|
+
this.#toggleScrollStyles();
|
356
|
+
|
357
|
+
/** Dispatched when the dropdown position finishes adjusting */
|
358
|
+
this.dispatchEvent(new CustomEvent('d2l-dropdown-position', { bubbles: true, composed: true }));
|
359
|
+
}
|
360
|
+
|
361
|
+
#toggleScrollStyles() {
|
362
|
+
this._blockEndScroll = this.#contentElement.scrollHeight - (this.#contentElement.scrollTop + this.#contentElement.clientHeight) >= 5;
|
363
|
+
this._blockStartScroll = this.#contentElement.scrollTop !== 0;
|
364
|
+
}
|
365
|
+
|
366
|
+
};
|
@@ -1,63 +1,127 @@
|
|
1
1
|
import { css, LitElement } from 'lit';
|
2
|
+
import { DropdownPopoverMixin, usePopoverMixin } from './dropdown-popover-mixin.js';
|
2
3
|
import { DropdownContentMixin } from './dropdown-content-mixin.js';
|
3
4
|
import { dropdownContentStyles } from './dropdown-content-styles.js';
|
4
5
|
|
5
|
-
|
6
|
-
* A container for a "d2l-tabs" component. It provides additional support on top of "d2l-dropdown-content" for automatic resizing when the tab changes.
|
7
|
-
* @slot - Anything inside of "d2l-dropdown-content" that isn't in the "header" or "footer" slots appears as regular content
|
8
|
-
* @slot header - Sticky container at the top of the dropdown
|
9
|
-
* @slot footer - Sticky container at the bottom of the dropdown
|
10
|
-
* @fires d2l-dropdown-open - Dispatched when the dropdown is opened
|
11
|
-
*/
|
12
|
-
class DropdownTabs extends DropdownContentMixin(LitElement) {
|
13
|
-
|
14
|
-
static get styles() {
|
15
|
-
return [ dropdownContentStyles, css`
|
16
|
-
::slotted(d2l-tabs) {
|
17
|
-
margin-bottom: 0;
|
18
|
-
}
|
19
|
-
`];
|
20
|
-
}
|
6
|
+
if (usePopoverMixin) {
|
21
7
|
|
22
|
-
|
23
|
-
|
8
|
+
/**
|
9
|
+
* A container for a "d2l-tabs" component. It provides additional support on top of "d2l-dropdown-content" for automatic resizing when the tab changes.
|
10
|
+
* @slot - Anything inside of "d2l-dropdown-content" that isn't in the "header" or "footer" slots appears as regular content
|
11
|
+
* @slot header - Sticky container at the top of the dropdown
|
12
|
+
* @slot footer - Sticky container at the bottom of the dropdown
|
13
|
+
* @fires d2l-dropdown-open - Dispatched when the dropdown is opened
|
14
|
+
*/
|
15
|
+
class DropdownTabs extends DropdownPopoverMixin(LitElement) {
|
24
16
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
17
|
+
static get styles() {
|
18
|
+
return [super.styles, css`
|
19
|
+
::slotted(d2l-tabs) {
|
20
|
+
margin-bottom: 0;
|
21
|
+
}
|
22
|
+
`];
|
23
|
+
}
|
29
24
|
|
30
|
-
|
31
|
-
|
32
|
-
}
|
25
|
+
firstUpdated(changedProperties) {
|
26
|
+
super.firstUpdated(changedProperties);
|
33
27
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
.filter(node => node.hasAttribute && node.tagName === 'D2L-TABS')[0];
|
39
|
-
}
|
28
|
+
this.addEventListener('d2l-dropdown-open', this.#handleOpen);
|
29
|
+
this.addEventListener('d2l-menu-resize', this.#handleMenuResize);
|
30
|
+
this.addEventListener('d2l-tab-panel-selected', this.#handleTabSelected);
|
31
|
+
}
|
40
32
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
33
|
+
#initializingHeight;
|
34
|
+
|
35
|
+
#handleMenuResize(e) {
|
36
|
+
|
37
|
+
const tabs = this.shadowRoot?.querySelector('.dropdown-content > slot')
|
38
|
+
.assignedNodes()
|
39
|
+
.filter(node => node.hasAttribute && node.tagName === 'D2L-TABS')[0];
|
40
|
+
|
41
|
+
if (!tabs) return;
|
42
|
+
const tabListRect = tabs.getTabListRect();
|
43
|
+
|
44
|
+
// need to include height of tablist, dropdown padding, tab margins
|
45
|
+
const rect = {
|
46
|
+
height: e.detail.height + tabListRect.height + 52,
|
47
|
+
width: e.detail.width
|
48
|
+
};
|
49
|
+
this.position(rect, { updateLocation: this.#initializingHeight });
|
50
|
+
this.#initializingHeight = false;
|
51
|
+
}
|
52
|
+
|
53
|
+
#handleOpen(e) {
|
54
|
+
if (e.target !== this) return;
|
55
|
+
this.#initializingHeight = true;
|
56
|
+
}
|
57
|
+
|
58
|
+
#handleTabSelected() {
|
59
|
+
this.position();
|
60
|
+
}
|
52
61
|
|
53
|
-
_onOpen(e) {
|
54
|
-
if (e.target !== this) return;
|
55
|
-
this._initializingHeight = true;
|
56
62
|
}
|
63
|
+
customElements.define('d2l-dropdown-tabs', DropdownTabs);
|
64
|
+
|
65
|
+
} else {
|
66
|
+
|
67
|
+
/**
|
68
|
+
* A container for a "d2l-tabs" component. It provides additional support on top of "d2l-dropdown-content" for automatic resizing when the tab changes.
|
69
|
+
* @slot - Anything inside of "d2l-dropdown-content" that isn't in the "header" or "footer" slots appears as regular content
|
70
|
+
* @slot header - Sticky container at the top of the dropdown
|
71
|
+
* @slot footer - Sticky container at the bottom of the dropdown
|
72
|
+
* @fires d2l-dropdown-open - Dispatched when the dropdown is opened
|
73
|
+
*/
|
74
|
+
class DropdownTabs extends DropdownContentMixin(LitElement) {
|
75
|
+
|
76
|
+
static get styles() {
|
77
|
+
return [ dropdownContentStyles, css`
|
78
|
+
::slotted(d2l-tabs) {
|
79
|
+
margin-bottom: 0;
|
80
|
+
}
|
81
|
+
`];
|
82
|
+
}
|
83
|
+
|
84
|
+
firstUpdated(changedProperties) {
|
85
|
+
super.firstUpdated(changedProperties);
|
86
|
+
|
87
|
+
this.addEventListener('d2l-dropdown-open', this._onOpen);
|
88
|
+
this.addEventListener('d2l-menu-resize', this._onMenuResize);
|
89
|
+
this.addEventListener('d2l-tab-panel-selected', this._onTabSelected);
|
90
|
+
}
|
91
|
+
|
92
|
+
render() {
|
93
|
+
return this._renderContent();
|
94
|
+
}
|
95
|
+
|
96
|
+
_getTabsElement() {
|
97
|
+
if (!this.shadowRoot) return undefined;
|
98
|
+
return this.shadowRoot.querySelector('.d2l-dropdown-content-container > slot')
|
99
|
+
.assignedNodes()
|
100
|
+
.filter(node => node.hasAttribute && node.tagName === 'D2L-TABS')[0];
|
101
|
+
}
|
102
|
+
|
103
|
+
_onMenuResize(e) {
|
104
|
+
const tabs = this._getTabsElement();
|
105
|
+
const tabListRect = tabs.getTabListRect();
|
106
|
+
// need to include height of tablist, dropdown padding, tab margins
|
107
|
+
const rect = {
|
108
|
+
height: e.detail.height + tabListRect.height + 52,
|
109
|
+
width: e.detail.width
|
110
|
+
};
|
111
|
+
this.__position(rect, { updateAboveBelow: this._initializingHeight });
|
112
|
+
this._initializingHeight = false;
|
113
|
+
}
|
114
|
+
|
115
|
+
_onOpen(e) {
|
116
|
+
if (e.target !== this) return;
|
117
|
+
this._initializingHeight = true;
|
118
|
+
}
|
119
|
+
|
120
|
+
_onTabSelected() {
|
121
|
+
this.__position();
|
122
|
+
}
|
57
123
|
|
58
|
-
_onTabSelected() {
|
59
|
-
this.__position();
|
60
124
|
}
|
125
|
+
customElements.define('d2l-dropdown-tabs', DropdownTabs);
|
61
126
|
|
62
127
|
}
|
63
|
-
customElements.define('d2l-dropdown-tabs', DropdownTabs);
|
@@ -16,9 +16,9 @@
|
|
16
16
|
window.wireUpPopover = demo => {
|
17
17
|
const popover = demo.querySelector('d2l-test-popover');
|
18
18
|
const openButton = demo.querySelector('d2l-button-subtle[text="Open"]');
|
19
|
-
openButton.addEventListener('click', () => popover.
|
19
|
+
openButton.addEventListener('click', () => popover.open(openButton));
|
20
20
|
const closeButton = demo.querySelector('d2l-button-subtle[text="Close"]');
|
21
|
-
if (closeButton) closeButton.addEventListener('click', () => popover.
|
21
|
+
if (closeButton) closeButton.addEventListener('click', () => popover.close());
|
22
22
|
};
|
23
23
|
</script>
|
24
24
|
</head>
|