diff --git a/src/uix/morfo/components/field.ts b/src/uix/morfo/components/field.ts index 3f69c31ef..ae6eebb67 100644 --- a/src/uix/morfo/components/field.ts +++ b/src/uix/morfo/components/field.ts @@ -15,14 +15,18 @@ export const fieldMorfo = { optional: false, states: ['idle', 'error', 'disabled', 'readonly', 'loading'], data: [ - { attr: 'data-state', values: ['idle', 'error', 'disabled', 'readonly', 'loading'] }, - { attr: 'data-disabled', severity: 'optional' }, - { attr: 'data-readonly', severity: 'optional' }, - { attr: 'data-required', severity: 'optional' }, - { attr: 'data-invalid', severity: 'optional' }, - { attr: 'data-dirty', severity: 'optional' }, - { attr: 'data-touched', severity: 'optional' }, - { attr: 'data-field-name', severity: 'optional' } + { + attr: 'data-state', + values: ['idle', 'error', 'disabled', 'readonly', 'loading'], + value: v.stateRef('state') + }, + { attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }, + { attr: 'data-readonly', value: v.propRef('readonly'), severity: 'optional' }, + { attr: 'data-required', value: v.propRef('required'), severity: 'optional' }, + { attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' }, + { attr: 'data-dirty', value: v.propRef('dirty'), severity: 'optional' }, + { attr: 'data-touched', value: v.propRef('touched'), severity: 'optional' }, + { attr: 'data-field-name', value: v.propRef('name'), emit: 'value', severity: 'optional' } ], aria: [] }, @@ -34,9 +38,9 @@ export const fieldMorfo = { defaultElement: 'label', optional: true, data: [ - { attr: 'data-disabled', severity: 'optional' }, - { attr: 'data-invalid', severity: 'optional' }, - { attr: 'data-required', severity: 'optional' } + { attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }, + { attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' }, + { attr: 'data-required', value: v.propRef('required'), severity: 'optional' } ], aria: [ { @@ -55,9 +59,13 @@ export const fieldMorfo = { optional: true, states: ['idle', 'error', 'disabled', 'readonly', 'loading'], data: [ - { attr: 'data-state', values: ['idle', 'error', 'disabled', 'readonly', 'loading'] }, - { attr: 'data-disabled', severity: 'optional' }, - { attr: 'data-invalid', severity: 'optional' } + { + attr: 'data-state', + values: ['idle', 'error', 'disabled', 'readonly', 'loading'], + value: v.stateRef('state') + }, + { attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }, + { attr: 'data-invalid', value: v.propRef('invalid'), severity: 'optional' } ], aria: [] }, @@ -69,7 +77,13 @@ export const fieldMorfo = { defaultElement: 'input', optional: true, states: ['idle', 'error', 'disabled', 'readonly', 'loading'], - data: [{ attr: 'data-state', values: ['idle', 'error', 'disabled', 'readonly', 'loading'] }], + data: [ + { + attr: 'data-state', + values: ['idle', 'error', 'disabled', 'readonly', 'loading'], + value: v.stateRef('state') + } + ], aria: [ { attr: 'type', value: v.propRef('type') }, { @@ -106,7 +120,7 @@ export const fieldMorfo = { kind: 'public', defaultElement: 'div', optional: true, - data: [{ attr: 'data-disabled', severity: 'optional' }], + data: [{ attr: 'data-disabled', value: v.propRef('disabled'), severity: 'optional' }], aria: [] }, { diff --git a/src/uix/soma/components/field/field-provider.svelte.ts b/src/uix/soma/components/field/field-provider.svelte.ts index 2de2ddd82..01a611491 100644 --- a/src/uix/soma/components/field/field-provider.svelte.ts +++ b/src/uix/soma/components/field/field-provider.svelte.ts @@ -1,5 +1,5 @@ import { context, type WithRefOpts } from '../../provider'; -import { boolToEmptyStrOrUndef, boolToStr } from '$adom'; +import { boolToStr } from '$adom'; import { readableActive, state, type ActiveProps, type StateProps } from '$libs/reactive'; import type { OnChangeFn, SomaEvent } from '../../types'; import type { FieldInputType, FieldState } from './types'; @@ -56,12 +56,32 @@ export class FieldProvider { private constructor(opts: FieldOpts) { this.opts = opts; this.soma = Soma.require(); - this.runtime = this.soma.runtime(fieldMorfo, {}); + this.runtime = this.soma.runtime(fieldMorfo, { + states: { + state: () => this.effectiveState + }, + props: { + name: () => this.opts.name.current, + disabled: () => this.isDisabled, + readonly: () => this.isReadonly, + required: () => this.isRequired, + invalid: () => this.isInvalid, + dirty: () => this.isDirty, + touched: () => this.isTouched + }, + parts: { + input: () => this.inputId.current || undefined, + label: () => this.labelId.current || undefined, + 'helper-text': () => this.helperId.current || undefined, + 'error-text': () => this.errorId.current || undefined + } + }); this.runtimePart = this.runtime.part('provider', { id: opts.id, ref: opts.ref, owner: this, - context: FieldProvider.ctx + context: FieldProvider.ctx, + syncAttrs: true }); // Auto-register / unregister with the parent Form (if `name` is set). @@ -153,14 +173,6 @@ export class FieldProvider { readonly props = $derived.by(() => this.runtimePart.assert({ ...this.runtimePart.props, - 'data-field-name': this.opts.name.current, - 'data-state': this.effectiveState, - 'data-disabled': boolToEmptyStrOrUndef(this.isDisabled), - 'data-readonly': boolToEmptyStrOrUndef(this.isReadonly), - 'data-required': boolToEmptyStrOrUndef(this.isRequired), - 'data-invalid': boolToEmptyStrOrUndef(this.isInvalid), - 'data-dirty': boolToEmptyStrOrUndef(this.isDirty), - 'data-touched': boolToEmptyStrOrUndef(this.isTouched), onfocusout: this.onfocusout } as const) ); @@ -185,18 +197,15 @@ export class FieldLabelProvider { this.runtimePart = this.provider.runtime.part('label', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + syncAttrs: true }); this.provider.labelId.current = opts.id.current; } readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - for: this.provider.inputId.current || undefined, - 'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled), - 'data-invalid': boolToEmptyStrOrUndef(this.provider.isInvalid), - 'data-required': boolToEmptyStrOrUndef(this.provider.isRequired) + ...this.runtimePart.props } as const) ); } @@ -220,16 +229,14 @@ export class FieldControlProvider { this.runtimePart = this.provider.runtime.part('control', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + syncAttrs: true }); } readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - 'data-state': this.provider.effectiveState, - 'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled), - 'data-invalid': boolToEmptyStrOrUndef(this.provider.isInvalid) + ...this.runtimePart.props } as const) ); } @@ -320,7 +327,8 @@ export class FieldHelperTextProvider { this.runtimePart = this.provider.runtime.part('helper-text', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + syncAttrs: true }); this.provider.helperId.current = opts.id.current; @@ -334,8 +342,7 @@ export class FieldHelperTextProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - 'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled) + ...this.runtimePart.props } as const) ); } @@ -359,7 +366,8 @@ export class FieldErrorTextProvider { this.runtimePart = this.provider.runtime.part('error-text', { id: opts.id, ref: opts.ref, - owner: this + owner: this, + syncAttrs: true }); this.provider.errorId.current = opts.id.current; @@ -373,9 +381,7 @@ export class FieldErrorTextProvider { readonly props = $derived.by(() => this.runtimePart.assert({ - ...this.runtimePart.props, - role: 'alert' as const, - 'aria-live': 'polite' as const + ...this.runtimePart.props } as const) ); }