mirror of
https://github.com/penpot/penpot.git
synced 2025-06-11 17:41:38 +02:00
🎉 Allow calculations in numeric fields
This commit is contained in:
parent
dd92e5d773
commit
fad9d2fd3a
4 changed files with 285 additions and 59 deletions
|
@ -8,83 +8,122 @@
|
|||
(:require
|
||||
[app.common.data :as d]
|
||||
[app.common.math :as math]
|
||||
[app.common.spec :as us]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.keyboard :as kbd]
|
||||
[app.util.object :as obj]
|
||||
[app.util.simple-math :as sm]
|
||||
[rumext.alpha :as mf]))
|
||||
|
||||
(mf/defc numeric-input
|
||||
{::mf/wrap-props false
|
||||
::mf/forward-ref true}
|
||||
[props ref]
|
||||
(let [value (obj/get props "value")
|
||||
on-change (obj/get props "onChange")
|
||||
min-val (obj/get props "min")
|
||||
max-val (obj/get props "max")
|
||||
(let [value-str (obj/get props "value")
|
||||
min-val-str (obj/get props "min")
|
||||
max-val-str (obj/get props "max")
|
||||
wrap-value? (obj/get props "data-wrap")
|
||||
on-change (obj/get props "onChange")
|
||||
|
||||
stored-val (mf/use-var value)
|
||||
local-ref (mf/use-ref nil)
|
||||
local-ref (mf/use-ref)
|
||||
ref (or ref local-ref)
|
||||
|
||||
min-val (cond-> min-val
|
||||
(string? min-val) (d/parse-integer nil))
|
||||
value (d/parse-integer value-str)
|
||||
|
||||
max-val (cond-> max-val
|
||||
(string? max-val) (d/parse-integer nil))
|
||||
min-val (when (string? min-val-str)
|
||||
(d/parse-integer min-val-str))
|
||||
max-val (when (string? max-val-str)
|
||||
(d/parse-integer max-val-str))
|
||||
|
||||
num? (fn [val] (and (number? val)
|
||||
(not (math/nan? val))
|
||||
(math/finite? val)))
|
||||
|
||||
num? (fn [value] (and (number? value)
|
||||
(not (math/nan? value))
|
||||
(math/finite? value)))
|
||||
|
||||
parse-value (fn [event]
|
||||
(let [value (-> (dom/get-target-val event) (d/parse-integer nil))]
|
||||
(when (num? value)
|
||||
(cond-> value
|
||||
(num? min-val) (cljs.core/max min-val)
|
||||
(num? max-val) (cljs.core/min max-val)))))
|
||||
handle-change
|
||||
parse-value
|
||||
(mf/use-callback
|
||||
(mf/deps on-change)
|
||||
(fn [event]
|
||||
(let [value (parse-value event)]
|
||||
(when (and on-change (num? value))
|
||||
(on-change value)))))
|
||||
(mf/deps ref min-val max-val value)
|
||||
(fn []
|
||||
(let [input-node (mf/ref-val ref)
|
||||
new-value (-> (dom/get-value input-node)
|
||||
(sm/expr-eval value))]
|
||||
(when (num? new-value)
|
||||
(cond-> new-value
|
||||
true
|
||||
(math/round)
|
||||
|
||||
true
|
||||
(cljs.core/max us/min-safe-int)
|
||||
|
||||
true
|
||||
(cljs.core/min us/max-safe-int)
|
||||
|
||||
(num? min-val)
|
||||
(cljs.core/max min-val)
|
||||
|
||||
(num? max-val)
|
||||
(cljs.core/min max-val))))))
|
||||
|
||||
update-input
|
||||
(mf/use-callback
|
||||
(mf/deps ref)
|
||||
(fn [new-value]
|
||||
(let [input-node (mf/ref-val ref)]
|
||||
(dom/set-value! input-node (str new-value)))))
|
||||
|
||||
apply-value
|
||||
(mf/use-callback
|
||||
(mf/deps on-change update-input)
|
||||
(fn [new-value]
|
||||
(when new-value
|
||||
(when on-change
|
||||
(on-change new-value))
|
||||
(update-input new-value))))
|
||||
|
||||
set-delta
|
||||
(mf/use-callback
|
||||
(mf/deps on-change wrap-value? min-val max-val)
|
||||
(mf/deps wrap-value? min-val max-val parse-value apply-value)
|
||||
(fn [event up? down?]
|
||||
(let [value (parse-value event)
|
||||
increment (if up? 9 -9)]
|
||||
(when (and (or up? down?) (num? value))
|
||||
(cond
|
||||
(kbd/shift? event)
|
||||
(let [new-value (+ value increment)
|
||||
new-value (cond
|
||||
(and wrap-value? (num? max-val) (num? min-val) (> new-value max-val) up?)
|
||||
(+ min-val (- max-val new-value))
|
||||
(let [current-value (parse-value)]
|
||||
(when current-value
|
||||
(let [increment (if (kbd/shift? event)
|
||||
(if up? 10 -10)
|
||||
(if up? 1 -1))
|
||||
|
||||
(and wrap-value? (num? min-val) (num? max-val) (< new-value min-val) down?)
|
||||
(- max-val (- new-value min-val))
|
||||
new-value (+ current-value increment)
|
||||
new-value (cond
|
||||
(and wrap-value? (num? max-val) (num? min-val)
|
||||
(> new-value max-val) up?)
|
||||
(-> new-value (- max-val) (+ min-val) (- 1))
|
||||
|
||||
(and (num? min-val) (< new-value min-val)) min-val
|
||||
(and (num? max-val) (> new-value max-val)) max-val
|
||||
:else new-value)]
|
||||
(dom/set-value! (dom/get-target event) new-value))
|
||||
(and wrap-value? (num? min-val) (num? max-val)
|
||||
(< new-value min-val) down?)
|
||||
(-> new-value (- min-val) (+ max-val) (+ 1))
|
||||
|
||||
(and wrap-value? (num? max-val) (num? min-val) (= value max-val) up?)
|
||||
(dom/set-value! (dom/get-target event) (dec min-val))
|
||||
(and (num? min-val) (< new-value min-val))
|
||||
min-val
|
||||
|
||||
(and wrap-value? (num? min-val) (num? max-val) (= value min-val) down?)
|
||||
(dom/set-value! (dom/get-target event) (inc max-val)))))))
|
||||
(and (num? max-val) (> new-value max-val))
|
||||
max-val
|
||||
|
||||
:else new-value)]
|
||||
|
||||
(apply-value new-value))))))
|
||||
|
||||
handle-key-down
|
||||
(mf/use-callback
|
||||
(mf/deps set-delta)
|
||||
(mf/deps set-delta apply-value update-input)
|
||||
(fn [event]
|
||||
(set-delta event (kbd/up-arrow? event) (kbd/down-arrow? event))))
|
||||
(let [up? (kbd/up-arrow? event)
|
||||
down? (kbd/down-arrow? event)
|
||||
enter? (kbd/enter? event)
|
||||
esc? (kbd/esc? event)]
|
||||
(when (or up? down?)
|
||||
(set-delta event up? down?))
|
||||
(when enter?
|
||||
(let [new-value (parse-value)]
|
||||
(apply-value new-value)))
|
||||
(when esc?
|
||||
(update-input value-str)))))
|
||||
|
||||
handle-mouse-wheel
|
||||
(mf/use-callback
|
||||
|
@ -93,27 +132,30 @@
|
|||
(set-delta event (< (.-deltaY event) 0) (> (.-deltaY event) 0))))
|
||||
|
||||
handle-blur
|
||||
(fn [event]
|
||||
(when-let [input-node (and ref (mf/ref-val ref))]
|
||||
(dom/set-value! input-node @stored-val)))
|
||||
(mf/use-callback
|
||||
(mf/deps parse-value apply-value update-input)
|
||||
(fn [event]
|
||||
(let [new-value (parse-value)]
|
||||
(if new-value
|
||||
(apply-value new-value)
|
||||
(update-input value-str)))))
|
||||
|
||||
props (-> props
|
||||
(obj/without ["value" "onChange"])
|
||||
(obj/set! "className" "input-text")
|
||||
(obj/set! "type" "number")
|
||||
(obj/set! "type" "text")
|
||||
(obj/set! "ref" ref)
|
||||
(obj/set! "defaultValue" value)
|
||||
(obj/set! "defaultValue" value-str)
|
||||
(obj/set! "onWheel" handle-mouse-wheel)
|
||||
(obj/set! "onKeyDown" handle-key-down)
|
||||
(obj/set! "onChange" handle-change)
|
||||
(obj/set! "onBlur" handle-blur))]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps value)
|
||||
(mf/deps value-str)
|
||||
(fn []
|
||||
(when-let [input-node (and ref (mf/ref-val ref))]
|
||||
(if-not (dom/active? input-node)
|
||||
(dom/set-value! input-node value)
|
||||
(reset! stored-val value)))))
|
||||
(when-let [input-node (mf/ref-val ref)]
|
||||
(when-not (dom/active? input-node)
|
||||
(dom/set-value! input-node value-str)))))
|
||||
|
||||
[:> :input props]))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue