diff --git a/module/components/incrementer.mjs b/module/components/incrementer.mjs index 146a391..6f8c974 100644 --- a/module/components/incrementer.mjs +++ b/module/components/incrementer.mjs @@ -15,150 +15,143 @@ export class DotDungeonIncrementer extends HTMLElement { static elementName = `dd-incrementer`; static formAssociated = true; - static styles = ``; - - #min; - #max; - #smallStep; - #largeStep; - + static #styles = ``; + _internals; + _shadow; #input; - #publicInput; - #sr; + + _min; + _max; + _smallStep; + _largeStep; constructor() { super(); - + this._shadow = this.attachShadow({ mode: `open`, delegatesFocus: true }); this._internals = this.attachInternals(); + this._internals.role = `spinbutton`; const value = this.getAttribute(`value`); - this.#min = parseInt(this.getAttribute(`min`) ?? 0); - this.#max = parseInt(this.getAttribute(`max`) ?? 0); - this.#smallStep = parseInt(this.getAttribute(`smallStep`) ?? 1); - this.#largeStep = parseInt(this.getAttribute(`largeStep`) ?? 5); + this._min = parseInt(this.getAttribute(`min`) ?? 0); + this._max = parseInt(this.getAttribute(`max`) ?? 0); + this._smallStep = parseInt(this.getAttribute(`smallStep`) ?? 1); + this._largeStep = parseInt(this.getAttribute(`largeStep`) ?? 5); + + this._internals.ariaValueMin = this._min; + this._internals.ariaValueMax = this._max; - const sr = this.attachShadow({ - mode: `open`, - delegatesFocus: true - }); - this.#sr = sr; const container = document.createElement(`div`); - if (DotDungeonIncrementer.styles) this.#embedStyles(); + if (DotDungeonIncrementer.#styles) this.#embedStyles(); // The input that the user can see / modify const input = document.createElement(`input`); this.#input = input; input.type = `number`; + input.ariaHidden = true; input.min = this.getAttribute(`min`); input.max = this.getAttribute(`max`); input.addEventListener(`change`, this.#updateValue.bind(this)); input.value = value; - // input.id = this.id; - // this.removeAttribute(`id`); - // plus button const increment = document.createElement(`span`); increment.innerHTML = `+`; - // increment.type = `button`; - // increment.tabIndex = -1; + increment.ariaHidden = true; increment.classList.value = `increment`; - increment.addEventListener(`click`, this.#increment.bind(this)); + increment.addEventListener(`mousedown`, this.#increment.bind(this)); // minus button const decrement = document.createElement(`span`); decrement.innerHTML = `-`; - // decrement.type = `button`; - // decrement.tabIndex = -1; + decrement.ariaHidden = true; decrement.classList.value = `decrement`; - decrement.addEventListener(`click`, this.#decrement.bind(this)); - + decrement.addEventListener(`mousedown`, this.#decrement.bind(this)); // Construct the DOM container.appendChild(decrement); container.appendChild(input); container.appendChild(increment); - sr.appendChild(container); + this._shadow.appendChild(container); }; - connectedCallback() { - /* - This input exists for the sole purpose of making it so that the form data - works with this input without needing to do jank work arounds, as Foundry - only listens for change events from a small subset of elements which makes - this a bit a jank work around as it is. - */ - const hiddenInput = document.createElement(`input`); - this.#publicInput = hiddenInput; - hiddenInput.type = `hidden`; - hiddenInput.value = this.#input.value; - hiddenInput.name = this.getAttribute(`name`); - // this.removeAttribute(`name`); - // this.appendChild(hiddenInput); + get form() { + return this._internals.form; + } - if (!DotDungeonIncrementer.styles) { + get name() { + return this.getAttribute(`name`); + } + set name(value) { + this.setAttribute(`name`, value); + } + + get value() { + return this.getAttribute(`value`); + }; + set value(value) { + this.setAttribute(`value`, value); + }; + + get type() { + return `number`; + } + + connectedCallback() { + this.replaceChildren(); + + /* + This converts all of the double-dash 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(`--`)) { + this.style.setProperty(attrVar.name, attrVar.value); + }; + }; + + if (!DotDungeonIncrementer.#styles) { fetch(`./systems/dotdungeon/.styles/v3/components/incrementer.css`) .then(r => r.text()) .then(t => { - DotDungeonIncrementer.styles = t; + DotDungeonIncrementer.#styles = t; this.#embedStyles(); }); }; }; - get value() { - return this.#input.value; - }; - get form() { - return this._internals.form; - }; - get type() { - return `number`; - }; - #embedStyles() { const style = document.createElement(`style`); - style.innerHTML = DotDungeonIncrementer.styles; - this.#sr.appendChild(style); + style.innerHTML = DotDungeonIncrementer.#styles; + this._shadow.appendChild(style); }; #updateValue() { let value = parseInt(this.#input.value); - if (this.getAttribute(`min`)) value = Math.max(this.#min, value); - if (this.getAttribute(`max`)) value = Math.min(this.#max, value); + if (this.getAttribute(`min`)) value = Math.max(this._min, value); + if (this.getAttribute(`max`)) value = Math.min(this._max, value); this.#input.value = value; - if (this.#input.value === this.#publicInput.value) return; - this.#publicInput.value = value; - const event = new Event(`change`); - // this.#publicInput.dispatchEvent(event); - console.log(`#updateValue`) - this.dispatchEvent(event); + this.value = value; + this.dispatchEvent(new Event(`change`, { bubbles: true })); + + // NOTE: This may be really annoying, in that case, remove it later + this.blur(); }; + /** @param {Event} $e */ #increment($e) { + $e.preventDefault(); let value = parseInt(this.#input.value); - value += $e.ctrlKey ? this.#largeStep : this.#smallStep; + value += $e.ctrlKey ? this._largeStep : this._smallStep; this.#input.value = value; this.#updateValue(); }; + /** @param {Event} $e */ #decrement($e) { + $e.preventDefault(); let value = parseInt(this.#input.value); - value -= $e.ctrlKey ? this.#largeStep : this.#smallStep; + value -= $e.ctrlKey ? this._largeStep : this._smallStep; this.#input.value = value; this.#updateValue(); }; - - focus() { - console.log(1) - super.focus(); - } -}; - - -if (!window.customElements.get(DotDungeonIncrementer.elementName)) { - window.customElements.define( - DotDungeonIncrementer.elementName, - DotDungeonIncrementer - ); }; diff --git a/module/components/index.mjs b/module/components/index.mjs index 8c92384..4912d9a 100644 --- a/module/components/index.mjs +++ b/module/components/index.mjs @@ -1 +1,21 @@ -import "./incrementer.mjs"; +import { DotDungeonIncrementer } from "./incrementer.mjs"; + +const components = [ + DotDungeonIncrementer, +]; + +export function registerCustomComponents() { + (CONFIG.CACHE ??= {}).componentListeners ??= []; + for (const component of components) { + if (!window.customElements.get(component.elementName)) { + console.debug(`.dungeon | Registering component "${component.elementName}"`); + window.customElements.define( + component.elementName, + component + ); + if (component.formAssociated) { + CONFIG.CACHE.componentListeners.push(component.elementName); + } + }; + } +}; diff --git a/module/dotdungeon.mjs b/module/dotdungeon.mjs index a7a5ae7..aa79b99 100644 --- a/module/dotdungeon.mjs +++ b/module/dotdungeon.mjs @@ -32,10 +32,10 @@ import * as hbs from "./handlebars.mjs"; import "./hooks/hotReload.mjs"; // Misc Imports +import { registerCustomComponents } from "./components/index.mjs"; import loadSettings from "./settings/index.mjs"; import { devInit } from "./hooks/devInit.mjs"; import DOTDUNGEON from "./config.mjs"; -import "./components/index.mjs"; Hooks.once(`init`, async () => { @@ -109,8 +109,9 @@ Hooks.once(`init`, async () => { hbs.registerHandlebarsHelpers(); hbs.preloadHandlebarsTemplates(); + registerCustomComponents(); - CONFIG.CACHE = {}; + CONFIG.CACHE ??= {}; CONFIG.CACHE.icons = await hbs.preloadIcons(); }); diff --git a/module/sheets/GenericActorSheet.mjs b/module/sheets/GenericActorSheet.mjs index d4d38b0..1f39bf3 100644 --- a/module/sheets/GenericActorSheet.mjs +++ b/module/sheets/GenericActorSheet.mjs @@ -52,6 +52,17 @@ export class GenericActorSheet extends ActorSheet { if (!this.isEditable) return; console.debug(`.dungeon | Generic sheet adding listeners`); + /* + Custom element event listeners because Foundry doesn't listen to them by + default. + */ + html.find( + CONFIG.CACHE.componentListeners.join(`,`) + ).on(`change`, this._onChangeInput.bind(this)); + + /* + Utility event listeners that apply + */ html.find(`[data-collapse-id]`).on(`click`, this._handleSummaryToggle.bind(this)); html.find(`[data-roll-formula]`).on(`click`, this._handleRoll.bind(this)); html.find(`[data-embedded-update-on="change"]`) diff --git a/styles/v3/components/incrementer.scss b/styles/v3/components/incrementer.scss index 650eead..80c085c 100644 --- a/styles/v3/components/incrementer.scss +++ b/styles/v3/components/incrementer.scss @@ -11,9 +11,6 @@ $default-height: 1.25rem; div { display: grid; grid-template-columns: var(--height, $default-height) var(--width, 50px) var(--height, $default-height); - // I dunno why this is needed for the height to not be calculated as 17px, - // but it is for some arcane reason - grid-template-rows: var(--height, $default-height); border-radius: var(--border-radius, $default-border-radius); @include material.elevate(2); @@ -34,8 +31,9 @@ span, input { } input { - font-family: inherit; + font-family: var(--font-family, inherit); text-align: center; + font-size: var(--font-size, inherit); padding: 2px 4px; &::-webkit-inner-spin-button, &::-webkit-outer-spin-button {