` | The gradient preview chip (paints `--gp-current-gradient`). |
+
+`Content` / footer (`Clear` / `Cancel` / `Close`) are the composed **Popover** +
+shared **PickerShell** parts — re-exported under the eidos namespace.
+
+## Provider API (via `GradientPickerProvider.require()`)
+
+`value` (getter), `setValue(g)`, `css` (derived `gradientToCss`), `isDisabled`,
+`triggerNode` · `commit()` (save + close), `cancel()` (revert to open-edge value
++ close), `clear()` (reset to the default gradient). `valueOnOpen` is snapshotted
+on the opening edge to power `cancel()`.
+
+## Data Attributes
+
+| Part | Attribute | Values |
+| -------- | ----------------------------- | ------------------ |
+| Provider | `data-gradient-picker` | Always present |
+| Provider | `data-state` | `open` \| `closed` |
+| Trigger | `data-gradient-picker-trigger`| Always present |
+| Trigger | `data-state` | `open` \| `closed` |
+| Content | `data-gradient-picker-content`| Present when open |
+
+## Keyboard
+
+| Key | Action |
+| ------------------ | --------------------------------- |
+| `Enter` / `Space` | Open the editor (on the trigger) |
+| `Escape` | Close the popover |
+| `Tab` | Move focus within the editor |
+
+## Sema events
+
+| Event | Family | Verb | Target | When |
+| -------------- | -------- | ------- | --------- | ----------------------------- |
+| `commit-reset` | `commit` | `reset` | `content` | The Clear action resets it. |
+
+`expression: 'delegated'` — no dialog semantics here (open / close / focus trap
+belong to the composed Popover). The editing firma (handle / commit) lives on the
+embedded GradientBuilder; the open / close firma lives on the Popover.
+
+## Usage
+
+```svelte
+
+
+
+
+
+
+
+
+```
diff --git a/src/uix/soma/components/gradient-picker/components/gradient-picker-trigger.svelte b/src/uix/soma/components/gradient-picker/components/gradient-picker-trigger.svelte
new file mode 100644
index 000000000..b371041b9
--- /dev/null
+++ b/src/uix/soma/components/gradient-picker/components/gradient-picker-trigger.svelte
@@ -0,0 +1,51 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+{/if}
diff --git a/src/uix/soma/components/gradient-picker/components/gradient-picker-value-swatch.svelte b/src/uix/soma/components/gradient-picker/components/gradient-picker-value-swatch.svelte
new file mode 100644
index 000000000..c3a2e609f
--- /dev/null
+++ b/src/uix/soma/components/gradient-picker/components/gradient-picker-value-swatch.svelte
@@ -0,0 +1,35 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/src/uix/soma/components/gradient-picker/components/gradient-picker.svelte b/src/uix/soma/components/gradient-picker/components/gradient-picker.svelte
new file mode 100644
index 000000000..6284e0fb5
--- /dev/null
+++ b/src/uix/soma/components/gradient-picker/components/gradient-picker.svelte
@@ -0,0 +1,67 @@
+
+
+{#if child}
+ {@render child({ props: mergedProps })}
+{:else}
+
+ {@render children?.()}
+
+{/if}
diff --git a/src/uix/soma/components/gradient-picker/exports.ts b/src/uix/soma/components/gradient-picker/exports.ts
new file mode 100644
index 000000000..5afd73b42
--- /dev/null
+++ b/src/uix/soma/components/gradient-picker/exports.ts
@@ -0,0 +1,16 @@
+export { default as Provider } from './components/gradient-picker.svelte';
+export { default as Trigger } from './components/gradient-picker-trigger.svelte';
+export { default as ValueSwatch } from './components/gradient-picker-value-swatch.svelte';
+
+export {
+ GradientPickerProvider,
+ GradientPickerTriggerProvider,
+ GradientPickerValueSwatchProvider,
+ DEFAULT_PICKER_GRADIENT
+} from './gradient-picker-provider.svelte';
+
+export type {
+ GradientPickerProviderProps as ProviderProps,
+ GradientPickerTriggerProps as TriggerProps,
+ GradientPickerValueSwatchProps as ValueSwatchProps
+} from './types';
diff --git a/src/uix/soma/components/gradient-picker/gradient-picker-provider.svelte.ts b/src/uix/soma/components/gradient-picker/gradient-picker-provider.svelte.ts
new file mode 100644
index 000000000..e73adb71a
--- /dev/null
+++ b/src/uix/soma/components/gradient-picker/gradient-picker-provider.svelte.ts
@@ -0,0 +1,206 @@
+import { untrack } from 'svelte';
+import { watch } from '$libs/reactive';
+import { context, type ProviderOpts, type WithRefOpts } from '../../provider';
+import { boolToEmptyStrOrUndef } from '$adom';
+import { type ActiveProps, type StateProps } from '$libs/reactive';
+import type { OnChangeFn } from '../../types';
+import { gradientToCss, type Gradient } from '$libs/gradient';
+import { pickerShellContext, type PickerShellHandle, type PickerShellMode } from '../picker-shell';
+
+import { gradientPickerMorfo } from '../../../morfo/components/gradient-picker';
+import { Soma } from '../../core/soma.svelte';
+import type { SomaRuntime, SomaRuntimePart } from '../../runtime.svelte';
+
+/** The fallback gradient the Clear action restores (matches GradientBuilder's default). */
+export const DEFAULT_PICKER_GRADIENT: Gradient = {
+ kind: 'linear',
+ angle: 90,
+ stops: [
+ { color: { kind: 'css', value: '#000000' } },
+ { color: { kind: 'css', value: '#ffffff' } }
+ ]
+};
+
+// ── Root ─────────────────────────────────────────────────────────────────────
+
+interface GradientPickerOpts
+ extends ProviderOpts,
+ StateProps<{ value: Gradient; open: boolean }>,
+ ActiveProps<{
+ disabled: boolean;
+ mode: PickerShellMode;
+ onValueCommit: OnChangeFn
;
+ }> {}
+
+/**
+ * Headless GradientPicker — the popover-picker shell around the interactive
+ * GradientBuilder. Owns the `open` state and the commit / cancel / clear
+ * lifecycle (exposed to `` via `pickerShellContext`). The
+ * gradient editing itself flows through the composed GradientBuilder, which
+ * binds the SAME `value` — so `cancel()` reverts to the value the popover
+ * opened with.
+ */
+export class GradientPickerProvider {
+ readonly opts: GradientPickerOpts;
+ readonly runtime: SomaRuntime;
+ readonly runtimePart: SomaRuntimePart;
+ readonly soma: Soma;
+ static readonly ctx = context('GradientPicker');
+ static get(): GradientPickerProvider | undefined {
+ return this.ctx.getOr(undefined) as GradientPickerProvider | undefined;
+ }
+ static require(): GradientPickerProvider {
+ return this.ctx.get();
+ }
+ static create(opts: GradientPickerOpts) {
+ return new GradientPickerProvider(opts);
+ }
+
+ triggerNode = $state(null);
+
+ get isDisabled(): boolean {
+ return this.opts.disabled.current;
+ }
+
+ /** The committed gradient, serialized for the trigger swatch / value chip. */
+ readonly css = $derived.by(() => gradientToCss(this.opts.value.current));
+
+ /** The live gradient — read/written by the composed GradientBuilder body. */
+ get value(): Gradient {
+ return this.opts.value.current;
+ }
+ setValue(next: Gradient): void {
+ this.opts.value.current = next;
+ }
+
+ private constructor(opts: GradientPickerOpts) {
+ this.opts = opts;
+ this.soma = Soma.require();
+ this.runtime = this.soma.runtime(gradientPickerMorfo, {
+ states: { open: () => this.opts.open.current },
+ props: { disabled: () => this.isDisabled }
+ });
+ this.runtimePart = this.runtime.part('provider', {
+ id: opts.id,
+ ref: opts.ref,
+ owner: this,
+ context: GradientPickerProvider.ctx,
+ syncAttrs: true
+ });
+
+ // Snapshot the value on the opening edge so `cancel()` can revert.
+ watch(
+ () => this.opts.open.current,
+ (now, prev) => {
+ if (now && !prev) this.valueOnOpen = untrack(() => this.opts.value.current);
+ }
+ );
+
+ // Expose commit / cancel / clear to descendant `` parts.
+ pickerShellContext.set(this.pickerShellHandle);
+ }
+
+ setTriggerNode(el: HTMLElement | null): void {
+ this.triggerNode = el;
+ }
+
+ // ── PickerShell lifecycle ────────────────────────────────────────────────
+ private valueOnOpen: Gradient | undefined = undefined;
+
+ /** Save the current gradient and close (drives ``). */
+ commit(): void {
+ this.opts.open.current = false;
+ this.opts.onValueCommit.current(this.opts.value.current);
+ }
+
+ /** Revert to the value the popover opened with and close (``). */
+ cancel(): void {
+ if (this.valueOnOpen !== undefined) this.opts.value.current = this.valueOnOpen;
+ this.opts.open.current = false;
+ }
+
+ /** Reset the gradient to the default without closing (``). */
+ clear(): void {
+ if (this.isDisabled) return;
+ this.opts.value.current = DEFAULT_PICKER_GRADIENT;
+ void this.runtime.trigger('commit-reset');
+ }
+
+ readonly pickerShellHandle: PickerShellHandle = {
+ getMode: () => this.opts.mode.current,
+ commit: () => this.commit(),
+ cancel: () => this.cancel(),
+ clear: () => this.clear()
+ };
+
+ get props() {
+ return this.runtimePart.renderProps();
+ }
+}
+
+// ── Trigger ───────────────────────────────────────────────────────────────────
+
+interface GradientPickerTriggerOpts
+ extends WithRefOpts,
+ ActiveProps<{ ariaLabel: string | undefined }> {}
+
+export class GradientPickerTriggerProvider {
+ readonly opts: GradientPickerTriggerOpts;
+ readonly runtimePart: SomaRuntimePart;
+ readonly provider = GradientPickerProvider.require();
+ static create(opts: GradientPickerTriggerOpts) {
+ return new GradientPickerTriggerProvider(opts);
+ }
+
+ private constructor(opts: GradientPickerTriggerOpts) {
+ this.opts = opts;
+ this.runtimePart = this.provider.runtime.part('trigger', {
+ id: opts.id,
+ ref: opts.ref,
+ owner: this,
+ onRefChange: (v) => this.provider.setTriggerNode(v)
+ });
+ }
+
+ /** Stamp the live gradient so eidos paints the swatch purely via CSS. */
+ readonly inlineStyle = $derived.by(() => `--gp-current-gradient: ${this.provider.css}`);
+
+ get props() {
+ const ariaLabel = this.opts.ariaLabel.current;
+ return {
+ ...this.runtimePart.renderProps(),
+ ...(ariaLabel ? { 'aria-label': ariaLabel } : {}),
+ disabled: this.provider.isDisabled || undefined,
+ 'data-disabled': boolToEmptyStrOrUndef(this.provider.isDisabled),
+ style: this.inlineStyle
+ };
+ }
+}
+
+// ── ValueSwatch ───────────────────────────────────────────────────────────────
+
+export class GradientPickerValueSwatchProvider {
+ readonly opts: WithRefOpts;
+ readonly runtimePart: SomaRuntimePart;
+ readonly provider = GradientPickerProvider.require();
+ static create(opts: WithRefOpts) {
+ return new GradientPickerValueSwatchProvider(opts);
+ }
+
+ private constructor(opts: WithRefOpts) {
+ this.opts = opts;
+ this.runtimePart = this.provider.runtime.part('value-swatch', {
+ id: opts.id,
+ ref: opts.ref,
+ owner: this
+ });
+ }
+
+ readonly backgroundStyle = $derived.by(
+ () => `background: ${this.provider.css}; --gp-current-gradient: ${this.provider.css};`
+ );
+
+ get props() {
+ return { ...this.runtimePart.renderProps(), style: this.backgroundStyle };
+ }
+}
diff --git a/src/uix/soma/components/gradient-picker/index.ts b/src/uix/soma/components/gradient-picker/index.ts
new file mode 100644
index 000000000..8570a426a
--- /dev/null
+++ b/src/uix/soma/components/gradient-picker/index.ts
@@ -0,0 +1 @@
+export * from './exports';
diff --git a/src/uix/soma/components/gradient-picker/types.ts b/src/uix/soma/components/gradient-picker/types.ts
new file mode 100644
index 000000000..8e5519b21
--- /dev/null
+++ b/src/uix/soma/components/gradient-picker/types.ts
@@ -0,0 +1,37 @@
+import type {
+ WithChild,
+ Without,
+ OnChangeFn,
+ PrimitiveDivAttributes,
+ PrimitiveButtonAttributes
+} from '../../types';
+import type { Gradient } from '$libs/gradient';
+import type { PickerShellMode } from '../picker-shell';
+
+export type GradientPickerProviderProps = WithChild<{
+ /** Unique identifier. Auto-generated if omitted. */
+ id?: string;
+ /** The gradient being edited. Bindable. */
+ value?: Gradient;
+ /** Called when the user commits (Save / Done). */
+ onValueCommit?: OnChangeFn;
+ /** Popover open state. Bindable. @default false */
+ open?: boolean;
+ /** Called when the open state changes. */
+ onOpenChange?: OnChangeFn;
+ /** Footer layout mode — `inline` (clear/cancel) vs `modal` (cancel/save). @default 'inline' */
+ mode?: PickerShellMode;
+ /** @default false */
+ disabled?: boolean;
+}> &
+ Without;
+
+export type GradientPickerTriggerProps = WithChild<{
+ id?: string;
+}> &
+ Without;
+
+export type GradientPickerValueSwatchProps = WithChild<{
+ id?: string;
+}> &
+ Without;
diff --git a/web/routes/uix/components/gradient-builder/+page.svelte b/web/routes/uix/components/gradient-builder/+page.svelte
new file mode 100644
index 000000000..b494f90a4
--- /dev/null
+++ b/web/routes/uix/components/gradient-builder/+page.svelte
@@ -0,0 +1,605 @@
+
+
+
+
+ Controls · GradientBuilder
+ GradientBuilder
+
+ Interactive editor over the canonical Gradient model (the same model the theming
+ axis derives) — so a gradient built here is also a themeable token. Every color stop is a real
+ role="slider"
+ with aria-valuetext and arrow / Home / End / Delete keys: gradient editing that works
+ from the keyboard and a screen reader, which no incumbent ships.
+
+
+ parts{partsList.length}
+ events{actions.length}
+ sizes{sizes.length}
+ variants{variants.length}
+ model$libs/gradient
+
+
+
+
+
+
+
+ trace
+ {#if trace.entries.length === 0}
+ drag a stop, add one, or ▶ play in Sema to see events
+ {:else}
+ {#each trace.entries.slice(0, 3) as entry}
+ {entry.event} · {entry.family}{entry.intent
+ ? ' · ' + entry.intent
+ : ''}
+ {/each}
+ {/if}
+ kind {value.kind}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if tab === 'live'}
+
+ Controls
+
+ The component API. GradientBuilder has no color prop — it
+ paints the user's gradient, not a palette role — so the palette picker is absent (by
+ design). Foundation axes live in the
+ tab.
+
+
+
+ eidos size · variant
+
+
+
+
+
+
+
+ soma behavior
+
+
+
+
+
+ live value · gradientToCss(value)
+
+
+ $libs/gradient
+ the bound gradient, serialized
+ css
+
+
background: {css};
+
+
+
+
+
+ eidos
+ visual · size · variant
+ svelte
+
+
{snippet}
+
+
+ {/if}
+
+ {#if tab === 'system'}
+
+ eidos System axes
+
+ The foundation knobs that are NOT component props, applied live to the stage above. Change
+ density / mode / scaling / border and watch the editor card respond. The gradient itself is
+ the user's data — it doesn't retint with the theme.
+
+
+
+ {/if}
+
+ {#if tab === 'motion'}
+
+ Motion catalog
+
+ GradientBuilder has no motion prop. Its motion is the direct-manipulation
+ feedback of the stops (the handle-* firma — see Sema) and the eidos focus ring. The
+ catalog below teaches the system presets a consuming layout can apply.
+
+
+
+ {/if}
+
+ {#if tab === 'sema'}
+
+ sema firma + sound
+
+ Four events: grabbing a stop (handle-pick) and dragging it (handle-drag) carry the slider's pickup / drag firma; settling (commit-set) and resetting
+ (commit-reset) are soft commits. Turn on Semantics → Sound in the
+ topbar, then ▶ play.
+
+
+ stageRef?.querySelector('[data-gradient-builder-track]') ??
+ stageRef?.querySelector('[data-gradient-builder]') ??
+ stageRef}
+ />
+
+ {/if}
+
+ {#if tab === 'services'}
+
+ Services
+
+ GradientBuilder consumes one app service: langs (i18n). Its morfo declares
+ translatable texts (the track / add-stop / angle / kind labels) that resolve
+ through uix.langs — switch the language in the topbar to see them follow.
+
+
+
+ | Text key | Default | Service |
+
+ | track | Gradient stops | langs |
+ | add-stop | Add color stop | langs |
+ | angle-dial | Gradient angle | langs |
+ | kind-switch | Gradient type | langs |
+
+
+
+
+ {/if}
+
+ {#if tab === 'api'}
+
+ API reference
+
+
+ | Prop | Type | Default | Description |
+
+ | value | Gradient | 2-stop linear | The gradient being edited. Bindable. |
+ | size eidos | 'sm' | 'md' | 'lg' | 'md' | Visual size for the editor + child controls. |
+ | variant eidos | 'surface' | 'outline' | 'ghost' | 'surface' | The editor card framing. |
+ | disabled | boolean | false | Disables all editing. |
+ | onValueCommit | (g: Gradient) => void | — | Fires on every settled change. |
+
+
+
+
+ Compound parts — .Preview · .Track · .Stop · .StopColor · .KindSwitch · .AngleDial · .AddStop — are rendered by default when no children are passed; compose them explicitly to customise
+ the layout.
+
+
+ {/if}
+
+ {#if tab === 'morfo'}
+
+
+ morfo · declarative contract
+
+
+ Source: src/uix/morfo/components/gradient-builder.ts.
+
+
+ Parts
+
+
+ | Part | Marker | Element | Role | Archetype | Optional |
+
+ {#each partsList as part}
+
+ | {part.kebab} |
+ [{part.marker}] |
+ <{part.defaultElement}> |
+ {part.role ?? '—'} |
+ {part.archetype ?? '—'} |
+ {part.optional ? 'yes' : 'no'} |
+
+ {/each}
+
+
+
+
+ Events
+
+
+ | name | family | verb | sequence | target |
+
+ {#each actions as action}
+ {@const sem = action.semantic}
+
+ | {action.name} |
+ {sem.family} |
+ {sem.verb ?? '—'} |
+ {sem.sequence ?? 'pre'} |
+ {action.target} |
+
+ {/each}
+
+
+
+
+ {/if}
+
+ {#if tab === 'recipe'}
+
+ Eidos recipe
+
+ Selectors at src/uix/eidos/components/gradient-builder/gradient-builder.css;
+ internal tokens --_gradient-builder-* derive from global tokens (self-contained
+ — gradient-builder is on the active dev track, not yet registered in
+ recipes/base.ts). The child controls bring their own recipes (Slider /
+ ToggleGroup / Button / ColorPicker).
+
+
+
+ | Selector | Source |
+
+ | [data-gradient-builder] | morfo |
+ | [data-gradient-builder][data-size='X'] | eidos |
+ | [data-gradient-builder][data-variant='X'] | eidos |
+ | [data-gradient-builder][data-kind='X'] | morfo |
+ | [data-gradient-builder-track] / -stop | morfo |
+ | [data-gradient-builder-stop][data-selected] / [data-dragging] | morfo |
+
+
+
+
+ {/if}
+
+ {#if tab === 'a11y'}
+
+ Accessibility
+
+ The differentiator: every color stop is a real role="slider" — keyboard +
+ screen-reader gradient editing. The track is a labelled role="group".
+
+ Keyboard (Stop)
+
+
+ | Key | Action |
+
+ | ← / ↓ | Move stop −1% (Shift −10%) |
+ | → / ↑ | Move stop +1% (Shift +10%) |
+ | Home / End | Move stop to 0% / 100% |
+ | Delete / Backspace | Remove the stop (min 2 remain) |
+ | Enter / Space | Select the stop |
+
+
+
+ ARIA contract
+
+
+ | Part | Attribute | Value |
+
+ | Track | role | group + aria-label |
+ | Stop | role | slider |
+ | Stop | aria-valuenow | position % |
+ | Stop | aria-valuetext | "Stop 2 of 4, 40%" |
+
+
+
+
+ {/if}
+
+
+
diff --git a/web/routes/uix/components/gradient-picker/+page.svelte b/web/routes/uix/components/gradient-picker/+page.svelte
new file mode 100644
index 000000000..97ee2212c
--- /dev/null
+++ b/web/routes/uix/components/gradient-picker/+page.svelte
@@ -0,0 +1,410 @@
+
+
+
+
+ Controls · GradientPicker
+ GradientPicker
+
+ The GradientBuilder wrapped in a Popover —
+ a field-shaped trigger carrying a live gradient chip opens the editor, with a
+ Clear / Cancel / Save footer. An identity wrapper over the composed Popover (the Popover is
+ the dialog); Cancel reverts to the value the popover opened with.
+
+
+ parts{partsList.length}
+ events{actions.length}
+ sizes{sizes.length}
+ variants{variants.length}
+ model$libs/gradient
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ trace
+ {#if trace.entries.length === 0}
+ open the picker, edit, then Save / Cancel
+ {:else}
+ {#each trace.entries.slice(0, 3) as entry}
+ {entry.event} · {entry.family}{entry.intent ? ' · ' + entry.intent : ''}
+ {/each}
+ {/if}
+ open {open ? 'yes' : 'no'}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {#if tab === 'live'}
+
+ Controls
+
+ The component API. Like GradientBuilder it has no color prop. Foundation
+ axes live in the tab.
+
+
+
+ eidos size · variant
+
+
+
+
+
+
+
+ soma behavior
+
+
+
+
+
+
+
+ committed value · gradientToCss(value)
+
+
+ $libs/gradient
+ the bound gradient, serialized
+ css
+
+
background: {css};
+
+
+
+
+
+ eidos
+ visual · trigger + popover
+ svelte
+
+
{snippet}
+
+
+ {/if}
+
+ {#if tab === 'system'}
+
+ eidos System axes
+
+ Foundation knobs applied to the stage. Open the picker and switch mode to preview the
+ popover in dark.
+
+
+
+ {/if}
+
+ {#if tab === 'motion'}
+
+ Motion catalog
+
+ The picker's motion is the composed Popover's enter / exit (the emerge firma) plus the
+ editor's direct-manipulation feedback. The catalog teaches the system presets.
+
+
+
+ {/if}
+
+ {#if tab === 'sema'}
+
+ sema firma + sound
+
+ The picker declares one value event — commit-reset (the Clear action). The
+ editing firma (handle / commit) lives on the embedded GradientBuilder; the open / close
+ firma lives on the composed Popover. Turn on Semantics → Sound, then ▶ play.
+
+ stageRef?.querySelector('[data-gradient-picker]') ?? stageRef} />
+
+ {/if}
+
+ {#if tab === 'services'}
+
+ Services
+
+ GradientPicker consumes langs (i18n): the trigger / content labels +
+ the footer Clear / Cancel / Save (from the shared picker-shell). Switch language in the
+ topbar to see them follow.
+
+
+
+ | Text key | Default | Service |
+
+ | label | Gradient picker | langs |
+ | trigger | Open gradient editor | langs |
+ | content | Gradient editor | langs |
+
+
+
+
+ {/if}
+
+ {#if tab === 'api'}
+
+ API reference
+
+
+ | Prop | Type | Default | Description |
+
+ | value | Gradient | 2-stop linear | The committed gradient. Bindable. |
+ | open | boolean | false | Popover open state. Bindable. |
+ | mode | 'inline' | 'modal' | 'inline' | Footer layout (Clear/Cancel vs Cancel/Save). |
+ | size eidos | 'sm' | 'md' | 'lg' | 'md' | Trigger / popover / editor size. |
+ | variant eidos | 'surface' | 'outline' | 'ghost' | 'surface' | Trigger framing. |
+ | onValueCommit | (g: Gradient) => void | — | Fires on Save. |
+
+
+
+
+ {/if}
+
+ {#if tab === 'morfo'}
+
+ morfo · declarative contract
+ Source: src/uix/morfo/components/gradient-picker.ts · expression: 'delegated' (the Popover is the dialog).
+
+ Parts
+
+
+ | Part | Marker | Element | Role | Archetype | Optional |
+
+ {#each partsList as part}
+
+ | {part.kebab} |
+ [{part.marker}] |
+ <{part.defaultElement}> |
+ {part.role ?? '—'} |
+ {part.archetype ?? '—'} |
+ {part.optional ? 'yes' : 'no'} |
+
+ {/each}
+
+
+
+
+ Events
+
+
+ | name | family | verb | sequence | target |
+
+ {#each actions as action}
+ {@const sem = action.semantic}
+
+ | {action.name} |
+ {sem.family} |
+ {sem.verb ?? '—'} |
+ {sem.sequence ?? 'pre'} |
+ {action.target} |
+
+ {/each}
+
+
+
+
+ {/if}
+
+ {#if tab === 'recipe'}
+
+ Eidos recipe
+
+ Selectors at src/uix/eidos/components/gradient-picker/gradient-picker.css.
+ The trigger is a field-shaped pill carrying a gradient chip (--gp-current-gradient,
+ stamped inline by soma); the popover content + editor reuse the Popover + PickerShell +
+ GradientBuilder recipes.
+
+
+
+ | Selector | Source |
+
+ | [data-gradient-picker] | morfo |
+ | [data-gradient-picker][data-size='X'] / [data-variant='X'] | eidos |
+ | [data-gradient-picker-trigger][data-state='open'] | morfo |
+ | .gradient-picker-trigger-swatch | eidos |
+ | [data-gradient-picker-content] | morfo + Popover |
+
+
+
+
+ {/if}
+
+ {#if tab === 'a11y'}
+
+ Accessibility
+
+ The trigger is aria-haspopup="dialog" + aria-expanded; the
+ Popover provides the role="dialog", focus trap and Escape-to-close. Inside,
+ the GradientBuilder's keyboard-accessible stops apply.
+
+
+
+ | Key | Action |
+
+ | Enter / Space | Open the editor (on the trigger) |
+ | Escape | Close the popover |
+ | Tab | Move focus within the editor |
+
+
+
+
+ {/if}
+
+
+