Get the icon component coded and being used in the incrementer component

This commit is contained in:
Oliver-Akins 2024-04-11 22:32:04 -06:00
parent 3ace1df5a2
commit f15f3a4456
5 changed files with 141 additions and 70 deletions

View file

@ -7,32 +7,37 @@ export class DotDungeonIcon extends HTMLElement {
static elementName = `dd-icon`; static elementName = `dd-icon`;
static formAssociated = false; static formAssociated = false;
static #styles = ``; #shadow;
_internals;
_shadow;
_min; static #styles = ``;
_max; static _cache = new Map();
_smallStep; #style;
_largeStep; #container;
_name;
_path;
constructor() { constructor() {
super(); super();
this._shadow = this.attachShadow({ mode: `open`, delegatesFocus: true }); this.#shadow = this.attachShadow({ mode: `open`, delegatesFocus: true });
if (DotDungeonIcon.#styles) this.#embedStyles(); if (DotDungeonIcon.#styles) this.#embedStyles();
this.#container = document.createElement(`div`);
this.#shadow.appendChild(this.#container);
}; };
async connectedCallback() {
connectedCallback() { this._name = this.getAttribute(`name`);
this.replaceChildren(); this._path = this.getAttribute(`path`);
/* /*
This converts all of the double-dash prefixed properties on the element to 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="" CSS variables so that they don't all need to be provided by doing style=""
*/ */
for (const attrVar of this.attributes) { for (const attrVar of this.attributes) {
if (attrVar.name?.startsWith(`--`)) { if (attrVar.name?.startsWith(`var:`)) {
this.style.setProperty(attrVar.name, attrVar.value); const prop = attrVar.name.replace(`var:`, ``);
this.style.setProperty(`--` + prop, attrVar.value);
}; };
}; };
@ -40,7 +45,7 @@ export class DotDungeonIcon extends HTMLElement {
Style fetching if we haven't gotten them yet Style fetching if we haven't gotten them yet
*/ */
if (!DotDungeonIcon.#styles) { if (!DotDungeonIcon.#styles) {
fetch(`./systems/dotdungeon/.styles/v3/components/incrementer.css`) fetch(`./systems/dotdungeon/.styles/v3/components/icon.css`)
.then(r => r.text()) .then(r => r.text())
.then(t => { .then(t => {
DotDungeonIcon.#styles = t; DotDungeonIcon.#styles = t;
@ -53,16 +58,50 @@ export class DotDungeonIcon extends HTMLElement {
the slot content, as then we can have a default per-icon usage the slot content, as then we can have a default per-icon usage
*/ */
let content; let content;
// TODO: Make name request
if (this._name) {
content = await this.#getIcon(`./systems/dotdungeon/assets/${this._name}.svg`);
};
if (!content) { // TODO: make path request
let slot = document.createElement(`slot`); if (this._path && !content) {
content ??= slot; content = await this.#getIcon(this._path);
} };
// TODO: insert content into DOM
if (content) {
this.#container.appendChild(content);
};
}; };
#embedStyles() { #embedStyles() {
const style = document.createElement(`style`); this.#style = document.createElement(`style`);
style.innerHTML = DotDungeonIcon.#styles; this.#style.innerHTML = DotDungeonIcon.#styles;
this._shadow.appendChild(style); this.#shadow.appendChild(this.#style);
};
async #getIcon(path) {
// Cache hit!
if (DotDungeonIcon._cache.has(path)) {
console.debug(`.dungeon | Icon ${path} cache hit`);
return DotDungeonIcon._cache.get(path);
};
const r = await fetch(path);
switch (r.status) {
case 200:
case 201:
break;
default:
console.error(`.dungeon | Failed to fetch icon: ${path}`);
return;
};
console.debug(`.dungeon | Adding icon ${path} to the cache`);
const temp = document.createElement(`div`);
temp.innerHTML = await r.text();
const svg = temp.querySelector(`svg`);
DotDungeonIcon._cache.set(path, svg);
return svg;
}; };
}; };

View file

@ -1,3 +1,5 @@
import { DotDungeonIcon } from "./icon.mjs";
/** /**
Attributes: Attributes:
@property {string} name - The path to the value to update @property {string} name - The path to the value to update
@ -19,6 +21,7 @@ export class DotDungeonIncrementer extends HTMLElement {
_internals; _internals;
_shadow; _shadow;
#input; #input;
#style;
_min; _min;
_max; _max;
@ -33,48 +36,6 @@ export class DotDungeonIncrementer extends HTMLElement {
// Form internals // Form internals
this._internals = this.attachInternals(); this._internals = this.attachInternals();
this._internals.role = `spinbutton`; this._internals.role = `spinbutton`;
// Attribute parsing / registration
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._internals.ariaValueMin = this._min;
this._internals.ariaValueMax = this._max;
const container = document.createElement(`div`);
// 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;
// plus button
const increment = document.createElement(`span`);
increment.innerHTML = `+`;
increment.ariaHidden = true;
increment.classList.value = `increment`;
increment.addEventListener(`mousedown`, this.#increment.bind(this));
// minus button
const decrement = document.createElement(`span`);
decrement.innerHTML = `-`;
decrement.ariaHidden = true;
decrement.classList.value = `decrement`;
decrement.addEventListener(`mousedown`, this.#decrement.bind(this));
// Construct the DOM
container.appendChild(decrement);
container.appendChild(input);
container.appendChild(increment);
this._shadow.appendChild(container);
}; };
get form() { get form() {
@ -102,13 +63,60 @@ export class DotDungeonIncrementer extends HTMLElement {
connectedCallback() { connectedCallback() {
this.replaceChildren(); this.replaceChildren();
// Attribute parsing / registration
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._internals.ariaValueMin = this._min;
this._internals.ariaValueMax = this._max;
const container = document.createElement(`div`);
// 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;
// plus button
const increment = document.createElement("dd-icon");
increment.setAttribute(`name`, `create`);
increment.setAttribute(`var:size`, `0.75rem`);
increment.setAttribute(`var:fill`, `currentColor`);
increment.ariaHidden = true;
increment.classList.value = `increment`;
increment.addEventListener(`mousedown`, this.#increment.bind(this));
// minus button
const decrement = document.createElement(DotDungeonIcon.elementName);
decrement.setAttribute(`name`, `minus`);
decrement.setAttribute(`var:size`, `0.75rem`);
decrement.setAttribute(`var:fill`, `currentColor`);
decrement.ariaHidden = true;
decrement.classList.value = `decrement`;
decrement.addEventListener(`mousedown`, this.#decrement.bind(this));
// Construct the DOM
container.appendChild(decrement);
container.appendChild(input);
container.appendChild(increment);
this._shadow.appendChild(container);
/* /*
This converts all of the double-dash prefixed properties on the element to 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="" CSS variables so that they don't all need to be provided by doing style=""
*/ */
for (const attrVar of this.attributes) { for (const attrVar of this.attributes) {
if (attrVar.name?.startsWith(`--`)) { if (attrVar.name?.startsWith(`var:`)) {
this.style.setProperty(attrVar.name, attrVar.value); const prop = attrVar.name.replace(`var:`, ``);
this.style.setProperty(`--` + prop, attrVar.value);
}; };
}; };
@ -123,9 +131,9 @@ export class DotDungeonIncrementer extends HTMLElement {
}; };
#embedStyles() { #embedStyles() {
const style = document.createElement(`style`); this.#style = document.createElement(`style`);
style.innerHTML = DotDungeonIncrementer.#styles; this.#style.innerHTML = DotDungeonIncrementer.#styles;
this._shadow.appendChild(style); this._shadow.appendChild(this.#style);
}; };
#updateValue() { #updateValue() {

View file

@ -1,6 +1,8 @@
import { DotDungeonIncrementer } from "./incrementer.mjs"; import { DotDungeonIncrementer } from "./incrementer.mjs";
import { DotDungeonIcon } from "./icon.mjs";
const components = [ const components = [
DotDungeonIcon,
DotDungeonIncrementer, DotDungeonIncrementer,
]; ];

View file

@ -0,0 +1,21 @@
/*
Disclaimer: This CSS is used by a custom web component and is scoped to JUST
the corresponding web component. Importing this into other files is forbidden.
*/
$default-size: 1rem;
div {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
svg {
width: var(--size, $default-size);
height: var(--size, $default-size);
fill: var(--fill);
stroke: var(--stroke);
}

View file

@ -6,11 +6,12 @@ the corresponding web component. Importing this into other files is forbidden.
@use "../mixins/material"; @use "../mixins/material";
$default-border-radius: 4px; $default-border-radius: 4px;
$default-height: 1.25rem; $default-height: 1.5rem;
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);
grid-template-rows: var(--height, 1fr);
border-radius: var(--border-radius, $default-border-radius); border-radius: var(--border-radius, $default-border-radius);
@include material.elevate(2); @include material.elevate(2);
@ -44,7 +45,7 @@ input {
} }
} }
span { .increment, .decrement {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
padding: 0; padding: 0;
display: flex; display: flex;