@brightspace-ui/core 3.93.1 → 3.94.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.
@@ -40,12 +40,12 @@
40
40
 
41
41
  <d2l-demo-snippet>
42
42
  <template>
43
- <d2l-tabs>
43
+ <d2l-tabs text="Courses">
44
44
  <d2l-tab id="all" text="All" slot="tabs"></d2l-tab>
45
- <d2l-tab id="biology" text="Biology" slot="tabs"></d2l-tab>
45
+ <d2l-tab id="biology" text="Biology" slot="tabs" selected></d2l-tab>
46
46
  <d2l-tab id="chemistry" text="Chemistry" slot="tabs"></d2l-tab>
47
- <d2l-tab-panel labelled-by="all" slot="panels">Tab content for All</d2l-tab-panel>
48
- <d2l-tab-panel labelled-by="biology" slot="panels">Tab content for Biology</d2l-tab-panel>
47
+ <d2l-tab-panel labelled-by="all" slot="panels" id="all-panel">Tab content for All</d2l-tab-panel>
48
+ <d2l-tab-panel labelled-by="biology" slot="panels" id="biology-panel">Tab content for Biology</d2l-tab-panel>
49
49
  <d2l-tab-panel labelled-by="chemistry" slot="panels">Tab content for Chemistry</d2l-tab-panel>
50
50
  </d2l-tabs>
51
51
  </template>
@@ -13,7 +13,8 @@ export const TabMixin = superclass => class extends SkeletonMixin(superclass) {
13
13
  return {
14
14
  selected: { type: Boolean, reflect: true },
15
15
  // eslint-disable-next-line lit/no-native-attributes
16
- role: { type: String, reflect: true }
16
+ role: { type: String, reflect: true },
17
+ tabIndex: { type: Number, reflect: true, attribute: 'tabindex' }
17
18
  };
18
19
  }
19
20
 
@@ -42,18 +43,18 @@ export const TabMixin = superclass => class extends SkeletonMixin(superclass) {
42
43
  margin-inline-start: 0;
43
44
  width: calc(100% - 0.6rem);
44
45
  }
45
- :host([aria-selected="true"]:focus) {
46
+ :host([selected]:focus) {
46
47
  text-decoration: none;
47
48
  }
48
49
  :host(:hover) {
49
50
  color: var(--d2l-color-celestine);
50
51
  cursor: pointer;
51
52
  }
52
- :host([aria-selected="true"]:hover) {
53
+ :host([selected]:hover) {
53
54
  color: inherit;
54
55
  cursor: default;
55
56
  }
56
- :host([aria-selected="true"]) .d2l-tab-selected-indicator {
57
+ :host([selected]) .d2l-tab-selected-indicator {
57
58
  display: block;
58
59
  }
59
60
  `];
@@ -66,6 +67,7 @@ export const TabMixin = superclass => class extends SkeletonMixin(superclass) {
66
67
  super();
67
68
  this.role = 'tab';
68
69
  this.selected = false;
70
+ this.tabIndex = -1;
69
71
  }
70
72
 
71
73
  connectedCallback() {
@@ -101,7 +103,7 @@ export const TabMixin = superclass => class extends SkeletonMixin(superclass) {
101
103
  super.update(changedProperties);
102
104
 
103
105
  if (changedProperties.has('selected')) {
104
- this.ariaSelected = this.selected;
106
+ this.ariaSelected = `${this.selected}`;
105
107
  if (this.selected) {
106
108
  this.dispatchEvent(new CustomEvent(
107
109
  'd2l-tab-selected', { bubbles: true, composed: true }
@@ -8,6 +8,7 @@ import { ArrowKeysMixin } from '../../mixins/arrow-keys/arrow-keys-mixin.js';
8
8
  import { bodyCompactStyles } from '../typography/styles.js';
9
9
  import { classMap } from 'lit/directives/class-map.js';
10
10
  import { getFocusPseudoClass } from '../../helpers/focus.js';
11
+ import { ifDefined } from 'lit/directives/if-defined.js';
11
12
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
12
13
  import { repeat } from 'lit/directives/repeat.js';
13
14
  import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
@@ -32,6 +33,11 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
32
33
  * @type {number}
33
34
  */
34
35
  maxToShow: { type: Number, attribute: 'max-to-show' },
36
+ /**
37
+ * REQUIRED: ACCESSIBILITY: Accessible text for the tablist
38
+ * @type {string}
39
+ */
40
+ text: { type: String },
35
41
  _allowScrollNext: { type: Boolean },
36
42
  _allowScrollPrevious: { type: Boolean },
37
43
  _defaultSlotBehavior: { state: true },
@@ -305,6 +311,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
305
311
  <div class="d2l-tabs-container-list"
306
312
  @d2l-tab-selected="${this._handleTabSelected}"
307
313
  @focusout="${this._handleFocusOut}"
314
+ aria-label="${ifDefined(this.text)}"
308
315
  role="tablist"
309
316
  style="${styleMap(tabsContainerListStyles)}">
310
317
  ${repeat(this._tabInfos, (tabInfo) => tabInfo.id, (tabInfo) => html`
@@ -333,7 +340,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
333
340
  @d2l-tab-panel-selected="${this._handlePanelSelected}"
334
341
  @d2l-tab-panel-text-changed="${this._handlePanelTextChange}">
335
342
  <slot @slotchange="${this._handleDefaultSlotChange}"></slot>
336
- <slot name="panels"></slot>
343
+ <slot name="panels" @slotchange="${this._handlePanelsSlotChange}"></slot>
337
344
  </div>
338
345
  `;
339
346
  }
@@ -630,6 +637,12 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
630
637
  this.requestUpdate();
631
638
  }
632
639
 
640
+ _handlePanelsSlotChange() {
641
+ if (this._defaultSlotBehavior) return;
642
+
643
+ this.#setAriaControls();
644
+ }
645
+
633
646
  async _handlePanelTextChange(e) {
634
647
  const tabInfo = this._getTabInfo(e.target.id);
635
648
  // event could be from nested tabs
@@ -725,32 +738,14 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
725
738
 
726
739
  }
727
740
 
728
- async _handleTabSelected(e) {
741
+ _handleTabSelected(e) {
729
742
  if (this._defaultSlotBehavior) {
730
743
  this._handleTabSelectedDefaultSlotBehavior(e);
731
744
  return;
732
745
  }
733
746
 
734
747
  const selectedTab = e.target;
735
- const selectedPanel = this._getPanel(e.target.id);
736
- selectedTab.tabIndex = 0;
737
-
738
- await this.updateComplete;
739
-
740
- selectedPanel.selected = true;
741
- this._tabs.forEach((tab) => {
742
- if (tab.id !== selectedTab.id) {
743
- if (tab.selected) {
744
- tab.selected = false;
745
- const panel = this._getPanel(tab.id);
746
- // panel may not exist if it's being removed
747
- if (panel) panel.selected = false;
748
- }
749
- if (tab.tabIndex === 0) tab.tabIndex = -1;
750
- }
751
- });
752
-
753
- this.requestUpdate();
748
+ this.#updateSelectedTab(selectedTab);
754
749
  }
755
750
 
756
751
  // remove after d2l-tab/d2l-tab-panel backport
@@ -791,26 +786,21 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
791
786
 
792
787
  if (!this._initialized && this._tabs.length === 0) return;
793
788
 
794
- let selectedTab;
795
- if (this._tabs.length > 0) {
789
+ let selectedTab = this._tabs.find((tab) => tab.selected && tab.state !== 'removing');
790
+ if (!selectedTab) {
796
791
  selectedTab = this._tabs.find((tab) => tab.state !== 'removing');
797
- if (selectedTab) {
798
- selectedTab.selected = true;
799
- selectedTab.tabIndex = 0;
800
- }
792
+ if (selectedTab) selectedTab.selected = true;
793
+ }
794
+ if (selectedTab) {
795
+ this.#updateSelectedTab(selectedTab);
801
796
  }
797
+ this.#setAriaControls();
802
798
 
803
799
  await this.updateComplete;
804
800
 
805
801
  if (!this._initialized && this._tabs.length > 0) {
806
802
  this._initialized = true;
807
803
  }
808
-
809
- if (selectedTab) {
810
- // set corresponding panel to selected
811
- const selectedPanel = this._getPanel(selectedTab.id);
812
- if (selectedPanel) selectedPanel.selected = true;
813
- }
814
804
  }
815
805
 
816
806
  _isPositionInLeftScrollArea(position) {
@@ -1015,6 +1005,41 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
1015
1005
  return document.documentElement.getAttribute('dir') === 'rtl';
1016
1006
  }
1017
1007
 
1008
+ #setAriaControls() {
1009
+ // debounce so only runs once when tabs/panels slots changing
1010
+ if (this._updateAriaControlsRequested) return;
1011
+
1012
+ this._updateAriaControlsRequested = true;
1013
+ setTimeout(() => {
1014
+ this._tabs?.forEach((tab) => {
1015
+ const panel = this._getPanel(tab.id);
1016
+ if (!panel) return;
1017
+ tab.setAttribute('aria-controls', `${panel.id}`);
1018
+ });
1019
+ this._updateAriaControlsRequested = false;
1020
+ }, 0);
1021
+ }
1022
+
1023
+ async #updateSelectedTab(selectedTab) {
1024
+ const selectedPanel = this._getPanel(selectedTab.id);
1025
+ selectedTab.tabIndex = 0;
1026
+
1027
+ await this.updateComplete;
1028
+
1029
+ selectedPanel.selected = true;
1030
+ this._tabs.forEach((tab) => {
1031
+ if (tab.id !== selectedTab.id) {
1032
+ if (tab.selected) {
1033
+ tab.selected = false;
1034
+ const panel = this._getPanel(tab.id);
1035
+ // panel may not exist if it's being removed
1036
+ if (panel) panel.selected = false;
1037
+ }
1038
+ if (tab.tabIndex === 0) tab.tabIndex = -1;
1039
+ }
1040
+ });
1041
+ }
1042
+
1018
1043
  }
1019
1044
 
1020
1045
  customElements.define('d2l-tabs', Tabs);
@@ -12848,6 +12848,11 @@
12848
12848
  "type": "boolean",
12849
12849
  "default": "false"
12850
12850
  },
12851
+ {
12852
+ "name": "tabindex",
12853
+ "type": "number",
12854
+ "default": "-1"
12855
+ },
12851
12856
  {
12852
12857
  "name": "skeleton",
12853
12858
  "description": "Render the component as a [skeleton loader](https://github.com/BrightspaceUI/core/tree/main/components/skeleton).",
@@ -12873,6 +12878,12 @@
12873
12878
  "type": "boolean",
12874
12879
  "default": "false"
12875
12880
  },
12881
+ {
12882
+ "name": "tabIndex",
12883
+ "attribute": "tabindex",
12884
+ "type": "number",
12885
+ "default": "-1"
12886
+ },
12876
12887
  {
12877
12888
  "name": "skeleton",
12878
12889
  "attribute": "skeleton",
@@ -12894,6 +12905,11 @@
12894
12905
  "path": "./components/tabs/tabs.js",
12895
12906
  "description": "A component for tabbed content. It supports the \"d2l-tab-panel\" component for the content, renders tabs responsively, and provides virtual scrolling for large tab lists.",
12896
12907
  "attributes": [
12908
+ {
12909
+ "name": "text",
12910
+ "description": "REQUIRED: ACCESSIBILITY: Accessible text for the tablist",
12911
+ "type": "string"
12912
+ },
12897
12913
  {
12898
12914
  "name": "max-to-show",
12899
12915
  "description": "Limit the number of tabs to initially display",
@@ -12907,6 +12923,12 @@
12907
12923
  }
12908
12924
  ],
12909
12925
  "properties": [
12926
+ {
12927
+ "name": "text",
12928
+ "attribute": "text",
12929
+ "description": "REQUIRED: ACCESSIBILITY: Accessible text for the tablist",
12930
+ "type": "string"
12931
+ },
12910
12932
  {
12911
12933
  "name": "maxToShow",
12912
12934
  "attribute": "max-to-show",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.93.1",
3
+ "version": "3.94.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",