@brightspace-ui/core 1.235.3 → 1.236.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/README.md CHANGED
@@ -66,6 +66,7 @@ npm install @brightspace-ui/core
66
66
  * Mixins
67
67
  * [ArrowKeysMixin](mixins/arrow-keys-mixin.md): manage focus with arrow keys
68
68
  * [AsyncContainerMixin](mixins/async-container/): manage collective async state
69
+ * [FocusMixin](mixins/focus-mixin.md): delegate focus to a nested element when `focus()` is called
69
70
  * [FocusVisiblePolyfillMixin](mixins/focus-visible-polyfill-mixin.md): components can use the `:focus-visible` pseudo-class polyfill
70
71
  * [FormElementMixin](components/form/docs/form-element-mixin.md): allow components to participate in forms and validation
71
72
  * [LabelledMixin](mixins/labelled-mixin.md): label custom elements by referencing elements across DOM scopes
@@ -1,6 +1,7 @@
1
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
1
2
  import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
2
3
 
3
- export const ButtonMixin = superclass => class extends FocusVisiblePolyfillMixin(superclass) {
4
+ export const ButtonMixin = superclass => class extends FocusMixin(FocusVisiblePolyfillMixin(superclass)) {
4
5
 
5
6
  static get properties() {
6
7
  return {
@@ -65,6 +66,8 @@ export const ButtonMixin = superclass => class extends FocusVisiblePolyfillMixin
65
66
  };
66
67
  }
67
68
 
69
+ static focusElementSelector = 'button';
70
+
68
71
  constructor() {
69
72
  super();
70
73
  this.disabled = false;
@@ -107,11 +110,6 @@ export const ButtonMixin = superclass => class extends FocusVisiblePolyfillMixin
107
110
  this.removeEventListener('click', this._handleClick, true);
108
111
  }
109
112
 
110
- focus() {
111
- const button = this.shadowRoot && this.shadowRoot.querySelector('button');
112
- if (button) button.focus();
113
- }
114
-
115
113
  /** @internal */
116
114
  _getType() {
117
115
  if (this.type === 'submit' || this.type === 'reset') {
@@ -1,6 +1,7 @@
1
1
  import '../colors/colors.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
3
  import { classMap } from 'lit-html/directives/class-map.js';
4
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
4
5
  import { ifDefined } from 'lit-html/directives/if-defined.js';
5
6
  import { offscreenStyles } from '../offscreen/offscreen.js';
6
7
  import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
@@ -15,7 +16,7 @@ import { styleMap } from 'lit-html/directives/style-map.js';
15
16
  * @slot footer - Slot for footer content, such secondary actions
16
17
  * @slot header - Slot for header content, such as course image (no actionable elements)
17
18
  */
18
- class Card extends RtlMixin(LitElement) {
19
+ class Card extends FocusMixin(RtlMixin(LitElement)) {
19
20
 
20
21
  static get properties() {
21
22
  return {
@@ -234,6 +235,8 @@ class Card extends RtlMixin(LitElement) {
234
235
  `];
235
236
  }
236
237
 
238
+ static focusElementSelector = 'a';
239
+
237
240
  constructor() {
238
241
  super();
239
242
  this.alignCenter = false;
@@ -306,12 +309,6 @@ class Card extends RtlMixin(LitElement) {
306
309
  `;
307
310
  }
308
311
 
309
- focus() {
310
- const elem = this.shadowRoot && this.shadowRoot.querySelector('a');
311
- if (!elem) return;
312
- elem.focus();
313
- }
314
-
315
312
  _onBadgeResize(entries) {
316
313
  if (!entries || entries.length === 0) return;
317
314
  const entry = entries[0];
@@ -19,6 +19,7 @@ import { bodyCompactStyles, bodySmallStyles, bodyStandardStyles } from '../typog
19
19
  import { css, html, LitElement } from 'lit-element/lit-element.js';
20
20
  import { announce } from '../../helpers/announce.js';
21
21
  import { classMap } from 'lit-html/directives/class-map.js';
22
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
22
23
  import { ifDefined } from 'lit-html/directives/if-defined.js';
23
24
  import { LocalizeCoreElement } from '../../lang/localize-core-element.js';
24
25
  import { offscreenStyles } from '../offscreen/offscreen.js';
@@ -37,7 +38,7 @@ const SET_DIMENSION_ID_PREFIX = 'list-';
37
38
  * @fires d2l-filter-dimension-first-open - Dispatched when a dimension is opened for the first time
38
39
  * @fires d2l-filter-dimension-search - Dispatched when a dimension that supports searching and has the "manual" search-type is searched
39
40
  */
40
- class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
41
+ class Filter extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
41
42
 
42
43
  static get properties() {
43
44
  return {
@@ -136,6 +137,8 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
136
137
  `];
137
138
  }
138
139
 
140
+ static focusElementSelector = 'd2l-dropdown-button-subtle';
141
+
139
142
  constructor() {
140
143
  super();
141
144
  this.disabled = false;
@@ -229,11 +232,6 @@ class Filter extends LocalizeCoreElement(RtlMixin(LitElement)) {
229
232
  `;
230
233
  }
231
234
 
232
- focus() {
233
- const opener = this.shadowRoot && this.shadowRoot.querySelector('d2l-dropdown-button-subtle');
234
- if (opener) opener.focus();
235
- }
236
-
237
235
  getSubscriberController() {
238
236
  return this._activeFiltersSubscribers;
239
237
  }
@@ -1,5 +1,6 @@
1
1
  import { css, html, LitElement } from 'lit-element/lit-element.js';
2
2
  import { forceFocusVisible, getNextFocusable, getPreviousFocusable } from '../../helpers/focus.js';
3
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
3
4
  import { ifDefined } from 'lit-html/directives/if-defined.js';
4
5
  import { isComposedAncestor } from '../../helpers/dom.js';
5
6
 
@@ -7,7 +8,7 @@ import { isComposedAncestor } from '../../helpers/dom.js';
7
8
  * A generic container component to trap user focus.
8
9
  * @fires d2l-focus-trap-enter - Dispatched when focus enters the trap. May be used to override initial focus placement when focus enters the trap.
9
10
  */
10
- class FocusTrap extends LitElement {
11
+ class FocusTrap extends FocusMixin(LitElement) {
11
12
 
12
13
  static get properties() {
13
14
  return {
@@ -30,6 +31,8 @@ class FocusTrap extends LitElement {
30
31
  `;
31
32
  }
32
33
 
34
+ static focusElementSelector = '.d2l-focus-trap-start';
35
+
33
36
  constructor() {
34
37
  super();
35
38
  this.trap = false;
@@ -55,11 +58,6 @@ class FocusTrap extends LitElement {
55
58
  `;
56
59
  }
57
60
 
58
- focus() {
59
- const focusable = this.shadowRoot && this.shadowRoot.querySelector('.d2l-focus-trap-start');
60
- if (focusable) focusable.focus();
61
- }
62
-
63
61
  _focusFirst() {
64
62
  const focusable = this.shadowRoot &&
65
63
  getNextFocusable(this.shadowRoot.querySelector('.d2l-focus-trap-start'));
@@ -1,6 +1,7 @@
1
1
  import '../colors/colors.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
3
  import { classMap } from 'lit-html/directives/class-map.js';
4
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
4
5
  import { getUniqueId } from '../../helpers/uniqueId.js';
5
6
  import { ifDefined } from 'lit-html/directives/if-defined.js';
6
7
  import { offscreenStyles } from '../offscreen/offscreen.js';
@@ -56,7 +57,7 @@ export const checkboxStyles = css`
56
57
  * @slot - Checkbox information (e.g., text)
57
58
  * @fires change - Dispatched when the checkbox's state changes
58
59
  */
59
- class InputCheckbox extends SkeletonMixin(RtlMixin(LitElement)) {
60
+ class InputCheckbox extends FocusMixin(SkeletonMixin(RtlMixin(LitElement))) {
60
61
 
61
62
  static get properties() {
62
63
  return {
@@ -163,6 +164,8 @@ class InputCheckbox extends SkeletonMixin(RtlMixin(LitElement)) {
163
164
  ];
164
165
  }
165
166
 
167
+ static focusElementSelector = 'input.d2l-input-checkbox';
168
+
166
169
  constructor() {
167
170
  super();
168
171
  this.checked = false;
@@ -205,11 +208,6 @@ class InputCheckbox extends SkeletonMixin(RtlMixin(LitElement)) {
205
208
  `;
206
209
  }
207
210
 
208
- focus() {
209
- const elem = this.shadowRoot && this.shadowRoot.querySelector('input.d2l-input-checkbox');
210
- if (elem) elem.focus();
211
- }
212
-
213
211
  simulateClick() {
214
212
  this.checked = !this.checked;
215
213
  this.indeterminate = false;
@@ -4,6 +4,7 @@ import './input-fieldset.js';
4
4
  import '../tooltip/tooltip.js';
5
5
  import { css, html, LitElement } from 'lit-element/lit-element.js';
6
6
  import { formatDateTimeInISO, getDateFromISODate, parseISODateTime } from '../../helpers/dateTime.js';
7
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
7
8
  import { FormElementMixin } from '../form/form-element-mixin.js';
8
9
  import { getUniqueId } from '../../helpers/uniqueId.js';
9
10
  import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -30,7 +31,7 @@ export function getShiftedEndDate(startValue, endValue, prevStartValue, inclusiv
30
31
  * A component consisting of two input-date components - one for start of range and one for end of range. Values specified for these components (through start-value and/or end-value attributes) should be localized to the user's timezone if applicable and must be in ISO 8601 calendar date format ("YYYY-MM-DD").
31
32
  * @fires change - Dispatched when there is a change to selected start date or selected end date. `start-value` and `end-value` correspond to the selected values and are formatted in ISO 8601 calendar date format (`YYYY-MM-DD`).
32
33
  */
33
- class InputDateRange extends SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCoreElement(LitElement)))) {
34
+ class InputDateRange extends FocusMixin(SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCoreElement(LitElement))))) {
34
35
 
35
36
  static get properties() {
36
37
  return {
@@ -128,6 +129,8 @@ class InputDateRange extends SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCor
128
129
  `];
129
130
  }
130
131
 
132
+ static focusElementSelector = 'd2l-input-date';
133
+
131
134
  constructor() {
132
135
  super();
133
136
 
@@ -251,11 +254,6 @@ class InputDateRange extends SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCor
251
254
  });
252
255
  }
253
256
 
254
- focus() {
255
- const input = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-date');
256
- if (input) input.focus();
257
- }
258
-
259
257
  async validate() {
260
258
  if (!this.shadowRoot) return;
261
259
  const startDateInput = this.shadowRoot.querySelector('.d2l-input-date-range-start');
@@ -5,6 +5,7 @@ import '../tooltip/tooltip.js';
5
5
  import { convertLocalToUTCDateTime, convertUTCToLocalDateTime } from '@brightspace-ui/intl/lib/dateTime.js';
6
6
  import { css, html, LitElement } from 'lit-element/lit-element.js';
7
7
  import { formatDateTimeInISO, getAdjustedTime, getDateFromISODateTime, getDateNoConversion, parseISODateTime } from '../../helpers/dateTime.js';
8
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
8
9
  import { FormElementMixin } from '../form/form-element-mixin.js';
9
10
  import { getUniqueId } from '../../helpers/uniqueId.js';
10
11
  import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -63,7 +64,7 @@ export function getShiftedEndDateTime(startValue, endValue, prevStartValue, incl
63
64
  * @slot end - Optional content that would appear below the end input-date-time
64
65
  * @fires change - Dispatched when there is a change to selected start date-time or selected end date-time. `start-value` and `end-value` correspond to the selected values and are formatted in ISO 8601 combined date and time format (`YYYY-MM-DDTHH:mm:ss.sssZ`).
65
66
  */
66
- class InputDateTimeRange extends SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCoreElement(LitElement)))) {
67
+ class InputDateTimeRange extends FocusMixin(SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCoreElement(LitElement))))) {
67
68
 
68
69
  static get properties() {
69
70
  return {
@@ -171,6 +172,8 @@ class InputDateTimeRange extends SkeletonMixin(FormElementMixin(RtlMixin(Localiz
171
172
  `];
172
173
  }
173
174
 
175
+ static focusElementSelector = 'd2l-input-date-time';
176
+
174
177
  constructor() {
175
178
  super();
176
179
 
@@ -307,11 +310,6 @@ class InputDateTimeRange extends SkeletonMixin(FormElementMixin(RtlMixin(Localiz
307
310
  });
308
311
  }
309
312
 
310
- focus() {
311
- const input = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-date-time');
312
- if (input) input.focus();
313
- }
314
-
315
313
  async validate() {
316
314
  if (!this.shadowRoot) return;
317
315
  const startDateTimeInput = this.shadowRoot.querySelector('.d2l-input-date-time-range-start');
@@ -13,6 +13,7 @@ import { formatDateInISO,
13
13
  parseISODate,
14
14
  parseISODateTime,
15
15
  parseISOTime } from '../../helpers/dateTime.js';
16
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
16
17
  import { FormElementMixin } from '../form/form-element-mixin.js';
17
18
  import { getDefaultTime } from './input-time.js';
18
19
  import { getDocumentLocaleSettings } from '@brightspace-ui/intl/lib/common.js';
@@ -40,7 +41,7 @@ function _getFormattedDefaultTime(defaultValue) {
40
41
  * A component that consists of a "<d2l-input-date>" and a "<d2l-input-time>" component. The time input only appears once a date is selected. This component displays the "value" if one is specified, and reflects the selected value when one is selected or entered.
41
42
  * @fires change - Dispatched when there is a change to selected date or selected time. `value` corresponds to the selected value and is formatted in ISO 8601 combined date and time format (`YYYY-MM-DDTHH:mm:ss.sssZ`).
42
43
  */
43
- class InputDateTime extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(RtlMixin(LitElement))))) {
44
+ class InputDateTime extends FocusMixin(LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(RtlMixin(LitElement)))))) {
44
45
 
45
46
  static get properties() {
46
47
  return {
@@ -112,6 +113,8 @@ class InputDateTime extends LabelledMixin(SkeletonMixin(FormElementMixin(Localiz
112
113
  `];
113
114
  }
114
115
 
116
+ static focusElementSelector = 'd2l-input-date';
117
+
115
118
  constructor() {
116
119
  super();
117
120
  this.disabled = false;
@@ -295,11 +298,6 @@ class InputDateTime extends LabelledMixin(SkeletonMixin(FormElementMixin(Localiz
295
298
  });
296
299
  }
297
300
 
298
- focus() {
299
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-date');
300
- if (elem) elem.focus();
301
- }
302
-
303
301
  async validate() {
304
302
  if (!this.shadowRoot) return;
305
303
  const dateInput = this.shadowRoot.querySelector('d2l-input-date');
@@ -8,6 +8,7 @@ import './input-text.js';
8
8
  import { css, html, LitElement } from 'lit-element/lit-element.js';
9
9
  import { formatDate, parseDate } from '@brightspace-ui/intl/lib/dateTime.js';
10
10
  import { formatDateInISO, getDateFromISODate, getDateTimeDescriptorShared, getToday } from '../../helpers/dateTime.js';
11
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
11
12
  import { FormElementMixin } from '../form/form-element-mixin.js';
12
13
  import { getUniqueId } from '../../helpers/uniqueId.js';
13
14
  import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -27,7 +28,7 @@ export function formatISODateInUserCalDescriptor(val) {
27
28
  * A component that consists of a text input field for typing a date and an attached calendar (d2l-calendar) dropdown. It displays the "value" if one is specified, or a placeholder if not, and reflects the selected value when one is selected in the calendar or entered in the text input.
28
29
  * @fires change - Dispatched when there is a change to selected date. `value` corresponds to the selected value and is formatted in ISO 8601 calendar date format (`YYYY-MM-DD`).
29
30
  */
30
- class InputDate extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(LitElement)))) {
31
+ class InputDate extends FocusMixin(LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(LitElement))))) {
31
32
 
32
33
  static get properties() {
33
34
  return {
@@ -137,6 +138,8 @@ class InputDate extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCor
137
138
  `];
138
139
  }
139
140
 
141
+ static focusElementSelector = 'd2l-input-text';
142
+
140
143
  constructor() {
141
144
  super();
142
145
 
@@ -331,10 +334,6 @@ class InputDate extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCor
331
334
  });
332
335
  }
333
336
 
334
- focus() {
335
- if (this._textInput) this._textInput.focus();
336
- }
337
-
338
337
  async validate() {
339
338
  if (!this.shadowRoot) return;
340
339
  const textInput = this.shadowRoot.querySelector('d2l-input-text');
@@ -1,6 +1,7 @@
1
1
  import './input-text.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
3
  import { formatNumber, getNumberDescriptor, parseNumber } from '@brightspace-ui/intl/lib/number.js';
4
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
4
5
  import { FormElementMixin } from '../form/form-element-mixin.js';
5
6
  import { getUniqueId } from '../../helpers/uniqueId.js';
6
7
  import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -68,7 +69,7 @@ function roundPrecisely(val, maxFractionDigits) {
68
69
  * @slot right - Slot within the input on the right side. Useful for an "icon" or "button-icon".
69
70
  * @fires change - Dispatched when an alteration to the value is committed (typically after focus is lost) by the user. The `value` attribute reflects a JavaScript Number which is parsed from the formatted input value.
70
71
  */
71
- class InputNumber extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(LitElement)))) {
72
+ class InputNumber extends FocusMixin(LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(LitElement))))) {
72
73
 
73
74
  static get properties() {
74
75
  return {
@@ -185,6 +186,8 @@ class InputNumber extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeC
185
186
  ];
186
187
  }
187
188
 
189
+ static focusElementSelector = 'd2l-input-text';
190
+
188
191
  constructor() {
189
192
  super();
190
193
  this.autofocus = false;
@@ -364,16 +367,6 @@ class InputNumber extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeC
364
367
  });
365
368
  }
366
369
 
367
- async focus() {
368
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-text');
369
- if (elem) {
370
- elem.focus();
371
- } else {
372
- await this.updateComplete;
373
- this.focus();
374
- }
375
- }
376
-
377
370
  async validate() {
378
371
  if (!this.shadowRoot) return;
379
372
  const inputTextElem = this.shadowRoot.querySelector('d2l-input-text');
@@ -1,5 +1,6 @@
1
1
  import './input-number.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
3
4
  import { FormElementMixin } from '../form/form-element-mixin.js';
4
5
  import { ifDefined } from 'lit-html/directives/if-defined.js';
5
6
  import { LabelledMixin } from '../../mixins/labelled-mixin.js';
@@ -12,7 +13,7 @@ import { SkeletonMixin } from '../skeleton/skeleton-mixin.js';
12
13
  * @slot after - Slot beside the input on the right side. Useful for an "icon" or "button-icon".
13
14
  * @fires change - Dispatched when an alteration to the value is committed (typically after focus is lost) by the user. The `value` attribute reflects a JavaScript Number which is parsed from the formatted input value.
14
15
  */
15
- class InputPercent extends LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(RtlMixin(LitElement))))) {
16
+ class InputPercent extends FocusMixin(LabelledMixin(SkeletonMixin(FormElementMixin(LocalizeCoreElement(RtlMixin(LitElement)))))) {
16
17
 
17
18
  static get properties() {
18
19
  return {
@@ -85,6 +86,8 @@ class InputPercent extends LabelledMixin(SkeletonMixin(FormElementMixin(Localize
85
86
  ];
86
87
  }
87
88
 
89
+ static focusElementSelector = 'd2l-input-number';
90
+
88
91
  constructor() {
89
92
  super();
90
93
  this.autofocus = false;
@@ -131,11 +134,6 @@ class InputPercent extends LabelledMixin(SkeletonMixin(FormElementMixin(Localize
131
134
  `;
132
135
  }
133
136
 
134
- focus() {
135
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-number');
136
- if (elem) elem.focus();
137
- }
138
-
139
137
  async validate() {
140
138
  if (!this.shadowRoot) return;
141
139
  const inputNumberElem = this.shadowRoot.querySelector('d2l-input-number');
@@ -2,6 +2,7 @@ import '../button/button-icon.js';
2
2
  import '../colors/colors.js';
3
3
  import './input-text.js';
4
4
  import { css, html, LitElement } from 'lit-element/lit-element.js';
5
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
5
6
  import { ifDefined } from 'lit-html/directives/if-defined.js';
6
7
  import { inputStyles } from './input-styles.js';
7
8
  import { LocalizeCoreElement } from '../../lang/localize-core-element.js';
@@ -11,7 +12,7 @@ import { RtlMixin } from '../../mixins/rtl-mixin.js';
11
12
  * This component wraps the native "<input type="search">"" element and is for text searching.
12
13
  * @fires d2l-input-search-searched - Dispatched when a search is performed. When the input is cleared, this will be fired with an empty value.
13
14
  */
14
- class InputSearch extends LocalizeCoreElement(RtlMixin(LitElement)) {
15
+ class InputSearch extends FocusMixin(LocalizeCoreElement(RtlMixin(LitElement))) {
15
16
 
16
17
  static get properties() {
17
18
  return {
@@ -73,6 +74,8 @@ class InputSearch extends LocalizeCoreElement(RtlMixin(LitElement)) {
73
74
  ];
74
75
  }
75
76
 
77
+ static focusElementSelector = 'd2l-input-text';
78
+
76
79
  constructor() {
77
80
  super();
78
81
  this._lastSearchValue = '';
@@ -122,11 +125,6 @@ class InputSearch extends LocalizeCoreElement(RtlMixin(LitElement)) {
122
125
  `;
123
126
  }
124
127
 
125
- focus() {
126
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-text');
127
- if (elem) elem.focus();
128
- }
129
-
130
128
  search() {
131
129
  this._setLastSearchValue(this.value);
132
130
  this._dispatchEvent();
@@ -1,6 +1,7 @@
1
1
  import '../tooltip/tooltip.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
3
  import { classMap } from 'lit-html/directives/class-map.js';
4
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
4
5
  import { formatNumber } from '@brightspace-ui/intl/lib/number.js';
5
6
  import { FormElementMixin } from '../form/form-element-mixin.js';
6
7
  import { getUniqueId } from '../../helpers/uniqueId.js';
@@ -22,7 +23,7 @@ import { styleMap } from 'lit-html/directives/style-map.js';
22
23
  * @fires change - Dispatched when an alteration to the value is committed (typically after focus is lost) by the user
23
24
  * @fires input - Dispatched immediately after changes by the user
24
25
  */
25
- class InputText extends LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(LitElement)))) {
26
+ class InputText extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(LitElement))))) {
26
27
 
27
28
  static get properties() {
28
29
  return {
@@ -234,6 +235,8 @@ class InputText extends LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(Li
234
235
  ];
235
236
  }
236
237
 
238
+ static focusElementSelector = '.d2l-input';
239
+
237
240
  constructor() {
238
241
  super();
239
242
  this.autofocus = false;
@@ -463,16 +466,6 @@ class InputText extends LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(Li
463
466
  });
464
467
  }
465
468
 
466
- async focus() {
467
- const elem = this.shadowRoot && this.shadowRoot.querySelector('.d2l-input');
468
- if (elem) {
469
- elem.focus();
470
- } else {
471
- await this.updateComplete;
472
- this.focus();
473
- }
474
- }
475
-
476
469
  _getAriaLabel() {
477
470
  if (this.label && (this.labelHidden || this.labelledBy)) {
478
471
  return this.label;
@@ -1,5 +1,6 @@
1
1
  import '../tooltip/tooltip.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
3
4
  import { formatNumber } from '@brightspace-ui/intl/lib/number.js';
4
5
  import { FormElementMixin } from '../form/form-element-mixin.js';
5
6
  import { getUniqueId } from '../../helpers/uniqueId.js';
@@ -17,7 +18,7 @@ import { styleMap } from 'lit-html/directives/style-map.js';
17
18
  * @fires change - Dispatched when an alteration to the value is committed (typically after focus is lost) by the user
18
19
  * @fires input - Dispatched immediately after changes by the user
19
20
  */
20
- class InputTextArea extends LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(LitElement)))) {
21
+ class InputTextArea extends FocusMixin(LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixin(LitElement))))) {
21
22
 
22
23
  static get properties() {
23
24
  return {
@@ -149,6 +150,8 @@ class InputTextArea extends LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixi
149
150
  `];
150
151
  }
151
152
 
153
+ static focusElementSelector = 'textarea';
154
+
152
155
  constructor() {
153
156
  super();
154
157
  this.disabled = false;
@@ -263,16 +266,6 @@ class InputTextArea extends LabelledMixin(FormElementMixin(SkeletonMixin(RtlMixi
263
266
  });
264
267
  }
265
268
 
266
- async focus() {
267
- const elem = this.shadowRoot && this.shadowRoot.querySelector('textarea');
268
- if (elem) {
269
- elem.focus();
270
- } else {
271
- await this.updateComplete;
272
- this.focus();
273
- }
274
- }
275
-
276
269
  async select() {
277
270
  const elem = this.shadowRoot && this.shadowRoot.querySelector('textarea');
278
271
  if (elem) {
@@ -4,6 +4,7 @@ import '../tooltip/tooltip.js';
4
4
  import { css, html, LitElement } from 'lit-element/lit-element.js';
5
5
  import { formatDateInISOTime, formatTimeInISO, getAdjustedTime, getDateFromISOTime, isValidTime, parseISOTime } from '../../helpers/dateTime.js';
6
6
  import { getDefaultTime, getIntervalNumber, getTimeAtInterval } from './input-time.js';
7
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
7
8
  import { FormElementMixin } from '../form/form-element-mixin.js';
8
9
  import { getUniqueId } from '../../helpers/uniqueId.js';
9
10
  import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -38,7 +39,7 @@ function getValidISOTimeAtInterval(val, timeInterval) {
38
39
  * @fires change - Dispatched when there is a change to selected start time or selected end time. `start-value` and `end-value` correspond to the selected values and are formatted in ISO 8601 calendar time format (`hh:mm:ss`).
39
40
  */
40
41
 
41
- class InputTimeRange extends SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCoreElement(LitElement)))) {
42
+ class InputTimeRange extends FocusMixin(SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCoreElement(LitElement))))) {
42
43
 
43
44
  static get properties() {
44
45
  return {
@@ -136,6 +137,8 @@ class InputTimeRange extends SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCor
136
137
  `];
137
138
  }
138
139
 
140
+ static focusElementSelector = 'd2l-input-time';
141
+
139
142
  constructor() {
140
143
  super();
141
144
 
@@ -302,11 +305,6 @@ class InputTimeRange extends SkeletonMixin(FormElementMixin(RtlMixin(LocalizeCor
302
305
  });
303
306
  }
304
307
 
305
- focus() {
306
- const input = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-time');
307
- if (input) input.focus();
308
- }
309
-
310
308
  async validate() {
311
309
  if (!this.shadowRoot) return;
312
310
  const startTimeInput = this.shadowRoot.querySelector('.d2l-input-time-range-start');
@@ -7,6 +7,7 @@ import { css, html, LitElement } from 'lit-element/lit-element.js';
7
7
  import { formatDateInISOTime, getDateFromISOTime, getToday } from '../../helpers/dateTime.js';
8
8
  import { formatTime, parseTime } from '@brightspace-ui/intl/lib/dateTime.js';
9
9
  import { bodySmallStyles } from '../typography/styles.js';
10
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
10
11
  import { FormElementMixin } from '../form/form-element-mixin.js';
11
12
  import { getUniqueId } from '../../helpers/uniqueId.js';
12
13
  import { ifDefined } from 'lit-html/directives/if-defined.js';
@@ -116,7 +117,7 @@ function initIntervals(size, enforceTimeIntervals) {
116
117
  * A component that consists of a text input field for typing a time and an attached dropdown for time selection. It displays the "value" if one is specified, or a placeholder if not, and reflects the selected value when one is selected in the dropdown or entered in the text input.
117
118
  * @fires change - Dispatched when there is a change to selected time. `value` corresponds to the selected value and is formatted in ISO 8601 time format (`hh:mm:ss`).
118
119
  */
119
- class InputTime extends LabelledMixin(SkeletonMixin(FormElementMixin(LitElement))) {
120
+ class InputTime extends FocusMixin(LabelledMixin(SkeletonMixin(FormElementMixin(LitElement)))) {
120
121
 
121
122
  static get properties() {
122
123
  return {
@@ -214,6 +215,8 @@ class InputTime extends LabelledMixin(SkeletonMixin(FormElementMixin(LitElement)
214
215
  ];
215
216
  }
216
217
 
218
+ static focusElementSelector = '.d2l-input';
219
+
217
220
  constructor() {
218
221
  super();
219
222
  this.disabled = false;
@@ -349,11 +352,6 @@ class InputTime extends LabelledMixin(SkeletonMixin(FormElementMixin(LitElement)
349
352
  });
350
353
  }
351
354
 
352
- focus() {
353
- const elem = this.shadowRoot && this.shadowRoot.querySelector('.d2l-input');
354
- if (elem) elem.focus();
355
- }
356
-
357
355
  getTime() {
358
356
  const time = getDateFromISOTime(this.value);
359
357
  return {
@@ -1,6 +1,7 @@
1
1
  import '../colors/colors.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
3
  import { classMap } from 'lit-html/directives/class-map.js';
4
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
4
5
  import { ifDefined } from 'lit-html/directives/if-defined.js';
5
6
 
6
7
  export const linkStyles = css`
@@ -41,7 +42,7 @@ export const linkStyles = css`
41
42
  * This component can be used just like the native anchor tag.
42
43
  * @slot - The content (e.g., text) that when selected causes navigation
43
44
  */
44
- class Link extends LitElement {
45
+ class Link extends FocusMixin(LitElement) {
45
46
 
46
47
  static get properties() {
47
48
  return {
@@ -96,6 +97,8 @@ class Link extends LitElement {
96
97
  ];
97
98
  }
98
99
 
100
+ static focusElementSelector = '.d2l-link';
101
+
99
102
  constructor() {
100
103
  super();
101
104
  this.download = false;
@@ -117,9 +120,5 @@ class Link extends LitElement {
117
120
  target="${ifDefined(this.target)}"><slot></slot></a>`;
118
121
  }
119
122
 
120
- focus() {
121
- const link = this.shadowRoot && this.shadowRoot.querySelector('.d2l-link');
122
- if (link) link.focus();
123
- }
124
123
  }
125
124
  customElements.define('d2l-link', Link);
@@ -2,6 +2,7 @@ import '../button/button-subtle.js';
2
2
  import { html, LitElement } from 'lit-element/lit-element.js';
3
3
  import { DropdownOpenerMixin } from '../dropdown/dropdown-opener-mixin.js';
4
4
  import { dropdownOpenerStyles } from '../dropdown/dropdown-opener-styles.js';
5
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
5
6
  import { ifDefined } from 'lit-html/directives/if-defined.js';
6
7
  import { LocalizeCoreElement } from '../../lang/localize-core-element.js';
7
8
  import { SelectionActionMixin } from './selection-action-mixin.js';
@@ -11,7 +12,7 @@ import { SelectionActionMixin } from './selection-action-mixin.js';
11
12
  * @slot - Dropdown content (e.g., "d2l-dropdown-content", "d2l-dropdown-menu" or "d2l-dropdown-tabs")
12
13
  * @fires d2l-selection-observer-subscribe - Internal event
13
14
  */
14
- class ActionDropdown extends LocalizeCoreElement(SelectionActionMixin(DropdownOpenerMixin(LitElement))) {
15
+ class ActionDropdown extends FocusMixin(LocalizeCoreElement(SelectionActionMixin(DropdownOpenerMixin(LitElement)))) {
15
16
 
16
17
  static get properties() {
17
18
  return {
@@ -27,6 +28,8 @@ class ActionDropdown extends LocalizeCoreElement(SelectionActionMixin(DropdownOp
27
28
  return dropdownOpenerStyles;
28
29
  }
29
30
 
31
+ static focusElementSelector = 'd2l-button-subtle';
32
+
30
33
  render() {
31
34
  return html`
32
35
  <d2l-button-subtle
@@ -39,11 +42,6 @@ class ActionDropdown extends LocalizeCoreElement(SelectionActionMixin(DropdownOp
39
42
  `;
40
43
  }
41
44
 
42
- focus() {
43
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-button-subtle');
44
- if (elem) elem.focus();
45
- }
46
-
47
45
  /**
48
46
  * Gets the opener element with class "d2l-dropdown-opener" (required by dropdown-opener-mixin).
49
47
  * @return {HTMLElement}
@@ -1,6 +1,7 @@
1
1
  import '../button/button-subtle.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
3
  import { ButtonMixin } from '../button/button-mixin.js';
4
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
4
5
  import { ifDefined } from 'lit-html/directives/if-defined.js';
5
6
  import { LocalizeCoreElement } from '../../lang/localize-core-element.js';
6
7
  import { SelectionActionMixin } from './selection-action-mixin.js';
@@ -11,7 +12,7 @@ import { SelectionInfo } from './selection-mixin.js';
11
12
  * @fires d2l-selection-action-click - Dispatched when the user clicks the action button. The `SelectionInfo` is provided as the event `detail`. If `requires-selection` was specified then the event will only be dispatched if items are selected.
12
13
  * @fires d2l-selection-observer-subscribe - Internal event
13
14
  */
14
- class Action extends LocalizeCoreElement(SelectionActionMixin(ButtonMixin(LitElement))) {
15
+ class Action extends FocusMixin(LocalizeCoreElement(SelectionActionMixin(ButtonMixin(LitElement)))) {
15
16
 
16
17
  static get properties() {
17
18
  return {
@@ -39,6 +40,8 @@ class Action extends LocalizeCoreElement(SelectionActionMixin(ButtonMixin(LitEle
39
40
  `;
40
41
  }
41
42
 
43
+ static focusElementSelector = 'd2l-button-subtle';
44
+
42
45
  connectedCallback() {
43
46
  super.connectedCallback();
44
47
  this.addEventListener('d2l-button-ghost-click', this._handleActionClick);
@@ -61,11 +64,6 @@ class Action extends LocalizeCoreElement(SelectionActionMixin(ButtonMixin(LitEle
61
64
  `;
62
65
  }
63
66
 
64
- focus() {
65
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-button-subtle');
66
- if (elem) elem.focus();
67
- }
68
-
69
67
  _handleActionClick(e) {
70
68
  e.stopPropagation();
71
69
 
@@ -1,5 +1,6 @@
1
1
  import '../button/button-subtle.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
3
4
  import { LocalizeCoreElement } from '../../lang/localize-core-element.js';
4
5
  import { SelectionInfo } from './selection-mixin.js';
5
6
  import { SelectionObserverMixin } from './selection-observer-mixin.js';
@@ -8,7 +9,7 @@ import { SelectionObserverMixin } from './selection-observer-mixin.js';
8
9
  * A subtle button that selects all items for all pages.
9
10
  * @fires d2l-selection-observer-subscribe - Internal event
10
11
  */
11
- class SelectAllPages extends LocalizeCoreElement(SelectionObserverMixin(LitElement)) {
12
+ class SelectAllPages extends FocusMixin(LocalizeCoreElement(SelectionObserverMixin(LitElement))) {
12
13
 
13
14
  static get styles() {
14
15
  return css`
@@ -21,6 +22,8 @@ class SelectAllPages extends LocalizeCoreElement(SelectionObserverMixin(LitEleme
21
22
  `;
22
23
  }
23
24
 
25
+ static focusElementSelector = 'd2l-button-subtle';
26
+
24
27
  render() {
25
28
  if (!this._provider) return;
26
29
  if (!this._provider.itemCount) return;
@@ -34,11 +37,6 @@ class SelectAllPages extends LocalizeCoreElement(SelectionObserverMixin(LitEleme
34
37
  </d2l-button-subtle>`;
35
38
  }
36
39
 
37
- focus() {
38
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-button-subtle');
39
- if (elem) elem.focus();
40
- }
41
-
42
40
  _handleClick() {
43
41
  if (this._provider) this._provider.setSelectionForAll(true, true);
44
42
  }
@@ -1,5 +1,6 @@
1
1
  import '../inputs/input-checkbox.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
3
4
  import { ifDefined } from 'lit-html/directives/if-defined.js';
4
5
  import { LocalizeCoreElement } from '../../lang/localize-core-element.js';
5
6
  import { SelectionInfo } from './selection-mixin.js';
@@ -9,7 +10,7 @@ import { SelectionObserverMixin } from './selection-observer-mixin.js';
9
10
  * A checkbox that provides select-all behavior for selection components such as tables and lists.
10
11
  * @fires d2l-selection-observer-subscribe - Internal event
11
12
  */
12
- class SelectAll extends LocalizeCoreElement(SelectionObserverMixin(LitElement)) {
13
+ class SelectAll extends FocusMixin(LocalizeCoreElement(SelectionObserverMixin(LitElement))) {
13
14
 
14
15
  static get properties() {
15
16
  return {
@@ -33,6 +34,8 @@ class SelectAll extends LocalizeCoreElement(SelectionObserverMixin(LitElement))
33
34
  `;
34
35
  }
35
36
 
37
+ static focusElementSelector = 'd2l-input-checkbox';
38
+
36
39
  constructor() {
37
40
  super();
38
41
  this.disabled = false;
@@ -56,11 +59,6 @@ class SelectAll extends LocalizeCoreElement(SelectionObserverMixin(LitElement))
56
59
  `;
57
60
  }
58
61
 
59
- focus() {
60
- const elem = this.shadowRoot && this.shadowRoot.querySelector('d2l-input-checkbox');
61
- if (elem) elem.focus();
62
- }
63
-
64
62
  _handleCheckboxChange(e) {
65
63
  if (this._provider) this._provider.setSelectionForAll(e.target.checked, false);
66
64
  }
@@ -1,12 +1,13 @@
1
1
  import '../colors/colors.js';
2
2
  import '../tooltip/tooltip.js';
3
3
  import { css, html } from 'lit-element/lit-element.js';
4
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
4
5
  import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
5
6
  import { getUniqueId } from '../../helpers/uniqueId.js';
6
7
  import { ifDefined } from 'lit-html/directives/if-defined.js';
7
8
  import { RtlMixin } from '../../mixins/rtl-mixin.js';
8
9
 
9
- export const SwitchMixin = superclass => class extends RtlMixin(FocusVisiblePolyfillMixin(superclass)) {
10
+ export const SwitchMixin = superclass => class extends FocusMixin(RtlMixin(FocusVisiblePolyfillMixin(superclass))) {
10
11
 
11
12
  static get properties() {
12
13
  return {
@@ -165,6 +166,8 @@ export const SwitchMixin = superclass => class extends RtlMixin(FocusVisiblePoly
165
166
  `;
166
167
  }
167
168
 
169
+ static focusElementSelector = '.d2l-switch-container';
170
+
168
171
  constructor() {
169
172
  super();
170
173
  this.disabled = false;
@@ -212,11 +215,6 @@ export const SwitchMixin = superclass => class extends RtlMixin(FocusVisiblePoly
212
215
  `;
213
216
  }
214
217
 
215
- focus() {
216
- const elem = this.shadowRoot && this.shadowRoot.querySelector('.d2l-switch-container');
217
- if (elem) elem.focus();
218
- }
219
-
220
218
  _handleClick() {
221
219
  this._toggleState();
222
220
  }
@@ -1,11 +1,12 @@
1
1
  import '../icons/icon.js';
2
2
  import { css, html, LitElement } from 'lit-element/lit-element.js';
3
+ import { FocusMixin } from '../../mixins/focus-mixin.js';
3
4
 
4
5
  /**
5
6
  * Button for sorting a table column in ascending/descending order.
6
7
  * @slot - Text of the sort button
7
8
  */
8
- export class TableColSortButton extends LitElement {
9
+ export class TableColSortButton extends FocusMixin(LitElement) {
9
10
 
10
11
  static get properties() {
11
12
  return {
@@ -57,6 +58,8 @@ export class TableColSortButton extends LitElement {
57
58
  `;
58
59
  }
59
60
 
61
+ static focusElementSelector = 'button';
62
+
60
63
  constructor() {
61
64
  super();
62
65
  this.nosort = false;
@@ -70,11 +73,6 @@ export class TableColSortButton extends LitElement {
70
73
  return html`<button type="button"><slot></slot>${iconView}</button>`;
71
74
  }
72
75
 
73
- focus() {
74
- const button = this.shadowRoot && this.shadowRoot.querySelector('button');
75
- if (button) button.focus();
76
- }
77
-
78
76
  }
79
77
 
80
78
  customElements.define('d2l-table-col-sort-button', TableColSortButton);
@@ -408,6 +408,11 @@
408
408
  "type": "boolean",
409
409
  "default": "false"
410
410
  },
411
+ {
412
+ "name": "focusElementSelector",
413
+ "type": "string",
414
+ "default": "\"button\""
415
+ },
411
416
  {
412
417
  "name": "visibleOnAncestor",
413
418
  "type": "boolean",
@@ -502,6 +507,11 @@
502
507
  "description": "Disables the button",
503
508
  "type": "boolean",
504
509
  "default": "false"
510
+ },
511
+ {
512
+ "name": "focusElementSelector",
513
+ "type": "string",
514
+ "default": "\"button\""
505
515
  }
506
516
  ],
507
517
  "slots": [
@@ -565,6 +575,11 @@
565
575
  "description": "Disables the button",
566
576
  "type": "boolean",
567
577
  "default": "false"
578
+ },
579
+ {
580
+ "name": "focusElementSelector",
581
+ "type": "string",
582
+ "default": "\"button\""
568
583
  }
569
584
  ],
570
585
  "slots": [
@@ -979,6 +994,11 @@
979
994
  "description": "Subtle aesthetic on non-white backgrounds",
980
995
  "type": "boolean",
981
996
  "default": "false"
997
+ },
998
+ {
999
+ "name": "focusElementSelector",
1000
+ "type": "string",
1001
+ "default": "\"a\""
982
1002
  }
983
1003
  ],
984
1004
  "slots": [
@@ -3137,6 +3157,11 @@
3137
3157
  "description": "Indicates if the filter is open",
3138
3158
  "type": "boolean",
3139
3159
  "default": "false"
3160
+ },
3161
+ {
3162
+ "name": "focusElementSelector",
3163
+ "type": "string",
3164
+ "default": "\"d2l-dropdown-button-subtle\""
3140
3165
  }
3141
3166
  ],
3142
3167
  "events": [
@@ -3179,6 +3204,11 @@
3179
3204
  "description": "Whether the component should trap user focus.",
3180
3205
  "type": "boolean",
3181
3206
  "default": "false"
3207
+ },
3208
+ {
3209
+ "name": "focusElementSelector",
3210
+ "type": "string",
3211
+ "default": "\".d2l-focus-trap-start\""
3182
3212
  }
3183
3213
  ],
3184
3214
  "events": [
@@ -3759,6 +3789,11 @@
3759
3789
  "type": "string",
3760
3790
  "default": "\"on\""
3761
3791
  },
3792
+ {
3793
+ "name": "focusElementSelector",
3794
+ "type": "string",
3795
+ "default": "\"input.d2l-input-checkbox\""
3796
+ },
3762
3797
  {
3763
3798
  "name": "skeleton",
3764
3799
  "attribute": "skeleton",
@@ -3983,6 +4018,11 @@
3983
4018
  "type": "boolean",
3984
4019
  "default": "false"
3985
4020
  },
4021
+ {
4022
+ "name": "focusElementSelector",
4023
+ "type": "string",
4024
+ "default": "\"d2l-input-date\""
4025
+ },
3986
4026
  {
3987
4027
  "name": "skeleton",
3988
4028
  "attribute": "skeleton",
@@ -4280,6 +4320,11 @@
4280
4320
  "type": "boolean",
4281
4321
  "default": "false"
4282
4322
  },
4323
+ {
4324
+ "name": "focusElementSelector",
4325
+ "type": "string",
4326
+ "default": "\"d2l-input-date-time\""
4327
+ },
4283
4328
  {
4284
4329
  "name": "skeleton",
4285
4330
  "attribute": "skeleton",
@@ -4450,6 +4495,11 @@
4450
4495
  "type": "string",
4451
4496
  "default": "\"startOfDay\""
4452
4497
  },
4498
+ {
4499
+ "name": "focusElementSelector",
4500
+ "type": "string",
4501
+ "default": "\"d2l-input-date\""
4502
+ },
4453
4503
  {
4454
4504
  "name": "labelledBy",
4455
4505
  "attribute": "labelled-by",
@@ -4616,6 +4666,11 @@
4616
4666
  "type": "string",
4617
4667
  "default": "\"\""
4618
4668
  },
4669
+ {
4670
+ "name": "focusElementSelector",
4671
+ "type": "string",
4672
+ "default": "\"d2l-input-text\""
4673
+ },
4619
4674
  {
4620
4675
  "name": "labelledBy",
4621
4676
  "attribute": "labelled-by",
@@ -4955,6 +5010,11 @@
4955
5010
  "type": "boolean",
4956
5011
  "default": "false"
4957
5012
  },
5013
+ {
5014
+ "name": "focusElementSelector",
5015
+ "type": "string",
5016
+ "default": "\"d2l-input-text\""
5017
+ },
4958
5018
  {
4959
5019
  "name": "labelledBy",
4960
5020
  "attribute": "labelled-by",
@@ -5153,6 +5213,11 @@
5153
5213
  "type": "boolean",
5154
5214
  "default": "false"
5155
5215
  },
5216
+ {
5217
+ "name": "focusElementSelector",
5218
+ "type": "string",
5219
+ "default": "\"d2l-input-number\""
5220
+ },
5156
5221
  {
5157
5222
  "name": "labelledBy",
5158
5223
  "attribute": "labelled-by",
@@ -5290,6 +5355,11 @@
5290
5355
  "description": "Value of the input",
5291
5356
  "type": "string",
5292
5357
  "default": "\"\""
5358
+ },
5359
+ {
5360
+ "name": "focusElementSelector",
5361
+ "type": "string",
5362
+ "default": "\"d2l-input-text\""
5293
5363
  }
5294
5364
  ],
5295
5365
  "events": [
@@ -5629,6 +5699,11 @@
5629
5699
  "type": "'text'|'email'|'number'|'password'|'search'|'tel'|'url'",
5630
5700
  "default": "\"text\""
5631
5701
  },
5702
+ {
5703
+ "name": "focusElementSelector",
5704
+ "type": "string",
5705
+ "default": "\".d2l-input\""
5706
+ },
5632
5707
  {
5633
5708
  "name": "labelledBy",
5634
5709
  "attribute": "labelled-by",
@@ -5868,6 +5943,11 @@
5868
5943
  "type": "string",
5869
5944
  "default": "\"\""
5870
5945
  },
5946
+ {
5947
+ "name": "focusElementSelector",
5948
+ "type": "string",
5949
+ "default": "\"textarea\""
5950
+ },
5871
5951
  {
5872
5952
  "name": "labelledBy",
5873
5953
  "attribute": "labelled-by",
@@ -6117,6 +6197,11 @@
6117
6197
  "type": "'five'|'ten'|'fifteen'|'twenty'|'thirty'|'sixty'",
6118
6198
  "default": "\"thirty\""
6119
6199
  },
6200
+ {
6201
+ "name": "focusElementSelector",
6202
+ "type": "string",
6203
+ "default": "\"d2l-input-time\""
6204
+ },
6120
6205
  {
6121
6206
  "name": "skeleton",
6122
6207
  "attribute": "skeleton",
@@ -6277,6 +6362,11 @@
6277
6362
  "type": "'five'|'ten'|'fifteen'|'twenty'|'thirty'|'sixty'",
6278
6363
  "default": "\"thirty\""
6279
6364
  },
6365
+ {
6366
+ "name": "focusElementSelector",
6367
+ "type": "string",
6368
+ "default": "\".d2l-input\""
6369
+ },
6280
6370
  {
6281
6371
  "name": "labelledBy",
6282
6372
  "attribute": "labelled-by",
@@ -6393,6 +6483,11 @@
6393
6483
  "description": "Whether to apply the \"small\" link style",
6394
6484
  "type": "boolean",
6395
6485
  "default": "false"
6486
+ },
6487
+ {
6488
+ "name": "focusElementSelector",
6489
+ "type": "string",
6490
+ "default": "\".d2l-link\""
6396
6491
  }
6397
6492
  ],
6398
6493
  "slots": [
@@ -8518,6 +8613,11 @@
8518
8613
  "description": "REQUIRED: Text for the dropdown opener button",
8519
8614
  "type": "string"
8520
8615
  },
8616
+ {
8617
+ "name": "focusElementSelector",
8618
+ "type": "string",
8619
+ "default": "\"d2l-button-subtle\""
8620
+ },
8521
8621
  {
8522
8622
  "name": "requiresSelection",
8523
8623
  "attribute": "requires-selection",
@@ -8751,6 +8851,11 @@
8751
8851
  "description": "Disables the button",
8752
8852
  "type": "boolean",
8753
8853
  "default": "false"
8854
+ },
8855
+ {
8856
+ "name": "focusElementSelector",
8857
+ "type": "string",
8858
+ "default": "\"d2l-button-subtle\""
8754
8859
  }
8755
8860
  ],
8756
8861
  "events": [
@@ -8870,6 +8975,11 @@
8870
8975
  }
8871
8976
  ],
8872
8977
  "properties": [
8978
+ {
8979
+ "name": "focusElementSelector",
8980
+ "type": "string",
8981
+ "default": "\"d2l-button-subtle\""
8982
+ },
8873
8983
  {
8874
8984
  "name": "selectionFor",
8875
8985
  "attribute": "selection-for",
@@ -8912,6 +9022,11 @@
8912
9022
  "type": "boolean",
8913
9023
  "default": "false"
8914
9024
  },
9025
+ {
9026
+ "name": "focusElementSelector",
9027
+ "type": "string",
9028
+ "default": "\"d2l-input-checkbox\""
9029
+ },
8915
9030
  {
8916
9031
  "name": "selectionFor",
8917
9032
  "attribute": "selection-for",
@@ -9334,6 +9449,11 @@
9334
9449
  "description": "Determines where text should be positioned relative to the switch.",
9335
9450
  "type": "'start'|'end'|'hidden'",
9336
9451
  "default": "\"end\""
9452
+ },
9453
+ {
9454
+ "name": "focusElementSelector",
9455
+ "type": "string",
9456
+ "default": "\".d2l-switch-container\""
9337
9457
  }
9338
9458
  ],
9339
9459
  "events": [
@@ -9423,6 +9543,11 @@
9423
9543
  "description": "Determines where text should be positioned relative to the switch.",
9424
9544
  "type": "'start'|'end'|'hidden'",
9425
9545
  "default": "\"end\""
9546
+ },
9547
+ {
9548
+ "name": "focusElementSelector",
9549
+ "type": "string",
9550
+ "default": "\".d2l-switch-container\""
9426
9551
  }
9427
9552
  ],
9428
9553
  "events": [
@@ -9516,6 +9641,11 @@
9516
9641
  "description": "Whether sort direction is descending",
9517
9642
  "type": "boolean",
9518
9643
  "default": "false"
9644
+ },
9645
+ {
9646
+ "name": "focusElementSelector",
9647
+ "type": "string",
9648
+ "default": "\"button\""
9519
9649
  }
9520
9650
  ],
9521
9651
  "slots": [
@@ -0,0 +1,41 @@
1
+ import { dedupeMixin } from '@open-wc/dedupe-mixin';
2
+
3
+ export const FocusMixin = dedupeMixin(superclass => class extends superclass {
4
+
5
+ static focusElementSelector = null;
6
+
7
+ constructor() {
8
+ super();
9
+ this._focusOnFirstRender = false;
10
+ }
11
+
12
+ firstUpdated(changedProperties) {
13
+ super.firstUpdated(changedProperties);
14
+ if (this._focusOnFirstRender) {
15
+ this._focusOnFirstRender = false;
16
+ this.focus();
17
+ }
18
+ }
19
+
20
+ focus() {
21
+
22
+ const selector = this.constructor.focusElementSelector;
23
+ if (!selector) {
24
+ throw new Error(`FocusMixin: no static focusElementSelector provided for "${this.tagName}"`);
25
+ }
26
+
27
+ if (!this.hasUpdated) {
28
+ this._focusOnFirstRender = true;
29
+ return;
30
+ }
31
+
32
+ const elem = this.shadowRoot.querySelector(selector);
33
+ if (!elem) {
34
+ throw new Error(`FocusMixin: selector "${selector}" yielded no element for "${this.tagName}"`);
35
+ }
36
+
37
+ elem.focus();
38
+
39
+ }
40
+
41
+ });
@@ -0,0 +1,24 @@
1
+ # FocusMixin
2
+
3
+ The `FocusMixin` can be used to delegate focus to an element within a component's shadow root when its `focus()` method is called.
4
+
5
+ If the component has yet to render, focus will automatically be applied after `firstUpdated`.
6
+
7
+ ## Usage
8
+
9
+ Apply the mixin and set the static `focusElementSelector` to a CSS query selector which will match the element to which focus should be delegated.
10
+
11
+ ```js
12
+ import { FocusMixin } from '@brightspace-ui/core/mixins/focus-mixin.js';
13
+
14
+ class MyComponent extends FocusMixin(LitElement) {
15
+
16
+ // delegate focus to the underlying input
17
+ static focusElementSelector = 'input';
18
+
19
+ render() {
20
+ return html`<input type="text">`;
21
+ }
22
+
23
+ }
24
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "1.235.3",
3
+ "version": "1.236.0",
4
4
  "description": "A collection of accessible, free, open-source web components for building Brightspace applications",
5
5
  "type": "module",
6
6
  "repository": "https://github.com/BrightspaceUI/core.git",