@brightspace-ui/core 3.99.3 → 3.100.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.
@@ -35,7 +35,7 @@
35
35
  </template>
36
36
  </d2l-demo-snippet>
37
37
 
38
- <h2>Tabs (paired d2l-tab with d2l-panel)</h2>
38
+ <h2>Tabs (new structure)</h2>
39
39
  <div>This format is still a WIP. Please do not use yet.</div>
40
40
 
41
41
  <d2l-demo-snippet>
@@ -72,30 +72,30 @@
72
72
  <h3>Tabs (with slot)</h3>
73
73
 
74
74
  <div style="margin-bottom: 30px;">
75
- <d2l-button-subtle id="add" text="Add"></d2l-button-subtle>
76
- <d2l-button-subtle id="add-selected" text="Add Selected"></d2l-button-subtle>
77
- <d2l-button-subtle id="remove" text="Remove"></d2l-button-subtle>
78
- <d2l-button-subtle id="remove-multiple" text="Remove Multiple"></d2l-button-subtle>
75
+ <d2l-button-subtle id="add-old" text="Add"></d2l-button-subtle>
76
+ <d2l-button-subtle id="add-selected-old" text="Add Selected"></d2l-button-subtle>
77
+ <d2l-button-subtle id="remove-old" text="Remove"></d2l-button-subtle>
78
+ <d2l-button-subtle id="remove-multiple-old" text="Remove Multiple"></d2l-button-subtle>
79
79
  </div>
80
80
  <script>
81
- let newPanelId = 0;
82
- const addPanel = (selected, tabs) => {
83
- newPanelId += 1;
81
+ let newPanelIdOld = 0;
82
+ const addPanelOld = (selected, tabs) => {
83
+ newPanelIdOld += 1;
84
84
  const panel = document.createElement('d2l-tab-panel');
85
85
  panel.selected = selected;
86
- panel.text = `New Panel ${newPanelId}`;
87
- panel.textContent = `Content for new panel ${newPanelId}`;
86
+ panel.text = `New Panel ${newPanelIdOld}`;
87
+ panel.textContent = `Content for new panel ${newPanelIdOld}`;
88
88
  const panels = [...tabs.querySelectorAll('d2l-tab-panel')];
89
89
  if (panels.length < 2) tabs.appendChild(panel);
90
90
  else tabs.insertBefore(panel, panels[1]);
91
91
  };
92
- const removePanel = (tabs) => {
92
+ const removePanelOld = (tabs) => {
93
93
  const panels = [...tabs.querySelectorAll('d2l-tab-panel')];
94
94
  if (panels.length === 0) return;
95
95
  if (panels.length === 1) tabs.removeChild(panels[0]);
96
96
  else tabs.removeChild(panels[1]);
97
97
  };
98
- const removePanels = (tabs) => {
98
+ const removePanelsOld = (tabs) => {
99
99
  const panels = [...tabs.querySelectorAll('d2l-tab-panel')];
100
100
  if (panels.length === 0) return;
101
101
  if (panels.length === 1) tabs.removeChild(panels[0]);
@@ -105,13 +105,13 @@
105
105
  }
106
106
  };
107
107
 
108
- document.querySelector('#add').addEventListener('click', () => addPanel(false, document.querySelector('#withSlot').querySelector('d2l-tabs')));
109
- document.querySelector('#add-selected').addEventListener('click', () => addPanel(true, document.querySelector('#withSlot').querySelector('d2l-tabs')));
110
- document.querySelector('#remove').addEventListener('click', () => removePanel(document.querySelector('#withSlot').querySelector('d2l-tabs')));
111
- document.querySelector('#remove-multiple').addEventListener('click', () => removePanels(document.querySelector('#withSlot').querySelector('d2l-tabs')));
108
+ document.querySelector('#add-old').addEventListener('click', () => addPanelOld(false, document.querySelector('#withSlotOld').querySelector('d2l-tabs')));
109
+ document.querySelector('#add-selected-old').addEventListener('click', () => addPanelOld(true, document.querySelector('#withSlotOld').querySelector('d2l-tabs')));
110
+ document.querySelector('#remove-old').addEventListener('click', () => removePanelOld(document.querySelector('#withSlotOld').querySelector('d2l-tabs')));
111
+ document.querySelector('#remove-multiple-old').addEventListener('click', () => removePanelsOld(document.querySelector('#withSlotOld').querySelector('d2l-tabs')));
112
112
  </script>
113
113
 
114
- <d2l-demo-snippet id="withSlot">
114
+ <d2l-demo-snippet id="withSlotOld">
115
115
  <template>
116
116
  <d2l-tabs>
117
117
  <d2l-tab-panel text="Biology">Tab content for Biology</d2l-tab-panel>
@@ -135,6 +135,107 @@
135
135
  </template>
136
136
  </d2l-demo-snippet>
137
137
 
138
+ <h3>Tabs (with slot, new structure)</h3>
139
+
140
+ <div style="margin-bottom: 30px;">
141
+ <d2l-button-subtle id="add" text="Add"></d2l-button-subtle>
142
+ <d2l-button-subtle id="add-selected" text="Add Selected"></d2l-button-subtle>
143
+ <d2l-button-subtle id="remove" text="Remove"></d2l-button-subtle>
144
+ <d2l-button-subtle id="remove-multiple" text="Remove Multiple"></d2l-button-subtle>
145
+ </div>
146
+ <script>
147
+ let newPanelId = 0;
148
+ const addPanel = (selected, tabs) => {
149
+ newPanelId += 1;
150
+ const panel = document.createElement('d2l-tab-panel');
151
+ panel.textContent = `Content for new panel ${newPanelId}`;
152
+ panel.slot = 'panels';
153
+ panel.labelledBy = `newPanel${newPanelId}`;
154
+ tabs.appendChild(panel);
155
+
156
+ const tab = document.createElement('d2l-tab');
157
+ tab.text = `New Panel ${newPanelId}`;
158
+ tab.slot = 'tabs';
159
+ tab.id = `newPanel${newPanelId}`;
160
+ if (selected) tab.selected = true;
161
+
162
+ const tabList = [...tabs.querySelectorAll('d2l-tab')];
163
+ if (tabList.length < 2) {
164
+ const firstPanel = tabs.querySelector('d2l-tab-panel');
165
+ tabs.insertBefore(tab, firstPanel);
166
+ } else {
167
+ tabs.insertBefore(tab, tabList[1]);
168
+ }
169
+ };
170
+ const removeTab = (tabs) => {
171
+ const tabElems = [...tabs.querySelectorAll('d2l-tab')];
172
+ if (tabElems.length === 0) return;
173
+
174
+ const tab = tabElems.length === 1 ? tabElems[0] : tabElems[1];
175
+ Promise.resolve(tabs.hideTab((tab))).then(() => {
176
+ const panel = tabs.querySelector(`d2l-tab-panel[labelled-by="${tab.id}"]`);
177
+ if (panel) tabs.removeChild(panel);
178
+ tabs.removeChild(tab);
179
+ });
180
+ };
181
+ const removeTabs = (tabs) => {
182
+ const tabElems = [...tabs.querySelectorAll('d2l-tab')];
183
+ if (tabElems.length === 0) return;
184
+ else if (tabElems.length === 1) removeTab(tabs);
185
+ else {
186
+ const tab1 = tabElems[0];
187
+ const tab2 = tabElems[1];
188
+ const animPromises = [];
189
+ animPromises.push(tabs.hideTab(tab1));
190
+ animPromises.push(tabs.hideTab(tab2));
191
+ Promise.all(animPromises).then(() => {
192
+ const panel1 = tabs.querySelector(`d2l-tab-panel[labelled-by="${tab1.id}"]`);
193
+ const panel2 = tabs.querySelector(`d2l-tab-panel[labelled-by="${tab2.id}"]`);
194
+ if (panel1) tabs.removeChild(panel1);
195
+ if (panel2) tabs.removeChild(panel2);
196
+ tabs.removeChild(tab1);
197
+ tabs.removeChild(tab2);
198
+ });
199
+ }
200
+ };
201
+
202
+ document.querySelector('#add').addEventListener('click', () => addPanel(false, document.querySelector('#withSlot').querySelector('d2l-tabs')));
203
+ document.querySelector('#add-selected').addEventListener('click', () => addPanel(true, document.querySelector('#withSlot').querySelector('d2l-tabs')));
204
+ document.querySelector('#remove').addEventListener('click', () => removeTab(document.querySelector('#withSlot').querySelector('d2l-tabs')));
205
+ document.querySelector('#remove-multiple').addEventListener('click', () => removeTabs(document.querySelector('#withSlot').querySelector('d2l-tabs')));
206
+ </script>
207
+
208
+ <d2l-demo-snippet id="withSlot">
209
+ <template>
210
+ <d2l-tabs>
211
+ <d2l-tab id="all" text="All" slot="tabs"></d2l-tab>
212
+ <d2l-tab id="biology" text="Biology" slot="tabs" selected></d2l-tab>
213
+ <d2l-tab id="chemistry" text="Chemistry" slot="tabs"></d2l-tab>
214
+ <d2l-tab id="physics" text="Physics" slot="tabs"></d2l-tab>
215
+ <d2l-tab id="math" text="Math" slot="tabs"></d2l-tab>
216
+ <d2l-tab id="earth-sciences" text="Earth Sciences" slot="tabs"></d2l-tab>
217
+ <d2l-tab-panel labelled-by="all" slot="panels">Tab content for All</d2l-tab-panel>
218
+ <d2l-tab-panel labelled-by="biology" slot="panels">Tab content for Biology</d2l-tab-panel>
219
+ <d2l-tab-panel labelled-by="chemistry" slot="panels">Tab content for Chemistry</d2l-tab-panel>
220
+ <d2l-tab-panel labelled-by="physics" slot="panels">Tab content for Physics</d2l-tab-panel>
221
+ <d2l-tab-panel labelled-by="math" slot="panels">Tab content for Math</d2l-tab-panel>
222
+ <d2l-tab-panel labelled-by="earth-sciences" slot="panels">Tab content for Earth Sciences</d2l-tab-panel>
223
+ <d2l-dropdown-button-subtle slot="ext" text="Explore Topics">
224
+ <d2l-dropdown-menu>
225
+ <d2l-menu label="Astronomy">
226
+ <d2l-menu-item text="Introduction"></d2l-menu-item>
227
+ <d2l-menu-item text="Searching for the Heavens "></d2l-menu-item>
228
+ <d2l-menu-item text="The Solar System"></d2l-menu-item>
229
+ <d2l-menu-item text="Stars &amp; Galaxies"></d2l-menu-item>
230
+ <d2l-menu-item text="The Night Sky"></d2l-menu-item>
231
+ <d2l-menu-item text="The Universe"></d2l-menu-item>
232
+ </d2l-menu>
233
+ </d2l-dropdown-menu>
234
+ </d2l-dropdown-button-subtle>
235
+ </d2l-tabs>
236
+ </template>
237
+ </d2l-demo-snippet>
238
+
138
239
  <h3>Tabs (responsive)</h3>
139
240
 
140
241
  <d2l-demo-snippet>
@@ -9,7 +9,7 @@ export const TabPanelMixin = superclass => class extends superclass {
9
9
  * Id of the tab that labels this panel
10
10
  * @type {string}
11
11
  */
12
- labelledBy: { type: String, attribute: 'labelled-by' },
12
+ labelledBy: { type: String, attribute: 'labelled-by', reflect: true },
13
13
  /**
14
14
  * Opt out of default padding/whitespace around the panel
15
15
  * @type {boolean}
@@ -169,11 +169,14 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
169
169
  transition: margin-top 200ms ease-out;
170
170
  }
171
171
 
172
- d2l-tab-internal {
172
+ d2l-tab-internal, ::slotted([role="tab"]) {
173
173
  -webkit-transition: max-width 200ms ease-out, opacity 200ms ease-out, transform 200ms ease-out;
174
174
  transition: max-width 200ms ease-out, opacity 200ms ease-out, transform 200ms ease-out;
175
175
  }
176
- d2l-tab-internal[data-state="adding"], d2l-tab-internal[data-state="removing"] {
176
+ d2l-tab-internal[data-state="adding"],
177
+ d2l-tab-internal[data-state="removing"],
178
+ ::slotted([role="tab"][data-state="adding"]),
179
+ ::slotted([role="tab"][data-state="removing"]) {
177
180
  max-width: 0;
178
181
  opacity: 0;
179
182
  transform: translateY(20px);
@@ -197,7 +200,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
197
200
  -webkit-transition: none;
198
201
  transition: none;
199
202
  }
200
- d2l-tab-internal {
203
+ d2l-tab-internal, ::slotted([role="tab"]) {
201
204
  -webkit-transition: none;
202
205
  transition: none;
203
206
  }
@@ -217,6 +220,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
217
220
  this._maxWidth = null;
218
221
  this._scrollCollapsed = false;
219
222
  this._state = 'shown';
223
+ this._tabIds = {};
220
224
  this._tabInfos = []; // remove after d2l-tab/d2l-tab-panel backport
221
225
  this._translationValue = 0;
222
226
  }
@@ -393,37 +397,83 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
393
397
  return this.shadowRoot.querySelector('.d2l-tabs-container-list').getBoundingClientRect();
394
398
  }
395
399
 
400
+ hideTab(tab) {
401
+ tab.setAttribute('data-state', 'removing');
402
+ return (Object.keys(this._tabIds).length > 1 && !reduceMotion) ? this._animateTabRemoval(tab) : Promise.resolve();
403
+ }
404
+
396
405
  #checkTabPanelMatchRequested;
397
406
  #panels;
398
407
  #updateAriaControlsRequested;
399
408
 
400
- _animateTabAddition(tabInfo) {
409
+ _animateTabAddition(tab) {
410
+ if (!tab || reduceMotion) {
411
+ return new Promise((resolve) => {
412
+ tab.setAttribute('data-state', '');
413
+ this.requestUpdate();
414
+ resolve();
415
+ });
416
+ }
417
+
418
+ return new Promise((resolve) => {
419
+ const handleTransitionEnd = (e) => {
420
+ if (e.propertyName !== 'max-width') return;
421
+ tab.removeEventListener('transitionend', handleTransitionEnd);
422
+ resolve();
423
+ };
424
+ tab.addEventListener('transitionend', handleTransitionEnd);
425
+ tab.setAttribute('data-state', '');
426
+ this.requestUpdate();
427
+ });
428
+ }
429
+
430
+ // remove after d2l-tab/d2l-tab-panel backport
431
+ _animateTabAdditionDefaultSlotBehavior(tabInfo) {
401
432
  const tab = this.shadowRoot
402
433
  && this.shadowRoot.querySelector(`d2l-tab-internal[controls-panel="${cssEscape(tabInfo.id)}"]`);
434
+ if (!tab) Promise.resolve();
435
+
403
436
  return new Promise((resolve) => {
404
437
  const handleTransitionEnd = (e) => {
405
438
  if (e.propertyName !== 'max-width') return;
406
- if (tab) tab.removeEventListener('transitionend', handleTransitionEnd);
439
+ tab.removeEventListener('transitionend', handleTransitionEnd);
407
440
  resolve();
408
441
  };
409
- if (tab) tab.addEventListener('transitionend', handleTransitionEnd);
442
+ tab.addEventListener('transitionend', handleTransitionEnd);
410
443
  tabInfo.state = '';
411
444
  this.requestUpdate();
412
445
  });
413
446
  }
414
447
 
415
- _animateTabRemoval(tabInfo) {
448
+ _animateTabRemoval(tab) {
449
+ if (!tab || reduceMotion) return Promise.resolve();
450
+
451
+ return new Promise((resolve) => {
452
+ const handleTransitionEnd = (e) => {
453
+ if (e.propertyName !== 'max-width') return;
454
+ tab.removeEventListener('transitionend', handleTransitionEnd);
455
+ this.requestUpdate();
456
+ resolve();
457
+ };
458
+ tab.addEventListener('transitionend', handleTransitionEnd);
459
+ });
460
+ }
461
+
462
+ // remove after d2l-tab/d2l-tab-panel backport
463
+ _animateTabRemovalDefaultSlotBehavior(tabInfo) {
416
464
  const tab = this.shadowRoot &&
417
465
  this.shadowRoot.querySelector(`d2l-tab-internal[controls-panel="${cssEscape(tabInfo.id)}"]`);
466
+ if (!tab) Promise.resolve();
467
+
418
468
  return new Promise((resolve) => {
419
469
  const handleTransitionEnd = (e) => {
420
470
  if (e.propertyName !== 'max-width') return;
421
- if (tab) tab.removeEventListener('transitionend', handleTransitionEnd);
471
+ tab.removeEventListener('transitionend', handleTransitionEnd);
422
472
  this._tabInfos.splice(this._tabInfos.findIndex(info => info.id === tabInfo.id), 1);
423
473
  this.requestUpdate();
424
474
  resolve();
425
475
  };
426
- if (tab) tab.addEventListener('transitionend', handleTransitionEnd);
476
+ tab.addEventListener('transitionend', handleTransitionEnd);
427
477
  });
428
478
  }
429
479
 
@@ -579,8 +629,8 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
579
629
 
580
630
  if (this._tabInfos.length > 1) {
581
631
  this._tabInfos.forEach((info) => {
582
- if (info.state === 'adding') animPromises.push(this._animateTabAddition(info));
583
- else if (info.state === 'removing') animPromises.push(this._animateTabRemoval(info));
632
+ if (info.state === 'adding') animPromises.push(this._animateTabAdditionDefaultSlotBehavior(info));
633
+ else if (info.state === 'removing') animPromises.push(this._animateTabRemovalDefaultSlotBehavior(info));
584
634
  });
585
635
  }
586
636
 
@@ -774,9 +824,26 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
774
824
 
775
825
  if (!this._initialized && this._tabs.length === 0) return;
776
826
 
777
- let selectedTab = this._tabs.find((tab) => tab.selected && tab.state !== 'removing');
827
+ let selectedTab = null;
828
+ const newTabIds = {};
829
+ this._tabs?.forEach((tab) => {
830
+ if (this._initialized && !reduceMotion && this._tabs.length !== Object.keys(this._tabIds).length) {
831
+ // if it's a new tab, update state to animate addition
832
+ if (!this._tabIds[tab.id]) {
833
+ this._tabIds[tab.id] = true;
834
+ tab.setAttribute('data-state', 'adding');
835
+ }
836
+ }
837
+ if (!selectedTab && tab.selected && tab.getAttribute('data-state') !== 'removing') {
838
+ selectedTab = tab;
839
+ }
840
+ newTabIds[tab.id] = true;
841
+ });
842
+
843
+ this._tabIds = newTabIds;
844
+
778
845
  if (!selectedTab) {
779
- selectedTab = this._tabs.find((tab) => tab.state !== 'removing');
846
+ selectedTab = this._tabs.find((tab) => tab.getAttribute('data-state') !== 'removing');
780
847
  if (selectedTab) selectedTab.selected = true;
781
848
  }
782
849
  if (selectedTab) {
@@ -787,14 +854,25 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
787
854
  this.#checkTabPanelMatch();
788
855
  this.#setAriaControls();
789
856
 
857
+ const animPromises = [];
858
+
790
859
  if (!this._initialized && this._tabs.length > 0) {
791
860
  this._initialized = true;
792
861
  await this._updateTabsContainerWidth(selectedTab);
862
+ } else {
863
+ if (this._tabs.length > 1) {
864
+ this._tabs.forEach((tab) => {
865
+ if (tab.getAttribute('data-state') === 'adding') animPromises.push(this._animateTabAddition(tab));
866
+ });
867
+ }
868
+ this._updateMeasures();
793
869
  }
794
870
 
795
871
  if (selectedTab) {
796
- this._updateMeasures();
797
- this._updateScrollPosition(selectedTab);
872
+ Promise.all(animPromises).then(() => {
873
+ this._updateMeasures();
874
+ this._updateScrollPosition(selectedTab);
875
+ });
798
876
  }
799
877
  }
800
878
 
@@ -1138,6 +1216,9 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
1138
1216
 
1139
1217
  await this.updateComplete;
1140
1218
 
1219
+ selectedTab.selected = true;
1220
+ selectedTab.tabIndex = 0;
1221
+
1141
1222
  const selectedPanel = this._getPanel(selectedTab.id);
1142
1223
  if (selectedPanel) selectedPanel.selected = true;
1143
1224
  this._tabs.forEach((tab) => {
@@ -5,15 +5,18 @@ const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
5
5
 
6
6
  export const visibleOnAncestorStyles = css`
7
7
 
8
- :host([__voa-state="hidden"]),
9
- :host([__voa-state="hiding"]) {
8
+ :host([__voa-state="hidden"]) {
10
9
  opacity: 0 !important;
11
10
  transform: translateY(-10px) !important;
12
11
  }
13
- :host([__voa-state="showing"]),
14
- :host([__voa-state="hiding"]) {
12
+ :host([__voa-state="showing"]) {
15
13
  transition: transform 200ms ease-out, opacity 200ms ease-out !important;
16
14
  }
15
+ :host([__voa-state="hiding"]) {
16
+ opacity: 0 !important;
17
+ transform: none !important;
18
+ transition: opacity 200ms ease-out !important;
19
+ }
17
20
 
18
21
  :host([__voa-state="hidden"][animation-type="opacity"]),
19
22
  :host([__voa-state="hiding"][animation-type="opacity"]) {
@@ -30,12 +33,7 @@ export const visibleOnAncestorStyles = css`
30
33
  :host([__voa-state="hidden"]),
31
34
  :host([__voa-state="hiding"]) {
32
35
  opacity: 1 !important;
33
- transform: translateY(0) !important;
34
- }
35
- :host([__voa-state="hidden"][d2l-visible-on-ancestor-no-hover-hide]),
36
- :host([__voa-state="hiding"][d2l-visible-on-ancestor-no-hover-hide]) {
37
- opacity: 0 !important;
38
- transform: translateY(-10px) !important;
36
+ transform: none !important;
39
37
  }
40
38
  }
41
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.99.3",
3
+ "version": "3.100.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",