@brightspace-ui/core 3.100.2 → 3.101.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.
@@ -258,6 +258,86 @@ document.querySelector('#open').addEventListener('click', () => {
258
258
  });
259
259
  ```
260
260
 
261
+ ## Loading Asynchronous Content
262
+
263
+ The `until` directive can be used for manging the rendering of dialog content when it is loaded asynchronously. The example below loads some dialog content asynchronously, displaying a [Loading Spinner](../loading-spinner/) until the content is ready.
264
+
265
+ Notes on this example:
266
+ - Dialog sizing: `this.resolveLoadingComplete` (part of `LoadingCompleteMixin`) on image load triggers the dialog resize
267
+ - Focus management: focus will go to the first footer button by default. If focus should instead go to a focusable element in the content then that would need to be specified, often by using `.focus()` on the other focusable element.
268
+
269
+ <!-- docs: demo code autoSize:false size:xlarge -->
270
+ ```html
271
+ <script type="module">
272
+ import '@brightspace-ui/core/components/button/button.js';
273
+ import '@brightspace-ui/core/components/loading-spinner/loading-spinner.js';
274
+ import { html, LitElement, noChange } from 'lit';
275
+ import { guard } from 'lit/directives/guard.js';
276
+ import { LoadingCompleteMixin } from '@brightspace-ui/core/mixins/loading-complete/loading-complete-mixin.js';
277
+ import { until } from 'lit/directives/until.js';
278
+
279
+ class DialogAsyncContentUntil extends LoadingCompleteMixin(LitElement) {
280
+
281
+ static get properties() {
282
+ return {
283
+ key: { type: String }
284
+ };
285
+ }
286
+
287
+ constructor() {
288
+ super();
289
+ this.key = null;
290
+ }
291
+
292
+ render() {
293
+ const loadingSpinner = html`<d2l-loading-spinner size="100" style="width: 100%;"></d2l-loading-spinner>`;
294
+ return html`${guard([this.key], () => until(this.#getContent(this.key), loadingSpinner, noChange))}`;
295
+ }
296
+
297
+ #getContent(key) {
298
+ return new Promise((resolve) => {
299
+ if (!key) return;
300
+ setTimeout(() => {
301
+ resolve(html`
302
+ <d2l-button>Focus on me!</d2l-button>
303
+ <img
304
+ src="https://us.v-cdn.net/cdn-cgi/image/fit=scale-down,width=1600/https://us.v-cdn.net/6036482/uploads/Y6MNPRWX5OVH/moose-poses-guided-tour.png"
305
+ style="height: 300px; display: block;"
306
+ @load="${this.#handleImageLoad}"
307
+ >
308
+ `);
309
+ }, 1000);
310
+ });
311
+ }
312
+
313
+ #handleImageLoad() {
314
+ this.shadowRoot.querySelector('d2l-button').focus();
315
+ this.resolveLoadingComplete();
316
+ }
317
+ }
318
+
319
+ customElements.define('d2l-dialog-demo-async-content-until', DialogAsyncContentUntil);
320
+ </script>
321
+ <script type="module">
322
+ import '@brightspace-ui/core/components/button/button.js';
323
+ import '@brightspace-ui/core/components/dialog/dialog.js';
324
+
325
+ document.querySelector('#open').addEventListener('click', () => {
326
+ document.querySelector('#dialog').opened = true;
327
+ document.querySelector('d2l-dialog-demo-async-content-until').key = 'my-dialog';
328
+ });
329
+ document.querySelector('#dialog').addEventListener('d2l-dialog-close', (e) => {
330
+ console.log('dialog action:', e.detail.action);
331
+ });
332
+ </script>
333
+ <d2l-dialog id="dialog" title-text="Async Dialog">
334
+ <d2l-dialog-demo-async-content-until></d2l-dialog-demo-async-content-until>
335
+ <d2l-button slot="footer" primary data-dialog-action="done">Done</d2l-button>
336
+ <d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
337
+ </d2l-dialog>
338
+ <d2l-button id="open">Show Dialog</d2l-button>
339
+ ```
340
+
261
341
  ## Accessibility
262
342
 
263
343
  ### Focus Management
@@ -0,0 +1,89 @@
1
+ import '../../button/button.js';
2
+ import '../../list/list.js';
3
+ import '../../list/list-item.js';
4
+ import '../../list/list-item-content.js';
5
+ import '../../loading-spinner/loading-spinner.js';
6
+ import { css, html, LitElement, noChange } from 'lit';
7
+ import { guard } from 'lit/directives/guard.js';
8
+ import { LoadingCompleteMixin } from '../../../mixins/loading-complete/loading-complete-mixin.js';
9
+ import { until } from 'lit/directives/until.js';
10
+
11
+ class DialogAsyncContentUntil extends LoadingCompleteMixin(LitElement) {
12
+
13
+ static get properties() {
14
+ return {
15
+ href: { type: String }
16
+ };
17
+ }
18
+
19
+ static get styles() {
20
+ return css`
21
+ .content-button {
22
+ padding-block: 1rem;
23
+ }
24
+ .d2l-dialog-content-loading {
25
+ text-align: center;
26
+ }
27
+ `;
28
+ }
29
+
30
+ constructor() {
31
+ super();
32
+ this.href = null;
33
+ }
34
+
35
+ render() {
36
+ const loadingSpinner = html`
37
+ <div class="d2l-dialog-content-loading">
38
+ <d2l-loading-spinner size="100"></d2l-loading-spinner>
39
+ </div>
40
+ `;
41
+ return html`${guard([this.href], () => until(this._getContent(this.href), loadingSpinner, noChange))}`;
42
+ }
43
+
44
+ _getContent(href) {
45
+ return new Promise((resolve) => {
46
+ if (!href) return;
47
+ setTimeout(() => {
48
+ resolve(html`
49
+ <d2l-button class="content-button">Focus on me!</d2l-button>
50
+ <d2l-list>
51
+ <d2l-list-item>
52
+ <img slot="illustration" src="https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg" @load="${this.#handleImageLoad}">
53
+ <d2l-list-item-content>
54
+ <div>Introductory Earth Sciences</div>
55
+ <div slot="supporting-info">This course explores the geological process of the Earth's interior and surface. These include volcanism, earthquakes, mountains...</div>
56
+ </d2l-list-item-content>
57
+ </d2l-list-item>
58
+ <d2l-list-item>
59
+ <img slot="illustration" src="https://s.brightspace.com/course-images/images/e5fd575a-bc14-4a80-89e1-46f349a76178/tile-high-density-max-size.jpg" @load="${this.#handleImageLoad}">
60
+ <d2l-list-item-content>
61
+ <div>Engineering Materials for Energy Systems</div>
62
+ <div slot="supporting-info">This course explores the geological processes of the Earth's interior and surface. These include volcanism, earthquakes, mountain...</div>
63
+ </d2l-list-item-content>
64
+ </d2l-list-item>
65
+ <d2l-list-item>
66
+ <img slot="illustration" src="https://s.brightspace.com/course-images/images/63b162ab-b582-4bf9-8c1d-1dad04714121/tile-high-density-max-size.jpg" @load="${this.#handleImageLoad}">
67
+ <d2l-list-item-content>
68
+ <div>Geomorphology and GIS </div>
69
+ <div slot="supporting-info">This course explores the geological processes of the Earth's interior and surface. These include volcanism, earthquakes, mountain...</div>
70
+ </d2l-list-item-content>
71
+ </d2l-list-item>
72
+ </d2l-list>
73
+ `);
74
+ }, 1000);
75
+ });
76
+ }
77
+
78
+ #handleImageLoad() {
79
+ const images = this.shadowRoot.querySelectorAll('img');
80
+ for (const image of images) {
81
+ if (!image.complete) return;
82
+ }
83
+ this.shadowRoot.querySelector('.content-button').focus();
84
+ this.resolveLoadingComplete();
85
+ }
86
+
87
+ }
88
+
89
+ customElements.define('d2l-dialog-demo-async-content-until', DialogAsyncContentUntil);
@@ -18,6 +18,7 @@
18
18
  import '../../list/demo/demo-list.js';
19
19
  import '../dialog.js';
20
20
  import './dialog-async-content.js';
21
+ import './dialog-async-content-until.js';
21
22
  import './dialog-container.js';
22
23
  </script>
23
24
  <style>
@@ -133,6 +134,28 @@
133
134
  </template>
134
135
  </d2l-demo-snippet>
135
136
 
137
+ <h2>Dialog (async using until)</h2>
138
+
139
+ <d2l-demo-snippet>
140
+ <template>
141
+ <d2l-button id="openAsyncUntil">Show Dialog</d2l-button>
142
+ <d2l-dialog id="dialogAsyncUntil" title-text="Dialog Title">
143
+ <d2l-dialog-demo-async-content-until></d2l-dialog-demo-async-content-until>
144
+ <d2l-button slot="footer" primary data-dialog-action="ok">Click Me!</d2l-button>
145
+ <d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
146
+ </d2l-dialog>
147
+ <script>
148
+ document.querySelector('#openAsyncUntil').addEventListener('click', () => {
149
+ document.querySelector('#dialogAsyncUntil').opened = true;
150
+ document.querySelector('d2l-dialog-demo-async-content-until').href = 'some-href';
151
+ });
152
+ document.querySelector('#dialogAsyncUntil').addEventListener('d2l-dialog-close', (e) => {
153
+ console.log('dialog action:', e.detail.action);
154
+ });
155
+ </script>
156
+ </template>
157
+ </d2l-demo-snippet>
158
+
136
159
  <h2>Dialog (intercept closing)</h2>
137
160
 
138
161
  <d2l-demo-snippet>
@@ -53,7 +53,6 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
53
53
  _isFullHeight: { state: true },
54
54
  _left: { state: true },
55
55
  _margin: { state: true },
56
- _mobileDropdownShowing: { state: true },
57
56
  _nestedShowing: { state: true },
58
57
  _overflowBottom: { state: true },
59
58
  _overflowTop: { state: true },
@@ -73,7 +72,6 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
73
72
  this._autoSize = true;
74
73
  this._dialogId = getUniqueId();
75
74
  this._fullscreenWithin = 0;
76
- this._handleDropdownOpenClose = this._handleDropdownOpenClose.bind(this);
77
75
  this._handleMvcDialogOpen = this._handleMvcDialogOpen.bind(this);
78
76
  this._inIframe = false;
79
77
  this._isDialogMixin = true;
@@ -81,7 +79,6 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
81
79
  this._height = 0;
82
80
  this._left = 0;
83
81
  this._margin = { top: defaultMargin.top, right: defaultMargin.right, bottom: defaultMargin.bottom, left: defaultMargin.left };
84
- this._mobileDropdownShowing = false;
85
82
  this._nestedShowing = false;
86
83
  this._overflowBottom = false;
87
84
  this._overflowTop = false;
@@ -158,8 +155,6 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
158
155
  _addHandlers() {
159
156
  window.addEventListener('resize', this._updateSize);
160
157
  this.addEventListener('touchstart', this._handleTouchStart);
161
- this.addEventListener('d2l-dropdown-open', this._handleDropdownOpenClose, { capture: true });
162
- this.addEventListener('d2l-dropdown-close', this._handleDropdownOpenClose, { capture: true });
163
158
  if (this.shadowRoot) this.shadowRoot.querySelector('.d2l-dialog-content').addEventListener('scroll', this._updateOverflow);
164
159
  }
165
160
 
@@ -350,10 +345,6 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
350
345
  e.stopPropagation();
351
346
  }
352
347
 
353
- _handleDropdownOpenClose(e) {
354
- this._mobileDropdownShowing = e.composedPath()[0]._useMobileStyling;
355
- }
356
-
357
348
  _handleFocusTrapEnter(e) {
358
349
  // ignore focus trap events when the target is another element
359
350
  // to prevent infinite focus loops
@@ -476,8 +467,6 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
476
467
  _removeHandlers() {
477
468
  window.removeEventListener('resize', this._updateSize);
478
469
  this.removeEventListener('touchstart', this._handleTouchStart);
479
- this.removeEventListener('d2l-dropdown-open', this._handleDropdownOpenClose, { capture: true });
480
- this.removeEventListener('d2l-dropdown-close', this._handleDropdownOpenClose, { capture: true });
481
470
  if (this.shadowRoot) this.shadowRoot.querySelector('.d2l-dialog-content').removeEventListener('scroll', this._updateOverflow);
482
471
  }
483
472
 
@@ -506,8 +495,7 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
506
495
  'd2l-dialog-outer-nested-showing': !this._useNative && this._nestedShowing,
507
496
  'd2l-dialog-outer-scroll': this._scroll,
508
497
  'd2l-dialog-fullscreen-mobile': info.fullscreenMobile,
509
- 'd2l-dialog-fullscreen-within': this._fullscreenWithin !== 0,
510
- 'd2l-dialog-dropdown-mobile': this._mobileDropdownShowing
498
+ 'd2l-dialog-fullscreen-within': this._fullscreenWithin !== 0
511
499
  };
512
500
 
513
501
  return html`${this._useNative ?
@@ -151,10 +151,6 @@ export const dialogStyles = css`
151
151
  overflow: auto;
152
152
  }
153
153
 
154
- .d2l-dialog-dropdown-mobile .d2l-dialog-content {
155
- overflow: hidden; /* workaround to fix clipping of nested fixed position elements with overlowing content in Safari bug: https://bugs.webkit.org/show_bug.cgi?id=160953 */
156
- }
157
-
158
154
  .d2l-dialog-footer {
159
155
  box-sizing: border-box;
160
156
  flex: none;
@@ -285,7 +285,7 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
285
285
  case 'left': return 'inline-start';
286
286
  case 'right': return 'inline-end';
287
287
  default: return undefined;
288
- }
288
+ }
289
289
  }
290
290
 
291
291
  #adaptPositionSpan(val) {
@@ -293,7 +293,7 @@ export const DropdownPopoverMixin = superclass => class extends LocalizeCoreElem
293
293
  case 'start': return 'end';
294
294
  case 'end': return 'start';
295
295
  default: return 'all';
296
- }
296
+ }
297
297
  }
298
298
 
299
299
  #getMobileCloseButtonStyles() {
@@ -644,9 +644,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
644
644
  <div class="d2l-filter-dimension-set-value d2l-body-compact">
645
645
  <div class="d2l-filter-dimension-set-value-text">${item.text}</div>
646
646
  ${item.count !== undefined ? html`<div class="d2l-body-small">(${formatNumber(item.count)})</div>` : nothing}
647
- ${item.additionalContent
648
- ? html`<d2l-icon icon="${item.selected ? 'tier1:arrow-collapse-small' : 'tier1:arrow-expand-small'}" aria-hidden="true"></d2l-icon>`
649
- : nothing}
647
+ ${item.additionalContent ? html`<d2l-icon icon="${item.selected ? 'tier1:arrow-collapse-small' : 'tier1:arrow-expand-small'}" aria-hidden="true"></d2l-icon>` : nothing}
650
648
  </div>
651
649
  ${item.additionalContent ? html`
652
650
  <d2l-expand-collapse-content
@@ -658,9 +656,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
658
656
  ` : nothing}
659
657
  </div>
660
658
  </d2l-list-item>
661
- ${item.additionalContent && item.selected && this._displayKeyboardTooltip
662
- ? html`<d2l-tooltip align="start" announced for="${itemId}" for-type="descriptor" @d2l-tooltip-hide="${this._handleTooltipHide}">${this.localizeHTML('components.filter.additionalContentTooltip')}</d2l-tooltip>`
663
- : nothing}
659
+ ${item.additionalContent && item.selected && this._displayKeyboardTooltip ? html`<d2l-tooltip align="start" announced for="${itemId}" for-type="descriptor" @d2l-tooltip-hide="${this._handleTooltipHide}">${this.localizeHTML('components.filter.additionalContentTooltip')}</d2l-tooltip>` : nothing}
664
660
  `;
665
661
  }
666
662
 
@@ -1,4 +1,3 @@
1
- /* eslint-disable indent */
2
1
  import '../../button/button-icon.js';
3
2
  import '../../dropdown/dropdown-button-subtle.js';
4
3
  import '../../dropdown/dropdown-menu.js';
@@ -1982,6 +1982,31 @@
1982
1982
  }
1983
1983
  ]
1984
1984
  },
1985
+ {
1986
+ "name": "d2l-dialog-demo-async-content-until",
1987
+ "path": "./components/dialog/demo/dialog-async-content-until.js",
1988
+ "attributes": [
1989
+ {
1990
+ "name": "href",
1991
+ "type": "string"
1992
+ }
1993
+ ],
1994
+ "properties": [
1995
+ {
1996
+ "name": "href",
1997
+ "attribute": "href",
1998
+ "type": "string"
1999
+ },
2000
+ {
2001
+ "name": "loadingComplete",
2002
+ "type": "Promise<any>"
2003
+ },
2004
+ {
2005
+ "name": "resolveLoadingComplete",
2006
+ "type": "() => void"
2007
+ }
2008
+ ]
2009
+ },
1985
2010
  {
1986
2011
  "name": "d2l-dialog-demo-async-content",
1987
2012
  "path": "./components/dialog/demo/dialog-async-content.js",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.100.2",
3
+ "version": "3.101.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",