-
+
-
+
-
+
` 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`
+