sidewayss / html-elements

custom html elements
0 stars 0 forks source link

html-elements contains four custom HTML elements

Usage

There are three JavaScript files for the four elements. check-box and check-tri fit into one file. Here is some sample HTML that includes all four elements in a page:

<head>
    <script src="https://github.com/sidewayss/html-elements/raw/main/html-elements/multi-check.js"  type="module"></script>
    <script src="https://github.com/sidewayss/html-elements/raw/main/html-elements/multi-button.js" type="module"></script>
    <script src="https://github.com/sidewayss/html-elements/raw/main/html-elements/input-num.js"    type="module"></script>
</head>

NOTE: html-elements uses await at the top level of modules. The browser support grid is here. The workaround is funky enough that I'm not inclined to make any changes. Current global support is 92.93%, and while support for await without this feature goes back another 4 or 5 years, it only increases the global support to 94.66%.

html-elements also uses private properties, which also have ~93% support, but can be transpiled out without too much trouble. If you want to use html-elements but require either of these workarounds, please submit an issue or a pull request.

Managing Template Files

There are built-in template files in the root directory:

Instead of modifying those, you should create an /html-templates directory as a sibling of your /html-elements directory. Store your template files there. That keeps the source and user files separate and preserves the source files as fallbacks.

When the page is loading, if the element doesn't find its template file in /html-templates, the DOM generates an unsupressable HTML 404 "file not found" error. Then it falls back to the built-in template file. I would suggest copying the built-in files to your templates directory as a starting point. Then edit them to create your own designs within the template structure.

NOTE: The CSS files in the css sub-directory are samples. They are not used directly by the elements, as the template files are.

check-box

check-tri

state-btn

input-num

If you want to jump right in, the test/demo app is here.

Based on an informal survey and my own repeated frustrations, it became clear to me that <input type="number"/> isn't just "the worst" HTML input, it's a complete waste of time. I needed an alternative. I spent over a decade programming for finance executives and financial analysts, so I spent a lot of time in Excel. Regardless of the brand, spreadsheets have a model for inputting numbers that is tried and true. I decided to create a custom element that imitates a spreadsheet, while maintaining consistency with the default <input type="number"/> as implemented by the major browsers.

Features

<input type="number"/> emulation:

Spreadsheet emulation:

Additional features:

For keyboard users:

Attributes & Properties

HTML Attributes / JavaScript Properties, property names excludes the "data-" prefix:

These boolean attributes/properties are inverted. The default is: attribute = unset / property = true:

JavaScript only:

Events

The only event that you can listen to is change. There is no need for an input event, as the element does not enforce any limitations on keyboard entry as it happens, it only formats via the "NaN" class.

The change event fires every time the value changes:

When the user inputs via the spinner, the event object has two additional properties:

Before the value is committed and the change event is fired, you can insert your own validation function, to take full control of that process. Because it runs before committing the value, it happens before the isNaN() validation done internally. Invalid value styling is up to you.

The property is named validate, and it must be an instance of Function or undefined. The function takes two arguments: value and isSpinning. value is a string from the keyboard or a number from spinning. The return value falls into three categories:

Styling

You can obviously style the element itself, but you can also style some of its parts via the ::part pseudo-element. Remember that ::part overrides the shadow DOM elements' style. You must use !important if you want to override ::part. See the html-elements/css/input-num.css file for example styling.

The available parts:

My preference, as illustrated in the sample css/input-num.css file, is to not display the spinner or confirm buttons on devices that can't hover:

@media (hover:none) {
  input-num::part(buttons) { display:none }
}

Those devices are all touchscreen, and focusing the element will focus the <input>, which will display the appropriate virtual keyboard. Touch and hold has system meanings on touch devices, which conflicts with spinning. At font-size:1rem the buttons are smaller than recommended for touch. So unless you create oversized buttons or use a much larger font size, it's best not to display them at all.

NOTE: Auto-sizing only works if the element is displayed. If the element or any of it's ancestors is set to display:none, the element and its shadow DOM have a width and height of zero. During page load, don't set display:none until after your elements have resized.

NOTE: If you load the font for your element in JavaScript using document.fonts.add(), it will probably not load before the element. So resize() won't be using the correct font, and you'll have to run it again after the fonts have loaded. Something like this:

document.addEventListener("DOMContentLoaded", load);
function load() {
    const whenDefined = customElements.whenDefined("in-number");
    Promise.all([whenDefined, document.fonts.ready]).then(() => {
        for (const elm of document.getElementsByTagName("input-num"))
            elm.resize();
    });
}

If you are doing this and want to be more efficient, set the data-no-resize attribute on the element:

<input-num data-no-resize></input-num>

Then turn on the autoResize property prior to calling resize() :

        for (const elm of document.getElementsByTagName("input-num")) {
            elm.autoResize = true;
            elm.resize();
        }

The Template: template-number.html

I am more familiar with SVG than other image formats, so the spin and confirm buttons in the built-in template are in SVG. You can create your own template file that uses JPEG or whatever image format you prefer.

The core of the template is a flex <div>, with three children:

Do not modify:

Everything else is user-configurable, though you'll probably want to keep the flex container:

<defs> defines the shapes, and <style> formats them. There are two pairs of buttons:

The definitions are setup as a single block containing each pair. This allows you to create a single image that responds differently when interacting with the top or bottom button. That kind of design makes more sense for the spinner than the confirm buttons...

There is a definition for each cell in this matrix for a total of 19 ids. Confirm has an additional set of definitions for when the ok button is disabled due to user input == NaN. Those are labeled cancel. The <rect> elements that handle events have the ids "top" and "bot" (for "bottom"): Pair State #id/href
spinner idle #spinner-idle
spinner keydown* #spinner-key-top
#spinner-key-bot
spinner hover #spinner-hover-top
#spinner-hover-bot
spinner active #spinner-active-top
#spinner-active-bot
spinner spin #spinner-spin-top
#spinner-spin-bot
confirm idle #confirm-idle
confirm hover #confirm-hover-top
#confirm-hover-bot
confirm active #confirm-active-top
#confirm-active-bot
cancel idle #cancel-idle
cancel hover #cancel-hover-top
#cancel-hover-bot
cancel active #cancel-active-top
#cancel-active-bot

* ArrowUp or ArrowDown key, initial image is state:key, full-speed spin uses spin-
spin-top and spin-bot are the full-speed spin images, used after data-delay expires