@brightspace-ui/core 3.9.1 → 3.10.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.
@@ -164,6 +164,14 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
164
164
  reflect: true,
165
165
  attribute: 'no-pointer'
166
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
+ },
167
175
  /**
168
176
  * Whether the dropdown is open or not
169
177
  * @type {boolean}
@@ -285,6 +293,7 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
285
293
  this._verticalOffset = defaultVerticalOffset;
286
294
 
287
295
  this.__reposition = this.__reposition.bind(this);
296
+ this.__onAncestorMutation = this.__onAncestorMutation.bind(this);
288
297
  this.__onResize = this.__onResize.bind(this);
289
298
  this.__onAutoCloseFocus = this.__onAutoCloseFocus.bind(this);
290
299
  this.__onAutoCloseClick = this.__onAutoCloseClick.bind(this);
@@ -458,10 +467,16 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
458
467
  return (value === 'scroll' || value === 'auto');
459
468
  };
460
469
 
461
- let node = this;
462
470
  this.__removeRepositionHandlers();
471
+
472
+ this._ancestorMutationObserver ??= new MutationObserver(this.__onAncestorMutation);
473
+ const mutationConfig = { attributes: true, childList: true, subtree: true };
474
+
475
+ let node = this;
463
476
  this._scrollablesObserved = [];
464
477
  while (node) {
478
+
479
+ // observe scrollables
465
480
  let observeScrollable = false;
466
481
  if (node.nodeType === Node.ELEMENT_NODE) {
467
482
  observeScrollable = isScrollable(node, 'overflow-y') || isScrollable(node, 'overflow-x');
@@ -472,6 +487,12 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
472
487
  this._scrollablesObserved.push(node);
473
488
  node.addEventListener('scroll', this.__reposition);
474
489
  }
490
+
491
+ // observe mutations on each DOM scope (excludes sibling scopes... can only do so much)
492
+ if (node.nodeType === Node.DOCUMENT_NODE || (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && node.host)) {
493
+ this._ancestorMutationObserver.observe(node, mutationConfig);
494
+ }
495
+
475
496
  node = getComposedParent(node);
476
497
  }
477
498
 
@@ -528,6 +549,13 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
528
549
  this._hasHeader = e.target.assignedNodes().length !== 0;
529
550
  }
530
551
 
552
+ __onAncestorMutation(mutations) {
553
+ const opener = this.__getOpener();
554
+ // ignore mutations that are within this dropdown
555
+ const reposition = !!mutations.find(mutation => !isComposedAncestor(opener, mutation.target));
556
+ if (reposition) this.__reposition();
557
+ }
558
+
531
559
  __onAutoCloseClick(e) {
532
560
  if (!this.opened || this.noAutoClose) {
533
561
  return;
@@ -791,12 +819,13 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
791
819
 
792
820
  __removeRepositionHandlers() {
793
821
  if (!this._fixedPositioning) return;
794
- if (!this._scrollablesObserved) return;
795
822
 
796
- this._scrollablesObserved.forEach(node => {
823
+ this._scrollablesObserved?.forEach(node => {
797
824
  node.removeEventListener('scroll', this.__reposition);
798
825
  });
799
826
  this._scrollablesObserved = null;
827
+
828
+ this._ancestorMutationObserver?.disconnect();
800
829
  }
801
830
 
802
831
  __reposition() {
@@ -1,4 +1,5 @@
1
1
  import '../colors/colors.js';
2
+ import { _offscreenStyleDeclarations } from '../offscreen/offscreen.js';
2
3
  import { css } from 'lit';
3
4
 
4
5
  const pointerLength = 16;
@@ -349,4 +350,7 @@ export const dropdownContentStyles = css`
349
350
  }
350
351
  }
351
352
 
353
+ :host([offscreen]) {
354
+ ${_offscreenStyleDeclarations}
355
+ }
352
356
  `;
@@ -1,6 +1,12 @@
1
1
  import { getUniqueId } from '../../helpers/uniqueId.js';
2
2
  import { isComposedAncestor } from '../../helpers/dom.js';
3
3
 
4
+ const intersectionObserver = new IntersectionObserver(entries => {
5
+ entries.forEach(entry => {
6
+ entry.target.__updateContentVisibility(entry.isIntersecting);
7
+ });
8
+ }, { threshold: 0 }); // 0-1 (0 -> intersection requires any pixel visible, 1 -> intersection requires all pixels visible)
9
+
4
10
  export const DropdownOpenerMixin = superclass => class extends superclass {
5
11
 
6
12
  static get properties() {
@@ -91,6 +97,9 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
91
97
  this.addEventListener('mouseenter', this.__onMouseEnter);
92
98
  this.addEventListener('mouseleave', this.__onMouseLeave);
93
99
 
100
+ if (this._fixedPositioning && this.dropdownOpened) {
101
+ intersectionObserver.observe(this);
102
+ }
94
103
  if (this.openOnHover) {
95
104
  document.body.addEventListener('mouseup', this._onOutsideClick);
96
105
  }
@@ -104,6 +113,9 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
104
113
  this.removeEventListener('mouseenter', this.__onMouseEnter);
105
114
  this.removeEventListener('mouseleave', this.__onMouseLeave);
106
115
 
116
+ if (this._fixedPositioning) {
117
+ intersectionObserver.unobserve(this);
118
+ }
107
119
  if (this.openOnHover) {
108
120
  document.body.removeEventListener('mouseup', this._onOutsideClick);
109
121
  }
@@ -137,7 +149,7 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
137
149
  }
138
150
 
139
151
  willUpdate(changedProperties) {
140
- if (changedProperties.has('preferFixedPositioning')) {
152
+ if (this._fixedPositioning === undefined || changedProperties.has('preferFixedPositioning')) {
141
153
  this._fixedPositioning = (window.D2L?.LP?.Web?.UI?.Flags.Flag('GAUD-131-dropdown-fixed-positioning', false) && this.preferFixedPositioning);
142
154
  }
143
155
  }
@@ -207,6 +219,10 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
207
219
  }
208
220
 
209
221
  __onClosed() {
222
+ if (this._fixedPositioning) {
223
+ intersectionObserver.unobserve(this);
224
+ }
225
+
210
226
  const opener = this.getOpenerElement();
211
227
  if (!opener) {
212
228
  return;
@@ -280,6 +296,10 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
280
296
  opener.setAttribute('aria-expanded', 'true');
281
297
  opener.setAttribute('active', 'true');
282
298
  this._isFading = false;
299
+
300
+ if (this._fixedPositioning) {
301
+ intersectionObserver.observe(this);
302
+ }
283
303
  }
284
304
 
285
305
  __onOpenerMouseUp(e) {
@@ -301,6 +321,10 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
301
321
  } else this.toggleOpen(true);
302
322
  }
303
323
 
324
+ __updateContentVisibility(visible) {
325
+ this.__getContentElement().offscreen = !visible;
326
+ }
327
+
304
328
  /* used by open-on-hover option */
305
329
  _closeTimerStart() {
306
330
  if (this.dropdownOpened) return;
@@ -1,7 +1,10 @@
1
1
  import { css, html, LitElement } from 'lit';
2
2
  import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';
3
3
 
4
- const offscreenStyleDeclarations = css`
4
+ /**
5
+ * A private helper declarations that should not be used by general consumers
6
+ */
7
+ export const _offscreenStyleDeclarations = css`
5
8
  direction: var(--d2l-document-direction, ${document.dir === 'rtl' ? css`rtl` : css`ltr`}); /* stylelint-disable-line @stylistic/string-quotes */
6
9
  height: 1px;
7
10
  inset-inline-start: -10000px;
@@ -14,7 +17,7 @@ const offscreenStyleDeclarations = css`
14
17
 
15
18
  export const offscreenStyles = css`
16
19
  .d2l-offscreen {
17
- ${offscreenStyleDeclarations}
20
+ ${_offscreenStyleDeclarations}
18
21
  }
19
22
  `;
20
23
 
@@ -26,7 +29,7 @@ class Offscreen extends RtlMixin(LitElement) {
26
29
  static get styles() {
27
30
  return css`
28
31
  :host {
29
- ${offscreenStyleDeclarations}
32
+ ${_offscreenStyleDeclarations}
30
33
  }
31
34
  `;
32
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.9.1",
3
+ "version": "3.10.0",
4
4
  "description": "A collection of accessible, free, open-source web components for building Brightspace applications",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/BrightspaceUI/core.git",