From db24d09305e550d373204206c1fc801c1f1f38fd Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Tue, 3 Nov 2026 15:45:51 +0100 Subject: [PATCH 1/4] use toggle button for rendering of `BooleanFormField` --- .../templates/shared_booleanFormField.tpl | 40 ++---- .../Form/Builder/Field/Dependency/Manager.ts | 3 +- .../woltlab-core-toggle-button.ts | 136 ++++++++++++++---- ts/global.d.ts | 6 + .../files/acp/templates/__boxPipGui.tpl | 1 - .../files/acp/templates/__menuPipGui.tpl | 1 - .../Form/Builder/Field/Dependency/Manager.js | 3 +- .../files/js/WoltLabSuite/WebComponent.min.js | 31 ++-- .../builder/field/BooleanFormField.class.php | 6 +- 9 files changed, 151 insertions(+), 76 deletions(-) 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 @@ -
    -
  1. - 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}{* - *}> - -
  2. -
  3. - 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}{* - *}> - -
  4. -
+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/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager.ts b/ts/WoltLabSuite/Core/Form/Builder/Field/Dependency/Manager.ts index d064eb31ac3..400d5afe03a 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; 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 `