Skip to content

Commit

Permalink
cleanup + added docblock comments
Browse files Browse the repository at this point in the history
  • Loading branch information
erinnmclaughlin committed May 20, 2024
1 parent f79e18c commit f49f07c
Show file tree
Hide file tree
Showing 13 changed files with 240 additions and 43 deletions.
18 changes: 16 additions & 2 deletions src/HeadlessBlazor.Core/HBElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,38 @@

namespace HeadlessBlazor;

/// <summary>
/// A base class for implementing a HeadlessBlazor element with child content.
/// </summary>
public class HBElement : HBElementBase
{
/// <summary>
/// The child content.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }

protected override void AddChildContent(ref int sequence, RenderTreeBuilder builder)
/// <inheritdoc />
protected override void OnBeforeCloseElement(ref int sequence, RenderTreeBuilder builder)
{
if (ChildContent != null)
builder.AddContent(sequence, ChildContent);
}
}

/// <summary>
/// A base class for implementing a HeadlessBlazor element with child content of type <typeparamref name="T"/>.
/// </summary>
public class HBElement<T> : HBElementBase where T : HBElement<T>
{
/// <summary>
/// The child content.
/// </summary>
[Parameter]
public RenderFragment<T>? ChildContent { get; set; }

protected override void AddChildContent(ref int sequence, RenderTreeBuilder builder)
/// <inheritdoc />
protected override void OnBeforeCloseElement(ref int sequence, RenderTreeBuilder builder)
{
if (ChildContent != null)
builder.AddContent(sequence, ChildContent, (T)this);
Expand Down
91 changes: 64 additions & 27 deletions src/HeadlessBlazor.Core/HBElementBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,35 @@

namespace HeadlessBlazor;

/// <summary>
/// A base class for all HeadlessBlazor components.
/// </summary>
public abstract class HBElementBase : ComponentBase
{
/// <summary>
/// The type of element to render.
/// </summary>
public virtual string ElementName { get; set; } = "div";

/// <summary>
/// The HTML attributes to apply to the element.
/// </summary>
[Parameter(CaptureUnmatchedValues = true)]
public virtual IDictionary<string, object?> UserAttributes { get; set; } = new Dictionary<string, object?>();

/// <summary>
/// When <see langword="true"/>, will stop propagation of click events. Default is <see langword="false" />.
/// </summary>
[Parameter]
public bool OnClickStopPropagation { get; set; }

/// <summary>
/// When <see langword="true"/>, will stop default behavior of click events. Default is <see langword="false" />.
/// </summary>
[Parameter]
public bool OnClickPreventDefault { get; set; }

protected virtual void OnBeforeInitialized() { }
protected virtual void OnAfterInitialized() { }
/// <inheritdoc />
protected sealed override void OnInitialized()
{
OnBeforeInitialized();
Expand All @@ -32,53 +46,76 @@ protected sealed override void OnInitialized()
OnAfterInitialized();
}

/// <summary>
/// Called before the <see cref="OnInitialized"/> method.
/// </summary>
protected virtual void OnBeforeInitialized()
{
}

/// <summary>
/// Called after the <see cref="OnInitialized"/> method.
/// </summary>
protected virtual void OnAfterInitialized()
{
}

/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var seq = 0;
BuildRenderTree(ref seq, builder);
}

/// <summary>
/// Renders the component to the supplied <see cref="RenderTreeBuilder"/> starting at the given <paramref name="sequence"/> number
/// </summary>
/// <param name="sequence">A reference to the sequence number.</param>
/// <param name="builder">A <see cref="RenderTreeBuilder"/> that will receive the render output.</param>
protected void BuildRenderTree(ref int sequence, RenderTreeBuilder builder)
{
var createElement = !string.IsNullOrWhiteSpace(ElementName);
OnBeforeOpenElement(ref sequence, builder);

if (createElement)
{
builder.OpenElement(sequence++, ElementName);

foreach (var attr in UserAttributes)
{
if (attr.Value != null)
builder.AddAttribute(sequence, attr.Key, attr.Value);
}

AddEventHandlers(ref sequence, builder);
builder.AddEventStopPropagationAttribute(sequence, "onclick", OnClickStopPropagation);
builder.AddEventPreventDefaultAttribute(sequence, "onclick", OnClickPreventDefault);
builder.OpenElement(sequence++, ElementName);

AddElementReference(ref sequence, builder);
foreach (var attr in UserAttributes)
{
if (attr.Value != null)
builder.AddAttribute(sequence, attr.Key, attr.Value);
}

AddBehaviors(ref sequence, builder);
AddChildContent(ref sequence, builder);
builder.AddEventPreventDefaultAttribute(sequence++, "onclick", OnClickPreventDefault);
builder.AddEventStopPropagationAttribute(sequence++, "onclick", OnClickStopPropagation);

if (createElement)
builder.CloseElement();
}
OnBeforeCloseElement(ref sequence, builder);

protected virtual void AddChildContent(ref int sequence, RenderTreeBuilder builder)
{
builder.CloseElement();
}

protected virtual void AddBehaviors(ref int sequence, RenderTreeBuilder builder)
/// <summary>
/// Adds additional render content to the render tree before the element is opened.
/// </summary>
/// <param name="sequence">A reference to the sequence number.</param>
/// <param name="builder">A <see cref="RenderTreeBuilder"/> that will receive the render output.</param>
protected virtual void OnBeforeOpenElement(ref int sequence, RenderTreeBuilder builder)
{
}

protected virtual void AddElementReference(ref int sequence, RenderTreeBuilder builder)
/// <summary>
/// Adds additional render content to the render tree before the element is closed.
/// </summary>
/// <param name="sequence">A reference to the sequence number.</param>
/// <param name="builder">A <see cref="RenderTreeBuilder"/> that will receive the render output.</param>
protected virtual void OnBeforeCloseElement(ref int sequence, RenderTreeBuilder builder)
{
}

protected virtual void AddEventHandlers(ref int sequence, RenderTreeBuilder builder)
/// <summary>
/// Adds additional render content to the render tree after the element is closed.
/// </summary>
/// <param name="sequence">A reference to the sequence number.</param>
/// <param name="builder">A <see cref="RenderTreeBuilder"/> that will receive the render output.</param>
protected virtual void OnAfterCloseElement(ref int sequence, RenderTreeBuilder builder)
{
}
}
41 changes: 37 additions & 4 deletions src/HeadlessBlazor.Dropdown/HBDropdown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,44 +4,77 @@

namespace HeadlessBlazor;

/// <summary>
/// The dropdown container.
/// </summary>
public class HBDropdown : HBElement<HBDropdown>
{
/// <summary>
/// A reference to the HTML element.
/// </summary>
public ElementReference ElementReference { get; private set; }

/// <summary>
/// Indicates whether or not the dropdown menu is currently open.
/// Default is <see langword="false" />.
/// </summary>
public bool IsOpen { get; private set; }

/// <summary>
/// When <see langword="true"/>, the dropdown menu will close when the escape key is pressed.
/// Default is <see langword="true"/>.
/// </summary>
[Parameter]
public bool CloseOnEscape { get; set; } = true;

/// <summary>
/// When <see langword="true"/>, the dropdown menu will close when a click event occurs outside of the dropdown container.
/// Default is <see langword="true"/>.
/// </summary>
[Parameter]
public bool CloseOnOutsideClick { get; set; } = true;

/// <summary>
/// The default behavior when a dropdown item is clicked.
/// If a value is not specified, the default click behavior is set to close the dropdown menu.
/// </summary>
[Parameter]
public EventCallback<HBDropdownItem> OnClickItem { get; set; }

/// <summary>
/// Opens the dropdown menu.
/// </summary>
public async Task OpenAsync() => await InvokeAsync(() =>
{
IsOpen = true;
StateHasChanged();
});

/// <summary>
/// Closes the dropdown menu.
/// </summary>
public async Task CloseAsync() => await InvokeAsync(() =>
{
IsOpen = false;
StateHasChanged();
});

/// <summary>
/// Toggles the dropdown menu.
/// </summary>
public async Task ToggleAsync()
{
await (IsOpen ? CloseAsync() : OpenAsync());
}

/// <inheritdoc />
protected override void OnAfterInitialized()
{
if (!OnClickItem.HasDelegate)
OnClickItem = new EventCallback<HBDropdownItem>(this, CloseAsync);
}

/// <inheritdoc />
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
var seq = 0;
Expand All @@ -63,7 +96,8 @@ protected override void BuildRenderTree(RenderTreeBuilder builder)
builder.CloseComponent();
}

protected override void AddEventHandlers(ref int sequence, RenderTreeBuilder builder)
/// <inheritdoc />
protected override void OnBeforeCloseElement(ref int sequence, RenderTreeBuilder builder)
{
if (CloseOnEscape)
{
Expand All @@ -73,14 +107,13 @@ protected override void AddEventHandlers(ref int sequence, RenderTreeBuilder bui
await CloseAsync();
}));
}
}

protected override void AddElementReference(ref int sequence, RenderTreeBuilder builder)
{
builder.AddElementReferenceCapture(sequence++, async capturedRef =>
{
ElementReference = capturedRef;
await InvokeAsync(StateHasChanged);
});

base.OnBeforeCloseElement(ref sequence, builder);
}
}
10 changes: 10 additions & 0 deletions src/HeadlessBlazor.Dropdown/HBDropdownItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@

namespace HeadlessBlazor;

/// <summary>
/// A dropdown item.
/// </summary>
public class HBDropdownItem : HBElement
{
/// <summary>
/// The parent <see cref="HBDropdown"/> component.
/// </summary>
[CascadingParameter]
public HBDropdown Dropdown { get; set; } = default!;

/// <inheritdoc />
protected override void OnParametersSet()
{
UserAttributes.TryAdd("onclick", new EventCallback(this, HandleClick));
}

/// <summary>
/// The click event handler.
/// </summary>
protected virtual async Task HandleClick()
{
if (!OnClickStopPropagation)
Expand Down
4 changes: 4 additions & 0 deletions src/HeadlessBlazor.Dropdown/HBDropdownItemButton.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
namespace HeadlessBlazor;

/// <summary>
/// A dropdown item that renders as an HTML button element.
/// </summary>
public class HBDropdownItemButton : HBDropdownItem
{
/// <inheritdoc />
public override string ElementName { get; set; } = "button";
}
4 changes: 4 additions & 0 deletions src/HeadlessBlazor.Dropdown/HBDropdownItemLink.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
namespace HeadlessBlazor;

/// <summary>
/// A dropdown item that renders as an HTML anchor element.
/// </summary>
public class HBDropdownItemLink : HBDropdownItem
{
/// <inheritdoc />
public override string ElementName { get; set; } = "a";
}
27 changes: 23 additions & 4 deletions src/HeadlessBlazor.Dropdown/HBDropdownMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,43 @@

namespace HeadlessBlazor;

/// <summary>
/// The dropdown menu.
/// </summary>
public class HBDropdownMenu : HBElement
{
/// <summary>
/// A reference to the HTML element.
/// </summary>
public ElementReference ElementReference { get; private set; }

/// <summary>
/// The dropdown menu alignment, relative to the <see cref="HBDropdownTrigger"/>.
/// </summary>
[Parameter]
public HBFloatAlignment? Alignment { get; set; }

/// <summary>
/// When <see langword="true"/>, the dropdown menu will be automatically positioned based on the available UI space.
/// Default is <see langword="true"/>.
/// </summary>
[Parameter]
public bool AutoPosition { get; set; } = true;

/// <summary>
/// The parent <see cref="HBDropdown"/> component.
/// </summary>
[CascadingParameter]
protected HBDropdown Dropdown { get; set; } = default!;

/// <summary>
/// The dropdown menu side, relative to the <see cref="HBDropdownTrigger"/>.
/// </summary>
[Parameter]
public HBFloatSide? Side { get; set; }

protected override void AddBehaviors(ref int sequence, RenderTreeBuilder builder)
/// <inheritdoc/>
protected override void OnBeforeCloseElement(ref int sequence, RenderTreeBuilder builder)
{
if (AutoPosition)
{
Expand All @@ -30,14 +50,13 @@ protected override void AddBehaviors(ref int sequence, RenderTreeBuilder builder
builder.AddAttribute(sequence, nameof(HBFloatBehavior.Side), Side);
builder.CloseComponent();
}
}

protected override void AddElementReference(ref int sequence, RenderTreeBuilder builder)
{
builder.AddElementReferenceCapture(sequence, async (elementRef) =>
{
ElementReference = elementRef;
await InvokeAsync(StateHasChanged);
});

base.OnBeforeCloseElement(ref sequence, builder);
}
}
Loading

0 comments on commit f49f07c

Please sign in to comment.