diff --git a/com.woltlab.wcf/templates/shared_booleanFormField.tpl b/com.woltlab.wcf/templates/shared_booleanFormField.tpl
index fb7e2ed4589..07c11ce7d68 100644
--- a/com.woltlab.wcf/templates/shared_booleanFormField.tpl
+++ b/com.woltlab.wcf/templates/shared_booleanFormField.tpl
@@ -1,29 +1,11 @@
-
- -
- getPrefixedId()}" {*
- *}name="{$field->getPrefixedId()}" {*
- *}value="1" {*
- *}data-no-input-id="{$field->getPrefixedId()}_no"{*
- *}{if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
- *}{if $field->isAutofocused()} autofocus{/if}{*
- *}{if $field->isRequired()} required{/if}{*
- *}{if $field->isImmutable()} disabled{/if}{*
- *}{if $field->getValue()} checked{/if}{*
- *}{foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
- *}>
-
-
- -
- getPrefixedId()}_no" {*
- *}name="{$field->getPrefixedId()}" {*
- *}value="0"{*
- *}{if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
- *}{if $field->isImmutable()} disabled{/if}{*
- *}{if !$field->getValue()} checked{/if}{*
- *}{foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
- *}>
-
-
-
+getPrefixedId()}" {*
+ *}name="{$field->getPrefixedId()}" {*
+ *}value="1"{*
+ *}{if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{if $field->isAutofocused()} autofocus{/if}{*
+ *}{if $field->isRequired()} required{/if}{*
+ *}{if $field->isImmutable()} disabled{/if}{*
+ *}{if $field->getValue()} checked{/if}{*
+ *}{foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+*}>
diff --git a/com.woltlab.wcf/templates/shared_checkboxFormField.tpl b/com.woltlab.wcf/templates/shared_checkboxFormField.tpl
index 5e92fc55b29..0a1832eb078 100644
--- a/com.woltlab.wcf/templates/shared_checkboxFormField.tpl
+++ b/com.woltlab.wcf/templates/shared_checkboxFormField.tpl
@@ -1,23 +1,10 @@
-getClasses()|empty} class="{implode from=$field->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{foreach from=$field->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{if !$field->checkDependencies()} style="display: none;"{/if}>
-
- -
-
-
- {include file='shared_formFieldDescription'}
- {include file='shared_formFieldErrors'}
- {include file='shared_formFieldDependencies'}
- {include file='shared_formFieldDataHandler'}
-
-
+getPrefixedId()}" {*
+ *}name="{$field->getPrefixedId()}" {*
+ *}value="1"{*
+ *}{if !$field->getFieldClasses()|empty} class="{implode from=$field->getFieldClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{if $field->isRequired()} required{/if}{*
+ *}{if $field->isImmutable()} disabled{/if}{*
+ *}{if $field->getValue()} checked{/if}{*
+ *}{foreach from=$field->getFieldAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+*}>
diff --git a/com.woltlab.wcf/templates/shared_inlineLabelFormField.tpl b/com.woltlab.wcf/templates/shared_inlineLabelFormField.tpl
new file mode 100644
index 00000000000..c4f7b10cce4
--- /dev/null
+++ b/com.woltlab.wcf/templates/shared_inlineLabelFormField.tpl
@@ -0,0 +1,18 @@
+getClasses()|empty} class="{implode from=$field->getClasses() item='class' glue=' '}{$class}{/implode}"{/if}{*
+ *}{foreach from=$field->getAttributes() key='attributeName' item='attributeValue'} {$attributeName}="{$attributeValue}"{/foreach}{*
+ *}{if !$field->checkDependencies()} style="display: none;"{/if}{*
+*}>
+
+ -
+
+
+ {include file='shared_formFieldErrors'}
+ {include file='shared_formFieldDescription'}
+ {include file='shared_formFieldDependencies'}
+ {include file='shared_formFieldDataHandler'}
+
+
diff --git a/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager.ts b/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager.ts
index d064eb31ac3..5af259c58c9 100644
--- a/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager.ts
+++ b/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager.ts
@@ -92,7 +92,8 @@ function _show(node: HTMLElement): void {
});
}
- node.querySelectorAll("input, select, textarea").forEach((validatedField: HTMLInputElement | HTMLSelectElement) => {
+ const validatedFields = "input, select, textarea, woltlab-core-toggle-button";
+ node.querySelectorAll(validatedFields).forEach((validatedField: HTMLInputElement | HTMLSelectElement) => {
// if a container is shown, ignore all fields that
// have a hidden parent element within the container
let parentNode = validatedField.parentNode! as HTMLElement;
@@ -149,11 +150,13 @@ export function addDependency(dependency: FormBuilderFormFieldDependency): void
if (!_fields.has(id)) {
_fields.set(id, field);
+ // Toggle buttons fire `change` on every state change, including programmatic ones.
if (
- field.tagName === "INPUT" &&
- ((field as HTMLInputElement).type === "checkbox" ||
- (field as HTMLInputElement).type === "radio" ||
- (field as HTMLInputElement).type === "hidden")
+ field.tagName === "WOLTLAB-CORE-TOGGLE-BUTTON" ||
+ (field.tagName === "INPUT" &&
+ ((field as HTMLInputElement).type === "checkbox" ||
+ (field as HTMLInputElement).type === "radio" ||
+ (field as HTMLInputElement).type === "hidden"))
) {
field.addEventListener("change", () => checkDependencies());
} else {
diff --git a/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Value.ts b/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Value.ts
index 096f594bf9a..eb418dd8302 100644
--- a/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Value.ts
+++ b/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Value.ts
@@ -26,7 +26,13 @@ class Value extends Abstract {
return false;
}
- values.push((this._field as HTMLInputElement).value);
+ if (this._field.tagName === "WOLTLAB-CORE-TOGGLE-BUTTON") {
+ // The value of a toggle button is always set, regardless of its state.
+ const toggleButton = this._field as WoltlabCoreToggleButtonElement;
+ values.push(toggleButton.checked ? toggleButton.value : "0");
+ } else {
+ values.push((this._field as HTMLInputElement).value);
+ }
} else {
let hasCheckedField = true;
this._fields.forEach((field: HTMLInputElement) => {
diff --git a/ts/WoltLabSuite/WebComponent/woltlab-core-toggle-button.ts b/ts/WoltLabSuite/WebComponent/woltlab-core-toggle-button.ts
index 30716755344..7af56662c0c 100644
--- a/ts/WoltLabSuite/WebComponent/woltlab-core-toggle-button.ts
+++ b/ts/WoltLabSuite/WebComponent/woltlab-core-toggle-button.ts
@@ -2,6 +2,9 @@
* `` creates a toggle button.
* Usage: ``
*
+ * The element is form-associated and submits its `value` (defaults to `1`)
+ * when checked, behaving like a regular checkbox.
+ *
* @author Marcel Werk
* @copyright 2001-2024 WoltLab GmbH
* @license GNU Lesser General Public License
@@ -10,13 +13,20 @@
{
class WoltlabCoreToggleButtonElement extends HTMLElement {
+ static formAssociated = true;
+
static get observedAttributes() {
- return ["checked"];
+ return ["checked", "required"];
}
+ readonly #internals: ElementInternals;
+ #defaultChecked: boolean | undefined = undefined;
+
constructor() {
super();
+ this.#internals = this.attachInternals();
+
const shadow = this.attachShadow({ mode: "open" });
const style = document.createElement("style");
style.textContent = `
@@ -24,7 +34,11 @@
display: inline-flex;
vertical-align: middle;
}
-
+
+ :host(:disabled) {
+ opacity: 0.54;
+ }
+
[part="track"] {
position: relative;
border-radius: 14px;
@@ -35,6 +49,10 @@
transition: 0.4s;
}
+ :host(:disabled) [part="track"] {
+ cursor: default;
+ }
+
:host([checked]) [part="track"] {
background-color: var(--wcfToggleButtonBackgroundActive);
}
@@ -74,46 +92,49 @@
const iconSlot = document.createElement("slot");
iconSlot.name = "icon";
slider.append(iconSlot);
- const checkboxSlot = document.createElement("slot");
- checkboxSlot.name = "checkbox";
- shadow.append(style, track, checkboxSlot);
+ shadow.append(style, track);
+ // Clicks on an associated `