Spec event bridge and named stores, move plan to reactive islands section
- signals.sx: add def-store/use-store/clear-stores (L3 named stores) and emit-event/on-event/bridge-event (lake→island DOM events) - reactive-islands.sx: add event bridge, named stores, and plan pages - Remove ~plan-reactive-islands-content from plans.sx - Update nav-data.sx and docs.sx routing accordingly Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -288,3 +288,87 @@
|
||||
(fn (disposable)
|
||||
(when *island-scope*
|
||||
(*island-scope* disposable))))
|
||||
|
||||
|
||||
;; ==========================================================================
|
||||
;; 12. Named stores — page-level signal containers (L3)
|
||||
;; ==========================================================================
|
||||
;;
|
||||
;; Stores persist across island creation/destruction. They live at page
|
||||
;; scope, not island scope. When an island is swapped out and re-created,
|
||||
;; it reconnects to the same store instance.
|
||||
;;
|
||||
;; The store registry is global page-level state. It survives island
|
||||
;; disposal but is cleared on full page navigation.
|
||||
|
||||
(define *store-registry* (dict))
|
||||
|
||||
(define def-store
|
||||
(fn (name init-fn)
|
||||
(let ((registry *store-registry*))
|
||||
;; Only create the store once — subsequent calls return existing
|
||||
(when (not (has? registry name))
|
||||
(set! *store-registry* (assoc registry name (init-fn))))
|
||||
(get *store-registry* name))))
|
||||
|
||||
(define use-store
|
||||
(fn (name)
|
||||
(if (has? *store-registry* name)
|
||||
(get *store-registry* name)
|
||||
(error (str "Store not found: " name
|
||||
". Call (def-store ...) before (use-store ...).")))))
|
||||
|
||||
(define clear-stores
|
||||
(fn ()
|
||||
(set! *store-registry* (dict))))
|
||||
|
||||
|
||||
;; ==========================================================================
|
||||
;; 13. Event bridge — DOM event communication for lake→island
|
||||
;; ==========================================================================
|
||||
;;
|
||||
;; Server-rendered content ("htmx lakes") inside reactive islands can
|
||||
;; communicate with island signals via DOM custom events. The bridge
|
||||
;; pattern:
|
||||
;;
|
||||
;; 1. Server renders a button/link with data-sx-emit="event-name"
|
||||
;; 2. When clicked, the client dispatches a CustomEvent on the element
|
||||
;; 3. The event bubbles up to the island container
|
||||
;; 4. An island effect listens for the event and updates signals
|
||||
;;
|
||||
;; This keeps server content pure HTML — no signal references needed.
|
||||
;; The island effect is the only reactive code.
|
||||
;;
|
||||
;; Platform interface required:
|
||||
;; (dom-listen el event-name handler) → remove-fn
|
||||
;; (dom-dispatch el event-name detail) → void
|
||||
;; (event-detail e) → any
|
||||
;;
|
||||
;; These are platform primitives because they require browser DOM APIs.
|
||||
|
||||
(define emit-event
|
||||
(fn (el event-name detail)
|
||||
(dom-dispatch el event-name detail)))
|
||||
|
||||
(define on-event
|
||||
(fn (el event-name handler)
|
||||
(dom-listen el event-name handler)))
|
||||
|
||||
;; Convenience: create an effect that listens for a DOM event on an
|
||||
;; element and writes the event detail (or a transformed value) into
|
||||
;; a target signal. Returns the effect's dispose function.
|
||||
;; When the effect is disposed (island teardown), the listener is
|
||||
;; removed automatically via the cleanup return.
|
||||
|
||||
(define bridge-event
|
||||
(fn (el event-name target-signal transform-fn)
|
||||
(effect (fn ()
|
||||
(let ((remove (dom-listen el event-name
|
||||
(fn (e)
|
||||
(let ((detail (event-detail e))
|
||||
(new-val (if transform-fn
|
||||
(transform-fn detail)
|
||||
detail)))
|
||||
(reset! target-signal new-val))))))
|
||||
;; Return cleanup — removes listener on dispose/re-run
|
||||
remove)))))
|
||||
|
||||
Reference in New Issue
Block a user