Merge pull request #175 from Oliver-Akins/feature/incrementer-component
Custom Web Components
This commit is contained in:
commit
118dcfb71c
16 changed files with 543 additions and 63 deletions
27
.vscode/components.html-data.json
vendored
Normal file
27
.vscode/components.html-data.json
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"version": 1.1,
|
||||
"tags": [
|
||||
{
|
||||
"name": "dd-incrementer",
|
||||
"description": "A number input that allows more flexible increase/decrease buttons",
|
||||
"attributes": [
|
||||
{ "name": "value", "description": "The initial value to put in the input" },
|
||||
{ "name": "name", "description": "The form name to use when this input is used to submit data" },
|
||||
{ "name": "min", "description": "The minimum value that this input can contain" },
|
||||
{ "name": "max", "description": "The maximum value that this input can contain" },
|
||||
{ "name": "smallStep", "description": "The value that the input is changed by when clicking a delta button or using the up/down arrow key" },
|
||||
{ "name": "largeStep", "description": "The value that the input is changed by when clicking a delta button with control held or using the page up/ page down arrow key" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "dd-icon",
|
||||
"description": "Loads an icon asynchronously, caching the result for future uses",
|
||||
"attributes": [
|
||||
{ "name": "name", "description": "The name of the icon, this is relative to the assets folder of the dotdungeon system" },
|
||||
{ "name": "path", "description": "The full path of the icon, this will only be used if `name` isn't provided or fails to fetch." }
|
||||
]
|
||||
}
|
||||
],
|
||||
"globalAttributes": [],
|
||||
"valueSets": []
|
||||
}
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
|
|
@ -12,5 +12,8 @@
|
|||
"node_modules": true,
|
||||
"packs": true,
|
||||
".gitattributes": true,
|
||||
}
|
||||
},
|
||||
"html.customData": [
|
||||
"./.vscode/components.html-data.json"
|
||||
]
|
||||
}
|
||||
125
module/components/icon.mjs
Normal file
125
module/components/icon.mjs
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import { StyledShadowElement } from "./mixins/Styles.mjs";
|
||||
|
||||
/**
|
||||
Attributes:
|
||||
@property {string} name - The name of the icon, takes precedence over the path
|
||||
@property {string} path - The path of the icon file
|
||||
*/
|
||||
export class DotDungeonIcon extends StyledShadowElement(HTMLElement) {
|
||||
static elementName = `dd-icon`;
|
||||
static formAssociated = false;
|
||||
|
||||
/* Stuff for the mixin to use */
|
||||
static _stylePath = `v3/components/icon.css`;
|
||||
|
||||
|
||||
static _cache = new Map();
|
||||
#container;
|
||||
/** @type {null | string} */
|
||||
_name;
|
||||
/** @type {null | string} */
|
||||
_path;
|
||||
|
||||
/* Stored IDs for all of the hooks that are in this component */
|
||||
#svgHmr;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
// this._shadow = this.attachShadow({ mode: `open`, delegatesFocus: true });
|
||||
|
||||
this.#container = document.createElement(`div`);
|
||||
this._shadow.appendChild(this.#container);
|
||||
};
|
||||
|
||||
_mounted = false;
|
||||
async connectedCallback() {
|
||||
super.connectedCallback();
|
||||
if (this._mounted) return;
|
||||
|
||||
this._name = this.getAttribute(`name`);
|
||||
this._path = this.getAttribute(`path`);
|
||||
|
||||
/*
|
||||
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(`var:`)) {
|
||||
const prop = attrVar.name.replace(`var:`, ``);
|
||||
this.style.setProperty(`--` + prop, attrVar.value);
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
Try to retrieve the icon if it isn't present, try the path then default to
|
||||
the slot content, as then we can have a default per-icon usage
|
||||
*/
|
||||
let content;
|
||||
if (this._name) {
|
||||
content = await this.#getIcon(`./systems/dotdungeon/assets/${this._name}.svg`);
|
||||
};
|
||||
|
||||
if (this._path && !content) {
|
||||
content = await this.#getIcon(this._path);
|
||||
};
|
||||
|
||||
if (content) {
|
||||
this.#container.appendChild(content.cloneNode(true));
|
||||
};
|
||||
|
||||
/*
|
||||
This is so that when we get an HMR event from Foundry we can appropriately
|
||||
handle it using our logic to update the component and the icon cache.
|
||||
*/
|
||||
if (game.settings.get(`dotdungeon`, `devMode`)) {
|
||||
this.#svgHmr = Hooks.on(`dd-hmr:svg`, (iconName, data) => {
|
||||
if (this._name === iconName || this._path?.endsWith(data.path)) {
|
||||
const svg = this.#parseSVG(data.content);
|
||||
this.constructor._cache.set(iconName, svg);
|
||||
this.#container.replaceChildren(svg.cloneNode(true));
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
this._mounted = true;
|
||||
};
|
||||
|
||||
disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
if (!this._mounted) return;
|
||||
|
||||
Hooks.off(`dd-hmr:svg`, this.#svgHmr);
|
||||
|
||||
this._mounted = false;
|
||||
};
|
||||
|
||||
async #getIcon(path) {
|
||||
// Cache hit!
|
||||
if (this.constructor._cache.has(path)) {
|
||||
console.debug(`.dungeon | Icon ${path} cache hit`);
|
||||
return this.constructor._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 svg = this.#parseSVG(await r.text());
|
||||
this.constructor._cache.set(path, svg);
|
||||
return svg;
|
||||
};
|
||||
|
||||
/** Takes an SVG string and returns it as a DOM node */
|
||||
#parseSVG(content) {
|
||||
const temp = document.createElement(`div`);
|
||||
temp.innerHTML = content;
|
||||
return temp.querySelector(`svg`);
|
||||
};
|
||||
};
|
||||
152
module/components/incrementer.mjs
Normal file
152
module/components/incrementer.mjs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
import { DotDungeonIcon } from "./icon.mjs";
|
||||
import { StyledShadowElement } from "./mixins/Styles.mjs";
|
||||
|
||||
/**
|
||||
Attributes:
|
||||
@property {string} name - The path to the value to update
|
||||
@property {number} value - The actual value of the input
|
||||
@property {number} min - The minimum value of the input
|
||||
@property {number} max - The maximum value of the input
|
||||
@property {number?} smallStep - The step size used for the buttons and arrow keys
|
||||
@property {number?} largeStep - The step size used for the buttons + Ctrl and page up / down
|
||||
|
||||
Styling:
|
||||
- `--height`: Controls the height of the element + the width of the buttons (default: 1.25rem)
|
||||
- `--width`: Controls the width of the number input (default 50px)
|
||||
*/
|
||||
export class DotDungeonIncrementer extends StyledShadowElement(HTMLElement) {
|
||||
static elementName = `dd-incrementer`;
|
||||
static formAssociated = true;
|
||||
|
||||
static _stylePath = `v3/components/incrementer.css`;
|
||||
|
||||
_internals;
|
||||
#input;
|
||||
|
||||
_min;
|
||||
_max;
|
||||
_smallStep;
|
||||
_largeStep;
|
||||
|
||||
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() {
|
||||
return this.getAttribute(`value`);
|
||||
};
|
||||
set value(value) {
|
||||
this.setAttribute(`value`, value);
|
||||
};
|
||||
|
||||
get type() {
|
||||
return `number`;
|
||||
}
|
||||
|
||||
connectedCallback() {
|
||||
super.connectedCallback();
|
||||
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 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() {
|
||||
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);
|
||||
this.#input.value = value;
|
||||
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;
|
||||
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;
|
||||
this.#input.value = value;
|
||||
this.#updateValue();
|
||||
};
|
||||
};
|
||||
23
module/components/index.mjs
Normal file
23
module/components/index.mjs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { DotDungeonIncrementer } from "./incrementer.mjs";
|
||||
import { DotDungeonIcon } from "./icon.mjs";
|
||||
|
||||
const components = [
|
||||
DotDungeonIcon,
|
||||
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);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
80
module/components/mixins/Styles.mjs
Normal file
80
module/components/mixins/Styles.mjs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* @param {HTMLElement} Base
|
||||
*/
|
||||
export function StyledShadowElement(Base) {
|
||||
return class extends Base {
|
||||
/**
|
||||
* The path to the CSS that is loaded
|
||||
* @type {string}
|
||||
*/
|
||||
static _stylePath;
|
||||
|
||||
/**
|
||||
* The stringified CSS to use
|
||||
* @type {string}
|
||||
*/
|
||||
static _styles;
|
||||
|
||||
/**
|
||||
* The HTML element of the stylesheet
|
||||
* @type {HTMLStyleElement}
|
||||
*/
|
||||
_style;
|
||||
|
||||
/** @type {ShadowRoot} */
|
||||
_shadow;
|
||||
|
||||
/**
|
||||
* The hook ID for this element's CSS hot reload
|
||||
* @type {number}
|
||||
*/
|
||||
#cssHmr;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._shadow = this.attachShadow({ mode: `open` });
|
||||
this._style = document.createElement(`style`);
|
||||
this._shadow.appendChild(this._style);
|
||||
};
|
||||
|
||||
#mounted = false;
|
||||
connectedCallback() {
|
||||
if (this.#mounted) return;
|
||||
|
||||
this._getStyles();
|
||||
|
||||
if (game.settings.get(`dotdungeon`, `devMode`)) {
|
||||
this.#cssHmr = Hooks.on(`dd-hmr:css`, (data) => {
|
||||
if (data.path.endsWith(this.constructor._stylePath)) {
|
||||
this._style.innerHTML = data.content;
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
this.#mounted = true;
|
||||
};
|
||||
|
||||
disconnectedCallback() {
|
||||
if (!this.#mounted) return;
|
||||
if (this.#cssHmr != null) {
|
||||
Hooks.off(`dd-hmr:css`, this.#cssHmr);
|
||||
this.#cssHmr = null;
|
||||
};
|
||||
this.#mounted = false;
|
||||
};
|
||||
|
||||
_getStyles() {
|
||||
if (this.constructor._styles) {
|
||||
this._style.innerHTML = this.constructor._styles;
|
||||
} else {
|
||||
fetch(`./systems/dotdungeon/.styles/${this.constructor._stylePath}`)
|
||||
.then(r => r.text())
|
||||
.then(t => {
|
||||
this.constructor._styles = t;
|
||||
this._style.innerHTML = t;
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
@ -32,6 +32,7 @@ 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";
|
||||
|
|
@ -108,8 +109,9 @@ Hooks.once(`init`, async () => {
|
|||
|
||||
hbs.registerHandlebarsHelpers();
|
||||
hbs.preloadHandlebarsTemplates();
|
||||
registerCustomComponents();
|
||||
|
||||
CONFIG.CACHE = {};
|
||||
CONFIG.CACHE ??= {};
|
||||
CONFIG.CACHE.icons = await hbs.preloadIcons();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import * as hbs from "../handlebars.mjs";
|
|||
const loaders = {
|
||||
svg(data) {
|
||||
const iconName = data.path.split(`/`).slice(-1)[0].slice(0, -4);
|
||||
console.log(`.dungeon | hot-reloading icon: ${iconName}`);
|
||||
CONFIG.CACHE.icons[iconName] = data.content;
|
||||
console.debug(`.dungeon | hot-reloading icon: ${iconName}`);
|
||||
Hooks.call(`dd-hmr:svg`, iconName, data);
|
||||
},
|
||||
hbs(data) {
|
||||
if (!hbs.partials.some(p => data.path.endsWith(p))) {
|
||||
|
|
@ -35,6 +35,10 @@ const loaders = {
|
|||
},
|
||||
js() {window.location.reload()},
|
||||
mjs() {window.location.reload()},
|
||||
css(data) {
|
||||
console.debug(`.dungeon | Hot-reloading CSS: ${data.path}`);
|
||||
Hooks.call(`dd-hmr:css`, data);
|
||||
},
|
||||
};
|
||||
|
||||
Hooks.on(`hotReload`, async (data) => {
|
||||
|
|
|
|||
|
|
@ -167,9 +167,4 @@ export class PlayerSheetv2 extends GenericActorSheet {
|
|||
max: this.actor.system.inventory_slots,
|
||||
};
|
||||
};
|
||||
|
||||
_updateObject(...args) {
|
||||
console.log(args)
|
||||
super._updateObject(...args);
|
||||
};
|
||||
}
|
||||
|
|
@ -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"]`)
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
|
||||
.bytes-panel {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr min-content 50px min-content;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
|
||||
|
|
|
|||
7
styles/v3/components/common.scss
Normal file
7
styles/v3/components/common.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Disclaimer: This CSS is used by a custom web component and is scoped to JUST
|
||||
// the corresponding web component. This should only be imported by web component
|
||||
// style files.
|
||||
|
||||
:host {
|
||||
display: inline-block;
|
||||
}
|
||||
23
styles/v3/components/icon.scss
Normal file
23
styles/v3/components/icon.scss
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
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;
|
||||
|
||||
@use "./common.scss";
|
||||
|
||||
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);
|
||||
}
|
||||
63
styles/v3/components/incrementer.scss
Normal file
63
styles/v3/components/incrementer.scss
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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-border-radius: 4px;
|
||||
$default-height: 1.5rem;
|
||||
|
||||
@use "../mixins/material";
|
||||
@use "./common.scss";
|
||||
|
||||
div {
|
||||
display: grid;
|
||||
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);
|
||||
@include material.elevate(2);
|
||||
|
||||
&:hover {
|
||||
@include material.elevate(4);
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
@include material.elevate(6);
|
||||
}
|
||||
}
|
||||
|
||||
span, input {
|
||||
border: none;
|
||||
outline: none;
|
||||
background: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
input {
|
||||
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 {
|
||||
-webkit-appearance: none;
|
||||
-moz-appearance: none;
|
||||
appearance: none;
|
||||
margin: 0
|
||||
}
|
||||
}
|
||||
|
||||
.increment, .decrement {
|
||||
aspect-ratio: 1 / 1;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.increment {
|
||||
border-radius: 0 var(--border-radius, $default-border-radius) var(--border-radius, 4px) 0;
|
||||
}
|
||||
.decrement {
|
||||
border-radius: var(--border-radius, $default-border-radius) 0 0 var(--border-radius, $default-border-radius);
|
||||
}
|
||||
|
|
@ -11,35 +11,17 @@
|
|||
</div>
|
||||
<div class="e-1dp panel bytes-panel">
|
||||
<label
|
||||
for="{{meta.idp}}-player-inventory-supplies-input"
|
||||
for="{{meta.idp}}-player-inventory-supplies"
|
||||
>
|
||||
Supplies
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="icon"
|
||||
data-decrement="system.supplies"
|
||||
aria-label="Decrease supply count by one"
|
||||
>
|
||||
<div aria-hidden="true" class="icon icon--14">
|
||||
{{{ icons.minus }}}
|
||||
</div>
|
||||
</button>
|
||||
<input
|
||||
type="number"
|
||||
id="{{meta.idp}}-player-inventory-supplies-input"
|
||||
<dd-incrementer
|
||||
var:height="1.5rem"
|
||||
name="system.supplies"
|
||||
value="{{system.supplies}}"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="icon"
|
||||
data-increment="system.supplies"
|
||||
aria-label="Increase supply count by one"
|
||||
>
|
||||
<div aria-hidden="true" class="icon icon--14">
|
||||
{{{ icons.create }}}
|
||||
</div>
|
||||
</button>
|
||||
id="{{meta.idp}}-player-inventory-supplies"
|
||||
min="0"
|
||||
></dd-incrementer>
|
||||
</div>
|
||||
<div class="e-1dp panel bytes-panel">
|
||||
<label
|
||||
|
|
@ -47,31 +29,13 @@
|
|||
>
|
||||
Bytes
|
||||
</label>
|
||||
<button
|
||||
type="button"
|
||||
class="equal-padding"
|
||||
data-decrement="system.bytes"
|
||||
aria-label="Decrease byte count by one"
|
||||
>
|
||||
<div aria-hidden="true" class="icon icon--14">
|
||||
{{{ icons.minus }}}
|
||||
</div>
|
||||
</button>
|
||||
<input
|
||||
type="number"
|
||||
id="{{meta.idp}}-player-inventory-bytes-input"
|
||||
<dd-incrementer
|
||||
var:height="1.5rem"
|
||||
name="system.bytes"
|
||||
value="{{system.bytes}}"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="equal-padding"
|
||||
data-increment="system.bytes"
|
||||
aria-label="Increase byte count by one"
|
||||
>
|
||||
<div aria-hidden="true" class="icon icon--14">
|
||||
{{{ icons.create }}}
|
||||
</div>
|
||||
</button>
|
||||
id="{{meta.idp}}-player-inventory-bytes"
|
||||
min="0"
|
||||
></dd-incrementer>
|
||||
</div>
|
||||
<div class="e-1dp panel filter-panel">
|
||||
<h2>Show</h2>
|
||||
|
|
|
|||
|
|
@ -73,13 +73,14 @@
|
|||
<label for="{{meta.idp}}-quantity">
|
||||
Quantity
|
||||
</label>
|
||||
<input
|
||||
<dd-incrementer value="{{system.supplies}}"></dd-incrementer>
|
||||
{{!-- <input
|
||||
type="number"
|
||||
min="0"
|
||||
name="system.quantity"
|
||||
value="{{system.quantity}}"
|
||||
id="{{meta.idp}}-quantity"
|
||||
>
|
||||
> --}}
|
||||
{{else}}
|
||||
Quantity
|
||||
{{system.quantity}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue