@brightspace-ui/core 1.210.0 → 1.213.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.
package/README.md CHANGED
@@ -55,6 +55,8 @@ npm install @brightspace-ui/core
55
55
  * [Tooltip](components/tooltip/): tooltip components
56
56
  * [Typography](components/typography/): typography styles and components
57
57
  * [Validation](components/validation/): plugin custom validation logic to native and custom form elements
58
+ * Controllers
59
+ * [Subscriber](controllers/subscriber/): for managing a registry of subscribers in a many-to-many relationship
58
60
  * Directives
59
61
  * [Animate](directives/animate/): animate showing, hiding and removal of elements
60
62
  * Helpers
@@ -119,20 +121,23 @@ Note: The axe tests require `prefers-reduced-motion` emulation to be turned on i
119
121
 
120
122
  This repo uses the [@brightspace-ui/visual-diff utility](https://github.com/BrightspaceUI/visual-diff/) to compare current snapshots against a set of golden snapshots stored in source control.
121
123
 
122
- The golden snapshots in source control must be updated by Github Actions. If your PR's code changes result in visual differences, a PR with the new goldens will be automatically opened for you against your branch.
124
+ The golden snapshots in source control must be updated by the [visual-diff GitHub Action](https://github.com/BrightspaceUI/actions/tree/main/visual-diff). If a pull request results in visual differences, a draft pull request with the new goldens will automatically be opened against its branch.
123
125
 
124
- If you'd like to run the tests locally to help troubleshoot or develop new tests, you can use these commands:
126
+ To run the tests locally to help troubleshoot or develop new tests, first install these dependencies:
125
127
 
126
128
  ```shell
127
- # Install dependencies locally
128
- npm install mocha puppeteer @brightspace-ui/visual-diff --no-save
129
+ npm install @brightspace-ui/visual-diff@X mocha@Y puppeteer@Z --no-save
130
+ ```
131
+
132
+ Replace `X`, `Y` and `Z` with [the current versions](https://github.com/BrightspaceUI/actions/tree/main/visual-diff#current-dependency-versions) the action is using.
133
+
134
+ Then run the tests:
129
135
 
136
+ ```shell
130
137
  # run visual-diff tests
131
138
  npx mocha './**/*.visual-diff.js' -t 10000
132
-
133
139
  # subset of visual-diff tests:
134
140
  npx mocha './**/*.visual-diff.js' -t 10000 -g some-pattern
135
-
136
141
  # update visual-diff goldens
137
142
  npx mocha './**/*.visual-diff.js' -t 10000 --golden
138
143
  ```
@@ -394,7 +394,7 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
394
394
  if (this.opened) {
395
395
  this.close();
396
396
  } else {
397
- this.open(applyFocus);
397
+ this.open(!this.noAutoFocus && applyFocus);
398
398
  }
399
399
  }
400
400
 
@@ -518,11 +518,10 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
518
518
 
519
519
  await this.__position();
520
520
  this._showBackdrop = this._useMobileStyling && this.mobileTray;
521
-
522
521
  if (!this.noAutoFocus && this.__applyFocus) {
523
522
  const focusable = getFirstFocusableDescendant(this);
524
523
  if (focusable) {
525
- // bumping this to the next frame is required to prevent Legacy-Edge from crazily invoking click on the focused element
524
+ // Removing the rAF call can allow infinite focus looping to happen in content using a focus trap
526
525
  requestAnimationFrame(() => focusable.focus());
527
526
  } else {
528
527
  content.setAttribute('tabindex', '-1');
@@ -1014,7 +1013,7 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
1014
1013
  const content = this.__getContentContainer();
1015
1014
  const focusable = getFirstFocusableDescendant(content);
1016
1015
  if (focusable) {
1017
- // bumping this to the next frame is required to prevent Legacy-Edge from crazily invoking click on the focused element
1016
+ // Removing the rAF call can allow infinite focus looping to happen in content using a focus trap
1018
1017
  requestAnimationFrame(() => focusable.focus());
1019
1018
  } else {
1020
1019
  content.setAttribute('tabindex', '-1');
@@ -112,9 +112,7 @@ class DropdownMenu extends ThemeMixin(DropdownContentMixin(LitElement)) {
112
112
 
113
113
  menu.resize();
114
114
 
115
- if (this.__applyFocus) {
116
- menu.focus();
117
- }
115
+ menu.focus();
118
116
  }
119
117
 
120
118
  _onSelect(e) {
@@ -262,7 +262,7 @@ export const DropdownOpenerMixin = superclass => class extends superclass {
262
262
  this._isHovering = false;
263
263
  this.openDropdown(false);
264
264
  }
265
- } else this.toggleOpen(false);
265
+ } else this.toggleOpen(true);
266
266
  }
267
267
 
268
268
  /* used by open-on-hover option */
@@ -212,6 +212,25 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
212
212
  `;
213
213
  }
214
214
 
215
+ focus() {
216
+ const opener = this.shadowRoot.querySelector('d2l-dropdown-button-subtle');
217
+ if (opener) opener.focus();
218
+ }
219
+
220
+ requestFilterClearAll() {
221
+ this._handleClearAll();
222
+ }
223
+
224
+ requestFilterValueClear(keyObject) {
225
+ const dimension = this._dimensions.find(dimension => dimension.key === keyObject.dimension);
226
+
227
+ switch (dimension.type) {
228
+ case 'd2l-filter-dimension-set':
229
+ this._performChangeSetDimension(dimension, keyObject.value, false);
230
+ break;
231
+ }
232
+ }
233
+
215
234
  _buildDimension(dimension, singleDimension) {
216
235
  let dimensionHTML;
217
236
  switch (dimension.type) {
@@ -433,20 +452,9 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
433
452
  const dimensionKey = e.target.id.slice(SET_DIMENSION_ID_PREFIX.length);
434
453
  const dimension = this._dimensions.find(dimension => dimension.key === dimensionKey);
435
454
  const valueKey = e.detail.key;
436
- const value = dimension.values.find(value => value.key === valueKey);
437
455
  const selected = e.detail.selected;
438
456
 
439
- value.selected = selected;
440
-
441
- if (selected) {
442
- dimension.appliedCount++;
443
- this._totalAppliedCount++;
444
- } else {
445
- dimension.appliedCount--;
446
- this._totalAppliedCount--;
447
- }
448
-
449
- this._dispatchChangeEvent(dimension, { valueKey: valueKey, selected: selected });
457
+ this._performChangeSetDimension(dimension, valueKey, selected);
450
458
  }
451
459
 
452
460
  _handleClear() {
@@ -621,6 +629,22 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
621
629
  return false;
622
630
  }
623
631
 
632
+ _performChangeSetDimension(dimension, valueKey, selected) {
633
+ const value = dimension.values.find(value => value.key === valueKey);
634
+ if (value.selected === selected) return;
635
+ value.selected = selected;
636
+
637
+ if (selected) {
638
+ dimension.appliedCount++;
639
+ this._totalAppliedCount++;
640
+ } else {
641
+ dimension.appliedCount--;
642
+ this._totalAppliedCount--;
643
+ }
644
+
645
+ this._dispatchChangeEvent(dimension, { valueKey: valueKey, selected: selected });
646
+ }
647
+
624
648
  _performDimensionClear(dimension) {
625
649
  this._totalAppliedCount = this._totalAppliedCount - dimension.appliedCount;
626
650
  dimension.appliedCount = 0;
@@ -5,7 +5,8 @@ import { html, LitElement } from 'lit-element/lit-element.js';
5
5
  import { ifDefined } from 'lit-html/directives/if-defined.js';
6
6
  import { repeat } from 'lit-html/directives/repeat.js';
7
7
 
8
- class ListDemoDragAndDropUsage extends LitElement {
8
+ class ListDemoDragAndDropPosition extends LitElement {
9
+
9
10
  static get properties() {
10
11
  return {
11
12
  list: { type: Array },
@@ -92,4 +93,4 @@ class ListDemoDragAndDropUsage extends LitElement {
92
93
  }
93
94
  }
94
95
 
95
- customElements.define('d2l-list-demo-drag-and-drop-usage', ListDemoDragAndDropUsage);
96
+ customElements.define('d2l-demo-list-drag-and-drop-position', ListDemoDragAndDropPosition);
@@ -6,45 +6,38 @@
6
6
  <link rel="stylesheet" href="../../demo/styles.css" type="text/css">
7
7
  <script type="module">
8
8
  import '../../demo/demo-page.js';
9
- import './list-demo-drag-and-drop-usage.js';
9
+ import './list-drag-and-drop-position.js';
10
+ import './list-drag-and-drop.js';
10
11
  </script>
11
12
  </head>
12
13
  <body unresolved>
13
14
 
14
15
  <d2l-demo-page page-title="d2l-list (with drag & drop)">
15
16
 
16
- <h2>Draggable</h2>
17
+ <h2>Position Change Event</h2>
17
18
 
18
19
  <d2l-demo-snippet>
19
20
  <template>
20
- <d2l-list-demo-drag-and-drop-usage></d2l-list-demo-drag-and-drop-usage>
21
+ <d2l-demo-list-drag-and-drop-position grid selectable hrefs></d2l-demo-list-drag-and-drop-position>
21
22
  </template>
22
23
  </d2l-demo-snippet>
23
24
 
24
- <h2>Draggable and Selectable</h2>
25
+ <h2>Move Event</h2>
25
26
 
26
27
  <d2l-demo-snippet>
27
28
  <template>
28
- <d2l-list-demo-drag-and-drop-usage selectable></d2l-list-demo-drag-and-drop-usage>
29
+ <d2l-demo-list-drag-and-drop></d2l-demo-list-drag-and-drop>
29
30
  </template>
30
31
  </d2l-demo-snippet>
31
32
 
32
- <h2>Draggable with Grid and Selectable</h2>
33
33
 
34
- <d2l-demo-snippet>
35
- <template>
36
- <d2l-list-demo-drag-and-drop-usage grid selectable></d2l-list-demo-drag-and-drop-usage>
37
- </template>
38
- </d2l-demo-snippet>
39
-
40
- <h2>All the Fixins (grid, draggable, selectable, hrefs)</h2>
34
+ </d2l-demo-page>
41
35
 
42
- <d2l-demo-snippet>
43
- <template>
44
- <d2l-list-demo-drag-and-drop-usage grid selectable hrefs></d2l-list-demo-drag-and-drop-usage>
45
- </template>
46
- </d2l-demo-snippet>
36
+ <script>
37
+ document.body.addEventListener('d2l-list-items-move', e => {
38
+ console.log('d2l-list-items-move', e.detail);
39
+ });
40
+ </script>
47
41
 
48
- </d2l-demo-page>
49
42
  </body>
50
43
  </html>
@@ -0,0 +1,171 @@
1
+ import '../list-item-content.js';
2
+ import '../list-item.js';
3
+ import '../list.js';
4
+ import { html, LitElement } from 'lit-element/lit-element.js';
5
+ import { ifDefined } from 'lit-html/directives/if-defined.js';
6
+ import { moveLocations } from '../list-item-drag-drop-mixin.js';
7
+ import { repeat } from 'lit-html/directives/repeat.js';
8
+
9
+ class ListDemoDragAndDrop extends LitElement {
10
+
11
+ static get properties() {
12
+ return {
13
+ items: { type: Array }
14
+ };
15
+ }
16
+
17
+ constructor() {
18
+ super();
19
+ this.items = [{
20
+ key: '1',
21
+ primaryText: 'Introductory Earth Sciences',
22
+ supportingText: 'This course explores the geological processes of the Earth\'s interior and surface. These include volcanism, earthquakes, mountain building, glaciation and weathering.',
23
+ imgSrc: 'https://s.brightspace.com/course-images/images/63b162ab-b582-4bf9-8c1d-1dad04714121/tile-high-density-max-size.jpg',
24
+ items: [{
25
+ key: '1-1',
26
+ primaryText: 'Glaciation',
27
+ supportingText: 'Supporting Info',
28
+ imgSrc: '',
29
+ items: []
30
+ }, {
31
+ key: '1-2',
32
+ primaryText: 'Weathering',
33
+ supportingText: 'Supporting Info',
34
+ imgSrc: '',
35
+ items: []
36
+ }, {
37
+ key: '1-3',
38
+ primaryText: 'Volcanism',
39
+ supportingText: 'Supporting Info',
40
+ imgSrc: '',
41
+ items: []
42
+ }]
43
+ }, {
44
+ key: '2',
45
+ primaryText: 'Flow and Transport Through Fractured Rocks',
46
+ supportingText: 'Fractures are ubiquitous in geologic media and important in disciplines such as physical and contaminant hydrogeology, geotechnical engineering, civil and environmental engineering, petroleum engineering among other areas.',
47
+ imgSrc: 'https://s.brightspace.com/course-images/images/e5fd575a-bc14-4a80-89e1-46f349a76178/tile-high-density-max-size.jpg',
48
+ items: [{
49
+ key: '2-1',
50
+ primaryText: 'Contaminant Transport',
51
+ supportingText: 'Supporting Info',
52
+ imgSrc: '',
53
+ items: []
54
+ }, {
55
+ key: '2-2',
56
+ primaryText: 'Modelling Flow in Fractured Media',
57
+ supportingText: 'Supporting Info',
58
+ imgSrc: '',
59
+ items: []
60
+ }]
61
+ }, {
62
+ key: '3',
63
+ primaryText: 'Applied Wetland Science',
64
+ supportingText: 'Advanced concepts on wetland ecosystems in the context of regional and global earth systems processes such as carbon and nitrogen cycling and climate change, applications of wetland paleoecology, use of isotopes and other geochemical tools in wetland science, and wetland engineering in landscape rehabilitation and ecotechnology.',
65
+ imgSrc: 'https://s.brightspace.com/course-images/images/38e839b1-37fa-470c-8830-b189ce4ae134/tile-high-density-max-size.jpg',
66
+ items: [{
67
+ key: '3-1',
68
+ primaryText: 'Carbon & Nitrogen Cycling',
69
+ supportingText: 'Supporting Info',
70
+ imgSrc: '',
71
+ items: []
72
+ }, {
73
+ key: '3-2',
74
+ primaryText: 'Wetland Engineering',
75
+ supportingText: 'Supporting Info',
76
+ imgSrc: '',
77
+ items: []
78
+ }]
79
+ }];
80
+ }
81
+
82
+ render() {
83
+ const renderList = (items, nested) => {
84
+ return html`
85
+ <d2l-list grid slot="${ifDefined(nested ? 'nested' : undefined)}">
86
+ ${repeat(items, item => item.key, item => html`
87
+ <d2l-list-item
88
+ action-href="http://www.d2l.com"
89
+ draggable
90
+ drag-handle-text="${item.primaryText}"
91
+ drop-nested
92
+ key="${item.key}"
93
+ label="${item.primaryText}"
94
+ selectable>
95
+ ${nested ? null : html`<img slot="illustration" src="${item.imgSrc}">`}
96
+ <d2l-list-item-content>
97
+ <div>${item.primaryText}</div>
98
+ <div slot="supporting-info">${item.supportingText}</div>
99
+ </d2l-list-item-content>
100
+ ${item.items.length > 0 ? renderList(item.items, true) : null}
101
+ </d2l-list-item>
102
+ `)}
103
+ </d2l-list>
104
+ `;
105
+ };
106
+
107
+ return html`
108
+ <div @d2l-list-items-move="${this._handleListItemsMove}">
109
+ ${renderList(this.items, false)}
110
+ </div>
111
+ `;
112
+ }
113
+
114
+ async _handleListItemsMove(e) {
115
+
116
+ const sourceListItems = e.detail.sourceItems;
117
+ const target = e.detail.target;
118
+
119
+ // helper that gets the array containing item data, the item data, and the index within the array
120
+ const getItemInfo = (items, key) => {
121
+ for (let i = 0; i < items.length; i++) {
122
+ if (items[i].key === key) {
123
+ return { owner: items, item: items[i], index: i };
124
+ }
125
+ if (items[i].items && items[i].items.length > 0) {
126
+ const tempItemData = getItemInfo(items[i].items, key);
127
+ if (tempItemData) return tempItemData;
128
+ }
129
+ }
130
+ };
131
+
132
+ const dataToMove = [];
133
+
134
+ // remove data elements from original locations
135
+ sourceListItems.forEach(sourceListItem => {
136
+ const info = getItemInfo(this.items, sourceListItem.key);
137
+ info.owner.splice(info.index, 1);
138
+ dataToMove.push(info.item);
139
+ });
140
+
141
+ // append data elements to new location
142
+ const targetInfo = getItemInfo(this.items, target.item.key);
143
+ let targetItems;
144
+ let targetIndex;
145
+ if (target.location === moveLocations.nest) {
146
+ if (!targetInfo.item.items) targetInfo.item.items = [];
147
+ targetItems = targetInfo.item.items;
148
+ targetIndex = targetItems.length;
149
+ } else {
150
+ targetItems = targetInfo.owner;
151
+ if (target.location === moveLocations.above) targetIndex = targetInfo.index;
152
+ else if (target.location === moveLocations.below) targetIndex = targetInfo.index + 1;
153
+ }
154
+ for (let i = dataToMove.length - 1; i >= 0; i--) {
155
+ targetItems.splice(targetIndex, 0, dataToMove[i]);
156
+ }
157
+
158
+ await this.requestUpdate();
159
+
160
+ if (e.detail.keyboardActive) {
161
+ requestAnimationFrame(() => {
162
+ const newItem = this.shadowRoot.querySelector('d2l-list').getListItemByKey(sourceListItems[0].key);
163
+ newItem.activateDragHandle();
164
+ });
165
+ }
166
+
167
+ }
168
+
169
+ }
170
+
171
+ customElements.define('d2l-demo-list-drag-and-drop', ListDemoDragAndDrop);
@@ -113,16 +113,6 @@ export const ListItemCheckboxMixin = superclass => class extends SkeletonMixin(L
113
113
  }));
114
114
  }
115
115
 
116
- _getNestedList() {
117
- const nestedSlot = this.shadowRoot.querySelector('slot[name="nested"]');
118
- let nestedNodes = nestedSlot.assignedNodes();
119
- if (nestedNodes.length === 0) {
120
- nestedNodes = [...nestedSlot.childNodes];
121
- }
122
-
123
- return nestedNodes.find(node => (node.nodeType === Node.ELEMENT_NODE && node.tagName === 'D2L-LIST'));
124
- }
125
-
126
116
  _onCheckboxActionClick(event) {
127
117
  event.preventDefault();
128
118
  if (this.disabled) return;
@@ -140,6 +130,10 @@ export const ListItemCheckboxMixin = superclass => class extends SkeletonMixin(L
140
130
  }
141
131
  }
142
132
 
133
+ _onNestedSlotChange() {
134
+ this._updateNestedSelectionProvider();
135
+ }
136
+
143
137
  _onSelectionProviderConnected(e) {
144
138
  e.stopPropagation();
145
139
  this._updateNestedSelectionProvider();