@conectate/ct-radio
Version:
HTML Radio web component based on Material design
203 lines (190 loc) • 5.17 kB
text/typescript
import { CtLit, customElement, property, query } from "@conectate/ct-lit";
import { PropertyValueMap, css, html } from "lit";
/**
* ## `ct-radio`
* radio element
*
* @group ct-elements
* @element ct-radio
* @attr {boolean} checked
*/
("ct-radio")
export class CtRadio extends CtLit {
({ type: Boolean, reflect: true }) disabled = false;
({ type: Boolean, reflect: true }) checked = false;
({ type: Object }) value: any;
({ type: Object }) parent: any;
({ type: String }) name!: string;
static styles = [
css`
:host {
display: inline-flex;
position: relative;
--ct-checkbox-box-size: 24px;
--ct-checkbox-box-border-radius: 50%;
--ct-checkbox-height: var(--ct-checkbox-box-size);
--ct-checkbox-box-border-size: 3px;
}
#input:focus-visible + .c {
box-shadow: 0 0 0 1px var(--color-primary);
border-radius: 8px;
}
:host([disabled]) {
pointer-events: none;
opacity: 0.33;
}
.c {
display: flex;
flex-direction: row;
align-items: center;
}
#input {
font-family: inherit;
font-size: 100%;
line-height: 1.15;
margin: 0px;
overflow: visible;
box-sizing: border-box;
padding: 0px;
position: absolute;
width: 100%;
height: 100%;
opacity: 0.0001;
z-index: 1;
cursor: pointer;
}
`,
// Checkmark
css`
#checkmark {
opacity: 0;
transform: scale(0);
transition:
opacity 0.13s ease-in-out,
transform 0.13s ease-in-out;
height: 10px;
width: 10px;
overflow: hidden;
background: var(--color-primary, #2cb5e8);
border-radius: 50%;
}
#input:checked + .c > #box #checkmark {
transform: scale(1);
opacity: 1;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
#checkmark.rotate {
animation: rotate 0.3s none ease-out;
animation: rotate 0.3s none cubic-bezier(0.6, 0.49, 0.46, 0.85);
}
`,
// Box
css`
#box {
display: flex;
align-items: center;
justify-content: center;
position: relative;
box-sizing: border-box;
width: var(--ct-checkbox-box-size);
height: var(--ct-checkbox-box-size);
margin: calc((var(--ct-checkbox-height) - var(--ct-checkbox-box-size)) / 2) 0;
flex-grow: 0;
color: var(--color-on-primary, #fff);
flex-shrink: 0;
margin: 8px;
}
#box::before {
display: block;
z-index: 0;
inset: 0;
content: "";
box-sizing: border-box;
position: absolute;
width: var(--ct-checkbox-box-size);
height: var(--ct-checkbox-box-size);
border-radius: var(--ct-checkbox-box-border-radius);
border-width: var(--ct-checkbox-box-border-size);
border-style: solid;
border-color: var(--color-on-background, #535353);
transition:
border 0.13s ease-in-out,
box-shadow 0.13s ease-in-out;
}
#input:checked + .c > #box::before {
/* border-width: calc(var(--ct-checkbox-box-size) / 2); */
}
#input:checked + .c > #box::before {
border-color: var(--color-primary, #2cb5e8);
color: var(--color-on-primary, #fff);
}
`
];
("#input") $input!: HTMLInputElement;
render() {
return html`
<input id="input" type="checkbox" @click=${this.toogleCheck} .checked=${this.checked} .disabled=${this.disabled} @change=${this.handleChange} />
<div class="c">
<span id="box">
<div id="checkmark" dir="ltr"></div>
</span>
<label id="label" for="input"><slot></slot></label>
</div>
`;
}
protected updated(_changedProperties: PropertyValueMap<this>): void {
if (_changedProperties.has("checked") && _changedProperties.get("checked") != undefined) {
// @ts-ignore
this.change();
}
}
click() {
this.$input.click();
}
toogleCheck() {
this.checked = !this.checked;
}
isFn(obj: any) {
return !!(obj && obj.constructor && obj.call && obj.apply);
}
private handleChange(event: Event) {
const target = event.target as HTMLInputElement;
this.checked = target.checked;
redispatchEvent(this, event);
}
change() {
let parent: HTMLElement | null = (this.isFn(this.parent) ? this.parent() : false) || this.parent || this.parentElement || this.parentNode;
if (this.name && parent?.querySelectorAll) {
if (this.checked) {
let radios: NodeListOf<CtRadio> = parent.querySelectorAll("ct-radio");
radios.forEach(radio => {
if (radio.name == this.name && radio != this && this.checked) {
radio.checked = false;
}
});
this.dispatchEvent(new CustomEvent("checked", { detail: { checked: this.checked } }));
}
} else {
this.dispatchEvent(new CustomEvent("checked", { detail: { checked: this.checked } }));
}
}
}
function redispatchEvent(wc: Element, event: Event) {
if (event.bubbles && (!wc.shadowRoot || event.composed)) event.stopPropagation();
const copy = Reflect.construct(event.constructor, [event.type, event]);
const dispatched = wc.dispatchEvent(copy);
if (!dispatched) event.preventDefault();
return dispatched;
}
declare global {
interface HTMLElementTagNameMap {
"ct-radio": CtRadio;
}
}