// Reference <form id="UI">
const ui = document.forms.UI;
/**
* Register #UI to the "change" event, call change handler
* userInterface() when a "change" event triggers on #UI or it's
* form controls (ex. <input>, <fieldset>, etc).
*/
ui.onchange = userInterface;
// Event handlers always passes the Event Object by default
function userInterface(event) {
/**
* Reference the element that the user is interacting with
* (ex. clicked <button>, an <input> the user typed into, etc)
*/
const point = event.target;
// Declare variables used later on
let input, cache;
// If the element the user interacted with has the [name="switch"]...
if (point.name === "switch") {
// ...reference the <input type="number"> that proceeds the checkbox...
input = point.nextElementSibling;
// ...reference the <input type="hidden"> that proceeds <input type="number">...
cache = input.nextElementSibling;
// ...toggle the [disabled] attribute on <input type="number">
input.toggleAttribute("disabled");
// If the checkbox is checked...
if (point.checked) {
// ...assign the value of <input type="number"> to <input type="hidden">
input.value = cache.value;
}
}
// If the interacted element is <input type="number">...
if (point.name === "number") {
/**
* ...constrain the .value of <input type="number">
* input value is below .min -- return .min,
* above .max -- return .max,
* between .min and .max -- return value
*/
point.value = +point.value < +point.min ? point.min :
+point.value > +point.max ? point.max :
point.value;
// ...remove class "error" from parent <label>
point.parentElement.classList.remove("error");
// Reference the proceeding <input type="hidden">
cache = point.nextElementSibling;
// Assign value of <input type="number"> to <input type="hidden">
cache.value = point.value;
}
}
// Register "input" event to #UI
ui.oninput = limitHint;
function limitHint(event) {
const point = event.target;
// Reference the <label> that contains <input type="number">
const line = point.parentElement;
// If <input name="number">...
if (point.name === "number") {
// ...and if it's value is lower than .min OR higher than .max...
if (+point.value < +point.min || +point.value > +point.max) {
// ...add class: "error"...
line.classList.add("error");
// ...otherwise...
} else {
// ...remove class: "error"
line.classList.remove("error");
}
}
/**
* This is an immediately invoked function expression (iife)
* It will run first and only once. It basically interpolates
* min/max values into an error message and then adds it to
* each <label> as the [data-error] value.
* ex. <label data-error="Enter a number within\nthe range of -10 and 10.">
*/
(() => {
Array.from(ui.elements.number).forEach(input => {
let hint = `Enter a number within\nthe range of ${input.min} and ${input.max}.`;
input.parentElement.dataset.error = hint;
});
})();
:root {
font: 2.25ch/1.15 "Segoe UI"
}
fieldset {
width: max-content;
}
legend {
font-size: 1.1rem;
letter-spacing: 1.5px;
}
label {
display: flex;
align-items: center;
position: relative;
width: 100%;
margin: 0.25rem 0 0;
}
label+label {
margin-top: 0.5rem;
}
.error input {
color: red;
outline: 3px solid red;
}
.error::after {
content: attr(data-error);
display: block;
position: absolute;
z-index: 1;
left: 75%;
top: -5px;
min-width: 18ch;
max-height: 8ex;
padding: 4px 8px;
font-size: 0.75rem;
white-space: pre;
color: red;
background: rgb(240, 234, 214);
box-shadow: rgba(0, 0, 0, 0.09) 0px 2px 1px, rgba(0, 0, 0, 0.09) 0px 4px 2px, rgba(0, 0, 0, 0.09) 0px 8px 4px, rgba(0, 0, 0, 0.09) 0px 16px 8px, rgba(0, 0, 0, 0.09) 0px 32px 16px;
}
input {
font: inherit;
font-family: Consolas;
}
[name="switch"] {
width: 1.95ch;
height: 1.95ch;
}
[name="number"] {
width: 10ch;
margin-left: 1rem;
text-align: center;
}
<form id="UI">
<fieldset>
<legend><code>[disabled]</code> Attribute</legend>
<label>
<input name="switch" type="checkbox">
<input name="number" type="number" min="2" max="10" placeholder="2 - 10" disabled>
<input type="hidden">
</label>
<label>
<input name="switch" type="checkbox">
<input name="number" type="number" min="0" max="40" placeholder="0 - 40" disabled>
<input type="hidden">
</label>
<label>
<input name="switch" type="checkbox">
<input name="number" type="number" min="-10" max="10" placeholder="-10 - 10" disabled>
<input type="hidden">
</label>
</fieldset>
</form>