@brightspace-ui/core 3.74.0 → 3.74.2
Sign up to get free protection for your applications and to get access to all the features.
- package/components/object-property-list/demo/object-property-list.html +1 -0
- package/components/object-property-list/object-property-list-item-link.js +5 -14
- package/components/popover/demo/popover.html +242 -17
- package/components/popover/popover-mixin.js +556 -50
- package/custom-elements.json +83 -0
- package/helpers/mathjax.js +4 -1
- package/package.json +1 -1
@@ -35,6 +35,7 @@
|
|
35
35
|
<d2l-object-property-list-item text="Example item"></d2l-object-property-list-item>
|
36
36
|
<d2l-object-property-list-item text="Example item with icon" icon="tier1:grade"></d2l-object-property-list-item>
|
37
37
|
<d2l-object-property-list-item-link text="Example link" href="https://www.d2l.com/"></d2l-object-property-list-item-link>
|
38
|
+
<d2l-object-property-list-item-link target="_blank" text="Example new tab link" href="https://www.d2l.com/"></d2l-object-property-list-item-link>
|
38
39
|
<d2l-object-property-list-item-link text="Example link with icon" href="https://www.d2l.com/" icon="tier1:alert"></d2l-object-property-list-item-link>
|
39
40
|
</d2l-object-property-list>
|
40
41
|
</template>
|
@@ -1,7 +1,7 @@
|
|
1
|
+
import '../link/link.js';
|
1
2
|
import { FocusMixin } from '../../mixins/focus/focus-mixin.js';
|
2
3
|
import { html } from 'lit';
|
3
4
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
4
|
-
import { linkStyles } from '../link/link.js';
|
5
5
|
import { ObjectPropertyListItem } from './object-property-list-item.js';
|
6
6
|
|
7
7
|
/**
|
@@ -29,29 +29,20 @@ class ObjectPropertyListItemLink extends FocusMixin(ObjectPropertyListItem) {
|
|
29
29
|
};
|
30
30
|
}
|
31
31
|
|
32
|
-
static get styles() {
|
33
|
-
return [
|
34
|
-
...super.styles,
|
35
|
-
linkStyles
|
36
|
-
];
|
37
|
-
}
|
38
|
-
|
39
32
|
static get focusElementSelector() {
|
40
|
-
return '
|
33
|
+
return 'd2l-link';
|
41
34
|
}
|
42
35
|
|
43
36
|
render() {
|
44
37
|
return html`
|
45
38
|
${this._renderIcon()}
|
46
39
|
${!this.skeleton ? html`
|
47
|
-
<
|
40
|
+
<d2l-link
|
48
41
|
?download="${this.download}"
|
49
|
-
class="d2l-link"
|
50
42
|
href="${ifDefined(this.href)}"
|
51
|
-
target="${ifDefined(this.target)}"
|
52
|
-
>
|
43
|
+
target="${ifDefined(this.target)}">
|
53
44
|
${this._renderText()}
|
54
|
-
</
|
45
|
+
</d2l-link>
|
55
46
|
` : this._renderText()}
|
56
47
|
${this._renderSeparator()}
|
57
48
|
`;
|
@@ -6,10 +6,20 @@
|
|
6
6
|
<meta charset="UTF-8">
|
7
7
|
<link rel="stylesheet" href="../../demo/styles.css" type="text/css">
|
8
8
|
<script type="module">
|
9
|
+
import '../../button/button.js';
|
9
10
|
import '../../button/button-subtle.js';
|
10
11
|
import '../../demo/demo-page.js';
|
12
|
+
import '../../dialog/dialog.js';
|
11
13
|
import '../../link/link.js';
|
12
14
|
import '../test/popover.js';
|
15
|
+
|
16
|
+
window.wireUpPopover = demo => {
|
17
|
+
const popover = demo.querySelector('d2l-test-popover');
|
18
|
+
const openButton = demo.querySelector('d2l-button-subtle[text="Open"]');
|
19
|
+
openButton.addEventListener('click', () => popover.opened = !popover.opened);
|
20
|
+
const closeButton = demo.querySelector('d2l-button-subtle[text="Close"]');
|
21
|
+
if (closeButton) closeButton.addEventListener('click', () => popover.opened = false);
|
22
|
+
};
|
13
23
|
</script>
|
14
24
|
</head>
|
15
25
|
|
@@ -20,41 +30,256 @@
|
|
20
30
|
<h2>Popover</h2>
|
21
31
|
<d2l-demo-snippet>
|
22
32
|
<template>
|
23
|
-
<d2l-button-subtle
|
24
|
-
<d2l-test-popover
|
33
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
34
|
+
<d2l-test-popover>
|
35
|
+
<div slot="header">header content</div>
|
36
|
+
<d2l-button-subtle text="Close"></d2l-button-subtle>
|
37
|
+
<div>Sink me piracy Gold Road quarterdeck wherry long boat line pillage walk the plank Plate Fleet. Haul wind black spot strike colors deadlights lee Barbary Coast yo-ho-ho ballast gally Shiver me timbers. Sea Legs quarterdeck yard scourge of the seven seas coffer plunder lanyard holystone code of conduct belay.</div>
|
38
|
+
<div style="border: 1px solid black; width: 600px;">stuff</div>
|
39
|
+
<div slot="footer">footer content</div>
|
40
|
+
</d2l-test-popover>
|
41
|
+
<script>
|
42
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
43
|
+
</script>
|
44
|
+
</template>
|
45
|
+
</d2l-demo-snippet>
|
46
|
+
|
47
|
+
<h2>Popover (content width less than min-width)</h2>
|
48
|
+
<d2l-demo-snippet>
|
49
|
+
<template>
|
50
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
51
|
+
<d2l-test-popover>
|
52
|
+
<div>1</div>
|
53
|
+
</d2l-test-popover>
|
54
|
+
<script>
|
55
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
56
|
+
</script>
|
57
|
+
</template>
|
58
|
+
</d2l-demo-snippet>
|
59
|
+
|
60
|
+
<h2>Popover (content width greater than min-width, less than max-width)</h2>
|
61
|
+
<d2l-demo-snippet>
|
62
|
+
<template>
|
63
|
+
<div style="text-align: center;"><d2l-button-subtle text="Open"></d2l-button-subtle></div>
|
64
|
+
<d2l-test-popover>
|
65
|
+
<div>Sink me piracy Gold Road.</div>
|
66
|
+
</d2l-test-popover>
|
67
|
+
<script>
|
68
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
69
|
+
</script>
|
70
|
+
</template>
|
71
|
+
</d2l-demo-snippet>
|
72
|
+
|
73
|
+
<h2>Popover (content width greater than max-width)</h2>
|
74
|
+
<d2l-demo-snippet>
|
75
|
+
<template>
|
76
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
77
|
+
<d2l-test-popover>
|
25
78
|
<div>Sink me piracy Gold Road quarterdeck wherry long boat line pillage walk the plank Plate Fleet. Haul wind black spot strike colors deadlights lee Barbary Coast yo-ho-ho ballast gally Shiver me timbers. Sea Legs quarterdeck yard scourge of the seven seas coffer plunder lanyard holystone code of conduct belay.</div>
|
26
|
-
<d2l-button-subtle id="close1" text="Close"></d2l-button-subtle>
|
27
79
|
</d2l-test-popover>
|
28
80
|
<script>
|
29
|
-
|
30
|
-
|
31
|
-
|
81
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
82
|
+
</script>
|
83
|
+
</template>
|
84
|
+
</d2l-demo-snippet>
|
85
|
+
|
86
|
+
<h2>Popover (no pointer)</h2>
|
87
|
+
<d2l-demo-snippet>
|
88
|
+
<template>
|
89
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
90
|
+
<d2l-test-popover no-pointer>
|
91
|
+
<div>Sink me piracy Gold Road quarterdeck wherry long boat line pillage walk the plank Plate Fleet. Haul wind black spot strike colors deadlights lee Barbary Coast yo-ho-ho ballast gally Shiver me timbers. Sea Legs quarterdeck yard scourge of the seven seas coffer plunder lanyard holystone code of conduct belay.</div>
|
92
|
+
</d2l-test-popover>
|
93
|
+
<script>
|
94
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
95
|
+
</script>
|
96
|
+
</template>
|
97
|
+
</d2l-demo-snippet>
|
98
|
+
|
99
|
+
<h2>Popover (custom max-width, single-line)</h2>
|
100
|
+
<d2l-demo-snippet>
|
101
|
+
<template>
|
102
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
103
|
+
<d2l-test-popover max-width="1000">
|
104
|
+
<div>Sink me piracy Gold Road quarterdeck wherry and some.</div>
|
105
|
+
</d2l-test-popover>
|
106
|
+
<script>
|
107
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
108
|
+
</script>
|
109
|
+
</template>
|
110
|
+
</d2l-demo-snippet>
|
111
|
+
|
112
|
+
<h2>Popover (custom max-width, multi-line)</h2>
|
113
|
+
<d2l-demo-snippet>
|
114
|
+
<template>
|
115
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
116
|
+
<d2l-test-popover max-width="1000">
|
117
|
+
<div>Sink me piracy Gold Road quarterdeck wherry long boat line pillage walk the plank Plate Fleet. Haul wind black spot strike colors deadlights lee Barbary Coast yo-ho-ho ballast gally Shiver me timbers. Sea Legs quarterdeck yard scourge of the seven seas coffer plunder lanyard holystone code of conduct belay.</div>
|
118
|
+
</d2l-test-popover>
|
119
|
+
<script>
|
120
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
121
|
+
</script>
|
122
|
+
</template>
|
123
|
+
</d2l-demo-snippet>
|
124
|
+
|
125
|
+
<h2>Popover (custom min-width)</h2>
|
126
|
+
<d2l-demo-snippet>
|
127
|
+
<template>
|
128
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
129
|
+
<d2l-test-popover min-width="60">
|
130
|
+
<div>1</div>
|
131
|
+
</d2l-test-popover>
|
132
|
+
<script>
|
133
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
134
|
+
</script>
|
135
|
+
</template>
|
136
|
+
</d2l-demo-snippet>
|
137
|
+
|
138
|
+
<h2>Popover (position location)</h2>
|
139
|
+
<d2l-demo-snippet>
|
140
|
+
<template>
|
141
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
142
|
+
<d2l-test-popover position-location="block-start">
|
143
|
+
<div>Sink me piracy Gold Road.</div>
|
144
|
+
</d2l-test-popover>
|
145
|
+
<script>
|
146
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
147
|
+
</script>
|
148
|
+
</template>
|
149
|
+
</d2l-demo-snippet>
|
150
|
+
|
151
|
+
<h2>Popover (position span)</h2>
|
152
|
+
<d2l-demo-snippet>
|
153
|
+
<template>
|
154
|
+
<div style="text-align: center;"><d2l-button-subtle text="Open"></d2l-button-subtle></div>
|
155
|
+
<d2l-test-popover position-span="end">
|
156
|
+
<div>Sink me piracy Gold Road.</div>
|
157
|
+
</d2l-test-popover>
|
158
|
+
<script>
|
159
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
160
|
+
</script>
|
161
|
+
</template>
|
162
|
+
</d2l-demo-snippet>
|
163
|
+
|
164
|
+
<h2>Popover (in a scrollable container)</h2>
|
165
|
+
<d2l-demo-snippet>
|
166
|
+
<template>
|
167
|
+
<div style="height: 250px; overflow: scroll;">
|
168
|
+
<p>Gabion warp American Main gunwalls cutlass gally cable gibbet jib keel. Trysail chantey swing the lead hempen halter hang the jib chase Jack Tar furl galleon scurvy. Brig splice the main brace provost pink rutters tender heave to Shiver me timbers belaying pin Brethren of the Coast.</p>
|
169
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
170
|
+
<d2l-test-popover>
|
171
|
+
<div>Sink me piracy Gold Road quarterdeck wherry long boat line pillage walk the plank Plate Fleet. Haul wind black spot strike colors deadlights lee Barbary Coast yo-ho-ho ballast gally Shiver me timbers. Sea Legs quarterdeck yard scourge of the seven seas coffer plunder lanyard holystone code of conduct belay.</div>
|
172
|
+
</d2l-test-popover>
|
173
|
+
<p>Shiver me timbers to go on account lookout wherry doubloon chase. Belay yo-ho-ho keelhaul squiffy black spot yardarm spyglass sheet transom heave to.</p>
|
174
|
+
<p>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.</p>
|
175
|
+
<p>Hardtack hang the jib haul wind booty pillage spike hearties Pirate Round tack yard. Piracy fire ship trysail stern scurvy blow the man down skysail salmagundi lee grog blossom. Hands gabion ho schooner lad ballast keel mutiny square-rigged haul wind.</p>
|
176
|
+
</div>
|
177
|
+
<script>
|
178
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
32
179
|
</script>
|
33
180
|
</template>
|
34
181
|
</d2l-demo-snippet>
|
35
182
|
|
183
|
+
<h2>Popover (with DOM mutation)</h2>
|
184
|
+
<d2l-demo-snippet>
|
185
|
+
<template>
|
186
|
+
<div>
|
187
|
+
<div id="mutations-above"></div>
|
188
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
189
|
+
<d2l-test-popover>
|
190
|
+
<d2l-button-subtle id="mutations-add-above">Add to Above</d2l-button-subtle>
|
191
|
+
</d2l-test-popover>
|
192
|
+
</div>
|
193
|
+
<script>
|
194
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
195
|
+
|
196
|
+
document.querySelector('#mutations-add-above').addEventListener('click', e => {
|
197
|
+
const mutationsContainer = e.target.parentNode.parentNode.parentNode.querySelector('#mutations-above');
|
198
|
+
const newContent = document.createElement('p');
|
199
|
+
newContent.innerText = 'Blimey brigantine gangplank booty rope\'s end lugger heave down run a rig Yellow Jack dead men tell no tales. Pirate Round scuppers spanker hogshead Davy Jone\'s Locker heave down wench fluke marooned boom. Lanyard salmagundi careen doubloon swing the lead shrouds crow\'s nest parrel gun pressgang.';
|
200
|
+
mutationsContainer.appendChild(newContent);
|
201
|
+
});
|
202
|
+
</script>
|
203
|
+
</template>
|
204
|
+
</d2l-demo-snippet>
|
205
|
+
|
206
|
+
<h2>Popover (in a dialog)</h2>
|
207
|
+
<d2l-demo-snippet>
|
208
|
+
<template>
|
209
|
+
<d2l-button id="openDialog1">Show Dialog</d2l-button>
|
210
|
+
<d2l-dialog id="dialog1" title-text="Dialog Title">
|
211
|
+
<div>
|
212
|
+
<p>Bilge tack furl dance the hempen jig fathom weigh anchor mizzen Blimey Jack Ketch flogging. Lee galleon avast schooner long clothes scuppers pinnace bucko deadlights gibbet. Nipper brigantine Buccaneer Gold Road matey gangway booty tender killick Brethren of the Coast.</p>
|
213
|
+
<d2l-button-subtle id="openPopover1" text="Open"></d2l-button-subtle>
|
214
|
+
<d2l-test-popover id="popover1">
|
215
|
+
<div>Sink me piracy Gold Road quarterdeck wherry long boat line pillage walk the plank Plate Fleet. Haul wind black spot strike colors deadlights lee Barbary Coast yo-ho-ho ballast gally Shiver me timbers. Sea Legs quarterdeck yard scourge of the seven seas coffer plunder lanyard holystone code of conduct belay.</div>
|
216
|
+
</d2l-test-popover>
|
217
|
+
<p>Piracy bowsprit Arr shrouds salmagundi scuttle heave down doubloon trysail Jack Ketch. Killick boom Jolly Roger Pieces of Eight crack Jennys tea cup Cat o'nine tails league Privateer topgallant lanyard. Cat o'nine tails coxswain scurvy spirits keelhaul quarterdeck matey nipper scallywag Jolly Roger.</p>
|
218
|
+
<p>Clap of thunder aye Corsair Barbary Coast prow shrouds schooner keel topmast code of conduct. Matey case shot spirits Davy Jones' Locker draft schooner Brethren of the Coast barkadeer jury mast measured fer yer chains. Bilge rat run a rig gaff warp loot clipper belaying pin main sheet lanyard avast.</p>
|
219
|
+
<p>Pieces of Eight lookout Letter of Marque mutiny tender spanker Jack Ketch long clothes crow's nest line. Lass draught six pounders spirits skysail jib American Main chase hulk coxswain. Run a shot across the bow galleon Cat o'nine tails brigantine reef Admiral of the Black wherry quarterdeck keelhaul coffer.</p>
|
220
|
+
</div>
|
221
|
+
<d2l-button slot="footer" primary data-dialog-action="ok">Click Me!</d2l-button>
|
222
|
+
<d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
|
223
|
+
</d2l-dialog>
|
224
|
+
<script>
|
225
|
+
document.querySelector('#openDialog1').addEventListener('click', () => {
|
226
|
+
document.querySelector('#dialog1').opened = true;
|
227
|
+
});
|
228
|
+
document.querySelector('#openPopover1').addEventListener('click', () => {
|
229
|
+
const popover = document.querySelector('#popover1');
|
230
|
+
popover.opened = !popover.opened;
|
231
|
+
});
|
232
|
+
</script>
|
233
|
+
</template>
|
234
|
+
</d2l-demo-snippet>
|
235
|
+
|
236
|
+
<h2>Popover (in another popover)</h2>
|
237
|
+
<d2l-demo-snippet>
|
238
|
+
<template>
|
239
|
+
<d2l-button-subtle id="outerOpener" text="Open"></d2l-button-subtle>
|
240
|
+
<d2l-test-popover id="outerPopover">
|
241
|
+
<d2l-button-subtle id="innerOpener" text="Open Nested"></d2l-button-subtle>
|
242
|
+
<d2l-test-popover id="innerPopover">
|
243
|
+
<div>Piracy bowsprit Arr shrouds salmagundi scuttle heave down doubloon trysail Jack Ketch. Killick boom Jolly Roger Pieces of Eight crack Jennys tea cup Cat o'nine tails league Privateer topgallant lanyard. Cat o'nine tails coxswain scurvy spirits keelhaul quarterdeck matey nipper scallywag Jolly Roger.</div>
|
244
|
+
</d2l-test-popover>
|
245
|
+
</d2l-test-popover>
|
246
|
+
<script>
|
247
|
+
document.querySelector('#outerOpener').addEventListener('click', () => {
|
248
|
+
const popover = document.querySelector('#outerPopover');
|
249
|
+
popover.opened = !popover.opened;
|
250
|
+
});
|
251
|
+
document.querySelector('#innerOpener').addEventListener('click', () => {
|
252
|
+
const popover = document.querySelector('#innerPopover');
|
253
|
+
popover.opened = !popover.opened;
|
254
|
+
});
|
255
|
+
</script>
|
256
|
+
</template>
|
257
|
+
</d2l-demo-snippet>
|
258
|
+
|
259
|
+
|
36
260
|
<h2>Popover (trap-focus)</h2>
|
37
261
|
<d2l-demo-snippet>
|
38
262
|
<template>
|
39
|
-
<d2l-button-subtle
|
40
|
-
<d2l-test-popover trap-focus
|
263
|
+
<d2l-button-subtle text="Open"></d2l-button-subtle>
|
264
|
+
<d2l-test-popover trap-focus style="max-width: 400px;">
|
41
265
|
<d2l-link href="https://pirateipsum.me/" target="_blank">Pirate Ipsum</d2l-link>
|
42
266
|
<div>Sink me piracy Gold Road quarterdeck wherry long boat line pillage walk the plank Plate Fleet. Haul wind black spot strike colors deadlights lee Barbary Coast yo-ho-ho ballast gally Shiver me timbers. Sea Legs quarterdeck yard scourge of the seven seas coffer plunder lanyard holystone code of conduct belay.</div>
|
43
|
-
<d2l-button-subtle
|
267
|
+
<d2l-button-subtle text="Close"></d2l-button-subtle>
|
44
268
|
</d2l-test-popover>
|
45
269
|
<script>
|
46
|
-
|
47
|
-
document.querySelector('#open2').addEventListener('click', () => popover2.opened = !popover2.opened);
|
48
|
-
document.querySelector('#close2').addEventListener('click', () => popover2.opened = false);
|
49
|
-
popover2.addEventListener('d2l-popover-focus-enter', e => console.log(e.type, e));
|
270
|
+
window.wireUpPopover(document.currentScript.parentNode);
|
50
271
|
</script>
|
51
272
|
</template>
|
273
|
+
</template>
|
52
274
|
</d2l-demo-snippet>
|
53
275
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
276
|
+
</d2l-demo-page>
|
277
|
+
|
278
|
+
<script>
|
279
|
+
document.addEventListener('d2l-popover-open', e => console.log(e.type, e));
|
280
|
+
document.addEventListener('d2l-popover-close', e => console.log(e.type, e));
|
281
|
+
document.addEventListener('d2l-popover-focus-enter', e => console.log(e.type, e), true);
|
282
|
+
</script>
|
58
283
|
</body>
|
59
284
|
|
60
285
|
</html>
|
@@ -1,10 +1,19 @@
|
|
1
1
|
import '../colors/colors.js';
|
2
2
|
import '../focus-trap/focus-trap.js';
|
3
3
|
import { clearDismissible, setDismissible } from '../../helpers/dismissible.js';
|
4
|
-
import { css, html } from 'lit';
|
4
|
+
import { css, html, nothing } from 'lit';
|
5
5
|
import { getComposedActiveElement, getFirstFocusableDescendant, getPreviousFocusableAncestor } from '../../helpers/focus.js';
|
6
|
-
import { isComposedAncestor } from '../../helpers/dom.js';
|
7
|
-
|
6
|
+
import { getComposedParent, isComposedAncestor } from '../../helpers/dom.js';
|
7
|
+
import { _offscreenStyleDeclarations } from '../offscreen/offscreen.js';
|
8
|
+
import { styleMap } from 'lit/directives/style-map.js';
|
9
|
+
|
10
|
+
const defaultPreferredPosition = {
|
11
|
+
location: 'block-end', // block-start, block-end
|
12
|
+
span: 'all', // start, end, all
|
13
|
+
allowFlip: true
|
14
|
+
};
|
15
|
+
const pointerLength = 16;
|
16
|
+
const pointerRotatedLength = Math.SQRT2 * parseFloat(pointerLength);
|
8
17
|
const isSupported = ('popover' in HTMLElement.prototype);
|
9
18
|
|
10
19
|
// eslint-disable-next-line no-console
|
@@ -14,11 +23,27 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
14
23
|
|
15
24
|
static get properties() {
|
16
25
|
return {
|
26
|
+
_contentHeight: { state: true },
|
27
|
+
_location: { type: String, reflect: true, attribute: '_location' },
|
28
|
+
_margin: { state: true },
|
29
|
+
_maxHeight: { state: true },
|
30
|
+
_maxWidth: { state: true },
|
31
|
+
_minHeight: { state: true },
|
32
|
+
_minWidth: { state: true },
|
17
33
|
_noAutoClose: { state: true },
|
34
|
+
_noAutoFit: { state: true },
|
18
35
|
_noAutoFocus: { state: true },
|
36
|
+
_noPointer: { state: true },
|
37
|
+
_offscreen: { type: Boolean, reflect: true, attribute: '_offscreen' },
|
38
|
+
_offset: { state: true },
|
19
39
|
_opened: { type: Boolean, reflect: true, attribute: '_opened' },
|
40
|
+
_pointerPosition: { state: true },
|
41
|
+
_position: { state: true },
|
42
|
+
_preferredPosition: { state: true },
|
43
|
+
_rtl: { state: true },
|
20
44
|
_trapFocus: { state: true },
|
21
|
-
_useNativePopover: { type: String, reflect: true, attribute: 'popover' }
|
45
|
+
_useNativePopover: { type: String, reflect: true, attribute: 'popover' },
|
46
|
+
_width: { state: true }
|
22
47
|
};
|
23
48
|
}
|
24
49
|
|
@@ -31,18 +56,18 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
31
56
|
--d2l-popover-default-border-radius: 0.3rem;
|
32
57
|
--d2l-popover-default-foreground-color: var(--d2l-color-ferrite);
|
33
58
|
--d2l-popover-default-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.15);
|
34
|
-
background-color: transparent;
|
35
|
-
border: none;
|
59
|
+
background-color: transparent; /* override popover default */
|
60
|
+
border: none; /* override popover */
|
36
61
|
box-sizing: border-box;
|
37
62
|
color: var(--d2l-popover-foreground-color, var(--d2l-popover-default-foreground-color));
|
38
63
|
display: none;
|
39
|
-
height: fit-content;
|
40
|
-
inset: 0;
|
41
|
-
margin:
|
42
|
-
overflow: visible;
|
43
|
-
padding: 0;
|
44
|
-
position: fixed;
|
45
|
-
width: fit-content;
|
64
|
+
height: fit-content; /* normalize popover */
|
65
|
+
inset: 0; /* normalize popover */
|
66
|
+
margin: 0; /* override popover */
|
67
|
+
overflow: visible; /* override popover */
|
68
|
+
padding: 0; /* override popover */
|
69
|
+
position: fixed; /* normalize popover */
|
70
|
+
width: fit-content; /* normalize popover */
|
46
71
|
}
|
47
72
|
:host([hidden]) {
|
48
73
|
display: none;
|
@@ -53,14 +78,56 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
53
78
|
:host([_opened]) {
|
54
79
|
display: inline-block;
|
55
80
|
}
|
81
|
+
:host([_location="block-start"]) {
|
82
|
+
bottom: 0;
|
83
|
+
top: auto;
|
84
|
+
}
|
56
85
|
|
57
|
-
.content {
|
86
|
+
.content-position {
|
87
|
+
display: inline-block;
|
88
|
+
position: absolute;
|
89
|
+
}
|
90
|
+
.content-width {
|
58
91
|
background-color: var(--d2l-popover-background-color, var(--d2l-popover-default-background-color));
|
59
92
|
border: 1px solid var(--d2l-popover-border-color, var(--d2l-popover-default-border-color));
|
60
93
|
border-radius: var(--d2l-popover-border-radius, var(--d2l-popover-default-border-radius));
|
61
94
|
box-shadow: var(--d2l-popover-shadow, var(--d2l-popover-default-shadow));
|
62
95
|
box-sizing: border-box;
|
96
|
+
max-width: 370px;
|
97
|
+
min-width: 70px;
|
98
|
+
width: 100vw;
|
99
|
+
}
|
100
|
+
.content-container {
|
101
|
+
box-sizing: border-box;
|
102
|
+
display: inline-block;
|
103
|
+
max-width: 100%;
|
63
104
|
outline: none;
|
105
|
+
overflow-y: auto;
|
106
|
+
}
|
107
|
+
|
108
|
+
.pointer {
|
109
|
+
clip: rect(-5px, 21px, 8px, -7px);
|
110
|
+
display: inline-block;
|
111
|
+
position: absolute;
|
112
|
+
z-index: 1;
|
113
|
+
}
|
114
|
+
|
115
|
+
.pointer > div {
|
116
|
+
background-color: var(--d2l-popover-background-color, var(--d2l-popover-default-background-color));
|
117
|
+
border: 1px solid var(--d2l-popover-border-color, var(--d2l-popover-default-border-color));
|
118
|
+
border-radius: 0.1rem;
|
119
|
+
box-shadow: -4px -4px 12px -5px rgba(32, 33, 34, 0.2); /* ferrite */
|
120
|
+
height: ${pointerLength}px;
|
121
|
+
transform: rotate(45deg);
|
122
|
+
width: ${pointerLength}px;
|
123
|
+
}
|
124
|
+
|
125
|
+
:host([_location="block-start"]) .pointer {
|
126
|
+
clip: rect(9px, 21px, 22px, -3px);
|
127
|
+
}
|
128
|
+
|
129
|
+
:host([_location="block-start"]) .pointer > div {
|
130
|
+
box-shadow: 4px 4px 12px -5px rgba(32, 33, 34, 0.2); /* ferrite */
|
64
131
|
}
|
65
132
|
|
66
133
|
@keyframes d2l-popover-animation {
|
@@ -72,6 +139,10 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
72
139
|
animation: var(--d2l-popover-animation-name, var(--d2l-popover-default-animation-name)) 300ms ease;
|
73
140
|
}
|
74
141
|
}
|
142
|
+
|
143
|
+
:host([_offscreen]) {
|
144
|
+
${_offscreenStyleDeclarations}
|
145
|
+
}
|
75
146
|
`;
|
76
147
|
}
|
77
148
|
|
@@ -79,19 +150,26 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
79
150
|
super();
|
80
151
|
this.configure();
|
81
152
|
this._useNativePopover = isSupported ? 'manual' : undefined;
|
82
|
-
this
|
83
|
-
this
|
153
|
+
this.#handleAncestorMutationBound = this.#handleAncestorMutation.bind(this);
|
154
|
+
this.#handleAutoCloseClickBound = this.#handleAutoCloseClick.bind(this);
|
155
|
+
this.#handleAutoCloseFocusBound = this.#handleAutoCloseFocus.bind(this);
|
156
|
+
this.#handleResizeBound = this.#handleResize.bind(this);
|
157
|
+
this.#repositionBound = this.#reposition.bind(this);
|
84
158
|
}
|
85
159
|
|
86
160
|
connectedCallback() {
|
87
161
|
super.connectedCallback();
|
88
|
-
if (this._opened)
|
162
|
+
if (this._opened) {
|
163
|
+
this.#addAutoCloseHandlers();
|
164
|
+
this.#addRepositionHandlers();
|
165
|
+
}
|
89
166
|
}
|
90
167
|
|
91
168
|
disconnectedCallback() {
|
92
169
|
super.disconnectedCallback();
|
93
|
-
this
|
94
|
-
this
|
170
|
+
this.#removeAutoCloseHandlers();
|
171
|
+
this.#removeRepositionHandlers();
|
172
|
+
this.#clearDismissible();
|
95
173
|
}
|
96
174
|
|
97
175
|
async close() {
|
@@ -102,22 +180,44 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
102
180
|
if (this._useNativePopover) this.hidePopover();
|
103
181
|
|
104
182
|
this._previousFocusableAncestor = null;
|
105
|
-
this
|
106
|
-
this
|
183
|
+
this.#removeAutoCloseHandlers();
|
184
|
+
this.#removeRepositionHandlers();
|
185
|
+
this.#clearDismissible();
|
107
186
|
await this.updateComplete; // wait before applying focus to opener
|
108
|
-
this
|
187
|
+
this.#focusOpener();
|
109
188
|
this.dispatchEvent(new CustomEvent('d2l-popover-close', { bubbles: true, composed: true }));
|
189
|
+
|
110
190
|
}
|
111
191
|
|
112
192
|
configure(properties) {
|
193
|
+
this._margin = properties?.margin ?? 18;
|
194
|
+
this._maxHeight = properties?.maxHeight;
|
195
|
+
this._maxWidth = properties?.maxWidth;
|
196
|
+
this._minHeight = properties?.minHeight;
|
197
|
+
this._minWidth = properties?.minWidth;
|
113
198
|
this._noAutoClose = properties?.noAutoClose ?? false;
|
199
|
+
this._noAutoFit = properties?.noAutoFit ?? false;
|
114
200
|
this._noAutoFocus = properties?.noAutoFocus ?? false;
|
201
|
+
this._noPointer = properties?.noPointer ?? false;
|
202
|
+
this._offset = properties?.offset ?? 16;
|
203
|
+
if (!properties) {
|
204
|
+
this._preferredPosition = defaultPreferredPosition;
|
205
|
+
} else if (this._preferredPosition?.location !== properties.position?.location
|
206
|
+
|| this._preferredPosition?.span !== properties.position?.span
|
207
|
+
|| this._preferredPosition?.allowFlip !== properties.position?.allowFlip) {
|
208
|
+
this._preferredPosition = {
|
209
|
+
location: properties?.position?.location ?? 'block-end',
|
210
|
+
span: properties?.position?.span ?? 'all',
|
211
|
+
allowFlip: properties?.position?.allowFlip ?? true
|
212
|
+
};
|
213
|
+
}
|
115
214
|
this._trapFocus = properties?.trapFocus ?? false;
|
116
215
|
}
|
117
216
|
|
118
217
|
async open(applyFocus = true) {
|
119
218
|
if (this._opened) return;
|
120
219
|
|
220
|
+
this._rtl = document.documentElement.getAttribute('dir') === 'rtl';
|
121
221
|
this._applyFocus = applyFocus !== undefined ? applyFocus : true;
|
122
222
|
this._opened = true;
|
123
223
|
|
@@ -127,10 +227,73 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
127
227
|
this._previousFocusableAncestor = getPreviousFocusableAncestor(this, false, false);
|
128
228
|
|
129
229
|
this._opener = getComposedActiveElement();
|
130
|
-
this
|
230
|
+
this.#addAutoCloseHandlers();
|
231
|
+
|
232
|
+
await this.#position();
|
233
|
+
|
131
234
|
this._dismissibleId = setDismissible(() => this.close());
|
132
|
-
|
235
|
+
|
236
|
+
this.#focusContent(this);
|
237
|
+
|
238
|
+
this.#addRepositionHandlers();
|
239
|
+
|
133
240
|
this.dispatchEvent(new CustomEvent('d2l-popover-open', { bubbles: true, composed: true }));
|
241
|
+
|
242
|
+
}
|
243
|
+
|
244
|
+
renderPopover(content) {
|
245
|
+
|
246
|
+
const stylesMap = this.#getStyleMaps();
|
247
|
+
const widthStyle = stylesMap['width'];
|
248
|
+
const contentStyle = stylesMap['content'];
|
249
|
+
|
250
|
+
content = html`
|
251
|
+
<div class="content-width vdiff-target" style=${styleMap(widthStyle)}>
|
252
|
+
<div class="content-container" style=${styleMap(contentStyle)}>${content}</div>
|
253
|
+
</div>
|
254
|
+
`;
|
255
|
+
|
256
|
+
if (this._trapFocus) {
|
257
|
+
content = html`
|
258
|
+
<d2l-focus-trap @d2l-focus-trap-enter="${this.#handleFocusTrapEnter}" ?trap="${this._opened}">
|
259
|
+
${content}
|
260
|
+
</d2l-focus-trap>
|
261
|
+
`;
|
262
|
+
}
|
263
|
+
|
264
|
+
const positionStyles = {};
|
265
|
+
if (this._position) {
|
266
|
+
for (const prop in this._position) {
|
267
|
+
positionStyles[prop] = `${this._position[prop]}px`;
|
268
|
+
}
|
269
|
+
}
|
270
|
+
|
271
|
+
content = html`
|
272
|
+
<div class="content-position" style=${styleMap(positionStyles)}>
|
273
|
+
${content}
|
274
|
+
</div>
|
275
|
+
`;
|
276
|
+
|
277
|
+
const pointerPositionStyles = {};
|
278
|
+
if (this._pointerPosition) {
|
279
|
+
for (const prop in this._pointerPosition) {
|
280
|
+
pointerPositionStyles[prop] = `${this._pointerPosition[prop]}px`;
|
281
|
+
}
|
282
|
+
}
|
283
|
+
|
284
|
+
const pointer = !this._noPointer ? html`
|
285
|
+
<div class="pointer" style="${styleMap(pointerPositionStyles)}">
|
286
|
+
<div></div>
|
287
|
+
</div>
|
288
|
+
` : nothing;
|
289
|
+
|
290
|
+
return html`${content}${pointer}`;
|
291
|
+
|
292
|
+
}
|
293
|
+
|
294
|
+
async resize() {
|
295
|
+
if (!this._opened) return;
|
296
|
+
await this.#position();
|
134
297
|
}
|
135
298
|
|
136
299
|
toggleOpen(applyFocus = true) {
|
@@ -138,49 +301,289 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
138
301
|
else return this.open(!this._noAutoFocus && applyFocus);
|
139
302
|
}
|
140
303
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
304
|
+
#handleAncestorMutationBound;
|
305
|
+
#handleAutoCloseClickBound;
|
306
|
+
#handleAutoCloseFocusBound;
|
307
|
+
#handleResizeBound;
|
308
|
+
#repositionBound;
|
309
|
+
|
310
|
+
#addAutoCloseHandlers() {
|
311
|
+
this.addEventListener('blur', this.#handleAutoCloseFocusBound, { capture: true });
|
312
|
+
document.body.addEventListener('focus', this.#handleAutoCloseFocusBound, { capture: true });
|
313
|
+
document.addEventListener('click', this.#handleAutoCloseClickBound, { capture: true });
|
314
|
+
}
|
315
|
+
|
316
|
+
#addRepositionHandlers() {
|
317
|
+
|
318
|
+
const isScrollable = (node, prop) => {
|
319
|
+
const value = window.getComputedStyle(node, null).getPropertyValue(prop);
|
320
|
+
return (value === 'scroll' || value === 'auto');
|
321
|
+
};
|
322
|
+
|
323
|
+
this.#removeRepositionHandlers();
|
324
|
+
|
325
|
+
window.addEventListener('resize', this.#handleResizeBound);
|
326
|
+
|
327
|
+
this._ancestorMutationObserver ??= new MutationObserver(this.#handleAncestorMutationBound);
|
328
|
+
const mutationConfig = { attributes: true, childList: true, subtree: true };
|
329
|
+
|
330
|
+
let node = this;
|
331
|
+
this._scrollablesObserved = [];
|
332
|
+
while (node) {
|
333
|
+
|
334
|
+
// observe scrollables
|
335
|
+
let observeScrollable = false;
|
336
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
337
|
+
observeScrollable = isScrollable(node, 'overflow-y') || isScrollable(node, 'overflow-x');
|
338
|
+
} else if (node.nodeType === Node.DOCUMENT_NODE) {
|
339
|
+
observeScrollable = true;
|
340
|
+
}
|
341
|
+
if (observeScrollable) {
|
342
|
+
this._scrollablesObserved.push(node);
|
343
|
+
node.addEventListener('scroll', this.#repositionBound);
|
344
|
+
}
|
345
|
+
|
346
|
+
// observe mutations on each DOM scope (excludes sibling scopes... can only do so much)
|
347
|
+
if (node.nodeType === Node.DOCUMENT_NODE || (node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && node.host)) {
|
348
|
+
this._ancestorMutationObserver.observe(node, mutationConfig);
|
349
|
+
}
|
350
|
+
|
351
|
+
node = getComposedParent(node);
|
352
|
+
}
|
353
|
+
|
354
|
+
this._openerIntersectionObserver = new IntersectionObserver(entries => {
|
355
|
+
entries.forEach(entry => this._offscreen = !entry.isIntersecting);
|
356
|
+
}, { threshold: 0 }); // 0-1 (0 -> intersection requires any pixel visible, 1 -> intersection requires all pixels visible)
|
357
|
+
if (this._opener) {
|
358
|
+
this._openerIntersectionObserver.observe(this._opener);
|
359
|
+
}
|
360
|
+
|
145
361
|
}
|
146
362
|
|
147
|
-
|
363
|
+
#clearDismissible() {
|
148
364
|
if (!this._dismissibleId) return;
|
149
365
|
clearDismissible(this._dismissibleId);
|
150
366
|
this._dismissibleId = null;
|
151
367
|
}
|
152
368
|
|
153
|
-
|
369
|
+
#constrainSpaceAround(spaceAround, spaceRequired, openerRect) {
|
370
|
+
const constrained = { ...spaceAround };
|
371
|
+
|
372
|
+
if ((this._preferredPosition.span === 'end' && !this._rtl) || (this._preferredPosition.span === 'start' && this._rtl)) {
|
373
|
+
constrained.left = Math.max(0, spaceRequired.width - (openerRect.width + spaceAround.right));
|
374
|
+
} else if ((this._preferredPosition.span === 'end' && this._rtl) || (this._preferredPosition.span === 'start' && !this._rtl)) {
|
375
|
+
constrained.right = Math.max(0, spaceRequired.width - (openerRect.width + spaceAround.left));
|
376
|
+
}
|
377
|
+
|
378
|
+
return constrained;
|
379
|
+
}
|
380
|
+
|
381
|
+
#focusContent(container) {
|
154
382
|
if (this._noAutoFocus || this._applyFocus === false) return;
|
155
383
|
|
156
384
|
const focusable = getFirstFocusableDescendant(container);
|
157
385
|
if (focusable) {
|
158
|
-
//
|
386
|
+
// removing the rAF call can allow infinite focus looping to happen in content using a focus trap
|
159
387
|
requestAnimationFrame(() => focusable.focus());
|
160
388
|
} else {
|
161
|
-
const content = this
|
389
|
+
const content = this.#getContentContainer();
|
162
390
|
content.setAttribute('tabindex', '-1');
|
163
391
|
content.focus();
|
164
392
|
}
|
165
393
|
}
|
166
394
|
|
167
|
-
|
395
|
+
#focusOpener() {
|
168
396
|
if (!document.activeElement) return;
|
169
397
|
if (!isComposedAncestor(this, getComposedActiveElement())) return;
|
170
398
|
|
171
399
|
this?._opener.focus();
|
172
400
|
}
|
173
401
|
|
174
|
-
|
175
|
-
return this.shadowRoot.querySelector('.content');
|
402
|
+
#getContentContainer() {
|
403
|
+
return this.shadowRoot.querySelector('.content-container');
|
404
|
+
}
|
405
|
+
|
406
|
+
#getLocation(spaceAround, spaceAroundScroll, spaceRequired) {
|
407
|
+
|
408
|
+
const preferred = this._preferredPosition;
|
409
|
+
if (!preferred.allowFlip) {
|
410
|
+
return preferred.location;
|
411
|
+
}
|
412
|
+
|
413
|
+
if (preferred.location === 'block-end') {
|
414
|
+
if (spaceAround.below >= spaceRequired.height) return 'block-end';
|
415
|
+
if (spaceAround.above >= spaceRequired.height) return 'block-start';
|
416
|
+
// if auto-fit is enabled, scroll will be enabled for the inner content so it will always fit in the available space so pick the largest space it can be displayed in
|
417
|
+
if (!this.noAutoFit) return spaceAround.above > spaceAround.below ? 'block-start' : 'block-end';
|
418
|
+
if (spaceAroundScroll.below >= spaceRequired.height) return 'block-end';
|
419
|
+
if (spaceAroundScroll.above >= spaceRequired.height) return 'block-start';
|
420
|
+
}
|
421
|
+
|
422
|
+
if (preferred.location === 'block-start') {
|
423
|
+
if (spaceAround.above >= spaceRequired.height) return 'block-start';
|
424
|
+
if (spaceAround.below >= spaceRequired.height) return 'block-end';
|
425
|
+
// if auto-fit is enabled, scroll will be enabled for the inner content so it will always fit in the available space so pick the largest space it can be displayed in
|
426
|
+
if (!this.noAutoFit) return spaceAround.above > spaceAround.below ? 'block-start' : 'block-end';
|
427
|
+
if (spaceAroundScroll.above >= spaceRequired.height) return 'block-start';
|
428
|
+
if (spaceAroundScroll.below >= spaceRequired.height) return 'block-end';
|
429
|
+
}
|
430
|
+
|
431
|
+
// todo: add location order for inline-start and inline-end
|
432
|
+
|
433
|
+
// if auto-fit is disabled and it doesn't fit in the scrollable space above or below, always open down because it can add scrollable space
|
434
|
+
return 'block-end';
|
435
|
+
}
|
436
|
+
|
437
|
+
#getPointer() {
|
438
|
+
return this.shadowRoot.querySelector('.pointer');
|
439
|
+
}
|
440
|
+
|
441
|
+
#getPointerPosition(openerRect) {
|
442
|
+
const position = {};
|
443
|
+
const pointer = this.#getPointer();
|
444
|
+
if (!pointer) return position;
|
445
|
+
|
446
|
+
const pointerRect = pointer.getBoundingClientRect();
|
447
|
+
|
448
|
+
if (this._preferredPosition.span !== 'all') {
|
449
|
+
const xAdjustment = Math.min(20 + ((pointerRotatedLength - pointerLength) / 2), (openerRect.width - pointerLength) / 2);
|
450
|
+
if (!this._rtl) {
|
451
|
+
if (this._preferredPosition.span === 'end') {
|
452
|
+
position.left = openerRect.left + xAdjustment;
|
453
|
+
} else {
|
454
|
+
position.right = (openerRect.right * -1) + xAdjustment;
|
455
|
+
}
|
456
|
+
} else {
|
457
|
+
if (this._preferredPosition.span === 'end') {
|
458
|
+
position.right = window.innerWidth - openerRect.right + xAdjustment;
|
459
|
+
} else {
|
460
|
+
position.left = (window.innerWidth - openerRect.left - xAdjustment) * -1;
|
461
|
+
}
|
462
|
+
}
|
463
|
+
} else {
|
464
|
+
if (!this._rtl) {
|
465
|
+
position.left = openerRect.left + ((openerRect.width - pointerRect.width) / 2);
|
466
|
+
} else {
|
467
|
+
position.right = window.innerWidth - openerRect.left - ((openerRect.width + pointerRect.width) / 2);
|
468
|
+
}
|
469
|
+
}
|
470
|
+
|
471
|
+
if (this._location === 'block-start') {
|
472
|
+
position.bottom = window.innerHeight - openerRect.top + 8;
|
473
|
+
} else {
|
474
|
+
position.top = openerRect.top + openerRect.height + this._offset - 7;
|
475
|
+
}
|
476
|
+
|
477
|
+
return position;
|
478
|
+
}
|
479
|
+
|
480
|
+
#getPosition(spaceAround, openerRect, contentRect) {
|
481
|
+
const position = {};
|
482
|
+
|
483
|
+
if (this._location === 'block-end' || this._location === 'block-start') {
|
484
|
+
|
485
|
+
const xAdjustment = this.#getPositionXAdjustment(spaceAround, openerRect, contentRect);
|
486
|
+
if (xAdjustment !== null) {
|
487
|
+
if (!this._rtl) {
|
488
|
+
position.left = openerRect.left + xAdjustment;
|
489
|
+
} else {
|
490
|
+
position.right = window.innerWidth - openerRect.left - openerRect.width + xAdjustment;
|
491
|
+
}
|
492
|
+
}
|
493
|
+
|
494
|
+
if (this._location === 'block-start') {
|
495
|
+
position.bottom = window.innerHeight - openerRect.top + this._offset;
|
496
|
+
} else {
|
497
|
+
position.top = openerRect.top + openerRect.height + this._offset;
|
498
|
+
}
|
499
|
+
|
500
|
+
}
|
501
|
+
|
502
|
+
// todo: add position styles for inline-start and inline-end
|
503
|
+
|
504
|
+
return position;
|
505
|
+
}
|
506
|
+
|
507
|
+
#getPositionXAdjustment(spaceAround, openerRect, contentRect) {
|
508
|
+
|
509
|
+
if (this._location === 'block-end' || this._location === 'block-start') {
|
510
|
+
|
511
|
+
const centerDelta = contentRect.width - openerRect.width;
|
512
|
+
const contentXAdjustment = centerDelta / 2;
|
513
|
+
|
514
|
+
if (this._preferredPosition.span === 'all' && centerDelta <= 0) {
|
515
|
+
// center with target (opener wider than content)
|
516
|
+
return contentXAdjustment * -1;
|
517
|
+
}
|
518
|
+
if (this._preferredPosition.span === 'all' && spaceAround.left > contentXAdjustment && spaceAround.right > contentXAdjustment) {
|
519
|
+
// center with target (content wider than opener and enough space around)
|
520
|
+
return contentXAdjustment * -1;
|
521
|
+
}
|
522
|
+
|
523
|
+
if (!this._rtl) {
|
524
|
+
if (spaceAround.left < contentXAdjustment) {
|
525
|
+
// slide content right (not enough space to center)
|
526
|
+
return spaceAround.left * -1;
|
527
|
+
} else if (spaceAround.right < contentXAdjustment) {
|
528
|
+
// slide content left (not enough space to center)
|
529
|
+
return (centerDelta * -1) + spaceAround.right;
|
530
|
+
}
|
531
|
+
} else {
|
532
|
+
if (spaceAround.left < contentXAdjustment) {
|
533
|
+
// slide content right (not enough space to center)
|
534
|
+
return (centerDelta * -1) + spaceAround.left;
|
535
|
+
} else if (spaceAround.right < contentXAdjustment) {
|
536
|
+
// slide content left (not enough space to center)
|
537
|
+
return spaceAround.right * -1;
|
538
|
+
}
|
539
|
+
}
|
540
|
+
|
541
|
+
if (this._preferredPosition.span !== 'all') {
|
542
|
+
// shift it (not enough space to align as requested)
|
543
|
+
const shift = Math.min((openerRect.width / 2) - (20 + pointerLength / 2), 0); // 20 ~= 1rem
|
544
|
+
if (this._preferredPosition.span === 'end') {
|
545
|
+
return shift;
|
546
|
+
} else {
|
547
|
+
return openerRect.width - contentRect.width - shift;
|
548
|
+
}
|
549
|
+
}
|
550
|
+
|
551
|
+
}
|
552
|
+
|
553
|
+
// todo: add position styles for inline-start and inline-end
|
554
|
+
|
555
|
+
return null;
|
556
|
+
}
|
557
|
+
|
558
|
+
#getStyleMaps() {
|
559
|
+
const widthStyle = {
|
560
|
+
maxWidth: this._maxWidth ? `${this._maxWidth}px` : undefined,
|
561
|
+
minWidth: this._minWidth ? `${this._minWidth}px` : undefined,
|
562
|
+
width: this._width ? `${this._width + 3}px` : undefined // add 3 to content to account for possible rounding and also scrollWidth does not include border
|
563
|
+
};
|
564
|
+
|
565
|
+
const contentStyle = {
|
566
|
+
maxHeight: this._contentHeight ? `${this._contentHeight}px` : undefined,
|
567
|
+
};
|
568
|
+
|
569
|
+
return {
|
570
|
+
'width' : widthStyle,
|
571
|
+
'content' : contentStyle
|
572
|
+
};
|
176
573
|
}
|
177
574
|
|
178
|
-
|
575
|
+
#handleAncestorMutation(mutations) {
|
576
|
+
if (!this._opener) return;
|
577
|
+
// ignore mutations that are within this popover
|
578
|
+
const reposition = !!mutations.find(mutation => !isComposedAncestor(this._opener, mutation.target));
|
579
|
+
if (reposition) this.#reposition();
|
580
|
+
}
|
179
581
|
|
582
|
+
#handleAutoCloseClick(e) {
|
180
583
|
if (!this._opened || this._noAutoClose) return;
|
181
584
|
|
182
585
|
const rootTarget = e.composedPath()[0];
|
183
|
-
if (isComposedAncestor(this
|
586
|
+
if (isComposedAncestor(this.#getContentContainer(), rootTarget)
|
184
587
|
|| (this._opener !== document.body && isComposedAncestor(this._opener, rootTarget))) {
|
185
588
|
return;
|
186
589
|
}
|
@@ -188,7 +591,7 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
188
591
|
this.close();
|
189
592
|
}
|
190
593
|
|
191
|
-
|
594
|
+
#handleAutoCloseFocus() {
|
192
595
|
|
193
596
|
// todo: try to use relatedTarget instead - this logic is largely copied as-is from dropdown simply to mitigate risk of this fragile code
|
194
597
|
setTimeout(() => {
|
@@ -212,27 +615,130 @@ export const PopoverMixin = superclass => class extends superclass {
|
|
212
615
|
|
213
616
|
}
|
214
617
|
|
215
|
-
|
216
|
-
this
|
618
|
+
#handleFocusTrapEnter() {
|
619
|
+
this.#focusContent(this.#getContentContainer());
|
217
620
|
|
218
621
|
/** Dispatched when user focus enters the popover (trap-focus option only) */
|
219
622
|
this.dispatchEvent(new CustomEvent('d2l-popover-focus-enter', { detail: { applyFocus: this._applyFocus } }));
|
220
623
|
}
|
221
624
|
|
222
|
-
|
223
|
-
this.
|
224
|
-
|
225
|
-
|
625
|
+
#handleResize() {
|
626
|
+
this.resize();
|
627
|
+
}
|
628
|
+
|
629
|
+
async #position(contentRect, options) {
|
630
|
+
if (!this._opener) return;
|
631
|
+
|
632
|
+
options = Object.assign({ updateLocation: true, updateHeight: true }, options);
|
633
|
+
|
634
|
+
const content = this.#getContentContainer();
|
635
|
+
|
636
|
+
if (!this._noAutoFit && options.updateHeight) {
|
637
|
+
this._contentHeight = null;
|
638
|
+
}
|
639
|
+
|
640
|
+
// don't let popover content horizontally overflow viewport
|
641
|
+
this._width = null;
|
642
|
+
|
643
|
+
await this.updateComplete;
|
644
|
+
|
645
|
+
const adjustPosition = async() => {
|
646
|
+
|
647
|
+
const scrollHeight = document.documentElement.scrollHeight;
|
648
|
+
const openerRect = this._opener.getBoundingClientRect();
|
649
|
+
contentRect = contentRect ?? content.getBoundingClientRect();
|
650
|
+
|
651
|
+
const height = this._minHeight ?? Math.min(this._maxHeight ?? Number.MAX_VALUE, contentRect.height);
|
652
|
+
|
653
|
+
const spaceRequired = {
|
654
|
+
height: height + 10,
|
655
|
+
width: contentRect.width
|
656
|
+
};
|
657
|
+
|
658
|
+
// space in viewport
|
659
|
+
const spaceAround = this.#constrainSpaceAround({
|
660
|
+
// allow for opener offset + outer margin
|
661
|
+
above: openerRect.top - this._offset - this._margin,
|
662
|
+
// allow for opener offset + outer margin
|
663
|
+
below: window.innerHeight - openerRect.bottom - this._offset - this._margin,
|
664
|
+
// allow for outer margin
|
665
|
+
left: openerRect.left - 20,
|
666
|
+
// allow for outer margin
|
667
|
+
right: document.documentElement.clientWidth - openerRect.right - 15
|
668
|
+
}, spaceRequired, openerRect);
|
669
|
+
|
670
|
+
// space in document
|
671
|
+
const spaceAroundScroll = this.#constrainSpaceAround({
|
672
|
+
above: openerRect.top + document.documentElement.scrollTop,
|
673
|
+
below: scrollHeight - openerRect.bottom - document.documentElement.scrollTop
|
674
|
+
}, spaceRequired, openerRect);
|
675
|
+
|
676
|
+
if (options.updateLocation) {
|
677
|
+
this._location = this.#getLocation(spaceAround, spaceAroundScroll, spaceRequired);
|
678
|
+
}
|
679
|
+
|
680
|
+
this._position = this.#getPosition(spaceAround, openerRect, contentRect);
|
681
|
+
if (!this._noPointer) this._pointerPosition = this.#getPointerPosition(openerRect);
|
682
|
+
|
683
|
+
if (options.updateHeight) {
|
684
|
+
|
685
|
+
// calculate height available to the popover contents for overflow because that is the only area capable of scrolling
|
686
|
+
const availableHeight = (this._location === 'block-start') ? spaceAround.above : spaceAround.below;
|
687
|
+
|
688
|
+
if (!this._noAutoFit && availableHeight && availableHeight > 0) {
|
689
|
+
// only apply maximum if it's less than space available and the header/footer alone won't exceed it (content must be visible)
|
690
|
+
this._contentHeight = this._maxHeight !== null && availableHeight > this._maxHeight
|
691
|
+
? this._maxHeight - 2 : availableHeight;
|
692
|
+
|
693
|
+
// ensure the content height has updated when the __toggleScrollStyles event handler runs
|
694
|
+
await this.updateComplete;
|
695
|
+
}
|
696
|
+
|
697
|
+
// todo: handle inline-start and inline-end locations
|
698
|
+
|
699
|
+
}
|
700
|
+
|
701
|
+
/** Dispatched when the popover position finishes adjusting */
|
702
|
+
this.dispatchEvent(new CustomEvent('d2l-popover-position', { bubbles: true, composed: true }));
|
703
|
+
|
704
|
+
};
|
705
|
+
|
706
|
+
const scrollWidth = content.scrollWidth;
|
707
|
+
const availableWidth = window.innerWidth - 40;
|
708
|
+
|
709
|
+
this._width = (availableWidth > scrollWidth ? scrollWidth : availableWidth);
|
710
|
+
|
711
|
+
await this.updateComplete;
|
712
|
+
|
713
|
+
await adjustPosition();
|
714
|
+
|
226
715
|
}
|
227
716
|
|
228
|
-
|
229
|
-
|
717
|
+
#removeAutoCloseHandlers() {
|
718
|
+
this.removeEventListener('blur', this.#handleAutoCloseFocusBound, { capture: true });
|
719
|
+
document.body?.removeEventListener('focus', this.#handleAutoCloseFocusBound, { capture: true }); // DE41322: document.body can be null in some scenarios
|
720
|
+
document.removeEventListener('click', this.#handleAutoCloseClickBound, { capture: true });
|
721
|
+
}
|
230
722
|
|
231
|
-
|
232
|
-
|
233
|
-
|
723
|
+
#removeRepositionHandlers() {
|
724
|
+
this._openerIntersectionObserver?.unobserve(this._opener);
|
725
|
+
this._scrollablesObserved?.forEach(node => {
|
726
|
+
node.removeEventListener('scroll', this.#repositionBound);
|
727
|
+
});
|
728
|
+
this._scrollablesObserved = null;
|
729
|
+
this._ancestorMutationObserver?.disconnect();
|
730
|
+
window.removeEventListener('resize', this.#handleResizeBound);
|
731
|
+
}
|
234
732
|
|
235
|
-
|
733
|
+
#reposition() {
|
734
|
+
// throttle repositioning (https://developer.mozilla.org/en-US/docs/Web/API/Document/scroll_event#scroll_event_throttling)
|
735
|
+
if (!this._repositioning) {
|
736
|
+
requestAnimationFrame(() => {
|
737
|
+
this.#position(undefined, { updateLocation: false, updateHeight: false });
|
738
|
+
this._repositioning = false;
|
739
|
+
});
|
740
|
+
}
|
741
|
+
this._repositioning = true;
|
236
742
|
}
|
237
743
|
|
238
744
|
};
|
package/custom-elements.json
CHANGED
@@ -10599,6 +10599,36 @@
|
|
10599
10599
|
"name": "d2l-test-popover",
|
10600
10600
|
"path": "./components/popover/test/popover.js",
|
10601
10601
|
"attributes": [
|
10602
|
+
{
|
10603
|
+
"name": "max-height",
|
10604
|
+
"description": "Max-height. Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed.",
|
10605
|
+
"type": "number"
|
10606
|
+
},
|
10607
|
+
{
|
10608
|
+
"name": "max-width",
|
10609
|
+
"description": "Max-width (undefined). Specify a number that would be the px value.",
|
10610
|
+
"type": "number"
|
10611
|
+
},
|
10612
|
+
{
|
10613
|
+
"name": "min-height",
|
10614
|
+
"description": "Min-height used 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.",
|
10615
|
+
"type": "number"
|
10616
|
+
},
|
10617
|
+
{
|
10618
|
+
"name": "min-width",
|
10619
|
+
"description": "Min-width (undefined). Specify a number that would be the px value.",
|
10620
|
+
"type": "number"
|
10621
|
+
},
|
10622
|
+
{
|
10623
|
+
"name": "position-location",
|
10624
|
+
"description": "Position the popover before or after the opener. Default is \"block-end\" (after).",
|
10625
|
+
"type": "'block-start'|'block-end'"
|
10626
|
+
},
|
10627
|
+
{
|
10628
|
+
"name": "position-span",
|
10629
|
+
"description": "Position the popover to span from the opener edge to this grid line. Default is \"all\" (centered).",
|
10630
|
+
"type": "'start'|'end'|'all'"
|
10631
|
+
},
|
10602
10632
|
{
|
10603
10633
|
"name": "no-auto-close",
|
10604
10634
|
"description": "Whether to disable auto-close/light-dismiss",
|
@@ -10611,6 +10641,12 @@
|
|
10611
10641
|
"type": "boolean",
|
10612
10642
|
"default": "false"
|
10613
10643
|
},
|
10644
|
+
{
|
10645
|
+
"name": "no-pointer",
|
10646
|
+
"description": "Render without a pointer",
|
10647
|
+
"type": "boolean",
|
10648
|
+
"default": "false"
|
10649
|
+
},
|
10614
10650
|
{
|
10615
10651
|
"name": "opened",
|
10616
10652
|
"description": "Whether the popover is open or not",
|
@@ -10625,6 +10661,42 @@
|
|
10625
10661
|
}
|
10626
10662
|
],
|
10627
10663
|
"properties": [
|
10664
|
+
{
|
10665
|
+
"name": "maxHeight",
|
10666
|
+
"attribute": "max-height",
|
10667
|
+
"description": "Max-height. Note that the default behaviour is to be as tall as necessary within the viewport, so this property is usually not needed.",
|
10668
|
+
"type": "number"
|
10669
|
+
},
|
10670
|
+
{
|
10671
|
+
"name": "maxWidth",
|
10672
|
+
"attribute": "max-width",
|
10673
|
+
"description": "Max-width (undefined). Specify a number that would be the px value.",
|
10674
|
+
"type": "number"
|
10675
|
+
},
|
10676
|
+
{
|
10677
|
+
"name": "minHeight",
|
10678
|
+
"attribute": "min-height",
|
10679
|
+
"description": "Min-height used 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.",
|
10680
|
+
"type": "number"
|
10681
|
+
},
|
10682
|
+
{
|
10683
|
+
"name": "minWidth",
|
10684
|
+
"attribute": "min-width",
|
10685
|
+
"description": "Min-width (undefined). Specify a number that would be the px value.",
|
10686
|
+
"type": "number"
|
10687
|
+
},
|
10688
|
+
{
|
10689
|
+
"name": "positionLocation",
|
10690
|
+
"attribute": "position-location",
|
10691
|
+
"description": "Position the popover before or after the opener. Default is \"block-end\" (after).",
|
10692
|
+
"type": "'block-start'|'block-end'"
|
10693
|
+
},
|
10694
|
+
{
|
10695
|
+
"name": "positionSpan",
|
10696
|
+
"attribute": "position-span",
|
10697
|
+
"description": "Position the popover to span from the opener edge to this grid line. Default is \"all\" (centered).",
|
10698
|
+
"type": "'start'|'end'|'all'"
|
10699
|
+
},
|
10628
10700
|
{
|
10629
10701
|
"name": "noAutoClose",
|
10630
10702
|
"attribute": "no-auto-close",
|
@@ -10639,6 +10711,13 @@
|
|
10639
10711
|
"type": "boolean",
|
10640
10712
|
"default": "false"
|
10641
10713
|
},
|
10714
|
+
{
|
10715
|
+
"name": "noPointer",
|
10716
|
+
"attribute": "no-pointer",
|
10717
|
+
"description": "Render without a pointer",
|
10718
|
+
"type": "boolean",
|
10719
|
+
"default": "false"
|
10720
|
+
},
|
10642
10721
|
{
|
10643
10722
|
"name": "opened",
|
10644
10723
|
"attribute": "opened",
|
@@ -10664,6 +10743,10 @@
|
|
10664
10743
|
{
|
10665
10744
|
"name": "d2l-popover-focus-enter",
|
10666
10745
|
"description": "Dispatched when user focus enters the popover (trap-focus option only)"
|
10746
|
+
},
|
10747
|
+
{
|
10748
|
+
"name": "d2l-popover-position",
|
10749
|
+
"description": "Dispatched when the popover position finishes adjusting"
|
10667
10750
|
}
|
10668
10751
|
]
|
10669
10752
|
},
|
package/helpers/mathjax.js
CHANGED
@@ -98,7 +98,10 @@ export function loadMathJax(mathJaxConfig) {
|
|
98
98
|
options: {
|
99
99
|
menuOptions: {
|
100
100
|
settings: { zoom: 'None' }
|
101
|
-
}
|
101
|
+
},
|
102
|
+
skipHtmlTags: [
|
103
|
+
'd2l-html-block' // Prevents MathJax from reaching into the html-block to try to parse what's inside; we leave that to the custom renderer
|
104
|
+
]
|
102
105
|
},
|
103
106
|
loader: {
|
104
107
|
load: ['ui/menu']
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@brightspace-ui/core",
|
3
|
-
"version": "3.74.
|
3
|
+
"version": "3.74.2",
|
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",
|