;; ========================================================================== ;; 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 ;; -------------------------------------------------------------------------- ;; def-store, use-store, clear-stores are now OCaml primitives ;; (sx_primitives.ml) with a global mutable registry that survives ;; env scoping across bytecode modules and island hydration. ;; -------------------------------------------------------------------------- ;; 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)))