@brightspace-ui/core 3.94.1 → 3.94.3

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.
@@ -242,33 +242,61 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
242
242
  super.firstUpdated(changedProperties);
243
243
 
244
244
  this.arrowKeysFocusablesProvider = async() => {
245
- return [...this.shadowRoot.querySelectorAll('d2l-tab-internal')];
245
+ return this._defaultSlotBehavior ? [...this.shadowRoot.querySelectorAll('d2l-tab-internal')] : this._tabs;
246
246
  };
247
247
 
248
248
  this.arrowKeysOnBeforeFocus = async(tab) => {
249
- const tabInfo = this._getTabInfo(tab.controlsPanel);
250
- this._setFocusable(tabInfo);
249
+ if (this._defaultSlotBehavior) {
250
+ // remove this section after d2l-tab/d2l-tab-panel backport
251
+ const tabInfo = this._getTabInfo(tab.controlsPanel);
252
+ this._setFocusableDefaultSlotBehavior(tabInfo);
251
253
 
252
- this.requestUpdate();
253
- await this.updateComplete;
254
-
255
- if (!this._scrollCollapsed) {
256
- return this._updateScrollPositionDefaultSlotBehavior(tabInfo);
257
- } else {
258
- const measures = this._getMeasures();
259
- const newTranslationValue = this._calculateScrollPositionDefaultSlotBehavior(tabInfo, measures);
254
+ this.requestUpdate();
255
+ await this.updateComplete;
260
256
 
261
- if (!this.#isRTL()) {
262
- if (newTranslationValue >= 0) return;
257
+ if (!this._scrollCollapsed) {
258
+ return this._updateScrollPositionDefaultSlotBehavior(tabInfo);
263
259
  } else {
264
- if (newTranslationValue <= 0) return;
260
+ const measures = this._getMeasures();
261
+ const newTranslationValue = this._calculateScrollPositionDefaultSlotBehavior(tabInfo, measures);
262
+
263
+ if (!this.#isRTL()) {
264
+ if (newTranslationValue >= 0) return;
265
+ } else {
266
+ if (newTranslationValue <= 0) return;
267
+ }
268
+
269
+ const expanded = await this._tryExpandTabsContainer(measures);
270
+ if (expanded) {
271
+ return;
272
+ } else {
273
+ return this._updateScrollPositionDefaultSlotBehavior(tabInfo);
274
+ }
265
275
  }
276
+ } else {
277
+ this._setFocusable(tab);
266
278
 
267
- const expanded = await this._tryExpandTabsContainer(measures);
268
- if (expanded) {
269
- return;
279
+ this.requestUpdate();
280
+ await this.updateComplete;
281
+
282
+ if (!this._scrollCollapsed) {
283
+ return this._updateScrollPosition(tab);
270
284
  } else {
271
- return this._updateScrollPositionDefaultSlotBehavior(tabInfo);
285
+ const measures = this._getMeasures();
286
+ const newTranslationValue = this._calculateScrollPosition(tab, measures);
287
+
288
+ if (!this.#isRTL()) {
289
+ if (newTranslationValue >= 0) return;
290
+ } else {
291
+ if (newTranslationValue <= 0) return;
292
+ }
293
+
294
+ const expanded = await this._tryExpandTabsContainer(measures);
295
+ if (expanded) {
296
+ return;
297
+ } else {
298
+ return this._updateScrollPosition(tab);
299
+ }
272
300
  }
273
301
  }
274
302
  };
@@ -364,6 +392,10 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
364
392
  return this.shadowRoot.querySelector('.d2l-tabs-container-list').getBoundingClientRect();
365
393
  }
366
394
 
395
+ #checkTabPanelMatchRequested;
396
+ #panels;
397
+ #updateAriaControlsRequested;
398
+
367
399
  _animateTabAddition(tabInfo) {
368
400
  const tab = this.shadowRoot
369
401
  && this.shadowRoot.querySelector(`d2l-tab-internal[controls-panel="${cssEscape(tabInfo.id)}"]`);
@@ -407,6 +439,21 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
407
439
  }
408
440
 
409
441
  async _focusSelected() {
442
+ if (this._defaultSlotBehavior) {
443
+ this._focusSelectedDefaultSlotBehavior();
444
+ return;
445
+ }
446
+
447
+ const selectedTab = this._tabs.find(ti => ti.selected);
448
+ if (!selectedTab) return;
449
+
450
+ await this._updateScrollPosition(selectedTab);
451
+
452
+ selectedTab.focus();
453
+ }
454
+
455
+ // remove after d2l-tab/d2l-tab-panel backport
456
+ async _focusSelectedDefaultSlotBehavior() {
410
457
  const selectedTab = this.shadowRoot && this.shadowRoot.querySelector('d2l-tab-internal[aria-selected="true"]');
411
458
  if (!selectedTab) return;
412
459
 
@@ -438,10 +485,8 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
438
485
  _getPanel(id) {
439
486
  if (this._defaultSlotBehavior) return this._getPanelDefaultSlotBehavior(id);
440
487
 
441
- if (!this.shadowRoot) return;
442
- const slot = this.shadowRoot.querySelector('slot[name="panels"]');
443
- const panels = this._getPanels(slot);
444
- return panels.find(panel => panel.labelledBy === id);
488
+ if (!this.#panels) return;
489
+ return this.#panels.find(panel => panel.labelledBy === id);
445
490
  }
446
491
 
447
492
  // remove after d2l-tab/d2l-tab-panel backport
@@ -449,7 +494,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
449
494
  if (!this.shadowRoot) return;
450
495
  // use simple selector for slot (Edge)
451
496
  const slot = this.shadowRoot.querySelector('.d2l-panels-container').querySelector('slot');
452
- const panels = this._getPanels(slot);
497
+ const panels = this._getPanelsDefaultSlotBehavior(slot);
453
498
  for (let i = 0; i < panels.length; i++) {
454
499
  if (panels[i].nodeType === Node.ELEMENT_NODE && panels[i].role === 'tabpanel' && panels[i].id === id) {
455
500
  return panels[i];
@@ -457,7 +502,8 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
457
502
  }
458
503
  }
459
504
 
460
- _getPanels(slot) {
505
+ // remove after d2l-tab/d2l-tab-panel backport
506
+ _getPanelsDefaultSlotBehavior(slot) {
461
507
  if (!slot) return;
462
508
  return slot.assignedElements({ flatten: true }).filter((node) => node.role === 'tabpanel');
463
509
  }
@@ -470,7 +516,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
470
516
  async _handleDefaultSlotChange(e) {
471
517
  if (!this._defaultSlotBehavior) return;
472
518
 
473
- const panels = this._getPanels(e.target);
519
+ const panels = this._getPanelsDefaultSlotBehavior(e.target);
474
520
 
475
521
  // handle case where there are less than two tabs initially
476
522
  this._updateTabListVisibility(panels);
@@ -495,7 +541,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
495
541
  };
496
542
  if (tabInfo.selected) {
497
543
  selectedTabInfo = tabInfo;
498
- this._setFocusable(tabInfo);
544
+ this._setFocusableDefaultSlotBehavior(tabInfo);
499
545
  }
500
546
  return tabInfo;
501
547
  });
@@ -563,17 +609,20 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
563
609
  // event could be from nested tabs
564
610
  if (!tabInfo) return;
565
611
 
566
- this._setFocusable(tabInfo);
612
+ this._setFocusableDefaultSlotBehavior(tabInfo);
567
613
  tabInfo.selected = true;
568
614
  this.requestUpdate();
569
615
  }
570
616
 
571
- _handlePanelsSlotChange() {
617
+ _handlePanelsSlotChange(e) {
572
618
  if (this._defaultSlotBehavior) return;
573
619
 
620
+ this.#panels = e.target.assignedElements({ flatten: true }).filter((node) => node.role === 'tabpanel');
621
+ this.#checkTabPanelMatch();
574
622
  this.#setAriaControls();
575
623
  }
576
624
 
625
+ // remove after d2l-tab/d2l-tab-panel backport
577
626
  async _handlePanelTextChange(e) {
578
627
  const tabInfo = this._getTabInfo(e.target.id);
579
628
  // event could be from nested tabs
@@ -727,9 +776,10 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
727
776
  if (selectedTab) {
728
777
  this.#updateSelectedTab(selectedTab);
729
778
  }
730
- this.#setAriaControls();
731
779
 
732
780
  await this.updateComplete;
781
+ this.#checkTabPanelMatch();
782
+ this.#setAriaControls();
733
783
 
734
784
  if (!this._initialized && this._tabs.length > 0) {
735
785
  this._initialized = true;
@@ -751,8 +801,13 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
751
801
  }
752
802
 
753
803
  _resetFocusables() {
754
- const selectedTab = this._tabInfos.find(ti => ti.selected);
755
- if (selectedTab) this._setFocusable(selectedTab);
804
+ if (this._defaultSlotBehavior) {
805
+ const selectedTab = this._tabInfos.find(ti => ti.selected);
806
+ if (selectedTab) this._setFocusableDefaultSlotBehavior(selectedTab);
807
+ } else {
808
+ const selectedTab = this._tabs.find(ti => ti.selected);
809
+ if (selectedTab) this._setFocusable(selectedTab);
810
+ }
756
811
  this.requestUpdate();
757
812
  }
758
813
 
@@ -777,7 +832,15 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
777
832
  });
778
833
  }
779
834
 
780
- _setFocusable(tabInfo) {
835
+ _setFocusable(tab) {
836
+ const currentFocusable = this._tabs.find(tab => tab.tabIndex === 0);
837
+ if (currentFocusable) currentFocusable.tabIndex = -1;
838
+
839
+ tab.tabIndex = 0;
840
+ }
841
+
842
+ // remove after d2l-tab/d2l-tab-panel backport
843
+ _setFocusableDefaultSlotBehavior(tabInfo) {
781
844
  const currentFocusable = this._tabInfos.find(ti => ti.activeFocusable);
782
845
  if (currentFocusable) currentFocusable.activeFocusable = false;
783
846
 
@@ -1011,22 +1074,40 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
1011
1074
  return newTranslationValue;
1012
1075
  }
1013
1076
 
1077
+ #checkTabPanelMatch() {
1078
+ // debounce so only runs once when tabs/panels slots changing
1079
+ if (this.#checkTabPanelMatchRequested) return;
1080
+
1081
+ this.#checkTabPanelMatchRequested = true;
1082
+ setTimeout(() => {
1083
+ if ((this._tabs && !this.#panels) || (this.#panels && !this._tabs)) {
1084
+ console.warn('d2l-tabs: tabs and panels are not in sync');
1085
+ } else if (this._tabs.length !== this.#panels.length) {
1086
+ console.warn('d2l-tabs: number of tabs and panels does not match');
1087
+ }
1088
+ this.#checkTabPanelMatchRequested = false;
1089
+ }, 0);
1090
+ }
1091
+
1014
1092
  #isRTL() {
1015
1093
  return document.documentElement.getAttribute('dir') === 'rtl';
1016
1094
  }
1017
1095
 
1018
1096
  #setAriaControls() {
1019
1097
  // debounce so only runs once when tabs/panels slots changing
1020
- if (this._updateAriaControlsRequested) return;
1098
+ if (this.#updateAriaControlsRequested) return;
1021
1099
 
1022
- this._updateAriaControlsRequested = true;
1100
+ this.#updateAriaControlsRequested = true;
1023
1101
  setTimeout(() => {
1024
1102
  this._tabs?.forEach((tab) => {
1025
1103
  const panel = this._getPanel(tab.id);
1026
- if (!panel) return;
1104
+ if (!panel) {
1105
+ console.warn('d2l-tabs: tab without matching panel');
1106
+ return;
1107
+ }
1027
1108
  tab.setAttribute('aria-controls', `${panel.id}`);
1028
1109
  });
1029
- this._updateAriaControlsRequested = false;
1110
+ this.#updateAriaControlsRequested = false;
1030
1111
  }, 0);
1031
1112
  }
1032
1113
 
@@ -1047,12 +1128,12 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
1047
1128
  }
1048
1129
 
1049
1130
  async #updateSelectedTab(selectedTab) {
1050
- const selectedPanel = this._getPanel(selectedTab.id);
1051
1131
  selectedTab.tabIndex = 0;
1052
1132
 
1053
1133
  await this.updateComplete;
1054
1134
 
1055
- selectedPanel.selected = true;
1135
+ const selectedPanel = this._getPanel(selectedTab.id);
1136
+ if (selectedPanel) selectedPanel.selected = true;
1056
1137
  this._tabs.forEach((tab) => {
1057
1138
  if (tab.id !== selectedTab.id) {
1058
1139
  if (tab.selected) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.94.1",
3
+ "version": "3.94.3",
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",