;;; etaf-counter-example.el --- Retained state example -*- lexical-binding: t; -*- ;; SPDX-License-Identifier: GPL-3.0-or-later ;;; Commentary: ;; This example keeps state inside one retained Component, derives display ;; values with `etaf-computed', and routes named mutations through an Action. ;;; Code: (require 'etaf) (defconst etaf-counter-example-buffer-name "*ETAF Counter Example*" "Buffer opened by `etaf-counter-example-open'.") (etaf-action-define etaf-counter-example-update (runtime count operation) "Apply OPERATION to COUNT inside RUNTIME." (ignore runtime) (etaf-set-value count (pcase operation ('increment (1+ (etaf-value count))) ('decrement (max 0 (1- (etaf-value count)))) ('reset 0) (_ (user-error "Unknown counter operation: %S" operation))))) (etaf-define-component etaf-counter-example-action (&key count label operation) "Render one semantic counter action." :view (box :class "action" :role 'button :use (list (etaf-focusable)) :on-press (let ((cell count) (next-operation operation)) (lambda () (etaf-dispatch 'etaf-counter-example-update cell next-operation))) (text (expr label))) :styles (styles ("&" :flex-grow 1 :flex-shrink 1 :flex-basis (160) :min-width (140) :padding (1 (12)) :border "#4E7890" :bgcolor "#D9EAF2" :text-align center :font-weight bold))) (etaf-define-component etaf-counter-example-card (&key title initial-value) "Render a retained counter named TITLE starting at INITIAL-VALUE." :setup (let* ((count (etaf-ref (or initial-value 0) :name 'counter)) (double (etaf-computed (lambda () (* 2 (etaf-value count))) :name 'counter-double)) (status (etaf-computed (lambda () (if (zerop (etaf-value count)) "READY" "ACTIVE")) :name 'counter-status))) (list :count count :double double :status status)) :view (column (box :class "hero" (column (text :class "eyebrow" "BEST PRACTICE / RETAINED STATE") (text :font-weight 'bold (expr title)) (text :color "#66706A" "State belongs to setup; rendering only reads it."))) (box :height 1) (flex :width '(680) :flex-flow '(row wrap) :gap '(1 (12)) (box :class "metric" (text (expr (format "COUNT %d" (etaf-value (plist-get (etaf-state) :count)))))) (box :class "metric" (text (expr (format "DOUBLE %d" (etaf-value (plist-get (etaf-state) :double)))))) (box :class "metric" (text (expr (format "STATE %s" (etaf-value (plist-get (etaf-state) :status))))))) (box :height 1) (flex :width '(680) :flex-flow '(row wrap) :gap '(1 (12)) (etaf-counter-example-action :ref 'counter-decrement :count (plist-get (etaf-state) :count) :label "− DECREMENT" :operation 'decrement) (etaf-counter-example-action :ref 'counter-reset :count (plist-get (etaf-state) :count) :label "RESET" :operation 'reset) (etaf-counter-example-action :ref 'counter-increment :count (plist-get (etaf-state) :count) :label "+ INCREMENT" :operation 'increment)) (box :height 1) (box :class "note" (text "Public path: Event → Action → Ref → Computed → Runtime commit"))) :styles (styles ("&" :width (680) :color "#252A2E" :bgcolor "#F8F5EE") (".hero" :width (680) :padding (1 (18)) :border "#8F432F" :bgcolor "#FFFDF8" :text-align center) (".eyebrow" :color "#8F432F" :font-weight bold) (".metric" :flex-grow 1 :flex-shrink 1 :flex-basis (200) :min-width (180) :padding (1 (14)) :border "#6D8A73" :bgcolor "#DCEBDD" :text-align center) (".note" :width (680) :padding (1 (16)) :border "#8D887F" :color "#4D5651" :bgcolor "#EEEAE2"))) ;;;###autoload (defun etaf-counter-example-view () "Return the counter example View." (etaf-view (etaf-counter-example-card :title "Counter workspace" :initial-value 2))) ;;;###autoload (defun etaf-counter-example-open () "Mount the counter example and return its buffer." (interactive) (let ((buffer (etaf-mount etaf-counter-example-buffer-name (etaf-counter-example-view)))) (when (called-interactively-p 'interactive) (pop-to-buffer buffer)) buffer)) ;;;###autoload (defun etaf-counter-example-close () "Unmount and kill the counter example buffer." (interactive) (when-let* ((runtime (etaf-runtime-for-buffer etaf-counter-example-buffer-name))) (etaf-unmount runtime)) (when-let* ((buffer (get-buffer etaf-counter-example-buffer-name))) (kill-buffer buffer))) (provide 'etaf-counter-example) ;;; etaf-counter-example.el ends here