/// import { type Cell, cell, Default, handler, lift, recipe, str, type Stream, } from "commontools"; interface ParentChildBubbleArgs { parent: Default; child: Default; } type BubbleEvent = { amount?: unknown; via?: unknown; }; type BubbleRecord = { amount: number; via: string; }; const asIncrementStream = ( ref: unknown, ): Stream<{ amount?: number }> => ref as Stream<{ amount?: number }>; const sanitizeAmount = (value: unknown): number => { return typeof value === "number" && Number.isFinite(value) ? value : 1; }; const sanitizeVia = (value: unknown): string => { return typeof value === "string" && value.length > 0 ? value : "parent"; }; const childIncrement = handler( ( event: { amount?: number } | undefined, context: { value: Cell }, ) => { const amount = sanitizeAmount(event?.amount); const current = context.value.get() ?? 0; context.value.set(current + amount); }, ); const childCounter = recipe<{ value: Default }>( "Bubbled Child Counter", ({ value }) => { const safeValue = lift((count: number | undefined) => typeof count === "number" && Number.isFinite(count) ? count : 0 )(value); return { value, label: str`Child count ${safeValue}`, increment: childIncrement({ value }), }; }, ); const bubbleToChild = handler( ( event: BubbleEvent | undefined, context: { childIncrement: Stream<{ amount?: number }>; parent: Cell; history: Cell; forwardedCount: Cell; }, ) => { const amount = sanitizeAmount(event?.amount); const via = sanitizeVia(event?.via); const parentCurrent = context.parent.get() ?? 0; context.parent.set(parentCurrent + amount); const existingHistory = context.history.get(); const history = Array.isArray(existingHistory) ? existingHistory.slice() : []; history.push({ amount, via }); context.history.set(history); const forwarded = context.forwardedCount.get() ?? 0; context.forwardedCount.set(forwarded + 1); context.childIncrement.send({ amount }); }, ); const parentIncrement = handler( ( event: { amount?: number } | undefined, context: { parent: Cell }, ) => { const amount = sanitizeAmount(event?.amount); const parentCurrent = context.parent.get() ?? 0; context.parent.set(parentCurrent + amount); }, ); /** Pattern simulating parent handler bubbling events into a child stream. */ export const counterWithParentChildBubbling = recipe( "Counter With Parent-Child Event Bubbling", ({ parent, child }) => { const parentView = lift((count: number | undefined) => typeof count === "number" && Number.isFinite(count) ? count : 0 )(parent); const forwardedCount = cell(0); const history = cell([]); const forwardedView = lift((count: number | undefined) => typeof count === "number" && Number.isFinite(count) ? count : 0 )(forwardedCount); const historyView = lift((records: BubbleRecord[] | undefined) => Array.isArray(records) ? records : [] )(history); const childState = childCounter({ value: child }); return { parentValue: parentView, child: childState, forwardedCount: forwardedView, bubbleHistory: historyView, bubbleToChild: bubbleToChild({ childIncrement: asIncrementStream(childState.key("increment")), parent, history, forwardedCount, }), parentIncrement: parentIncrement({ parent }), }; }, );