♻️ Refactor (again) numeric input component

This commit is contained in:
Andrey Antukh 2023-07-06 15:27:14 +02:00
parent e96d129ee8
commit ab0245279f
11 changed files with 182 additions and 198 deletions

View file

@ -6,6 +6,7 @@
(ns app.main.ui.components.color-input (ns app.main.ui.components.color-input
(:require (:require
[app.common.data :as d]
[app.util.color :as uc] [app.util.color :as uc]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.globals :as globals] [app.util.globals :as globals]
@ -13,8 +14,7 @@
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.object :as obj] [app.util.object :as obj]
[goog.events :as events] [goog.events :as events]
[rumext.v2 :as mf]) [rumext.v2 :as mf]))
(:import goog.events.EventType))
(defn clean-color (defn clean-color
[value] [value]
@ -31,7 +31,7 @@
on-change (obj/get props "onChange") on-change (obj/get props "onChange")
on-blur (obj/get props "onBlur") on-blur (obj/get props "onBlur")
on-focus (obj/get props "onFocus") on-focus (obj/get props "onFocus")
select-on-focus? (obj/get props "data-select-on-focus" true) select-on-focus? (d/nilv (unchecked-get props "selectOnFocus") true)
;; We need a ref pointing to the input dom element, but the user ;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here). ;; of this component may provide one (that is forwarded here).
@ -128,8 +128,10 @@
;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect ;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect
(.addEventListener target "mouseup" on-mouse-up #js {"once" true}))))) (.addEventListener target "mouseup" on-mouse-up #js {"once" true})))))
props (-> props props (-> (obj/clone props)
(obj/without ["value" "onChange" "onFocus"]) (obj/unset! "selectOnFocus")
(obj/set! "value" mf/undefined)
(obj/set! "onChange" mf/undefined)
(obj/set! "type" "text") (obj/set! "type" "text")
(obj/set! "ref" ref) (obj/set! "ref" ref)
;; (obj/set! "list" list-id) ;; (obj/set! "list" list-id)
@ -157,8 +159,8 @@
(mf/use-layout-effect (mf/use-layout-effect
(fn [] (fn []
(let [keys [(events/listen globals/window EventType.POINTERDOWN on-click) (let [keys [(events/listen globals/window "pointerdown" on-click)
(events/listen globals/window EventType.CLICK on-click)]] (events/listen globals/window "click" on-click)]]
#(doseq [key keys] #(doseq [key keys]
(events/unlistenByKey key))))) (events/unlistenByKey key)))))

View file

@ -7,153 +7,135 @@
(ns app.main.ui.components.numeric-input (ns app.main.ui.components.numeric-input
(:require (:require
[app.common.data :as d] [app.common.data :as d]
[app.common.spec :as us] [app.common.schema :as sm]
[app.main.ui.formats :as fmt] [app.main.ui.formats :as fmt]
[app.main.ui.hooks :as h]
[app.util.dom :as dom] [app.util.dom :as dom]
[app.util.globals :as globals] [app.util.globals :as globals]
[app.util.keyboard :as kbd] [app.util.keyboard :as kbd]
[app.util.object :as obj] [app.util.object :as obj]
[app.util.simple-math :as sm] [app.util.simple-math :as smt]
[cljs.core :as c]
[cuerdas.core :as str] [cuerdas.core :as str]
[goog.events :as events] [goog.events :as events]
[rumext.v2 :as mf]) [rumext.v2 :as mf]))
(:import goog.events.EventType))
(mf/defc numeric-input (mf/defc numeric-input
{::mf/wrap-props false {::mf/wrap-props false
::mf/forward-ref true} ::mf/forward-ref true}
[props external-ref] [props external-ref]
(let [value-str (obj/get props "value") (let [value-str (unchecked-get props "value")
min-val-str (obj/get props "min") min-value (unchecked-get props "min")
max-val-str (obj/get props "max") max-value (unchecked-get props "max")
step-val-str (obj/get props "step") step-value (unchecked-get props "step")
wrap-value? (obj/get props "data-wrap") wrap-value? (unchecked-get props "data-wrap")
on-change (obj/get props "onChange") on-change (unchecked-get props "onChange")
on-blur (obj/get props "onBlur") on-blur (unchecked-get props "onBlur")
on-focus (obj/get props "onFocus") on-focus (unchecked-get props "onFocus")
title (obj/get props "title")
default-val (obj/get props "default") title (unchecked-get props "title")
nillable (obj/get props "nillable") default (unchecked-get props "default")
select-on-focus? (obj/get props "data-select-on-focus" true) nillable? (unchecked-get props "nillable")
class (obj/get props "klass") class (d/nilv (unchecked-get props "className") "input-text")
min-value (d/parse-double min-value)
max-value (d/parse-double max-value)
step-value (d/parse-double step-value 1)
default (d/parse-double default 0)
select-on-focus? (d/nilv (unchecked-get props "selectOnFocus") true)
;; We need a ref pointing to the input dom element, but the user ;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here). ;; of this component may provide one (that is forwarded here).
;; So we use the external ref if provided, and the local one if not. ;; So we use the external ref if provided, and the local one if not.
local-ref (mf/use-ref) local-ref (mf/use-ref)
ref (or external-ref local-ref) ref (or external-ref local-ref)
;; We need to store the handle-blur ref so we can call it on unmount
handle-blur-ref (mf/use-ref nil)
dirty-ref (mf/use-ref false)
;; This `value` represents the previous value and is used as ;; This `value` represents the previous value and is used as
;; initil value for the simple math expression evaluation. ;; initil value for the simple math expression evaluation.
value (d/parse-double value-str default-val) value (d/parse-double value-str default)
min-val (cond ;; We need to store the handle-blur ref so we can call it on unmount
(number? min-val-str) dirty-ref (mf/use-ref false)
min-val-str
(string? min-val-str)
(d/parse-double min-val-str))
max-val (cond
(number? max-val-str)
max-val-str
(string? max-val-str)
(d/parse-double max-val-str))
step-val (cond
(number? step-val-str)
step-val-str
(string? step-val-str)
(d/parse-double step-val-str)
:else 1)
parse-value parse-value
(mf/use-callback (mf/use-fn
(mf/deps ref min-val max-val value nillable default-val) (mf/deps min-value max-value value nillable? default)
(fn [] (fn []
(let [input-node (mf/ref-val ref) (when-let [node (mf/ref-val ref)]
new-value (-> (dom/get-value input-node) (let [new-value (-> (dom/get-value node)
(str/strip-suffix ".") (str/strip-suffix ".")
(sm/expr-eval value))] (smt/expr-eval value))]
(cond (cond
(d/num? new-value) (d/num? new-value)
(-> new-value (-> new-value
(cljs.core/max (/ us/min-safe-int 2)) (d/max (/ sm/min-safe-int 2))
(cljs.core/min (/ us/max-safe-int 2)) (d/min (/ sm/max-safe-int 2))
(cond-> (cond-> (d/num? min-value)
(d/num? min-val) (d/max min-value))
(cljs.core/max min-val) (cond-> (d/num? max-value)
(d/min max-value)))
(d/num? max-val) nillable?
(cljs.core/min max-val))) default
nillable :else value)))))
default-val
:else value))))
update-input update-input
(mf/use-callback (mf/use-fn
(mf/deps ref)
(fn [new-value] (fn [new-value]
(let [input-node (mf/ref-val ref)] (when-let [node (mf/ref-val ref)]
(dom/set-value! input-node (fmt/format-number new-value))))) (dom/set-value! node (fmt/format-number new-value)))))
apply-value apply-value
(mf/use-callback (mf/use-fn
(mf/deps on-change update-input value) (mf/deps on-change update-input value)
(fn [new-value event] (fn [event new-value]
(mf/set-ref-val! dirty-ref false) (mf/set-ref-val! dirty-ref false)
(when (and (not= new-value value) (when (and (not= new-value value)
(fn? on-change)) (fn? on-change))
;; FIXME: on-change very slow, makes the handler laggy
(on-change new-value event)) (on-change new-value event))
(update-input new-value))) (update-input new-value)))
set-delta set-delta
(mf/use-callback (mf/use-fn
(mf/deps wrap-value? min-val max-val parse-value apply-value) (mf/deps wrap-value? min-value max-value parse-value apply-value)
(fn [event up? down?] (fn [event up? down?]
(let [current-value (parse-value)] (let [current-value (parse-value)]
(when current-value (when current-value
(let [increment (cond (let [increment (cond
(kbd/shift? event) (kbd/shift? event)
(if up? (* step-val 10) (* step-val -10)) (if up? (* step-value 10) (* step-value -10))
(kbd/alt? event) (kbd/alt? event)
(if up? (* step-val 0.1) (* step-val -0.1)) (if up? (* step-value 0.1) (* step-value -0.1))
:else :else
(if up? step-val (- step-val))) (if up? step-value (- step-value)))
new-value (+ current-value increment) new-value (+ current-value increment)
new-value (cond new-value (cond
(and wrap-value? (d/num? max-val min-val) (and wrap-value? (d/num? max-value min-value)
(> new-value max-val) up?) (> new-value max-value) up?)
(-> new-value (- max-val) (+ min-val) (- step-val)) (-> new-value (- max-value) (+ min-value) (- step-value))
(and wrap-value? (d/num? max-val min-val) (and wrap-value? (d/num? max-value min-value)
(< new-value min-val) down?) (< new-value min-value) down?)
(-> new-value (- min-val) (+ max-val) (+ step-val)) (-> new-value (- min-value) (+ max-value) (+ step-value))
(and (d/num? min-val) (< new-value min-val)) (and (d/num? min-value) (< new-value min-value))
min-val min-value
(and (d/num? max-val) (> new-value max-val)) (and (d/num? max-value) (> new-value max-value))
max-val max-value
:else new-value)] :else new-value)]
(apply-value new-value event)))))) (apply-value event new-value))))))
handle-key-down handle-key-down
(mf/use-callback (mf/use-fn
(mf/deps set-delta apply-value update-input) (mf/deps set-delta apply-value update-input)
(fn [event] (fn [event]
(mf/set-ref-val! dirty-ref true) (mf/set-ref-val! dirty-ref true)
@ -161,44 +143,47 @@
down? (kbd/down-arrow? event) down? (kbd/down-arrow? event)
enter? (kbd/enter? event) enter? (kbd/enter? event)
esc? (kbd/esc? event) esc? (kbd/esc? event)
input-node (mf/ref-val ref)] node (mf/ref-val ref)]
(when (or up? down?) (when (or up? down?)
(set-delta event up? down?)) (set-delta event up? down?))
(when enter? (when enter?
(dom/blur! input-node)) (dom/blur! node))
(when esc? (when esc?
(update-input value-str) (update-input value-str)
(dom/blur! input-node))))) (dom/blur! node)))))
handle-mouse-wheel handle-mouse-wheel
(mf/use-callback (mf/use-fn
(mf/deps set-delta) (mf/deps set-delta)
(fn [event] (fn [event]
(let [input-node (mf/ref-val ref)] (when-let [node (mf/ref-val ref)]
(when (dom/active? input-node) (when (dom/active? node)
(let [event (.getBrowserEvent ^js event)] (dom/prevent-default event)
(dom/prevent-default event) (dom/stop-propagation event)
(dom/stop-propagation event) (let [{:keys [y]} (dom/get-delta-position event)]
(set-delta event (< (.-deltaY event) 0) (> (.-deltaY event) 0))))))) (set-delta event (< y 0) (> y 0)))))))
handle-blur handle-blur
(mf/use-callback (mf/use-fn
(mf/deps parse-value apply-value update-input on-blur) (mf/deps parse-value apply-value update-input on-blur)
(fn [event] (fn [event]
(let [new-value (or (parse-value) default-val)] (let [new-value (or (parse-value) default)]
(if (or nillable new-value) (if (or nillable? new-value)
(apply-value new-value event) (apply-value event new-value)
(update-input new-value))) (update-input new-value)))
(when on-blur (on-blur event)))) (when (fn? on-blur)
(on-blur event))))
handle-unmount
(h/use-ref-callback handle-blur)
on-click on-click
(mf/use-callback (mf/use-fn
(fn [event] (fn [event]
(let [target (dom/get-target event)] (let [target (dom/get-target event)
(when (some? ref) node (mf/ref-val ref)]
(let [current (mf/ref-val ref)] (when (and (some? node) (not (dom/child? node target)))
(when (and (some? current) (not (.contains current target))) (dom/blur! node)))))
(dom/blur! current)))))))
handle-focus handle-focus
(mf/use-callback (mf/use-callback
@ -212,9 +197,12 @@
;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect ;; In webkit browsers the mouseup event will be called after the on-focus causing and unselect
(.addEventListener target "mouseup" dom/prevent-default #js {:once true}))))) (.addEventListener target "mouseup" dom/prevent-default #js {:once true})))))
props (-> props props (-> (obj/clone props)
(obj/without ["value" "onChange" "nillable" "onFocus"]) (obj/unset! "selectOnFocus")
(obj/set! "className" (or class "input-text")) (obj/unset! "nillable")
(obj/set! "value" mf/undefined)
(obj/set! "onChange" mf/undefined)
(obj/set! "className" class)
(obj/set! "type" "text") (obj/set! "type" "text")
(obj/set! "ref" ref) (obj/set! "ref" ref)
(obj/set! "defaultValue" (fmt/format-number value)) (obj/set! "defaultValue" (fmt/format-number value))
@ -223,50 +211,23 @@
(obj/set! "onBlur" handle-blur) (obj/set! "onBlur" handle-blur)
(obj/set! "onFocus" handle-focus))] (obj/set! "onFocus" handle-focus))]
(mf/use-effect (mf/with-effect [value]
(mf/deps value) (when-let [input-node (mf/ref-val ref)]
(fn [] (dom/set-value! input-node (fmt/format-number value))))
(when-let [input-node (mf/ref-val ref)]
(dom/set-value! input-node (fmt/format-number value)))))
(mf/use-effect (mf/with-effect []
(mf/deps handle-blur) (fn []
(fn [] (when (mf/ref-val dirty-ref)
(mf/set-ref-val! handle-blur-ref {:fn handle-blur}))) (handle-unmount))))
(mf/use-layout-effect (mf/with-layout-effect []
(fn [] (let [keys [(events/listen globals/window "pointerdown" on-click)
#(when (mf/ref-val dirty-ref) (events/listen globals/window "click" on-click)]]
(let [handle-blur (:fn (mf/ref-val handle-blur-ref))] #(run! events/unlistenByKey keys)))
(handle-blur)))))
(mf/use-layout-effect (mf/with-layout-effect [handle-mouse-wheel]
(mf/deps handle-mouse-wheel) (when-let [node (mf/ref-val ref)]
(fn [] (let [key (events/listen node "wheel" handle-mouse-wheel #js {:passive false})]
(let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:passive false})]] #(events/unlistenByKey key))))
#(doseq [key keys]
(events/unlistenByKey key)))))
(mf/use-layout-effect
(fn []
(let [keys [(events/listen globals/window EventType.POINTERDOWN on-click)
(events/listen globals/window EventType.CLICK on-click)]]
#(doseq [key keys]
(events/unlistenByKey key)))))
(mf/use-layout-effect
(mf/deps handle-mouse-wheel)
(fn []
(let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:passive false})]]
#(doseq [key keys]
(events/unlistenByKey key)))))
(mf/use-layout-effect
(mf/deps handle-mouse-wheel)
(fn []
(let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:passive false})]]
#(doseq [key keys]
(events/unlistenByKey key)))))
[:> :input props])) [:> :input props]))

View file

@ -151,7 +151,7 @@
:on-remove (on-remove index) :on-remove (on-remove index)
:disable-drag disable-drag :disable-drag disable-drag
:on-focus on-focus :on-focus on-focus
:data-select-on-focus (not @disable-drag) :select-on-focus (not @disable-drag)
:on-blur on-blur}])]) :on-blur on-blur}])])
(when (or (= type :frame) (when (or (= type :frame)

View file

@ -189,6 +189,6 @@
:on-reorder (handle-reorder index) :on-reorder (handle-reorder index)
:disable-drag disable-drag :disable-drag disable-drag
:on-focus on-focus :on-focus on-focus
:data-select-on-focus (not @disable-drag) :select-on-focus (not @disable-drag)
:on-blur on-blur :on-blur on-blur
:disable-stroke-style disable-stroke-style}])])]])) :disable-stroke-style disable-stroke-style}])])]]))

View file

@ -461,7 +461,7 @@
:max 200 :max 200
:step 0.1 :step 0.1
:default "1.2" :default "1.2"
:klass (css :line-height-input) :class (css :line-height-input)
:value (attr->string line-height) :value (attr->string line-height)
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:nillable line-height-nillable :nillable line-height-nillable
@ -477,7 +477,7 @@
{:min -200 {:min -200
:max 200 :max 200
:step 0.1 :step 0.1
:klass (css :letter-spacing-input) :class (css :letter-spacing-input)
:value (attr->string letter-spacing) :value (attr->string letter-spacing)
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-change #(handle-change % :letter-spacing) :on-change #(handle-change % :letter-spacing)

View file

@ -17,31 +17,23 @@
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc options (mf/defc options
{::mf/wrap [mf/memo]} {::mf/wrap [mf/memo]
::mf/wrap-props false}
[] []
(let [options (mf/deref refs/workspace-page-options) (let [options (mf/deref refs/workspace-page-options)
on-change (mf/use-fn #(st/emit! (dw/change-canvas-color %)))
on-change on-open (mf/use-fn #(st/emit! (dwu/start-undo-transaction :options)))
(fn [value] on-close (mf/use-fn #(st/emit! (dwu/commit-undo-transaction :options)))]
(st/emit! (dw/change-canvas-color value)))
on-open
(fn []
(st/emit! (dwu/start-undo-transaction :options)))
on-close
(fn []
(st/emit! (dwu/commit-undo-transaction :options)))]
[:div.element-set [:div.element-set
[:div.element-set-title (tr "workspace.options.canvas-background")] [:div.element-set-title (tr "workspace.options.canvas-background")]
[:div.element-set-content [:div.element-set-content
[:& color-row {:disable-gradient true [:& color-row
:disable-opacity true {:disable-gradient true
:title (tr "workspace.options.canvas-background") :disable-opacity true
:color {:color (get options :background clr/canvas) :title (tr "workspace.options.canvas-background")
:opacity 1} :color {:color (get options :background clr/canvas)
:on-change on-change :opacity 1}
:on-open on-open :on-change on-change
:on-close on-close}]]])) :on-open on-open
:on-close on-close}]]]))

View file

@ -41,7 +41,7 @@
(mf/defc color-row (mf/defc color-row
[{:keys [index color disable-gradient disable-opacity on-change [{:keys [index color disable-gradient disable-opacity on-change
on-reorder on-detach on-open on-close title on-remove on-reorder on-detach on-open on-close title on-remove
disable-drag on-focus on-blur select-only data-select-on-focus]}] disable-drag on-focus on-blur select-only select-on-focus]}]
(let [current-file-id (mf/use-ctx ctx/current-file-id) (let [current-file-id (mf/use-ctx ctx/current-file-id)
file-colors (mf/deref refs/workspace-file-colors) file-colors (mf/deref refs/workspace-file-colors)
shared-libs (mf/deref refs/workspace-libraries) shared-libs (mf/deref refs/workspace-libraries)
@ -207,7 +207,7 @@
{:class (dom/classnames :percentail (not= (:opacity color) :multiple))} {:class (dom/classnames :percentail (not= (:opacity color) :multiple))}
[:> numeric-input {:value (-> color :opacity opacity->string) [:> numeric-input {:value (-> color :opacity opacity->string)
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:data-select-on-focus data-select-on-focus :select-on-focus select-on-focus
:on-focus on-focus :on-focus on-focus
:on-blur on-blur :on-blur on-blur
:on-change handle-opacity-change :on-change handle-opacity-change

View file

@ -12,7 +12,8 @@
[app.util.object :as obj] [app.util.object :as obj]
[rumext.v2 :as mf])) [rumext.v2 :as mf]))
(mf/defc input-row [{:keys [label options value class min max on-change type placeholder default nillable on-focus data-select-on-focus]}] (mf/defc input-row
[{:keys [label options value class min max on-change type placeholder default nillable on-focus select-on-focus]}]
[:div.row-flex.input-row [:div.row-flex.input-row
[:span.element-set-subtitle label] [:span.element-set-subtitle label]
[:div.input-element {:class class} [:div.input-element {:class class}
@ -47,7 +48,7 @@
:nillable nillable :nillable nillable
:on-change on-change :on-change on-change
:on-focus on-focus :on-focus on-focus
:data-select-on-focus data-select-on-focus :select-on-focus select-on-focus
:value (or value "")}])]]) :value (or value "")}])]])

View file

@ -49,7 +49,7 @@
(mf/defc stroke-row (mf/defc stroke-row
{::mf/wrap-props false} {::mf/wrap-props false}
[{:keys [index stroke title show-caps on-color-change on-reorder on-color-detach on-remove on-stroke-width-change on-stroke-style-change on-stroke-alignment-change open-caps-select close-caps-select on-stroke-cap-start-change on-stroke-cap-end-change on-stroke-cap-switch disable-drag on-focus on-blur disable-stroke-style data-select-on-focus]}] [{:keys [index stroke title show-caps on-color-change on-reorder on-color-detach on-remove on-stroke-width-change on-stroke-style-change on-stroke-alignment-change open-caps-select close-caps-select on-stroke-cap-start-change on-stroke-cap-end-change on-stroke-cap-switch disable-drag on-focus on-blur disable-stroke-style select-on-focus]}]
(let [start-caps-state (mf/use-state {:open? false (let [start-caps-state (mf/use-state {:open? false
:top 0 :top 0
:left 0}) :left 0})
@ -88,7 +88,7 @@
:on-remove (on-remove index) :on-remove (on-remove index)
:disable-drag disable-drag :disable-drag disable-drag
:on-focus on-focus :on-focus on-focus
:data-select-on-focus data-select-on-focus :select-on-focus select-on-focus
:on-blur on-blur}] :on-blur on-blur}]
;; Stroke Width, Alignment & Style ;; Stroke Width, Alignment & Style
@ -103,7 +103,7 @@
:placeholder (tr "settings.multiple") :placeholder (tr "settings.multiple")
:on-change (on-stroke-width-change index) :on-change (on-stroke-width-change index)
:on-focus on-focus :on-focus on-focus
:data-select-on-focus data-select-on-focus :select-on-focus select-on-focus
:on-blur on-blur}]] :on-blur on-blur}]]
[:select#style.input-select {:value (enum->string (:stroke-alignment stroke)) [:select#style.input-select {:value (enum->string (:stroke-alignment stroke))

View file

@ -17,7 +17,21 @@
[app.util.webapi :as wapi] [app.util.webapi :as wapi]
[cuerdas.core :as str] [cuerdas.core :as str]
[goog.dom :as dom] [goog.dom :as dom]
[promesa.core :as p])) [promesa.core :as p])
(:import goog.events.BrowserEvent))
(extend-type BrowserEvent
cljs.core/IDeref
(-deref [it] (.getBrowserEvent it)))
(defn browser-event?
[o]
(instance? BrowserEvent o))
(defn native-event?
[o]
(instance? js/Event o))
(log/set-level! :warn) (log/set-level! :warn)
@ -338,10 +352,19 @@
y (.-offsetY event)] y (.-offsetY event)]
(gpt/point x y)))) (gpt/point x y))))
(defn get-delta-position
[event]
(let [e (if (browser-event? event)
(deref event)
event)
x (.-deltaX ^js e)
y (.-deltaY ^js e)]
(gpt/point x y)))
(defn get-client-size (defn get-client-size
[^js node] [^js node]
(when (some? node) (when (some? node)
(grc/make-rect 0 0 (.-clientWidth ^js node) (.-clientHeight ^js node)))) (grc/make-rect 0 0 (.-clientWidth node) (.-clientHeight node))))
(defn get-bounding-rect (defn get-bounding-rect
[node] [node]

View file

@ -73,6 +73,11 @@
(unchecked-set obj key value) (unchecked-set obj key value)
obj) obj)
(defn unset!
[obj key]
(js-delete obj key)
obj)
(defn update! (defn update!
[obj key f & args] [obj key f & args]
(let [found (get obj key ::not-found)] (let [found (get obj key ::not-found)]