@brightspace-ui/core 3.237.5 → 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 +63 -12
- 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,8 +8,11 @@ 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
|
|
|
14
|
+
const enableStickyScrollyFix = getFlag('table-sticky-scrolly-fix', true);
|
|
15
|
+
|
|
13
16
|
export const tableStyles = css`
|
|
14
17
|
.d2l-table {
|
|
15
18
|
border-collapse: separate; /* needed to override reset stylesheets */
|
|
@@ -264,7 +267,7 @@ const SELECTORS = {
|
|
|
264
267
|
* @slot controls - Slot for `d2l-table-controls` to be rendered above the table
|
|
265
268
|
* @slot pager - Slot for `d2l-pager-load-more` to be rendered below the table
|
|
266
269
|
*/
|
|
267
|
-
export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
270
|
+
export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionMixin(LitElement))) {
|
|
268
271
|
|
|
269
272
|
static get properties() {
|
|
270
273
|
return {
|
|
@@ -311,13 +314,39 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
|
311
314
|
type: Boolean,
|
|
312
315
|
},
|
|
313
316
|
/**
|
|
314
|
-
*
|
|
315
|
-
* @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'}
|
|
316
319
|
*/
|
|
317
|
-
|
|
320
|
+
dataState: {
|
|
318
321
|
reflect: true,
|
|
319
|
-
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
|
|
320
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
|
+
}
|
|
321
350
|
};
|
|
322
351
|
}
|
|
323
352
|
|
|
@@ -388,7 +417,10 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
|
388
417
|
this._tableIntersectionObserver = null;
|
|
389
418
|
this._tableMutationObserver = null;
|
|
390
419
|
this._tableScrollers = {};
|
|
391
|
-
this.
|
|
420
|
+
this.dataState = 'clean';
|
|
421
|
+
|
|
422
|
+
this.dirtyText = null;
|
|
423
|
+
this.dirtyButtonText = null;
|
|
392
424
|
|
|
393
425
|
this._excludeStickyColumnsFromScrollCalculations = getFlag('GAUD-9530-exclude-sticky-columns-from-scroll-calculations', false);
|
|
394
426
|
}
|
|
@@ -422,7 +454,7 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
|
422
454
|
const slot = html`
|
|
423
455
|
<div style="position:relative">
|
|
424
456
|
<slot id="table-slot" @slotchange="${this._handleSlotChange}"></slot>
|
|
425
|
-
<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>
|
|
426
458
|
</div>
|
|
427
459
|
`;
|
|
428
460
|
const useScrollWrapper = this.stickyHeadersScrollWrapper || !this.stickyHeaders;
|
|
@@ -447,6 +479,10 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
|
447
479
|
}
|
|
448
480
|
}
|
|
449
481
|
|
|
482
|
+
#hasIntersected = false;
|
|
483
|
+
|
|
484
|
+
#noScrollWidthTimeout = null;
|
|
485
|
+
|
|
450
486
|
_applyClassNames() {
|
|
451
487
|
if (!this._table) return;
|
|
452
488
|
|
|
@@ -545,6 +581,11 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
|
545
581
|
this._handleControlsChange();
|
|
546
582
|
}
|
|
547
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
|
+
|
|
548
589
|
_handlePopoverClose(e) {
|
|
549
590
|
this._updateStickyAncestor(e.target, false);
|
|
550
591
|
}
|
|
@@ -581,6 +622,7 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
|
581
622
|
this._tableIntersectionObserver = new IntersectionObserver((entries) => {
|
|
582
623
|
entries.forEach((entry) => {
|
|
583
624
|
if (entry.isIntersecting) {
|
|
625
|
+
this.#hasIntersected = true;
|
|
584
626
|
this._handleTableChange();
|
|
585
627
|
}
|
|
586
628
|
});
|
|
@@ -647,11 +689,20 @@ export class TableWrapper extends PageableMixin(SelectionMixin(LitElement)) {
|
|
|
647
689
|
const head = this._table.querySelector('thead');
|
|
648
690
|
const body = this._table.querySelector('tbody');
|
|
649
691
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
this
|
|
653
|
-
|
|
654
|
-
|
|
692
|
+
if (enableStickyScrollyFix) {
|
|
693
|
+
clearTimeout(this.#noScrollWidthTimeout);
|
|
694
|
+
this.#noScrollWidthTimeout = setTimeout(() => {
|
|
695
|
+
const maxScrollWidth = Math.max(head?.scrollWidth, body?.scrollWidth);
|
|
696
|
+
this._noScrollWidth = (maxScrollWidth <= this.clientWidth);
|
|
697
|
+
});
|
|
698
|
+
if (!head || !body || !this.stickyHeaders || !this.stickyHeadersScrollWrapper || this._noScrollWidth || !this.#hasIntersected) return;
|
|
699
|
+
} else {
|
|
700
|
+
const maxScrollWidth = Math.max(head?.scrollWidth, body?.scrollWidth);
|
|
701
|
+
setTimeout(() => {
|
|
702
|
+
this._noScrollWidth = this.clientWidth === maxScrollWidth;
|
|
703
|
+
});
|
|
704
|
+
if (!head || !body || !this._table || !this.stickyHeaders || !this.stickyHeadersScrollWrapper || this._noScrollWidth) return;
|
|
705
|
+
}
|
|
655
706
|
|
|
656
707
|
const candidateRowHeadCells = [];
|
|
657
708
|
|
|
@@ -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",
|