ui-lit
Version:
UI Elements on LIT
420 lines (419 loc) • 13.5 kB
JavaScript
import { __decorate } from "tslib";
import { ResizeObserverController } from './../controllers/ResizeObserverController';
import { LitElement, html, nothing } from 'lit';
import { customElement, property, state, query } from 'lit/decorators.js';
import { formAssociated } from '../mixins/form-associated/index';
import { noselect } from '../styles/noselect';
import { labled } from '../mixins/labled/index';
import { focusable } from '../mixins/focusable/index';
import { notificatable } from '../mixins/notificatable/index';
import { rangeStyles } from './style';
/**
* <lit-range></lit-range>
*
* @cssprop --lit-range-thumb-size Thumb size
*
* @cssprop --lit-range-track Track color
* @cssprop --lit-range-track-hover Track color when hovered
*
* @cssprop --lit-range-thumb Thumb color
* @cssprop --lit-range-thumb-shadow Thumb shadow
*
* @cssprop --lit-range-points-background Points background
*
* @cssprop --lit-range-filled Fillted color
* @cssprop --lit-range-filled-hover Fillted color
*
*
* @cssprop --lit-range-outline-focus Outline when focused
*
* */
let LitRange = class LitRange extends focusable(labled(notificatable(formAssociated(LitElement)))) {
constructor() {
super(...arguments);
this.isPercentHidden = true;
this.disabledByVol = true;
this.decimals = 8;
this.showPercent = false;
this.usePoints = true;
this.startFromMin = false;
this._RO = new ResizeObserverController(this);
this._points = [0, 25, 50, 75, 100];
this._timeout = 0;
this._trackSize = 0;
this._trackStartX = 0;
this._thumbSize = 0;
this._padding = 0;
this._rect = null;
this._min = 0;
this._percent = 0;
this._offsetX = 0;
this._lastX = 0;
this.tabindex = 0;
this._max = 100;
this._value = '0';
this._onChangeSize = (rect) => {
this._rect = this.getBoundingClientRect();
this._trackSize = this._calcTackWidth(rect);
this._trackStartX = this._calcTrackStartX(rect);
this._updateOffset();
this.requestUpdate();
};
}
static get styles() {
return [
...super.elementStyles,
noselect,
rangeStyles
];
}
static get properties() {
return {
...super.properties,
value: { type: String, hasChanged: () => true },
min: { type: Number },
max: { type: Number },
};
}
get offsetX() {
return this._offsetX;
}
get min() {
return this._min;
}
set min(value) {
const oldValue = this._min;
if (oldValue === value)
return;
if (value < 0) {
console.warn("Min value must be => 0, replaced to 0.");
value = 0;
}
this._min = value;
this._recalcValue();
this._percent = this._calcPercentByValue();
this.requestUpdate('max', oldValue);
}
get max() {
return this._max;
}
set max(value) {
const oldValue = this._max;
if (oldValue === value)
return;
this._max = value;
this._recalcValue();
this._percent = this._calcPercentByValue();
this.requestUpdate('max', oldValue);
}
get percent() {
return this._percent;
}
get valueAsNumber() {
return Number(this._value);
}
set valueAsNumber(value) {
if (typeof value === 'number') {
this.value = value.toFixed(this.decimals);
}
else if (typeof value === 'string') {
this.value = value;
}
}
get value() {
return this._value;
}
set value(value) {
const oldValue = this._value;
if (oldValue === value)
return;
this._value = value;
this._recalcValue();
this._percent = this._calcPercentByValue();
this.requestUpdate('value', oldValue);
}
get minPercent() {
if (this.startFromMin) {
return 0;
}
if (!this.max)
return 0;
return this.min / this.max * 100;
}
isDisabled() {
return this.disabled || this.max < this.min || !this.max;
}
connectedCallback() {
super.connectedCallback();
this._thumbSize = parseInt(window.getComputedStyle(this).getPropertyValue("--pointer"));
this._padding = parseInt(window.getComputedStyle(this).getPropertyValue("--padding"));
}
willUpdate() {
this._value = this._calcValueByPercent(this._percent).toFixed(this.decimals);
this._updateOffset();
}
updated(props) {
super.updated(props);
if (props.has("value") || props.has("min") || props.has("max")) {
this.style.setProperty(`--percent`, this._percent + "%");
}
}
// ==== templates ====
_pointersTemplate() {
if (this.usePoints) {
return this._points.map(it => {
const filled = this._percent >= it; //&& !this.disabled
return html `<div
data-value = "${it}"
style = "left: ${it}%;"
class = "point point-${it} ${filled ? 'filled' : ''}"></div>`;
});
}
return nothing;
}
_percentTemplate() {
if (!this.showPercent)
return nothing;
const left = this._offsetX + this._padding - (this._thumbSize + 6) * this._percent / 100;
return html `<div style = "transform: translateX(${left}px);"
class = "noselect percent ${this.isPercentHidden ? 'hidden' : ''}">${this._percent}%</div> `;
}
_thumbTemplate() {
const offset = (this._offsetX).toFixed(1);
return html `<div class = "thumb-wrapper"
style = "transform: translateX(${offset}px);">
${this.isDisabled()
? nothing
: html `<div part = "thumb"
class = "thumb ${this._percent === 100 ? 'fullfiled' : ''}"></div>`}
</div>`;
}
render() {
return html `
<div tabindex = "${this.tabindex}"
role = "slider"
class = "wrapper"
= "${this._handleKeyboard}"
aria-valuemin="${this.min}"
aria-valuemax="${this.max}"
aria-valuenow="${this.value}">
<div class = "track"
${this._RO.observe(this._onChangeSize)}
= "${this._onPreventTouch}"
= "${this._onPointerDown}"
= "${this._onPointerMove}"
= "${this._onPointLeave}"
= "${this._onPointerOver}"
= "${this._onPointerLostCapture}">
${this._thumbTemplate()}
<div class = "track-line"></div>
${this._pointersTemplate()}
</div>
${this._percentTemplate()}
</div>`;
}
// ==== Actions ====
_recalcValue() {
let val = this.valueAsNumber;
if (this.min > this.max) {
val = this.min;
}
else if (val > this.max) {
val = this.max;
}
else if (val < this.min) {
val = this.min;
}
this._value = val.toFixed(this.decimals);
}
_calcTrackStartX(rect) {
return rect.x + 2 * this._padding;
}
_calcTackWidth(rect) {
return rect.width;
}
_calcOffset(x) {
return x - this._trackStartX - this._rect.left + this._padding;
}
_calcPercentByOffset(offset) {
let percent = Math.round(offset / (this._trackSize) * 100 * 10) / 10;
if (percent > 100) {
percent = 100;
}
if (this.usePoints) {
if (percent < 3 || !percent) {
percent = 0;
}
else if (percent > 22 && percent < 28) {
percent = 25;
}
else if (percent > 47 && percent < 53) {
percent = 50;
}
else if (percent > 72 && percent < 78) {
percent = 75;
}
else if (percent > 97) {
percent = 100;
}
}
if (percent < this.minPercent) {
return this.minPercent;
}
return percent;
}
_calcPercentByValue() {
let value = 0;
if (!this.max)
return 0;
if (this.startFromMin) {
value = Math.round(((this.valueAsNumber - this.min) / (this.max - this.min)) * 100 * 10) / 10;
}
else {
value = Math.round((this.valueAsNumber / this.max) * 100 * 10) / 10;
}
if (value < 0) {
return 0;
}
if (value > 100) {
return 100;
}
return value;
}
_calcValueByPercent(percent) {
let value = 0;
value = this.max * (percent / 100);
if (this.startFromMin && value < this.min) {
return this.min;
}
if (value > this.max) {
return this.max;
}
return value;
}
_updateOffset() {
const offsetX = Math.round((this._trackSize) * this._percent / 100 * 1e2) / 1e2;
if (offsetX !== this._offsetX) {
this._offsetX = offsetX;
}
}
_hidePercent() {
clearTimeout(this._timeout);
this._timeout = window.setTimeout(() => {
this.isPercentHidden = true;
}, 800);
}
_movePosition(x) {
if (this.isDisabled())
return;
this._lastX = x;
requestAnimationFrame(() => {
const offset = this._calcOffset(x);
const percent = this._calcPercentByOffset(offset);
this.value = this._calcValueByPercent(percent).toString();
this.notify();
});
}
setPercent(value) {
this._percent = Math.max(Math.min(value, 100), 0);
this.value = this._calcValueByPercent(this._percent).toString();
setTimeout(() => this.notify());
}
// ==== Events ====
_onPreventTouch(e) {
e.preventDefault();
}
_onPointerDown(e) {
e.target.setPointerCapture(e.pointerId);
this._handlePointerDown();
this._handlePointerMove(e.clientX);
e.preventDefault();
// Atop auto focus
}
_onPointerMove(e) {
if (!e.isPrimary || !this.hasAttribute("pressed"))
return;
this._handlePointerMove(e.clientX);
e.preventDefault();
}
_onPointerLostCapture(e) {
this._handlePointerUp(this._lastX);
// e.preventDefault();
}
_onPointerOver(e) {
this._onPointOver(e);
}
_handlePointerDown() {
this._rect = this.getBoundingClientRect();
if (this.isDisabled())
return;
this.isPercentHidden = false;
this.setAttribute('pressed', '');
}
_handlePointerUp(x) {
this._hidePercent();
this._movePosition(x);
this.removeAttribute('pressed');
}
_handlePointerMove(x) {
this._movePosition(x);
this.isPercentHidden = false;
}
_onPointOver(e) {
if (this.isDisabled())
return;
clearTimeout(this._timeout);
this.isPercentHidden = false;
this.setAttribute('hover', '');
e.preventDefault();
}
_onPointLeave(e) {
e.preventDefault();
this._hidePercent();
this.removeAttribute('hover');
}
_handleKeyboard(e) {
if (e.key === "ArrowRight" || e.key === "ArrowTop") {
this.setPercent(this._percent + 1);
}
if (e.key === "ArrowLeft" || e.key === "ArrowBottom") {
this.setPercent(this._percent - 1);
}
}
notify() {
this.dispatchEvent(new CustomEvent("changed", {
detail: {
value: this.value,
percent: this._percent,
valueAsNumber: this.valueAsNumber,
type: 'range'
},
bubbles: true,
}));
}
};
__decorate([
state()
], LitRange.prototype, "isPercentHidden", void 0);
__decorate([
state()
], LitRange.prototype, "disabledByVol", void 0);
__decorate([
property({ type: Number })
], LitRange.prototype, "decimals", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], LitRange.prototype, "showPercent", void 0);
__decorate([
property({ type: Boolean })
], LitRange.prototype, "usePoints", void 0);
__decorate([
property({ type: Boolean, reflect: true })
], LitRange.prototype, "startFromMin", void 0);
__decorate([
query('.track')
], LitRange.prototype, "_wrapper", void 0);
LitRange = __decorate([
customElement("lit-range")
], LitRange);
export { LitRange };