First version

This commit is contained in:
trading_peter 2024-08-19 21:47:51 +02:00
parent 8739cac670
commit 9ee517e810
4 changed files with 578 additions and 39 deletions

227
package-lock.json generated Normal file
View File

@ -0,0 +1,227 @@
{
"name": "@tp/tp-element",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@tp/tp-element",
"version": "0.0.1",
"license": "Apache-2.0",
"dependencies": {
"@tp/helpers": "^2.3.1",
"@tp/tp-input": "^1.0.6",
"lit": "^3.0.0"
}
},
"node_modules/@lit-labs/ssr-dom-shim": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g=="
},
"node_modules/@lit/reactive-element": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
"node_modules/@tp/helpers": {
"version": "2.3.1",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Fhelpers/-/2.3.1/helpers-2.3.1.tgz",
"integrity": "sha512-LjwqF6wHy6SrdY+0m5+fMP0CG8mwb85rVd7ZkMTpmkJXn4PiTYMaLXNcCjq6k4R5AwhzWUasUOteBJNyl9pynw==",
"license": "Apache-2.0"
},
"node_modules/@tp/tp-input": {
"version": "1.0.6",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-input/-/1.0.6/tp-input-1.0.6.tgz",
"integrity": "sha512-i24LTVNX8taafWEcO4FII1YwrLQO5RvA0f4KDGpxU0Ca+WWfSkq3xwJEbiC9eovP/wrB9e2XoLQ1mjm0S79+mg==",
"license": "Apache-2.0",
"dependencies": {
"@tp/helpers": "^1.0.0",
"lit": "^2.2.0"
}
},
"node_modules/@tp/tp-input/node_modules/@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"node_modules/@tp/tp-input/node_modules/@tp/helpers": {
"version": "1.3.0",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Fhelpers/-/1.3.0/helpers-1.3.0.tgz",
"integrity": "sha512-mOAVP45kkEYXwonaOd5jkFQLX1nbeKtl8YX8FpL2ytON0cOSsh6TUAbCEcMU5xqgyD6L1ZEZNvxCjhOKOKdGyA==",
"license": "Apache-2.0"
},
"node_modules/@tp/tp-input/node_modules/lit": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
"dependencies": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/@tp/tp-input/node_modules/lit-element": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.1.0",
"@lit/reactive-element": "^1.3.0",
"lit-html": "^2.8.0"
}
},
"node_modules/@tp/tp-input/node_modules/lit-html": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
},
"node_modules/lit": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/lit/-/lit-3.1.4.tgz",
"integrity": "sha512-q6qKnKXHy2g1kjBaNfcoLlgbI3+aSOZ9Q4tiGa9bGYXq5RBXxkVTqTIVmP2VWMp29L4GyvCFm8ZQ2o56eUAMyA==",
"dependencies": {
"@lit/reactive-element": "^2.0.4",
"lit-element": "^4.0.4",
"lit-html": "^3.1.2"
}
},
"node_modules/lit-element": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.6.tgz",
"integrity": "sha512-U4sdJ3CSQip7sLGZ/uJskO5hGiqtlpxndsLr6mt3IQIjheg93UKYeGQjWMRql1s/cXNOaRrCzC2FQwjIwSUqkg==",
"dependencies": {
"@lit-labs/ssr-dom-shim": "^1.2.0",
"@lit/reactive-element": "^2.0.4",
"lit-html": "^3.1.2"
}
},
"node_modules/lit-html": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz",
"integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==",
"dependencies": {
"@types/trusted-types": "^2.0.2"
}
}
},
"dependencies": {
"@lit-labs/ssr-dom-shim": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.2.0.tgz",
"integrity": "sha512-yWJKmpGE6lUURKAaIltoPIE/wrbY3TEkqQt+X0m+7fQNnAv0keydnYvbiJFP1PnMhizmIWRWOG5KLhYyc/xl+g=="
},
"@lit/reactive-element": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.4.tgz",
"integrity": "sha512-GFn91inaUa2oHLak8awSIigYz0cU0Payr1rcFsrkf5OJ5eSPxElyZfKh0f2p9FsTiZWXQdWGJeXZICEfXXYSXQ==",
"requires": {
"@lit-labs/ssr-dom-shim": "^1.2.0"
}
},
"@tp/helpers": {
"version": "2.3.1",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Fhelpers/-/2.3.1/helpers-2.3.1.tgz",
"integrity": "sha512-LjwqF6wHy6SrdY+0m5+fMP0CG8mwb85rVd7ZkMTpmkJXn4PiTYMaLXNcCjq6k4R5AwhzWUasUOteBJNyl9pynw=="
},
"@tp/tp-input": {
"version": "1.0.6",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Ftp-input/-/1.0.6/tp-input-1.0.6.tgz",
"integrity": "sha512-i24LTVNX8taafWEcO4FII1YwrLQO5RvA0f4KDGpxU0Ca+WWfSkq3xwJEbiC9eovP/wrB9e2XoLQ1mjm0S79+mg==",
"requires": {
"@tp/helpers": "^1.0.0",
"lit": "^2.2.0"
},
"dependencies": {
"@lit/reactive-element": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz",
"integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==",
"requires": {
"@lit-labs/ssr-dom-shim": "^1.0.0"
}
},
"@tp/helpers": {
"version": "1.3.0",
"resolved": "https://gitea.codeblob.work/api/packages/tp-elements/npm/%40tp%2Fhelpers/-/1.3.0/helpers-1.3.0.tgz",
"integrity": "sha512-mOAVP45kkEYXwonaOd5jkFQLX1nbeKtl8YX8FpL2ytON0cOSsh6TUAbCEcMU5xqgyD6L1ZEZNvxCjhOKOKdGyA=="
},
"lit": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz",
"integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==",
"requires": {
"@lit/reactive-element": "^1.6.0",
"lit-element": "^3.3.0",
"lit-html": "^2.8.0"
}
},
"lit-element": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz",
"integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==",
"requires": {
"@lit-labs/ssr-dom-shim": "^1.1.0",
"@lit/reactive-element": "^1.3.0",
"lit-html": "^2.8.0"
}
},
"lit-html": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz",
"integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==",
"requires": {
"@types/trusted-types": "^2.0.2"
}
}
}
},
"@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
},
"lit": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/lit/-/lit-3.1.4.tgz",
"integrity": "sha512-q6qKnKXHy2g1kjBaNfcoLlgbI3+aSOZ9Q4tiGa9bGYXq5RBXxkVTqTIVmP2VWMp29L4GyvCFm8ZQ2o56eUAMyA==",
"requires": {
"@lit/reactive-element": "^2.0.4",
"lit-element": "^4.0.4",
"lit-html": "^3.1.2"
}
},
"lit-element": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.6.tgz",
"integrity": "sha512-U4sdJ3CSQip7sLGZ/uJskO5hGiqtlpxndsLr6mt3IQIjheg93UKYeGQjWMRql1s/cXNOaRrCzC2FQwjIwSUqkg==",
"requires": {
"@lit-labs/ssr-dom-shim": "^1.2.0",
"@lit/reactive-element": "^2.0.4",
"lit-html": "^3.1.2"
}
},
"lit-html": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.1.4.tgz",
"integrity": "sha512-yKKO2uVv7zYFHlWMfZmqc+4hkmSbFp8jgjdZY9vvR9jr4J8fH6FUMXhr+ljfELgmjpvlF7Z1SJ5n5/Jeqtc9YA==",
"requires": {
"@types/trusted-types": "^2.0.2"
}
}
}
}

View File

@ -1,18 +1,20 @@
{ {
"name": "@tp/tp-element", "name": "@tp/tp-number-input",
"version": "0.0.1", "version": "1.0.0",
"description": "", "description": "",
"main": "tp-element.js", "main": "tp-number-input.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.codeblob.work/tp-elements/tp-element.git" "url": "https://gitea.codeblob.work/tp-elements/tp-number-input.git"
}, },
"author": "trading_peter", "author": "trading_peter",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@tp/helpers": "^2.3.1",
"@tp/tp-input": "^1.0.6",
"lit": "^3.0.0" "lit": "^3.0.0"
} }
} }

View File

@ -1,35 +0,0 @@
/**
@license
Copyright (c) 2024 trading_peter
This program is available under Apache License Version 2.0
*/
import { LitElement, html, css } from 'lit';
class TpElement extends LitElement {
static get styles() {
return [
css`
:host {
display: block;
}
`
];
}
render() {
const { } = this;
return html`
`;
}
static get properties() {
return { };
}
}
window.customElements.define('tp-element', TpElement);

345
tp-number-input.js Normal file
View File

@ -0,0 +1,345 @@
/**
@license
Copyright (c) 2024 trading_peter
This program is available under Apache License Version 2.0
*/
import '@tp/tp-input/tp-input.js';
import { LitElement, html, css, svg } from 'lit';
import { FormElement } from '@tp/helpers/form-element.js';
import { EventHelpers } from '@tp/helpers/event-helpers.js';
import { DomQuery } from '@tp/helpers/dom-query.js';
import { closest } from '@tp/helpers/closest.js'
class TpNumberInput extends FormElement(EventHelpers(DomQuery(LitElement))) {
static get styles() {
return [
css`
:host {
display: block;
}
tp-input input {
text-align: center;
}
div[slot="prefix"],
div[slot="suffix"] {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
div[slot="prefix"] > tp-icon,
div[slot="suffix"] > tp-icon {
display: inline;
--tp-icon-width: var(--tp-number-input-icon-width, 17px);
--tp-icon-height: var(--tp-number-input-icon-height, 17px);
}
`
];
}
render() {
const { disabled, invalid, errorMessage, required, autofocus, _internValue } = this;
return html`
<tp-input id="input" exportparts="wrap" .disabled=${disabled} .invalid=${invalid} .value=${_internValue} .autofocus=${autofocus} .errorMessage=${errorMessage} .required=${required}>
<div slot="prefix">
<tp-icon id="sub" part="icons" .icon=${this.decreaseIcon || TpNumberInput.defaultDecreaseIcon} .tooltip=${this.decreaseTooltip}></tp-icon>
</div>
<input id="innerInput" type="text" pattern="[0-9\\.,]+">
<div slot="suffix">
<tp-icon id="add" part="icons" .icon=${this.increaseIcon || TpNumberInput.defaultIncreaseIcon} .tooltip=${this.increaseTooltip}></tp-icon>
</div>
</tp-input>
`;
}
static get properties() {
return {
disabled: { type: Boolean },
invalid: { type: Boolean },
errorMessage: { type: String },
required: { type: Boolean },
autofocus: { type: Boolean },
step: { type: Number },
value: { type: String },
min: { type: Number },
max: { type: Number },
increaseIcon: { type: Object },
decreaseIcon: { type: Object },
increaseTooltip: { type: String },
decreaseTooltip: { type: String },
// Calculation speed after medium long press time.
fastInterval: { type: Number },
// Calculation speed after really long press on one of the buttons.
superFastInterval: { type: Number },
// String to add after the number. Use this to show a unit for example.
// `13` becomes `13px` for example.
suffix: { type: String },
// If true don't include the suffix in the `value` property.
ignoreSuffix: { type: Boolean },
// Value to set if the input is NaN or user tries to go under `min`.
// Useful if you need to support something like `0px` -> `user tabs decrease` -> `inherit` for example.
specialMin: { type: String },
// If true the controls allows to switch through time in intervals defined by step.
// Most other settings are ignore if the control is in this mode.
timeMode: { type: Boolean },
_internValue: { type: String }
};
}
static get defaultIncreaseIcon() {
return svg`<path fill="var(--tp-icon-color)" d="M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z"></path>`;
}
static get defaultDecreaseIcon() {
return svg`<path fill="var(--tp-icon-color)" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10H7v-2h10v2z"></path>`;
}
constructor() {
super();
this._internValue = '0';
this.value = '0';
this.step = 1;
this.min = Number.MIN_SAFE_INTEGER;
this.max = Number.MAX_SAFE_INTEGER;
this.fastInterval = 50;
this.superFastInterval = 100;
}
firstUpdated() {
this.listen(this, 'click', '_onTap');
this.listen(this.$.innerInput, 'blur', '_onBlur');
this.listen(this.$.innerInput, 'keydown', '_onKey');
}
updated(changes) {
if (changes.has('timeMode')) {
this._timeModeChanged();
}
if (changes.has('value') || changes.has('min') || changes.has('max') || changes.has('suffix') || changes.has('ignoreSuffix') || changes.has('timeMode')) {
this._updateValue(this.value);
}
}
_timeModeChanged() {
if (this.timeMode) {
this.unlisten(this, 'mousedown', '_onMouseDown');
} else {
this.listen(this, 'mousedown', '_onMouseDown');
}
}
_onTap(e) {
let val = parseInt(this.value, 10) || 0;
const op = this._getOperation(e);
if(!op) {
return;
}
if(this.timeMode) {
val = this._calcTime(op);
} else {
if(op === 'add') {
val = Math.max(val + this.step, this.min);
} else {
val -= this.step;
}
}
this._updateValue(val);
}
_calcTime(op) {
const parts = this.value.split(':');
if(parts.length)
if(op === 'add') {
this._updateTime(this.step);
} else {
this._updateTime(-this.step);
}
}
_timeValueChanged(value) {
if(!this.timeMode) return;
const parts = this._parseTime(value);
const h = parts[0];
const m = parts[1];
this.value = this._prefix(h) + ':' + this._prefix(m);
this.$.input.value = this.value;
}
_parseTime(value) {
if(!/[0-9]{1,2}:[0-9]{1,2}/.test(value)) {
value = '00:00';
}
const parts = value.split(':');
let h = parts[0];
let m = parts[1];
h = parseInt(h, 10) % 24;
m = parseInt(m, 10) % 59;
return [h, m];
}
_updateTime(amount) {
const parts = this._parseTime(this.$.input.value);
let h = parts[0];
let m = parts[1];
// if (amount > 0) {
// 1. Calculate how many hours were made full.
const addHours = Math.floor((m + amount) / 60);
// 2. Calculate the rest of the minutes after we added to hours.
const minuteRest = m + amount - 60 * addHours;
h += addHours;
if(h < 0) {
h += 24;
}
// 3. Update the time.
this.value = h + ':' + minuteRest;
}
_prefix(val) {
val = val.toString();
if(val.length === 1) {
return '0' + val;
} else {
return val;
}
}
_onMouseDown(e) {
this.unlisten(window, 'mouseup', '_onMouseUp');
this.listen(window, 'mouseup', '_onMouseUp');
const op = this._getOperation(e);
this._asyncJob = setTimeout(() => {
let val = parseInt(this.value, 10) || 0;
const func = function () {
if (op == 'add') {
val += this.step;
} else {
val -= this.step;
}
this._updateValue(val);
}.bind(this);
this._timer = setInterval(func, this.slowInterval);
// Speed up the interval.
this._speedUpJob = setTimeout(() => {
clearInterval(this._timer);
this._timer = null;
this._timer = setInterval(func, this.fastInterval);
}, 2000);
// Switch to super fast interval.
this._superSpeedUpJob = setTimeout(() => {
clearInterval(this._timer);
this._timer = null;
this._timer = setInterval(func, this.superFastInterval);
}, 5000);
}, 500);
}
_onMouseUp(e) {
this.unlisten(window, 'mouseup', '_onMouseUp');
clearTimeout(this._asyncJob);
clearTimeout(this._speedUpJob);
clearTimeout(this._superSpeedUpJob);
clearInterval(this._timer);
this._speedUpJob = null;
this._superSpeedUpJob = null;
this._asyncJob = null;
this._timer = null;
}
_onBlur() {
if(this.timeMode) {
this._timeValueChanged(this.$.input.value);
} else {
this._updateValue(parseInt(this.$.input.value, 10));
}
}
_onKey(e) {
if(e.keyCode === 13) {
this._updateValue(parseInt(this.$.input.value, 10));
}
}
_getOperation(e) {
const btn = e.composedPath()[0];
if(closest(btn, '#sub', true)) {
return 'sub';
} else if(closest(btn, '#add', true)) {
return 'add';
}
}
_internValueChanged(val) {
if(val === '') return;
if(this.timeMode) {
this._timeValueChanged(val);
} else {
this._updateValue(val);
}
}
_updateValue(val) {
if(this.timeMode) return;
if(typeof this.specialMin === 'string' && val === this.specialMin) {
this.value = this.specialMin;
this.$.input.value = this.value;
return;
}
val = parseInt(val, 10);
// If val is NaN and an specialMin is defined, then set it.
// Else set the min value.
if(isNaN(val)) {
val = typeof this.specialMin === 'string' ? this.specialMin : this.min;
}
if((typeof this.specialMin === 'string' && val === this.specialMin) || (val < this.min && typeof this.specialMin === 'string')) {
this.value = this.specialMin;
this.$.input.value = this.value;
return;
}
val = Math.min(this.max, val);
val = Math.max(this.min, val);
this.value = this.ignoreSuffix ? val : (!!this.suffix ? val + this.suffix : val);
this.$.input.value = !!this.suffix ? val + this.suffix : val;
}
}
window.customElements.define('tp-number-input', TpNumberInput);