| | | 1 | | namespace Allyaria.Theming.Helpers; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Provides a fluent, immutable-safe builder for constructing CSS property/value declarations used throughout the Allya |
| | | 5 | | /// theming system. |
| | | 6 | | /// <para> |
| | | 7 | | /// <see cref="CssBuilder" /> is designed to aggregate style key-value pairs in a deterministic order using a |
| | | 8 | | /// <see cref="SortedDictionary{TKey,TValue}" /> for consistent and predictable CSS output. |
| | | 9 | | /// </para> |
| | | 10 | | /// </summary> |
| | | 11 | | /// <remarks> |
| | | 12 | | /// This class is central to how Allyaria generates serialized CSS from the strongly-typed <see cref="IStyleValue" /> an |
| | | 13 | | /// <see cref="StyleGroup" /> abstractions. |
| | | 14 | | /// </remarks> |
| | | 15 | | public sealed class CssBuilder |
| | | 16 | | { |
| | | 17 | | /// <summary> |
| | | 18 | | /// The underlying collection of CSS property/value pairs. The use of <see cref="SortedDictionary{TKey,TValue}" /> e |
| | | 19 | | /// consistent ordering of CSS properties in the final output string, improving diffability and test determinism. |
| | | 20 | | /// </summary> |
| | 116 | 21 | | private readonly SortedDictionary<string, string> _styles = new(); |
| | | 22 | | |
| | | 23 | | /// <summary> |
| | | 24 | | /// Adds a new CSS property/value pair to the builder, if both are valid and non-empty. |
| | | 25 | | /// <para> |
| | | 26 | | /// Property names and prefixes are automatically converted to CSS-compliant formats using the <c>ToCssName()</c> he |
| | | 27 | | /// (e.g., PascalCase → kebab-case). |
| | | 28 | | /// </para> |
| | | 29 | | /// </summary> |
| | | 30 | | /// <param name="name">The CSS property name or token name to add. This will be normalized to kebab-case.</param> |
| | | 31 | | /// <param name="value"> |
| | | 32 | | /// The CSS value associated with the property. Must be non-empty and already validated by the corresponding |
| | | 33 | | /// <see cref="IStyleValue" /> implementation. |
| | | 34 | | /// </param> |
| | | 35 | | /// <param name="varPrefix"> |
| | | 36 | | /// An optional variable prefix used to generate CSS custom properties (e.g., <c>--theme-color-primary</c>). If prov |
| | | 37 | | /// the final property name becomes <c>--{prefix}-{property}</c>. |
| | | 38 | | /// </param> |
| | | 39 | | /// <returns>The current <see cref="CssBuilder" /> instance, enabling fluent method chaining.</returns> |
| | | 40 | | /// <remarks> |
| | | 41 | | /// If the property or value is empty or invalid, the call is ignored and the builder remains unchanged. |
| | | 42 | | /// </remarks> |
| | | 43 | | public CssBuilder Add(string? name, string? value, string? varPrefix = "") |
| | | 44 | | { |
| | 88 | 45 | | if (string.IsNullOrWhiteSpace(value: name) || string.IsNullOrWhiteSpace(value: value)) |
| | | 46 | | { |
| | 6 | 47 | | return this; |
| | | 48 | | } |
| | | 49 | | |
| | | 50 | | string property; |
| | | 51 | | |
| | | 52 | | try |
| | | 53 | | { |
| | 82 | 54 | | property = name.FromPrefixedCase().ToCssName(); |
| | 81 | 55 | | } |
| | 1 | 56 | | catch |
| | | 57 | | { |
| | 1 | 58 | | property = string.Empty; |
| | 1 | 59 | | } |
| | | 60 | | |
| | 82 | 61 | | if (string.IsNullOrWhiteSpace(value: property)) |
| | | 62 | | { |
| | 1 | 63 | | return this; |
| | | 64 | | } |
| | | 65 | | |
| | 81 | 66 | | var prefix = varPrefix.ToCssName(); |
| | | 67 | | |
| | 81 | 68 | | var propertyName = string.IsNullOrWhiteSpace(value: prefix) |
| | 81 | 69 | | ? property |
| | 81 | 70 | | : $"--{prefix}-{property}"; |
| | | 71 | | |
| | 81 | 72 | | _ = _styles.TryAdd(key: propertyName, value: value); |
| | | 73 | | |
| | 81 | 74 | | return this; |
| | | 75 | | } |
| | | 76 | | |
| | | 77 | | /// <summary>Adds multiple CSS property/value pairs to the builder by parsing a semicolon-delimited list.</summary> |
| | | 78 | | /// <param name="cssList"> |
| | | 79 | | /// A semicolon-separated list of CSS declarations in the form <c>property:value</c>. Entries with missing property |
| | | 80 | | /// value segments are ignored. |
| | | 81 | | /// </param> |
| | | 82 | | /// <returns>The current <see cref="CssBuilder" /> instance, enabling fluent method chaining.</returns> |
| | | 83 | | /// <remarks> |
| | | 84 | | /// This method provides a convenience parser for raw CSS fragments and forwards each parsed entry to |
| | | 85 | | /// <see cref="Add(string?, string?, string?)" />. Invalid entries are safely skipped. |
| | | 86 | | /// </remarks> |
| | | 87 | | public CssBuilder AddRange(string? cssList) |
| | | 88 | | { |
| | 10 | 89 | | if (string.IsNullOrWhiteSpace(value: cssList)) |
| | | 90 | | { |
| | 3 | 91 | | return this; |
| | | 92 | | } |
| | | 93 | | |
| | 7 | 94 | | var split = cssList.Split(separator: ';', options: StringSplitOptions.RemoveEmptyEntries); |
| | | 95 | | |
| | 40 | 96 | | foreach (var item in split) |
| | | 97 | | { |
| | 13 | 98 | | var pair = item.Split(separator: ':', options: StringSplitOptions.RemoveEmptyEntries); |
| | | 99 | | |
| | 13 | 100 | | if (pair.Length < 2) |
| | | 101 | | { |
| | | 102 | | continue; |
| | | 103 | | } |
| | | 104 | | |
| | 11 | 105 | | Add(name: pair[0], value: pair[1]); |
| | | 106 | | } |
| | | 107 | | |
| | 7 | 108 | | return this; |
| | | 109 | | } |
| | | 110 | | |
| | | 111 | | /// <summary> |
| | | 112 | | /// Builds the final CSS representation of all accumulated styles in this builder. |
| | | 113 | | /// <para> |
| | | 114 | | /// The result is a semicolon-separated list of <c>property:value</c> pairs, ready for inline or stylesheet inclusio |
| | | 115 | | /// </para> |
| | | 116 | | /// </summary> |
| | | 117 | | /// <returns> |
| | | 118 | | /// A single CSS string containing all property/value pairs. If no styles are defined, returns <see cref="string.Emp |
| | | 119 | | /// </returns> |
| | | 120 | | public override string ToString() |
| | | 121 | | { |
| | 116 | 122 | | if (_styles.Count is 0) |
| | | 123 | | { |
| | 68 | 124 | | return string.Empty; |
| | | 125 | | } |
| | | 126 | | |
| | 48 | 127 | | var list = new List<string>(); |
| | | 128 | | |
| | 254 | 129 | | foreach (var styleValue in _styles) |
| | | 130 | | { |
| | 79 | 131 | | list.Add(item: $"{styleValue.Key}:{styleValue.Value}"); |
| | | 132 | | } |
| | | 133 | | |
| | 48 | 134 | | return string.Join(separator: ';', values: list) + ";"; |
| | | 135 | | } |
| | | 136 | | } |