From c44a259916ee984881762d8a10b185d624b12fca Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 11 Jun 2026 23:44:48 +0200 Subject: [PATCH] refactor(soma): de-dup spin-field triggers + scrubbers via renderProps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finishes the spin-field family. The 4 triggers + 2 scrubbers (number-field + css-field) now spread renderProps() and add only soma-specific extras. - Scrubbers: role + aria-hidden + data-spin-field-scrubber now morfo-sourced; soma keeps data-scrubbing/disabled, cursor style, pointer handlers. - Triggers: type + aria-label + data-spin-field + aria-disabled now morfo- sourced. The trigger morfos' aria-disabled moves from literal-'true'+condition to propRef('disabled') + ariaBoolean (the de-facto always-emit shape); each trigger registers its own `disabled` source (includes atMax/atMin) so renderProps resolves it. aria-label resolves from the morfo's commonRef('buttons.increment/decrement') — the SAME idlangref the provider used, so the string is unchanged ("Aumentar"/"Disminuir" in es). - Dropped the now-orphaned NUMBER_FIELD_LANGS + boolToStr imports. Verified in browser for all parts (type/aria-label/aria-disabled/role/aria-hidden identical) + soma/morfo tests 95/95 (2 pre-existing palabras contract failures only). The entire spin-field family is now morfo-sourced — zero re-declared attrs. Co-Authored-By: Claude Opus 4.8 --- src/uix/morfo/components/css-field.ts | 8 ++--- src/uix/morfo/components/number-field.ts | 8 ++--- .../css-field/css-field-provider.svelte.ts | 30 ++++++++---------- .../number-field-provider.svelte.ts | 31 ++++++++----------- 4 files changed, 34 insertions(+), 43 deletions(-) diff --git a/src/uix/morfo/components/css-field.ts b/src/uix/morfo/components/css-field.ts index 68b2a984e..3cd03085e 100644 --- a/src/uix/morfo/components/css-field.ts +++ b/src/uix/morfo/components/css-field.ts @@ -158,9 +158,9 @@ export const cssFieldMorfo = { }, { attr: 'aria-disabled', - value: v.literal('true'), + value: v.propRef('disabled'), severity: 'optional', - condition: { when: 'prop-truthy', prop: 'disabled' } + ariaBoolean: true } ] }, @@ -185,9 +185,9 @@ export const cssFieldMorfo = { }, { attr: 'aria-disabled', - value: v.literal('true'), + value: v.propRef('disabled'), severity: 'optional', - condition: { when: 'prop-truthy', prop: 'disabled' } + ariaBoolean: true } ] }, diff --git a/src/uix/morfo/components/number-field.ts b/src/uix/morfo/components/number-field.ts index 5691063b6..03c505a4a 100644 --- a/src/uix/morfo/components/number-field.ts +++ b/src/uix/morfo/components/number-field.ts @@ -150,9 +150,9 @@ export const numberFieldMorfo = { }, { attr: 'aria-disabled', - value: v.literal('true'), + value: v.propRef('disabled'), severity: 'optional', - condition: { when: 'prop-truthy', prop: 'disabled' } + ariaBoolean: true } ] }, @@ -177,9 +177,9 @@ export const numberFieldMorfo = { }, { attr: 'aria-disabled', - value: v.literal('true'), + value: v.propRef('disabled'), severity: 'optional', - condition: { when: 'prop-truthy', prop: 'disabled' } + ariaBoolean: true } ] }, diff --git a/src/uix/soma/components/css-field/css-field-provider.svelte.ts b/src/uix/soma/components/css-field/css-field-provider.svelte.ts index 8a4de984e..cf4901569 100644 --- a/src/uix/soma/components/css-field/css-field-provider.svelte.ts +++ b/src/uix/soma/components/css-field/css-field-provider.svelte.ts @@ -1,6 +1,6 @@ import { watch } from 'runed'; import { context, type WithRefOpts } from '../../provider'; -import { boolToEmptyStrOrUndef, boolToStr, isHTMLElement } from '$adom'; +import { boolToEmptyStrOrUndef, isHTMLElement } from '$adom'; import { readableActive, type Active, type ActiveProps, type StateProps } from '$libs/reactive'; import type { OnChangeFn, @@ -586,7 +586,9 @@ export class CssFieldIncrementTriggerProvider { this.runtimePart = this.provider.runtime.part('increment-trigger', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + // trigger-local disabled (includes atMax) → renderProps resolves aria-disabled + props: { disabled: () => this.isDisabled } }); } @@ -609,13 +611,10 @@ export class CssFieldIncrementTriggerProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - type: 'button' as const, + // type, aria-label, data-spin-field, aria-disabled are morfo-declared. + ...this.runtimePart.renderProps(), tabindex: -1, disabled: this.isDisabled || undefined, - 'aria-label': this.provider.soma.langs.ts(CSS_FIELD_LANGS.INCREMENT), - 'data-spin-field-increment-trigger': '', - 'aria-disabled': boolToStr(this.isDisabled), 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), onclick: this.handleClick, onpointerdown: this.handlePointerDown, @@ -653,7 +652,9 @@ export class CssFieldDecrementTriggerProvider { this.runtimePart = this.provider.runtime.part('decrement-trigger', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + // trigger-local disabled (includes atMin) → renderProps resolves aria-disabled + props: { disabled: () => this.isDisabled } }); } @@ -676,13 +677,10 @@ export class CssFieldDecrementTriggerProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - type: 'button' as const, + // type, aria-label, data-spin-field, aria-disabled are morfo-declared. + ...this.runtimePart.renderProps(), tabindex: -1, disabled: this.isDisabled || undefined, - 'aria-label': this.provider.soma.langs.ts(CSS_FIELD_LANGS.DECREMENT), - 'data-spin-field-decrement-trigger': '', - 'aria-disabled': boolToStr(this.isDisabled), 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), onclick: this.handleClick, onpointerdown: this.handlePointerDown, @@ -810,10 +808,8 @@ export class CssFieldScrubberProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - role: 'presentation' as const, - 'aria-hidden': true as const, - 'data-spin-field-scrubber': '', + // role, aria-hidden, data-spin-field-scrubber are morfo-declared. + ...this.runtimePart.renderProps(), 'data-scrubbing': boolToEmptyStrOrUndef(this.provider.scrubbing), 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), style: { diff --git a/src/uix/soma/components/number-field/number-field-provider.svelte.ts b/src/uix/soma/components/number-field/number-field-provider.svelte.ts index cde5fa552..384414adb 100644 --- a/src/uix/soma/components/number-field/number-field-provider.svelte.ts +++ b/src/uix/soma/components/number-field/number-field-provider.svelte.ts @@ -1,6 +1,6 @@ import { watch } from 'runed'; import { context, type WithRefOpts } from '../../provider'; -import { boolToEmptyStrOrUndef, boolToStr, isHTMLElement } from '$adom'; +import { boolToEmptyStrOrUndef, isHTMLElement } from '$adom'; import { readableActive, type Active, type ActiveProps, type StateProps } from '$libs/reactive'; import type { OnChangeFn, @@ -11,7 +11,6 @@ import type { } from '../../types'; import { KEYS } from '../../keyboard'; import { Soma } from '../../core/soma.svelte'; -import { NUMBER_FIELD_LANGS } from './langs'; import { FieldProvider } from '../field'; // ── Intl fallback cache ────────────────────────────────────────────────────── @@ -660,7 +659,9 @@ export class NumberFieldIncrementTriggerProvider { this.runtimePart = this.provider.runtime.part('increment-trigger', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + // trigger-local disabled (includes atMax) → renderProps resolves aria-disabled + props: { disabled: () => this.isDisabled } }); } @@ -683,13 +684,10 @@ export class NumberFieldIncrementTriggerProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - type: 'button' as const, + // type, aria-label, data-spin-field, aria-disabled are morfo-declared. + ...this.runtimePart.renderProps(), tabindex: -1, disabled: this.isDisabled || undefined, - 'aria-label': this.provider.soma.langs.ts(NUMBER_FIELD_LANGS.INCREMENT), - 'data-spin-field-increment-trigger': '', - 'aria-disabled': boolToStr(this.isDisabled), 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), onclick: this.handleClick, onpointerdown: this.handlePointerDown, @@ -727,7 +725,9 @@ export class NumberFieldDecrementTriggerProvider { this.runtimePart = this.provider.runtime.part('decrement-trigger', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + // trigger-local disabled (includes atMin) → renderProps resolves aria-disabled + props: { disabled: () => this.isDisabled } }); } @@ -750,13 +750,10 @@ export class NumberFieldDecrementTriggerProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - type: 'button' as const, + // type, aria-label, data-spin-field, aria-disabled are morfo-declared. + ...this.runtimePart.renderProps(), tabindex: -1, disabled: this.isDisabled || undefined, - 'aria-label': this.provider.soma.langs.ts(NUMBER_FIELD_LANGS.DECREMENT), - 'data-spin-field-decrement-trigger': '', - 'aria-disabled': boolToStr(this.isDisabled), 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), onclick: this.handleClick, onpointerdown: this.handlePointerDown, @@ -889,10 +886,8 @@ export class NumberFieldScrubberProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - role: 'presentation' as const, - 'aria-hidden': true as const, - 'data-spin-field-scrubber': '', + // role, aria-hidden, data-spin-field-scrubber are morfo-declared. + ...this.runtimePart.renderProps(), 'data-scrubbing': boolToEmptyStrOrUndef(this.provider.scrubbing), 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), style: {