From 9694f15b1cb174fb3372871beb2a35284529eb76 Mon Sep 17 00:00:00 2001
From: Cody Mullins <joshua.c.mullins@gmail.com>
Date: Sat, 4 May 2024 14:54:11 -0400
Subject: [PATCH 1/2] Fix theme customization issues

---
 .../HostApplicationBuilderExtensions.cs       |  4 +-
 .../ComponentStyle.cs                         | 22 +++---
 .../IPureTheme.cs                             | 19 -----
 .../PureComponent.cs                          |  2 +-
 .../PureTheme.cs                              | 51 ++++++++++++
 .../Common/DefaultTheme.cs                    | 77 +++++++++----------
 .../WebAssemblyHostBuilderExtensions.cs       |  4 +-
 7 files changed, 105 insertions(+), 74 deletions(-)
 delete mode 100644 src/Pure.Blazor.Components.Primitives/IPureTheme.cs
 create mode 100644 src/Pure.Blazor.Components.Primitives/PureTheme.cs

diff --git a/src/Pure.Blazor.Components.AspNetCore/HostApplicationBuilderExtensions.cs b/src/Pure.Blazor.Components.AspNetCore/HostApplicationBuilderExtensions.cs
index b42d734..44d9954 100644
--- a/src/Pure.Blazor.Components.AspNetCore/HostApplicationBuilderExtensions.cs
+++ b/src/Pure.Blazor.Components.AspNetCore/HostApplicationBuilderExtensions.cs
@@ -12,7 +12,7 @@ namespace Pure.Blazor.Components.AspNetCore;
 public static class HostApplicationBuilderExtensions
 {
     public static IHostApplicationBuilder AddPureBlazorComponents(this IHostApplicationBuilder builder,
-        IPureTheme? theme = null)
+        PureTheme? theme = null)
     {
         // javascript
         builder.Services.AddScoped<IElementUtils, ElementUtils>();
@@ -24,7 +24,7 @@ public static IHostApplicationBuilder AddPureBlazorComponents(this IHostApplicat
         builder.Services.AddCascadingValue(sp =>
         {
             theme ??= new DefaultTheme();
-            var source = new CascadingValueSource<IPureTheme>(theme, isFixed: true);
+            var source = new CascadingValueSource<PureTheme>(theme, isFixed: true);
             return source;
         });
 
diff --git a/src/Pure.Blazor.Components.Primitives/ComponentStyle.cs b/src/Pure.Blazor.Components.Primitives/ComponentStyle.cs
index 032bb8a..fed06b5 100644
--- a/src/Pure.Blazor.Components.Primitives/ComponentStyle.cs
+++ b/src/Pure.Blazor.Components.Primitives/ComponentStyle.cs
@@ -1,19 +1,19 @@
 namespace Pure.Blazor.Components.Primitives;
 
-public class ComponentStyle
+public record ComponentStyle
 {
-    private readonly IReadOnlyDictionary<Accent, string> accents;
-    private readonly IReadOnlyDictionary<PureVariant, Dictionary<Accent, string>> variants;
-    private readonly IReadOnlyDictionary<PureSize, string> sizes;
+    public readonly IReadOnlyDictionary<Accent, string> Accents;
+    public readonly IReadOnlyDictionary<PureVariant, Dictionary<Accent, string>> Variants;
+    public readonly IReadOnlyDictionary<PureSize, string> Sizes;
 
     public ComponentStyle(string baseStyle,
         IReadOnlyDictionary<Accent, string>? accents,
         IReadOnlyDictionary<PureVariant, Dictionary<Accent, string>>? variants,
         IReadOnlyDictionary<PureSize, string>? sizes)
     {
-        this.accents = accents ?? new Dictionary<Accent, string>();
-        this.variants = variants ?? new Dictionary<PureVariant, Dictionary<Accent, string>>();
-        this.sizes = sizes ?? new Dictionary<PureSize, string>();
+        this.Accents = accents ?? new Dictionary<Accent, string>();
+        this.Variants = variants ?? new Dictionary<PureVariant, Dictionary<Accent, string>>();
+        this.Sizes = sizes ?? new Dictionary<PureSize, string>();
         Base = baseStyle;
     }
 
@@ -36,12 +36,12 @@ public ComponentStyle(string baseStyle,
 
     public string Accent(Accent accent)
     {
-        return accents.TryGetValue(accent, out var value) ? value : string.Empty;
+        return Accents.TryGetValue(accent, out var value) ? value : string.Empty;
     }
 
     public string Variant(PureVariant variant, Accent accent)
     {
-        if (variants.TryGetValue(variant, out var value) && value.TryGetValue(accent, out var style))
+        if (Variants.TryGetValue(variant, out var value) && value.TryGetValue(accent, out var style))
         {
             return style;
         }
@@ -51,6 +51,6 @@ public string Variant(PureVariant variant, Accent accent)
 
     public string Size(PureSize size)
     {
-        return sizes.TryGetValue(size, out var value) ? value : string.Empty;
+        return Sizes.TryGetValue(size, out var value) ? value : string.Empty;
     }
-}
\ No newline at end of file
+}
diff --git a/src/Pure.Blazor.Components.Primitives/IPureTheme.cs b/src/Pure.Blazor.Components.Primitives/IPureTheme.cs
deleted file mode 100644
index 6715625..0000000
--- a/src/Pure.Blazor.Components.Primitives/IPureTheme.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-namespace Pure.Blazor.Components.Primitives;
-
-public interface IPureTheme
-{
-    public ButtonDefaults ButtonDefaults { get; set; }
-    public IStylePrioritizer StylePrioritizer { get; set; }
-    public Dictionary<string, ComponentStyle> Styles { get; set; }
-
-    public ComponentStyle GetStyle(Type type)
-    {
-        return GetStyleByName(type.Name);
-    }
-
-    public ComponentStyle GetStyleByName(string name)
-    {
-        // TODO: decide if we want this to be an exceptional event
-        return Styles.GetValueOrDefault(name) ?? new ComponentStyle("", null, null, null);
-    }
-}
diff --git a/src/Pure.Blazor.Components.Primitives/PureComponent.cs b/src/Pure.Blazor.Components.Primitives/PureComponent.cs
index 99f8229..1f6e73d 100644
--- a/src/Pure.Blazor.Components.Primitives/PureComponent.cs
+++ b/src/Pure.Blazor.Components.Primitives/PureComponent.cs
@@ -26,7 +26,7 @@ protected override void OnParametersSet()
     ///     The current theme styles
     /// </summary>
     [CascadingParameter]
-    public required IPureTheme PureTheme { get; set; }
+    public required PureTheme PureTheme { get; set; }
 
     [Parameter] public RenderFragment? ChildContent { get; set; }
 
diff --git a/src/Pure.Blazor.Components.Primitives/PureTheme.cs b/src/Pure.Blazor.Components.Primitives/PureTheme.cs
new file mode 100644
index 0000000..2e7c011
--- /dev/null
+++ b/src/Pure.Blazor.Components.Primitives/PureTheme.cs
@@ -0,0 +1,51 @@
+namespace Pure.Blazor.Components.Primitives;
+
+public record PureTheme
+{
+    public ButtonDefaults ButtonDefaults { get; set; } = new();
+    public IStylePrioritizer? StylePrioritizer { get; set; }
+    public Dictionary<string, ComponentStyle> Styles { get; set; } = new();
+
+    public ComponentStyle GetStyle(Type type)
+    {
+        return GetStyleByName(type.Name);
+    }
+
+    public ComponentStyle GetStyleByName(string name)
+    {
+        // TODO: decide if we want this to be an exceptional event
+        return Styles.GetValueOrDefault(name) ?? new ComponentStyle("", null, null, null);
+    }
+
+    /// <summary>
+    /// Merges the styles into the current theme, overwriting any existing styles.
+    /// </summary>
+    /// <param name="styles"></param>
+    public void Merge(Dictionary<string, ComponentStyle> styles)
+    {
+        Styles = Styles.MergeLeft(styles);
+    }
+}
+
+internal static class DictionaryExtensions
+{
+    // Works in C#3/VS2008:
+    // Returns a new dictionary of this ... others merged leftward.
+    // Keeps the type of 'this', which must be default-instantiable.
+    // Example:
+    //   result = map.MergeLeft(other1, other2, ...)
+    public static T MergeLeft<T,K,V>(this T me, params IDictionary<K,V>[] others)
+        where T : IDictionary<K,V>, new()
+    {
+        T newMap = new T();
+        foreach (IDictionary<K,V> src in
+                 (new List<IDictionary<K,V>> { me }).Concat(others)) {
+            // ^-- echk. Not quite there type-system.
+            foreach (KeyValuePair<K,V> p in src) {
+                newMap[p.Key] = p.Value;
+            }
+        }
+        return newMap;
+    }
+
+}
diff --git a/src/Pure.Blazor.Components/Common/DefaultTheme.cs b/src/Pure.Blazor.Components/Common/DefaultTheme.cs
index 98fe476..17f7f98 100644
--- a/src/Pure.Blazor.Components/Common/DefaultTheme.cs
+++ b/src/Pure.Blazor.Components/Common/DefaultTheme.cs
@@ -6,50 +6,49 @@
 
 namespace Pure.Blazor.Components.Common;
 
-public class DefaultTheme : IPureTheme
+public record DefaultTheme : PureTheme
 {
-    public ButtonDefaults ButtonDefaults { get; set; } = new()
+    public DefaultTheme()
     {
-        PressEffect = Effect.InsetShadow,
-        HoverEffect = Effect.Unset
-    };
-
-    public IStylePrioritizer StylePrioritizer { get; set; } = new StylePrioritizer();
-    public Dictionary<string, ComponentStyle> Styles { get; set; } = new()
-    {
-        {
-            nameof(PureButton),
-            new ComponentStyle(ButtonStyles.Base, null, ButtonStyles.Variants, ButtonStyles.Sizes)
-        },
+        ButtonDefaults.PressEffect = Effect.InsetShadow;
+        ButtonDefaults.HoverEffect = Effect.Unset;
+        StylePrioritizer = new StylePrioritizer();
+        Styles = new Dictionary<string, ComponentStyle>
         {
-            nameof(PureIconButton),
-            new ComponentStyle(ButtonStyles.Base, null, ButtonStyles.Variants, ButtonStyles.Sizes)
-        },
-        {
-            nameof(PureDropdown),
-            new ComponentStyle(DropdownStyles.Base, null, null, DropdownStyles.Sizes)
             {
-                InnerContainer = new ComponentStyle(DropdownStyles.Container.Base, null, null, null)
-            }
-        },
-        {
-            nameof(PureDropdownItem),
-            new ComponentStyle(DropdownStyles.MenuItem.Base, DropdownStyles.MenuItem.Accents, null,
-                DropdownStyles.MenuItem.Sizes)
-        },
-        { nameof(PureBanner), new ComponentStyle(BannerStyles.Base, null, BannerStyles.Variants, null) },
-        { nameof(PureLink), new ComponentStyle(LinkStyles.Base, null, null, null) },
-        { nameof(PureBadge), new ComponentStyle("", null, BadgeStyles.Variants, null) },
-        { nameof(PureAlert), new ComponentStyle(AlertStyles.Base, AlertStyles.Accents, null, null) },
-        {
-            nameof(PureColorIndicator),
-            new ComponentStyle("", null, null, null)
+                nameof(PureButton),
+                new ComponentStyle(ButtonStyles.Base, null, ButtonStyles.Variants, ButtonStyles.Sizes)
+            },
+            {
+                nameof(PureIconButton),
+                new ComponentStyle(ButtonStyles.Base, null, ButtonStyles.Variants, ButtonStyles.Sizes)
+            },
+            {
+                nameof(PureDropdown),
+                new ComponentStyle(DropdownStyles.Base, null, null, DropdownStyles.Sizes)
+                {
+                    InnerContainer = new ComponentStyle(DropdownStyles.Container.Base, null, null, null)
+                }
+            },
+            {
+                nameof(PureDropdownItem),
+                new ComponentStyle(DropdownStyles.MenuItem.Base, DropdownStyles.MenuItem.Accents, null,
+                    DropdownStyles.MenuItem.Sizes)
+            },
+            { nameof(PureBanner), new ComponentStyle(BannerStyles.Base, null, BannerStyles.Variants, null) },
+            { nameof(PureLink), new ComponentStyle(LinkStyles.Base, null, null, null) },
+            { nameof(PureBadge), new ComponentStyle("", null, BadgeStyles.Variants, null) },
+            { nameof(PureAlert), new ComponentStyle(AlertStyles.Base, AlertStyles.Accents, null, null) },
             {
-                InnerContainer = new ComponentStyle("", IndicatorStyles.Background, null, null)
+                nameof(PureColorIndicator),
+                new ComponentStyle("", null, null, null)
                 {
-                    OuterContainer = new ComponentStyle("", IndicatorStyles.Foreground, null, null)
+                    InnerContainer = new ComponentStyle("", IndicatorStyles.Background, null, null)
+                    {
+                        OuterContainer = new ComponentStyle("", IndicatorStyles.Foreground, null, null)
+                    }
                 }
-            }
-        },
-    };
+            },
+        };
+    }
 }
diff --git a/src/Pure.Blazor.Components/WebAssemblyHostBuilderExtensions.cs b/src/Pure.Blazor.Components/WebAssemblyHostBuilderExtensions.cs
index 09c1093..189308a 100644
--- a/src/Pure.Blazor.Components/WebAssemblyHostBuilderExtensions.cs
+++ b/src/Pure.Blazor.Components/WebAssemblyHostBuilderExtensions.cs
@@ -12,7 +12,7 @@ namespace Pure.Blazor.Components;
 public static class WebAssemblyHostBuilderExtensions
 {
     public static WebAssemblyHostBuilder AddPureBlazorComponents(this WebAssemblyHostBuilder builder,
-        IPureTheme? theme = null)
+        PureTheme? theme = null)
     {
         // javascript
         builder.Services.AddSingleton<IElementUtils, ElementUtils>();
@@ -23,7 +23,7 @@ public static WebAssemblyHostBuilder AddPureBlazorComponents(this WebAssemblyHos
         builder.Services.AddCascadingValue(sp =>
         {
             theme ??= new DefaultTheme();
-            var source = new CascadingValueSource<IPureTheme>(theme, isFixed: true);
+            var source = new CascadingValueSource<PureTheme>(theme, isFixed: true);
             return source;
         });
         return builder;

From afa9efeec02bf948920f428024a86d2814526ef9 Mon Sep 17 00:00:00 2001
From: Cody Mullins <joshua.c.mullins@gmail.com>
Date: Sat, 4 May 2024 15:05:14 -0400
Subject: [PATCH 2/2] add missing attribution

---
 src/Pure.Blazor.Components.Primitives/PureTheme.cs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/Pure.Blazor.Components.Primitives/PureTheme.cs b/src/Pure.Blazor.Components.Primitives/PureTheme.cs
index 2e7c011..db7146a 100644
--- a/src/Pure.Blazor.Components.Primitives/PureTheme.cs
+++ b/src/Pure.Blazor.Components.Primitives/PureTheme.cs
@@ -27,6 +27,7 @@ public void Merge(Dictionary<string, ComponentStyle> styles)
     }
 }
 
+// https://stackoverflow.com/a/2679857/783284
 internal static class DictionaryExtensions
 {
     // Works in C#3/VS2008: