@brightspace-ui/core 3.276.0 → 3.278.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-loading.js +100 -133
- package/components/backdrop/backdrop-stale-overlay.js +58 -0
- package/components/table/demo/table-test.js +9 -9
- package/components/table/table-wrapper.js +41 -46
- package/components/tabs/tab-mixin.js +1 -10
- package/components/tabs/tabs.js +1 -2
- package/custom-elements.json +104 -104
- package/lang/ar.js +1 -0
- package/lang/ca.js +1 -0
- package/lang/cy.js +1 -0
- package/lang/da.js +1 -0
- package/lang/de.js +1 -0
- package/lang/en-gb.js +1 -0
- package/lang/en.js +1 -0
- package/lang/es-es.js +1 -0
- package/lang/es.js +1 -0
- package/lang/fr-fr.js +1 -0
- package/lang/fr.js +1 -0
- package/lang/haw.js +1 -0
- package/lang/hi.js +1 -0
- package/lang/ja.js +1 -0
- package/lang/ko.js +1 -0
- package/lang/mi.js +1 -0
- package/lang/nl.js +1 -0
- package/lang/pt.js +1 -0
- package/lang/sv.js +1 -0
- package/lang/th.js +1 -0
- package/lang/tr.js +1 -0
- package/lang/vi.js +1 -0
- package/lang/zh-cn.js +1 -0
- package/lang/zh-tw.js +1 -0
- package/mixins/freshness/freshness-mixin.js +35 -0
- package/package.json +1 -1
- package/components/backdrop/backdrop-dirty-overlay.js +0 -73
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import './backdrop-stale-overlay.js';
|
|
1
2
|
import '../colors/colors.js';
|
|
2
3
|
import '../loading-spinner/loading-spinner.js';
|
|
3
|
-
import '
|
|
4
|
-
import {
|
|
4
|
+
import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
|
|
5
|
+
import { freshness, FreshnessMixin } from '../../mixins/freshness/freshness-mixin.js';
|
|
5
6
|
import { getComposedChildren, getComposedParent } from '../../helpers/dom.js';
|
|
6
7
|
import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
|
|
7
|
-
|
|
8
8
|
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
|
|
9
9
|
import { styleMap } from 'lit/directives/style-map.js';
|
|
10
10
|
|
|
@@ -12,7 +12,6 @@ const BACKDROP_DELAY_MS = 800;
|
|
|
12
12
|
const FADE_DURATION_MS = 500;
|
|
13
13
|
const SPINNER_DELAY_MS = FADE_DURATION_MS;
|
|
14
14
|
const LOADING_ANNOUNCEMENT_DELAY = 1000;
|
|
15
|
-
|
|
16
15
|
const LOADING_SPINNER_SIZE = 50;
|
|
17
16
|
|
|
18
17
|
const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
@@ -20,56 +19,31 @@ const reduceMotion = matchMedia('(prefers-reduced-motion: reduce)').matches;
|
|
|
20
19
|
/**
|
|
21
20
|
* A component for displaying a semi-transparent backdrop and a loading spinner over the containing element
|
|
22
21
|
*/
|
|
23
|
-
class
|
|
22
|
+
class BackdropLoading extends PropertyRequiredMixin(FreshnessMixin(LocalizeCoreElement(LitElement))) {
|
|
24
23
|
|
|
25
24
|
static properties = {
|
|
26
|
-
/**
|
|
27
|
-
* 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
|
|
28
|
-
* @type {'clean'|'dirty'|'loading'}
|
|
29
|
-
*/
|
|
30
|
-
dataState: {
|
|
31
|
-
reflect: true,
|
|
32
|
-
type: String
|
|
33
|
-
},
|
|
34
25
|
/**
|
|
35
26
|
* Used to identify content that the backdrop should make inert
|
|
36
27
|
* @type {string}
|
|
37
28
|
*/
|
|
38
29
|
for: { type: String, required: true },
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
dirtyText: {
|
|
44
|
-
reflect: true,
|
|
45
|
-
attribute: 'dirty-text',
|
|
46
|
-
type: String
|
|
47
|
-
},
|
|
48
|
-
/**
|
|
49
|
-
* The text displayed on the button of the dirty state overlay when the 'dirty' dataState is set.
|
|
50
|
-
* @type {string}
|
|
51
|
-
*/
|
|
52
|
-
dirtyButtonText: {
|
|
53
|
-
reflect: true,
|
|
54
|
-
attribute: 'dirty-button-text',
|
|
55
|
-
type: String
|
|
56
|
-
},
|
|
57
|
-
_state: { type: String, reflect: true },
|
|
58
|
-
_spinnerTop: { state: true },
|
|
59
|
-
_ariaContent: { state: true }
|
|
30
|
+
_ariaContent: { state: true },
|
|
31
|
+
_loadingSpinnerTop: { state: true },
|
|
32
|
+
_staleOverlayTop: { state: true },
|
|
33
|
+
_state: { type: String, reflect: true }
|
|
60
34
|
};
|
|
61
35
|
|
|
62
36
|
static styles = [css`
|
|
63
|
-
|
|
37
|
+
.backdrop-container {
|
|
64
38
|
display: none;
|
|
65
39
|
}
|
|
66
40
|
|
|
67
41
|
:host([_state="showing"]),
|
|
68
42
|
:host([_state="shown"]),
|
|
69
43
|
:host([_state="hiding"]),
|
|
70
|
-
:host([_state="showing"])
|
|
71
|
-
:host([_state="shown"])
|
|
72
|
-
:host([_state="hiding"])
|
|
44
|
+
:host([_state="showing"]) .backdrop-container,
|
|
45
|
+
:host([_state="shown"]) .backdrop-container,
|
|
46
|
+
:host([_state="hiding"]) .backdrop-container {
|
|
73
47
|
display: flex;
|
|
74
48
|
inset: 0;
|
|
75
49
|
justify-content: center;
|
|
@@ -99,13 +73,13 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
|
|
|
99
73
|
opacity: 1;
|
|
100
74
|
transition: opacity ${FADE_DURATION_MS}ms ease-in ${SPINNER_DELAY_MS}ms;
|
|
101
75
|
}
|
|
102
|
-
:host([_state="shown"][
|
|
76
|
+
:host([_state="shown"][freshness="${unsafeCSS(freshness.stale)}"]) d2l-loading-spinner,
|
|
103
77
|
:host([_state="hiding"]) d2l-loading-spinner {
|
|
104
78
|
opacity: 0;
|
|
105
79
|
transition: opacity ${FADE_DURATION_MS}ms ease-out;
|
|
106
80
|
}
|
|
107
81
|
|
|
108
|
-
d2l-backdrop-
|
|
82
|
+
d2l-backdrop-stale-overlay {
|
|
109
83
|
background-color: var(--d2l-theme-backdrop-dialog-color);
|
|
110
84
|
height: fit-content;
|
|
111
85
|
justify-content: center;
|
|
@@ -114,12 +88,12 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
|
|
|
114
88
|
top: 0;
|
|
115
89
|
z-index: 1000;
|
|
116
90
|
}
|
|
117
|
-
:host([_state="shown"]) d2l-backdrop-
|
|
91
|
+
:host([_state="shown"]) d2l-backdrop-stale-overlay {
|
|
118
92
|
opacity: 1;
|
|
119
93
|
transition: opacity ${FADE_DURATION_MS}ms ease-in;
|
|
120
94
|
}
|
|
121
|
-
:host([_state="shown"][
|
|
122
|
-
:host([_state="hiding"]) d2l-backdrop-
|
|
95
|
+
:host([_state="shown"][freshness="${unsafeCSS(freshness.loading)}"]) d2l-backdrop-stale-overlay,
|
|
96
|
+
:host([_state="hiding"]) d2l-backdrop-stale-overlay {
|
|
123
97
|
opacity: 0;
|
|
124
98
|
transition: opacity ${FADE_DURATION_MS}ms ease-out;
|
|
125
99
|
}
|
|
@@ -131,46 +105,46 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
|
|
|
131
105
|
|
|
132
106
|
constructor() {
|
|
133
107
|
super();
|
|
134
|
-
this.dataState = 'clean';
|
|
135
|
-
this._state = 'hidden';
|
|
136
|
-
this._spinnerTop = 0;
|
|
137
|
-
this._dirtyDialogTop = 0;
|
|
138
108
|
this._ariaContent = '';
|
|
109
|
+
this._loadingSpinnerTop = 0;
|
|
110
|
+
this._staleOverlayTop = 0;
|
|
111
|
+
this._state = 'hidden';
|
|
139
112
|
}
|
|
140
113
|
|
|
141
114
|
render() {
|
|
142
115
|
const backdropVisible = this._state !== 'hidden';
|
|
143
116
|
|
|
117
|
+
const backdropContainer = backdropVisible ? html`<div class="backdrop-container">
|
|
118
|
+
<div class="backdrop" @transitionend="${this.#handleTransitionEnd}" @transitioncancel="${this.#handleTransitionEnd}"></div>
|
|
119
|
+
<d2l-loading-spinner style="${styleMap({ top: `${this._loadingSpinnerTop}px` })}" size="${LOADING_SPINNER_SIZE}"></d2l-loading-spinner>
|
|
120
|
+
</div>` : nothing;
|
|
121
|
+
|
|
122
|
+
const backdropStaleOverlay = backdropVisible ? html`<d2l-backdrop-stale-overlay
|
|
123
|
+
button-text="${this.freshnessStaleButtonText}"
|
|
124
|
+
text="${this.freshnessStaleText}"
|
|
125
|
+
?inert="${this.freshness !== freshness.stale}"
|
|
126
|
+
style="${styleMap({ top: `${this._staleOverlayTop}px` })}">
|
|
127
|
+
</d2l-backdrop-stale-overlay>` : nothing;
|
|
128
|
+
|
|
144
129
|
return html`
|
|
145
|
-
${
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
</div>` : nothing
|
|
150
|
-
}
|
|
151
|
-
<div aria-live="polite" id="d2l-live-region">
|
|
152
|
-
${backdropVisible ?
|
|
153
|
-
html`<d2l-backdrop-dirty-overlay
|
|
154
|
-
style=${styleMap({ top: `${this._dirtyDialogTop}px` })}
|
|
155
|
-
description="${this.dirtyText}"
|
|
156
|
-
action="${this.dirtyButtonText}"
|
|
157
|
-
?inert=${this.dataState !== 'dirty'}
|
|
158
|
-
></d2l-backdrop-dirty-overlay>` : nothing }
|
|
159
|
-
<d2l-offscreen>
|
|
160
|
-
${this._ariaContent}
|
|
161
|
-
</d2l-offscreen>
|
|
130
|
+
${backdropContainer}
|
|
131
|
+
<div aria-live="polite" class="backdrop-stale-container">
|
|
132
|
+
${backdropStaleOverlay}
|
|
133
|
+
<d2l-offscreen>${this._ariaContent}</d2l-offscreen>
|
|
162
134
|
</div>
|
|
163
135
|
`;
|
|
164
136
|
}
|
|
137
|
+
|
|
165
138
|
updated(changedProperties) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
139
|
+
super.updated(changedProperties);
|
|
140
|
+
|
|
141
|
+
if (changedProperties.get('_state') && changedProperties.get('_state') === 'hidden') {
|
|
142
|
+
this.#updatePosition();
|
|
169
143
|
}
|
|
170
144
|
|
|
171
145
|
if (changedProperties.has('_state')) {
|
|
172
146
|
if (this._state === 'showing') {
|
|
173
|
-
if (this.
|
|
147
|
+
if (this.freshness === freshness.loading) {
|
|
174
148
|
setTimeout(() => {
|
|
175
149
|
if (this._state === 'showing') this._state = 'shown';
|
|
176
150
|
}, BACKDROP_DELAY_MS);
|
|
@@ -180,26 +154,27 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
|
|
|
180
154
|
}
|
|
181
155
|
}
|
|
182
156
|
}
|
|
157
|
+
|
|
183
158
|
willUpdate(changedProperties) {
|
|
184
|
-
|
|
185
|
-
this.#clearLiveArea();
|
|
159
|
+
super.willUpdate(changedProperties);
|
|
186
160
|
|
|
187
|
-
|
|
188
|
-
|
|
161
|
+
if (changedProperties.has('freshness') && changedProperties.get('freshness') !== undefined) {
|
|
162
|
+
this.#clearLiveRegionContent();
|
|
189
163
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
this.#
|
|
164
|
+
const oldState = changedProperties.get('freshness');
|
|
165
|
+
const newState = this.freshness;
|
|
166
|
+
|
|
167
|
+
if (newState === freshness.loading) {
|
|
168
|
+
this.#setLiveRegionContent(this.localize('components.backdrop-loading.loadingAnnouncement'), { delay: LOADING_ANNOUNCEMENT_DELAY });
|
|
169
|
+
} else if (oldState === freshness.loading && newState === freshness.fresh) {
|
|
170
|
+
this.#setLiveRegionContent(this.localize('components.backdrop-loading.loadingCompleteAnnouncement'));
|
|
195
171
|
}
|
|
196
172
|
|
|
197
|
-
|
|
198
|
-
if (oldState === 'clean') {
|
|
173
|
+
if (oldState === freshness.fresh) {
|
|
199
174
|
this.#show();
|
|
200
|
-
} else if (newState ===
|
|
175
|
+
} else if (newState === freshness.fresh) {
|
|
201
176
|
this.#fade();
|
|
202
|
-
} else if (oldState ===
|
|
177
|
+
} else if (oldState === freshness.loading && newState === freshness.stale) {
|
|
203
178
|
setTimeout(() => {
|
|
204
179
|
if (this._state === 'showing') this._state = 'shown';
|
|
205
180
|
}, BACKDROP_DELAY_MS);
|
|
@@ -207,52 +182,19 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
|
|
|
207
182
|
}
|
|
208
183
|
}
|
|
209
184
|
|
|
210
|
-
|
|
211
|
-
if (this._state === 'hidden') { return; }
|
|
212
|
-
|
|
213
|
-
const loadingSpinner = this.shadowRoot.querySelector('d2l-loading-spinner');
|
|
214
|
-
if (!loadingSpinner) { return; }
|
|
215
|
-
|
|
216
|
-
const boundingRect = this.shadowRoot.querySelector('#visible').getBoundingClientRect();
|
|
217
|
-
|
|
218
|
-
// Calculate the centerpoint of the visible portion of the element
|
|
219
|
-
const upperVisibleBound = Math.max(0, boundingRect.top);
|
|
220
|
-
const lowerVisibleBound = Math.min(window.innerHeight, boundingRect.bottom);
|
|
221
|
-
const visibleHeight = lowerVisibleBound - upperVisibleBound;
|
|
222
|
-
const centeringOffset = (visibleHeight / 4);
|
|
223
|
-
|
|
224
|
-
// Calculate if an offset is required to move to the top of the viewport before centering
|
|
225
|
-
const topOffset = Math.max(0, -boundingRect.top); // measures the distance below the top of the viewport, which is negative if the element starts above the viewport
|
|
226
|
-
|
|
227
|
-
// Adjust for the size of the spinner
|
|
228
|
-
const spinnerSizeOffset = LOADING_SPINNER_SIZE / 2;
|
|
229
|
-
|
|
230
|
-
this._spinnerTop = centeringOffset + topOffset - spinnerSizeOffset;
|
|
185
|
+
#announcementTimeoutId;
|
|
231
186
|
|
|
232
|
-
|
|
233
|
-
const dirtyOverlay = this.shadowRoot.querySelector('d2l-backdrop-dirty-overlay');
|
|
234
|
-
if (dirtyOverlay) {
|
|
235
|
-
await this.shadowRoot.querySelector('d2l-empty-state-action-button')?.getUpdateComplete();
|
|
236
|
-
const dirtyDialogSizeOffset = dirtyOverlay.getBoundingClientRect().height / 2;
|
|
237
|
-
|
|
238
|
-
this._dirtyDialogTop = centeringOffset + topOffset - dirtyDialogSizeOffset;
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
#clearLiveArea() {
|
|
187
|
+
#clearLiveRegionContent() {
|
|
243
188
|
this._ariaContent = '';
|
|
244
189
|
|
|
245
|
-
if (this
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
this.announcementTimeout = null;
|
|
190
|
+
if (this.#announcementTimeoutId) clearTimeout(this.#announcementTimeoutId);
|
|
191
|
+
this.#announcementTimeoutId = null;
|
|
250
192
|
}
|
|
251
193
|
|
|
252
194
|
#fade() {
|
|
253
195
|
let hideImmediately = reduceMotion || this._state === 'showing';
|
|
254
196
|
if (this._state === 'shown') {
|
|
255
|
-
const currentOpacity = getComputedStyle(this.shadowRoot.querySelector('
|
|
197
|
+
const currentOpacity = getComputedStyle(this.shadowRoot.querySelector('.backdrop-stale-container')).opacity;
|
|
256
198
|
hideImmediately ||= (currentOpacity === '0');
|
|
257
199
|
}
|
|
258
200
|
|
|
@@ -266,14 +208,10 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
|
|
|
266
208
|
#getBackdropTarget() {
|
|
267
209
|
const parent = getComposedParent(this);
|
|
268
210
|
|
|
269
|
-
const targetedChildren = getComposedChildren(
|
|
270
|
-
|
|
271
|
-
(
|
|
272
|
-
|
|
273
|
-
);
|
|
274
|
-
|
|
275
|
-
if (targetedChildren.length === 0) { throw new Error(`Backdrop cannot find sibling identified by 'for' property with value ${this.for}`);}
|
|
276
|
-
|
|
211
|
+
const targetedChildren = getComposedChildren(parent, (elem) => elem.id === this.for, false);
|
|
212
|
+
if (targetedChildren.length === 0) {
|
|
213
|
+
throw new Error(`Backdrop cannot find sibling identified by 'for' property with value ${this.for}`);
|
|
214
|
+
}
|
|
277
215
|
return targetedChildren[0];
|
|
278
216
|
}
|
|
279
217
|
|
|
@@ -287,24 +225,53 @@ class LoadingBackdrop extends PropertyRequiredMixin(LocalizeCoreElement(LitEleme
|
|
|
287
225
|
this._state = 'hidden';
|
|
288
226
|
|
|
289
227
|
const containingBlock = this.#getBackdropTarget();
|
|
290
|
-
|
|
291
228
|
if (containingBlock.dataset.initiallyInert !== '1') containingBlock.removeAttribute('inert');
|
|
292
229
|
}
|
|
293
230
|
|
|
294
|
-
#
|
|
295
|
-
this
|
|
231
|
+
#setLiveRegionContent(content, { delay } = {}) {
|
|
232
|
+
this.#announcementTimeoutId = setTimeout(() => this._ariaContent = content, delay || 0);
|
|
296
233
|
}
|
|
297
234
|
|
|
298
235
|
#show() {
|
|
299
236
|
this._state = reduceMotion ? 'shown' : 'showing';
|
|
300
237
|
|
|
301
238
|
const containingBlock = this.#getBackdropTarget();
|
|
302
|
-
|
|
303
239
|
if (containingBlock.getAttribute('inert') !== null) containingBlock.dataset.initiallyInert = '1';
|
|
304
|
-
|
|
305
240
|
containingBlock.setAttribute('inert', 'inert');
|
|
306
241
|
}
|
|
307
242
|
|
|
243
|
+
async #updatePosition() {
|
|
244
|
+
if (this._state === 'hidden') return;
|
|
245
|
+
|
|
246
|
+
const loadingSpinner = this.shadowRoot.querySelector('d2l-loading-spinner');
|
|
247
|
+
if (!loadingSpinner) return;
|
|
248
|
+
|
|
249
|
+
const backdropContainerRect = this.shadowRoot.querySelector('.backdrop-container').getBoundingClientRect();
|
|
250
|
+
|
|
251
|
+
// Calculate the centerpoint of the visible portion of the element
|
|
252
|
+
const upperVisibleBound = Math.max(0, backdropContainerRect.top);
|
|
253
|
+
const lowerVisibleBound = Math.min(window.innerHeight, backdropContainerRect.bottom);
|
|
254
|
+
const visibleHeight = lowerVisibleBound - upperVisibleBound;
|
|
255
|
+
const centeringOffset = (visibleHeight / 4);
|
|
256
|
+
|
|
257
|
+
// Calculate if an offset is required to move to the top of the viewport before centering
|
|
258
|
+
const topOffset = Math.max(0, -backdropContainerRect.top); // measures the distance below the top of the viewport, which is negative if the element starts above the viewport
|
|
259
|
+
|
|
260
|
+
// Adjust for the size of the spinner
|
|
261
|
+
const spinnerSizeOffset = LOADING_SPINNER_SIZE / 2;
|
|
262
|
+
|
|
263
|
+
this._loadingSpinnerTop = centeringOffset + topOffset - spinnerSizeOffset;
|
|
264
|
+
|
|
265
|
+
// Adjust for the size of the stale overlay
|
|
266
|
+
const staleOverlay = this.shadowRoot.querySelector('d2l-backdrop-stale-overlay');
|
|
267
|
+
if (staleOverlay) {
|
|
268
|
+
await this.shadowRoot.querySelector('d2l-empty-state-action-button')?.getUpdateComplete();
|
|
269
|
+
const staleOverlaySizeOffset = staleOverlay.getBoundingClientRect().height / 2;
|
|
270
|
+
|
|
271
|
+
this._staleOverlayTop = centeringOffset + topOffset - staleOverlaySizeOffset;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
308
275
|
}
|
|
309
276
|
|
|
310
|
-
customElements.define('d2l-backdrop-loading',
|
|
277
|
+
customElements.define('d2l-backdrop-loading', BackdropLoading);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import '../button/button-subtle.js';
|
|
2
|
+
import { css, html, LitElement } from 'lit';
|
|
3
|
+
import { bodyCompactStyles } from '../typography/styles.js';
|
|
4
|
+
import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A component to render as an overlay over another element with stale data.
|
|
8
|
+
*/
|
|
9
|
+
class BackdropStaleOverlay extends LocalizeCoreElement(LitElement) {
|
|
10
|
+
|
|
11
|
+
static properties = {
|
|
12
|
+
/**
|
|
13
|
+
* The action button text
|
|
14
|
+
* @type {string}
|
|
15
|
+
*/
|
|
16
|
+
buttonText: { type: String, attribute: 'button-text' },
|
|
17
|
+
/**
|
|
18
|
+
* The text displayed on the overlay
|
|
19
|
+
* @type {string}
|
|
20
|
+
*/
|
|
21
|
+
text: { type: String }
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
static styles = [bodyCompactStyles, css`
|
|
25
|
+
:host {
|
|
26
|
+
align-items: center;
|
|
27
|
+
border: 1px solid var(--d2l-color-mica);
|
|
28
|
+
border-radius: 0.3rem;
|
|
29
|
+
column-gap: 0.75rem;
|
|
30
|
+
display: flex;
|
|
31
|
+
flex-wrap: wrap;
|
|
32
|
+
padding: 1.2rem 1.5rem;
|
|
33
|
+
}
|
|
34
|
+
`];
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
const message = this.text || this.localize('components.backdrop-stale-overlay.message');
|
|
38
|
+
const buttonText = this.buttonText || this.localize('intl-common:actions:reload');;
|
|
39
|
+
|
|
40
|
+
return html`
|
|
41
|
+
<p class="d2l-body-compact">${message}</p>
|
|
42
|
+
<d2l-button-subtle
|
|
43
|
+
@click="${this.#handleActionClick}"
|
|
44
|
+
text="${buttonText}">
|
|
45
|
+
</d2l-button-subtle>
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#handleActionClick(e) {
|
|
50
|
+
e.stopPropagation();
|
|
51
|
+
|
|
52
|
+
/** Dispatched when the action button on the overlay is clicked */
|
|
53
|
+
this.dispatchEvent(new CustomEvent('d2l-backdrop-stale-overlay-action', { bubbles: true, composed: true }));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
customElements.define('d2l-backdrop-stale-overlay', BackdropStaleOverlay);
|
|
@@ -18,7 +18,7 @@ import '../../inputs/input-radio.js';
|
|
|
18
18
|
import '../../inputs/input-radio-group.js';
|
|
19
19
|
|
|
20
20
|
import { css, html, nothing } from 'lit';
|
|
21
|
-
import { tableStyles, TableWrapper } from '../table-wrapper.js';
|
|
21
|
+
import { tableFreshness, tableStyles, TableWrapper } from '../table-wrapper.js';
|
|
22
22
|
import { DemoPassthroughMixin } from '../../demo/demo-passthrough-mixin.js';
|
|
23
23
|
import { ifDefined } from 'lit/directives/if-defined.js';
|
|
24
24
|
|
|
@@ -83,12 +83,12 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
|
|
|
83
83
|
this._data = data();
|
|
84
84
|
this._sortField = undefined;
|
|
85
85
|
this._sortDesc = false;
|
|
86
|
-
this.
|
|
86
|
+
this.freshness = tableFreshness.fresh;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
render() {
|
|
90
90
|
return html`
|
|
91
|
-
<d2l-table-wrapper item-count="${ifDefined(this.paging ? 500 : undefined)}"
|
|
91
|
+
<d2l-table-wrapper item-count="${ifDefined(this.paging ? 500 : undefined)}" freshness-stale-text="This table data is out of date." freshness-stale-button-text="Reload Me!">
|
|
92
92
|
<d2l-table-controls slot="controls" ?no-sticky="${!this.stickyControls}" select-all-pages-allowed>
|
|
93
93
|
<d2l-selection-action
|
|
94
94
|
text="Sticky controls"
|
|
@@ -100,10 +100,10 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
|
|
|
100
100
|
icon="tier1:${this.stickyHeaders ? 'check' : 'close-default'}"
|
|
101
101
|
@d2l-selection-action-click="${this._toggleStickyHeaders}">
|
|
102
102
|
</d2l-selection-action>
|
|
103
|
-
<d2l-input-radio-group label="
|
|
104
|
-
<d2l-input-radio label="
|
|
105
|
-
<d2l-input-radio label="
|
|
106
|
-
<d2l-input-radio label="Loading" value="loading" ?checked=${this.
|
|
103
|
+
<d2l-input-radio-group label="Freshness" horizontal label-hidden name="freshness" @change=${this._handleFreshnessChange}>
|
|
104
|
+
<d2l-input-radio label="Fresh" value="${tableFreshness.fresh}" ?checked=${this.freshness === tableFreshness.fresh}></d2l-input-radio>
|
|
105
|
+
<d2l-input-radio label="Stale" value="${tableFreshness.stale}" ?checked=${this.freshness === tableFreshness.stale}></d2l-input-radio>
|
|
106
|
+
<d2l-input-radio label="Loading" value="${tableFreshness.loading}" ?checked=${this.freshness === tableFreshness.loading}></d2l-input-radio>
|
|
107
107
|
</d2l-input-radio-group>
|
|
108
108
|
</d2l-table-controls>
|
|
109
109
|
|
|
@@ -170,8 +170,8 @@ class TestTable extends DemoPassthroughMixin(TableWrapper, 'd2l-table-wrapper')
|
|
|
170
170
|
`;
|
|
171
171
|
}
|
|
172
172
|
|
|
173
|
-
|
|
174
|
-
this.
|
|
173
|
+
_handleFreshnessChange(e) {
|
|
174
|
+
this.freshness = e.detail.value;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
async _handlePagerLoadMore(e) {
|
|
@@ -2,13 +2,15 @@ import '../colors/colors.js';
|
|
|
2
2
|
import '../scroll-wrapper/scroll-wrapper.js';
|
|
3
3
|
import '../backdrop/backdrop-loading.js';
|
|
4
4
|
import { css, html, LitElement, nothing } from 'lit';
|
|
5
|
+
import { freshness, FreshnessMixin } from '../../mixins/freshness/freshness-mixin.js';
|
|
5
6
|
import { cssSizes } from '../inputs/input-checkbox-styles.js';
|
|
6
7
|
import { getComposedParent } from '../../helpers/dom.js';
|
|
7
8
|
import { isPopoverSupported } from '../popover/popover-mixin.js';
|
|
8
9
|
import { PageableMixin } from '../paging/pageable-mixin.js';
|
|
9
|
-
import { PropertyRequiredMixin } from '../../mixins/property-required/property-required-mixin.js';
|
|
10
10
|
import { SelectionMixin } from '../selection/selection-mixin.js';
|
|
11
11
|
|
|
12
|
+
export const tableFreshness = freshness;
|
|
13
|
+
|
|
12
14
|
export const tableStyles = css`
|
|
13
15
|
.d2l-table {
|
|
14
16
|
border-collapse: separate; /* needed to override reset stylesheets */
|
|
@@ -263,9 +265,24 @@ const SELECTORS = {
|
|
|
263
265
|
* @slot controls - Slot for `d2l-table-controls` to be rendered above the table
|
|
264
266
|
* @slot pager - Slot for `d2l-pager-load-more` to be rendered below the table
|
|
265
267
|
*/
|
|
266
|
-
export class TableWrapper extends
|
|
268
|
+
export class TableWrapper extends FreshnessMixin(PageableMixin(SelectionMixin(LitElement))) {
|
|
267
269
|
|
|
268
270
|
static properties = {
|
|
271
|
+
/**
|
|
272
|
+
* Todo: remove once consumers are updated to freshness. Use freshness instead.
|
|
273
|
+
* @ignore
|
|
274
|
+
*/
|
|
275
|
+
dataState: { reflect: true, type: String },
|
|
276
|
+
/**
|
|
277
|
+
* Todo: remove once consumers are updated to freshness-stale-text. Use freshness-stale-text instead.
|
|
278
|
+
* @ignore
|
|
279
|
+
*/
|
|
280
|
+
dirtyText: { reflect: true, attribute: 'dirty-text', type: String },
|
|
281
|
+
/**
|
|
282
|
+
* Todo: remove once consumers are updated to freshness-stale-button-text. Use freshness-stale-button-text instead.
|
|
283
|
+
* @ignore
|
|
284
|
+
*/
|
|
285
|
+
dirtyButtonText: { reflect: true, attribute: 'dirty-button-text', type: String },
|
|
269
286
|
/**
|
|
270
287
|
* Hides the column borders on "default" table type
|
|
271
288
|
* @type {boolean}
|
|
@@ -307,40 +324,6 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
|
|
|
307
324
|
attribute: '_no-scroll-width',
|
|
308
325
|
reflect: true,
|
|
309
326
|
type: Boolean,
|
|
310
|
-
},
|
|
311
|
-
/**
|
|
312
|
-
* 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
|
|
313
|
-
* @type {'clean'|'dirty'|'loading'}
|
|
314
|
-
*/
|
|
315
|
-
dataState: {
|
|
316
|
-
reflect: true,
|
|
317
|
-
type: String
|
|
318
|
-
},
|
|
319
|
-
/**
|
|
320
|
-
* The text displayed on the dirty state overlay when the 'dirty' dataState is set.
|
|
321
|
-
* @type {string}
|
|
322
|
-
*/
|
|
323
|
-
dirtyText: {
|
|
324
|
-
reflect: true,
|
|
325
|
-
attribute: 'dirty-text',
|
|
326
|
-
required: {
|
|
327
|
-
dependentProps: ['dataState'],
|
|
328
|
-
validator: (_value, elem, hasValue) => hasValue || elem.dataState !== 'dirty'
|
|
329
|
-
},
|
|
330
|
-
type: String
|
|
331
|
-
},
|
|
332
|
-
/**
|
|
333
|
-
* The text displayed on the button dirty state overlay when the 'dirty' dataState is set.
|
|
334
|
-
* @type {string}
|
|
335
|
-
*/
|
|
336
|
-
dirtyButtonText: {
|
|
337
|
-
reflect: true,
|
|
338
|
-
attribute: 'dirty-button-text',
|
|
339
|
-
required: {
|
|
340
|
-
dependentProps: ['dataState'],
|
|
341
|
-
validator: (_value, elem, hasValue) => hasValue || elem.dataState !== 'dirty'
|
|
342
|
-
},
|
|
343
|
-
type: String
|
|
344
327
|
}
|
|
345
328
|
};
|
|
346
329
|
|
|
@@ -409,10 +392,6 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
|
|
|
409
392
|
this._tableIntersectionObserver = null;
|
|
410
393
|
this._tableMutationObserver = null;
|
|
411
394
|
this._tableScrollers = {};
|
|
412
|
-
this.dataState = 'clean';
|
|
413
|
-
|
|
414
|
-
this.dirtyText = null;
|
|
415
|
-
this.dirtyButtonText = null;
|
|
416
395
|
}
|
|
417
396
|
|
|
418
397
|
connectedCallback() {
|
|
@@ -444,7 +423,7 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
|
|
|
444
423
|
const slot = html`
|
|
445
424
|
<div style="position:relative">
|
|
446
425
|
<slot id="table-slot" @slotchange="${this._handleSlotChange}"></slot>
|
|
447
|
-
<d2l-backdrop-loading @d2l-backdrop-
|
|
426
|
+
<d2l-backdrop-loading @d2l-backdrop-stale-overlay-action="${this._handleStaleButton}" for="table-slot" freshness="${this.freshness}" freshness-stale-text="${this.freshnessStaleText}" freshness-stale-button-text="${this.freshnessStaleButtonText}"></d2l-backdrop-loading>
|
|
448
427
|
</div>
|
|
449
428
|
`;
|
|
450
429
|
const useScrollWrapper = this.stickyHeadersScrollWrapper || !this.stickyHeaders;
|
|
@@ -469,6 +448,19 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
|
|
|
469
448
|
}
|
|
470
449
|
}
|
|
471
450
|
|
|
451
|
+
willUpdate(changedProperties) {
|
|
452
|
+
super.willUpdate(changedProperties);
|
|
453
|
+
|
|
454
|
+
// Todo: remove these once consumers are updated to freshness properties
|
|
455
|
+
if (this.dataState) {
|
|
456
|
+
if (this.dataState === 'clean') this.freshness = freshness.fresh;
|
|
457
|
+
else if (this.dataState === 'dirty') this.freshness = freshness.stale;
|
|
458
|
+
else if (this.dataState === 'loading') this.freshness = freshness.loading;
|
|
459
|
+
}
|
|
460
|
+
if (this.dirtyText) this.freshnessStaleText = this.dirtyText;
|
|
461
|
+
if (this.dirtyButtonText) this.freshnessStaleButtonText = this.dirtyButtonText;
|
|
462
|
+
}
|
|
463
|
+
|
|
472
464
|
#hasIntersected = false;
|
|
473
465
|
|
|
474
466
|
#noScrollWidthTimeout = null;
|
|
@@ -571,11 +563,6 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
|
|
|
571
563
|
this._handleControlsChange();
|
|
572
564
|
}
|
|
573
565
|
|
|
574
|
-
_handleDirtyButton() {
|
|
575
|
-
/** Dispatched when the action button on the dirty overlay is clicked */
|
|
576
|
-
this.dispatchEvent(new CustomEvent('d2l-table-dirty-button-clicked'));
|
|
577
|
-
}
|
|
578
|
-
|
|
579
566
|
_handlePopoverClose(e) {
|
|
580
567
|
this._updateStickyAncestor(e.target, false);
|
|
581
568
|
}
|
|
@@ -630,6 +617,14 @@ export class TableWrapper extends PropertyRequiredMixin(PageableMixin(SelectionM
|
|
|
630
617
|
this._handleTableChange();
|
|
631
618
|
}
|
|
632
619
|
|
|
620
|
+
_handleStaleButton() {
|
|
621
|
+
/** Dispatched when the action button on the stale overlay is clicked */
|
|
622
|
+
this.dispatchEvent(new CustomEvent('d2l-table-stale-button-click'));
|
|
623
|
+
|
|
624
|
+
/** @ignore Todo: remove once consumers are updated to d2l-table-stale-button-click */
|
|
625
|
+
this.dispatchEvent(new CustomEvent('d2l-table-dirty-button-clicked'));
|
|
626
|
+
}
|
|
627
|
+
|
|
633
628
|
async _handleTableChange(mutationRecords) {
|
|
634
629
|
const updateList = [];
|
|
635
630
|
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import '../colors/colors.js';
|
|
2
2
|
import { css, html } from 'lit';
|
|
3
|
-
import { getFlag } from '../../helpers/flags.js';
|
|
4
3
|
import { SkeletonMixin } from '../skeleton/skeleton-mixin.js';
|
|
5
4
|
|
|
6
5
|
const keyCodes = {
|
|
@@ -38,8 +37,7 @@ export const TabMixin = superclass => class extends SkeletonMixin(superclass) {
|
|
|
38
37
|
:host {
|
|
39
38
|
box-sizing: border-box;
|
|
40
39
|
display: inline-block;
|
|
41
|
-
|
|
42
|
-
max-width: var(--d2l-gaud-9963-tab-max-width, 200px);
|
|
40
|
+
max-width: min(20rem, max(33%, 10rem));
|
|
43
41
|
outline: none;
|
|
44
42
|
position: relative;
|
|
45
43
|
vertical-align: middle;
|
|
@@ -113,13 +111,6 @@ export const TabMixin = superclass => class extends SkeletonMixin(superclass) {
|
|
|
113
111
|
this.addEventListener('keyup', this.#handleKeyup);
|
|
114
112
|
|
|
115
113
|
this.#hasInitialized = true;
|
|
116
|
-
/**
|
|
117
|
-
* TODO: remove this whole if when removing the GAUD-9963-dropdown-tabs-not-resizing flag
|
|
118
|
-
* keep the min max code an place that into the styles above
|
|
119
|
-
*/
|
|
120
|
-
if (getFlag('GAUD-9963-dropdown-tabs-not-resizing', true)) {
|
|
121
|
-
this.style.setProperty('--d2l-gaud-9963-tab-max-width', 'min(20rem, max(33%, 10rem))');
|
|
122
|
-
}
|
|
123
114
|
}
|
|
124
115
|
|
|
125
116
|
render() {
|
package/components/tabs/tabs.js
CHANGED
|
@@ -426,7 +426,6 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
|
|
|
426
426
|
return (Object.keys(this._tabIds).length > 1 && !reduceMotion) ? this._animateTabRemoval(tab) : Promise.resolve();
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
#GAUD_9963_FLAG = getFlag('GAUD-9963-dropdown-tabs-not-resizing', true);
|
|
430
429
|
#checkTabPanelMatchRequested;
|
|
431
430
|
#newTabsPanelStructure = getUseNewTabsStructureFlag();
|
|
432
431
|
#panels;
|
|
@@ -897,7 +896,7 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(SkeletonMixin(LitElement))
|
|
|
897
896
|
|
|
898
897
|
if (selectedTab) {
|
|
899
898
|
Promise.all(animPromises).then(async() => {
|
|
900
|
-
|
|
899
|
+
await new Promise(resolve => requestAnimationFrame(resolve));
|
|
901
900
|
this._updateMeasures();
|
|
902
901
|
this._updateScrollPosition(selectedTab);
|
|
903
902
|
});
|