@brightspace-ui/core 3.219.5 → 3.219.7
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/README.md +0 -1
- package/components/dropdown/dropdown-content.js +10 -38
- package/components/dropdown/dropdown-menu.js +126 -316
- package/components/dropdown/dropdown-opener-mixin.js +3 -28
- package/components/dropdown/dropdown-popover-mixin.js +2 -7
- package/components/dropdown/dropdown-tabs.js +45 -113
- package/components/filter/filter.js +1 -20
- package/components/table/table-col-sort-button.js +1 -2
- package/components/table/table-wrapper.js +17 -28
- package/custom-elements.json +99 -144
- package/helpers/README.md +0 -14
- package/helpers/demo/prism.html +0 -1784
- package/helpers/visualReady.js +0 -2
- package/package.json +1 -1
- package/components/dropdown/dropdown-content-mixin.js +0 -1299
- package/components/dropdown/dropdown-content-styles.js +0 -327
|
@@ -1,1299 +0,0 @@
|
|
|
1
|
-
import '../backdrop/backdrop.js';
|
|
2
|
-
import '../button/button.js';
|
|
3
|
-
import '../focus-trap/focus-trap.js';
|
|
4
|
-
import { clearDismissible, setDismissible } from '../../helpers/dismissible.js';
|
|
5
|
-
import { findComposedAncestor, getBoundingAncestor, getComposedParent, isComposedAncestor, isVisible } from '../../helpers/dom.js';
|
|
6
|
-
import { getComposedActiveElement, getFirstFocusableDescendant, getPreviousFocusableAncestor } from '../../helpers/focus.js';
|
|
7
|
-
import { classMap } from 'lit/directives/class-map.js';
|
|
8
|
-
import { html } from 'lit';
|
|
9
|
-
import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
|
|
10
|
-
import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
|
|
11
|
-
import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';
|
|
12
|
-
import { styleMap } from 'lit/directives/style-map.js';
|
|
13
|
-
import { tryGetIfrauBackdropService } from '../../helpers/ifrauBackdropService.js';
|
|
14
|
-
import { visualReady } from '../../helpers/visualReady.js';
|
|
15
|
-
|
|
16
|
-
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
17
|
-
const minBackdropHeightMobile = 42;
|
|
18
|
-
const minBackdropWidthMobile = 30;
|
|
19
|
-
const outerMarginTopBottom = 18;
|
|
20
|
-
const defaultVerticalOffset = 16;
|
|
21
|
-
const pointerLength = 16;
|
|
22
|
-
const pointerRotatedLength = Math.SQRT2 * parseFloat(pointerLength);
|
|
23
|
-
|
|
24
|
-
export const DropdownContentMixin = superclass => class extends LocalizeCoreElement(RtlMixin(superclass)) {
|
|
25
|
-
|
|
26
|
-
static get properties() {
|
|
27
|
-
return {
|
|
28
|
-
/**
|
|
29
|
-
* Optionally align dropdown to either start or end. If not set, the dropdown will attempt to be centred.
|
|
30
|
-
* @type {'start'|'end'}
|
|
31
|
-
*/
|
|
32
|
-
align: {
|
|
33
|
-
type: String,
|
|
34
|
-
reflect: true
|
|
35
|
-
},
|
|
36
|
-
/**
|
|
37
|
-
* Optionally provide boundaries to where the dropdown will appear. Valid properties are "above", "below", "left", and "right".
|
|
38
|
-
* @type {object}
|
|
39
|
-
*/
|
|
40
|
-
boundary: {
|
|
41
|
-
type: Object,
|
|
42
|
-
},
|
|
43
|
-
/**
|
|
44
|
-
* Override default max-width (undefined). Specify a number that would be the px value.
|
|
45
|
-
* @type {number}
|
|
46
|
-
*/
|
|
47
|
-
maxWidth: {
|
|
48
|
-
type: Number,
|
|
49
|
-
reflect: true,
|
|
50
|
-
attribute: 'max-width'
|
|
51
|
-
},
|
|
52
|
-
/**
|
|
53
|
-
* Override default min-width (undefined). Specify a number that would be the px value.
|
|
54
|
-
* @type {number}
|
|
55
|
-
*/
|
|
56
|
-
minWidth: {
|
|
57
|
-
type: Number,
|
|
58
|
-
reflect: true,
|
|
59
|
-
attribute: 'min-width'
|
|
60
|
-
},
|
|
61
|
-
/**
|
|
62
|
-
* 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.
|
|
63
|
-
* @type {number}
|
|
64
|
-
*/
|
|
65
|
-
maxHeight: {
|
|
66
|
-
type: Number,
|
|
67
|
-
attribute: 'max-height'
|
|
68
|
-
},
|
|
69
|
-
/**
|
|
70
|
-
* Override the breakpoint at which mobile styling is used. Defaults to 616px.
|
|
71
|
-
* @type {number}
|
|
72
|
-
*/
|
|
73
|
-
mobileBreakpointOverride: {
|
|
74
|
-
type: Number,
|
|
75
|
-
attribute: 'mobile-breakpoint'
|
|
76
|
-
},
|
|
77
|
-
/**
|
|
78
|
-
* 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.
|
|
79
|
-
* @type {number}
|
|
80
|
-
*/
|
|
81
|
-
minHeight: {
|
|
82
|
-
type: Number,
|
|
83
|
-
reflect: true,
|
|
84
|
-
attribute: 'min-height'
|
|
85
|
-
},
|
|
86
|
-
/**
|
|
87
|
-
* Opt-out of showing a close button in the footer of tray-style mobile dropdowns.
|
|
88
|
-
* @type {boolean}
|
|
89
|
-
*/
|
|
90
|
-
noMobileCloseButton: {
|
|
91
|
-
type: Boolean,
|
|
92
|
-
reflect: true,
|
|
93
|
-
attribute: 'no-mobile-close-button'
|
|
94
|
-
},
|
|
95
|
-
/**
|
|
96
|
-
* Mobile dropdown style.
|
|
97
|
-
* @type {'left'|'right'|'bottom'}
|
|
98
|
-
*/
|
|
99
|
-
mobileTray: {
|
|
100
|
-
type: String,
|
|
101
|
-
reflect: true,
|
|
102
|
-
attribute: 'mobile-tray'
|
|
103
|
-
},
|
|
104
|
-
/**
|
|
105
|
-
* Opt out of automatically closing on focus or click outside of the dropdown content
|
|
106
|
-
* @type {boolean}
|
|
107
|
-
*/
|
|
108
|
-
noAutoClose: {
|
|
109
|
-
type: Boolean,
|
|
110
|
-
reflect: true,
|
|
111
|
-
attribute: 'no-auto-close'
|
|
112
|
-
},
|
|
113
|
-
/**
|
|
114
|
-
* Opt out of auto-sizing
|
|
115
|
-
* @type {boolean}
|
|
116
|
-
*/
|
|
117
|
-
noAutoFit: {
|
|
118
|
-
type: Boolean,
|
|
119
|
-
reflect: true,
|
|
120
|
-
attribute: 'no-auto-fit'
|
|
121
|
-
},
|
|
122
|
-
/**
|
|
123
|
-
* Opt out of focus being automatically moved to the first focusable element in the dropdown when opened
|
|
124
|
-
* @type {boolean}
|
|
125
|
-
*/
|
|
126
|
-
noAutoFocus: {
|
|
127
|
-
type: Boolean,
|
|
128
|
-
reflect: true,
|
|
129
|
-
attribute: 'no-auto-focus'
|
|
130
|
-
},
|
|
131
|
-
/**
|
|
132
|
-
* Render with no padding
|
|
133
|
-
* @type {boolean}
|
|
134
|
-
*/
|
|
135
|
-
noPadding: {
|
|
136
|
-
type: Boolean,
|
|
137
|
-
reflect: true,
|
|
138
|
-
attribute: 'no-padding'
|
|
139
|
-
},
|
|
140
|
-
/**
|
|
141
|
-
* Render the footer with no padding (if it has content)
|
|
142
|
-
* @type {boolean}
|
|
143
|
-
*/
|
|
144
|
-
noPaddingFooter: {
|
|
145
|
-
type: Boolean,
|
|
146
|
-
reflect: true,
|
|
147
|
-
attribute: 'no-padding-footer'
|
|
148
|
-
},
|
|
149
|
-
/**
|
|
150
|
-
* Render the header with no padding (if it has content)
|
|
151
|
-
* @type {boolean}
|
|
152
|
-
*/
|
|
153
|
-
noPaddingHeader: {
|
|
154
|
-
type: Boolean,
|
|
155
|
-
reflect: true,
|
|
156
|
-
attribute: 'no-padding-header'
|
|
157
|
-
},
|
|
158
|
-
/**
|
|
159
|
-
* Render without a pointer
|
|
160
|
-
* @type {boolean}
|
|
161
|
-
*/
|
|
162
|
-
noPointer: {
|
|
163
|
-
type: Boolean,
|
|
164
|
-
reflect: true,
|
|
165
|
-
attribute: 'no-pointer'
|
|
166
|
-
},
|
|
167
|
-
/**
|
|
168
|
-
* Private, set by the opener depending on whether it's intersecting
|
|
169
|
-
* @ignore
|
|
170
|
-
*/
|
|
171
|
-
offscreen: {
|
|
172
|
-
type: Boolean,
|
|
173
|
-
reflect: true
|
|
174
|
-
},
|
|
175
|
-
/**
|
|
176
|
-
* Whether the dropdown is open or not
|
|
177
|
-
* @type {boolean}
|
|
178
|
-
*/
|
|
179
|
-
opened: {
|
|
180
|
-
type: Boolean,
|
|
181
|
-
reflect: true
|
|
182
|
-
},
|
|
183
|
-
/**
|
|
184
|
-
* Private.
|
|
185
|
-
* @ignore
|
|
186
|
-
*/
|
|
187
|
-
openedAbove: {
|
|
188
|
-
type: Boolean,
|
|
189
|
-
reflect: true,
|
|
190
|
-
attribute: 'opened-above'
|
|
191
|
-
},
|
|
192
|
-
/**
|
|
193
|
-
* Optionally render a d2l-focus-trap around the dropdown content
|
|
194
|
-
* @type {boolean}
|
|
195
|
-
*/
|
|
196
|
-
trapFocus: {
|
|
197
|
-
type: Boolean,
|
|
198
|
-
reflect: true,
|
|
199
|
-
attribute: 'trap-focus'
|
|
200
|
-
},
|
|
201
|
-
/**
|
|
202
|
-
* Provide custom offset, positive or negative
|
|
203
|
-
* @type {string}
|
|
204
|
-
*/
|
|
205
|
-
verticalOffset: {
|
|
206
|
-
type: String,
|
|
207
|
-
attribute: 'vertical-offset'
|
|
208
|
-
},
|
|
209
|
-
_bottomOverflow: {
|
|
210
|
-
type: Boolean
|
|
211
|
-
},
|
|
212
|
-
_closing: {
|
|
213
|
-
type: Boolean
|
|
214
|
-
},
|
|
215
|
-
_dropdownContent: {
|
|
216
|
-
type: Boolean,
|
|
217
|
-
attribute: 'dropdown-content',
|
|
218
|
-
reflect: true
|
|
219
|
-
},
|
|
220
|
-
_useMobileStyling: {
|
|
221
|
-
type: Boolean,
|
|
222
|
-
attribute: 'data-mobile',
|
|
223
|
-
reflect: true
|
|
224
|
-
},
|
|
225
|
-
_hasHeader: {
|
|
226
|
-
type: Boolean
|
|
227
|
-
},
|
|
228
|
-
_hasFooter: {
|
|
229
|
-
type: Boolean
|
|
230
|
-
},
|
|
231
|
-
_contentHeight: {
|
|
232
|
-
type: Number
|
|
233
|
-
},
|
|
234
|
-
_pointerPosition: {
|
|
235
|
-
state: true
|
|
236
|
-
},
|
|
237
|
-
_position: {
|
|
238
|
-
state: true
|
|
239
|
-
},
|
|
240
|
-
_showBackdrop: {
|
|
241
|
-
type: Boolean
|
|
242
|
-
},
|
|
243
|
-
_topOverflow: {
|
|
244
|
-
type: Boolean
|
|
245
|
-
},
|
|
246
|
-
_width: {
|
|
247
|
-
type: Number
|
|
248
|
-
}
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
constructor() {
|
|
253
|
-
super();
|
|
254
|
-
|
|
255
|
-
this.noAutoClose = false;
|
|
256
|
-
this.noAutoFit = false;
|
|
257
|
-
this.noAutoFocus = false;
|
|
258
|
-
this.noMobileCloseButton = false;
|
|
259
|
-
this.noPadding = false;
|
|
260
|
-
this.noPaddingFooter = false;
|
|
261
|
-
this.noPaddingHeader = false;
|
|
262
|
-
this.noPointer = false;
|
|
263
|
-
this.mobileBreakpointOverride = 616;
|
|
264
|
-
this.trapFocus = false;
|
|
265
|
-
this._useMobileStyling = false;
|
|
266
|
-
|
|
267
|
-
this.__opened = false;
|
|
268
|
-
this.__content = null;
|
|
269
|
-
this.__previousFocusableAncestor = null;
|
|
270
|
-
this.__applyFocus = true;
|
|
271
|
-
this.__dismissibleId = null;
|
|
272
|
-
|
|
273
|
-
this._dropdownContent = true;
|
|
274
|
-
this._bottomOverflow = false;
|
|
275
|
-
this._topOverflow = false;
|
|
276
|
-
this._closing = false;
|
|
277
|
-
this._hasHeader = false;
|
|
278
|
-
this._hasFooter = false;
|
|
279
|
-
this._showBackdrop = false;
|
|
280
|
-
this._verticalOffset = defaultVerticalOffset;
|
|
281
|
-
|
|
282
|
-
this.__reposition = this.__reposition.bind(this);
|
|
283
|
-
this.__onAncestorMutation = this.__onAncestorMutation.bind(this);
|
|
284
|
-
this.__onResize = this.__onResize.bind(this);
|
|
285
|
-
this.__onAutoCloseFocus = this.__onAutoCloseFocus.bind(this);
|
|
286
|
-
this.__onAutoCloseClick = this.__onAutoCloseClick.bind(this);
|
|
287
|
-
this.__toggleScrollStyles = this.__toggleScrollStyles.bind(this);
|
|
288
|
-
this._handleMobileResize = this._handleMobileResize.bind(this);
|
|
289
|
-
this.__disconnectResizeObserver = this.__disconnectResizeObserver.bind(this);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
get opened() {
|
|
293
|
-
return this.__opened;
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
set opened(val) {
|
|
297
|
-
const oldVal = this.__opened;
|
|
298
|
-
if (oldVal !== val) {
|
|
299
|
-
this.__opened = val;
|
|
300
|
-
this.requestUpdate('opened', oldVal);
|
|
301
|
-
this.__openedChanged(val);
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
connectedCallback() {
|
|
306
|
-
super.connectedCallback();
|
|
307
|
-
|
|
308
|
-
window.addEventListener('resize', this.__onResize);
|
|
309
|
-
this.addEventListener('blur', this.__onAutoCloseFocus, true);
|
|
310
|
-
this.addEventListener('touchstart', this.__onTouchStart);
|
|
311
|
-
document.body.addEventListener('focus', this.__onAutoCloseFocus, true);
|
|
312
|
-
document.addEventListener('click', this.__onAutoCloseClick, true);
|
|
313
|
-
this.mediaQueryList = window.matchMedia(`(max-width: ${this.mobileBreakpointOverride - 1}px)`);
|
|
314
|
-
this._useMobileStyling = this.mediaQueryList.matches;
|
|
315
|
-
if (this.mediaQueryList.addEventListener) this.mediaQueryList.addEventListener('change', this._handleMobileResize);
|
|
316
|
-
if (this.opened) this.__addRepositionHandlers();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
disconnectedCallback() {
|
|
320
|
-
super.disconnectedCallback();
|
|
321
|
-
if (this.mediaQueryList.removeEventListener) this.mediaQueryList.removeEventListener('change', this._handleMobileResize);
|
|
322
|
-
this.removeEventListener('blur', this.__onAutoCloseFocus);
|
|
323
|
-
this.removeEventListener('touchstart', this.__onTouchStart);
|
|
324
|
-
window.removeEventListener('resize', this.__onResize);
|
|
325
|
-
document.body?.removeEventListener('focus', this.__onAutoCloseFocus, true); // DE41322: document.body can be null in some scenarios
|
|
326
|
-
document.removeEventListener('click', this.__onAutoCloseClick, true);
|
|
327
|
-
clearDismissible(this.__dismissibleId);
|
|
328
|
-
this.__dismissibleId = null;
|
|
329
|
-
|
|
330
|
-
if (this.__resizeObserver) this.__resizeObserver.disconnect();
|
|
331
|
-
this.__removeRepositionHandlers();
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
firstUpdated(changedProperties) {
|
|
335
|
-
super.firstUpdated(changedProperties);
|
|
336
|
-
|
|
337
|
-
this.__content = this.getContentContainer();
|
|
338
|
-
this.addEventListener('d2l-dropdown-close', this.__onClose);
|
|
339
|
-
this.addEventListener('d2l-dropdown-position', this.__toggleScrollStyles);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
async getUpdateComplete() {
|
|
343
|
-
await super.getUpdateComplete();
|
|
344
|
-
await visualReady;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
updated(changedProperties) {
|
|
348
|
-
changedProperties.forEach((_, propName) => {
|
|
349
|
-
if (propName === 'verticalOffset') {
|
|
350
|
-
let newVerticalOffset = parseInt(this.verticalOffset);
|
|
351
|
-
if (isNaN(newVerticalOffset)) {
|
|
352
|
-
newVerticalOffset = defaultVerticalOffset;
|
|
353
|
-
}
|
|
354
|
-
this._verticalOffset = newVerticalOffset;
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
close() {
|
|
360
|
-
const hide = () => {
|
|
361
|
-
this._closing = false;
|
|
362
|
-
this._showBackdrop = false;
|
|
363
|
-
this.opened = false;
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
if (!reduceMotion && this._useMobileStyling && this.mobileTray && isVisible(this)) {
|
|
367
|
-
if (this.shadowRoot) this.shadowRoot.querySelector('.d2l-dropdown-content-width')
|
|
368
|
-
.addEventListener('animationend', hide, { once: true });
|
|
369
|
-
this._closing = true;
|
|
370
|
-
this._showBackdrop = false;
|
|
371
|
-
} else {
|
|
372
|
-
hide();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
/**
|
|
377
|
-
* forceRender is no longer necessary, this is left as a stub so that
|
|
378
|
-
* places calling it will not break. It will be removed once the Polymer
|
|
379
|
-
* dropdown is swapped over to use this and all instances of
|
|
380
|
-
* forceRender are removed.
|
|
381
|
-
*/
|
|
382
|
-
forceRender() {}
|
|
383
|
-
|
|
384
|
-
getContentContainer() {
|
|
385
|
-
return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-container');
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
/**
|
|
389
|
-
* Private.
|
|
390
|
-
*/
|
|
391
|
-
height() {
|
|
392
|
-
return this.__content && this.__content.offsetHeight;
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
async open(applyFocus) {
|
|
396
|
-
this.__applyFocus = applyFocus !== undefined ? applyFocus : true;
|
|
397
|
-
this.opened = true;
|
|
398
|
-
await this.updateComplete;
|
|
399
|
-
this._showBackdrop = this._useMobileStyling && this.mobileTray;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
/**
|
|
403
|
-
* Waits for the next resize when elem has a height > 0px,
|
|
404
|
-
* then calls the __position function.
|
|
405
|
-
*/
|
|
406
|
-
requestRepositionNextResize(elem) {
|
|
407
|
-
if (!elem) return;
|
|
408
|
-
if (this.__resizeObserver) this.__resizeObserver.disconnect();
|
|
409
|
-
this.__resizeObserver = new ResizeObserver(this.__disconnectResizeObserver);
|
|
410
|
-
this.__resizeObserver.observe(elem);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
async resize() {
|
|
414
|
-
if (!this.opened) {
|
|
415
|
-
return;
|
|
416
|
-
}
|
|
417
|
-
this._showBackdrop = this._useMobileStyling && this.mobileTray;
|
|
418
|
-
await this.__position();
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
/**
|
|
422
|
-
* Private.
|
|
423
|
-
*/
|
|
424
|
-
scrollTo(scrollTop) {
|
|
425
|
-
const content = this.__content;
|
|
426
|
-
if (content) {
|
|
427
|
-
if (typeof scrollTop === 'number') {
|
|
428
|
-
content.scrollTop = scrollTop;
|
|
429
|
-
}
|
|
430
|
-
return content.scrollTop;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
toggleOpen(applyFocus) {
|
|
435
|
-
if (this.opened) {
|
|
436
|
-
this.close();
|
|
437
|
-
} else {
|
|
438
|
-
this.open(!this.noAutoFocus && applyFocus);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
__addRepositionHandlers() {
|
|
443
|
-
|
|
444
|
-
const isScrollable = (node, prop) => {
|
|
445
|
-
const value = window.getComputedStyle(node, null).getPropertyValue(prop);
|
|
446
|
-
return (value === 'scroll' || value === 'auto');
|
|
447
|
-
};
|
|
448
|
-
|
|
449
|
-
this.__removeRepositionHandlers();
|
|
450
|
-
|
|
451
|
-
this._ancestorMutationObserver ??= new MutationObserver(this.__onAncestorMutation);
|
|
452
|
-
const mutationConfig = { attributes: true, childList: true, subtree: true };
|
|
453
|
-
|
|
454
|
-
let node = this;
|
|
455
|
-
this._scrollablesObserved = [];
|
|
456
|
-
while (node) {
|
|
457
|
-
|
|
458
|
-
// observe scrollables
|
|
459
|
-
let observeScrollable = false;
|
|
460
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
461
|
-
observeScrollable = isScrollable(node, 'overflow-y') || isScrollable(node, 'overflow-x');
|
|
462
|
-
} else if (node.nodeType === Node.DOCUMENT_NODE) {
|
|
463
|
-
observeScrollable = true;
|
|
464
|
-
}
|
|
465
|
-
if (observeScrollable) {
|
|
466
|
-
this._scrollablesObserved.push(node);
|
|
467
|
-
node.addEventListener('scroll', this.__reposition);
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
// observe mutations on each DOM scope (excludes sibling scopes... can only do so much)
|
|
471
|
-
if (node.nodeType === Node.DOCUMENT_NODE || (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && node.host)) {
|
|
472
|
-
this._ancestorMutationObserver.observe(node, mutationConfig);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
node = getComposedParent(node);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
__disconnectResizeObserver(entries) {
|
|
481
|
-
for (let i = 0; i < entries.length; i++) {
|
|
482
|
-
const entry = entries[i];
|
|
483
|
-
if (this.__resizeObserver && entry.contentRect.height !== 0) {
|
|
484
|
-
this.__resizeObserver.disconnect();
|
|
485
|
-
// wrap in rAF for Firefox
|
|
486
|
-
requestAnimationFrame(() => {
|
|
487
|
-
if (this.opened) this.__position();
|
|
488
|
-
});
|
|
489
|
-
break;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
__getContentBottom() {
|
|
495
|
-
return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-bottom');
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
__getContentTop() {
|
|
499
|
-
return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-top');
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
__getOpener() {
|
|
503
|
-
const opener = findComposedAncestor(this, (elem) => {
|
|
504
|
-
if (elem.dropdownOpener) {
|
|
505
|
-
return true;
|
|
506
|
-
}
|
|
507
|
-
});
|
|
508
|
-
return opener;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
__getPointer() {
|
|
512
|
-
return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-pointer');
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
__getPositionContainer() {
|
|
516
|
-
return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-position');
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
__getWidthContainer() {
|
|
520
|
-
return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-width');
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
__handleFooterSlotChange(e) {
|
|
524
|
-
this._hasFooter = e.target.assignedNodes().length !== 0;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
__handleHeaderSlotChange(e) {
|
|
528
|
-
this._hasHeader = e.target.assignedNodes().length !== 0;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
__onAncestorMutation(mutations) {
|
|
532
|
-
const opener = this.__getOpener();
|
|
533
|
-
// ignore mutations that are within this dropdown
|
|
534
|
-
const reposition = !!mutations.find(mutation => !isComposedAncestor(opener, mutation.target));
|
|
535
|
-
if (reposition) this.__reposition();
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
__onAutoCloseClick(e) {
|
|
539
|
-
if (!this.opened || this.noAutoClose) {
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
const rootTarget = e.composedPath()[0];
|
|
543
|
-
const clickInside = isComposedAncestor(this.getContentContainer(), rootTarget) ||
|
|
544
|
-
isComposedAncestor(this.__getContentTop(), rootTarget) ||
|
|
545
|
-
isComposedAncestor(this.__getContentBottom(), rootTarget);
|
|
546
|
-
if (clickInside) {
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
const opener = this.__getOpener();
|
|
550
|
-
if (isComposedAncestor(opener.getOpenerElement(), rootTarget)) {
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
this.close();
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
__onAutoCloseFocus() {
|
|
558
|
-
|
|
559
|
-
/* timeout needed to work around lack of support for relatedTarget */
|
|
560
|
-
setTimeout(() => {
|
|
561
|
-
if (!this.opened
|
|
562
|
-
|| this.noAutoClose
|
|
563
|
-
|| !document.activeElement
|
|
564
|
-
|| document.activeElement === this.__previousFocusableAncestor
|
|
565
|
-
|| document.activeElement === document.body) {
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const activeElement = getComposedActiveElement();
|
|
570
|
-
|
|
571
|
-
if (isComposedAncestor(this, activeElement)
|
|
572
|
-
|| isComposedAncestor(this.__getOpener(), activeElement)
|
|
573
|
-
|| activeElement === this.__previousFocusableAncestor) {
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
this.close();
|
|
577
|
-
}, 0);
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
__onClose(e) {
|
|
581
|
-
|
|
582
|
-
if (e.target !== this || !document.activeElement) {
|
|
583
|
-
return;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
const activeElement = getComposedActiveElement();
|
|
587
|
-
|
|
588
|
-
if (!isComposedAncestor(this, activeElement)) {
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
const opener = this.__getOpener();
|
|
593
|
-
opener.getOpenerElement().focus();
|
|
594
|
-
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
__onResize() {
|
|
598
|
-
this.resize();
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
__onTouchStart(e) {
|
|
602
|
-
// elements external to the dropdown content such as primary-secondary template should not be reacting
|
|
603
|
-
// to touchstart events originating inside the dropdown content
|
|
604
|
-
e.stopPropagation();
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
async __openedChanged(newValue) {
|
|
608
|
-
|
|
609
|
-
// DE44538: wait for dropdown content to fully render,
|
|
610
|
-
// otherwise this.getContentContainer() can return null.
|
|
611
|
-
await this.__waitForContentContainer();
|
|
612
|
-
|
|
613
|
-
this.__previousFocusableAncestor =
|
|
614
|
-
newValue === true
|
|
615
|
-
? getPreviousFocusableAncestor(this, false, false)
|
|
616
|
-
: null;
|
|
617
|
-
|
|
618
|
-
const doOpen = async() => {
|
|
619
|
-
|
|
620
|
-
const content = this.getContentContainer();
|
|
621
|
-
|
|
622
|
-
if (!this.noAutoFit) {
|
|
623
|
-
content.scrollTop = 0;
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
await this.__position();
|
|
627
|
-
this._showBackdrop = this._useMobileStyling && this.mobileTray;
|
|
628
|
-
if (!this.noAutoFocus && this.__applyFocus) {
|
|
629
|
-
const focusable = getFirstFocusableDescendant(this);
|
|
630
|
-
if (focusable) {
|
|
631
|
-
// Removing the rAF call can allow infinite focus looping to happen in content using a focus trap
|
|
632
|
-
requestAnimationFrame(() => focusable.focus());
|
|
633
|
-
} else {
|
|
634
|
-
content.setAttribute('tabindex', '-1');
|
|
635
|
-
content.focus();
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
setTimeout(() =>
|
|
640
|
-
this.dispatchEvent(new CustomEvent('d2l-dropdown-open', { bubbles: true, composed: true })), 0
|
|
641
|
-
);
|
|
642
|
-
|
|
643
|
-
this.__dismissibleId = setDismissible(() => {
|
|
644
|
-
this.close();
|
|
645
|
-
});
|
|
646
|
-
};
|
|
647
|
-
|
|
648
|
-
const ifrauBackdropService = await tryGetIfrauBackdropService();
|
|
649
|
-
|
|
650
|
-
if (newValue) {
|
|
651
|
-
|
|
652
|
-
if (ifrauBackdropService && this.mobileTray && this._useMobileStyling) {
|
|
653
|
-
this._ifrauContextInfo = await ifrauBackdropService.showBackdrop();
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
await doOpen();
|
|
657
|
-
|
|
658
|
-
this.__addRepositionHandlers();
|
|
659
|
-
|
|
660
|
-
} else {
|
|
661
|
-
|
|
662
|
-
this.__removeRepositionHandlers();
|
|
663
|
-
|
|
664
|
-
if (this.__dismissibleId) {
|
|
665
|
-
clearDismissible(this.__dismissibleId);
|
|
666
|
-
this.__dismissibleId = null;
|
|
667
|
-
}
|
|
668
|
-
if (ifrauBackdropService && this.mobileTray && this._useMobileStyling) {
|
|
669
|
-
ifrauBackdropService.hideBackdrop();
|
|
670
|
-
this._ifrauContextInfo = null;
|
|
671
|
-
}
|
|
672
|
-
this._showBackdrop = false;
|
|
673
|
-
await this.updateComplete;
|
|
674
|
-
|
|
675
|
-
/** Dispatched when the dropdown is closed */
|
|
676
|
-
this.dispatchEvent(new CustomEvent('d2l-dropdown-close', { bubbles: true, composed: true }));
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
async __position(contentRect, options) {
|
|
682
|
-
|
|
683
|
-
options = Object.assign({ updateAboveBelow: true, updateHeight: true }, options);
|
|
684
|
-
|
|
685
|
-
const opener = this.__getOpener();
|
|
686
|
-
if (!opener) {
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
const target = opener.getOpenerElement();
|
|
690
|
-
if (!target) {
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const content = this.getContentContainer();
|
|
695
|
-
const header = this.__getContentTop(); // todo: rename
|
|
696
|
-
const footer = this.__getContentBottom(); // todo: rename
|
|
697
|
-
|
|
698
|
-
if (!this.noAutoFit && options.updateHeight) {
|
|
699
|
-
this._contentHeight = null;
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
/* don't let dropdown content horizontally overflow viewport */
|
|
703
|
-
this._width = null;
|
|
704
|
-
|
|
705
|
-
const boundingContainer = getBoundingAncestor(target.parentNode);
|
|
706
|
-
const scrollHeight = boundingContainer.scrollHeight;
|
|
707
|
-
|
|
708
|
-
await this.updateComplete;
|
|
709
|
-
|
|
710
|
-
const adjustPosition = async() => {
|
|
711
|
-
|
|
712
|
-
const targetRect = target.getBoundingClientRect();
|
|
713
|
-
contentRect = contentRect ? contentRect : content.getBoundingClientRect();
|
|
714
|
-
const headerFooterHeight = header.getBoundingClientRect().height + footer.getBoundingClientRect().height;
|
|
715
|
-
|
|
716
|
-
const height = this.minHeight ? this.minHeight : Math.min(this.maxHeight ? this.maxHeight : Number.MAX_VALUE, contentRect.height + headerFooterHeight);
|
|
717
|
-
|
|
718
|
-
const spaceRequired = {
|
|
719
|
-
height: height + 10,
|
|
720
|
-
width: contentRect.width
|
|
721
|
-
};
|
|
722
|
-
|
|
723
|
-
const spaceAround = this._constrainSpaceAround({
|
|
724
|
-
// allow for target offset + outer margin
|
|
725
|
-
above: targetRect.top - this._verticalOffset - outerMarginTopBottom,
|
|
726
|
-
// allow for target offset + outer margin
|
|
727
|
-
below: window.innerHeight - targetRect.bottom - this._verticalOffset - outerMarginTopBottom,
|
|
728
|
-
// allow for outer margin
|
|
729
|
-
left: targetRect.left - 20,
|
|
730
|
-
// allow for outer margin
|
|
731
|
-
right: document.documentElement.clientWidth - targetRect.right - 15
|
|
732
|
-
}, spaceRequired, targetRect);
|
|
733
|
-
|
|
734
|
-
const spaceAroundScroll = this._constrainSpaceAround({
|
|
735
|
-
above: targetRect.top + document.documentElement.scrollTop,
|
|
736
|
-
below: scrollHeight - targetRect.bottom - document.documentElement.scrollTop
|
|
737
|
-
}, spaceRequired, targetRect);
|
|
738
|
-
|
|
739
|
-
if (options.updateAboveBelow) {
|
|
740
|
-
this.openedAbove = this._getOpenedAbove(spaceAround, spaceAroundScroll, spaceRequired);
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
this._position = this._getPosition(spaceAround, targetRect, contentRect);
|
|
744
|
-
this._pointerPosition = this._getPointerPosition(targetRect);
|
|
745
|
-
|
|
746
|
-
if (options.updateHeight) {
|
|
747
|
-
// calculate height available to the dropdown contents for overflow because that is the only area capable of scrolling
|
|
748
|
-
const availableHeight = this.openedAbove ? spaceAround.above : spaceAround.below;
|
|
749
|
-
if (!this.noAutoFit && availableHeight && availableHeight > 0) {
|
|
750
|
-
// only apply maximum if it's less than space available and the header/footer alone won't exceed it (content must be visible)
|
|
751
|
-
this._contentHeight = this.maxHeight !== null
|
|
752
|
-
&& availableHeight > this.maxHeight
|
|
753
|
-
&& headerFooterHeight < this.maxHeight
|
|
754
|
-
? this.maxHeight - headerFooterHeight - 2
|
|
755
|
-
: availableHeight - headerFooterHeight;
|
|
756
|
-
|
|
757
|
-
// ensure the content height has updated when the __toggleScrollStyles event handler runs
|
|
758
|
-
await this.updateComplete;
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
/** Dispatched when the dropdown position finishes adjusting */
|
|
763
|
-
this.dispatchEvent(new CustomEvent('d2l-dropdown-position', { bubbles: true, composed: true }));
|
|
764
|
-
};
|
|
765
|
-
|
|
766
|
-
const scrollWidth = Math.max(header.scrollWidth, content.scrollWidth, footer.scrollWidth);
|
|
767
|
-
const availableWidth = window.innerWidth - 40;
|
|
768
|
-
this._width = (availableWidth > scrollWidth ? scrollWidth : availableWidth) ;
|
|
769
|
-
|
|
770
|
-
await this.updateComplete;
|
|
771
|
-
|
|
772
|
-
await adjustPosition();
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
__removeRepositionHandlers() {
|
|
776
|
-
this._scrollablesObserved?.forEach(node => {
|
|
777
|
-
node.removeEventListener('scroll', this.__reposition);
|
|
778
|
-
});
|
|
779
|
-
this._scrollablesObserved = null;
|
|
780
|
-
|
|
781
|
-
this._ancestorMutationObserver?.disconnect();
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
__reposition() {
|
|
785
|
-
// throttle repositioning (https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event#scroll_event_throttling)
|
|
786
|
-
if (!this.__repositioning) {
|
|
787
|
-
requestAnimationFrame(() => {
|
|
788
|
-
this.__position(undefined, { updateAboveBelow: false, updateHeight: false });
|
|
789
|
-
this.__repositioning = false;
|
|
790
|
-
});
|
|
791
|
-
}
|
|
792
|
-
this.__repositioning = true;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
__toggleScrollStyles() {
|
|
796
|
-
/* scrollHeight incorrect in IE by 4px second time opened */
|
|
797
|
-
this._bottomOverflow = this.__content.scrollHeight - (this.__content.scrollTop + this.__content.clientHeight) >= 5;
|
|
798
|
-
this._topOverflow = this.__content.scrollTop !== 0;
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
async __waitForContentContainer() {
|
|
802
|
-
if (this.getContentContainer() !== null) return;
|
|
803
|
-
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
804
|
-
return this.__waitForContentContainer();
|
|
805
|
-
}
|
|
806
|
-
|
|
807
|
-
_constrainSpaceAround(spaceAround, spaceRequired, targetRect) {
|
|
808
|
-
const constrained = { ...spaceAround };
|
|
809
|
-
if (this.boundary) {
|
|
810
|
-
constrained.above = this.boundary.above >= 0 ? Math.min(spaceAround.above, this.boundary.above) : spaceAround.above;
|
|
811
|
-
constrained.below = this.boundary.below >= 0 ? Math.min(spaceAround.below, this.boundary.below) : spaceAround.below;
|
|
812
|
-
constrained.left = this.boundary.left >= 0 ? Math.min(spaceAround.left, this.boundary.left) : spaceAround.left;
|
|
813
|
-
constrained.right = this.boundary.right >= 0 ? Math.min(spaceAround.right, this.boundary.right) : spaceAround.right;
|
|
814
|
-
}
|
|
815
|
-
const isRTL = this.getAttribute('dir') === 'rtl';
|
|
816
|
-
if ((this.align === 'start' && !isRTL) || (this.align === 'end' && isRTL)) {
|
|
817
|
-
constrained.left = Math.max(0, spaceRequired.width - (targetRect.width + spaceAround.right));
|
|
818
|
-
} else if ((this.align === 'start' && isRTL) || (this.align === 'end' && !isRTL)) {
|
|
819
|
-
constrained.right = Math.max(0, spaceRequired.width - (targetRect.width + spaceAround.left));
|
|
820
|
-
}
|
|
821
|
-
return constrained;
|
|
822
|
-
}
|
|
823
|
-
|
|
824
|
-
_getBottomTrayStyling() {
|
|
825
|
-
|
|
826
|
-
let maxHeightOverride;
|
|
827
|
-
let availableHeight = Math.min(window.innerHeight, window.screen.height);
|
|
828
|
-
if (this._ifrauContextInfo) availableHeight = this._ifrauContextInfo.availableHeight;
|
|
829
|
-
// default maximum height for bottom tray (42px margin)
|
|
830
|
-
const mobileTrayMaxHeightDefault = availableHeight - minBackdropHeightMobile;
|
|
831
|
-
if (this.maxHeight) {
|
|
832
|
-
// if maxWidth provided is smaller, use the maxWidth
|
|
833
|
-
maxHeightOverride = Math.min(mobileTrayMaxHeightDefault, this.maxHeight);
|
|
834
|
-
} else {
|
|
835
|
-
maxHeightOverride = mobileTrayMaxHeightDefault;
|
|
836
|
-
}
|
|
837
|
-
maxHeightOverride = `${maxHeightOverride}px`;
|
|
838
|
-
|
|
839
|
-
let bottomOverride;
|
|
840
|
-
if (this._ifrauContextInfo) {
|
|
841
|
-
// Bottom override is measured as
|
|
842
|
-
// the distance from the bottom of the screen
|
|
843
|
-
const screenHeight =
|
|
844
|
-
window.innerHeight
|
|
845
|
-
- this._ifrauContextInfo.availableHeight
|
|
846
|
-
+ Math.min(this._ifrauContextInfo.top, 0);
|
|
847
|
-
bottomOverride = `${screenHeight}px`;
|
|
848
|
-
}
|
|
849
|
-
|
|
850
|
-
const widthOverride = '100vw';
|
|
851
|
-
|
|
852
|
-
const widthStyle = {
|
|
853
|
-
minWidth: widthOverride,
|
|
854
|
-
width: widthOverride,
|
|
855
|
-
maxHeight: maxHeightOverride,
|
|
856
|
-
bottom: bottomOverride
|
|
857
|
-
};
|
|
858
|
-
|
|
859
|
-
const contentWidthStyle = {
|
|
860
|
-
/* set width of content in addition to width container so header and footer borders are full width */
|
|
861
|
-
width: widthOverride
|
|
862
|
-
};
|
|
863
|
-
|
|
864
|
-
const headerStyle = {
|
|
865
|
-
...contentWidthStyle,
|
|
866
|
-
minHeight: this._hasHeader ? 'auto' : '5px'
|
|
867
|
-
};
|
|
868
|
-
|
|
869
|
-
const footerStyle = {
|
|
870
|
-
...contentWidthStyle,
|
|
871
|
-
minHeight: this._hasFooter || !this.noMobileCloseButton ? 'auto' : '5px'
|
|
872
|
-
};
|
|
873
|
-
|
|
874
|
-
const contentStyle = {
|
|
875
|
-
...contentWidthStyle,
|
|
876
|
-
maxHeight: maxHeightOverride,
|
|
877
|
-
};
|
|
878
|
-
|
|
879
|
-
const closeButtonStyles = {
|
|
880
|
-
display: !this.noMobileCloseButton ? 'inline-block' : 'none',
|
|
881
|
-
width: this._getTrayFooterWidth(),
|
|
882
|
-
padding: this._hasFooter && !this.noPaddingFooter ? '12px 0 0 0' : '12px',
|
|
883
|
-
margin: this._getTrayFooterMargin()
|
|
884
|
-
};
|
|
885
|
-
|
|
886
|
-
return {
|
|
887
|
-
'width' : widthStyle,
|
|
888
|
-
'header' : headerStyle,
|
|
889
|
-
'footer' : footerStyle,
|
|
890
|
-
'content' : contentStyle,
|
|
891
|
-
'close' : closeButtonStyles
|
|
892
|
-
};
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
_getDropdownStyling() {
|
|
896
|
-
const widthStyle = {
|
|
897
|
-
maxWidth: this.maxWidth ? `${this.maxWidth}px` : '',
|
|
898
|
-
minWidth: this.minWidth ? `${this.minWidth}px` : '',
|
|
899
|
-
/* add 2 to content width since scrollWidth does not include border */
|
|
900
|
-
width: this._width ? `${this._width + 20}px` : ''
|
|
901
|
-
};
|
|
902
|
-
|
|
903
|
-
const contentWidthStyle = {
|
|
904
|
-
minWidth: this.minWidth ? `${this.minWidth}px` : '',
|
|
905
|
-
/* set width of content in addition to width container so header and footer borders are full width */
|
|
906
|
-
width: this._width ? `${this._width + 18}px` : '',
|
|
907
|
-
};
|
|
908
|
-
|
|
909
|
-
const contentStyle = {
|
|
910
|
-
...contentWidthStyle,
|
|
911
|
-
maxHeight: this._contentHeight ? `${this._contentHeight}px` : '',
|
|
912
|
-
};
|
|
913
|
-
|
|
914
|
-
const closeButtonStyle = {
|
|
915
|
-
display: 'none',
|
|
916
|
-
};
|
|
917
|
-
|
|
918
|
-
return {
|
|
919
|
-
'width' : widthStyle,
|
|
920
|
-
'content' : contentStyle,
|
|
921
|
-
'close' : closeButtonStyle,
|
|
922
|
-
'header' : contentWidthStyle,
|
|
923
|
-
'footer' : contentWidthStyle
|
|
924
|
-
};
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
_getLeftRightTrayStyling() {
|
|
928
|
-
|
|
929
|
-
let maxWidthOverride = this.maxWidth;
|
|
930
|
-
let availableWidth = Math.min(window.innerWidth, window.screen.width);
|
|
931
|
-
if (this._ifrauContextInfo) availableWidth = this._ifrauContextInfo.availableWidth;
|
|
932
|
-
// default maximum width for tray (30px margin)
|
|
933
|
-
const mobileTrayMaxWidthDefault = Math.min(availableWidth - minBackdropWidthMobile, 420);
|
|
934
|
-
if (maxWidthOverride) {
|
|
935
|
-
// if maxWidth provided is smaller, use the maxWidth
|
|
936
|
-
maxWidthOverride = Math.min(mobileTrayMaxWidthDefault, maxWidthOverride);
|
|
937
|
-
} else {
|
|
938
|
-
maxWidthOverride = mobileTrayMaxWidthDefault;
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
let minWidthOverride = this.minWidth;
|
|
942
|
-
// minimum size - 285px
|
|
943
|
-
const mobileTrayMinWidthDefault = 285;
|
|
944
|
-
if (minWidthOverride) {
|
|
945
|
-
// if minWidth provided is smaller, use the minumum width for tray
|
|
946
|
-
minWidthOverride = Math.max(mobileTrayMinWidthDefault, minWidthOverride);
|
|
947
|
-
} else {
|
|
948
|
-
minWidthOverride = mobileTrayMinWidthDefault;
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
// if no width property set, automatically size to maximum width
|
|
952
|
-
let widthOverride = this._width ? this._width : maxWidthOverride;
|
|
953
|
-
// ensure width is between minWidth and maxWidth
|
|
954
|
-
if (widthOverride && maxWidthOverride && widthOverride > (maxWidthOverride - 20)) widthOverride = maxWidthOverride - 20;
|
|
955
|
-
if (widthOverride && minWidthOverride && widthOverride < (minWidthOverride - 20)) widthOverride = minWidthOverride - 20;
|
|
956
|
-
|
|
957
|
-
maxWidthOverride = `${maxWidthOverride}px`;
|
|
958
|
-
minWidthOverride = `${minWidthOverride}px`;
|
|
959
|
-
const contentWidth = `${widthOverride + 18}px`;
|
|
960
|
-
/* add 2 to content width since scrollWidth does not include border */
|
|
961
|
-
const containerWidth = `${widthOverride + 20}px`;
|
|
962
|
-
|
|
963
|
-
let maxHeightOverride = '';
|
|
964
|
-
if (this._ifrauContextInfo) maxHeightOverride = `${this._ifrauContextInfo.availableHeight}px`;
|
|
965
|
-
|
|
966
|
-
let topOverride;
|
|
967
|
-
if (this._ifrauContextInfo) {
|
|
968
|
-
// if inside iframe, use ifrauContext top as top of screen
|
|
969
|
-
topOverride = `${this._ifrauContextInfo.top < 0 ? -this._ifrauContextInfo.top : 0}px`;
|
|
970
|
-
} else if (window.innerHeight > window.screen.height) {
|
|
971
|
-
// non-responsive page, manually override top to scroll distance
|
|
972
|
-
topOverride = window.pageYOffset;
|
|
973
|
-
}
|
|
974
|
-
|
|
975
|
-
let rightOverride;
|
|
976
|
-
let leftOverride;
|
|
977
|
-
if (this.mobileTray === 'right') {
|
|
978
|
-
// On non-responsive pages, the innerWidth may be wider than the screen,
|
|
979
|
-
// override right to stick to right of viewport
|
|
980
|
-
rightOverride = `${Math.max(window.innerWidth - window.screen.width, 0)}px`;
|
|
981
|
-
}
|
|
982
|
-
if (this.mobileTray === 'left') {
|
|
983
|
-
// On non-responsive pages, the innerWidth may be wider than the screen,
|
|
984
|
-
// override left to stick to left of viewport
|
|
985
|
-
leftOverride = `${Math.max(window.innerWidth - window.screen.width, 0)}px`;
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
if (minWidthOverride > maxWidthOverride) {
|
|
989
|
-
minWidthOverride = maxWidthOverride;
|
|
990
|
-
}
|
|
991
|
-
const widthStyle = {
|
|
992
|
-
maxWidth: maxWidthOverride,
|
|
993
|
-
minWidth: minWidthOverride,
|
|
994
|
-
width: containerWidth,
|
|
995
|
-
maxHeight: maxHeightOverride,
|
|
996
|
-
top: topOverride,
|
|
997
|
-
right: rightOverride,
|
|
998
|
-
left: leftOverride,
|
|
999
|
-
};
|
|
1000
|
-
|
|
1001
|
-
const contentWidthStyle = {
|
|
1002
|
-
minWidth: minWidthOverride,
|
|
1003
|
-
/* set width of content in addition to width container so header and footer borders are full width */
|
|
1004
|
-
width: contentWidth,
|
|
1005
|
-
};
|
|
1006
|
-
|
|
1007
|
-
const headerStyle = {
|
|
1008
|
-
...contentWidthStyle,
|
|
1009
|
-
minHeight: this._hasHeader ? 'auto' : '5px'
|
|
1010
|
-
};
|
|
1011
|
-
|
|
1012
|
-
const footerStyle = {
|
|
1013
|
-
...contentWidthStyle,
|
|
1014
|
-
minHeight: this._hasFooter || !this.noMobileCloseButton ? 'auto' : '5px'
|
|
1015
|
-
};
|
|
1016
|
-
|
|
1017
|
-
const contentStyle = {
|
|
1018
|
-
...contentWidthStyle,
|
|
1019
|
-
maxHeight: maxHeightOverride,
|
|
1020
|
-
};
|
|
1021
|
-
|
|
1022
|
-
const closeButtonStyles = {
|
|
1023
|
-
display: !this.noMobileCloseButton ? 'inline-block' : 'none',
|
|
1024
|
-
width: this._getTrayFooterWidth(),
|
|
1025
|
-
padding: this._hasFooter && !this.noPaddingFooter ? '12px 0 0 0' : '12px',
|
|
1026
|
-
margin: this._getTrayFooterMargin()
|
|
1027
|
-
};
|
|
1028
|
-
|
|
1029
|
-
return {
|
|
1030
|
-
'width' : widthStyle,
|
|
1031
|
-
'header' : headerStyle,
|
|
1032
|
-
'footer' : footerStyle,
|
|
1033
|
-
'content' : contentStyle,
|
|
1034
|
-
'close' : closeButtonStyles
|
|
1035
|
-
};
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
|
-
_getOpenedAbove(spaceAround, spaceAroundScroll, spaceRequired) {
|
|
1039
|
-
if (spaceAround.below >= spaceRequired.height) {
|
|
1040
|
-
return false;
|
|
1041
|
-
}
|
|
1042
|
-
if (spaceAround.above >= spaceRequired.height) {
|
|
1043
|
-
return true;
|
|
1044
|
-
}
|
|
1045
|
-
if (!this.noAutoFit) {
|
|
1046
|
-
// if auto-fit is enabled, scroll will be enabled for the
|
|
1047
|
-
// inner content so it will always fit in the available space
|
|
1048
|
-
// so pick the largest space it can be displayed in
|
|
1049
|
-
return spaceAround.above > spaceAround.below;
|
|
1050
|
-
}
|
|
1051
|
-
if (spaceAroundScroll.below >= spaceRequired.height) {
|
|
1052
|
-
return false;
|
|
1053
|
-
}
|
|
1054
|
-
if (spaceAroundScroll.above >= spaceRequired.height) {
|
|
1055
|
-
return true;
|
|
1056
|
-
}
|
|
1057
|
-
// if auto-fit is disabled and it doesn't fit in the scrollable space
|
|
1058
|
-
// above or below, always open down because it can add scrollable space
|
|
1059
|
-
return false;
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
_getPointerPosition(targetRect) {
|
|
1063
|
-
const position = {};
|
|
1064
|
-
|
|
1065
|
-
const pointer = this.__getPointer();
|
|
1066
|
-
if (!pointer) return position;
|
|
1067
|
-
|
|
1068
|
-
const pointerRect = pointer.getBoundingClientRect();
|
|
1069
|
-
const isRTL = this.getAttribute('dir') === 'rtl';
|
|
1070
|
-
if (this.align === 'start' || this.align === 'end') {
|
|
1071
|
-
const pointerXAdjustment = Math.min(20 + ((pointerRotatedLength - pointerLength) / 2), (targetRect.width - pointerLength) / 2);
|
|
1072
|
-
if ((this.align === 'start' && !isRTL) || (this.align === 'end' && isRTL)) {
|
|
1073
|
-
position.left = targetRect.left + pointerXAdjustment;
|
|
1074
|
-
} else {
|
|
1075
|
-
position.right = window.innerWidth - targetRect.right + pointerXAdjustment;
|
|
1076
|
-
}
|
|
1077
|
-
} else {
|
|
1078
|
-
if (!isRTL) {
|
|
1079
|
-
position.left = targetRect.left + ((targetRect.width - pointerRect.width) / 2);
|
|
1080
|
-
} else {
|
|
1081
|
-
position.right = window.innerWidth - targetRect.left - ((targetRect.width + pointerRect.width) / 2);
|
|
1082
|
-
}
|
|
1083
|
-
}
|
|
1084
|
-
if (this.openedAbove) {
|
|
1085
|
-
position.bottom = window.innerHeight - targetRect.top + this._verticalOffset - 8;
|
|
1086
|
-
} else {
|
|
1087
|
-
position.top = targetRect.top + targetRect.height + this._verticalOffset - 7;
|
|
1088
|
-
}
|
|
1089
|
-
|
|
1090
|
-
return position;
|
|
1091
|
-
}
|
|
1092
|
-
|
|
1093
|
-
_getPosition(spaceAround, targetRect, contentRect) {
|
|
1094
|
-
const position = {};
|
|
1095
|
-
const isRTL = this.getAttribute('dir') === 'rtl';
|
|
1096
|
-
const positionXAdjustment = this._getPositionXAdjustment(spaceAround, targetRect, contentRect);
|
|
1097
|
-
|
|
1098
|
-
if (positionXAdjustment !== null) {
|
|
1099
|
-
if (!isRTL) {
|
|
1100
|
-
position.left = targetRect.left + positionXAdjustment;
|
|
1101
|
-
} else {
|
|
1102
|
-
position.right = window.innerWidth - targetRect.left - targetRect.width + positionXAdjustment;
|
|
1103
|
-
}
|
|
1104
|
-
}
|
|
1105
|
-
if (this.openedAbove) {
|
|
1106
|
-
position.bottom = window.innerHeight - targetRect.top + this._verticalOffset;
|
|
1107
|
-
} else {
|
|
1108
|
-
position.top = targetRect.top + targetRect.height + this._verticalOffset;
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
return position;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
_getPositionXAdjustment(spaceAround, targetRect, contentRect) {
|
|
1115
|
-
const centerDelta = contentRect.width - targetRect.width;
|
|
1116
|
-
const contentXAdjustment = centerDelta / 2;
|
|
1117
|
-
if (!this.align && centerDelta <= 0) {
|
|
1118
|
-
return contentXAdjustment * -1;
|
|
1119
|
-
}
|
|
1120
|
-
if (!this.align && spaceAround.left > contentXAdjustment && spaceAround.right > contentXAdjustment) {
|
|
1121
|
-
// center with target
|
|
1122
|
-
return contentXAdjustment * -1;
|
|
1123
|
-
}
|
|
1124
|
-
const isRTL = this.getAttribute('dir') === 'rtl';
|
|
1125
|
-
if (!isRTL) {
|
|
1126
|
-
if (spaceAround.left < contentXAdjustment) {
|
|
1127
|
-
// slide content right (not enough space to center)
|
|
1128
|
-
return spaceAround.left * -1;
|
|
1129
|
-
} else if (spaceAround.right < contentXAdjustment) {
|
|
1130
|
-
// slide content left (not enough space to center)
|
|
1131
|
-
return (centerDelta * -1) + spaceAround.right;
|
|
1132
|
-
}
|
|
1133
|
-
} else {
|
|
1134
|
-
if (spaceAround.left < contentXAdjustment) {
|
|
1135
|
-
// slide content right (not enough space to center)
|
|
1136
|
-
return (centerDelta * -1) + spaceAround.left;
|
|
1137
|
-
} else if (spaceAround.right < contentXAdjustment) {
|
|
1138
|
-
// slide content left (not enough space to center)
|
|
1139
|
-
return spaceAround.right * -1;
|
|
1140
|
-
}
|
|
1141
|
-
}
|
|
1142
|
-
if (this.align === 'start' || this.align === 'end') {
|
|
1143
|
-
const shift = Math.min((targetRect.width / 2) - (20 + pointerLength / 2), 0); // 20 ~= 1rem
|
|
1144
|
-
if (this.align === 'start') {
|
|
1145
|
-
return shift;
|
|
1146
|
-
} else {
|
|
1147
|
-
return targetRect.width - contentRect.width - shift;
|
|
1148
|
-
}
|
|
1149
|
-
}
|
|
1150
|
-
return null;
|
|
1151
|
-
}
|
|
1152
|
-
|
|
1153
|
-
_getTrayFooterMargin() {
|
|
1154
|
-
let footerMargin;
|
|
1155
|
-
if (this._hasFooter) {
|
|
1156
|
-
footerMargin = '0';
|
|
1157
|
-
} else if (this.getAttribute('dir') === 'rtl') {
|
|
1158
|
-
footerMargin = '-20px -20px -20px 0px';
|
|
1159
|
-
} else {
|
|
1160
|
-
footerMargin = '-20px 0 -20px -20px';
|
|
1161
|
-
}
|
|
1162
|
-
return footerMargin;
|
|
1163
|
-
}
|
|
1164
|
-
|
|
1165
|
-
_getTrayFooterWidth() {
|
|
1166
|
-
let footerWidth;
|
|
1167
|
-
if (this.noPaddingFooter) {
|
|
1168
|
-
footerWidth = 'calc(100% - 24px)';
|
|
1169
|
-
} else if (this._hasFooter) {
|
|
1170
|
-
footerWidth = '100%';
|
|
1171
|
-
} else {
|
|
1172
|
-
footerWidth = 'calc(100% + 16px)';
|
|
1173
|
-
}
|
|
1174
|
-
return footerWidth;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
_handleFocusTrapEnter() {
|
|
1178
|
-
if (this.__applyFocus && !this.noAutoFocus) {
|
|
1179
|
-
const content = this.__getWidthContainer();
|
|
1180
|
-
const focusable = getFirstFocusableDescendant(content);
|
|
1181
|
-
if (focusable) {
|
|
1182
|
-
// Removing the rAF call can allow infinite focus looping to happen in content using a focus trap
|
|
1183
|
-
requestAnimationFrame(() => focusable.focus());
|
|
1184
|
-
} else {
|
|
1185
|
-
content.setAttribute('tabindex', '-1');
|
|
1186
|
-
content.focus();
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
/** Dispatched when user focus enters the dropdown content (trap-focus option only) */
|
|
1190
|
-
this.dispatchEvent(new CustomEvent('d2l-dropdown-focus-enter', { detail:{ applyFocus: this.__applyFocus } }));
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
async _handleMobileResize() {
|
|
1194
|
-
this._useMobileStyling = this.mediaQueryList.matches;
|
|
1195
|
-
if (this.opened) this._showBackdrop = this._useMobileStyling && this.mobileTray;
|
|
1196
|
-
if (this.opened) await this.__position();
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
_renderContent() {
|
|
1200
|
-
|
|
1201
|
-
const mobileTrayRightLeft = this._useMobileStyling && (this.mobileTray === 'right' || this.mobileTray === 'left');
|
|
1202
|
-
const mobileTrayBottom = this._useMobileStyling && (this.mobileTray === 'bottom');
|
|
1203
|
-
|
|
1204
|
-
let stylesMap;
|
|
1205
|
-
if (mobileTrayBottom) {
|
|
1206
|
-
stylesMap = this._getBottomTrayStyling();
|
|
1207
|
-
} else if (mobileTrayRightLeft) {
|
|
1208
|
-
stylesMap = this._getLeftRightTrayStyling();
|
|
1209
|
-
} else {
|
|
1210
|
-
stylesMap = this._getDropdownStyling();
|
|
1211
|
-
}
|
|
1212
|
-
const widthStyle = stylesMap['width'];
|
|
1213
|
-
const headerStyle = stylesMap['header'];
|
|
1214
|
-
const footerStyle = stylesMap['footer'];
|
|
1215
|
-
const contentStyle = stylesMap['content'];
|
|
1216
|
-
const closeButtonStyles = stylesMap['close'];
|
|
1217
|
-
|
|
1218
|
-
const topClasses = {
|
|
1219
|
-
'd2l-dropdown-content-top': true,
|
|
1220
|
-
'd2l-dropdown-content-top-scroll': this._topOverflow,
|
|
1221
|
-
'd2l-dropdown-content-header': this._hasHeader
|
|
1222
|
-
};
|
|
1223
|
-
const bottomClasses = {
|
|
1224
|
-
'd2l-dropdown-content-bottom': true,
|
|
1225
|
-
'd2l-dropdown-content-bottom-scroll': this._bottomOverflow,
|
|
1226
|
-
'd2l-dropdown-content-footer': this._hasFooter || (this._useMobileStyling && this.mobileTray && !this.noMobileCloseButton)
|
|
1227
|
-
};
|
|
1228
|
-
|
|
1229
|
-
let dropdownContentSlots = html`
|
|
1230
|
-
<div
|
|
1231
|
-
id="d2l-dropdown-wrapper"
|
|
1232
|
-
class="d2l-dropdown-content-width vdiff-target"
|
|
1233
|
-
style=${styleMap(widthStyle)}
|
|
1234
|
-
?data-closing="${this._closing}">
|
|
1235
|
-
<div class=${classMap(topClasses)} style=${styleMap(headerStyle)}>
|
|
1236
|
-
<slot name="header" @slotchange="${this.__handleHeaderSlotChange}"></slot>
|
|
1237
|
-
</div>
|
|
1238
|
-
<div
|
|
1239
|
-
class="d2l-dropdown-content-container"
|
|
1240
|
-
style=${styleMap(contentStyle)}
|
|
1241
|
-
@scroll=${this.__toggleScrollStyles}>
|
|
1242
|
-
<slot class="d2l-dropdown-content-slot"></slot>
|
|
1243
|
-
</div>
|
|
1244
|
-
<div class=${classMap(bottomClasses)} style=${styleMap(footerStyle)}>
|
|
1245
|
-
<slot name="footer" @slotchange="${this.__handleFooterSlotChange}"></slot>
|
|
1246
|
-
<d2l-button
|
|
1247
|
-
class="dropdown-close-btn"
|
|
1248
|
-
style=${styleMap(closeButtonStyles)}
|
|
1249
|
-
@click=${this.close}>
|
|
1250
|
-
${this.localize('components.dropdown.close')}
|
|
1251
|
-
</d2l-button>
|
|
1252
|
-
</div>
|
|
1253
|
-
</div>
|
|
1254
|
-
`;
|
|
1255
|
-
|
|
1256
|
-
if (this.trapFocus) {
|
|
1257
|
-
dropdownContentSlots = html`
|
|
1258
|
-
<d2l-focus-trap @d2l-focus-trap-enter="${this._handleFocusTrapEnter}" ?trap="${this.opened}">
|
|
1259
|
-
${dropdownContentSlots}
|
|
1260
|
-
</d2l-focus-trap>`;
|
|
1261
|
-
}
|
|
1262
|
-
|
|
1263
|
-
const positionStyle = {};
|
|
1264
|
-
if (this._position) {
|
|
1265
|
-
for (const prop in this._position) {
|
|
1266
|
-
positionStyle[prop] = `${this._position[prop]}px`;
|
|
1267
|
-
}
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
const dropdown = html`
|
|
1271
|
-
<div class="d2l-dropdown-content-position" style=${styleMap(positionStyle)}>
|
|
1272
|
-
${dropdownContentSlots}
|
|
1273
|
-
</div>
|
|
1274
|
-
`;
|
|
1275
|
-
|
|
1276
|
-
const pointerPositionStyle = {};
|
|
1277
|
-
if (this._pointerPosition) {
|
|
1278
|
-
for (const prop in this._pointerPosition) {
|
|
1279
|
-
pointerPositionStyle[prop] = `${this._pointerPosition[prop]}px`;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
const pointer = html`
|
|
1284
|
-
<div class="d2l-dropdown-content-pointer" style="${styleMap(pointerPositionStyle)}">
|
|
1285
|
-
<div></div>
|
|
1286
|
-
</div>
|
|
1287
|
-
`;
|
|
1288
|
-
|
|
1289
|
-
return (this.mobileTray) ? html`
|
|
1290
|
-
${dropdown}
|
|
1291
|
-
<d2l-backdrop
|
|
1292
|
-
for-target="d2l-dropdown-wrapper"
|
|
1293
|
-
?shown="${this._showBackdrop}" >
|
|
1294
|
-
</d2l-backdrop>
|
|
1295
|
-
${pointer}`
|
|
1296
|
-
: html`${dropdown}${pointer}`;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
};
|