From a3c0bd43f0f3fcfc8d9e4b79db2375bae1c751e8 Mon Sep 17 00:00:00 2001 From: lue-bird Date: Sun, 11 Sep 2022 08:22:37 +0200 Subject: [PATCH 1/7] =?UTF-8?q?tecnique=20=E2=86=92=20technique=20correct?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2d8751a..e0be7ac 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ This has the advantage of keeping a nice API while making it (almost) impossible Notice how `text` simply requires a context that includes a `language` field, so is very generic. -This tecnique can be adapted for image sources, title texts, and anything that needs localization. +This technique can be adapted for image sources, title texts, and anything that needs localization. Strings with placeholders can be represented as `L10N (a -> b -> String)` and used by defining an `apply : L10N (a -> b) -> a -> L10N b`. Beware: different languages can have very different rules on plurals, genders, special cases, ... From e7a7dc2915eb7661d1f3e73b8c52dcfd361510d5 Mon Sep 17 00:00:00 2001 From: lue-bird Date: Sun, 11 Sep 2022 08:26:19 +0200 Subject: [PATCH 2/7] =?UTF-8?q?`\{theme}`=20format=20=E2=86=92=20`\{=20the?= =?UTF-8?q?me=20}`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e0be7ac..3db9a4a 100644 --- a/README.md +++ b/README.md @@ -107,13 +107,13 @@ fontColor theme = someViewFunction = el [ Element.withAttribute - (\{theme} -> fontColor theme) + (\{ theme } -> fontColor theme) Font.color ] (text "Hello") ``` -(`\{theme} -> fontColor theme` can be replaced by `.theme >> fontColor`, depending on taste). +(`\{ theme } -> fontColor theme` can be replaced by `.theme >> fontColor`, depending on taste.) This also has the advantage that you can "force" a particular theme in places that need it, like the theme picker, by just doing `fontColor Light`. From e119179595fabb70e8edf867e980e30c57993526 Mon Sep 17 00:00:00 2001 From: lue-bird Date: Sun, 11 Sep 2022 10:03:13 +0200 Subject: [PATCH 3/7] =?UTF-8?q?everwhere=20=E2=86=92=20everywhere?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3db9a4a..8c6c3ef 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This library wraps [`mdgriffith/elm-ui`](https://package.elm-lang.org/packages/m A context is a global, *constant or mostly constant* object. It can be used to store those things that you will need *almost everywhere* in your `view` but don't change often, or at all. Examples of things you could want to put in the context: -1. theme (dark/light/custom) - this is needed almost everwhere for colors, and styles, and changes very rarely; +1. theme (dark/light/custom) - this is needed almost everywhere for colors, and styles, and changes very rarely; 2. language - this is needed for every single label for localization, and changes rarely or never; 3. timezone - this is needed to display local times for the user, and mostly doesn't change; 4. responsive class (phone/tablet/desktop) - this doesn't usually change (unless the user dramatically resizes the window); From 03cee05805a938bee873b5852237a7b05230cb86 Mon Sep 17 00:00:00 2001 From: lue-bird Date: Sun, 11 Sep 2022 10:05:08 +0200 Subject: [PATCH 4/7] `withX`, `fromX`, `toX`, `mapContextInX` add --- src/Element/WithContext/Input.elm | 184 ++++++++++++++++++++++++------ 1 file changed, 149 insertions(+), 35 deletions(-) diff --git a/src/Element/WithContext/Input.elm b/src/Element/WithContext/Input.elm index 45b24e4..b13f662 100644 --- a/src/Element/WithContext/Input.elm +++ b/src/Element/WithContext/Input.elm @@ -1,5 +1,7 @@ module Element.WithContext.Input exposing - ( focusedOnLoad + ( withLabel, withOption, withPlaceholder, withThumb, mapContextInLabel, mapContextInOption, mapContextInPlaceholder, mapContextInThumb, toLabel, toOption, toPlaceholder, toThumb + , fromOption, fromPlaceholder, fromThumb, fromLabel + , focusedOnLoad , button , checkbox, defaultCheckbox , text, multiline @@ -10,7 +12,19 @@ module Element.WithContext.Input exposing , Label, labelAbove, labelBelow, labelLeft, labelRight, labelHidden ) -{-| Input elements have a lot of constraints! +{-| + + +# `elm-ui-with-context` specific functions + +@docs withLabel, withOption, withPlaceholder, withThumb, mapContextInLabel, mapContextInOption, mapContextInPlaceholder, mapContextInThumb, toLabel, toOption, toPlaceholder, toThumb + +`elm-ui` to `elm-ui-with-context` conversions are named "fromX" instead of "x" +to avoid name clashes with existing functions. + +@docs fromOption, fromPlaceholder, fromThumb, fromLabel + +Input elements have a lot of constraints! We want all of our input elements to: @@ -27,7 +41,7 @@ This module is intended to be accessible by default. You shouldn't have to wade # Focus Styling -All Elements can be styled on focus by using [`Element.focusStyle`](Element#focusStyle) to set a global focus style or [`Element.focused`](Element#focused) to set a focus style individually for an element. +All Elements can be styled on focus by using [`Element.focusStyle`](Element-WithContext#focusStyle) to set a global focus style or [`Element.focused`](Element-WithContext#focused) to set a focus style individually for an element. @docs focusedOnLoad @@ -110,7 +124,7 @@ Nevertheless, here we are. Here's how you put one together ] } -**Note** we're using `Input.option`, which will render the default radio icon you're probably used to. If you want compeltely custom styling, use `Input.optionWith`! +**Note** we're using `Input.option`, which will render the default radio icon you're probably used to. If you want completely custom styling, use `Input.optionWith`! @docs radio, radioRow, Option, option, optionWith, OptionState @@ -195,6 +209,36 @@ type Placeholder context msg = Placeholder (context -> Input.Placeholder msg) +{-| Embed a label from the original elm-ui library. This is useful for interop with existing code. +-} +fromPlaceholder : Input.Placeholder msg -> Placeholder context msg +fromPlaceholder elmUiPlaceHolder = + Placeholder <| \_ -> elmUiPlaceHolder + + +{-| Construct a placeholder from the original elm-ui by supplying a complete context. +[`toElement`](Element-WithContext#toElement) documents use-cases. +-} +toPlaceholder : context -> Placeholder context msg -> Input.Placeholder msg +toPlaceholder context (Placeholder f) = + f context + + +{-| Use a property from the context to build a `Placeholder`. Have a look at the README for examples. +-} +withPlaceholder : (context -> property) -> (property -> Placeholder context msg) -> Placeholder context msg +withPlaceholder selector f = + Placeholder <| \context -> toPlaceholder context <| f <| selector context + + +{-| Change how the context looks for a given placeholder. +[`mapContextInElement`](Element-WithContext#mapContextInElement) documents use-cases. +-} +mapContextInPlaceholder : (contextOuter -> contextInner) -> Placeholder contextInner msg -> Placeholder contextOuter msg +mapContextInPlaceholder outerToInnerContext (Placeholder f) = + Placeholder (outerToInnerContext >> f) + + {-| -} placeholder : List (Attribute context msg) -> Element context msg -> Placeholder context msg placeholder attrs child = @@ -209,6 +253,36 @@ type Label context msg = Label (context -> Input.Label msg) +{-| Embed a label from the original elm-ui library. This is useful for interop with existing code. +-} +fromLabel : Input.Label msg -> Label context msg +fromLabel elmUiLabel = + Label <| \_ -> elmUiLabel + + +{-| Construct a label from the original elm-ui by supplying a complete context. +[`toElement`](Element-WithContext#toElement) documents use-cases. +-} +toLabel : context -> Label context msg -> Input.Label msg +toLabel context (Label f) = + f context + + +{-| Use a property from the context to build a `Label`. Have a look at the README for examples. +-} +withLabel : (context -> property) -> (property -> Label context msg) -> Label context msg +withLabel selector f = + Label <| \context -> toLabel context <| f <| selector context + + +{-| Change how the context looks for a given label. +[`mapContextInElement`](Element-WithContext#mapContextInElement) documents use-cases. +-} +mapContextInLabel : (contextOuter -> contextInner) -> Label contextInner msg -> Label contextOuter msg +mapContextInLabel outerToInnerContext (Label f) = + Label (outerToInnerContext >> f) + + buildLabel : (List (Vanilla.Attribute msg) -> Vanilla.Element msg @@ -286,7 +360,7 @@ The `onPress` handler will be fired either `onClick` or when the element is focu , label = text "My Button" } -**Note** If you have an icon button but want it to be accessible, consider adding a [`Region.description`](Element-Region#description), which will describe the button to screen readers. +**Note** If you have an icon button but want it to be accessible, consider adding a [`Region.description`](Element-WithContext-Region#description), which will describe the button to screen readers. -} button : @@ -325,7 +399,7 @@ checkbox : checkbox = wrapAttrs Input.checkbox (\context { label, icon, checked, onChange } -> - { label = runLabel context label + { label = toLabel context label , icon = icon >> run context , checked = checked , onChange = onChange @@ -338,17 +412,42 @@ type Thumb context = Thumb (context -> Input.Thumb) +{-| Embed a thumb from the original elm-ui library. This is useful for interop with existing code. +-} +fromThumb : Input.Thumb -> Thumb context +fromThumb elmUiThumb = + Thumb <| \_ -> elmUiThumb + + +{-| Construct a thumb from the original elm-ui by supplying a complete context. +[`toElement`](Element-WithContext#toElement) documents use-cases. +-} +toThumb : context -> Thumb context -> Input.Thumb +toThumb context (Thumb f) = + f context + + +{-| Use a property from the context to build a `Thumb`. Have a look at the README for examples. +-} +withThumb : (context -> property) -> (property -> Thumb context) -> Thumb context +withThumb selector f = + Thumb <| \context -> toThumb context <| f <| selector context + + +{-| Change how the context looks for a given thumb. +[`mapContextInElement`](Element-WithContext#mapContextInElement) documents use-cases. +-} +mapContextInThumb : (contextOuter -> contextInner) -> Thumb contextInner -> Thumb contextOuter +mapContextInThumb outerToInnerContext (Thumb f) = + Thumb (outerToInnerContext >> f) + + {-| -} thumb : List (Attribute context Never) -> Thumb context thumb attrs = Thumb <| \context -> Input.thumb <| attributes context attrs -runThumb : a -> Thumb a -> Input.Thumb -runThumb context (Thumb f) = - f context - - {-| -} defaultThumb : Thumb context defaultThumb = @@ -415,11 +514,11 @@ slider = wrapAttrs Input.slider (\context config -> { onChange = config.onChange - , label = runLabel context config.label + , label = toLabel context config.label , min = config.min , max = config.max , value = config.value - , thumb = runThumb context config.thumb + , thumb = toThumb context config.thumb , step = config.step } ) @@ -448,22 +547,12 @@ textHelper f = (\context config -> { onChange = config.onChange , text = config.text - , label = runLabel context config.label - , placeholder = Maybe.map (runPlaceholder context) config.placeholder + , label = toLabel context config.label + , placeholder = Maybe.map (toPlaceholder context) config.placeholder } ) -runPlaceholder : context -> Placeholder context msg -> Input.Placeholder msg -runPlaceholder context (Placeholder f) = - f context - - -runLabel : context -> Label context msg -> Input.Label msg -runLabel context (Label f) = - f context - - {-| -} text : List (Attribute context msg) @@ -529,8 +618,8 @@ newPassword = (\context config -> { onChange = config.onChange , text = config.text - , label = runLabel context config.label - , placeholder = Maybe.map (runPlaceholder context) config.placeholder + , label = toLabel context config.label + , placeholder = Maybe.map (toPlaceholder context) config.placeholder , show = config.show } ) @@ -552,8 +641,8 @@ currentPassword = (\context config -> { onChange = config.onChange , text = config.text - , label = runLabel context config.label - , placeholder = Maybe.map (runPlaceholder context) config.placeholder + , label = toLabel context config.label + , placeholder = Maybe.map (toPlaceholder context) config.placeholder , show = config.show } ) @@ -607,8 +696,8 @@ multiline = (\context config -> { onChange = config.onChange , text = config.text - , label = runLabel context config.label - , placeholder = Maybe.map (runPlaceholder context) config.placeholder + , label = toLabel context config.label + , placeholder = Maybe.map (toPlaceholder context) config.placeholder , spellcheck = config.spellcheck } ) @@ -619,11 +708,36 @@ type Option context value msg = Option (context -> Input.Option value msg) -runOption : a -> Option a value msg -> Input.Option value msg -runOption context (Option f) = +{-| Embed an option from the original elm-ui library. This is useful for interop with existing code. +-} +fromOption : Input.Option value msg -> Option context value msg +fromOption elmUiOption = + Option <| \_ -> elmUiOption + + +{-| Construct an option from the original elm-ui by supplying a complete context. +[`toElement`](Element-WithContext#toElement) documents use-cases. +-} +toOption : context -> Option context value msg -> Input.Option value msg +toOption context (Option f) = f context +{-| Use a property from the context to build an `Option`. Have a look at the README for examples. +-} +withOption : (context -> property) -> (property -> Option context value msg) -> Option context value msg +withOption selector f = + Option <| \context -> toOption context <| f <| selector context + + +{-| Change how the context looks for a given option. +[`mapContextInElement`](Element-WithContext#mapContextInElement) documents use-cases. +-} +mapContextInOption : (contextOuter -> contextInner) -> Option contextInner value msg -> Option contextOuter value msg +mapContextInOption outerToInnerContext (Option f) = + Option (outerToInnerContext >> f) + + {-| -} type OptionState = Idle @@ -705,9 +819,9 @@ radioHelper : } radioHelper context config = { onChange = config.onChange - , options = List.map (runOption context) config.options + , options = List.map (toOption context) config.options , selected = config.selected - , label = runLabel context config.label + , label = toLabel context config.label } From 76c0833d356b27fb5ae6f46699b23825c153525d Mon Sep 17 00:00:00 2001 From: lue-bird Date: Sun, 11 Sep 2022 10:06:08 +0200 Subject: [PATCH 5/7] `toX`, `mapContextInX` add --- src/Element/WithContext.elm | 169 +++++++++++++++++++++++++++++++++--- 1 file changed, 157 insertions(+), 12 deletions(-) diff --git a/src/Element/WithContext.elm b/src/Element/WithContext.elm index c4b38b6..6121cb7 100644 --- a/src/Element/WithContext.elm +++ b/src/Element/WithContext.elm @@ -1,5 +1,5 @@ module Element.WithContext exposing - ( with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr + ( with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr, toElement, toAttribute, toAttr, mapContextInElement, mapContextInAttr , Element, none, text, el , row, wrappedRow, column , paragraph, textColumn @@ -31,7 +31,10 @@ module Element.WithContext exposing # `elm-ui-with-context` specific functions -@docs with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr +@docs with, withAttribute, withDecoration, layout, layoutWith, element, attribute, attr, toElement, toAttribute, toAttr, mapContextInElement, mapContextInAttr + +Apart from some other functions in [`Element.WithContext.Input`](Element-WithContext-Input), +this is everything `elm-ui-with-context` adds to `elm-ui`. # Basic Elements @@ -208,17 +211,17 @@ You'll also need to retrieve the initial window size. You can either use [`Brows @docs modular -## Mapping +# Mapping @docs map, mapAttribute -## Compatibility +# Compatibility @docs html, htmlAttribute -## Advanced +# Advanced Sometimes it's more convenient to just access the whole context while building your view. This functions allow you do just that. @@ -227,7 +230,7 @@ Sometimes it's more convenient to just access the whole context while building y -} import Element -import Element.WithContext.Internal as Internal exposing (Attr(..), Attribute, Element(..), attr, attribute, attributes, run, runAttr, wrapAttrs, wrapContainer) +import Element.WithContext.Internal as Internal exposing (Attr(..), Attribute, Element(..), attributes, run, runAttr, wrapAttrs, wrapContainer) import Html exposing (Html) @@ -349,6 +352,10 @@ htmlAttribute child = {-| Embed an element from the original elm-ui library. This is useful for interop with existing code, like `lemol/ant-design-icons-elm-ui`. + +`element` can also be used in combination with `with` +to supply arguments to an elm-ui element from the context. + -} element : Element.Element msg -> Element context msg element elem = @@ -369,6 +376,144 @@ attr elem = Attribute <| \_ -> elem +{-| Construct an element from the original elm-ui supplying a complete context. + +This can be used as the final step before +embedding sub-elements that use elm-ui-with-context +in a bigger elm-ui element +without elm-ui-with-context becoming "infectious" +and forcing higher-level elements to adopt context. + +For example + + module SomePackage exposing (Context, view) + + import Element.WithContext exposing (Element) + + type alias Context = + { backgroundColor : Element.WithContext.Color + , foregroundColor : Element.WithContext.Color + } + + view : Element Context msg + +in your code + + module YourCode exposing (main) + + import Element exposing (Element) + import Element.WithContext + + view : Element msg + view = + Element.WithContext.column + [] + [ ... + , SomePackage.view + -- app currently only supports a black theme + -- but `SomePackage`'s theme must be configured ↓ + |> Element.WithContext.toElement + { backgroundColor = Element.WithContext.rgb 0 0 0 + , foregroundColor = Element.WithContext.rgb 1 1 1 + } + ] + +-} +toElement : context -> Element context msg -> Element.Element msg +toElement context (Element f) = + f context + + +{-| Construct an attribute for the original elm-ui supplying a complete context. +[`toElement`](#toElement) documents use-cases. +-} +toAttribute : context -> Attribute context msg -> Element.Attribute msg +toAttribute context (Attribute f) = + f context + + +{-| Construct an attribute from the original elm-ui by supplying a complete context. +[`toElement`](#toElement) documents use-cases. +-} +toAttr : context -> Attr context decorative msg -> Element.Attr decorative msg +toAttr context (Attribute f) = + f context + + +{-| Change how the context looks for a given element. + +This is used to embed elm-ui-with-context +from another origin (like a package) +with another context type. + +This is quite similar to [`map`](#map) +but instead of transforming the msg type to match the outer type, +it transforms the context type to match the outer type. + +For example + + module SomePackage exposing (Context, view) + + import Element.WithContext exposing (Element) + + type alias Context = + { backgroundColor : Element.WithContext.Color + , foregroundColor : Element.WithContext.Color + } + + view : Element Context msg + +in your code + + module YourCode exposing (main) + + import Element.WithContext exposing (Element) + + type alias Context = + { theme : Theme } + + type Theme + = Black + | White + + themeToColors : + Theme + -> { background : Element.WithContext.Color + , foreground : Element.WithContext.Color + } + + view : Element Context msg + view = + Element.WithContext.column + [] + [ ... + , Element.WithContext.mapContextInElement + (\{ theme } -> + let + themeColors = + theme |> themeToColors + in + { backgroundColor = themeColors.background + , foregroundColor = themeColors.foreground + } + ) + SomePackage.view + ] + +-} +mapContextInElement : (outerContext -> innerContext) -> Element innerContext msg -> Element outerContext msg +mapContextInElement outerToInnerContext (Element f) = + Element (outerToInnerContext >> f) + + +{-| Change how the context looks for a given attribute. +[`mapContextInElement`](#mapContextInElement) documents use-cases. +-} +mapContextInAttr : (outerContext -> innerContext) -> Attr innerContext decorative msg -> Attr outerContext decorative msg +mapContextInAttr outerToInnerContext (Attribute f) = + Attribute (outerToInnerContext >> f) + + {-| -} map : (msg -> msg1) -> Element context msg -> Element context msg1 map f (Element g) = @@ -497,14 +642,14 @@ fillPortion = {-| This is your top level node where you can turn `Element` into `Html`. -} layout : context -> List (Attribute context msg) -> Element context msg -> Html msg -layout context attrs (Element f) = - Element.layout (attributes context attrs) (f context) +layout context attrs elem = + Element.layout (attributes context attrs) (elem |> toElement context) {-| -} layoutWith : context -> { options : List Option } -> List (Attribute context msg) -> Element context msg -> Html msg -layoutWith context options attrs (Element f) = - Element.layoutWith options (attributes context attrs) (f context) +layoutWith context options attrs elem = + Element.layoutWith options (attributes context attrs) (elem |> toElement context) {-| -} @@ -917,8 +1062,8 @@ downloadAs = createNearby : (Element.Element msg -> Element.Attribute msg) -> Element context msg -> Attribute context msg -createNearby toAttr (Element f) = - Attribute (f >> toAttr) +createNearby elementToAttr (Element f) = + Attribute (f >> elementToAttr) {-| -} From 5ab0631fcce976fe3ac3b2899455ec9c05569453 Mon Sep 17 00:00:00 2001 From: lue-bird Date: Sun, 11 Sep 2022 10:16:03 +0200 Subject: [PATCH 6/7] `element` + `with` example --- src/Element/WithContext.elm | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/src/Element/WithContext.elm b/src/Element/WithContext.elm index 6121cb7..10b8512 100644 --- a/src/Element/WithContext.elm +++ b/src/Element/WithContext.elm @@ -356,6 +356,56 @@ htmlAttribute child = `element` can also be used in combination with `with` to supply arguments to an elm-ui element from the context. +For example + + module SomePackage exposing (Context, view) + + import Element exposing (Element) + + view : + { backgroundColor : Element.Color + , foregroundColor : Element.Color + } + -> Element msg + +in your code + + module YourCode exposing (main) + + import Element.WithContext exposing (Element) + + type alias Context = + { theme : Theme } + + type Theme + = Black + | White + + themeToColors : + Theme + -> { background : Element.WithContext.Color + , foreground : Element.WithContext.Color + } + + view : Element Context msg + view = + Element.WithContext.column + [] + [ ... + , Element.WithContext.with + (\{ theme } -> + let + themeColors = + theme |> themeToColors + in + SomePackage.view + { backgroundColor = themeColors.background + , foregroundColor = themeColors.foreground + } + |> Element.WithContext.element + ) + ] + -} element : Element.Element msg -> Element context msg element elem = From 70a703d4bda5860ea489de96118d1702b77a72b6 Mon Sep 17 00:00:00 2001 From: lue-bird Date: Sun, 11 Sep 2022 11:17:32 +0200 Subject: [PATCH 7/7] `mapContextInElement` to `map` comparison correct --- src/Element/WithContext.elm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Element/WithContext.elm b/src/Element/WithContext.elm index 10b8512..e164215 100644 --- a/src/Element/WithContext.elm +++ b/src/Element/WithContext.elm @@ -497,8 +497,8 @@ from another origin (like a package) with another context type. This is quite similar to [`map`](#map) -but instead of transforming the msg type to match the outer type, -it transforms the context type to match the outer type. +but instead of transforming the inner msg type to match the outer type, +it transforms the outer context type to match the inner type. For example