@brightspace-ui/core 2.137.3 → 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/filter/README.md +31 -0
- package/components/filter/filter-dimension-set.js +6 -6
- 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 +207 -216
- package/package.json +1 -1
|
@@ -189,6 +189,7 @@ The `d2l-filter-dimension-set` component is the main dimension type that will wo
|
|
|
189
189
|
|
|
190
190
|
| Property | Type | Description |
|
|
191
191
|
|---|---|---|
|
|
192
|
+
| `has-more` | Boolean | Whether the dimension has more values to load. Must be used with selected-first and manual search-type. |
|
|
192
193
|
| `header-text` | String | A heading displayed above the list items. This is usually unnecessary, but can be used to emphasize or promote something specific about the list of items to help orient users. |
|
|
193
194
|
| `introductory-text` | String | The introductory text to display at the top of the filter dropdown |
|
|
194
195
|
| `key` | String, required | Unique identifier for the dimension |
|
|
@@ -232,6 +233,36 @@ This component is built to be used alongside the [d2l-filter-dimension-set](#d2l
|
|
|
232
233
|
| `selected` | Boolean, default: `false` | Whether the value in the filter is selected or not |
|
|
233
234
|
<!-- docs: end hidden content -->
|
|
234
235
|
|
|
236
|
+
## Search and Paging
|
|
237
|
+
|
|
238
|
+
Most filters will not need search or paging features since filter value lists are generally short. For longer lists of filter values when Search is necessary, it can be enabled by setting search-type to `automatic` or `manual`.
|
|
239
|
+
|
|
240
|
+
`automatic` search runs a basic case-insensitive text comparison on the dimension values that are loaded in the browser, having no awareness of server-side values that are not yet loaded.
|
|
241
|
+
|
|
242
|
+
`manual` search dispatches a `d2l-filter-dimension-search` event delegating the search to the component's consumer. The event's detail will contain the key of the dimension from where the event was dispatched (`key`), the text value used for the search (`value`) and a callback (`searchCompleteCallback`). This callback gives the consumer control of which keys to display, either by setting `displayAllKeys` to `true` or passing a list of the keys to display as `keysToDisplay` (all other keys will be hidden). The dimension will be in a loading state until the callback is called.
|
|
243
|
+
```js
|
|
244
|
+
e.detail.searchCompleteCallback({ keysToDisplay: keysToDisplay });
|
|
245
|
+
e.detail.searchCompleteCallback({ displayAllKeys: true });
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
As with Search, paging is often unnecessary since filter lists are generally short. For long lists of filter values, load-more paging can be enabled by setting `has-more` on a dimension set, which will display a `d2l-pager-load-more` button at the end of the values. Note however that paging requires the search type to be set to `manual`. Clicking the button replaces its text with a loading spinner and dispatches a `d2l-filter-dimension-load-more` event whose detail, like the search event, contains the dimension key (`key`), active search value (`value`) and a callback (`loadMoreCompleteCallback`) that works just like `searchCompleteCallback` described above. The pager will also be in a loading state until the callback is called.
|
|
249
|
+
```js
|
|
250
|
+
e.detail.loadMoreCompleteCallback({ keysToDisplay: keysToDisplay });
|
|
251
|
+
e.detail.loadMoreCompleteCallback({ displayAllKeys: true });
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Selection and manual search/paging
|
|
255
|
+
|
|
256
|
+
The filter component depends entirely on the consumer to include the selected filter values in order for the selected counts and `d2l-filter-tags` to display the correct values. Ideally, all values should be loaded into the dimensions and the event callbacks should be leveraged to set the visibility on those values. However, in the cases where this is not possible and new values are being added/removed manually from the dimension, then selection should be persisted. This means that selected items should always be loaded and included in the dimension and they should not be removed in order to maintain the functionality of counts and filter tags.
|
|
257
|
+
<!-- docs: demo -->
|
|
258
|
+
```html
|
|
259
|
+
<script type="module">
|
|
260
|
+
import '@brightspace-ui/core/components/filter/demo/filter-load-more-demo.js'
|
|
261
|
+
</script>
|
|
262
|
+
<d2l-filter-load-more-demo>
|
|
263
|
+
</d2l-filter-load-more-demo>
|
|
264
|
+
```
|
|
265
|
+
|
|
235
266
|
## Counts
|
|
236
267
|
|
|
237
268
|
The `count` property displays a count next to each filter value to indicate the number of results a value will yield. This helps users more effectively explore data and make selections, so it’s a good idea to provide these counts if it can be done performantly.
|
|
@@ -11,6 +11,11 @@ class FilterDimensionSet extends LitElement {
|
|
|
11
11
|
|
|
12
12
|
static get properties() {
|
|
13
13
|
return {
|
|
14
|
+
/**
|
|
15
|
+
* Whether the dimension has more values to load. Manual search and selected first should be set if has more is being used
|
|
16
|
+
* @type {boolean}
|
|
17
|
+
*/
|
|
18
|
+
hasMore: { type: Boolean, attribute: 'has-more' },
|
|
14
19
|
/**
|
|
15
20
|
* A heading displayed above the list items. This is usually unnecessary, but can be used to emphasize or promote something specific about the list of items to help orient users.
|
|
16
21
|
* @type {string}
|
|
@@ -31,11 +36,6 @@ class FilterDimensionSet extends LitElement {
|
|
|
31
36
|
* @type {boolean}
|
|
32
37
|
*/
|
|
33
38
|
loading: { type: Boolean },
|
|
34
|
-
/**
|
|
35
|
-
* Whether the dimension has more values to load
|
|
36
|
-
* @type {boolean}
|
|
37
|
-
*/
|
|
38
|
-
hasMore: { type: Boolean, attribute: 'has-more' },
|
|
39
39
|
/**
|
|
40
40
|
* Whether to hide the search input, perform a simple text search, or fire an event on search
|
|
41
41
|
* @type {'none'|'automatic'|'manual'}
|
|
@@ -47,7 +47,7 @@ class FilterDimensionSet extends LitElement {
|
|
|
47
47
|
*/
|
|
48
48
|
selectAll: { type: Boolean, attribute: 'select-all' },
|
|
49
49
|
/**
|
|
50
|
-
* Whether to render the selected items at the top of the filter
|
|
50
|
+
* Whether to render the selected items at the top of the filter. Forced on if load more paging is being used
|
|
51
51
|
* @type {boolean}
|
|
52
52
|
*/
|
|
53
53
|
selectedFirst: { type: Boolean, attribute: 'selected-first' },
|
|
@@ -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() {
|