@brightspace-ui/core 2.132.5 → 2.134.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.
@@ -0,0 +1,161 @@
1
+ import '../filter.js';
2
+ import '../filter-dimension-set.js';
3
+ import '../filter-dimension-set-value.js';
4
+ import { html, LitElement } from 'lit';
5
+ import { repeat } from 'lit/directives/repeat.js';
6
+
7
+ const FullData = [
8
+ {
9
+ key: 'course',
10
+ text: 'Course',
11
+ values: [
12
+ { key: 'art', selected:false, text: 'Art' },
13
+ { key: 'astronomy', selected:false, text: 'Astronomy' },
14
+ { key: 'biology', selected:true, text: 'Biology' },
15
+ { key: 'chemistry', selected:false, text: 'Chemistry' },
16
+ { key: 'drama', selected:false, text: 'Drama' },
17
+ { key: 'english', selected:false, text: 'English' },
18
+ { key: 'how-to', selected:false, text: 'How To Write a How To Article With a Flashy Title' },
19
+ { key: 'math', selected:false, text: 'Math' },
20
+ { key: 'physics', selected:false, text: 'Physics' },
21
+ { key: 'stats', selected:false, text: 'Statistics' },
22
+ { key: 'writerscraft', selected:true, text: 'Writer\'s Craft' },
23
+ ],
24
+ initialCount: 6
25
+ },
26
+ {
27
+ key: 'role',
28
+ text: 'Role',
29
+ values: [
30
+ { key: 'admin', selected:false, text: 'Admin' },
31
+ { key: 'instructor', selected:false, text: 'Instructor' },
32
+ { key: 'student', selected:false, text: 'Student' }
33
+ ],
34
+ initialCount: 2
35
+ }
36
+ ];
37
+
38
+ class FilterLoadMoreDemo extends LitElement {
39
+
40
+ constructor() {
41
+ super();
42
+ const dimensions = [];
43
+ for (const dim of FullData) {
44
+ const values = {};
45
+ let selectedCount = 0;
46
+ for (const v of dim.values) {
47
+ if (!v.selected) continue;
48
+ values[v.key] = { ...v };
49
+ selectedCount++;
50
+ }
51
+ const data = {
52
+ key: dim.key,
53
+ text: dim.text,
54
+ values
55
+ };
56
+ this._addKeys(data, dim.initialCount - selectedCount);
57
+ dimensions.push(data);
58
+ }
59
+ this._dimensions = dimensions;
60
+ }
61
+
62
+ render() {
63
+ return html`
64
+ <d2l-filter
65
+ @d2l-filter-change="${this._handleFilterChange}"
66
+ @d2l-filter-dimension-load-more=${this._handleLoadMore}
67
+ @d2l-filter-dimension-search=${this._handleSearch}>
68
+ ${repeat(this._dimensions, dimension => dimension.key, dimension => this._renderDimensionSet(dimension))}
69
+ </d2l-filter>
70
+ `;
71
+ }
72
+
73
+ _addKeys(dimension, addCount, searchValue = '') {
74
+ const dimData = FullData.find(dim => dim.key === dimension.key);
75
+
76
+ const keys = [];
77
+ for (const valKey in dimension.values) {
78
+ if (this._textIsInSearch(searchValue, dimension.values[valKey].text)) {
79
+ keys.push(valKey);
80
+ }
81
+ }
82
+ const total = keys.length + addCount;
83
+ dimension.hasMore = false;
84
+ for (const v of dimData.values) {
85
+ if (v.key in dimension.values || !this._textIsInSearch(searchValue, v.text)) continue;
86
+ if (total <= keys.length) {
87
+ dimension.hasMore = true;
88
+ break;
89
+ }
90
+ dimension.values[v.key] = { ...v };
91
+ keys.push(v.key);
92
+ }
93
+ return keys;
94
+ }
95
+
96
+ _handleFilterChange(e) {
97
+ e.detail.dimensions.forEach(dimension => {
98
+ const localData = Object.values(this._dimensions.find(dim => dim.key === dimension.dimensionKey).values);
99
+ const FullDataValues = FullData.find(dim => dim.key === dimension.dimensionKey).values;
100
+ if (dimension.cleared) {
101
+ localData.forEach(value => value.selected = false);
102
+ FullDataValues.forEach(value => value.selected = false);
103
+ } else {
104
+ dimension.changes.forEach(change => {
105
+ localData.find(value => value.key === change.valueKey).selected = change.selected;
106
+ FullDataValues.find(value => value.key === change.valueKey).selected = change.selected;
107
+ });
108
+ }
109
+ });
110
+ this.requestUpdate();
111
+ }
112
+
113
+ async _handleLoadMore(e) {
114
+ const dimensionKey = e.detail.key;
115
+ const dimension = this._dimensions.find(dim => dim.key === dimensionKey);
116
+
117
+ const keysToDisplay = this._addKeys(dimension, 2, e.detail.value);
118
+ this.requestUpdate();
119
+ await this.updateComplete;
120
+ e.detail.loadMoreCompleteCallback({ keysToDisplay });
121
+ }
122
+
123
+ async _handleSearch(e) {
124
+ const dimensionKey = e.detail.key;
125
+ const dimension = this._dimensions.find(dim => dim.key === dimensionKey);
126
+ const dimData = FullData.find(dim => dim.key === dimensionKey);
127
+
128
+ let selectedCount = 0;
129
+ for (const valKey in dimension.values) {
130
+ if (!dimension.values[valKey].selected) delete dimension.values[valKey];
131
+ else if (this._textIsInSearch(e.detail.value, dimension.values[valKey].text)) selectedCount++;
132
+ }
133
+ const keysToDisplay = this._addKeys(dimension, dimData.initialCount - selectedCount, e.detail.value);
134
+
135
+ this.requestUpdate();
136
+ await this.updateComplete;
137
+ e.detail.searchCompleteCallback({ keysToDisplay });
138
+ }
139
+
140
+ _renderDimensionSet(dimension) {
141
+ const { values, key, text, hasMore } = dimension;
142
+
143
+ return html`
144
+ <d2l-filter-dimension-set key="${key}" text="${text}" ?has-more="${hasMore}" search-type="manual">
145
+ ${repeat(Object.values(values).sort((a, b) => (a.text < b.text ? -1 : 0)), value => value.key, value => html`
146
+ <d2l-filter-dimension-set-value
147
+ key="${value.key}"
148
+ text="${value.text}"
149
+ ?selected=${value.selected}>
150
+ </d2l-filter-dimension-set-value>
151
+ `)}
152
+ </d2l-filter-dimension-set>`;
153
+ }
154
+
155
+ _textIsInSearch(searchValue, text) {
156
+ return searchValue === '' || text.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
157
+ }
158
+
159
+ }
160
+ customElements.define('d2l-filter-load-more-demo', FilterLoadMoreDemo);
161
+
@@ -11,6 +11,7 @@
11
11
  import '../../filter/filter-dimension-set-empty-state.js';
12
12
  import '../../filter/filter-dimension-set-value.js';
13
13
  import './filter-search-demo.js';
14
+ import './filter-load-more-demo.js';
14
15
  </script>
15
16
  <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1.0">
16
17
  <meta charset="UTF-8">
@@ -141,6 +142,13 @@
141
142
  </template>
142
143
  </d2l-demo-snippet>
143
144
 
145
+ <h2>Load More</h2>
146
+ <d2l-demo-snippet>
147
+ <template>
148
+ <d2l-filter-load-more-demo></d2l-filter-load-more-demo>
149
+ </template>
150
+ </d2l-demo-snippet>
151
+
144
152
  <h2>Search</h2>
145
153
  <d2l-demo-snippet>
146
154
  <template>
@@ -31,6 +31,11 @@ class FilterDimensionSet extends LitElement {
31
31
  * @type {boolean}
32
32
  */
33
33
  loading: { type: Boolean },
34
+ /**
35
+ * Whether the dimension has more values to load
36
+ * @type {boolean}
37
+ */
38
+ hasMore: { type: Boolean, attribute: 'has-more' },
34
39
  /**
35
40
  * Whether to hide the search input, perform a simple text search, or fire an event on search
36
41
  * @type {'none'|'automatic'|'manual'}
@@ -69,6 +74,7 @@ class FilterDimensionSet extends LitElement {
69
74
  this.headerText = '';
70
75
  this.introductoryText = '';
71
76
  this.loading = false;
77
+ this.hasMore = false;
72
78
  this.searchType = 'automatic';
73
79
  this.selectAll = false;
74
80
  this.selectedFirst = false;
@@ -100,7 +106,7 @@ class FilterDimensionSet extends LitElement {
100
106
  changedProperties.forEach((oldValue, prop) => {
101
107
  if (oldValue === undefined) return;
102
108
 
103
- if (prop === 'text' || prop === 'loading') {
109
+ if (prop === 'text' || prop === 'loading' || prop === 'hasMore' || prop === 'selectedFirst') {
104
110
  changes.set(prop, this[prop]);
105
111
  }
106
112
  });
@@ -132,6 +138,16 @@ class FilterDimensionSet extends LitElement {
132
138
  return values;
133
139
  }
134
140
 
141
+ willUpdate(changedProperties) {
142
+ if (changedProperties.has('hasMore') && this.hasMore) {
143
+ if (this.searchType !== 'manual') {
144
+ console.warn('Paging requires search type set to manual.');
145
+ this.hasMore = false;
146
+ }
147
+ else this.selectedFirst = true;
148
+ }
149
+ }
150
+
135
151
  _dispatchDataChangeEvent(eventDetail) {
136
152
  /** @ignore */
137
153
  this.dispatchEvent(new CustomEvent('d2l-filter-dimension-data-change', {
@@ -15,6 +15,7 @@ import '../list/list-item.js';
15
15
  import '../loading-spinner/loading-spinner.js';
16
16
  import '../menu/menu.js';
17
17
  import '../menu/menu-item.js';
18
+ import '../paging/pager-load-more.js';
18
19
  import '../selection/selection-select-all.js';
19
20
  import '../selection/selection-summary.js';
20
21
 
@@ -42,6 +43,7 @@ const SET_DIMENSION_ID_PREFIX = 'list-';
42
43
  * @fires d2l-filter-dimension-empty-state-action - Dispatched when an empty state action button is clicked
43
44
  * @fires d2l-filter-dimension-first-open - Dispatched when a dimension is opened for the first time
44
45
  * @fires d2l-filter-dimension-search - Dispatched when a dimension that supports searching and has the "manual" search-type is searched
46
+ * @fires d2l-filter-dimension-load-more - Dispatched when a dimension load more pager clicked
45
47
  */
46
48
  class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
47
49
 
@@ -292,7 +294,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
292
294
  }
293
295
 
294
296
  requestFilterValueClear(keyObject) {
295
- const dimension = this._dimensions.find(dimension => dimension.key === keyObject.dimension);
297
+ const dimension = this._getDimensionByKey(keyObject.dimension);
296
298
 
297
299
  switch (dimension.type) {
298
300
  case 'd2l-filter-dimension-set':
@@ -541,6 +543,10 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
541
543
  ${selectedListItems}
542
544
  ${listHeader}
543
545
  ${listItems}
546
+ <d2l-pager-load-more slot="pager"
547
+ @d2l-pager-load-more="${this._handleDimensionLoadMore}"
548
+ ?has-more="${dimension.hasMore}">
549
+ </d2l-pager-load-more>
544
550
  </d2l-list>
545
551
  `;
546
552
  }
@@ -607,7 +613,23 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
607
613
  }
608
614
 
609
615
  _getActiveDimension() {
610
- return !this._activeDimensionKey ? this._dimensions[0] : this._dimensions.find(dimension => dimension.key === this._activeDimensionKey);
616
+ return !this._activeDimensionKey ? this._dimensions[0] : this._getDimensionByKey(this._activeDimensionKey);
617
+ }
618
+
619
+ _getDimensionByKey(key) {
620
+ return this._dimensions.find(dimension => dimension.key === key);
621
+ }
622
+
623
+ _getSearchCallback(dimension) {
624
+ return function({ keysToDisplay = [], displayAllKeys = false } = {}) {
625
+ requestAnimationFrame(() => {
626
+ dimension.displayAllKeys = displayAllKeys;
627
+ dimension.searchKeysToDisplay = keysToDisplay;
628
+ this._performDimensionSearch(dimension);
629
+ dimension.loading = false;
630
+ this.requestUpdate();
631
+ });
632
+ }.bind(this);
611
633
  }
612
634
 
613
635
  _getSlottedNodes(slot) {
@@ -618,7 +640,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
618
640
 
619
641
  _handleChangeSetDimension(e) {
620
642
  const dimensionKey = e.target.id.slice(SET_DIMENSION_ID_PREFIX.length);
621
- const dimension = this._dimensions.find(dimension => dimension.key === dimensionKey);
643
+ const dimension = this._getDimensionByKey(dimensionKey);
622
644
  const valueKey = e.detail.key;
623
645
  const selected = e.detail.selected;
624
646
 
@@ -652,7 +674,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
652
674
 
653
675
  _handleDimensionDataChange(e) {
654
676
  const changes = e.detail.changes;
655
- const dimension = this._dimensions.find(dimension => dimension.key === e.detail.dimensionKey);
677
+ const dimension = this._getDimensionByKey(e.detail.dimensionKey);
656
678
  const value = e.detail.valueKey && dimension.values.find(value => value.key === e.detail.valueKey);
657
679
  const toUpdate = e.detail.valueKey ? value : dimension;
658
680
 
@@ -710,6 +732,25 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
710
732
  this._activeDimensionKey = null;
711
733
  }
712
734
 
735
+ _handleDimensionLoadMore(e) {
736
+ const dimensionKey = e.target.parentNode.id.slice(SET_DIMENSION_ID_PREFIX.length);
737
+ const dimension = this._getDimensionByKey(dimensionKey);
738
+ const applySearch = this._getSearchCallback(dimension);
739
+
740
+ this.dispatchEvent(new CustomEvent('d2l-filter-dimension-load-more', {
741
+ detail: {
742
+ key: dimensionKey,
743
+ value: dimension.searchValue,
744
+ loadMoreCompleteCallback: (options) => {
745
+ applySearch(options);
746
+ e.detail.complete();
747
+ }
748
+ },
749
+ bubbles: true,
750
+ composed: false
751
+ }));
752
+ }
753
+
713
754
  _handleDimensionShowComplete() {
714
755
  const returnButton = this.shadowRoot
715
756
  && this.shadowRoot.querySelector('d2l-button-icon[icon="tier1:chevron-left"]');
@@ -718,7 +759,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
718
759
 
719
760
  _handleDimensionShowStart(e) {
720
761
  this._activeDimensionKey = e.detail.sourceView.getAttribute('data-key');
721
- const dimension = this._dimensions.find(dimension => dimension.key === this._activeDimensionKey);
762
+ const dimension = this._getDimensionByKey(this._activeDimensionKey);
722
763
  if (dimension.introductoryText) announce(dimension.introductoryText);
723
764
  if (dimension.selectedFirst) this._updateDimensionShouldBubble(dimension);
724
765
  this._dispatchDimensionFirstOpenEvent(dimension);
@@ -753,9 +794,6 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
753
794
  const searchValue = e.detail.value.trim();
754
795
  dimension.searchValue = searchValue;
755
796
 
756
- if (dimension.selectedFirst) {
757
- this._updateDimensionShouldBubble(dimension);
758
- }
759
797
  this._search(dimension);
760
798
  }
761
799
 
@@ -775,6 +813,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
775
813
  case 'd2l-filter-dimension-set': {
776
814
  info.headerText = dimension.headerText;
777
815
  info.introductoryText = dimension.introductoryText;
816
+ info.hasMore = dimension.hasMore;
778
817
  info.searchType = dimension.searchType;
779
818
  info.searchValue = '';
780
819
  info.selectedFirst = dimension.selectedFirst;
@@ -839,6 +878,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
839
878
  }
840
879
 
841
880
  _performDimensionSearch(dimension) {
881
+ if (dimension.selectedFirst) this._updateDimensionShouldBubble(dimension);
842
882
  switch (dimension.type) {
843
883
  case 'd2l-filter-dimension-set':
844
884
  dimension.values.forEach(value => {
@@ -882,15 +922,7 @@ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
882
922
  detail: {
883
923
  key: dimension.key,
884
924
  value: dimension.searchValue,
885
- searchCompleteCallback: function({ keysToDisplay = [], displayAllKeys = false } = {}) {
886
- requestAnimationFrame(() => {
887
- dimension.displayAllKeys = displayAllKeys;
888
- dimension.searchKeysToDisplay = keysToDisplay;
889
- this._performDimensionSearch(dimension);
890
- dimension.loading = false;
891
- this.requestUpdate();
892
- });
893
- }.bind(this)
925
+ searchCompleteCallback: this._getSearchCallback(dimension)
894
926
  }
895
927
  }));
896
928
  }
@@ -201,11 +201,13 @@ class MoreLess extends LocalizeCoreElement(LitElement) {
201
201
  __adjustToContent_makeInactive() {
202
202
  this.inactive = true;
203
203
  this.expanded = false;
204
- this.__maxHeight = `${this.__content.scrollHeight}px`;
204
+ // Include 1px of given room to account for issues with Firefox rounding the content's scroll height
205
+ this.__maxHeight = `${this.__content.scrollHeight + 1}px`;
205
206
  }
206
207
 
207
208
  __adjustToContent_resize(contentHeight) {
208
- this.__maxHeight = `${contentHeight}px`;
209
+ // Include 1px of given room to account for issues with Firefox rounding the content's scroll height
210
+ this.__maxHeight = `${contentHeight + 1}px`;
209
211
  }
210
212
 
211
213
  __computeAriaExpanded() {
@@ -3669,6 +3669,10 @@
3669
3669
  }
3670
3670
  ]
3671
3671
  },
3672
+ {
3673
+ "name": "d2l-filter-load-more-demo",
3674
+ "path": "./components/filter/demo/filter-load-more-demo.js"
3675
+ },
3672
3676
  {
3673
3677
  "name": "d2l-filter-search-demo",
3674
3678
  "path": "./components/filter/demo/filter-search-demo.js"
@@ -3819,6 +3823,12 @@
3819
3823
  "type": "boolean",
3820
3824
  "default": "false"
3821
3825
  },
3826
+ {
3827
+ "name": "has-more",
3828
+ "description": "Whether the dimension has more values to load",
3829
+ "type": "boolean",
3830
+ "default": "false"
3831
+ },
3822
3832
  {
3823
3833
  "name": "search-type",
3824
3834
  "description": "Whether to hide the search input, perform a simple text search, or fire an event on search",
@@ -3884,6 +3894,13 @@
3884
3894
  "type": "boolean",
3885
3895
  "default": "false"
3886
3896
  },
3897
+ {
3898
+ "name": "hasMore",
3899
+ "attribute": "has-more",
3900
+ "description": "Whether the dimension has more values to load",
3901
+ "type": "boolean",
3902
+ "default": "false"
3903
+ },
3887
3904
  {
3888
3905
  "name": "searchType",
3889
3906
  "attribute": "search-type",
@@ -4102,6 +4119,10 @@
4102
4119
  {
4103
4120
  "name": "d2l-filter-dimension-search",
4104
4121
  "description": "Dispatched when a dimension that supports searching and has the \"manual\" search-type is searched"
4122
+ },
4123
+ {
4124
+ "name": "d2l-filter-dimension-load-more",
4125
+ "description": "Dispatched when a dimension load more pager clicked"
4105
4126
  }
4106
4127
  ],
4107
4128
  "slots": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "2.132.5",
3
+ "version": "2.134.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",