Finish the custom incrementer component

This commit is contained in:
Oliver-Akins 2024-04-10 21:27:10 -06:00
parent 878d278303
commit c5c5a71587
5 changed files with 112 additions and 89 deletions

View file

@ -15,150 +15,143 @@ export class DotDungeonIncrementer extends HTMLElement {
static elementName = `dd-incrementer`; static elementName = `dd-incrementer`;
static formAssociated = true; static formAssociated = true;
static styles = ``; static #styles = ``;
_internals;
#min; _shadow;
#max;
#smallStep;
#largeStep;
#input; #input;
#publicInput;
#sr; _min;
_max;
_smallStep;
_largeStep;
constructor() { constructor() {
super(); super();
this._shadow = this.attachShadow({ mode: `open`, delegatesFocus: true });
this._internals = this.attachInternals(); this._internals = this.attachInternals();
this._internals.role = `spinbutton`;
const value = this.getAttribute(`value`); const value = this.getAttribute(`value`);
this.#min = parseInt(this.getAttribute(`min`) ?? 0); this._min = parseInt(this.getAttribute(`min`) ?? 0);
this.#max = parseInt(this.getAttribute(`max`) ?? 0); this._max = parseInt(this.getAttribute(`max`) ?? 0);
this.#smallStep = parseInt(this.getAttribute(`smallStep`) ?? 1); this._smallStep = parseInt(this.getAttribute(`smallStep`) ?? 1);
this.#largeStep = parseInt(this.getAttribute(`largeStep`) ?? 5); 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`); const container = document.createElement(`div`);
if (DotDungeonIncrementer.styles) this.#embedStyles(); if (DotDungeonIncrementer.#styles) this.#embedStyles();
// The input that the user can see / modify // The input that the user can see / modify
const input = document.createElement(`input`); const input = document.createElement(`input`);
this.#input = input; this.#input = input;
input.type = `number`; input.type = `number`;
input.ariaHidden = true;
input.min = this.getAttribute(`min`); input.min = this.getAttribute(`min`);
input.max = this.getAttribute(`max`); input.max = this.getAttribute(`max`);
input.addEventListener(`change`, this.#updateValue.bind(this)); input.addEventListener(`change`, this.#updateValue.bind(this));
input.value = value; input.value = value;
// input.id = this.id;
// this.removeAttribute(`id`);
// plus button // plus button
const increment = document.createElement(`span`); const increment = document.createElement(`span`);
increment.innerHTML = `+`; increment.innerHTML = `+`;
// increment.type = `button`; increment.ariaHidden = true;
// increment.tabIndex = -1;
increment.classList.value = `increment`; increment.classList.value = `increment`;
increment.addEventListener(`click`, this.#increment.bind(this)); increment.addEventListener(`mousedown`, this.#increment.bind(this));
// minus button // minus button
const decrement = document.createElement(`span`); const decrement = document.createElement(`span`);
decrement.innerHTML = `-`; decrement.innerHTML = `-`;
// decrement.type = `button`; decrement.ariaHidden = true;
// decrement.tabIndex = -1;
decrement.classList.value = `decrement`; decrement.classList.value = `decrement`;
decrement.addEventListener(`click`, this.#decrement.bind(this)); decrement.addEventListener(`mousedown`, this.#decrement.bind(this));
// Construct the DOM // Construct the DOM
container.appendChild(decrement); container.appendChild(decrement);
container.appendChild(input); container.appendChild(input);
container.appendChild(increment); container.appendChild(increment);
sr.appendChild(container); this._shadow.appendChild(container);
}; };
connectedCallback() { get form() {
/* return this._internals.form;
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);
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`) fetch(`./systems/dotdungeon/.styles/v3/components/incrementer.css`)
.then(r => r.text()) .then(r => r.text())
.then(t => { .then(t => {
DotDungeonIncrementer.styles = t; DotDungeonIncrementer.#styles = t;
this.#embedStyles(); this.#embedStyles();
}); });
}; };
}; };
get value() {
return this.#input.value;
};
get form() {
return this._internals.form;
};
get type() {
return `number`;
};
#embedStyles() { #embedStyles() {
const style = document.createElement(`style`); const style = document.createElement(`style`);
style.innerHTML = DotDungeonIncrementer.styles; style.innerHTML = DotDungeonIncrementer.#styles;
this.#sr.appendChild(style); this._shadow.appendChild(style);
}; };
#updateValue() { #updateValue() {
let value = parseInt(this.#input.value); let value = parseInt(this.#input.value);
if (this.getAttribute(`min`)) value = Math.max(this.#min, value); if (this.getAttribute(`min`)) value = Math.max(this._min, value);
if (this.getAttribute(`max`)) value = Math.min(this.#max, value); if (this.getAttribute(`max`)) value = Math.min(this._max, value);
this.#input.value = value; this.#input.value = value;
if (this.#input.value === this.#publicInput.value) return; this.value = value;
this.#publicInput.value = value; this.dispatchEvent(new Event(`change`, { bubbles: true }));
const event = new Event(`change`);
// this.#publicInput.dispatchEvent(event); // NOTE: This may be really annoying, in that case, remove it later
console.log(`#updateValue`) this.blur();
this.dispatchEvent(event);
}; };
/** @param {Event} $e */
#increment($e) { #increment($e) {
$e.preventDefault();
let value = parseInt(this.#input.value); 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.#input.value = value;
this.#updateValue(); this.#updateValue();
}; };
/** @param {Event} $e */
#decrement($e) { #decrement($e) {
$e.preventDefault();
let value = parseInt(this.#input.value); 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.#input.value = value;
this.#updateValue(); this.#updateValue();
}; };
focus() {
console.log(1)
super.focus();
}
};
if (!window.customElements.get(DotDungeonIncrementer.elementName)) {
window.customElements.define(
DotDungeonIncrementer.elementName,
DotDungeonIncrementer
);
}; };

View file

@ -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);
}
};
}
};

View file

@ -32,10 +32,10 @@ import * as hbs from "./handlebars.mjs";
import "./hooks/hotReload.mjs"; import "./hooks/hotReload.mjs";
// Misc Imports // Misc Imports
import { registerCustomComponents } from "./components/index.mjs";
import loadSettings from "./settings/index.mjs"; import loadSettings from "./settings/index.mjs";
import { devInit } from "./hooks/devInit.mjs"; import { devInit } from "./hooks/devInit.mjs";
import DOTDUNGEON from "./config.mjs"; import DOTDUNGEON from "./config.mjs";
import "./components/index.mjs";
Hooks.once(`init`, async () => { Hooks.once(`init`, async () => {
@ -109,8 +109,9 @@ Hooks.once(`init`, async () => {
hbs.registerHandlebarsHelpers(); hbs.registerHandlebarsHelpers();
hbs.preloadHandlebarsTemplates(); hbs.preloadHandlebarsTemplates();
registerCustomComponents();
CONFIG.CACHE = {}; CONFIG.CACHE ??= {};
CONFIG.CACHE.icons = await hbs.preloadIcons(); CONFIG.CACHE.icons = await hbs.preloadIcons();
}); });

View file

@ -52,6 +52,17 @@ export class GenericActorSheet extends ActorSheet {
if (!this.isEditable) return; if (!this.isEditable) return;
console.debug(`.dungeon | Generic sheet adding listeners`); 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-collapse-id]`).on(`click`, this._handleSummaryToggle.bind(this));
html.find(`[data-roll-formula]`).on(`click`, this._handleRoll.bind(this)); html.find(`[data-roll-formula]`).on(`click`, this._handleRoll.bind(this));
html.find(`[data-embedded-update-on="change"]`) html.find(`[data-embedded-update-on="change"]`)

View file

@ -11,9 +11,6 @@ $default-height: 1.25rem;
div { div {
display: grid; display: grid;
grid-template-columns: var(--height, $default-height) var(--width, 50px) var(--height, $default-height); 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); border-radius: var(--border-radius, $default-border-radius);
@include material.elevate(2); @include material.elevate(2);
@ -34,8 +31,9 @@ span, input {
} }
input { input {
font-family: inherit; font-family: var(--font-family, inherit);
text-align: center; text-align: center;
font-size: var(--font-size, inherit);
padding: 2px 4px; padding: 2px 4px;
&::-webkit-inner-spin-button, &::-webkit-outer-spin-button { &::-webkit-inner-spin-button, &::-webkit-outer-spin-button {