FractalDataWorks.UI.Abstractions
0.1.0-preview.11
dotnet add package FractalDataWorks.UI.Abstractions --version 0.1.0-preview.11
NuGet\Install-Package FractalDataWorks.UI.Abstractions -Version 0.1.0-preview.11
<PackageReference Include="FractalDataWorks.UI.Abstractions" Version="0.1.0-preview.11" />
<PackageVersion Include="FractalDataWorks.UI.Abstractions" Version="0.1.0-preview.11" />
<PackageReference Include="FractalDataWorks.UI.Abstractions" />
paket add FractalDataWorks.UI.Abstractions --version 0.1.0-preview.11
#r "nuget: FractalDataWorks.UI.Abstractions, 0.1.0-preview.11"
#:package FractalDataWorks.UI.Abstractions@0.1.0-preview.11
#addin nuget:?package=FractalDataWorks.UI.Abstractions&version=0.1.0-preview.11&prerelease
#tool nuget:?package=FractalDataWorks.UI.Abstractions&version=0.1.0-preview.11&prerelease
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
- Always use CRTP correctly: Derived class must pass itself as TSelf
- Implement CanContain: Validate nesting at compile-time
- Override reflection-based methods: TypeCollectionComponent uses reflection by default; override
GetCollectionInstance(),GetOptions(), andGetSelectedOption()for direct access - Keep abstractions pure: Do not add UI framework dependencies to this layer
- Use TypeCollections: Prefer
IRenderModeandICollectionDisplayModeover creating custom enums
See Also
- UI.Web.Abstractions - Web component layer
- UI.Components.Blazor - Blazor implementation
- UI.Components.TUI - Terminal UI implementation
| Product | Versions 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. |
-
.NETStandard 2.0
- FractalDataWorks.Collections (>= 0.1.0-preview.11)
- System.Collections.Immutable (>= 10.0.1)
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 |