Skip to content

Add @attributes merging, class prefixing, lazy evaluation, and NullIfEmpty helpers#2

Draft
Copilot wants to merge 7 commits intomainfrom
copilot/improve-class-merging-functionality
Draft

Add @attributes merging, class prefixing, lazy evaluation, and NullIfEmpty helpers#2
Copilot wants to merge 7 commits intomainfrom
copilot/improve-class-merging-functionality

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Dec 30, 2025

Summary

Successfully implemented all features from the issue to achieve feature parity with BlazorComponentUtilities plus recommended enhancements.

✅ Features Implemented

1. Merge class from @attributes

  • AddClassFromAttributes() method for ClassBuilder
  • Null-safe, handles missing keys and empty values
  • Tokenizes by whitespace, deduplicates tokens
  • Does NOT apply prefixes to attribute classes
  • Exception-safe implementation with try-finally

2. Merge style from @attributes

  • AddStyleFromAttributes() method for StyleBuilder
  • Null-safe, handles missing keys and empty values
  • Properly normalizes semicolons
  • Builder styles declared first; attributes override in CSS

3. Class prefixing

  • SetPrefix() and ClearPrefix() methods
  • Custom separator support (default: "-")
  • Prefixes only builder-added classes, not attribute classes
  • Handles multi-token adds correctly

4. NullIfEmpty() helper

  • Available on ClassBuilder and StyleBuilder
  • Returns null for empty/whitespace strings
  • Prevents Blazor from rendering empty attributes

5. Lazy evaluation overloads

  • ClassBuilder: Add(Func<bool>, string), Add(bool canAdd, Func<string>)
  • StyleBuilder: Add(Func<bool>, ...), Add(..., bool canAdd, Func<string>), Add(..., bool canAdd, Func<double>, ...)
  • AttributeBuilder: Add(bool canAdd, string, Func<object?>)
  • Guarantee: Factories never invoked when condition is false
  • Consistent naming: All boolean parameters use canAdd to match existing conventions
  • Consistent ordering: Condition parameter comes first, matching AddIf pattern

6. Raw style injection

  • AddVerbatim() method for StyleBuilder
  • Explicit alias for clarity

📊 Testing

  • 126 tests total (80 original + 46 new)
  • 100% passing
  • Comprehensive edge case coverage

📚 Documentation

  • 4 new README sections with examples
  • Updated API reference tables
  • Clear explanations of behavior

🔧 Code Quality

  • Minimal, surgical changes
  • Backward compatible
  • Exception-safe implementations
  • Optimized performance (materialized enumerations)
  • Helper method to reduce duplication
  • Consistent parameter naming and ordering across all methods

📁 Files Changed

  • 7 files modified
  • +1,070 lines added
  • 0 breaking changes
Original prompt

This section details on the original issue you should resolve

<issue_title>Improve based on BlazorComponentUtilities</issue_title>
<issue_description># ✅ Feature checklist (parity gaps + recommended additions)

This issue tracks features currently missing in Kebechet.Blazor.ClassBuilder (compared to BlazorComponentUtilities) plus closely-related additions that complete the @attributes story.


✅ 1) Merge class from @attributes

  • Add API to merge existing class values from an attributes dictionary (e.g., Blazor AdditionalAttributes / @attributes) into ClassBuilder.
  • Null-safe: attributes == null → no-op.
  • Missing key ("class") → no-op.
  • Empty/whitespace value → no-op.
  • Non-string values: decide behavior (recommended: ToString()), document it.
  • Tokenize by whitespace, trim, normalize.
  • (Recommended) Deduplicate tokens when merging.
  • Document merge order (builder vs attributes first).

Proposed API

  • ClassBuilder AddClassFromAttributes(IReadOnlyDictionary<string, object?>? attributes, string key = "class")

Example

var css = new ClassBuilder()
  .Add("btn")
  .AddClassFromAttributes(AdditionalAttributes)
  .Build();

✅ 2) Merge style from @attributes

  • Add API to merge existing style values from an attributes dictionary into StyleBuilder.
  • Null-safe: attributes == null → no-op.
  • Missing key ("style") → no-op.
  • Empty/whitespace value → no-op.
  • Non-string values: decide behavior (recommended: ToString()), document it.
  • Handle leading/trailing semicolons gracefully.
  • Define deterministic ordering (document which side comes first).
  • Define precedence when the same CSS property appears multiple times (recommended: builder wins).

Proposed API

  • StyleBuilder AddStyleFromAttributes(IReadOnlyDictionary<string, object?>? attributes, string key = "style")

Example

var style = new StyleBuilder()
  .Add("width", 100, "px")
  .AddStyleFromAttributes(AdditionalAttributes)
  .Build();

✅ 3) Class prefixing (SetPrefix-style)

  • Add ability to apply a prefix to every class token added (useful for BEM/scoped design systems).
  • Support custom separator (default -).
  • Decide whether prefix applies to existing tokens or only future adds (document).
  • Decide whether prefix applies to merged @attributes classes (recommended: do not prefix attribute classes).
  • (Optional) Avoid double-prefixing when token already starts with prefix.
  • Ensure multi-token adds are prefixed per token.

Proposed API

  • ClassBuilder SetPrefix(string? prefix, string separator = "-")
  • (Optional) ClassBuilder ClearPrefix()

Example

var css = new ClassBuilder()
  .SetPrefix("sf")
  .Add("btn")
  .Add("primary")
  .Build(); // "sf-btn sf-primary"

✅ 4) NullIfEmpty() helper

  • Add helper returning null instead of empty/whitespace so Blazor won’t render empty attributes.
  • Works for ClassBuilder and StyleBuilder (optionally AttributeBuilder too, if it returns strings).

Proposed API

  • string? ClassBuilder.NullIfEmpty()
  • string? StyleBuilder.NullIfEmpty()

Example

var classValue = new ClassBuilder()
  .AddIf(false, "x")
  .NullIfEmpty(); // null

✅ 5) Lazy evaluation overloads (avoid work unless needed)

  • Support Func<bool> conditions to evaluate condition late.
  • Support factories (Func<string>, Func<object?>) so expensive values are only computed if condition is true.
  • Guarantee: when condition is false, the factory is never invoked.
  • Provide consistent lazy overloads across ClassBuilder, StyleBuilder, and AttributeBuilder (recommended).

Proposed API (examples)

  • ClassBuilder Add(Func<bool> when, string value)
  • ClassBuilder Add(bool when, Func<string> valueFactory)
  • StyleBuilder Add(string name, bool when, Func<string> valueFactory, string? unit = null)
  • AttributeBuilder Add(string name, bool when, Func<object?> valueFactory)

Example

var css = new ClassBuilder()
  .Add(isEnabled, () => ExpensiveComputeCssClass())
  .Build();

✅ 6) Raw style injection (alignment with merge story)

If this already exists in your public API (e.g., AddVerbatim), keep it; if not, add it and document it.

  • Provide an explicit way to append a raw style snippet (without parsing).
  • Document interaction/ordering with AddStyleFromAttributes.
  • Ensure it preserves user-provided formatting reasonably (trim + optional semicolon normalization).

Proposed API

  • StyleBuilder AddVerbatim(string cssSnippet)
  • (or) StyleBuilder AddRaw(string cssSnippet)

Example

var style = new StyleBuilder()
  .AddVerbatim("backdrop-filter: blur(10px)")
  .Build();

✅ Test checklist (must-have)

Class merg...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 4 commits December 30, 2025 10:39
…luation

Co-authored-by: Kebechet <37485681+Kebechet@users.noreply.github.com>
Co-authored-by: Kebechet <37485681+Kebechet@users.noreply.github.com>
…cumentation

Co-authored-by: Kebechet <37485681+Kebechet@users.noreply.github.com>
Co-authored-by: Kebechet <37485681+Kebechet@users.noreply.github.com>
Copilot AI changed the title [WIP] Add class merging functionality to ClassBuilder Add @attributes merging, class prefixing, lazy evaluation, and NullIfEmpty helpers Dec 30, 2025
Copilot AI requested a review from Kebechet December 30, 2025 10:47
Comment thread src/Blazor.ClassBuilder/AttributeBuilder.cs Outdated
Co-authored-by: Kebechet <37485681+Kebechet@users.noreply.github.com>
Comment on lines +78 to +80
public AttributeBuilder Add(string parameterName, bool when, Func<object?> valueFactory)
public AttributeBuilder Add(string parameterName, bool canAdd, Func<object?> valueFactory)
{
if (!when)
if (!canAdd)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

order is still incorrect. Look at other ones and fix

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 4543e23. Changed parameter order to Add(bool canAdd, string parameterName, Func<object?> valueFactory) to match the AddIf pattern where the condition comes first.

Co-authored-by: Kebechet <37485681+Kebechet@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve based on BlazorComponentUtilities

2 participants