Skip to content

Commit

Permalink
Merge branch 'main' into ghworkflow/package_lock_auto_update
Browse files Browse the repository at this point in the history
  • Loading branch information
dlockhart authored Dec 4, 2024
2 parents 6a1192b + e6f551c commit b389f75
Show file tree
Hide file tree
Showing 54 changed files with 1,352 additions and 198 deletions.
11 changes: 9 additions & 2 deletions components/backdrop/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Backdrops

The `d2l-backdrop` element is a web component to display a semi-transparent backdrop behind a specified sibling element. It also hides elements other than the target from assistive technologies by applying `role="presentation"` and `aria-hidden="true"`.
The `d2l-backdrop` element displays a semi-transparent backdrop behind a specified sibling target element. It also hides elements other than the target from assistive technologies by applying `aria-hidden="true"`.

## Backdrop [d2l-backdrop]

Expand All @@ -9,7 +9,6 @@ The `d2l-backdrop` element is a web component to display a semi-transparent back
<script type="module">
import '@brightspace-ui/core/components/button/button.js';
import '@brightspace-ui/core/components/backdrop/backdrop.js';
import '@brightspace-ui/core/components/switch/switch.js';
const backdrop = document.querySelector('d2l-backdrop');
document.querySelector('#target > d2l-button').addEventListener('click', () => {
Expand All @@ -36,3 +35,11 @@ The `d2l-backdrop` element is a web component to display a semi-transparent back
| `shown` | Boolean | Used to control whether the backdrop is shown |
| `slow-transition` | Boolean | Increases the fade transition time to 1200ms (default is 200ms) |
<!-- docs: end hidden content -->

### Focus Management

Elements with `aria-hidden` applied (as well as their descendants) are completely hidden from assistive technologies. It's therefore very important that the element with active focus be within the backdrop target.

**When showing a backdrop**: first move focus inside the target, then set the `shown` attribute on the backdrop.

**When hiding a backdrop**: first remove the `shown` attribute on the backdrop, then if appropriate move focus outside the target.
33 changes: 26 additions & 7 deletions components/backdrop/backdrop.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import '../colors/colors.js';
import { css, html, LitElement } from 'lit';
import { cssEscape, getComposedChildren, getComposedParent, isVisible } from '../../helpers/dom.js';
import { cssEscape, getComposedChildren, getComposedParent, isComposedAncestor, isVisible } from '../../helpers/dom.js';
import { getComposedActiveElement } from '../../helpers/focus.js';

const BACKDROP_HIDDEN = 'data-d2l-backdrop-hidden';
const BACKDROP_ARIA_HIDDEN = 'data-d2l-backdrop-aria-hidden';
Expand All @@ -12,7 +13,7 @@ const modals = new Set();
let scrollOverflow = null;

/**
* A component for displaying a semi-transparent backdrop behind a specified sibling element. It also hides elements other than the target from assistive technologies by applying 'role="presentation"' and 'aria-hidden="true"'.
* A component for displaying a semi-transparent backdrop behind a specified sibling element. It also hides elements other than the target from assistive technologies by applying 'aria-hidden="true"'.
*/
class Backdrop extends LitElement {

Expand Down Expand Up @@ -85,10 +86,8 @@ class Backdrop extends LitElement {
disconnectedCallback() {
// allow body scrolling, show hidden elements, if backdrop is removed from the DOM
allowBodyScroll(this);
if (this._hiddenElements) {
showAccessible(this._hiddenElements);
this._hiddenElements = null;
}
showAccessible(this._hiddenElements);
this._hiddenElements = null;
this._state = null;
super.disconnectedCallback();
}
Expand All @@ -106,7 +105,12 @@ class Backdrop extends LitElement {

if (this._state === null) {
preventBodyScroll(this);
this._hiddenElements = hideAccessible(this.parentNode.querySelector(`#${cssEscape(this.forTarget)}`));
const target = this.parentNode.querySelector(`#${cssEscape(this.forTarget)}`);
// aria-hidden elements cannot have focus, so wait for focus to be within target
waitForFocusWithinTarget(target, Date.now() + 200).then(() => {
if (!this.shown || this._state !== 'showing') return;
this._hiddenElements = hideAccessible(target);
});
}
this._state = 'showing';

Expand Down Expand Up @@ -188,6 +192,7 @@ function hideAccessible(target) {
}

function showAccessible(elems) {
if (!elems) return;
for (let i = 0; i < elems.length; i++) {
const elem = elems[i];
if (elem.hasAttribute(BACKDROP_ARIA_HIDDEN)) {
Expand All @@ -200,4 +205,18 @@ function showAccessible(elems) {
}
}

async function waitForFocusWithinTarget(target, expireTime) {

if (Date.now() > expireTime) return;

const activeElem = getComposedActiveElement();
const targetIsAncestor = isComposedAncestor(target, activeElem);

if (targetIsAncestor) return;

await new Promise(resolve => requestAnimationFrame(resolve));
return waitForFocusWithinTarget(target, expireTime);

}

customElements.define('d2l-backdrop', Backdrop);
44 changes: 42 additions & 2 deletions components/backdrop/test/backdrop.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import '../backdrop.js';
import { expect, fixture, html, runConstructor } from '@brightspace-ui/testing';
import { aTimeout, expect, fixture, focusElem, html, runConstructor } from '@brightspace-ui/testing';

const backdropFixture = html`
<div>
Expand All @@ -21,7 +21,10 @@ const backdropFixture = html`
const multiBackdropFixture = html`
<div>
<div id="target1">
<div id="target2"></div>
<button id="focusTarget1">Focus</button>
<div id="target2">
<button id="focusTarget2">Focus</button>
</div>
<div id="target2Sibling"></div>
<d2l-backdrop for-target="target2" no-animate-hide></d2l-backdrop>
</div>
Expand All @@ -45,6 +48,7 @@ describe('d2l-backdrop', () => {
it('should hide accessible elements', async() => {
const elem = await fixture(backdropFixture);
const backdrop = elem.querySelector('d2l-backdrop');
await focusElem(elem.querySelector('#target button'));
backdrop.shown = true;
await backdrop.updateComplete;

Expand All @@ -70,9 +74,43 @@ describe('d2l-backdrop', () => {
expect(linkAriaHidden.getAttribute('aria-hidden')).to.equal('true');
});

it('should not hide accessible elements until focus is inside target', async() => {

const elem = await fixture(backdropFixture);
const backdrop = elem.querySelector('d2l-backdrop');
const targetSibling = elem.querySelector('#targetSibling');

backdrop.shown = true;
await backdrop.updateComplete;

expect(targetSibling.hasAttribute('aria-hidden')).to.be.false;

await focusElem(elem.querySelector('#target button'));
await new Promise(resolve => requestAnimationFrame(resolve));

expect(targetSibling.getAttribute('aria-hidden')).to.equal('true');

});

it('should eventually hide accessible elements if focus never moves into target', async() => {

const elem = await fixture(backdropFixture);
const backdrop = elem.querySelector('d2l-backdrop');
const targetSibling = elem.querySelector('#targetSibling');

backdrop.shown = true;
await backdrop.updateComplete;

await aTimeout(300);

expect(targetSibling.getAttribute('aria-hidden')).to.equal('true');

});

it('should show accessible elements', async() => {
const elem = await fixture(backdropFixture);
const backdrop = elem.querySelector('d2l-backdrop');
await focusElem(elem.querySelector('#target button'));
backdrop.shown = true;
await backdrop.updateComplete;
backdrop.shown = false;
Expand Down Expand Up @@ -104,10 +142,12 @@ describe('d2l-backdrop', () => {
const elem = await fixture(multiBackdropFixture);

const backdrop1 = elem.querySelector('d2l-backdrop[for-target="target1"]');
await focusElem(elem.querySelector('#focusTarget1'));
backdrop1.shown = true;
await backdrop1.updateComplete;

const backdrop2 = elem.querySelector('d2l-backdrop[for-target="target2"]');
await focusElem(elem.querySelector('#focusTarget2'));
backdrop2.shown = true;
await backdrop2.updateComplete;

Expand Down
112 changes: 54 additions & 58 deletions components/breadcrumbs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,124 +28,120 @@ Breadcrumbs are a way-finding tool that helps users understand where they are w
<!-- docs: end donts -->
<!-- docs: end best practices -->

## Responsive Behavior

Breadcrumbs that overflow their container will appear to fade at the edge.
## Breadcrumbs [d2l-breadcrumbs]

<!-- docs: demo display:block -->
<!-- docs: demo code properties name:d2l-breadcrumbs sandboxTitle:'Breadcrumbs' display:block -->
```html
<script type="module">
import '@brightspace-ui/core/components/breadcrumbs/breadcrumbs.js';
</script>
<d2l-breadcrumbs>
<d2l-breadcrumb text="Table of Contents" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Unit 1: Shakespeare" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Lesson 1: Introduction" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="The Comedies, Tragedies, and Histories" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Item 1" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Item 2" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Item 3" href="#"></d2l-breadcrumb>
</d2l-breadcrumbs>
```

This works well for mobile or other touch devices but not as well for mouse or keyboard users, so we have two other options for managing width.
<!-- docs: start hidden content -->
### Properties

### Limited Width
| Property | Type | Description |
|--|--|--|
| `compact` | Boolean | Renders in compact mode, displaying only the last item |
<!-- docs: end hidden content -->

Set a `max-width` to constrain breadcrumbs to a particular width:
## Breadcrumb (child) [d2l-breadcrumb]

<!-- docs: demo code display:block -->
<!-- docs: demo code properties name:d2l-breadcrumb sandboxTitle:'Breadcrumb' display:block -->
```html
<script type="module">
import '@brightspace-ui/core/components/breadcrumbs/breadcrumbs.js';
</script>
<d2l-breadcrumbs style="max-width: 250px">
<d2l-breadcrumb text="Trucate Basic Item 1" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Truncate Basic Item 2" href="#"></d2l-breadcrumb>
<d2l-breadcrumbs>
<d2l-breadcrumb text="Item 1" href="#"></d2l-breadcrumb>
</d2l-breadcrumbs>
```

### Compact Mode
<!-- docs: start hidden content -->
### Properties

Alternately, add the `compact` attribute to only display the last breadcrumb. The `d2l-breadcrumb-current-page` will be hidden:
| Property | Type | Description |
|--|--|--|
| `text` | String, required | The text of the breadcrumb link |
| `aria-label` | String | ARIA label for the breadcrumb, used if `text` does not provide enough context for screen reader users |
| `href` | String | The Url that breadcrumb is pointing to |
| `target` | String | Target of the breadcrumb item |
<!-- docs: end hidden content -->

<!-- docs: demo code display:block -->
## Current Page [d2l-breadcrumb-current-page]

Only include the current page in the breadcrumb if your page or view does not have a visible heading. You will notice that some older pages or tools in Brightspace still display the current page as the last breadcrumb despite having a visible page heading, but this is now a legacy pattern.

<!-- docs: demo code properties name:d2l-breadcrumb-current-page sandboxTitle:'Current Page Breadcrumb' display:block -->
```html
<script type="module">
import '@brightspace-ui/core/components/breadcrumbs/breadcrumb-current-page.js';
import '@brightspace-ui/core/components/breadcrumbs/breadcrumbs.js';
</script>
<d2l-breadcrumbs compact>
<d2l-breadcrumbs>
<d2l-breadcrumb text="Item 1" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Item 2" href="#"></d2l-breadcrumb>
<d2l-breadcrumb-current-page text="Current Page"></d2l-breadcrumb-current-page>
</d2l-breadcrumbs>
```

## Breadcrumbs [d2l-breadcrumbs]
## Responsive Behavior

<!-- docs: demo code properties name:d2l-breadcrumbs sandboxTitle:'Breadcrumbs' display:block -->
Breadcrumbs that overflow their container will appear to fade at the edge, as in this example:

<!-- docs: demo display:block -->
```html
<script type="module">
import '@brightspace-ui/core/components/breadcrumbs/breadcrumbs.js';
</script>
<d2l-breadcrumbs>
<d2l-breadcrumb text="Item 1" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Item 2" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Item 3" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Table of Contents" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Unit 1: Shakespeare" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Lesson 1: Introduction" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Sub-lesson 3: The Breadth of Shakespearean Literature" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="The Comedies, Tragedies, Histories, and Other Long Words" href="#"></d2l-breadcrumb>
</d2l-breadcrumbs>
```

<!-- docs: start hidden content -->
### Properties

| Property | Type | Description |
|--|--|--|
| `compact` | Boolean | Indicates whether the component should render in compact mode |
<!-- docs: end hidden content -->
### Limited Width

## Breadcrumb (child) [d2l-breadcrumb]
Set a `max-width` to constrain breadcrumbs to a particular width:

<!-- docs: demo code properties name:d2l-breadcrumb sandboxTitle:'Breadcrumb' display:block -->
<!-- docs: demo code display:block -->
```html
<script type="module">
import '@brightspace-ui/core/components/breadcrumbs/breadcrumbs.js';
</script>
<d2l-breadcrumbs>
<d2l-breadcrumb text="Item 1" href="#"></d2l-breadcrumb>
<d2l-breadcrumbs style="max-width: 250px">
<d2l-breadcrumb text="Trucate Basic Item 1" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Truncate Basic Item 2" href="#"></d2l-breadcrumb>
</d2l-breadcrumbs>
```

<!-- docs: start hidden content -->
### Properties

| Property | Type | Description |
|--|--|--|
| `text` | String, required | Text of the breadcrumb item |
| `aria-label` | String | AriaLabel of breadcrumb item |
| `href` | String | Href of the breadcrumb item |
| `target` | String | Target of the breadcrumb item |
<!-- docs: end hidden content -->

### Accessibility Properties

To make your usage of `d2l-breadcrumb` (child) accessible, use the following attribute when applicable:

| Attribute | Description |
|---|---|
| `aria-label` | Acts as a primary label. Use if `text` does not provide enough context. |

## Current Page [d2l-breadcrumb-current-page]
### Compact Mode

Only include the current page in the breadcrumb if your page or view does not have a visible heading. You will notice that some older pages or tools in Brightspace still display the current page as the last breadcrumb despite having a visible page heading, but this is now a legacy pattern.
Alternately, add the `compact` attribute to only display the last breadcrumb. The `d2l-breadcrumb-current-page` will be hidden:

<!-- docs: demo code properties name:d2l-breadcrumb-current-page sandboxTitle:'Current Page Breadcrumb' display:block -->
<!-- docs: demo code display:block -->
```html
<script type="module">
import '@brightspace-ui/core/components/breadcrumbs/breadcrumb-current-page.js';
import '@brightspace-ui/core/components/breadcrumbs/breadcrumbs.js';
</script>
<d2l-breadcrumbs>
<d2l-breadcrumbs compact>
<d2l-breadcrumb text="Item 1" href="#"></d2l-breadcrumb>
<d2l-breadcrumb text="Item 2" href="#"></d2l-breadcrumb>
<d2l-breadcrumb-current-page text="Current Page"></d2l-breadcrumb-current-page>
</d2l-breadcrumbs>
```

## Accessibility

Breadcrumbs adhere to [W3C's Breadcrumbs Pattern](https://www.w3.org/WAI/ARIA/apg/patterns/breadcrumb/), so they are contained in a navigation landmark region with proper aria labelling and add `aria-current` to the final breadcrumb if it represents the [Current Page](#d2l-breadcrumb-current-page).

Note that we do not apply a `visited` style to breadcrumbs, since they reflect tool hiearchy and are part of the UI rather than part of the page content.
4 changes: 2 additions & 2 deletions components/breadcrumbs/breadcrumb.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ class Breadcrumb extends RtlMixin(FocusMixin(LitElement)) {
*/
target: { type: String, reflect: true },
/**
* REQUIRED: text of the breadcrumb link
* REQUIRED: The text of the breadcrumb link
* @type {string}
*/
text: { type: String, reflect: true },
/**
* ARIA label of the breadcrumb
* ACCESSIBILITY: ARIA label for the breadcrumb, used if `text` does not provide enough context for screen reader users
* @type {string}
*/
ariaLabel: { attribute: 'aria-label', type: String, reflect: true }
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions components/inputs/demo/input-date.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ <h2>Value Specified</h2>
</template>
</d2l-demo-snippet>

<h2>Min and Max Value</h2>
<h2>Min and Max Value, Required</h2>
<d2l-demo-snippet>
<template>
<d2l-input-date label="Date" max-value="2018-09-30" min-value="2018-08-27" value="2018-09-08"></d2l-input-date>
<d2l-input-date label="Date" max-value="2018-09-30" min-value="2018-08-27" value="2018-08-27" required></d2l-input-date>
</template>
</d2l-demo-snippet>

Expand Down
Loading

0 comments on commit b389f75

Please sign in to comment.