FractalDataWorks.UI.Abstractions 0.1.0-preview.11

This is a prerelease version of FractalDataWorks.UI.Abstractions.
dotnet add package FractalDataWorks.UI.Abstractions --version 0.1.0-preview.11
                    
NuGet\Install-Package FractalDataWorks.UI.Abstractions -Version 0.1.0-preview.11
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="FractalDataWorks.UI.Abstractions" Version="0.1.0-preview.11" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="FractalDataWorks.UI.Abstractions" Version="0.1.0-preview.11" />
                    
Directory.Packages.props
<PackageReference Include="FractalDataWorks.UI.Abstractions" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add FractalDataWorks.UI.Abstractions --version 0.1.0-preview.11
                    
#r "nuget: FractalDataWorks.UI.Abstractions, 0.1.0-preview.11"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package FractalDataWorks.UI.Abstractions@0.1.0-preview.11
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=FractalDataWorks.UI.Abstractions&version=0.1.0-preview.11&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=FractalDataWorks.UI.Abstractions&version=0.1.0-preview.11&prerelease
                    
Install as a Cake Tool

FractalDataWorks.UI.Abstractions

Pure abstraction layer for UI components using CRTP (Curiously Recurring Template Pattern). Provides framework-agnostic base classes for building type-safe, composable UI components that work across Blazor, Terminal UI, and other UI frameworks.

Overview

This package provides the foundational CRTP-based component architecture for the FractalDataWorks UI system. It has zero dependencies on any UI framework, making it the base for building UI components that can render to multiple targets.

Target Framework: netstandard2.0

Dependencies: FractalDataWorks.Collections (for TypeCollection support)

Installation

dotnet add package FractalDataWorks.UI.Abstractions

Core Architecture

CRTP Pattern

The Curiously Recurring Template Pattern (CRTP) enables type-safe self-references in component hierarchies. The TSelf parameter refers to the derived class itself, enabling method chaining that preserves the actual derived type.

From ComponentBase.cs:14-37:

/// <summary>
/// CRTP base for all UI components across any framework.
/// TSelf enables self-referential operations and type-safe chaining.
/// TModel is the data model being edited/displayed.
/// </summary>
/// <typeparam name="TSelf">The derived component type (CRTP pattern)</typeparam>
/// <typeparam name="TModel">The model type being rendered</typeparam>
public abstract class ComponentBase<TSelf, TModel>
    where TSelf : ComponentBase<TSelf, TModel>
{
    /// <summary>
    /// The model value being edited or displayed.
    /// </summary>
    public TModel? Value { get; set; }

    /// <summary>
    /// Callback invoked when the value changes.
    /// Framework-specific: Blazor uses EventCallback, React uses Action, etc.
    /// </summary>
    public Action<TModel>? ValueChanged { get; set; }

    /// <summary>
    /// Rendering mode for this component.
    /// </summary>
    public IRenderMode? RenderMode { get; set; }

    /// <summary>
    /// Self-reference for CRTP pattern.
    /// Enables type-safe extension methods and fluent APIs.
    /// </summary>
    protected TSelf This => (TSelf)this;

From ComponentBase.cs:39-65:

    /// <summary>
    /// Gets all property-level components for this model.
    /// Implemented by derived classes or source generators.
    /// </summary>
    protected abstract IEnumerable<IPropertyComponent> GetPropertyComponents();

    /// <summary>
    /// Validates whether this component can contain a child component of type TChild.
    /// Compile-time validated by source generators.
    /// </summary>
    /// <typeparam name="TChild">The child model type</typeparam>
    /// <returns>True if this component can contain TChild</returns>
    protected abstract bool CanContain<TChild>();

    /// <summary>
    /// Gets metadata about this component.
    /// Used by source generators and framework adapters.
    /// </summary>
    public virtual ComponentMetadata GetMetadata()
    {
        return new ComponentMetadata
        {
            ComponentType = typeof(TSelf).Name,
            ModelType = typeof(TModel).Name,
            RenderMode = RenderMode
        };
    }
}

Core Components

PropertyComponent

Base class for property-level components (individual fields/inputs).

From PropertyComponent.cs:1-47:

/// <summary>
/// CRTP base for property-level components.
/// Renders a single property of a model.
/// </summary>
/// <typeparam name="TSelf">The derived property component type (CRTP)</typeparam>
/// <typeparam name="TProperty">The property value type</typeparam>
public abstract class PropertyComponent<TSelf, TProperty> : IPropertyComponent
    where TSelf : PropertyComponent<TSelf, TProperty>
{
    /// <summary>
    /// The current value of the property.
    /// </summary>
    public TProperty? Value { get; set; }

    /// <summary>
    /// Callback invoked when the property value changes.
    /// </summary>
    public Action<TProperty>? ValueChanged { get; set; }

    /// <summary>
    /// Metadata about the property (label, help text, validation, etc.)
    /// </summary>
    public PropertyMetadata? Metadata { get; set; }

    /// <summary>
    /// Whether this property is read-only.
    /// </summary>
    public bool ReadOnly { get; set; }

    /// <summary>
    /// Self-reference for CRTP pattern.
    /// </summary>
    protected TSelf This => (TSelf)this;

    /// <summary>
    /// Invokes the value changed callback.
    /// </summary>
    protected virtual void OnValueChanged(TProperty? newValue)
    {
        Value = newValue;
        ValueChanged?.Invoke(newValue!);
    }
}

CollectionComponent

Base class for rendering collections/lists of nested models.

From CollectionComponent.cs:1-62:

/// <summary>
/// CRTP base for collection components.
/// Renders a list of nested components with add/remove/reorder capabilities.
/// </summary>
/// <typeparam name="TSelf">The derived collection component type (CRTP)</typeparam>
/// <typeparam name="TModel">The item model type</typeparam>
/// <typeparam name="TComponent">The component type for each item</typeparam>
public abstract class CollectionComponent<TSelf, TModel, TComponent>
    where TSelf : CollectionComponent<TSelf, TModel, TComponent>
    where TComponent : ComponentBase<TComponent, TModel>
{
    private List<TModel>? _items;

    /// <summary>
    /// The list of items in this collection.
    /// </summary>
    public IReadOnlyCollection<TModel>? Items
    {
        get => _items;
        set => _items = value != null ? new List<TModel>(value) : null;
    }

    /// <summary>
    /// Callback invoked when the items change.
    /// </summary>
    public Action<IReadOnlyCollection<TModel>>? ItemsChanged { get; set; }

    /// <summary>
    /// How to display the collection (accordion, tabs, list, grid, tree).
    /// </summary>
    public ICollectionDisplayMode? DisplayMode { get; set; }

    /// <summary>
    /// Whether users can add new items.
    /// </summary>
    public bool AllowAdd { get; set; } = true;

    /// <summary>
    /// Whether users can remove items.
    /// </summary>
    public bool AllowRemove { get; set; } = true;

    /// <summary>
    /// Whether users can reorder items.
    /// </summary>
    public bool AllowReorder { get; set; } = true;

    /// <summary>
    /// Text for the "Add" button.
    /// </summary>
    public string? AddButtonText { get; set; }

    /// <summary>
    /// Self-reference for CRTP pattern.
    /// </summary>
    protected TSelf This => (TSelf)this;

From CollectionComponent.cs:64-112:

    /// <summary>
    /// Adds a new item to the collection.
    /// </summary>
    protected virtual void AddItem(TModel item)
    {
        _items ??= new List<TModel>();
        _items.Add(item);
        ItemsChanged?.Invoke(_items);
    }

    /// <summary>
    /// Removes an item at the specified index.
    /// </summary>
    protected virtual void RemoveItem(int index)
    {
        if (_items != null && index >= 0 && index < _items.Count)
        {
            _items.RemoveAt(index);
            ItemsChanged?.Invoke(_items);
        }
    }

    /// <summary>
    /// Moves an item from one index to another.
    /// </summary>
    protected virtual void MoveItem(int fromIndex, int toIndex)
    {
        if (_items != null && fromIndex >= 0 && fromIndex < _items.Count &&
            toIndex >= 0 && toIndex < _items.Count)
        {
            var item = _items[fromIndex];
            _items.RemoveAt(fromIndex);
            _items.Insert(toIndex, item);
            ItemsChanged?.Invoke(_items);
        }
    }

    /// <summary>
    /// Updates an item at the specified index.
    /// </summary>
    protected virtual void UpdateItem(int index, TModel updatedItem)
    {
        if (_items != null && index >= 0 && index < _items.Count)
        {
            _items[index] = updatedItem;
            ItemsChanged?.Invoke(_items);
        }
    }
}

TypeCollectionComponent

Base class for TypeCollection reference components (dropdowns/selectors).

From TypeCollectionComponent.cs:1-49:

/// <summary>
/// CRTP base for TypeCollection selection components.
/// Provides type-safe dropdown/selection for TypeCollection options.
/// </summary>
/// <typeparam name="TSelf">The derived component type (CRTP)</typeparam>
/// <typeparam name="TCollection">The TypeCollection type</typeparam>
/// <typeparam name="TOption">The TypeOption interface</typeparam>
public abstract class TypeCollectionComponent<TSelf, TCollection, TOption>
    where TSelf : TypeCollectionComponent<TSelf, TCollection, TOption>
    where TCollection : class
    where TOption : class, ITypeOption
{
    /// <summary>
    /// The currently selected option ID.
    /// </summary>
    public int SelectedId { get; set; }

    /// <summary>
    /// Callback invoked when selection changes.
    /// </summary>
    public Action<int>? SelectedIdChanged { get; set; }

    /// <summary>
    /// Placeholder text when nothing is selected.
    /// </summary>
    public string? Placeholder { get; set; } = "-- Select --";

    /// <summary>
    /// Whether this is read-only.
    /// </summary>
    public bool ReadOnly { get; set; }

    /// <summary>
    /// Property metadata (label, help text).
    /// </summary>
    public PropertyMetadata? Metadata { get; set; }

    /// <summary>
    /// Self-reference for CRTP pattern.
    /// </summary>
    protected TSelf This => (TSelf)this;

From TypeCollectionComponent.cs:51-121:

    /// <summary>
    /// Gets the TypeCollection instance.
    /// Uses reflection to get static Instance property.
    /// Derived classes can override to provide direct access.
    /// </summary>
    protected virtual TCollection? GetCollectionInstance()
    {
        var instanceProperty = typeof(TCollection).GetProperty("Instance",
            BindingFlags.Public | BindingFlags.Static);
        return instanceProperty?.GetValue(null) as TCollection;
    }

    /// <summary>
    /// Gets all available options.
    /// Uses reflection to call the generated All() method.
    /// Derived classes should override to provide direct access.
    /// </summary>
    protected virtual IEnumerable<TOption> GetOptions()
    {
        var collection = GetCollectionInstance();
        if (collection == null)
        {
            return Enumerable.Empty<TOption>();
        }

        var allMethod = typeof(TCollection).GetMethod("All",
            BindingFlags.Public | BindingFlags.Static);

        if (allMethod != null)
        {
            var result = allMethod.Invoke(null, null);
            if (result is IEnumerable<TOption> options)
            {
                return options;
            }
        }

        return Enumerable.Empty<TOption>();
    }

    /// <summary>
    /// Gets the currently selected option.
    /// Uses reflection to call the generated Id() method.
    /// Derived classes should override to provide direct access.
    /// </summary>
    protected virtual TOption? GetSelectedOption()
    {
        var idMethod = typeof(TCollection).GetMethod("Id",
            BindingFlags.Public | BindingFlags.Static,
            null,
            new[] { typeof(int) },
            null);

        if (idMethod != null)
        {
            var result = idMethod.Invoke(null, new object[] { SelectedId });
            return result as TOption;
        }

        return null;
    }

    /// <summary>
    /// Handles selection change.
    /// </summary>
    protected virtual void OnSelectionChanged(int newId)
    {
        SelectedId = newId;
        SelectedIdChanged?.Invoke(newId);
    }
}

Supporting Types

RenderModes TypeCollection

Rendering modes are implemented as a TypeCollection for extensibility.

From RenderModes/IRenderMode.cs:1-20:

/// <summary>
/// Interface for component rendering modes.
/// Extends ITypeOption to enable TypeCollection discovery.
/// </summary>
public interface IRenderMode : ITypeOption<int, RenderModeBase>
{
    /// <summary>
    /// Gets a value indicating whether this mode allows editing.
    /// </summary>
    bool AllowsEditing { get; }

    /// <summary>
    /// Gets a value indicating whether this mode shows view.
    /// </summary>
    bool ShowsView { get; }
}

From RenderModes/RenderModes.cs:1-17:

/// <summary>
/// TypeCollection for component rendering modes.
/// </summary>
/// <remarks>
/// Provides compile-time discovery and O(1) lookup for render modes.
/// Source generator creates static properties for each registered render mode.
/// </remarks>
[TypeCollection(typeof(RenderModeBase), typeof(IRenderMode), typeof(RenderModes))]
public sealed partial class RenderModes : TypeCollectionBase<RenderModeBase, IRenderMode>
{
}

Built-in render modes:

From RenderModes/ViewRenderMode.cs:11-17:

[TypeOption(typeof(RenderModes), "View")]
public sealed class ViewRenderMode : RenderModeBase
{
    public ViewRenderMode() : base(0, "View", allowsEditing: false, showsView: true) { }
}

From RenderModes/EditRenderMode.cs:11-17:

[TypeOption(typeof(RenderModes), "Edit")]
public sealed class EditRenderMode : RenderModeBase
{
    public EditRenderMode() : base(1, "Edit", allowsEditing: true, showsView: false) { }
}

From RenderModes/BothRenderMode.cs:11-17:

[TypeOption(typeof(RenderModes), "Both")]
public sealed class BothRenderMode : RenderModeBase
{
    public BothRenderMode() : base(2, "Both", allowsEditing: true, showsView: true) { }
}

CollectionDisplayModes TypeCollection

Collection display modes are implemented as a TypeCollection.

From CollectionDisplayModes/ICollectionDisplayMode.cs:1-20:

/// <summary>
/// Interface for collection display modes.
/// Extends ITypeOption to enable TypeCollection discovery.
/// </summary>
public interface ICollectionDisplayMode : ITypeOption<int, CollectionDisplayModeBase>
{
    /// <summary>
    /// Gets a value indicating whether this display mode supports expand/collapse.
    /// </summary>
    bool SupportsExpandCollapse { get; }

    /// <summary>
    /// Gets a value indicating whether this display mode supports grouping.
    /// </summary>
    bool SupportsGrouping { get; }
}

Built-in display modes:

Mode ID SupportsExpandCollapse SupportsGrouping
Accordion 0 true true
Tabs 1 false false
List 2 false false
Grid 3 false true
Tree 4 true true

PropertyMetadata

From Metadata/PropertyMetadata.cs:1-22:

/// <summary>
/// Metadata about a property.
/// </summary>
public class PropertyMetadata
{
    public string Name { get; set; } = "";
    public string PropertyType { get; set; } = "";
    public string? Label { get; set; }
    public string? HelpText { get; set; }
    public string? Group { get; set; }
    public int Order { get; set; }
    public bool Required { get; set; }
    public bool ReadOnly { get; set; }
    public object? DefaultValue { get; set; }
    public IReadOnlyDictionary<string, object> ValidationRules { get; set; } = new Dictionary<string, object>(StringComparer.Ordinal);
    public IReadOnlyDictionary<string, object> Attributes { get; set; } = new Dictionary<string, object>(StringComparer.Ordinal);
}

IPropertyComponent

From Interfaces/IPropertyComponent.cs:1-11:

/// <summary>
/// Non-generic interface for property components.
/// Allows heterogeneous collections of property components.
/// </summary>
public interface IPropertyComponent
{
    PropertyMetadata? Metadata { get; set; }
    bool ReadOnly { get; set; }
}

ComponentMetadata

From Metadata/ComponentMetadata.cs:1-18:

/// <summary>
/// Metadata about a component.
/// Used by source generators and framework adapters.
/// </summary>
public class ComponentMetadata
{
    public string ComponentType { get; set; } = "";
    public string ModelType { get; set; } = "";
    public IRenderMode? RenderMode { get; set; }
    public IReadOnlyCollection<PropertyMetadata> Properties { get; set; } = Array.Empty<PropertyMetadata>();
    public IReadOnlyCollection<ComponentMetadata> ChildComponents { get; set; } = Array.Empty<ComponentMetadata>();
}

Design Principles

1. Zero UI Framework Dependencies

This abstraction layer has no dependencies on Blazor, Spectre.Console, or any UI framework. This allows it to be the foundation for all UI implementations.

2. CRTP for Type Safety

The Curiously Recurring Template Pattern ensures:

  • Compile-time type checking
  • Type-safe method chaining
  • Self-referential types that preserve the derived type through the hierarchy

3. TypeCollections for Extensibility

RenderModes and CollectionDisplayModes use the TypeCollection pattern, allowing:

  • O(1) lookups via FrozenDictionary
  • Cross-assembly discovery of new modes
  • Type-safe enumeration alternatives

4. Composition Over Inheritance

Components are composed from:

  • Property components (individual fields)
  • Collection components (lists)
  • TypeCollection components (references/selectors)

Best Practices

  1. Always use CRTP correctly: Derived class must pass itself as TSelf
  2. Implement CanContain: Validate nesting at compile-time
  3. Override reflection-based methods: TypeCollectionComponent uses reflection by default; override GetCollectionInstance(), GetOptions(), and GetSelectedOption() for direct access
  4. Keep abstractions pure: Do not add UI framework dependencies to this layer
  5. Use TypeCollections: Prefer IRenderMode and ICollectionDisplayMode over creating custom enums

See Also

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (7)

Showing the top 5 NuGet packages that depend on FractalDataWorks.UI.Abstractions:

Package Downloads
FractalDataWorks.UI.Components.TUI

Development tools and utilities for the FractalDataWorks ecosystem. Build:

FractalDataWorks.UI.Components.RazorConsole

Development tools and utilities for the FractalDataWorks ecosystem. Build:

FractalDataWorks.UI.Web.Abstractions

Development tools and utilities for the FractalDataWorks ecosystem. Build:

FractalDataWorks.UI.Components.Blazor

Development tools and utilities for the FractalDataWorks ecosystem. Build:

FractalDataWorks.UI.Components

Development tools and utilities for the FractalDataWorks ecosystem. Build:

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.11 41 1/12/2026
0.1.0-preview.10 43 1/12/2026
0.1.0-preview.9 47 1/9/2026
0.1.0-preview.7 107 1/7/2026