Skip to content

Commit a5bbcd6

Browse files
committed
add value:selected attribute for radio inputs and handle range/number input constraints
1 parent 3893e2b commit a5bbcd6

File tree

2 files changed

+41
-8
lines changed

2 files changed

+41
-8
lines changed

attributes.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export const htmlElementAttributes = {
138138
script: [...src, "type", "async", "defer", "nomodule", "crossorigin", "integrity", "referrerpolicy"],
139139

140140
progress: ["value", "max", "min"],
141-
input: [...input, alt, ...src, alt, ...widthAndHeight, "min", "minlength", "accept", "autocomplete", "autofocus", "checked", "dirname", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "list", "max", "maxlength", "multiple", "name", "pattern", "placeholder", "readonly", "required", "size", "step", "type", "value", "value:out", "value:in"],
141+
input: [...input, alt, ...src, alt, ...widthAndHeight, "min", "minlength", "accept", "autocomplete", "autofocus", "checked", "dirname", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "list", "max", "maxlength", "multiple", "name", "pattern", "placeholder", "readonly", "required", "size", "step", "type", "value", "value:out", "value:in", "value:selected"],
142142
button: ["type", "disabled", "form"],
143143
form: ["method", "enctype", "action", "rel"],
144144
img: [alt, ...src, ...widthAndHeight, "border", "crossorigin", "ismap", "loading", "longdesc", "referrerpolicy", "sizes", "srcset", "usemap"],
@@ -186,7 +186,8 @@ export type htmlElementAttributeValues = {
186186

187187
value: primitive|Date,
188188
"value:out": primitive|Date,
189-
"value:in": primitive|Date
189+
"value:in": primitive|Date,
190+
"value:selected": string|number
190191
})
191192
// TODO: conditional attributes
192193
// & (

datex-bindings/dom-utils.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ export namespace DOMUtils {
2828
[DOMUtils.STYLE_DX_VALUES]:Map<string, Datex.ReactiveValue<string>>,
2929
[DOMUtils.STYLE_WEAK_PROPS]:Map<string, boolean>,
3030
[DOMUtils.CHILDREN_DX_VALUES]:Set<Datex.ReactiveValue>,
31-
[DOMUtils.DATEX_UPDATE_TYPE]?: string
31+
[DOMUtils.DATEX_UPDATE_TYPE]?: string,
32+
[DOMUtils.ATTR_SELECTED_BINDING]?: Datex.ReactiveValue
3233
}
3334
}
3435

@@ -45,6 +46,7 @@ export class DOMUtils {
4546
static readonly STYLE_DX_VALUES: unique symbol = Symbol.for("DOMUtils.STYLE_DX_VALUES");
4647
static readonly STYLE_WEAK_PROPS: unique symbol = Symbol.for("DOMUtils.STYLE_WEAK_PROPS");
4748
static readonly CHILDREN_DX_VALUES: unique symbol = Symbol.for("DOMUtils.CHILDREN_DX_VALUES");
49+
static readonly ATTR_SELECTED_BINDING: unique symbol = Symbol.for("DOMUtils.ATTR_SELECTED_BINDING");
4850

4951
static readonly DATEX_UPDATE_TYPE: unique symbol = Symbol.for("DOMUtils.DATEX_UPDATE_TYPE");
5052
static readonly PLACEHOLDER_CONTENT: unique symbol = Symbol.for("DOMUtils.PLACEHOLDER_CONTENT");
@@ -344,6 +346,11 @@ export class DOMUtils {
344346
if (element instanceof this.context.HTMLInputElement && attr == "value" && element.getAttribute("type") == "checkbox" && typeof Datex.ReactiveValue.collapseValue(value, true, true) == "boolean") {
345347
logger.warn(`You are assigning the "value" attribute of an <input type="checkbox"> to a boolean value. This has no effect on the checkbox state. Did you mean to use the "checked" attribute instead\\?`)
346348
}
349+
350+
// value:selected is only allowed for input type="radio"
351+
else if (attr == "value:selected" && !(element instanceof this.context.HTMLInputElement && (element as HTMLInputElement).type == "radio")) {
352+
throw new Error("The \"value:selected\" attribute is only allowed for <input type=\"radio\"> elements");
353+
}
347354

348355
value = value?.[JSX_INSERT_STRING] ? value.val : value; // collapse safely injected strings
349356

@@ -435,13 +442,13 @@ export class DOMUtils {
435442
}
436443

437444
// :out attributes
438-
if ((isSelectElement || isInputElement || isTextareaElement) && (attr == "value:out" || attr == "value:in" || attr == "value")) {
445+
if ((isSelectElement || isInputElement || isTextareaElement) && (attr == "value:selected" || attr == "value:out" || attr == "value:in" || attr == "value")) {
439446

440447
const event = isSelectElement ? 'change' : 'input';
441448
const inputElement = element as unknown as HTMLInputElement;
442449

443450
// out
444-
if (attr == "value" || attr == "value:out") {
451+
if (attr == "value" || attr == "value:out" || attr == "value:selected") {
445452
if (!(type instanceof Datex.Type)) {
446453
console.warn("Value has no type", value);
447454
}
@@ -451,11 +458,22 @@ export class DOMUtils {
451458
else if (type.matchesType(Datex.Type.std.boolean)) element.addEventListener(event, () => this.handleSetVal(value, inputElement, inputElement.value, "boolean"))
452459
else if (type.matchesType(Datex.Type.std.void) || type.matchesType(Datex.Type.std.null)) {console.warn("setting value attribute to " + type, element)}
453460
else if (type.matchesType(Datex.Type.std.time)) element.addEventListener(event, () => {
454-
handleSetVal(new Time((element as unknown as HTMLInputElement).valueAsDate ?? new Date((element as unknown as HTMLInputElement).value)))
461+
this.handleSetVal(value, inputElement, new Time((element as unknown as HTMLInputElement).valueAsDate ?? new Date((element as unknown as HTMLInputElement).value)), "time")
455462
})
456463
else throw new Error("The type "+type+" is not supported for the '"+attr+"' attribute of the <"+element.tagName.toLowerCase()+"> element");
464+
465+
466+
// for all input elements that support a max, min or step attribute: listen for attribute changes and update the output value
467+
if (isInputElement && (inputElement.type == "number" || inputElement.type == "range")) {
468+
const observer = new MutationObserver(() => {
469+
if (type.matchesType(Datex.Type.std.decimal)) this.handleSetVal(value, inputElement, inputElement.value, "number");
470+
else if (type.matchesType(Datex.Type.std.integer)) this.handleSetVal(value, inputElement, inputElement.value, "bigint");
471+
});
472+
observer.observe(inputElement, {attributes: true, attributeFilter: ["max", "min", "step"]})
473+
}
474+
457475
}
458-
476+
459477
// in
460478
if (attr == "value" || attr == "value:in") {
461479
const valid = this.setAttribute(element, "value", value.val, rootPath)
@@ -488,6 +506,13 @@ export class DOMUtils {
488506
return valid;
489507
}
490508

509+
// value:selected initial state
510+
if (attr == "value:selected") {
511+
(<DOMUtils.elWithUIXAttributes><unknown>inputElement)[DOMUtils.ATTR_SELECTED_BINDING] = value;
512+
// check if the defined value of the input is equal to the value of the pointer
513+
if (inputElement.value == value.val) inputElement.checked = true;
514+
}
515+
491516
return true;
492517
}
493518

@@ -596,7 +621,7 @@ export class DOMUtils {
596621
}
597622

598623
// invalid :out attributes here
599-
if (attr.endsWith(":out")) throw new Error("Invalid value for "+attr+" attribute - must be a pointer");
624+
if (attr.endsWith(":out") || attr.endsWith(":selected")) throw new Error("Invalid value for "+attr+" attribute - must be a pointer");
600625

601626
// value attribute
602627
else if (attr == "value") {
@@ -615,6 +640,13 @@ export class DOMUtils {
615640
else {
616641
(element as HTMLInputElement).value = this.formatAttributeValue(val, root_path, element)
617642
}
643+
644+
// handle DOMUtils.ATTR_SELECTED_BINDING
645+
if ((<DOMUtils.elWithUIXAttributes>element)[DOMUtils.ATTR_SELECTED_BINDING]) {
646+
if ((<DOMUtils.elWithUIXAttributes>element)[DOMUtils.ATTR_SELECTED_BINDING].val == val) {
647+
(element as HTMLInputElement).checked = true;
648+
}
649+
}
618650
}
619651

620652
// set datex-update

0 commit comments

Comments
 (0)