@brightspace-ui/core 3.6.0 → 3.7.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.
@@ -0,0 +1,171 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
5
+ <meta charset="UTF-8">
6
+ <link rel="stylesheet" href="../../demo/styles.css" type="text/css">
7
+ <script>
8
+ window.D2L = { LP: { Web: { UI: { Flags: { Flag: () => true } } } } };
9
+ </script>
10
+ <script type="module">
11
+ import '../../demo/demo-page.js';
12
+ import '../../button/button.js';
13
+ import '../../button/button-subtle.js';
14
+ import '../../dialog/dialog.js';
15
+ import '../dropdown.js';
16
+ import '../dropdown-content.js';
17
+ import { css, html, LitElement } from 'lit';
18
+
19
+ class MutationDemo extends LitElement {
20
+
21
+ static get properties() {
22
+ return {
23
+ count: { type: Number }
24
+ };
25
+ }
26
+
27
+ static get styles() {
28
+ return css`
29
+ :host {
30
+ border: 1px solid var(--d2l-color-tungsten);
31
+ border-radius: 6px;
32
+ display: block;
33
+ margin: 1rem;
34
+ position: relative;
35
+ }
36
+ div::before {
37
+ border-bottom: 1px solid black;
38
+ border-left: 1px solid black;
39
+ content: 'WC';
40
+ font-size: 0.7rem;
41
+ padding: 0 0.2rem;
42
+ position: absolute;
43
+ right: 0;
44
+ top: 0;
45
+ }
46
+ p {
47
+ margin: 0.5rem;
48
+ }
49
+ `;
50
+ }
51
+
52
+ constructor() {
53
+ super();
54
+ this.count = 1;
55
+ }
56
+
57
+ render() {
58
+ const elems = [];
59
+ for (let i = 0; i < this.count; i++) {
60
+ const newContent = document.createElement('p');
61
+ newContent.innerText = 'Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway.';
62
+ elems.push(newContent);
63
+ }
64
+ return html`<div>${elems}</div>`;
65
+ }
66
+
67
+ }
68
+ customElements.define('d2l-demo-mutation', MutationDemo);
69
+
70
+ </script>
71
+ </head>
72
+
73
+ <body unresolved>
74
+
75
+ <d2l-demo-page page-title="d2l-dropdown (fixed position)">
76
+
77
+ <h2>Dropdown (mutation testing)</h2>
78
+
79
+ <d2l-demo-snippet>
80
+ <template>
81
+ <div style="border: 1px solid var(--d2l-color-tungsten); border-radius: 6px; height: 600px; overflow: scroll;">
82
+ <d2l-demo-mutation count="1"></d2l-demo-mutation>
83
+ <div style="padding: 25px; position: relative;">
84
+ <d2l-dropdown prefer-fixed-positioning>
85
+ <d2l-button class="d2l-dropdown-opener">Open it!</d2l-button>
86
+ <d2l-dropdown-content max-width="400" prefer-fixed-positioning>
87
+ <d2l-button-subtle id="add-content1">Add to Light</d2l-button-subtle>
88
+ <d2l-button-subtle id="add-content2">Add to Shadow</d2l-button-subtle>
89
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
90
+ magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
91
+ commodo consequat. </p>
92
+ <a href="http://www.desire2learn.com">D2L</a>
93
+ </d2l-dropdown-content>
94
+ </d2l-dropdown>
95
+ <br><br><br><br><br><br>
96
+ </div>
97
+ </div>
98
+ <script>
99
+ document.querySelector('#add-content1').addEventListener('click', e => {
100
+ const dropdown = e.target.parentNode.parentNode;
101
+ const newContent = document.createElement('p');
102
+ newContent.innerText = 'Trysail Sail ho Corsair red ensign hulk smartly boom jib rum gangway. Case shot Shiver me timbers gangplank crack Jennys tea cup ballast Blimey lee snow crow\'s nest rutters. Fluke jib scourge of the seven seas boatswain schooner gaff booty Jack Tar transom spirits.';
103
+ dropdown.parentNode.insertBefore(newContent, dropdown);
104
+ });
105
+ document.querySelector('#add-content2').addEventListener('click', () => {
106
+ const mutationDemo = document.querySelector('d2l-demo-mutation');
107
+ mutationDemo.count += 1;
108
+ });
109
+ </script>
110
+ </template>
111
+ </d2l-demo-snippet>
112
+
113
+ <h2>Dropdown (transform container)</h2>
114
+
115
+ <d2l-demo-snippet>
116
+ <template>
117
+
118
+ <div style="padding: 25px; transform: translate(0);">
119
+ <d2l-dropdown prefer-fixed-positioning>
120
+ <d2l-button class="d2l-dropdown-opener">Open it!</d2l-button>
121
+ <d2l-dropdown-content max-width="400" prefer-fixed-positioning>
122
+ <a href="https://youtu.be/9ze87zQFSak">Google</a>
123
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
124
+ magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
125
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
126
+ nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
127
+ anim id est laborum.</p>
128
+ <a href="http://www.desire2learn.com">D2L</a>
129
+ </d2l-dropdown-content>
130
+ </d2l-dropdown>
131
+ </div>
132
+
133
+ </template>
134
+ </d2l-demo-snippet>
135
+
136
+ <h2>Dropdown (in a dialog)</h2>
137
+
138
+ <d2l-demo-snippet>
139
+ <template>
140
+ <d2l-button id="openDialog1">Show Dialog</d2l-button>
141
+ <d2l-dialog id="dialog1" title-text="Dialog Title">
142
+ <div>
143
+ <p>Deadlights jack lad schooner scallywag dance the hempen jig carouser broadside cable strike colors. Bring a spring upon her cable holystone blow the man down spanker</p>
144
+ <d2l-dropdown prefer-fixed-positioning>
145
+ <d2l-button class="d2l-dropdown-opener">Open it!</d2l-button>
146
+ <d2l-dropdown-content max-width="400" prefer-fixed-positioning>
147
+ <a href="https://youtu.be/9ze87zQFSak">Google</a>
148
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
149
+ magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
150
+ commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
151
+ nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
152
+ anim id est laborum.</p>
153
+ <a href="http://www.desire2learn.com">D2L</a>
154
+ </d2l-dropdown-content>
155
+ </d2l-dropdown>
156
+ </div>
157
+ <d2l-button slot="footer" primary data-dialog-action="ok">Click Me!</d2l-button>
158
+ <d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
159
+ </d2l-dialog>
160
+ <script>
161
+ document.querySelector('#openDialog1').addEventListener('click', () => {
162
+ document.querySelector('#dialog1').opened = true;
163
+ });
164
+ </script>
165
+ </template>
166
+ </d2l-demo-snippet>
167
+
168
+ </d2l-demo-page>
169
+
170
+ </body>
171
+ </html>
@@ -7,6 +7,7 @@ import { getComposedActiveElement, getFirstFocusableDescendant, getPreviousFocus
7
7
  import { classMap } from 'lit/directives/class-map.js';
8
8
  import { html } from 'lit';
9
9
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
10
+ import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
10
11
  import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';
11
12
  import { styleMap } from 'lit/directives/style-map.js';
12
13
  import { tryGetIfrauBackdropService } from '../../helpers/ifrauBackdropService.js';
@@ -17,6 +18,8 @@ const minBackdropHeightMobile = 42;
17
18
  const minBackdropWidthMobile = 30;
18
19
  const outerMarginTopBottom = 18;
19
20
  const defaultVerticalOffset = 16;
21
+ const pointerLength = 16;
22
+ const pointerRotatedLength = Math.SQRT2 * parseFloat(pointerLength);
20
23
 
21
24
  export const DropdownContentMixin = superclass => class extends LocalizeCoreElement(RtlMixin(superclass)) {
22
25
 
@@ -178,6 +181,14 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
178
181
  reflect: true,
179
182
  attribute: 'opened-above'
180
183
  },
184
+ /**
185
+ * Temporary.
186
+ * @ignore
187
+ */
188
+ preferFixedPositioning: {
189
+ type: Boolean,
190
+ attribute: 'prefer-fixed-positioning'
191
+ },
181
192
  /**
182
193
  * Optionally render a d2l-focus-trap around the dropdown content
183
194
  * @type {boolean}
@@ -206,6 +217,11 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
206
217
  attribute: 'dropdown-content',
207
218
  reflect: true
208
219
  },
220
+ _fixedPositioning: {
221
+ type: Boolean,
222
+ attribute: '_fixed-positioning',
223
+ reflect: true
224
+ },
209
225
  _useMobileStyling: {
210
226
  type: Boolean,
211
227
  attribute: 'data-mobile',
@@ -220,8 +236,11 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
220
236
  _contentHeight: {
221
237
  type: Number
222
238
  },
239
+ _pointerPosition: {
240
+ state: true
241
+ },
223
242
  _position: {
224
- type: Number
243
+ state: true
225
244
  },
226
245
  _showBackdrop: {
227
246
  type: Boolean
@@ -339,6 +358,12 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
339
358
  });
340
359
  }
341
360
 
361
+ willUpdate(changedProperties) {
362
+ if (this._fixedPositioning === undefined || changedProperties.has('preferFixedPositioning')) {
363
+ this._fixedPositioning = (window.D2L?.LP?.Web?.UI?.Flags.Flag('GAUD-131-dropdown-fixed-positioning', false) && this.preferFixedPositioning);
364
+ }
365
+ }
366
+
342
367
  close() {
343
368
  const hide = () => {
344
369
  this._closing = false;
@@ -453,6 +478,10 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
453
478
  return opener;
454
479
  }
455
480
 
481
+ __getPointer() {
482
+ return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-pointer');
483
+ }
484
+
456
485
  __getPositionContainer() {
457
486
  return this.shadowRoot && this.shadowRoot.querySelector('.d2l-dropdown-content-position');
458
487
  }
@@ -619,8 +648,8 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
619
648
  }
620
649
 
621
650
  const content = this.getContentContainer();
622
- const header = this.__getContentTop();
623
- const footer = this.__getContentBottom();
651
+ const header = this.__getContentTop(); // todo: rename
652
+ const footer = this.__getContentBottom(); // todo: rename
624
653
 
625
654
  if (!this.noAutoFit) {
626
655
  this._contentHeight = null;
@@ -629,15 +658,15 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
629
658
  /* don't let dropdown content horizontally overflow viewport */
630
659
  this._width = null;
631
660
 
632
- const openerPosition = window.getComputedStyle(opener, null).getPropertyValue('position');
661
+ const openerPosition = window.getComputedStyle(opener, null).getPropertyValue('position'); // todo: cleanup when switched to fixed positioning
633
662
  const boundingContainer = getBoundingAncestor(target.parentNode);
634
- const boundingContainerRect = boundingContainer.getBoundingClientRect();
663
+ const boundingContainerRect = boundingContainer.getBoundingClientRect(); // todo: cleanup when switched to fixed positioning
635
664
  const scrollHeight = boundingContainer.scrollHeight;
636
665
 
637
666
  await this.updateComplete;
638
667
 
639
668
  // position check in case consuming app (LMS) has overriden position to make content absolute wrt document
640
- const bounded = (openerPosition === 'relative' && boundingContainer !== document.documentElement);
669
+ const bounded = (!this._fixedPositioning && openerPosition === 'relative' && boundingContainer !== document.documentElement);
641
670
 
642
671
  const adjustPosition = async() => {
643
672
 
@@ -646,13 +675,16 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
646
675
  const headerFooterHeight = header.getBoundingClientRect().height + footer.getBoundingClientRect().height;
647
676
 
648
677
  const height = this.minHeight ? this.minHeight : Math.min(this.maxHeight ? this.maxHeight : Number.MAX_VALUE, contentRect.height + headerFooterHeight);
678
+
649
679
  const spaceRequired = {
650
680
  height: height + 10,
651
681
  width: contentRect.width
652
682
  };
683
+
653
684
  let spaceAround;
654
685
  let spaceAroundScroll;
655
686
  if (bounded) {
687
+
656
688
  spaceAround = this._constrainSpaceAround({
657
689
  // allow for target offset + outer margin
658
690
  above: targetRect.top - boundingContainerRect.top - this._verticalOffset - outerMarginTopBottom,
@@ -663,11 +695,14 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
663
695
  // allow for outer margin
664
696
  right: boundingContainerRect.right - targetRect.right - 20
665
697
  }, spaceRequired, targetRect);
698
+
666
699
  spaceAroundScroll = this._constrainSpaceAround({
667
700
  above: targetRect.top - boundingContainerRect.top + boundingContainer.scrollTop,
668
701
  below: scrollHeight - targetRect.bottom + boundingContainerRect.top - boundingContainer.scrollTop
669
702
  }, spaceRequired, targetRect);
703
+
670
704
  } else {
705
+
671
706
  spaceAround = this._constrainSpaceAround({
672
707
  // allow for target offset + outer margin
673
708
  above: targetRect.top - this._verticalOffset - outerMarginTopBottom,
@@ -678,20 +713,20 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
678
713
  // allow for outer margin
679
714
  right: document.documentElement.clientWidth - targetRect.right - 15
680
715
  }, spaceRequired, targetRect);
716
+
681
717
  spaceAroundScroll = this._constrainSpaceAround({
682
718
  above: targetRect.top + document.documentElement.scrollTop,
683
719
  below: scrollHeight - targetRect.bottom - document.documentElement.scrollTop
684
720
  }, spaceRequired, targetRect);
721
+
685
722
  }
686
723
 
687
724
  if (!ignoreVertical) {
688
725
  this.openedAbove = this._getOpenedAbove(spaceAround, spaceAroundScroll, spaceRequired);
689
726
  }
690
727
 
691
- const position = this._getPosition(spaceAround, targetRect.width, contentRect.width);
692
- if (position !== null) {
693
- this._position = position;
694
- }
728
+ this._position = this._getPosition(spaceAround, targetRect, contentRect);
729
+ this._pointerPosition = this._getPointerPosition(targetRect);
695
730
 
696
731
  //Calculate height available to the dropdown contents for overflow because that is the only area capable of scrolling
697
732
  const availableHeight = this.openedAbove ? spaceAround.above : spaceAround.below;
@@ -978,8 +1013,69 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
978
1013
  return false;
979
1014
  }
980
1015
 
981
- _getPosition(spaceAround, targetWidth, contentWidth) {
982
- const centerDelta = contentWidth - targetWidth;
1016
+ _getPointerPosition(targetRect) {
1017
+ const position = {};
1018
+ if (!this._fixedPositioning) return position;
1019
+
1020
+ const pointer = this.__getPointer();
1021
+ if (!pointer) return position;
1022
+
1023
+ const pointerRect = pointer.getBoundingClientRect();
1024
+ const isRTL = this.getAttribute('dir') === 'rtl';
1025
+ if (this.align === 'start' || this.align === 'end') {
1026
+ const pointerXAdjustment = Math.min(20 + ((pointerRotatedLength - pointerLength) / 2), (targetRect.width - pointerLength) / 2);
1027
+ if ((this.align === 'start' && !isRTL) || (this.align === 'end' && isRTL)) {
1028
+ position.left = targetRect.left + pointerXAdjustment;
1029
+ } else {
1030
+ position.right = window.innerWidth - targetRect.right + pointerXAdjustment;
1031
+ }
1032
+ } else {
1033
+ if (!isRTL) {
1034
+ position.left = targetRect.left + ((targetRect.width - pointerRect.width) / 2);
1035
+ } else {
1036
+ position.right = window.innerWidth - targetRect.left - ((targetRect.width + pointerRect.width) / 2);
1037
+ }
1038
+ }
1039
+ if (this.openedAbove) {
1040
+ position.bottom = window.innerHeight - targetRect.top + 8;
1041
+ } else {
1042
+ position.top = targetRect.top + targetRect.height + this._verticalOffset - 7;
1043
+ }
1044
+
1045
+ return position;
1046
+ }
1047
+
1048
+ _getPosition(spaceAround, targetRect, contentRect) {
1049
+ const position = {};
1050
+ const isRTL = this.getAttribute('dir') === 'rtl';
1051
+ const positionXAdjustment = this._getPositionXAdjustment(spaceAround, targetRect, contentRect);
1052
+ if (this._fixedPositioning) {
1053
+ if (positionXAdjustment !== null) {
1054
+ if (!isRTL) {
1055
+ position.left = targetRect.left + positionXAdjustment;
1056
+ } else {
1057
+ position.right = window.innerWidth - targetRect.left - targetRect.width + positionXAdjustment;
1058
+ }
1059
+ }
1060
+ if (this.openedAbove) {
1061
+ position.bottom = window.innerHeight - targetRect.top + this._verticalOffset;
1062
+ } else {
1063
+ position.top = targetRect.top + targetRect.height + this._verticalOffset;
1064
+ }
1065
+ } else {
1066
+ if (positionXAdjustment !== null) {
1067
+ if (!isRTL) {
1068
+ position.left = positionXAdjustment;
1069
+ } else {
1070
+ position.right = positionXAdjustment;
1071
+ }
1072
+ }
1073
+ }
1074
+ return position;
1075
+ }
1076
+
1077
+ _getPositionXAdjustment(spaceAround, targetRect, contentRect) {
1078
+ const centerDelta = contentRect.width - targetRect.width;
983
1079
  const contentXAdjustment = centerDelta / 2;
984
1080
  if (!this.align && centerDelta <= 0) {
985
1081
  return contentXAdjustment * -1;
@@ -1007,11 +1103,11 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
1007
1103
  }
1008
1104
  }
1009
1105
  if (this.align === 'start' || this.align === 'end') {
1010
- const shift = Math.min((targetWidth / 2) - (20 + 16 / 2), 0); // 20 ~= 1rem, 16 = pointer size
1106
+ const shift = Math.min((targetRect.width / 2) - (20 + pointerLength / 2), 0); // 20 ~= 1rem
1011
1107
  if (this.align === 'start') {
1012
1108
  return shift;
1013
1109
  } else {
1014
- return targetWidth - contentWidth - shift;
1110
+ return targetRect.width - contentRect.width - shift;
1015
1111
  }
1016
1112
  }
1017
1113
  return null;
@@ -1064,15 +1160,6 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
1064
1160
  }
1065
1161
 
1066
1162
  _renderContent() {
1067
- const positionStyle = {};
1068
- const isRTL = this.getAttribute('dir') === 'rtl';
1069
- if (this._position) {
1070
- if (!isRTL) {
1071
- positionStyle.left = `${this._position}px`;
1072
- } else {
1073
- positionStyle.right = `${this._position}px`;
1074
- }
1075
- }
1076
1163
 
1077
1164
  const mobileTrayRightLeft = this._useMobileStyling && (this.mobileTray === 'right' || this.mobileTray === 'left');
1078
1165
  const mobileTrayBottom = this._useMobileStyling && (this.mobileTray === 'bottom');
@@ -1131,16 +1218,34 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
1131
1218
 
1132
1219
  if (this.trapFocus) {
1133
1220
  dropdownContentSlots = html`
1134
- <d2l-focus-trap
1135
- @d2l-focus-trap-enter="${this._handleFocusTrapEnter}"
1136
- ?trap="${this.opened}">
1137
- ${dropdownContentSlots}
1221
+ <d2l-focus-trap @d2l-focus-trap-enter="${this._handleFocusTrapEnter}" ?trap="${this.opened}">
1222
+ ${dropdownContentSlots}
1138
1223
  </d2l-focus-trap>`;
1139
1224
  }
1140
1225
 
1226
+ const positionStyle = {};
1227
+ if (this._position) {
1228
+ for (const prop in this._position) {
1229
+ positionStyle[prop] = `${this._position[prop]}px`;
1230
+ }
1231
+ }
1232
+
1141
1233
  const dropdown = html`
1142
1234
  <div class="d2l-dropdown-content-position" style=${styleMap(positionStyle)}>
1143
- ${dropdownContentSlots}
1235
+ ${dropdownContentSlots}
1236
+ </div>
1237
+ `;
1238
+
1239
+ const pointerPositionStyle = {};
1240
+ if (this._pointerPosition) {
1241
+ for (const prop in this._pointerPosition) {
1242
+ pointerPositionStyle[prop] = `${this._pointerPosition[prop]}px`;
1243
+ }
1244
+ }
1245
+
1246
+ const pointer = html`
1247
+ <div class="d2l-dropdown-content-pointer" style="${styleMap(pointerPositionStyle)}">
1248
+ <div></div>
1144
1249
  </div>
1145
1250
  `;
1146
1251
 
@@ -1149,8 +1254,9 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
1149
1254
  <d2l-backdrop
1150
1255
  for-target="d2l-dropdown-wrapper"
1151
1256
  ?shown="${this._showBackdrop}" >
1152
- </d2l-backdrop>`
1153
- : html`${dropdown}`;
1257
+ </d2l-backdrop>
1258
+ ${pointer}`
1259
+ : html`${dropdown}${pointer}`;
1154
1260
  }
1155
1261
 
1156
1262
  };
@@ -23,6 +23,10 @@ export const dropdownContentStyles = css`
23
23
  width: 100%;
24
24
  z-index: 998; /* position on top of floating buttons */
25
25
  }
26
+ :host([_fixed-positioning]) {
27
+ position: fixed;
28
+ top: 0;
29
+ }
26
30
 
27
31
  :host([theme="dark"]) {
28
32
  --d2l-dropdown-above-animation-name: d2l-dropdown-above-animation-dark;
@@ -44,6 +48,9 @@ export const dropdownContentStyles = css`
44
48
  bottom: calc(100% + var(--d2l-dropdown-verticaloffset, 16px));
45
49
  top: auto;
46
50
  }
51
+ :host([_fixed-positioning][opened-above]) {
52
+ bottom: 0;
53
+ }
47
54
 
48
55
  :host([data-mobile][opened]:not([mobile-tray])) {
49
56
  animation: var(--d2l-dropdown-animation-name) 300ms ease;
@@ -59,18 +66,24 @@ export const dropdownContentStyles = css`
59
66
  .d2l-dropdown-content-pointer {
60
67
  clip: rect(-5px, 21px, 8px, -7px);
61
68
  display: inline-block;
62
- left: calc(50% - 7px);
69
+ left: calc(50% - 7px); /* todo: cleanup when switched to fixed positioning */
63
70
  position: absolute;
64
- top: -7px;
71
+ top: -7px; /* todo: cleanup when switched to fixed positioning */
65
72
  z-index: 1;
66
73
  }
74
+ :host([_fixed-positioning][dir="rtl"]) .d2l-dropdown-content-pointer {
75
+ left: auto;
76
+ }
77
+
67
78
  :host([align="start"]) .d2l-dropdown-content-pointer,
68
79
  :host([align="end"][dir="rtl"]) .d2l-dropdown-content-pointer {
80
+ /* todo: cleanup when switched to fixed positioning */
69
81
  left: min(calc(1rem + ${(pointerRotatedLength - pointerLength) / 2}px), calc(50% - ${pointerLength / 2}px)); /* 1rem corresponds to .d2l-dropdown-content-container padding */
70
82
  right: auto;
71
83
  }
72
84
  :host([align="end"]) .d2l-dropdown-content-pointer,
73
85
  :host([align="start"][dir="rtl"]) .d2l-dropdown-content-pointer {
86
+ /* todo: cleanup when switched to fixed positioning */
74
87
  left: auto;
75
88
  right: min(calc(1rem + ${(pointerRotatedLength - pointerLength) / 2}px), calc(50% - ${pointerLength / 2}px)); /* 1rem corresponds to .d2l-dropdown-content-container padding */
76
89
  }
@@ -91,6 +104,9 @@ export const dropdownContentStyles = css`
91
104
  clip: rect(9px, 21px, 22px, -3px);
92
105
  top: auto;
93
106
  }
107
+ :host([_fixed-positioning][opened-above]) .d2l-dropdown-content-pointer {
108
+ bottom: auto;
109
+ }
94
110
 
95
111
  :host([opened-above]) .d2l-dropdown-content-pointer > div {
96
112
  box-shadow: 4px 4px 12px -5px rgba(32, 33, 34, 0.2); /* ferrite */
@@ -1,6 +1,6 @@
1
- import { html, LitElement } from 'lit';
2
1
  import { DropdownContentMixin } from './dropdown-content-mixin.js';
3
2
  import { dropdownContentStyles } from './dropdown-content-styles.js';
3
+ import { LitElement } from 'lit';
4
4
 
5
5
  /**
6
6
  * A generic container for dropdown content. It provides behavior such as sizing, positioning, and managing focus gain/loss.
@@ -16,12 +16,7 @@ class DropdownContent extends DropdownContentMixin(LitElement) {
16
16
  }
17
17
 
18
18
  render() {
19
- return html`
20
- ${this._renderContent()}
21
- <div class="d2l-dropdown-content-pointer">
22
- <div></div>
23
- </div>
24
- `;
19
+ return this._renderContent();
25
20
  }
26
21
 
27
22
  }
@@ -1,4 +1,4 @@
1
- import { css, html, LitElement } from 'lit';
1
+ import { css, LitElement } from 'lit';
2
2
  import { DropdownContentMixin } from './dropdown-content-mixin.js';
3
3
  import { dropdownContentStyles } from './dropdown-content-styles.js';
4
4
  import { ThemeMixin } from '../../mixins/theme/theme-mixin.js';
@@ -91,12 +91,7 @@ class DropdownMenu extends ThemeMixin(DropdownContentMixin(LitElement)) {
91
91
  }
92
92
 
93
93
  render() {
94
- return html`
95
- ${this._renderContent()}
96
- <div class="d2l-dropdown-content-pointer">
97
- <div></div>
98
- </div>
99
- `;
94
+ return this._renderContent();
100
95
  }
101
96
 
102
97
  __getMenuElement() {
@@ -44,6 +44,19 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
44
44
  type: Boolean,
45
45
  attribute: 'open-on-hover'
46
46
  },
47
+ /**
48
+ * Temporary.
49
+ * @ignore
50
+ */
51
+ preferFixedPositioning: {
52
+ type: Boolean,
53
+ attribute: 'prefer-fixed-positioning'
54
+ },
55
+ _fixedPositioning: {
56
+ type: Boolean,
57
+ attribute: '_fixed-positioning',
58
+ reflect: true,
59
+ },
47
60
  _isHovering: { type: Boolean },
48
61
  _isOpenedViaClick: { type: Boolean },
49
62
  _isFading: { type: Boolean }
@@ -123,6 +136,12 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
123
136
  }
124
137
  }
125
138
 
139
+ willUpdate(changedProperties) {
140
+ if (changedProperties.has('preferFixedPositioning')) {
141
+ this._fixedPositioning = (window.D2L?.LP?.Web?.UI?.Flags.Flag('GAUD-131-dropdown-fixed-positioning', false) && this.preferFixedPositioning);
142
+ }
143
+ }
144
+
126
145
  /* used by open-on-hover option */
127
146
  async closeDropdown(fadeOut) {
128
147
  this.dropdownOpened = false;
@@ -7,6 +7,9 @@ export const dropdownOpenerStyles = css`
7
7
  overflow: visible;
8
8
  position: relative;
9
9
  }
10
+ :host([_fixed-positioning]) {
11
+ position: static;
12
+ }
10
13
  :host([hidden]) {
11
14
  display: none;
12
15
  }
@@ -1,4 +1,4 @@
1
- import { css, html, LitElement } from 'lit';
1
+ import { css, LitElement } from 'lit';
2
2
  import { DropdownContentMixin } from './dropdown-content-mixin.js';
3
3
  import { dropdownContentStyles } from './dropdown-content-styles.js';
4
4
 
@@ -28,12 +28,7 @@ class DropdownTabs extends DropdownContentMixin(LitElement) {
28
28
  }
29
29
 
30
30
  render() {
31
- return html`
32
- ${this._renderContent()}
33
- <div class="d2l-dropdown-content-pointer">
34
- <div></div>
35
- </div>
36
- `;
31
+ return this._renderContent();
37
32
  }
38
33
 
39
34
  _getTabsElement() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.6.0",
3
+ "version": "3.7.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",