From 74ec674ab20a85f3de07c4384a44feff62084ce7 Mon Sep 17 00:00:00 2001 From: Agustin Cornu Date: Tue, 17 Mar 2020 22:52:20 -0300 Subject: [PATCH 1/4] Allow selecting month and year separately --- src/DatePicker.elm | 297 ++++++++++++++++++++++++++++++++------------- 1 file changed, 215 insertions(+), 82 deletions(-) diff --git a/src/DatePicker.elm b/src/DatePicker.elm index 515fa90..2f3fcf4 100644 --- a/src/DatePicker.elm +++ b/src/DatePicker.elm @@ -27,7 +27,7 @@ import Html.Events exposing (on, onBlur, onClick, onFocus, onInput, targetValue) import Html.Keyed import Json.Decode as Json import Task -import Time exposing (Weekday(..)) +import Time exposing (Month(..), Weekday(..)) {-| An opaque type representing messages that are passed inside the DatePicker. @@ -42,6 +42,8 @@ type Msg | Blur | MouseDown | MouseUp + | ChangePicking Pickable + | ChangeFocusAndPicking Date Pickable {-| The type of date picker settings. @@ -66,12 +68,19 @@ type alias Settings = } +type Pickable + = Day + | Month + | Year + + type alias Model = { open : Bool , forceOpen : Bool , focused : Maybe Date -- date currently center-focused by picker, but not necessarily chosen , inputText : Maybe String , today : Date -- actual, current day as far as we know + , picking : Pickable } @@ -203,6 +212,7 @@ init = , focused = Just initDate , inputText = Nothing , today = initDate + , picking = Day } , Task.perform CurrentDate Date.today ) @@ -222,6 +232,7 @@ initFromDate date = , focused = Just date , inputText = Nothing , today = date + , picking = Day } @@ -239,6 +250,7 @@ initFromDates today date = , focused = date , inputText = Nothing , today = today + , picking = Day } @@ -331,7 +343,7 @@ type InputError date. -} update : Settings -> Msg -> DatePicker -> ( DatePicker, DateEvent ) -update settings msg (DatePicker ({ forceOpen, focused } as model)) = +update settings msg (DatePicker ({ forceOpen } as model)) = case msg of CurrentDate date -> ( DatePicker { model | focused = Just date, today = date }, None ) @@ -339,6 +351,12 @@ update settings msg (DatePicker ({ forceOpen, focused } as model)) = ChangeFocus date -> ( DatePicker { model | focused = Just date }, None ) + ChangeFocusAndPicking date pickable -> + ( DatePicker { model | focused = Just date, picking = pickable }, None ) + + ChangePicking pickable -> + ( DatePicker { model | picking = pickable }, None ) + Pick date -> ( DatePicker <| { model @@ -353,51 +371,50 @@ update settings msg (DatePicker ({ forceOpen, focused } as model)) = ( DatePicker { model | inputText = Just text }, None ) SubmitText -> - case forceOpen of - True -> - ( DatePicker model, None ) - - False -> - let - dateEvent = - case model.inputText of - Nothing -> - FailedInput EmptyString - - Just "" -> - FailedInput EmptyString - - Just rawInput -> - case settings.parser rawInput of - Ok date -> - if settings.isDisabled date then - FailedInput <| Disabled date - - else - Picked date - - Err e -> - FailedInput <| Invalid e - in - ( DatePicker - { model - | inputText = - case dateEvent of - Picked _ -> - Nothing - - _ -> - model.inputText - , focused = - case dateEvent of - Picked date -> - Just date - - _ -> - model.focused - } - , dateEvent - ) + if forceOpen then + ( DatePicker model, None ) + + else + let + dateEvent = + case model.inputText of + Nothing -> + FailedInput EmptyString + + Just "" -> + FailedInput EmptyString + + Just rawInput -> + case settings.parser rawInput of + Ok date -> + if settings.isDisabled date then + FailedInput <| Disabled date + + else + Picked date + + Err e -> + FailedInput <| Invalid e + in + ( DatePicker + { model + | inputText = + case dateEvent of + Picked _ -> + Nothing + + _ -> + model.inputText + , focused = + case dateEvent of + Picked date -> + Just date + + _ -> + model.focused + } + , dateEvent + ) Focus -> ( DatePicker { model | open = True, forceOpen = False }, None ) @@ -461,7 +478,7 @@ close = {-| The date picker view. The Date passed is whatever date it should treat as selected. -} view : Maybe Date -> Settings -> DatePicker -> Html Msg -view pickedDate settings (DatePicker (model as datepicker)) = +view pickedDate settings (DatePicker model) = let potentialInputId = settings.inputId @@ -469,8 +486,8 @@ view pickedDate settings (DatePicker (model as datepicker)) = |> (List.singleton >> List.filterMap identity) inputClasses = - [ ( settings.classNamespace ++ "input", True ) ] - ++ settings.inputClassList + ( settings.classNamespace ++ "input", True ) + :: settings.inputClassList inputCommon xs = input @@ -515,7 +532,7 @@ view pickedDate settings (DatePicker (model as datepicker)) = datePicker : Maybe Date -> Settings -> Model -> Html Msg -datePicker pickedDate settings ({ focused, today } as model) = +datePicker pickedDate settings { focused, today, picking } = let currentDate = focused |> maybeOr pickedDate |> Maybe.withDefault today @@ -587,7 +604,7 @@ datePicker pickedDate settings ({ focused, today } as model) = Between from_ to_ -> ( front (from_ - 1), back (to_ + 1) ) - MoreOrLess y -> + MoreOrLess _ -> ( [], [] ) Off -> @@ -610,37 +627,144 @@ datePicker pickedDate settings ({ focused, today } as model) = , Html.Events.stopPropagationOn "mousedown" <| Json.succeed ( MouseDown, True ) , Html.Events.stopPropagationOn "mouseup" <| Json.succeed ( MouseUp, True ) ] - [ div [ dpClass "picker-header" ] - [ div [ dpClass "prev-container" ] - [ arrow "prev" (ChangeFocus (Date.add Date.Months -1 currentDate)) ] - , div [ dpClass "month-container" ] - [ span [ dpClass "month" ] - [ text <| settings.monthFormatter <| month currentMonth ] - , span [ dpClass "year" ] - [ if not (yearRangeActive settings.changeYear) then - text <| settings.yearFormatter <| year currentMonth - - else - Html.Keyed.node "span" [] [ ( String.fromInt (year currentMonth), dropdownYear ) ] + <| + case picking of + Day -> + [ div [ dpClass "picker-header" ] + [ div [ dpClass "prev-container" ] + [ arrow "prev" (ChangeFocus (Date.add Date.Months -1 currentDate)) ] + , div [ dpClass "month-container" ] + [ button [ dpClass "month", type_ "button", onClick <| ChangePicking Month ] + [ text <| settings.monthFormatter <| month currentMonth ] + , button [ dpClass "year", type_ "button", onClick <| ChangePicking Year ] + [ if not (yearRangeActive settings.changeYear) then + text <| settings.yearFormatter <| year currentMonth + + else + Html.Keyed.node "span" [] [ ( String.fromInt (year currentMonth), dropdownYear ) ] + ] + ] + , div [ dpClass "next-container" ] + [ arrow "next" (ChangeFocus (Date.add Date.Months 1 currentDate)) ] + ] + , table [ dpClass "table" ] + [ thead [ dpClass "weekdays" ] + [ tr [] + ([ Mon, Tue, Wed, Thu, Fri, Sat, Sun ] + |> List.repeat 2 + |> List.concat + |> List.drop firstDayOffset + |> List.take 7 + |> List.map (\d -> td [ dpClass "dow" ] [ text <| settings.dayFormatter d ]) + ) + ] + , tbody [ dpClass "days" ] dayList ] ] - , div [ dpClass "next-container" ] - [ arrow "next" (ChangeFocus (Date.add Date.Months 1 currentDate)) ] - ] - , table [ dpClass "table" ] - [ thead [ dpClass "weekdays" ] - [ tr [] - ([ Mon, Tue, Wed, Thu, Fri, Sat, Sun ] - |> List.repeat 2 - |> List.concat - |> List.drop firstDayOffset - |> List.take 7 - |> List.map (\d -> td [ dpClass "dow" ] [ text <| settings.dayFormatter d ]) - ) + + Month -> + [ div [ dpClass "picker-header" ] + [ div [ dpClass "prev-container" ] + [ arrow "prev" (ChangeFocus (Date.add Date.Years -1 currentDate)) ] + , div [ dpClass "month-container" ] + [ button [ dpClass "year", type_ "button", onClick <| ChangePicking Year ] + [ if not (yearRangeActive settings.changeYear) then + text <| settings.yearFormatter <| year currentMonth + + else + Html.Keyed.node "span" [] [ ( String.fromInt (year currentMonth), dropdownYear ) ] + ] + ] + , div [ dpClass "next-container" ] + [ arrow "next" (ChangeFocus (Date.add Date.Years 1 currentDate)) ] + ] + , table [ dpClass "table" ] + [ tbody [ dpClass "months" ] <| monthList currentDate settings + ] ] - , tbody [ dpClass "days" ] dayList - ] + + Year -> + [ div [ dpClass "picker-header" ] + [ div [ dpClass "prev-container" ] + [ arrow "prev" (ChangeFocus (Date.add Date.Years -12 currentDate)) ] + , div [ dpClass "month-container" ] + [ span [ dpClass "year" ] + [ if not (yearRangeActive settings.changeYear) then + text <| settings.yearFormatter <| year currentMonth + + else + Html.Keyed.node "span" [] [ ( String.fromInt (year currentMonth), dropdownYear ) ] + ] + ] + , div [ dpClass "next-container" ] + [ arrow "next" (ChangeFocus (Date.add Date.Years 12 currentDate)) ] + ] + , table [ dpClass "table" ] + [ tbody [ dpClass "years" ] <| yearList currentDate settings + ] + ] + + +monthList : Date -> Settings -> List (Html Msg) +monthList focused settings = + let + months = + [ Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec ] + + splitMonths = + split 3 months + in + splitMonths + |> List.map (\monthsRow -> tr [] <| List.map (viewMonth focused settings) monthsRow) + + +yearList : Date -> Settings -> List (Html Msg) +yearList focused settings = + let + focusedYear = + Date.year focused + + years = + List.range (focusedYear - 1) (focusedYear + 10) + + splitYears = + split 3 years + in + splitYears |> List.map (\yearsRow -> tr [] <| List.map (viewYear focused settings) yearsRow) + + +viewMonth : Date -> Settings -> Month -> Html Msg +viewMonth focused settings month = + let + year = + Date.year focused + + day = + Date.day focused + in + td + [ mkClassList settings + [ ( "day", True ) ] + , onClick <| ChangeFocusAndPicking (Date.fromCalendarDate year month day) Day + ] + [ settings.cellFormatter <| settings.monthFormatter month ] + + +viewYear : Date -> Settings -> Int -> Html Msg +viewYear focused settings year = + let + month = + Date.month focused + + day = + Date.day focused + in + td + [ mkClassList settings + [ ( "day", True ) ] + , onClick <| ChangeFocusAndPicking (Date.fromCalendarDate year month day) Month ] + [ settings.cellFormatter <| settings.yearFormatter year ] viewDay : Settings -> (Date -> Bool) -> (Date -> Bool) -> (Date -> Bool) -> Date -> Html Msg @@ -660,15 +784,14 @@ viewDay settings picked isOtherMonth isToday d = [] in td - ([ classList + (classList [ ( "day", True ) , ( "disabled", disabled ) , ( "picked", picked d ) , ( "today", isToday d ) , ( "other-month", isOtherMonth d ) ] - ] - ++ props + :: props ) [ settings.cellFormatter <| String.fromInt <| Date.day d ] @@ -713,3 +836,13 @@ maybeOr lhs rhs = Nothing -> lhs + + +split : Int -> List a -> List (List a) +split into list = + case List.take into list of + [] -> + [] + + head -> + head :: split into (List.drop into list) From b6dfbbfea8a57ad037882bf8aee5ebf6308fbbf7 Mon Sep 17 00:00:00 2001 From: Agustin Cornu Date: Wed, 11 Nov 2020 19:33:24 -0300 Subject: [PATCH 2/4] Select typed date. --- elm.json | 6 +++--- src/DatePicker.elm | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/elm.json b/elm.json index b58c8b8..5a2245f 100644 --- a/elm.json +++ b/elm.json @@ -1,9 +1,9 @@ { "type": "package", - "name": "CurrySoftware/elm-datepicker", + "name": "agustinrhcp/elm-datepicker", "summary": "A reusable date picker component", "license": "BSD-3-Clause", - "version": "4.0.0", + "version": "1.0.1", "exposed-modules": [ "DatePicker" ], @@ -17,4 +17,4 @@ "justinmimbs/date": "2.0.0 <= v < 4.0.0" }, "test-dependencies": {} -} \ No newline at end of file +} diff --git a/src/DatePicker.elm b/src/DatePicker.elm index 2f3fcf4..2a8459e 100644 --- a/src/DatePicker.elm +++ b/src/DatePicker.elm @@ -44,6 +44,7 @@ type Msg | MouseUp | ChangePicking Pickable | ChangeFocusAndPicking Date Pickable + | KeyDown Int {-| The type of date picker settings. @@ -368,7 +369,14 @@ update settings msg (DatePicker ({ forceOpen } as model)) = ) Text text -> - ( DatePicker { model | inputText = Just text }, None ) + case settings.parser text of + Ok date -> + ( DatePicker { model | inputText = Just text, focused = Just date, open = True } + , Picked date + ) + + Err _ -> + ( DatePicker { model | inputText = Just text, open = True }, None ) SubmitText -> if forceOpen then @@ -428,6 +436,14 @@ update settings msg (DatePicker ({ forceOpen } as model)) = MouseUp -> ( DatePicker { model | forceOpen = False }, None ) + KeyDown keyCode -> + case keyCode of + 27 -> + ( DatePicker { model | open = False }, None ) + + _ -> + ( DatePicker model, None ) + {-| Generate a message that will act as if the user has chosen a certain date, so you can call `update` on the model yourself. @@ -499,6 +515,7 @@ view pickedDate settings (DatePicker model) = , onBlur Blur , onClick Focus , onFocus Focus + , on "keydown" (Json.map KeyDown Html.Events.keyCode) ] ++ settings.inputAttributes ++ potentialInputId From f5723c91213754bdad646948059b956477756bff Mon Sep 17 00:00:00 2001 From: elm-review-bot <80852742+elm-review-bot@users.noreply.github.com> Date: Tue, 20 Apr 2021 22:49:59 +0200 Subject: [PATCH 3/4] Remove unused dependencies --- elm.json | 1 - 1 file changed, 1 deletion(-) diff --git a/elm.json b/elm.json index 5a2245f..f34595b 100644 --- a/elm.json +++ b/elm.json @@ -9,7 +9,6 @@ ], "elm-version": "0.19.0 <= v < 0.20.0", "dependencies": { - "elm/browser": "1.0.0 <= v < 2.0.0", "elm/core": "1.0.0 <= v < 2.0.0", "elm/html": "1.0.0 <= v < 2.0.0", "elm/json": "1.0.0 <= v < 2.0.0", From baff23f3150e43b585b56f16d928be7912784830 Mon Sep 17 00:00:00 2001 From: elm-review-bot <80852742+elm-review-bot@users.noreply.github.com> Date: Tue, 20 Apr 2021 23:08:16 +0200 Subject: [PATCH 4/4] Remove unused dependencies --- elm.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elm.json b/elm.json index f34595b..f0a1a18 100644 --- a/elm.json +++ b/elm.json @@ -1,9 +1,9 @@ { "type": "package", - "name": "agustinrhcp/elm-datepicker", + "name": "CurrySoftware/elm-datepicker", "summary": "A reusable date picker component", "license": "BSD-3-Clause", - "version": "1.0.1", + "version": "4.0.0", "exposed-modules": [ "DatePicker" ],