Rebuilds the GradientPicker on the generic Picker.Provider<Gradient> transaction core (open / commit / cancel / clear / workingValue + a composed Popover), like the natural-time-picker — the bespoke coordinator is now a thin domain provider (morfo runtime + working-value CSS + the presets state). - deferValue prop (live vs deferred draft-until-Listo). The trigger chip reflects the COMMITTED value, so a deferred picker's chip stays put while you edit the working draft. - Clear restores the INITIAL bound value the picker was given, not a hardcoded default gradient. - No tabs: the editor IS the popover body; the presets grid opens in a panel from a Layers button next to "Añadir degradado" (the Add <-> Remove toggle) — ColorPicker-style "select a saved preset -> Remove", and it reclaims the tab-header height. - Add / Remove presets fire commit.fulfill / commit.loss firmas (new gradient-picker sema pack, registered in the demo). Per the Button doctrine the press is a neutral contact.activate and the consumer emits the RESULT commit; the events target the REGISTERED `provider` part (targeting the unregistered `content` part was also why the Clear commit-reset was silent). - Stop color editor hides the remove-stop button for the endpoint stops (first / last by position — they anchor 0% / 100%). - maxPresets cap (default 10). StopList in a ScrollArea (autosize, slim scrollbar with a gap so the rows never sit under it). Composes the existing components AS-IS (Popover / Button / ScrollArea / the GradientBuilder parts) without overriding their styles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>menubar-v4-safe
parent
b3d0e29037
commit
f5eb8acb40
@ -0,0 +1,36 @@
|
||||
<script lang="ts">
|
||||
// The Presets tab body — a grid of swatches (provided + saved). Clicking a
|
||||
// provided preset applies it; clicking a saved one applies + selects it (so the
|
||||
// footer Save action flips to Delete). The Save/Delete button itself lives in
|
||||
// the footer (<GradientPicker.SaveAction>), mirroring the ColorPicker.
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import { GradientPickerProvider } from '$soma/components/gradient-picker';
|
||||
import { gradientToCss } from '$libs/gradient';
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const provider = GradientPickerProvider.require();
|
||||
const label = $derived(eidos.langs.ts('#?components.gradient-picker.presets|Gradient presets'));
|
||||
const provided = $derived(provider.presets ?? []);
|
||||
</script>
|
||||
|
||||
<div data-gradient-picker-presets role="group" aria-label={label}>
|
||||
{#each provided as preset, i (i)}
|
||||
<button
|
||||
type="button"
|
||||
data-gradient-picker-preset
|
||||
style="background: {gradientToCss(preset)};"
|
||||
aria-label="{label} {i + 1}"
|
||||
onclick={() => provider.applyPreset(preset, null)}
|
||||
></button>
|
||||
{/each}
|
||||
{#each provider.savedPresets as preset, i (i)}
|
||||
<button
|
||||
type="button"
|
||||
data-gradient-picker-preset
|
||||
data-selected={provider.selectedSaved === i || undefined}
|
||||
style="background: {gradientToCss(preset)};"
|
||||
aria-label="{label} {provided.length + i + 1}"
|
||||
onclick={() => provider.applyPreset(preset, i)}
|
||||
></button>
|
||||
{/each}
|
||||
</div>
|
||||
@ -0,0 +1,35 @@
|
||||
<script lang="ts">
|
||||
// "Añadir degradado" — adds the current gradient to the presets; flips to
|
||||
// "Quitar degradado" (threat) when a saved preset is selected. A framework
|
||||
// <Button>. Composable; the default Content places it below the stops list.
|
||||
import { ActiveEidos } from '$uix/eidos';
|
||||
import { Button } from '$uix/eidos/components/button';
|
||||
import { GradientPickerProvider } from '$soma/components/gradient-picker';
|
||||
import { getGradientPickerVisualContext } from './context.svelte';
|
||||
|
||||
const eidos = ActiveEidos.require();
|
||||
const provider = GradientPickerProvider.require();
|
||||
const visual = getGradientPickerVisualContext();
|
||||
const size = $derived(visual?.getSize() ?? 'md');
|
||||
const addLabel = $derived(eidos.langs.ts('#?components.gradient-picker.add-preset|Add gradient'));
|
||||
const removeLabel = $derived(
|
||||
eidos.langs.ts('#?components.gradient-picker.remove-preset|Remove gradient')
|
||||
);
|
||||
|
||||
const showRemove = $derived(provider.showDelete);
|
||||
function action(): void {
|
||||
if (showRemove) provider.deletePreset();
|
||||
else provider.savePreset();
|
||||
}
|
||||
</script>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
color={showRemove ? 'threat' : 'neutral'}
|
||||
{size}
|
||||
disabled={!showRemove && !provider.canSave}
|
||||
onclick={action}
|
||||
data-gradient-picker-save-action
|
||||
>
|
||||
{showRemove ? removeLabel : addLabel}
|
||||
</Button>
|
||||
@ -0,0 +1,47 @@
|
||||
import { semaSelector } from '$uix/morfo';
|
||||
import { gradientPickerMorfo } from '$uix/morfo/components/gradient-picker';
|
||||
import { sound } from '../sounds';
|
||||
import type { Sema } from '../sema-map';
|
||||
|
||||
/**
|
||||
* GradientPicker perceptual defaults.
|
||||
*
|
||||
* The editing firma (handle / commit while dragging stops, recoloring, …)
|
||||
* lives on the embedded GradientBuilder. The picker owns only its own value
|
||||
* commits — the RESULT commits the Button doctrine leaves to the consumer
|
||||
* (the press itself is a neutral `contact.activate`):
|
||||
*
|
||||
* • commit-save — a gradient saved to the presets: a fulfilling chime.
|
||||
* • commit-remove — a saved preset removed: a soft descending "gone" cue.
|
||||
* • commit-reset — cleared back to the initial value: a neutral reset.
|
||||
*
|
||||
* The `fulfill` / `loss` / `neutral` intents further shape pitch / gain /
|
||||
* contour via the cascade's intent deltas — the base sound just names the body.
|
||||
*/
|
||||
|
||||
const onProvider = (matchers?: Parameters<typeof semaSelector<typeof gradientPickerMorfo>>[2]) =>
|
||||
semaSelector(gradientPickerMorfo, 'provider', matchers);
|
||||
|
||||
export const gradientPickerSema: Sema = {
|
||||
name: 'gradient-picker',
|
||||
cascade: [
|
||||
{
|
||||
selector: onProvider({ eventName: 'commit-save' }),
|
||||
channels: ['sound', 'haptic'],
|
||||
sound: sound('handle.snap.chime', { gain: 0.1 }),
|
||||
haptic: { kind: 'tick', intensity: 0.2, duration: 8 }
|
||||
},
|
||||
{
|
||||
selector: onProvider({ eventName: 'commit-remove' }),
|
||||
channels: ['sound', 'haptic'],
|
||||
sound: sound('handle.release.soft', { gain: 0.1 }),
|
||||
haptic: { kind: 'tick', intensity: 0.18, duration: 8 }
|
||||
},
|
||||
{
|
||||
selector: onProvider({ eventName: 'commit-reset' }),
|
||||
channels: ['sound', 'haptic'],
|
||||
sound: sound('handle.release.soft', { gain: 0.1 }),
|
||||
haptic: { kind: 'tick', intensity: 0.18, duration: 8 }
|
||||
}
|
||||
]
|
||||
};
|
||||
Loading…
Reference in new issue