@brightspace-ui/core 2.93.3 → 2.94.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
@@ -68,7 +68,6 @@ npm install @brightspace-ui/core
68
68
  * [ArrowKeysMixin](mixins/arrow-keys-mixin.md): manage focus with arrow keys
69
69
  * [AsyncContainerMixin](mixins/async-container/): manage collective async state
70
70
  * [FocusMixin](mixins/focus-mixin.md): delegate focus to a nested element when `focus()` is called
71
- * [FocusVisiblePolyfillMixin](mixins/focus-visible-polyfill-mixin.md): components can use the `:focus-visible` pseudo-class polyfill
72
71
  * [FormElementMixin](components/form/docs/form-element-mixin.md): allow components to participate in forms and validation
73
72
  * [InteractiveMixin](mixins/interactive-mixin.md): enables toggling interactive elements inside of nested grids
74
73
  * [LabelledMixin](mixins/labelled-mixin.md): label custom elements by referencing elements across DOM scopes
@@ -1,7 +1,8 @@
1
1
  import '../colors/colors.js';
2
2
  import '../icons/icon.js';
3
- import { css, html, LitElement } from 'lit';
3
+ import { css, html, LitElement, unsafeCSS } from 'lit';
4
4
  import { CountBadgeMixin } from './count-badge-mixin.js';
5
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
5
6
  import { getUniqueId } from '../../helpers/uniqueId.js';
6
7
  import { ifDefined } from 'lit/directives/if-defined.js';
7
8
 
@@ -22,9 +23,11 @@ class CountBadgeIcon extends CountBadgeMixin(LitElement) {
22
23
 
23
24
  static get styles() {
24
25
  return [super.styles, css`
25
- :host(.focus-visible) d2l-icon,
26
26
  :host([focus-ring]) d2l-icon,
27
- d2l-icon.focus-visible {
27
+ :host(.focus-visible) d2l-icon,
28
+ d2l-icon.focus-visible,
29
+ :host(:${unsafeCSS(getFocusPseudoClass())}) d2l-icon,
30
+ d2l-icon:${unsafeCSS(getFocusPseudoClass())} {
28
31
  box-shadow: 0 0 0 2px var(--d2l-color-celestine);
29
32
  outline: none;
30
33
  }
@@ -105,8 +108,8 @@ class CountBadgeIcon extends CountBadgeMixin(LitElement) {
105
108
  ${this.renderCount(numberStyles)}
106
109
  <div class="d2l-skeletize d2l-count-badge-wrapper">
107
110
  <d2l-icon id="${this._badgeId}"
108
- icon="${this.icon}"
109
- tabindex="${ifDefined(tabbable ? '0' : undefined)}"
111
+ icon="${this.icon}"
112
+ tabindex="${ifDefined(tabbable ? '0' : undefined)}"
110
113
  aria-labelledby="${ifDefined(this.getAriaLabelId())}"
111
114
  role="img">
112
115
  </d2l-icon>
@@ -1,6 +1,7 @@
1
1
  import '../colors/colors.js';
2
- import { css, html, LitElement } from 'lit';
2
+ import { css, html, LitElement, unsafeCSS } from 'lit';
3
3
  import { CountBadgeMixin } from './count-badge-mixin.js';
4
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
4
5
  import { getUniqueId } from '../../helpers/uniqueId.js';
5
6
  import { ifDefined } from 'lit/directives/if-defined.js';
6
7
 
@@ -8,9 +9,11 @@ class CountBadge extends CountBadgeMixin(LitElement) {
8
9
 
9
10
  static get styles() {
10
11
  return [super.styles, css`
11
- :host(.focus-visible) .d2l-count-badge-wrapper,
12
12
  :host([focus-ring]) .d2l-count-badge-wrapper,
13
- .d2l-count-badge-wrapper.focus-visible {
13
+ :host(.focus-visible) .d2l-count-badge-wrapper,
14
+ .d2l-count-badge-wrapper.focus-visible,
15
+ :host(:${unsafeCSS(getFocusPseudoClass())}) .d2l-count-badge-wrapper,
16
+ .d2l-count-badge-wrapper:${unsafeCSS(getFocusPseudoClass())} {
14
17
  box-shadow: 0 0 0 2px var(--d2l-color-celestine);
15
18
  }
16
19
 
@@ -22,7 +25,7 @@ class CountBadge extends CountBadgeMixin(LitElement) {
22
25
  border-radius: 0.65rem;
23
26
  outline: none;
24
27
  }
25
-
28
+
26
29
  :host([size="large"]) .d2l-count-badge-wrapper {
27
30
  border-radius: 0.8rem;
28
31
  outline: none;
@@ -38,10 +41,10 @@ class CountBadge extends CountBadgeMixin(LitElement) {
38
41
  render() {
39
42
  const tabbable = (this.tabStop || this.hasTooltip) && !(this.hideZero && this.number === 0) && !this.skeleton;
40
43
  const innerHtml = html`
41
- <div
44
+ <div
42
45
  class="d2l-count-badge-wrapper d2l-skeletize"
43
46
  id="${this._badgeId}"
44
- tabindex="${ifDefined(tabbable ? '0' : undefined)}"
47
+ tabindex="${ifDefined(tabbable ? '0' : undefined)}"
45
48
  aria-labelledby="${ifDefined(this.getAriaLabelId())}"
46
49
  role="img">
47
50
  ${this.renderCount()}
@@ -3,7 +3,7 @@ import '../../helpers/viewport-size.js';
3
3
  import { allowBodyScroll, preventBodyScroll } from '../backdrop/backdrop.js';
4
4
  import { clearDismissible, setDismissible } from '../../helpers/dismissible.js';
5
5
  import { findComposedAncestor, isComposedAncestor } from '../../helpers/dom.js';
6
- import { forceFocusVisible, getComposedActiveElement, getNextFocusable, tryApplyFocus } from '../../helpers/focus.js';
6
+ import { forceFocusVisible, getComposedActiveElement, getNextFocusable, isFocusVisibleApplied, tryApplyFocus } from '../../helpers/focus.js';
7
7
  import { classMap } from 'lit/directives/class-map.js';
8
8
  import { getUniqueId } from '../../helpers/uniqueId.js';
9
9
  import { html } from 'lit';
@@ -411,7 +411,7 @@ export const DialogMixin = superclass => class extends RtlMixin(superclass) {
411
411
  const activeElement = getComposedActiveElement();
412
412
  if (!activeElement
413
413
  || !isComposedAncestor(dialog, activeElement)
414
- || !activeElement.classList.contains('focus-visible')) {
414
+ || !isFocusVisibleApplied(activeElement)) {
415
415
  // wait till the dialog is visible for Safari
416
416
  requestAnimationFrame(() => this._focusInitial());
417
417
  }
@@ -101,6 +101,7 @@ export const htmlBlockContentStyles = css`
101
101
  color: var(--d2l-color-celestine-minus-1, #004489);
102
102
  text-decoration: underline;
103
103
  }
104
+ a.focus-visible,
104
105
  a:${unsafeCSS(getFocusPseudoClass())} {
105
106
  border-radius: 2px;
106
107
  outline: 2px solid var(--d2l-color-celestine, #006fbf);
@@ -85,4 +85,4 @@ To make your usage of `d2l-input-search` accessible, use the following property
85
85
  | Attribute | Description |
86
86
  |---|---|
87
87
  | `description` | Use when label on input does not provide enough context. |
88
- | label | **REQUIRED** [Acts as a primary label on the input](https://www.w3.org/WAI/tutorials/forms/labels/). Not visible. |
88
+ | `label` | **REQUIRED** [Acts as a primary label on the input](https://www.w3.org/WAI/tutorials/forms/labels/). Not visible. |
@@ -20,6 +20,7 @@ export const linkStyles = css`
20
20
  color: var(--d2l-color-celestine-minus-1);
21
21
  text-decoration: underline;
22
22
  }
23
+ .d2l-link.focus-visible,
23
24
  .d2l-link:${unsafeCSS(getFocusPseudoClass())} {
24
25
  border-radius: 2px;
25
26
  outline: 2px solid var(--d2l-color-celestine);
@@ -21,6 +21,8 @@ export const menuItemStyles = css`
21
21
  color: var(--d2l-menu-foreground-color-hover);
22
22
  }
23
23
 
24
+ :host(.focus-visible),
25
+ :host([first].focus-visible),
24
26
  :host(:${unsafeCSS(getFocusPseudoClass())}),
25
27
  :host([first]:${unsafeCSS(getFocusPseudoClass())}) {
26
28
  border-radius: 6px;
@@ -36,6 +38,7 @@ export const menuItemStyles = css`
36
38
  opacity: 0.75;
37
39
  }
38
40
 
41
+ :host([disabled].focus-visible),
39
42
  :host([disabled]:${unsafeCSS(getFocusPseudoClass())}) {
40
43
  cursor: default;
41
44
  opacity: 0.75;
@@ -4,7 +4,6 @@ import { css, html, LitElement, nothing } from 'lit';
4
4
  import { buttonStyles } from '../button/button-styles.js';
5
5
  import { findComposedAncestor } from '../../helpers/dom.js';
6
6
  import { FocusMixin } from '../../mixins/focus-mixin.js';
7
- import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
8
7
  import { formatNumber } from '@brightspace-ui/intl/lib/number.js';
9
8
  import { getFirstFocusableDescendant } from '../../helpers/focus.js';
10
9
  import { getSeparator } from '@brightspace-ui/intl/lib/list.js';
@@ -18,7 +17,7 @@ const nativeFocus = document.createElement('div').focus;
18
17
  * A pager component for load-more paging.
19
18
  * @fires d2l-pager-load-more - Dispatched when the user clicks the load-more button. Consumers must call the provided "complete" method once items have been loaded.
20
19
  */
21
- class LoadMore extends FocusMixin(FocusVisiblePolyfillMixin(LocalizeCoreElement(LitElement))) {
20
+ class LoadMore extends FocusMixin(LocalizeCoreElement(LitElement)) {
22
21
 
23
22
  static get properties() {
24
23
  return {
@@ -1,7 +1,7 @@
1
1
  import '../colors/colors.js';
2
2
  import '../icons/icon.js';
3
- import { css, html, LitElement } from 'lit';
4
- import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
3
+ import { css, html, LitElement, unsafeCSS } from 'lit';
4
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
5
5
  import { ifDefined } from 'lit/directives/if-defined.js';
6
6
  import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
7
7
  import { RtlMixin } from '../../mixins/rtl-mixin.js';
@@ -14,7 +14,7 @@ const SCROLL_AMOUNT = 0.8;
14
14
  * Wraps content which may overflow its horizontal boundaries, providing left/right scroll buttons.
15
15
  * @slot - User provided content to wrap
16
16
  */
17
- class ScrollWrapper extends FocusVisiblePolyfillMixin(RtlMixin(LitElement)) {
17
+ class ScrollWrapper extends RtlMixin(LitElement) {
18
18
 
19
19
  static get properties() {
20
20
  return {
@@ -59,7 +59,8 @@ class ScrollWrapper extends FocusVisiblePolyfillMixin(RtlMixin(LitElement)) {
59
59
  overflow-x: auto;
60
60
  overflow-y: var(--d2l-scroll-wrapper-overflow-y, visible);
61
61
  }
62
- .d2l-scroll-wrapper-container.focus-visible {
62
+ .d2l-scroll-wrapper-container.focus-visible,
63
+ .d2l-scroll-wrapper-container:${unsafeCSS(getFocusPseudoClass())} {
63
64
  box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine), 0 2px 12px 0 rgba(0, 0, 0, 0.15);
64
65
  }
65
66
  :host([h-scrollbar]) .d2l-scroll-wrapper-container {
@@ -1,13 +1,13 @@
1
1
  import '../colors/colors.js';
2
- import { css, html } from 'lit';
2
+ import { css, html, unsafeCSS } from 'lit';
3
3
  import { classMap } from 'lit/directives/class-map.js';
4
4
  import { FocusMixin } from '../../mixins/focus-mixin.js';
5
- import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
5
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
6
6
  import { getUniqueId } from '../../helpers/uniqueId.js';
7
7
  import { ifDefined } from 'lit/directives/if-defined.js';
8
8
  import { RtlMixin } from '../../mixins/rtl-mixin.js';
9
9
 
10
- export const SwitchMixin = superclass => class extends FocusMixin(RtlMixin(FocusVisiblePolyfillMixin(superclass))) {
10
+ export const SwitchMixin = superclass => class extends FocusMixin(RtlMixin(superclass)) {
11
11
 
12
12
  static get properties() {
13
13
  return {
@@ -59,7 +59,8 @@ export const SwitchMixin = superclass => class extends FocusMixin(RtlMixin(Focus
59
59
  padding: 0.1rem;
60
60
  vertical-align: middle;
61
61
  }
62
- .d2l-switch-container.focus-visible {
62
+ .d2l-switch-container.focus-visible,
63
+ .d2l-switch-container:${unsafeCSS(getFocusPseudoClass())} {
63
64
  border-color: var(--d2l-color-celestine);
64
65
  }
65
66
  :host([disabled]) .d2l-switch-container {
@@ -221,9 +222,9 @@ export const SwitchMixin = superclass => class extends FocusMixin(RtlMixin(Focus
221
222
  }
222
223
 
223
224
  get _labelContent() {
224
- return html`<span
225
- @click='${this._handleClick}'
226
- @mouseenter='${this._handleSwitchHover}'
225
+ return html`<span
226
+ @click='${this._handleClick}'
227
+ @mouseenter='${this._handleSwitchHover}'
227
228
  @mouseleave='${this._handleSwitchHoverLeave}'>
228
229
  ${this.text}
229
230
  </span>`;
@@ -1,7 +1,8 @@
1
1
  import '../colors/colors.js';
2
2
  import '../icons/icon.js';
3
- import { css, html, LitElement } from 'lit';
3
+ import { css, html, LitElement, unsafeCSS } from 'lit';
4
4
  import { FocusMixin } from '../../mixins/focus-mixin.js';
5
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
5
6
 
6
7
  /**
7
8
  * Button for sorting a table column in ascending/descending order.
@@ -55,7 +56,8 @@ export class TableColSortButton extends FocusMixin(LitElement) {
55
56
  button:hover {
56
57
  text-decoration: underline;
57
58
  }
58
- button:focus-visible {
59
+ button:focus-visible,
60
+ button:${unsafeCSS(getFocusPseudoClass())} {
59
61
  border-radius: 0.2rem;
60
62
  box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine);
61
63
  outline-style: none;
@@ -1,5 +1,6 @@
1
1
  import '../colors/colors.js';
2
- import { css, html, LitElement } from 'lit';
2
+ import { css, html, LitElement, unsafeCSS } from 'lit';
3
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
3
4
  import { RtlMixin } from '../../mixins/rtl-mixin.js';
4
5
 
5
6
  const keyCodes = {
@@ -62,7 +63,8 @@ class Tab extends RtlMixin(LitElement) {
62
63
  margin-left: 0.6rem;
63
64
  margin-right: 0;
64
65
  }
65
- :host(.focus-visible) > .d2l-tab-text {
66
+ :host(.focus-visible) > .d2l-tab-text,
67
+ :host(:${unsafeCSS(getFocusPseudoClass())}) > .d2l-tab-text {
66
68
  border-radius: 0.3rem;
67
69
  box-shadow: 0 0 0 2px var(--d2l-color-celestine);
68
70
  color: var(--d2l-color-celestine);
@@ -2,12 +2,12 @@ import '../colors/colors.js';
2
2
  import '../icons/icon.js';
3
3
  import '../../helpers/queueMicrotask.js';
4
4
  import './tab-internal.js';
5
- import { css, html, LitElement } from 'lit';
5
+ import { css, html, LitElement, unsafeCSS } from 'lit';
6
6
  import { cssEscape, findComposedAncestor } from '../../helpers/dom.js';
7
7
  import { ArrowKeysMixin } from '../../mixins/arrow-keys-mixin.js';
8
8
  import { bodyCompactStyles } from '../typography/styles.js';
9
9
  import { classMap } from 'lit/directives/class-map.js';
10
- import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
10
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
11
11
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
12
12
  import { repeat } from 'lit/directives/repeat.js';
13
13
  import ResizeObserver from 'resize-observer-polyfill/dist/ResizeObserver.es.js';
@@ -52,7 +52,7 @@ if (!Array.prototype.findIndex) {
52
52
  * @slot ext - Additional content (e.g., a button) positioned at right
53
53
  * @fires d2l-tabs-initialized - Dispatched when the component is initialized
54
54
  */
55
- class Tabs extends LocalizeCoreElement(ArrowKeysMixin(RtlMixin(FocusVisiblePolyfillMixin(LitElement)))) {
55
+ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(RtlMixin(LitElement))) {
56
56
 
57
57
  static get properties() {
58
58
  return {
@@ -182,14 +182,17 @@ class Tabs extends LocalizeCoreElement(ArrowKeysMixin(RtlMixin(FocusVisiblePolyf
182
182
  border: 0;
183
183
  }
184
184
  .d2l-tabs-scroll-button[disabled]:hover,
185
- .d2l-tabs-scroll-button[disabled].focus-visible {
185
+ .d2l-tabs-scroll-button[disabled].focus-visible,
186
+ .d2l-tabs-scroll-button[disabled]:${unsafeCSS(getFocusPseudoClass())} {
186
187
  background-color: transparent;
187
188
  }
188
189
  .d2l-tabs-scroll-button:hover,
189
- .d2l-tabs-scroll-button.focus-visible {
190
+ .d2l-tabs-scroll-button.focus-visible,
191
+ .d2l-tabs-scroll-button:${unsafeCSS(getFocusPseudoClass())} {
190
192
  background-color: var(--d2l-color-gypsum);
191
193
  }
192
- .d2l-tabs-scroll-button.focus-visible {
194
+ .d2l-tabs-scroll-button.focus-visible,
195
+ .d2l-tabs-scroll-button:${unsafeCSS(getFocusPseudoClass())} {
193
196
  box-shadow: 0 0 0 2px #ffffff, 0 0 0 4px var(--d2l-color-celestine);
194
197
  }
195
198
  .d2l-panels-container-no-whitespace ::slotted(*) {
@@ -1,10 +1,10 @@
1
1
  import '../colors/colors.js';
2
2
  import '../tooltip/tooltip.js';
3
- import { css, html, LitElement, nothing } from 'lit';
3
+ import { css, html, LitElement, nothing, unsafeCSS } from 'lit';
4
4
  import { bodySmallStyles } from '../typography/styles.js';
5
5
  import { classMap } from 'lit/directives/class-map.js';
6
6
  import { FocusMixin } from '../../mixins/focus-mixin.js';
7
- import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
7
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
8
8
  import { ifDefined } from 'lit/directives/if-defined.js';
9
9
  import { SkeletonMixin } from '../skeleton/skeleton-mixin.js';
10
10
 
@@ -12,7 +12,7 @@ import { SkeletonMixin } from '../skeleton/skeleton-mixin.js';
12
12
  * A component used to display additional information when users focus or hover over some text.
13
13
  * @slot - Default content placed inside of the tooltip
14
14
  */
15
- class TooltipHelp extends SkeletonMixin(FocusMixin(FocusVisiblePolyfillMixin(LitElement))) {
15
+ class TooltipHelp extends SkeletonMixin(FocusMixin(LitElement)) {
16
16
 
17
17
  static get properties() {
18
18
  return {
@@ -60,7 +60,8 @@ class TooltipHelp extends SkeletonMixin(FocusMixin(FocusVisiblePolyfillMixin(Lit
60
60
  #d2l-tooltip-help-text:focus {
61
61
  outline-style: none;
62
62
  }
63
- #d2l-tooltip-help-text.focus-visible {
63
+ #d2l-tooltip-help-text.focus-visible,
64
+ #d2l-tooltip-help-text:${unsafeCSS(getFocusPseudoClass())} {
64
65
  border-radius: 0.05rem;
65
66
  outline: 2px solid var(--d2l-color-celestine);
66
67
  outline-offset: 0.05rem;
@@ -1,5 +1,5 @@
1
1
  import { directive, PartType } from 'lit/directive.js';
2
- import { getComposedActiveElement, getNextFocusable } from '../../helpers/focus.js';
2
+ import { getComposedActiveElement, getNextFocusable, isFocusVisibleApplied } from '../../helpers/focus.js';
3
3
  import { AsyncDirective } from 'lit/async-directive.js';
4
4
  import { isComposedAncestor } from '../../helpers/dom.js';
5
5
  import { noChange } from 'lit';
@@ -180,7 +180,7 @@ class AnimationState {
180
180
 
181
181
  // if focus is inside and was placed by keyboard, move it to next focusable
182
182
  const activeElem = getComposedActiveElement();
183
- if (activeElem && activeElem.classList.contains('focus-visible')) {
183
+ if (activeElem && isFocusVisibleApplied(activeElem)) {
184
184
  const focusIsInside = isComposedAncestor(this.elem, activeElem);
185
185
  if (focusIsInside) {
186
186
  const nextFocusable = getNextFocusable(activeElem);
package/helpers/README.md CHANGED
@@ -130,6 +130,10 @@ getComposedActiveElement()
130
130
  getFirstFocusableDescendant(node, includeHidden, predicate, includeTabbablesOnly)
131
131
 
132
132
  // gets the focus pseudo-class to used in selectors (focus-visible if supported, or focus)
133
+ // Always also include `.focus-visible` as a selector, to support `forceFocusVisible`. Usage:
134
+ // css`
135
+ // some-element.focus-visible, some-element:${unsafeCSS(getFocusPseudoClass())} { ... }
136
+ // `
133
137
  getFocusPseudoClass()
134
138
 
135
139
  // gets the last focusable descendant given a node, including those within the shadow DOM
@@ -147,6 +151,9 @@ getPreviousFocusableAncestor(node, includeHidden, includeTabbablesOnly)
147
151
  // returns true/false whether the element is focusable
148
152
  isFocusable(node, includeHidden, includeTabbablesOnly, includeDisabled)
149
153
 
154
+ // returns true/false whether the element has :focus-visible or .focus-visible applied
155
+ isFocusVisibleApplied(node)
156
+
150
157
  // returns true/false whether the :focus-visible is supported
151
158
  isFocusVisibleSupported()
152
159
 
package/helpers/focus.js CHANGED
@@ -186,6 +186,12 @@ export function isFocusable(node, includeHidden, includeTabbablesOnly, includeDi
186
186
 
187
187
  }
188
188
 
189
+ export function isFocusVisibleApplied(node) {
190
+ if (!node) return false;
191
+ if (node.classList.contains('focus-visible')) return true;
192
+ return isFocusVisibleSupported() && node.parentNode?.querySelector(':focus-visible') === node;
193
+ }
194
+
189
195
  let _isFocusVisibleSupported;
190
196
 
191
197
  export function isFocusVisibleSupported() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brightspace-ui/core",
3
- "version": "2.93.3",
3
+ "version": "2.94.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",
@@ -71,7 +71,6 @@
71
71
  "@brightspace-ui/intl": "^3",
72
72
  "@formatjs/intl-pluralrules": "^1",
73
73
  "@open-wc/dedupe-mixin": "^1",
74
- "focus-visible": "^5",
75
74
  "ifrau": "^0.40",
76
75
  "intl-messageformat": "^7",
77
76
  "lit": "^2",
@@ -2,9 +2,9 @@ import '../../components/colors/colors.js';
2
2
  import '../../components/icons/icon-custom.js';
3
3
  import '../../components/icons/icon.js';
4
4
  import '../../components/offscreen/offscreen.js';
5
- import { css, html, LitElement } from 'lit';
5
+ import { css, html, LitElement, unsafeCSS } from 'lit';
6
6
  import { classMap } from 'lit/directives/class-map.js';
7
- import { FocusVisiblePolyfillMixin } from '../../mixins/focus-visible-polyfill-mixin.js';
7
+ import { getFocusPseudoClass } from '../../helpers/focus.js';
8
8
  import { getUniqueId } from '../../helpers/uniqueId.js';
9
9
  import { ifDefined } from 'lit/directives/if-defined.js';
10
10
  import { LocalizeCoreElement } from '../../helpers/localize-core-element.js';
@@ -536,7 +536,7 @@ class MobileTouchResizer extends Resizer {
536
536
  * @fires d2l-template-primary-secondary-resize-start - Dispatched when a user begins moving the divider.
537
537
  * @fires d2l-template-primary-secondary-resize-end - Dispatched when a user finishes moving the divider.
538
538
  */
539
- class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(LocalizeCoreElement(LitElement))) {
539
+ class TemplatePrimarySecondary extends RtlMixin(LocalizeCoreElement(LitElement)) {
540
540
 
541
541
  static get properties() {
542
542
  return {
@@ -743,7 +743,9 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
743
743
  width: 0.1rem;
744
744
  }
745
745
  .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-right,
746
- .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-left {
746
+ .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-left,
747
+ .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} .d2l-template-primary-secondary-divider-handle-right,
748
+ .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} .d2l-template-primary-secondary-divider-handle-left {
747
749
  display: block;
748
750
  }
749
751
  :host(:not([dir="rtl"]):not([secondary-first])) [data-is-expanded] .d2l-template-primary-secondary-divider-handle-left,
@@ -776,7 +778,9 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
776
778
  box-shadow: none;
777
779
  }
778
780
  :host([resizable]) .d2l-template-primary-secondary-divider.focus-visible,
779
- :host([resizable][dir="rtl"]) .d2l-template-primary-secondary-divider.focus-visible {
781
+ :host([resizable][dir="rtl"]) .d2l-template-primary-secondary-divider.focus-visible,
782
+ :host([resizable]) .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())},
783
+ :host([resizable][dir="rtl"]) .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} {
780
784
  background-color: var(--d2l-color-celestine);
781
785
  }
782
786
  .d2l-template-primary-secondary-divider:focus .d2l-template-primary-secondary-divider-handle-line::before,
@@ -786,7 +790,9 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
786
790
  background-color: var(--d2l-color-ferrite);
787
791
  }
788
792
  .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-line::before,
789
- .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-line::after {
793
+ .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-line::after,
794
+ .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} .d2l-template-primary-secondary-divider-handle-line::before,
795
+ .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} .d2l-template-primary-secondary-divider-handle-line::after {
790
796
  background-color: white;
791
797
  }
792
798
 
@@ -913,13 +919,15 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
913
919
  height: 1rem;
914
920
  width: 2.2rem;
915
921
  }
916
- .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle {
922
+ .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle,
923
+ .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} .d2l-template-primary-secondary-divider-handle {
917
924
  box-shadow: none;
918
925
  height: 1.2rem;
919
926
  right: 17px;
920
927
  width: 2.6rem;
921
928
  }
922
- :host([dir="rtl"]) .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle {
929
+ :host([dir="rtl"]) .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle,
930
+ :host([dir="rtl"]) .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} .d2l-template-primary-secondary-divider-handle {
923
931
  left: 17px;
924
932
  right: auto;
925
933
  }
@@ -927,7 +935,8 @@ class TemplatePrimarySecondary extends FocusVisiblePolyfillMixin(RtlMixin(Locali
927
935
  color: white;
928
936
  display: block;
929
937
  }
930
- .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-mobile {
938
+ .d2l-template-primary-secondary-divider.focus-visible .d2l-template-primary-secondary-divider-handle-mobile,
939
+ .d2l-template-primary-secondary-divider:${unsafeCSS(getFocusPseudoClass())} .d2l-template-primary-secondary-divider-handle-mobile {
931
940
  box-shadow: 0 0 0 0.1rem white, 0 0 0 0.2rem var(--d2l-color-celestine);
932
941
  right: 0.2rem;
933
942
  }
@@ -0,0 +1,12 @@
1
+ import { resetMouse, sendKeys, sendMouse } from '@web/test-runner-commands';
2
+
3
+ export const focusWithKeyboard = async(element) => {
4
+ await sendKeys({ press: 'Tab' });
5
+ element.focus({ focusVisible: true });
6
+ };
7
+
8
+ export const focusWithMouse = async(element) => {
9
+ const { x, y } = element.getBoundingClientRect();
10
+ await sendMouse({ type: 'click', position: [Math.ceil(x), Math.ceil(y)] });
11
+ await resetMouse();
12
+ };
@@ -1,29 +0,0 @@
1
- let polyfillLoading = false;
2
-
3
- export const FocusVisiblePolyfillMixin = superclass => class extends superclass {
4
-
5
- connectedCallback() {
6
-
7
- super.connectedCallback();
8
-
9
- if (!this.shadowRoot) return;
10
-
11
- if (window.applyFocusVisiblePolyfill) {
12
- window.applyFocusVisiblePolyfill(this.shadowRoot);
13
- return;
14
- }
15
-
16
- window.addEventListener('focus-visible-polyfill-ready', () => {
17
- // somehow it's occasionally still not available
18
- if (this.shadowRoot && window.applyFocusVisiblePolyfill) {
19
- window.applyFocusVisiblePolyfill(this.shadowRoot);
20
- }
21
- }, { once: true });
22
-
23
- if (!polyfillLoading) {
24
- polyfillLoading = true;
25
- import('focus-visible');
26
- }
27
-
28
- }
29
- };
@@ -1,50 +0,0 @@
1
- # FocusVisiblePolyfillMixin
2
-
3
- From [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/:focus-visible):
4
-
5
- > The `:focus-visible` pseudo-class applies while an element matches the `:focus` pseudo-class and the user agent determines via heuristics that the focus should be made evident on the element.
6
-
7
- In order words, it allows you to provide focus styles for an element that only get applied when the user accessed it via keyboard. But today only Chrome supports `:focus-visible`, so a polyfill is required.
8
-
9
- The `FocusVisiblePolyfillMixin` adds support to a component to use the [focus-visible polyfill](https://github.com/WICG/focus-visible), ensuring that the polyfill is lazy loaded only once and that it's properly applied to the component's shadow root.
10
-
11
- ## Usage
12
-
13
- Apply the mixin and target the `focus-visible` CSS class for styles you'd like to apply when focus should be visible.
14
-
15
- ```js
16
- import { FocusVisiblePolyfillMixin } from '@brightspace-ui/core/mixins/focus-visible-polyfill-mixin.js';
17
-
18
- class MyComponent extends FocusVisiblePolyfillMixin(LitElement) {
19
- static get styles() {
20
- return css`
21
- /* styles to apply when clicked or focused with a keyboard */
22
- button:hover,
23
- button:focus {
24
- background-color: #cccccc;
25
- }
26
- /* styles to apply only when focused with a keyboard */
27
- button.focus-visible {
28
- outline: 2px solid black;
29
- }
30
- `;
31
- }
32
- }
33
- ```
34
-
35
- **Learn More:** [focus-visible polyfill documentation](https://github.com/WICG/focus-visible)
36
-
37
- ## Forcing Visible Focus
38
-
39
- When applying focus programatically in JavaScript via `elem.focus()`, unless a `Tab` keyboard event has just occurred, a visible focus ring will not be shown. In certain circumstances, forcing focus to be visible is desired -- like when moving focus from one element to another.
40
-
41
- For example, opening a dialog moves the user's focus from an opener to inside the dialog and then closing the dialog moves it back to the opener. In cases like this, not being able to clearly see which element has focus can be disorienting for the user.
42
-
43
- To force visible focus, use the `forceFocusVisible()` helper:
44
-
45
- ```js
46
- import { forceFocusVisible } from '@brightspace-ui/core/helpers/focus.js';
47
-
48
- // focus and force a visible focus ring
49
- forceFocusVisible(elem);
50
- ```