GitLab Commit is coming up on August 3-4. Learn how to innovate together using GitLab, the DevOps platform. Register for free: gitlabcommitvirtual2021.com

Commit bf5cd6ef authored by David Sveningsson's avatar David Sveningsson
Browse files

feat(meta): new property `labelable`

Marks that an element can be labeled by a `<label>` element.
parent ee287745
Pipeline #180219204 passed with stages
in 10 minutes and 18 seconds
......@@ -38,6 +38,7 @@ export interface MetaElement {
transparent?: boolean;
scriptSupporting?: boolean;
form?: boolean;
labelable?: boolean;
/* attributes */
deprecatedAttributes?: string[];
......@@ -176,6 +177,13 @@ In HTML5 both the `<script>` and `<template>` tags are considered script-support
Elements which are considered to be a form-element should set this flag to `true`.
In plain HTML only the `<form>` element is considered a form but when using custom components the form element might be wrapped inside and to make rules related to forms pick up the custom element this flag should be set.
### `labelable`
[Labelable elements][whatwg-labelable] are elements which can have an associated `<label>` element.
This is typically elements input elements such as `<input>`.
[whatwg-labelable]: https://html.spec.whatwg.org/multipage/forms.html#category-label
## Permitted content
### `attributes`
......
......@@ -3454,7 +3454,7 @@ exports[`HTML elements <keygen> valid markup 1`] = `Array []`;
exports[`HTML elements <label> invalid markup 1`] = `
Array [
Object {
"errorCount": 2,
"errorCount": 10,
"filePath": "test-files/elements/label-invalid.html",
"messages": Array [
Object {
......@@ -3479,6 +3479,94 @@ Array [
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 12,
"message": "<label> is associated with multiple controls",
"offset": 182,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(3)",
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 16,
"message": "<label> is associated with multiple controls",
"offset": 265,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(4)",
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 20,
"message": "<label> is associated with multiple controls",
"offset": 324,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(5)",
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 24,
"message": "<label> is associated with multiple controls",
"offset": 361,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(6)",
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 28,
"message": "<label> is associated with multiple controls",
"offset": 412,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(7)",
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 32,
"message": "<label> is associated with multiple controls",
"offset": 467,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(8)",
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 36,
"message": "<label> is associated with multiple controls",
"offset": 530,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(9)",
"severity": 2,
"size": 5,
},
Object {
"column": 2,
"context": undefined,
"line": 40,
"message": "<label> is associated with multiple controls",
"offset": 585,
"ruleId": "multiple-labeled-controls",
"selector": "label:nth-child(10)",
"severity": 2,
"size": 5,
},
],
"source": "<!-- should not allow flow -->
<label>
......@@ -3489,6 +3577,40 @@ Array [
<label>
<label>foo</label>
</label>
<!-- should not allow multiple controls -->
<label>
<button type=\\"button\\"></button>
<button type=\\"button\\"></button>
</label>
<label>
<input type=\\"text\\">
<input type=\\"text\\">
</label>
<label>
<keygen>
<keygen>
</label>
<label>
<meter></meter>
<meter></meter>
</label>
<label>
<output></output>
<output></output>
</label>
<label>
<progress></progress>
<progress></progress>
</label>
<label>
<select></select>
<select></select>
</label>
<label>
<textarea></textarea>
<textarea></textarea>
</label>
",
"warningCount": 0,
},
......
......@@ -188,6 +188,7 @@
"flow": true,
"phrasing": true,
"interactive": true,
"labelable": true,
"deprecatedAttributes": ["datasrc", "datafld", "dataformatas"],
"requiredAttributes": ["type"],
"attributes": {
......@@ -533,6 +534,7 @@
"phrasing": true,
"interactive": ["matchAttribute", ["type", "!=", "hidden"]],
"void": true,
"labelable": true,
"deprecatedAttributes": [
"datasrc",
"datafld",
......@@ -612,7 +614,8 @@
"flow": true,
"phrasing": true,
"interactive": true,
"void": true
"void": true,
"labelable": true
},
"label": {
......@@ -699,6 +702,7 @@
"meter": {
"flow": true,
"phrasing": true,
"labelable": true,
"permittedContent": ["@phrasing"],
"permittedDescendants": [{ "exclude": "meter" }]
},
......@@ -812,6 +816,7 @@
"output": {
"flow": true,
"phrasing": true,
"labelable": true,
"permittedContent": ["@phrasing"]
},
......@@ -879,6 +884,7 @@
"progress": {
"flow": true,
"phrasing": true,
"labelable": true,
"permittedContent": ["@phrasing"],
"permittedDescendants": [{ "exclude": "progress" }]
},
......@@ -953,6 +959,7 @@
"flow": true,
"phrasing": true,
"interactive": true,
"labelable": true,
"attributes": {
"autofocus": [],
"disabled": [],
......@@ -1118,6 +1125,7 @@
"flow": true,
"phrasing": true,
"interactive": true,
"labelable": true,
"attributes": {
"autocomplete": ["on", "off"],
"autofocus": [],
......
......@@ -41,6 +41,7 @@ export interface MetaData {
implicitClosed?: string[];
scriptSupporting?: boolean;
form?: boolean;
labelable?: boolean;
/* attribute */
deprecatedAttributes?: string[];
......@@ -72,7 +73,8 @@ export type MetaLookupableProperty =
| "void"
| "transparent"
| "scriptSupporting"
| "form";
| "form"
| "labelable";
/**
* Properties listed here can be copied (loaded) onto another element using
......@@ -88,6 +90,7 @@ export const MetaCopyableProperty = [
"interactive",
"transparent",
"form",
"labelable",
"requiredAttributes",
"attributes",
"permittedContent",
......
......@@ -2,18 +2,9 @@ import { ElementReadyEvent } from "../event";
import { Rule, RuleDocumentation, ruleDocumentationUrl } from "../rule";
import { HtmlElement } from "../dom";
const labelable = [
"button",
"input",
"keygen",
"meter",
"output",
"progress",
"select",
"textarea",
].join(",");
export default class MultipleLabeledControls extends Rule {
private labelable: string;
public documentation(): RuleDocumentation {
return {
description: `A \`<label>\` element can only be associated with one control at a time.`,
......@@ -22,6 +13,8 @@ export default class MultipleLabeledControls extends Rule {
}
public setup(): void {
this.labelable = this.getTagsWithProperty("labelable").join(",");
this.on("element:ready", (event: ElementReadyEvent) => {
const { target } = event;
......@@ -46,7 +39,9 @@ export default class MultipleLabeledControls extends Rule {
private getNumLabledControls(src: HtmlElement): number {
/* get all controls wrapped by label element */
const controls = src.querySelectorAll(labelable).map((node) => node.id);
const controls = src
.querySelectorAll(this.labelable)
.map((node) => node.id);
/* only count wrapped controls if the "for" attribute is missing or static,
* for dynamic "for" attributes it is better to run in document mode later */
......
......@@ -7,3 +7,37 @@
<label>
<label>foo</label>
</label>
<!-- should not allow multiple controls -->
<label>
<button type="button"></button>
<button type="button"></button>
</label>
<label>
<input type="text">
<input type="text">
</label>
<label>
<keygen>
<keygen>
</label>
<label>
<meter></meter>
<meter></meter>
</label>
<label>
<output></output>
<output></output>
</label>
<label>
<progress></progress>
<progress></progress>
</label>
<label>
<select></select>
<select></select>
</label>
<label>
<textarea></textarea>
<textarea></textarea>
</label>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment