|
| 1 | +const html = String.raw |
| 2 | +const css = String.raw |
| 3 | +const sliderInputStyles = new CSSStyleSheet() |
| 4 | +sliderInputStyles.replaceSync(css` |
| 5 | + :host { |
| 6 | + cursor: ew-resize; |
| 7 | + } |
| 8 | + input { |
| 9 | + cursor: inherit; |
| 10 | + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.25) 0% var(--value-filled, 0%), transparent var(--value-filled, 0%) 100%); |
| 11 | + } |
| 12 | + input::-webkit-inner-spin-button { |
| 13 | + display: none; |
| 14 | + } |
| 15 | +`) |
| 16 | +/** |
| 17 | + * An example Custom Element. This documentation ends up in the |
| 18 | + * README so describe how this elements works here. |
| 19 | + * |
| 20 | + * You can event add examples on the element is used with Markdown. |
| 21 | + * |
| 22 | + * ``` |
| 23 | + * <slider-input></slider-input> |
| 24 | + * ``` |
| 25 | + */ |
| 26 | +class SliderInputElement extends HTMLElement { |
| 27 | + static observedAttributes = ['value', 'min', 'max'] |
| 28 | + |
| 29 | + #renderRoot!: ShadowRoot |
| 30 | + #initialX = 0 |
| 31 | + #initialValue = 0 |
| 32 | + #valueChanged = false |
| 33 | + #sliderAbortController: AbortController | null |
| 34 | + #stylesheet = new CSSStyleSheet() |
| 35 | + |
| 36 | + get value() { |
| 37 | + return Number(this.#input.value) || 0 |
| 38 | + } |
| 39 | + |
| 40 | + set value(value: number) { |
| 41 | + this.#stylesheet.replaceSync(`:host { |
| 42 | + --value-filled: ${100 * value / (this.max - this.min)}% |
| 43 | + }`) |
| 44 | + this.#input.value = `${value}` |
| 45 | + } |
| 46 | + |
| 47 | + get max() { |
| 48 | + return Number(this.#input.max) || 0 |
| 49 | + } |
| 50 | + |
| 51 | + set max(value: number) { |
| 52 | + this.#input.max = `${value}` |
| 53 | + } |
| 54 | + |
| 55 | + get min() { |
| 56 | + return Number(this.#input.min) || 0 |
| 57 | + } |
| 58 | + |
| 59 | + set min(value: number) { |
| 60 | + this.#input.min = `${value}` |
| 61 | + } |
| 62 | + |
| 63 | + get #input() { |
| 64 | + return this.#renderRoot?.querySelector('input')! |
| 65 | + } |
| 66 | + |
| 67 | + attributeChangedCallback(name: 'value', oldValue: string | null, newValue: string | null) { |
| 68 | + if (!this.#renderRoot) return |
| 69 | + if (name === 'value') { |
| 70 | + console.log(name, newValue) |
| 71 | + this.#input.value = newValue |
| 72 | + } else if (name === 'min') { |
| 73 | + this.#input.min = newValue |
| 74 | + } else if (name === 'max') { |
| 75 | + this.#input.max = newValue |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + connectedCallback(): void { |
| 80 | + this.#renderRoot = this.attachShadow({mode: 'open'}) |
| 81 | + this.#renderRoot.adoptedStyleSheets = [sliderInputStyles, this.#stylesheet] |
| 82 | + this.#renderRoot.innerHTML = html`<input type="number" />` |
| 83 | + this.addEventListener('mousedown', this) |
| 84 | + this.#input.addEventListener('input', this) |
| 85 | + this.#input.value = this.getAttribute('value') |
| 86 | + this.#input.min = this.getAttribute('min') |
| 87 | + this.#input.max = this.getAttribute('max') |
| 88 | + } |
| 89 | + |
| 90 | + handleEvent(event: Event) { |
| 91 | + if (event.type === 'mousedown' && event.which === 1) { |
| 92 | + this.#activateSlider(event) |
| 93 | + } else if (event.type === 'mouseup') { |
| 94 | + this.#sliderAbortController?.abort() |
| 95 | + if (!this.#valueChanged) { |
| 96 | + this.#input.focus() |
| 97 | + this.#input.select() |
| 98 | + } |
| 99 | + this.#valueChanged = false |
| 100 | + } else if (event.type === 'mousemove') { |
| 101 | + const delta = event.screenX - this.#initialX |
| 102 | + const newValue = this.#initialValue + delta |
| 103 | + this.value = Math.max(this.min, Math.min(this.max, newValue)) |
| 104 | + this.#valueChanged = true |
| 105 | + } else if (event.type === 'input') { |
| 106 | + this.value = this.#input.value |
| 107 | + } else { |
| 108 | + console.log(event.screenX, event.clientX, event.pageX) |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + #activateSlider(event: Event) { |
| 113 | + this.#initialX = event.screenX |
| 114 | + this.#initialValue = this.value |
| 115 | + console.log(this.#initialX) |
| 116 | + this.#sliderAbortController?.abort() |
| 117 | + const {signal} = (this.#sliderAbortController = new AbortController()) |
| 118 | + this.ownerDocument.addEventListener('mousemove', this, {signal}) |
| 119 | + this.ownerDocument.addEventListener('mouseup', this, {signal}) |
| 120 | + } |
| 121 | + |
| 122 | + trackCusorPosition() {} |
| 123 | +} |
| 124 | + |
| 125 | +declare global { |
| 126 | + interface Window { |
| 127 | + SliderInputElement: typeof SliderInputElement |
| 128 | + } |
| 129 | +} |
| 130 | + |
| 131 | +export default SliderInputElement |
| 132 | + |
| 133 | +if (!window.customElements.get('slider-input')) { |
| 134 | + window.SliderInputElement = SliderInputElement |
| 135 | + window.customElements.define('slider-input', SliderInputElement) |
| 136 | +} |
0 commit comments