139 lines
3.7 KiB
JavaScript
139 lines
3.7 KiB
JavaScript
import { DotDungeonIcon } from "./icon.mjs";
|
|
import { StyledShadowElement } from "./mixins/Styles.mjs";
|
|
|
|
/**
|
|
Attributes:
|
|
@property {string} name - The path to the value to update in the datamodel
|
|
@property {number} value - The actual value of the input
|
|
@property {number} max - The maximum value that this range has
|
|
|
|
@extends {HTMLElement}
|
|
*/
|
|
export class DotDungeonRange
|
|
extends StyledShadowElement(
|
|
HTMLElement,
|
|
{ mode: `open`, delegatesFocus: true }
|
|
) {
|
|
static elementName = `dd-range`;
|
|
static formAssociated = true;
|
|
|
|
static observedAttributes = [`max`];
|
|
|
|
static _stylePath = `v3/components/range.css`;
|
|
|
|
_internals;
|
|
#input;
|
|
|
|
constructor() {
|
|
super();
|
|
|
|
// Form internals
|
|
this._internals = this.attachInternals();
|
|
this._internals.role = `spinbutton`;
|
|
};
|
|
|
|
get form() {
|
|
return this._internals.form;
|
|
};
|
|
|
|
get name() {
|
|
return this.getAttribute(`name`);
|
|
};
|
|
set name(value) {
|
|
this.setAttribute(`name`, value);
|
|
};
|
|
|
|
get value() {
|
|
try {
|
|
return parseInt(this.getAttribute(`value`));
|
|
} catch {
|
|
throw new Error(`Failed to parse attribute: "value" - Make sure it's an integer`);
|
|
};
|
|
};
|
|
set value(value) {
|
|
this.setAttribute(`value`, value);
|
|
};
|
|
|
|
get max() {
|
|
try {
|
|
return parseInt(this.getAttribute(`max`));
|
|
} catch {
|
|
throw new Error(`Failed to parse attribute: "max" - Make sure it's an integer`);
|
|
};
|
|
};
|
|
set max(value) {
|
|
this.setAttribute(`max`, value);
|
|
};
|
|
|
|
get type() {
|
|
return `number`;
|
|
};
|
|
|
|
connectedCallback() {
|
|
super.connectedCallback();
|
|
|
|
// Attribute validation
|
|
if (!this.hasAttribute(`max`)) {
|
|
throw new Error(`dotdungeon | Cannot have a range without a maximum value`);
|
|
};
|
|
|
|
// Keyboard accessible input for the thing
|
|
this.#input = document.createElement(`input`);
|
|
this.#input.type = `number`;
|
|
this.#input.min = 0;
|
|
this.#input.max = this.max;
|
|
this.#input.value = this.value;
|
|
this.#input.addEventListener(`change`, () => {
|
|
const inputValue = parseInt(this.#input.value);
|
|
if (inputValue === this.value) return;
|
|
this._updateValue.bind(this)(Math.sign(this.value - inputValue));
|
|
this._updateValue(Math.sign(this.value - inputValue));
|
|
});
|
|
this._shadow.appendChild(this.#input);
|
|
|
|
// Shadow-DOM construction
|
|
this._elements = new Array(this.max);
|
|
const container = document.createElement(`div`);
|
|
container.classList.add(`container`);
|
|
|
|
// Creating the node for filled content
|
|
const filledContainer = document.createElement(`div`);
|
|
filledContainer.classList.add(`range-increment`, `filled`);
|
|
const filledNode = this.querySelector(`[slot="filled"]`);
|
|
if (filledNode) filledContainer.appendChild(filledNode);
|
|
|
|
const emptyContainer = document.createElement(`div`);
|
|
emptyContainer.classList.add(`range-increment`, `empty`);
|
|
const emptyNode = this.querySelector(`[slot="empty"]`);
|
|
if (emptyNode) emptyContainer.appendChild(emptyNode);
|
|
|
|
this._elements.fill(filledContainer, 0, this.value);
|
|
this._elements.fill(emptyContainer, this.value);
|
|
container.append(...this._elements.map((slot, i) => {
|
|
const node = slot.cloneNode(true);
|
|
node.setAttribute(`data-index`, i + 1);
|
|
node.addEventListener(`click`, () => {
|
|
const filled = node.classList.contains(`filled`);
|
|
this._updateValue(filled ? -1 : 1);
|
|
});
|
|
return node;
|
|
}));
|
|
this._shadow.appendChild(container);
|
|
|
|
/*
|
|
This converts all of the namespace prefixed properties on the element to
|
|
CSS variables so that they don't all need to be provided by doing style=""
|
|
*/
|
|
for (const attrVar of this.attributes) {
|
|
if (attrVar.name?.startsWith(`var:`)) {
|
|
const prop = attrVar.name.replace(`var:`, ``);
|
|
this.style.setProperty(`--` + prop, attrVar.value);
|
|
};
|
|
};
|
|
};
|
|
|
|
_updateValue(delta) {
|
|
this.value += delta;
|
|
this.dispatchEvent(new Event(`change`, { bubbles: true }));
|
|
};
|
|
};
|