blob: 459476ea811c988b9beef0b32d92ee1f04ebfe39 [file] [log] [blame]
/* eslint-disable no-self-assign */
// Copyright 2018 Google LLC
//
// Licensed 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
//
// https://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 CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/**
* @module elements-sk/checkbox-sk
* @description <h2><code>checkbox-sk</code></h2>
*
* <p>
* The checkbox-sk and element contains a native 'input'
* element in light DOM so that it can participate in a form element.
* </p>
*
* <p>
* Each element also supports the following attributes exactly as the
* native checkbox element:
* <ul>
* <li>checked</li>
* <li>disabled</li>
* <li>name</li>
* </ul>
* </p>
*
* <p>
* All the normal events of a native checkbox are supported.
* </p>
*
* @attr label - A string, with no markup, that is to be used as the label for
* the checkbox. If you wish to have a label with markup then set
* 'label' to the empty string and create your own
* <code>label</code> element in the DOM with the 'for' attribute
* set to match the name of the checkbox-sk.
*
* @prop checked This mirrors the checked attribute.
* @prop disabled This mirrors the disabled attribute.
* @prop name This mirrors the name attribute.
* @prop label This mirrors the label attribute.
*
*/
import { define } from '../define';
import { upgradeProperty } from '../upgradeProperty';
export class CheckOrRadio extends HTMLElement {
protected get _role() {
return 'checkbox';
}
static get observedAttributes() {
return ['checked', 'disabled', 'name', 'label'];
}
private _label: HTMLSpanElement | null = null;
private _input: HTMLInputElement | null = null;
content: string = `<label><input type=${this._role}></input>
<span class=icons>
<span class="icon-sk unchecked">check_box_outline_blank</span>
<span class="icon-sk checked">check_box</span>
</span>
<span class=label></span></label>`;
connectedCallback() {
this.innerHTML = this.content;
this._label = this.querySelector<HTMLSpanElement>('.label');
this._input = this.querySelector<HTMLInputElement>('input');
upgradeProperty(this, 'checked');
upgradeProperty(this, 'disabled');
upgradeProperty(this, 'name');
upgradeProperty(this, 'label');
// Since attributeChangedCallback can be called before connectedCallback,
// and we have non-trivial actions when the attributes change to reflect
// the values to this._input, we need trigger each setter on
// connectedCallback.
this.checked = this.checked;
this.disabled = this.disabled;
this.name = this.name;
this.label = this.label;
this._input!.checked = this.checked;
this._input!.disabled = this.disabled;
this._input!.setAttribute('name', this.getAttribute('name') || '');
this._label!.textContent = this.getAttribute('label');
// TODO(jcgregorio) Do we capture and alter the 'input' and 'change' events generated
// by the input element so that the evt.target points to 'this'?
this._input!.addEventListener('change', (e) => {
this.checked = (e.target as HTMLInputElement).checked;
});
this.addEventListener('click', (e) => {
if (e.target === this) {
if (this.checked && this._role === 'radio') {
return;
}
this._input!.click();
}
});
}
get checked(): boolean {
return this.hasAttribute('checked');
}
set checked(val: boolean) {
const isTrue = !!val;
this._input!.checked = isTrue;
if (val) {
this.setAttribute('checked', '');
} else {
this.removeAttribute('checked');
}
}
get disabled(): boolean {
return this.hasAttribute('disabled');
}
set disabled(val: boolean) {
const isTrue = !!val;
this._input!.disabled = isTrue;
if (isTrue) {
this.setAttribute('disabled', '');
} else {
this.removeAttribute('disabled');
}
}
get name(): string {
return this.getAttribute('name') || '';
}
set name(val: string) {
if (val === null || val === undefined) {
return;
}
this.setAttribute('name', val);
this._input!.setAttribute('name', val);
}
get label(): string {
return this.getAttribute('label') || '';
}
set label(val: string) {
if (val === null || val === undefined) {
return;
}
this.setAttribute('label', val);
}
attributeChangedCallback(
name: string,
oldValue: string | null,
newValue: string | null
): void {
if (!this._input) {
return;
}
// Strictly check for null since an empty string doesn't mean false
// for a boolean attribute.
const isTrue = newValue != null;
switch (name) {
case 'checked':
this._input.checked = isTrue;
break;
case 'disabled':
this._input.disabled = isTrue;
break;
case 'name':
this._input.name = newValue || '';
break;
case 'label':
this._label!.textContent = newValue;
break;
default:
break;
}
}
}
define('checkbox-sk', CheckOrRadio);
export const sayHello = () => 'hello';