;; ========================================================================== ;; web/signals.sx — Web platform signal extensions ;; ;; Extends the core reactive signal spec (spec/signals.sx) with web-specific ;; features: marsh scopes (DOM lifecycle), named stores (page-level state), ;; event bridge (lake→island communication), and async resources. ;; ;; These depend on platform primitives: ;; dom-set-data, dom-get-data, dom-listen, dom-dispatch, event-detail, ;; promise-then ;; ========================================================================== ;; -------------------------------------------------------------------------- ;; Marsh scopes — child scopes within islands ;; -------------------------------------------------------------------------- (define with-marsh-scope :effects [mutation io] (fn (marsh-el (body-fn :as lambda)) (let ((disposers (list))) (with-island-scope (fn (d) (append! disposers d)) body-fn) (dom-set-data marsh-el "sx-marsh-disposers" disposers)))) (define dispose-marsh-scope :effects [mutation io] (fn (marsh-el) (let ((disposers (dom-get-data marsh-el "sx-marsh-disposers"))) (when disposers (for-each (fn ((d :as lambda)) (cek-call d nil)) disposers) (dom-set-data marsh-el "sx-marsh-disposers" nil))))) ;; -------------------------------------------------------------------------- ;; Named stores — page-level signal containers ;; -------------------------------------------------------------------------- (define *store-registry* (dict)) (define def-store :effects [mutation] (fn ((name :as string) (init-fn :as lambda)) (let ((registry *store-registry*)) (when (not (has-key? registry name)) (set! *store-registry* (assoc registry name (cek-call init-fn nil)))) (get *store-registry* name)))) (define use-store :effects [] (fn ((name :as string)) (if (has-key? *store-registry* name) (get *store-registry* name) (error (str "Store not found: " name ". Call (def-store ...) before (use-store ...)."))))) (define clear-stores :effects [mutation] (fn () (set! *store-registry* (dict)))) ;; -------------------------------------------------------------------------- ;; Event bridge — DOM event communication for lake→island ;; -------------------------------------------------------------------------- (define emit-event :effects [io] (fn (el (event-name :as string) detail) (dom-dispatch el event-name detail))) (define on-event :effects [io] (fn (el (event-name :as string) (handler :as lambda)) (dom-on el event-name handler))) (define bridge-event :effects [mutation io] (fn (el (event-name :as string) (target-signal :as signal) transform-fn) (effect (fn () (let ((remove (dom-on el event-name (fn (e) (let ((detail (event-detail e)) (new-val (if transform-fn (cek-call transform-fn (list detail)) detail))) (reset! target-signal new-val)))))) remove))))) ;; -------------------------------------------------------------------------- ;; Resource — async signal with loading/resolved/error states ;; -------------------------------------------------------------------------- (define resource :effects [mutation io] (fn ((fetch-fn :as lambda)) (let ((state (signal (dict "loading" true "data" nil "error" nil)))) (promise-then (cek-call fetch-fn nil) (fn (data) (reset! state (dict "loading" false "data" data "error" nil))) (fn (err) (reset! state (dict "loading" false "data" nil "error" err)))) state)))