Moving range input component

This commit is contained in:
Jake Archibald
2018-10-09 12:19:24 +01:00
parent 765cc213d2
commit 48bb58dc89
6 changed files with 4 additions and 4 deletions

View File

@@ -1,116 +0,0 @@
import { bind } from '../../../../lib/util';
import './styles.css';
const RETARGETED_EVENTS = ['focus', 'blur'];
const UPDATE_EVENTS = ['input', 'change'];
const REFLECTED_PROPERTIES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
const REFLECTED_ATTRIBUTES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
class RangeInputElement extends HTMLElement {
private _input = document.createElement('input');
private _valueDisplayWrapper = document.createElement('div');
private _valueDisplay = document.createElement('span');
private _ignoreChange = false;
static get observedAttributes() {
return REFLECTED_ATTRIBUTES;
}
constructor() {
super();
this._input.type = 'range';
for (const event of RETARGETED_EVENTS) {
this._input.addEventListener(event, this._retargetEvent, true);
}
for (const event of UPDATE_EVENTS) {
this._input.addEventListener(event, this._update, true);
}
}
get labelPrecision(): string {
return this.getAttribute('label-precision') || '';
}
set labelPrecision(precision: string) {
this.setAttribute('label-precision', precision);
}
connectedCallback() {
if (this._input.parentNode !== this) {
this.appendChild(this._input);
this._valueDisplayWrapper.appendChild(this._valueDisplay);
this.appendChild(this._valueDisplayWrapper);
}
}
attributeChangedCallback(name: string, oldValue: string, newValue: string | null) {
if (this._ignoreChange) return;
if (newValue === null) {
this._input.removeAttribute(name);
} else {
this._input.setAttribute(name, newValue);
}
this._reflectAttributes();
this._update();
}
@bind
private _retargetEvent(event: Event) {
event.stopImmediatePropagation();
const retargetted = new Event(event.type, event);
this.dispatchEvent(retargetted);
}
@bind
private _update() {
const value = parseFloat(this.value || '0');
const min = parseFloat(this.min || '0');
const max = parseFloat(this.max || '100');
const labelPrecision = parseFloat(this.labelPrecision || '0');
const percent = 100 * (value - min) / (max - min);
const displayValue = labelPrecision ? value.toPrecision(labelPrecision) :
Math.round(value).toString();
this._valueDisplay.textContent = displayValue;
this.style.setProperty('--value-percent', percent + '%');
this.style.setProperty('--value-width', '' + displayValue.length);
}
private _reflectAttributes() {
this._ignoreChange = true;
for (const attributeName in REFLECTED_ATTRIBUTES) {
const attributeValue = this._input.getAttribute(attributeName);
if (attributeValue === null) continue;
this.setAttribute(attributeName, attributeValue);
}
this._ignoreChange = false;
}
}
interface RangeInputElement {
name: string;
min: string;
max: string;
step: string;
value: string;
disabled: boolean;
}
for (const prop of REFLECTED_PROPERTIES) {
Object.defineProperty(RangeInputElement.prototype, prop, {
get() {
return this._input[prop];
},
set(val) {
this._input[prop] = val;
this._reflectAttributes();
this._update();
},
});
}
export default RangeInputElement;
customElements.define('range-input', RangeInputElement);

View File

@@ -1,9 +0,0 @@
declare namespace JSX {
interface RangeInputAttributes extends HTMLAttributes {
reversed?: boolean;
}
interface IntrinsicElements {
'range-input': RangeInputAttributes;
}
}

View File

@@ -1,122 +0,0 @@
range-input {
position: relative;
display: flex;
height: 18px;
width: 130px;
margin: 2px;
font: inherit;
line-height: 16px;
overflow: visible;
}
/* Disabled inputs are greyed out */
range-input[disabled] {
filter: grayscale(1);
}
/* Reversed Variant */
range-input[reversed] input,
range-input[reversed]::before,
range-input[reversed] > div {
transform: scaleX(-1);
}
range-input[reversed] > div > span {
transform: scaleX(-1) scale(.2);
}
range-input[reversed] input:focus + div span {
transform: scaleX(-1) scale(1);
}
range-input input {
position: relative;
flex: 1;
vertical-align: middle;
width: 100%;
padding: 0 !important;
margin: 0 !important;
background: none;
appearance: none;
-webkit-appearance: none;
outline: none;
}
range-input input::-webkit-slider-runnable-track,
range-input input::-moz-range-track,
range-input input::-ms-track {
appearance: none;
-ms-appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
}
range-input::before {
content: '';
display: block;
position: absolute;
top: 8px;
left: 0;
width: 100%;
height: 2px;
outline: none;
border: none;
background: #eee;
border-radius: 1px;
box-shadow: 0 -.5px 0 rgba(0,0,0,0.3), inset 0 .5px 0 rgba(255,255,255,0.2), 0 .5px 0 rgba(255,255,255,0.3);
background: linear-gradient(#34B9EB, #218ab1) 0/ var(--value-percent, 0%) 100% no-repeat #eee;
}
range-input input::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><circle cx="5" cy="5" r="1" fill="#5D509E" /></svg>') center no-repeat #34B9EB;
box-sizing: content-box;
border-radius: 50%;
width: 12px;
height: 12px;
border: none;
box-shadow: 0 0.5px 2px rgba(0,0,0,0.3);
outline: none;
}
range-input input:focus::-webkit-slider-thumb {
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
range-input > div {
position: absolute;
left: 6px;
right: 6px;
bottom: 0;
height: 0;
overflow: visible;
}
range-input > div > span {
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="62" fill="none"><path fill="#34B9EB" d="M27.3 27.3C25 29.6 17 35.8 17 43v3c0 3 2.5 5 3.2 5.8a6 6 0 1 1-8.5 0C12.6 51 15 49 15 46v-3c0-7.2-8-13.4-10.3-15.7A16 16 0 0 1 16 0a16 16 0 0 1 11.3 27.3z"/><circle cx="16" cy="56" r="1" fill="#5D509E"/></svg>') top center no-repeat;
position: absolute;
box-sizing: border-box;
left: var(--value-percent, 0%);
bottom: 3px;
width: 32px;
height: 62px;
text-align: center;
padding: 8px 3px 0;
margin: 0 0 0 -16px;
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.3));
transform-origin: 50% 90%;
opacity: 0.0001;
transform: scale(.2);
color: #fff;
font: inherit;
font-size: calc(100% - var(--value-width, 3) / 5 * .2em);
text-overflow: clip;
text-shadow: 0 -.5px 0 rgba(0,0,0,0.4);
transition: transform 200ms ease, opacity 200ms ease;
will-change: transform;
pointer-events: none;
overflow: hidden;
}
range-input input:focus + div span {
opacity: 1;
transform: scale(1);
}