soft-components
Version:
Simple soft flexible set of web components
411 lines (410 loc) • 11 kB
JavaScript
import { Component, Host, h, Prop, Event, State, Method, Element, } from '@stencil/core';
// import { validityMessages } from '../../utils/validity-messages'
import throttle from 'lodash.throttle';
export class ScDial {
constructor() {
this.value = 50;
/**
* Diameter in pixels (can be changed via CSS variable --sc-dial-size)
*/
this.size = 80;
/**
* Step value of each change
*/
this.step = 1;
this.error = '';
this.focused = false;
this.percent = 0;
this.rotation = 0;
this.cycles = 0;
this.reachedMax = false;
this.reachedMin = false;
// private quadrant: number
// private lastQuadrant: number
this.mouseDirectionX = null;
this.mouseDirectionY = null;
this.mousePrevX = 0;
this.mousePrevY = 0;
}
async setValue(value) {
const { min, max, step } = this;
if (!isNaN(min) && value <= min) {
this.reachedMin = true;
this.value = min;
return;
}
if (!isNaN(max) && value >= max) {
this.reachedMax = true;
this.value = max;
return;
}
this.value = Math.ceil(value / step) * step;
}
setRotationByValue(value) {
this.rotation = ((value % this.total) / this.total) * 360;
}
handleScroll(e) {
e.preventDefault();
const deltaX = -e.detail || e.wheelDeltaX;
const deltaY = -e.detail || e.wheelDeltaY;
const direction = deltaX > 0 || deltaY < 0 ? 1 : deltaX < 0 || deltaY > 0 ? -1 : 0;
direction < 0 ? this.stepUp() : this.stepDown();
}
componentDidLoad() {
const elBox = this.hostEl.getBoundingClientRect();
const { min, max, step } = this;
this.total = 36 * step;
if (typeof min !== 'undefined' && typeof max !== 'undefined') {
this.total = max - min;
}
this.oneStepDeg = (step / this.total) * 360;
// console.log({ oneStepDeg: this.oneStepDeg })
this.centerX =
Math.floor(elBox.left) + document.body.scrollLeft + elBox.width / 2;
this.centerY =
Math.floor(elBox.top) + document.body.scrollTop + elBox.height / 2;
this.setValue(this.value);
this.setRotationByValue(this.value);
this.hostEl.addEventListener('mousewheel', e => this.handleScroll(e));
this.hostEl.addEventListener('touchstart', e => this.handleMoveStart(e, 'touchmove', 'touchend'));
this.hostEl.addEventListener('mousedown', e => this.handleMoveStart(e, 'mousemove', 'mouseup'));
}
disconnectedCallback() {
this.hostEl.removeEventListener('mousewheel', this.handleScroll.bind(this));
}
handleMoveStart(e, onMove, onEnd) {
const fnc = throttle(this.updateOnMove.bind(this), 40);
const body = document.body;
const { deg } = this.getDegFromPointer(e);
this.startingDeg = deg;
this.startingValue = this.value;
body.addEventListener(onMove, fnc, false);
body.addEventListener(onEnd, () => {
body.removeEventListener(onMove, fnc, false);
}, false);
}
getDegFromPointer(event) {
const e = event.changedTouches ? event.changedTouches[0] : event;
const x = e.pageX;
const y = e.pageY;
// get mouse direction
if (x < this.mousePrevX) {
this.mouseDirectionX = 'left';
}
if (x > this.mousePrevX) {
this.mouseDirectionX = 'right';
}
if (y < this.mousePrevY) {
this.mouseDirectionY = 'up';
}
if (y > this.mousePrevY) {
this.mouseDirectionY = 'down';
}
this.mousePrevX = x;
this.mousePrevY = y;
// get angle
const diffX = this.centerX - x;
const diffY = this.centerY - y;
const angleRad = Math.atan2(diffY, diffX); //rad
let angleDeg = (angleRad * 180) / Math.PI + 90;
if (angleDeg < 0) {
angleDeg += 360;
}
return {
deg: angleDeg,
x,
y,
diffX,
diffY,
};
}
updateOnMove(event) {
event.preventDefault();
const { deg: angleDeg, x, y, diffX, diffY } = this.getDegFromPointer(event);
// const degDiff = angleDeg - this.lastDeg
this.degDiff = angleDeg - this.startingDeg;
console.log({ x, y, diffX, diffY });
// if diff in degree is larger than 1 step, step over
if (this.degDiff > this.oneStepDeg) {
if (x > this.centerX &&
y > this.centerY &&
this.mouseDirectionX === 'left') {
console.log(this.mouseDirectionY);
console.log('yo');
// if (!isNaN(this.max)) {
// return
// }
}
this.stepUp(this.degDiff);
}
if (this.degDiff < -1 * this.oneStepDeg) {
this.stepDown(this.degDiff);
}
// const newPercent = angleDeg / 360
// const newVal = newPercent * this.max
// console.log({ percent: this.percent, newPercent, released: this.released })
// if (!this.released) {
// this.setValue(newVal)
// }
}
valueDiff(degDiff) {
return Math.floor(degDiff / this.oneStepDeg) * this.step;
}
stepUp(degDiff = null) {
if (this.reachedMax) {
return;
}
const newVal = degDiff
? this.startingValue + this.valueDiff(degDiff)
: this.value + this.step;
this.setValue(newVal);
this.setRotationByValue(newVal);
this.reachedMin = false;
}
stepDown(degDiff = null) {
if (this.reachedMin) {
return;
}
const newVal = degDiff
? this.startingValue + this.valueDiff(degDiff)
: this.value - this.step;
this.setValue(newVal);
this.setRotationByValue(newVal);
this.reachedMax = false;
}
// private updateRotation(direction) {
// switch (this.diff) {
// case 1:
// this.rotation += this.oneStepDeg
// break
// case -1:
// this.rotation -= this.oneStepDeg
// break
// default:
// break
// }
// }
render() {
const { value, size, rotation, reachedMax, reachedMin, } = this;
return (h(Host, { style: { '--sc-dial-size': `${size}px` } },
h("div", { class: "dial-circle" },
h("div", { class: "pointer", style: { '--sc-dial-angle': `${rotation}deg` } },
h("div", { class: "pointer-circle" })),
h("div", { class: "temp" },
value,
h("div", null,
"max? ",
reachedMax ? 'true' : 'false'),
h("div", null,
"min? ",
reachedMin ? 'true' : 'false')))));
}
static get is() { return "sc-dial"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() { return {
"$": ["sc-dial.scss"]
}; }
static get styleUrls() { return {
"$": ["sc-dial.css"]
}; }
static get properties() { return {
"value": {
"type": "number",
"mutable": true,
"complexType": {
"original": "number | null",
"resolved": "number",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": ""
},
"attribute": "value",
"reflect": false,
"defaultValue": "50"
},
"size": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Diameter in pixels (can be changed via CSS variable --sc-dial-size)"
},
"attribute": "size",
"reflect": false,
"defaultValue": "80"
},
"max": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Max value of dial"
},
"attribute": "max",
"reflect": false
},
"min": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Min value of dial"
},
"attribute": "min",
"reflect": false
},
"step": {
"type": "number",
"mutable": false,
"complexType": {
"original": "number",
"resolved": "number",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Step value of each change"
},
"attribute": "step",
"reflect": false,
"defaultValue": "1"
}
}; }
static get states() { return {
"error": {},
"focused": {},
"percent": {},
"rotation": {},
"degDiff": {},
"cycles": {}
}; }
static get events() { return [{
"method": "inputEvent",
"name": "inputEvent",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when a keyboard input occurred."
},
"complexType": {
"original": "KeyboardEvent",
"resolved": "KeyboardEvent",
"references": {
"KeyboardEvent": {
"location": "global"
}
}
}
}, {
"method": "changeEvent",
"name": "changeEvent",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the value has changed."
},
"complexType": {
"original": "any",
"resolved": "any",
"references": {}
}
}, {
"method": "blurEvent",
"name": "blurEvent",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the input loses focus."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "focusEvent",
"name": "focusEvent",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when the input has focus."
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}, {
"method": "keyDownEvent",
"name": "keyDownEvent",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when a key is pressed down"
},
"complexType": {
"original": "void",
"resolved": "void",
"references": {}
}
}]; }
static get methods() { return {
"setValue": {
"complexType": {
"signature": "(value: any) => Promise<void>",
"parameters": [{
"tags": [],
"text": ""
}],
"references": {
"Promise": {
"location": "global"
}
},
"return": "Promise<void>"
},
"docs": {
"text": "",
"tags": []
}
}
}; }
static get elementRef() { return "hostEl"; }
}