diff --git a/.circleci/config.yml b/.circleci/config.yml index 4dd92d944e..a5e093dcfc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ executors: parameters: current_golden_images_hash: type: string - default: 2ecdb30466ca9cb15b7850ca9827d6fabf69736f + default: bc2b4f3fe51f9edb858dc4c63248cdf0bb06a046 commands: setup: steps: diff --git a/documentation/src/components/layout.css b/documentation/src/components/layout.css index 498afddb43..4f4ea8d255 100644 --- a/documentation/src/components/layout.css +++ b/documentation/src/components/layout.css @@ -93,11 +93,11 @@ header svg { justify-content: flex-end; } -:host([dir='ltr']) .manage-theme label { +:host([dir='ltr']) .manage-theme sp-field-label { margin-left: var(--spectrum-global-dimension-size-400); } -:host([dir='rtl']) .manage-theme label { +:host([dir='rtl']) .manage-theme sp-field-label { margin-right: var(--spectrum-global-dimension-size-400); } @@ -107,71 +107,6 @@ header svg { } } -/** - * TODO: Remove this manual copy of the Spectrum Field Label styles - * once https://github.com/adobe/spectrum-web-components/issues/475 - * has been addressed and this element is natively part of the library - **/ -label { - /* .spectrum-FieldLabel, - * .spectrum-Form-itemLabel */ - display: block; - - box-sizing: border-box; - - padding-top: var( - --spectrum-fieldlabel-padding-top, - var(--spectrum-global-dimension-size-50) - ); - - padding-bottom: var( - --spectrum-fieldlabel-padding-bottom, - var(--spectrum-global-dimension-size-65) - ); - padding-left: 0; - padding-right: 0; - - font-size: var( - --spectrum-fieldlabel-text-size, - var(--spectrum-global-dimension-font-size-75) - ); - font-weight: var( - --spectrum-fieldlabel-text-font-weight, - var(--spectrum-global-font-weight-regular) - ); - line-height: var( - --spectrum-fieldlabel-text-line-height, - var(--spectrum-global-font-line-height-small) - ); - - -webkit-font-smoothing: subpixel-antialiased; - -moz-osx-font-smoothing: auto; - font-smoothing: subpixel-antialiased; - - display: inline-block; - padding-top: var( - --spectrum-fieldlabel-side-padding-top, - var(--spectrum-global-dimension-size-100) - ); - padding-bottom: 0; -} - -:host([dir='ltr']) label { - padding-left: 0; - padding-right: var( - --spectrum-fieldlabel-side-padding-x, - var(--spectrum-global-dimension-size-100) - ); -} - -:host([dir='rtl']) label { - padding-right: 0; - padding-left: var( - --spectrum-fieldlabel-side-padding-x, - var(--spectrum-global-dimension-size-100) - ); -} - .alerts { width: 100vw; text-align: center; diff --git a/documentation/src/components/layout.ts b/documentation/src/components/layout.ts index e8395944e1..8f1e0afa35 100644 --- a/documentation/src/components/layout.ts +++ b/documentation/src/components/layout.ts @@ -23,6 +23,7 @@ import layoutStyles from './layout.css'; import '@spectrum-web-components/theme/sp-theme.js'; import '@spectrum-web-components/theme/src/themes.js'; import { Color, Scale } from '@spectrum-web-components/theme'; +import '@spectrum-web-components/field-label/sp-field-label.js'; import { Dropdown } from '@spectrum-web-components/dropdown'; import '@spectrum-web-components/dropdown/sp-dropdown.js'; import '@spectrum-web-components/menu/sp-menu.js'; @@ -105,25 +106,6 @@ export class LayoutElement extends SpectrumElement { this.dir = dir === 'rtl' ? dir : 'ltr'; } - // TODO: remove this manual link relationship when - // https://github.com/adobe/spectrum-web-components/issues/475 - // has been completed and links are natively part of the library - private onClickLabel(event: { target: HTMLElement }) { - const { target } = event; - const next = target.nextElementSibling as Dropdown; - if (!next || next.open) return; - next.click(); - } - - private onKeypressLabel(event: KeyboardEvent & { target: HTMLElement }) { - const { code, target } = event; - if (code === 'Space' || code === 'Enter') { - const next = target.nextElementSibling as Dropdown; - if (!next || next.open) return; - next.click(); - } - } - private addAlert(event: CustomEvent<{ message: string }>): void { const target = event.composedPath()[0] as HTMLElement; if (!this.alerts.has(target)) { @@ -232,13 +214,14 @@ export class LayoutElement extends SpectrumElement { >
- + - + - + ` provides accessible labelling for form elements. Use the `for` attribute to outline the `id` of an element in the same DOM tree to which it should associate itself. + +### Usage + +[![See it on NPM!](https://img.shields.io/npm/v/@spectrum-web-components/field-label?style=for-the-badge)](https://www.npmjs.com/package/@spectrum-web-components/field-label) +[![How big is this package in your project?](https://img.shields.io/bundlephobia/minzip/@spectrum-web-components/field-label?style=for-the-badge)](https://bundlephobia.com/result?p=@spectrum-web-components/field-label) + +``` +yarn add @spectrum-web-components/field-label +``` + +Import the side effectful registration of `` via: + +``` +import '@spectrum-web-components/field-label/sp-field-label.js'; +``` + +When looking to leverage the `FieldLabel` base class as a type and/or for extension purposes, do so via: + +``` +import { FieldLabel } from '@spectrum-web-components/field-label'; +``` + +## Example + +```html + + Life Story + + + + + + Birthplace + + + Choose a location: + + Istanbul + London + Maputo + Melbuorne + New York + San Fransisco + Santiago + Tokyo + + +``` + +## Side Aligned + +Using the `side-aligned` attribute will display the `` element inline with surrounding elements and the `start` and `end` values outline the alignment of the label text in the width specified. + +### Start + +Use `side-aligned="start"` to display the `` inline and to align the label text to the "start" of the flow of text: + +```html + + Life Story + + +
+
+ + Birthplace + + + Choose a location: + + Istanbul + London + Maputo + Melbuorne + New York + San Fransisco + Santiago + Tokyo + + +``` + +### End + +Use `side-aligned="end"` to display the `` inline and to align the label text to the "end" of the flow of text: + +```html + + Life Story + + +
+
+ + Birthplace + + + Choose a location: + + Istanbul + London + Maputo + Melbuorne + New York + San Fransisco + Santiago + Tokyo + + +``` diff --git a/packages/field-label/package.json b/packages/field-label/package.json new file mode 100644 index 0000000000..1491eac9a6 --- /dev/null +++ b/packages/field-label/package.json @@ -0,0 +1,59 @@ +{ + "name": "@spectrum-web-components/field-label", + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/adobe/spectrum-web-components.git", + "directory": "packages/field-label" + }, + "bugs": { + "url": "https://github.com/adobe/spectrum-web-components/issues" + }, + "homepage": "https://adobe.github.io/spectrum-web-components/components/field-label", + "keywords": [ + "spectrum css", + "web components", + "lit-element", + "lit-html" + ], + "version": "0.0.1", + "description": "", + "main": "src/index.js", + "module": "src/index.js", + "type": "module", + "exports": { + "./src/": "./src/", + "./custom-elements.json": "./custom-elements.json", + "./package.json": "./package.json", + "./sp-field-label": "./sp-field-label.js", + "./sp-field-label.js": "./sp-field-label.js" + }, + "files": [ + "custom-elements.json", + "*.d.ts", + "*.js", + "*.js.map", + "/src/" + ], + "sideEffects": [ + "./sp-*.js", + "./sp-*.ts" + ], + "scripts": { + "test": "echo \"Error: run tests from mono-repo root.\" && exit 1" + }, + "author": "", + "license": "Apache-2.0", + "devDependencies": { + "@spectrum-css/fieldlabel": "^3.0.0-beta.6", + "@spectrum-web-components/textfield": "^0.5.4" + }, + "dependencies": { + "@spectrum-web-components/base": "^0.1.3", + "@spectrum-web-components/icons-ui": "^0.3.3", + "@spectrum-web-components/shared": "^0.7.4", + "tslib": "^2.0.0" + } +} diff --git a/packages/field-label/sp-field-label.ts b/packages/field-label/sp-field-label.ts new file mode 100644 index 0000000000..9bca15f20b --- /dev/null +++ b/packages/field-label/sp-field-label.ts @@ -0,0 +1,21 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import { FieldLabel } from './src/FieldLabel.js'; + +customElements.define('sp-field-label', FieldLabel); + +declare global { + interface HTMLElementTagNameMap { + 'sp-field-label': FieldLabel; + } +} diff --git a/packages/field-label/src/FieldLabel.ts b/packages/field-label/src/FieldLabel.ts new file mode 100644 index 0000000000..24aae4a96d --- /dev/null +++ b/packages/field-label/src/FieldLabel.ts @@ -0,0 +1,126 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import { + html, + SpectrumElement, + CSSResultArray, + TemplateResult, + property, + PropertyValues, +} from '@spectrum-web-components/base'; +import type { Focusable } from '@spectrum-web-components/shared'; +import { AsteriskIcon } from '@spectrum-web-components/icons-ui'; +import '@spectrum-web-components/icon/sp-icon.js'; +import asterickIconStyles from '@spectrum-web-components/icon/src/spectrum-icon-asterick.css.js'; + +import styles from './field-label.css.js'; + +type AcceptsFocusVisisble = HTMLElement & { forceFocusVisible?(): void }; + +/** + * @element sp-field-label + */ +export class FieldLabel extends SpectrumElement { + public static get styles(): CSSResultArray { + return [styles, asterickIconStyles]; + } + + static instanceCount = 0; + + @property({ type: Boolean, reflect: true }) + public disabled = false; + + @property({ type: String }) + public id = ''; + + @property({ type: String }) + public for = ''; + + @property({ type: Boolean, reflect: true }) + public required = false; + + @property({ type: String, reflect: true, attribute: 'side-aligned' }) + public sideAligned?: 'start' | 'end'; + + private target?: HTMLElement; + + private handleClick(): void { + if (!this.target || this.disabled) return; + this.target.focus(); + const parent = this.getRootNode() as ShadowRoot; + const target = this.target as AcceptsFocusVisisble; + const targetParent = target.getRootNode() as ShadowRoot; + const targetHost = targetParent.host as AcceptsFocusVisisble; + if (targetParent.isSameNode(parent) && target.forceFocusVisible) { + target.forceFocusVisible(); + } else if (targetHost && targetHost.forceFocusVisible) { + targetHost.forceFocusVisible(); + } + } + + private async manageFor(): Promise { + if (!this.for) { + return; + } + const parent = this.getRootNode() as HTMLElement; + const target = parent.querySelector(`#${this.for}`) as Focusable; + if (typeof target.updateComplete !== 'undefined') { + await target.updateComplete; + } + this.target = target.focusElement || target; + if (this.target) { + const targetParent = this.target.getRootNode() as HTMLElement; + if (targetParent.isSameNode(parent)) { + this.target.setAttribute('aria-labelledby', this.id); + } else { + this.target.setAttribute( + 'aria-label', + (this.textContent || /* c8 ignore next */ '').trim() + ); + } + } + } + + protected render(): TemplateResult { + return html` + + `; + } + + protected firstUpdated(changes: PropertyValues): void { + super.firstUpdated(changes); + if (!this.hasAttribute('id')) { + this.setAttribute( + 'id', + `${this.tagName.toLowerCase()}-${FieldLabel.instanceCount++}` + ); + } + this.addEventListener('click', this.handleClick); + } + + protected updated(changes: PropertyValues): void { + super.updated(changes); + if (changes.has('for') || changes.has('id')) { + this.manageFor(); + } + } +} diff --git a/packages/field-label/src/field-label.css b/packages/field-label/src/field-label.css new file mode 100644 index 0000000000..0642bd5401 --- /dev/null +++ b/packages/field-label/src/field-label.css @@ -0,0 +1,13 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +@import './spectrum-field-label.css'; diff --git a/packages/field-label/src/index.ts b/packages/field-label/src/index.ts new file mode 100644 index 0000000000..f43503cd43 --- /dev/null +++ b/packages/field-label/src/index.ts @@ -0,0 +1,13 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +export * from './FieldLabel.js'; diff --git a/packages/field-label/src/spectrum-config.js b/packages/field-label/src/spectrum-config.js new file mode 100644 index 0000000000..112b21abc0 --- /dev/null +++ b/packages/field-label/src/spectrum-config.js @@ -0,0 +1,52 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +const config = { + spectrum: 'fieldlabel', + components: [ + { + name: 'field-label', + host: { + selector: '.spectrum-FieldLabel', + }, + attributes: [ + { + type: 'boolean', + selector: '.is-disabled', + name: 'disabled', + }, + { + type: 'enum', + name: 'side-aligned', + values: [ + { + name: 'start', + selector: '.spectrum-FieldLabel--left', + }, + { + name: 'end', + selector: '.spectrum-FieldLabel--right', + }, + ], + }, + ], + classes: [ + { + selector: '.spectrum-FieldLabel-requiredIcon', + name: 'requiredIcon', + }, + ], + }, + ], +}; + +export default config; diff --git a/packages/field-label/src/spectrum-field-label.css b/packages/field-label/src/spectrum-field-label.css new file mode 100644 index 0000000000..85a8c8c82e --- /dev/null +++ b/packages/field-label/src/spectrum-field-label.css @@ -0,0 +1,178 @@ +/* stylelint-disable */ /* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. + +THIS FILE IS MACHINE GENERATED. DO NOT EDIT */ +:host { + /* .spectrum-FieldLabel, + * .spectrum-Form-itemLabel */ + display: block; + box-sizing: border-box; + padding-top: var( + --spectrum-fieldlabel-padding-top, + var(--spectrum-global-dimension-size-50) + ); + padding-bottom: var( + --spectrum-fieldlabel-padding-bottom, + var(--spectrum-global-dimension-size-65) + ); + padding-left: 0; + padding-right: 0; + font-size: var( + --spectrum-fieldlabel-text-size, + var(--spectrum-global-dimension-font-size-75) + ); + font-weight: var( + --spectrum-fieldlabel-text-font-weight, + var(--spectrum-global-font-weight-regular) + ); + line-height: var( + --spectrum-fieldlabel-text-line-height, + var(--spectrum-global-font-line-height-small) + ); + vertical-align: top; + -webkit-font-smoothing: subpixel-antialiased; + -moz-osx-font-smoothing: auto; + font-smoothing: subpixel-antialiased; +} +:host([dir='ltr']) .requiredIcon { + /* [dir=ltr] .spectrum-FieldLabel-requiredIcon */ + margin-left: var( + --spectrum-fieldlabel-asterisk-gap, + var(--spectrum-global-dimension-size-25) + ); + margin-right: 0; +} +:host([dir='rtl']) .requiredIcon { + /* [dir=rtl] .spectrum-FieldLabel-requiredIcon */ + margin-right: var( + --spectrum-fieldlabel-asterisk-gap, + var(--spectrum-global-dimension-size-25) + ); + margin-left: 0; +} +.requiredIcon { + /* .spectrum-FieldLabel-requiredIcon */ + margin-top: var( + --spectrum-fieldlabel-asterisk-margin-y, + var(--spectrum-global-dimension-size-50) + ); + margin-bottom: 0; +} +:host([dir='ltr'][side-aligned='start']) { + /* [dir=ltr] .spectrum-FieldLabel--left */ + padding-left: 0; + padding-right: var( + --spectrum-fieldlabel-side-padding-x, + var(--spectrum-global-dimension-size-100) + ); +} +:host([dir='rtl'][side-aligned='start']) { + /* [dir=rtl] .spectrum-FieldLabel--left */ + padding-right: 0; + padding-left: var( + --spectrum-fieldlabel-side-padding-x, + var(--spectrum-global-dimension-size-100) + ); +} +:host([side-aligned='start']) { + /* .spectrum-FieldLabel--left */ + display: inline-block; + padding-top: var( + --spectrum-fieldlabel-side-padding-top, + var(--spectrum-global-dimension-size-100) + ); + padding-bottom: 0; +} +:host([dir='ltr'][side-aligned='start']) .requiredIcon { + /* [dir=ltr] .spectrum-FieldLabel--left .spectrum-FieldLabel-requiredIcon */ + margin-left: var( + --spectrum-fieldlabel-asterisk-gap, + var(--spectrum-global-dimension-size-25) + ); + margin-right: 0; +} +:host([dir='rtl'][side-aligned='start']) .requiredIcon { + /* [dir=rtl] .spectrum-FieldLabel--left .spectrum-FieldLabel-requiredIcon */ + margin-right: var( + --spectrum-fieldlabel-asterisk-gap, + var(--spectrum-global-dimension-size-25) + ); + margin-left: 0; +} +:host([side-aligned='start']) .requiredIcon { + /* .spectrum-FieldLabel--left .spectrum-FieldLabel-requiredIcon */ + margin-top: var(--spectrum-fieldlabel-side-asterisk-margin-y, 0); + margin-bottom: 0; +} +:host([dir='ltr'][side-aligned='end']) { + /* [dir=ltr] .spectrum-FieldLabel--right */ + text-align: right; +} +:host([dir='rtl'][side-aligned='end']) { + /* [dir=rtl] .spectrum-FieldLabel--right */ + text-align: left; +} +:host([dir='ltr'][side-aligned='end']) { + /* [dir=ltr] .spectrum-FieldLabel--right */ + padding-left: 0; + padding-right: var( + --spectrum-fieldlabel-side-padding-x, + var(--spectrum-global-dimension-size-100) + ); +} +:host([dir='rtl'][side-aligned='end']) { + /* [dir=rtl] .spectrum-FieldLabel--right */ + padding-right: 0; + padding-left: var( + --spectrum-fieldlabel-side-padding-x, + var(--spectrum-global-dimension-size-100) + ); +} +:host([side-aligned='end']) { + /* .spectrum-FieldLabel--right */ + display: inline-block; + padding-top: var( + --spectrum-fieldlabel-side-padding-top, + var(--spectrum-global-dimension-size-100) + ); + padding-bottom: 0; +} +:host { + /* .spectrum-FieldLabel, + * .spectrum-Form-itemLabel */ + color: var( + --spectrum-fieldlabel-text-color, + var(--spectrum-alias-label-text-color) + ); +} +:host([disabled]) { + /* .spectrum-FieldLabel.is-disabled, + * .spectrum-Form-itemLabel.is-disabled */ + color: var( + --spectrum-fieldlabel-text-color-disabled, + var(--spectrum-alias-text-color-disabled) + ); +} +:host([disabled]) .requiredIcon { + /* .spectrum-FieldLabel.is-disabled .spectrum-FieldLabel-requiredIcon, + * .spectrum-Form-itemLabel.is-disabled .spectrum-FieldLabel-requiredIcon */ + color: var( + --spectrum-fieldlabel-asterisk-color-disabled, + var(--spectrum-alias-text-color-disabled) + ); +} +.requiredIcon { + /* .spectrum-FieldLabel-requiredIcon */ + color: var( + --spectrum-fieldlabel-asterisk-color, + var(--spectrum-global-color-gray-600) + ); +} diff --git a/packages/field-label/stories/field-label.stories.ts b/packages/field-label/stories/field-label.stories.ts new file mode 100644 index 0000000000..2c56cfae49 --- /dev/null +++ b/packages/field-label/stories/field-label.stories.ts @@ -0,0 +1,156 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import { html, TemplateResult } from '@spectrum-web-components/base'; +import '@spectrum-web-components/textfield/sp-textfield.js'; + +import '../sp-field-label.js'; + +export default { + title: 'Field Label', + component: 'sp-field-label', +}; + +export const standard = (): TemplateResult => { + return html` + Life Story + + + Life Story + + + `; +}; + +export const sideAlignStart = (): TemplateResult => { + return html` + + Life Story + + + `; +}; + +export const sideAlignEnd = (): TemplateResult => { + return html` + + Life Story + + + `; +}; + +export const required = (): TemplateResult => { + return html` + Life Story + + Life Story (Required) + +
+
+ + Life Story + + +
+
+ + Life Story + + + + Life Story + + + `; +}; + +export const picker = (): TemplateResult => { + return html` + + Select a Country with a very long label, too long in fact + + + + + Deselect + + + Select Inverse + + + Feather... + + + Select and Mask... + + + + Save Selection + + + Make Work Path + + + + `; +}; + +export const nativeInput = (): TemplateResult => { + return html` + Life Story + + `; +}; diff --git a/packages/field-label/test/benchmark/basic-test.ts b/packages/field-label/test/benchmark/basic-test.ts new file mode 100644 index 0000000000..81a0617149 --- /dev/null +++ b/packages/field-label/test/benchmark/basic-test.ts @@ -0,0 +1,19 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import '@spectrum-web-components/field-label/sp-field-label.js'; +import { html } from '@spectrum-web-components/base'; +import { measureFixtureCreation } from '../../../../test/benchmark/helpers.js'; + +measureFixtureCreation(html` + +`); diff --git a/packages/field-label/test/field-label.test.ts b/packages/field-label/test/field-label.test.ts new file mode 100644 index 0000000000..630b711ab6 --- /dev/null +++ b/packages/field-label/test/field-label.test.ts @@ -0,0 +1,138 @@ +/* +Copyright 2020 Adobe. All rights reserved. +This file is licensed to you under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. You may obtain a copy +of the License at http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed under +the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS +OF ANY KIND, either express or implied. See the License for the specific language +governing permissions and limitations under the License. +*/ + +import { fixture, elementUpdated, expect, html } from '@open-wc/testing'; + +import '@spectrum-web-components/textfield/sp-textfield.js'; +import { Textfield } from '@spectrum-web-components/textfield'; + +import '../sp-field-label.js'; +import { FieldLabel } from '..'; + +describe('FieldLabel', () => { + it('loads default field-label accessibly', async () => { + const el = await fixture( + html` +
+ Input label + +
+ ` + ); + + await elementUpdated(el); + + await expect(el).to.be.accessible(); + }); + it('loads [required] field-label accessibly', async () => { + const el = await fixture( + html` +
+ + Required input label + + +
+ ` + ); + + await elementUpdated(el); + + await expect(el).to.be.accessible(); + }); + it('loads with no "for"', async () => { + const el = await fixture( + html` + Input label + ` + ); + + await elementUpdated(el); + + await expect(el).to.be.accessible(); + }); + it('associates itself to an element whose "id" matches its "for" attribute', async () => { + const test = await fixture( + html` +
+ + +
+ ` + ); + const el = test.querySelector('sp-field-label') as FieldLabel; + const input = test.querySelector('input') as HTMLInputElement; + + await elementUpdated(el); + + expect(input.hasAttribute('aria-labelledby')); + expect(input.getAttribute('aria-labelledby')).to.equal(el.id); + }); + it('passed clicks to assiciated form element as focus', async () => { + const test = await fixture( + html` +
+ + +
+ ` + ); + const el = test.querySelector('sp-field-label') as FieldLabel; + const input = test.querySelector('input') as HTMLInputElement; + + await elementUpdated(el); + + el.click(); + await elementUpdated(el); + + expect(document.activeElement === input); + }); + it('associates itself to an element with a focueElement whose "id" matches its "for" attribute', async () => { + const test = await fixture( + html` +
+ + +
+ ` + ); + const el = test.querySelector('sp-field-label') as FieldLabel; + const input = (test.querySelector('sp-textfield') as Textfield) + .focusElement as HTMLInputElement; + + await elementUpdated(el); + + expect(input.hasAttribute('aria-label')); + expect(input.getAttribute('aria-label')).to.equal( + (el.textContent || '').trim() + ); + }); + it('passed clicks to assiciated form element with a focueElement as focus', async () => { + const test = await fixture( + html` +
+ + +
+ ` + ); + const el = test.querySelector('sp-field-label') as FieldLabel; + const input = test.querySelector('sp-textfield') as Textfield; + + await elementUpdated(el); + + el.click(); + await elementUpdated(el); + + expect(document.activeElement === input); + }); +}); diff --git a/packages/field-label/tsconfig.json b/packages/field-label/tsconfig.json new file mode 100644 index 0000000000..75919f9078 --- /dev/null +++ b/packages/field-label/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "./" + }, + "include": ["*.ts", "src/*.ts"], + "exclude": ["test/*.ts", "stories/*.ts"], + "references": [{ "path": "../base" }] +} diff --git a/packages/overlay/src/overlay-stack.ts b/packages/overlay/src/overlay-stack.ts index 73fb692788..24c12e81f8 100644 --- a/packages/overlay/src/overlay-stack.ts +++ b/packages/overlay/src/overlay-stack.ts @@ -328,7 +328,23 @@ export class OverlayStack { overlay.interaction === 'inline') && !overlay.tabbingAway) ) { - overlay.trigger.focus(); + const overlayRoot = overlay.overlayContent.getRootNode() as ShadowRoot; + const overlayContentActiveElement = + overlayRoot.activeElement; + const triggerRoot = overlay.trigger.getRootNode() as ShadowRoot; + const triggerActiveElement = triggerRoot.activeElement; + if ( + overlay.overlayContent.contains( + overlayContentActiveElement + ) || + overlay.trigger + .getRootNode() + .contains(triggerActiveElement) || + (triggerRoot.host && + triggerRoot.host.isSameNode(triggerActiveElement)) + ) { + overlay.trigger.focus(); + } } overlay.tabbingAway = false; } diff --git a/test/visual/stories.js b/test/visual/stories.js index fbbc7bbc57..8703fe83a8 100644 --- a/test/visual/stories.js +++ b/test/visual/stories.js @@ -135,6 +135,12 @@ module.exports = [ 'dropzone--default', 'field-group--horizontal', 'field-group--vertical', + 'field-label--standard', + 'field-label--side-align-start', + 'field-label--side-align-end', + 'field-label--required', + 'field-label--picker', + 'field-label--native-input', 'icon--medium', 'icon--large', 'icon--image-icon', diff --git a/yarn.lock b/yarn.lock index 3eeea2c268..ef790c1ec8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3495,6 +3495,11 @@ resolved "https://registry.yarnpkg.com/@spectrum-css/fieldgroup/-/fieldgroup-3.0.0-beta.5.tgz#2df6116a5c9131a1b30b6cc51b9fdf9bfa1a22ab" integrity sha512-P8w2ASt4Z47vsnaOxkGOKBzfXE7B/3x//awkaKUFf7fDjGUmGkuyPtUt5Pd1YxzQ57jGMpyL/8fZmFmn3UiDRw== +"@spectrum-css/fieldlabel@^3.0.0-beta.6": + version "3.0.0-beta.6" + resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.0-beta.6.tgz#e72aa90f3eb03d7e5f3e7da5594c18c680206918" + integrity sha512-AeGmqMJczwTgHn/YTQ++tdTSS/4eBaFXJwW43LxJBI9U1aj68OB1dj/rmfuJCvsbr4yfmTEM+EszcLrZgEx7Xw== + "@spectrum-css/icon@^3.0.0-beta.1": version "3.0.0-beta.1" resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0-beta.1.tgz#62e18e5a83fdfc9ad638ade68e6f2e6457114495"