You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte-kit-vice/src/uix/soma/components/button/button-provider.svelte.ts

123 lines
3.8 KiB

feat(eidos): Button + 9 demo rebuilds + picker recipe polish Build: - New <Button> compound (morfo + soma + sema + eidos): 6 variants (solid/soft/surface/outline/ghost/plain), 8 colors, 5 sizes, optional icon/endIcon snippets, loading state with built-in Spinner, asChild Slot pattern, intent-driven sema (commit-action with fromProp:intent). Demo rebuilds (canonical depth per DEMO_AUTHORING_GUIDE): - context-menu, dropdown-menu, pin-input, alert-dialog, toggle-group: full per-part Morfo contracts, reference-parity tables, ARIA contract tables, keyboard tables, composition controls. - color-field, time-field, time-picker, time-range-picker, color-picker, month-grid, year-grid: same canonical treatment; per-part data/aria/ keyboard iteration of raw morfo, reference parity vs react-aria/ ark-ui/Chakra/bits-ui/Mantine. Picker recipe polish: - PickerShell.Clear/Cancel/Close refactored to use <Button size="xs"> (affects 5 pickers — date / date-range / time / time-range / color). - TimePicker + TimeRangePicker: popover condensed (min-width 18rem -> 14 / 32 -> 28), per-channel accent (hour=primary, minute=secondary, second=neutral; end variant muted), ruler off by default, hour ruler cleaned to 0/6/12/18/23. - ColorPicker: typography token --_color-picker-trigger-font-size that scales with data-size (10/12/14px); autoFormat default true; Area recipe override for [data-x-channel='hue'] with rainbow gradient + saturation/brightness Y overlays. - ValueText eidos wrapper: fix children snippet bug that suppressed soma's displayValue fallback. - date-field.css: segment editing affordance (cursor + hover + active ring) via new --_date-field-accent-element token across 8 color cascades. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { Soma } from '../../core/soma.svelte';
refactor(soma): route FieldProvider through field/internals barrel + register DRP view sub-parts (audit Round 3 §3 #23 + P4 follow-up) Two related housekeeping items in one commit: **1. FieldProvider via barrel (audit Round 3 §3 #23)** 37 consumer files across soma imported `FieldProvider` from the deep path `'../field/field-provider.svelte'`, mirroring the same pattern that motivated `internals.ts` indirection on the 5 pickers earlier. Adds `src/uix/soma/components/field/internals.ts` re-exporting `FieldProvider` and exposes it from the barrel `exports.ts`. The 37 imports become the canonical `from '../field'`. Consistency only — no behavioral change. **2. DateRangePicker view sub-parts (P4 follow-up)** The MonthView/YearView soma parts created in commit `e93c0c73` stamp internal layout data-attrs (`-header`, `-prev`, `-heading`, `-next`, `-grid`, `-cell`) that weren't declared in the morfo, surfacing as 12 hardcoded-data-attr violations once the contract suite caught up. Declares the 12 sub-parts in the morfo with the canonical kebab prefix (`month-view-*`, `year-view-*`). Each entry minimal (data: [], aria: []) except `*-prev` / `*-next` which stamp `type='button'` and `*-cell` which surfaces the `data-range-{start,end,in-range}` classification states used by the eidos range tinting CSS. Test result: 2393/2399 passing — back to the canonical 6 fails (Words + cookie infra). The 13 transient regressions are closed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { FieldProvider } from '../field';
feat(eidos): Button + 9 demo rebuilds + picker recipe polish Build: - New <Button> compound (morfo + soma + sema + eidos): 6 variants (solid/soft/surface/outline/ghost/plain), 8 colors, 5 sizes, optional icon/endIcon snippets, loading state with built-in Spinner, asChild Slot pattern, intent-driven sema (commit-action with fromProp:intent). Demo rebuilds (canonical depth per DEMO_AUTHORING_GUIDE): - context-menu, dropdown-menu, pin-input, alert-dialog, toggle-group: full per-part Morfo contracts, reference-parity tables, ARIA contract tables, keyboard tables, composition controls. - color-field, time-field, time-picker, time-range-picker, color-picker, month-grid, year-grid: same canonical treatment; per-part data/aria/ keyboard iteration of raw morfo, reference parity vs react-aria/ ark-ui/Chakra/bits-ui/Mantine. Picker recipe polish: - PickerShell.Clear/Cancel/Close refactored to use <Button size="xs"> (affects 5 pickers — date / date-range / time / time-range / color). - TimePicker + TimeRangePicker: popover condensed (min-width 18rem -> 14 / 32 -> 28), per-channel accent (hour=primary, minute=secondary, second=neutral; end variant muted), ruler off by default, hour ruler cleaned to 0/6/12/18/23. - ColorPicker: typography token --_color-picker-trigger-font-size that scales with data-size (10/12/14px); autoFormat default true; Area recipe override for [data-x-channel='hue'] with rainbow gradient + saturation/brightness Y overlays. - ValueText eidos wrapper: fix children snippet bug that suppressed soma's displayValue fallback. - date-field.css: segment editing affordance (cursor + hover + active ring) via new --_date-field-accent-element token across 8 color cascades. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
import { buttonMorfo } from '../../../morfo/components/button';
import type { OptsFromProps } from '../../provider';
import type { OnChangeFn, SomaMouseEvent } from '../../types';
import type { ButtonProps, ButtonIntent } from './types';
import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
// ── Provider ───────────────────────────────────────────────────────────────
/**
* Provider's reactive contract derived from `ButtonProps`.
*/
type ButtonOpts = OptsFromProps<
ButtonProps,
| 'intent'
| 'color'
| 'type'
| 'disabled'
| 'loading'
| 'loadingText'
| 'spinnerPlacement'
| 'aria-label'
| 'onPress',
never
>;
/**
* Button provider — runtime-direct.
*
* Owns the press → `commit-action` flow. Sequence is `pre` so the
* perceptual signal lands at the moment of the click; the consumer's
* onclick may then run async work without blocking it.
*/
export class ButtonProvider {
static create(opts: ButtonOpts) {
return new ButtonProvider(opts);
}
readonly opts: ButtonOpts;
readonly field = FieldProvider.get();
readonly runtime: SomaRuntime;
readonly runtimePart: SomaRuntimePart;
readonly soma: Soma;
private constructor(opts: ButtonOpts) {
this.opts = opts;
this.soma = Soma.require();
this.runtime = this.soma.runtime(buttonMorfo, {
states: {
loading: () => opts.loading.current ?? false
},
props: {
disabled: () => this.isDisabled,
loading: () => opts.loading.current ?? false,
intent: () => opts.intent.current ?? 'neutral',
type: () => opts.type.current ?? 'button',
/**
* Resolved visual color. Doctrine: aesthetic must not
* contradict semantic. When intent is evaluative
* (affirm/fulfill/risk/threat/loss), it WINS over any
* hierarchy override. When intent is neutral, `color` may
* promote the visual to primary/secondary; if neither,
* default 'primary' (Button's default is promoted because
* actions are typically the focal point of their surface).
*/
color: () => {
const intent = opts.intent.current ?? 'neutral';
if (intent !== 'neutral') return intent;
return opts.color.current ?? 'primary';
},
'aria-label': () => opts['aria-label'].current
},
events: {
'commit-action': () => {
// The consumer's onclick has already been (or will be)
// invoked by native button click dispatch. We only fire
// onPress for diagnostic / analytics consumers.
opts.onPress.current?.({ intent: opts.intent.current ?? 'neutral' });
}
}
});
this.runtimePart = this.runtime.part('provider', {
id: opts.id,
ref: opts.ref,
owner: this,
syncAttrs: true
});
}
readonly isDisabled = $derived.by(
() => (this.opts.disabled.current ?? false) || (this.field?.isDisabled ?? false)
);
readonly isLoading = $derived.by(() => this.opts.loading.current ?? false);
readonly snippetProps = $derived.by(() => ({
loading: this.isLoading,
disabled: this.isDisabled
}));
readonly onclick = (_e: SomaMouseEvent<HTMLButtonElement>) => {
if (this.isDisabled || this.isLoading) return;
// Sequence 'pre' — the perceptual signal fires synchronously with
// the click; consumer's onclick runs in parallel.
void this.runtime.trigger('commit-action');
};
/**
* Render-time props. Composed of:
* - identity from the runtime (`id`, `data-button`, ref attachment)
* - the HTML `disabled` attribute
* - the `onclick` handler
*/
readonly props = $derived.by(() => ({
...this.runtimePart.props,
disabled: this.isDisabled || undefined,
onclick: this.onclick
}));
}
export type { OnChangeFn, ButtonIntent };

Powered by TurnKey Linux.