@brightspace-ui/core 3.177.1 → 3.179.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
 
@@ -49,6 +49,8 @@ Import and use the `<d2l-link>` web component instead of the native `<a>` elemen
49
49
  |--|--|--|
50
50
  | `aria-label` | String | Label to provide more context for screen reader users when the link text is not enough |
51
51
  | `href` | String, required | URL or URL fragment of the link |
52
+ | `disabled` | Boolean | Disables the link |
53
+ | `disabled-tooltip` | String | Tooltip text when disabled |
52
54
  | `download` | String | If the attribute is provided, it will prompt the user to download the resource instead of navigating to it. Additionally, if the attribute is provided with a value, that value will be used for the filename. |
53
55
  | `main` | Boolean | Whether to apply the "main" link style |
54
56
  | `lines` | Number | The number of lines to display before truncating text with an ellipsis. The text will not be truncated unless a value is specified. |
@@ -92,6 +92,20 @@
92
92
  </template>
93
93
  </d2l-demo-snippet>
94
94
 
95
+ <h2>Disabled</h2>
96
+ <d2l-demo-snippet>
97
+ <template>
98
+ <d2l-link disabled href="https://www.d2l.com" target="_blank">Disabled Link</d2l-link>
99
+ </template>
100
+ </d2l-demo-snippet>
101
+
102
+ <h2>Disabled with Tooltip</h2>
103
+ <d2l-demo-snippet>
104
+ <template>
105
+ <d2l-link disabled disabled-tooltip="This link is disabled because you do not have permission" href="https://www.d2l.com" target="_blank">Disabled Link with Tooltip</d2l-link>
106
+ </template>
107
+ </d2l-demo-snippet>
108
+
95
109
  </d2l-demo-page>
96
110
  </body>
97
111
  </html>
@@ -1,11 +1,13 @@
1
1
  import '../colors/colors.js';
2
2
  import '../icons/icon.js';
3
+ import '../tooltip/tooltip.js';
3
4
  import { css, html, LitElement, nothing } from 'lit';
4
5
  import { getOverflowDeclarations, overflowEllipsisDeclarations } from '../../helpers/overflow.js';
5
6
  import { _generateLinkStyles } from './link-styles.js';
6
7
  import { classMap } from 'lit/directives/class-map.js';
7
8
  import { FocusMixin } from '../../mixins/focus/focus-mixin.js';
8
9
  import { getFlag } from '../../helpers/flags.js';
10
+ import { getUniqueId } from '../../helpers/uniqueId.js';
9
11
  import { ifDefined } from 'lit/directives/if-defined.js';
10
12
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
11
13
  import { offscreenStyles } from '../offscreen/offscreen.js';
@@ -28,6 +30,16 @@ class Link extends LocalizeCoreElement(FocusMixin(LitElement)) {
28
30
  * @type {string}
29
31
  */
30
32
  ariaLabel: { type: String, attribute: 'aria-label' },
33
+ /**
34
+ * Disables the link
35
+ * @type {boolean}
36
+ */
37
+ disabled: { type: Boolean, reflect: true },
38
+ /**
39
+ * Tooltip text when disabled
40
+ * @type {string}
41
+ */
42
+ disabledTooltip: { type: String, attribute: 'disabled-tooltip', reflect: true },
31
43
  /**
32
44
  * Download a URL instead of navigating to it
33
45
  * @type {boolean}
@@ -113,6 +125,24 @@ class Link extends LocalizeCoreElement(FocusMixin(LitElement)) {
113
125
  --d2l-icon-fill-color: var(--d2l-color-celestine-minus-1);
114
126
  }
115
127
 
128
+ :host([disabled]:not([disabled-tooltip])) a:hover {
129
+ color: var(--d2l-color-celestine);
130
+ text-decoration: none;
131
+ }
132
+ :host([disabled]:not([disabled-tooltip])) a:hover d2l-icon {
133
+ --d2l-icon-fill-color: var(--d2l-color-celestine);
134
+ }
135
+ a[aria-disabled="true"],
136
+ a[aria-disabled="true"]:active {
137
+ cursor: default;
138
+ }
139
+ a[aria-disabled="true"] .d2l-link-content {
140
+ opacity: 0.74;
141
+ }
142
+ a[aria-disabled="true"] d2l-icon {
143
+ opacity: 0.64;
144
+ }
145
+
116
146
  @media print {
117
147
  d2l-icon {
118
148
  display: none;
@@ -124,6 +154,7 @@ class Link extends LocalizeCoreElement(FocusMixin(LitElement)) {
124
154
 
125
155
  constructor() {
126
156
  super();
157
+ this.disabled = false;
127
158
  this.download = false;
128
159
  this.main = false;
129
160
  this.small = false;
@@ -141,6 +172,7 @@ class Link extends LocalizeCoreElement(FocusMixin(LitElement)) {
141
172
  'd2l-link-small': this.small
142
173
  };
143
174
  const spanClasses = {
175
+ 'd2l-link-content': true,
144
176
  'truncate': this.lines > 1,
145
177
  'truncate-one': this.lines === 1
146
178
  };
@@ -148,6 +180,9 @@ class Link extends LocalizeCoreElement(FocusMixin(LitElement)) {
148
180
  const newWindowElements = (this.target === '_blank')
149
181
  ? html`<span id="new-window"><span style="font-size: 0;">&nbsp;</span><d2l-icon icon="tier1:new-window"></d2l-icon></span><span class="d2l-offscreen">${this.localize('components.link.open-in-new-window')}</span>`
150
182
  : nothing;
183
+ const disabledTooltip = this.disabled && this.disabledTooltip
184
+ ? html`<d2l-tooltip class="vdiff-target" for="${this.#linkId}">${this.disabledTooltip}</d2l-tooltip>`
185
+ : nothing;
151
186
 
152
187
  /*
153
188
  * NOTICE:
@@ -155,14 +190,27 @@ class Link extends LocalizeCoreElement(FocusMixin(LitElement)) {
155
190
  * Do not modify for readability!
156
191
  */
157
192
  return html`<a
193
+ aria-disabled="${ifDefined(this.disabled ? 'true' : undefined)}"
158
194
  aria-label="${ifDefined(this.ariaLabel)}"
159
195
  class="${classMap(linkClasses)}"
196
+ @click="${this.#handleClick}"
160
197
  ?download="${this.download}"
161
- href="${ifDefined(this.href)}"
198
+ href="${ifDefined(!this.disabled ? this.href : undefined)}"
199
+ id="${this.#linkId}"
200
+ tabindex="${ifDefined(this.disabled && this.disabledTooltip ? 0 : undefined)}"
162
201
  target="${ifDefined(this.target)}"
163
202
  ><span
164
203
  class="${classMap(spanClasses)}"
165
- style="${styleMap(styles)}"><slot></slot></span>${newWindowElements}</a>`;
204
+ style="${styleMap(styles)}"><slot></slot></span>${newWindowElements}</a>${disabledTooltip}`;
205
+ }
206
+
207
+ #linkId = getUniqueId();
208
+
209
+ #handleClick(e) {
210
+ if (this.disabled) {
211
+ e.stopPropagation();
212
+ e.preventDefault();
213
+ }
166
214
  }
167
215
 
168
216
  }
@@ -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",
@@ -8470,6 +8483,11 @@
8470
8483
  "description": "ACCESSIBILITY: Label to provide more context for screen reader users when the link text is not enough",
8471
8484
  "type": "string"
8472
8485
  },
8486
+ {
8487
+ "name": "disabled-tooltip",
8488
+ "description": "Tooltip text when disabled",
8489
+ "type": "string"
8490
+ },
8473
8491
  {
8474
8492
  "name": "href",
8475
8493
  "description": "REQUIRED: URL or URL fragment of the link",
@@ -8480,6 +8498,12 @@
8480
8498
  "description": "Where to display the linked URL",
8481
8499
  "type": "string"
8482
8500
  },
8501
+ {
8502
+ "name": "disabled",
8503
+ "description": "Disables the link",
8504
+ "type": "boolean",
8505
+ "default": "false"
8506
+ },
8483
8507
  {
8484
8508
  "name": "download",
8485
8509
  "description": "Download a URL instead of navigating to it",
@@ -8512,6 +8536,12 @@
8512
8536
  "description": "ACCESSIBILITY: Label to provide more context for screen reader users when the link text is not enough",
8513
8537
  "type": "string"
8514
8538
  },
8539
+ {
8540
+ "name": "disabledTooltip",
8541
+ "attribute": "disabled-tooltip",
8542
+ "description": "Tooltip text when disabled",
8543
+ "type": "string"
8544
+ },
8515
8545
  {
8516
8546
  "name": "href",
8517
8547
  "attribute": "href",
@@ -8524,6 +8554,13 @@
8524
8554
  "description": "Where to display the linked URL",
8525
8555
  "type": "string"
8526
8556
  },
8557
+ {
8558
+ "name": "disabled",
8559
+ "attribute": "disabled",
8560
+ "description": "Disables the link",
8561
+ "type": "boolean",
8562
+ "default": "false"
8563
+ },
8527
8564
  {
8528
8565
  "name": "download",
8529
8566
  "attribute": "download",
@@ -35,7 +35,7 @@ export function isInteractive(ele, elems, roles) {
35
35
  return true;
36
36
  }
37
37
  const role = (ele.getAttribute('role') || '');
38
- return (nodeName === 'a' && ele.hasAttribute('href')) || roles[role] || false;
38
+ return (nodeName === 'a' && (ele.hasAttribute('href') || ele.getAttribute('tabindex') === '0')) || roles[role] || false;
39
39
  }
40
40
 
41
41
  export function isInteractiveInComposedPath(composedPath, predicate, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.177.1",
3
+ "version": "3.179.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",