@brightspace-ui/core 2.137.4 → 2.138.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/skeleton/README.md +18 -7
- package/components/skeleton/demo/skeleton-group-nested-test.js +71 -0
- package/components/skeleton/demo/skeleton-group-test-wrapper.js +9 -0
- package/components/skeleton/demo/skeleton-group-test.js +91 -0
- package/components/skeleton/demo/skeleton-mixin.html +11 -0
- package/components/skeleton/demo/skeleton-test-container.js +3 -3
- package/components/skeleton/skeleton-group-mixin.js +49 -0
- package/components/skeleton/skeleton-mixin.js +50 -3
- package/controllers/subscriber/subscriberControllers.js +5 -1
- package/custom-elements.json +203 -212
- package/package.json +1 -1
|
@@ -12,13 +12,7 @@ For example, this causes a text input to be skeletized:
|
|
|
12
12
|
<d2l-input-text label="Name" skeleton></d2l-input-text>
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
```html
|
|
18
|
-
<d2l-input-text label="Name" ?skeleton="${this.skeleton}"></d2l-text-input>
|
|
19
|
-
<d2l-input-date label="Due Date" ?skeleton="${this.skeleton}"></d2l-input-date>
|
|
20
|
-
<my-element ?skeleton="${this.skeleton}"></my-element>
|
|
21
|
-
```
|
|
15
|
+
A parent component could contain several skeleton-aware components. In this case, the parent would extend [`SkeletonGroupMixin`](#skeleton-groups), which would automatically handle the skeleton state of its contents.
|
|
22
16
|
|
|
23
17
|
## Skeletizing Custom Elements with SkeletonMixin
|
|
24
18
|
|
|
@@ -133,6 +127,23 @@ For example:
|
|
|
133
127
|
|
|
134
128
|
When skeletized, this heading will take up `45%` of the available width.
|
|
135
129
|
|
|
130
|
+
## Skeleton groups
|
|
131
|
+
Skeleton groups can be used to ensure a collection of components all appear at the same time. This can be used to prevent individual components from popping in before everything has loaded.
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
import { SkeletonGroupMixin } from '@brightspace-ui/core/skeleton/skeleton-group-mixin.js';
|
|
135
|
+
|
|
136
|
+
class MyElement extends SkeletonGroupMixin(LitElement) {
|
|
137
|
+
|
|
138
|
+
render() {
|
|
139
|
+
return html`
|
|
140
|
+
// Anything that can be skeletonized.
|
|
141
|
+
// All components will remain in skeleton state until they have all loaded
|
|
142
|
+
`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
136
147
|
## Future Enhancements
|
|
137
148
|
|
|
138
149
|
Looking for an enhancement not listed here? Is there a core component that should support skeletons but doesn't yet? Create a GitHub issue!
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import '../../switch/switch.js';
|
|
2
|
+
import './skeleton-group-test-wrapper.js';
|
|
3
|
+
import './skeleton-test-box.js';
|
|
4
|
+
import './skeleton-test-container.js';
|
|
5
|
+
import './skeleton-test-heading.js';
|
|
6
|
+
|
|
7
|
+
import { css, html, LitElement } from 'lit';
|
|
8
|
+
import { SkeletonGroupMixin } from '../skeleton-group-mixin.js';
|
|
9
|
+
|
|
10
|
+
class SkeletonTestNestedGroup extends SkeletonGroupMixin(LitElement) {
|
|
11
|
+
static get properties() {
|
|
12
|
+
return {
|
|
13
|
+
_skeletonParent: { state: true },
|
|
14
|
+
_skeletonContainer: { state: true },
|
|
15
|
+
_skeletonHeading: { state: true },
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
static get styles() {
|
|
19
|
+
return css`
|
|
20
|
+
.controls {
|
|
21
|
+
align-items: center;
|
|
22
|
+
display: flex;
|
|
23
|
+
gap: 0.6rem;
|
|
24
|
+
margin-bottom: 0.6rem;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
this._skeletonParent = false;
|
|
32
|
+
this._skeletonContainer = false;
|
|
33
|
+
this._skeletonHeading = false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
render() {
|
|
37
|
+
return html`
|
|
38
|
+
<div class="controls">
|
|
39
|
+
<d2l-switch @click="${this._loadGroup}" ?on="${this._skeletonSetExplicitly}" text="parent skeleton"></d2l-switch>
|
|
40
|
+
<d2l-switch @click="${this._loadList}" ?on="${this._skeletonContainer}" text="container skeleton"></d2l-switch>
|
|
41
|
+
<d2l-switch @click="${this._loadInput}" ?on="${this._skeletonHeading}" text="heading skeleton"></d2l-switch>
|
|
42
|
+
</div>
|
|
43
|
+
<d2l-skeleton-group-test-wrapper>
|
|
44
|
+
<d2l-test-skeleton-heading level="1">Heading 1</d2l-test-skeleton-heading>
|
|
45
|
+
<d2l-skeleton-group-test-wrapper ?skeleton="${this._skeletonContainer}">
|
|
46
|
+
<d2l-test-skeleton-heading level="3" ?skeleton="${this._skeletonHeading}">Inner heading</d2l-test-skeleton-heading>
|
|
47
|
+
<d2l-test-skeleton-box></d2l-test-skeleton-box>
|
|
48
|
+
</d2l-skeleton-group-test-wrapper>
|
|
49
|
+
<d2l-skeleton-group-test-wrapper>
|
|
50
|
+
<d2l-test-skeleton-heading level="3">Heading 3</d2l-test-skeleton-heading>
|
|
51
|
+
<d2l-test-skeleton-container></d2l-test-skeleton-container>
|
|
52
|
+
</d2l-skeleton-group-test-wrapper>
|
|
53
|
+
</d2l-skeleton-group-test-wrapper>
|
|
54
|
+
`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_loadGroup() {
|
|
58
|
+
this._skeletonParent = !this._skeletonParent;
|
|
59
|
+
this.skeleton = this._skeletonParent;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
_loadInput() {
|
|
63
|
+
this._skeletonHeading = !this._skeletonHeading;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_loadList() {
|
|
67
|
+
this._skeletonContainer = !this._skeletonContainer;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
customElements.define('d2l-test-nested-skeleton-group', SkeletonTestNestedGroup);
|
|
71
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { html, LitElement } from 'lit';
|
|
2
|
+
import { SkeletonGroupMixin } from '../skeleton-group-mixin.js';
|
|
3
|
+
|
|
4
|
+
class SkeletonGroupTestWrapper extends SkeletonGroupMixin(LitElement) {
|
|
5
|
+
render() {
|
|
6
|
+
return html`<slot></slot>`;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
customElements.define('d2l-skeleton-group-test-wrapper', SkeletonGroupTestWrapper);
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import '../../switch/switch.js';
|
|
2
|
+
import '../../button/button-subtle.js';
|
|
3
|
+
import './skeleton-test-container.js';
|
|
4
|
+
import { css, html, LitElement } from 'lit';
|
|
5
|
+
import { SkeletonGroupMixin } from '../skeleton-group-mixin.js';
|
|
6
|
+
|
|
7
|
+
class SkeletonTestGroup extends LitElement {
|
|
8
|
+
static get properties() {
|
|
9
|
+
return {
|
|
10
|
+
_items: { state: true },
|
|
11
|
+
_loadAsGroup: { state: true },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
static get styles() {
|
|
15
|
+
return css`
|
|
16
|
+
.controls {
|
|
17
|
+
align-items: center;
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: 0.6rem;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
margin-bottom: 0.6rem;
|
|
22
|
+
}
|
|
23
|
+
d2l-test-skeleton-container {
|
|
24
|
+
margin-bottom: 0.6rem;
|
|
25
|
+
}
|
|
26
|
+
`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
constructor() {
|
|
30
|
+
super();
|
|
31
|
+
this._items = [1, 2, 3];
|
|
32
|
+
this._loadAsGroup = true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
render() {
|
|
36
|
+
return html`
|
|
37
|
+
<div class="controls">
|
|
38
|
+
<div>
|
|
39
|
+
<d2l-button-subtle @click="${this._loadItems}" text="Load items" icon="tier1:download"></d2l-button-subtle>
|
|
40
|
+
<d2l-button-subtle @click="${this._addItem}" text="Add item" icon="tier1:add"></d2l-button-subtle>
|
|
41
|
+
<d2l-button-subtle @click="${this._removeItem}" text="Remove item" icon="tier1:delete"></d2l-button-subtle>
|
|
42
|
+
</div>
|
|
43
|
+
<d2l-switch @click="${this._toggleLoadType}" text="Wait for all elements to load" ?on="${this._loadAsGroup}"></d2l-switch>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
${this._loadAsGroup ? this._renderGroup() : this._renderIndividual() }
|
|
47
|
+
`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_addItem() {
|
|
51
|
+
this._items.push((this._items.length + 1));
|
|
52
|
+
this.requestUpdate();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_loadItems() {
|
|
56
|
+
this._items.forEach(id => {
|
|
57
|
+
const item = this.shadowRoot.getElementById(id);
|
|
58
|
+
item.skeleton = true;
|
|
59
|
+
setTimeout(() => item.skeleton = false, Math.random() * 2000);
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
_removeItem() {
|
|
64
|
+
this._items.pop();
|
|
65
|
+
this.requestUpdate();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_renderContents() {
|
|
69
|
+
return html`
|
|
70
|
+
${this._items.map(item => html`<d2l-test-skeleton-container skeleton id="${item}"></d2l-test-skeleton-container>`)}
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_renderGroup() {
|
|
75
|
+
return html`<d2l-test-skeleton-group-on>${this._renderContents()}</d2l-test-skeleton-group-on>`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
_renderIndividual() {
|
|
79
|
+
return html`<div class="panels">${this._renderContents()}</div>`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
_toggleLoadType() {
|
|
83
|
+
this._loadAsGroup = !this._loadAsGroup;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
customElements.define('d2l-test-skeleton-group', SkeletonTestGroup);
|
|
87
|
+
|
|
88
|
+
class SkeletonTestGroupOn extends SkeletonGroupMixin(LitElement) {
|
|
89
|
+
render() { return html`<slot></slot>`; }
|
|
90
|
+
}
|
|
91
|
+
customElements.define('d2l-test-skeleton-group-on', SkeletonTestGroupOn);
|
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
import './skeleton-test-heading.js';
|
|
12
12
|
import './skeleton-test-link.js';
|
|
13
13
|
import './skeleton-test-paragraph.js';
|
|
14
|
+
import './skeleton-group-nested-test.js';
|
|
15
|
+
import './skeleton-group-test.js';
|
|
14
16
|
</script>
|
|
15
17
|
</head>
|
|
16
18
|
<body unresolved>
|
|
@@ -62,6 +64,15 @@
|
|
|
62
64
|
<d2l-test-skeleton-container></d2l-test-skeleton-container>
|
|
63
65
|
</d2l-demo-snippet>
|
|
64
66
|
|
|
67
|
+
<h2>Skeleton group</h2>
|
|
68
|
+
<d2l-demo-snippet>
|
|
69
|
+
<d2l-test-skeleton-group></d2l-test-skeleton-group>
|
|
70
|
+
</d2l-demo-snippet>
|
|
71
|
+
|
|
72
|
+
<h2>Nested skeleton groups</h2>
|
|
73
|
+
<d2l-demo-snippet>
|
|
74
|
+
<d2l-test-nested-skeleton-group></d2l-test-nested-skeleton-group>
|
|
75
|
+
</d2l-demo-snippet>
|
|
65
76
|
</d2l-demo-page>
|
|
66
77
|
</body>
|
|
67
78
|
</html>
|
|
@@ -2,9 +2,9 @@ import '../../colors/colors.js';
|
|
|
2
2
|
import '../../inputs/input-checkbox.js';
|
|
3
3
|
import { css, html, LitElement } from 'lit';
|
|
4
4
|
import { bodyCompactStyles } from '../../typography/styles.js';
|
|
5
|
-
import {
|
|
5
|
+
import { SkeletonGroupMixin } from '../skeleton-group-mixin.js';
|
|
6
6
|
|
|
7
|
-
export class SkeletonTestContainer extends
|
|
7
|
+
export class SkeletonTestContainer extends SkeletonGroupMixin(LitElement) {
|
|
8
8
|
|
|
9
9
|
static get styles() {
|
|
10
10
|
return [
|
|
@@ -36,7 +36,7 @@ export class SkeletonTestContainer extends SkeletonMixin(LitElement) {
|
|
|
36
36
|
<div class="d2l-demo-box d2l-skeletize-container">
|
|
37
37
|
<div class="d2l-skeletize">Container with Skeletons Inside</div>
|
|
38
38
|
<span class="d2l-body-compact">No skeleton</span>
|
|
39
|
-
<d2l-input-checkbox checked
|
|
39
|
+
<d2l-input-checkbox checked>Skeleton</d2l-input-checkbox>
|
|
40
40
|
</div>
|
|
41
41
|
`;
|
|
42
42
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|
2
|
+
import { SkeletonMixin } from './skeleton-mixin.js';
|
|
3
|
+
import { SubscriberRegistryController } from '../../controllers/subscriber/subscriberControllers.js';
|
|
4
|
+
|
|
5
|
+
export const SkeletonGroupMixin = dedupeMixin(superclass => class extends SkeletonMixin(superclass) {
|
|
6
|
+
|
|
7
|
+
static get properties() {
|
|
8
|
+
return {
|
|
9
|
+
_anySubscribersWithSkeletonActive : { state: true },
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
super();
|
|
15
|
+
this._anySubscribersWithSkeletonActive = false;
|
|
16
|
+
this._skeletonSubscribers = new SubscriberRegistryController(this, 'skeleton', {
|
|
17
|
+
onSubscribe: this.onSubscriberChange.bind(this),
|
|
18
|
+
onUnsubscribe: this.onSubscriberChange.bind(this),
|
|
19
|
+
updateSubscribers: this._checkSubscribersSkeletonState.bind(this),
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
updated(changedProperties) {
|
|
24
|
+
super.updated(changedProperties);
|
|
25
|
+
if (changedProperties.has('skeleton')) {
|
|
26
|
+
this._skeletonSubscribers.updateSubscribers();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
onSubscriberChange() {
|
|
31
|
+
this._skeletonSubscribers.updateSubscribers();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_checkSubscribersSkeletonState(subscribers) {
|
|
35
|
+
this._anySubscribersWithSkeletonActive = [...subscribers.values()].some(subscriber => (
|
|
36
|
+
subscriber._skeletonSetExplicitly || subscriber._anySubscribersWithSkeletonActive
|
|
37
|
+
));
|
|
38
|
+
|
|
39
|
+
this.setSkeletonActive(this._skeletonSetExplicitly || this._anySubscribersWithSkeletonActive || this._skeletonSetByParent);
|
|
40
|
+
|
|
41
|
+
subscribers.forEach(subscriber => {
|
|
42
|
+
subscriber.setSkeletonActive(this._skeletonActive);
|
|
43
|
+
subscriber.setSkeletonSetByParent(this._skeletonActive && !subscriber._skeletonSetExplicitly);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this._parentSkeleton?.registry?.onSubscriberChange();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import '../colors/colors.js';
|
|
2
2
|
import { css } from 'lit';
|
|
3
3
|
import { dedupeMixin } from '@open-wc/dedupe-mixin';
|
|
4
|
+
import { EventSubscriberController } from '../../controllers/subscriber/subscriberControllers.js';
|
|
4
5
|
import { RtlMixin } from '../../mixins/rtl/rtl-mixin.js';
|
|
5
6
|
|
|
6
7
|
// DE50056: starting in Safari 16, the pulsing animation causes FACE
|
|
@@ -152,10 +153,10 @@ export const SkeletonMixin = dedupeMixin(superclass => class extends RtlMixin(su
|
|
|
152
153
|
static get properties() {
|
|
153
154
|
return {
|
|
154
155
|
/**
|
|
155
|
-
*
|
|
156
|
+
* Render the component as a [skeleton loader](https://github.com/BrightspaceUI/core/tree/main/components/skeleton).
|
|
156
157
|
* @type {boolean}
|
|
157
158
|
*/
|
|
158
|
-
skeleton: { reflect: true, type: Boolean
|
|
159
|
+
skeleton: { reflect: true, type: Boolean },
|
|
159
160
|
};
|
|
160
161
|
}
|
|
161
162
|
|
|
@@ -167,7 +168,53 @@ export const SkeletonMixin = dedupeMixin(superclass => class extends RtlMixin(su
|
|
|
167
168
|
|
|
168
169
|
constructor() {
|
|
169
170
|
super();
|
|
170
|
-
this.
|
|
171
|
+
this._skeletonSetByParent = false;
|
|
172
|
+
this._skeletonSetExplicitly = false;
|
|
173
|
+
this._skeletonActive = false;
|
|
174
|
+
this._skeletonWait = false;
|
|
175
|
+
|
|
176
|
+
this._parentSkeleton = new EventSubscriberController(this, 'skeleton', {
|
|
177
|
+
onSubscribe: this._onSubscribe.bind(this),
|
|
178
|
+
onUnsubscribe: this._onUnsubscribe.bind(this)
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
get skeleton() {
|
|
183
|
+
return this._skeletonActive;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
set skeleton(val) {
|
|
187
|
+
const oldVal = this._skeletonSetExplicitly;
|
|
188
|
+
if (oldVal === val) return;
|
|
189
|
+
this._skeletonSetExplicitly = val;
|
|
190
|
+
|
|
191
|
+
// keep _skeletonActive aligned with _skeletonSetExplicitly. _skeletonActive may be modified separately by a parent SkeletonGroup
|
|
192
|
+
this._skeletonActive = val;
|
|
193
|
+
|
|
194
|
+
this.requestUpdate('skeleton', oldVal);
|
|
195
|
+
this._parentSkeleton?.registry?.onSubscriberChange();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
setSkeletonActive(skeletonActive) {
|
|
199
|
+
const oldVal = this._skeletonActive;
|
|
200
|
+
if (skeletonActive !== oldVal) {
|
|
201
|
+
this._skeletonActive = skeletonActive;
|
|
202
|
+
this.requestUpdate('skeleton', oldVal);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
setSkeletonSetByParent(skeletonSetByParent) {
|
|
207
|
+
this._skeletonSetByParent = skeletonSetByParent;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
_onSubscribe() {
|
|
211
|
+
this._skeletonWait = true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_onUnsubscribe() {
|
|
215
|
+
this._skeletonWait = false;
|
|
216
|
+
this._skeletonActive = this._skeletonSetExplicitly;
|
|
217
|
+
this.requestUpdate('skeleton', this._skeletonSetExplicitly);
|
|
171
218
|
}
|
|
172
219
|
|
|
173
220
|
});
|
|
@@ -115,6 +115,8 @@ export class SubscriberRegistryController extends BaseController {
|
|
|
115
115
|
}
|
|
116
116
|
|
|
117
117
|
_handleSubscribe(e) {
|
|
118
|
+
if (e.detail.subscriber === this._host) { return; }
|
|
119
|
+
|
|
118
120
|
e.stopPropagation();
|
|
119
121
|
e.detail.registry = this._host;
|
|
120
122
|
e.detail.registryController = this;
|
|
@@ -137,7 +139,9 @@ export class EventSubscriberController extends BaseSubscriber {
|
|
|
137
139
|
}
|
|
138
140
|
|
|
139
141
|
hostConnected() {
|
|
140
|
-
|
|
142
|
+
requestAnimationFrame(() => {
|
|
143
|
+
this._subscriptionComplete = this._keepTrying(() => this._subscribe(), 40, 400);
|
|
144
|
+
});
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
hostDisconnected() {
|