@brightspace-ui/core 3.177.0 → 3.178.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.
@@ -1,6 +1,7 @@
1
1
  import '../filter.js';
2
2
  import '../filter-dimension-set.js';
3
3
  import '../filter-dimension-set-value.js';
4
+ import '../filter-overflow-group.js';
4
5
  import { html, LitElement } from 'lit';
5
6
  import { repeat } from 'lit/directives/repeat.js';
6
7
 
@@ -32,11 +33,48 @@ const FullData = [
32
33
  { key: 'student', selected:false, text: 'Student' }
33
34
  ],
34
35
  initialCount: 2
36
+ },
37
+ {
38
+ key: 'dep',
39
+ text: 'Department',
40
+ values: [
41
+ { key: 'english', selected:false, text: 'English' },
42
+ { key: 'spanish', selected:false, text: 'Spanish' },
43
+ { key: 'science', selected:false, text: 'Science' }
44
+ ],
45
+ initialCount: 2
46
+ },
47
+ {
48
+ key: 'grad',
49
+ text: 'Grade Level',
50
+ values: [
51
+ { key: '1', selected:false, text: '1st Grade' },
52
+ { key: '2', selected:false, text: '2nd Grade' },
53
+ { key: '3', selected:false, text: '3rd Grade' }
54
+ ],
55
+ initialCount: 2
56
+ }
57
+ ,
58
+ {
59
+ key: 'city',
60
+ text: 'City',
61
+ values: [
62
+ { key: '1', selected:false, text: '1st City' },
63
+ { key: '2', selected:false, text: '2nd City' },
64
+ { key: '3', selected:false, text: '3rd City' }
65
+ ],
66
+ initialCount: 2
35
67
  }
36
68
  ];
37
69
 
38
70
  class FilterLoadMoreDemo extends LitElement {
39
71
 
72
+ static get properties() {
73
+ return {
74
+ useOverflowGroup: { type: Boolean, attribute: 'use-overflow-group' }
75
+ };
76
+ }
77
+
40
78
  constructor() {
41
79
  super();
42
80
  const dimensions = [];
@@ -60,8 +98,16 @@ class FilterLoadMoreDemo extends LitElement {
60
98
  }
61
99
 
62
100
  render() {
101
+ if (this.useOverflowGroup) return html`<d2l-filter-overflow-group min-to-show="0">
102
+ ${repeat(this._dimensions, dimension => dimension.key, dimension => html`<d2l-filter
103
+ @d2l-filter-change="${this._handleFilterChange}"
104
+ @d2l-filter-dimension-load-more=${this._handleLoadMore}
105
+ @d2l-filter-dimension-search=${this._handleSearch}>
106
+ ${this._renderDimensionSet(dimension)}
107
+ </d2l-filter>`)}
108
+ </d2l-filter-overflow-group>`;
63
109
  return html`
64
- <d2l-filter
110
+ <d2l-filter
65
111
  @d2l-filter-change="${this._handleFilterChange}"
66
112
  @d2l-filter-dimension-load-more=${this._handleLoadMore}
67
113
  @d2l-filter-dimension-search=${this._handleSearch}>
@@ -10,6 +10,7 @@
10
10
  import '../../filter/filter-dimension-set-value.js';
11
11
  import '../../filter/filter-overflow-group.js';
12
12
  import '../../filter/filter-tags.js';
13
+ import './filter-load-more-demo.js';
13
14
  </script>
14
15
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1.0">
15
16
  <meta charset="UTF-8">
@@ -233,6 +234,13 @@
233
234
  <d2l-filter-tags filter-ids="filter1 filter2 filter3 filter4 filter5 filter6" label="Applied Filters:"></d2l-filter-tags>
234
235
  </template>
235
236
  </d2l-demo-snippet>
237
+
238
+ <h2>Filter Overflow Group with Load More and Search</h2>
239
+ <d2l-demo-snippet>
240
+ <template>
241
+ <d2l-filter-load-more-demo use-overflow-group></d2l-filter-load-more-demo>
242
+ </template>
243
+ </d2l-demo-snippet>
236
244
  </d2l-demo-page>
237
245
  </body>
238
246
  </html>
@@ -4,11 +4,7 @@ import { css, html, LitElement } from 'lit';
4
4
  import { OVERFLOW_CLASS, OverflowGroupMixin } from '../overflow-group/overflow-group-mixin.js';
5
5
  import { getUniqueId } from '../../helpers/uniqueId.js';
6
6
 
7
- function createFilterItem(node) {
8
- const dimensionSets = node.querySelectorAll('d2l-filter-dimension-set');
9
- const clones = Array.from(dimensionSets).map((set) => set.cloneNode(true));
10
- return clones;
11
- }
7
+ const updateEvents = ['d2l-filter-dimension-load-more', 'd2l-filter-dimension-search'];
12
8
 
13
9
  /**
14
10
  * A component that can be used to display a group of filters that will be put into an overflow filter when they no longer fit on the first line of their container
@@ -23,6 +19,7 @@ class FilterOverflowGroup extends OverflowGroupMixin(LitElement) {
23
19
  * @type {boolean}
24
20
  */
25
21
  tags: { type: Boolean },
22
+ _openedDimensions: { state: true },
26
23
  _filterIds: { state: true }
27
24
  };
28
25
  }
@@ -39,10 +36,18 @@ class FilterOverflowGroup extends OverflowGroupMixin(LitElement) {
39
36
  super();
40
37
 
41
38
  this._filterIds = '';
39
+ this._openedDimensions = [];
40
+ this._updateFilterData = this._updateFilterData.bind(this);
41
+ this._handleSlottedFilterChange = this._handleSlottedFilterChange.bind(this);
42
+ this._handleSlottedDimensionFirstOpen = this._handleSlottedDimensionFirstOpen.bind(this);
42
43
  }
43
44
 
44
45
  connectedCallback() {
45
46
  super.connectedCallback();
47
+ for (const event of updateEvents)
48
+ this.addEventListener(event, this._updateFilterData);
49
+ this.addEventListener('d2l-filter-change', this._handleSlottedFilterChange);
50
+ this.addEventListener('d2l-filter-dimension-first-open', this._handleSlottedDimensionFirstOpen);
46
51
 
47
52
  if (!this.tags) return;
48
53
 
@@ -51,10 +56,12 @@ class FilterOverflowGroup extends OverflowGroupMixin(LitElement) {
51
56
  this.appendChild(this._filterTags);
52
57
  }
53
58
 
54
- firstUpdated(changedProperties) {
55
- super.firstUpdated(changedProperties);
56
-
57
- this.addEventListener('d2l-filter-change', this._handleFilterChange);
59
+ disconnectedCallback() {
60
+ super.disconnectedCallback();
61
+ for (const event of updateEvents)
62
+ this.removeEventListener(event, this._updateFilterData);
63
+ this.removeEventListener('d2l-filter-change', this._handleSlottedFilterChange);
64
+ this.removeEventListener('d2l-filter-dimension-first-open', this._handleSlottedDimensionFirstOpen);
58
65
  }
59
66
 
60
67
  updated(changedProperties) {
@@ -79,30 +86,83 @@ class FilterOverflowGroup extends OverflowGroupMixin(LitElement) {
79
86
  node.setAttribute('data-filter-tags-subscribed', 'data-filter-tags-subscribed');
80
87
  }
81
88
 
82
- return createFilterItem(node);
89
+ return node._dimensions;
83
90
  }
84
91
 
85
92
  getOverflowContainer(overflowItems) {
93
+ const newDimensions = overflowItems.reduce((p, n) => p.concat(n), []).map(dim => {
94
+ return { ...dim, values: dim.values.map(v => ({ ...v })) };
95
+ });
96
+
97
+ /* eslint-disable lit/no-private-properties */
86
98
  return html`
87
- <d2l-filter class="${OVERFLOW_CLASS} vdiff-target" @d2l-filter-change="${this._handleFilterChange}">
88
- ${overflowItems}
99
+ <d2l-filter
100
+ class="${OVERFLOW_CLASS} vdiff-target"
101
+ _ignoreSlotChanges
102
+ ._openedDimensions=${this._openedDimensions}
103
+ ._dimensions=${newDimensions}
104
+ @d2l-filter-change="${this.#handleFilterChange}"
105
+ @d2l-filter-dimension-load-more="${this.#handleFilterLoadMore}"
106
+ @d2l-filter-dimension-search="${this.#handleFilterSearch}"
107
+ @d2l-filter-dimension-first-open="${this.#handleFirstOpen}">
89
108
  </d2l-filter>
90
109
  `;
110
+ /* eslint-enable lit/no-private-properties */
111
+ }
112
+
113
+ _handleSlottedDimensionFirstOpen(e) {
114
+ this._openedDimensions.push(e.detail.key);
115
+ this.requestUpdate();
91
116
  }
92
117
 
93
- _handleFilterChange(e) {
94
- const target = (e.target.classList && e.target.classList.contains(OVERFLOW_CLASS)) ? this : e.target;
118
+ _handleSlottedFilterChange(e) {
95
119
  e.detail.dimensions.forEach((dimension) => {
96
- const filterSet = target.querySelector(`d2l-filter-dimension-set[key=${dimension.dimensionKey}`);
97
- if (!filterSet) return;
98
120
  dimension.changes.forEach((change) => {
99
- const filterSetValue = filterSet.querySelector(`d2l-filter-dimension-set-value[key=${change.valueKey}]`);
121
+ const filterSetValue = this.querySelector(`d2l-filter-dimension-set[key=${dimension.dimensionKey}] > d2l-filter-dimension-set-value[key="${change.valueKey}"]`);
100
122
  if (!filterSetValue) return;
101
123
  filterSetValue.selected = change.selected;
102
124
  });
103
125
  });
104
126
  }
105
127
 
128
+ async _updateFilterData() {
129
+ await Promise.all([...this.querySelectorAll('d2l-filter')].map(f => f.updateComplete));
130
+ this.requestUpdate();
131
+ }
132
+
133
+ #handleFilterChange(e) {
134
+ e.detail.dimensions.forEach((dimension) => {
135
+ this.#runOnFilterDimension(dimension.dimensionKey, filter => {
136
+ filter.requestFilterChangeEvent(e.detail.allCleared, [dimension]);
137
+ });
138
+ });
139
+ }
140
+
141
+ #handleFilterLoadMore(e) {
142
+ this.#runOnFilterDimension(e.detail.key, filter => {
143
+ filter.requestFilterLoadMoreEvent(e.detail.key, e.detail.value, e.detail.loadMoreCompleteCallback);
144
+ });
145
+ }
146
+
147
+ #handleFilterSearch(e) {
148
+ this.#runOnFilterDimension(e.detail.key, filter => {
149
+ filter.requestFilterSearchEvent(e.detail.key, e.detail.value, e.detail.searchCompleteCallback);
150
+ });
151
+ }
152
+
153
+ #handleFirstOpen(e) {
154
+ this.#runOnFilterDimension(e.detail.key, filter => {
155
+ filter.requestFilterDimensionFirstOpenEvent(e.detail.key);
156
+ });
157
+ }
158
+
159
+ #runOnFilterDimension(key, callback) {
160
+ const dimension = this.querySelector(`d2l-filter-dimension-set[key=${key}]`);
161
+ const filter = dimension?.parentNode;
162
+ if (filter?.tagName !== 'D2L-FILTER') return;
163
+ callback(filter);
164
+ }
165
+
106
166
  }
107
167
 
108
168
  customElements.define('d2l-filter-overflow-group', FilterOverflowGroup);
@@ -93,6 +93,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
93
93
  _activeDimensionKey: { type: String, attribute: false },
94
94
  _dimensions: { type: Array, attribute: false },
95
95
  _displayKeyboardTooltip: { state: true },
96
+ _ignoreSlotChanges: { type: Boolean },
96
97
  _minWidth: { type: Number, attribute: false },
97
98
  _totalAppliedCount: { type: Number, attribute: false }
98
99
  };
@@ -228,6 +229,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
228
229
  this._changeEventsToDispatch = new Map();
229
230
  this._dimensions = [];
230
231
  this._displayKeyboardTooltip = false;
232
+ this._ignoreSlotChanges = false;
231
233
  this._minWidth = 285;
232
234
  this._openedDimensions = [];
233
235
  this._totalAppliedCount = 0;
@@ -332,7 +334,8 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
332
334
  `;
333
335
  }
334
336
 
335
- update(changedProperties) {
337
+ willUpdate(changedProperties) {
338
+ super.willUpdate(changedProperties);
336
339
  if (
337
340
  changedProperties.has('opened')
338
341
  && this.opened
@@ -341,13 +344,70 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
341
344
  ) {
342
345
  this._updateDimensionShouldBubble(this._dimensions[0]);
343
346
  }
344
- super.update(changedProperties);
347
+ if (changedProperties.has('_dimensions')) {
348
+ this._setFilterCounts();
349
+ this._activeFiltersSubscribers.updateSubscribers();
350
+ }
351
+ }
352
+
353
+ requestFilterChangeEvent(allCleared, dimensions) {
354
+ this.dispatchEvent(new CustomEvent('d2l-filter-change', {
355
+ bubbles: true,
356
+ composed: false,
357
+ detail: { allCleared, dimensions }
358
+ }));
345
359
  }
346
360
 
347
361
  requestFilterClearAll() {
348
362
  this._handleClearAll();
349
363
  }
350
364
 
365
+ requestFilterDimensionFirstOpenEvent(dimensionKey) {
366
+ this._openedDimensions.push(dimensionKey);
367
+ this.dispatchEvent(new CustomEvent('d2l-filter-dimension-first-open', {
368
+ bubbles: true,
369
+ composed: false,
370
+ detail: { key: dimensionKey }
371
+ }));
372
+ }
373
+
374
+ requestFilterLoadMoreEvent(key, searchValue, callback = () => {}) {
375
+ const applySearch = this._getSearchCallback(key);
376
+ this.dispatchEvent(new CustomEvent('d2l-filter-dimension-load-more', {
377
+ detail: {
378
+ key: key,
379
+ value: searchValue,
380
+ loadMoreCompleteCallback: (options) => {
381
+ applySearch(options);
382
+ callback(options);
383
+ }
384
+ },
385
+ bubbles: true,
386
+ composed: false
387
+ }));
388
+ }
389
+
390
+ requestFilterSearchEvent(key, searchValue, callback = () => {}) {
391
+ const dimension = this._getDimensionByKey(key);
392
+ dimension.searchValue = searchValue;
393
+ dimension.loading = true;
394
+ this.requestUpdate();
395
+ const searchCallback = this._getSearchCallback(key);
396
+
397
+ this.dispatchEvent(new CustomEvent('d2l-filter-dimension-search', {
398
+ bubbles: true,
399
+ composed: false,
400
+ detail: {
401
+ key: dimension.key,
402
+ value: dimension.searchValue,
403
+ searchCompleteCallback: (options) => {
404
+ searchCallback(options);
405
+ callback(options);
406
+ }
407
+ }
408
+ }));
409
+ }
410
+
351
411
  requestFilterValueClear(keyObject) {
352
412
  const dimension = this._getDimensionByKey(keyObject.dimension);
353
413
 
@@ -660,11 +720,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
660
720
  dimension.changes = Array.from(dimension.changes.values());
661
721
  });
662
722
 
663
- this.dispatchEvent(new CustomEvent('d2l-filter-change', {
664
- bubbles: true,
665
- composed: false,
666
- detail: { allCleared: allCleared, dimensions: dimensions }
667
- }));
723
+ this.requestFilterChangeEvent(allCleared, dimensions);
668
724
  this._changeEventsToDispatch = new Map();
669
725
  clearTimeout(this._changeEventTimeout);
670
726
  this._activeFiltersSubscribers.updateSubscribers();
@@ -678,11 +734,10 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
678
734
 
679
735
  _dispatchDimensionFirstOpenEvent(dimension) {
680
736
  if (!this._openedDimensions.includes(dimension.key)) {
681
- this.dispatchEvent(new CustomEvent('d2l-filter-dimension-first-open', { bubbles: true, composed: false, detail: { key: dimension.key } }));
737
+ this.requestFilterDimensionFirstOpenEvent(dimension.key);
682
738
  if (dimension.searchType === 'manual') {
683
739
  this._search(dimension);
684
740
  }
685
- this._openedDimensions.push(dimension.key);
686
741
  }
687
742
  }
688
743
 
@@ -694,9 +749,10 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
694
749
  return this._dimensions.find(dimension => dimension.key === key);
695
750
  }
696
751
 
697
- _getSearchCallback(dimension) {
752
+ _getSearchCallback(key) {
698
753
  return function({ keysToDisplay = [], displayAllKeys = false } = {}) {
699
754
  requestAnimationFrame(() => {
755
+ const dimension = this._getDimensionByKey(key);
700
756
  dimension.displayAllKeys = displayAllKeys;
701
757
  dimension.searchKeysToDisplay = keysToDisplay;
702
758
  this._performDimensionSearch(dimension);
@@ -830,21 +886,11 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
830
886
  _handleDimensionLoadMore(e) {
831
887
  const dimensionKey = e.target.parentNode.id.slice(SET_DIMENSION_ID_PREFIX.length);
832
888
  const dimension = this._getDimensionByKey(dimensionKey);
833
- const applySearch = this._getSearchCallback(dimension);
834
889
 
835
- this.dispatchEvent(new CustomEvent('d2l-filter-dimension-load-more', {
836
- detail: {
837
- key: dimensionKey,
838
- value: dimension.searchValue,
839
- loadMoreCompleteCallback: (options) => {
840
- applySearch(options);
841
- const menu = this.shadowRoot.querySelector('d2l-dropdown-menu');
842
- menu ? menu.addEventListener('d2l-dropdown-position', e.detail.complete, { once: true }) : e.detail.complete();
843
- }
844
- },
845
- bubbles: true,
846
- composed: false
847
- }));
890
+ this.requestFilterLoadMoreEvent(dimensionKey, dimension.value, () => {
891
+ const menu = this.shadowRoot.querySelector('d2l-dropdown-menu');
892
+ menu ? menu.addEventListener('d2l-dropdown-position', e.detail.complete, { once: true }) : e.detail.complete();
893
+ });
848
894
  }
849
895
 
850
896
  _handleDimensionShowComplete() {
@@ -919,6 +965,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
919
965
 
920
966
  _handleSlotChange(e) {
921
967
  const dimensionNodes = this._getSlottedNodes(e.target);
968
+ if (this._ignoreSlotChanges) return;
922
969
 
923
970
  this._dimensions = dimensionNodes.map(dimension => {
924
971
  const type = dimension.tagName.toLowerCase();
@@ -951,9 +998,6 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
951
998
 
952
999
  return info;
953
1000
  });
954
-
955
- this._setFilterCounts();
956
- this._activeFiltersSubscribers.updateSubscribers();
957
1001
  }
958
1002
 
959
1003
  _handleTooltipHide() {
@@ -1055,15 +1099,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(LitElement)) {
1055
1099
  dimension.loading = true;
1056
1100
  this.requestUpdate();
1057
1101
 
1058
- this.dispatchEvent(new CustomEvent('d2l-filter-dimension-search', {
1059
- bubbles: false,
1060
- composed: false,
1061
- detail: {
1062
- key: dimension.key,
1063
- value: dimension.searchValue,
1064
- searchCompleteCallback: this._getSearchCallback(dimension)
1065
- }
1066
- }));
1102
+ this.requestFilterSearchEvent(dimension.key, dimension.searchValue);
1067
1103
  }
1068
1104
  }
1069
1105
 
@@ -1,6 +1,7 @@
1
1
  import '../colors/colors.js';
2
2
  import { css, html, nothing } from 'lit';
3
3
  import { isInteractiveInListItemComposedPath, ListItemMixin } from './list-item-mixin.js';
4
+ import { getFlag } from '../../helpers/flags.js';
4
5
  import { getUniqueId } from '../../helpers/uniqueId.js';
5
6
  import { ifDefined } from 'lit/directives/if-defined.js';
6
7
 
@@ -45,6 +46,7 @@ export const ListItemLinkMixin = superclass => class extends ListItemMixin(super
45
46
  super();
46
47
  this.actionHref = null;
47
48
  this._primaryActionId = getUniqueId();
49
+ this._propagateLinkClickEvent = getFlag('GAUD-8733-list-item-propagate-link-click-event', true);
48
50
  }
49
51
 
50
52
  willUpdate(changedProperties) {
@@ -64,15 +66,17 @@ export const ListItemLinkMixin = superclass => class extends ListItemMixin(super
64
66
  /** Dispatched when the item's primary link action is clicked */
65
67
  this.dispatchEvent(new CustomEvent('d2l-list-item-link-click', { bubbles: true }));
66
68
 
67
- e.stopPropagation();
69
+ if (!this._propagateLinkClickEvent) {
70
+ e.stopPropagation();
68
71
 
69
- // Dispatches click event from the list item to maintain existing functionality in consumers that listen for the click event
70
- const listItemClickEvent = new e.constructor(e.type, e);
71
- listItemClickEvent.preventDefault = () => {
72
- e.preventDefault();
73
- };
74
- /** @ignore */
75
- this.dispatchEvent(listItemClickEvent);
72
+ // Dispatches click event from the list item to maintain existing functionality in consumers that listen for the click event
73
+ const listItemClickEvent = new e.constructor(e.type, e);
74
+ listItemClickEvent.preventDefault = () => {
75
+ e.preventDefault();
76
+ };
77
+ /** @ignore */
78
+ this.dispatchEvent(listItemClickEvent);
79
+ }
76
80
  }
77
81
  }
78
82
 
@@ -4172,7 +4172,20 @@
4172
4172
  },
4173
4173
  {
4174
4174
  "name": "d2l-filter-load-more-demo",
4175
- "path": "./components/filter/demo/filter-load-more-demo.js"
4175
+ "path": "./components/filter/demo/filter-load-more-demo.js",
4176
+ "attributes": [
4177
+ {
4178
+ "name": "use-overflow-group",
4179
+ "type": "boolean"
4180
+ }
4181
+ ],
4182
+ "properties": [
4183
+ {
4184
+ "name": "useOverflowGroup",
4185
+ "attribute": "use-overflow-group",
4186
+ "type": "boolean"
4187
+ }
4188
+ ]
4176
4189
  },
4177
4190
  {
4178
4191
  "name": "d2l-filter-search-demo",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.177.0",
3
+ "version": "3.178.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",