@brightspace-ui/core 3.237.6 → 3.238.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,77 @@
1
+ import '../button/button-subtle.js';
2
+ import { css, html, LitElement } from 'lit';
3
+ import { bodyCompactStyles } from '../typography/styles.js';
4
+ import { getFocusRingStyles } from '../../helpers/focus.js';
5
+
6
+ const overlayStyles = css`
7
+ .action-slot::slotted(*) {
8
+ display: inline;
9
+ }
10
+ :host {
11
+ align-items: center;
12
+ border: 1px solid var(--d2l-color-mica);
13
+ border-radius: 0.3rem;
14
+ column-gap: 0.75rem;
15
+ display: block;
16
+ display: flex;
17
+ flex-wrap: wrap;
18
+ padding: 1.2rem 1.5rem;
19
+ }
20
+
21
+ .d2l-backdrop-dirty-overlay {
22
+ --d2l-focus-ring-offset: 3px;
23
+ border-radius: 0.3rem;
24
+ margin: 0;
25
+ }
26
+
27
+ .d2l-backdrop-dirty-overlay-action {
28
+ vertical-align: top;
29
+ }
30
+ `;
31
+
32
+ /**
33
+ * The `d2l-backdrop-dirty-overlay` component is used to render a dialog with text and an action over another element that needs to be refreshed after user input.
34
+ */
35
+ class BackdropDirtyOverlay extends LitElement {
36
+
37
+ static get properties() {
38
+ return {
39
+ /**
40
+ * The text displayed on the dirty state overlay.
41
+ * @type {string}
42
+ */
43
+ description: { type: String, required: true },
44
+
45
+ /**
46
+ * The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.
47
+ * @type {string}
48
+ */
49
+ action: { type: String, required: true }
50
+ };
51
+ }
52
+
53
+ static get styles() {
54
+ return [bodyCompactStyles, overlayStyles, getFocusRingStyles('.d2l-backdrop-dirty-overlay')];
55
+ }
56
+
57
+ render() {
58
+ return html`
59
+ <p class="d2l-body-compact d2l-backdrop-dirty-overlay" tabindex="-1">${this.description}</p>
60
+ <d2l-button-subtle
61
+ class="d2l-backdrop-dirty-overlay-action"
62
+ @click=${this.#handleActionClick}
63
+ h-align="text"
64
+ text=${this.action}>
65
+ </d2l-button-subtle>
66
+ `;
67
+ }
68
+ #handleActionClick(e) {
69
+ e.stopPropagation();
70
+
71
+ /** Dispatched when the action button on the overlay is clicked */
72
+ this.dispatchEvent(new CustomEvent('d2l-backdrop-dirty-overlay-action', { bubbles: true, composed: true }));
73
+ }
74
+
75
+ }
76
+
77
+ customElements.define('d2l-backdrop-dirty-overlay', BackdropDirtyOverlay);
@@ -1,5 +1,6 @@
1
1
  import '../colors/colors.js';
2
2
  import '../loading-spinner/loading-spinner.js';
3
+ import './backdrop-dirty-overlay.js';
3
4
  import '../offscreen/offscreen.js';
4
5
  import { css, html, LitElement, nothing } from 'lit';
5
6
  import { getComposedChildren, getComposedParent } from '../../helpers/dom.js';
@@ -14,6 +15,7 @@ const BACKDROP_DELAY_MS = 800;
14
15
  const FADE_DURATION_MS = 500;
15
16
  const SPINNER_DELAY_MS = FADE_DURATION_MS;
16
17
  const LOADING_ANNOUNCEMENT_DELAY = 1000;
18
+ const DIRTY_ANNOUNCEMENT_DELAY = 1000;
17
19
 
18
20
  const LOADING_SPINNER_SIZE = 50;
19
21
 
@@ -27,15 +29,36 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
27
29
  static get properties() {
28
30
  return {
29
31
  /**
30
- * Used to control whether the loading backdrop is shown
31
- * @type {boolean}
32
+ * The state of data in the element being overlaid. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed
33
+ * @type {'clean'|'dirty'|'loading'}
32
34
  */
33
- shown: { type: Boolean },
35
+ dataState: {
36
+ reflect: true,
37
+ type: String
38
+ },
34
39
  /**
35
40
  * Used to identify content that the backdrop should make inert
36
- * @type {boolean}
41
+ * @type {string}
37
42
  */
38
43
  for: { type: String, required: true },
44
+ /**
45
+ * The text displayed on the dirty state overlay when the 'dirty' dataState is set.
46
+ * @type {string}
47
+ */
48
+ dirtyText: {
49
+ reflect: true,
50
+ attribute: 'dirty-text',
51
+ type: String
52
+ },
53
+ /**
54
+ * The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.
55
+ * @type {string}
56
+ */
57
+ dirtyButtonText: {
58
+ reflect: true,
59
+ attribute: 'dirty-button-text',
60
+ type: String
61
+ },
39
62
  _state: { type: String, reflect: true },
40
63
  _spinnerTop: { state: true },
41
64
  _ariaContent: { state: true }
@@ -83,9 +106,28 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
83
106
  opacity: 1;
84
107
  transition: opacity ${FADE_DURATION_MS}ms ease-in ${SPINNER_DELAY_MS}ms;
85
108
  }
86
-
87
- :host([_state="hiding"]) .d2l-backdrop,
109
+ :host([_state="shown"][dataState="dirty"]) d2l-loading-spinner,
88
110
  :host([_state="hiding"]) d2l-loading-spinner {
111
+ opacity: 0;
112
+ transition: opacity ${FADE_DURATION_MS}ms ease-out;
113
+ }
114
+
115
+ d2l-backdrop-dirty-overlay {
116
+ background-color: var(--d2l-theme-backdrop-dialog-color);
117
+ height: fit-content;
118
+ justify-content: center;
119
+ opacity: 0;
120
+ position: relative;
121
+ top: 0;
122
+ z-index: 1000;
123
+ }
124
+ :host([_state="shown"]) d2l-backdrop-dirty-overlay {
125
+ opacity: 1;
126
+ transition: opacity ${FADE_DURATION_MS}ms ease-in;
127
+ }
128
+ :host([_state="shown"][dataState="loading"]) d2l-backdrop-dirty-overlay,
129
+ :host([_state="hiding"]) d2l-backdrop-dirty-overlay {
130
+ opacity: 0;
89
131
  transition: opacity ${FADE_DURATION_MS}ms ease-out;
90
132
  }
91
133
 
@@ -97,9 +139,10 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
97
139
 
98
140
  constructor() {
99
141
  super();
100
- this.shown = false;
142
+ this.dataState = 'clean';
101
143
  this._state = 'hidden';
102
144
  this._spinnerTop = 0;
145
+ this._dirtyDialogTop = 0;
103
146
  this._ariaContent = '';
104
147
  }
105
148
 
@@ -111,40 +154,60 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
111
154
  html`<div id="visible">
112
155
  <div class="backdrop" @transitionend="${this.#handleTransitionEnd}" @transitioncancel="${this.#handleTransitionEnd}"></div>
113
156
  <d2l-loading-spinner style=${styleMap({ top: `${this._spinnerTop}px` })} size="${LOADING_SPINNER_SIZE}"></d2l-loading-spinner>
157
+ ${this.#renderDirtyOverlay()}
114
158
  </div>`
115
159
  }
116
160
  <d2l-offscreen style=${styleMap(forcedOffscreenSizelessStyles)} aria-live="polite">${this._ariaContent}</d2l-offscreen>
117
161
  `;
118
162
  }
119
163
  updated(changedProperties) {
164
+ if (changedProperties.get('_state') && changedProperties.get('_state') === 'hidden')
165
+ {
166
+ this.#centerLoadingSpinnerAndDialog();
167
+ }
168
+
120
169
  if (changedProperties.has('_state')) {
121
170
  if (this._state === 'showing') {
122
- setTimeout(() => {
123
- if (this._state === 'showing') this._state = 'shown';
124
- }, BACKDROP_DELAY_MS);
171
+ if (this.dataState === 'loading') {
172
+ setTimeout(() => {
173
+ if (this._state === 'showing') this._state = 'shown';
174
+ }, BACKDROP_DELAY_MS);
175
+ } else {
176
+ this._state = 'shown';
177
+ }
125
178
  }
126
179
  }
127
-
128
- if (changedProperties.has('shown') && (
129
- (reduceMotion && this._state === 'shown') || (!reduceMotion && this._state === 'showing')
130
- )) {
131
- this.#centerLoadingSpinner();
132
- }
133
180
  }
134
181
  willUpdate(changedProperties) {
135
- if (changedProperties.has('shown')) {
182
+ if (changedProperties.has('dataState') && changedProperties.get('dataState') !== undefined) {
136
183
  this.#clearLiveArea();
137
- if (this.shown) {
184
+
185
+ const oldState = changedProperties.get('dataState');
186
+ const newState = this.dataState;
187
+
188
+ // Calculate announcements
189
+ if (newState === 'loading') {
138
190
  this.#setLiveArea(this.localize('components.backdrop-loading.loadingAnnouncement'), { delay: LOADING_ANNOUNCEMENT_DELAY });
139
- this.#show();
140
- } else if (changedProperties.get('shown') !== undefined) {
191
+ } else if (oldState === 'loading' && newState === 'clean') {
141
192
  this.#setLiveArea(this.localize('components.backdrop-loading.loadingCompleteAnnouncement'));
193
+ } else if (newState === 'dirty') {
194
+ this.#setLiveArea(this.#renderDirtyOverlay(), { delay: DIRTY_ANNOUNCEMENT_DELAY });
195
+ }
196
+
197
+ // Update backdrop
198
+ if (oldState === 'clean') {
199
+ this.#show();
200
+ } else if (newState === 'clean') {
142
201
  this.#fade();
202
+ } else if (oldState === 'loading' && newState === 'dirty') {
203
+ setTimeout(() => {
204
+ if (this._state === 'showing') this._state = 'shown';
205
+ }, BACKDROP_DELAY_MS);
143
206
  }
144
207
  }
145
208
  }
146
209
 
147
- #centerLoadingSpinner() {
210
+ async #centerLoadingSpinnerAndDialog() {
148
211
  if (this._state === 'hidden') { return; }
149
212
 
150
213
  const loadingSpinner = this.shadowRoot.querySelector('d2l-loading-spinner');
@@ -164,7 +227,13 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
164
227
  // Adjust for the size of the spinner
165
228
  const spinnerSizeOffset = LOADING_SPINNER_SIZE / 2;
166
229
 
230
+ // Adjust for the size of the dirty dialog
231
+ await this.shadowRoot.querySelector('d2l-backdrop-dirty-overlay').getUpdateComplete();
232
+ await this.shadowRoot.querySelector('d2l-empty-state-action-button')?.getUpdateComplete();
233
+ const dirtyDialogSizeOffset = this.shadowRoot.querySelector('d2l-backdrop-dirty-overlay').getBoundingClientRect().height / 2;
234
+
167
235
  this._spinnerTop = centeringOffset + topOffset - spinnerSizeOffset;
236
+ this._dirtyDialogTop = centeringOffset + topOffset - dirtyDialogSizeOffset;
168
237
  }
169
238
 
170
239
  #clearLiveArea() {
@@ -190,6 +259,7 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
190
259
  this._state = 'hiding';
191
260
  }
192
261
  }
262
+
193
263
  #getBackdropTarget() {
194
264
  const parent = getComposedParent(this);
195
265
 
@@ -203,11 +273,13 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
203
273
 
204
274
  return targetedChildren[0];
205
275
  }
276
+
206
277
  #handleTransitionEnd() {
207
278
  if (this._state === 'hiding') {
208
279
  this.#hide();
209
280
  }
210
281
  }
282
+
211
283
  #hide() {
212
284
  this._state = 'hidden';
213
285
 
@@ -215,9 +287,19 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
215
287
 
216
288
  if (containingBlock.dataset.initiallyInert !== '1') containingBlock.removeAttribute('inert');
217
289
  }
290
+
291
+ #renderDirtyOverlay() {
292
+ return html`<d2l-backdrop-dirty-overlay
293
+ style=${styleMap({ top: `${this._dirtyDialogTop}px` })}
294
+ description="${this.dirtyText}"
295
+ action="${this.dirtyButtonText}"
296
+ ></d2l-backdrop-dirty-overlay>`;
297
+ }
298
+
218
299
  #setLiveArea(content, { delay } = {}) {
219
300
  this.announcementTimeout = setTimeout(() => this._ariaContent = content, delay || 0);
220
301
  }
302
+
221
303
  #show() {
222
304
  this._state = reduceMotion ? 'shown' : 'showing';
223
305
 
@@ -113,6 +113,7 @@ const lightVariables = new Map([
113
113
  // figma - undefined
114
114
  ['--d2l-theme-backdrop-background-color', '--d2l-color-regolith'],
115
115
  ['--d2l-theme-backdrop-opacity', '0.7'],
116
+ ['--d2l-theme-backdrop-dialog-color', '#ffffff'],
116
117
  ['--d2l-theme-background-color-interactive-faint-disabled', '#f9fbff80'], /* --d2l-theme-background-color-interactive-faint-default at 50% opacity, remove once color-mix is widely supported */
117
118
  ['--d2l-theme-badge-background-color', '--d2l-color-gypsum'],
118
119
  ['--d2l-theme-badge-text-color', '--d2l-theme-text-color-static-standard'],
@@ -187,6 +188,7 @@ const darkVariables = new Map([
187
188
  // figma - undefined
188
189
  ['--d2l-theme-backdrop-background-color', '--d2l-color-ferrite'],
189
190
  ['--d2l-theme-backdrop-opacity', '0.7'],
191
+ ['--d2l-theme-backdrop-dialog-color', '#ffffff'],
190
192
  ['--d2l-theme-background-color-interactive-faint-disabled', '#20212280'], /* --d2l-theme-background-color-interactive-faint-default at 50% opacity, remove once color-mix is widely supported */
191
193
  ['--d2l-theme-badge-background-color', '#303335'],
192
194
  ['--d2l-theme-badge-text-color', '--d2l-theme-text-color-static-standard'],
@@ -14,6 +14,8 @@ import '../../selection/selection-action.js';
14
14
  import '../../selection/selection-action-dropdown.js';
15
15
  import '../../selection/selection-action-menu-item.js';
16
16
  import '../../selection/selection-input.js';
17
+ import '../../inputs/input-radio.js';
18
+ import '../../inputs/input-radio-group.js';
17
19
 
18
20
  import { css, html, nothing } from 'lit';
19
21
  import { tableStyles, TableWrapper } from '../table-wrapper.js';
@@ -81,11 +83,12 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
81
83
  this._data = data();
82
84
  this._sortField = undefined;
83
85
  this._sortDesc = false;
86
+ this.dataState = 'clean';
84
87
  }
85
88
 
86
89
  render() {
87
90
  return html`
88
- <d2l-table-wrapper item-count="${ifDefined(this.paging ? 500 : undefined)}">
91
+ <d2l-table-wrapper item-count="${ifDefined(this.paging ? 500 : undefined)}" dirty-text="This text indicates we're in the dirty state" dirty-button-text="Refresh">
89
92
  <d2l-table-controls slot="controls" ?no-sticky="${!this.stickyControls}" select-all-pages-allowed>
90
93
  <d2l-selection-action
91
94
  text="Sticky controls"
@@ -97,11 +100,11 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
97
100
  icon="tier1:${this.stickyHeaders ? 'check' : 'close-default'}"
98
101
  @d2l-selection-action-click="${this._toggleStickyHeaders}"
99
102
  ></d2l-selection-action>
100
- <d2l-selection-action
101
- text="Loading"
102
- icon="tier1:${this.loading ? 'check' : 'close-default'}"
103
- @d2l-selection-action-click="${this._toggleLoading}"
104
- ></d2l-selection-action>
103
+ <d2l-input-radio-group style="align-content:center;max-height:42px" label="Date State" horizontal label-hidden name="dataState" @change=${this._handleDataStateChange}>
104
+ <d2l-input-radio label="Clean" value="clean" ?checked=${this.dataState === 'clean'}></d2l-input-radio>
105
+ <d2l-input-radio label="Dirty" value="dirty" ?checked=${this.dataState === 'dirty'}></d2l-input-radio>
106
+ <d2l-input-radio label="Loading" value="loading" ?checked=${this.dataState === 'loading'}></d2l-input-radio>
107
+ </d2l-input-radio-group>
105
108
  </d2l-table-controls>
106
109
 
107
110
  <table class="d2l-table">
@@ -167,6 +170,10 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
167
170
  `;
168
171
  }
169
172
 
173
+ _handleDataStateChange(e) {
174
+ this.dataState = e.detail.value;
175
+ }
176
+
170
177
  async _handlePagerLoadMore(e) {
171
178
  const pageSize = e.target.pageSize;
172
179
  await new Promise(resolve => setTimeout(resolve, 1000));
@@ -256,9 +263,6 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
256
263
  this.requestUpdate();
257
264
  }
258
265
 
259
- _toggleLoading() {
260
- this.loading = !this.loading;
261
- }
262
266
  _toggleStickyControls() {
263
267
  this.stickyControls = !this.stickyControls;
264
268
  }
@@ -8,6 +8,7 @@ import { getFlag } from '../../helpers/flags.js';
8
8
  import { ifDefined } from 'lit/directives/if-defined.js';
9
9
  import { isPopoverSupported } from '../popover/popover-mixin.js';
10
10
  import { PageableMixin } from '../paging/pageable-mixin.js';
11
+ import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
11
12
  import { SelectionMixin } from '../selection/selection-mixin.js';
12
13
 
13
14
  const enableStickyScrollyFix = getFlag('table-sticky-scrolly-fix', true);
@@ -266,7 +267,7 @@ const SELECTORS = {
266
267
  * @slot controls - Slot for `d2l-table-controls` to be rendered above the table
267
268
  * @slot pager - Slot for `d2l-pager-load-more` to be rendered below the table
268
269
  */
269
- export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
270
+ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionMixin(LitElement))) {
270
271
 
271
272
  static get properties() {
272
273
  return {
@@ -313,13 +314,39 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
313
314
  type: Boolean,
314
315
  },
315
316
  /**
316
- * Whether or not to display a loading backdrop. Set this property when the content in the table is being refreshed.
317
- * @type {boolean}
317
+ * The state of data in the table. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed
318
+ * @type {'clean'|'dirty'|'loading'}
318
319
  */
319
- loading: {
320
+ dataState: {
320
321
  reflect: true,
321
- type: Boolean
322
+ type: String
323
+ },
324
+ /**
325
+ * The text displayed on the dirty state overlay when the 'dirty' dataState is set.
326
+ * @type {string}
327
+ */
328
+ dirtyText: {
329
+ reflect: true,
330
+ attribute: 'dirty-text',
331
+ required: {
332
+ dependentProps: ['dataState'],
333
+ validator: (_value, elem, hasValue) => hasValue || elem.dataState !== 'dirty'
334
+ },
335
+ type: String
322
336
  },
337
+ /**
338
+ * The text displayed on the button dirty state overlay when the 'dirty' dataState is set.
339
+ * @type {string}
340
+ */
341
+ dirtyButtonText: {
342
+ reflect: true,
343
+ attribute: 'dirty-button-text',
344
+ required: {
345
+ dependentProps: ['dataState'],
346
+ validator: (_value, elem, hasValue) => hasValue || elem.dataState !== 'dirty'
347
+ },
348
+ type: String
349
+ }
323
350
  };
324
351
  }
325
352
 
@@ -390,7 +417,10 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
390
417
  this._tableIntersectionObserver = null;
391
418
  this._tableMutationObserver = null;
392
419
  this._tableScrollers = {};
393
- this.loading = false;
420
+ this.dataState = 'clean';
421
+
422
+ this.dirtyText = null;
423
+ this.dirtyButtonText = null;
394
424
 
395
425
  this._excludeStickyColumnsFromScrollCalculations = getFlag('GAUD-9530-exclude-sticky-columns-from-scroll-calculations', false);
396
426
  }
@@ -424,7 +454,7 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
424
454
  const slot = html`
425
455
  <div style="position:relative">
426
456
  <slot id="table-slot" @slotchange="${this._handleSlotChange}"></slot>
427
- <d2l-backdrop-loading for="table-slot" ?shown=${this.loading}></d2l-backdrop-loading>
457
+ <d2l-backdrop-loading @d2l-backdrop-dirty-overlay-action=${this._handleDirtyButton} for="table-slot" dataState=${this.dataState} dirty-text="${this.dirtyText}" dirty-button-text="${this.dirtyButtonText}"></d2l-backdrop-loading>
428
458
  </div>
429
459
  `;
430
460
  const useScrollWrapper = this.stickyHeadersScrollWrapper || !this.stickyHeaders;
@@ -551,6 +581,11 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
551
581
  this._handleControlsChange();
552
582
  }
553
583
 
584
+ _handleDirtyButton() {
585
+ /** Dispatched when the action button on the dirty overlay is clicked */
586
+ this.dispatchEvent(new CustomEvent('d2l-table-dirty-button-clicked'));
587
+ }
588
+
554
589
  _handlePopoverClose(e) {
555
590
  this._updateStickyAncestor(e.target, false);
556
591
  }
@@ -39,11 +39,16 @@ export const TabMixin = superclass => class extends SkeletonMixin(superclass) {
39
39
  :host {
40
40
  box-sizing: border-box;
41
41
  display: inline-block;
42
- max-width: 200px;
42
+ max-width: 10rem;
43
43
  outline: none;
44
44
  position: relative;
45
45
  vertical-align: middle;
46
46
  }
47
+ @container tabs-container (width >= 615px) {
48
+ :host {
49
+ max-width: min(20rem, 40%);
50
+ }
51
+ }
47
52
  :host([hidden]) {
48
53
  display: none;
49
54
  }
@@ -60,6 +60,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
60
60
  :host {
61
61
  --d2l-tabs-background-color: var(--d2l-theme-background-color-base);
62
62
  box-sizing: border-box;
63
+ container: tabs-container / inline-size;
63
64
  display: block;
64
65
  margin-bottom: 1.2rem;
65
66
  }
@@ -186,6 +186,43 @@
186
186
  }
187
187
  ]
188
188
  },
189
+ {
190
+ "name": "d2l-backdrop-dirty-overlay",
191
+ "path": "./components/backdrop/backdrop-dirty-overlay.js",
192
+ "description": "The `d2l-backdrop-dirty-overlay` component is used to render a dialog with text and an action over another element that needs to be refreshed after user input.",
193
+ "attributes": [
194
+ {
195
+ "name": "description",
196
+ "description": "The text displayed on the dirty state overlay.",
197
+ "type": "string"
198
+ },
199
+ {
200
+ "name": "action",
201
+ "description": "The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.",
202
+ "type": "string"
203
+ }
204
+ ],
205
+ "properties": [
206
+ {
207
+ "name": "description",
208
+ "attribute": "description",
209
+ "description": "The text displayed on the dirty state overlay.",
210
+ "type": "string"
211
+ },
212
+ {
213
+ "name": "action",
214
+ "attribute": "action",
215
+ "description": "The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.",
216
+ "type": "string"
217
+ }
218
+ ],
219
+ "events": [
220
+ {
221
+ "name": "d2l-backdrop-dirty-overlay-action",
222
+ "description": "Dispatched when the action button on the overlay is clicked"
223
+ }
224
+ ]
225
+ },
189
226
  {
190
227
  "name": "d2l-backdrop-loading",
191
228
  "path": "./components/backdrop/backdrop-loading.js",
@@ -194,13 +231,23 @@
194
231
  {
195
232
  "name": "for",
196
233
  "description": "Used to identify content that the backdrop should make inert",
197
- "type": "boolean"
234
+ "type": "string"
198
235
  },
199
236
  {
200
- "name": "shown",
201
- "description": "Used to control whether the loading backdrop is shown",
202
- "type": "boolean",
203
- "default": "false"
237
+ "name": "dirty-text",
238
+ "description": "The text displayed on the dirty state overlay when the 'dirty' dataState is set.",
239
+ "type": "string"
240
+ },
241
+ {
242
+ "name": "dirty-button-text",
243
+ "description": "The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.",
244
+ "type": "string"
245
+ },
246
+ {
247
+ "name": "dataState",
248
+ "description": "The state of data in the element being overlaid. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed",
249
+ "type": "'clean'|'dirty'|'loading'",
250
+ "default": "\"clean\""
204
251
  }
205
252
  ],
206
253
  "properties": [
@@ -208,14 +255,26 @@
208
255
  "name": "for",
209
256
  "attribute": "for",
210
257
  "description": "Used to identify content that the backdrop should make inert",
211
- "type": "boolean"
258
+ "type": "string"
212
259
  },
213
260
  {
214
- "name": "shown",
215
- "attribute": "shown",
216
- "description": "Used to control whether the loading backdrop is shown",
217
- "type": "boolean",
218
- "default": "false"
261
+ "name": "dirtyText",
262
+ "attribute": "dirty-text",
263
+ "description": "The text displayed on the dirty state overlay when the 'dirty' dataState is set.",
264
+ "type": "string"
265
+ },
266
+ {
267
+ "name": "dirtyButtonText",
268
+ "attribute": "dirty-button-text",
269
+ "description": "The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.",
270
+ "type": "string"
271
+ },
272
+ {
273
+ "name": "dataState",
274
+ "attribute": "dataState",
275
+ "description": "The state of data in the element being overlaid. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed",
276
+ "type": "'clean'|'dirty'|'loading'",
277
+ "default": "\"clean\""
219
278
  }
220
279
  ]
221
280
  },
@@ -14176,10 +14235,20 @@
14176
14235
  "default": "\"default\""
14177
14236
  },
14178
14237
  {
14179
- "name": "loading",
14180
- "description": "Whether or not to display a loading backdrop. Set this property when the content in the table is being refreshed.",
14181
- "type": "boolean",
14182
- "default": "false"
14238
+ "name": "dataState",
14239
+ "description": "The state of data in the table. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed",
14240
+ "type": "'clean'|'dirty'|'loading'",
14241
+ "default": "\"clean\""
14242
+ },
14243
+ {
14244
+ "name": "dirty-text",
14245
+ "description": "The text displayed on the dirty state overlay when the 'dirty' dataState is set.",
14246
+ "type": "string"
14247
+ },
14248
+ {
14249
+ "name": "dirty-button-text",
14250
+ "description": "The text displayed on the button dirty state overlay when the 'dirty' dataState is set.",
14251
+ "type": "string"
14183
14252
  },
14184
14253
  {
14185
14254
  "name": "item-count",
@@ -14258,11 +14327,23 @@
14258
14327
  "default": "\"default\""
14259
14328
  },
14260
14329
  {
14261
- "name": "loading",
14262
- "attribute": "loading",
14263
- "description": "Whether or not to display a loading backdrop. Set this property when the content in the table is being refreshed.",
14264
- "type": "boolean",
14265
- "default": "false"
14330
+ "name": "dataState",
14331
+ "attribute": "dataState",
14332
+ "description": "The state of data in the table. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed",
14333
+ "type": "'clean'|'dirty'|'loading'",
14334
+ "default": "\"clean\""
14335
+ },
14336
+ {
14337
+ "name": "dirtyText",
14338
+ "attribute": "dirty-text",
14339
+ "description": "The text displayed on the dirty state overlay when the 'dirty' dataState is set.",
14340
+ "type": "string"
14341
+ },
14342
+ {
14343
+ "name": "dirtyButtonText",
14344
+ "attribute": "dirty-button-text",
14345
+ "description": "The text displayed on the button dirty state overlay when the 'dirty' dataState is set.",
14346
+ "type": "string"
14266
14347
  },
14267
14348
  {
14268
14349
  "name": "itemCount",
@@ -14283,6 +14364,12 @@
14283
14364
  "default": "false"
14284
14365
  }
14285
14366
  ],
14367
+ "events": [
14368
+ {
14369
+ "name": "d2l-table-dirty-button-clicked",
14370
+ "description": "Dispatched when the action button on the dirty overlay is clicked"
14371
+ }
14372
+ ],
14286
14373
  "slots": [
14287
14374
  {
14288
14375
  "name": "",
@@ -14600,10 +14687,20 @@
14600
14687
  "default": "\"default\""
14601
14688
  },
14602
14689
  {
14603
- "name": "loading",
14604
- "description": "Whether or not to display a loading backdrop. Set this property when the content in the table is being refreshed.",
14605
- "type": "boolean",
14606
- "default": "false"
14690
+ "name": "dataState",
14691
+ "description": "The state of data in the table. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed",
14692
+ "type": "'clean'|'dirty'|'loading'",
14693
+ "default": "\"clean\""
14694
+ },
14695
+ {
14696
+ "name": "dirty-text",
14697
+ "description": "The text displayed on the dirty state overlay when the 'dirty' dataState is set.",
14698
+ "type": "string"
14699
+ },
14700
+ {
14701
+ "name": "dirty-button-text",
14702
+ "description": "The text displayed on the button dirty state overlay when the 'dirty' dataState is set.",
14703
+ "type": "string"
14607
14704
  },
14608
14705
  {
14609
14706
  "name": "item-count",
@@ -14647,11 +14744,23 @@
14647
14744
  "default": "\"default\""
14648
14745
  },
14649
14746
  {
14650
- "name": "loading",
14651
- "attribute": "loading",
14652
- "description": "Whether or not to display a loading backdrop. Set this property when the content in the table is being refreshed.",
14653
- "type": "boolean",
14654
- "default": "false"
14747
+ "name": "dataState",
14748
+ "attribute": "dataState",
14749
+ "description": "The state of data in the table. Set to 'clean' when the data represents the user's latest selections, 'dirty' when the data does not represent the user's latest selections, and 'loading' if the data is being actively refreshed",
14750
+ "type": "'clean'|'dirty'|'loading'",
14751
+ "default": "\"clean\""
14752
+ },
14753
+ {
14754
+ "name": "dirtyText",
14755
+ "attribute": "dirty-text",
14756
+ "description": "The text displayed on the dirty state overlay when the 'dirty' dataState is set.",
14757
+ "type": "string"
14758
+ },
14759
+ {
14760
+ "name": "dirtyButtonText",
14761
+ "attribute": "dirty-button-text",
14762
+ "description": "The text displayed on the button dirty state overlay when the 'dirty' dataState is set.",
14763
+ "type": "string"
14655
14764
  },
14656
14765
  {
14657
14766
  "name": "itemCount",
@@ -14672,6 +14781,12 @@
14672
14781
  "default": "false"
14673
14782
  }
14674
14783
  ],
14784
+ "events": [
14785
+ {
14786
+ "name": "d2l-table-dirty-button-clicked",
14787
+ "description": "Dispatched when the action button on the dirty overlay is clicked"
14788
+ }
14789
+ ],
14675
14790
  "slots": [
14676
14791
  {
14677
14792
  "name": "",
@@ -40,7 +40,7 @@ export const PropertyRequiredMixin = dedupeMixin(superclass => class extends sup
40
40
  updated(changedProperties) {
41
41
  super.updated(changedProperties);
42
42
  this._requiredProperties.forEach((value, name) => {
43
- const doValidate = changedProperties.has(name) || value.dependentProps.includes(name);
43
+ const doValidate = changedProperties.has(name) || value.dependentProps.find((prop) => changedProperties.has(prop));
44
44
  if (doValidate) this._validateRequiredProperty(name);
45
45
  });
46
46
  }
@@ -79,6 +79,7 @@ export const PropertyRequiredMixin = dedupeMixin(superclass => class extends sup
79
79
  if (!this._requiredProperties.has(name) || !this.isConnected) return;
80
80
 
81
81
  const info = this._requiredProperties.get(name);
82
+ if (!info.timeout) return;
82
83
  clearTimeout(info.timeout);
83
84
  info.timeout = null;
84
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "3.237.6",
3
+ "version": "3.238.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",
@@ -57,7 +57,7 @@
57
57
  "@web/dev-server": "^0.4",
58
58
  "chalk": "^5",
59
59
  "eslint": "^9",
60
- "eslint-config-brightspace": "^2.0.0",
60
+ "eslint-config-brightspace": "^3",
61
61
  "eslint-plugin-unicorn": "^64",
62
62
  "glob-all": "^3",
63
63
  "messageformat-validator": "^3.0.0-beta",