forked from andreasbm/weightless
-
Notifications
You must be signed in to change notification settings - Fork 0
/
label.ts
136 lines (117 loc) · 3.37 KB
/
label.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import { customElement, LitElement, property, html, PropertyValues, TemplateResult } from "lit-element";
import { sharedStyles } from "../style/shared";
import { cssResult } from "../util/css";
import { addListener, EventListenerSubscription, removeListeners } from "../util/event";
import { getSlottedElements, queryParentRoots } from "../util/dom";
import { uniqueID } from "../util/unique";
import styles from "./label.scss";
/**
* Properties of the label.
*/
export interface ILabelProperties {
required: boolean;
nowrap: boolean;
for?: string;
}
/**
* Make form elements more accessible.
* @slot - Default content. If the first element is a form element, clicks on the entire label will be re-fired upon that element.
* @cssprop --label-font-size - Font size
* @cssprop --label-font-family - Font family
* @cssprop --label-line-height - Line height
* @cssprop --label-color - Color.
* @cssprop --label-required-color - Color of the required astrix
*/
@customElement("wl-label")
export class Label extends LitElement implements ILabelProperties {
static styles = [sharedStyles, cssResult(styles)];
/**
* Styles the label as required.
* @attr
*/
@property({ type: Boolean }) required: boolean = false;
/**
* Caps the label element with ellipsis if overflowing.
* @attr
*/
@property({ type: Boolean }) nowrap: boolean = false;
/**
* Query of the form element click events are re-fired upon.
* @attr
*/
@property({ type: String }) for?: string;
/**
* Event listener subscriptions.
*/
protected listeners: EventListenerSubscription[] = [];
/**
* Hooks up the element.
* @param props
*/
firstUpdated(props: PropertyValues) {
super.firstUpdated(props);
this.listeners.push(addListener(this, "click", this.refireClick.bind(this)));
this.updateAria();
}
/**
* Tears down the element.
*/
disconnectedCallback() {
super.disconnectedCallback();
removeListeners(this.listeners);
}
/**
* Returns the target element.
*/
getTargetElement(): Element | null {
// Only continue of a target was specified
if (this.for == null) {
const $elements = getSlottedElements(this.shadowRoot!);
return $elements.length > 0 ? $elements[0] : null;
}
// Make sure for is an id
const forId = this.for[0] === "#" ? this.for : `#${this.for}`;
const matches = queryParentRoots<Element>(this, forId);
return matches.length > 0 ? matches[0] : null;
}
/**
* Updates the aria attributes.
*/
protected updateAria() {
const $target = this.getTargetElement();
if ($target != null) {
// If the ID is not defined we give the label a random ID.
if (this.id == "") {
this.id = uniqueID();
}
// Tell the rest of the world that the target is labelled by this label.
$target.setAttribute("aria-labelledby", this.id);
}
}
/**
* Refires the click event on the target.
*/
protected refireClick(e: MouseEvent) {
const $target = this.getTargetElement();
// Only refire the click if the target was not the target element to begin with
if ($target != null && e.target !== $target) {
$target.dispatchEvent(new MouseEvent("click", { relatedTarget: this }));
if ($target instanceof HTMLElement) {
$target.focus();
}
}
}
/**
* Returns the template for the element.
*/
protected render(): TemplateResult {
return html`
<slot></slot>
`;
}
}
declare global {
interface HTMLElementTagNameMap {
"wl-label": Label;
}
}