@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.
- package/components/backdrop/backdrop-dirty-overlay.js +77 -0
- package/components/backdrop/backdrop-loading.js +103 -21
- package/components/colors/colors.js +2 -0
- package/components/table/demo/table-test.js +13 -9
- package/components/table/table-wrapper.js +42 -7
- package/components/tabs/tab-mixin.js +6 -1
- package/components/tabs/tabs.js +1 -0
- package/custom-elements.json +144 -29
- package/mixins/property-required/property-required-mixin.js +2 -1
- package/package.json +2 -2
|
@@ -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
|
-
*
|
|
31
|
-
* @type {
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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('
|
|
182
|
+
if (changedProperties.has('dataState') && changedProperties.get('dataState') !== undefined) {
|
|
136
183
|
this.#clearLiveArea();
|
|
137
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
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-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
*
|
|
317
|
-
* @type {
|
|
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
|
-
|
|
320
|
+
dataState: {
|
|
320
321
|
reflect: true,
|
|
321
|
-
type:
|
|
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.
|
|
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"
|
|
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:
|
|
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
|
}
|
package/components/tabs/tabs.js
CHANGED
|
@@ -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
|
}
|
package/custom-elements.json
CHANGED
|
@@ -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": "
|
|
234
|
+
"type": "string"
|
|
198
235
|
},
|
|
199
236
|
{
|
|
200
|
-
"name": "
|
|
201
|
-
"description": "
|
|
202
|
-
"type": "
|
|
203
|
-
|
|
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": "
|
|
258
|
+
"type": "string"
|
|
212
259
|
},
|
|
213
260
|
{
|
|
214
|
-
"name": "
|
|
215
|
-
"attribute": "
|
|
216
|
-
"description": "
|
|
217
|
-
"type": "
|
|
218
|
-
|
|
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": "
|
|
14180
|
-
"description": "
|
|
14181
|
-
"type": "
|
|
14182
|
-
"default": "
|
|
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": "
|
|
14262
|
-
"attribute": "
|
|
14263
|
-
"description": "
|
|
14264
|
-
"type": "
|
|
14265
|
-
"default": "
|
|
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": "
|
|
14604
|
-
"description": "
|
|
14605
|
-
"type": "
|
|
14606
|
-
"default": "
|
|
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": "
|
|
14651
|
-
"attribute": "
|
|
14652
|
-
"description": "
|
|
14653
|
-
"type": "
|
|
14654
|
-
"default": "
|
|
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.
|
|
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.
|
|
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": "^
|
|
60
|
+
"eslint-config-brightspace": "^3",
|
|
61
61
|
"eslint-plugin-unicorn": "^64",
|
|
62
62
|
"glob-all": "^3",
|
|
63
63
|
"messageformat-validator": "^3.0.0-beta",
|