@brightspace-ui/core 1.176.4 → 1.178.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.
@@ -24,6 +24,21 @@ The `d2l-count-badge` element is a web component to display a number count, eith
24
24
  | `type`, default: `count` | String | The type of the badge. Valid options are `"notification"` and `"count"`. Notification badges are orange and count badges are grey. |
25
25
  | `max-digits`, default: `2` when `type="notification"` | Number | Optionally specify a digit limit, after which numbers are truncated. Defaults to two for `"notification"` type and no limit for `"count"` type.
26
26
  | `hide-zero`, default: `false` | Boolean | Optionally choose not to show the count badge when the number is zero. |
27
+ | `text`, required | String | Descriptive text for the badge which will act as an accessible label and tooltip text when tooltips are enabled. |
28
+ | `tab-stop`, default: `false` | Boolean | Optionally choose to make the badge a tab stop. |
29
+ | `announce-changes`, default: `false` | Boolean | Optionally choose to announce changes to the badge with an aria-live region. If the text property is changed, the text will be read by screen-readers. |
30
+ | `has-tooltip`, default: `false` | Boolean | Optionally choose to have a tooltip below the badge. |
31
+
32
+ ### Accessibility Properties
33
+
34
+ To make your `d2l-count-badge` accessible, use the following properties when applicable:
35
+
36
+ | Attribute | Description |
37
+ |--|--|
38
+ | `text`, required | Only the text will be read by screen-readers (not the number), so include the number in the text. |
39
+ | `tab-stop` | A tab stop allows screen-reader users to easily tab to the badge. Otherwise, screen-reader users will need to arrow through to the badge. |
40
+ | `announce-changes` | Use "announce-changes" if screen-reader users should be notified that the badge has been updated, such as a new notification. The "text" property will be read as soon as the screen-reader is idle. |
41
+ | `has-tooltip` | The tooltip will be visible on hover/tab-stop, and read out by screen-readers. |
27
42
 
28
43
  ## Future Enhancements
29
44
 
@@ -1,18 +1,53 @@
1
1
  import '../colors/colors.js';
2
+ import '../tooltip/tooltip.js';
2
3
  import { css, html, LitElement } from 'lit-element/lit-element.js';
4
+ import { getUniqueId } from '../../helpers/uniqueId.js';
5
+ import { ifDefined } from 'lit-html/directives/if-defined.js';
6
+ import { offscreenStyles } from '../offscreen/offscreen.js';
3
7
  import { RtlMixin } from '../../mixins/rtl-mixin.js';
4
8
 
5
9
  class CountBadge extends RtlMixin(LitElement) {
6
10
 
7
11
  static get properties() {
8
12
  return {
13
+ /**
14
+ * Optionally choose to announce changes to the badge with an aria-live region. If the number property is changed, the text will be read by screenreaders. Defaults to false.
15
+ * @type {boolean}
16
+ */
17
+ announceChanges: {
18
+ type: Boolean,
19
+ attribute: 'announce-changes'
20
+ },
21
+ /**
22
+ * Optionally add a tooltip on the badge. Defaults to false.
23
+ * @type {boolean}
24
+ */
25
+ hasTooltip: {
26
+ type: Boolean,
27
+ attribute: 'has-tooltip'
28
+ },
29
+ /**
30
+ * Optionally choose to not render the count badge when the number is zero. Defaults to false.
31
+ * @type {boolean}
32
+ */
33
+ hideZero: {
34
+ type: Boolean,
35
+ attribute: 'hide-zero'
36
+ },
37
+ /**
38
+ * Optionally specify a digit limit, after which numbers are truncated. Defaults to two for "notification" type and no limit for "count" type.
39
+ * @type {number}
40
+ */
41
+ maxDigits: {
42
+ type: Number,
43
+ attribute: 'max-digits'
44
+ },
9
45
  /**
10
46
  * The number to be displayed on the badge. Must be a positive integer.
11
47
  * @type {number}
12
48
  */
13
49
  number: {
14
50
  type: Number,
15
- reflect: true,
16
51
  attribute: 'number'
17
52
  },
18
53
  /**
@@ -25,37 +60,34 @@ class CountBadge extends RtlMixin(LitElement) {
25
60
  attribute: 'size'
26
61
  },
27
62
  /**
28
- * The type of the badge. Defaults to "count".
29
- * @type {'count'|'notification'}
63
+ * Optionally choose to add a tab stop to the badge. Defaults to false.
64
+ * @type {boolean}
30
65
  */
31
- type: {
32
- type: String,
33
- reflect: true,
34
- attribute: 'type'
66
+ tabStop: {
67
+ type: Boolean,
68
+ attribute: 'tab-stop'
35
69
  },
36
70
  /**
37
- * Optionally specify a digit limit, after which numbers are truncated. Defaults to two for "notification" type and no limit for "count" type.
38
- * @type {number}
71
+ * * Descriptive text for the badge which will act as an accessible label and tooltip text when tooltips are enabled.
72
+ * @type {string}
39
73
  */
40
- maxDigits: {
41
- type: Number,
42
- reflect: true,
43
- attribute: 'max-digits'
74
+ text: {
75
+ type: String
44
76
  },
45
77
  /**
46
- * Optionally choose to not render the count badge when the number is zero. Defaults to false.
47
- * @type {boolean}
78
+ * The type of the badge. Defaults to "count".
79
+ * @type {'count'|'notification'}
48
80
  */
49
- hideZero: {
50
- type: Boolean,
81
+ type: {
82
+ type: String,
51
83
  reflect: true,
52
- attribute: 'hide-zero'
53
- },
84
+ attribute: 'type'
85
+ }
54
86
  };
55
87
  }
56
88
 
57
89
  static get styles() {
58
- return [ css`
90
+ return [offscreenStyles, css`
59
91
  :host([hidden]) {
60
92
  display: none;
61
93
  }
@@ -112,9 +144,16 @@ class CountBadge extends RtlMixin(LitElement) {
112
144
 
113
145
  constructor() {
114
146
  super();
115
- this.type = 'count';
116
- this.size = 'small';
147
+ this.announceChanges = false;
148
+ this.hasTooltip = false;
117
149
  this.hideZero = false;
150
+ this.size = 'small';
151
+ this.tabStop = false;
152
+ this.text = '';
153
+ this.type = 'count';
154
+
155
+ this._badgeId = getUniqueId();
156
+ this._textId = getUniqueId();
118
157
  }
119
158
 
120
159
  connectedCallback() {
@@ -134,7 +173,17 @@ class CountBadge extends RtlMixin(LitElement) {
134
173
  numberString = `${'9'.repeat(this.maxDigits)}+`;
135
174
  }
136
175
  return html`
137
- <div class="d2l-count-badge-number">${numberString}</div>`;
176
+ <div
177
+ class="d2l-count-badge-number"
178
+ id="${this._badgeId}"
179
+ tabindex="${ifDefined(this.tabStop || this.hasTooltip ? '0' : undefined)}"
180
+ aria-labelledby="${ifDefined(this.hasTooltip ? undefined : this._textId)}">
181
+ <div aria-hidden="true">${numberString}</div>
182
+ ${this.hasTooltip ?
183
+ html`<d2l-tooltip id="${this._textId}" for="${this._badgeId}" aria-live="${this.announceChanges ? 'polite' : 'off'}">${this.text}</d2l-tooltip>`
184
+ : html`<span id="${this._textId}" class="d2l-offscreen" aria-live="${this.announceChanges ? 'polite' : 'off'}">"${this.text}"</span>`}
185
+ </div>
186
+ `;
138
187
  }
139
188
  }
140
189
 
@@ -9,6 +9,7 @@
9
9
  <script type="module">
10
10
  import '../../demo/demo-page.js';
11
11
  import '../count-badge.js';
12
+ import '../../button/button.js';
12
13
  </script>
13
14
  </head>
14
15
  <body unresolved>
@@ -17,22 +18,43 @@
17
18
  <h2>Small Notification Badge</h2>
18
19
  <d2l-demo-snippet>
19
20
  <template>
20
- <d2l-count-badge size="small" type="notification" number="1"></d2l-count-badge>
21
- <d2l-count-badge size="small" type="notification" number="10"></d2l-count-badge>
22
- <d2l-count-badge size="small" type="notification" number="100"></d2l-count-badge>
21
+ <d2l-count-badge size="small" tab-stop text=" 1 new notification." type="notification" number="1"></d2l-count-badge>
22
+ <d2l-count-badge size="small" text="10 new notifications." type="notification" number="10"></d2l-count-badge>
23
+ <d2l-count-badge size="small" text="100 new notifications." type="notification" number="100"></d2l-count-badge>
24
+ </template>
25
+ </d2l-demo-snippet>
26
+
27
+ <h2>Small Notification Badge with live region</h2>
28
+ <d2l-demo-snippet>
29
+ <template>
30
+ <d2l-count-badge id="badge-announce-changes"size="small" announce-changes tab-stop text=" 1 new notification." type="notification" number="1"></d2l-count-badge>
31
+ <d2l-button id="increment-count">Increment Count</d2l-button>
23
32
  </template>
24
33
  </d2l-demo-snippet>
25
34
 
26
35
  <h2>Large Count Badge</h2>
27
36
  <d2l-demo-snippet>
28
37
  <template>
29
- <d2l-count-badge size="large" type="count" number="1"></d2l-count-badge>
30
- <d2l-count-badge size="large" type="count" number="10"></d2l-count-badge>
31
- <d2l-count-badge size="large" type="count" number="100"></d2l-count-badge>
32
- <d2l-count-badge size="large" type="count" number="777777"></d2l-count-badge>
33
- <d2l-count-badge size="large" type="count" number="777777" max-digits="5"></d2l-count-badge>
38
+ <d2l-count-badge size="large" text="1 new notification." type="count" number="1"></d2l-count-badge>
39
+ <d2l-count-badge size="large" text="10 new notifications." type="count" number="10"></d2l-count-badge>
40
+ <d2l-count-badge size="large" text="100 new notifications." type="count" number="100"></d2l-count-badge>
41
+ <d2l-count-badge size="large" text="777777 new notifications." type="count" number="777777"></d2l-count-badge>
42
+ <d2l-count-badge size="large" text="777777 new notifications."type="count" number="777777" max-digits="5"></d2l-count-badge>
43
+ </template>
44
+ </d2l-demo-snippet>
45
+
46
+ <h2>Small Notification Badge with Tooltip</h2>
47
+ <d2l-demo-snippet>
48
+ <template>
49
+ <d2l-count-badge size="small" has-tooltip text=" 3 new notification." type="notification" number="1"></d2l-count-badge>
34
50
  </template>
35
51
  </d2l-demo-snippet>
36
52
  </d2l-demo-page>
53
+ <script type="module">
54
+ document.getElementById('increment-count').addEventListener('click', () => {
55
+ document.getElementById('badge-announce-changes').number += 1;
56
+ document.getElementById('badge-announce-changes').text = `${document.getElementById('badge-announce-changes').number} new notifications.`;
57
+ });
58
+ </script>
37
59
  </body>
38
60
  </html>
@@ -1,24 +1,98 @@
1
1
  # Dialogs
2
2
 
3
- ## d2l-dialog
3
+ Dialogs interrupt the user to complete a set of tasks, confirm an action, or offer important options.
4
+
5
+ <!-- docs: demo autoSize:false size:large -->
6
+ ```html
7
+ <script type="module">
8
+ import '@brightspace-ui/core/components/button/button.js';
9
+ import '@brightspace-ui/core/components/dialog/dialog.js';
10
+
11
+ window.addEventListener('load', function () {
12
+ setTimeout(function() {
13
+ var dialog = document.querySelector('#dialog');
14
+ dialog.opened = true;
15
+ }, 100);
16
+ });
17
+ </script>
18
+
19
+ <d2l-dialog id="dialog" title-text="Dialog Title">
20
+ <div>Some dialog content</div>
21
+ <d2l-button slot="footer" primary data-dialog-action="done">Done</d2l-button>
22
+ <d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
23
+ </d2l-dialog>
24
+ ```
25
+
26
+ ## General Dialog [d2l-dialog]
4
27
 
5
28
  The `d2l-dialog` element is a generic dialog that provides a slot for arbitrary content, and a `footer` slot for workflow buttons. Apply the `data-dialog-action` attribute to workflow buttons to automatically close the dialog with the action value.
6
29
 
30
+ ### Best Practices
31
+ <!-- docs: start best practices -->
32
+ <!-- docs: start dos -->
33
+ * Keep dialogs small and easy to understand
34
+ * Limit workflow buttons to 2
35
+ * Label primary actions with clear and predictable language. Use verbs like, “Add” or “Save” that indicate the outcome of a dialog rather than, “OK” or “Close”
36
+ * Keep dialog titles concise
37
+ * Maintain a language relationship between the action that triggered the dialog, dialog title, and dialog primary button.
38
+ <!-- docs: end dos -->
39
+
40
+ <!-- docs: start donts -->
41
+ * Don’t use a dialog when you could reasonably use an alternative that preserves user context, like expanding options inline
42
+ * Don’t use a dialog to show error, success, or warning messages. Use an inline or toast alert instead.
43
+ * Avoid creating large, complex dialogs
44
+ * Avoid invoking a dialog from another dialog (nested dialogs)
45
+ * Avoid a title length that could easily wrap to two lines
46
+ <!-- docs: end donts -->
47
+ <!-- docs: end best practices -->
48
+
49
+ <!-- docs: start hidden content -->
7
50
  ![Dialog](./screenshots/dialog.png?raw=true)
51
+ <!-- docs: end hidden content -->
8
52
 
53
+ <!-- docs: demo live name:d2l-dialog autoSize:false display:block size:large -->
9
54
  ```html
10
55
  <script type="module">
56
+ import '@brightspace-ui/core/components/button/button.js';
11
57
  import '@brightspace-ui/core/components/dialog/dialog.js';
12
58
  </script>
59
+ <script>
60
+ document.querySelector('#open').addEventListener('click', () => {
61
+ document.querySelector('#dialog').opened = true;
62
+ });
63
+ </script>
13
64
 
14
65
  <d2l-button id="open">Show Dialog</d2l-button>
15
66
 
16
- <d2l-dialog title-text="Dialog Title">
67
+ <d2l-dialog id="dialog" title-text="Dialog Title">
17
68
  <div>Some dialog content</div>
18
69
  <d2l-button slot="footer" primary data-dialog-action="done">Done</d2l-button>
19
70
  <d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
20
71
  </d2l-dialog>
21
72
  ```
73
+
74
+ <!-- docs: start hidden content -->
75
+ ### Properties
76
+
77
+ | Property | Type | Description |
78
+ |--|--|--|
79
+ | `title-text` | String, required | Text displayed in the header of the dialog |
80
+ | `async` | Boolean | Whether to render a loading-spinner and wait for state changes via [AsyncContainerMixin](../../mixins/async-container) |
81
+ | `opened` | Boolean | Whether or not the dialog is open |
82
+ | `width` | Number, default: `600` | The preferred width (unit-less) for the dialog |
83
+
84
+ ### Events
85
+
86
+ - `d2l-dialog-open`: dispatched when the dialog is opened
87
+ - `d2l-dialog-close`: dispatched with the action value when the dialog is closed for any reason
88
+ <!-- docs: end hidden content -->
89
+
90
+ ### Methods
91
+
92
+ - `resize`: resizes the dialog based on specified `width` and measured content height
93
+
94
+ ### Usage
95
+
22
96
  Open the dialog declaratively using a boolean attribute `opened`:
23
97
 
24
98
  ```html
@@ -59,43 +133,67 @@ document.querySelector('d2l-dialog').addEventListener('d2l-dialog-close', (e) =>
59
133
  });
60
134
  ```
61
135
 
62
- **Properties:**
63
-
64
- | Property | Type | Description |
65
- |--|--|--|
66
- | `title-text` | String, required | Text displayed in the header of the dialog |
67
- | `async` | Boolean | Whether to render a loading-spinner and wait for state changes via [AsyncContainerMixin](../../mixins/async-container) |
68
- | `opened` | Boolean | Whether or not the dialog is open |
69
- | `width` | Number, default: `600` | The preferred width (unit-less) for the dialog |
70
-
71
- **Methods:**
72
-
73
- - `resize`: resizes the dialog based on specified `width` and measured content height
74
-
75
- **Events:**
76
-
77
- - `d2l-dialog-open`: dispatched when the dialog is opened
78
- - `d2l-dialog-close`: dispatched with the action value when the dialog is closed for any reason
79
-
80
- ## d2l-dialog-confirm
136
+ ## Confirmation Dialog [d2l-dialog-confirm]
81
137
 
82
138
  The `d2l-dialog-confirm` element is a simple confirmation dialog for prompting the user. It provides properties for specifying the required `text` and optional `title-text`, and a `footer` slot for workflow buttons. Apply the `data-dialog-action` attribute to workflow buttons to automatically close the confirm dialog with the action value.
83
139
 
140
+ ### Best Practices
141
+ <!-- docs: start best practices -->
142
+ <!-- docs: start dos -->
143
+ * Label the primary button with a verb that indicates the outcome of the action. Use “Delete” over “Yes” or “No”
144
+ * Keep the confirmation statement or question concise. Starting with, “Are you sure you want to…” is wordy.
145
+ * Use “Cancel” for the secondary button
146
+ <!-- docs: end dos -->
147
+
148
+ <!-- docs: start donts -->
149
+ * Don’t present users with unclear choices
150
+ * Don’t label the primary button with a potentially ambiguous word like, “OK”
151
+ * Don’t use a confirmation dialog if the action is not critical in nature (avoid interrupting the user)
152
+ * Don’t use a confirmation dialog if you can introduce an undo option instead
153
+ * Don’t use a confirmation dialog to show error, success, or warning messages. Use an inline or toast alert instead
154
+ <!-- docs: end donts -->
155
+ <!-- docs: end best practices -->
156
+
157
+ <!-- docs: start hidden content -->
84
158
  ![Confirmation Dialog](./screenshots/dialog-confirm.png?raw=true)
159
+ <!-- docs: end hidden content -->
85
160
 
161
+ <!-- docs: demo live name:d2l-dialog-confirm autoSize:false display:block size:large -->
86
162
  ```html
87
163
  <script type="module">
164
+ import '@brightspace-ui/core/components/button/button.js';
88
165
  import '@brightspace-ui/core/components/dialog/dialog-confirm.js';
166
+
167
+ document.querySelector('#open-confirm').addEventListener('click', () => {
168
+ document.querySelector('#dialog-confirm').opened = true;
169
+ });
89
170
  </script>
90
171
 
91
- <d2l-button id="open">Show Confirm</d2l-button>
172
+ <d2l-button id="open-confirm">Show Confirm</d2l-button>
92
173
 
93
- <d2l-dialog-confirm title-text="Confirm Title" text="Are you sure?">
174
+ <d2l-dialog-confirm id="dialog-confirm" title-text="Confirm Title" text="Are you sure?">
94
175
  <d2l-button slot="footer" primary data-dialog-action="yes">Yes</d2l-button>
95
176
  <d2l-button slot="footer" data-dialog-action>No</d2l-button>
96
177
  </d2l-dialog-confirm>
97
178
  ```
98
179
 
180
+ <!-- docs: start hidden content -->
181
+ ### Properties
182
+
183
+ | Property | Type | Description |
184
+ |--|--|--|
185
+ | `text` | String, required | The required text content for the confirmation dialog |
186
+ | `opened` | Boolean | Whether or not the dialog is open |
187
+ | `title-text` | String | The optional title for the confirmation dialog |
188
+
189
+ ### Events
190
+
191
+ - `d2l-dialog-open`: dispatched when the dialog is opened
192
+ - `d2l-dialog-close`: dispatched with the action value when the dialog is closed for any reason
193
+ <!-- docs: end hidden content -->
194
+
195
+ ### Usage
196
+
99
197
  Open the confirm dialog as described for generic dialogs, either by setting the `opened` property/attribute, or by calling the `open` method to get a promise for the result.
100
198
 
101
199
  ```javascript
@@ -108,39 +206,51 @@ document.querySelector('#open').addEventListener('click', () => {
108
206
  });
109
207
  ```
110
208
 
111
- **Properties:**
112
-
113
- | Property | Type | Description |
114
- |--|--|--|
115
- | `text` | String, required | The required text content for the confirmation dialog |
116
- | `opened` | Boolean | Whether or not the dialog is open |
117
- | `title-text` | String | The optional title for the confirmation dialog |
118
-
119
- **Events:**
120
-
121
- - `d2l-dialog-open`: dispatched when the dialog is opened
122
- - `d2l-dialog-close`: dispatched with the action value when the dialog is closed for any reason
123
-
124
- ## d2l-dialog-fullscreen
209
+ ## Fullscreen Dialog [d2l-dialog-fullscreen]
125
210
 
126
211
  The `d2l-dialog-fullscreen` element is a fullscreen variant of the generic `d2l-dialog`. It provides a slot for arbitrary content, and a `footer` slot for workflow buttons. Apply the `data-dialog-action` attribute to workflow buttons to automatically close the dialog with the action value.
127
212
 
213
+ <!-- docs: start hidden content -->
128
214
  ![Fullscreen Dialog](./screenshots/dialog-fullscreen.png?raw=true)
215
+ <!-- docs: end hidden content -->
129
216
 
217
+ <!-- docs: demo live name:d2l-dialog-fullscreen autoSize:false display:block size:large -->
130
218
  ```html
131
219
  <script type="module">
220
+ import '@brightspace-ui/core/components/button/button.js';
132
221
  import '@brightspace-ui/core/components/dialog/dialog-fullscreen.js';
222
+
223
+ document.querySelector('#open-fullscreen').addEventListener('click', () => {
224
+ document.querySelector('#dialog-fullscreen').opened = true;
225
+ });
133
226
  </script>
134
227
 
135
- <d2l-button id="open">Show Dialog</d2l-button>
228
+ <d2l-button id="open-fullscreen">Show Dialog</d2l-button>
136
229
 
137
- <d2l-dialog-fullscreen title-text="Fullscreen Dialog Title">
230
+ <d2l-dialog-fullscreen id="dialog-fullscreen" title-text="Fullscreen Dialog Title">
138
231
  <div>Some dialog content</div>
139
232
  <d2l-button slot="footer" primary data-dialog-action="done">Done</d2l-button>
140
233
  <d2l-button slot="footer" data-dialog-action>Cancel</d2l-button>
141
234
  </d2l-dialog-fullscreen>
142
235
  ```
143
236
 
237
+ <!-- docs: start hidden content -->
238
+ ### Properties
239
+
240
+ | Property | Type | Description |
241
+ |--|--|--|
242
+ | `title-text` | String, required | Text displayed in the header of the dialog |
243
+ | `async` | Boolean | Whether to render a loading-spinner and wait for state changes via [AsyncContainerMixin](../../mixins/async-container) |
244
+ | `opened` | Boolean | Whether or not the dialog is open |
245
+
246
+ ### Events
247
+
248
+ - `d2l-dialog-open`: dispatched when the dialog is opened
249
+ - `d2l-dialog-close`: dispatched with the action value when the dialog is closed for any reason
250
+ <!-- docs: end hidden content -->
251
+
252
+ ### Usage
253
+
144
254
  Open the fullscreen dialog as described for generic dialogs, either by setting the `opened` property/attribute, or by calling the `open` method to get a promise for the result.
145
255
 
146
256
  ```javascript
@@ -153,21 +263,10 @@ document.querySelector('#open').addEventListener('click', () => {
153
263
  });
154
264
  ```
155
265
 
156
- **Properties:**
157
-
158
- | Property | Type | Description |
159
- |--|--|--|
160
- | `title-text` | String, required | Text displayed in the header of the dialog |
161
- | `async` | Boolean | Whether to render a loading-spinner and wait for state changes via [AsyncContainerMixin](../../mixins/async-container) |
162
- | `opened` | Boolean | Whether or not the dialog is open |
163
-
164
- **Events:**
165
-
166
- - `d2l-dialog-open`: dispatched when the dialog is opened
167
- - `d2l-dialog-close`: dispatched with the action value when the dialog is closed for any reason
168
-
266
+ <!-- docs: start hidden content -->
169
267
  ## Future Enhancements
170
268
 
171
269
  * scroll API for the dialog content (see [#341](https://github.com/BrightspaceUI/core/issues/341))
172
270
 
173
271
  Looking for an enhancement not listed here? Create a GitHub issue!
272
+ <!-- docs: end hidden content -->
@@ -323,7 +323,7 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
323
323
  this.opened = false;
324
324
  };
325
325
 
326
- if (!reduceMotion && this.mediaQueryList.matches && this.mobileTray && isVisible(this)) {
326
+ if (!reduceMotion && this._useMobileStyling && this.mobileTray && isVisible(this)) {
327
327
  this.shadowRoot.querySelector('.d2l-dropdown-content-width')
328
328
  .addEventListener('animationend', hide, { once: true });
329
329
  this._closing = true;
@@ -352,14 +352,14 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
352
352
  this.__applyFocus = applyFocus !== undefined ? applyFocus : true;
353
353
  this.opened = true;
354
354
  await this.updateComplete;
355
- this._showBackdrop = this.mediaQueryList.matches && this.mobileTray;
355
+ this._showBackdrop = this._useMobileStyling && this.mobileTray;
356
356
  }
357
357
 
358
358
  async resize() {
359
359
  if (!this.opened) {
360
360
  return;
361
361
  }
362
- this._showBackdrop = this.mediaQueryList.matches && this.mobileTray;
362
+ this._showBackdrop = this._useMobileStyling && this.mobileTray;
363
363
  await this.__position();
364
364
  }
365
365
 
@@ -504,7 +504,7 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
504
504
  }
505
505
 
506
506
  await this.__position();
507
- this._showBackdrop = this.mediaQueryList.matches && this.mobileTray;
507
+ this._showBackdrop = this._useMobileStyling && this.mobileTray;
508
508
 
509
509
  if (!this.noAutoFocus && this.__applyFocus) {
510
510
  const focusable = getFirstFocusableDescendant(this);
@@ -530,7 +530,7 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
530
530
 
531
531
  if (newValue) {
532
532
 
533
- if (ifrauBackdropService && this.mobileTray && this.mediaQueryList.matches) {
533
+ if (ifrauBackdropService && this.mobileTray && this._useMobileStyling) {
534
534
  this._ifrauContextInfo = await ifrauBackdropService.showBackdrop();
535
535
  }
536
536
 
@@ -542,7 +542,7 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
542
542
  clearDismissible(this.__dismissibleId);
543
543
  this.__dismissibleId = null;
544
544
  }
545
- if (ifrauBackdropService && this.mobileTray && this.mediaQueryList.matches) {
545
+ if (ifrauBackdropService && this.mobileTray && this._useMobileStyling) {
546
546
  ifrauBackdropService.hideBackdrop();
547
547
  this._ifrauContextInfo = null;
548
548
  }
@@ -771,8 +771,10 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
771
771
  this.dispatchEvent(new CustomEvent('d2l-dropdown-focus-enter'));
772
772
  }
773
773
 
774
- _handleMobileResize() {
774
+ async _handleMobileResize() {
775
775
  this._useMobileStyling = this.mediaQueryList.matches;
776
+ this._showBackdrop = this._useMobileStyling && this.mobileTray;
777
+ if (this.opened) await this.__position();
776
778
  }
777
779
 
778
780
  _renderContent() {
@@ -787,9 +789,9 @@ export const DropdownContentMixin = superclass => class extends LocalizeCoreElem
787
789
  }
788
790
  }
789
791
 
790
- const specialMobileStyle = this.mediaQueryList.matches && this.mobileTray;
791
- const mobileTrayRightLeft = this.mediaQueryList.matches && (this.mobileTray === 'right' || this.mobileTray === 'left');
792
- const mobileTrayBottom = this.mediaQueryList.matches && (this.mobileTray === 'bottom');
792
+ const specialMobileStyle = this._useMobileStyling && this.mobileTray;
793
+ const mobileTrayRightLeft = this._useMobileStyling && (this.mobileTray === 'right' || this.mobileTray === 'left');
794
+ const mobileTrayBottom = this._useMobileStyling && (this.mobileTray === 'bottom');
793
795
 
794
796
  let maxWidthOverride = this.maxWidth;
795
797
  if (mobileTrayRightLeft) {
@@ -26,7 +26,7 @@ The `d2l-filter` component allows a user to filter on one or more dimensions of
26
26
  | `disabled` | Boolean, default: `false` | Disables the dropdown opener for the filter |
27
27
 
28
28
  **Events:**
29
- * `d2l-filter-change`: dispatched when any filter value has changed (may contain info about multiple changes)
29
+ * `d2l-filter-change`: dispatched when any filter value has changed (may contain info about multiple dimensions and multiple changes in each)
30
30
  * `d2l-filter-dimension-first-open`: dispatched when a dimension is opened for the first time (if there is only one dimension, this will be dispatched when the dropdown is first opened)
31
31
  * `d2l-filter-dimension-search`: dispatched when a dimension that supports searching and has the "manual" search-type is searched
32
32
 
@@ -50,16 +50,19 @@ class FilterSearchDemo extends LitElement {
50
50
  }
51
51
 
52
52
  _handleFilterChange(e) {
53
- (e.detail.changes.length === 1) ?
54
- console.log(`Filter selection changed for dimension "${e.detail.changes[0].dimension}":`, e.detail.changes[0].value) : // eslint-disable-line no-console
55
- console.log('Batch filter selection changed:', e.detail.changes); // eslint-disable-line no-console
53
+ (e.detail.dimensions.length === 1) ?
54
+ console.log(`Filter selection changed for dimension "${e.detail.dimensions[0].dimensionKey}":`, e.detail.dimensions[0].changes) : // eslint-disable-line no-console
55
+ console.log('Batch filter selection changed:', e.detail.dimensions); // eslint-disable-line no-console
56
56
 
57
- e.detail.changes.forEach(change => {
58
- if (change.dimension === 'event') {
59
- this._fullData.find(value => value.key === change.value.key).selected = change.value.selected;
60
- } else if (change.dimension === 'event-single') {
61
- this._fullDataSingle.find(value => value.key === change.value.key).selected = change.value.selected;
62
- }
57
+ e.detail.dimensions.forEach(dimension => {
58
+ if (!dimension.dimensionKey.includes('event')) return;
59
+ dimension.changes.forEach(change => {
60
+ if (dimension.dimensionKey === 'event') {
61
+ this._fullData.find(value => value.key === change.valueKey).selected = change.selected;
62
+ } else if (change.dimension === 'event-single') {
63
+ this._fullDataSingle.find(value => value.key === change.valueKey).selected = change.selected;
64
+ }
65
+ });
63
66
  });
64
67
  }
65
68
 
@@ -112,9 +112,9 @@
112
112
 
113
113
  <script type="module">
114
114
  document.addEventListener('d2l-filter-change', e => {
115
- (e.detail.changes.length === 1) ?
116
- console.log(`Filter selection changed for dimension "${e.detail.changes[0].dimension}":`, e.detail.changes[0].value) : // eslint-disable-line no-console
117
- console.log('Batch filter selection changed:', e.detail.changes); // eslint-disable-line no-console
115
+ (e.detail.dimensions.length === 1) ?
116
+ console.log(`Filter selection changed for dimension "${e.detail.dimensions[0].dimensionKey}":`, e.detail.dimensions[0].changes) : // eslint-disable-line no-console
117
+ console.log('Batch filter selection changed:', e.detail.dimensions); // eslint-disable-line no-console
118
118
  });
119
119
 
120
120
  document.addEventListener('d2l-filter-dimension-first-open', e => {
@@ -213,11 +213,12 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
213
213
  }
214
214
  return this._dimensions.map((dimension) => {
215
215
  const builtDimension = this._buildDimension(dimension);
216
- const dimensionDescription = `${dimension.text}. ${this.localize('components.filter.filterCountDescription', { number: dimension.appliedCount })}`;
217
- return html`<d2l-menu-item text="${dimension.text}" description="${dimensionDescription}">
216
+ const countBadgeId = `filters-applied-count-${dimension.key}`;
217
+ const filtersAppliedText = `${this.localize('components.filter.filterCountDescription', { number: dimension.appliedCount })}`;
218
+ return html`<d2l-menu-item text="${dimension.text}" description="${dimension.text}." aria-describedby="${countBadgeId}">
218
219
  ${builtDimension}
219
220
  <div slot="supporting">
220
- <d2l-count-badge number="${dimension.appliedCount}" max-digits="2" hide-zero></d2l-count-badge>
221
+ <d2l-count-badge id="${countBadgeId}" number="${dimension.appliedCount}" max-digits="2" text="${filtersAppliedText}" hide-zero></d2l-count-badge>
221
222
  </div>
222
223
  </d2l-menu-item>`;
223
224
  });
@@ -332,22 +333,31 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
332
333
  `;
333
334
  }
334
335
 
335
- _dispatchChangeEvent(eventKey, eventDetail) {
336
- this._changeEventsToDispatch.set(eventKey, eventDetail);
336
+ _dispatchChangeEvent(dimension, change) {
337
+ this._setDimensionChangeEvent(dimension, change);
337
338
 
338
339
  if (!this._changeEventTimeout) {
339
340
  this._changeEventTimeout = setTimeout(() => {
340
- this.dispatchEvent(new CustomEvent('d2l-filter-change', {
341
- bubbles: true,
342
- composed: false,
343
- detail: { changes: Array.from(this._changeEventsToDispatch.values()) }
344
- }));
345
- this._changeEventsToDispatch = new Map();
346
- this._changeEventTimeout = null;
341
+ this._dispatchChangeEventNow();
347
342
  }, 200);
348
343
  }
349
344
  }
350
345
 
346
+ _dispatchChangeEventNow() {
347
+ const dimensions = Array.from(this._changeEventsToDispatch.values());
348
+ dimensions.forEach(dimension => {
349
+ dimension.changes = Array.from(dimension.changes.values());
350
+ });
351
+
352
+ this.dispatchEvent(new CustomEvent('d2l-filter-change', {
353
+ bubbles: true,
354
+ composed: false,
355
+ detail: { dimensions: dimensions }
356
+ }));
357
+ this._changeEventsToDispatch = new Map();
358
+ this._changeEventTimeout = null;
359
+ }
360
+
351
361
  _dispatchDimensionFirstOpenEvent(key) {
352
362
  if (!this._openedDimensions.includes(key)) {
353
363
  this.dispatchEvent(new CustomEvent('d2l-filter-dimension-first-open', { bubbles: true, composed: false, detail: { key: key } }));
@@ -384,7 +394,7 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
384
394
  this._totalAppliedCount--;
385
395
  }
386
396
 
387
- this._dispatchChangeEvent(`${dimensionKey}-${valueKey}`, { dimension: dimensionKey, value: { key: valueKey, selected: selected } });
397
+ this._dispatchChangeEvent(dimension, { valueKey: valueKey, selected: selected });
388
398
  }
389
399
 
390
400
  _handleDimensionDataChange(e) {
@@ -537,6 +547,19 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
537
547
  this.requestUpdate();
538
548
  }
539
549
 
550
+ _setDimensionChangeEvent(dimension, change) {
551
+ if (!this._changeEventsToDispatch.has(dimension.key)) {
552
+ this._changeEventsToDispatch.set(dimension.key, { dimensionKey: dimension.key, changes: new Map() });
553
+ }
554
+ const dimensionChanges = this._changeEventsToDispatch.get(dimension.key);
555
+
556
+ switch (dimension.type) {
557
+ case 'd2l-filter-dimension-set':
558
+ dimensionChanges.changes.set(change.valueKey, change);
559
+ break;
560
+ }
561
+ }
562
+
540
563
  _setFilterCounts(dimensionToRecount) {
541
564
  this._totalAppliedCount = 0;
542
565
 
@@ -1008,21 +1008,33 @@
1008
1008
  "name": "d2l-count-badge",
1009
1009
  "path": "./components/count-badge/count-badge.js",
1010
1010
  "attributes": [
1011
+ {
1012
+ "name": "max-digits",
1013
+ "description": "Optionally specify a digit limit, after which numbers are truncated. Defaults to two for \"notification\" type and no limit for \"count\" type.",
1014
+ "type": "number"
1015
+ },
1011
1016
  {
1012
1017
  "name": "number",
1013
1018
  "description": "The number to be displayed on the badge. Must be a positive integer.",
1014
1019
  "type": "number"
1015
1020
  },
1016
1021
  {
1017
- "name": "max-digits",
1018
- "description": "Optionally specify a digit limit, after which numbers are truncated. Defaults to two for \"notification\" type and no limit for \"count\" type.",
1019
- "type": "number"
1022
+ "name": "announce-changes",
1023
+ "description": "Optionally choose to announce changes to the badge with an aria-live region. If the number property is changed, the text will be read by screenreaders. Defaults to false.",
1024
+ "type": "boolean",
1025
+ "default": "false"
1020
1026
  },
1021
1027
  {
1022
- "name": "type",
1023
- "description": "The type of the badge. Defaults to \"count\".",
1024
- "type": "'count'|'notification'",
1025
- "default": "\"count\""
1028
+ "name": "has-tooltip",
1029
+ "description": "Optionally add a tooltip on the badge. Defaults to false.",
1030
+ "type": "boolean",
1031
+ "default": "false"
1032
+ },
1033
+ {
1034
+ "name": "hide-zero",
1035
+ "description": "Optionally choose to not render the count badge when the number is zero. Defaults to false.",
1036
+ "type": "boolean",
1037
+ "default": "false"
1026
1038
  },
1027
1039
  {
1028
1040
  "name": "size",
@@ -1031,13 +1043,31 @@
1031
1043
  "default": "\"small\""
1032
1044
  },
1033
1045
  {
1034
- "name": "hide-zero",
1035
- "description": "Optionally choose to not render the count badge when the number is zero. Defaults to false.",
1046
+ "name": "tab-stop",
1047
+ "description": "Optionally choose to add a tab stop to the badge. Defaults to false.",
1036
1048
  "type": "boolean",
1037
1049
  "default": "false"
1050
+ },
1051
+ {
1052
+ "name": "text",
1053
+ "description": "* Descriptive text for the badge which will act as an accessible label and tooltip text when tooltips are enabled.",
1054
+ "type": "string",
1055
+ "default": "\"\""
1056
+ },
1057
+ {
1058
+ "name": "type",
1059
+ "description": "The type of the badge. Defaults to \"count\".",
1060
+ "type": "'count'|'notification'",
1061
+ "default": "\"count\""
1038
1062
  }
1039
1063
  ],
1040
1064
  "properties": [
1065
+ {
1066
+ "name": "maxDigits",
1067
+ "attribute": "max-digits",
1068
+ "description": "Optionally specify a digit limit, after which numbers are truncated. Defaults to two for \"notification\" type and no limit for \"count\" type.",
1069
+ "type": "number"
1070
+ },
1041
1071
  {
1042
1072
  "name": "number",
1043
1073
  "attribute": "number",
@@ -1045,17 +1075,25 @@
1045
1075
  "type": "number"
1046
1076
  },
1047
1077
  {
1048
- "name": "maxDigits",
1049
- "attribute": "max-digits",
1050
- "description": "Optionally specify a digit limit, after which numbers are truncated. Defaults to two for \"notification\" type and no limit for \"count\" type.",
1051
- "type": "number"
1078
+ "name": "announceChanges",
1079
+ "attribute": "announce-changes",
1080
+ "description": "Optionally choose to announce changes to the badge with an aria-live region. If the number property is changed, the text will be read by screenreaders. Defaults to false.",
1081
+ "type": "boolean",
1082
+ "default": "false"
1052
1083
  },
1053
1084
  {
1054
- "name": "type",
1055
- "attribute": "type",
1056
- "description": "The type of the badge. Defaults to \"count\".",
1057
- "type": "'count'|'notification'",
1058
- "default": "\"count\""
1085
+ "name": "hasTooltip",
1086
+ "attribute": "has-tooltip",
1087
+ "description": "Optionally add a tooltip on the badge. Defaults to false.",
1088
+ "type": "boolean",
1089
+ "default": "false"
1090
+ },
1091
+ {
1092
+ "name": "hideZero",
1093
+ "attribute": "hide-zero",
1094
+ "description": "Optionally choose to not render the count badge when the number is zero. Defaults to false.",
1095
+ "type": "boolean",
1096
+ "default": "false"
1059
1097
  },
1060
1098
  {
1061
1099
  "name": "size",
@@ -1065,11 +1103,25 @@
1065
1103
  "default": "\"small\""
1066
1104
  },
1067
1105
  {
1068
- "name": "hideZero",
1069
- "attribute": "hide-zero",
1070
- "description": "Optionally choose to not render the count badge when the number is zero. Defaults to false.",
1106
+ "name": "tabStop",
1107
+ "attribute": "tab-stop",
1108
+ "description": "Optionally choose to add a tab stop to the badge. Defaults to false.",
1071
1109
  "type": "boolean",
1072
1110
  "default": "false"
1111
+ },
1112
+ {
1113
+ "name": "text",
1114
+ "attribute": "text",
1115
+ "description": "* Descriptive text for the badge which will act as an accessible label and tooltip text when tooltips are enabled.",
1116
+ "type": "string",
1117
+ "default": "\"\""
1118
+ },
1119
+ {
1120
+ "name": "type",
1121
+ "attribute": "type",
1122
+ "description": "The type of the badge. Defaults to \"count\".",
1123
+ "type": "'count'|'notification'",
1124
+ "default": "\"count\""
1073
1125
  }
1074
1126
  ]
1075
1127
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "1.176.4",
3
+ "version": "1.178.0",
4
4
  "description": "A collection of accessible, free, open-source web components for building Brightspace applications",
5
5
  "repository": "https://github.com/BrightspaceUI/core.git",
6
6
  "publishConfig": {