Skip to content

Commit

Permalink
ARIA components & refactoring (#40)
Browse files Browse the repository at this point in the history
* aria-controls on ListboxButton

* IListbox selectedOption

* listbox aria-controls

* ListboxOptions aria-activedescendant

* listbox aria attributes

* code format

* only set aria-controls if Listbox is open

* aria concept

* code format

* ARIA concept

* aria-popup listbox

* rework HeadlessUI components (Listbox)

* make all listbox components generic

* remove redundant interfaces

* refactor IDynamicParentComponent concept

* remove redundant interfaces

* fixes

* fixes

* generics

* generics

* fixes

* refactor dynamic parent components

* refactor dynamic parent components

* refactor dynamic parent components

* refactor dynamic parent components

* introduce IgnisDynamicComponentBase and remove IgnisRigidComponentBase

* IgnisDynamicComponentBase

* rename component bases

* refactor dynamic components

* refactor dynamic components

* refactor dynamic components

* fixes

* cascade non-generic IAriaPopup

* fix transitions

* fixes

* refactor dialog

* refactor disclosure (aria)

* extract aria extensions & refactor menu

* aria popup

* code format

* refactor popover

* code format

* radio group aria concept

* radio group aria component

* aria radio group

* aria check group

* aria tabs

* transitions

* code format

* fixes

* code format

* docs

* bump dependencies

* fix tests

* don't render transitions if not needed

* code format
  • Loading branch information
DavidVollmers authored Nov 26, 2023
1 parent 66a6104 commit f99ac90
Show file tree
Hide file tree
Showing 122 changed files with 1,254 additions and 2,726 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/npm-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
with:
# https://github.com/actions/checkout/issues/701 https://github.com/actions/checkout/pull/579
fetch-depth: 0
- run: echo "VERSION=$(git describe --tags --dirty)" >> $GITHUB_ENV
- run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
- uses: actions/setup-node@v3
with:
node-version: 18.x
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/nuget-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 18.x
- run: echo "VERSION=$(git describe --tags --dirty)" >> $GITHUB_ENV
- run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV
- run: npm i
# Remove Meziantou.Analyzer from all projects (Roslyn analyzer which does not need to pollute the nuget package)
- run: dotnet remove package Meziantou.Analyzer
Expand Down
21 changes: 1 addition & 20 deletions docs/components/ComponentLifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,6 @@ will trigger a re-render of the component.
You can read more about when parameters are
set [here](https://learn.microsoft.com/en-us/aspnet/core/blazor/components/lifecycle?view=aspnetcore-7.0#when-parameters-are-set-setparametersasync).

## Rigid components

Rigid components are components that only render when parameters are changed. You can make your component rigid by
inheriting from `IgnisRigidComponentBase`.

```csharp
public class MyComponent : IgnisRigidComponentBase
{
[Parameter]
public string MyParameter { get; set; }

protected override void OnRender()
{
// This will be called when the component is first rendered and when MyParameter is changed.
}
}
```

## Additional lifecycle methods

Contrary to Razor components, Ignis components do not offer an `OnAfterRender` method, nor do they automatically update
Expand Down Expand Up @@ -74,5 +56,4 @@ public class MyComponent : IgnisComponentBase, IHandleAfterRender, IHandleEvent
Also contrary to Razor components, Ignis component's lifecycle methods are not called twice when the rendering mode is
set to `ServerPrerendered`.

If you still want to have the same behaviour as Razor components, you can either inherit from
the `IgnisRigidComponentBase` class or implement the `IHandleAfterRender` interface.
If you still want to have the same behaviour as Razor components, you can implement the `IHandleAfterRender` interface.
30 changes: 27 additions & 3 deletions docs/components/DynamicComponents.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,11 @@ You **should** set a default for either the `AsElement` or `AsComponent` propert
dynamic component without caring about the dynamic part.

To build the render tree of a dynamic component, you **can** use
the `OpenAs`, `CloseAs`, `AddContentFor`, `AddChildContentFor<TContext, TDynamic>`
and `GetChildContent<TContext, TDynamic>` methods.
the `OpenAs`, `CloseAs`, `AddContentFor`, `AddChildContentFor<TContext, TDynamic>` methods.

```csharp
// Example implmentation of the <Dynamic> component. (This is deviating from the actual implementation)
public sealed class Dynamic : IgnisRigidComponentBase, IDynamicParentComponent
public sealed class Dynamic : IgnisComponentBase, IDynamicParentComponent<Dynamic>
{
private Type? _asComponent;
private string? _asElement;
Expand Down Expand Up @@ -130,3 +129,28 @@ public sealed class Dynamic : IgnisRigidComponentBase, IDynamicParentComponent
}
}
```

You can also use the `DynamicComponentBase` class to simplify the implementation of a dynamic component.

```csharp
public sealed class Dynamic : DynamicComponentBase<Dynamic>
{
[Parameter] public RenderFragment? ChildContent { get; set; }

// Make sure to provide a default for either the AsElement or AsComponent property via the base constructor.
public Dynamic() : base(typeof(Fragment))
{
// Use the SetAttributes method to set the attributes of your component. (Even if there are none)
SetAttributes(ArraySegment<Func<KeyValuePair<string, object?>>>.Empty);
}

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenAs(0, this);
builder.AddMultipleAttributes(1, Attributes!);
builder.AddChildContentFor(2, this, ChildContent);

builder.CloseAs(this);
}
}
```
6 changes: 3 additions & 3 deletions packages/Ignis.Components.Reactivity/ReactiveSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ignis.Components.Reactivity;

public sealed class ReactiveSection : IgnisComponentBase, IDynamicParentComponent, IDisposable
public sealed class ReactiveSection : IgnisComponentBase, IDynamicParentComponent<ReactiveSection>, IDisposable
{
private Type? _asComponent;
private string? _asElement;
Expand Down Expand Up @@ -32,7 +32,7 @@ public Type? AsComponent

[Parameter, EditorRequired] public ReactiveExpression For { get; set; } = null!;

[Parameter] public RenderFragment<IDynamicComponent>? _ { get; set; }
[Parameter] public RenderFragment<ReactiveSection>? _ { get; set; }

[Parameter] public RenderFragment? ChildContent { get; set; }

Expand Down Expand Up @@ -61,7 +61,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenAs(0, this);
builder.AddMultipleAttributes(1, Attributes!);
builder.AddChildContentFor<IDynamicComponent, ReactiveSection>(2, this, ChildContent);
builder.AddChildContentFor(2, this, ChildContent);

builder.CloseAs(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ignis.Components;

public abstract class IgnisContentProviderComponentBase : IgnisComponentBase, IContentProvider, IDisposable
public abstract class ContentProviderBase : IgnisComponentBase, IContentProvider, IDisposable
{
private bool _ignoreOutlet;

Expand Down
43 changes: 5 additions & 38 deletions packages/Ignis.Components/Dynamic.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,20 @@

namespace Ignis.Components;

public sealed class Dynamic : IgnisRigidComponentBase, IDynamicParentComponent
public sealed class Dynamic : DynamicComponentBase<Dynamic>
{
private Type? _asComponent;
private string? _asElement;

[Parameter]
public string? AsElement
{
get => _asElement;
set
{
_asElement = value;
_asComponent = null;
}
}
[Parameter] public RenderFragment? ChildContent { get; set; }

[Parameter]
public Type? AsComponent
public Dynamic() : base(typeof(Fragment))
{
get => _asComponent;
set
{
_asComponent = value;
_asElement = null;
}
SetAttributes(ArraySegment<Func<KeyValuePair<string, object?>>>.Empty);
}

[Parameter] public RenderFragment<IDynamicComponent>? _ { get; set; }

[Parameter] public RenderFragment? ChildContent { get; set; }

[Parameter(CaptureUnmatchedValues = true)]
public IEnumerable<KeyValuePair<string, object?>>? AdditionalAttributes { get; set; }

/// <inheritdoc cref="IElementReferenceProvider.Element" />
public ElementReference? Element { get; set; }

/// <inheritdoc />
public object? Component { get; set; }

public IEnumerable<KeyValuePair<string, object?>>? Attributes => AdditionalAttributes;

protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenAs(0, this);
builder.AddMultipleAttributes(1, Attributes!);
builder.AddChildContentFor<IDynamicComponent, Dynamic>(2, this, ChildContent);
builder.AddChildContentFor(2, this, ChildContent);

builder.CloseAs(this);
}
Expand Down
83 changes: 83 additions & 0 deletions packages/Ignis.Components/DynamicComponentBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
using Microsoft.AspNetCore.Components;

namespace Ignis.Components;

public abstract class DynamicComponentBase<T> : IgnisComponentBase, IDynamicParentComponent<T>
where T : DynamicComponentBase<T>
{
private const string AttributesNotSetExceptionMessage =
"Dynamic component attributes not set. Please use the SetAttributes method in the component's constructor.";

private AttributeCollection? _attributes;

private Type? _asComponent;
private string? _asElement;

[Parameter]
public string? AsElement
{
get => _asElement;
set
{
_asElement = value;
_asComponent = null;
}
}

[Parameter]
public Type? AsComponent
{
get => _asComponent;
set
{
_asComponent = value;
_asElement = null;
}
}

[Parameter] public RenderFragment<T>? _ { get; set; }

[Parameter(CaptureUnmatchedValues = true)]
public IEnumerable<KeyValuePair<string, object?>>? AdditionalAttributes
{
get => _attributes?.AdditionalAttributes;
set
{
if (_attributes == null) throw new InvalidOperationException(AttributesNotSetExceptionMessage);

_attributes.AdditionalAttributes = value;
}
}

public ElementReference? Element { get; set; }

public object? Component { get; set; }

public IEnumerable<KeyValuePair<string, object?>>? Attributes => _attributes;

protected DynamicComponentBase(string asElement)
{
_asElement = asElement ?? throw new ArgumentNullException(nameof(asElement));
}

protected DynamicComponentBase(Type asComponent)
{
_asComponent = asComponent ?? throw new ArgumentNullException(nameof(asComponent));
}

protected void SetAttributes(IEnumerable<Func<KeyValuePair<string, object?>>> attributes)
{
if (attributes == null) throw new ArgumentNullException(nameof(attributes));

if (_attributes != null) throw new InvalidOperationException("Attributes already set.");

_attributes = new AttributeCollection(attributes);
}

internal override Task OnInitializedCoreAsync()
{
if (_attributes == null) throw new InvalidOperationException(AttributesNotSetExceptionMessage);

return base.OnInitializedCoreAsync();
}
}
2 changes: 1 addition & 1 deletion packages/Ignis.Components/Fragment.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

namespace Ignis.Components;

public sealed class Fragment : IgnisRigidComponentBase
public sealed class Fragment : IgnisComponentBase
{
[Parameter] public RenderFragment? ChildContent { get; set; }

Expand Down
6 changes: 1 addition & 5 deletions packages/Ignis.Components/IDynamicParentComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@

namespace Ignis.Components;

public interface IDynamicParentComponent<T> : IDynamicComponent where T : IDynamicComponent
public interface IDynamicParentComponent<T> : IDynamicComponent where T : IDynamicParentComponent<T>
{
RenderFragment<T>? _ { get; set; }

IEnumerable<KeyValuePair<string, object?>>? Attributes { get; }
}

public interface IDynamicParentComponent : IDynamicParentComponent<IDynamicComponent>
{
}
6 changes: 3 additions & 3 deletions packages/Ignis.Components/Ignis.Components.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,15 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net6.0'">
<PackageReference Include="Microsoft.AspNetCore.Components" Version="6.0.20"/>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="6.0.25"/>
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<PackageReference Include="Microsoft.AspNetCore.Components" Version="7.0.9"/>
<PackageReference Include="Microsoft.AspNetCore.Components" Version="7.0.14"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.85"/>
<PackageReference Include="Meziantou.Analyzer" Version="2.0.112"/>
</ItemGroup>

<ItemGroup>
Expand Down
44 changes: 29 additions & 15 deletions packages/Ignis.Components/IgnisComponentExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Ignis.Components.Extensions;
using System.Reflection;
using Ignis.Components.Extensions;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.DependencyInjection;
Expand Down Expand Up @@ -76,16 +77,14 @@ public static void CloseAs(this RenderTreeBuilder builder, IDynamicComponent dyn
}
}

public static RenderFragment? GetChildContent<TContext, TDynamic>(this TDynamic dynamicComponent,
RenderFragment<TContext>? childContent)
where TContext : IDynamicComponent where TDynamic : IDynamicParentComponent<TContext>, TContext
private static RenderFragment? GetChildContent<T>(this T dynamicComponent, RenderFragment<T>? childContent)
where T : IDynamicParentComponent<T>
{
return GetChildContent<TContext, TDynamic>(dynamicComponent, childContent?.Invoke(dynamicComponent));
return GetChildContent(dynamicComponent, childContent?.Invoke(dynamicComponent));
}

public static RenderFragment? GetChildContent<TContext, TDynamic>(this TDynamic dynamicComponent,
RenderFragment? childContent)
where TContext : IDynamicComponent where TDynamic : IDynamicParentComponent<TContext>, TContext
private static RenderFragment? GetChildContent<T>(this T dynamicComponent, RenderFragment? childContent)
where T : IDynamicParentComponent<T>
{
switch (dynamicComponent)
{
Expand All @@ -99,17 +98,32 @@ public static void CloseAs(this RenderTreeBuilder builder, IDynamicComponent dyn
return dynamicComponent._ != null ? dynamicComponent._.Invoke(dynamicComponent) : childContent;
}

public static void AddChildContentFor<TContext, TDynamic>(this RenderTreeBuilder builder, int sequence,
TDynamic dynamicComponent, RenderFragment? childContent)
where TContext : IDynamicComponent where TDynamic : IDynamicParentComponent<TContext>, TContext
public static void AddChildContentFor<T>(this RenderTreeBuilder builder, int sequence, T dynamicComponent,
RenderFragment? childContent) where T : IDynamicParentComponent<T>
{
AddContentFor(builder, sequence, dynamicComponent,
GetChildContent<TContext, TDynamic>(dynamicComponent, childContent));
AddContentForCore(builder, sequence, dynamicComponent, GetChildContent(dynamicComponent, childContent));
}

public static void AddChildContentFor<T>(this RenderTreeBuilder builder, int sequence, T dynamicComponent,
RenderFragment<T>? childContent) where T : IDynamicParentComponent<T>
{
AddContentForCore(builder, sequence, dynamicComponent, GetChildContent(dynamicComponent, childContent));
}

#pragma warning disable ASP0006
public static void AddContentFor(this RenderTreeBuilder builder, int sequence, IDynamicComponent dynamicComponent,
RenderFragment? content)
{
if (dynamicComponent.GetType().GetInterfaces().Any(i => i.IsGenericType &&
i.GetGenericTypeDefinition() ==
typeof(IDynamicParentComponent<>)))
throw new InvalidOperationException(
$"You cannot use {nameof(AddContentFor)} with a IDynamicParentComponent. Use {nameof(AddChildContentFor)} instead.");
AddContentForCore(builder, sequence, dynamicComponent, content);
}

#pragma warning disable ASP0006
private static void AddContentForCore(this RenderTreeBuilder builder, int sequence,
IDynamicComponent dynamicComponent, RenderFragment? content)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
switch (dynamicComponent)
Expand Down Expand Up @@ -140,7 +154,7 @@ public static void AddContentFor(this RenderTreeBuilder builder, int sequence, I

return dynamicComponent.Component switch
{
IDynamicParentComponent component => component.TryProvideElementReference(),
IDynamicComponent component => component.TryProvideElementReference(),
IElementReferenceProvider elementReferenceProvider => elementReferenceProvider.Element,
_ => dynamicComponent.Element
};
Expand Down
Loading

0 comments on commit f99ac90

Please sign in to comment.