Added compiled JavaScript to repository for GitHub pages
This feels like a mistake...
This commit is contained in:
parent
3d5a2fb322
commit
dc226b1f25
468 changed files with 212152 additions and 2 deletions
116
resources/public/js/compiled/out/re_com/alert.cljs
Normal file
116
resources/public/js/compiled/out/re_com/alert.cljs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
(ns re-com.alert
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.util :refer [deref-or-value]]
|
||||
[re-com.buttons :refer [button]]
|
||||
[re-com.box :refer [h-box v-box box scroller border flex-child-style]]
|
||||
[re-com.validate :refer [string-or-hiccup? alert-type? alert-types-list
|
||||
vector-of-maps? css-style? html-attr?] :refer-macros [validate-args-macro]]))
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: alert
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def alert-box-args-desc
|
||||
[{:name :id :required false :type "anything" :description [:span "a unique identifier, usually an integer or string."]}
|
||||
{:name :alert-type :required false :default :info :type "keyword" :validate-fn alert-type? :description [:span "one of " alert-types-list]}
|
||||
{:name :heading :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description [:span "displayed as a larger heading. One of " [:code ":heading"] " or " [:code ":body"] " should be provided"]}
|
||||
{:name :body :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "displayed within the body of the alert"}
|
||||
{:name :padding :required false :default "15px" :type "string" :validate-fn string? :description "padding surounding the alert"}
|
||||
{:name :closeable? :required false :default false :type "boolean" :description [:span "if true, render a close button. " [:code ":on-close"] " should be supplied"]}
|
||||
{:name :on-close :required false :type ":id -> nil" :validate-fn fn? :description [:span "called when the user clicks the close 'X' button. Passed the " [:code ":id"] " of the alert to close"]}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS classes (whitespace separated). Applied to outer container"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles. Applied to outer container"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed. Applied to outer container"]}])
|
||||
|
||||
(defn alert-box
|
||||
"Displays one alert box. A close button allows the message to be removed"
|
||||
[& {:keys [id alert-type heading body padding closeable? on-close class style attr]
|
||||
:or {alert-type :info}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro alert-box-args-desc args "alert-box")]}
|
||||
(let [close-button [button
|
||||
:label [:i {:class "zmdi created zmdi-hc-fw-rc zmdi-close"
|
||||
:style {:font-size "20px"}}] ;"×"
|
||||
:on-click (handler-fn (on-close id))
|
||||
:class "close"]
|
||||
alert-class (alert-type {:none ""
|
||||
:info "alert-success"
|
||||
:warning "alert-warning"
|
||||
:danger "alert-danger"})]
|
||||
[:div
|
||||
(merge {:class (str "rc-alert alert fade in " alert-class " " class)
|
||||
:style (merge (flex-child-style "none")
|
||||
{:padding (when padding padding)}
|
||||
style)}
|
||||
attr)
|
||||
(when heading
|
||||
[h-box
|
||||
:justify :between
|
||||
:align :center
|
||||
:style {:margin-bottom (if body "10px" "0px")}
|
||||
:children [[:h4
|
||||
{:style {:margin-bottom "0px"}} ;; Override h4
|
||||
heading]
|
||||
(when (and closeable? on-close)
|
||||
close-button)]])
|
||||
(when body
|
||||
[h-box
|
||||
:justify :between
|
||||
:align :center
|
||||
:children [[:div body]
|
||||
(when (and (not heading) closeable? on-close)
|
||||
close-button)]])]))
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: alert-list
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def alert-list-args-desc
|
||||
[{:name :alerts :required true :type "vector of maps | atom" :validate-fn vector-of-maps? :description "alerts to render (in the order supplied). Can also be a list of maps"}
|
||||
{:name :on-close :required true :type ":id -> nil" :validate-fn fn? :description [:span "called when the user clicks the close 'X' button. Passed the alert's " [:code ":id"]]}
|
||||
{:name :max-height :required false :type "string" :validate-fn string? :description "CSS style for maximum list height. By default, it grows forever"}
|
||||
{:name :padding :required false :default "4px" :type "string" :validate-fn string? :description "CSS padding within the alert"}
|
||||
{:name :border-style :required false :default "1px solid lightgrey" :type "string" :validate-fn string? :description "CSS border style surrounding the list"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated. Applied to outer container"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles. Applied to outer container"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed. Applied to outer container"]}])
|
||||
|
||||
(defn alert-list
|
||||
"Displays a list of alert-box components in a v-box. Sample alerts object:
|
||||
[{:id 2
|
||||
:alert-type :warning
|
||||
:heading \"Heading\"
|
||||
:body \"Body\"
|
||||
:padding \"8px\"
|
||||
:closeable? true}
|
||||
{:id 1
|
||||
:alert-type :info
|
||||
:heading \"Heading\"
|
||||
:body \"Body\"}]"
|
||||
[& {:keys [alerts on-close max-height padding border-style class style attr]
|
||||
:or {padding "4px"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro alert-list-args-desc args "alert-list")]}
|
||||
(let [alerts (deref-or-value alerts)]
|
||||
[box
|
||||
:child [border
|
||||
:padding padding
|
||||
:border border-style
|
||||
:class class
|
||||
:style style
|
||||
:attr attr
|
||||
:child [scroller
|
||||
:v-scroll :auto
|
||||
:style {:max-height max-height}
|
||||
:child [v-box
|
||||
:size "auto"
|
||||
:children [(for [alert alerts]
|
||||
(let [{:keys [id alert-type heading body padding closeable?]} alert]
|
||||
^{:key id} [alert-box
|
||||
:id id
|
||||
:alert-type alert-type
|
||||
:heading heading
|
||||
:body body
|
||||
:padding padding
|
||||
:closeable? closeable?
|
||||
:on-close on-close]))]]]]]))
|
||||
File diff suppressed because one or more lines are too long
211
resources/public/js/compiled/out/re_com/alert.js
Normal file
211
resources/public/js/compiled/out/re_com/alert.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/alert.js.map
Normal file
1
resources/public/js/compiled/out/re_com/alert.js.map
Normal file
File diff suppressed because one or more lines are too long
505
resources/public/js/compiled/out/re_com/box.cljs
Normal file
505
resources/public/js/compiled/out/re_com/box.cljs
Normal file
|
|
@ -0,0 +1,505 @@
|
|||
(ns re-com.box
|
||||
(:require [clojure.string :as string]
|
||||
[re-com.validate :refer [justify-style? justify-options-list align-style? align-options-list
|
||||
scroll-style? scroll-options-list string-or-hiccup? css-style? html-attr?] :refer-macros [validate-args-macro]]))
|
||||
|
||||
(def debug false)
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Private Helper functions
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(defn flex-child-style
|
||||
"Determines the value for the 'flex' attribute (which has grow, shrink and basis), based on the :size parameter.
|
||||
IMPORTANT: The term 'size' means width of the item in the case of flex-direction 'row' OR height of the item in the case of flex-direction 'column'.
|
||||
Flex property explanation:
|
||||
- grow Integer ratio (used with other siblings) to determined how a flex item grows it's size if there is extra space to distribute. 0 for no growing.
|
||||
- shrink Integer ratio (used with other siblings) to determined how a flex item shrinks it's size if space needs to be removed. 0 for no shrinking.
|
||||
- basis Initial size (width, actually) of item before any growing or shrinking. Can be any size value, e.g. 60%, 100px, auto
|
||||
Note: auto will cause the initial size to be calculated to take up as much space as possible, in conjunction with it's siblings :flex settings.
|
||||
Supported values:
|
||||
- initial '0 1 auto' - Use item's width/height for dimensions (or content dimensions if w/h not specifed). Never grow. Shrink (to min-size) if necessary.
|
||||
Good for creating boxes with fixed maximum size, but that can shrink to a fixed smaller size (min-width/height) if space becomes tight.
|
||||
NOTE: When using initial, you should also set a width/height value (depending on flex-direction) to specify it's default size
|
||||
and an optional min-width/height value to specify the size it can shrink to.
|
||||
- auto '1 1 auto' - Use item's width/height for dimensions. Grow if necessary. Shrink (to min-size) if necessary.
|
||||
Good for creating really flexible boxes that will gobble as much available space as they are allowed or shrink as much as they are forced to.
|
||||
- none '0 0 auto' - Use item's width/height for dimensions (or content dimensions if not specifed). Never grow. Never shrink.
|
||||
Good for creating rigid boxes that stick to their width/height if specified, otherwise their content size.
|
||||
- 100px '0 0 100px' - Non flexible 100px size (in the flex direction) box.
|
||||
Good for fixed headers/footers and side bars of an exact size.
|
||||
- 60% '60 1 0px' - Set the item's size (it's width/height depending on flex-direction) to be 60% of the parent container's width/height.
|
||||
NOTE: If you use this, then all siblings with percentage values must add up to 100%.
|
||||
- 60 '60 1 0px' - Same as percentage above.
|
||||
- grow shrink basis 'grow shrink basis' - If none of the above common valaues above meet your needs, this gives you precise control.
|
||||
If number of words is not 1 or 3, an exception is thrown.
|
||||
Reference: http://www.w3.org/TR/css3-flexbox/#flexibility
|
||||
Diagram: http://www.w3.org/TR/css3-flexbox/#flex-container
|
||||
Regex101 testing: ^(initial|auto|none)|(\\d+)(px|%|em)|(\\d+)\\w(\\d+)\\w(.*) - remove double backslashes"
|
||||
[size]
|
||||
;; TODO: Could make initial/auto/none into keywords???
|
||||
(let [split-size (string/split (string/trim size) #"\s+") ;; Split into words separated by whitespace
|
||||
split-count (count split-size)
|
||||
_ (assert (contains? #{1 3} split-count) "Must pass either 1 or 3 words to flex-child-style")
|
||||
size-only (when (= split-count 1) (first split-size)) ;; Contains value when only one word passed (e.g. auto, 60px)
|
||||
split-size-only (when size-only (string/split size-only #"(\d+)(.*)")) ;; Split into number + string
|
||||
[_ num units] (when size-only split-size-only) ;; grab number and units
|
||||
pass-through? (nil? num) ;; If we can't split, then we'll pass this straign through
|
||||
grow-ratio? (or (= units "%") (= units "") (nil? units)) ;; Determine case for using grow ratio
|
||||
grow (if grow-ratio? num "0") ;; Set grow based on percent or integer, otherwise no grow
|
||||
shrink (if grow-ratio? "1" "0") ;; If grow set, then set shrink to even shrinkage as well
|
||||
basis (if grow-ratio? "0px" size) ;; If grow set, then even growing, otherwise set basis size to the passed in size (e.g. 100px, 5em)
|
||||
flex (if (and size-only (not pass-through?))
|
||||
(str grow " " shrink " " basis)
|
||||
size)]
|
||||
{:-webkit-flex flex
|
||||
:flex flex}))
|
||||
|
||||
|
||||
(defn flex-flow-style
|
||||
"A cross-browser helper function to output flex-flow with all it's potential browser prefixes"
|
||||
[flex-flow]
|
||||
{:-webkit-flex-flow flex-flow
|
||||
:flex-flow flex-flow})
|
||||
|
||||
(defn justify-style
|
||||
"Determines the value for the flex 'justify-content' attribute.
|
||||
This parameter determines how children are aligned along the main axis.
|
||||
The justify parameter is a keyword.
|
||||
Reference: http://www.w3.org/TR/css3-flexbox/#justify-content-property"
|
||||
[justify]
|
||||
(let [js (case justify
|
||||
:start "flex-start"
|
||||
:end "flex-end"
|
||||
:center "center"
|
||||
:between "space-between"
|
||||
:around "space-around")]
|
||||
{:-webkit-justify-content js
|
||||
:justify-content js}))
|
||||
|
||||
|
||||
(defn align-style
|
||||
"Determines the value for the flex align type attributes.
|
||||
This parameter determines how children are aligned on the cross axis.
|
||||
The justify parameter is a keyword.
|
||||
Reference: http://www.w3.org/TR/css3-flexbox/#align-items-property"
|
||||
[attribute align]
|
||||
(let [attribute-wk (->> attribute name (str "-webkit-") keyword)
|
||||
as (case align
|
||||
:start "flex-start"
|
||||
:end "flex-end"
|
||||
:center "center"
|
||||
:baseline "baseline"
|
||||
:stretch "stretch")]
|
||||
{attribute-wk as
|
||||
attribute as}))
|
||||
|
||||
|
||||
(defn scroll-style
|
||||
"Determines the value for the 'overflow' attribute.
|
||||
The scroll parameter is a keyword.
|
||||
Because we're translating scroll into overflow, the keyword doesn't appear to match the attribute value"
|
||||
[attribute scroll]
|
||||
{attribute (case scroll
|
||||
:auto "auto"
|
||||
:off "hidden"
|
||||
:on "scroll"
|
||||
:spill "visible")})
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Private Component: box-base (debug color: lightblue)
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(defn- box-base
|
||||
"This should generally NOT be used as it is the basis for the box, scroller and border components"
|
||||
[& {:keys [size scroll h-scroll v-scroll width height min-width min-height max-width max-height justify align align-self
|
||||
margin padding border l-border r-border t-border b-border radius bk-color child class-name class style attr]}]
|
||||
(let [s (merge
|
||||
(flex-flow-style "inherit")
|
||||
(flex-child-style size)
|
||||
(when scroll (scroll-style :overflow scroll))
|
||||
(when h-scroll (scroll-style :overflow-x h-scroll))
|
||||
(when v-scroll (scroll-style :overflow-y v-scroll))
|
||||
(when width {:width width})
|
||||
(when height {:height height})
|
||||
(when min-width {:min-width min-width})
|
||||
(when min-height {:min-height min-height})
|
||||
(when max-width {:max-width max-width})
|
||||
(when max-height {:max-height max-height})
|
||||
(when justify (justify-style justify))
|
||||
(when align (align-style :align-items align))
|
||||
(when align-self (align-style :align-self align-self))
|
||||
(when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left"
|
||||
(when padding {:padding padding})
|
||||
(when border {:border border})
|
||||
(when l-border {:border-left l-border})
|
||||
(when r-border {:border-right r-border})
|
||||
(when t-border {:border-top t-border})
|
||||
(when b-border {:border-bottom b-border})
|
||||
(when radius {:border-radius radius})
|
||||
(if bk-color
|
||||
{:background-color bk-color}
|
||||
(if debug {:background-color "lightblue"} {}))
|
||||
style)]
|
||||
[:div
|
||||
(merge
|
||||
{:class (str class-name "display-flex " class) :style s}
|
||||
attr)
|
||||
child]))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: gap (debug color: chocolate)
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def gap-args-desc
|
||||
[{:name :size :required true :type "string" :validate-fn string? :description "the length of the whitespace. Typically, an absolute CSS length like 10px or 10em, but can be a stretchy proportional amount like 2"}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn gap
|
||||
"Returns a component which produces a gap between children in a v-box/h-box along the main axis"
|
||||
[& {:keys [size width height class style attr]
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro gap-args-desc args "gap")]}
|
||||
(let [s (merge
|
||||
(when size (flex-child-style size))
|
||||
(when width {:width width})
|
||||
(when height {:height height})
|
||||
(when debug {:background-color "chocolate"})
|
||||
style)]
|
||||
[:div
|
||||
(merge
|
||||
{:class (str "rc-gap " class) :style s}
|
||||
attr)]))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: line
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def line-args-desc
|
||||
[{:name :size :required false :default "1px" :type "string" :validate-fn string? :description "a CSS style for the thickness of the line. Usually px, % or em"}
|
||||
{:name :color :required false :default "lightgray" :type "string" :validate-fn string? :description "a CSS color"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn line
|
||||
"Returns a component which produces a line between children in a v-box/h-box along the main axis.
|
||||
Specify size in pixels and a stancard CSS color. Defaults to a 1px lightgray line"
|
||||
[& {:keys [size color class style attr]
|
||||
:or {size "1px" color "lightgray"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro line-args-desc args "line")]}
|
||||
(let [s (merge
|
||||
(flex-child-style (str "0 0 " size))
|
||||
{:background-color color}
|
||||
style)]
|
||||
[:div
|
||||
(merge
|
||||
{:class (str "rc-line " class) :style s}
|
||||
attr)]))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: h-box (debug color: gold)
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def h-box-args-desc
|
||||
[{:name :children :required true :type "vector" :validate-fn sequential? :description "a vector (or list) of components"}
|
||||
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"]", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
|
||||
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
|
||||
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
|
||||
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
|
||||
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
|
||||
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
|
||||
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
|
||||
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
|
||||
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
|
||||
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
|
||||
{:name :gap :required false :type "string" :validate-fn string? :description "the amount of whitespace to put between each child. Typically, an absolute CSS length like 10px or 10em, but can be a stretchy proportional amount like 2"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn h-box
|
||||
"Returns hiccup which produces a horizontal box.
|
||||
It's primary role is to act as a container for components and lays it's children from left to right.
|
||||
By default, it also acts as a child under it's parent"
|
||||
[& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding gap children class style attr]
|
||||
:or {size "none" justify :start align :stretch}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro h-box-args-desc args "h-box")]}
|
||||
(let [s (merge
|
||||
(flex-flow-style "row nowrap")
|
||||
(flex-child-style size)
|
||||
(when width {:width width})
|
||||
(when height {:height height})
|
||||
(when min-width {:min-width min-width})
|
||||
(when min-height {:min-height min-height})
|
||||
(when max-width {:max-width max-width})
|
||||
(when max-height {:max-height max-height})
|
||||
(justify-style justify)
|
||||
(align-style :align-items align)
|
||||
(when align-self (align-style :align-self align-self))
|
||||
(when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left"
|
||||
(when padding {:padding padding})
|
||||
(when debug {:background-color "gold"})
|
||||
style)
|
||||
gap-form (when gap [re-com.box/gap
|
||||
:size gap
|
||||
:width gap]) ;; TODO: required to get around a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=423112. Remove once fixed.
|
||||
children (if gap
|
||||
(interpose gap-form (filter identity children)) ;; filter is to remove possible nils so we don't add unwanted gaps
|
||||
children)]
|
||||
(into [:div
|
||||
(merge
|
||||
{:class (str "rc-h-box display-flex " class) :style s}
|
||||
attr)]
|
||||
children)))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: v-box (debug color: antiquewhite)
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def v-box-args-desc
|
||||
[{:name :children :required true :type "vector" :validate-fn sequential? :description "a vector (or list) of components"}
|
||||
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"]", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
|
||||
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
|
||||
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
|
||||
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
|
||||
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
|
||||
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
|
||||
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
|
||||
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
|
||||
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
|
||||
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
|
||||
{:name :gap :required false :type "string" :validate-fn string? :description "the amount of whitespace to put between each child. Typically, an absolute CSS length like 10px or 10em, but can be a stretchy proportional amount like 2"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn v-box
|
||||
"Returns hiccup which produces a vertical box.
|
||||
It's primary role is to act as a container for components and lays it's children from top to bottom.
|
||||
By default, it also acts as a child under it's parent"
|
||||
[& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding gap children class style attr]
|
||||
:or {size "none" justify :start align :stretch}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro v-box-args-desc args "v-box")]}
|
||||
(let [s (merge
|
||||
(flex-flow-style "column nowrap")
|
||||
(flex-child-style size)
|
||||
(when width {:width width})
|
||||
(when height {:height height})
|
||||
(when min-width {:min-width min-width})
|
||||
(when min-height {:min-height min-height})
|
||||
(when max-width {:max-width max-width})
|
||||
(when max-height {:max-height max-height})
|
||||
(justify-style justify)
|
||||
(align-style :align-items align)
|
||||
(when align-self (align-style :align-self align-self))
|
||||
(when margin {:margin margin}) ;; margin and padding: "all" OR "top&bottom right&left" OR "top right bottom left"
|
||||
(when padding {:padding padding})
|
||||
(when debug {:background-color "antiquewhite"})
|
||||
style)
|
||||
gap-form (when gap [re-com.box/gap
|
||||
:size gap
|
||||
:height gap]) ;; TODO: required to get around a Chrome bug: https://code.google.com/p/chromium/issues/detail?id=423112. Remove once fixed.
|
||||
children (if gap
|
||||
(interpose gap-form (filter identity children)) ;; filter is to remove possible nils so we don't add unwanted gaps
|
||||
children)]
|
||||
(into [:div
|
||||
(merge
|
||||
{:class (str "rc-v-box display-flex " class) :style s}
|
||||
attr)]
|
||||
children)))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: box
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def box-args-desc
|
||||
[{:name :child :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "a component (or string)"}
|
||||
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"]", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width style"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "a CSS height style"}
|
||||
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
|
||||
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
|
||||
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
|
||||
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
|
||||
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
|
||||
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
|
||||
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
|
||||
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
|
||||
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn box
|
||||
"Returns hiccup which produces a box, which is generally used as a child of a v-box or an h-box.
|
||||
By default, it also acts as a container for further child compenents, or another h-box or v-box"
|
||||
[& {:keys [size width height min-width min-height max-width max-height justify align align-self margin padding child class style attr]
|
||||
:or {size "none"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro box-args-desc args "box")]}
|
||||
(box-base :size size
|
||||
:width width
|
||||
:height height
|
||||
:min-width min-width
|
||||
:min-height min-height
|
||||
:max-width max-width
|
||||
:max-height max-height
|
||||
:justify justify
|
||||
:align align
|
||||
:align-self align-self
|
||||
:margin margin
|
||||
:padding padding
|
||||
:child child
|
||||
:class-name "rc-box "
|
||||
:class class
|
||||
:style style
|
||||
:attr attr))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: scroller
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def scroller-args-desc
|
||||
[{:name :child :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "a component (or string)"}
|
||||
{:name :size :required false :default "auto" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"]", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
|
||||
{:name :scroll :required false :default "auto" :type "keyword" :validate-fn scroll-style? :description [:span "Sets both h-scroll and v-scroll at once: " [:br]
|
||||
[:code ":auto"] ": only show scroll bar(s) if the content is larger than the scroller" [:br]
|
||||
[:code ":on"] ": always show scroll bars" [:br]
|
||||
[:code ":off"] ": never show scroll bar(s). Content which is not in the bounds of the scroller can not be seen" [:br]
|
||||
[:code ":spill"] ": never show scroll bar(s). Content which is not in the bounds of the scroller spills all over the place"]}
|
||||
{:name :h-scroll :required false :type "keyword" :validate-fn scroll-style? :description [:span "see " [:code ":scroll"] ". Overrides that setting"]}
|
||||
{:name :v-scroll :required false :type "keyword" :validate-fn scroll-style? :description [:span "see " [:code ":scroll"] ". Overrides that setting"]}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "initial width"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "initial height"}
|
||||
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
|
||||
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
|
||||
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
|
||||
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
|
||||
{:name :justify :required false :default :start :type "keyword" :validate-fn justify-style? :description [:span "equivalent to CSS style " [:span.bold "justify-content"] "." [:br] "One of " justify-options-list]}
|
||||
{:name :align :required false :default :stretch :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-items"] "." [:br] " One of " align-options-list]}
|
||||
{:name :align-self :required false :type "keyword" :validate-fn align-style? :description [:span "equivalent to CSS style " [:span.bold "align-self"] "." [:br] "Used when a child must override the parent's align-items setting."]}
|
||||
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
|
||||
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn scroller
|
||||
"Returns hiccup which produces a scoller component.
|
||||
This is the way scroll bars are added to boxes, in favour of adding the scroll attributes directly to the boxes themselves.
|
||||
IMPORTANT: Because this component becomes the flex child in place of the component it is wrapping, you must copy the size attibutes to this componenet.
|
||||
There are three scroll types:
|
||||
- h-scroll Determines how the horizontal scroll bar will be displayed.
|
||||
- v-scroll Determines how the vertical scroll bar will be displayed.
|
||||
- scroll Sets both h-scroll and v-scroll at once.
|
||||
Syntax: :auto [DEFAULT] Only show scroll bar(s) if the content is larger than the scroller.
|
||||
:on Always show scroll bar(s).
|
||||
:off Never show scroll bar(s). Content which is not in the bounds of the scroller can not be seen.
|
||||
:spill Never show scroll bar(s). Content which is not in the bounds of the scroller spills all over the place.
|
||||
Note: If scroll is set, then setting h-scroll or v-scroll overrides the scroll value"
|
||||
[& {:keys [size scroll h-scroll v-scroll width height min-width min-height max-width max-height justify align align-self margin padding child class style attr]
|
||||
:or {size "auto"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro scroller-args-desc args "scroller")]}
|
||||
(let [not-v-or-h (and (nil? v-scroll) (nil? h-scroll))
|
||||
scroll (if (and (nil? scroll) not-v-or-h) :auto scroll)]
|
||||
(box-base :size size
|
||||
:scroll scroll
|
||||
:h-scroll h-scroll
|
||||
:v-scroll v-scroll
|
||||
:width width
|
||||
:height height
|
||||
:min-width min-width
|
||||
:min-height min-height
|
||||
:max-width max-width
|
||||
:max-height max-height
|
||||
:justify justify
|
||||
:align align
|
||||
:align-self align-self
|
||||
:margin margin
|
||||
:padding padding
|
||||
:child child
|
||||
:class-name "rc-scroller "
|
||||
:class class
|
||||
:style style
|
||||
:attr attr)))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: border
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def border-args-desc
|
||||
[{:name :child :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "a component (or string)"}
|
||||
{:name :border :required false :default "1px solid lightgrey" :type "string" :validate-fn string? :description "a CSS border style. A convenience to describe all borders in one parameter"}
|
||||
{:name :l-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the left border. Overrides " [:code ":border"]]}
|
||||
{:name :r-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the right border. Overrides " [:code ":border"]]}
|
||||
{:name :t-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the top border. Overrides " [:code ":border"]]}
|
||||
{:name :b-border :required false :type "string" :validate-fn string? :description [:span "a CSS border style for the bottom. Overrides " [:code ":border"]]}
|
||||
{:name :radius :required false :type "string" :validate-fn string? :description "a CSS radius style eg.\"2px\""}
|
||||
{:name :size :required false :default "none" :type "string" :validate-fn string? :description [:span "equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"]", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS style describing the initial width"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "a CSS style describing the initial height"}
|
||||
{:name :min-width :required false :type "string" :validate-fn string? :description "a CSS width style. The minimum width to which the box can shrink"}
|
||||
{:name :min-height :required false :type "string" :validate-fn string? :description "a CSS height style. The minimum height to which the box can shrink"}
|
||||
{:name :max-width :required false :type "string" :validate-fn string? :description "a CSS width style. The maximum width to which the box can grow"}
|
||||
{:name :max-height :required false :type "string" :validate-fn string? :description "a CSS height style. The maximum height to which the box can grow"}
|
||||
{:name :margin :required false :type "string" :validate-fn string? :description "a CSS margin style"}
|
||||
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS padding style"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn border
|
||||
"Returns hiccup which produces a border component.
|
||||
This is the way borders are added to boxes, in favour of adding the border attributes directly to the boxes themselves.
|
||||
border property syntax: '<border-width> || <border-style> || <color>'
|
||||
- border-width: thin, medium, thick or standard CSS size (e.g. 2px, 0.5em)
|
||||
- border-style: none, hidden, dotted, dashed, solid, double, groove, ridge, inset, outset
|
||||
- color: standard CSS color (e.g. grey #88ffee)"
|
||||
[& {:keys [size width height min-width min-height max-width max-height margin padding border l-border r-border t-border b-border radius child class style attr]
|
||||
:or {size "none"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro border-args-desc args "border")]}
|
||||
(let [no-border (every? nil? [border l-border r-border t-border b-border])
|
||||
default-border "1px solid lightgrey"]
|
||||
(box-base :size size
|
||||
:width width
|
||||
:height height
|
||||
:min-width min-width
|
||||
:min-height min-height
|
||||
:max-width max-width
|
||||
:max-height max-height
|
||||
:margin margin
|
||||
:padding padding
|
||||
:border (if no-border default-border border)
|
||||
:l-border l-border
|
||||
:r-border r-border
|
||||
:t-border t-border
|
||||
:b-border b-border
|
||||
:radius radius
|
||||
:child child
|
||||
:class-name "rc-border "
|
||||
:class class
|
||||
:style style
|
||||
:attr attr)))
|
||||
File diff suppressed because one or more lines are too long
659
resources/public/js/compiled/out/re_com/box.js
Normal file
659
resources/public/js/compiled/out/re_com/box.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/box.js.map
Normal file
1
resources/public/js/compiled/out/re_com/box.js.map
Normal file
File diff suppressed because one or more lines are too long
404
resources/public/js/compiled/out/re_com/buttons.cljs
Normal file
404
resources/public/js/compiled/out/re_com/buttons.cljs
Normal file
|
|
@ -0,0 +1,404 @@
|
|||
(ns re-com.buttons
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.util :refer [deref-or-value px]]
|
||||
[re-com.validate :refer [position? position-options-list button-size? button-sizes-list
|
||||
string-or-hiccup? css-style? html-attr? string-or-atom?] :refer-macros [validate-args-macro]]
|
||||
[re-com.popover :refer [popover-tooltip]]
|
||||
[re-com.box :refer [h-box v-box box gap line flex-child-style]]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: button
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def button-args-desc
|
||||
[{:name :label :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "label for the button"}
|
||||
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
||||
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
||||
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't click the button"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn button
|
||||
"Returns the markup for a basic button"
|
||||
[]
|
||||
(let [showing? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [label on-click tooltip tooltip-position disabled? class style attr]
|
||||
:or {class "btn-default"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro button-args-desc args "button")]}
|
||||
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
||||
(let [disabled? (deref-or-value disabled?)
|
||||
the-button [:button
|
||||
(merge
|
||||
{:class (str "rc-button btn " class)
|
||||
:style (merge
|
||||
(flex-child-style "none")
|
||||
style)
|
||||
:disabled disabled?
|
||||
:on-click (handler-fn
|
||||
(when (and on-click (not disabled?))
|
||||
(on-click event)))}
|
||||
(when tooltip
|
||||
{:on-mouse-over (handler-fn (reset! showing? true))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))})
|
||||
attr)
|
||||
label]]
|
||||
(when disabled?
|
||||
(reset! showing? false))
|
||||
[box
|
||||
:class "display-inline-flex"
|
||||
:align :start
|
||||
:child (if tooltip
|
||||
[popover-tooltip
|
||||
:label tooltip
|
||||
:position (if tooltip-position tooltip-position :below-center)
|
||||
:showing? showing?
|
||||
:anchor the-button]
|
||||
the-button)]))))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: md-circle-icon-button
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def md-circle-icon-button-args-desc
|
||||
[{:name :md-icon-name :required true :default "zmdi-plus" :type "string" :validate-fn string? :description [:span "the name of the icon." [:br] "For example, " [:code "\"zmdi-plus\""] " or " [:code "\"zmdi-undo\""]] }
|
||||
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
||||
{:name :size :required false :default :regular :type "keyword" :validate-fn button-size? :description [:span "one of " button-sizes-list]}
|
||||
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
||||
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :emphasise? :required false :default false :type "boolean" :description "if true, use emphasised styling so the button really stands out"}
|
||||
{:name :disabled? :required false :default false :type "boolean" :description "if true, the user can't click the button"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn md-circle-icon-button
|
||||
"a circular button containing a material design icon"
|
||||
[]
|
||||
(let [showing? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [md-icon-name on-click size tooltip tooltip-position emphasise? disabled? class style attr]
|
||||
:or {md-icon-name "zmdi-plus"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro md-circle-icon-button-args-desc args "md-circle-icon-button")]}
|
||||
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
||||
(let [the-button [:div
|
||||
(merge
|
||||
{:class (str
|
||||
"rc-md-circle-icon-button noselect "
|
||||
(case size
|
||||
:smaller "rc-circle-smaller "
|
||||
:larger "rc-circle-larger "
|
||||
" ")
|
||||
(when emphasise? "rc-circle-emphasis ")
|
||||
(when disabled? "rc-circle-disabled ")
|
||||
class)
|
||||
:style (merge
|
||||
{:cursor (when-not disabled? "pointer")}
|
||||
style)
|
||||
:on-click (handler-fn
|
||||
(when (and on-click (not disabled?))
|
||||
(on-click event)))}
|
||||
(when tooltip
|
||||
{:on-mouse-over (handler-fn (reset! showing? true))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))})
|
||||
attr)
|
||||
[:i {:class (str "zmdi zmdi-hc-fw-rc " md-icon-name)}]]]
|
||||
(if tooltip
|
||||
[popover-tooltip
|
||||
:label tooltip
|
||||
:position (if tooltip-position tooltip-position :below-center)
|
||||
:showing? showing?
|
||||
:anchor the-button]
|
||||
the-button)))))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: md-icon-button
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def md-icon-button-args-desc
|
||||
[{:name :md-icon-name :required true :default "zmdi-plus" :type "string" :validate-fn string? :description [:span "the name of the icon." [:br] "For example, " [:code "\"zmdi-plus\""] " or " [:code "\"zmdi-undo\""]]}
|
||||
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
||||
{:name :size :required false :default :regular :type "keyword" :validate-fn button-size? :description [:span "one of " button-sizes-list]}
|
||||
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
||||
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :emphasise? :required false :default false :type "boolean" :description "if true, use emphasised styling so the button really stands out"}
|
||||
{:name :disabled? :required false :default false :type "boolean" :description "if true, the user can't click the button"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn md-icon-button
|
||||
"a square button containing a material design icon"
|
||||
[]
|
||||
(let [showing? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [md-icon-name on-click size tooltip tooltip-position emphasise? disabled? class style attr]
|
||||
:or {md-icon-name "zmdi-plus"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro md-icon-button-args-desc args "md-icon-button")]}
|
||||
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
||||
(let [the-button [:div
|
||||
(merge
|
||||
{:class (str
|
||||
"rc-md-icon-button noselect "
|
||||
(case size
|
||||
:smaller "rc-icon-smaller "
|
||||
:larger "rc-icon-larger "
|
||||
" ")
|
||||
(when emphasise? "rc-icon-emphasis ")
|
||||
(when disabled? "rc-icon-disabled ")
|
||||
class)
|
||||
:style (merge
|
||||
{:cursor (when-not disabled? "pointer")}
|
||||
style)
|
||||
:on-click (handler-fn
|
||||
(when (and on-click (not disabled?))
|
||||
(on-click event)))}
|
||||
(when tooltip
|
||||
{:on-mouse-over (handler-fn (reset! showing? true))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))})
|
||||
attr)
|
||||
[:i {:class (str "zmdi zmdi-hc-fw-rc " md-icon-name)}]]]
|
||||
(if tooltip
|
||||
[popover-tooltip
|
||||
:label tooltip
|
||||
:position (if tooltip-position tooltip-position :below-center)
|
||||
:showing? showing?
|
||||
:anchor the-button]
|
||||
the-button)))))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: info-button
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def info-button-args-desc
|
||||
[{:name :info :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "what's shown in the popover"}
|
||||
{:name :position :required false :default :right-below :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :width :required false :default "250px" :type "string" :validate-fn string? :description "width in px"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn info-button
|
||||
"A tiny light grey button, with an 'i' in it. Meant to be unobrusive.
|
||||
When pressed, displays a popup assumidly containing helpful information.
|
||||
Primarily designed to be nestled against the label of an input field, explaining the purpose of that field.
|
||||
Create a very small \"i\" icon via SVG"
|
||||
[]
|
||||
(let [showing? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [info position width class style attr] :as args}]
|
||||
{:pre [(validate-args-macro info-button-args-desc args "info-button")]}
|
||||
[popover-tooltip
|
||||
:label info
|
||||
:status :info
|
||||
:position (if position position :right-below)
|
||||
:width (if width width "250px")
|
||||
:showing? showing?
|
||||
:on-cancel #(swap! showing? not)
|
||||
:anchor [:div
|
||||
(merge
|
||||
{:class (str "rc-info-button noselect " class)
|
||||
:style (merge {:cursor "pointer"} style)
|
||||
:on-click (handler-fn (swap! showing? not))}
|
||||
attr)
|
||||
[:svg {:width "11" :height "11"}
|
||||
[:circle {:cx "5.5" :cy "5.5" :r "5.5"}]
|
||||
[:circle {:cx "5.5" :cy "2.5" :r "1.4" :fill "white"}]
|
||||
[:line {:x1 "5.5" :y1 "5.2" :x2 "5.5" :y2 "9.7" :stroke "white" :stroke-width "2.5"}]]]])))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: row-button
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def row-button-args-desc
|
||||
[{:name :md-icon-name :required true :default "zmdi-plus" :type "string" :validate-fn string? :description [:span "the name of the icon." [:br] "For example, " [:code "\"zmdi-plus\""] " or " [:code "\"zmdi-undo\""]]}
|
||||
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
||||
{:name :mouse-over-row? :required false :default false :type "boolean" :description "true if the mouse is hovering over the row"}
|
||||
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
||||
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :disabled? :required false :default false :type "boolean" :description "if true, the user can't click the button"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn row-button
|
||||
"a small button containing a material design icon"
|
||||
[]
|
||||
(let [showing? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [md-icon-name on-click mouse-over-row? tooltip tooltip-position disabled? class style attr]
|
||||
:or {md-icon-name "zmdi-plus"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro row-button-args-desc args "row-button")]}
|
||||
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
||||
(let [the-button [:div
|
||||
(merge
|
||||
{:class (str
|
||||
"rc-row-button noselect "
|
||||
(when mouse-over-row? "rc-row-mouse-over-row ")
|
||||
(when disabled? "rc-row-disabled ")
|
||||
class)
|
||||
:style style
|
||||
:on-click (handler-fn
|
||||
(when (and on-click (not disabled?))
|
||||
(on-click event)))}
|
||||
(when tooltip
|
||||
{:on-mouse-over (handler-fn (reset! showing? true))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))}) ;; Need to return true to ALLOW default events to be performed
|
||||
attr)
|
||||
[:i {:class (str "zmdi zmdi-hc-fw-rc " md-icon-name)}]]]
|
||||
(if tooltip
|
||||
[popover-tooltip
|
||||
:label tooltip
|
||||
:position (if tooltip-position tooltip-position :below-center)
|
||||
:showing? showing?
|
||||
:anchor the-button]
|
||||
the-button)))))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: hyperlink
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def hyperlink-args-desc
|
||||
[{:name :label :required true :type "string | hiccup | atom" :validate-fn string-or-hiccup? :description "label/hiccup for the button"}
|
||||
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the button is clicked"}
|
||||
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
||||
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't click the button"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn hyperlink
|
||||
"Renders an underlined text hyperlink component.
|
||||
This is very similar to the button component above but styled to looks like a hyperlink.
|
||||
Useful for providing button functionality for less important functions, e.g. Cancel"
|
||||
[]
|
||||
(let [showing? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [label on-click tooltip tooltip-position disabled? class style attr] :as args}]
|
||||
{:pre [(validate-args-macro hyperlink-args-desc args "hyperlink")]}
|
||||
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
||||
(let [label (deref-or-value label)
|
||||
disabled? (deref-or-value disabled?)
|
||||
the-button [box
|
||||
:align :start
|
||||
:child [:a
|
||||
(merge
|
||||
{:class (str "rc-hyperlink noselect " class)
|
||||
:style (merge
|
||||
(flex-child-style "none")
|
||||
{:cursor (if disabled? "not-allowed" "pointer")
|
||||
:color (when disabled? "grey")}
|
||||
style)
|
||||
:on-click (handler-fn
|
||||
(when (and on-click (not disabled?))
|
||||
(on-click event)))}
|
||||
(when tooltip
|
||||
{:on-mouse-over (handler-fn (reset! showing? true))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))})
|
||||
attr)
|
||||
label]]]
|
||||
(if tooltip
|
||||
[popover-tooltip
|
||||
:label tooltip
|
||||
:position (if tooltip-position tooltip-position :below-center)
|
||||
:showing? showing?
|
||||
:anchor the-button]
|
||||
the-button)))))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: hyperlink-href
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def hyperlink-href-args-desc
|
||||
[{:name :label :required true :type "string | hiccup | atom" :validate-fn string-or-hiccup? :description "label/hiccup for the button"}
|
||||
{:name :href :required true :type "string | atom" :validate-fn string-or-atom? :description "if specified, the link target URL"}
|
||||
{:name :target :required false :default "_self" :type "string | atom" :validate-fn string-or-atom? :description "one of \"_self\" or \"_blank\""}
|
||||
{:name :tooltip :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "what to show in the tooltip"}
|
||||
{:name :tooltip-position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn hyperlink-href
|
||||
"Renders an underlined text hyperlink component.
|
||||
This is very similar to the button component above but styled to looks like a hyperlink.
|
||||
Useful for providing button functionality for less important functions, e.g. Cancel"
|
||||
[]
|
||||
(let [showing? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [label href target tooltip tooltip-position class style attr] :as args}]
|
||||
{:pre [(validate-args-macro hyperlink-href-args-desc args "hyperlink-href")]}
|
||||
(when-not tooltip (reset! showing? false)) ;; To prevent tooltip from still showing after button drag/drop
|
||||
(let [label (deref-or-value label)
|
||||
href (deref-or-value href)
|
||||
target (deref-or-value target)
|
||||
the-button [:a
|
||||
(merge {:class (str "rc-hyperlink-href noselect " class)
|
||||
:style (merge (flex-child-style "none")
|
||||
style)
|
||||
:href href
|
||||
:target target}
|
||||
(when tooltip
|
||||
{:on-mouse-over (handler-fn (reset! showing? true))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))})
|
||||
attr)
|
||||
label]]
|
||||
|
||||
(if tooltip
|
||||
[popover-tooltip
|
||||
:label tooltip
|
||||
:position (if tooltip-position tooltip-position :below-center)
|
||||
:showing? showing?
|
||||
:anchor the-button]
|
||||
the-button)))))
|
||||
|
||||
|
||||
;; TODO: Eventually remove
|
||||
;;----------------------------------------------------------------------
|
||||
;; Round button with no dependencies - for use in re-frame demo
|
||||
;;----------------------------------------------------------------------
|
||||
|
||||
#_(defn round-button
|
||||
"a circular button containing a material design icon"
|
||||
[]
|
||||
(let [mouse-over? (reagent/atom false)]
|
||||
(fn
|
||||
[& {:keys [md-icon-name on-click disabled? style attr]
|
||||
:or {md-icon-name "md-add"}}]
|
||||
[:div
|
||||
(merge
|
||||
{:style (merge
|
||||
{:cursor (when-not disabled? "pointer")
|
||||
:font-size "24px"
|
||||
:width "40px"
|
||||
:height "40px"
|
||||
:line-height "44px"
|
||||
:text-align "center"
|
||||
:-webkit-user-select "none"}
|
||||
(if disabled?
|
||||
{:color "lightgrey"}
|
||||
{:border (str "1px solid " (if @mouse-over? "#428bca" "lightgrey"))
|
||||
:color (when @mouse-over? "#428bca")
|
||||
:border-radius "50%"})
|
||||
style)
|
||||
:on-mouse-over #(do (reset! mouse-over? true) nil)
|
||||
:on-mouse-out #(do (reset! mouse-over? false) nil)
|
||||
:on-click #(when (and on-click (not disabled?))
|
||||
(on-click %)
|
||||
nil)}
|
||||
attr)
|
||||
[:i {:class md-icon-name}]])))
|
||||
File diff suppressed because one or more lines are too long
594
resources/public/js/compiled/out/re_com/buttons.js
Normal file
594
resources/public/js/compiled/out/re_com/buttons.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/buttons.js.map
Normal file
1
resources/public/js/compiled/out/re_com/buttons.js.map
Normal file
File diff suppressed because one or more lines are too long
89
resources/public/js/compiled/out/re_com/core.cljs
Normal file
89
resources/public/js/compiled/out/re_com/core.cljs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
(ns re-com.core
|
||||
(:require [re-com.alert :as alert]
|
||||
[re-com.box :as box]
|
||||
[re-com.buttons :as buttons]
|
||||
[re-com.datepicker :as datepicker]
|
||||
[re-com.dropdown :as dropdown]
|
||||
[re-com.typeahead :as typeahead]
|
||||
[re-com.input-time :as input-time]
|
||||
[re-com.splits :as splits]
|
||||
[re-com.misc :as misc]
|
||||
[re-com.modal-panel :as modal-panel]
|
||||
[re-com.popover :as popover]
|
||||
[re-com.selection-list :as selection-list]
|
||||
[re-com.tabs :as tabs]
|
||||
[re-com.text :as text]
|
||||
[re-com.tour :as tour]))
|
||||
|
||||
;; -----------------------------------------------------------------------------
|
||||
;; re-com public API (see also re-com.util)
|
||||
;; -----------------------------------------------------------------------------
|
||||
|
||||
(def alert-box alert/alert-box)
|
||||
(def alert-list alert/alert-list)
|
||||
|
||||
(def flex-child-style box/flex-child-style)
|
||||
(def flex-flow-style box/flex-flow-style)
|
||||
(def justify-style box/justify-style)
|
||||
(def align-style box/align-style)
|
||||
(def scroll-style box/scroll-style)
|
||||
|
||||
(def h-box box/h-box)
|
||||
(def v-box box/v-box)
|
||||
(def box box/box)
|
||||
(def line box/line)
|
||||
(def gap box/gap)
|
||||
(def scroller box/scroller)
|
||||
(def border box/border)
|
||||
|
||||
(def button buttons/button)
|
||||
(def md-circle-icon-button buttons/md-circle-icon-button)
|
||||
(def md-icon-button buttons/md-icon-button)
|
||||
(def info-button buttons/info-button)
|
||||
(def row-button buttons/row-button)
|
||||
(def hyperlink buttons/hyperlink)
|
||||
(def hyperlink-href buttons/hyperlink-href)
|
||||
|
||||
(def datepicker datepicker/datepicker)
|
||||
(def datepicker-dropdown datepicker/datepicker-dropdown)
|
||||
|
||||
(def single-dropdown dropdown/single-dropdown)
|
||||
|
||||
(def typeahead typeahead/typeahead)
|
||||
|
||||
(def input-time input-time/input-time)
|
||||
|
||||
(def h-split splits/h-split)
|
||||
(def v-split splits/v-split)
|
||||
|
||||
(def input-text misc/input-text)
|
||||
(def input-password misc/input-password)
|
||||
(def input-textarea misc/input-textarea)
|
||||
(def checkbox misc/checkbox)
|
||||
(def radio-button misc/radio-button)
|
||||
(def slider misc/slider)
|
||||
(def progress-bar misc/progress-bar)
|
||||
(def throbber misc/throbber)
|
||||
|
||||
(def modal-panel modal-panel/modal-panel)
|
||||
|
||||
(def popover-content-wrapper popover/popover-content-wrapper)
|
||||
(def popover-anchor-wrapper popover/popover-anchor-wrapper)
|
||||
(def popover-border popover/popover-border)
|
||||
(def popover-tooltip popover/popover-tooltip)
|
||||
|
||||
(def selection-list selection-list/selection-list)
|
||||
|
||||
(def horizontal-tabs tabs/horizontal-tabs)
|
||||
(def horizontal-bar-tabs tabs/horizontal-bar-tabs)
|
||||
(def vertical-bar-tabs tabs/vertical-bar-tabs)
|
||||
(def horizontal-pill-tabs tabs/horizontal-pill-tabs)
|
||||
(def vertical-pill-tabs tabs/vertical-pill-tabs)
|
||||
|
||||
(def label text/label)
|
||||
(def title text/title)
|
||||
(def p text/p)
|
||||
|
||||
(def make-tour tour/make-tour)
|
||||
(def start-tour tour/start-tour)
|
||||
(def make-tour-nav tour/make-tour-nav)
|
||||
File diff suppressed because one or more lines are too long
73
resources/public/js/compiled/out/re_com/core.js
Normal file
73
resources/public/js/compiled/out/re_com/core.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
// Compiled by ClojureScript 1.9.229 {}
|
||||
goog.provide('re_com.core');
|
||||
goog.require('cljs.core');
|
||||
goog.require('re_com.modal_panel');
|
||||
goog.require('re_com.text');
|
||||
goog.require('re_com.popover');
|
||||
goog.require('re_com.box');
|
||||
goog.require('re_com.buttons');
|
||||
goog.require('re_com.splits');
|
||||
goog.require('re_com.selection_list');
|
||||
goog.require('re_com.tabs');
|
||||
goog.require('re_com.typeahead');
|
||||
goog.require('re_com.datepicker');
|
||||
goog.require('re_com.alert');
|
||||
goog.require('re_com.dropdown');
|
||||
goog.require('re_com.input_time');
|
||||
goog.require('re_com.tour');
|
||||
goog.require('re_com.misc');
|
||||
re_com.core.alert_box = re_com.alert.alert_box;
|
||||
re_com.core.alert_list = re_com.alert.alert_list;
|
||||
re_com.core.flex_child_style = re_com.box.flex_child_style;
|
||||
re_com.core.flex_flow_style = re_com.box.flex_flow_style;
|
||||
re_com.core.justify_style = re_com.box.justify_style;
|
||||
re_com.core.align_style = re_com.box.align_style;
|
||||
re_com.core.scroll_style = re_com.box.scroll_style;
|
||||
re_com.core.h_box = re_com.box.h_box;
|
||||
re_com.core.v_box = re_com.box.v_box;
|
||||
re_com.core.box = re_com.box.box;
|
||||
re_com.core.line = re_com.box.line;
|
||||
re_com.core.gap = re_com.box.gap;
|
||||
re_com.core.scroller = re_com.box.scroller;
|
||||
re_com.core.border = re_com.box.border;
|
||||
re_com.core.button = re_com.buttons.button;
|
||||
re_com.core.md_circle_icon_button = re_com.buttons.md_circle_icon_button;
|
||||
re_com.core.md_icon_button = re_com.buttons.md_icon_button;
|
||||
re_com.core.info_button = re_com.buttons.info_button;
|
||||
re_com.core.row_button = re_com.buttons.row_button;
|
||||
re_com.core.hyperlink = re_com.buttons.hyperlink;
|
||||
re_com.core.hyperlink_href = re_com.buttons.hyperlink_href;
|
||||
re_com.core.datepicker = re_com.datepicker.datepicker;
|
||||
re_com.core.datepicker_dropdown = re_com.datepicker.datepicker_dropdown;
|
||||
re_com.core.single_dropdown = re_com.dropdown.single_dropdown;
|
||||
re_com.core.typeahead = re_com.typeahead.typeahead;
|
||||
re_com.core.input_time = re_com.input_time.input_time;
|
||||
re_com.core.h_split = re_com.splits.h_split;
|
||||
re_com.core.v_split = re_com.splits.v_split;
|
||||
re_com.core.input_text = re_com.misc.input_text;
|
||||
re_com.core.input_password = re_com.misc.input_password;
|
||||
re_com.core.input_textarea = re_com.misc.input_textarea;
|
||||
re_com.core.checkbox = re_com.misc.checkbox;
|
||||
re_com.core.radio_button = re_com.misc.radio_button;
|
||||
re_com.core.slider = re_com.misc.slider;
|
||||
re_com.core.progress_bar = re_com.misc.progress_bar;
|
||||
re_com.core.throbber = re_com.misc.throbber;
|
||||
re_com.core.modal_panel = re_com.modal_panel.modal_panel;
|
||||
re_com.core.popover_content_wrapper = re_com.popover.popover_content_wrapper;
|
||||
re_com.core.popover_anchor_wrapper = re_com.popover.popover_anchor_wrapper;
|
||||
re_com.core.popover_border = re_com.popover.popover_border;
|
||||
re_com.core.popover_tooltip = re_com.popover.popover_tooltip;
|
||||
re_com.core.selection_list = re_com.selection_list.selection_list;
|
||||
re_com.core.horizontal_tabs = re_com.tabs.horizontal_tabs;
|
||||
re_com.core.horizontal_bar_tabs = re_com.tabs.horizontal_bar_tabs;
|
||||
re_com.core.vertical_bar_tabs = re_com.tabs.vertical_bar_tabs;
|
||||
re_com.core.horizontal_pill_tabs = re_com.tabs.horizontal_pill_tabs;
|
||||
re_com.core.vertical_pill_tabs = re_com.tabs.vertical_pill_tabs;
|
||||
re_com.core.label = re_com.text.label;
|
||||
re_com.core.title = re_com.text.title;
|
||||
re_com.core.p = re_com.text.p;
|
||||
re_com.core.make_tour = re_com.tour.make_tour;
|
||||
re_com.core.start_tour = re_com.tour.start_tour;
|
||||
re_com.core.make_tour_nav = re_com.tour.make_tour_nav;
|
||||
|
||||
//# sourceMappingURL=core.js.map?rel=1603199197089
|
||||
1
resources/public/js/compiled/out/re_com/core.js.map
Normal file
1
resources/public/js/compiled/out/re_com/core.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"\/Users\/simon\/workspace\/swinging-needle-meter\/docs\/js\/compiled\/out\/re_com\/core.js","sources":["core.cljs?rel=1603199197090"],"lineCount":73,"mappings":";AAAA;;;;;;;;;;;;;;;;;AAqBA,AAAKA,wBAA2BC;AAChC,AAAKC,yBAA2BC;AAEhC,AAAKC,+BAA2BC;AAChC,AAAKC,8BAA2BC;AAChC,AAAKC,4BAA2BC;AAChC,AAAKC,0BAA2BC;AAChC,AAAKC,2BAA2BC;AAEhC,AAAKC,oBAA2BC;AAChC,AAAKC,oBAA2BC;AAChC,AAAKC,kBAA2BC;AAChC,AAAKC,mBAA2BC;AAChC,AAAKC,kBAA2BC;AAChC,AAAKC,uBAA2BC;AAChC,AAAKC,qBAA2BC;AAEhC,AAAKC,qBAA2BC;AAChC,AAAKC,oCAA2BC;AAChC,AAAKC,6BAA2BC;AAChC,AAAKC,0BAA2BC;AAChC,AAAKC,yBAA2BC;AAChC,AAAKC,wBAA2BC;AAChC,AAAKC,6BAA2BC;AAEhC,AAAKC,yBAA2BC;AAChC,AAAKC,kCAA2BC;AAEhC,AAAKC,8BAA2BC;AAEhC,AAAKC,wBAA2BC;AAEhC,AAAKC,yBAA2BC;AAEhC,AAAKC,sBAA2BC;AAChC,AAAKC,sBAA2BC;AAEhC,AAAKC,yBAA2BC;AAChC,AAAKC,6BAA2BC;AAChC,AAAKC,6BAA2BC;AAChC,AAAKC,uBAA2BC;AAChC,AAAKC,2BAA2BC;AAChC,AAAKC,qBAA2BC;AAChC,AAAKC,2BAA2BC;AAChC,AAAKC,uBAA2BC;AAEhC,AAAKC,0BAA2BC;AAEhC,AAAKC,sCAA2BC;AAChC,AAAKC,qCAA2BC;AAChC,AAAKC,6BAA2BC;AAChC,AAAKC,8BAA2BC;AAEhC,AAAKC,6BAA2BC;AAEhC,AAAKC,8BAA2BC;AAChC,AAAKC,kCAA2BC;AAChC,AAAKC,gCAA2BC;AAChC,AAAKC,mCAA2BC;AAChC,AAAKC,iCAA2BC;AAEhC,AAAKC,oBAA2BC;AAChC,AAAKC,oBAA2BC;AAChC,AAAKC,gBAA2BC;AAEhC,AAAKC,wBAA2BC;AAChC,AAAKC,yBAA2BC;AAChC,AAAKC,4BAA2BC","names":["re-com.core\/alert-box","re-com.alert\/alert-box","re-com.core\/alert-list","re-com.alert\/alert-list","re-com.core\/flex-child-style","re-com.box\/flex-child-style","re-com.core\/flex-flow-style","re-com.box\/flex-flow-style","re-com.core\/justify-style","re-com.box\/justify-style","re-com.core\/align-style","re-com.box\/align-style","re-com.core\/scroll-style","re-com.box\/scroll-style","re-com.core\/h-box","re-com.box\/h-box","re-com.core\/v-box","re-com.box\/v-box","re-com.core\/box","re-com.box\/box","re-com.core\/line","re-com.box\/line","re-com.core\/gap","re-com.box\/gap","re-com.core\/scroller","re-com.box\/scroller","re-com.core\/border","re-com.box\/border","re-com.core\/button","re-com.buttons\/button","re-com.core\/md-circle-icon-button","re-com.buttons\/md-circle-icon-button","re-com.core\/md-icon-button","re-com.buttons\/md-icon-button","re-com.core\/info-button","re-com.buttons\/info-button","re-com.core\/row-button","re-com.buttons\/row-button","re-com.core\/hyperlink","re-com.buttons\/hyperlink","re-com.core\/hyperlink-href","re-com.buttons\/hyperlink-href","re-com.core\/datepicker","re-com.datepicker\/datepicker","re-com.core\/datepicker-dropdown","re-com.datepicker\/datepicker-dropdown","re-com.core\/single-dropdown","re-com.dropdown\/single-dropdown","re-com.core\/typeahead","re-com.typeahead\/typeahead","re-com.core\/input-time","re-com.input-time\/input-time","re-com.core\/h-split","re-com.splits\/h-split","re-com.core\/v-split","re-com.splits\/v-split","re-com.core\/input-text","re-com.misc\/input-text","re-com.core\/input-password","re-com.misc\/input-password","re-com.core\/input-textarea","re-com.misc\/input-textarea","re-com.core\/checkbox","re-com.misc\/checkbox","re-com.core\/radio-button","re-com.misc\/radio-button","re-com.core\/slider","re-com.misc\/slider","re-com.core\/progress-bar","re-com.misc\/progress-bar","re-com.core\/throbber","re-com.misc\/throbber","re-com.core\/modal-panel","re-com.modal-panel\/modal-panel","re-com.core\/popover-content-wrapper","re-com.popover\/popover-content-wrapper","re-com.core\/popover-anchor-wrapper","re-com.popover\/popover-anchor-wrapper","re-com.core\/popover-border","re-com.popover\/popover-border","re-com.core\/popover-tooltip","re-com.popover\/popover-tooltip","re-com.core\/selection-list","re-com.selection-list\/selection-list","re-com.core\/horizontal-tabs","re-com.tabs\/horizontal-tabs","re-com.core\/horizontal-bar-tabs","re-com.tabs\/horizontal-bar-tabs","re-com.core\/vertical-bar-tabs","re-com.tabs\/vertical-bar-tabs","re-com.core\/horizontal-pill-tabs","re-com.tabs\/horizontal-pill-tabs","re-com.core\/vertical-pill-tabs","re-com.tabs\/vertical-pill-tabs","re-com.core\/label","re-com.text\/label","re-com.core\/title","re-com.text\/title","re-com.core\/p","re-com.text\/p","re-com.core\/make-tour","re-com.tour\/make-tour","re-com.core\/start-tour","re-com.tour\/start-tour","re-com.core\/make-tour-nav","re-com.tour\/make-tour-nav"]}
|
||||
290
resources/public/js/compiled/out/re_com/datepicker.cljs
Normal file
290
resources/public/js/compiled/out/re_com/datepicker.cljs
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
(ns re-com.datepicker
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require
|
||||
[reagent.core :as reagent]
|
||||
[cljs-time.core :refer [now minus plus months days year month day day-of-week first-day-of-the-month before? after?]]
|
||||
[re-com.validate :refer [goog-date? css-style? html-attr?] :refer-macros [validate-args-macro]]
|
||||
[cljs-time.predicates :refer [sunday?]]
|
||||
[cljs-time.format :refer [parse unparse formatters formatter]]
|
||||
[re-com.box :refer [border h-box flex-child-style]]
|
||||
[re-com.util :refer [deref-or-value now->utc]]
|
||||
[re-com.popover :refer [popover-anchor-wrapper popover-content-wrapper]]))
|
||||
|
||||
;; Loosely based on ideas: https://github.com/dangrossman/bootstrap-daterangepicker
|
||||
|
||||
;; --- cljs-time facades ------------------------------------------------------
|
||||
|
||||
(def ^:const month-format (formatter "MMMM yyyy"))
|
||||
|
||||
(def ^:const week-format (formatter "ww"))
|
||||
|
||||
(def ^:const date-format (formatter "yyyy MMM dd"))
|
||||
|
||||
(defn iso8601->date [iso8601]
|
||||
(when (seq iso8601)
|
||||
(parse (formatters :basic-date) iso8601)))
|
||||
|
||||
(defn- month-label [date] (unparse month-format date))
|
||||
|
||||
(defn- dec-month [date] (minus date (months 1)))
|
||||
|
||||
(defn- inc-month [date] (plus date (months 1)))
|
||||
|
||||
(defn- inc-date [date n] (plus date (days n)))
|
||||
|
||||
(defn previous
|
||||
"If date fails pred, subtract period until true, otherwise answer date"
|
||||
;; date - a goog.date.UtcDateTime now if ommited
|
||||
;; pred - can be one of cljs-time.predicate e.g. sunday? but anything that can deal with goog.date.UtcDateTime
|
||||
;; period - a period which will be subtracted see cljs-time.core periods
|
||||
;; Note: If period and pred do not represent same granularity, some steps may be skipped
|
||||
; e.g Pass a Wed date, specify sunday? as pred and a period (days 2) will skip one Sunday.
|
||||
([pred]
|
||||
(previous pred (now->utc)))
|
||||
([pred date]
|
||||
(previous pred date (days 1)))
|
||||
([pred date period]
|
||||
(if (pred date)
|
||||
date
|
||||
(recur pred (minus date period) period))))
|
||||
|
||||
(defn- =date [date1 date2]
|
||||
(and
|
||||
(= (year date1) (year date2))
|
||||
(= (month date1) (month date2))
|
||||
(= (day date1) (day date2))))
|
||||
|
||||
(defn- <=date [date1 date2]
|
||||
(or (=date date1 date2) (before? date1 date2)))
|
||||
|
||||
(defn- >=date [date1 date2]
|
||||
(or (=date date1 date2) (after? date1 date2)))
|
||||
|
||||
|
||||
(def ^:private days-vector
|
||||
[{:key :Mo :short-name "M" :name "MON"}
|
||||
{:key :Tu :short-name "T" :name "TUE"}
|
||||
{:key :We :short-name "W" :name "WED"}
|
||||
{:key :Th :short-name "T" :name "THU"}
|
||||
{:key :Fr :short-name "F" :name "FRI"}
|
||||
{:key :Sa :short-name "S" :name "SAT"}
|
||||
{:key :Su :short-name "S" :name "SUN"}])
|
||||
|
||||
(defn- rotate
|
||||
[n coll]
|
||||
(let [c (count coll)]
|
||||
(take c (drop (mod n c) (cycle coll)))))
|
||||
|
||||
(defn- is-day-pred [d]
|
||||
#(= (day-of-week %) (inc d)))
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
(defn- main-div-with
|
||||
[table-div hide-border? class style attr]
|
||||
;;extra h-box is currently necessary so that calendar & border do not strecth to width of any containing v-box
|
||||
[h-box
|
||||
:children [[border
|
||||
:radius "4px"
|
||||
:size "none"
|
||||
:border (when hide-border? "none")
|
||||
:child [:div
|
||||
(merge
|
||||
{:class (str "rc-datepicker datepicker noselect " class)
|
||||
;; override inherrited body larger 14px font-size
|
||||
;; override position from css because we are inline
|
||||
:style (merge {:font-size "13px"
|
||||
:position "static"}
|
||||
style)}
|
||||
attr)
|
||||
table-div]]]])
|
||||
|
||||
|
||||
(defn- table-thead
|
||||
"Answer 2 x rows showing month with nav buttons and days NOTE: not internationalized"
|
||||
[display-month {show-weeks? :show-weeks? minimum :minimum maximum :maximum start-of-week :start-of-week}]
|
||||
(let [prev-date (dec-month @display-month)
|
||||
;prev-enabled? (if minimum (after? prev-date (dec-month minimum)) true)
|
||||
prev-enabled? (if minimum (after? prev-date minimum) true)
|
||||
next-date (inc-month @display-month)
|
||||
next-enabled? (if maximum (before? next-date maximum) true)
|
||||
template-row (if show-weeks? [:tr [:th]] [:tr])]
|
||||
[:thead
|
||||
(conj template-row
|
||||
[:th {:class (str "prev " (if prev-enabled? "available selectable" "disabled"))
|
||||
:style {:padding "0px"}
|
||||
:on-click (handler-fn (when prev-enabled? (reset! display-month prev-date)))}
|
||||
[:i.zmdi.zmdi-chevron-left
|
||||
{:style {:font-size "24px"}}]]
|
||||
[:th {:class "month" :col-span "5"} (month-label @display-month)]
|
||||
[:th {:class (str "next " (if next-enabled? "available selectable" "disabled"))
|
||||
:style {:padding "0px"}
|
||||
:on-click (handler-fn (when next-enabled? (reset! display-month next-date)))}
|
||||
[:i.zmdi.zmdi-chevron-right
|
||||
{:style {:font-size "24px"}}]])
|
||||
(conj template-row
|
||||
(for [day (rotate start-of-week days-vector)]
|
||||
^{:key (:key day)} [:th {:class "day-enabled"} (str (:name day))]))]))
|
||||
|
||||
|
||||
(defn- selection-changed
|
||||
[selection change-callback]
|
||||
(change-callback selection))
|
||||
|
||||
|
||||
(defn- table-td
|
||||
[date focus-month selected today {minimum :minimum maximum :maximum :as attributes} disabled? on-change]
|
||||
;;following can be simplified and terse
|
||||
(let [enabled-min (if minimum (>=date date minimum) true)
|
||||
enabled-max (if maximum (<=date date maximum) true)
|
||||
enabled-day (and enabled-min enabled-max)
|
||||
disabled-day? (if enabled-day
|
||||
(not ((:selectable-fn attributes) date))
|
||||
true)
|
||||
classes (cond disabled? "off"
|
||||
disabled-day? "off"
|
||||
(= focus-month (month date)) "available"
|
||||
:else "available off")
|
||||
classes (cond (and selected (=date selected date)) (str classes " active start-date end-date")
|
||||
(and today (=date date today)) (str classes " today")
|
||||
:else classes)
|
||||
on-click #(when-not (or disabled? disabled-day?) (selection-changed date on-change))]
|
||||
[:td {:class classes
|
||||
:on-click (handler-fn (on-click))} (day date)]))
|
||||
|
||||
|
||||
(defn- week-td [date]
|
||||
[:td {:class "week"} (unparse week-format date)])
|
||||
|
||||
|
||||
(defn- table-tr
|
||||
"Return 7 columns of date cells from date inclusive"
|
||||
[date focus-month selected attributes disabled? on-change]
|
||||
; {:pre [(sunday? date)]}
|
||||
(let [table-row (if (:show-weeks? attributes) [:tr (week-td date)] [:tr])
|
||||
row-dates (map #(inc-date date %) (range 7))
|
||||
today (if (:show-today? attributes) (:today attributes) nil)]
|
||||
(into table-row (map #(table-td % focus-month selected today attributes disabled? on-change) row-dates))))
|
||||
|
||||
|
||||
(defn- table-tbody
|
||||
"Return matrix of 6 rows x 7 cols table cells representing 41 days from start-date inclusive"
|
||||
[display-month selected attributes disabled? on-change]
|
||||
(let [start-of-week (:start-of-week attributes)
|
||||
current-start (previous (is-day-pred start-of-week) display-month)
|
||||
focus-month (month display-month)
|
||||
row-start-dates (map #(inc-date current-start (* 7 %)) (range 6))]
|
||||
(into [:tbody] (map #(table-tr % focus-month selected attributes disabled? on-change) row-start-dates))))
|
||||
|
||||
|
||||
(defn- configure
|
||||
"Augment passed attributes with extra info/defaults"
|
||||
[attributes]
|
||||
(let [selectable-fn (if (-> attributes :selectable-fn fn?)
|
||||
(:selectable-fn attributes)
|
||||
(fn [date] true))]
|
||||
(merge attributes {:selectable-fn selectable-fn :today (now->utc)})))
|
||||
|
||||
(def datepicker-args-desc
|
||||
[{:name :model :required false :type "goog.date.UtcDateTime | atom" :validate-fn goog-date? :description "the selected date. If provided, should pass pred :selectable-fn"}
|
||||
{:name :on-change :required true :type "goog.date.UtcDateTime -> nil" :validate-fn fn? :description "called when a new selection is made"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "when true, the can't select dates but can navigate"}
|
||||
{:name :selectable-fn :required false :default "(fn [date] true)" :type "pred" :validate-fn fn? :description "Predicate is passed a date. If it answers false, day will be shown disabled and can't be selected."}
|
||||
{:name :show-weeks? :required false :default false :type "boolean" :description "when true, week numbers are shown to the left"}
|
||||
{:name :show-today? :required false :default false :type "boolean" :description "when true, today's date is highlighted"}
|
||||
{:name :minimum :required false :type "goog.date.UtcDateTime" :validate-fn goog-date? :description "no selection or navigation before this date"}
|
||||
{:name :maximum :required false :type "goog.date.UtcDateTime" :validate-fn goog-date? :description "no selection or navigation after this date"}
|
||||
{:name :start-of-week :required false :default 6 :type "int" :description "first day of week (Monday = 0 ... Sunday = 6)"}
|
||||
{:name :hide-border? :required false :default false :type "boolean" :description "when true, the border is not displayed"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn datepicker
|
||||
[& {:keys [model] :as args}]
|
||||
{:pre [(validate-args-macro datepicker-args-desc args "datepicker")]}
|
||||
(let [external-model (reagent/atom (deref-or-value model)) ;; Holds the last known external value of model, to detect external model changes
|
||||
internal-model (reagent/atom @external-model) ;; Create a new atom from the model to be used internally
|
||||
display-month (reagent/atom (first-day-of-the-month (or @internal-model (now))))]
|
||||
(fn datepicker-component
|
||||
[& {:keys [model disabled? hide-border? on-change start-of-week class style attr]
|
||||
:or {start-of-week 6} ;; Default to Sunday
|
||||
:as properties}]
|
||||
{:pre [(validate-args-macro datepicker-args-desc properties "datepicker")]}
|
||||
(let [latest-ext-model (deref-or-value model)
|
||||
props-with-defaults (merge properties {:start-of-week start-of-week})
|
||||
configuration (configure props-with-defaults)]
|
||||
(when (not= @external-model latest-ext-model) ;; Has model changed externally?
|
||||
(reset! external-model latest-ext-model)
|
||||
(reset! internal-model latest-ext-model)
|
||||
(reset! display-month (first-day-of-the-month (or @internal-model (now)))))
|
||||
[main-div-with
|
||||
[:table {:class "table-condensed"}
|
||||
[table-thead display-month configuration]
|
||||
[table-tbody
|
||||
@display-month
|
||||
@internal-model
|
||||
configuration
|
||||
(if (nil? disabled?) false (deref-or-value disabled?))
|
||||
on-change]]
|
||||
hide-border?
|
||||
class
|
||||
style
|
||||
attr]))))
|
||||
|
||||
|
||||
(defn- anchor-button
|
||||
"Provide clickable field with current date label and dropdown button e.g. [ 2014 Sep 17 | # ]"
|
||||
[shown? model format]
|
||||
[:div {:class "input-group display-flex noselect"
|
||||
:style (flex-child-style "none")
|
||||
:on-click (handler-fn (swap! shown? not))}
|
||||
[h-box
|
||||
:align :center
|
||||
:class "noselect"
|
||||
:min-width "10em"
|
||||
:children [[:label {:class "form-control dropdown-button"}
|
||||
(if (instance? js/goog.date.Date (deref-or-value model))
|
||||
(unparse (if (seq format) (formatter format) date-format) (deref-or-value model))
|
||||
"")]
|
||||
[:span.dropdown-button.activator.input-group-addon
|
||||
{:style {:padding "3px 0px 0px 0px"}}
|
||||
[:i.zmdi.zmdi-apps {:style {:font-size "24px"}}]]]]])
|
||||
|
||||
(def datepicker-dropdown-args-desc
|
||||
(conj datepicker-args-desc
|
||||
{:name :format :required false :default "yyyy MMM dd" :type "string" :description "[datepicker-dropdown only] a represenatation of a date format. See cljs_time.format"}
|
||||
{:name :no-clip? :required false :default true :type "boolean" :description "[datepicker-dropdown only] when an anchor is in a scrolling region (e.g. scroller component), the popover can sometimes be clipped. When this parameter is true (which is the default), re-com will use a different CSS method to show the popover. This method is slightly inferior because the popover can't track the anchor if it is repositioned"}))
|
||||
|
||||
(defn datepicker-dropdown
|
||||
[& {:as args}]
|
||||
{:pre [(validate-args-macro datepicker-dropdown-args-desc args "datepicker-dropdown")]}
|
||||
(let [shown? (reagent/atom false)
|
||||
cancel-popover #(reset! shown? false)
|
||||
position :below-left]
|
||||
(fn
|
||||
[& {:keys [model show-weeks? on-change format no-clip?]
|
||||
:or {no-clip? true}
|
||||
:as passthrough-args}]
|
||||
(let [collapse-on-select (fn [new-model]
|
||||
(reset! shown? false)
|
||||
(when on-change (on-change new-model))) ;; wrap callback to collapse popover
|
||||
passthrough-args (dissoc passthrough-args :format :no-clip?) ;; :format and :no-clip? only valid at this API level
|
||||
passthrough-args (->> (assoc passthrough-args :on-change collapse-on-select)
|
||||
(merge {:hide-border? true}) ;; apply defaults
|
||||
vec
|
||||
flatten)]
|
||||
[popover-anchor-wrapper
|
||||
:showing? shown?
|
||||
:position position
|
||||
:anchor [anchor-button shown? model format]
|
||||
:popover [popover-content-wrapper
|
||||
:position-offset (if show-weeks? 43 44)
|
||||
:no-clip? no-clip?
|
||||
:arrow-length 0
|
||||
:arrow-width 0
|
||||
:arrow-gap 3
|
||||
:padding "0px"
|
||||
:on-cancel cancel-popover
|
||||
:body (into [datepicker] passthrough-args)]]))))
|
||||
File diff suppressed because one or more lines are too long
523
resources/public/js/compiled/out/re_com/datepicker.js
Normal file
523
resources/public/js/compiled/out/re_com/datepicker.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
324
resources/public/js/compiled/out/re_com/dropdown.cljs
Normal file
324
resources/public/js/compiled/out/re_com/dropdown.cljs
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
(ns re-com.dropdown
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.util :refer [deref-or-value position-for-id item-for-id]]
|
||||
[re-com.box :refer [align-style flex-child-style]]
|
||||
[re-com.validate :refer [vector-of-maps? css-style? html-attr? number-or-string?] :refer-macros [validate-args-macro]]
|
||||
[clojure.string :as string]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
;; Inspiration: http://alxlit.name/bootstrap-chosen
|
||||
;; Alternative: http://silviomoreto.github.io/bootstrap-select
|
||||
|
||||
(defn- move-to-new-choice
|
||||
"In a vector of maps (where each map has an :id), return the id of the choice offset posititions away
|
||||
from id (usually +1 or -1 to go to next/previous). Also accepts :start and :end"
|
||||
[choices id-fn id offset]
|
||||
(let [current-index (position-for-id id choices :id-fn id-fn)
|
||||
new-index (cond
|
||||
(= offset :start) 0
|
||||
(= offset :end) (dec (count choices))
|
||||
(nil? current-index) 0
|
||||
:else (mod (+ current-index offset) (count choices)))]
|
||||
(when (and new-index (pos? (count choices)))
|
||||
(id-fn (nth choices new-index)))))
|
||||
|
||||
|
||||
(defn- choices-with-group-headings
|
||||
"If necessary, inserts group headings entries into the choices"
|
||||
[opts group-fn]
|
||||
(let [groups (partition-by group-fn opts)
|
||||
group-headers (->> groups
|
||||
(map first)
|
||||
(map group-fn)
|
||||
(map #(hash-map :id (gensym) :group %)))]
|
||||
[group-headers groups]))
|
||||
|
||||
|
||||
(defn- filter-choices
|
||||
"Filter a list of choices based on a filter string using plain string searches (case insensitive). Less powerful
|
||||
than regex's but no confusion with reserved characters"
|
||||
[choices group-fn label-fn filter-text]
|
||||
(let [lower-filter-text (string/lower-case filter-text)
|
||||
filter-fn (fn [opt]
|
||||
(let [group (if (nil? (group-fn opt)) "" (group-fn opt))
|
||||
label (str (label-fn opt))]
|
||||
(or
|
||||
(>= (.indexOf (string/lower-case group) lower-filter-text) 0)
|
||||
(>= (.indexOf (string/lower-case label) lower-filter-text) 0))))]
|
||||
(filter filter-fn choices)))
|
||||
|
||||
|
||||
(defn- filter-choices-regex
|
||||
"Filter a list of choices based on a filter string using regex's (case insensitive). More powerful but can cause
|
||||
confusion for users entering reserved characters such as [ ] * + . ( ) etc."
|
||||
[choices group-fn label-fn filter-text]
|
||||
(let [re (try
|
||||
(js/RegExp. filter-text "i")
|
||||
(catch js/Object e nil))
|
||||
filter-fn (partial (fn [re opt]
|
||||
(when-not (nil? re)
|
||||
(or (.test re (group-fn opt)) (.test re (label-fn opt)))))
|
||||
re)]
|
||||
(filter filter-fn choices)))
|
||||
|
||||
|
||||
(defn filter-choices-by-keyword
|
||||
"Filter a list of choices extra data within the choices vector"
|
||||
[choices keyword value]
|
||||
(let [filter-fn (fn [opt] (>= (.indexOf (keyword opt) value) 0))]
|
||||
(filter filter-fn choices)))
|
||||
|
||||
|
||||
(defn show-selected-item
|
||||
[node]
|
||||
(let [item-offset-top (.-offsetTop node)
|
||||
item-offset-bottom (+ item-offset-top (.-clientHeight node))
|
||||
parent (.-parentNode node)
|
||||
parent-height (.-clientHeight parent)
|
||||
parent-visible-top (.-scrollTop parent)
|
||||
parent-visible-bottom (+ parent-visible-top parent-height)
|
||||
new-scroll-top (cond
|
||||
(> item-offset-bottom parent-visible-bottom) (max (- item-offset-bottom parent-height) 0)
|
||||
(< item-offset-top parent-visible-top) item-offset-top)]
|
||||
(when new-scroll-top (set! (.-scrollTop parent) new-scroll-top))))
|
||||
|
||||
|
||||
(defn- make-group-heading
|
||||
"Render a group heading"
|
||||
[m]
|
||||
^{:key (:id m)} [:li.group-result
|
||||
(:group m)])
|
||||
|
||||
|
||||
(defn- choice-item
|
||||
"Render a choice item and set up appropriate mouse events"
|
||||
[id label on-click internal-model]
|
||||
(let [mouse-over? (reagent/atom false)]
|
||||
(reagent/create-class
|
||||
{:component-did-mount
|
||||
(fn [this]
|
||||
(let [node (reagent/dom-node this)
|
||||
selected (= @internal-model id)]
|
||||
(when selected (show-selected-item node))))
|
||||
|
||||
:component-did-update
|
||||
(fn [this]
|
||||
(let [node (reagent/dom-node this)
|
||||
selected (= @internal-model id)]
|
||||
(when selected (show-selected-item node))))
|
||||
|
||||
:display-name "choice-item"
|
||||
|
||||
:reagent-render
|
||||
(fn
|
||||
[id label on-click internal-model]
|
||||
(let [selected (= @internal-model id)
|
||||
class (if selected
|
||||
"highlighted"
|
||||
(when @mouse-over? "mouseover"))]
|
||||
[:li
|
||||
{:class (str "active-result group-option " class)
|
||||
:on-mouse-over (handler-fn (reset! mouse-over? true))
|
||||
:on-mouse-out (handler-fn (reset! mouse-over? false))
|
||||
:on-mouse-down (handler-fn (on-click id))}
|
||||
label]))})))
|
||||
|
||||
|
||||
(defn make-choice-item
|
||||
[id-fn render-fn callback internal-model opt]
|
||||
(let [id (id-fn opt)
|
||||
markup (render-fn opt)]
|
||||
^{:key (str id)} [choice-item id markup callback internal-model]))
|
||||
|
||||
|
||||
(defn- filter-text-box-base
|
||||
"Base function (before lifecycle metadata) to render a filter text box"
|
||||
[]
|
||||
(fn [filter-box? filter-text key-handler drop-showing?]
|
||||
[:div.chosen-search
|
||||
[:input
|
||||
{:type "text"
|
||||
:auto-complete "off"
|
||||
:style (when-not filter-box? {:position "absolute" ;; When no filter box required, use it but hide it off screen
|
||||
:width "0px" ;; The rest of these styles make the textbox invisible
|
||||
:padding "0px"
|
||||
:border "none"})
|
||||
:value @filter-text
|
||||
:on-change (handler-fn (reset! filter-text (-> event .-target .-value)))
|
||||
:on-key-down (handler-fn (when-not (key-handler event)
|
||||
(.preventDefault event))) ;; When key-handler returns false, preventDefault
|
||||
:on-blur (handler-fn (reset! drop-showing? false))}]]))
|
||||
|
||||
|
||||
(def ^:private filter-text-box
|
||||
"Render a filter text box"
|
||||
(with-meta filter-text-box-base
|
||||
{:component-did-mount #(let [node (.-firstChild (reagent/dom-node %))]
|
||||
(.focus node))
|
||||
:component-did-update #(let [node (.-firstChild (reagent/dom-node %))]
|
||||
(.focus node))}))
|
||||
|
||||
(defn- dropdown-top
|
||||
"Render the top part of the dropdown, with the clickable area and the up/down arrow"
|
||||
[]
|
||||
(let [ignore-click (atom false)]
|
||||
(fn
|
||||
[internal-model choices id-fn label-fn tab-index placeholder dropdown-click key-handler filter-box? drop-showing? title?]
|
||||
(let [_ (reagent/set-state (reagent/current-component) {:filter-box? filter-box?})
|
||||
text (if @internal-model
|
||||
(label-fn (item-for-id @internal-model choices :id-fn id-fn))
|
||||
placeholder)]
|
||||
[:a.chosen-single.chosen-default
|
||||
{:href "javascript:" ;; Required to make this anchor appear in the tab order
|
||||
:tab-index (when tab-index tab-index)
|
||||
:on-click (handler-fn
|
||||
(if @ignore-click
|
||||
(reset! ignore-click false)
|
||||
(dropdown-click)))
|
||||
:on-mouse-down (handler-fn
|
||||
(when @drop-showing?
|
||||
(reset! ignore-click true))) ;; TODO: Hmmm, have a look at calling preventDefault (and stopProp?) and removing the ignore-click stuff
|
||||
:on-key-down (handler-fn
|
||||
(key-handler event)
|
||||
(when (= (.-which event) 13) ;; Pressing enter on an anchor also triggers click event, which we don't want
|
||||
(reset! ignore-click true))) ;; TODO: Hmmm, have a look at calling preventDefault (and stopProp?) and removing the ignore-click stuff
|
||||
}
|
||||
[:span (when title?
|
||||
{:title text})
|
||||
text]
|
||||
[:div [:b]]])))) ;; This odd bit of markup produces the visual arrow on the right
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: single-dropdown
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def single-dropdown-args-desc
|
||||
[{:name :choices :required true :type "vector of choices | atom" :validate-fn vector-of-maps? :description [:span "Each is expected to have an id, label and, optionally, a group, provided by " [:code ":id-fn"] ", " [:code ":label-fn"] " & " [:code ":group-fn"]]}
|
||||
{:name :model :required true :type "the id of a choice | atom" :description [:span "the id of the selected choice. If nil, " [:code ":placeholder"] " text is shown"]}
|
||||
{:name :on-change :required true :type "id -> nil" :validate-fn fn? :description [:span "called when a new choice is selected. Passed the id of new choice"] }
|
||||
{:name :id-fn :required false :default :id :type "choice -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":choices"] ", returns its unique identifier (aka id)"]}
|
||||
{:name :label-fn :required false :default :label :type "choice -> string" :validate-fn ifn? :description [:span "given an element of " [:code ":choices"] ", returns its displayable label."]}
|
||||
{:name :group-fn :required false :default :group :type "choice -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":choices"] ", returns its group identifier"]}
|
||||
{:name :render-fn :required false :type "choice -> string | hiccup" :validate-fn ifn? :description [:span "given an element of " [:code ":choices"] ", returns the markup that will be rendered for that choice. Defaults to the label if no custom markup is required."]}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, no user selection is allowed"}
|
||||
{:name :filter-box? :required false :default false :type "boolean" :description "if true, a filter text field is placed at the top of the dropdown"}
|
||||
{:name :regex-filter? :required false :default false :type "boolean | atom" :description "if true, the filter text field will support JavaScript regular expressions. If false, just plain text"}
|
||||
{:name :placeholder :required false :type "string" :validate-fn string? :description "background text when no selection"}
|
||||
{:name :title? :required false :default false :type "boolean" :description "if true, allows the title for the selected dropdown to be displayed via a mouse over. Handy when dropdown width is small and text is truncated"}
|
||||
{:name :width :required false :default "100%" :type "string" :validate-fn string? :description "the CSS width. e.g.: \"500px\" or \"20em\""}
|
||||
{:name :max-height :required false :default "240px" :type "string" :validate-fn string? :description "the maximum height of the dropdown part"}
|
||||
{:name :tab-index :required false :type "integer | string" :validate-fn number-or-string? :description "component's tabindex. A value of -1 removes from order"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn single-dropdown
|
||||
"Render a single dropdown component which emulates the bootstrap-choosen style. Sample choices object:
|
||||
[{:id \"AU\" :label \"Australia\" :group \"Group 1\"}
|
||||
{:id \"US\" :label \"United States\" :group \"Group 1\"}
|
||||
{:id \"GB\" :label \"United Kingdom\" :group \"Group 1\"}
|
||||
{:id \"AF\" :label \"Afghanistan\" :group \"Group 2\"}]"
|
||||
[& {:keys [model] :as args}]
|
||||
{:pre [(validate-args-macro single-dropdown-args-desc args "single-dropdown")]}
|
||||
(let [external-model (reagent/atom (deref-or-value model)) ;; Holds the last known external value of model, to detect external model changes
|
||||
internal-model (reagent/atom @external-model) ;; Create a new atom from the model to be used internally
|
||||
drop-showing? (reagent/atom false)
|
||||
filter-text (reagent/atom "")]
|
||||
(fn [& {:keys [choices model on-change disabled? filter-box? regex-filter? placeholder width max-height tab-index id-fn label-fn group-fn render-fn class style attr title?]
|
||||
:or {id-fn :id label-fn :label group-fn :group render-fn label-fn}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro single-dropdown-args-desc args "single-dropdown")]}
|
||||
(let [choices (deref-or-value choices)
|
||||
disabled? (deref-or-value disabled?)
|
||||
regex-filter? (deref-or-value regex-filter?)
|
||||
latest-ext-model (reagent/atom (deref-or-value model))
|
||||
_ (when (not= @external-model @latest-ext-model) ;; Has model changed externally?
|
||||
(reset! external-model @latest-ext-model)
|
||||
(reset! internal-model @latest-ext-model))
|
||||
changeable? (and on-change (not disabled?))
|
||||
callback #(do
|
||||
(reset! internal-model %)
|
||||
(when (and changeable? (not= @internal-model @latest-ext-model))
|
||||
(on-change @internal-model))
|
||||
(swap! drop-showing? not) ;; toggle to allow opening dropdown on Enter key
|
||||
(reset! filter-text ""))
|
||||
cancel #(do
|
||||
(reset! drop-showing? false)
|
||||
(reset! filter-text "")
|
||||
(reset! internal-model @external-model))
|
||||
dropdown-click #(when-not disabled?
|
||||
(swap! drop-showing? not))
|
||||
filtered-choices (if regex-filter?
|
||||
(filter-choices-regex choices group-fn label-fn @filter-text)
|
||||
(filter-choices choices group-fn label-fn @filter-text))
|
||||
press-enter (fn []
|
||||
(if disabled?
|
||||
(cancel)
|
||||
(callback @internal-model))
|
||||
true)
|
||||
press-escape (fn []
|
||||
(cancel)
|
||||
true)
|
||||
press-tab (fn []
|
||||
(if disabled?
|
||||
(cancel)
|
||||
(do ;; Was (callback @internal-model) but needed a customised version
|
||||
(when changeable? (on-change @internal-model))
|
||||
(reset! drop-showing? false)
|
||||
(reset! filter-text "")))
|
||||
(reset! drop-showing? false)
|
||||
true)
|
||||
press-up (fn []
|
||||
(if @drop-showing? ;; Up arrow
|
||||
(reset! internal-model (move-to-new-choice filtered-choices id-fn @internal-model -1))
|
||||
(reset! drop-showing? true))
|
||||
true)
|
||||
press-down (fn []
|
||||
(if @drop-showing? ;; Down arrow
|
||||
(reset! internal-model (move-to-new-choice filtered-choices id-fn @internal-model 1))
|
||||
(reset! drop-showing? true))
|
||||
true)
|
||||
press-home (fn []
|
||||
(reset! internal-model (move-to-new-choice filtered-choices id-fn @internal-model :start))
|
||||
true)
|
||||
press-end (fn []
|
||||
(reset! internal-model (move-to-new-choice filtered-choices id-fn @internal-model :end))
|
||||
true)
|
||||
key-handler #(if disabled?
|
||||
false
|
||||
(case (.-which %)
|
||||
13 (press-enter)
|
||||
27 (press-escape)
|
||||
9 (press-tab)
|
||||
38 (press-up)
|
||||
40 (press-down)
|
||||
36 (press-home)
|
||||
35 (press-end)
|
||||
filter-box?))] ;; Use this boolean to allow/prevent the key from being processed by the text box
|
||||
[:div
|
||||
(merge
|
||||
{:class (str "rc-dropdown chosen-container chosen-container-single noselect " (when @drop-showing? "chosen-container-active chosen-with-drop ") class)
|
||||
:style (merge (flex-child-style (if width "0 0 auto" "auto"))
|
||||
(align-style :align-self :start)
|
||||
{:width (when width width)}
|
||||
style)}
|
||||
attr) ;; Prevent user text selection
|
||||
[dropdown-top internal-model choices id-fn label-fn tab-index placeholder dropdown-click key-handler filter-box? drop-showing? title?]
|
||||
(when (and @drop-showing? (not disabled?))
|
||||
[:div.chosen-drop
|
||||
[filter-text-box filter-box? filter-text key-handler drop-showing?]
|
||||
[:ul.chosen-results
|
||||
(when max-height {:style {:max-height max-height}})
|
||||
(if (-> filtered-choices count pos?)
|
||||
(let [[group-names group-opt-lists] (choices-with-group-headings filtered-choices group-fn)
|
||||
make-a-choice (partial make-choice-item id-fn render-fn callback internal-model)
|
||||
make-choices #(map make-a-choice %1)
|
||||
make-h-then-choices (fn [h opts]
|
||||
(cons (make-group-heading h)
|
||||
(make-choices opts)))
|
||||
has-no-group-names? (nil? (:group (first group-names)))]
|
||||
(if (and (= 1 (count group-opt-lists)) has-no-group-names?)
|
||||
(make-choices (first group-opt-lists)) ;; one group means no headings
|
||||
(apply concat (map make-h-then-choices group-names group-opt-lists))))
|
||||
[:li.no-results (str "No results match \"" @filter-text "\"")])]])]))))
|
||||
File diff suppressed because one or more lines are too long
548
resources/public/js/compiled/out/re_com/dropdown.js
Normal file
548
resources/public/js/compiled/out/re_com/dropdown.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/dropdown.js.map
Normal file
1
resources/public/js/compiled/out/re_com/dropdown.js.map
Normal file
File diff suppressed because one or more lines are too long
189
resources/public/js/compiled/out/re_com/input_time.cljs
Normal file
189
resources/public/js/compiled/out/re_com/input_time.cljs
Normal file
|
|
@ -0,0 +1,189 @@
|
|||
(ns re-com.input-time
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [reagent.core :as reagent]
|
||||
[re-com.validate :refer [css-style? html-attr? number-or-string?] :refer-macros [validate-args-macro]]
|
||||
[re-com.text :refer [label]]
|
||||
[re-com.box :refer [h-box gap]]
|
||||
[re-com.util :refer [pad-zero-number deref-or-value]]))
|
||||
|
||||
|
||||
(defn- time->mins
|
||||
[time]
|
||||
(rem time 100))
|
||||
|
||||
|
||||
(defn- time->hrs
|
||||
[time]
|
||||
(quot time 100))
|
||||
|
||||
(defn- to-int
|
||||
"Parse the string 's' to a valid int. On parse failure, return 0"
|
||||
[s]
|
||||
(let [val (js/parseInt s)]
|
||||
(if (js/isNaN val) 0 val)))
|
||||
|
||||
(defn- triple->time
|
||||
"Return a time integer from a triple int vector of form [H _ M]"
|
||||
[[hr _ mi]]
|
||||
(+ (* hr 100) mi)) ;; a four digit integer: HHMM
|
||||
|
||||
|
||||
;; This regular expression matchs all valid forms of time entry, including partial
|
||||
;; forms which happen during user entry.
|
||||
;; It is composed of 3 'or' options, separated by '|', and within each, is a sub-re which
|
||||
;; attempts to match the HH ':' MM parts.
|
||||
;; So any attempt to match against this re, using "re-matches" will return
|
||||
;; a vector of 10 items:
|
||||
;; - the 1st item will be the entire string matched
|
||||
;; - followed by 3 groups of 3.
|
||||
(def ^{:private true}
|
||||
triple-seeking-re #"^(\d{0,2})()()$|^(\d{0,1})(:{0,1})(\d{0,2})$|^(\d{0,2})(:{0,1})(\d{0,2})$")
|
||||
|
||||
(defn- extract-triple-from-text
|
||||
[text]
|
||||
(->> text
|
||||
(re-matches triple-seeking-re) ;; looks for different ways of matching triples H : M
|
||||
(rest) ;; get rid of the first value. It is the entire matched string.
|
||||
(filter (comp not nil?)))) ;; of the 9 items, there should be only 3 non-nil matches coresponding to H : M
|
||||
|
||||
|
||||
(defn- text->time
|
||||
"return as a time int, the contents of 'text'"
|
||||
[text]
|
||||
(->> text
|
||||
extract-triple-from-text
|
||||
(map to-int) ;; make them ints (or 0)
|
||||
triple->time)) ;; turn the triple of values into a single int
|
||||
|
||||
|
||||
(defn- time->text
|
||||
"return a string of format HH:MM for 'time'"
|
||||
[time]
|
||||
(let [hrs (time->hrs time)
|
||||
mins (time->mins time)]
|
||||
(str (pad-zero-number hrs 2) ":" (pad-zero-number mins 2))))
|
||||
|
||||
(defn- valid-text?
|
||||
"Return true if text passes basic time validation.
|
||||
Can't do to much validation because user input might not be finished.
|
||||
Why? On the way to entering 6:30, you must pass through the invalid state of '63'.
|
||||
So we only really check against the triple-extracting regular expression"
|
||||
[text]
|
||||
(= 3 (count (extract-triple-from-text text))))
|
||||
|
||||
(defn- valid-time?
|
||||
[time]
|
||||
(cond
|
||||
(nil? time) false ;; can't be nil
|
||||
(> 0 time) false ;; must be a poistive number
|
||||
(< 60 (time->mins time)) false ;; too many mins
|
||||
:else true))
|
||||
|
||||
(defn- validate-arg-times
|
||||
[model minimum maximum]
|
||||
(assert (and (number? model) (valid-time? model)) (str "[input-time] given an invalid :model - " model))
|
||||
(assert (and (number? minimum) (valid-time? minimum)) (str "[input-time] given an invalid :minimum - " minimum))
|
||||
(assert (and (number? maximum) (valid-time? maximum)) (str "[input-time] given an invalid :maximum - " maximum))
|
||||
(assert (<= minimum maximum) (str "[input-time] :minimum " minimum " > :maximum " maximum))
|
||||
true)
|
||||
|
||||
(defn- force-valid-time
|
||||
"Validate the time supplied.
|
||||
Return either the time or, if it is invalid, return something valid"
|
||||
[time min max previous]
|
||||
(cond
|
||||
(nil? time) previous
|
||||
(not (valid-time? time)) previous
|
||||
(< time min) min
|
||||
(< max time) max
|
||||
:else time))
|
||||
|
||||
(defn- on-new-keypress
|
||||
"Called each time the <input> field gets a keypress, or paste operation.
|
||||
Rests the text-model only if the new text is valid"
|
||||
[event text-model]
|
||||
(let [current-text (-> event .-target .-value)] ;; gets the current input field text
|
||||
(when (valid-text? current-text)
|
||||
(reset! text-model current-text))))
|
||||
|
||||
(defn- lose-focus-if-enter
|
||||
"When Enter is pressed, force the component to lose focus"
|
||||
[ev]
|
||||
(when (= (.-keyCode ev) 13)
|
||||
(-> ev .-target .blur)
|
||||
true))
|
||||
|
||||
(defn- on-defocus
|
||||
"Called when the field looses focus.
|
||||
Re-validate what has been entered, comparing to mins and maxs.
|
||||
Invoke the callback as necessary"
|
||||
[text-model min max callback previous-val]
|
||||
(let [time (text->time @text-model)
|
||||
time (force-valid-time time min max previous-val)]
|
||||
(reset! text-model (time->text time))
|
||||
(when (and callback (not= time previous-val))
|
||||
(callback time))))
|
||||
|
||||
(def input-time-args-desc
|
||||
[{:name :model :required true :type "integer | string | atom" :validate-fn number-or-string? :description "a time in integer form. e.g. '09:30am' is 930"}
|
||||
{:name :on-change :required true :type "integer -> nil" :validate-fn fn? :description "called when user entry completes and value is new. Passed new value as integer"}
|
||||
{:name :minimum :required false :default 0 :type "integer | string" :validate-fn number-or-string? :description "user can't enter a time less than this value"}
|
||||
{:name :maximum :required false :default 2359 :type "integer | string" :validate-fn number-or-string? :description "user can't enter a time more than this value"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "when true, user input is disabled"}
|
||||
{:name :show-icon? :required false :default false :type "boolean" :description "when true, a clock icon will be displayed to the right of input field"}
|
||||
{:name :hide-border? :required false :default false :type "boolean" :description "when true, input filed is displayed without a border"}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "standard CSS width setting for width of the input box (excluding the icon if present)"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "standard CSS height setting"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS style. e.g. {:color \"red\" :width \"50px\"}" }
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn input-time
|
||||
"I return the markup for an input box which will accept and validate times.
|
||||
Parameters - refer input-time-args above"
|
||||
[& {:keys [model minimum maximum on-change class style attr] :as args
|
||||
:or {minimum 0 maximum 2359}}]
|
||||
{:pre [(validate-args-macro input-time-args-desc args "input-time")
|
||||
(validate-arg-times (deref-or-value model) minimum maximum)]}
|
||||
(let [deref-model (deref-or-value model)
|
||||
text-model (reagent/atom (time->text deref-model))
|
||||
previous-model (reagent/atom deref-model)]
|
||||
(fn
|
||||
[& {:keys [model minimum maximum width height disabled? hide-border? show-icon?] :as args
|
||||
:or {minimum 0 maximum 2359}}]
|
||||
{:pre [(validate-args-macro input-time-args-desc args "input-time")
|
||||
(validate-arg-times (deref-or-value model) minimum maximum)]}
|
||||
(let [style (merge (when hide-border? {:border "none"})
|
||||
style)
|
||||
new-val (deref-or-value model)
|
||||
new-val (if (< new-val minimum) minimum new-val)
|
||||
new-val (if (> new-val maximum) maximum new-val)]
|
||||
;; if the model is different to that currently shown in text, then reset the text to match
|
||||
;; other than that we want to keep the current text, because the user is probably typing
|
||||
(when (not= @previous-model new-val)
|
||||
(reset! text-model (time->text new-val))
|
||||
(reset! previous-model new-val))
|
||||
|
||||
[h-box
|
||||
:class "rc-input-time"
|
||||
:style (merge {:height height}
|
||||
style)
|
||||
:children [[:input
|
||||
(merge
|
||||
{:type "text"
|
||||
:class (str "time-entry " class)
|
||||
:style (merge {:width width}
|
||||
style)
|
||||
:value @text-model
|
||||
:disabled (deref-or-value disabled?)
|
||||
:on-change (handler-fn (on-new-keypress event text-model))
|
||||
:on-blur (handler-fn (on-defocus text-model minimum maximum on-change @previous-model))
|
||||
:on-key-up (handler-fn (lose-focus-if-enter event))}
|
||||
attr)]
|
||||
(when show-icon?
|
||||
#_[:div.time-icon ;; TODO: Remove
|
||||
[:span.glyphicon.glyphicon-time
|
||||
{:style {:position "static" :margin "auto"}}]]
|
||||
[:div.time-icon
|
||||
[:i.zmdi.zmdi-hc-fw-rc.zmdi-time
|
||||
{:style {:position "static" :margin "auto"}}]])]]))))
|
||||
File diff suppressed because one or more lines are too long
321
resources/public/js/compiled/out/re_com/input_time.js
Normal file
321
resources/public/js/compiled/out/re_com/input_time.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
384
resources/public/js/compiled/out/re_com/misc.cljs
Normal file
384
resources/public/js/compiled/out/re_com/misc.cljs
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
(ns re-com.misc
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.util :refer [deref-or-value px]]
|
||||
[re-com.popover :refer [popover-tooltip]]
|
||||
[re-com.box :refer [h-box v-box box gap line flex-child-style align-style]]
|
||||
[re-com.validate :refer [input-status-type? input-status-types-list regex?
|
||||
string-or-hiccup? css-style? html-attr? number-or-string?
|
||||
string-or-atom? throbber-size? throbber-sizes-list] :refer-macros [validate-args-macro]]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: throbber
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def throbber-args-desc
|
||||
[{:name :size :required false :type "keyword" :default :regular :validate-fn throbber-size? :description [:span "one of " throbber-sizes-list]}
|
||||
{:name :color :required false :type "string" :default "#999" :validate-fn string? :description "CSS color"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn throbber
|
||||
"Render an animated throbber using CSS"
|
||||
[& {:keys [size color class style attr] :as args}]
|
||||
{:pre [(validate-args-macro throbber-args-desc args "throbber")]}
|
||||
(let [seg (fn [] [:li (when color {:style {:background-color color}})])]
|
||||
[box
|
||||
:align :start
|
||||
:child [:ul
|
||||
(merge {:class (str "rc-throbber loader "
|
||||
(case size :regular ""
|
||||
:smaller "smaller "
|
||||
:small "small "
|
||||
:large "large "
|
||||
"")
|
||||
class)
|
||||
:style style}
|
||||
attr)
|
||||
[seg] [seg] [seg] [seg]
|
||||
[seg] [seg] [seg] [seg]]])) ;; Each :li element in [seg] represents one of the eight circles in the throbber
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: input-text
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def input-text-args-desc
|
||||
[{:name :model :required true :type "string | atom" :validate-fn string-or-atom? :description "text of the input (can be atom or value)"}
|
||||
{:name :on-change :required true :type "string -> nil" :validate-fn fn? :description [:span [:code ":change-on-blur?"] " controls when it is called. Passed the current input string"] }
|
||||
{:name :status :required false :type "keyword" :validate-fn input-status-type? :description [:span "validation status. " [:code "nil/omitted"] " for normal status or one of: " input-status-types-list]}
|
||||
{:name :status-icon? :required false :default false :type "boolean" :description [:span "when true, display an icon to match " [:code ":status"] " (no icon for nil)"]}
|
||||
{:name :status-tooltip :required false :type "string" :validate-fn string? :description "displayed in status icon's tooltip"}
|
||||
{:name :placeholder :required false :type "string" :validate-fn string? :description "background text shown when empty"}
|
||||
{:name :width :required false :default "250px" :type "string" :validate-fn string? :description "standard CSS width setting for this input"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "standard CSS height setting for this input"}
|
||||
{:name :rows :required false :default 3 :type "integer | string" :validate-fn number-or-string? :description "ONLY applies to 'input-textarea': the number of rows of text to show"}
|
||||
{:name :change-on-blur? :required false :default true :type "boolean | atom" :description [:span "when true, invoke " [:code ":on-change"] " function on blur, otherwise on every change (character by character)"] }
|
||||
{:name :validation-regex :required false :type "regex" :validate-fn regex? :description "user input is only accepted if it would result in a string that matches this regular expression"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't interact (input anything)"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}
|
||||
{:name :input-type :required false :type "keyword" :validate-fn keyword? :description [:span "ONLY applies to super function 'base-input-text': either " [:code ":input"] ", " [:code ":password"] " or " [:code ":textarea"]]}])
|
||||
|
||||
;; Sample regex's:
|
||||
;; - #"^(-{0,1})(\d*)$" ;; Signed integer
|
||||
;; - #"^(\d{0,2})$|^(\d{0,2}\.\d{0,1})$" ;; Specific numeric value ##.#
|
||||
;; - #"^.{0,8}$" ;; 8 chars max
|
||||
;; - #"^[0-9a-fA-F]*$" ;; Hex number
|
||||
;; - #"^(\d{0,2})()()$|^(\d{0,1})(:{0,1})(\d{0,2})$|^(\d{0,2})(:{0,1})(\d{0,2})$" ;; Time input
|
||||
|
||||
(defn- input-text-base
|
||||
"Returns markup for a basic text input label"
|
||||
[& {:keys [model input-type] :as args}]
|
||||
{:pre [(validate-args-macro input-text-args-desc args "input-text")]}
|
||||
(let [external-model (reagent/atom (deref-or-value model)) ;; Holds the last known external value of model, to detect external model changes
|
||||
internal-model (reagent/atom (if (nil? @external-model) "" @external-model))] ;; Create a new atom from the model to be used internally (avoid nil)
|
||||
(fn
|
||||
[& {:keys [model status status-icon? status-tooltip placeholder width height rows on-change change-on-blur? validation-regex disabled? class style attr]
|
||||
:or {change-on-blur? true}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro input-text-args-desc args "input-text")]}
|
||||
(let [latest-ext-model (deref-or-value model)
|
||||
disabled? (deref-or-value disabled?)
|
||||
change-on-blur? (deref-or-value change-on-blur?)
|
||||
showing? (reagent/atom false)]
|
||||
(when (not= @external-model latest-ext-model) ;; Has model changed externally?
|
||||
(reset! external-model latest-ext-model)
|
||||
(reset! internal-model latest-ext-model))
|
||||
[h-box
|
||||
:align :start
|
||||
:width (if width width "250px")
|
||||
:class "rc-input-text "
|
||||
:children [[:div
|
||||
{:class (str "rc-input-text-inner " ;; form-group
|
||||
(case status
|
||||
:success "has-success "
|
||||
:warning "has-warning "
|
||||
:error "has-error "
|
||||
"")
|
||||
(when (and status status-icon?) "has-feedback"))
|
||||
:style (flex-child-style "auto")}
|
||||
[(if (= input-type :password) :input input-type)
|
||||
(merge
|
||||
{:class (str "form-control " class)
|
||||
:type (case input-type
|
||||
:input "text"
|
||||
:password "password"
|
||||
nil)
|
||||
:rows (when (= input-type :textarea) (if rows rows 3))
|
||||
:style (merge
|
||||
(flex-child-style "none")
|
||||
{:height height
|
||||
:padding-right "12px"} ;; override for when icon exists
|
||||
style)
|
||||
:placeholder placeholder
|
||||
:value @internal-model
|
||||
:disabled disabled?
|
||||
:on-change (handler-fn
|
||||
(let [new-val (-> event .-target .-value)]
|
||||
(when (and
|
||||
on-change
|
||||
(not disabled?)
|
||||
(if validation-regex (re-find validation-regex new-val) true))
|
||||
(reset! internal-model new-val)
|
||||
(when-not change-on-blur?
|
||||
(on-change @internal-model)))))
|
||||
:on-blur (handler-fn
|
||||
(when (and
|
||||
on-change
|
||||
change-on-blur?
|
||||
(not= @internal-model @external-model))
|
||||
(on-change @internal-model)))
|
||||
:on-key-up (handler-fn
|
||||
(if disabled?
|
||||
(.preventDefault event)
|
||||
(case (.-which event)
|
||||
13 (when on-change (on-change @internal-model))
|
||||
27 (reset! internal-model @external-model)
|
||||
true)))
|
||||
|
||||
}
|
||||
attr)]]
|
||||
(when (and status-icon? status)
|
||||
(let [icon-class (case status :success "zmdi-check-circle" :warning "zmdi-alert-triangle" :error "zmdi-alert-circle zmdi-spinner" :validating "zmdi-hc-spin zmdi-rotate-right zmdi-spinner")]
|
||||
(if status-tooltip
|
||||
[popover-tooltip
|
||||
:label status-tooltip
|
||||
:position :right-center
|
||||
:status status
|
||||
;:width "200px"
|
||||
:showing? showing?
|
||||
:anchor (if (= :validating status)
|
||||
[throbber
|
||||
:size :regular
|
||||
:class "smaller"
|
||||
:attr {:on-mouse-over (handler-fn (when (and status-icon? status) (reset! showing? true)))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))}]
|
||||
[:i {:class (str "zmdi zmdi-hc-fw " icon-class " form-control-feedback")
|
||||
:style {:position "static"
|
||||
:height "auto"
|
||||
:opacity (if (and status-icon? status) "1" "0")}
|
||||
:on-mouse-over (handler-fn (when (and status-icon? status) (reset! showing? true)))
|
||||
:on-mouse-out (handler-fn (reset! showing? false))}])
|
||||
:style (merge (flex-child-style "none")
|
||||
(align-style :align-self :center)
|
||||
{:font-size "130%"
|
||||
:margin-left "4px"})]
|
||||
(if (= :validating status)
|
||||
[throbber :size :regular :class "smaller"]
|
||||
[:i {:class (str "zmdi zmdi-hc-fw " icon-class " form-control-feedback")
|
||||
:style (merge (flex-child-style "none")
|
||||
(align-style :align-self :center)
|
||||
{:position "static"
|
||||
:font-size "130%"
|
||||
:margin-left "4px"
|
||||
:opacity (if (and status-icon? status) "1" "0")
|
||||
:height "auto"})
|
||||
:title status-tooltip}]))))]]))))
|
||||
|
||||
|
||||
(defn input-text
|
||||
[& args]
|
||||
(apply input-text-base :input-type :input args))
|
||||
|
||||
|
||||
(defn input-password
|
||||
[& args]
|
||||
(apply input-text-base :input-type :password args))
|
||||
|
||||
|
||||
(defn input-textarea
|
||||
[& args]
|
||||
(apply input-text-base :input-type :textarea args))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: checkbox
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def checkbox-args-desc
|
||||
[{:name :model :required true :type "boolean | atom" :description "holds state of the checkbox when it is called"}
|
||||
{:name :on-change :required true :type "boolean -> nil" :validate-fn fn? :description "called when the checkbox is clicked. Passed the new value of the checkbox"}
|
||||
{:name :label :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "the label shown to the right"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, user interaction is disabled"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "the CSS style style map"}
|
||||
{:name :label-style :required false :type "CSS style map" :validate-fn css-style? :description "the CSS class applied overall to the component"}
|
||||
{:name :label-class :required false :type "string" :validate-fn string? :description "the CSS class applied to the label"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
;; TODO: when disabled?, should the text appear "disabled".
|
||||
(defn checkbox
|
||||
"I return the markup for a checkbox, with an optional RHS label"
|
||||
[& {:keys [model on-change label disabled? style label-class label-style attr]
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro checkbox-args-desc args "checkbox")]}
|
||||
(let [cursor "default"
|
||||
model (deref-or-value model)
|
||||
disabled? (deref-or-value disabled?)
|
||||
callback-fn #(when (and on-change (not disabled?))
|
||||
(on-change (not model)))] ;; call on-change with either true or false
|
||||
[h-box
|
||||
:align :start
|
||||
:class "noselect"
|
||||
:children [[:input
|
||||
(merge
|
||||
{:class "rc-checkbox"
|
||||
:type "checkbox"
|
||||
:style (merge (flex-child-style "none")
|
||||
{:cursor cursor}
|
||||
style)
|
||||
:disabled disabled?
|
||||
:checked (boolean model)
|
||||
:on-change (handler-fn (callback-fn))}
|
||||
attr)]
|
||||
(when label
|
||||
[:span
|
||||
{:on-click (handler-fn (callback-fn))
|
||||
:class label-class
|
||||
:style (merge (flex-child-style "none")
|
||||
{:padding-left "8px"
|
||||
:cursor cursor}
|
||||
label-style)}
|
||||
label])]]))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: radio-button
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def radio-button-args-desc
|
||||
[{:name :model :required true :type "anything | atom" :description [:span "selected value of the radio button group. See also " [:code ":value"]] }
|
||||
{:name :value :required false :type "anything" :description [:span "if " [:code ":model"] " equals " [:code ":value"] " then this radio button is selected"] }
|
||||
{:name :on-change :required true :type "anything -> nil" :validate-fn fn? :description [:span "called when the radio button is clicked. Passed " [:code ":value"]]}
|
||||
{:name :label :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "the label shown to the right"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't click the radio button"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "radio button style map"}
|
||||
{:name :label-style :required false :type "CSS style map" :validate-fn css-style? :description "the CSS class applied overall to the component"}
|
||||
{:name :label-class :required false :type "string" :validate-fn string? :description "the CSS class applied to the label"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn radio-button
|
||||
"I return the markup for a radio button, with an optional RHS label"
|
||||
[& {:keys [model on-change value label disabled? style label-class label-style attr]
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro radio-button-args-desc args "radio-button")]}
|
||||
(let [cursor "default"
|
||||
model (deref-or-value model)
|
||||
disabled? (deref-or-value disabled?)
|
||||
callback-fn #(when (and on-change (not disabled?))
|
||||
(on-change value))] ;; call on-change with the :value arg
|
||||
[h-box
|
||||
:align :start
|
||||
:class "noselect"
|
||||
:children [[:input
|
||||
(merge
|
||||
{:class "rc-radio-button"
|
||||
:type "radio"
|
||||
:style (merge
|
||||
(flex-child-style "none")
|
||||
{:cursor cursor}
|
||||
style)
|
||||
:disabled disabled?
|
||||
:checked (= model value)
|
||||
:on-change (handler-fn (callback-fn))}
|
||||
attr)]
|
||||
(when label
|
||||
[:span
|
||||
{:on-click (handler-fn (callback-fn))
|
||||
:class label-class
|
||||
:style (merge (flex-child-style "none")
|
||||
{:padding-left "8px"
|
||||
:cursor cursor}
|
||||
label-style)}
|
||||
label])]]))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: slider
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def slider-args-desc
|
||||
[{:name :model :required true :type "double | string | atom" :validate-fn number-or-string? :description "current value of the slider"}
|
||||
{:name :on-change :required true :type "double -> nil" :validate-fn fn? :description "called when the slider is moved. Passed the new value of the slider"}
|
||||
{:name :min :required false :default 0 :type "double | string | atom" :validate-fn number-or-string? :description "the minimum value of the slider"}
|
||||
{:name :max :required false :default 100 :type "double | string | atom" :validate-fn number-or-string? :description "the maximum value of the slider"}
|
||||
{:name :step :required false :default 1 :type "double | string | atom" :validate-fn number-or-string? :description "step value between min and max"}
|
||||
{:name :width :required false :default "400px" :type "string" :validate-fn string? :description "standard CSS width setting for the slider"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't change the slider"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn slider
|
||||
"Returns markup for an HTML5 slider input"
|
||||
[]
|
||||
(fn
|
||||
[& {:keys [model min max step width on-change disabled? class style attr]
|
||||
:or {min 0 max 100}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro slider-args-desc args "slider")]}
|
||||
(let [model (deref-or-value model)
|
||||
min (deref-or-value min)
|
||||
max (deref-or-value max)
|
||||
step (deref-or-value step)
|
||||
disabled? (deref-or-value disabled?)]
|
||||
[box
|
||||
:align :start
|
||||
:child [:input
|
||||
(merge
|
||||
{:class (str "rc-slider " class)
|
||||
:type "range"
|
||||
;:orient "vertical" ;; Make Firefox slider vertical (doesn't work because React ignores it, I think)
|
||||
:style (merge
|
||||
(flex-child-style "none")
|
||||
{;:-webkit-appearance "slider-vertical" ;; TODO: Make a :orientation (:horizontal/:vertical) option
|
||||
;:writing-mode "bt-lr" ;; Make IE slider vertical
|
||||
:width (if width width "400px")
|
||||
:cursor (if disabled? "not-allowed" "default")}
|
||||
style)
|
||||
:min min
|
||||
:max max
|
||||
:step step
|
||||
:value model
|
||||
:disabled disabled?
|
||||
:on-change (handler-fn (on-change (js/Number (-> event .-target .-value))))}
|
||||
attr)]])))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: progress-bar
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def progress-bar-args-desc
|
||||
[{:name :model :required true :type "double | string | atom" :validate-fn number-or-string? :description "current value of the slider. A number between 0 and 100"}
|
||||
{:name :width :required false :type "string" :default "100%" :validate-fn string? :description "a CSS width"}
|
||||
{:name :striped? :required false :type "boolean" :default false :description "when true, the progress section is a set of animated stripes"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :bar-class :required false :type "string" :validate-fn string? :description "CSS class name(s) for the actual progress bar itself, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn progress-bar
|
||||
"Render a bootstrap styled progress bar"
|
||||
[& {:keys [model width striped? class bar-class style attr]
|
||||
:or {width "100%"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro progress-bar-args-desc args "progress-bar")]}
|
||||
(let [model (deref-or-value model)]
|
||||
[box
|
||||
:align :start
|
||||
:child [:div
|
||||
(merge
|
||||
{:class (str "rc-progress-bar progress " class)
|
||||
:style (merge (flex-child-style "none")
|
||||
{:width width}
|
||||
style)}
|
||||
attr)
|
||||
[:div
|
||||
{:class (str "progress-bar " (when striped? "progress-bar-striped active ") bar-class)
|
||||
:role "progressbar"
|
||||
:style {:width (str model "%")
|
||||
:transition "none"}} ;; Default BS transitions cause the progress bar to lag behind
|
||||
(str model "%")]]]))
|
||||
File diff suppressed because one or more lines are too long
748
resources/public/js/compiled/out/re_com/misc.js
Normal file
748
resources/public/js/compiled/out/re_com/misc.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/misc.js.map
Normal file
1
resources/public/js/compiled/out/re_com/misc.js.map
Normal file
File diff suppressed because one or more lines are too long
54
resources/public/js/compiled/out/re_com/modal_panel.cljs
Normal file
54
resources/public/js/compiled/out/re_com/modal_panel.cljs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
(ns re-com.modal-panel
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.validate :refer [string-or-hiccup? number-or-string? css-style? html-attr?] :refer-macros [validate-args-macro]]))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; modal-panel
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def modal-panel-args-desc
|
||||
[{:name :child :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "hiccup to be centered within in the browser window"}
|
||||
{:name :wrap-nicely? :required false :default true :type "boolean" :description [:span "if true, wrap " [:code ":child"] " in a white, rounded panel"]}
|
||||
{:name :backdrop-color :required false :default "black" :type "string" :validate-fn string? :description "CSS color of backdrop"}
|
||||
{:name :backdrop-opacity :required false :default 0.6 :type "double | string" :validate-fn number-or-string? :description [:span "opacity of backdrop from:" [:br] "0.0 (transparent) to 1.0 (opaque)"]}
|
||||
{:name :backdrop-on-click :required false :default nil :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the backdrop is clicked"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn modal-panel
|
||||
"Renders a modal window centered on screen. A dark transparent backdrop sits between this and the underlying
|
||||
main window to prevent UI interactivity and place user focus on the modal window.
|
||||
Parameters:
|
||||
- child: The message to display in the modal (a string or a hiccup vector or function returning a hiccup vector)"
|
||||
[& {:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click]
|
||||
:or {wrap-nicely? true backdrop-color "black" backdrop-opacity 0.6}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro modal-panel-args-desc args "modal-panel")]}
|
||||
[:div ;; Containing div
|
||||
(merge {:class (str "rc-modal-panel display-flex " class)
|
||||
:style (merge {:position "fixed"
|
||||
:left "0px"
|
||||
:top "0px"
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:z-index 1020}
|
||||
style)}
|
||||
attr)
|
||||
[:div ;; Backdrop
|
||||
{:style {:position "fixed"
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:background-color backdrop-color
|
||||
:opacity backdrop-opacity
|
||||
:z-index 1}
|
||||
:on-click (handler-fn (when backdrop-on-click (backdrop-on-click))
|
||||
(.preventDefault event)
|
||||
(.stopPropagation event))}]
|
||||
[:div ;; Child container
|
||||
{:style (merge {:margin "auto"
|
||||
:z-index 2}
|
||||
(when wrap-nicely? {:background-color "white"
|
||||
:padding "16px"
|
||||
:border-radius "6px"}))}
|
||||
child]])
|
||||
|
|
@ -0,0 +1 @@
|
|||
{:rename-macros {}, :renames {}, :use-macros {validate-args-macro re-com.validate, handler-fn re-com.core}, :excludes #{}, :name re-com.modal-panel, :imports nil, :requires {re-com.validate re-com.validate}, :uses {html-attr? re-com.validate, css-style? re-com.validate, string-or-hiccup? re-com.validate, number-or-string? re-com.validate}, :defs {modal-panel-args-desc {:name re-com.modal-panel/modal-panel-args-desc, :file "docs/js/compiled/out/re_com/modal_panel.cljs", :line 9, :column 1, :end-line 9, :end-column 27, :meta {:file "/Users/simon/workspace/swinging-needle-meter/docs/js/compiled/out/re_com/modal_panel.cljs", :line 9, :column 6, :end-line 9, :end-column 27}}, modal-panel {:protocol-inline nil, :meta {:file "/Users/simon/workspace/swinging-needle-meter/docs/js/compiled/out/re_com/modal_panel.cljs", :line 19, :column 7, :end-line 19, :end-column 18, :arglists (quote ([& {:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click], :or {wrap-nicely? true, backdrop-color "black", backdrop-opacity 0.6}, :as args}])), :doc "Renders a modal window centered on screen. A dark transparent backdrop sits between this and the underlying\n main window to prevent UI interactivity and place user focus on the modal window.\n Parameters:\n - child: The message to display in the modal (a string or a hiccup vector or function returning a hiccup vector)", :top-fn {:variadic true, :max-fixed-arity 0, :method-params [({:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click], :or {wrap-nicely? true, backdrop-color "black", backdrop-opacity 0.6}, :as args})], :arglists ([& {:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click], :or {wrap-nicely? true, backdrop-color "black", backdrop-opacity 0.6}, :as args}]), :arglists-meta (nil)}}, :name re-com.modal-panel/modal-panel, :variadic true, :file "docs/js/compiled/out/re_com/modal_panel.cljs", :end-column 18, :top-fn {:variadic true, :max-fixed-arity 0, :method-params [({:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click], :or {wrap-nicely? true, backdrop-color "black", backdrop-opacity 0.6}, :as args})], :arglists ([& {:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click], :or {wrap-nicely? true, backdrop-color "black", backdrop-opacity 0.6}, :as args}]), :arglists-meta (nil)}, :method-params [({:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click], :or {wrap-nicely? true, backdrop-color "black", backdrop-opacity 0.6}, :as args})], :protocol-impl nil, :arglists-meta (nil), :column 1, :line 19, :end-line 19, :max-fixed-arity 0, :fn-var true, :arglists ([& {:keys [child wrap-nicely? backdrop-color backdrop-opacity class style attr backdrop-on-click], :or {wrap-nicely? true, backdrop-color "black", backdrop-opacity 0.6}, :as args}]), :doc "Renders a modal window centered on screen. A dark transparent backdrop sits between this and the underlying\n main window to prevent UI interactivity and place user focus on the modal window.\n Parameters:\n - child: The message to display in the modal (a string or a hiccup vector or function returning a hiccup vector)"}}, :require-macros {re-com.core re-com.core, re-com.validate re-com.validate}, :cljs.analyzer/constants {:seen #{:description :wrap-nicely? :backdrop-opacity :top :default :name :background-color :width :type :backdrop-color :on-click :style :div :z-index :opacity :class :padding :code :backdrop-on-click :position :validate-fn :br :child :border-radius :required :height :left :span :margin :attr}, :order [:name :required :type :validate-fn :description :child :default :wrap-nicely? :span :code :backdrop-color :backdrop-opacity :br :backdrop-on-click :class :style :attr :div :position :left :top :width :height :z-index :on-click :background-color :opacity :margin :padding :border-radius]}, :doc nil}
|
||||
72
resources/public/js/compiled/out/re_com/modal_panel.js
Normal file
72
resources/public/js/compiled/out/re_com/modal_panel.js
Normal file
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"\/Users\/simon\/workspace\/swinging-needle-meter\/docs\/js\/compiled\/out\/re_com\/modal_panel.js","sources":["modal_panel.cljs?rel=1603199189410"],"lineCount":72,"mappings":";AAAA;;;AAQA,2CAAA,mFAAA,2CAAA,qDAAA,sDAAA,6DAAA,KAAA,qDAAA,kBAAA,2GAAA,oEAAA,6DAAA,2CAAA,qDAAA,mEAAA,6DAAA,MAAA,4DAAA,KAAA,qDAAA,UAAA,oEAAA,mFAAA,qDAAA,iBAAA,mFAAA,qDAAA,iBAAA,6CAAA,2CAAA,qDAAA,yEAAA,6DAAA,MAAA,4DAAA,QAAA,qDAAA,SAAA,2FAAA,oEAAA,gCAAA,2CAAA,qDAAA,6EAAA,6DAAA,MAAA,4DAAA,IAAA,qDAAA,kBAAA,2GAAA,oEAAA,mFAAA,qDAAA,4BAAA,mFAAA,wDAAA,oDAAA,2CAAA,qDAAA,gFAAA,6DAAA,MAAA,4DAAA,KAAA,qDAAA,SAAA,uFAAA,oEAAA,oGAAA,2CAAA,qDAAA,wDAAA,6DAAA,MAAA,qDAAA,SAAA,2FAAA,oEAAA,2CAAA,2CAAA,qDAAA,uDAAA,6DAAA,MAAA,qDAAA,gBAAA,oGAAA,oEAAA,wCAAA,2CAAA,qDAAA,qDAAA,6DAAA,MAAA,qDAAA,gBAAA,oGAAA,oEAAA,mFAAA,qDAAA,yBAAA,mFAAA,qDAAA,yBAAA,mFAAA,wDAAA,MAAA,mFAAA,qDAAA,iBAAA,OAAA,mFAAA,qDAAA,iBAAA,5lLAAKA,geAC8FC,m2CAEAC,sjBACAC,o4BACAC,whBACAF,yeACAG,6eACAC;AAEnG,AAAA;;;;;;iCAAA,yCAAAC,1EAAMM;AAAN,AAAA,IAAAL,sBAAA;AAAA,AAAA,IAAAC,2BAAA,AAAA;AAAA,AAAA,IAAAC,yBAAA;;AAAA,AAAA,GAAA,CAAAA,yBAAAD;AAAA,AAAA,AAAAD,yBAAA,CAAA,UAAAE;;AAAA,eAAA,CAAAA,yBAAA;;;;AAAA;;;;AAAA,IAAAC,wBAAA,EAAA,CAAA,MAAA,AAAAH,6BAAA,AAAA,KAAAI,qBAAA,AAAAJ,0BAAA,KAAA,IAAA,OAAA;AAAA,AAAA,OAAAK,oEAAAF;;;AAAA,AAAA,AAAA,AAAAE,sEAAA,WAAAC;AAAA,AAAA,IAAAC,aAAAD;IAAAC,iBAAA,EAAA,EAAA,EAAA,CAAAA,cAAA,QAAA,EAAA,CAAA,CAAA,AAAAA,iDAAA,WAAA,AAAAA,6BAAA,KAAA,OAAA,QAAA,AAAAC,0BAAAC,mBAAAF,YAAAA;WAAAA,PAOYe;YAPZ,AAAAZ,wBAAAH,eAAA,\/CAKaO;yBALb,AAAAJ,wBAAAH,eAAA,mEAAA,\/HAKmBQ;qBALnB,AAAAL,wBAAAH,eAAA,yEAAA,jIAKgCS;uBALhC,AAAAN,wBAAAH,eAAA,6EAAA,vIAK+CU;aAL\/C,AAAAP,wBAAAH,eAAA,hDAKgEW;YALhE,AAAAR,wBAAAH,eAAA,\/CAKsEY;WALtE,AAAAT,wBAAAH,eAAA,9CAK4Ea;wBAL5E,AAAAV,wBAAAH,eAAA,3DAKiFc;AALjF,AAAA,oBAQS,EAAA,EAAAE,aAAA,KAAA,AAAAC,wCAAA,AAAAC,0FAAA,\/CAAqBjC,0CAAsB8B;AARpD;AAAA,AAAA,MAAA,KAAAX,MAAA;;;AAAA,0FAAA,mxBAAA,mFAAA,mDAAA,2CAAA,uDAAA,2CAAA,8DAAA,QAAA,uDAAA,OAAA,yDAAA,OAAA,2FAAA,2EAAA,2DAAA,YAAA,p6CAUG,0BAAA,2CAAA,uIAAA,5MAACe,6HAAe,eAAA,8CAAoCR,yEACrC,0BAAA,2CAAA,8DAAA,QAAA,qDAAA,MAAA,oDAAA,MAAA,uDAAA,OAAA,yDAAA,OAAA,2DAAA,zbAACQ,wcAMMP,eACfC,yiBAKwBJ,yEACAC,qJAEnB;kBAAAU;AAAA,AAAY,oBAAMN;AAAN,AAAwB,AAACA;;AAAzB;;AACA,AAAiBM;;AACjB,AAAkBA;;AAF9B;;iBA1Bf,mFAAA,mDAAA,2CAAA,uDA8BY,0BAAA,2CAAA,yDAAA,OAAA,2DAAA,hMAACD,4MAEM,sCAAA,AAAA,2CAAA,4EAAA,QAAA,2DAAA,OAAA,sEAAA,cAAA,zSAAMX,wTAGrBD;;;AAnCJ,AAAA,AAAAT,yDAAA;;AAAA,AAAA,AAAAA,mDAAA,WAAAO;AAAA,AAAA,OAAAP,oEAAA,AAAAQ,wBAAAD;;;AAAA","names":["re-com.modal-panel\/modal-panel-args-desc","re-com.validate\/string-or-hiccup?","cljs.core\/string?","re-com.validate\/number-or-string?","cljs.core\/fn?","re-com.validate\/css-style?","re-com.validate\/html-attr?","var_args","args__26212__auto__","len__26205__auto__","i__26206__auto__","argseq__26213__auto__","cljs.core\/IndexedSeq","re-com.modal-panel\/modal-panel","p__27087","map__27088","cljs.core\/apply","cljs.core\/hash-map","cljs.core\/get","js\/Error","seq27086","cljs.core\/seq","child","wrap-nicely?","backdrop-color","backdrop-opacity","class","style","attr","backdrop-on-click","args","js\/goog.DEBUG","re-com.validate\/validate-args","re-com.validate\/extract-arg-data","cljs.core\/merge","event"]}
|
||||
520
resources/public/js/compiled/out/re_com/popover.cljs
Normal file
520
resources/public/js/compiled/out/re_com/popover.cljs
Normal file
|
|
@ -0,0 +1,520 @@
|
|||
(ns re-com.popover
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.util :refer [get-element-by-id px deref-or-value sum-scroll-offsets]]
|
||||
[re-com.box :refer [h-box v-box flex-child-style flex-flow-style align-style]]
|
||||
[re-com.validate :refer [position? position-options-list popover-status-type? popover-status-types-list number-or-string?
|
||||
string-or-hiccup? string-or-atom? vector-of-maps? css-style? html-attr?] :refer-macros [validate-args-macro]]
|
||||
[clojure.string :as string]
|
||||
[reagent.core :as reagent]
|
||||
[reagent.ratom :refer-macros [reaction]]))
|
||||
|
||||
|
||||
(defn point
|
||||
[x y]
|
||||
(str x "," y " "))
|
||||
|
||||
|
||||
(defn- split-keyword
|
||||
"Return the vector of the two keywords formed by splitting another keyword 'kw' on an internal delimiter (usually '-')
|
||||
(split-keyword :above-left \"-\") => [:above :left]"
|
||||
[kw delimiter]
|
||||
(let [keywords (string/split (str kw) (re-pattern (str "[" delimiter ":]")))]
|
||||
[(keyword (keywords 1)) (keyword (keywords 2))]))
|
||||
|
||||
|
||||
(defn- close-button
|
||||
"A button with a big X in it, placed to the right of the popover title"
|
||||
[showing? close-callback style]
|
||||
;; Can't use [button] because [button] already uses [popover] which would be a circular dependency.
|
||||
[:button
|
||||
{:on-click (handler-fn
|
||||
(if close-callback
|
||||
(close-callback)
|
||||
(reset! showing? false)))
|
||||
:class "close"
|
||||
:style (merge {:width "34px"
|
||||
:font-size "26px"
|
||||
:position "absolute"
|
||||
:top "4px"
|
||||
:right "2px"}
|
||||
style)}
|
||||
[:i {:class "zmdi zmdi-hc-fw-rc zmdi-close"}]])
|
||||
|
||||
|
||||
(defn- calc-popover-pos
|
||||
"Determine values for :left :right :top :bottom CSS styles.
|
||||
- pop-orient What side of the anchor the popover will be attached to. One of :above :below :left :right
|
||||
- p-width The px width of the popover after it has been rendered
|
||||
- p-height The px height of the popover after it has been rendered
|
||||
- pop-offset The number of pixels the popover is offset from it's natural position in relation to the popover-arrow (ugh, hard to explain)
|
||||
- arrow-length The px length of the arrow (from the point to middle of arrow base)
|
||||
- arrow-gap The px distance between the anchor and the arrow tip. Positive numbers push the popover away from the anchor
|
||||
"
|
||||
[pop-orient p-width p-height pop-offset arrow-length arrow-gap]
|
||||
(let [total-offset (+ arrow-length arrow-gap)
|
||||
popover-left (case pop-orient
|
||||
:left "initial" ;; TODO: Ultimately remove this (must have NO :left which is in Bootstrap .popover class)
|
||||
:right (px total-offset)
|
||||
(:above :below) (px (if pop-offset pop-offset (/ p-width 2)) :negative))
|
||||
popover-top (case pop-orient
|
||||
(:left :right) (px (if pop-offset pop-offset (/ p-height 2)) :negative)
|
||||
:above "initial"
|
||||
:below (px total-offset))
|
||||
popover-right (case pop-orient
|
||||
:left (px total-offset)
|
||||
:right nil
|
||||
:above nil
|
||||
:below nil)
|
||||
popover-bottom (case pop-orient
|
||||
:left nil
|
||||
:right nil
|
||||
:above (px total-offset)
|
||||
:below nil)]
|
||||
{:left popover-left :top popover-top :right popover-right :bottom popover-bottom}))
|
||||
|
||||
|
||||
(defn calculate-optimal-position
|
||||
"Calculate the optimal :position value that results in the least amount of clipping by the screen edges
|
||||
Taken from: https://github.com/Lambda-X/cljs-repl-web/blob/0.3.1/src/cljs/cljs_repl_web/views/utils.cljs#L52
|
||||
Thanks to @richiardiandrea and @tomek for this code"
|
||||
[[x y]]
|
||||
(let [w (.-innerWidth js/window) ;; Width/height of the browser window viewport including, if rendered, the vertical scrollbar
|
||||
h (.-innerHeight js/window)
|
||||
h-threshold-left (quot w 3)
|
||||
h-threshold-cent (* 2 h-threshold-left)
|
||||
h-position (cond
|
||||
(< x h-threshold-left) "right"
|
||||
(< x h-threshold-cent) "center"
|
||||
:else "left")
|
||||
v-threshold (quot h 2)
|
||||
v-position (if (< y v-threshold) "below" "above")]
|
||||
(keyword (str v-position \- h-position))))
|
||||
|
||||
|
||||
(defn calc-element-midpoint
|
||||
"Given a node reference, calculate the absolute x and y coordinates of the node's midpoint"
|
||||
[node]
|
||||
(let [bounding-rect (.getBoundingClientRect node)]
|
||||
[(/ (+ (.-right bounding-rect) (.-left bounding-rect)) 2) ;; x
|
||||
(/ (+ (.-bottom bounding-rect) (.-top bounding-rect)) 2)])) ;; y
|
||||
|
||||
|
||||
(defn- popover-arrow
|
||||
"Render the triangle which connects the popover to the anchor (using SVG)"
|
||||
[orientation pop-offset arrow-length arrow-width grey-arrow? no-border? popover-color]
|
||||
(let [half-arrow-width (/ arrow-width 2)
|
||||
arrow-shape {:left (str (point 0 0) (point arrow-length half-arrow-width) (point 0 arrow-width))
|
||||
:right (str (point arrow-length 0) (point 0 half-arrow-width) (point arrow-length arrow-width))
|
||||
:above (str (point 0 0) (point half-arrow-width arrow-length) (point arrow-width 0))
|
||||
:below (str (point 0 arrow-length) (point half-arrow-width 0) (point arrow-width arrow-length))}]
|
||||
[:svg {:class "popover-arrow"
|
||||
:style {:position "absolute"
|
||||
(case orientation ;; Connect arrow to edge of popover
|
||||
:left :right
|
||||
:right :left
|
||||
:above :bottom
|
||||
:below :top) (px arrow-length :negative)
|
||||
|
||||
(case orientation ;; Position the arrow at the top/left, center or bottom/right of the popover
|
||||
(:left :right) :top
|
||||
(:above :below) :left) (if (nil? pop-offset) "50%" (px pop-offset))
|
||||
|
||||
(case orientation ;; Adjust the arrow position so it's center is attached to the desired position set above
|
||||
(:left :right) :margin-top
|
||||
(:above :below) :margin-left) (px half-arrow-width :negative)
|
||||
|
||||
:width (px (case orientation ;; Arrow is rendered in a rectangle so choose the correct edge length
|
||||
(:left :right) arrow-length
|
||||
(:above :below) arrow-width))
|
||||
|
||||
:height (px (case orientation ;; Same as :width comment above
|
||||
(:left :right) arrow-width
|
||||
(:above :below) arrow-length))}}
|
||||
[:polyline {:points (arrow-shape orientation)
|
||||
:style {:fill (if popover-color
|
||||
popover-color
|
||||
(if grey-arrow? "#f7f7f7" "white"))
|
||||
:stroke (when-not no-border? "rgba(0, 0, 0, .2)")
|
||||
:stroke-width "1"}}]]))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: backdrop
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def backdrop-args-desc
|
||||
[{:name :opacity :required false :default 0.0 :type "double | string" :validate-fn number-or-string? :description [:span "opacity of backdrop from:" [:br] "0.0 (transparent) to 1.0 (opaque)"]}
|
||||
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the backdrop is clicked"}])
|
||||
|
||||
(defn- backdrop
|
||||
"Renders a backdrop dive which fills the entire page and responds to clicks on it. Can also specify how tranparent it should be"
|
||||
[& {:keys [opacity on-click] :as args}]
|
||||
{:pre [(validate-args-macro backdrop-args-desc args "backdrop")]}
|
||||
[:div {:class "rc-backdrop noselect"
|
||||
:style {:position "fixed"
|
||||
:left "0px"
|
||||
:top "0px"
|
||||
:width "100%"
|
||||
:height "100%"
|
||||
:background-color "black"
|
||||
:opacity (if opacity opacity 0.0)}
|
||||
:on-click (handler-fn (on-click))}])
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: popover-title
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def popover-title-args-desc
|
||||
[{:name :showing? :required true :type "boolean atom" :description "an atom. When the value is true, the popover shows."}
|
||||
{:name :title :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "describes the title of the popover. Default font size is 18px to make it stand out"}
|
||||
{:name :close-button? :required false :default true :type "boolean" :description "when true, displays the close button"}
|
||||
{:name :close-callback :required false :type "-> nil" :validate-fn fn? :description [:span "a function which takes no params and returns nothing. Called when the close button is pressed. Not required if " [:code ":showing?"] " atom passed in OR " [:code ":close-button?"] " is set to false"]}])
|
||||
|
||||
(defn- popover-title
|
||||
"Renders a title at the top of a popover with an optional close button on the far right"
|
||||
[& {:keys [title showing? close-button? close-callback]
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro popover-title-args-desc args "popover-title")]}
|
||||
(assert (or ((complement nil?) showing?) ((complement nil?) close-callback)) "Must specify either showing? OR close-callback")
|
||||
(let [close-button? (if (nil? close-button?) true close-button?)]
|
||||
[:h3.popover-title {:style (merge (flex-child-style "inherit")
|
||||
{:font-size "18px"})}
|
||||
[h-box
|
||||
:justify :between
|
||||
:align :center
|
||||
:children [title
|
||||
(when close-button? [close-button showing? close-callback])]]]))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: popover-border
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(defn next-even-integer
|
||||
[num]
|
||||
(-> num inc (/ 2) int (* 2)))
|
||||
|
||||
(defn calc-pop-offset
|
||||
[arrow-pos position-offset p-width p-height]
|
||||
(case arrow-pos
|
||||
:center nil
|
||||
:right (+ 20 position-offset)
|
||||
:below (+ 20 position-offset)
|
||||
:left (if p-width (- (- p-width 25) position-offset) p-width)
|
||||
:above (if p-height (- (- p-height 25) position-offset) p-height)))
|
||||
|
||||
(defn popover-clipping
|
||||
[node]
|
||||
(let [viewport-width (.-innerWidth js/window) ;; Width (in pixels) of the browser window viewport including, if rendered, the vertical scrollbar.
|
||||
viewport-height (.-innerHeight js/window) ;; Height (in pixels) of the browser window viewport including, if rendered, the horizontal scrollbar.
|
||||
bounding-rect (.getBoundingClientRect node)
|
||||
left (.-left bounding-rect)
|
||||
right (.-right bounding-rect)
|
||||
top (.-top bounding-rect)
|
||||
bottom (.-bottom bounding-rect)
|
||||
clip-left (when (< left 0) (- left))
|
||||
clip-right (when (> right viewport-width) (- right viewport-width))
|
||||
clip-top (when (< top 0) (- top))
|
||||
clip-bottom (when (> bottom viewport-height) (- bottom viewport-height))]
|
||||
#_(when (or (some? clip-left) (some? clip-right) (some? clip-top) (some? clip-bottom)) ;; Return full clipping details (or nil if not clipped)
|
||||
{:left clip-left :right clip-right :top clip-top :bottom clip-bottom})
|
||||
(or (some? clip-left) (some? clip-right) (some? clip-top) (some? clip-bottom)))) ;; Return boolean
|
||||
|
||||
(def popover-border-args-desc
|
||||
[{:name :children :required true :type "vector" :validate-fn sequential? :description "a vector of component markups"}
|
||||
{:name :position :required true :type "keyword atom" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :position-offset :required false :type "integer" :validate-fn number? :description [:span "px offset of the arrow from its default " [:code ":position"] " along the popover border. Is ignored when " [:code ":position"] " is one of the " [:code ":xxx-center"] " variants. Positive numbers slide the popover toward its center"]}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS style describing the popover width"}
|
||||
{:name :height :required false :default "auto" :type "string" :validate-fn string? :description "a CSS style describing the popover height"}
|
||||
{:name :popover-color :required false :default "white" :type "string" :validate-fn string? :description "fill color of the popover"}
|
||||
{:name :arrow-length :required false :default 11 :type "integer | string" :validate-fn number-or-string? :description "the length in pixels of the arrow (from pointy part to middle of arrow base)"}
|
||||
{:name :arrow-width :required false :default 22 :type "integer | string" :validate-fn number-or-string? :description "the width in pixels of arrow base"}
|
||||
{:name :arrow-gap :required false :default -1 :type "integer" :validate-fn number? :description "px gap between the anchor and the arrow tip. Positive numbers push the popover away from the anchor"}
|
||||
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS style which overrides the inner padding of the popover"}
|
||||
{:name :margin-left :required false :type "string" :validate-fn string? :description "a CSS style describing the horiztonal offset from anchor after position"}
|
||||
{:name :margin-top :required false :type "string" :validate-fn string? :description "a CSS style describing the vertical offset from anchor after position"}
|
||||
{:name :tooltip-style? :required false :default false :type "boolean" :description "setup popover styles for a tooltip"}
|
||||
{:name :title :required false :type "string | markup" :description "describes a title"}])
|
||||
|
||||
(defn popover-border
|
||||
"Renders an element or control along with a Bootstrap popover"
|
||||
[& {:keys [children position position-offset width height popover-color arrow-length arrow-width arrow-gap padding margin-left margin-top tooltip-style? title]
|
||||
:or {arrow-length 11 arrow-width 22 arrow-gap -1}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro popover-border-args-desc args "popover-border")]}
|
||||
(let [pop-id (gensym "popover-")
|
||||
rendered-once (reagent/atom false) ;; The initial render is off screen because rendering it in place does not render at final width, and we need to measure it to be able to place it properly
|
||||
ready-to-show? (reagent/atom false) ;; This is used by the optimal position code to avoid briefly seeing it in its intended position before quickly moving to the optimal position
|
||||
p-width (reagent/atom 0)
|
||||
p-height (reagent/atom 0)
|
||||
pop-offset (reagent/atom 0)
|
||||
found-optimal (reagent/atom false)
|
||||
calc-metrics (fn [position]
|
||||
(let [popover-elem (get-element-by-id pop-id)
|
||||
[orientation arrow-pos] (split-keyword position "-")
|
||||
grey-arrow? (and title (or (= orientation :below) (= arrow-pos :below)))]
|
||||
(reset! p-width (if popover-elem (next-even-integer (.-clientWidth popover-elem)) 0)) ;; next-even-integer required to avoid wiggling popovers (width/height appears to prefer being even and toggles without this call)
|
||||
(reset! p-height (if popover-elem (next-even-integer (.-clientHeight popover-elem)) 0))
|
||||
(reset! pop-offset (calc-pop-offset arrow-pos position-offset @p-width @p-height))
|
||||
[orientation grey-arrow?]))]
|
||||
(reagent/create-class
|
||||
{:display-name "popover-border"
|
||||
|
||||
:component-did-mount
|
||||
(fn []
|
||||
(reset! rendered-once true))
|
||||
|
||||
:component-did-update
|
||||
(fn [this]
|
||||
(let [pop-border-node (reagent/dom-node this)
|
||||
clipped? (popover-clipping pop-border-node)
|
||||
anchor-node (-> pop-border-node .-parentNode .-parentNode .-parentNode)] ;; Get reference to rc-point-wrapper node
|
||||
(when (and clipped? (not @found-optimal))
|
||||
(reset! position (calculate-optimal-position (calc-element-midpoint anchor-node)))
|
||||
(reset! found-optimal true))
|
||||
(calc-metrics @position)
|
||||
(reset! ready-to-show? true)))
|
||||
|
||||
:reagent-render
|
||||
(fn
|
||||
[& {:keys [children position position-offset width height popover-color arrow-length arrow-width arrow-gap padding margin-left margin-top tooltip-style? title]
|
||||
:or {arrow-length 11 arrow-width 22 arrow-gap -1}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro popover-border-args-desc args "popover-border")]}
|
||||
(let [[orientation grey-arrow?] (calc-metrics @position)]
|
||||
[:div.popover.fade.in
|
||||
{:id pop-id
|
||||
:style (merge (if @rendered-once
|
||||
(when pop-id (calc-popover-pos orientation @p-width @p-height @pop-offset arrow-length arrow-gap))
|
||||
{:top "-10000px" :left "-10000px"})
|
||||
|
||||
(if width {:width width})
|
||||
(if height {:height height})
|
||||
(if popover-color {:background-color popover-color})
|
||||
(when tooltip-style?
|
||||
{:border-radius "4px"
|
||||
:box-shadow "none"
|
||||
:border "none"})
|
||||
|
||||
;; The popover point is zero width, therefore its absolute children will consider this width when deciding their
|
||||
;; natural size and in particular, how they natually wrap text. The right hand side of the popover is used as a
|
||||
;; text wrapping point so it will wrap, depending on where the child is positioned. The margin is also taken into
|
||||
;; consideration for this point so below, we set the margins to negative a lot to prevent
|
||||
;; this annoying wrapping phenomenon.
|
||||
(case orientation
|
||||
:left {:margin-left "-2000px"}
|
||||
(:right :above :below) {:margin-right "-2000px"})
|
||||
;; optional override offsets
|
||||
(when margin-left {:margin-left margin-left})
|
||||
(when margin-top {:margin-top margin-top})
|
||||
|
||||
;; make it visible and turn off Bootstrap max-width and remove Bootstrap padding which adds an internal white border
|
||||
{:display "block"
|
||||
:opacity (if @ready-to-show? "1" "0")
|
||||
:max-width "none"
|
||||
:padding "0px"})}
|
||||
[popover-arrow orientation @pop-offset arrow-length arrow-width grey-arrow? tooltip-style? popover-color]
|
||||
(when title title)
|
||||
(into [:div.popover-content {:style {:padding padding}}] children)]))})))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: popover-content-wrapper
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def popover-content-wrapper-args-desc
|
||||
[{:name :showing-injected? :required true :type "boolean atom" :description [:span "an atom. When the value is true, the popover shows." [:br] [:strong "NOTE: "] "When used as direct " [:code ":popover"] " arg in popover-anchor-wrapper, this arg will be injected automatically by popover-anchor-wrapper. If using your own popover function, you must add this yourself"]}
|
||||
{:name :position-injected :required true :type "keyword atom" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list [:br] [:strong "NOTE: "] "See above NOTE for " [:code ":showing-injected?"] ". Same applies" ]}
|
||||
{:name :position-offset :required false :type "integer" :validate-fn number? :description [:span "px offset of the arrow from its default " [:code ":position"] " along the popover border. Is ignored when " [:code ":position"] " is one of the " [:code ":xxx-center"] " variants. Positive numbers slide the popover toward its center"]}
|
||||
{:name :no-clip? :required false :default false :type "boolean" :description "when an anchor is in a scrolling region (e.g. scroller component), the popover can sometimes be clipped. By passing true for this parameter, re-com will use a different CSS method to show the popover. This method is slightly inferior because the popover can't track the anchor if it is repositioned"}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS style representing the popover width"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "a CSS style representing the popover height"}
|
||||
{:name :backdrop-opacity :required false :default 0.0 :type "double | string" :validate-fn number-or-string? :description "indicates the opacity of the backdrop where 0.0=transparent, 1.0=opaque"}
|
||||
{:name :on-cancel :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the popover is cancelled (e.g. user clicks away)"}
|
||||
{:name :title :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "describes the title of the popover. The default font size is 18px to make it stand out"}
|
||||
{:name :close-button? :required false :default true :type "boolean" :description "when true, displays the close button"}
|
||||
{:name :body :required false :type "string | hiccup" :validate-fn string-or-hiccup? :description "describes the popover body. Must be a single component"}
|
||||
{:name :tooltip-style? :required false :default false :type "boolean" :description "setup popover styles for a tooltip"}
|
||||
{:name :popover-color :required false :default "white" :type "string" :validate-fn string? :description "fill color of the popover"}
|
||||
{:name :arrow-length :required false :default 11 :type "integer | string" :validate-fn number-or-string? :description "the length in pixels of the arrow (from pointy part to middle of arrow base)"}
|
||||
{:name :arrow-width :required false :default 22 :type "integer | string" :validate-fn number-or-string? :description "the width in pixels of arrow base"}
|
||||
{:name :arrow-gap :required false :default -1 :type "integer" :validate-fn number? :description "px gap between the anchor and the arrow tip. Positive numbers push the popover away from the anchor"}
|
||||
{:name :padding :required false :type "string" :validate-fn string? :description "a CSS style which overrides the inner padding of the popover"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "override component style(s) with a style map, only use in case of emergency"}])
|
||||
|
||||
(defn popover-content-wrapper
|
||||
"Abstracts several components to handle the 90% of cases for general popovers and dialog boxes"
|
||||
[& {:keys [showing-injected? position-injected position-offset no-clip? width height backdrop-opacity on-cancel title close-button? body tooltip-style? popover-color arrow-length arrow-width arrow-gap padding style]
|
||||
:or {arrow-length 11 arrow-width 22 arrow-gap -1}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro popover-content-wrapper-args-desc args "popover-content-wrapper")]}
|
||||
(let [left-offset (reagent/atom 0)
|
||||
top-offset (reagent/atom 0)
|
||||
position-no-clip-popover (fn position-no-clip-popover
|
||||
[this]
|
||||
(when no-clip?
|
||||
(let [node (reagent/dom-node this)
|
||||
popover-point-node (.-parentNode node) ;; Get reference to rc-popover-point node
|
||||
bounding-rect (.getBoundingClientRect popover-point-node)] ;; The modern magical way of getting offsetLeft and offsetTop. Returns this: https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMClientRect
|
||||
(reset! left-offset (.-left bounding-rect))
|
||||
(reset! top-offset (.-top bounding-rect)))))]
|
||||
(reagent/create-class
|
||||
{:display-name "popover-content-wrapper"
|
||||
|
||||
:component-did-mount
|
||||
(fn [this]
|
||||
(position-no-clip-popover this))
|
||||
|
||||
:component-did-update
|
||||
(fn [this]
|
||||
(position-no-clip-popover this))
|
||||
|
||||
:reagent-render
|
||||
(fn
|
||||
[& {:keys [showing-injected? position-injected position-offset no-clip? width height backdrop-opacity on-cancel title close-button? body tooltip-style? popover-color arrow-length arrow-width arrow-gap padding style]
|
||||
:or {arrow-length 11 arrow-width 22 arrow-gap -1}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro popover-content-wrapper-args-desc args "popover-content-wrapper")]}
|
||||
|
||||
@position-injected ;; Dereference this atom. Although nothing here needs its value explicitly, the calculation of left-offset and top-offset are affected by it for :no-clip? true
|
||||
[:div
|
||||
{:class "popover-content-wrapper"
|
||||
:style (merge (flex-child-style "inherit")
|
||||
(when no-clip? {:position "fixed"
|
||||
:left (px @left-offset)
|
||||
:top (px @top-offset)})
|
||||
style)}
|
||||
(when (and @showing-injected? on-cancel)
|
||||
[backdrop
|
||||
:opacity backdrop-opacity
|
||||
:on-click on-cancel])
|
||||
[popover-border
|
||||
:position position-injected
|
||||
:position-offset position-offset
|
||||
:width width
|
||||
:height height
|
||||
:tooltip-style? tooltip-style?
|
||||
:popover-color popover-color
|
||||
:arrow-length arrow-length
|
||||
:arrow-width arrow-width
|
||||
:arrow-gap arrow-gap
|
||||
:padding padding
|
||||
:title (when title [popover-title
|
||||
:title title
|
||||
:showing? showing-injected?
|
||||
:close-button? close-button?
|
||||
:close-callback on-cancel])
|
||||
:children [body]]])})))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: popover-anchor-wrapper
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def popover-anchor-wrapper-args-desc
|
||||
[{:name :showing? :required true :type "boolean atom" :description "an atom. When the value is true, the popover shows"}
|
||||
{:name :position :required true :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :anchor :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "the component the popover is attached to"}
|
||||
{:name :popover :required true :type "string | hiccup" :validate-fn string-or-hiccup? :description "the popover body component"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "override component style(s) with a style map, only use in case of emergency"}])
|
||||
|
||||
(defn popover-anchor-wrapper
|
||||
"Renders an element or control along with a Bootstrap popover"
|
||||
[& {:keys [showing? position anchor popover style] :as args}]
|
||||
{:pre [(validate-args-macro popover-anchor-wrapper-args-desc args "popover-anchor-wrapper")]}
|
||||
(let [external-position (reagent/atom position)
|
||||
internal-position (reagent/atom @external-position)
|
||||
reset-on-hide (reaction (when-not @showing? (reset! internal-position @external-position)))]
|
||||
(reagent/create-class
|
||||
{:display-name "popover-anchor-wrapper"
|
||||
|
||||
:reagent-render
|
||||
(fn
|
||||
[& {:keys [showing? position anchor popover style] :as args}]
|
||||
{:pre [(validate-args-macro popover-anchor-wrapper-args-desc args "popover-anchor-wrapper")]}
|
||||
|
||||
@reset-on-hide ;; Dereference this reaction, otherwise it won't be set up. The reaction is set to run whenever the popover closes
|
||||
(when (not= @external-position position) ;; Has position changed externally?
|
||||
(reset! external-position position)
|
||||
(reset! internal-position @external-position))
|
||||
(let [[orientation arrow-pos] (split-keyword @internal-position "-") ;; only need orientation here
|
||||
place-anchor-before? (case orientation (:left :above) false true)
|
||||
flex-flow (case orientation (:left :right) "row" "column")]
|
||||
[:div {:class "rc-popover-anchor-wrapper display-inline-flex"
|
||||
:style (merge (flex-child-style "inherit")
|
||||
style)}
|
||||
[:div ;; Wrapper around the anchor and the "point"
|
||||
{:class "rc-point-wrapper display-inline-flex"
|
||||
:style (merge (flex-child-style "auto")
|
||||
(flex-flow-style flex-flow)
|
||||
(align-style :align-items :center))}
|
||||
(when place-anchor-before? anchor)
|
||||
(when @showing?
|
||||
[:div ;; The "point" that connects the anchor to the popover
|
||||
{:class "rc-popover-point display-inline-flex"
|
||||
:style (merge (flex-child-style "auto")
|
||||
{:position "relative"
|
||||
:z-index 4})}
|
||||
(into popover [:showing-injected? showing? :position-injected internal-position])]) ;; NOTE: Inject showing? and position to the popover
|
||||
(when-not place-anchor-before? anchor)]]))})))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: popover-tooltip
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def popover-tooltip-args-desc
|
||||
[{:name :label :required true :type "string | hiccup | atom" :validate-fn string-or-hiccup? :description "the text (or component) for the tooltip"}
|
||||
{:name :showing? :required true :type "boolean atom" :description "an atom. When the value is true, the tooltip shows"}
|
||||
{:name :on-cancel :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the popover is cancelled (e.g. user clicks away)"}
|
||||
{:name :close-button? :required false :default false :type "boolean" :description "when true, displays the close button"}
|
||||
{:name :status :required false :type "keyword" :validate-fn popover-status-type? :description [:span "controls background color of the tooltip. " [:code "nil/omitted"] " for black or one of " popover-status-types-list " (although " [:code ":validating"] " is only used by the input-text component)"]}
|
||||
{:name :anchor :required true :type "hiccup" :validate-fn string-or-hiccup? :description "the component the tooltip is attached to"}
|
||||
{:name :position :required false :default :below-center :type "keyword" :validate-fn position? :description [:span "relative to this anchor. One of " position-options-list]}
|
||||
{:name :no-clip? :required false :default true :type "boolean" :description "when an anchor is in a scrolling region (e.g. scroller component), the popover can sometimes be clipped. When this parameter is true (which is the default), re-com will use a different CSS method to show the popover. This method is slightly inferior because the popover can't track the anchor if it is repositioned"}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "specifies width of the tooltip"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "override component style(s) with a style map, only use in case of emergency"}])
|
||||
|
||||
(defn popover-tooltip
|
||||
"Renders text as a tooltip in Bootstrap popover style"
|
||||
[& {:keys [label showing? on-cancel close-button? status anchor position no-clip? width style]
|
||||
:or {no-clip? true}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro popover-tooltip-args-desc args "popover-tooltip")]}
|
||||
(let [label (deref-or-value label)
|
||||
popover-color (case status
|
||||
:warning "#f57c00"
|
||||
:error "#d50000"
|
||||
:info "#333333"
|
||||
:success "#13C200"
|
||||
"black")]
|
||||
[popover-anchor-wrapper
|
||||
:showing? showing?
|
||||
:position (or position :below-center)
|
||||
:anchor anchor
|
||||
:style style
|
||||
:popover [popover-content-wrapper
|
||||
:no-clip? no-clip?
|
||||
:on-cancel on-cancel
|
||||
:width width
|
||||
:tooltip-style? true
|
||||
:popover-color popover-color
|
||||
:padding "3px 8px"
|
||||
:arrow-length 6
|
||||
:arrow-width 12
|
||||
:arrow-gap 4
|
||||
:body [v-box
|
||||
:style (if (= status :info)
|
||||
{:color "white"
|
||||
:font-size "14px"
|
||||
:padding "4px"}
|
||||
{:color "white"
|
||||
:font-size "12px"
|
||||
:font-weight "bold"
|
||||
:text-align "center"})
|
||||
:children [label (when close-button?
|
||||
[close-button showing? on-cancel {:font-size "20px"
|
||||
:color "white"
|
||||
:text-shadow "none"
|
||||
:right "1px"}])]]]]))
|
||||
File diff suppressed because one or more lines are too long
984
resources/public/js/compiled/out/re_com/popover.js
Normal file
984
resources/public/js/compiled/out/re_com/popover.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/popover.js.map
Normal file
1
resources/public/js/compiled/out/re_com/popover.js.map
Normal file
File diff suppressed because one or more lines are too long
154
resources/public/js/compiled/out/re_com/selection_list.cljs
Normal file
154
resources/public/js/compiled/out/re_com/selection_list.cljs
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
(ns re-com.selection-list
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require
|
||||
[re-com.text :refer [label]]
|
||||
[re-com.misc :refer [checkbox radio-button]]
|
||||
[re-com.box :refer [box border h-box v-box]]
|
||||
[re-com.validate :refer [vector-of-maps? string-or-atom? set-or-atom?] :refer-macros [validate-args-macro]]
|
||||
[re-com.util :refer [fmap deref-or-value]]))
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
(defn label-style
|
||||
([selected? as-exclusions?]
|
||||
(label-style selected? as-exclusions? nil))
|
||||
|
||||
([selected? as-exclusions? selected-color]
|
||||
;;TODO: margin-top required because currently checkbox & radio-button don't center label
|
||||
(let [base-style {:margin-top "1px"}
|
||||
base-style (if (and selected? as-exclusions?)
|
||||
(merge base-style {:text-decoration "line-through"})
|
||||
base-style)
|
||||
base-style (if (and selected? selected-color)
|
||||
(merge base-style {:color selected-color})
|
||||
base-style)]
|
||||
base-style)))
|
||||
|
||||
|
||||
(defn- check-clicked
|
||||
[selections item-id ticked? required?]
|
||||
(let [num-selected (count selections)
|
||||
only-item (when (= 1 num-selected) (first selections))]
|
||||
(if (and required? (= only-item item-id))
|
||||
selections ;; prevent unselect of last item
|
||||
(if ticked? (conj selections item-id) (disj selections item-id)))))
|
||||
|
||||
(defn- as-checked
|
||||
[item id-fn selections on-change disabled? label-fn required? as-exclusions?]
|
||||
;;TODO: Do we really need an anchor now that bootstrap styles not realy being used ?
|
||||
(let [item-id (id-fn item)]
|
||||
[box
|
||||
:class "list-group-item compact"
|
||||
:attr {:on-click (handler-fn (when-not disabled?
|
||||
(on-change (check-clicked selections item-id (not (selections item-id)) required?))))}
|
||||
:child [checkbox
|
||||
:model (some? (selections item-id))
|
||||
:on-change #() ;; handled by enclosing box
|
||||
:disabled? disabled?
|
||||
:label-style (label-style (selections item-id) as-exclusions?)
|
||||
:label (label-fn item)]]))
|
||||
|
||||
|
||||
(defn- radio-clicked
|
||||
[selections item-id required?]
|
||||
(if (and required? (selections item-id))
|
||||
selections ;; prevent unselect of radio
|
||||
(if (selections item-id) #{} #{item-id})))
|
||||
|
||||
(defn- as-radio
|
||||
[item id-fn selections on-change disabled? label-fn required? as-exclusions?]
|
||||
(let [item-id (id-fn item)]
|
||||
[box
|
||||
:class "list-group-item compact"
|
||||
:attr {:on-click (handler-fn (when-not disabled?
|
||||
(on-change (radio-clicked selections item-id required?))))}
|
||||
:child [radio-button
|
||||
:model (first selections)
|
||||
:value item-id
|
||||
:on-change #() ;; handled by enclosing box
|
||||
:disabled? disabled?
|
||||
:label-style (label-style (selections item-id) as-exclusions?)
|
||||
:label (label-fn item)]]))
|
||||
|
||||
|
||||
(def ^:const list-style
|
||||
;;TODO: These should be in CSS resource
|
||||
{:overflow-x "hidden"
|
||||
:overflow-y "auto"}) ;;TODO this should be handled by scroller later
|
||||
|
||||
(def ^:const spacing-bordered
|
||||
{:padding-top "0px"
|
||||
:padding-bottom "0px"
|
||||
:padding-left "5px"
|
||||
:padding-right "5px"
|
||||
:margin-top "5px"
|
||||
:margin-bottom "5px"})
|
||||
|
||||
(def ^:const spacing-unbordered
|
||||
{:padding-left "0px"
|
||||
:padding-right "5px"
|
||||
:padding-top "0px"
|
||||
:padding-bottom "0px"
|
||||
:margin-top "0px"
|
||||
:margin-bottom "0px"})
|
||||
|
||||
|
||||
(def selection-list-args-desc
|
||||
[{:name :choices :required true :type "vector of choices | atom" :validate-fn vector-of-maps? :description [:span "the selectable items. Elements can be strings or more interesting data items like {:label \"some name\" :sort 5}. Also see " [:code ":label-fn"] " below (list of maps also allowed)"]}
|
||||
{:name :model :required true :type "set of :ids within :choices | atom" :validate-fn set-or-atom? :description "the currently selected items. Note: items are considered distinct"}
|
||||
{:name :on-change :required true :type "set of :ids -> nil | atom" :validate-fn fn? :description [:span "a callback which will be passed set of the ids (as defined by " [:code ":id-fn"] ") of the selected items"]}
|
||||
{:name :id-fn :required false :default :id :type "choice -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":choices"] ", returns its unique identifier (aka id)"]}
|
||||
{:name :label-fn :required false :default :label :type "choice -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":choices"] ", returns its displayable label"]}
|
||||
{:name :multi-select? :required false :default true :type "boolean | atom" :description "when true, use check boxes, otherwise radio buttons"}
|
||||
{:name :as-exclusions? :required false :default false :type "boolean | atom" :description "when true, selected items are shown with struck-out labels"}
|
||||
{:name :required? :required false :default false :type "boolean | atom" :description "when true, at least one item must be selected. Note: being able to un-select a radio button is not a common use case, so this should probably be set to true when in single select mode"}
|
||||
{:name :width :required false :type "string | atom" :validate-fn string-or-atom? :description "a CSS style e.g. \"250px\". When specified, item labels may be clipped. Otherwise based on widest label"}
|
||||
{:name :height :required false :type "string | atom" :validate-fn string-or-atom? :description "a CSS style e.g. \"150px\". Size beyond which items will scroll"}
|
||||
{:name :max-height :required false :type "string | atom" :validate-fn string-or-atom? :description "a CSS style e.g. \"150px\". If there are less items then this height, box will shrink. If there are more, items will scroll"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "when true, the time input will be disabled. Can be atom or value"}
|
||||
{:name :hide-border? :required false :default false :type "boolean | atom" :description "when true, the list will be displayed without a border"}
|
||||
{:name :item-renderer :required false :type "-> nil | atom" :validate-fn fn? :description "a function which takes no params and returns nothing. Called for each element during setup, the returned component renders the element, responds to clicks etc."}])
|
||||
|
||||
;;TODO hide hover highlights for links when disabled
|
||||
(defn- list-container
|
||||
[{:keys [choices model on-change id-fn multi-select? disabled? hide-border? label-fn required? as-exclusions? item-renderer]
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro selection-list-args-desc args "selection-list")]}
|
||||
(let [selected (if multi-select? model (-> model first vector set))
|
||||
items (map (if item-renderer
|
||||
#(item-renderer % id-fn selected on-change disabled? label-fn required? as-exclusions?) ;; TODO do we need to pass id-fn?
|
||||
(if multi-select?
|
||||
#(as-checked % id-fn selected on-change disabled? label-fn required? as-exclusions?)
|
||||
#(as-radio % id-fn selected on-change disabled? label-fn required? as-exclusions?)))
|
||||
choices)
|
||||
bounds (select-keys args [:width :height :max-height])
|
||||
spacing (if hide-border? spacing-unbordered spacing-bordered)]
|
||||
;; In single select mode force selections to one. This causes a second render
|
||||
;; TODO: GR commented this out to fix the bug where #{nil} was being returned for an empty list. Remove when we're sure there are no ill effects.
|
||||
#_(when-not (= selected model) (on-change selected))
|
||||
[border
|
||||
:radius "4px"
|
||||
:border (when hide-border? "none")
|
||||
:child (into [:div {:class "list-group noselect" :style (merge list-style bounds spacing)}] items)]))
|
||||
|
||||
|
||||
(defn- configure
|
||||
"Augment passed attributes with defaults and deref any atoms"
|
||||
[attributes]
|
||||
(merge {:multi-select? true
|
||||
:as-exclusions? false
|
||||
:required? false
|
||||
:disabled? false
|
||||
:hide-border? false
|
||||
:id-fn :id
|
||||
:label-fn :label}
|
||||
(fmap deref-or-value attributes)))
|
||||
|
||||
(defn selection-list
|
||||
"Produce a list box with items arranged vertically"
|
||||
[& {:as args}]
|
||||
{:pre [(validate-args-macro selection-list-args-desc args "selection-list")]}
|
||||
;;NOTE: Consumer has complete control over what is selected or not. A current design tradeoff
|
||||
;; causes all selection changes to trigger a complete list re-render as a result of on-change callback.
|
||||
;; this approach may be not ideal for very large list choices.
|
||||
(fn [& {:as args}]
|
||||
[list-container (configure args)]))
|
||||
File diff suppressed because one or more lines are too long
251
resources/public/js/compiled/out/re_com/selection_list.js
Normal file
251
resources/public/js/compiled/out/re_com/selection_list.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
214
resources/public/js/compiled/out/re_com/splits.cljs
Normal file
214
resources/public/js/compiled/out/re_com/splits.cljs
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
(ns re-com.splits
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.util :refer [get-element-by-id sum-scroll-offsets]]
|
||||
[re-com.box :refer [flex-child-style flex-flow-style]]
|
||||
[re-com.validate :refer [string-or-hiccup? number-or-string? html-attr? css-style?] :refer-macros [validate-args-macro]]
|
||||
[reagent.core :as reagent]))
|
||||
|
||||
|
||||
(defn drag-handle
|
||||
"Return a drag handle to go into a vertical or horizontal splitter bar:
|
||||
orientation: Can be :horizonal or :vertical
|
||||
over?: When true, the mouse is assumed to be over the splitter so show a bolder color"
|
||||
[orientation over?]
|
||||
(let [vertical? (= orientation :vertical)
|
||||
length "20px"
|
||||
width "8px"
|
||||
pos1 "3px"
|
||||
pos2 "3px"
|
||||
color (if over? "#999" "#ccc")
|
||||
border (str "solid 1px " color)
|
||||
flex-flow (str (if vertical? "row" "column") " nowrap")]
|
||||
[:div {:class "display-flex"
|
||||
:style (merge (flex-flow-style flex-flow)
|
||||
{:width (if vertical? width length)
|
||||
:height (if vertical? length width)
|
||||
:margin "auto"})}
|
||||
[:div {:style (if vertical?
|
||||
{:width pos1 :height length :border-right border}
|
||||
{:width length :height pos1 :border-bottom border})}]
|
||||
[:div {:style (if vertical?
|
||||
{:width pos2 :height length :border-right border}
|
||||
{:width length :height pos2 :border-bottom border})}]]))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: h-split
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def hv-split-args-desc
|
||||
[{:name :panel-1 :required true :type "hiccup" :validate-fn string-or-hiccup? :description "markup to go in the left (or top) panel"}
|
||||
{:name :panel-2 :required true :type "hiccup" :validate-fn string-or-hiccup? :description "markup to go in the right (or bottom) panel"}
|
||||
{:name :size :required false :default "auto" :type "string" :validate-fn string? :description [:span "applied to the outer container of the two panels. Equivalent to CSS style " [:span.bold "flex"] "." [:br] "Examples: " [:code "initial"] ", " [:code "auto"] ", " [:code "none"]", " [:code "100px"] ", " [:code "2"] " or a generic triple of " [:code "grow shrink basis"]]}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "width of the outer container of the two panels. A CSS width style"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "height of the outer container of the two panels. A CSS height style"}
|
||||
{:name :on-split-change :required false :type "double -> nil" :validate-fn fn? :description [:span "called when the user moves the splitter bar (on mouse up, not on each mouse move). Given the new " [:code ":panel-1"] " percentage split"]}
|
||||
{:name :initial-split :required false :default 50 :type "double | string" :validate-fn number-or-string? :description [:span "initial split percentage for " [:code ":panel-1"] ". Can be double value or string (with/without percentage sign)"]}
|
||||
{:name :splitter-size :required false :default "8px" :type "string" :validate-fn string? :description "thickness of the splitter"}
|
||||
{:name :margin :required false :default "8px" :type "string" :validate-fn string? :description "thickness of the margin around the panels"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated, applied to outer container"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override, applied to outer container"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed, applied to outer container"]}])
|
||||
|
||||
(defn h-split
|
||||
"Returns markup for a horizontal layout component"
|
||||
[& {:keys [size width height on-split-change initial-split splitter-size margin]
|
||||
:or {size "auto" initial-split 50 splitter-size "8px" margin "8px"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro hv-split-args-desc args "h-split")]}
|
||||
(let [container-id (gensym "h-split-")
|
||||
split-perc (reagent/atom (js/parseInt initial-split)) ;; splitter position as a percentage of width
|
||||
dragging? (reagent/atom false) ;; is the user dragging the splitter (mouse is down)?
|
||||
over? (reagent/atom false) ;; is the mouse over the splitter, if so, highlight it
|
||||
|
||||
stop-drag (fn []
|
||||
(when on-split-change (on-split-change @split-perc))
|
||||
(reset! dragging? false))
|
||||
|
||||
calc-perc (fn [mouse-x] ;; turn a mouse y coordinate into a percentage position
|
||||
(let [container (get-element-by-id container-id) ;; the outside container
|
||||
offsets (sum-scroll-offsets container) ;; take any scrolling into account
|
||||
c-width (.-clientWidth container) ;; the container's width
|
||||
c-left-x (.-offsetLeft container) ;; the container's left X
|
||||
relative-x (+ (- mouse-x c-left-x) (:left offsets))] ;; the X of the mouse, relative to container
|
||||
(* 100.0 (/ relative-x c-width)))) ;; do the percentage calculation
|
||||
|
||||
<html>? #(= % (.-documentElement js/document)) ;; test for the <html> element
|
||||
|
||||
mouseout (fn [event]
|
||||
(if (<html>? (.-relatedTarget event)) ;; stop drag if we leave the <html> element
|
||||
(stop-drag)))
|
||||
|
||||
mousemove (fn [event]
|
||||
(reset! split-perc (calc-perc (.-clientX event))))
|
||||
|
||||
mousedown (fn [event]
|
||||
(.preventDefault event) ;; stop selection of text during drag
|
||||
(reset! dragging? true))
|
||||
|
||||
mouseover-split #(reset! over? true) ;; true CANCELs mouse-over (false cancels all others)
|
||||
mouseout-split #(reset! over? false)
|
||||
|
||||
make-container-attrs (fn [class style attr in-drag?]
|
||||
(merge {:class (str "rc-h-split display-flex " class)
|
||||
:id container-id
|
||||
:style (merge (flex-child-style size)
|
||||
(flex-flow-style "row nowrap")
|
||||
{:margin margin
|
||||
:width width
|
||||
:height height}
|
||||
style)}
|
||||
(when in-drag? ;; only listen when we are dragging
|
||||
{:on-mouse-up (handler-fn (stop-drag))
|
||||
:on-mouse-move (handler-fn (mousemove event))
|
||||
:on-mouse-out (handler-fn (mouseout event))})
|
||||
attr))
|
||||
|
||||
make-panel-attrs (fn [class in-drag? percentage]
|
||||
{:class (str "display-flex " class)
|
||||
:style (merge (flex-child-style (str percentage " 1 0px"))
|
||||
{:overflow "hidden"} ;; TODO: Shouldn't have this...test removing it
|
||||
(when in-drag? {:pointer-events "none"}))})
|
||||
|
||||
make-splitter-attrs (fn [class]
|
||||
{:class (str "display-flex " class)
|
||||
:on-mouse-down (handler-fn (mousedown event))
|
||||
:on-mouse-over (handler-fn (mouseover-split))
|
||||
:on-mouse-out (handler-fn (mouseout-split))
|
||||
:style (merge (flex-child-style (str "0 0 " splitter-size))
|
||||
{:cursor "col-resize"}
|
||||
(when @over? {:background-color "#f8f8f8"}))})]
|
||||
|
||||
(fn
|
||||
[& {:keys [panel-1 panel-2 class style attr]}]
|
||||
[:div (make-container-attrs class style attr @dragging?)
|
||||
[:div (make-panel-attrs "rc-h-split-top" @dragging? @split-perc)
|
||||
panel-1]
|
||||
[:div (make-splitter-attrs "rc-h-split-splitter")
|
||||
[drag-handle :vertical @over?]]
|
||||
[:div (make-panel-attrs "rc-h-split-bottom" @dragging? (- 100 @split-perc))
|
||||
panel-2]])))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: v-split
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(defn v-split
|
||||
"Returns markup for a vertical layout component"
|
||||
[& {:keys [size width height on-split-change initial-split splitter-size margin]
|
||||
:or {size "auto" initial-split 50 splitter-size "8px" margin "8px"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro hv-split-args-desc args "v-split")]}
|
||||
(let [container-id (gensym "v-split-")
|
||||
split-perc (reagent/atom (js/parseInt initial-split)) ;; splitter position as a percentage of height
|
||||
dragging? (reagent/atom false) ;; is the user dragging the splitter (mouse is down)?
|
||||
over? (reagent/atom false) ;; is the mouse over the splitter, if so, highlight it
|
||||
|
||||
stop-drag (fn []
|
||||
(when on-split-change (on-split-change @split-perc))
|
||||
(reset! dragging? false))
|
||||
|
||||
calc-perc (fn [mouse-y] ;; turn a mouse y coordinate into a percentage position
|
||||
(let [container (get-element-by-id container-id) ;; the outside container
|
||||
offsets (sum-scroll-offsets container) ;; take any scrolling into account
|
||||
c-height (.-clientHeight container) ;; the container's height
|
||||
c-top-y (.-offsetTop container) ;; the container's top Y
|
||||
relative-y (+ (- mouse-y c-top-y) (:top offsets))] ;; the Y of the mouse, relative to container
|
||||
(* 100.0 (/ relative-y c-height)))) ;; do the percentage calculation
|
||||
|
||||
<html>? #(= % (.-documentElement js/document)) ;; test for the <html> element
|
||||
|
||||
mouseout (fn [event]
|
||||
(if (<html>? (.-relatedTarget event)) ;; stop drag if we leave the <html> element
|
||||
(stop-drag)))
|
||||
|
||||
mousemove (fn [event]
|
||||
(reset! split-perc (calc-perc (.-clientY event))))
|
||||
|
||||
mousedown (fn [event]
|
||||
(.preventDefault event) ;; stop selection of text during drag
|
||||
(reset! dragging? true))
|
||||
|
||||
mouseover-split #(reset! over? true)
|
||||
mouseout-split #(reset! over? false)
|
||||
|
||||
make-container-attrs (fn [class style attr in-drag?]
|
||||
(merge {:class (str "rc-v-split display-flex " class)
|
||||
:id container-id
|
||||
:style (merge (flex-child-style size)
|
||||
(flex-flow-style "column nowrap")
|
||||
{:margin margin
|
||||
:width width
|
||||
:height height}
|
||||
style)}
|
||||
(when in-drag? ;; only listen when we are dragging
|
||||
{:on-mouse-up (handler-fn (stop-drag))
|
||||
:on-mouse-move (handler-fn (mousemove event))
|
||||
:on-mouse-out (handler-fn (mouseout event))})
|
||||
attr))
|
||||
|
||||
make-panel-attrs (fn [class in-drag? percentage]
|
||||
{:class (str "display-flex " class)
|
||||
:style (merge (flex-child-style (str percentage " 1 0px"))
|
||||
{:overflow "hidden"} ;; TODO: Shouldn't have this...test removing it
|
||||
(when in-drag? {:pointer-events "none"}))})
|
||||
|
||||
make-splitter-attrs (fn [class]
|
||||
{:class (str "display-flex " class)
|
||||
:on-mouse-down (handler-fn (mousedown event))
|
||||
:on-mouse-over (handler-fn (mouseover-split))
|
||||
:on-mouse-out (handler-fn (mouseout-split))
|
||||
:style (merge (flex-child-style (str "0 0 " splitter-size))
|
||||
{:cursor "row-resize"}
|
||||
(when @over? {:background-color "#f8f8f8"}))})]
|
||||
|
||||
(fn
|
||||
[& {:keys [panel-1 panel-2 class style attr]}]
|
||||
[:div (make-container-attrs class style attr @dragging?)
|
||||
[:div (make-panel-attrs "re-v-split-top" @dragging? @split-perc)
|
||||
panel-1]
|
||||
[:div (make-splitter-attrs "re-v-split-splitter")
|
||||
[drag-handle :horizontal @over?]]
|
||||
[:div (make-panel-attrs "re-v-split-bottom" @dragging? (- 100 @split-perc))
|
||||
panel-2]])))
|
||||
File diff suppressed because one or more lines are too long
402
resources/public/js/compiled/out/re_com/splits.js
Normal file
402
resources/public/js/compiled/out/re_com/splits.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/splits.js.map
Normal file
1
resources/public/js/compiled/out/re_com/splits.js.map
Normal file
File diff suppressed because one or more lines are too long
153
resources/public/js/compiled/out/re_com/tabs.cljs
Normal file
153
resources/public/js/compiled/out/re_com/tabs.cljs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
(ns re-com.tabs
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.util :refer [deref-or-value]]
|
||||
[re-com.box :refer [flex-child-style]]
|
||||
[re-com.validate :refer [css-style? vector-of-maps?] :refer-macros [validate-args-macro]]))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: horizontal-tabs
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(def tabs-args-desc
|
||||
[{:name :tabs :required true :type "vector of tabs | atom" :validate-fn vector-of-maps? :description "one element in the vector for each tab. Typically, each element is a map with :id and :label keys"}
|
||||
{:name :model :required true :type "unique-id | atom" :description "the unique identifier of the currently selected tab"}
|
||||
{:name :on-change :required true :type "unique-id -> nil" :validate-fn fn? :description "called when user alters the selection. Passed the unique identifier of the selection"}
|
||||
{:name :id-fn :required false :default :id :type "tab -> anything" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its unique identifier (aka id)"]}
|
||||
{:name :label-fn :required false :default :label :type "tab -> string | hiccup" :validate-fn ifn? :description [:span "given an element of " [:code ":tabs"] ", returns its displayable label"]}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override (for each individual tab rather than the container)"}])
|
||||
|
||||
(defn horizontal-tabs
|
||||
[& {:keys [model tabs on-change id-fn label-fn style]
|
||||
:or {id-fn :id label-fn :label}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro tabs-args-desc args "tabs")]}
|
||||
(let [current (deref-or-value model)
|
||||
tabs (deref-or-value tabs)
|
||||
_ (assert (not-empty (filter #(= current (id-fn %)) tabs)) "model not found in tabs vector")]
|
||||
[:ul
|
||||
{:class "rc-tabs nav nav-tabs noselect"
|
||||
:style (flex-child-style "none")}
|
||||
(for [t tabs]
|
||||
(let [id (id-fn t)
|
||||
label (label-fn t)
|
||||
selected? (= id current)] ;; must use current instead of @model to avoid reagent warnings
|
||||
[:li
|
||||
{:class (if selected? "active")
|
||||
:key (str id)}
|
||||
[:a
|
||||
{:style (merge {:cursor "pointer"}
|
||||
style)
|
||||
:on-click (when on-change (handler-fn (on-change id)))}
|
||||
label]]))]))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: horizontal-bar-tabs
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(defn- bar-tabs
|
||||
[& {:keys [model tabs on-change id-fn label-fn style vertical?]}]
|
||||
(let [current (deref-or-value model)
|
||||
tabs (deref-or-value tabs)
|
||||
_ (assert (not-empty (filter #(= current (id-fn %)) tabs)) "model not found in tabs vector")]
|
||||
[:div
|
||||
{:class (str "rc-tabs noselect btn-group" (if vertical? "-vertical"))
|
||||
:style (flex-child-style "none")}
|
||||
(for [t tabs]
|
||||
(let [id (id-fn t)
|
||||
label (label-fn t)
|
||||
selected? (= id current)] ;; must use current instead of @model to avoid reagent warnings
|
||||
[:button
|
||||
{:type "button"
|
||||
:key (str id)
|
||||
:class (str "btn btn-default " (if selected? "active"))
|
||||
:style style
|
||||
:on-click (when on-change (handler-fn (on-change id)))}
|
||||
label]))]))
|
||||
|
||||
|
||||
(defn horizontal-bar-tabs
|
||||
[& {:keys [model tabs on-change id-fn label-fn style]
|
||||
:or {id-fn :id label-fn :label}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro tabs-args-desc args "tabs")]}
|
||||
(bar-tabs
|
||||
:model model
|
||||
:tabs tabs
|
||||
:on-change on-change
|
||||
:style style
|
||||
:id-fn id-fn
|
||||
:label-fn label-fn
|
||||
:vertical? false))
|
||||
|
||||
(defn vertical-bar-tabs
|
||||
[& {:keys [model tabs on-change id-fn label-fn style]
|
||||
:or {id-fn :id label-fn :label}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro tabs-args-desc args "tabs")]}
|
||||
(bar-tabs
|
||||
:model model
|
||||
:tabs tabs
|
||||
:on-change on-change
|
||||
:style style
|
||||
:id-fn id-fn
|
||||
:label-fn label-fn
|
||||
:vertical? true))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: pill-tabs
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(defn- pill-tabs ;; tabs-like in action
|
||||
[& {:keys [model tabs on-change id-fn label-fn style vertical?]}]
|
||||
(let [current (deref-or-value model)
|
||||
tabs (deref-or-value tabs)
|
||||
_ (assert (not-empty (filter #(= current (id-fn %)) tabs)) "model not found in tabs vector")]
|
||||
[:ul
|
||||
{:class (str "rc-tabs noselect nav nav-pills" (when vertical? " nav-stacked"))
|
||||
:style (flex-child-style "none")
|
||||
:role "tabslist"}
|
||||
(for [t tabs]
|
||||
(let [id (id-fn t)
|
||||
label (label-fn t)
|
||||
selected? (= id current)] ;; must use 'current' instead of @model to avoid reagent warnings
|
||||
[:li
|
||||
{:class (if selected? "active" "")
|
||||
:key (str id)}
|
||||
[:a
|
||||
{:style (merge {:cursor "pointer"}
|
||||
style)
|
||||
:on-click (when on-change (handler-fn (on-change id)))}
|
||||
label]]))]))
|
||||
|
||||
|
||||
(defn horizontal-pill-tabs
|
||||
[& {:keys [model tabs on-change id-fn style label-fn]
|
||||
:or {id-fn :id label-fn :label}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro tabs-args-desc args "tabs")]}
|
||||
(pill-tabs
|
||||
:model model
|
||||
:tabs tabs
|
||||
:on-change on-change
|
||||
:style style
|
||||
:id-fn id-fn
|
||||
:label-fn label-fn
|
||||
:vertical? false))
|
||||
|
||||
|
||||
(defn vertical-pill-tabs
|
||||
[& {:keys [model tabs on-change id-fn style label-fn]
|
||||
:or {id-fn :id label-fn :label}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro tabs-args-desc args "tabs")]}
|
||||
(pill-tabs
|
||||
:model model
|
||||
:tabs tabs
|
||||
:on-change on-change
|
||||
:style style
|
||||
:id-fn id-fn
|
||||
:label-fn label-fn
|
||||
:vertical? true))
|
||||
File diff suppressed because one or more lines are too long
526
resources/public/js/compiled/out/re_com/tabs.js
Normal file
526
resources/public/js/compiled/out/re_com/tabs.js
Normal file
|
|
@ -0,0 +1,526 @@
|
|||
// Compiled by ClojureScript 1.9.229 {}
|
||||
goog.provide('re_com.tabs');
|
||||
goog.require('cljs.core');
|
||||
goog.require('re_com.util');
|
||||
goog.require('re_com.box');
|
||||
goog.require('re_com.validate');
|
||||
re_com.tabs.tabs_args_desc = new cljs.core.PersistentVector(null, 6, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.PersistentArrayMap(null, 5, [new cljs.core.Keyword(null,"name","name",1843675177),new cljs.core.Keyword(null,"tabs","tabs",-779855354),new cljs.core.Keyword(null,"required","required",1807647006),true,new cljs.core.Keyword(null,"type","type",1174270348),"vector of tabs | atom",new cljs.core.Keyword(null,"validate-fn","validate-fn",1430169944),re_com.validate.vector_of_maps_QMARK_,new cljs.core.Keyword(null,"description","description",-1428560544),"one element in the vector for each tab. Typically, each element is a map with :id and :label keys"], null),new cljs.core.PersistentArrayMap(null, 4, [new cljs.core.Keyword(null,"name","name",1843675177),new cljs.core.Keyword(null,"model","model",331153215),new cljs.core.Keyword(null,"required","required",1807647006),true,new cljs.core.Keyword(null,"type","type",1174270348),"unique-id | atom",new cljs.core.Keyword(null,"description","description",-1428560544),"the unique identifier of the currently selected tab"], null),new cljs.core.PersistentArrayMap(null, 5, [new cljs.core.Keyword(null,"name","name",1843675177),new cljs.core.Keyword(null,"on-change","on-change",-732046149),new cljs.core.Keyword(null,"required","required",1807647006),true,new cljs.core.Keyword(null,"type","type",1174270348),"unique-id -> nil",new cljs.core.Keyword(null,"validate-fn","validate-fn",1430169944),cljs.core.fn_QMARK_,new cljs.core.Keyword(null,"description","description",-1428560544),"called when user alters the selection. Passed the unique identifier of the selection"], null),new cljs.core.PersistentArrayMap(null, 6, [new cljs.core.Keyword(null,"name","name",1843675177),new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"required","required",1807647006),false,new cljs.core.Keyword(null,"default","default",-1987822328),new cljs.core.Keyword(null,"id","id",-1388402092),new cljs.core.Keyword(null,"type","type",1174270348),"tab -> anything",new cljs.core.Keyword(null,"validate-fn","validate-fn",1430169944),cljs.core.ifn_QMARK_,new cljs.core.Keyword(null,"description","description",-1428560544),new cljs.core.PersistentVector(null, 4, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"span","span",1394872991),"given an element of ",new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"code","code",1586293142),":tabs"], null),", returns its unique identifier (aka id)"], null)], null),new cljs.core.PersistentArrayMap(null, 6, [new cljs.core.Keyword(null,"name","name",1843675177),new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),new cljs.core.Keyword(null,"required","required",1807647006),false,new cljs.core.Keyword(null,"default","default",-1987822328),new cljs.core.Keyword(null,"label","label",1718410804),new cljs.core.Keyword(null,"type","type",1174270348),"tab -> string | hiccup",new cljs.core.Keyword(null,"validate-fn","validate-fn",1430169944),cljs.core.ifn_QMARK_,new cljs.core.Keyword(null,"description","description",-1428560544),new cljs.core.PersistentVector(null, 4, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"span","span",1394872991),"given an element of ",new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"code","code",1586293142),":tabs"], null),", returns its displayable label"], null)], null),new cljs.core.PersistentArrayMap(null, 5, [new cljs.core.Keyword(null,"name","name",1843675177),new cljs.core.Keyword(null,"style","style",-496642736),new cljs.core.Keyword(null,"required","required",1807647006),false,new cljs.core.Keyword(null,"type","type",1174270348),"CSS style map",new cljs.core.Keyword(null,"validate-fn","validate-fn",1430169944),re_com.validate.css_style_QMARK_,new cljs.core.Keyword(null,"description","description",-1428560544),"CSS styles to add or override (for each individual tab rather than the container)"], null)], null);
|
||||
re_com.tabs.horizontal_tabs = (function re_com$tabs$horizontal_tabs(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___31634 = arguments.length;
|
||||
var i__26206__auto___31635 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___31635 < len__26205__auto___31634)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___31635]));
|
||||
|
||||
var G__31636 = (i__26206__auto___31635 + (1));
|
||||
i__26206__auto___31635 = G__31636;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.tabs.horizontal_tabs.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_tabs.cljs$core$IFn$_invoke$arity$variadic = (function (p__31627){
|
||||
var map__31628 = p__31627;
|
||||
var map__31628__$1 = ((((!((map__31628 == null)))?((((map__31628.cljs$lang$protocol_mask$partition0$ & (64))) || (map__31628.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__31628):map__31628);
|
||||
var args = map__31628__$1;
|
||||
var model = cljs.core.get.call(null,map__31628__$1,new cljs.core.Keyword(null,"model","model",331153215));
|
||||
var tabs = cljs.core.get.call(null,map__31628__$1,new cljs.core.Keyword(null,"tabs","tabs",-779855354));
|
||||
var on_change = cljs.core.get.call(null,map__31628__$1,new cljs.core.Keyword(null,"on-change","on-change",-732046149));
|
||||
var id_fn = cljs.core.get.call(null,map__31628__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
var label_fn = cljs.core.get.call(null,map__31628__$1,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),new cljs.core.Keyword(null,"label","label",1718410804));
|
||||
var style = cljs.core.get.call(null,map__31628__$1,new cljs.core.Keyword(null,"style","style",-496642736));
|
||||
if(cljs.core.truth_(((!(goog.DEBUG))?true:re_com.validate.validate_args.call(null,re_com.validate.extract_arg_data.call(null,re_com.tabs.tabs_args_desc),args,"tabs")))){
|
||||
} else {
|
||||
throw (new Error("Assert failed: (validate-args-macro tabs-args-desc args \"tabs\")"));
|
||||
}
|
||||
|
||||
var current = re_com.util.deref_or_value.call(null,model);
|
||||
var tabs__$1 = re_com.util.deref_or_value.call(null,tabs);
|
||||
var _ = (cljs.core.truth_(cljs.core.not_empty.call(null,cljs.core.filter.call(null,((function (current,tabs__$1,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style){
|
||||
return (function (p1__31625_SHARP_){
|
||||
return cljs.core._EQ_.call(null,current,id_fn.call(null,p1__31625_SHARP_));
|
||||
});})(current,tabs__$1,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style))
|
||||
,tabs__$1)))?null:(function(){throw (new Error([cljs.core.str("Assert failed: "),cljs.core.str("model not found in tabs vector"),cljs.core.str("\n"),cljs.core.str("(not-empty (filter (fn* [p1__31625#] (= current (id-fn p1__31625#))) tabs))")].join('')))})());
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"ul","ul",-1349521403),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"class","class",-2030961996),"rc-tabs nav nav-tabs noselect",new cljs.core.Keyword(null,"style","style",-496642736),re_com.box.flex_child_style.call(null,"none")], null),(function (){var iter__25910__auto__ = ((function (current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style){
|
||||
return (function re_com$tabs$iter__31630(s__31631){
|
||||
return (new cljs.core.LazySeq(null,((function (current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style){
|
||||
return (function (){
|
||||
var s__31631__$1 = s__31631;
|
||||
while(true){
|
||||
var temp__4657__auto__ = cljs.core.seq.call(null,s__31631__$1);
|
||||
if(temp__4657__auto__){
|
||||
var s__31631__$2 = temp__4657__auto__;
|
||||
if(cljs.core.chunked_seq_QMARK_.call(null,s__31631__$2)){
|
||||
var c__25908__auto__ = cljs.core.chunk_first.call(null,s__31631__$2);
|
||||
var size__25909__auto__ = cljs.core.count.call(null,c__25908__auto__);
|
||||
var b__31633 = cljs.core.chunk_buffer.call(null,size__25909__auto__);
|
||||
if((function (){var i__31632 = (0);
|
||||
while(true){
|
||||
if((i__31632 < size__25909__auto__)){
|
||||
var t = cljs.core._nth.call(null,c__25908__auto__,i__31632);
|
||||
cljs.core.chunk_append.call(null,b__31633,(function (){var id = id_fn.call(null,t);
|
||||
var label = label_fn.call(null,t);
|
||||
var selected_QMARK_ = cljs.core._EQ_.call(null,id,current);
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"li","li",723558921),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"class","class",-2030961996),((selected_QMARK_)?"active":null),new cljs.core.Keyword(null,"key","key",-1516042587),[cljs.core.str(id)].join('')], null),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"a","a",-2123407586),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"style","style",-496642736),cljs.core.merge.call(null,new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"cursor","cursor",1011937484),"pointer"], null),style),new cljs.core.Keyword(null,"on-click","on-click",1632826543),(cljs.core.truth_(on_change)?((function (i__31632,id,label,selected_QMARK_,t,c__25908__auto__,size__25909__auto__,b__31633,s__31631__$2,temp__4657__auto__,current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style){
|
||||
return (function (event){
|
||||
on_change.call(null,id);
|
||||
|
||||
return null;
|
||||
});})(i__31632,id,label,selected_QMARK_,t,c__25908__auto__,size__25909__auto__,b__31633,s__31631__$2,temp__4657__auto__,current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style))
|
||||
:null)], null),label], null)], null);
|
||||
})());
|
||||
|
||||
var G__31637 = (i__31632 + (1));
|
||||
i__31632 = G__31637;
|
||||
continue;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})()){
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__31633),re_com$tabs$iter__31630.call(null,cljs.core.chunk_rest.call(null,s__31631__$2)));
|
||||
} else {
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__31633),null);
|
||||
}
|
||||
} else {
|
||||
var t = cljs.core.first.call(null,s__31631__$2);
|
||||
return cljs.core.cons.call(null,(function (){var id = id_fn.call(null,t);
|
||||
var label = label_fn.call(null,t);
|
||||
var selected_QMARK_ = cljs.core._EQ_.call(null,id,current);
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"li","li",723558921),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"class","class",-2030961996),((selected_QMARK_)?"active":null),new cljs.core.Keyword(null,"key","key",-1516042587),[cljs.core.str(id)].join('')], null),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"a","a",-2123407586),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"style","style",-496642736),cljs.core.merge.call(null,new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"cursor","cursor",1011937484),"pointer"], null),style),new cljs.core.Keyword(null,"on-click","on-click",1632826543),(cljs.core.truth_(on_change)?((function (id,label,selected_QMARK_,t,s__31631__$2,temp__4657__auto__,current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style){
|
||||
return (function (event){
|
||||
on_change.call(null,id);
|
||||
|
||||
return null;
|
||||
});})(id,label,selected_QMARK_,t,s__31631__$2,temp__4657__auto__,current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style))
|
||||
:null)], null),label], null)], null);
|
||||
})(),re_com$tabs$iter__31630.call(null,cljs.core.rest.call(null,s__31631__$2)));
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});})(current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style))
|
||||
,null,null));
|
||||
});})(current,tabs__$1,_,map__31628,map__31628__$1,args,model,tabs,on_change,id_fn,label_fn,style))
|
||||
;
|
||||
return iter__25910__auto__.call(null,tabs__$1);
|
||||
})()], null);
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_tabs.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.tabs.horizontal_tabs.cljs$lang$applyTo = (function (seq31626){
|
||||
return re_com.tabs.horizontal_tabs.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq31626));
|
||||
});
|
||||
|
||||
re_com.tabs.bar_tabs = (function re_com$tabs$bar_tabs(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___31647 = arguments.length;
|
||||
var i__26206__auto___31648 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___31648 < len__26205__auto___31647)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___31648]));
|
||||
|
||||
var G__31649 = (i__26206__auto___31648 + (1));
|
||||
i__26206__auto___31648 = G__31649;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.tabs.bar_tabs.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.tabs.bar_tabs.cljs$core$IFn$_invoke$arity$variadic = (function (p__31640){
|
||||
var map__31641 = p__31640;
|
||||
var map__31641__$1 = ((((!((map__31641 == null)))?((((map__31641.cljs$lang$protocol_mask$partition0$ & (64))) || (map__31641.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__31641):map__31641);
|
||||
var model = cljs.core.get.call(null,map__31641__$1,new cljs.core.Keyword(null,"model","model",331153215));
|
||||
var tabs = cljs.core.get.call(null,map__31641__$1,new cljs.core.Keyword(null,"tabs","tabs",-779855354));
|
||||
var on_change = cljs.core.get.call(null,map__31641__$1,new cljs.core.Keyword(null,"on-change","on-change",-732046149));
|
||||
var id_fn = cljs.core.get.call(null,map__31641__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798));
|
||||
var label_fn = cljs.core.get.call(null,map__31641__$1,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263));
|
||||
var style = cljs.core.get.call(null,map__31641__$1,new cljs.core.Keyword(null,"style","style",-496642736));
|
||||
var vertical_QMARK_ = cljs.core.get.call(null,map__31641__$1,new cljs.core.Keyword(null,"vertical?","vertical?",-1522630444));
|
||||
var current = re_com.util.deref_or_value.call(null,model);
|
||||
var tabs__$1 = re_com.util.deref_or_value.call(null,tabs);
|
||||
var _ = (cljs.core.truth_(cljs.core.not_empty.call(null,cljs.core.filter.call(null,((function (current,tabs__$1,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (p1__31638_SHARP_){
|
||||
return cljs.core._EQ_.call(null,current,id_fn.call(null,p1__31638_SHARP_));
|
||||
});})(current,tabs__$1,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
,tabs__$1)))?null:(function(){throw (new Error([cljs.core.str("Assert failed: "),cljs.core.str("model not found in tabs vector"),cljs.core.str("\n"),cljs.core.str("(not-empty (filter (fn* [p1__31638#] (= current (id-fn p1__31638#))) tabs))")].join('')))})());
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"class","class",-2030961996),[cljs.core.str("rc-tabs noselect btn-group"),cljs.core.str((cljs.core.truth_(vertical_QMARK_)?"-vertical":null))].join(''),new cljs.core.Keyword(null,"style","style",-496642736),re_com.box.flex_child_style.call(null,"none")], null),(function (){var iter__25910__auto__ = ((function (current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function re_com$tabs$iter__31643(s__31644){
|
||||
return (new cljs.core.LazySeq(null,((function (current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (){
|
||||
var s__31644__$1 = s__31644;
|
||||
while(true){
|
||||
var temp__4657__auto__ = cljs.core.seq.call(null,s__31644__$1);
|
||||
if(temp__4657__auto__){
|
||||
var s__31644__$2 = temp__4657__auto__;
|
||||
if(cljs.core.chunked_seq_QMARK_.call(null,s__31644__$2)){
|
||||
var c__25908__auto__ = cljs.core.chunk_first.call(null,s__31644__$2);
|
||||
var size__25909__auto__ = cljs.core.count.call(null,c__25908__auto__);
|
||||
var b__31646 = cljs.core.chunk_buffer.call(null,size__25909__auto__);
|
||||
if((function (){var i__31645 = (0);
|
||||
while(true){
|
||||
if((i__31645 < size__25909__auto__)){
|
||||
var t = cljs.core._nth.call(null,c__25908__auto__,i__31645);
|
||||
cljs.core.chunk_append.call(null,b__31646,(function (){var id = id_fn.call(null,t);
|
||||
var label = label_fn.call(null,t);
|
||||
var selected_QMARK_ = cljs.core._EQ_.call(null,id,current);
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"button","button",1456579943),new cljs.core.PersistentArrayMap(null, 5, [new cljs.core.Keyword(null,"type","type",1174270348),"button",new cljs.core.Keyword(null,"key","key",-1516042587),[cljs.core.str(id)].join(''),new cljs.core.Keyword(null,"class","class",-2030961996),[cljs.core.str("btn btn-default "),cljs.core.str(((selected_QMARK_)?"active":null))].join(''),new cljs.core.Keyword(null,"style","style",-496642736),style,new cljs.core.Keyword(null,"on-click","on-click",1632826543),(cljs.core.truth_(on_change)?((function (i__31645,id,label,selected_QMARK_,t,c__25908__auto__,size__25909__auto__,b__31646,s__31644__$2,temp__4657__auto__,current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (event){
|
||||
on_change.call(null,id);
|
||||
|
||||
return null;
|
||||
});})(i__31645,id,label,selected_QMARK_,t,c__25908__auto__,size__25909__auto__,b__31646,s__31644__$2,temp__4657__auto__,current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
:null)], null),label], null);
|
||||
})());
|
||||
|
||||
var G__31650 = (i__31645 + (1));
|
||||
i__31645 = G__31650;
|
||||
continue;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})()){
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__31646),re_com$tabs$iter__31643.call(null,cljs.core.chunk_rest.call(null,s__31644__$2)));
|
||||
} else {
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__31646),null);
|
||||
}
|
||||
} else {
|
||||
var t = cljs.core.first.call(null,s__31644__$2);
|
||||
return cljs.core.cons.call(null,(function (){var id = id_fn.call(null,t);
|
||||
var label = label_fn.call(null,t);
|
||||
var selected_QMARK_ = cljs.core._EQ_.call(null,id,current);
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"button","button",1456579943),new cljs.core.PersistentArrayMap(null, 5, [new cljs.core.Keyword(null,"type","type",1174270348),"button",new cljs.core.Keyword(null,"key","key",-1516042587),[cljs.core.str(id)].join(''),new cljs.core.Keyword(null,"class","class",-2030961996),[cljs.core.str("btn btn-default "),cljs.core.str(((selected_QMARK_)?"active":null))].join(''),new cljs.core.Keyword(null,"style","style",-496642736),style,new cljs.core.Keyword(null,"on-click","on-click",1632826543),(cljs.core.truth_(on_change)?((function (id,label,selected_QMARK_,t,s__31644__$2,temp__4657__auto__,current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (event){
|
||||
on_change.call(null,id);
|
||||
|
||||
return null;
|
||||
});})(id,label,selected_QMARK_,t,s__31644__$2,temp__4657__auto__,current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
:null)], null),label], null);
|
||||
})(),re_com$tabs$iter__31643.call(null,cljs.core.rest.call(null,s__31644__$2)));
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});})(current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
,null,null));
|
||||
});})(current,tabs__$1,_,map__31641,map__31641__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
;
|
||||
return iter__25910__auto__.call(null,tabs__$1);
|
||||
})()], null);
|
||||
});
|
||||
|
||||
re_com.tabs.bar_tabs.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.tabs.bar_tabs.cljs$lang$applyTo = (function (seq31639){
|
||||
return re_com.tabs.bar_tabs.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq31639));
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_bar_tabs = (function re_com$tabs$horizontal_bar_tabs(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___31655 = arguments.length;
|
||||
var i__26206__auto___31656 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___31656 < len__26205__auto___31655)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___31656]));
|
||||
|
||||
var G__31657 = (i__26206__auto___31656 + (1));
|
||||
i__26206__auto___31656 = G__31657;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.tabs.horizontal_bar_tabs.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_bar_tabs.cljs$core$IFn$_invoke$arity$variadic = (function (p__31652){
|
||||
var map__31653 = p__31652;
|
||||
var map__31653__$1 = ((((!((map__31653 == null)))?((((map__31653.cljs$lang$protocol_mask$partition0$ & (64))) || (map__31653.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__31653):map__31653);
|
||||
var args = map__31653__$1;
|
||||
var model = cljs.core.get.call(null,map__31653__$1,new cljs.core.Keyword(null,"model","model",331153215));
|
||||
var tabs = cljs.core.get.call(null,map__31653__$1,new cljs.core.Keyword(null,"tabs","tabs",-779855354));
|
||||
var on_change = cljs.core.get.call(null,map__31653__$1,new cljs.core.Keyword(null,"on-change","on-change",-732046149));
|
||||
var id_fn = cljs.core.get.call(null,map__31653__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
var label_fn = cljs.core.get.call(null,map__31653__$1,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),new cljs.core.Keyword(null,"label","label",1718410804));
|
||||
var style = cljs.core.get.call(null,map__31653__$1,new cljs.core.Keyword(null,"style","style",-496642736));
|
||||
if(cljs.core.truth_(((!(goog.DEBUG))?true:re_com.validate.validate_args.call(null,re_com.validate.extract_arg_data.call(null,re_com.tabs.tabs_args_desc),args,"tabs")))){
|
||||
} else {
|
||||
throw (new Error("Assert failed: (validate-args-macro tabs-args-desc args \"tabs\")"));
|
||||
}
|
||||
|
||||
return re_com.tabs.bar_tabs.call(null,new cljs.core.Keyword(null,"model","model",331153215),model,new cljs.core.Keyword(null,"tabs","tabs",-779855354),tabs,new cljs.core.Keyword(null,"on-change","on-change",-732046149),on_change,new cljs.core.Keyword(null,"style","style",-496642736),style,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),id_fn,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),label_fn,new cljs.core.Keyword(null,"vertical?","vertical?",-1522630444),false);
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_bar_tabs.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.tabs.horizontal_bar_tabs.cljs$lang$applyTo = (function (seq31651){
|
||||
return re_com.tabs.horizontal_bar_tabs.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq31651));
|
||||
});
|
||||
|
||||
re_com.tabs.vertical_bar_tabs = (function re_com$tabs$vertical_bar_tabs(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___31662 = arguments.length;
|
||||
var i__26206__auto___31663 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___31663 < len__26205__auto___31662)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___31663]));
|
||||
|
||||
var G__31664 = (i__26206__auto___31663 + (1));
|
||||
i__26206__auto___31663 = G__31664;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.tabs.vertical_bar_tabs.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.tabs.vertical_bar_tabs.cljs$core$IFn$_invoke$arity$variadic = (function (p__31659){
|
||||
var map__31660 = p__31659;
|
||||
var map__31660__$1 = ((((!((map__31660 == null)))?((((map__31660.cljs$lang$protocol_mask$partition0$ & (64))) || (map__31660.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__31660):map__31660);
|
||||
var args = map__31660__$1;
|
||||
var model = cljs.core.get.call(null,map__31660__$1,new cljs.core.Keyword(null,"model","model",331153215));
|
||||
var tabs = cljs.core.get.call(null,map__31660__$1,new cljs.core.Keyword(null,"tabs","tabs",-779855354));
|
||||
var on_change = cljs.core.get.call(null,map__31660__$1,new cljs.core.Keyword(null,"on-change","on-change",-732046149));
|
||||
var id_fn = cljs.core.get.call(null,map__31660__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
var label_fn = cljs.core.get.call(null,map__31660__$1,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),new cljs.core.Keyword(null,"label","label",1718410804));
|
||||
var style = cljs.core.get.call(null,map__31660__$1,new cljs.core.Keyword(null,"style","style",-496642736));
|
||||
if(cljs.core.truth_(((!(goog.DEBUG))?true:re_com.validate.validate_args.call(null,re_com.validate.extract_arg_data.call(null,re_com.tabs.tabs_args_desc),args,"tabs")))){
|
||||
} else {
|
||||
throw (new Error("Assert failed: (validate-args-macro tabs-args-desc args \"tabs\")"));
|
||||
}
|
||||
|
||||
return re_com.tabs.bar_tabs.call(null,new cljs.core.Keyword(null,"model","model",331153215),model,new cljs.core.Keyword(null,"tabs","tabs",-779855354),tabs,new cljs.core.Keyword(null,"on-change","on-change",-732046149),on_change,new cljs.core.Keyword(null,"style","style",-496642736),style,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),id_fn,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),label_fn,new cljs.core.Keyword(null,"vertical?","vertical?",-1522630444),true);
|
||||
});
|
||||
|
||||
re_com.tabs.vertical_bar_tabs.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.tabs.vertical_bar_tabs.cljs$lang$applyTo = (function (seq31658){
|
||||
return re_com.tabs.vertical_bar_tabs.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq31658));
|
||||
});
|
||||
|
||||
re_com.tabs.pill_tabs = (function re_com$tabs$pill_tabs(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___31674 = arguments.length;
|
||||
var i__26206__auto___31675 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___31675 < len__26205__auto___31674)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___31675]));
|
||||
|
||||
var G__31676 = (i__26206__auto___31675 + (1));
|
||||
i__26206__auto___31675 = G__31676;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.tabs.pill_tabs.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.tabs.pill_tabs.cljs$core$IFn$_invoke$arity$variadic = (function (p__31667){
|
||||
var map__31668 = p__31667;
|
||||
var map__31668__$1 = ((((!((map__31668 == null)))?((((map__31668.cljs$lang$protocol_mask$partition0$ & (64))) || (map__31668.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__31668):map__31668);
|
||||
var model = cljs.core.get.call(null,map__31668__$1,new cljs.core.Keyword(null,"model","model",331153215));
|
||||
var tabs = cljs.core.get.call(null,map__31668__$1,new cljs.core.Keyword(null,"tabs","tabs",-779855354));
|
||||
var on_change = cljs.core.get.call(null,map__31668__$1,new cljs.core.Keyword(null,"on-change","on-change",-732046149));
|
||||
var id_fn = cljs.core.get.call(null,map__31668__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798));
|
||||
var label_fn = cljs.core.get.call(null,map__31668__$1,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263));
|
||||
var style = cljs.core.get.call(null,map__31668__$1,new cljs.core.Keyword(null,"style","style",-496642736));
|
||||
var vertical_QMARK_ = cljs.core.get.call(null,map__31668__$1,new cljs.core.Keyword(null,"vertical?","vertical?",-1522630444));
|
||||
var current = re_com.util.deref_or_value.call(null,model);
|
||||
var tabs__$1 = re_com.util.deref_or_value.call(null,tabs);
|
||||
var _ = (cljs.core.truth_(cljs.core.not_empty.call(null,cljs.core.filter.call(null,((function (current,tabs__$1,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (p1__31665_SHARP_){
|
||||
return cljs.core._EQ_.call(null,current,id_fn.call(null,p1__31665_SHARP_));
|
||||
});})(current,tabs__$1,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
,tabs__$1)))?null:(function(){throw (new Error([cljs.core.str("Assert failed: "),cljs.core.str("model not found in tabs vector"),cljs.core.str("\n"),cljs.core.str("(not-empty (filter (fn* [p1__31665#] (= current (id-fn p1__31665#))) tabs))")].join('')))})());
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"ul","ul",-1349521403),new cljs.core.PersistentArrayMap(null, 3, [new cljs.core.Keyword(null,"class","class",-2030961996),[cljs.core.str("rc-tabs noselect nav nav-pills"),cljs.core.str((cljs.core.truth_(vertical_QMARK_)?" nav-stacked":null))].join(''),new cljs.core.Keyword(null,"style","style",-496642736),re_com.box.flex_child_style.call(null,"none"),new cljs.core.Keyword(null,"role","role",-736691072),"tabslist"], null),(function (){var iter__25910__auto__ = ((function (current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function re_com$tabs$iter__31670(s__31671){
|
||||
return (new cljs.core.LazySeq(null,((function (current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (){
|
||||
var s__31671__$1 = s__31671;
|
||||
while(true){
|
||||
var temp__4657__auto__ = cljs.core.seq.call(null,s__31671__$1);
|
||||
if(temp__4657__auto__){
|
||||
var s__31671__$2 = temp__4657__auto__;
|
||||
if(cljs.core.chunked_seq_QMARK_.call(null,s__31671__$2)){
|
||||
var c__25908__auto__ = cljs.core.chunk_first.call(null,s__31671__$2);
|
||||
var size__25909__auto__ = cljs.core.count.call(null,c__25908__auto__);
|
||||
var b__31673 = cljs.core.chunk_buffer.call(null,size__25909__auto__);
|
||||
if((function (){var i__31672 = (0);
|
||||
while(true){
|
||||
if((i__31672 < size__25909__auto__)){
|
||||
var t = cljs.core._nth.call(null,c__25908__auto__,i__31672);
|
||||
cljs.core.chunk_append.call(null,b__31673,(function (){var id = id_fn.call(null,t);
|
||||
var label = label_fn.call(null,t);
|
||||
var selected_QMARK_ = cljs.core._EQ_.call(null,id,current);
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"li","li",723558921),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"class","class",-2030961996),((selected_QMARK_)?"active":""),new cljs.core.Keyword(null,"key","key",-1516042587),[cljs.core.str(id)].join('')], null),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"a","a",-2123407586),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"style","style",-496642736),cljs.core.merge.call(null,new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"cursor","cursor",1011937484),"pointer"], null),style),new cljs.core.Keyword(null,"on-click","on-click",1632826543),(cljs.core.truth_(on_change)?((function (i__31672,id,label,selected_QMARK_,t,c__25908__auto__,size__25909__auto__,b__31673,s__31671__$2,temp__4657__auto__,current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (event){
|
||||
on_change.call(null,id);
|
||||
|
||||
return null;
|
||||
});})(i__31672,id,label,selected_QMARK_,t,c__25908__auto__,size__25909__auto__,b__31673,s__31671__$2,temp__4657__auto__,current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
:null)], null),label], null)], null);
|
||||
})());
|
||||
|
||||
var G__31677 = (i__31672 + (1));
|
||||
i__31672 = G__31677;
|
||||
continue;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})()){
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__31673),re_com$tabs$iter__31670.call(null,cljs.core.chunk_rest.call(null,s__31671__$2)));
|
||||
} else {
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__31673),null);
|
||||
}
|
||||
} else {
|
||||
var t = cljs.core.first.call(null,s__31671__$2);
|
||||
return cljs.core.cons.call(null,(function (){var id = id_fn.call(null,t);
|
||||
var label = label_fn.call(null,t);
|
||||
var selected_QMARK_ = cljs.core._EQ_.call(null,id,current);
|
||||
return new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"li","li",723558921),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"class","class",-2030961996),((selected_QMARK_)?"active":""),new cljs.core.Keyword(null,"key","key",-1516042587),[cljs.core.str(id)].join('')], null),new cljs.core.PersistentVector(null, 3, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"a","a",-2123407586),new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"style","style",-496642736),cljs.core.merge.call(null,new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"cursor","cursor",1011937484),"pointer"], null),style),new cljs.core.Keyword(null,"on-click","on-click",1632826543),(cljs.core.truth_(on_change)?((function (id,label,selected_QMARK_,t,s__31671__$2,temp__4657__auto__,current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_){
|
||||
return (function (event){
|
||||
on_change.call(null,id);
|
||||
|
||||
return null;
|
||||
});})(id,label,selected_QMARK_,t,s__31671__$2,temp__4657__auto__,current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
:null)], null),label], null)], null);
|
||||
})(),re_com$tabs$iter__31670.call(null,cljs.core.rest.call(null,s__31671__$2)));
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
});})(current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
,null,null));
|
||||
});})(current,tabs__$1,_,map__31668,map__31668__$1,model,tabs,on_change,id_fn,label_fn,style,vertical_QMARK_))
|
||||
;
|
||||
return iter__25910__auto__.call(null,tabs__$1);
|
||||
})()], null);
|
||||
});
|
||||
|
||||
re_com.tabs.pill_tabs.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.tabs.pill_tabs.cljs$lang$applyTo = (function (seq31666){
|
||||
return re_com.tabs.pill_tabs.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq31666));
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_pill_tabs = (function re_com$tabs$horizontal_pill_tabs(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___31682 = arguments.length;
|
||||
var i__26206__auto___31683 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___31683 < len__26205__auto___31682)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___31683]));
|
||||
|
||||
var G__31684 = (i__26206__auto___31683 + (1));
|
||||
i__26206__auto___31683 = G__31684;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.tabs.horizontal_pill_tabs.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_pill_tabs.cljs$core$IFn$_invoke$arity$variadic = (function (p__31679){
|
||||
var map__31680 = p__31679;
|
||||
var map__31680__$1 = ((((!((map__31680 == null)))?((((map__31680.cljs$lang$protocol_mask$partition0$ & (64))) || (map__31680.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__31680):map__31680);
|
||||
var args = map__31680__$1;
|
||||
var model = cljs.core.get.call(null,map__31680__$1,new cljs.core.Keyword(null,"model","model",331153215));
|
||||
var tabs = cljs.core.get.call(null,map__31680__$1,new cljs.core.Keyword(null,"tabs","tabs",-779855354));
|
||||
var on_change = cljs.core.get.call(null,map__31680__$1,new cljs.core.Keyword(null,"on-change","on-change",-732046149));
|
||||
var id_fn = cljs.core.get.call(null,map__31680__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
var style = cljs.core.get.call(null,map__31680__$1,new cljs.core.Keyword(null,"style","style",-496642736));
|
||||
var label_fn = cljs.core.get.call(null,map__31680__$1,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),new cljs.core.Keyword(null,"label","label",1718410804));
|
||||
if(cljs.core.truth_(((!(goog.DEBUG))?true:re_com.validate.validate_args.call(null,re_com.validate.extract_arg_data.call(null,re_com.tabs.tabs_args_desc),args,"tabs")))){
|
||||
} else {
|
||||
throw (new Error("Assert failed: (validate-args-macro tabs-args-desc args \"tabs\")"));
|
||||
}
|
||||
|
||||
return re_com.tabs.pill_tabs.call(null,new cljs.core.Keyword(null,"model","model",331153215),model,new cljs.core.Keyword(null,"tabs","tabs",-779855354),tabs,new cljs.core.Keyword(null,"on-change","on-change",-732046149),on_change,new cljs.core.Keyword(null,"style","style",-496642736),style,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),id_fn,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),label_fn,new cljs.core.Keyword(null,"vertical?","vertical?",-1522630444),false);
|
||||
});
|
||||
|
||||
re_com.tabs.horizontal_pill_tabs.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.tabs.horizontal_pill_tabs.cljs$lang$applyTo = (function (seq31678){
|
||||
return re_com.tabs.horizontal_pill_tabs.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq31678));
|
||||
});
|
||||
|
||||
re_com.tabs.vertical_pill_tabs = (function re_com$tabs$vertical_pill_tabs(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___31689 = arguments.length;
|
||||
var i__26206__auto___31690 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___31690 < len__26205__auto___31689)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___31690]));
|
||||
|
||||
var G__31691 = (i__26206__auto___31690 + (1));
|
||||
i__26206__auto___31690 = G__31691;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.tabs.vertical_pill_tabs.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.tabs.vertical_pill_tabs.cljs$core$IFn$_invoke$arity$variadic = (function (p__31686){
|
||||
var map__31687 = p__31686;
|
||||
var map__31687__$1 = ((((!((map__31687 == null)))?((((map__31687.cljs$lang$protocol_mask$partition0$ & (64))) || (map__31687.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__31687):map__31687);
|
||||
var args = map__31687__$1;
|
||||
var model = cljs.core.get.call(null,map__31687__$1,new cljs.core.Keyword(null,"model","model",331153215));
|
||||
var tabs = cljs.core.get.call(null,map__31687__$1,new cljs.core.Keyword(null,"tabs","tabs",-779855354));
|
||||
var on_change = cljs.core.get.call(null,map__31687__$1,new cljs.core.Keyword(null,"on-change","on-change",-732046149));
|
||||
var id_fn = cljs.core.get.call(null,map__31687__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
var style = cljs.core.get.call(null,map__31687__$1,new cljs.core.Keyword(null,"style","style",-496642736));
|
||||
var label_fn = cljs.core.get.call(null,map__31687__$1,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),new cljs.core.Keyword(null,"label","label",1718410804));
|
||||
if(cljs.core.truth_(((!(goog.DEBUG))?true:re_com.validate.validate_args.call(null,re_com.validate.extract_arg_data.call(null,re_com.tabs.tabs_args_desc),args,"tabs")))){
|
||||
} else {
|
||||
throw (new Error("Assert failed: (validate-args-macro tabs-args-desc args \"tabs\")"));
|
||||
}
|
||||
|
||||
return re_com.tabs.pill_tabs.call(null,new cljs.core.Keyword(null,"model","model",331153215),model,new cljs.core.Keyword(null,"tabs","tabs",-779855354),tabs,new cljs.core.Keyword(null,"on-change","on-change",-732046149),on_change,new cljs.core.Keyword(null,"style","style",-496642736),style,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),id_fn,new cljs.core.Keyword(null,"label-fn","label-fn",-860923263),label_fn,new cljs.core.Keyword(null,"vertical?","vertical?",-1522630444),true);
|
||||
});
|
||||
|
||||
re_com.tabs.vertical_pill_tabs.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.tabs.vertical_pill_tabs.cljs$lang$applyTo = (function (seq31685){
|
||||
return re_com.tabs.vertical_pill_tabs.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq31685));
|
||||
});
|
||||
|
||||
|
||||
//# sourceMappingURL=tabs.js.map?rel=1603199194299
|
||||
1
resources/public/js/compiled/out/re_com/tabs.js.map
Normal file
1
resources/public/js/compiled/out/re_com/tabs.js.map
Normal file
File diff suppressed because one or more lines are too long
103
resources/public/js/compiled/out/re_com/text.cljs
Normal file
103
resources/public/js/compiled/out/re_com/text.cljs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
(ns re-com.text
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [re-com.box :refer [v-box box line flex-child-style]]
|
||||
[re-com.util :refer [deep-merge]]
|
||||
[re-com.validate :refer [title-levels-list title-level-type? css-style?
|
||||
html-attr? string-or-hiccup?] :refer-macros [validate-args-macro]]))
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: label
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def label-args-desc
|
||||
[{:name :label :required true :type "anything" :description "text or hiccup or whatever to display"}
|
||||
{:name :on-click :required false :type "-> nil" :validate-fn fn? :description "a function which takes no params and returns nothing. Called when the label is clicked"}
|
||||
{:name :width :required false :type "string" :validate-fn string? :description "a CSS width"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "additional CSS styles"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn label
|
||||
"Returns markup for a basic label"
|
||||
[& {:keys [label on-click width class style attr]
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro label-args-desc args "label")]}
|
||||
[box
|
||||
:width width
|
||||
:align :start
|
||||
:class "display-inline-flex"
|
||||
:child [:span
|
||||
(merge
|
||||
{:class (str "rc-label " class)
|
||||
:style (merge (flex-child-style "none")
|
||||
style)}
|
||||
(when on-click
|
||||
{:on-click (handler-fn (on-click))})
|
||||
attr)
|
||||
label]])
|
||||
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: title
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def title-args-desc
|
||||
[{:name :label :required true :type "anything" :description "title or hiccup or anything to display"}
|
||||
{:name :level :required false :type "keyword" :validate-fn title-level-type? :description [:span "one of " title-levels-list ". If not provided then style the title using " [:code ":class"] " or " [:code ":style"]] }
|
||||
{:name :underline? :required false :default false :type "boolean" :description "if true, the title is underlined"}
|
||||
{:name :margin-top :required false :default "0.4em" :type "string" :validate-fn string? :description "CSS size for space above the title"}
|
||||
{:name :margin-bottom :required false :default "0.1em" :type "string" :validate-fn string? :description "CSS size for space below the title"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
(defn title
|
||||
"A title with four preset levels"
|
||||
[& {:keys [label level underline? margin-top margin-bottom class style attr]
|
||||
:or {margin-top "0.6em" margin-bottom "0.3em"}
|
||||
:as args}]
|
||||
{:pre [(validate-args-macro title-args-desc args "title")]}
|
||||
(let [preset-class (if (nil? level) "" (name level))]
|
||||
[v-box
|
||||
:class preset-class
|
||||
:children [[:span (merge {:class (str "rc-title display-flex " preset-class " " class)
|
||||
:style (merge (flex-child-style "none")
|
||||
{:margin-top margin-top}
|
||||
{:line-height 1} ;; so that the margins are correct
|
||||
(when-not underline? {:margin-bottom margin-bottom})
|
||||
style)}
|
||||
attr)
|
||||
label]
|
||||
(when underline? [line
|
||||
:size "1px"
|
||||
:style {:margin-bottom margin-bottom}])]]))
|
||||
|
||||
|
||||
(defn p
|
||||
"acts like [:p ]
|
||||
|
||||
Creates a paragraph of body text, expected to have a font-szie of 14px or 15px,
|
||||
which should have limited width.
|
||||
|
||||
Why limited text width? See http://baymard.com/blog/line-length-readability
|
||||
|
||||
The actualy font-size is inherited.
|
||||
|
||||
At 14px, 450px will yield between 69 and 73 chars.
|
||||
At 15px, 450px will yield about 66 to 70 chars.
|
||||
So we're at the upper end of the prefered 50 to 75 char range.
|
||||
|
||||
If the first child is a map, it is interpreted as a map of styles / attributes."
|
||||
[& children]
|
||||
(let [child1 (first children) ;; it might be a map of attributes, including styles
|
||||
[m children] (if (map? child1)
|
||||
[child1 (rest children)]
|
||||
[{} children])
|
||||
m (deep-merge {:style {:flex "none"
|
||||
:width "450px"
|
||||
:min-width "450px"}}
|
||||
m)]
|
||||
[:span m (into [:p] children)])) ;; the wrapping span allows children to contain [:ul] etc
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
164
resources/public/js/compiled/out/re_com/text.js
Normal file
164
resources/public/js/compiled/out/re_com/text.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/text.js.map
Normal file
1
resources/public/js/compiled/out/re_com/text.js.map
Normal file
File diff suppressed because one or more lines are too long
95
resources/public/js/compiled/out/re_com/tour.cljs
Normal file
95
resources/public/js/compiled/out/re_com/tour.cljs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
(ns re-com.tour
|
||||
(:require-macros [re-com.core :refer [handler-fn]])
|
||||
(:require [reagent.core :as reagent]
|
||||
[re-com.box :refer [flex-child-style]]
|
||||
[re-com.buttons :refer [button]]))
|
||||
|
||||
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
;; Component: tour
|
||||
;;
|
||||
;; Strings together
|
||||
;;--------------------------------------------------------------------------------------------------
|
||||
|
||||
(defn make-tour
|
||||
"Returns a map containing
|
||||
- A reagent atom for each tour step controlling popover show/hide (boolean)
|
||||
- A standard atom holding the current step (integer)
|
||||
- A copy of the steps parameter passed in, to determine the order for prev/next functions
|
||||
It sets the first step atom to true so that it will be initially shown
|
||||
Sample return value:
|
||||
{:steps [:step1 :step2 :step3]
|
||||
:current-step (atom 0)
|
||||
:step1 (reagent/atom true)
|
||||
:step2 (reagent/atom false)
|
||||
:step3 (reagent/atom false)}"
|
||||
[tour-spec]
|
||||
(let [tour-map {:current-step (atom 0) :steps tour-spec}] ;; Only need normal atom
|
||||
|
||||
(reduce #(assoc %1 %2 (reagent/atom false)) tour-map tour-spec))) ;; Old way: (merge {} (map #(hash-map % (reagent/atom false)) tour-spec))
|
||||
|
||||
|
||||
(defn- initialise-tour
|
||||
"Resets all poover atoms to false"
|
||||
[tour]
|
||||
(doall (for [step (:steps tour)] (reset! (step tour) false))))
|
||||
|
||||
|
||||
(defn start-tour
|
||||
"Sets the first popover atom in the tour to true"
|
||||
[tour]
|
||||
(initialise-tour tour)
|
||||
(reset! (:current-step tour) 0)
|
||||
(reset! ((first (:steps tour)) tour) true))
|
||||
|
||||
|
||||
(defn finish-tour
|
||||
"Closes all tour popovers"
|
||||
[tour]
|
||||
(initialise-tour tour))
|
||||
|
||||
|
||||
(defn- next-tour-step
|
||||
[tour]
|
||||
(let [steps (:steps tour)
|
||||
old-step @(:current-step tour)
|
||||
new-step (inc old-step)]
|
||||
(when (< new-step (count (:steps tour)))
|
||||
(reset! (:current-step tour) new-step)
|
||||
(reset! ((nth steps old-step) tour) false)
|
||||
(reset! ((nth steps new-step) tour) true))))
|
||||
|
||||
|
||||
(defn- prev-tour-step
|
||||
[tour]
|
||||
(let [steps (:steps tour)
|
||||
old-step @(:current-step tour)
|
||||
new-step (dec old-step)]
|
||||
(when (>= new-step 0)
|
||||
(reset! (:current-step tour) new-step)
|
||||
(reset! ((nth steps old-step) tour) false)
|
||||
(reset! ((nth steps new-step) tour) true))))
|
||||
|
||||
|
||||
(defn make-tour-nav
|
||||
"Generate the hr and previous/next buttons markup.
|
||||
If first button in tour, don't generate a Previous button.
|
||||
If last button in tour, change Next button to a Finish button"
|
||||
[tour]
|
||||
(let [on-first-button (= @(:current-step tour) 0)
|
||||
on-last-button (= @(:current-step tour) (dec (count (:steps tour))))]
|
||||
[:div
|
||||
[:hr {:style (merge (flex-child-style "none")
|
||||
{:margin "10px 0px 10px"})}]
|
||||
(when-not on-first-button
|
||||
[button
|
||||
:label "Previous"
|
||||
:on-click (handler-fn (prev-tour-step tour))
|
||||
:style {:margin-right "15px"}
|
||||
:class "btn-default"])
|
||||
[button
|
||||
:label (if on-last-button "Finish" "Next")
|
||||
:on-click (handler-fn (if on-last-button
|
||||
(finish-tour tour)
|
||||
(next-tour-step tour)))
|
||||
:class "btn-default"]]))
|
||||
File diff suppressed because one or more lines are too long
147
resources/public/js/compiled/out/re_com/tour.js
Normal file
147
resources/public/js/compiled/out/re_com/tour.js
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
// Compiled by ClojureScript 1.9.229 {}
|
||||
goog.provide('re_com.tour');
|
||||
goog.require('cljs.core');
|
||||
goog.require('reagent.core');
|
||||
goog.require('re_com.box');
|
||||
goog.require('re_com.buttons');
|
||||
/**
|
||||
* Returns a map containing
|
||||
* - A reagent atom for each tour step controlling popover show/hide (boolean)
|
||||
* - A standard atom holding the current step (integer)
|
||||
* - A copy of the steps parameter passed in, to determine the order for prev/next functions
|
||||
* It sets the first step atom to true so that it will be initially shown
|
||||
* Sample return value:
|
||||
* {:steps [:step1 :step2 :step3]
|
||||
* :current-step (atom 0)
|
||||
* :step1 (reagent/atom true)
|
||||
* :step2 (reagent/atom false)
|
||||
* :step3 (reagent/atom false)}
|
||||
*/
|
||||
re_com.tour.make_tour = (function re_com$tour$make_tour(tour_spec){
|
||||
var tour_map = new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"current-step","current-step",-2023410137),cljs.core.atom.call(null,(0)),new cljs.core.Keyword(null,"steps","steps",-128433302),tour_spec], null);
|
||||
return cljs.core.reduce.call(null,((function (tour_map){
|
||||
return (function (p1__32433_SHARP_,p2__32434_SHARP_){
|
||||
return cljs.core.assoc.call(null,p1__32433_SHARP_,p2__32434_SHARP_,reagent.core.atom.call(null,false));
|
||||
});})(tour_map))
|
||||
,tour_map,tour_spec);
|
||||
});
|
||||
/**
|
||||
* Resets all poover atoms to false
|
||||
*/
|
||||
re_com.tour.initialise_tour = (function re_com$tour$initialise_tour(tour){
|
||||
return cljs.core.doall.call(null,(function (){var iter__25910__auto__ = (function re_com$tour$initialise_tour_$_iter__32439(s__32440){
|
||||
return (new cljs.core.LazySeq(null,(function (){
|
||||
var s__32440__$1 = s__32440;
|
||||
while(true){
|
||||
var temp__4657__auto__ = cljs.core.seq.call(null,s__32440__$1);
|
||||
if(temp__4657__auto__){
|
||||
var s__32440__$2 = temp__4657__auto__;
|
||||
if(cljs.core.chunked_seq_QMARK_.call(null,s__32440__$2)){
|
||||
var c__25908__auto__ = cljs.core.chunk_first.call(null,s__32440__$2);
|
||||
var size__25909__auto__ = cljs.core.count.call(null,c__25908__auto__);
|
||||
var b__32442 = cljs.core.chunk_buffer.call(null,size__25909__auto__);
|
||||
if((function (){var i__32441 = (0);
|
||||
while(true){
|
||||
if((i__32441 < size__25909__auto__)){
|
||||
var step = cljs.core._nth.call(null,c__25908__auto__,i__32441);
|
||||
cljs.core.chunk_append.call(null,b__32442,cljs.core.reset_BANG_.call(null,step.call(null,tour),false));
|
||||
|
||||
var G__32443 = (i__32441 + (1));
|
||||
i__32441 = G__32443;
|
||||
continue;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})()){
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__32442),re_com$tour$initialise_tour_$_iter__32439.call(null,cljs.core.chunk_rest.call(null,s__32440__$2)));
|
||||
} else {
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__32442),null);
|
||||
}
|
||||
} else {
|
||||
var step = cljs.core.first.call(null,s__32440__$2);
|
||||
return cljs.core.cons.call(null,cljs.core.reset_BANG_.call(null,step.call(null,tour),false),re_com$tour$initialise_tour_$_iter__32439.call(null,cljs.core.rest.call(null,s__32440__$2)));
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}),null,null));
|
||||
});
|
||||
return iter__25910__auto__.call(null,new cljs.core.Keyword(null,"steps","steps",-128433302).cljs$core$IFn$_invoke$arity$1(tour));
|
||||
})());
|
||||
});
|
||||
/**
|
||||
* Sets the first popover atom in the tour to true
|
||||
*/
|
||||
re_com.tour.start_tour = (function re_com$tour$start_tour(tour){
|
||||
re_com.tour.initialise_tour.call(null,tour);
|
||||
|
||||
cljs.core.reset_BANG_.call(null,new cljs.core.Keyword(null,"current-step","current-step",-2023410137).cljs$core$IFn$_invoke$arity$1(tour),(0));
|
||||
|
||||
return cljs.core.reset_BANG_.call(null,cljs.core.first.call(null,new cljs.core.Keyword(null,"steps","steps",-128433302).cljs$core$IFn$_invoke$arity$1(tour)).call(null,tour),true);
|
||||
});
|
||||
/**
|
||||
* Closes all tour popovers
|
||||
*/
|
||||
re_com.tour.finish_tour = (function re_com$tour$finish_tour(tour){
|
||||
return re_com.tour.initialise_tour.call(null,tour);
|
||||
});
|
||||
re_com.tour.next_tour_step = (function re_com$tour$next_tour_step(tour){
|
||||
var steps = new cljs.core.Keyword(null,"steps","steps",-128433302).cljs$core$IFn$_invoke$arity$1(tour);
|
||||
var old_step = cljs.core.deref.call(null,new cljs.core.Keyword(null,"current-step","current-step",-2023410137).cljs$core$IFn$_invoke$arity$1(tour));
|
||||
var new_step = (old_step + (1));
|
||||
if((new_step < cljs.core.count.call(null,new cljs.core.Keyword(null,"steps","steps",-128433302).cljs$core$IFn$_invoke$arity$1(tour)))){
|
||||
cljs.core.reset_BANG_.call(null,new cljs.core.Keyword(null,"current-step","current-step",-2023410137).cljs$core$IFn$_invoke$arity$1(tour),new_step);
|
||||
|
||||
cljs.core.reset_BANG_.call(null,cljs.core.nth.call(null,steps,old_step).call(null,tour),false);
|
||||
|
||||
return cljs.core.reset_BANG_.call(null,cljs.core.nth.call(null,steps,new_step).call(null,tour),true);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
re_com.tour.prev_tour_step = (function re_com$tour$prev_tour_step(tour){
|
||||
var steps = new cljs.core.Keyword(null,"steps","steps",-128433302).cljs$core$IFn$_invoke$arity$1(tour);
|
||||
var old_step = cljs.core.deref.call(null,new cljs.core.Keyword(null,"current-step","current-step",-2023410137).cljs$core$IFn$_invoke$arity$1(tour));
|
||||
var new_step = (old_step - (1));
|
||||
if((new_step >= (0))){
|
||||
cljs.core.reset_BANG_.call(null,new cljs.core.Keyword(null,"current-step","current-step",-2023410137).cljs$core$IFn$_invoke$arity$1(tour),new_step);
|
||||
|
||||
cljs.core.reset_BANG_.call(null,cljs.core.nth.call(null,steps,old_step).call(null,tour),false);
|
||||
|
||||
return cljs.core.reset_BANG_.call(null,cljs.core.nth.call(null,steps,new_step).call(null,tour),true);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Generate the hr and previous/next buttons markup.
|
||||
* If first button in tour, don't generate a Previous button.
|
||||
* If last button in tour, change Next button to a Finish button
|
||||
*/
|
||||
re_com.tour.make_tour_nav = (function re_com$tour$make_tour_nav(tour){
|
||||
var on_first_button = cljs.core._EQ_.call(null,cljs.core.deref.call(null,new cljs.core.Keyword(null,"current-step","current-step",-2023410137).cljs$core$IFn$_invoke$arity$1(tour)),(0));
|
||||
var on_last_button = cljs.core._EQ_.call(null,cljs.core.deref.call(null,new cljs.core.Keyword(null,"current-step","current-step",-2023410137).cljs$core$IFn$_invoke$arity$1(tour)),(cljs.core.count.call(null,new cljs.core.Keyword(null,"steps","steps",-128433302).cljs$core$IFn$_invoke$arity$1(tour)) - (1)));
|
||||
return new cljs.core.PersistentVector(null, 4, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"div","div",1057191632),new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [new cljs.core.Keyword(null,"hr","hr",1377740067),new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"style","style",-496642736),cljs.core.merge.call(null,re_com.box.flex_child_style.call(null,"none"),new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"margin","margin",-995903681),"10px 0px 10px"], null))], null)], null),((on_first_button)?null:new cljs.core.PersistentVector(null, 9, 5, cljs.core.PersistentVector.EMPTY_NODE, [re_com.buttons.button,new cljs.core.Keyword(null,"label","label",1718410804),"Previous",new cljs.core.Keyword(null,"on-click","on-click",1632826543),((function (on_first_button,on_last_button){
|
||||
return (function (event){
|
||||
re_com.tour.prev_tour_step.call(null,tour);
|
||||
|
||||
return null;
|
||||
});})(on_first_button,on_last_button))
|
||||
,new cljs.core.Keyword(null,"style","style",-496642736),new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"margin-right","margin-right",809689658),"15px"], null),new cljs.core.Keyword(null,"class","class",-2030961996),"btn-default"], null)),new cljs.core.PersistentVector(null, 7, 5, cljs.core.PersistentVector.EMPTY_NODE, [re_com.buttons.button,new cljs.core.Keyword(null,"label","label",1718410804),((on_last_button)?"Finish":"Next"),new cljs.core.Keyword(null,"on-click","on-click",1632826543),((function (on_first_button,on_last_button){
|
||||
return (function (event){
|
||||
if(on_last_button){
|
||||
re_com.tour.finish_tour.call(null,tour);
|
||||
} else {
|
||||
re_com.tour.next_tour_step.call(null,tour);
|
||||
}
|
||||
|
||||
return null;
|
||||
});})(on_first_button,on_last_button))
|
||||
,new cljs.core.Keyword(null,"class","class",-2030961996),"btn-default"], null)], null);
|
||||
});
|
||||
|
||||
//# sourceMappingURL=tour.js.map?rel=1603199196399
|
||||
1
resources/public/js/compiled/out/re_com/tour.js.map
Normal file
1
resources/public/js/compiled/out/re_com/tour.js.map
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"\/Users\/simon\/workspace\/swinging-needle-meter\/docs\/js\/compiled\/out\/re_com\/tour.js","sources":["tour.cljs?rel=1603199196400"],"lineCount":147,"mappings":";AAAA;;;;;AAaA;;;;;;;;;;;;;wBAAA,xBAAMA,wDAYHC;AAZH,AAaE,eAAA,2CAAA,oGAAA,1JAAMC,4HAAwB,yBAAA,zBAACC,qFAAeF;AAA9C,AAEE,kCAAA,3BAACK;kBAADF,iBAAAC;AAAA,AAAS,iCAAAD,iBAAAC,3CAACE,4DAAY,4BAAA,5BAACC;;CAAqBN,SAASD;;AAGzD;;;8BAAA,9BAAOQ,oEAEJC;AAFH,AAGE,OAACC,0BAAM,iBAAAC,sBAAA,oDAAAC;AAAA,AAAA,YAAAC,kBAAA,KAAA;AAAA,AAAA,IAAAD,eAAAA;;AAAA,AAAA,IAAAE,qBAAA,AAAAC,wBAAAH;AAAA,AAAA,GAAAE;AAAA,AAAA,IAAAF,eAAAE;AAAA,AAAA,GAAA,AAAAE,uCAAAJ;AAAA,IAAAK,mBAuuE0C,AAAAkC,gCAAAvC;IAvuE1CM,sBAAA,AAAAC,0BAAAF;IAAAG,WAAA,AAAAC,iCAAAH;AAAA,AAAA,GAAA,AAAA,iBAAAI,WAAA;;AAAA,AAAA,GAAA,CAAAA,WAAAJ;AAAA,WAAA,AAAAK,yBAAAN,iBAAAK,jDAAMU;AAAN,AAAA,AAAA,AAAAR,iCAAAJ,SAA0B,qDAAA,rDAACa,gCAAO,AAACD,eAAKvB;;AAAxC,eAAA,CAAAa,WAAA;;;;AAAA;;;;;AAAA,OAAAG,+BAAA,AAAAC,0BAAAN,UAAA,AAAAO,oDAAA,AAAAC,+BAAAhB;;AAAA,OAAAa,+BAAA,AAAAC,0BAAAN,UAAA;;;AAAA,WAAA,AAAAS,0BAAAjB,jCAAMoB;AAAN,AAAA,OAAAF,qFAAA,AAAAH,oDAAA,AAAAI,yBAAAnB,zIAA0B,qDAAA,rDAACqB,gCAAO,AAACD,eAAKvB;;;AAAxC;;;;GAAA,KAAA;;AAAA,AAAA,OAAAE,8BAAW,AAAA,qFAAQF;;;AAG5B;;;yBAAA,zBAAMyB,0DAEHzB;AAFH,AAGE,AAACD,sCAAgBC;;AACjB,0IAAA,1IAACwB,gCAAO,AAAA,oGAAexB;;AACvB,6KAAA,tKAACwB,gCAAO,AAAC,AAACJ,0BAAM,AAAA,qFAAQpB,iBAAOA;;AAGjC;;;0BAAA,1BAAM0B,4DAEH1B;AAFH,AAGE,OAACD,sCAAgBC;;AAGnB,6BAAA,7BAAO2B,kEACJ3B;AADH,AAEE,IAAM6B,QAAU,AAAA,qFAAQ7B;eAAxB,AAAA4B,XACME,qCAAW,AAAA,oGAAe9B;IAC1B+B,WAAU,YAAA,XAAKD;AAFrB,AAGE,GAAM,CAAGC,WAAS,AAACrB,0BAAM,AAAA,qFAAQV;AAAjC,AACE,AAACwB,gCAAO,AAAA,oGAAexB,MAAM+B;;AAC7B,wFAAA,xFAACP,gCAAO,AAAC,AAACQ,wBAAIH,MAAMC,oBAAU9B;;AAC9B,+FAAA,xFAACwB,gCAAO,AAAC,AAACQ,wBAAIH,MAAME,oBAAU\/B;;AAHhC;;;AAMJ,6BAAA,7BAAOiC,kEACJjC;AADH,AAEE,IAAM6B,QAAS,AAAA,qFAAQ7B;eAAvB,AAAA4B,XACME,qCAAU,AAAA,oGAAe9B;IACzB+B,WAAS,YAAA,XAAKD;AAFpB,AAGE,GAAM,aAAA,ZAAIC;AAAV,AACE,AAACP,gCAAO,AAAA,oGAAexB,MAAM+B;;AAC7B,wFAAA,xFAACP,gCAAO,AAAC,AAACQ,wBAAIH,MAAMC,oBAAU9B;;AAC9B,+FAAA,xFAACwB,gCAAO,AAAC,AAACQ,wBAAIH,MAAME,oBAAU\/B;;AAHhC;;;AAMJ;;;;;4BAAA,5BAAMkC,gEAIHlC;AAJH,AAKE,IAAMmC,kBAAgB,yBAAA,AAAAP,qIAAA,9JAACQ,mDAAG,AAAA,oGAAepC;IACnCqC,iBAAgB,yBAAA,AAAAT,zBAACQ,mDAAG,AAAA,oGAAepC,OAAM,yHAAA,xHAAK,AAACU,0BAAM,AAAA,qFAAQV;AADnE,AAAA,0FAAA,mDAAA,mFAAA,iDAAA,2CAAA,uDAGgB,wEAAA,2CAAA,yDAAA,5KAACsC,0BAAM,sCAAA,tCAACC,2LAEpB,mBAAA,KAAA,AAAA,yGAAA,uDAAA,WAAA,jMAAUJ,yGACPK,qJAEU;kBAAAC;AAAA,AAAY,AAACR,qCAAejC;;AAA5B;;qQARjB,yGAAA,0FAAA,vcAKI,uDAAA,2CAAA,oEAAA,eAAA,wDAAA,0GAMCwC,6EACU,kBAAA,SAAA,zBAAIH,8FACJ;kBAAAI;AAAA,AAAY,GAAIJ;AACF,AAACX,kCAAY1B;;AACb,AAAC2B,qCAAe3B;;;AAF9B;;CAbf,wDAAA","names":["re-com.tour\/make-tour","tour-spec","tour-map","cljs.core\/atom","p1__32433#","p2__32434#","cljs.core\/reduce","cljs.core\/assoc","reagent.core\/atom","re-com.tour\/initialise-tour","tour","cljs.core\/doall","iter__25910__auto__","s__32440","cljs.core\/LazySeq","temp__4657__auto__","cljs.core\/seq","cljs.core\/chunked-seq?","c__25908__auto__","size__25909__auto__","cljs.core\/count","b__32442","cljs.core\/chunk-buffer","i__32441","cljs.core\/-nth","cljs.core\/chunk-append","cljs.core\/chunk-cons","cljs.core\/chunk","iter__32439","cljs.core\/chunk-rest","cljs.core\/first","cljs.core\/cons","cljs.core\/rest","step","cljs.core\/reset!","re-com.tour\/start-tour","re-com.tour\/finish-tour","re-com.tour\/next-tour-step","cljs.core\/deref","steps","old-step","new-step","cljs.core\/nth","re-com.tour\/prev-tour-step","re-com.tour\/make-tour-nav","on-first-button","cljs.core\/=","on-last-button","cljs.core\/merge","re-com.box\/flex-child-style","re-com.buttons\/button","event","cljs.core\/chunk-first"]}
|
||||
313
resources/public/js/compiled/out/re_com/typeahead.cljs
Normal file
313
resources/public/js/compiled/out/re_com/typeahead.cljs
Normal file
|
|
@ -0,0 +1,313 @@
|
|||
(ns re-com.typeahead
|
||||
(:require-macros [re-com.core :refer [handler-fn]]
|
||||
[cljs.core.async.macros :refer [alt! go-loop]])
|
||||
(:require [cljs.core.async :refer [chan timeout <! put!]]
|
||||
[re-com.misc :refer [throbber input-text]]
|
||||
[re-com.util :refer [deref-or-value px]]
|
||||
[re-com.popover :refer [popover-tooltip]] ;; need?
|
||||
[re-com.box :refer [h-box v-box box gap line flex-child-style align-style]] ;; need?
|
||||
[re-com.validate :refer [input-status-type? input-status-types-list regex?
|
||||
string-or-hiccup? css-style? html-attr? number-or-string?
|
||||
string-or-atom? throbber-size? throbber-sizes-list] :refer-macros [validate-args-macro]]
|
||||
[reagent.core :as reagent]
|
||||
[goog.events.KeyCodes]))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Component: typeahead
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(def typeahead-args-desc
|
||||
[{:name :data-source :required true :type "fn" :validate-fn fn? :description [:span [:code ":data-source"] " supplies suggestion objects. This can either accept a single string argument (the search term), or a string and a callback. For the first case, the fn should return a collection of suggestion objects (which can be anything). For the second case, the fn should return "[:code "nil" ]", and eventually result in a call to the callback with a collection of suggestion objects."]}
|
||||
{:name :on-change :required false :default nil :type "string -> nil" :validate-fn fn? :description [:span [:code ":change-on-blur?"] " controls when it is called. It is passed a suggestion object."] }
|
||||
{:name :change-on-blur? :required false :default true :type "boolean | atom" :description [:span "when true, invoke " [:code ":on-change"] " when the use chooses a suggestion, otherwise invoke it on every change (navigating through suggestions with the mouse or keyboard, or if "[:code "rigid?"]" is also "[:code "false" ]", invoke it on every character typed.)"] }
|
||||
{:name :model :required false :default nil :type "object | atom" :description "The initial value of the typeahead (should match the suggestion objects returned by " [:code ":data-source"] ")."}
|
||||
{:name :debounce-delay :required false :default 250 :type "integer" :validate-fn integer? :description [:span "After receiving input, the typeahead will wait this many milliseconds without receiving new input before calling " [:code ":data-source"] "."]}
|
||||
{:name :render-suggestion :required false :type "render fn" :validate-fn fn? :description "override the rendering of the suggestion items by passing a fn that returns hiccup forms. The fn will receive two arguments: the search term, and the suggestion object."}
|
||||
{:name :suggestion-to-string :required false :type "suggestion -> string" :validate-fn fn? :description "When a suggestion is chosen, the input-text value will be set to the result of calling this fn with the suggestion object."}
|
||||
{:name :rigid? :required false :default true :type "boolean | atom" :description [:span "If "[:code "false"]" the user will be allowed to choose arbitrary text input rather than a suggestion from " [:code ":data-source"]". In this case, a string will be supplied in lieu of a suggestion object." ]}
|
||||
|
||||
;; the rest of the arguments are forwarded to the wrapped `input-text`
|
||||
{:name :status :required false :type "keyword" :validate-fn input-status-type? :description [:span "validation status. " [:code "nil/omitted"] " for normal status or one of: " input-status-types-list]}
|
||||
{:name :status-icon? :required false :default false :type "boolean" :description [:span "when true, display an icon to match " [:code ":status"] " (no icon for nil)"]}
|
||||
{:name :status-tooltip :required false :type "string" :validate-fn string? :description "displayed in status icon's tooltip"}
|
||||
{:name :placeholder :required false :type "string" :validate-fn string? :description "background text shown when empty"}
|
||||
{:name :width :required false :default "250px" :type "string" :validate-fn string? :description "standard CSS width setting for this input"}
|
||||
{:name :height :required false :type "string" :validate-fn string? :description "standard CSS height setting for this input"}
|
||||
{:name :disabled? :required false :default false :type "boolean | atom" :description "if true, the user can't interact (input anything)"}
|
||||
{:name :class :required false :type "string" :validate-fn string? :description "CSS class names, space separated"}
|
||||
{:name :style :required false :type "CSS style map" :validate-fn css-style? :description "CSS styles to add or override"}
|
||||
{:name :attr :required false :type "HTML attr map" :validate-fn html-attr? :description [:span "HTML attributes, like " [:code ":on-mouse-move"] [:br] "No " [:code ":class"] " or " [:code ":style"] "allowed"]}])
|
||||
|
||||
;; TODO
|
||||
;; ability to focus & blur the input-text would be nice... this is also missing from input-text
|
||||
;; the typeahead should blur the input-text after a selection is chosen
|
||||
|
||||
(declare debounce display-suggestion)
|
||||
(defn- make-typeahead-state
|
||||
"Return an initial value for the typeahead state, given `args`."
|
||||
[{:as args :keys [on-change rigid? change-on-blur? data-source suggestion-to-string debounce-delay model]}]
|
||||
(let [external-model-value (deref-or-value model)]
|
||||
(cond-> (let [c-input (chan)]
|
||||
{:input-text ""
|
||||
:external-model (deref-or-value model)
|
||||
:model (deref-or-value model)
|
||||
:waiting? false
|
||||
:suggestions []
|
||||
:displaying-suggestion? false
|
||||
:suggestion-to-string (or suggestion-to-string str)
|
||||
:data-source data-source
|
||||
:change-on-blur? change-on-blur?
|
||||
:on-change on-change
|
||||
:rigid? rigid?
|
||||
:c-input c-input
|
||||
:c-search (debounce c-input debounce-delay)})
|
||||
external-model-value
|
||||
(display-suggestion external-model-value))))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; State predicates: state -> value? -> boolean
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(defn- event-updates-model?
|
||||
"Should `event` update the `typeahead` `model`?"
|
||||
[{:as state :keys [change-on-blur? rigid?]} event]
|
||||
(let [change-on-blur? (deref-or-value change-on-blur?)
|
||||
rigid? (deref-or-value rigid?)]
|
||||
(case event
|
||||
:input-text-blurred (and change-on-blur? (not rigid?))
|
||||
:suggestion-activated (not change-on-blur?)
|
||||
:input-text-changed (not (or change-on-blur? rigid?)))))
|
||||
|
||||
(defn- event-displays-suggestion?
|
||||
"Should `event` cause the `input-text` value to be used to show the active suggestion?"
|
||||
[{:as state :keys [change-on-blur?]} event]
|
||||
(let [change-on-blur? (deref-or-value change-on-blur?)]
|
||||
(case event
|
||||
:suggestion-activated (not change-on-blur?)
|
||||
false)))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; State update helpers: state -> value? -> next-state
|
||||
;; all pure, _except_ that they may call `on-change`
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(defn- update-model
|
||||
"Change the `typeahead` `model` value to `new-value`"
|
||||
[{:as state :keys [on-change]} new-value]
|
||||
(when on-change (on-change new-value))
|
||||
(assoc state :model new-value))
|
||||
|
||||
(defn- display-suggestion
|
||||
"Change the `input-text` `model` to the string representation of `suggestion`"
|
||||
[{:as state :keys [suggestion-to-string]} suggestion]
|
||||
(let [suggestion-string (suggestion-to-string suggestion)]
|
||||
(cond-> state
|
||||
suggestion-string (assoc :input-text suggestion-string
|
||||
:displaying-suggestion? true))))
|
||||
|
||||
(defn- clear-suggestions
|
||||
[state]
|
||||
(-> state
|
||||
(dissoc :suggestions :suggestion-active-index)))
|
||||
|
||||
(defn- activate-suggestion-by-index
|
||||
"Make the suggestion at `index` the active suggestion"
|
||||
[{:as state :keys [suggestions]} index]
|
||||
(let [suggestion (nth suggestions index)]
|
||||
(cond-> state
|
||||
:always (assoc :suggestion-active-index index)
|
||||
(event-updates-model? state :suggestion-activated) (update-model suggestion)
|
||||
(event-displays-suggestion? state :suggestion-activated) (display-suggestion suggestion))))
|
||||
|
||||
(defn- choose-suggestion-by-index
|
||||
"Choose the suggestion at `index`"
|
||||
[{:as state :keys [suggestions]} index]
|
||||
(let [suggestion (nth suggestions index)]
|
||||
(-> state
|
||||
(activate-suggestion-by-index index)
|
||||
(update-model suggestion)
|
||||
(display-suggestion suggestion)
|
||||
clear-suggestions)))
|
||||
|
||||
(defn- choose-suggestion-active
|
||||
[{:as state :keys [suggestion-active-index]}]
|
||||
(cond-> state
|
||||
suggestion-active-index (choose-suggestion-by-index suggestion-active-index)))
|
||||
|
||||
(defn- wrap [index count] (mod (+ count index) count))
|
||||
|
||||
(defn- activate-suggestion-next
|
||||
[{:as state :keys [suggestions suggestion-active-index]}]
|
||||
(cond-> state
|
||||
suggestions
|
||||
(activate-suggestion-by-index (-> suggestion-active-index (or -1) inc (wrap (count suggestions))))))
|
||||
|
||||
(defn- activate-suggestion-prev
|
||||
[{:as state :keys [suggestions suggestion-active-index]}]
|
||||
(cond-> state
|
||||
suggestions
|
||||
(activate-suggestion-by-index (-> suggestion-active-index (or 0) dec (wrap (count suggestions))))))
|
||||
|
||||
(defn- reset-typeahead
|
||||
[state]
|
||||
(cond-> state
|
||||
:always clear-suggestions
|
||||
:always (assoc :waiting? false :input-text "" :displaying-suggestion? false)
|
||||
(event-updates-model? state :input-text-changed) (update-model nil)))
|
||||
|
||||
(defn- got-suggestions
|
||||
"Update state when new suggestions are available"
|
||||
[state suggestions]
|
||||
(-> state
|
||||
(assoc :suggestions suggestions
|
||||
:waiting? false
|
||||
:suggestion-active-index nil)))
|
||||
|
||||
(defn- input-text-will-blur
|
||||
"Update state when the `input-text` is about to lose focus."
|
||||
[{:keys [input-text displaying-suggestion?] :as state}]
|
||||
(cond-> state
|
||||
(and (not displaying-suggestion?)
|
||||
(event-updates-model? state :input-text-blurred))
|
||||
(update-model input-text)))
|
||||
|
||||
(defn- change-data-source
|
||||
"Update `state` given a new `data-source`. Resets the typeahead since any existing suggestions
|
||||
came from the old `data-source`."
|
||||
[state data-source]
|
||||
(-> state
|
||||
reset-typeahead
|
||||
(assoc :data-source data-source)))
|
||||
|
||||
(defn- external-model-changed
|
||||
"Update state when the external model value has changed."
|
||||
[state new-value]
|
||||
(-> state
|
||||
(update-model new-value)
|
||||
(display-suggestion new-value)
|
||||
clear-suggestions))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; Functions with side-effects
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(defn- search-data-source!
|
||||
"Call the `data-source` fn with `text`, and then call `got-suggestions` with the result
|
||||
(asynchronously, if `data-source` does not return a truthy value)."
|
||||
[data-source state-atom text]
|
||||
(if-let [return-value (data-source text #(swap! state-atom got-suggestions %1))]
|
||||
(swap! state-atom got-suggestions return-value)
|
||||
(swap! state-atom assoc :waiting? true)))
|
||||
|
||||
(defn- search-data-source-loop!
|
||||
"For every value arriving on the `c-search` channel, call `search-data-source!`."
|
||||
[state-atom c-search]
|
||||
(go-loop []
|
||||
(let [new-text (<! c-search)
|
||||
data-source (:data-source @state-atom)]
|
||||
(if (= "" new-text)
|
||||
(swap! state-atom reset-typeahead)
|
||||
(search-data-source! data-source state-atom new-text))
|
||||
(recur))))
|
||||
|
||||
(defn- input-text-on-change!
|
||||
"Update state in response to `input-text` `on-change`, and put text on the `c-input` channel"
|
||||
[state-atom new-text]
|
||||
(let [{:as state :keys [input-text c-input]} @state-atom]
|
||||
(if (= new-text input-text) state ;; keypresses that do not change the value still call on-change, ignore these
|
||||
(do
|
||||
(when-not (clojure.string/blank? new-text) (put! c-input new-text))
|
||||
(swap! state-atom
|
||||
#(cond-> %
|
||||
:always (assoc :input-text new-text :displaying-suggestion? false)
|
||||
(event-updates-model? state :input-text-changed) (update-model new-text)))))))
|
||||
|
||||
(defn- input-text-on-key-down!
|
||||
[state-atom event]
|
||||
(condp = (.-which event)
|
||||
goog.events.KeyCodes.UP (swap! state-atom activate-suggestion-prev)
|
||||
goog.events.KeyCodes.DOWN (swap! state-atom activate-suggestion-next)
|
||||
goog.events.KeyCodes.ENTER (swap! state-atom choose-suggestion-active)
|
||||
goog.events.KeyCodes.ESC (swap! state-atom reset-typeahead)
|
||||
;; tab requires special treatment
|
||||
;; trap it IFF there are suggestions, otherwise let the input defocus
|
||||
goog.events.KeyCodes.TAB
|
||||
(if (not-empty (:suggestions @state-atom))
|
||||
(do (swap! state-atom activate-suggestion-next)
|
||||
(.preventDefault event))
|
||||
(swap! state-atom input-text-will-blur))
|
||||
true))
|
||||
|
||||
;; ------------------------------------------------------------------------------------
|
||||
;; The typeahead component
|
||||
;; ------------------------------------------------------------------------------------
|
||||
|
||||
(defn- typeahead
|
||||
"typeahead reagent component"
|
||||
[& {:keys [data-source on-change rigid? change-on-blur?] :as args}]
|
||||
{:pre [(validate-args-macro typeahead-args-desc args "typeahead")]}
|
||||
(let [{:as state :keys [c-search c-input]} (make-typeahead-state args)
|
||||
state-atom (reagent/atom state)
|
||||
input-text-model (reagent/cursor state-atom [:input-text])]
|
||||
(search-data-source-loop! state-atom c-search)
|
||||
(fn
|
||||
[& {:as args
|
||||
:keys [data-source rigid? render-suggestion suggestion-to-string model
|
||||
;; forwarded to wrapped `input-text`:
|
||||
placeholder width height status-icon? status status-tooltip disabled? class style]}]
|
||||
{:pre [(validate-args-macro typeahead-args-desc args "typeahead")]}
|
||||
(let [{:as state :keys [suggestions waiting? suggestion-active-index external-model]} @state-atom
|
||||
last-data-source (:data-source state)
|
||||
latest-external-model (deref-or-value model)
|
||||
width (or width "250px")]
|
||||
(when (not= last-data-source data-source)
|
||||
(swap! state-atom change-data-source data-source))
|
||||
(when (not= latest-external-model external-model)
|
||||
(swap! state-atom external-model-changed latest-external-model))
|
||||
[v-box
|
||||
:width width
|
||||
:children
|
||||
[[input-text
|
||||
:model input-text-model
|
||||
:class class
|
||||
:style style
|
||||
:disabled? disabled?
|
||||
:status-icon? status-icon?
|
||||
:status status
|
||||
:status-tooltip status-tooltip
|
||||
:width width
|
||||
:height height
|
||||
:placeholder placeholder
|
||||
:on-change (partial input-text-on-change! state-atom)
|
||||
:change-on-blur? false
|
||||
:attr {:on-key-down (partial input-text-on-key-down! state-atom)}]
|
||||
(if (or (not-empty suggestions) waiting?)
|
||||
[v-box
|
||||
:class "rc-typeahead-suggestions-container"
|
||||
:children [(when waiting?
|
||||
[box :align :center :child [throbber :size :small :class "rc-typeahead-throbber"]])
|
||||
(for [[ i s ] (map vector (range) suggestions)
|
||||
:let [selected? (= suggestion-active-index i)]]
|
||||
^{:key i}
|
||||
[box
|
||||
:child (if render-suggestion
|
||||
(render-suggestion s)
|
||||
s)
|
||||
:class (str "rc-typeahead-suggestion"
|
||||
(when selected? " active"))
|
||||
:attr {:on-mouse-over #(swap! state-atom activate-suggestion-by-index i)
|
||||
:on-mouse-down #(do (.preventDefault %) (swap! state-atom choose-suggestion-by-index i))}])]])]]))))
|
||||
|
||||
(defn- debounce
|
||||
"Return a channel which will receive a value from the `in` channel only
|
||||
if no further value is received on the `in` channel in the next `ms` milliseconds."
|
||||
[in ms]
|
||||
(let [out (chan)]
|
||||
(go-loop [last-val nil]
|
||||
(let [val (if (nil? last-val) (<! in) last-val)
|
||||
timer (timeout ms)]
|
||||
(let [v (alt!
|
||||
in ([val _] val)
|
||||
timer (do (>! out val) nil))]
|
||||
(recur v))))
|
||||
out))
|
||||
File diff suppressed because one or more lines are too long
1071
resources/public/js/compiled/out/re_com/typeahead.js
Normal file
1071
resources/public/js/compiled/out/re_com/typeahead.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/typeahead.js.map
Normal file
1
resources/public/js/compiled/out/re_com/typeahead.js.map
Normal file
File diff suppressed because one or more lines are too long
165
resources/public/js/compiled/out/re_com/util.cljs
Normal file
165
resources/public/js/compiled/out/re_com/util.cljs
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
(ns re-com.util
|
||||
(:require
|
||||
[clojure.set :refer [superset?]]
|
||||
[reagent.ratom :refer [RAtom Reaction RCursor Track Wrapper]]
|
||||
[goog.date.DateTime]
|
||||
[goog.date.UtcDateTime]))
|
||||
|
||||
(defn fmap
|
||||
"Takes a function 'f' amd a map 'm'. Applies 'f' to each value in 'm' and returns.
|
||||
(fmap inc {:a 4 :b 2}) => {:a 5 :b 3}"
|
||||
[f m]
|
||||
(into {} (for [[k val] m] [k (f val)])))
|
||||
|
||||
(defn deep-merge
|
||||
"Recursively merges maps. If vals are not maps, the last value wins."
|
||||
[& vals]
|
||||
(if (every? map? vals)
|
||||
(apply merge-with deep-merge vals)
|
||||
(last vals)))
|
||||
|
||||
|
||||
(defn deref-or-value
|
||||
"Takes a value or an atom
|
||||
If it's a value, returns it
|
||||
If it's a Reagent object that supports IDeref, returns the value inside it by derefing
|
||||
"
|
||||
[val-or-atom]
|
||||
(if (satisfies? IDeref val-or-atom)
|
||||
@val-or-atom
|
||||
val-or-atom))
|
||||
|
||||
|
||||
(defn deref-or-value-peek
|
||||
"Takes a value or an atom
|
||||
If it's a value, returns it
|
||||
If it's a Reagent object that supports IDeref, returns the value inside it, but WITHOUT derefing
|
||||
|
||||
The arg validation code uses this, since calling deref-or-value adds this arg to the watched ratom list for the component
|
||||
in question, which in turn can cause different rendering behaviour between dev (where we validate) and prod (where we don't).
|
||||
|
||||
This was experienced in popover-content-wrapper with the position-injected atom which was not derefed there, however
|
||||
the dev-only validation caused it to be derefed, modifying its render behaviour and causing mayhem and madness for the developer.
|
||||
|
||||
See below that different Reagent types have different ways of retrieving the value without causing capture, although in the case of
|
||||
Track, we just deref it as there is no peek or state, so hopefully this won't cause issues (surely this is used very rarely).
|
||||
"
|
||||
[val-or-atom]
|
||||
(if (satisfies? IDeref val-or-atom)
|
||||
(cond
|
||||
(instance? RAtom val-or-atom) val-or-atom.state
|
||||
(instance? Reaction val-or-atom) (._peek-at val-or-atom)
|
||||
(instance? RCursor val-or-atom) (._peek val-or-atom)
|
||||
(instance? Track val-or-atom) @val-or-atom
|
||||
(instance? Wrapper val-or-atom) val-or-atom.state
|
||||
:else (throw (js/Error. "Unknown reactive data type")))
|
||||
val-or-atom))
|
||||
|
||||
|
||||
(defn get-element-by-id
|
||||
[id]
|
||||
(.getElementById js/document id))
|
||||
|
||||
|
||||
(defn pad-zero
|
||||
"Left pad a string 's' with '0', until 's' has length 'len'. Return 's' unchanged, if it is already len or greater"
|
||||
[s len]
|
||||
(if (< (count s) len)
|
||||
(apply str (take-last len (concat (repeat len \0) s)))
|
||||
s))
|
||||
|
||||
|
||||
(defn pad-zero-number
|
||||
"return 'num' as a string of 'len' characters, left padding with '0' as necessary"
|
||||
[num len]
|
||||
(pad-zero (str num) len))
|
||||
|
||||
|
||||
(defn px
|
||||
"takes a number (and optional :negative keyword to indicate a negative value) and returns that number as a string with 'px' at the end"
|
||||
[val & negative]
|
||||
(str (if negative (- val) val) "px"))
|
||||
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; Handy vector functions
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(defn remove-nth
|
||||
"Removes the item at position n from a vector v, returning a shrunk vector"
|
||||
[v n]
|
||||
(vec
|
||||
(concat
|
||||
(subvec v 0 n) (subvec v (inc n) (count v)))))
|
||||
|
||||
|
||||
(defn insert-nth
|
||||
[vect index item]
|
||||
(apply merge (subvec vect 0 index) item (subvec vect index)))
|
||||
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; Utilities for vectors of maps containing :id
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(defn position-for-id
|
||||
"Takes a vector of maps 'v'. Returns the position of the first item in 'v' whose id-fn (default :id) matches 'id'.
|
||||
Returns nil if id not found"
|
||||
[id v & {:keys [id-fn] :or {id-fn :id}}]
|
||||
(let [index-fn (fn [index item] (when (= (id-fn item) id) index))]
|
||||
(first (keep-indexed index-fn v))))
|
||||
|
||||
|
||||
|
||||
(defn item-for-id
|
||||
"Takes a vector of maps 'v'. Returns the first item in 'v' whose id-fn (default :id) matches 'id'.
|
||||
Returns nil if id not found"
|
||||
[id v & {:keys [id-fn] :or {id-fn :id}}]
|
||||
(first (filter #(= (id-fn %) id) v)))
|
||||
|
||||
|
||||
(defn remove-id-item
|
||||
"Takes a vector of maps 'v', each of which has an id-fn (default :id) key.
|
||||
Return v where item matching 'id' is excluded"
|
||||
[id v & {:keys [id-fn] :or {id-fn :id}}]
|
||||
(filterv #(not= (id-fn %) id) v))
|
||||
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; Other functions
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(defn enumerate
|
||||
"(for [[index item first? last?] (enumerate coll)] ...) "
|
||||
[coll]
|
||||
(let [c (dec (count coll))
|
||||
f (fn [index item] [index item (= 0 index) (= c index)])]
|
||||
(map-indexed f coll)))
|
||||
|
||||
(defn sum-scroll-offsets
|
||||
"Given a DOM node, I traverse through all ascendant nodes (until I reach body), summing any scrollLeft and scrollTop values
|
||||
and return these sums in a map"
|
||||
[node]
|
||||
(loop [current-node (.-parentNode node) ;; Begin at parent
|
||||
sum-scroll-left 0
|
||||
sum-scroll-top 0]
|
||||
(if (not= (.-tagName current-node) "BODY")
|
||||
(recur (.-parentNode current-node)
|
||||
(+ sum-scroll-left (.-scrollLeft current-node))
|
||||
(+ sum-scroll-top (.-scrollTop current-node)))
|
||||
{:left sum-scroll-left
|
||||
:top sum-scroll-top})))
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; date functions
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(defn now->utc
|
||||
"Answer a goog.date.UtcDateTime based on local date/time."
|
||||
[]
|
||||
(let [local-date (js/goog.date.DateTime.)]
|
||||
(js/goog.date.UtcDateTime.
|
||||
(.getYear local-date)
|
||||
(.getMonth local-date)
|
||||
(.getDate local-date)
|
||||
0 0 0 0)))
|
||||
File diff suppressed because one or more lines are too long
400
resources/public/js/compiled/out/re_com/util.js
Normal file
400
resources/public/js/compiled/out/re_com/util.js
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
// Compiled by ClojureScript 1.9.229 {}
|
||||
goog.provide('re_com.util');
|
||||
goog.require('cljs.core');
|
||||
goog.require('clojure.set');
|
||||
goog.require('reagent.ratom');
|
||||
goog.require('goog.date.DateTime');
|
||||
goog.require('goog.date.UtcDateTime');
|
||||
/**
|
||||
* Takes a function 'f' amd a map 'm'. Applies 'f' to each value in 'm' and returns.
|
||||
* (fmap inc {:a 4 :b 2}) => {:a 5 :b 3}
|
||||
*/
|
||||
re_com.util.fmap = (function re_com$util$fmap(f,m){
|
||||
return cljs.core.into.call(null,cljs.core.PersistentArrayMap.EMPTY,(function (){var iter__25910__auto__ = (function re_com$util$fmap_$_iter__26710(s__26711){
|
||||
return (new cljs.core.LazySeq(null,(function (){
|
||||
var s__26711__$1 = s__26711;
|
||||
while(true){
|
||||
var temp__4657__auto__ = cljs.core.seq.call(null,s__26711__$1);
|
||||
if(temp__4657__auto__){
|
||||
var s__26711__$2 = temp__4657__auto__;
|
||||
if(cljs.core.chunked_seq_QMARK_.call(null,s__26711__$2)){
|
||||
var c__25908__auto__ = cljs.core.chunk_first.call(null,s__26711__$2);
|
||||
var size__25909__auto__ = cljs.core.count.call(null,c__25908__auto__);
|
||||
var b__26713 = cljs.core.chunk_buffer.call(null,size__25909__auto__);
|
||||
if((function (){var i__26712 = (0);
|
||||
while(true){
|
||||
if((i__26712 < size__25909__auto__)){
|
||||
var vec__26720 = cljs.core._nth.call(null,c__25908__auto__,i__26712);
|
||||
var k = cljs.core.nth.call(null,vec__26720,(0),null);
|
||||
var val = cljs.core.nth.call(null,vec__26720,(1),null);
|
||||
cljs.core.chunk_append.call(null,b__26713,new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [k,f.call(null,val)], null));
|
||||
|
||||
var G__26726 = (i__26712 + (1));
|
||||
i__26712 = G__26726;
|
||||
continue;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
})()){
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__26713),re_com$util$fmap_$_iter__26710.call(null,cljs.core.chunk_rest.call(null,s__26711__$2)));
|
||||
} else {
|
||||
return cljs.core.chunk_cons.call(null,cljs.core.chunk.call(null,b__26713),null);
|
||||
}
|
||||
} else {
|
||||
var vec__26723 = cljs.core.first.call(null,s__26711__$2);
|
||||
var k = cljs.core.nth.call(null,vec__26723,(0),null);
|
||||
var val = cljs.core.nth.call(null,vec__26723,(1),null);
|
||||
return cljs.core.cons.call(null,new cljs.core.PersistentVector(null, 2, 5, cljs.core.PersistentVector.EMPTY_NODE, [k,f.call(null,val)], null),re_com$util$fmap_$_iter__26710.call(null,cljs.core.rest.call(null,s__26711__$2)));
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}),null,null));
|
||||
});
|
||||
return iter__25910__auto__.call(null,m);
|
||||
})());
|
||||
});
|
||||
/**
|
||||
* Recursively merges maps. If vals are not maps, the last value wins.
|
||||
*/
|
||||
re_com.util.deep_merge = (function re_com$util$deep_merge(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___26728 = arguments.length;
|
||||
var i__26206__auto___26729 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___26729 < len__26205__auto___26728)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___26729]));
|
||||
|
||||
var G__26730 = (i__26206__auto___26729 + (1));
|
||||
i__26206__auto___26729 = G__26730;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((0) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((0)),(0),null)):null);
|
||||
return re_com.util.deep_merge.cljs$core$IFn$_invoke$arity$variadic(argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.util.deep_merge.cljs$core$IFn$_invoke$arity$variadic = (function (vals){
|
||||
if(cljs.core.every_QMARK_.call(null,cljs.core.map_QMARK_,vals)){
|
||||
return cljs.core.apply.call(null,cljs.core.merge_with,re_com.util.deep_merge,vals);
|
||||
} else {
|
||||
return cljs.core.last.call(null,vals);
|
||||
}
|
||||
});
|
||||
|
||||
re_com.util.deep_merge.cljs$lang$maxFixedArity = (0);
|
||||
|
||||
re_com.util.deep_merge.cljs$lang$applyTo = (function (seq26727){
|
||||
return re_com.util.deep_merge.cljs$core$IFn$_invoke$arity$variadic(cljs.core.seq.call(null,seq26727));
|
||||
});
|
||||
|
||||
/**
|
||||
* Takes a value or an atom
|
||||
* If it's a value, returns it
|
||||
* If it's a Reagent object that supports IDeref, returns the value inside it by derefing
|
||||
*
|
||||
*/
|
||||
re_com.util.deref_or_value = (function re_com$util$deref_or_value(val_or_atom){
|
||||
if(((!((val_or_atom == null)))?((((val_or_atom.cljs$lang$protocol_mask$partition0$ & (32768))) || (val_or_atom.cljs$core$IDeref$))?true:(((!val_or_atom.cljs$lang$protocol_mask$partition0$))?cljs.core.native_satisfies_QMARK_.call(null,cljs.core.IDeref,val_or_atom):false)):cljs.core.native_satisfies_QMARK_.call(null,cljs.core.IDeref,val_or_atom))){
|
||||
return cljs.core.deref.call(null,val_or_atom);
|
||||
} else {
|
||||
return val_or_atom;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Takes a value or an atom
|
||||
* If it's a value, returns it
|
||||
* If it's a Reagent object that supports IDeref, returns the value inside it, but WITHOUT derefing
|
||||
*
|
||||
* The arg validation code uses this, since calling deref-or-value adds this arg to the watched ratom list for the component
|
||||
* in question, which in turn can cause different rendering behaviour between dev (where we validate) and prod (where we don't).
|
||||
*
|
||||
* This was experienced in popover-content-wrapper with the position-injected atom which was not derefed there, however
|
||||
* the dev-only validation caused it to be derefed, modifying its render behaviour and causing mayhem and madness for the developer.
|
||||
*
|
||||
* See below that different Reagent types have different ways of retrieving the value without causing capture, although in the case of
|
||||
* Track, we just deref it as there is no peek or state, so hopefully this won't cause issues (surely this is used very rarely).
|
||||
*
|
||||
*/
|
||||
re_com.util.deref_or_value_peek = (function re_com$util$deref_or_value_peek(val_or_atom){
|
||||
if(((!((val_or_atom == null)))?((((val_or_atom.cljs$lang$protocol_mask$partition0$ & (32768))) || (val_or_atom.cljs$core$IDeref$))?true:(((!val_or_atom.cljs$lang$protocol_mask$partition0$))?cljs.core.native_satisfies_QMARK_.call(null,cljs.core.IDeref,val_or_atom):false)):cljs.core.native_satisfies_QMARK_.call(null,cljs.core.IDeref,val_or_atom))){
|
||||
if((val_or_atom instanceof reagent.ratom.RAtom)){
|
||||
return val_or_atom.state;
|
||||
} else {
|
||||
if((val_or_atom instanceof reagent.ratom.Reaction)){
|
||||
return val_or_atom._peek_at();
|
||||
} else {
|
||||
if((val_or_atom instanceof reagent.ratom.RCursor)){
|
||||
return val_or_atom._peek();
|
||||
} else {
|
||||
if((val_or_atom instanceof reagent.ratom.Track)){
|
||||
return cljs.core.deref.call(null,val_or_atom);
|
||||
} else {
|
||||
if((val_or_atom instanceof reagent.ratom.Wrapper)){
|
||||
return val_or_atom.state;
|
||||
} else {
|
||||
throw (new Error("Unknown reactive data type"));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return val_or_atom;
|
||||
}
|
||||
});
|
||||
re_com.util.get_element_by_id = (function re_com$util$get_element_by_id(id){
|
||||
return document.getElementById(id);
|
||||
});
|
||||
/**
|
||||
* Left pad a string 's' with '0', until 's' has length 'len'. Return 's' unchanged, if it is already len or greater
|
||||
*/
|
||||
re_com.util.pad_zero = (function re_com$util$pad_zero(s,len){
|
||||
if((cljs.core.count.call(null,s) < len)){
|
||||
return cljs.core.apply.call(null,cljs.core.str,cljs.core.take_last.call(null,len,cljs.core.concat.call(null,cljs.core.repeat.call(null,len,"0"),s)));
|
||||
} else {
|
||||
return s;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* return 'num' as a string of 'len' characters, left padding with '0' as necessary
|
||||
*/
|
||||
re_com.util.pad_zero_number = (function re_com$util$pad_zero_number(num,len){
|
||||
return re_com.util.pad_zero.call(null,[cljs.core.str(num)].join(''),len);
|
||||
});
|
||||
/**
|
||||
* takes a number (and optional :negative keyword to indicate a negative value) and returns that number as a string with 'px' at the end
|
||||
*/
|
||||
re_com.util.px = (function re_com$util$px(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___26737 = arguments.length;
|
||||
var i__26206__auto___26738 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___26738 < len__26205__auto___26737)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___26738]));
|
||||
|
||||
var G__26739 = (i__26206__auto___26738 + (1));
|
||||
i__26206__auto___26738 = G__26739;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((1) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((1)),(0),null)):null);
|
||||
return re_com.util.px.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.util.px.cljs$core$IFn$_invoke$arity$variadic = (function (val,negative){
|
||||
return [cljs.core.str((cljs.core.truth_(negative)?(- val):val)),cljs.core.str("px")].join('');
|
||||
});
|
||||
|
||||
re_com.util.px.cljs$lang$maxFixedArity = (1);
|
||||
|
||||
re_com.util.px.cljs$lang$applyTo = (function (seq26735){
|
||||
var G__26736 = cljs.core.first.call(null,seq26735);
|
||||
var seq26735__$1 = cljs.core.next.call(null,seq26735);
|
||||
return re_com.util.px.cljs$core$IFn$_invoke$arity$variadic(G__26736,seq26735__$1);
|
||||
});
|
||||
|
||||
/**
|
||||
* Removes the item at position n from a vector v, returning a shrunk vector
|
||||
*/
|
||||
re_com.util.remove_nth = (function re_com$util$remove_nth(v,n){
|
||||
return cljs.core.vec.call(null,cljs.core.concat.call(null,cljs.core.subvec.call(null,v,(0),n),cljs.core.subvec.call(null,v,(n + (1)),cljs.core.count.call(null,v))));
|
||||
});
|
||||
re_com.util.insert_nth = (function re_com$util$insert_nth(vect,index,item){
|
||||
return cljs.core.apply.call(null,cljs.core.merge,cljs.core.subvec.call(null,vect,(0),index),item,cljs.core.subvec.call(null,vect,index));
|
||||
});
|
||||
/**
|
||||
* Takes a vector of maps 'v'. Returns the position of the first item in 'v' whose id-fn (default :id) matches 'id'.
|
||||
* Returns nil if id not found
|
||||
*/
|
||||
re_com.util.position_for_id = (function re_com$util$position_for_id(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___26746 = arguments.length;
|
||||
var i__26206__auto___26747 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___26747 < len__26205__auto___26746)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___26747]));
|
||||
|
||||
var G__26748 = (i__26206__auto___26747 + (1));
|
||||
i__26206__auto___26747 = G__26748;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((2) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((2)),(0),null)):null);
|
||||
return re_com.util.position_for_id.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),(arguments[(1)]),argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.util.position_for_id.cljs$core$IFn$_invoke$arity$variadic = (function (id,v,p__26743){
|
||||
var map__26744 = p__26743;
|
||||
var map__26744__$1 = ((((!((map__26744 == null)))?((((map__26744.cljs$lang$protocol_mask$partition0$ & (64))) || (map__26744.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__26744):map__26744);
|
||||
var id_fn = cljs.core.get.call(null,map__26744__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
var index_fn = ((function (map__26744,map__26744__$1,id_fn){
|
||||
return (function (index,item){
|
||||
if(cljs.core._EQ_.call(null,id_fn.call(null,item),id)){
|
||||
return index;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});})(map__26744,map__26744__$1,id_fn))
|
||||
;
|
||||
return cljs.core.first.call(null,cljs.core.keep_indexed.call(null,index_fn,v));
|
||||
});
|
||||
|
||||
re_com.util.position_for_id.cljs$lang$maxFixedArity = (2);
|
||||
|
||||
re_com.util.position_for_id.cljs$lang$applyTo = (function (seq26740){
|
||||
var G__26741 = cljs.core.first.call(null,seq26740);
|
||||
var seq26740__$1 = cljs.core.next.call(null,seq26740);
|
||||
var G__26742 = cljs.core.first.call(null,seq26740__$1);
|
||||
var seq26740__$2 = cljs.core.next.call(null,seq26740__$1);
|
||||
return re_com.util.position_for_id.cljs$core$IFn$_invoke$arity$variadic(G__26741,G__26742,seq26740__$2);
|
||||
});
|
||||
|
||||
/**
|
||||
* Takes a vector of maps 'v'. Returns the first item in 'v' whose id-fn (default :id) matches 'id'.
|
||||
* Returns nil if id not found
|
||||
*/
|
||||
re_com.util.item_for_id = (function re_com$util$item_for_id(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___26756 = arguments.length;
|
||||
var i__26206__auto___26757 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___26757 < len__26205__auto___26756)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___26757]));
|
||||
|
||||
var G__26758 = (i__26206__auto___26757 + (1));
|
||||
i__26206__auto___26757 = G__26758;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((2) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((2)),(0),null)):null);
|
||||
return re_com.util.item_for_id.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),(arguments[(1)]),argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.util.item_for_id.cljs$core$IFn$_invoke$arity$variadic = (function (id,v,p__26753){
|
||||
var map__26754 = p__26753;
|
||||
var map__26754__$1 = ((((!((map__26754 == null)))?((((map__26754.cljs$lang$protocol_mask$partition0$ & (64))) || (map__26754.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__26754):map__26754);
|
||||
var id_fn = cljs.core.get.call(null,map__26754__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
return cljs.core.first.call(null,cljs.core.filter.call(null,((function (map__26754,map__26754__$1,id_fn){
|
||||
return (function (p1__26749_SHARP_){
|
||||
return cljs.core._EQ_.call(null,id_fn.call(null,p1__26749_SHARP_),id);
|
||||
});})(map__26754,map__26754__$1,id_fn))
|
||||
,v));
|
||||
});
|
||||
|
||||
re_com.util.item_for_id.cljs$lang$maxFixedArity = (2);
|
||||
|
||||
re_com.util.item_for_id.cljs$lang$applyTo = (function (seq26750){
|
||||
var G__26751 = cljs.core.first.call(null,seq26750);
|
||||
var seq26750__$1 = cljs.core.next.call(null,seq26750);
|
||||
var G__26752 = cljs.core.first.call(null,seq26750__$1);
|
||||
var seq26750__$2 = cljs.core.next.call(null,seq26750__$1);
|
||||
return re_com.util.item_for_id.cljs$core$IFn$_invoke$arity$variadic(G__26751,G__26752,seq26750__$2);
|
||||
});
|
||||
|
||||
/**
|
||||
* Takes a vector of maps 'v', each of which has an id-fn (default :id) key.
|
||||
* Return v where item matching 'id' is excluded
|
||||
*/
|
||||
re_com.util.remove_id_item = (function re_com$util$remove_id_item(var_args){
|
||||
var args__26212__auto__ = [];
|
||||
var len__26205__auto___26766 = arguments.length;
|
||||
var i__26206__auto___26767 = (0);
|
||||
while(true){
|
||||
if((i__26206__auto___26767 < len__26205__auto___26766)){
|
||||
args__26212__auto__.push((arguments[i__26206__auto___26767]));
|
||||
|
||||
var G__26768 = (i__26206__auto___26767 + (1));
|
||||
i__26206__auto___26767 = G__26768;
|
||||
continue;
|
||||
} else {
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
var argseq__26213__auto__ = ((((2) < args__26212__auto__.length))?(new cljs.core.IndexedSeq(args__26212__auto__.slice((2)),(0),null)):null);
|
||||
return re_com.util.remove_id_item.cljs$core$IFn$_invoke$arity$variadic((arguments[(0)]),(arguments[(1)]),argseq__26213__auto__);
|
||||
});
|
||||
|
||||
re_com.util.remove_id_item.cljs$core$IFn$_invoke$arity$variadic = (function (id,v,p__26763){
|
||||
var map__26764 = p__26763;
|
||||
var map__26764__$1 = ((((!((map__26764 == null)))?((((map__26764.cljs$lang$protocol_mask$partition0$ & (64))) || (map__26764.cljs$core$ISeq$))?true:false):false))?cljs.core.apply.call(null,cljs.core.hash_map,map__26764):map__26764);
|
||||
var id_fn = cljs.core.get.call(null,map__26764__$1,new cljs.core.Keyword(null,"id-fn","id-fn",316222798),new cljs.core.Keyword(null,"id","id",-1388402092));
|
||||
return cljs.core.filterv.call(null,((function (map__26764,map__26764__$1,id_fn){
|
||||
return (function (p1__26759_SHARP_){
|
||||
return cljs.core.not_EQ_.call(null,id_fn.call(null,p1__26759_SHARP_),id);
|
||||
});})(map__26764,map__26764__$1,id_fn))
|
||||
,v);
|
||||
});
|
||||
|
||||
re_com.util.remove_id_item.cljs$lang$maxFixedArity = (2);
|
||||
|
||||
re_com.util.remove_id_item.cljs$lang$applyTo = (function (seq26760){
|
||||
var G__26761 = cljs.core.first.call(null,seq26760);
|
||||
var seq26760__$1 = cljs.core.next.call(null,seq26760);
|
||||
var G__26762 = cljs.core.first.call(null,seq26760__$1);
|
||||
var seq26760__$2 = cljs.core.next.call(null,seq26760__$1);
|
||||
return re_com.util.remove_id_item.cljs$core$IFn$_invoke$arity$variadic(G__26761,G__26762,seq26760__$2);
|
||||
});
|
||||
|
||||
/**
|
||||
* (for [[index item first? last?] (enumerate coll)] ...)
|
||||
*/
|
||||
re_com.util.enumerate = (function re_com$util$enumerate(coll){
|
||||
var c = (cljs.core.count.call(null,coll) - (1));
|
||||
var f = ((function (c){
|
||||
return (function (index,item){
|
||||
return new cljs.core.PersistentVector(null, 4, 5, cljs.core.PersistentVector.EMPTY_NODE, [index,item,cljs.core._EQ_.call(null,(0),index),cljs.core._EQ_.call(null,c,index)], null);
|
||||
});})(c))
|
||||
;
|
||||
return cljs.core.map_indexed.call(null,f,coll);
|
||||
});
|
||||
/**
|
||||
* Given a DOM node, I traverse through all ascendant nodes (until I reach body), summing any scrollLeft and scrollTop values
|
||||
* and return these sums in a map
|
||||
*/
|
||||
re_com.util.sum_scroll_offsets = (function re_com$util$sum_scroll_offsets(node){
|
||||
var current_node = node.parentNode;
|
||||
var sum_scroll_left = (0);
|
||||
var sum_scroll_top = (0);
|
||||
while(true){
|
||||
if(cljs.core.not_EQ_.call(null,current_node.tagName,"BODY")){
|
||||
var G__26769 = current_node.parentNode;
|
||||
var G__26770 = (sum_scroll_left + current_node.scrollLeft);
|
||||
var G__26771 = (sum_scroll_top + current_node.scrollTop);
|
||||
current_node = G__26769;
|
||||
sum_scroll_left = G__26770;
|
||||
sum_scroll_top = G__26771;
|
||||
continue;
|
||||
} else {
|
||||
return new cljs.core.PersistentArrayMap(null, 2, [new cljs.core.Keyword(null,"left","left",-399115937),sum_scroll_left,new cljs.core.Keyword(null,"top","top",-1856271961),sum_scroll_top], null);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
/**
|
||||
* Answer a goog.date.UtcDateTime based on local date/time.
|
||||
*/
|
||||
re_com.util.now__GT_utc = (function re_com$util$now__GT_utc(){
|
||||
var local_date = (new goog.date.DateTime());
|
||||
return (new goog.date.UtcDateTime(local_date.getYear(),local_date.getMonth(),local_date.getDate(),(0),(0),(0),(0)));
|
||||
});
|
||||
|
||||
//# sourceMappingURL=util.js.map?rel=1603199188274
|
||||
1
resources/public/js/compiled/out/re_com/util.js.map
Normal file
1
resources/public/js/compiled/out/re_com/util.js.map
Normal file
File diff suppressed because one or more lines are too long
372
resources/public/js/compiled/out/re_com/validate.cljs
Normal file
372
resources/public/js/compiled/out/re_com/validate.cljs
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
(ns re-com.validate
|
||||
(:require
|
||||
[clojure.set :refer [superset?]]
|
||||
[re-com.util :refer [deref-or-value-peek]]
|
||||
[reagent.core :as reagent]
|
||||
[reagent.impl.template :refer [valid-tag?]]
|
||||
[goog.string :as gstring]
|
||||
[goog.date.UtcDateTime]))
|
||||
|
||||
|
||||
;; -- Helpers -----------------------------------------------------------------
|
||||
|
||||
(defn left-string
|
||||
"Converts obj to a string and truncates it to max-len chars if necessary.
|
||||
When truncation is necessary, adds an elipsis to the end"
|
||||
[obj max-len]
|
||||
(gstring/truncate (str obj) max-len))
|
||||
|
||||
(defn log-error
|
||||
"Sends a message to the DeV Tools console as an error. Returns false to indicate 'error' condition"
|
||||
[& args]
|
||||
(.error js/console (apply str args))
|
||||
false)
|
||||
|
||||
(defn log-warning
|
||||
"Sends a message to the DeV Tools console as an warning. Returns true to indicate 'not and error' condition"
|
||||
[& args]
|
||||
(.warn js/console (apply str args))
|
||||
true)
|
||||
|
||||
|
||||
(defn hash-map-with-name-keys
|
||||
[v]
|
||||
(zipmap (map :name v) v))
|
||||
|
||||
|
||||
(defn extract-arg-data
|
||||
"Package up all the relevant data for validation purposes from the xxx-args-desc map into a new map"
|
||||
[args-desc]
|
||||
{:arg-names (set (map :name args-desc))
|
||||
:required-args (->> args-desc
|
||||
(filter :required)
|
||||
(map :name)
|
||||
set)
|
||||
:validated-args (->> (filter :validate-fn args-desc)
|
||||
vec
|
||||
(hash-map-with-name-keys))})
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; Primary validation functions
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(defn arg-names-valid?
|
||||
"returns true if every passed-args is value. Otherwise log the problem and return false"
|
||||
[defined-args passed-args]
|
||||
(or (superset? defined-args passed-args)
|
||||
(let [missing-args (remove defined-args passed-args)]
|
||||
(log-error "Invalid argument(s): " missing-args)))) ;; Regent will show the component-path
|
||||
|
||||
(defn required-args-passed?
|
||||
"returns true if all the required args are supplied. Otherwise log the error and return false"
|
||||
[required-args passed-args]
|
||||
(or (superset? passed-args required-args)
|
||||
(let [missing-args (remove passed-args required-args)]
|
||||
(log-error "Missing required argument(s): " missing-args)))) ;; Regent will show the component-path
|
||||
|
||||
|
||||
(defn validate-fns-pass?
|
||||
"Gathers together a list of args that have a validator and...
|
||||
returns true if all argument values are valid OR are just warnings (log warning to the console).
|
||||
Otherwise log an error to the console and return false.
|
||||
Validation functions can return:
|
||||
- true: validation success
|
||||
- false: validation failed - use standard error message
|
||||
- map: validation failed - includes two keys:
|
||||
:status - :error: log to console as error
|
||||
:warning: log to console as warning
|
||||
:message - use this string in the message of the warning/error"
|
||||
[args-with-validators passed-args component-name]
|
||||
(let [validate-arg (fn [[_ v-arg-def]]
|
||||
(let [arg-name (:name v-arg-def)
|
||||
arg-val (deref-or-value-peek (arg-name passed-args)) ;; Automatically extract value if it's in an atom
|
||||
required? (:required v-arg-def)
|
||||
validate-result ((:validate-fn v-arg-def) arg-val)
|
||||
log-msg-base (str "Validation failed for argument '" arg-name "' in component '" component-name "': ")
|
||||
comp-path (str " at " (reagent/component-path (reagent/current-component)))
|
||||
warning? (= (:status validate-result) :warning)]
|
||||
;(println (str "[" component-name "] " arg-name " = '" (if (nil? arg-val) "nil" (left-string arg-val 200)) "' => " validate-result))
|
||||
(cond
|
||||
(or (true? validate-result)
|
||||
(and (nil? arg-val) ;; Allow nil values through if the arg is NOT required
|
||||
(not required?))) true
|
||||
(false? validate-result) (log-error log-msg-base "Expected '" (:type v-arg-def) "'. Got '" (if (nil? arg-val) "nil" (left-string arg-val 60)) "'" comp-path)
|
||||
(map? validate-result) ((if warning? log-warning log-error)
|
||||
log-msg-base
|
||||
(:message validate-result)
|
||||
(when warning? comp-path))
|
||||
:else (log-error "Invalid return from validate-fn: " validate-result comp-path))))]
|
||||
(->> (select-keys args-with-validators (vec (keys passed-args)))
|
||||
(map validate-arg)
|
||||
(every? true?))))
|
||||
|
||||
(defn validate-args
|
||||
"Calls three validation tests:
|
||||
- Are arg names valid?
|
||||
- Have all required args been passed?
|
||||
- Specific valiadation function calls to check arg values if specified
|
||||
If they all pass, returns true.
|
||||
Normally used for a call to the {:pre...} at the beginning of a function"
|
||||
[arg-defs passed-args & component-name]
|
||||
(if-not ^boolean js/goog.DEBUG
|
||||
true
|
||||
(let [passed-arg-keys (set (keys passed-args))]
|
||||
(and (arg-names-valid? (:arg-names arg-defs) passed-arg-keys)
|
||||
(required-args-passed? (:required-args arg-defs) passed-arg-keys)
|
||||
(validate-fns-pass? (:validated-args arg-defs) passed-args (first component-name))))))
|
||||
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; Custom :validate-fn functions based on (validate-arg-against-set)
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(def justify-options [:start :end :center :between :around])
|
||||
(def align-options [:start :end :center :baseline :stretch])
|
||||
(def scroll-options [:auto :off :on :spill])
|
||||
(def alert-types [:none :info :warning :danger])
|
||||
(def button-sizes [:regular :smaller :larger])
|
||||
(def throbber-sizes [:regular :smaller :small :large])
|
||||
(def input-status-types [:success :warning :error :validating])
|
||||
(def popover-status-types [:success :warning :error :validating :info])
|
||||
(def title-levels [:level1 :level2 :level3 :level4])
|
||||
(def position-options [:above-left :above-center :above-right
|
||||
:below-left :below-center :below-right
|
||||
:left-above :left-center :left-below
|
||||
:right-above :right-center :right-below])
|
||||
|
||||
(defn validate-arg-against-set
|
||||
"Validates the passed argument against the expected set"
|
||||
[arg arg-name valid-set]
|
||||
(let [arg (deref-or-value-peek arg)]
|
||||
(or (not= (some (hash-set arg) valid-set) nil)
|
||||
(str "Invalid " arg-name ". Expected one of " valid-set ". Got '" (left-string arg 40) "'"))))
|
||||
|
||||
(defn justify-style? [arg] (validate-arg-against-set arg ":justify-style" justify-options))
|
||||
(defn align-style? [arg] (validate-arg-against-set arg ":align-style" align-options))
|
||||
(defn scroll-style? [arg] (validate-arg-against-set arg ":scroll-style" scroll-options))
|
||||
(defn alert-type? [arg] (validate-arg-against-set arg ":alert-type" alert-types))
|
||||
(defn button-size? [arg] (validate-arg-against-set arg ":size" button-sizes))
|
||||
(defn throbber-size? [arg] (validate-arg-against-set arg ":size" throbber-sizes))
|
||||
(defn input-status-type? [arg] (validate-arg-against-set arg ":status" input-status-types))
|
||||
(defn popover-status-type? [arg] (validate-arg-against-set arg ":status" popover-status-types))
|
||||
(defn title-level-type? [arg] (validate-arg-against-set arg ":level" title-levels))
|
||||
(defn position? [arg] (validate-arg-against-set arg ":position" position-options))
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; Predefined hiccup lists for streamlined consumption in arg documentation
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(defn make-code-list
|
||||
"Given a vector or list of codes, create a [:span] hiccup vector containing a comma separated list of the codes"
|
||||
[codes]
|
||||
(into [:span] (interpose ", " (map #(vector :code (str %)) codes))))
|
||||
|
||||
(def justify-options-list (make-code-list justify-options))
|
||||
(def align-options-list (make-code-list align-options))
|
||||
(def scroll-options-list (make-code-list scroll-options))
|
||||
(def alert-types-list (make-code-list alert-types))
|
||||
(def button-sizes-list (make-code-list button-sizes))
|
||||
(def throbber-sizes-list (make-code-list throbber-sizes))
|
||||
(def input-status-types-list (make-code-list input-status-types))
|
||||
(def popover-status-types-list (make-code-list popover-status-types))
|
||||
(def title-levels-list (make-code-list title-levels))
|
||||
(def position-options-list (make-code-list position-options))
|
||||
|
||||
|
||||
;; ----------------------------------------------------------------------------
|
||||
;; Custom :validate-fn functions
|
||||
;; ----------------------------------------------------------------------------
|
||||
|
||||
(def html-attrs #{; ----- HTML attributes (:class and :style commented out as they are not valid in re-com)
|
||||
; ----- Reference: https://facebook.github.io/react/docs/dom-elements.html#all-supported-html-attributes
|
||||
:accept :accept-charset :access-key :action :allow-full-screen :allow-transparency :alt :async :auto-complete :auto-focus :auto-play :capture
|
||||
:cell-padding :cell-spacing :challenge :char-set :checked :cite #_:class :class-name :cols :col-span :content :content-editable :context-menu :controls
|
||||
:coords :cross-origin :data :date-time :default :defer :dir :disabled :download :draggable :enc-type :form :form-action :form-enc-type :form-method
|
||||
:form-no-validate :form-target :frame-border :headers :height :hidden :high :href :href-lang :html-for :http-equiv :icon :id :input-mode :integrity
|
||||
:is :key-params :key-type :kind :label :lang :list :loop :low :manifest
|
||||
:margin-height :margin-width :max :max-length :media :media-group :method :min :min-length :multiple :muted :name :no-validate :nonce :open :optimum :pattern :placeholder
|
||||
:poster :preload :profile :radio-group :read-only :rel :required :reversed :role :rows :row-span :sandbox :scope :scoped :scrolling :seamless :selected :shape :size :sizes
|
||||
:span :spell-check :src :src-doc :src-lang :src-set :start :step #_:style :summary :tab-index :target :title :type :use-map :value :width :wmode :wrap
|
||||
; ----- SVG attributes
|
||||
; ----- Reference: https://facebook.github.io/react/docs/dom-elements.html#all-supported-svg-attributes
|
||||
:accentheight :accumulate :additive :alignment-baseline :allow-reorder :alphabetic :amplitude :arabic-form :ascent :attribute-name :attribute-type
|
||||
:auto-reverse :azimuth :base-frequency :base-profile :baseline-shift :bbox :begin :bias :by :calc-mode :cap-height :clip :clip-path :clip-path-units
|
||||
:clip-rule :color-interpolation :color-interpolation-filters :color-profile :color-rendering :content-script-type :content-style-type :cursor :cx :cy :d
|
||||
:decelerate :descent :diffuse-constant :direction :display :divisor :dominant-baseline :dur :dx :dy :edge-mode :elevation :enable-background :end :exponent
|
||||
:external-resources-required :fill :fill-opacity :fill-rule :filter :filter-res :filter-units :flood-color :flood-opacity :focusable :font-family :font-size
|
||||
:font-size-adjust :font-stretch :font-style :font-variant :font-weight :format :from :fx :fy :g1 :g2 :glyph-name :glyph-orientation-horizontal :glyph-orientation-vertical
|
||||
:glyph-ref :gradient-transform :gradient-units :hanging :horiz-adv-x :horiz-origin-x :ideographic :image-rendering :in :in2 :intercept :k :k1 :k2 :k3 :k4
|
||||
:kernel-matrix :kernel-unit-length :kerning :key-points :key-splines :key-times :length-adjust :letter-spacing :lighting-color :limiting-cone-angle :local
|
||||
:marker-end :marker-height :marker-mid :marker-start :marker-units :marker-width :mask :mask-content-units :mask-units :mathematical :mode :num-octaves
|
||||
:offset :opacity :operator :order :orient :orientation :origin :overflow :overline-position :overline-thickness :paint-order :panose1 :path-length
|
||||
:pattern-content-units :pattern-transform :pattern-units :pointer-events :points :points-at-x :points-at-y :points-at-z :preserve-alpha :preserve-aspect-ratio
|
||||
:primitive-units :r :radius :ref-x :ref-y :rendering-intent :repeat-count :repeat-dur :required-extensions :required-features :restart :result :rotate :rx :ry
|
||||
:scale :seed :shape-rendering :slope :spacing :specular-constant :specular-exponent :speed :spread-method :start-offset :std-deviation :stemh :stemv :stitch-tiles
|
||||
:stop-color :stop-opacity :strikethrough-position :strikethrough-thickness :string :stroke :stroke-dasharray :stroke-dashoffset :stroke-linecap :stroke-linejoin
|
||||
:stroke-miterlimit :stroke-opacity :stroke-width :surface-scale :system-language :table-values :target-x :target-y :text-anchor :text-decoration :text-length
|
||||
:text-rendering :to :transform :u1 :u2 :underline-position :underline-thickness :unicode :unicode-bidi :unicode-range :units-per-em :v-alphabetic :v-hanging
|
||||
:v-ideographic :v-mathematical :values :vector-effect :version :vert-adv-y :vert-origin-x :vert-origin-y :view-box :view-target :visibility :widths :word-spacing
|
||||
:writing-mode :x :x1 :x2 :x-channel-selector :x-height :xlink-actuate :xlink-arcrole :xlink-href :xlink-role :xlink-show :xlink-title :xlink-type :xml-base
|
||||
:xml-lang :xml-space :y :y1 :y2 :y-channel-selector :z :zoom-and-pan
|
||||
; ----- Event attributes
|
||||
; ----- Reference: https://facebook.github.io/react/docs/events.html#supported-events
|
||||
:on-copy :on-cut :on-paste :on-composition-end :on-composition-start :on-composition-update :on-key-down
|
||||
:on-key-press :on-key-up :on-focus :on-blur :on-change :on-input :on-submit :on-click
|
||||
:on-context-menu :on-double-click :on-drag :on-drag-end :on-drag-enter :on-drag-exit :on-drag-leave
|
||||
:on-drag-over :on-drag-start :on-drop :on-mouse-down :on-mouse-enter :on-mouse-leave :on-mouse-move
|
||||
:on-mouse-out :on-mouse-over :on-mouse-up :on-select :on-touch-cancel :on-touch-end :on-touch-move
|
||||
:on-touch-start :on-scroll :on-wheel :on-abort :on-can-play :on-can-play-through :on-duration-change
|
||||
:on-emptied :on-encrypted :on-ended :on-error :on-loaded-data :on-loaded-metadata :on-load-start
|
||||
:on-pause :on-play :on-playing :on-progress :on-rate-change :on-seeked :on-seeking :on-stalled
|
||||
:on-suspend :on-time-update :on-volume-change :on-waiting :on-load #_:on-error :on-animation-start
|
||||
:on-animation-end :on-animation-iteration :on-transition-end
|
||||
; ----- '--capture' versions of the above events
|
||||
:on-copy-capture :on-cut-capture :on-paste-capture :on-composition-end-capture :on-composition-start-capture :on-composition-update-capture :on-key-down-capture
|
||||
:on-key-press-capture :on-key-up-capture :on-focus-capture :on-blur-capture :on-change-capture :on-input-capture :on-submit-capture :on-click-capture
|
||||
:on-context-menu-capture :on-double-click-capture :on-drag-capture :on-drag-end-capture :on-drag-enter-capture :on-drag-exit-capture :on-drag-leave-capture
|
||||
:on-drag-over-capture :on-drag-start-capture :on-drop-capture :on-mouse-down-capture :on-mouse-enter-capture :on-mouse-leave-capture :on-mouse-move-capture
|
||||
:on-mouse-out-capture :on-mouse-over-capture :on-mouse-up-capture :on-select-capture :on-touch-cancel-capture :on-touch-end-capture :on-touch-move-capture
|
||||
:on-touch-start-capture :on-scroll-capture :on-wheel-capture :on-abort-capture :on-can-play-capture :on-can-play-through-capture :on-duration-change-capture
|
||||
:on-emptied-capture :on-encrypted-capture :on-ended-capture :on-error-capture :on-loaded-data-capture :on-loaded-metadata-capture :on-load-start-capture
|
||||
:on-pause-capture :on-play-capture :on-playing-capture :on-progress-capture :on-rate-change-capture :on-seeked-capture :on-seeking-capture :on-stalled-capture
|
||||
:on-suspend-capture :on-time-update-capture :on-volume-change-capture :on-waiting-capture :on-load-capture #_:on-error-capture :on-animation-start-capture
|
||||
:on-animation-end-capture :on-animation-iteration-capture :on-transition-end-capture})
|
||||
|
||||
; ----- Reference: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/data-*
|
||||
; ----- https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA
|
||||
(def extension-attrs #{:data :aria})
|
||||
|
||||
(def css-styles #{; ----- Standard CSS styles
|
||||
; ----- Reference: https://developer.mozilla.org/en-US/docs/Web/CSS/Reference
|
||||
:align-content :align-items :align-self :all :animation :animation-delay :animation-direction :animation-duration :animation-fill-mode
|
||||
:animation-iteration-count :animation-name :animation-play-state :animation-timing-function :backface-visibility :background
|
||||
:background-attachment :background-blend-mode :background-clip :background-color :background-image :background-origin :background-position
|
||||
:background-repeat :background-size :block-size :border :border-block-end :border-block-end-color :border-block-end-style :border-block-end-width
|
||||
:border-block-start :border-block-start-color :border-block-start-style :border-block-start-width :border-bottom :border-bottom-color
|
||||
:border-bottom-left-radius :border-bottom-right-radius :border-bottom-style :border-bottom-width :border-collapse :border-color :border-image
|
||||
:border-image-outset :border-image-repeat :border-image-slice :border-image-source :border-image-width :border-inline-end :border-inline-end-color
|
||||
:border-inline-end-style :border-inline-end-width :border-inline-start :border-inline-start-color :border-inline-start-style :border-inline-start-width
|
||||
:border-left :border-left-color :border-left-style :border-left-width :border-radius :border-right :border-right-color :border-right-style
|
||||
:border-right-width :border-spacing :border-style :border-top :border-top-color :border-top-left-radius :border-top-right-radius :border-top-style
|
||||
:border-top-width :border-width :bottom :box-decoration-break :box-shadow :box-sizing :break-after :break-before :break-inside :caption-side :ch :clear
|
||||
:clip :clip-path :cm :color :column-count :column-fill :column-gap :column-rule :column-rule-color :column-rule-style :column-rule-width :columns
|
||||
:column-span :column-width :content :counter-increment :counter-reset :cursor :deg :direction :display :dpcm :dpi :dppx :em :empty-cells :ex :filter
|
||||
:flex :flex-basis :flex-direction :flex-flow :flex-grow :flex-shrink :flex-wrap :float :font :font-family :font-feature-settings :font-kerning
|
||||
:font-language-override :font-size :font-size-adjust :font-stretch :font-style :font-synthesis :font-variant :font-variant-alternates :font-variant-caps
|
||||
:font-variant-east-asian :font-variant-ligatures :font-variant-numeric :font-variant-position :font-weight :fr :grad :grid :grid-area :grid-auto-columns
|
||||
:grid-auto-flow :grid-auto-position :grid-auto-rows :grid-column :grid-column-end :grid-column-gap :grid-column-start :grid-gap :grid-row :grid-row-end
|
||||
:grid-row-gap :grid-row-start :grid-template :grid-template-areas :grid-template-columns :grid-template-rows :height :hyphens :hz :image-orientation
|
||||
:image-rendering :image-resolution :ime-mode :in :inherit :initial :inline-size :isolation :justify-content :khz :left :letter-spacing :line-break
|
||||
:line-height :list-style :list-style-image :list-style-position :list-style-type :margin :margin-block-end :margin-block-start :margin-bottom
|
||||
:margin-inline-end :margin-inline-start :margin-left :margin-right :margin-top :marks :mask :mask-clip :mask-composite :mask-image :mask-mode
|
||||
:mask-origin :mask-position :mask-repeat :mask-size :mask-type :max-block-size :max-height :max-inline-size :max-width :min-block-size :min-height
|
||||
:min-inline-size :min-width :mix-blend-mode :mm :ms :object-fit :object-position :offset-block-end :offset-block-start :offset-inline-end
|
||||
:offset-inline-start :opacity :order :orphans :outline :outline-color :outline-offset :outline-style :outline-width :overflow :overflow-wrap
|
||||
:overflow-x :overflow-y :padding :padding-block-end :padding-block-start :padding-bottom :padding-inline-end :padding-inline-start :padding-left
|
||||
:padding-right :padding-top :page-break-after :page-break-before :page-break-inside :pc :perspective :perspective-origin :pointer-events :position
|
||||
:pt :px :quotes :rad :rem :resize :revert :right :ruby-align :ruby-merge :ruby-position :s :scroll-behavior :scroll-snap-coordinate :scroll-snap-destination
|
||||
:scroll-snap-type :shape-image-threshold :shape-margin :shape-outside :table-layout :tab-size :text-align :text-align-last :text-combine-upright
|
||||
:text-decoration :text-decoration-color :text-decoration-line :text-decoration-style :text-emphasis :text-emphasis-color :text-emphasis-position
|
||||
:text-emphasis-style :text-indent :text-orientation :text-overflow :text-rendering :text-shadow :text-transform :text-underline-position :top
|
||||
:touch-action :transform :transform-box :transform-origin :transform-style :transition :transition-delay :transition-duration :transition-property
|
||||
:transition-timing-function :turn :unicode-bidi :unicode-range :unset :vertical-align :vh :visibility :vmax :vmin :vw :white-space :widows :width
|
||||
:will-change :word-break :word-spacing :word-wrap :writing-mode :z-index
|
||||
; ----- Browser specific styles
|
||||
:-webkit-user-select :-moz-user-select :-ms-user-select :user-select
|
||||
:-webkit-flex-flow :-webkit-flex-direction :-webkit-flex-wrap :-webkit-justify-content :-webkit-align-items :-webkit-align-content
|
||||
:-webkit-flex :-webkit-flex-grow :-webkit-flex-shrink :-webkit-flex-basis :-webkit-order :-webkit-align-self})
|
||||
|
||||
(defn string-or-hiccup?
|
||||
"Returns true if the passed argument is either valid hiccup or a string, otherwise false/error"
|
||||
[arg]
|
||||
(valid-tag? (deref-or-value-peek arg)))
|
||||
|
||||
(defn vector-of-maps?
|
||||
"Returns true if the passed argument is a vector of maps (either directly or contained in an atom), otherwise false/error
|
||||
Notes:
|
||||
- actually it also accepts a list of maps (should we rename this? Potential long/ugly names: sequential-of-maps?, vector-or-list-of-maps?)
|
||||
- vector/list can be empty
|
||||
- only checks the first element in the vector/list"
|
||||
[arg]
|
||||
(let [arg (deref-or-value-peek arg)]
|
||||
(and (sequential? arg) ;; Allows lists as well
|
||||
(or (empty? arg)
|
||||
(map? (first arg))))))
|
||||
|
||||
(defn css-style?
|
||||
"Returns true if the passed argument is a valid CSS style.
|
||||
Otherwise returns a warning map"
|
||||
[arg]
|
||||
(if-not ^boolean js/goog.DEBUG
|
||||
true
|
||||
(let [arg (deref-or-value-peek arg)]
|
||||
(and (map? arg)
|
||||
(let [arg-keys (keys arg)]
|
||||
(or (superset? css-styles arg-keys)
|
||||
{:status :warning
|
||||
:message (str "Unknown CSS style(s): " (remove css-styles arg-keys))}))))))
|
||||
|
||||
(defn extension-attribute?
|
||||
"Returns truthy if the attribute name is an extension attribute, that is data-* or aria-*, otherwise falsey."
|
||||
([attr]
|
||||
(let [attr (name attr)
|
||||
ext? #(and (= (.indexOf attr %) 0)
|
||||
(> (count attr) (count %)))]
|
||||
(some (comp ext? #(str % "-") name) extension-attrs))))
|
||||
|
||||
(defn invalid-html-attrs
|
||||
"Returns the subset of HTML attributes contained in the passed argument that are not valid HTML attributes."
|
||||
[attrs]
|
||||
(remove #(or (html-attrs %)
|
||||
(extension-attribute? %))
|
||||
attrs))
|
||||
|
||||
(defn html-attr?
|
||||
"Returns true if the passed argument is a valid HTML, SVG or event attribute.
|
||||
Otherwise returns a warning map.
|
||||
Notes:
|
||||
- Prevents :class and :style attributes"
|
||||
[arg]
|
||||
(if-not ^boolean js/goog.DEBUG
|
||||
true
|
||||
(let [arg (deref-or-value-peek arg)]
|
||||
(and (map? arg)
|
||||
(let [arg-keys (set (keys arg))
|
||||
contains-class? (contains? arg-keys :class)
|
||||
contains-style? (contains? arg-keys :style)
|
||||
result (cond
|
||||
contains-class? ":class not allowed in :attr argument"
|
||||
contains-style? ":style not allowed in :attr argument"
|
||||
:else (when-let [invalid (not-empty (invalid-html-attrs arg-keys))]
|
||||
(str "Unknown HTML attribute(s): " invalid)))]
|
||||
(or (nil? result)
|
||||
{:status (if (or contains-class? contains-style?) :error :warning)
|
||||
:message result}))))))
|
||||
|
||||
(defn goog-date?
|
||||
"Returns true if the passed argument is a valid goog.date.UtcDateTime, otherwise false/error"
|
||||
[arg]
|
||||
(let [arg (deref-or-value-peek arg)]
|
||||
(instance? js/goog.date.UtcDateTime arg)))
|
||||
|
||||
(defn regex?
|
||||
"Returns true if the passed argument is a valid regular expression, otherwise false/error"
|
||||
[arg]
|
||||
(let [arg (deref-or-value-peek arg)]
|
||||
(instance? js/RegExp arg)))
|
||||
|
||||
(defn number-or-string?
|
||||
"Returns true if the passed argument is a number or a string, otherwise false/error"
|
||||
[arg]
|
||||
(let [arg (deref-or-value-peek arg)]
|
||||
(or (number? arg) (string? arg))))
|
||||
|
||||
(defn string-or-atom?
|
||||
"Returns true if the passed argument is a string (or a string within an atom), otherwise false/error"
|
||||
[arg]
|
||||
(string? (deref-or-value-peek arg)))
|
||||
|
||||
(defn set-or-atom?
|
||||
"Returns true if the passed argument is a set (or a set within an atom), otherwise false/error"
|
||||
[arg]
|
||||
(set? (deref-or-value-peek arg)))
|
||||
File diff suppressed because one or more lines are too long
430
resources/public/js/compiled/out/re_com/validate.js
Normal file
430
resources/public/js/compiled/out/re_com/validate.js
Normal file
File diff suppressed because one or more lines are too long
1
resources/public/js/compiled/out/re_com/validate.js.map
Normal file
1
resources/public/js/compiled/out/re_com/validate.js.map
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue