diff --git a/examples/src/DurationDatePickerExample.elm b/examples/src/DurationDatePickerExample.elm index c7ac7a8..fa2d862 100644 --- a/examples/src/DurationDatePickerExample.elm +++ b/examples/src/DurationDatePickerExample.elm @@ -124,7 +124,7 @@ view model = , div [ style "margin-bottom" "1rem" ] [ div [ style "margin-bottom" "1rem" ] [ text "This is a duration picker" ] - , div [ style "margin-bottom" "1rem", style "position" "relative"] + , div [ style "margin-bottom" "1rem", style "position" "relative" ] [ button [ id "my-button", onClick <| OpenPicker ] [ text "Picker" ] , DurationDatePicker.view (userDefinedDatePickerSettings model.zone model.currentTime) model.picker diff --git a/examples/src/ModalPickerExample.elm b/examples/src/ModalPickerExample.elm index 9c2a213..28d1d02 100644 --- a/examples/src/ModalPickerExample.elm +++ b/examples/src/ModalPickerExample.elm @@ -42,7 +42,7 @@ update msg model = OpenPicker -> let ( newPicker, cmd ) = - SingleDatePicker.openPickerOutsideHierarchy "my-button" pickerSettings model.currentTime model.pickedTime model.picker + SingleDatePicker.openPicker "my-button" pickerSettings model.currentTime model.pickedTime model.picker in ( { model | picker = newPicker }, cmd ) @@ -60,10 +60,14 @@ update msg model = ( { model | currentTime = newTime }, Cmd.none ) OnViewportChange -> - ( model, SingleDatePicker.updatePickerPosition model.picker ) + let + ( newPicker, cmd ) = + SingleDatePicker.updatePickerPosition model.picker + in + ( { model | picker = newPicker }, cmd ) ToggleModal -> - ( { model | modalOpen = not model.modalOpen }, SingleDatePicker.updatePickerPosition model.picker ) + ( { model | modalOpen = not model.modalOpen }, Cmd.none ) NoOp -> ( model, Cmd.none ) @@ -150,10 +154,10 @@ viewModal model = [ div [ style "padding" "3rem" ] -- [ button [ id "my-button", onClick OpenPicker] [ text "Open Picker" ] -- , SingleDatePicker.view - -- (userDefinedDatePickerSettings model.zone model.currentTime) + -- (userDefinedDatePickerSettings model.zone model.currentTime) -- model.picker - [ SingleDatePicker.viewDateInput [ id "my-button", onClick <| OpenPicker ] - (userDefinedDatePickerSettings model.zone model.currentTime) + [ SingleDatePicker.viewDateInput [ id "my-button", onClick <| OpenPicker ] + (userDefinedDatePickerSettings model.zone model.currentTime) model.currentTime model.pickedTime model.picker diff --git a/examples/src/SingleDatePickerExample.elm b/examples/src/SingleDatePickerExample.elm index 6b05a46..a1481f1 100644 --- a/examples/src/SingleDatePickerExample.elm +++ b/examples/src/SingleDatePickerExample.elm @@ -20,8 +20,9 @@ import Utilities exposing (adjustAllowedTimesOfDayToClientZone, isDateBeforeToda type Msg - = OpenPicker - | UpdatePicker SingleDatePicker.Msg + = OpenDetachedPicker String + | UpdateDateInputPicker SingleDatePicker.Msg + | UpdateDetachedPicker SingleDatePicker.Msg | AdjustTimeZone Zone | Tick Posix @@ -29,23 +30,40 @@ type Msg type alias Model = { currentTime : Posix , zone : Zone - , pickedTime : Maybe Posix - , picker : SingleDatePicker.DatePicker Msg + , dateInputPickerTime : Maybe Posix + , dateInputPicker : SingleDatePicker.DatePicker Msg + , detachedPickerTime : Maybe Posix + , detachedPicker : SingleDatePicker.DatePicker Msg } update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of - OpenPicker -> - ( { model | picker = SingleDatePicker.openPicker (userDefinedDatePickerSettings model.zone model.currentTime) model.currentTime model.pickedTime model.picker }, Cmd.none ) + OpenDetachedPicker elementId -> + let + ( newPicker, cmd ) = + SingleDatePicker.openPicker elementId + (userDefinedDatePickerSettings model.zone model.currentTime) + model.currentTime + model.detachedPickerTime + model.detachedPicker + in + ( { model | detachedPicker = newPicker }, cmd ) - UpdatePicker subMsg -> + UpdateDetachedPicker subMsg -> let ( ( newPicker, maybeNewTime ), cmd ) = - SingleDatePicker.update (userDefinedDatePickerSettings model.zone model.currentTime) subMsg model.picker + SingleDatePicker.update (userDefinedDatePickerSettings model.zone model.currentTime) subMsg model.detachedPicker in - ( { model | picker = newPicker, pickedTime = maybeNewTime }, cmd ) + ( { model | detachedPicker = newPicker, detachedPickerTime = maybeNewTime }, cmd ) + + UpdateDateInputPicker subMsg -> + let + ( ( newPicker, maybeNewTime ), cmd ) = + SingleDatePicker.update (userDefinedDatePickerSettings model.zone model.currentTime) subMsg model.dateInputPicker + in + ( { model | dateInputPicker = newPicker, dateInputPickerTime = maybeNewTime }, cmd ) AdjustTimeZone newZone -> ( { model | zone = newZone }, Cmd.none ) @@ -84,6 +102,7 @@ userDefinedDatePickerSettings zone today = } , showCalendarWeekNumbers = True , presets = [ Settings.PresetDate { title = "Preset", date = today } ] + -- , presets = [] , dateInputSettings = dateInputSettings } @@ -107,15 +126,32 @@ view model = , style "padding" "3rem" ] [ h1 [ style "margin-bottom" "1rem" ] [ text "SingleDatePicker Example" ] - , div [ style "padding" "0 200px" ] + , div [] [ div [ style "margin-bottom" "1rem" ] - [ text "This is a basic picker" ] + [ text "This is the picker rendered with a date input" ] , div [ style "position" "relative", style "margin-bottom" "1rem", style "width" "250px" ] [ SingleDatePicker.viewDateInput [] (userDefinedDatePickerSettings model.zone model.currentTime) model.currentTime - model.pickedTime - model.picker + model.dateInputPickerTime + model.dateInputPicker + ] + , div [ style "margin-bottom" "1rem" ] + [ text "This is the detached picker rendered on click of a button" ] + , div [ style "position" "relative", style "margin-bottom" "1rem", style "display" "flex", style "gap" "1rem", style "align-items" "center" ] + [ button [ id "my-button", onClick (OpenDetachedPicker "my-button") ] [ text "Open the picker here" ] + , SingleDatePicker.view + (userDefinedDatePickerSettings model.zone model.currentTime) + model.detachedPicker + , div [] + [ text "Picked time: " + , case model.detachedPickerTime of + Just t -> + text (Utilities.posixToDateString model.zone t ++ " " ++ Utilities.posixToTimeString model.zone t) + + Nothing -> + text "No date selected yet!" + ] ] ] ] @@ -125,8 +161,10 @@ init : ( Model, Cmd Msg ) init = ( { currentTime = Time.millisToPosix 0 , zone = Time.utc - , pickedTime = Nothing - , picker = SingleDatePicker.init UpdatePicker + , dateInputPickerTime = Nothing + , dateInputPicker = SingleDatePicker.init UpdateDateInputPicker + , detachedPickerTime = Nothing + , detachedPicker = SingleDatePicker.init UpdateDetachedPicker } , Task.perform AdjustTimeZone Time.here ) @@ -135,6 +173,7 @@ init = subscriptions : Model -> Sub Msg subscriptions model = Sub.batch - [ SingleDatePicker.subscriptions (userDefinedDatePickerSettings model.zone model.currentTime) model.picker + [ SingleDatePicker.subscriptions (userDefinedDatePickerSettings model.zone model.currentTime) model.dateInputPicker + , SingleDatePicker.subscriptions (userDefinedDatePickerSettings model.zone model.currentTime) model.detachedPicker , Time.every 1000 Tick ] diff --git a/src/DatePicker/Alignment.elm b/src/DatePicker/Alignment.elm index d7b0979..f1c4423 100644 --- a/src/DatePicker/Alignment.elm +++ b/src/DatePicker/Alignment.elm @@ -1,15 +1,8 @@ module DatePicker.Alignment exposing (..) -import Browser exposing (element) import Browser.Dom as Dom -import Css exposing (block) -import Css.Global +import Css import DatePicker.Theme as Theme exposing (Theme) -import DatePicker.Utilities as Utilities -import Html.Styled exposing (div) -import Html.Styled.Attributes as Attrs -import Html.Styled.Events as Events -import Json.Decode as Decode import Task exposing (Task) import Task.Extra as TaskExtra @@ -32,7 +25,7 @@ type CssPosition type alias Element = - { x : Float, y : Float, width : Float, height : Float } + { id : String, x : Float, y : Float, width : Float, height : Float } type PlacementX @@ -46,35 +39,40 @@ type PlacementY | Bottom -init : { trigger : Dom.Element, picker : Dom.Element, viewport : Dom.Viewport } -> Alignment -init { trigger, picker, viewport } = +fromElements : + { trigger : Element + , picker : Element + , viewport : Element + } + -> Alignment +fromElements { trigger, picker, viewport } = let minOffset = 10 triggerLeft = - trigger.element.x + trigger.x triggerRight = - trigger.element.x + trigger.element.width + trigger.x + trigger.width triggerCenter = - trigger.element.x + trigger.element.width / 2 + trigger.x + trigger.width / 2 triggerBottom = - trigger.element.y + trigger.element.height + trigger.y + trigger.height pickerWidth = - picker.element.width + picker.width pickerHeight = - picker.element.height + picker.height viewPortWidth = - viewport.viewport.width + viewport.width viewPortHeight = - viewport.viewport.height + viewport.height alignX = if (triggerLeft + pickerWidth) <= (viewPortWidth - minOffset) then @@ -103,22 +101,44 @@ init { trigger, picker, viewport } = in Alignment { placement = ( alignX, alignY ) - , trigger = trigger.element - , picker = picker.element + , trigger = trigger + , picker = picker } -getAlignment : { triggerId : String, pickerId : String } -> (Result Dom.Error Alignment -> msg) -> Cmd msg -getAlignment { triggerId, pickerId } handleResponse = +init : { triggerId : String, pickerId : String } -> (Result Dom.Error Alignment -> msg) -> Cmd msg +init { triggerId, pickerId } handleResponse = Task.attempt handleResponse (getElements triggerId pickerId) +update : (Result Dom.Error Alignment -> msg) -> Alignment -> Cmd msg +update handleResponse (Alignment { trigger, picker }) = + init { pickerId = picker.id, triggerId = trigger.id } handleResponse + + getElements : String -> String -> Task Dom.Error Alignment -getElements containerId selectId = - Task.succeed (\trigger picker viewport -> init { trigger = trigger, picker = picker, viewport = viewport }) - |> TaskExtra.andMap (Dom.getElement containerId) - |> TaskExtra.andMap (Dom.getElement selectId) +getElements pickerId triggerId = + let + elementFromDomElement : String -> { x : Float, y : Float, width : Float, height : Float } -> Element + elementFromDomElement id domElement = + { id = id + , x = domElement.x + , y = domElement.y + , width = domElement.width + , height = domElement.height + } + in + Task.succeed + (\trigger picker viewport -> + fromElements + { trigger = elementFromDomElement triggerId trigger.element + , picker = elementFromDomElement pickerId picker.element + , viewport = elementFromDomElement "" viewport.viewport + } + ) + |> TaskExtra.andMap (Dom.getElement pickerId) + |> TaskExtra.andMap (Dom.getElement triggerId) |> TaskExtra.andMap Dom.getViewport @@ -133,7 +153,7 @@ dateInputStylesFromAlignment theme isPickerOpen showCalendarWeekNumbers maybeAli , Css.top (Css.px 0) , Css.left (Css.px 0) , Css.zIndex (Css.int (theme.zIndex + 10)) - , Css.width (Css.px (calendarWidth theme showCalendarWeekNumbers)) + , Css.width (Css.pct 100) ] in case ( maybeAlignment, isPickerOpen ) of @@ -191,11 +211,6 @@ fixedDateInputCoorinatesFromAlignment dateInputWidth (Alignment { placement, tri pickerStylesFromAlignment : Theme -> Maybe Alignment -> List Css.Style pickerStylesFromAlignment theme maybeAlignment = - -- [ Css.position Css.absolute - -- , Css.left (Css.px 0) - -- , Css.top (Css.pct 100) - -- , Css.zIndex (Css.int theme.zIndex) - -- ] case maybeAlignment of Just alignment -> let @@ -213,89 +228,65 @@ pickerStylesFromAlignment theme maybeAlignment = [ Css.visibility Css.hidden ] -pickerWithDateInputStyles : Theme -> Maybe Alignment -> List Css.Style -pickerWithDateInputStyles theme maybeAlignment = +applyPickerStyles : (Alignment -> List Css.Style) -> Maybe Alignment -> List Css.Style +applyPickerStyles stylingFn maybeAlignment = case maybeAlignment of - Just (Alignment { placement }) -> - let - ( placementX, placementY ) = - placement - - ( containerFlexDirection, presetsContainerBorderRightWidth, presetsContainerBorderLeftWidth ) = - case placementX of - Left -> - ( Css.rowReverse, Css.px 0, Css.px theme.borderWidth ) - - Right -> - ( Css.row, Css.px theme.borderWidth, Css.px 0 ) - - Center -> - ( Css.rowReverse, Css.px 0, Css.px theme.borderWidth ) - - xStyles = - [ -- move the presets container to the corresponding position - Css.flexDirection containerFlexDirection - , Css.Global.children - [ Css.Global.class (Utilities.classPrefix theme.classNamePrefix "presets-container") - [ Css.borderRight3 presetsContainerBorderRightWidth Css.solid theme.color.border - , Css.borderLeft3 presetsContainerBorderLeftWidth Css.solid theme.color.border - ] - ] - ] + Just alignment -> + stylingFn alignment + + Nothing -> + -- hide picker element until the DOM elements have been found + [ Css.visibility Css.hidden + , defaultGridLayout + ] - pickerFlexDirection = - case placementY of - Top -> - Css.columnReverse - - Bottom -> - Css.column - - yStyles = - [ -- move the date input placeholder to the corresponding position - Css.Global.children - [ Css.Global.class (Utilities.classPrefix theme.classNamePrefix "picker-with-date-input-container") - [ Css.displayFlex - , Css.flexDirection pickerFlexDirection - ] - ] - ] - inputHeight = - String.fromFloat theme.size.inputElement ++ "px" +pickerPositionFromAlignment : Theme -> Alignment -> Css.Style +pickerPositionFromAlignment theme alignment = + let + { x, y } = + fixedPickerCoorinatesFromAlignment alignment + in + Css.batch + [ Css.position Css.fixed + , Css.zIndex (Css.int theme.zIndex) + , Css.left (Css.px x) + , Css.top (Css.px y) + ] - translate : { x : String, y : String } -> Css.Style - translate { x, y } = - Css.property "transform" ("translateX(" ++ x ++ ") translateY(" ++ y ++ ")") - offsetTranslation = - -- container translation to frame date input - case ( placementX, placementY ) of - ( Left, Bottom ) -> - translate { x = "-1rem", y = "calc(-1rem - " ++ inputHeight ++ ")" } +pickerTranslationFromAlignment : Theme -> Alignment -> Css.Style +pickerTranslationFromAlignment theme (Alignment { placement }) = + let + ( placementX, placementY ) = + placement - ( Left, Top ) -> - translate { x = "-1rem", y = "calc(1rem + " ++ inputHeight ++ ")" } + inputHeight = + String.fromFloat theme.size.inputElement ++ "px" - ( Right, Bottom ) -> - translate { x = "1rem", y = "calc(-1rem - " ++ inputHeight ++ ")" } + translate : { x : String, y : String } -> Css.Style + translate { x, y } = + Css.property "transform" ("translateX(" ++ x ++ ") translateY(" ++ y ++ ")") + in + -- container translation to frame date input + case ( placementX, placementY ) of + ( Left, Bottom ) -> + translate { x = "-1rem", y = "calc(-1rem - " ++ inputHeight ++ ")" } - ( Right, Top ) -> - translate { x = "1rem", y = "calc(2rem + " ++ inputHeight ++ ")" } + ( Left, Top ) -> + translate { x = "-1rem", y = "calc(1rem + " ++ inputHeight ++ ")" } - ( Center, Bottom ) -> - translate { x = "-1rem", y = "calc(-1rem - " ++ inputHeight ++ ")" } + ( Right, Bottom ) -> + translate { x = "1rem", y = "calc(-1rem - " ++ inputHeight ++ ")" } - ( Center, Top ) -> - translate { x = "-1rem", y = "calc(2rem + " ++ inputHeight ++ ")" } - in - [ Css.batch xStyles - , Css.batch yStyles - , offsetTranslation - ] + ( Right, Top ) -> + translate { x = "1rem", y = "calc(2rem + " ++ inputHeight ++ ")" } - Nothing -> - [] + ( Center, Bottom ) -> + translate { x = "-1rem", y = "calc(-1rem - " ++ inputHeight ++ ")" } + + ( Center, Top ) -> + translate { x = "-1rem", y = "calc(2rem + " ++ inputHeight ++ ")" } fixedPickerCoorinatesFromAlignment : Alignment -> { x : Float, y : Float } @@ -324,3 +315,81 @@ fixedPickerCoorinatesFromAlignment (Alignment { placement, trigger, picker }) = trigger.y + trigger.height in { x = x, y = y } + + +gridAreaPresets : String +gridAreaPresets = + "presets" + + +gridAreaDateInput : String +gridAreaDateInput = + "input" + + +gridAreaCalendar : String +gridAreaCalendar = + "calendar" + + +gridTemplateFromList : List (List String) -> Css.Style +gridTemplateFromList listTemplate = + let + template = + String.join " " + (List.map + (\row -> "'" ++ String.join " " row ++ "'") + listTemplate + ) + in + Css.property "grid-template" template + + +pickerGridLayoutFromAlignment : Alignment -> Css.Style +pickerGridLayoutFromAlignment (Alignment { placement }) = + let + template = + case ( Tuple.first placement, Tuple.second placement ) of + ( Left, Bottom ) -> + [ [ gridAreaDateInput, gridAreaPresets ] + , [ gridAreaCalendar, gridAreaPresets ] + ] + + ( Left, Top ) -> + [ [ gridAreaCalendar, gridAreaPresets ] + , [ gridAreaDateInput, gridAreaPresets ] + ] + + ( Right, Bottom ) -> + [ [ gridAreaPresets, gridAreaDateInput ] + , [ gridAreaPresets, gridAreaCalendar ] + ] + + ( Right, Top ) -> + [ [ gridAreaPresets, gridAreaCalendar ] + , [ gridAreaPresets, gridAreaDateInput ] + ] + + ( _, _ ) -> + [ [ gridAreaDateInput, gridAreaPresets ] + , [ gridAreaCalendar, gridAreaPresets ] + ] + in + Css.batch + [ Css.property "display" "grid" + , gridTemplateFromList template + ] + + +defaultGridLayout : Css.Style +defaultGridLayout = + let + template = + [ [ gridAreaDateInput, gridAreaPresets ] + , [ gridAreaCalendar, gridAreaPresets ] + ] + in + Css.batch + [ Css.property "display" "grid" + , gridTemplateFromList template + ] diff --git a/src/DatePicker/DateInput.elm b/src/DatePicker/DateInput.elm index dd1c220..080e28f 100644 --- a/src/DatePicker/DateInput.elm +++ b/src/DatePicker/DateInput.elm @@ -93,11 +93,6 @@ type InputError | ValueNotAllowed -placholderId : Config -> String -placholderId { id } = - id ++ "-placeholder" - - defaultConfig : Zone -> Config defaultConfig zone = { dateInputSettings = defaultSettings @@ -607,15 +602,33 @@ viewError theme isVisible error = viewPlaceholder : Config -> Html.Styled.Html msg -viewPlaceholder ({ theme } as config) = +viewPlaceholder { theme } = div [ Attrs.css [ Css.displayFlex, Css.width (Css.pct 100), Css.height (Css.px theme.size.inputElement) ] - , Attrs.id (placholderId config) , Attrs.attribute "aria-hidden" "true" ] [] +containerId : Config -> String +containerId { id } = + id ++ "--container" + + +viewContainer : Theme.Theme -> List (Html.Styled.Attribute msg) -> List (Html.Styled.Html msg) -> Html.Styled.Html msg +viewContainer theme attrs children = + div + (Attrs.css + [ Css.position Css.relative + , Css.display Css.inlineFlex + , Css.width (Css.pct 100) + , Css.height (Css.px theme.size.inputElement) + ] + :: attrs + ) + children + + buildPlaceholderFromFormat : Format -> String buildPlaceholderFromFormat format = case format of diff --git a/src/DatePicker/ViewComponents.elm b/src/DatePicker/ViewComponents.elm index bde69ab..5f69687 100644 --- a/src/DatePicker/ViewComponents.elm +++ b/src/DatePicker/ViewComponents.elm @@ -288,11 +288,15 @@ viewPresetsContainer theme attributes children = ([ css [ Css.padding (Css.rem 0.75) , Css.borderRight3 (Css.px theme.borderWidth) Css.solid theme.color.border + , Css.borderLeft3 (Css.px theme.borderWidth) Css.solid theme.color.border + , Css.marginLeft (Css.px -1) + , Css.marginRight (Css.px -1) , Css.backgroundColor theme.color.background.presets , Css.width (Css.px theme.size.presetsContainer) , Css.displayFlex , Css.flexDirection Css.column , Css.flexShrink (Css.int 0) + , Css.zIndex (Css.int 1) ] , class (classPrefix theme.classNamePrefix "presets-container") ] diff --git a/src/SingleDatePicker.elm b/src/SingleDatePicker.elm index 4d6f6c8..8847d48 100644 --- a/src/SingleDatePicker.elm +++ b/src/SingleDatePicker.elm @@ -1,6 +1,6 @@ module SingleDatePicker exposing ( DatePicker, Msg, init, view, update, subscriptions - , openPicker, closePicker, openPickerOutsideHierarchy, updatePickerPosition + , openPicker, closePicker, updatePickerPosition , isOpen , viewDateInput ) @@ -36,7 +36,7 @@ import DatePicker.ViewComponents exposing (..) import Html exposing (Html) import Html.Events.Extra exposing (targetValueIntParse) import Html.Styled exposing (div, text, toUnstyled) -import Html.Styled.Attributes exposing (attribute, class, css, id) +import Html.Styled.Attributes exposing (align, class, css, id) import Html.Styled.Events exposing (onClick) import Json.Decode as Decode import List.Extra as List @@ -56,7 +56,6 @@ type alias Model msg = , internalMsg : Msg -> msg , viewOffset : Int , selectionTuple : Maybe ( PickerDay, Posix ) - , domLocation : DomLocation , alignment : Maybe Alignment , dateInput : DateInput.DateInput msg } @@ -77,7 +76,6 @@ init internalMsg = , internalMsg = internalMsg , viewOffset = 0 , selectionTuple = Nothing - , domLocation = InsideHierarchy , alignment = Nothing , dateInput = DateInput.init @@ -103,33 +101,11 @@ takes a default time the picker should center on (in the event a time has not ye been picked) as well as the picked time. A common example of a default time would be the datetime for the current day. -} -openPicker : Settings -> Posix -> Maybe Posix -> DatePicker msg -> DatePicker msg -openPicker settings baseTime pickedTime (DatePicker model) = +openPicker : String -> Settings -> Posix -> Maybe Posix -> DatePicker msg -> ( DatePicker msg, Cmd msg ) +openPicker triggerElementId settings baseTime pickedTime (DatePicker model) = let - ( ( updatedPicker, _ ), _ ) = - update settings (OpenPicker baseTime pickedTime InsideHierarchy) (DatePicker model) - in - updatedPicker - - -{-| Open the provided date picker outside the DOM hierarchy. Uses the openPicker function -and additionally takes an id of the trigger DOM element (e.g. a button) to manually attach -the picker's position to it. Returns the updated picker instance plus the necessary command -in order to find DOM elements and their positions. --} -openPickerOutsideHierarchy : String -> Settings -> Posix -> Maybe Posix -> DatePicker msg -> ( DatePicker msg, Cmd msg ) -openPickerOutsideHierarchy triggerElementId settings baseTime pickedTime (DatePicker model) = - let - domLocation = - OutsideHierarchy - { triggerDomElement = { id = triggerElementId, element = Nothing } - , pickerDomElement = { id = settings.id, element = Nothing } - } - ( ( updatedPicker, _ ), cmd ) = - update settings - (OpenPicker baseTime pickedTime domLocation) - (DatePicker model) + update settings (OpenPicker baseTime pickedTime triggerElementId) (DatePicker model) in ( updatedPicker, cmd ) @@ -157,24 +133,18 @@ isOpen (DatePicker { status }) = Is used internally but can also be used externally in case of a changing viewport (e.g. onScroll or onResize). -} -updatePickerPosition : DatePicker msg -> Cmd msg +updatePickerPosition : DatePicker msg -> ( DatePicker msg, Cmd msg ) updatePickerPosition (DatePicker model) = - updatePositionFromDomLocation model.internalMsg model.domLocation - - -updatePositionFromDomLocation : (Msg -> msg) -> DomLocation -> Cmd msg -updatePositionFromDomLocation internalMsg domLocation = - case domLocation of - OutsideHierarchy { triggerDomElement, pickerDomElement } -> - Utilities.updateDomElements - { triggerElementId = triggerDomElement.id - , pickerElementId = pickerDomElement.id - , onSuccess = \result -> internalMsg (SetDomElements result) - , onError = internalMsg NoOp - } + let + cmd = + case ( model.status, model.alignment ) of + ( Open _ _, Just alignment ) -> + Alignment.update (model.internalMsg << GotAlignment) alignment - InsideHierarchy -> - Cmd.none + ( _, _ ) -> + Cmd.none + in + ( DatePicker model, cmd ) {-| Internal Msg's to update the picker. @@ -191,11 +161,10 @@ type Msg | SetHour Int | SetMinute Int | Close - | SetDomElements { triggerDomElement : Dom.Element, pickerDomElement : Dom.Element } | GotAlignment (Result Dom.Error Alignment) | SetPresetDate PresetDateConfig | HandleDateInputUpdate DateInput.Msg - | OpenPicker Posix (Maybe Posix) DomLocation + | OpenPicker Posix (Maybe Posix) String | NoOp @@ -269,22 +238,6 @@ update settings msg (DatePicker model) = Close -> ( ( DatePicker { model | status = Closed, alignment = Nothing }, pickedTime ), Cmd.none ) - SetDomElements newDomElements -> - let - updatedDomLocation = - case model.domLocation of - OutsideHierarchy ({ triggerDomElement, pickerDomElement } as domElements) -> - OutsideHierarchy - { domElements - | triggerDomElement = { triggerDomElement | element = Just newDomElements.triggerDomElement } - , pickerDomElement = { pickerDomElement | element = Just newDomElements.pickerDomElement } - } - - InsideHierarchy -> - InsideHierarchy - in - ( ( DatePicker { model | domLocation = updatedDomLocation }, pickedTime ), Cmd.none ) - SetPresetDate presetDate -> let presetPickerDay = @@ -336,7 +289,7 @@ update settings msg (DatePicker model) = in ( ( DatePicker { model | dateInput = updatedDateInput }, pickedTime ), dateInputCmd ) - OpenPicker baseTime pickedTime_ domLocation -> + OpenPicker baseTime pickedTime_ triggerElementId -> let basePickerDay = generatePickerDay settings baseTime @@ -350,20 +303,16 @@ update settings msg (DatePicker model) = status = Open timePickerVisible basePickerDay - cmd = - Cmd.batch - [ updatePositionFromDomLocation model.internalMsg domLocation - , Alignment.getAlignment - { triggerId = DateInput.placholderId (dateInputConfig settings) - , pickerId = settings.id - } - (model.internalMsg << GotAlignment) - ] - ( DatePicker updatedModel, updatedPickedTime ) = updateSelection settings basePickerDay newSelectionTuple ( DatePicker model, pickedTime ) in - ( ( DatePicker { updatedModel | status = status, domLocation = domLocation }, updatedPickedTime ), cmd ) + ( ( DatePicker { updatedModel | status = status }, updatedPickedTime ) + , Alignment.init + { triggerId = triggerElementId + , pickerId = settings.id + } + (model.internalMsg << GotAlignment) + ) _ -> ( ( DatePicker model, pickedTime ), Cmd.none ) @@ -422,8 +371,8 @@ viewStyled settings (DatePicker model) = , class (classPrefix settings.theme.classNamePrefix "single") , css styles ] - [ viewPresets settings model - , viewPicker settings timePickerVisible baseDay model + [ viewPresets [] settings model + , viewPicker [] settings timePickerVisible baseDay model ] Closed -> @@ -454,36 +403,49 @@ viewDateInputStyled : List (Html.Styled.Attribute msg) -> Settings -> Posix -> M viewDateInputStyled attrs settings baseTime maybePickedTime (DatePicker model) = let onClickMsg = - OpenPicker baseTime - maybePickedTime - (OutsideHierarchy - { triggerDomElement = { id = DateInput.placholderId (dateInputConfig settings), element = Nothing } - , pickerDomElement = { id = settings.id, element = Nothing } - } - ) - |> model.internalMsg + model.internalMsg <| + OpenPicker baseTime maybePickedTime (DateInput.containerId <| dateInputConfig settings) + + isPickerOpen = + isOpen (DatePicker model) in - div (css [ Css.position Css.relative, Css.display Css.inlineFlex ] :: attrs) - [ viewDateInputContainer - [ DateInput.viewStyled [ onClick onClickMsg ] (dateInputConfig settings) model.dateInput ] - { settings = settings, alignment = model.alignment, isPickerOpen = isOpen (DatePicker model) } + DateInput.viewContainer settings.theme + (id (DateInput.containerId <| dateInputConfig settings) :: attrs) + [ DateInput.viewStyled + [ onClick onClickMsg + , css (Alignment.dateInputStylesFromAlignment settings.theme isPickerOpen settings.showCalendarWeekNumbers model.alignment) + ] + (dateInputConfig settings) + model.dateInput , case model.status of Open timePickerVisible baseDay -> viewContainer settings.theme [ id settings.id , class (classPrefix settings.theme.classNamePrefix "single") , css - [ Css.batch <| Alignment.pickerStylesFromAlignment settings.theme model.alignment - , Css.batch <| Alignment.pickerWithDateInputStyles settings.theme model.alignment - ] + (Alignment.applyPickerStyles + (\alignment -> + [ Alignment.pickerGridLayoutFromAlignment alignment + , Alignment.pickerPositionFromAlignment settings.theme alignment + , Alignment.pickerTranslationFromAlignment settings.theme alignment + ] + ) + model.alignment + ) ] - [ viewPresets settings model + [ viewPresets + [ css [ Css.property "grid-area" Alignment.gridAreaPresets ] ] + settings + model , div - [ class (classPrefix settings.theme.classNamePrefix "picker-with-date-input-container") ] - [ div [ css [ Css.padding (Css.rem 1) ] ] - [ DateInput.viewPlaceholder (dateInputConfig settings) ] - , viewPicker settings timePickerVisible baseDay model - ] + [ css [ Css.property "grid-area" Alignment.gridAreaDateInput, Css.padding (Css.rem 1) ] ] + [ DateInput.viewPlaceholder (dateInputConfig settings) ] + , viewPicker + [ css [ Css.property "grid-area" Alignment.gridAreaCalendar ] ] + settings + timePickerVisible + baseDay + model ] Closed -> @@ -491,25 +453,8 @@ viewDateInputStyled attrs settings baseTime maybePickedTime (DatePicker model) = ] -viewDateInputContainer : List (Html.Styled.Html msg) -> { settings : Settings, alignment : Maybe Alignment, isPickerOpen : Bool } -> Html.Styled.Html msg -viewDateInputContainer children { settings, alignment, isPickerOpen } = - div [ css [ Css.position Css.relative, Css.display Css.inlineFlex, Css.width (Css.pct 100) ] ] - [ DateInput.viewPlaceholder (dateInputConfig settings) - , div - [ css - (Alignment.dateInputStylesFromAlignment - settings.theme - isPickerOpen - settings.showCalendarWeekNumbers - alignment - ) - ] - children - ] - - -viewPicker : Settings -> Bool -> PickerDay -> Model msg -> Html.Styled.Html msg -viewPicker settings timePickerVisible baseDay model = +viewPicker : List (Html.Styled.Attribute msg) -> Settings -> Bool -> PickerDay -> Model msg -> Html.Styled.Html msg +viewPicker attrs settings timePickerVisible baseDay model = let offsetTime = Time.add Month model.viewOffset settings.zone baseDay.start @@ -557,7 +502,7 @@ viewPicker settings timePickerVisible baseDay model = ) in viewPickerContainer settings.theme - [] + attrs [ viewCalendarContainer settings.theme [] [ viewCalendarHeader settings.theme @@ -587,11 +532,11 @@ viewPicker settings timePickerVisible baseDay model = ] -viewPresets : Settings -> Model msg -> Html.Styled.Html msg -viewPresets settings model = +viewPresets : List (Html.Styled.Attribute msg) -> Settings -> Model msg -> Html.Styled.Html msg +viewPresets attrs settings model = if List.length settings.presets > 0 then viewPresetsContainer settings.theme - [] + attrs (List.map (\preset -> case preset of