From 651225eb218366c1ad77bd2b89eac0ddb9c6b6e6 Mon Sep 17 00:00:00 2001 From: Diego Tissot Date: Mon, 18 Aug 2025 20:27:26 -0300 Subject: [PATCH 1/7] Parameterize datepicker to allow for sunday-first convention --- src/om_widgets/datepicker.cljs | 55 ++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/src/om_widgets/datepicker.cljs b/src/om_widgets/datepicker.cljs index 19e3cc1..4959ddf 100644 --- a/src/om_widgets/datepicker.cljs +++ b/src/om_widgets/datepicker.cljs @@ -8,6 +8,10 @@ ;; TODO translate (defonce days-short ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"]) +(defonce days-short-sunday-first ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"]) + +(defn- get-weekdays [sunday-first?] + (if sunday-first? days-short-sunday-first days-short)) (defn- build-previous-month-days [date] (let [current-month (time/date-time (time/year date) (time/month date)) @@ -20,6 +24,18 @@ :year (time/year date) :belongs-to-month :previous}) days-to-fill))) +(defn- build-previous-month-days [date sunday-first?] + (let [current-month (time/date-time (time/year date) (time/month date)) + weekday-current-month (time/day-of-week current-month) + previous-month (time/minus current-month (time/months 1)) + last-day (time/number-of-days-in-the-month previous-month) + sunday-first-offset (if sunday-first? 1 0) + days-to-fill (range (inc (- last-day (dec weekday-current-month) sunday-first-offset)) (inc last-day))] + (mapv (fn [d] {:day d + :month (- 1 (time/month date)) + :year (time/year date) + :belongs-to-month :previous}) days-to-fill))) + (defn- build-current-month-days [date] (let [last-day (time/number-of-days-in-the-month date)] (mapv (fn [d] @@ -41,6 +57,19 @@ :year (time/year date) :belongs-to-month :next}) days-to-fill))) +(defn- build-next-month-days [date sunday-first?] + (let [current-month (time/date-time (time/year date) (time/month date)) + last-day-number (time/number-of-days-in-the-month current-month) + last-day (time/date-time (time/year current-month) (time/month current-month) last-day-number) + weekday-last-day (time/day-of-week last-day) + sunday-first-offset (if sunday-first? 1 0) + days-to-fill (range 1 (inc (- 14 weekday-last-day sunday-first-offset)))] + (println "days to fill " (inc (- 14 weekday-last-day))) + (mapv (fn [d] {:day d + :month (+ 1 (time/month date)) + :year (time/year date) + :belongs-to-month :next}) days-to-fill))) + (defn- build-weeks "We build a 7x6 matrix representing the 7 days in a week and 6 weeks in a month view, this view could include days from the @@ -55,11 +84,11 @@ [...] [{:day 1 :month 3 :year 2014 :belongs-to-month :next}] ] " - [date] - (let [previous-days (build-previous-month-days date) - currrent-days (build-current-month-days date) - next-days (build-next-month-days date) - days (into [] (concat previous-days currrent-days next-days))] + [date sunday-first?] + (let [previous-days (build-previous-month-days date sunday-first?) + current-days (build-current-month-days date) + next-days (build-next-month-days date sunday-first?) + days (into [] (concat previous-days current-days next-days))] (take 6 (mapv vec (partition 7 days))))) (defn- day-header [day] @@ -167,13 +196,13 @@ om/IDisplayName (display-name [_] "DatepickerWeeks") om/IRenderState - (render-state [this {:keys [date path onChange] :as state}] + (render-state [this {:keys [date path onChange sunday-first?] :as state}] (apply dom/tbody nil (map (fn [week] (apply dom/tr nil (map (fn [d] (om/build day-component app {:state {:day d :path path :date date :onChange onChange}})) week))) - (build-weeks date)))))) + (build-weeks date sunday-first?)))))) (defn- year-component [app owner] (reify @@ -202,7 +231,7 @@ om/IDisplayName (display-name [_] "DatepickerBody") om/IRenderState - (render-state [this {:keys [path date onChange] :as state}] + (render-state [this {:keys [path date onChange sunday-first?] :as state}] (dom/div #js {:className "datepicker datepicker-days" :style #js {:display "block"}} (dom/table #js {:className "table-condensed"} (dom/thead nil @@ -223,10 +252,10 @@ :onClick (fn [e] (om/set-state! owner :date (time/plus date (time/months 1))))} ">")) (apply dom/tr nil - (om/build-all day-header days-short))) + (om/build-all day-header (get-weekdays sunday-first?)))) ;; datepicker body - (om/build weeks-component app {:state {:path path :date date :onChange onChange}})))))) + (om/build weeks-component app {:state {:path path :date date :onChange onChange :sunday-first? sunday-first?}})))))) (defn datepicker "Datepicker public API @@ -239,11 +268,13 @@ note: we assume today date if the cursor does not have a date " - [app path {:keys [id hidden onChange] :or {hidden true}}] + [app path {:keys [id hidden onChange sunday-first?] :or {hidden true + sunday-first? false}}] (om/build body-component app {:state {:id id :hidden hidden :date (if (instance? js/Date (utils/om-get app [path])) (time/date-time (utils/om-get app [path])) (time/now)) :path path - :onChange onChange}})) + :onChange onChange + :sunday-first? sunday-first?}})) From 0743ac0f08120e841ba9e9e4cf72a9878a3446c8 Mon Sep 17 00:00:00 2001 From: Diego Tissot Date: Mon, 18 Aug 2025 20:28:57 -0300 Subject: [PATCH 2/7] Get rid of previous fns for building previous and next month's days for rendering of datepicker --- src/om_widgets/datepicker.cljs | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/src/om_widgets/datepicker.cljs b/src/om_widgets/datepicker.cljs index 4959ddf..c380ea2 100644 --- a/src/om_widgets/datepicker.cljs +++ b/src/om_widgets/datepicker.cljs @@ -13,17 +13,6 @@ (defn- get-weekdays [sunday-first?] (if sunday-first? days-short-sunday-first days-short)) -(defn- build-previous-month-days [date] - (let [current-month (time/date-time (time/year date) (time/month date)) - weekday-current-month (time/day-of-week current-month) - previous-month (time/minus current-month (time/months 1)) - last-day (time/number-of-days-in-the-month previous-month) - days-to-fill (range (inc (- last-day (dec weekday-current-month))) (inc last-day))] - (mapv (fn [d] {:day d - :month (- 1 (time/month date)) - :year (time/year date) - :belongs-to-month :previous}) days-to-fill))) - (defn- build-previous-month-days [date sunday-first?] (let [current-month (time/date-time (time/year date) (time/month date)) weekday-current-month (time/day-of-week current-month) @@ -45,18 +34,6 @@ :belongs-to-month :current}) (range 1 (inc last-day))))) -(defn- build-next-month-days [date] - (let [current-month (time/date-time (time/year date) (time/month date)) - last-day-number (time/number-of-days-in-the-month current-month) - last-day (time/date-time (time/year current-month) (time/month current-month) last-day-number) - weekday-last-day (time/day-of-week last-day) - weekday-current-month (time/day-of-week current-month) - days-to-fill (range 1 (inc (- 14 weekday-last-day)))] - (mapv (fn [d] {:day d - :month (+ 1 (time/month date)) - :year (time/year date) - :belongs-to-month :next}) days-to-fill))) - (defn- build-next-month-days [date sunday-first?] (let [current-month (time/date-time (time/year date) (time/month date)) last-day-number (time/number-of-days-in-the-month current-month) From 36597020b4f1188bb9bc7f35b3a2ab50208477d9 Mon Sep 17 00:00:00 2001 From: Diego Tissot Date: Mon, 18 Aug 2025 23:47:13 -0300 Subject: [PATCH 3/7] Parameterize textinput so it receives the format with which to show the date --- src/om_widgets/datepicker.cljs | 1 - src/om_widgets/textinput.cljs | 254 ++++++++++++++++++--------------- 2 files changed, 142 insertions(+), 113 deletions(-) diff --git a/src/om_widgets/datepicker.cljs b/src/om_widgets/datepicker.cljs index c380ea2..1178819 100644 --- a/src/om_widgets/datepicker.cljs +++ b/src/om_widgets/datepicker.cljs @@ -41,7 +41,6 @@ weekday-last-day (time/day-of-week last-day) sunday-first-offset (if sunday-first? 1 0) days-to-fill (range 1 (inc (- 14 weekday-last-day sunday-first-offset)))] - (println "days to fill " (inc (- 14 weekday-last-day))) (mapv (fn [d] {:day d :month (+ 1 (time/month date)) :year (time/year date) diff --git a/src/om_widgets/textinput.cljs b/src/om_widgets/textinput.cljs index f52d6d2..47fcf0b 100644 --- a/src/om_widgets/textinput.cljs +++ b/src/om_widgets/textinput.cljs @@ -1,6 +1,7 @@ (ns om-widgets.textinput (:require-macros [pallet.thread-expr :as th]) - (:require [om.core :as om :include-macros true] + (:require [clojure.string :as str] + [om.core :as om :include-macros true] [om.dom :as dom :include-macros true] [om-widgets.utils :as utils] [cljs-time.format :as time-format] @@ -8,21 +9,49 @@ [goog.object :as gobj] [pallet.thread-expr :as th])) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def date-local-mask "00/00/0000") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- date-format->mask + [format] + (when format + (str/replace format #"[YyMmDd]" "0"))) + +(def valid-formats + #{"yyyy-MM-dd" + "dd-MM-yyyy" + "MM-dd-yyyy" + + "yyyy/MM/dd" + "dd/MM/yyyy" + "MM/dd/yyyy" + + "yyyy.MM.dd" + "dd.MM.yyyy" + "MM.dd.yyyy" + + "yyyyMMdd" + "ddMMyyyy" + "MMddyyyy" + }) + +(defn ensure-valid-date-format [date-format] + (when (and date-format + (not (contains? valid-formats date-format))) + (throw (ex-info "Invalid date-format passed in textinput component" + {:date-format date-format})))) + (defn get-browser-locale [] (or (gobj/get js/navigator "language") (first (gobj/get js/navigator "languages")) - "en")) ;; Default to English if no locale is found + "en")) ;; Default to English if no locale is found (defn infer-date-format-pattern [] (let [locale (get-browser-locale) - test-date (js/Date. 2024 10 28) ; Nov 28, 2024 (months are zero-based) + test-date (js/Date. 2024 10 28) ; Nov 28, 2024 (months are zero-based) formatter (js/Intl.DateTimeFormat. locale) formatted-date (.format formatter test-date)] (-> formatted-date @@ -41,20 +70,20 @@ (time-format/unparse (time-format/formatter fmt) dt))) (defn- convert-input - [input-type value] + [input-type value {:keys [date-format]}] (condp = input-type "date" (try - (string-from-date value (infer-date-format-pattern)) + (string-from-date value (or date-format (infer-date-format-pattern))) (catch js/Error e ;; assume empty string for unhandled values (str value))) value)) (defn- convert-output - [output-type value] + [output-type value {:keys [date-format]}] (condp = output-type "date" (try - (date-from-localstring value (infer-date-format-pattern)) + (date-from-localstring value (or date-format (infer-date-format-pattern))) (catch js/Error e value)) "numeric" (let [f (js/parseFloat value)] @@ -91,30 +120,30 @@ (defn- special-key? [char-code] - (contains? #{9 ;; tab - 13 ;; enter - 16 ;; shift - 17 ;; ctrl - 18 ;; alt - 20 ;; caps lock - 27 ;; escape - 33 ;; page up - 34 ;; page down - 35 ;; home - 36 ;; end - 37 ;; left arrow - 38 ;; up arrow - 39 ;; right arrow - 40 ;; down arrow - 45 ;; insert - 144} ;; num lock; + (contains? #{9 ;; tab + 13 ;; enter + 16 ;; shift + 17 ;; ctrl + 18 ;; alt + 20 ;; caps lock + 27 ;; escape + 33 ;; page up + 34 ;; page down + 35 ;; home + 36 ;; end + 37 ;; left arrow + 38 ;; up arrow + 39 ;; right arrow + 40 ;; down arrow + 45 ;; insert + 144} ;; num lock; char-code)) -(defn- get-selection-start ;; assume modern browser IE9 and up +(defn- get-selection-start ;; assume modern browser IE9 and up [control] (.-selectionStart control)) -(defn- get-selection-end ;; assume modern browser IE9 and up +(defn- get-selection-end ;; assume modern browser IE9 and up [control] (.-selectionEnd control)) @@ -132,11 +161,11 @@ :mask)) (defn- update-target - [target owner {:keys [input-format path onChange private-state] :as state} bInternal] + [target owner {:keys [input-format path onChange private-state date-format] :as state} bInternal] (when (and target (not= 0 (:cbtimeout @private-state))) (let [dom-node (:dom-node @private-state) - value (convert-output input-format (.-value dom-node))] + value (convert-output input-format (.-value dom-node) {:date-format date-format})] (do (.clearTimeout js/window (:cbtimeout @private-state)) (swap! private-state assoc :cbtimeout 0 :prev-value value) @@ -184,7 +213,7 @@ (when-not (string? (nth mask-vector pos)) (let [new-entered-values (replace-item-at-pos entered-values pos \_)] (swap! private-state assoc :entered-values new-entered-values) - (set! (.-value dom-node) (apply str new-entered-values)))) + (set! (.-value dom-node) (apply str new-entered-values)))) (set-caret-pos dom-node pos)))) ;; delete 46 (when (< sel-start (count mask-vector)) @@ -192,12 +221,12 @@ (when-not (string? (nth mask-vector sel-start)) (let [new-entered-values (replace-item-at-pos entered-values sel-start \_)] (swap! private-state assoc :entered-values new-entered-values) - (set! (.-value dom-node) (apply str new-entered-values))))) + (set! (.-value dom-node) (apply str new-entered-values))))) (set-caret-pos dom-node (inc sel-start)))) ;; Selection (let [new-entered-values (erase-selection mask-vector entered-values sel-start sel-end)] (swap! private-state assoc :entered-values new-entered-values) - (set! (.-value dom-node) (apply str new-entered-values)) + (set! (.-value dom-node) (apply str new-entered-values)) (set-caret-pos dom-node sel-start))) false) true))) @@ -330,8 +359,8 @@ (recur (next (next mv)) (next cv) (conj r m c))) (recur (next mv) (next cv) (conj r (if (re-matches m c) c \_)))) r))) - (:mask-vector @private-state) - (vec (convert-input (:input-format state) value))) + (:mask-vector @private-state) + (vec (convert-input (:input-format state) value state))) prev-value (:prev-value @private-state) new-value (apply str entered-values) dom-node (:dom-node @private-state)] @@ -339,12 +368,12 @@ (do (swap! private-state assoc :entered-values entered-values :prev-value new-value) - (set! (.-value dom-node) new-value))))) + (set! (.-value dom-node) new-value))))) (defmethod applymask! :default [target owner state value] - (when-let [dom-node (:dom-node @(:private-state state))] - (when-not (= value (:prev-value @(:private-state state))) + (when-let [dom-node (:dom-node @(:private-state state))] + (when-not (= value (:prev-value @(:private-state state))) (set! (.-value dom-node) value)))) ;; --------------------------------------------------------------------- @@ -412,81 +441,82 @@ ((if (not (:multiline state)) dom/input dom/textarea) - (clj->js (-> {:id (:id state) - :name (:id state) - :hidden (:hidden state) - :className (clojure.string/join " " ["om-widgets-input-text" (:input-class state)]) - :autoComplete (or (:auto-complete state) - "off") - :readOnly (:read-only state) - :onKeyDown #(if (false? (handlekeydown target owner state %)) - (.preventDefault %) - nil) - :onKeyUp #(if (false? (handlekeyup target owner state %)) - (.preventDefault %) - nil) - :onInput #(if (false? (handle-on-input target owner state %)) - (.preventDefault %) - nil) - :onKeyPress #(do - (when (= "Enter" (.-key %)) - (do - (when (and (:flush-on-enter state) - (not (:multiline state))) - (update-target target owner state true)) - (when (:onEnter state) - ((:onEnter state) %)))) - (when (:onKeyPress state) - ((:onKeyPress state) %)) - (if (false? (handlekeypress target owner state %)) - (.preventDefault %) - nil)) - :autoFocus (:autofocus state) - :tabIndex (:tabIndex state) - :onBlur (fn [e] - (update-target target owner state true) - (when (:onBlur state) - ((:onBlur state))) - nil) - :onPaste #(if (false? (handlepaste target owner state %)) - (.preventDefault %) - nil) - :placeholder (:placeholder state) - :disabled (:disabled state) - ;:typing-timeout (:typing-timeout state) - :type (condp = (:input-format state) - "password" "password" - "numeric" "number" - "text") - :style {:textAlign (:align state)}} - (th/when-> (:step state) - (merge {:step (:step state)})) - (th/when-> (:pattern state) - (merge {:pattern (:pattern state)})) - (th/when-> (:min state) - (merge {:min (:min state)})) - (th/when-> (:max state) - (merge {:max (:max state)})) - (th/when-> (:resize state) - (merge {:resize (name (:resize state))})))))))) + (clj->js (-> {:id (:id state) + :name (:id state) + :hidden (:hidden state) + :className (clojure.string/join " " ["om-widgets-input-text" (:input-class state)]) + :autoComplete (or (:auto-complete state) + "off") + :readOnly (:read-only state) + :onKeyDown #(if (false? (handlekeydown target owner state %)) + (.preventDefault %) + nil) + :onKeyUp #(if (false? (handlekeyup target owner state %)) + (.preventDefault %) + nil) + :onInput #(if (false? (handle-on-input target owner state %)) + (.preventDefault %) + nil) + :onKeyPress #(do + (when (= "Enter" (.-key %)) + (do + (when (and (:flush-on-enter state) + (not (:multiline state))) + (update-target target owner state true)) + (when (:onEnter state) + ((:onEnter state) %)))) + (when (:onKeyPress state) + ((:onKeyPress state) %)) + (if (false? (handlekeypress target owner state %)) + (.preventDefault %) + nil)) + :autoFocus (:autofocus state) + :tabIndex (:tabIndex state) + :onBlur (fn [e] + (update-target target owner state true) + (when (:onBlur state) + ((:onBlur state))) + nil) + :onPaste #(if (false? (handlepaste target owner state %)) + (.preventDefault %) + nil) + :placeholder (:placeholder state) + :disabled (:disabled state) + ;:typing-timeout (:typing-timeout state) + :type (condp = (:input-format state) + "password" "password" + "numeric" "number" + "text") + :style {:textAlign (:align state)}} + (th/when-> (:step state) + (merge {:step (:step state)})) + (th/when-> (:pattern state) + (merge {:pattern (:pattern state)})) + (th/when-> (:min state) + (merge {:min (:min state)})) + (th/when-> (:max state) + (merge {:max (:max state)})) + (th/when-> (:resize state) + (merge {:resize (name (:resize state))})))))))) (defn textinput - [target path {:keys [input-class input-format align] :as opts - :or {input-class ""}}] + [target path {:keys [input-class input-format align read-only date-format] :as opts + :or {input-class "" + read-only false}}] + (ensure-valid-date-format date-format) + (om/build create-textinput target - {:state (-> opts - (cond-> (nil? (:read-only opts)) - (assoc :read-only false)) - (merge {:path path - :input-mask (cond - (= input-format "numeric") "numeric" - (= input-format "integer") "numeric" - (= input-format "currency") "numeric" - (= input-format "date") date-local-mask - :else input-format) - :currency (if (= input-format "currency") true false) - :align (or align - (cond (= input-format "numeric") "right" - (= input-format "integer") "right" - (= input-format "currency") "right" - :else "left"))}))})) + {:state (merge opts {:path path + :date-format date-format + :input-mask (cond + (= input-format "numeric") "numeric" + (= input-format "integer") "numeric" + (= input-format "currency") "numeric" + (= input-format "date") (or (date-format->mask date-format) date-local-mask) + :else input-format) + :currency (if (= input-format "currency") true false) + :align (or align + (cond (= input-format "numeric") "right" + (= input-format "integer") "right" + (= input-format "currency") "right" + :else "left"))})})) From 311296d6827aef2af936386429bf569eb0f4a36e Mon Sep 17 00:00:00 2001 From: Diego Tissot Date: Tue, 19 Aug 2025 09:35:02 -0300 Subject: [PATCH 4/7] Fix port nr in readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e579e40..929bf51 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ just run: ```bash lein figwheel basic ``` -inside the repo and this will start a webserver serving the sample files, point your browser to: `http://localhost:3449/examples/basic/index.html` +inside the repo and this will start a webserver serving the sample files, point your browser to: `http://localhost:3450/examples/basic/index.html` and you are ready to go. ## License From 61e77b199d35285ac49384709f5b6a5515673ee9 Mon Sep 17 00:00:00 2001 From: Diego Tissot Date: Tue, 19 Aug 2025 09:53:44 -0300 Subject: [PATCH 5/7] Extend datepicker demo to show formatting and Sunday-first convention --- src/examples/basic/datepicker_example.cljs | 75 +++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/examples/basic/datepicker_example.cljs b/src/examples/basic/datepicker_example.cljs index e7aa12f..b1691ea 100644 --- a/src/examples/basic/datepicker_example.cljs +++ b/src/examples/basic/datepicker_example.cljs @@ -12,10 +12,11 @@ (render-state [this state] (html [:div.panel.panel-default - [:div.panel-heading "Datepciker"] + [:div.panel-heading "Datepicker"] [:div.panel-body [:div.row [:div.col-lg-6 + [:label "Datepicker placement"] [:div.well [:label "Input Group - left side"] (w/popover @@ -46,6 +47,7 @@ (w/datepicker app :input-group-right)) {:for "btn-cal-right"})] + [:label "Actions when a date's been selected"] [:div.well [:label "Input Group - Close when selecting day"] (w/popover @@ -63,6 +65,75 @@ [:div.col-lg-6 [:div.well - (w/datepicker app :inline) + [:label "Monday-first:"] + (w/datepicker app :inline)]]] + + [:div.row + [:div.col-lg-6 + [:label "Formatting and separators"] + [:div.well + [:label "YYYY/MM/DD"] + (w/popover + (fn [show] + [:div.input-group + (w/textinput app :input-group-right {:input-class "form-control" + :input-format "date" + :date-format "yyyy/MM/dd"}) + [:span.input-group-btn + [:button.btn.btn-primary {:id "btn-cal-right" :onClick show} + [:span.glyphicon.glyphicon-calendar]]]]) + (fn [close] + (w/datepicker app :input-group-right)) + {:for "btn-cal-right"})] + + [:div.well + [:label "YYYY-MM-DD"] + (w/popover + (fn [show] + [:div.input-group + (w/textinput app :input-group-right {:input-class "form-control" + :input-format "date" + :date-format "yyyy-MM-dd"}) + [:span.input-group-btn + [:button.btn.btn-primary {:id "btn-cal-right" :onClick show} + [:span.glyphicon.glyphicon-calendar]]]]) + (fn [close] + (w/datepicker app :input-group-right)) + {:for "btn-cal-right"})] + + [:div.well + [:label "MM.DD.YYYY"] + (w/popover + (fn [show] + [:div.input-group + (w/textinput app :input-group-right {:input-class "form-control" + :input-format "date" + :date-format "MM.dd.yyyy"}) + [:span.input-group-btn + [:button.btn.btn-primary {:id "btn-cal-right" :onClick show} + [:span.glyphicon.glyphicon-calendar]]]]) + (fn [close] + (w/datepicker app :input-group-right)) + {:for "btn-cal-right"})] + + [:div.well + [:label "YYYYMMDD"] + (w/popover + (fn [show] + [:div.input-group + (w/textinput app :input-group-close-on-change {:input-class "form-control" + :input-format "date" + :date-format "yyyyMMdd"}) + [:span.input-group-btn + [:button.btn.btn-primary {:id "btn-cal-close-on-select" :onClick show} + [:span.glyphicon.glyphicon-calendar]]]]) + (fn [close] + (w/datepicker app :input-group-close-on-change {:onChange close})) + {:for "btn-cal-close-on-select"})]] + + [:div.col-lg-6 + [:div.well + [:label "Sunday-first:"] + (w/datepicker app :inline {:sunday-first? true}) ] [:label (str (:inline app))]]]]])))) \ No newline at end of file From 0b0f609c4749545f5f5204ca6de3978d2ba2f368 Mon Sep 17 00:00:00 2001 From: Diego Tissot Date: Tue, 19 Aug 2025 10:06:49 -0300 Subject: [PATCH 6/7] Uncouple state in granular textinput date formatting demo --- src/examples/basic/datepicker_example.cljs | 16 ++++++++-------- src/examples/basic/state_example.cljs | 6 +++++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/examples/basic/datepicker_example.cljs b/src/examples/basic/datepicker_example.cljs index b1691ea..93d5565 100644 --- a/src/examples/basic/datepicker_example.cljs +++ b/src/examples/basic/datepicker_example.cljs @@ -76,14 +76,14 @@ (w/popover (fn [show] [:div.input-group - (w/textinput app :input-group-right {:input-class "form-control" + (w/textinput app :slash-separator {:input-class "form-control" :input-format "date" :date-format "yyyy/MM/dd"}) [:span.input-group-btn [:button.btn.btn-primary {:id "btn-cal-right" :onClick show} [:span.glyphicon.glyphicon-calendar]]]]) (fn [close] - (w/datepicker app :input-group-right)) + (w/datepicker app :slash-separator)) {:for "btn-cal-right"})] [:div.well @@ -91,14 +91,14 @@ (w/popover (fn [show] [:div.input-group - (w/textinput app :input-group-right {:input-class "form-control" + (w/textinput app :hyphen-separator {:input-class "form-control" :input-format "date" :date-format "yyyy-MM-dd"}) [:span.input-group-btn [:button.btn.btn-primary {:id "btn-cal-right" :onClick show} [:span.glyphicon.glyphicon-calendar]]]]) (fn [close] - (w/datepicker app :input-group-right)) + (w/datepicker app :hyphen-separator)) {:for "btn-cal-right"})] [:div.well @@ -106,14 +106,14 @@ (w/popover (fn [show] [:div.input-group - (w/textinput app :input-group-right {:input-class "form-control" + (w/textinput app :dot-separator {:input-class "form-control" :input-format "date" :date-format "MM.dd.yyyy"}) [:span.input-group-btn [:button.btn.btn-primary {:id "btn-cal-right" :onClick show} [:span.glyphicon.glyphicon-calendar]]]]) (fn [close] - (w/datepicker app :input-group-right)) + (w/datepicker app :dot-separator)) {:for "btn-cal-right"})] [:div.well @@ -121,14 +121,14 @@ (w/popover (fn [show] [:div.input-group - (w/textinput app :input-group-close-on-change {:input-class "form-control" + (w/textinput app :no-separator {:input-class "form-control" :input-format "date" :date-format "yyyyMMdd"}) [:span.input-group-btn [:button.btn.btn-primary {:id "btn-cal-close-on-select" :onClick show} [:span.glyphicon.glyphicon-calendar]]]]) (fn [close] - (w/datepicker app :input-group-close-on-change {:onChange close})) + (w/datepicker app :no-separator)) {:for "btn-cal-close-on-select"})]] [:div.col-lg-6 diff --git a/src/examples/basic/state_example.cljs b/src/examples/basic/state_example.cljs index e195399..10b9f2f 100644 --- a/src/examples/basic/state_example.cljs +++ b/src/examples/basic/state_example.cljs @@ -72,7 +72,11 @@ :datepicker {:inline #inst "1991-01-25" :input-group-left #inst "1991-01-25" :input-group-right #inst "1991-01-25" - :input-group-close-on-change #inst "1991-01-25"} + :input-group-close-on-change #inst "1991-01-25" + :slash-separator #inst "1991-01-25" + :hyphen-separator #inst "1991-01-25" + :dot-separator #inst "1991-01-25" + :no-separator #inst "1991-01-25"} :sex :male :tab {:selected-tab :inbox} :form {:name "" From 9770bc97e0c8e35cd3cc487f95cd2f828aa31919 Mon Sep 17 00:00:00 2001 From: Diego Tissot Date: Tue, 19 Aug 2025 10:17:49 -0300 Subject: [PATCH 7/7] Fix negative month bug in generation of previous month's day --- src/om_widgets/datepicker.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/om_widgets/datepicker.cljs b/src/om_widgets/datepicker.cljs index 1178819..0a634bd 100644 --- a/src/om_widgets/datepicker.cljs +++ b/src/om_widgets/datepicker.cljs @@ -21,7 +21,7 @@ sunday-first-offset (if sunday-first? 1 0) days-to-fill (range (inc (- last-day (dec weekday-current-month) sunday-first-offset)) (inc last-day))] (mapv (fn [d] {:day d - :month (- 1 (time/month date)) + :month (- (time/month date) 1) :year (time/year date) :belongs-to-month :previous}) days-to-fill)))