wired-elements
Version:
Collection of hand-drawn sketchy web components
178 lines (163 loc) • 4.42 kB
text/typescript
import { WiredBase, BaseCSS, Point } from './wired-base';
import { line, ellipse } from './wired-lib';
import { css, TemplateResult, html, CSSResultArray } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
('wired-slider')
export class WiredSlider extends WiredBase {
({ type: Number }) min = 0;
({ type: Number }) max = 100;
({ type: Number }) step = 1;
({ type: Boolean, reflect: true }) disabled = false;
('input') private input?: HTMLInputElement;
private knob?: SVGElement;
private canvasWidth = 300;
private pendingValue?: number;
static get styles(): CSSResultArray {
return [
BaseCSS,
css`
:host {
display: inline-block;
position: relative;
width: 300px;
box-sizing: border-box;
}
:host([disabled]) {
opacity: 0.45 ;
cursor: default;
pointer-events: none;
background: rgba(0, 0, 0, 0.07);
border-radius: 5px;
}
input[type=range] {
width: 100%;
height: 40px;
box-sizing: border-box;
margin: 0;
-webkit-appearance: none;
background: transparent;
outline: none;
position: relative;
}
input[type=range]:focus {
outline: none;
}
input[type=range]::-ms-track {
width: 100%;
cursor: pointer;
background: transparent;
border-color: transparent;
color: transparent;
}
input[type=range]::-moz-focus-outer {
outline: none;
border: 0;
}
input[type=range]::-moz-range-thumb {
border-radius: 50px;
background: none;
cursor: pointer;
border: none;
margin: 0;
height: 20px;
width: 20px;
line-height: 1;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border-radius: 50px;
background: none;
cursor: pointer;
border: none;
height: 20px;
width: 20px;
margin: 0;
line-height: 1;
}
.knob{
fill: var(--wired-slider-knob-color, rgb(51, 103, 214));
stroke: var(--wired-slider-knob-color, rgb(51, 103, 214));
}
.bar {
stroke: var(--wired-slider-bar-color, rgb(0, 0, 0));
}
input:focus + div svg .knob {
stroke: var(--wired-slider-knob-outline-color, #000);
fill-opacity: 0.8;
}
`
];
}
get value(): number {
if (this.input) {
return +this.input.value;
}
return this.min;
}
set value(v: number) {
if (this.input) {
this.input.value = `${v}`;
} else {
this.pendingValue = v;
}
this.updateThumbPosition();
}
firstUpdated() {
this.value = this.pendingValue || +(this.getAttribute('value') || this.value || this.min);
delete this.pendingValue;
}
render(): TemplateResult {
return html`
<div id="container">
<input type="range"
min="${this.min}"
max="${this.max}"
step="${this.step}"
?disabled="${this.disabled}"
@input="${this.onInput}">
<div id="overlay">
<svg></svg>
</div>
</div>
`;
}
focus() {
if (this.input) {
this.input.focus();
} else {
super.focus();
}
}
private onInput(e: Event) {
e.stopPropagation();
this.updateThumbPosition();
if (this.input) {
this.fire('change', { value: +this.input.value });
}
}
wiredRender(force = false) {
super.wiredRender(force);
this.updateThumbPosition();
}
protected canvasSize(): Point {
const s = this.getBoundingClientRect();
return [s.width, s.height];
}
protected draw(svg: SVGSVGElement, size: Point) {
this.canvasWidth = size[0];
const midY = Math.round(size[1] / 2);
line(svg, 0, midY, size[0], midY, this.seed).classList.add('bar');
this.knob = ellipse(svg, 10, midY, 20, 20, this.seed);
this.knob.classList.add('knob');
}
private updateThumbPosition() {
if (this.input) {
const value = +this.input!.value;
const delta = Math.max(this.step, this.max - this.min);
const pct = (value - this.min) / delta;
if (this.knob) {
this.knob.style.transform = `translateX(${pct * (this.canvasWidth - 20)}px)`;
}
}
}
}