feat(pickers): wire runtime.trigger('close') in 5 picker providers

After the 2026-05-27 picker family polymorphic refactor, the morfos
declared the polymorphic `close` event but the providers were inert —
they just toggled `opts.open = false` without firing the event. This
commit cables them, mirroring the Dialog/Drawer/Popover dismissWith
pattern (book §5.3).

**Affected providers** (5): color-picker, date-picker, date-range-picker,
time-picker, time-range-picker.

**Pattern** (consistent across all 5):
- Private `triggerClose(cause)` helper maps:
    'committed' / 'range-committed' → commit.save + fulfill
    'cancelled'                     → emerge.close
    'dismissed'                     → emerge.dismiss
    'dismissed-outside'             → emerge.dismiss
- Writes `data-last-action` on the morfo's target part (calendar /
  clock / content) imperatively before the trigger fires so eidos can
  tint the exit animation per cause.
- Called from commit() / cancel() / handleSelect / handleDateSelect /
  handleRangeSelect — the close paths the provider already had.

The morfo target varies per picker:
- color-picker        → content
- date-picker         → calendar
- date-range-picker   → calendar
- time-picker         → clock
- time-range-picker   → clock

Internal state (`opts.open = false`) sequence preserved — the trigger
fires BEFORE the open flip so the perceptual signal stamps
`data-event-*` on a node that is still rendered.

**dismissed / dismissed-outside causes** are declared but not yet wired
to specific gestures — the picker shell delegates escape / outside-click
handling to its wrapping Popover, whose own polymorphic close fires
independently. The cause strings remain in the helper for future use
(e.g. if a picker grows its own escape handling).

**Result**:
- Pickers now emit perceptual signal on commit/cancel via sema engine.
- Eidos `[data-last-action='X']` selectors get exit-tint values to act
  on (mirror of Dialog/Drawer/Popover).
- Picker tests: 18/18 still pass. `morfo:check`: 0 issues.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
active-uix
dev 5 months ago
parent 312c8740ca
commit 28bf7f8b49

@ -300,6 +300,7 @@ export class ColorPickerProvider {
/** Close the popover when `closeOnSelect` is enabled. */
handleSelect(): void {
if (this.opts.closeOnSelect.current && !this.opts.inline.current) {
this.triggerClose('committed');
this.opts.open.current = false;
}
}
@ -317,6 +318,7 @@ export class ColorPickerProvider {
* the canonical picker footer.
*/
commit(): void {
this.triggerClose('committed');
this.opts.open.current = false;
this.commitChange();
}
@ -327,6 +329,7 @@ export class ColorPickerProvider {
*/
cancel(): void {
this.opts.value.current = this.valueOnOpen;
this.triggerClose('cancelled');
this.opts.open.current = false;
}
@ -338,6 +341,30 @@ export class ColorPickerProvider {
this.opts.value.current = undefined;
}
/**
* Polymorphic close (book §5.3) — fire the morfo `close` event with
* the per-cause semantic + write `data-last-action` on Content so
* eidos can tint the exit animation. Mirrors the date-picker pattern.
*/
private triggerClose(
cause: 'committed' | 'cancelled' | 'dismissed' | 'dismissed-outside'
): void {
const CAUSES = {
committed: { semantic: { family: 'commit' as const, verb: 'save', intent: 'fulfill' as const } },
cancelled: { semantic: { family: 'emerge' as const, verb: 'close' } },
dismissed: { semantic: { family: 'emerge' as const, verb: 'dismiss' } },
'dismissed-outside': { semantic: { family: 'emerge' as const, verb: 'dismiss' } }
};
const target = this.runtime.partRef('content') ?? undefined;
if (target) {
this.soma.dom.apply({ target, attrs: { 'data-last-action': cause } });
}
void this.runtime.trigger('close', {
...(target ? { fallbackTarget: target } : {}),
semantic: CAUSES[cause].semantic
});
}
readonly pickerShellHandle: PickerShellHandle = {
getMode: () => this.opts.mode.current,
commit: () => this.commit(),

@ -208,6 +208,7 @@ export class DatePickerProvider {
handleDateSelect(): void {
if (this.opts.mode.current === 'modal') return;
if (this.opts.closeOnDateSelect.current) {
this.triggerClose('committed');
this.opts.open.current = false;
}
}
@ -219,6 +220,7 @@ export class DatePickerProvider {
* for a "Save" or "Done" button in the footer, especially in modal mode.
*/
commit(): void {
this.triggerClose('committed');
this.opts.open.current = false;
}
@ -228,6 +230,7 @@ export class DatePickerProvider {
*/
cancel(): void {
this.opts.value.current = this.valueOnOpen;
this.triggerClose('cancelled');
this.opts.open.current = false;
}
@ -239,6 +242,36 @@ export class DatePickerProvider {
this.opts.value.current = undefined;
}
/**
* Fire the polymorphic `close` event on the morfo runtime with the
* right semantic concretion per cause + write `data-last-action` on
* Calendar so eidos can tint the exit animation. Mirrors the
* Dialog/Drawer/Popover dismissWith pattern (book §5.3).
*
* 'committed' → commit.save + fulfill, data-last-action='committed'
* 'cancelled' → emerge.close, data-last-action='cancelled'
* 'dismissed' → emerge.dismiss, data-last-action='dismissed'
* 'dismissed-outside'→ emerge.dismiss, data-last-action='dismissed-outside'
*/
private triggerClose(
cause: 'committed' | 'cancelled' | 'dismissed' | 'dismissed-outside'
): void {
const CAUSES = {
committed: { semantic: { family: 'commit' as const, verb: 'save', intent: 'fulfill' as const } },
cancelled: { semantic: { family: 'emerge' as const, verb: 'close' } },
dismissed: { semantic: { family: 'emerge' as const, verb: 'dismiss' } },
'dismissed-outside': { semantic: { family: 'emerge' as const, verb: 'dismiss' } }
};
const target = this.runtime.partRef('calendar') ?? undefined;
if (target) {
this.soma.dom.apply({ target, attrs: { 'data-last-action': cause } });
}
void this.runtime.trigger('close', {
...(target ? { fallbackTarget: target } : {}),
semantic: CAUSES[cause].semantic
});
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,

@ -212,6 +212,7 @@ export class DateRangePickerProvider {
handleRangeSelect(): void {
if (this.opts.mode.current === 'modal') return;
if (this.opts.closeOnRangeSelect.current) {
this.triggerClose('range-committed');
this.opts.open.current = false;
}
}
@ -223,6 +224,7 @@ export class DateRangePickerProvider {
* for a "Save" or "Done" button in the footer, especially in modal mode.
*/
commit(): void {
this.triggerClose('range-committed');
this.opts.open.current = false;
}
@ -235,6 +237,7 @@ export class DateRangePickerProvider {
if (snapshot) {
this.opts.value.current = { ...snapshot };
}
this.triggerClose('cancelled');
this.opts.open.current = false;
}
@ -246,6 +249,31 @@ export class DateRangePickerProvider {
this.opts.value.current = { start: undefined, end: undefined };
}
/**
* Polymorphic close (book §5.3) — fire the morfo `close` event with
* the per-cause semantic + write `data-last-action` on Calendar so
* eidos can tint the exit animation. Mirrors the date-picker /
* Dialog / Drawer / Popover pattern.
*/
private triggerClose(
cause: 'range-committed' | 'cancelled' | 'dismissed' | 'dismissed-outside'
): void {
const CAUSES = {
'range-committed': { semantic: { family: 'commit' as const, verb: 'save', intent: 'fulfill' as const } },
cancelled: { semantic: { family: 'emerge' as const, verb: 'close' } },
dismissed: { semantic: { family: 'emerge' as const, verb: 'dismiss' } },
'dismissed-outside': { semantic: { family: 'emerge' as const, verb: 'dismiss' } }
};
const target = this.runtime.partRef('calendar') ?? undefined;
if (target) {
this.soma.dom.apply({ target, attrs: { 'data-last-action': cause } });
}
void this.runtime.trigger('close', {
...(target ? { fallbackTarget: target } : {}),
semantic: CAUSES[cause].semantic
});
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,

@ -204,12 +204,14 @@ export class TimePickerProvider {
/** Commit current value and close. Default for Save/Done in the footer. */
commit(): void {
this.triggerClose('committed');
this.opts.open.current = false;
}
/** Discard mid-session edits (revert to OPEN-edge snapshot) and close. */
cancel(): void {
this.opts.value.current = this.valueOnOpen;
this.triggerClose('cancelled');
this.opts.open.current = false;
}
@ -218,6 +220,30 @@ export class TimePickerProvider {
this.opts.value.current = undefined;
}
/**
* Polymorphic close (book §5.3) — fire the morfo `close` event with
* the per-cause semantic + write `data-last-action` on Clock so eidos
* can tint the exit animation. Mirrors the date-picker pattern.
*/
private triggerClose(
cause: 'committed' | 'cancelled' | 'dismissed' | 'dismissed-outside'
): void {
const CAUSES = {
committed: { semantic: { family: 'commit' as const, verb: 'save', intent: 'fulfill' as const } },
cancelled: { semantic: { family: 'emerge' as const, verb: 'close' } },
dismissed: { semantic: { family: 'emerge' as const, verb: 'dismiss' } },
'dismissed-outside': { semantic: { family: 'emerge' as const, verb: 'dismiss' } }
};
const target = this.runtime.partRef('clock') ?? undefined;
if (target) {
this.soma.dom.apply({ target, attrs: { 'data-last-action': cause } });
}
void this.runtime.trigger('close', {
...(target ? { fallbackTarget: target } : {}),
semantic: CAUSES[cause].semantic
});
}
readonly props = $derived.by(() =>
this.runtimePart.assert({
...this.runtimePart.props,

@ -233,6 +233,7 @@ export class TimeRangePickerProvider {
const s = this.opts.startValue.current;
const e = this.opts.endValue.current;
if (s !== undefined && e !== undefined) {
this.triggerClose('committed');
this.opts.open.current = false;
}
}
@ -241,6 +242,7 @@ export class TimeRangePickerProvider {
/** Save current range and close the popover. */
commit(): void {
this.triggerClose('committed');
this.opts.open.current = false;
}
@ -251,6 +253,7 @@ export class TimeRangePickerProvider {
this.opts.startValue.current = snapshot.start;
this.opts.endValue.current = snapshot.end;
}
this.triggerClose('cancelled');
this.opts.open.current = false;
}
@ -260,6 +263,30 @@ export class TimeRangePickerProvider {
this.opts.endValue.current = undefined;
}
/**
* Polymorphic close (book §5.3) — fire the morfo `close` event with
* the per-cause semantic + write `data-last-action` on Clock so eidos
* can tint the exit animation. Mirrors the date-picker pattern.
*/
private triggerClose(
cause: 'committed' | 'cancelled' | 'dismissed' | 'dismissed-outside'
): void {
const CAUSES = {
committed: { semantic: { family: 'commit' as const, verb: 'save', intent: 'fulfill' as const } },
cancelled: { semantic: { family: 'emerge' as const, verb: 'close' } },
dismissed: { semantic: { family: 'emerge' as const, verb: 'dismiss' } },
'dismissed-outside': { semantic: { family: 'emerge' as const, verb: 'dismiss' } }
};
const target = this.runtime.partRef('clock') ?? undefined;
if (target) {
this.soma.dom.apply({ target, attrs: { 'data-last-action': cause } });
}
void this.runtime.trigger('close', {
...(target ? { fallbackTarget: target } : {}),
semantic: CAUSES[cause].semantic
});
}
// ── Root props (emitted onto `[data-time-range-picker]`) ───────────────
readonly props = $derived.by(() =>

Loading…
Cancel
Save

Powered by TurnKey Linux.