@brightspace-ui/core 3.25.0 → 3.25.1

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.
@@ -100,10 +100,6 @@ The `d2l-collapsible-panel` element is a container that provides specific layout
100
100
  d2l-collapsible-panel {
101
101
  width: 800px;
102
102
  }
103
- /* TODO: remove this when daylight demo resizing is fixed */
104
- d2l-collapsible-panel:not([expanded]) {
105
- margin-bottom: 14rem;
106
- }
107
103
  </style>
108
104
 
109
105
  <d2l-collapsible-panel panel-title="Collapsible Panel">
@@ -238,10 +234,6 @@ class CollapsiblePanelDaylightDemo extends LitElement {
238
234
  d2l-collapsible-panel {
239
235
  width: 500px;
240
236
  }
241
- /* TODO: remove this when daylight demo resizing is fixed */
242
- d2l-collapsible-panel:not([expanded]) {
243
- margin-bottom: 12rem;
244
- }
245
237
  `];
246
238
  }
247
239
 
@@ -216,7 +216,13 @@ class CollapsiblePanel extends SkeletonMixin(FocusMixin(RtlMixin(LitElement))) {
216
216
  }
217
217
  .d2l-collapsible-panel-opener {
218
218
  align-self: self-start;
219
+ background-color: transparent;
220
+ border: none;
219
221
  margin-inline-end: var(--d2l-collapsible-panel-spacing-inline);
222
+ order: 1;
223
+ outline: none;
224
+ padding-block: 0;
225
+ padding-inline: 0;
220
226
  }
221
227
  .d2l-collapsible-panel-opener > d2l-icon-custom {
222
228
  height: 0.9rem;
@@ -307,7 +313,7 @@ class CollapsiblePanel extends SkeletonMixin(FocusMixin(RtlMixin(LitElement))) {
307
313
  }
308
314
 
309
315
  static get focusElementSelector() {
310
- return 'button.d2l-offscreen';
316
+ return '.d2l-collapsible-panel-opener';
311
317
  }
312
318
 
313
319
  disconnectedCallback() {
@@ -324,17 +330,8 @@ class CollapsiblePanel extends SkeletonMixin(FocusMixin(RtlMixin(LitElement))) {
324
330
  'scrolled': this._scrolled,
325
331
  'no-bottom-border': this._noBottomBorder,
326
332
  };
327
- const expandCollapseLabel = this.expandCollapseLabel || this.panelTitle;
328
333
 
329
334
  return html`
330
- <button
331
- aria-expanded="${this.expanded}"
332
- class="d2l-offscreen"
333
- type="button"
334
- @click="${this._toggleExpand}"
335
- @focus="${this._onFocus}"
336
- @blur="${this._onBlur}"
337
- >${expandCollapseLabel}</button>
338
335
  <div class="${classMap(classes)}" @click="${this._handlePanelClick}">
339
336
  <div class="d2l-collapsible-panel-top-sentinel"></div>
340
337
  ${this._renderHeader()}
@@ -427,22 +424,31 @@ class CollapsiblePanel extends SkeletonMixin(FocusMixin(RtlMixin(LitElement))) {
427
424
  }
428
425
 
429
426
  _renderHeader() {
427
+ const expandCollapseLabel = this.expandCollapseLabel || this.panelTitle;
430
428
  return html`
431
429
  <div class="d2l-collapsible-panel-header" @click="${this._handleHeaderClick}">
432
430
  <div class="d2l-collapsible-panel-before">
433
431
  <slot name="before" @slotchange="${this._handleBeforeSlotChange}"></slot>
434
432
  </div>
435
433
  <div class="d2l-collapsible-panel-header-primary">
436
- ${this._renderPanelTitle()}
437
- <div class="d2l-collapsible-panel-header-actions" @click="${this._handleActionsClick}">
438
- <slot name="actions"></slot>
439
- </div>
440
- <div class="d2l-collapsible-panel-opener">
434
+ <button
435
+ class="d2l-collapsible-panel-opener"
436
+ aria-expanded="${this.expanded}"
437
+ type="button"
438
+ @click="${this._handleHeaderClick}"
439
+ @focus="${this._onFocus}"
440
+ @blur="${this._onBlur}"
441
+ aria-label="${expandCollapseLabel}"
442
+ >
441
443
  <d2l-icon-custom size="tier1" class="d2l-skeletize">
442
444
  <svg xmlns="http://www.w3.org/2000/svg" width="10" height="18" fill="none" viewBox="0 0 10 18">
443
445
  <path stroke="var(--d2l-color-tungsten)" stroke-linejoin="round" stroke-width="2" d="m9 9-8 8V1l8 8Z"/>
444
446
  </svg>
445
447
  </d2l-icon-custom>
448
+ </button>
449
+ ${this._renderPanelTitle()}
450
+ <div class="d2l-collapsible-panel-header-actions" @click="${this._handleActionsClick}">
451
+ <slot name="actions"></slot>
446
452
  </div>
447
453
  </div>
448
454
  <div class="d2l-collapsible-panel-header-secondary" @click="${this._handleHeaderSecondaryClick}">
@@ -8,6 +8,7 @@
8
8
  <script type="module">
9
9
  import '../../button/button-subtle.js';
10
10
  import '../../demo/demo-page.js';
11
+ import '../../link/link.js';
11
12
  import '../test/popover.js';
12
13
  </script>
13
14
  </head>
@@ -32,6 +33,24 @@
32
33
  </template>
33
34
  </d2l-demo-snippet>
34
35
 
36
+ <h2>Popover (trap-focus)</h2>
37
+ <d2l-demo-snippet>
38
+ <template>
39
+ <d2l-button-subtle id="open2" text="Open"></d2l-button-subtle>
40
+ <d2l-test-popover trap-focus id="popover2" style="max-width: 400px;">
41
+ <d2l-link href="https://pirateipsum.me/" target="_blank">Pirate Ipsum</d2l-link>
42
+ <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 id="close2" text="Close"></d2l-button-subtle>
44
+ </d2l-test-popover>
45
+ <script>
46
+ const popover2 = document.querySelector('#popover2');
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));
50
+ </script>
51
+ </template>
52
+ </d2l-demo-snippet>
53
+
35
54
  <script>
36
55
  document.addEventListener('d2l-popover-open', e => console.log(e.type, e));
37
56
  document.addEventListener('d2l-popover-close', e => console.log(e.type, e));
@@ -1,4 +1,5 @@
1
1
  import '../colors/colors.js';
2
+ import '../focus-trap/focus-trap.js';
2
3
  import { clearDismissible, setDismissible } from '../../helpers/dismissible.js';
3
4
  import { css, html } from 'lit';
4
5
  import { getComposedActiveElement, getFirstFocusableDescendant, getPreviousFocusableAncestor } from '../../helpers/focus.js';
@@ -16,6 +17,7 @@ export const PopoverMixin = superclass => class extends superclass {
16
17
  _noAutoClose: { state: true },
17
18
  _noAutoFocus: { state: true },
18
19
  _opened: { type: Boolean, reflect: true, attribute: '_opened' },
20
+ _trapFocus: { state: true },
19
21
  _useNativePopover: { type: String, reflect: true, attribute: 'popover' }
20
22
  };
21
23
  }
@@ -92,52 +94,43 @@ export const PopoverMixin = superclass => class extends superclass {
92
94
  this._clearDismissible();
93
95
  }
94
96
 
95
- updated(changedProperties) {
96
- super.updated(changedProperties);
97
- if (changedProperties.has('_opened')) {
97
+ async close() {
98
+ if (!this._opened) return;
98
99
 
99
- if (this._useNativePopover) {
100
- if (this._opened) this.showPopover();
101
- else this.hidePopover();
102
- }
103
-
104
- this._previousFocusableAncestor = this._opened ? getPreviousFocusableAncestor(this, false, false) : null;
105
-
106
- if (this._opened) {
107
-
108
- this._opener = getComposedActiveElement();
109
- this._addAutoCloseHandlers();
110
- this._dismissibleId = setDismissible(() => this.close());
111
- this._focusContent(this);
112
- this.dispatchEvent(new CustomEvent('d2l-popover-open', { bubbles: true, composed: true }));
113
-
114
- } else if (changedProperties.get('_opened') !== undefined) {
115
-
116
- this._removeAutoCloseHandlers();
117
- this._clearDismissible();
118
- this._focusOpener();
119
- this.dispatchEvent(new CustomEvent('d2l-popover-close', { bubbles: true, composed: true }));
120
-
121
- }
100
+ this._opened = false;
122
101
 
123
- }
124
- }
102
+ if (this._useNativePopover) this.hidePopover();
125
103
 
126
- close() {
127
- this._opened = false;
128
- return this.updateComplete;
104
+ this._previousFocusableAncestor = null;
105
+ this._removeAutoCloseHandlers();
106
+ this._clearDismissible();
107
+ await this.updateComplete; // wait before applying focus to opener
108
+ this._focusOpener();
109
+ this.dispatchEvent(new CustomEvent('d2l-popover-close', { bubbles: true, composed: true }));
129
110
  }
130
111
 
131
112
  configure(properties) {
132
113
  this._noAutoClose = properties?.noAutoClose ?? false;
133
114
  this._noAutoFocus = properties?.noAutoFocus ?? false;
134
- this._opened = properties?.opened ?? false;
115
+ this._trapFocus = properties?.trapFocus ?? false;
135
116
  }
136
117
 
137
- open(applyFocus = true) {
118
+ async open(applyFocus = true) {
119
+ if (this._opened) return;
120
+
138
121
  this._applyFocus = applyFocus !== undefined ? applyFocus : true;
139
122
  this._opened = true;
140
- return this.updateComplete;
123
+
124
+ await this.updateComplete; // wait for popover attribute before managing top-layer
125
+ if (this._useNativePopover) this.showPopover();
126
+
127
+ this._previousFocusableAncestor = getPreviousFocusableAncestor(this, false, false);
128
+
129
+ this._opener = getComposedActiveElement();
130
+ this._addAutoCloseHandlers();
131
+ this._dismissibleId = setDismissible(() => this.close());
132
+ this._focusContent(this);
133
+ this.dispatchEvent(new CustomEvent('d2l-popover-open', { bubbles: true, composed: true }));
141
134
  }
142
135
 
143
136
  toggleOpen(applyFocus = true) {
@@ -209,7 +202,7 @@ export const PopoverMixin = superclass => class extends superclass {
209
202
  }
210
203
 
211
204
  const activeElement = getComposedActiveElement();
212
- if (isComposedAncestor(this._getContentContainer(), activeElement)
205
+ if (isComposedAncestor(this, activeElement)
213
206
  || activeElement === this._opener) {
214
207
  return;
215
208
  }
@@ -219,6 +212,13 @@ export const PopoverMixin = superclass => class extends superclass {
219
212
 
220
213
  }
221
214
 
215
+ _handleFocusTrapEnter() {
216
+ this._focusContent(this._getContentContainer());
217
+
218
+ /** Dispatched when user focus enters the popover (trap-focus option only) */
219
+ this.dispatchEvent(new CustomEvent('d2l-popover-focus-enter', { detail: { applyFocus: this._applyFocus } }));
220
+ }
221
+
222
222
  _removeAutoCloseHandlers() {
223
223
  this.removeEventListener('blur', this._handleAutoCloseFocus, { capture: true });
224
224
  document.body?.removeEventListener('focus', this._handleAutoCloseFocus, { capture: true }); // DE41322: document.body can be null in some scenarios
@@ -226,7 +226,13 @@ export const PopoverMixin = superclass => class extends superclass {
226
226
  }
227
227
 
228
228
  _renderPopover() {
229
- return html`<div class="content"><slot></slot></div>`;
229
+ const content = html`<div class="content"><slot></slot></div>`;
230
+
231
+ if (this._trapFocus) return html`<d2l-focus-trap @d2l-focus-trap-enter="${this._handleFocusTrapEnter}" ?trap="${this._opened}">
232
+ ${content}
233
+ </d2l-focus-trap>`;
234
+
235
+ return content;
230
236
  }
231
237
 
232
238
  };
@@ -10734,6 +10734,12 @@
10734
10734
  "description": "Whether the popover is open or not",
10735
10735
  "type": "boolean",
10736
10736
  "default": "false"
10737
+ },
10738
+ {
10739
+ "name": "trap-focus",
10740
+ "description": "Whether to render a d2l-focus-trap around the content",
10741
+ "type": "boolean",
10742
+ "default": "false"
10737
10743
  }
10738
10744
  ],
10739
10745
  "properties": [
@@ -10757,14 +10763,25 @@
10757
10763
  "description": "Whether the popover is open or not",
10758
10764
  "type": "boolean",
10759
10765
  "default": "false"
10766
+ },
10767
+ {
10768
+ "name": "trapFocus",
10769
+ "attribute": "trap-focus",
10770
+ "description": "Whether to render a d2l-focus-trap around the content",
10771
+ "type": "boolean",
10772
+ "default": "false"
10760
10773
  }
10761
10774
  ],
10762
10775
  "events": [
10776
+ {
10777
+ "name": "d2l-popover-close"
10778
+ },
10763
10779
  {
10764
10780
  "name": "d2l-popover-open"
10765
10781
  },
10766
10782
  {
10767
- "name": "d2l-popover-close"
10783
+ "name": "d2l-popover-focus-enter",
10784
+ "description": "Dispatched when user focus enters the popover (trap-focus option only)"
10768
10785
  }
10769
10786
  ]
10770
10787
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.25.0",
3
+ "version": "3.25.1",
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",