diff --git a/.editorconfig b/.editorconfig index 82775b1bf..8d35dce98 100644 --- a/.editorconfig +++ b/.editorconfig @@ -18,7 +18,7 @@ insert_final_newline = true #### .NET Coding Conventions #### # Organize usings -dotnet_separate_import_directive_groups = false +dotnet_separate_import_directive_groups = true dotnet_sort_system_directives_first = false file_header_template = unset @@ -207,8 +207,7 @@ dotnet_naming_style.begins_with_i.capitalization = pascal_case # IDE0005: Using directive is unnecessary. dotnet_diagnostic.IDE0005.severity = suggestion -dotnet_separate_import_directive_groups = false -dotnet_sort_system_directives_first = false + csharp_style_namespace_declarations = block_scoped:silent csharp_style_prefer_method_group_conversion = true:silent csharp_style_prefer_top_level_statements = true:silent @@ -225,6 +224,7 @@ csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimenta csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent csharp_style_prefer_extended_property_pattern = true:suggestion +csharp_style_prefer_primary_constructors = true:suggestion [*.{cs,vb}] dotnet_style_operator_placement_when_wrapping = beginning_of_line @@ -260,4 +260,5 @@ dotnet_style_parentheses_in_other_operators = never_if_unnecessary:suggestion dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:suggestion -dotnet_style_qualification_for_event = false:suggestion \ No newline at end of file +dotnet_style_qualification_for_event = false:suggestion +dotnet_style_prefer_collection_expression = true:suggestion \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index fde7e83f0..ad5da73bd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -8,11 +8,11 @@ Server;WebAssembly - + BLAZOR_SERVER - + BLAZOR_WEBASSEMBLY diff --git a/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.razor b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.razor new file mode 100644 index 000000000..9a11c1cda --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.razor @@ -0,0 +1,72 @@ +@namespace Material.Blazor.MD2 + +@inherits SingleSelectComponentMD2> +@typeparam TItem + + +
+ + +
+
+ + +
+
+ @for (int col = 0; col < 2; col++) + { + var localCol = col; + var columnItems = SelectItems.Skip(col * SelectItemsPerColumn).Take(SelectItemsPerColumn); + +
+ @if (columnItems.Any()) + { + + + @item.Label + + } +
+ } + +
+ +
+
+
+
+
+
\ No newline at end of file diff --git a/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.razor.cs b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.razor.cs new file mode 100644 index 000000000..a775d7844 --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.razor.cs @@ -0,0 +1,390 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// An autocomplete built using an with the anchor and drop +/// down list implementation from a Material Theme select. +/// +public partial class MBAutocompletePagedField : SingleSelectComponentMD2> +{ +#nullable enable annotations + /// + /// Helper text that is displayed either with focus or persistently with . + /// + [Parameter] public string HelperText { get; set; } = ""; + + + /// + /// Makes the persistent if true. + /// + [Parameter] public bool HelperTextPersistent { get; set; } = false; + + + /// + /// Delivers Material Theme validation methods from native Blazor validation. Either use this or + /// the Blazor ValidationMessage component, but not both. This parameter takes the same input as + /// ValidationMessage's For parameter. + /// + [Parameter] public Expression> ValidationMessageFor { get; set; } + + + /// + /// The text input style. + /// Overrides + /// + [Parameter] public MBTextInputStyle? TextInputStyle { get; set; } + + + /// + /// The text alignment style. + /// Overrides + /// + [Parameter] public MBTextAlignStyle? TextAlignStyle { get; set; } + + + /// + /// Field label. + /// + [Parameter] public string? Label { get; set; } + + + /// + /// The leading icon's name. No leading icon shown if not set. + /// + [Parameter] public string? LeadingIcon { get; set; } + + + /// + /// The trailing icon's name. No leading icon shown if not set. + /// + [Parameter] public string? TrailingIcon { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// The autcomplete field's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// Debounce interval in milliseconds. + /// + [Parameter] public int? DebounceInterval { get; set; } +#nullable restore annotations + + + /// + /// The number of items to be displayed per column for a given paged selection. + /// + [Parameter] public int SelectItemsPerColumn { get; set; } + + + /// + /// Allow unmatched results. + /// + [Parameter] public bool AllowBlankResult { get; set; } = false; + + + /// + /// REQUIRED: an async method returning an enumerated selection list. Parameters + /// are the search string, followed by the requested page number, and then the page size. + /// + [Parameter] public Func>, Task> GetMatchingSelection { get; set; } + + + /// + /// REQUIRED: Gets a select element matching the supplied selected value. + /// + [Parameter] public Func>, Task> GetSelectElement { get; set; } + + + + private bool IsOpen { get; set; } = false; + private DotNetObjectReference> ObjectReference { get; set; } + private bool MenuHasFocus { get; set; } = false; + private bool SelectionClosedMenu { get; set; } = false; + private ElementReference MenuReference { get; set; } + private bool ResultsPending { get; set; } = false; + private MBLinearProgressType MenuSurfaceLinearProgressType => ResultsPending ? MBLinearProgressType.Indeterminate : MBLinearProgressType.Closed; + private MBSelectElement[] SelectItems { get; set; } = Array.Empty>(); + private string SearchText { get; set; } = ""; + private MBSearchResultTypes SearchResultType { get; set; } = MBSearchResultTypes.NoMatchesFound; + public int MatchingItemCount { get; set; } + private MBTextField TextField { get; set; } + private int AppliedDebounceInterval => CascadingDefaults.AppliedDebounceInterval(DebounceInterval); + private string CurrentValue { get; set; } = ""; + private Timer Timer { get; set; } + private TItem PreviousComponentValue { get; set; } = default; + private string PreviousSearchText { get; set; } = ""; + private int PageNumber { get; set; } = 0; + private int[] PaginatorItemsPerPage { get; set; } = { 0 }; + + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ForceShouldRenderToTrue = true; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + if (!ComponentValue.Equals(PreviousComponentValue)) + { + await RequerySearchText().ConfigureAwait(false); + } + + if (SelectItemsPerColumn * 2 != PaginatorItemsPerPage[0]) + { + PaginatorItemsPerPage[0] = SelectItemsPerColumn * 2; + } + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + Timer?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + /// + /// Forces the component to re-query search text. This can be used if the search text associated + /// with the component's value changes. For example if the Value parameter is a Guid but the search + /// text associated with it changes in your app, call this method to update the component accordingly. + /// + /// + public async Task RequerySearchText() + { + await GetSelectElement(ComponentValue, ReceiveSelectElement).ConfigureAwait(false); + + + void ReceiveSelectElement(MBSelectElement selectElement) + { + SearchText = selectElement.Label; + PreviousComponentValue = ComponentValue; + PreviousSearchText = SearchText; + + _ = InvokeAsync(StateHasChanged); + } + } + + + private async Task GetSelectionAsync(string searchString) + { + if (GetMatchingSelection is null) + { + SelectItems = Array.Empty>(); + SearchResultType = MBSearchResultTypes.NoMatchesFound; + MatchingItemCount = 0; + } + else + { + if (PageNumber != 0) + { + _ = 1; + } + await GetMatchingSelection(searchString ?? "", PageNumber, 2 * SelectItemsPerColumn, ReceiveSearchItem).ConfigureAwait(false); + } + + await InvokeAsync(StateHasChanged); + + + void ReceiveSearchItem(MBPagedSearchResult searchResult) + { + if (!searchResult.ResultsPending) + { + SelectItems = searchResult.MatchingItems.ToArray(); + } + + ResultsPending = searchResult.ResultsPending; + SearchResultType = searchResult.SearchResultType; + MatchingItemCount = searchResult.MatchingItemCount; + + _ = InvokeAsync(StateHasChanged); + } + } + + + private void OnInput(ChangeEventArgs args) + { + Timer?.Dispose(); + var autoReset = new AutoResetEvent(false); + Timer = new Timer(async _ => await GetSelectionAndDisplayMenuAsync(args.Value.ToString()).ConfigureAwait(false), autoReset, AppliedDebounceInterval, Timeout.Infinite); + } + + + private async Task GetSelectionAndDisplayMenuAsync(string searchString) + { + await GetSelectionAsync(searchString); + + if (SearchResultType == MBSearchResultTypes.FullMatchFound || (AllowBlankResult && ComponentValue.Equals(default))) + { + await CloseMenuAsync(); + ComponentValue = SelectItems[0].SelectedValue; + SearchText = SelectItems[0].Label; + } + else if (SelectItems.Any()) + { + await OpenMenuAsync(); + } + else + { + await OpenMenuAsync(); + } + + await InvokeAsync(StateHasChanged); + } + + + + private async Task OnTextChangeAsync() + { + await CloseMenuAsync(true); + + if (!MenuHasFocus) + { + await GetSelectionAsync(SearchText).ConfigureAwait(false); + } + } + + + private async Task OnTextFocusInAsync() + { + await GetSelectionAsync(SearchText); + + if (!SelectionClosedMenu) + { + await OpenMenuAsync().ConfigureAwait(false); + } + + SelectionClosedMenu = false; + } + + + private Task OnTextFocusOutAsync() + { + return Task.CompletedTask; + //if (!MenuHasFocus) + //{ + // await CloseMenuAsync().ConfigureAwait(false); + + // SearchText = PreviousSearchText; + + // await InvokeAsync(StateHasChanged).ConfigureAwait(false); + //} + } + + + private void OnMenuFocusIn() + { + MenuHasFocus = true; + } + + + private void OnMenuFocusOut() + { + MenuHasFocus = false; + } + + + private async Task OnListItemClickAsync(int col, int listIndex) + { + var selectedElement = SelectItems[(col * SelectItemsPerColumn) + listIndex]; + + ComponentValue = selectedElement.SelectedValue; + + SearchText = selectedElement.Label; + + SelectionClosedMenu = true; + + await CloseMenuAsync().ConfigureAwait(false); + + NotifyClosed(); + } + + + private async Task OnPageTurn() + { + await GetSelectionAndDisplayMenuAsync(SearchText).ConfigureAwait(false); + } + + + /// + /// For Material Theme to notify when the drop down is closed via JS Interop. + /// + /// + [JSInvokable] + public void NotifyClosed() + { + IsOpen = false; + + _ = InvokeAsync(StateHasChanged); + } + + + private async Task OpenMenuAsync(bool forceOpen = false) + { + if (!IsOpen || forceOpen) + { + IsOpen = true; + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompletePagedField.open", MenuReference); + } + } + + + private async Task CloseMenuAsync(bool forceClose = false) + { + if (IsOpen || forceClose) + { + IsOpen = false; + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompletePagedField.close", MenuReference); + } + } + + + /// + internal override async Task InstantiateMcwComponent() + { + ObjectReference ??= DotNetObjectReference.Create(this); + + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompletePagedField.init", TextField.ElementReference, MenuReference, ObjectReference).ConfigureAwait(false); + } +} diff --git a/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.scss b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.scss new file mode 100644 index 000000000..62aa3f2ea --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.scss @@ -0,0 +1,8 @@ +.mb-autocomplete-paged-field-list { + width: 600px; +} + +.mb-autocomplete-paged-field-paginator { + display: flex; + justify-content: flex-end; +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.ts b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.ts new file mode 100644 index 000000000..26d9094d5 --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompletePagedField/MBAutocompletePagedField.ts @@ -0,0 +1,37 @@ +import { MDCTextField } from '@material/textfield'; +import { MDCMenu } from '@material/menu'; + +export function init(textElem, menuElem, dotNetObject): any { + if (!textElem || !menuElem) { + return; + } + textElem._textField = MDCTextField.attachTo(textElem); + menuElem._menu = MDCMenu.attachTo(menuElem); + + menuElem._menu.menuSurface.foundation.adapter.handleMenuSurfaceOpened = () => { + menuElem._menu.foundation.setDefaultFocusState(0); + }; + + const closedCallback = () => { + dotNetObject.invokeMethodAsync('NotifyClosed'); + }; + + menuElem._menu.listen('MDCMenuSurface:closed', closedCallback); +} + +export function open(menuElem): void { + menuElem._menu.open = true; + menuElem._menu.foundation.setDefaultFocusState(0); +} + +export function close(menuElem): void { + menuElem._menu.open = false; +} + +export function setValue(textElem, value): void { + textElem._textField.value = value; +} + +export function setDisabled(textElem, disabled): void { + textElem._textField.disabled = disabled; +} diff --git a/Material.Blazor.MD2/Components/AutocompleteSelectField/MBAutocompleteSelectField.razor b/Material.Blazor.MD2/Components/AutocompleteSelectField/MBAutocompleteSelectField.razor new file mode 100644 index 000000000..efc4337f4 --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompleteSelectField/MBAutocompleteSelectField.razor @@ -0,0 +1,75 @@ +@namespace Material.Blazor.MD2 + +@inherits SingleSelectComponentMD2> +@typeparam TItem + + +
+ + +
+
+ +
    + + @if (SearchResultType == MBSearchResultTypes.NoMatchesFound || SearchResultType == MBSearchResultTypes.TooManyItemsFound) + { + Guid value = default; + var label = SearchResultType == MBSearchResultTypes.NoMatchesFound ? "No matches found" : $"{MatchingItemCount:N0} items found, display limited to {MaxItemCount:N0}"; + +
  • + + @label +
  • + } + else + { + foreach (var item in SelectItems) + { + var listClass = "mdc-deprecated-list-item" + (item.SelectedValue.Equals(Value) ? " mdc-deprecated-list-item--selected" : ""); + var ariaSelected = item.Equals(Value); + +
  • + + @item.Label +
  • + } + } +
+
+
+
\ No newline at end of file diff --git a/Material.Blazor.MD2/Components/AutocompleteSelectField/MBAutocompleteSelectField.razor.cs b/Material.Blazor.MD2/Components/AutocompleteSelectField/MBAutocompleteSelectField.razor.cs new file mode 100644 index 000000000..ef13c233e --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompleteSelectField/MBAutocompleteSelectField.razor.cs @@ -0,0 +1,339 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Threading; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// An autocomplete built using an with the anchor and drop +/// down list implementation from a Material Theme select. +/// +public partial class MBAutocompleteSelectField : SingleSelectComponentMD2> +{ +#nullable enable annotations + /// + /// Helper text that is displayed either with focus or persistently with . + /// + [Parameter] public string HelperText { get; set; } = ""; + + + /// + /// Makes the persistent if true. + /// + [Parameter] public bool HelperTextPersistent { get; set; } = false; + + + /// + /// Delivers Material Theme validation methods from native Blazor validation. Either use this or + /// the Blazor ValidationMessage component, but not both. This parameter takes the same input as + /// ValidationMessage's For parameter. + /// + [Parameter] public Expression> ValidationMessageFor { get; set; } + + + /// + /// The text input style. + /// Overrides + /// + [Parameter] public MBTextInputStyle? TextInputStyle { get; set; } + + + /// + /// The text alignment style. + /// Overrides + /// + [Parameter] public MBTextAlignStyle? TextAlignStyle { get; set; } + + + /// + /// Field label. + /// + [Parameter] public string? Label { get; set; } + + + /// + /// The leading icon's name. No leading icon shown if not set. + /// + [Parameter] public string? LeadingIcon { get; set; } + + + /// + /// The trailing icon's name. No leading icon shown if not set. + /// + [Parameter] public string? TrailingIcon { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// The autcomplete field's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// Debounce interval in milliseconds. + /// + [Parameter] public int? DebounceInterval { get; set; } +#nullable restore annotations + + + /// + /// Allow unmatched results. + /// + [Parameter] public bool AllowBlankResult { get; set; } = false; + + + /// + /// REQUIRED: an async method returning an enumerated selection list. + /// + [Parameter] + public Func>> GetMatchingSelection { get; set; } + + + /// + /// REQUIRED: Gets a select element matching the supplied selected value. + /// + [Parameter] + public Func>> GetSelectElement { get; set; } + + + + private bool IsOpen { get; set; } = false; + private DotNetObjectReference> ObjectReference { get; set; } + private bool MenuHasFocus { get; set; } = false; + private ElementReference MenuReference { get; set; } + private MBSelectElement[] SelectItems { get; set; } = Array.Empty>(); + private Dictionary> SelectItemsDict { get; set; } = new(); + private string SearchText { get; set; } = ""; + private MBSearchResultTypes SearchResultType { get; set; } = MBSearchResultTypes.NoMatchesFound; + public int MatchingItemCount { get; set; } + public int MaxItemCount { get; set; } + private MBTextField TextField { get; set; } + private int AppliedDebounceInterval => CascadingDefaults.AppliedDebounceInterval(DebounceInterval); + private string CurrentValue { get; set; } = ""; + private Timer Timer { get; set; } + private TItem PreviousComponetValue { get; set; } = default; + + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ForceShouldRenderToTrue = true; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + if (!ComponentValue.Equals(PreviousComponetValue)) + { + await RequerySearchText().ConfigureAwait(false); + } + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + Timer?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + /// + /// Forces the component to re-query search text. This can be used if the search text associated + /// with the component's value changes. For example if the Value parameter is a Guid but the search + /// text associated with it changes in your app, call this method to update the component accordingly. + /// + /// + public async Task RequerySearchText() + { + SearchText = (await GetSelectElement(ComponentValue).ConfigureAwait(false)).Label; + PreviousComponetValue = ComponentValue; + } + + + private async Task GetSelectionAsync(string searchString) + { + if (GetMatchingSelection is null) + { + SelectItems = Array.Empty>(); + SelectItemsDict = new(); + SearchResultType = MBSearchResultTypes.NoMatchesFound; + MatchingItemCount = 0; + MaxItemCount = 0; + } + else + { + var searchResult = await GetMatchingSelection.Invoke(searchString ?? ""); + + SelectItems = searchResult.MatchingItems.ToArray(); + SelectItemsDict = SelectItems.ToDictionary(x => x.SelectedValue.ToString(), x => x); + SearchResultType = searchResult.SearchResultType; + MatchingItemCount = searchResult.MatchingItemCount; + MaxItemCount = searchResult.MaxItemCount; + } + + await InvokeAsync(StateHasChanged); + } + + + private void OnInput(ChangeEventArgs args) + { + Timer?.Dispose(); + var autoReset = new AutoResetEvent(false); + Timer = new Timer(OnTimerComplete, autoReset, AppliedDebounceInterval, Timeout.Infinite); + + + async void OnTimerComplete(object stateInfo) + { + await GetSelectionAsync(args.Value.ToString()); + + if (SearchResultType == MBSearchResultTypes.FullMatchFound || (AllowBlankResult && ComponentValue.Equals(default))) + { + await CloseMenuAsync(); + ComponentValue = SelectItems[0].SelectedValue; + SearchText = SelectItems[0].Label; + } + else if (SelectItems.Any()) + { + await OpenMenuAsync(); + } + else + { + await OpenMenuAsync(); + } + + await InvokeAsync(StateHasChanged); + } + } + + + private async Task OnTextChangeAsync() + { + await CloseMenuAsync(true); + + if (!MenuHasFocus) + { + await GetSelectionAsync(SearchText); + } + } + + + private async Task OnTextFocusInAsync() + { + await GetSelectionAsync(SearchText); + await OpenMenuAsync(); + } + + + private async Task OnTextFocusOutAsync() + { + await CloseMenuAsync(); + } + + + private void OnMenuFocusIn() + { + MenuHasFocus = true; + } + + + private void OnMenuFocusOut() + { + MenuHasFocus = false; + } + + + /// + /// For Material Theme to notify when the drop down is closed via JS Interop. + /// + /// + [JSInvokable] + public void NotifyClosed() + { + IsOpen = false; + + //ComponentValue = Value?.Trim() ?? ""; + + StateHasChanged(); + } + + + /// + /// For Material Theme to notify of menu item selection via JS Interop. + /// + /// + [JSInvokable] + public void NotifySelected(string stringValue) + { + var selectedElement = SelectItemsDict[stringValue]; + + ComponentValue = selectedElement.SelectedValue; + + SearchText = selectedElement.Label; + + NotifyClosed(); + } + + + private async Task OpenMenuAsync(bool forceOpen = false) + { + if (!IsOpen || forceOpen) + { + IsOpen = true; + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompleteTextField.open", MenuReference); + } + } + + + private async Task CloseMenuAsync(bool forceClose = false) + { + if (IsOpen || forceClose) + { + IsOpen = false; + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompleteTextField.close", MenuReference); + } + } + + + /// + internal override async Task InstantiateMcwComponent() + { + ObjectReference ??= DotNetObjectReference.Create(this); + + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompleteTextField.init", TextField.ElementReference, MenuReference, ObjectReference).ConfigureAwait(false); + } +} diff --git a/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.razor b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.razor new file mode 100644 index 000000000..8f2e85e0a --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.razor @@ -0,0 +1,69 @@ +@namespace Material.Blazor.MD2 +@inherits InputComponentMD2 + + +
+ + +
+
+ +
    + + @if (SelectInfo.FirstValueIsCustomValue) + { + var item = SelectInfo.SelectList.First(); + var listClass = "mdc-deprecated-list-item mb-autocomplete-custom-value"; + var ariaSelected = SelectInfo.SelectList.Length == 1; +
  • + + @item +
  • + } + @foreach (var item in SelectInfo.SelectList.Skip(SelectInfo.FirstValueIsCustomValue ? 1 : 0)) + { + var listClass = "mdc-deprecated-list-item" + (item.Equals(Value) ? " mdc-deprecated-list-item--selected" : ""); + var ariaSelected = item.Equals(Value); + +
  • + + @item +
  • + } +
+
+
+
\ No newline at end of file diff --git a/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.razor.cs b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.razor.cs new file mode 100644 index 000000000..206dfe763 --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.razor.cs @@ -0,0 +1,363 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// An autocomplete built using an with the anchor and drop +/// down list implementation from a Material Theme select. +/// +public partial class MBAutocompleteTextField : InputComponentMD2 +{ + private class SelectionInfo + { + public string SelectedText { get; set; } + public string[] SelectList { get; set; } + public bool FirstValueIsCustomValue { get; set; } + public bool FullMatchFound => (SelectList.Length == 1) && SelectList.Contains(SelectedText); + } + + + private partial class SelectionItem + { + public string Item { get; set; } + public bool IgnoreWhitespace { get; set; } + public string SearchTarget => IgnoreWhitespace ? SearchTargetRegex().Replace(Item, string.Empty) : Item; + + + [GeneratedRegex("\\s+")] + private static partial Regex SearchTargetRegex(); + } + + +#nullable enable annotations + /// + /// Helper text that is displayed either with focus or persistently with . + /// + [Parameter] public string HelperText { get; set; } = ""; + + + /// + /// Makes the persistent if true. + /// + [Parameter] public bool HelperTextPersistent { get; set; } = false; + + + /// + /// Delivers Material Theme validation methods from native Blazor validation. Either use this or + /// the Blazor ValidationMessage component, but not both. This parameter takes the same input as + /// ValidationMessage's For parameter. + /// + [Parameter] public Expression> ValidationMessageFor { get; set; } + + + /// + /// The text input style. + /// Overrides + /// + [Parameter] public MBTextInputStyle? TextInputStyle { get; set; } + + + /// + /// The text alignment style. + /// Overrides + /// + [Parameter] public MBTextAlignStyle? TextAlignStyle { get; set; } + + + /// + /// Field label. + /// + [Parameter] public string? Label { get; set; } + + + /// + /// The leading icon's name. No leading icon shown if not set. + /// + [Parameter] public string? LeadingIcon { get; set; } + + + /// + /// The trailing icon's name. No leading icon shown if not set. + /// + [Parameter] public string? TrailingIcon { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// The autcomplete field's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// Ignores whitespace when searching the items list. + /// + [Parameter] public bool IgnoreWhitespace { get; set; } = false; + + + /// + /// Allow unmatched results. + /// + [Parameter] public bool AllowBlankResult { get; set; } = false; + + + /// + /// Forces the search string to match only from the start of each select item. + /// + [Parameter] public bool MatchFromItemStart { get; set; } = false; + + + //private string[] selectItems; + //private string[] newSelectItems = null; + private string[] _cachedSelectItems = null; + /// + /// List of items to select from. + /// + [Parameter] public IEnumerable SelectItems { get; set; } +#nullable restore annotations + + /// + /// When set, the value that the user enters does not have to match any of the selectable items. + /// + [Parameter] public bool AllowCustomValue { get; set; } + + + private bool IsOpen { get; set; } = false; + private DotNetObjectReference ObjectReference { get; set; } + private bool MenuHasFocus { get; set; } = false; + private ElementReference MenuReference { get; set; } + private SelectionItem[] DisplaySelectionItems { get; set; } + private SelectionInfo SelectInfo { get; set; } = new SelectionInfo(); + private MBTextField TextField { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ObjectReference = DotNetObjectReference.Create(this); + + ForceShouldRenderToTrue = true; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + SetParameters(); + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + private void SetParameters() + { + if (!IsOpen) + { + _cachedSelectItems = SelectItems.ToArray(); + } + + if (_cachedSelectItems != null) + { + DisplaySelectionItems = + _cachedSelectItems + .Select(x => new SelectionItem + { + Item = x, + IgnoreWhitespace = IgnoreWhitespace, + }) + .ToArray(); + } + + SelectInfo = BuildSelectList(ComponentValue); + + StateHasChanged(); + } + + + private SelectionInfo BuildSelectList(string fieldText) + { + var regexOptions = RegexOptions.IgnoreCase | (IgnoreWhitespace ? RegexOptions.IgnorePatternWhitespace : 0); + + var fullMatchRegex = new Regex($"^{fieldText}$", regexOptions); + var fullMatches = (from f in DisplaySelectionItems + where fullMatchRegex.Matches(f.SearchTarget).Count > 0 + select f.Item).ToArray(); + + if (fullMatches.Any()) + { + return new SelectionInfo() + { + SelectedText = fullMatches.FirstOrDefault(), + SelectList = fullMatches + }; + } + + var startMatch = MatchFromItemStart ? "^" : ""; + var partialMatchRegex = new Regex($"{startMatch}{fieldText}", regexOptions); + var partialMatches = (from f in DisplaySelectionItems + where partialMatchRegex.Matches(f.SearchTarget).Count > 0 + select f.Item).ToArray(); + var firstValueIsCustomValue = AllowCustomValue && fieldText != null && !partialMatches.Contains(fieldText); + if (firstValueIsCustomValue) + { + partialMatches = partialMatches.Prepend(fieldText).ToArray(); + } + + return new SelectionInfo() + { + SelectedText = fieldText, + SelectList = partialMatches, + FirstValueIsCustomValue = firstValueIsCustomValue + }; + } + + + private async Task OnInput(ChangeEventArgs args) + { + SelectInfo = BuildSelectList((string)args.Value); + + if (SelectInfo.FullMatchFound || (AllowBlankResult && string.IsNullOrWhiteSpace(SelectInfo.SelectedText))) + { + await CloseMenuAsync(); + ComponentValue = SelectInfo.SelectedText.Trim(); + SetParameters(); + } + + await OpenMenuAsync(); + } + + + private async Task OnTextChangeAsync() + { + await CloseMenuAsync(true); + + if (!MenuHasFocus) + { + if (SelectInfo.FullMatchFound || (AllowBlankResult && string.IsNullOrWhiteSpace(SelectInfo.SelectedText))) + { + ComponentValue = SelectInfo.SelectedText.Trim(); + } + + SetParameters(); + } + } + + + private async Task OnTextFocusOutAsync() + { + if (!SelectInfo.SelectList.Any()) + { + await CloseMenuAsync(); + } + } + + + private void OnMenuFocusIn() + { + MenuHasFocus = true; + } + + + private void OnMenuFocusOut() + { + MenuHasFocus = false; + } + + + /// + /// For Material Theme to notify when the drop down is closed via JS Interop. + /// + /// + [JSInvokable] + public void NotifyClosed() + { + IsOpen = false; + + SelectInfo.SelectedText = Value; + + if (_cachedSelectItems != null) + { + _cachedSelectItems = SelectItems.ToArray(); + } + + StateHasChanged(); + } + + + /// + /// For Material Theme to notify of menu item selection via JS Interop. + /// + /// + [JSInvokable] + public void NotifySelected(string value) + { + ComponentValue = value; + + NotifyClosed(); + } + + + private async Task OpenMenuAsync(bool forceOpen = false) + { + if (!IsOpen || forceOpen) + { + IsOpen = true; + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompleteTextField.open", MenuReference); + } + } + + + private async Task CloseMenuAsync(bool forceClose = false) + { + if (IsOpen || forceClose) + { + IsOpen = false; + await InvokeJsVoidAsync("MaterialBlazor.MBAutocompleteTextField.close", MenuReference); + } + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBAutocompleteTextField.init", TextField.ElementReference, MenuReference, ObjectReference); + } +} diff --git a/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.scss b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.scss new file mode 100644 index 000000000..4fd21261a --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.scss @@ -0,0 +1,15 @@ +.mb-autocomplete { + display: inline-flex; + flex-direction: column; +} + +.mb-autocomplete .mdc-select, +.mb-autocomplete .mdc-select__anchor, +.mb-autocomplete--blank { + height: 0; +} + +.mb-autocomplete .mdc-select__menu { + visibility: visible; + min-height: 1rem; +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.ts b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.ts new file mode 100644 index 000000000..a32460cd7 --- /dev/null +++ b/Material.Blazor.MD2/Components/AutocompleteTextField/MBAutocompleteTextField.ts @@ -0,0 +1,43 @@ +import { MDCTextField } from '@material/textfield'; +import { MDCMenu } from '@material/menu'; + +export function init(textElem, menuElem, dotNetObject): any { + if (!textElem || !menuElem) { + return; + } + textElem._textField = MDCTextField.attachTo(textElem); + menuElem._menu = MDCMenu.attachTo(menuElem); + //menuElem._menuSurface = mdc.menuSurface.MDCMenuSurface.attachTo(menuElem); + + menuElem._menu.foundation.handleItemAction = listItem => { + menuElem._menu.open = false; + dotNetObject.invokeMethodAsync('NotifySelected', listItem.getAttribute('data-value')); + }; + + menuElem._menu.foundation.adapter.handleMenuSurfaceOpened = () => { + menuElem._menu.foundation.setDefaultFocusState(0); + }; + + const closedCallback = () => { + dotNetObject.invokeMethodAsync('NotifyClosed'); + }; + + menuElem._menu.listen('MDCMenuSurface:closed', closedCallback); +} + +export function open(menuElem): void { + menuElem._menu.open = true; + menuElem._menu.foundation.setDefaultFocusState(0); +} + +export function close(menuElem): void { + menuElem._menu.open = false; +} + +export function setValue(textElem, value): void { + textElem._textField.value = value; +} + +export function setDisabled(textElem, disabled): void { + textElem._textField.disabled = disabled; +} diff --git a/Material.Blazor.MD2/Components/BladeSet/MBBladeComponent.cs b/Material.Blazor.MD2/Components/BladeSet/MBBladeComponent.cs new file mode 100644 index 000000000..637c208a3 --- /dev/null +++ b/Material.Blazor.MD2/Components/BladeSet/MBBladeComponent.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Components; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A component added to a blade via +/// must implement this interface. +/// +public abstract class MBBladeComponent : ComponentBase where TParam : MBBladeComponentParameters +{ + /// + /// The BladeSet is provided as a cascading value + /// + [CascadingParameter] protected MBBladeSet BladeSet { get; set; } + + + /// + /// The blade reference provided by the calling consumer + /// + [Parameter] public string BladeReference { get; set; } + + + /// + /// Parameters for a blade are held in this parameter, which is a class inheriting . + /// + [Parameter] public TParam Parameters { get; set; } + + + /// + /// Indicates whether the blade is open. + /// + public bool IsOpen { get; private set; } = true; + + + /// + /// A utility function to close the blade, calling , passing the blade reference. + /// + public Task CloseBladeAsync() + { + IsOpen = false; + return BladeSet.RemoveBladeAsync(BladeReference); + } +} diff --git a/Material.Blazor.MD2/Components/BladeSet/MBBladeComponentParameters.cs b/Material.Blazor.MD2/Components/BladeSet/MBBladeComponentParameters.cs new file mode 100644 index 000000000..e62029c2c --- /dev/null +++ b/Material.Blazor.MD2/Components/BladeSet/MBBladeComponentParameters.cs @@ -0,0 +1,7 @@ +namespace Material.Blazor.MD2; + +/// +/// Parameters passed to a new blade via must take +/// one parameter of a class implementing this interface. That class then holds all the parameters required for the component. +/// +public class MBBladeComponentParameters : object { } diff --git a/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.md b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.md new file mode 100644 index 000000000..ac7d4f96b --- /dev/null +++ b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.md @@ -0,0 +1,24 @@ +--- +uid: C.MBBladeSet +title: MBBladeSet +--- +# MBBladeSet + +## Summary + +A plus component to display blades to the right of the viewport in a manner inspired by Microsoft Azure. + +- Styling is completely defined by the consumer and without any styling, blades are transparent and without borders. +- Blades work through a cascading value of type `MBBladeSet`. You add and remove blades by calling the `AddBlade` and `RemoveBlade` methods on this cascading value. See the [demo website code](https://github.com/Material-Blazor/Material.Blazor.MD2/blob/main/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.razor.cs) for an example +- By default the page's main content reduces in width as blades are added, so blades appear within the viewport. + - You can however specify a minimum width for main content to be applied to the `mb-bladeset-main-content` block. + - You can do this either by modifying `mb-bladeset-main-content` directly in your CSS file, or adding CSS classes or styles to it using the `MainContentAdditionalCss` and `MainContentAdditionalStyles` parameters repectively. + - See the Material.Blazor.MD2's [app.scss](https://github.com/Material-Blazor/Material.Blazor.MD2/blob/main/Material.Blazor.MD2.Website/Styles/app.scss) file for how we have used media queries with a `demo-blade-main-content` class that we apply on our website via the `MainContentAdditionalCss` parameter. + - Note that `min-width` must be in absolute terms, i.e. using units of `px`, `em` or `rem`. Percentages, `auto`, `min-content` etc will be ignored by MBBladeSet. + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBBladeSet&color=brightgreen)](xref:Material.Blazor.MD2.MBBladeSet) diff --git a/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.razor b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.razor new file mode 100644 index 000000000..23d1aee11 --- /dev/null +++ b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.razor @@ -0,0 +1,27 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundation + + + + + @PageContent + + + @if (Blades.Any()) + { + + @foreach (var bladeInfo in Blades.Values.OrderBy(b => b.Created)) + { + + + @bladeInfo.BladeContent + + + } + + } + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.razor.cs b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.razor.cs new file mode 100644 index 000000000..ea91ffc20 --- /dev/null +++ b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.razor.cs @@ -0,0 +1,360 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A blade display component. Adds blades to the right hand side of the viewport (or bock where this component is located), with +/// blades displayed left to right in ascending order of when they were requested (newest blades to the right). +/// +public partial class MBBladeSet : ComponentFoundation +{ + private const int transitionMs = 200; + + + /// + /// The three states in a blade's lifecycle. + /// + private enum BladeStatus { NewClosed, Open, ClosedToRemove } + + + /// + /// Base class for concurrent queue elements + /// + private abstract class QueueElement + { + /// + /// The blade referene to be operated on. + /// + public string BladeReference { get; set; } + } + + + /// + /// Queue element for adding blades. + /// + private class AddQueueElement : QueueElement + { + /// + /// The blade's content as a render fragment. + /// + public RenderFragment BladeContent { get; set; } + + + /// + /// Parameters for the blade component. Used only for add actions. + /// + public object Parameters { get; set; } + + + /// + /// Additional CSS for the blade. Used only for add actions. + /// + public string AdditionalCss { get; set; } + + + /// + /// Additional style attributes the blade. Used only for add actions. + /// + public string AdditionalStyles { get; set; } + + + /// + /// Optional action supplied by the consumer to be called once the blade has been removed. Passes the blade reference. + /// + public Action OnRemoved { get; set; } + } + + + /// + /// Queue element for removing blades. + /// + private class RemoveQueueElement : QueueElement { } + + + /// + /// Information specifying the blade's status and CSS class. + /// + private class BladeInfo + { + /// + /// The blade's reference. + /// + public string BladeReference { get; init; } + + + /// + /// The blade's content as a render fragment. + /// + public RenderFragment BladeContent { get; set; } + + + /// + /// Optional action supplied by the consumer to be called once the blade has been removed. Passes the blade reference. + /// + public Action OnRemoved { get; set; } + + + /// + /// Additional CSS classes to add to the mb-blade block. + /// + public string AdditionalCss { get; set; } = ""; + + + /// + /// Additional stle attributes to add to the mb-blade block. + /// + public string AdditionalStyles { get; set; } = ""; + + + /// + /// The blade's current animation status. + /// + public BladeStatus Status { get; set; } + + + /// + /// JSInterop element ref for the mb-blade block. + /// + public ElementReference BladeElementReference { get; set; } + + + /// + /// JSInterop element ref for the mb-blade-content block. + /// + public ElementReference BladeContentElementReference { get; set; } + + + /// + /// JSInterop element ref for the mb-blade-content block. + /// + public readonly DateTime Created = DateTime.UtcNow; + } + + + /// + /// Render fragment for the page content. + /// + [Parameter] public RenderFragment PageContent { get; set; } + + + /// + /// Additional CSS classes to apply to the mb-bladeset-main-content block that contains page content. + /// + [Parameter] public string MainContentAdditionalCss { get; set; } + + + /// + /// Additional style attributes to apply to the mb-bladeset-main-content block that contains page content. + /// + [Parameter] public string MainContentAdditionalStyles { get; set; } + + + /// + /// Additional CSS classes to apply to the mb-blades block that contains all blades. Note that this block is not rendered if + /// no blades are currently being displayed, so it's safe to use (for instance) a class such as mdc-elevation--z3 + /// that bleeds outside its block. + /// + [Parameter] public string BladesAdditionalCss { get; set; } + + + /// + /// Additional style attributes to apply to the mb-blades block that contains all blades. Note that this block is not rendered if + /// no blades are currently being displayed, so it's safe to apply styles that bleed outside its block. + /// + [Parameter] public string BladesAdditionalStyles { get; set; } + + + /// + /// References to each blade presently shown. These references are passed back to render fragments via Context to tell the consumer + /// what contents to render. + /// + public ImmutableList BladeReferences => Blades.Select(b => b.Value.BladeReference).ToImmutableList(); + + + /// + /// Invoked without arguments at the outset of a blade being added or removed from the bladeset. + /// + public event Action BladeSetChanged; + + + private readonly SemaphoreSlim queueSemaphore = new(1, 1); + private readonly ConcurrentQueue bladeSetActionQueue = new(); + private readonly ConcurrentQueue addedBladesQueue = new(); + private readonly ConcurrentQueue removedBladesQueue = new(); + + private string CachedBladesAdditionalCss { get; set; } + private string CachedBladesAdditionalStyles { get; set; } + private bool BladesAttributesSet { get; set; } = false; + + private string CachedMainContentAdditionalCss { get; set; } + private string CachedMainContentAdditionalStyles { get; set; } + private bool MainContentAttributesSet { get; set; } = false; + + private Dictionary Blades { get; set; } = new(); + + + /// + /// Adds the specified blade then animating its opening sequence. + /// + /// A string reference that the MBBladeSet component passes back via Context so the consumer can display the correct blade contents. + /// Optional parameters of type (or descending from) + /// CSS styles to be applied to the <mb-blade> block. + /// Style attributes to be applied to the <mb-blade> block. + /// Action called back when the blade is removed, receiving the blade reference as the parameter + public Task AddBladeAsync(string bladeReference, TParameters parameters = null, string additionalCss = "", string additionalStyles = "", Action onRemoved = null) where TParameters : MBBladeComponentParameters + { + AddQueueElement queueElement = new() + { + Parameters = parameters, + BladeReference = bladeReference, + AdditionalCss = additionalCss, + AdditionalStyles = additionalStyles, + OnRemoved = onRemoved, + BladeContent = new RenderFragment(builder => + { + builder.OpenComponent(0, typeof(TComponent)); + builder.AddAttribute(1, nameof(MBBladeComponent.BladeReference), bladeReference); + builder.AddAttribute(2, nameof(MBBladeComponent.Parameters), parameters); + builder.CloseComponent(); + }) + }; + + return QueueAction(queueElement); + } + + + /// + /// Animates the specified blade's closing sequence then removes it. + /// + /// The reference of the blade to be removed. + /// + public Task RemoveBladeAsync(string bladeReference) + { + QueueElement queueElement = new RemoveQueueElement() + { + BladeReference = bladeReference + }; + + return QueueAction(queueElement); + } + + + /// + /// Places the blade action on a concurrent queue and then dequeues the next action governed by semaphore locking. + /// Dequeuing is throttled to ensure that only one blade is being added/removed at a time. + /// + /// + /// + private async Task QueueAction(QueueElement action) + { + bladeSetActionQueue.Enqueue(action); + + await queueSemaphore.WaitAsync(); + + try + { + if (bladeSetActionQueue.TryDequeue(out var queueElement)) + { + if (queueElement is RemoveQueueElement) + { + Blades[queueElement.BladeReference].Status = BladeStatus.ClosedToRemove; + + removedBladesQueue.Enqueue(Blades[queueElement.BladeReference]); + + await InvokeAsync(StateHasChanged).ConfigureAwait(false); + } + else + { + var addQueueElement = queueElement as AddQueueElement; + + BladeInfo addedBlade = new() + { + BladeReference = addQueueElement.BladeReference, + BladeContent = addQueueElement.BladeContent, + AdditionalCss = addQueueElement.AdditionalCss, + AdditionalStyles = addQueueElement.AdditionalStyles, + OnRemoved = addQueueElement.OnRemoved, + Status = BladeStatus.NewClosed + }; + + Blades.Add(queueElement.BladeReference, addedBlade); + + addedBladesQueue.Enqueue(addedBlade); + + await InvokeAsync(StateHasChanged).ConfigureAwait(false); + } + + await Task.Delay(transitionMs + 20); + } + } + finally + { + queueSemaphore.Release(); + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + if (!MainContentAttributesSet || CachedMainContentAdditionalCss != MainContentAdditionalCss || CachedMainContentAdditionalStyles != MainContentAdditionalStyles) + { + //Logger.LogInformation("MBBladeSet updating MainContentAttributeSet"); + + MainContentAttributesSet = true; + CachedMainContentAdditionalCss = MainContentAdditionalCss; + CachedMainContentAdditionalStyles = MainContentAdditionalStyles; + } + + if (!BladesAttributesSet || CachedBladesAdditionalCss != BladesAdditionalCss || CachedBladesAdditionalStyles != BladesAdditionalStyles) + { + //Logger.LogInformation("MBBladeSet updating BladeAttributeSet"); + + BladesAttributesSet = true; + CachedBladesAdditionalCss = BladesAdditionalCss; + CachedBladesAdditionalStyles = BladesAdditionalStyles; + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnAfterRenderAsync(bool firstRender) + { + base.OnAfterRender(firstRender); + + if (addedBladesQueue.TryDequeue(out var addedBlade)) + { + await InvokeJsVoidAsync("MaterialBlazor.MBBladeSet.openBlade", addedBlade.BladeElementReference, addedBlade.BladeContentElementReference, transitionMs); + + addedBlade.Status = BladeStatus.Open; + + StateHasChanged(); + + BladeSetChanged?.Invoke(); + } + else if (removedBladesQueue.TryDequeue(out var removedBlade)) + { + await InvokeJsVoidAsync("MaterialBlazor.MBBladeSet.closeBlade", removedBlade.BladeElementReference, transitionMs); + + await Task.Delay(transitionMs); + + _ = Blades.Remove(removedBlade.BladeReference); + + removedBlade.OnRemoved?.Invoke(removedBlade.BladeReference); + + StateHasChanged(); + + BladeSetChanged?.Invoke(); + } + } +} diff --git a/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.scss b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.scss new file mode 100644 index 000000000..137012754 --- /dev/null +++ b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.scss @@ -0,0 +1,33 @@ +mb-bladeset { + display: grid; + grid-template-columns: auto min-content; + grid-gap: 0px; + margin: 0px; + padding: 0px; + border: 0px; + height: 100vh; + overflow-y: hidden; +} + +mb-bladeset-main-content { + width: auto; + height: 100vh; + overflow-x: hidden; + overflow-y: auto; +} + +mb-blades { + display: flex; + flex-flow: row nowrap; +} + +mb-blade { + width: 0px; + height: 100vh; + overflow-x: hidden; + overflow-y: auto; +} + +mb-blade-content { + display: inline-block; +} diff --git a/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.ts b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.ts new file mode 100644 index 000000000..fab35095d --- /dev/null +++ b/Material.Blazor.MD2/Components/BladeSet/MBBladeSet.ts @@ -0,0 +1,32 @@ +const fps = 60; +const waitDelay = 1000 / fps; + +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +export async function openBlade(bladeElem, bladeContentElem, transitionMs): Promise { + if (!bladeElem || !bladeContentElem) { + return; + } + let transition = "width " + transitionMs + "ms"; + let bladeContentWidth = bladeContentElem.getBoundingClientRect().width; + + bladeElem.style.transition = transition; + bladeElem.style.width = bladeContentWidth + "px"; + bladeElem.scrollIntoView(); + + let intervals = Math.ceil(transitionMs / waitDelay) + 1; + + for (let i = 0; i < intervals; i++) { + await sleep(waitDelay); + bladeElem.scrollIntoView(); + } +} + +export function closeBlade(bladeElem): void { + if (!bladeElem) { + return; + } + bladeElem.style.width = "0px"; +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Carousel/InternalCarouselPanel.razor b/Material.Blazor.MD2/Components/Carousel/InternalCarouselPanel.razor new file mode 100644 index 000000000..3c95aaaa6 --- /dev/null +++ b/Material.Blazor.MD2/Components/Carousel/InternalCarouselPanel.razor @@ -0,0 +1,14 @@ +@namespace Material.Blazor.MD2.Internal + +@inherits InputComponentMD2 +@typeparam TItem + + + + + @Content(item) + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Carousel/InternalCarouselPanel.razor.cs b/Material.Blazor.MD2/Components/Carousel/InternalCarouselPanel.razor.cs new file mode 100644 index 000000000..0ce1d729d --- /dev/null +++ b/Material.Blazor.MD2/Components/Carousel/InternalCarouselPanel.razor.cs @@ -0,0 +1,50 @@ +using Microsoft.AspNetCore.Components; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +/// +/// Panel for the carousel, returning ShouldRender() => false; +/// +public partial class InternalCarouselPanel : InputComponentMD2 +{ + /// + /// The tab details plus items to be displayed under the tab bar depending upon tab index. + /// + [Parameter] public IEnumerable Items { get; set; } + + + /// + /// Content render fragments under the tab bar. + /// + [Parameter] public RenderFragment Content { get; set; } + + + /// + /// The index of the currently displayed item. + /// + [Parameter] + public int ItemIndex { get; set; } + + + ///// + ///// The change event callback for . + ///// + //[Parameter] public EventCallback ItemIndexChanged { get; set; } + + + /// + /// The embedded sliding content. + /// + public MBSlidingContent SlidingContent { get; set; } + + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ForceShouldRenderToTrue = false; + } +} + diff --git a/Material.Blazor.MD2/Components/Carousel/MBCarousel.razor b/Material.Blazor.MD2/Components/Carousel/MBCarousel.razor new file mode 100644 index 000000000..fea2a0a01 --- /dev/null +++ b/Material.Blazor.MD2/Components/Carousel/MBCarousel.razor @@ -0,0 +1,42 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 +@typeparam TItem + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Carousel/MBCarousel.razor.cs b/Material.Blazor.MD2/Components/Carousel/MBCarousel.razor.cs new file mode 100644 index 000000000..c6461807b --- /dev/null +++ b/Material.Blazor.MD2/Components/Carousel/MBCarousel.razor.cs @@ -0,0 +1,155 @@ +using Material.Blazor.MD2.Internal; + +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// An carousel, implementing animation using an . +/// +/// +public partial class MBCarousel : InputComponentMD2 +{ + /// + /// The tab details plus items to be displayed under the tab bar depending upon tab index. + /// + [Parameter] public IEnumerable Items { get; set; } + + + /// + /// Content render fragments under the tab bar. + /// + [Parameter] public RenderFragment Content { get; set; } + + + /// + /// The interval in milliseconds to roll over from one panel of content to the next. Clamped between 1,000 and 60,000. Defaults to 3,000. + /// + [Parameter] public int RolloverInterval { get; set; } = 3000; + + + private bool Play { get; set; } + private string PlayIcon => Play ? "stop" : "play_arrow"; + private CancellationTokenSource TokenSource { get; set; } = new(); + private InternalCarouselPanel ICP { get; set; } + private int ItemIndex { get; set; } = 0; + private int RadioItemIndex { get; set; } + private List> RadioElements { get; set; } + + + + private async Task AfterSetRadioItemIndex() + { + PlayStop(false); + ItemIndex = RadioItemIndex; + await ICP.SlidingContent.SetItemIndexAsync(ItemIndex).ConfigureAwait(false); + await InvokeAsync(StateHasChanged).ConfigureAwait(false); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ForceShouldRenderToTrue = true; + } + + + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + RolloverInterval = Math.Clamp(RolloverInterval, 1000, 60000); + + RadioElements = new(); + + for (var i = 0; i < Items.Count(); i++) + { + RadioElements.Add(new() { SelectedValue = i, Label = $"Image {i}" }); + } + } + + + protected override Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender) + { + PlayStop(true); + } + + return base.OnAfterRenderAsync(firstRender); + } + + + private void PlayStop(bool? play = null) + { + Play = play?? !Play; + + if (Play) + { + RunPanels(); + } + else + { + TokenSource.Cancel(); + TokenSource.Dispose(); + TokenSource = new(); + } + + _ = InvokeAsync(StateHasChanged); + } + + + private async Task NavigatePrevious() + { + PlayStop(false); + await ICP.SlidingContent.SlidePrevious(true).ConfigureAwait(false); + ItemIndex = ICP.SlidingContent.ItemIndex; + RadioItemIndex = ICP.SlidingContent.ItemIndex; + await InvokeAsync(StateHasChanged).ConfigureAwait(false); + } + + + private async Task NavigateNext() + { + PlayStop(false); + await ICP.SlidingContent.SlideNext(true).ConfigureAwait(false); + ItemIndex = ICP.SlidingContent.ItemIndex; + RadioItemIndex = ICP.SlidingContent.ItemIndex; + await InvokeAsync(StateHasChanged).ConfigureAwait(false); + } + + + private void RunPanels() + { + var ct = TokenSource.Token; + + _ = Task.Run(async () => + { + var continuePanelTransition = true; + + while (continuePanelTransition) + { + await Task.Delay(RolloverInterval).ConfigureAwait(false); + + if (ct.IsCancellationRequested) + { + continuePanelTransition = false; + } + else + { + await ICP.SlidingContent.SlideNext(true).ConfigureAwait(false); + ItemIndex = ICP.SlidingContent.ItemIndex; + RadioItemIndex = ICP.SlidingContent.ItemIndex; + await InvokeAsync(StateHasChanged).ConfigureAwait(false); + } + } + }, TokenSource.Token); + } +} diff --git a/Material.Blazor.MD2/Components/Carousel/MBCarousel.scss b/Material.Blazor.MD2/Components/Carousel/MBCarousel.scss new file mode 100644 index 000000000..e01581228 --- /dev/null +++ b/Material.Blazor.MD2/Components/Carousel/MBCarousel.scss @@ -0,0 +1,56 @@ +.mb-carousel { + display: flex; + flex-flow: column nowrap; + justify-content: space-between; + align-items: center; + + & > .mb-carousel__upper { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + width: 100%; + + s & .mb-carousel-nav-panel { + width: 100%; + flex-grow: 1; + } + } + + & > .mb-carousel__lower { + display: flex; + flex-flow: row nowrap; + justify-content: center; + width: 100%; + padding-top: 40px; + + & label { + max-width: 0px; + margin: 0px; + padding: 0px; + overflow: hidden; + } + } + + & .mdc-fab { + min-width: 40px; + max-width: 40px; + min-height: 40px; + max-height: 40px; + margin: -20px; + flex-grow: 0; + opacity: 0; + background-color: var(--mdc-theme-surface); + transition: opacity 150ms ease-in-out; + + & .mdc-fab__icon { + color: var(--mdc-theme-on-surface) !important; + } + } + + &:hover { + & .mdc-fab { + opacity: 1; + } + } +} diff --git a/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.md b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.md new file mode 100644 index 000000000..93de7f929 --- /dev/null +++ b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.md @@ -0,0 +1,34 @@ +--- +uid: C.MBChipsSelectMulti +title: MBChipsSelectMulti +--- +# MBChipsSelectMulti<TItem> + +## Summary + +A [Material Chip Set](https://github.com/material-components/material-components-web/tree/v9.0.0/packages/mdc-chips#chips) configured as a multi-select. + +## Details + +- Accepts an `IEnumerable>` of selectable items; +- Binds to an `IList` of all items selected; +- Ignores the `Disabled` parameter because Material Components Web segmented buttons do not implement a disabled state. + +## Assisting Blazor Rendering with `@key` + +- MBSegmentedButtonMulti renders similar buttons with a `foreach` loop; +- In general each item rendered in a loop in Blazor should be supplied with a unique object via the `@key` attribute - see [Blazor University](https://blazor-university.com/components/render-trees/optimising-using-key/); +- MBSegmentedButtonMulti by default uses the `SelectedValue` property of each item in the `Items` parameter as the key, however you can override this. Material.Blazor.MD2 does this because we have had instances where Blazor crashes with the default key giving an exception message such as "The given key 'MyObject' was not present"; +- You can provide a function delegate to the `GetKeysFunc` parameter - we have used two variants of this: + - First to get a unique `Id` property that happens to be in our item's class: `GetKeysFunc="@((item) => item.Id)"`; and + - Second using a "fake key" where we create a GUID to act as the key: `GetKeysFunc="@((item) => Guid.NewGuid())"`. + - You can see an example of this in the [MBList demonstration website page's code](https://github.com/Material-Blazor/Material.Blazor.MD2/blob/main/Material.Blazor.MD2.Website/Pages/List.razor#L155). + +- ChipsSelectSingle & Items are set in OnInitialized and not in SetParameters + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBChipsSelectMulti&color=brightgreen)](xref:Material.Blazor.MD2.MBChipsSelectMulti`1) diff --git a/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.razor b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.razor new file mode 100644 index 000000000..ee8ccf7b6 --- /dev/null +++ b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.razor @@ -0,0 +1,64 @@ +@namespace Material.Blazor.MD2 + +@inherits MultiSelectComponent> +@typeparam TItem + + + + + + @for (int j = 0; j < ItemsArray.Length; j++) + { + int i = j; + string id = $"{_chipSetName}__chip-{i}"; + int tabIndex = (i == 0) ? 0 : -1; + var selected = Value.Contains(ItemsArray[i].SelectedValue); + @*var chipClass = "mdc-evolution-chip mdc-evolution-chip--selectable mdc-evolution-chip--filter " + (selected ? "mdc-evolution-chip--selected" : "mdc-evolution-chip--deselected") + (AppliedDisabled ? " mdc-evolution-chip--disabled" : "");*@ + var chipClass = "mdc-evolution-chip mdc-evolution-chip--selectable mdc-evolution-chip--filter" + (AppliedDisabled ? " mdc-evolution-chip--disabled" : ""); + + + + + + + + + @*@if (!string.IsNullOrWhiteSpace(ItemsArray[i].Icon)) + { + var iconClass = "mdc-evolution-chip__icon mdc-evolution-chip__icon--primary" + (selected ? " mdc-evolution-chip__icon--leading-hidden" : ""); + + + }*@ + + @if (true || !IsMultiSelect) + { + + + + + + } + + + @ItemsArray[i].Label + + + } + + diff --git a/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.razor.cs b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.razor.cs new file mode 100644 index 000000000..e98b11ce8 --- /dev/null +++ b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.razor.cs @@ -0,0 +1,122 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme segmented button orientated as a multi-select. +/// +public partial class MBChipsSelectMulti : MultiSelectComponent> +{ + /// + /// If this component is rendered inside a single-select segmented button, add the "" class. + /// + [CascadingParameter] private MBChipsSelectSingle ChipsSelectSingle { get; set; } + + /// + /// Inclusion of touch target + /// + [Parameter] public bool? TouchTarget { get; set; } + + + private readonly string _chipSetName = Utilities.GenerateUniqueElementName(); + + private bool AppliedTouchTarget => CascadingDefaults.AppliedTouchTarget(TouchTarget); + //private Dictionary[] ChipAttributes { get; set; } + //private Dictionary[] ChipIconAttributes { get; set; } + //private Dictionary[] ChipSpanAttributes { get; set; } + private ElementReference ChipsReference { get; set; } + private MBIconBearingSelectElement[] ItemsArray { get; set; } + private bool IsMultiSelect { get; set; } + private DotNetObjectReference> ObjectReference { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + IsMultiSelect = ChipsSelectSingle == null; + + ItemsArray = Items.ToArray(); + + ObjectReference = DotNetObjectReference.Create(this); + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + /// + /// For Material Theme to notify of menu item selection via JS Interop. + /// + [JSInvokable] + public void NotifyMultiSelected(int[] selectedIndexes) + { + //var selectedIndexes = Enumerable.Range(0, selected.Length).Where(i => selected[i]); + ComponentValue = ItemsArray.Where((item, index) => selectedIndexes.Contains(index)).Select(x => x.SelectedValue).ToArray(); + } + + + /// + /// For Material Theme to notify of menu item selection via JS Interop. + /// + [JSInvokable] + public void NotifySingleSelected(int index) + { + ComponentValue = new TItem[] { ItemsArray[index].SelectedValue }; + } + + + /// + private protected override Task SetComponentValueAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBChipsSelectMulti.setSelected", ChipsReference, Items.Select(x => Value.Contains(x.SelectedValue)).ToArray()); + } + + + /// + private protected override Task OnDisabledSetAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBChipsSelectMulti.setDisabled", ChipsReference, AppliedDisabled); + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBChipsSelectMulti.init", ChipsReference, IsMultiSelect, ObjectReference); + } + + + /// + /// Used by to set the value. + /// + /// + internal Task SetSingleSelectValue(TItem value) + { + Value = new TItem[] { value }; + return SetComponentValueAsync(); + } +} diff --git a/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.ts b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.ts new file mode 100644 index 000000000..ed05bad03 --- /dev/null +++ b/Material.Blazor.MD2/Components/ChipsSelectMulti/MBChipsSelectMulti.ts @@ -0,0 +1,59 @@ +import { MDCChipSet } from '@material/chips'; +import { MDCChipActionType } from '@material/chips/action/constants'; + +export function init(elem, isMultiSelect, dotNetObject) { + if (!elem) { + return; + } + elem._chipSet = MDCChipSet.attachTo(elem); + elem._isMultiSelect = isMultiSelect; + + const clickedCallback = () => { + if (elem._isMultiSelect) { + dotNetObject.invokeMethodAsync('NotifyMultiSelected', Array.from(elem._chipSet.getSelectedChipIndexes())); + //dotNetObject.invokeMethodAsync('NotifyMultiSelected', elem._chipSet.chips.map(x => x.isActionSelected(0))); + } + else { + let result = -1; + + for (let i = 0; i < elem._chipSet.chips.length; i++) { + if (elem._chipSet.chips[i].foundation.isActionSelected(MDCChipActionType.PRIMARY)) { + result = i; + } + } + + dotNetObject.invokeMethodAsync('NotifySingleSelected', result); + + //var selectedChips = elem._chipSet.chips.filter(x => x.isActionSelected(0)); + + //if (selectedChips.length == 0) { + // dotNetObject.invokeMethodAsync('NotifySingleSelected', -1); + //} + //else { + // dotNetObject.invokeMethodAsync('NotifySingleSelected', elem._chipSet.chips.findIndex(x => x.id === selectedChips[0].id)); + //} + } + }; + + elem._chipSet.listen('MDCChipSet:selection', clickedCallback); +} + +export function setDisabled(elem, value) { + if (!elem) { + return; + } + elem._chipSet.disabled = value; +} + +// This function doesn't appear to work properly - see https://github.com/Material-Blazor/Material.Blazor.MD2/issues/366 +export function setSelected(elem, selectedFlags) { + if (!elem) { + return; + } + for (let i = 0; i < selectedFlags.length; i++) { + //elem._chipSet.chips[i].selected = selectedFlags[i]; + elem._chipSet.foundation.adapter.selectChipAtIndex(i, selectedFlags[i], false); + //elem._chipSet.chips[i].foundation.setSelectedFromChipSet(selectedFlags[i], false); + //elem._chipSet.chips[i].foundation.notifySelection(selectedFlags[i], false); + } +} diff --git a/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.md b/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.md new file mode 100644 index 000000000..4aead794b --- /dev/null +++ b/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.md @@ -0,0 +1,34 @@ +--- +uid: C.MBChipsSelectSingle +title: MBChipsSelectSingle +--- +# MBChipsSelectSingle<TItem> + +## Summary + +A [Material Chip Set](https://github.com/material-components/material-components-web/tree/v9.0.0/packages/mdc-chips#chips) configured as a single-select. + +## Details + +- Accepts an `IEnumerable>` of selectable items; +- Binds to a `TItem` for the selected value; +- Ignores the `Disabled` parameter because Material Components Web segmented buttons do not implement a disabled state. + +## Assisting Blazor Rendering with `@key` + +- MBSegmentedButtonMulti renders similar buttons with a `foreach` loop; +- In general each item rendered in a loop in Blazor should be supplied with a unique object via the `@key` attribute - see [Blazor University](https://blazor-university.com/components/render-trees/optimising-using-key/); +- MBSegmentedButtonMulti by default uses the `SelectedValue` property of each item in the `Items` parameter as the key, however you can override this. Material.Blazor.MD2 does this because we have had instances where Blazor crashes with the default key giving an exception message such as "The given key 'MyObject' was not present"; +- You can provide a function delegate to the `GetKeysFunc` parameter - we have used two variants of this: + - First to get a unique `Id` property that happens to be in our item's class: `GetKeysFunc="@((item) => item.Id)"`; and + - Second using a "fake key" where we create a GUID to act as the key: `GetKeysFunc="@((item) => Guid.NewGuid())"`. + - You can see an example of this in the [MBList demonstration website page's code](https://github.com/Material-Blazor/Material.Blazor.MD2/blob/main/Material.Blazor.MD2.Website/Pages/List.razor#L155). + +- Value, ChipsSelectSingle, & Items are set in OnInitialized and not in SetParameters + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBChipsSelectSingle&color=brightgreen)](xref:Material.Blazor.MD2.MBChipsSelectSingle`1) diff --git a/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.razor b/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.razor new file mode 100644 index 000000000..8eab69917 --- /dev/null +++ b/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.razor @@ -0,0 +1,11 @@ +@namespace Material.Blazor.MD2 + +@inherits SingleSelectComponent> +@typeparam TItem + + + + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.razor.cs b/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.razor.cs new file mode 100644 index 000000000..ceca3c90f --- /dev/null +++ b/Material.Blazor.MD2/Components/ChipsSelectSingle/MBChipsSelectSingle.razor.cs @@ -0,0 +1,59 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme segmented button orientated as a single-select. +/// +public partial class MBChipsSelectSingle : SingleSelectComponent> +{ +#nullable enable annotations + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } +#nullable restore annotations + + + private MBChipsSelectMulti ChipsSelectMulti { get; set; } + + private IList multiValues; + private IList MultiValues + { + get => multiValues; + set + { + multiValues = value; + ComponentValue = multiValues.FirstOrDefault(); + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + var appliedItemValidation = CascadingDefaults.AppliedItemValidation(ItemValidation); + + ComponentValue = ValidateItemList(Items, appliedItemValidation).value; + + multiValues = new TItem[] { Value }; + } + + + /// + private protected override Task SetComponentValueAsync() + { + ChipsSelectMulti.SetSingleSelectValue(Value); + return Task.CompletedTask; + } +} diff --git a/Material.Blazor.MD2/Components/ConfirmationDialog/MBConfirmationDialog.razor b/Material.Blazor.MD2/Components/ConfirmationDialog/MBConfirmationDialog.razor new file mode 100644 index 000000000..100342f12 --- /dev/null +++ b/Material.Blazor.MD2/Components/ConfirmationDialog/MBConfirmationDialog.razor @@ -0,0 +1,41 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundationMD2 + + + + @Body +

Please type @MyConfirmationPhrase @ActionPhrase.

+

+ +

+ + + @if (ConfirmedButtons is null || UnconfirmedButtons is null) + { + + + + } + else + { + if (Confirmed) + { + @ConfirmedButtons + } + else + { + @UnconfirmedButtons + } + } + +
diff --git a/Material.Blazor.MD2/Components/ConfirmationDialog/MBConfirmationDialog.razor.cs b/Material.Blazor.MD2/Components/ConfirmationDialog/MBConfirmationDialog.razor.cs new file mode 100644 index 000000000..850ee53b2 --- /dev/null +++ b/Material.Blazor.MD2/Components/ConfirmationDialog/MBConfirmationDialog.razor.cs @@ -0,0 +1,157 @@ +using Material.Blazor.MD2.Internal; + +using Microsoft.AspNetCore.Components; + +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A confirmation dialog requiring the user to type in either a generated six digit sequence or a string provided in . +/// The consumer can elect to use standard "OK" and "Cancel" buttons by leaving and +/// null, in which case the "OK" button gives the dialog an action result of , or +/// for the "Cancel" button. +/// Alternatively the consumer can supply render fragments with buttons to and . The former +/// is rendered until the correct confirmaton text is entered into the text box at which point it is replaced with the latter. Typically +/// these are identical save that one or more of the repeated buttons in is disabled, but not in . +/// This components throws an if one of these render fragments is set while the other is not. +/// +public partial class MBConfirmationDialog : ComponentFoundationMD2 +{ + public const string ConfirmActionResult = "confirm"; + public const string CancelActionResult = "cancel"; + + + /// + /// The dialog title. + /// + [Parameter] public string Title { get; set; } + + + /// + /// Optional confirmation text. If omitted a random six digit code is generated for confirmation. + /// + [Parameter] public string ConfirmationPhrase { get; set; } + + + /// + /// The phrase following in the confirmation paragraph. + /// "Please type " + /// + [Parameter] public string ActionPhrase { get; set; } = "to confirm"; + + + /// + /// Disables the confirmation text field if True. + /// + [Parameter] public bool ConfirmationDisabled { get; set; } + + + /// + /// The dialog body, with the confirmation paragraph rendered beneath it and the + /// confirmation text field beneath that. + /// + [Parameter] public RenderFragment Body { get; set; } + + + /// + /// A render fragment containing buttons in the unconfirmed state, typically one + /// or more should be disabled. Both this and should be set + /// or both null in unison. + /// + [Parameter] public RenderFragment UnconfirmedButtons { get; set; } + + + /// + /// A render fragment containing buttons in the confirmed state, typically none + /// disabled and they should otherwise . + /// Both this and should be set + /// or both null in unison. + /// + [Parameter] public RenderFragment ConfirmedButtons { get; set; } + + + /// + /// Dialogs by default apply overflow: hidden;. This means that selects or datepickers are often + /// cropped. Set this parameter to true to make the dialog apply overflow: visible; to rectify this. + /// Defaults to false. + /// + [Parameter] public bool OverflowVisible { get; set; } = false; + + + private MBDialog Dialog { get; set; } + private string EnteredText { get; set; } = ""; + private bool Confirmed { get; set; } = false; + private string MyConfirmationPhrase => string.IsNullOrWhiteSpace(ConfirmationPhrase) ? digitText : ConfirmationPhrase; + + + private string digitText = ""; + + + /// + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + if ((UnconfirmedButtons == null && ConfirmedButtons != null) || + (UnconfirmedButtons != null && ConfirmedButtons == null)) + { + throw new ArgumentException($"Material.Blazor.MD2: UnconfirmedButtons and ConfirmedButton in {UtilitiesMD2.GetTypeName(this.GetType())} must both be either null or not null"); + } + } + + + private void OnInput(ChangeEventArgs args) + { + Confirmed = args.Value.ToString() == MyConfirmationPhrase; + } + + + /// + /// Shows the dialog. + /// + /// + public Task ShowAsync() + { + EnteredText = ""; + Confirmed = false; + digitText = RandomDigits(); + StateHasChanged(); + return Dialog.ShowAsync(); + } + + + /// + /// Hides the dialog. + /// + /// + public Task HideAsync() + { + EnteredText = ""; + Confirmed = false; + return Dialog.HideAsync(); + } + + + /// + /// Builds a string of six random digits. Each digit is prevented from repeating the prior digit's value. + /// + /// + private static string RandomDigits() + { + var rand = new Random(); + + var digits = new int[] { rand.Next(0, 10), rand.Next(0, 9), rand.Next(0, 9), rand.Next(0, 9), rand.Next(0, 9), rand.Next(0, 9) }; + + var result = digits[0].ToString(); + + for (var i = 1; i < digits.Length; i++) + { + digits[i] += (digits[i] >= digits[i - 1]) ? 1 : 0; + result += digits[i].ToString(); + } + + return result; + } +} diff --git a/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerDayButton.razor b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerDayButton.razor new file mode 100644 index 000000000..5b19afcbb --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerDayButton.razor @@ -0,0 +1,17 @@ +@namespace Material.Blazor.MD2.Internal +@inherits ComponentFoundationMD2 + +@{ + var classes = DisplayDate.Month.CompareTo(StartOfDisplayMonth.Month) switch + { + -1 => "mb-dp-day-pad__button mb-dp-day-pad__button_earlier_month", + 1 => "mb-dp-day-pad__button mb-dp-day-pad__button_later_month", + _ => "mb-dp-day-pad__button", + }; +} + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerDayButton.razor.cs b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerDayButton.razor.cs new file mode 100644 index 000000000..1904b395e --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerDayButton.razor.cs @@ -0,0 +1,100 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +/// +/// For Material.Blazor.MD2 internal use only. +/// +public partial class InternalDatePickerDayButton : ComponentFoundationMD2 +{ + /// + /// The currently selected date. + /// + [Parameter] public DateTime CurrentDate { get; set; } + + + /// + /// The date being displayed by this button + /// + [Parameter] public DateTime DisplayDate { get; set; } + + + /// + /// First day of the month being displayed + /// + [Parameter] public DateTime StartOfDisplayMonth { get; set; } + + + /// + /// Callback returning the date if the button is clicked + /// + [Parameter] public EventCallback OnItemClickAsync { get; set; } + + + /// + /// Date selection criteria + /// + [Parameter] public MBDateSelectionCriteria? DateSelectionCriteria { get; set; } + + +#nullable enable annotations + /// + /// Control whether a date is selectable by evaluating the method. + /// + [Parameter] public Func? DateIsSelectable { get; set; } +#nullable restore annotations + + + /// + /// Minimum date set by the consumer + /// + [Parameter] public DateTime MinDate { get; set; } + + + /// + /// Maximum date set by the consumer + /// + [Parameter] public DateTime MaxDate { get; set; } + + + private string Day => DisplayDate.Day.ToString(); + private MBButtonStyle ButtonStyle => (DisplayDate == CurrentDate) ? MBButtonStyle.ContainedUnelevated : MBButtonStyle.Text; + + + private bool ButtonDisabled + { + get + { + if (DateIsSelectable != null && DateIsSelectable != MBDatePicker.DateIsSelectableNotUsed && !DateIsSelectable(DisplayDate)) + { + return true; + } + + var criteria = CascadingDefaults.AppliedDateSelectionCriteria(DateSelectionCriteria); + + switch (criteria) + { + case MBDateSelectionCriteria.WeekendsOnly: + if ((DisplayDate.DayOfWeek != DayOfWeek.Sunday) && (DisplayDate.DayOfWeek != DayOfWeek.Saturday)) + { + return true; + } + break; + + case MBDateSelectionCriteria.WeekdaysOnly: + if ((DisplayDate.DayOfWeek == DayOfWeek.Sunday) || (DisplayDate.DayOfWeek == DayOfWeek.Saturday)) + { + return true; + } + break; + } + + return DisplayDate < MinDate || DisplayDate > MaxDate; + } + } + + + private Task OnClickAsync() => OnItemClickAsync.InvokeAsync(DisplayDate); +} diff --git a/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerPanel.razor b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerPanel.razor new file mode 100644 index 000000000..41b97d0c8 --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerPanel.razor @@ -0,0 +1,123 @@ +@namespace Material.Blazor.MD2.Internal + +@inherits InputComponentMD2 + +
+ +
    +
  • + + @ValueText +
  • +
+ + @if (HasBeenOpened) + { +
+
+ @MonthText + +
+ + @if (!ShowYearPad) + { +
+ + + + + +
+ } +
+ + @if (ShowYearPad) + { +
+ @* We would like to use virtualization, however Material interaction causes the year pad scroll to misbehave when presented with many years *@ + @* + + @foreach (var year in group) + { + + } + + *@ + + @foreach (var group in YearsInGroupsOfFour) + { + foreach (var year in group) + { + + } + } +
+ } + else + { +
+
+ @foreach (var d in DaysOfWeek) + { + @d + } +
+
+ @foreach (var date in Dates) + { + + } +
+
+ } + } + else + { +
+ } +
\ No newline at end of file diff --git a/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerPanel.razor.cs b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerPanel.razor.cs new file mode 100644 index 000000000..7f36bdae4 --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerPanel.razor.cs @@ -0,0 +1,298 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +internal static class GroupingExtension +{ + public static IEnumerable InGroupsOf(this IEnumerable enumerable, int groupSize) + { + var group = new T[groupSize]; + int index = 0; + foreach (var element in enumerable) + { + group[index] = element; + ++index; + if (index == groupSize) + { + yield return group; + index = 0; + group = new T[groupSize]; + } + } + if (index > 0) + { + // the last group has less than groupSize elements, therefore we need to return a trimmed array. + yield return group.Take(index).ToArray(); + } + } +} + + +/// +/// For Material.Blazor.MD2 internal use only. +/// +public partial class InternalDatePickerPanel : InputComponentMD2 +{ + /// + /// Date selection criteria + /// + [Parameter] public MBDateSelectionCriteria? DateSelectionCriteria { get; set; } + + +#nullable enable annotations + /// + /// Control whether a date is selectable by evaluating the method. + /// + [Parameter] public Func? DateIsSelectable { get; set; } +#nullable restore annotations + + + /// + /// Minimum date set by the consumer + /// + [Parameter] public DateTime MinDate { get; set; } + + + /// + /// Maximum date set by the consumer + /// + [Parameter] public DateTime MaxDate { get; set; } + + + /// + /// Set to indicate that if the value is default(DateTime) then no date is initially shown + /// and the panel will start with the current year and month + /// + [Parameter] public bool SuppressDefaultDate { get; set; } + + + /// + /// The parent date picker + /// + [Parameter] public MBDatePicker Parent { get; set; } + + + /// + /// Reference to the <li> embedded in the panel. + /// + internal ElementReference ListItemReference { get; set; } + + + /// + /// Specification for date format + /// + [Parameter] public string DateFormat { get; set; } + + + private bool ScrollToYear { get; set; } = false; + + private string[] DaysOfWeek { get; set; } + + private bool PreviousMonthDisabled => false + || (StartOfDisplayMonth.Year == DateTime.MinValue.Year && StartOfDisplayMonth.Month == DateTime.MinValue.Month) + || (StartOfDisplayMonth <= MinDate); + + private bool NextMonthDisabled => false + || (StartOfDisplayMonth.Year == DateTime.MaxValue.Year && StartOfDisplayMonth.Month == DateTime.MaxValue.Month) // special case: + || (StartOfDisplayMonth.AddMonths(1) >= MaxDate); + + private bool _showYearPad = false; + internal bool ShowYearPad + { + get => _showYearPad; + set + { + _showYearPad = value; + ScrollToYear = value; + } + } + + private List Dates { get; set; } = new List(); + + private List Years { get; set; } = new List(); + + private List YearsInGroupsOfFour { get; set; } = new List(); + + private string ValueText => UtilitiesMD2.DateToString(Value, DateFormat); + + private int MonthsOffset { get; set; } = 0; + + private DateTime StartOfDisplayMonth { get; set; } + + private bool HasBeenOpened { get; set; } = false; + + private string MonthText => StartOfDisplayMonth.ToString("MMMM yyyy"); + + private readonly string currentYearId = UtilitiesMD2.GenerateUniqueElementName(); + + private readonly IMBIconFoundry foundry = MBIconHelper.MIFoundry(MBIconMITheme.Filled); + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + DaysOfWeek = CultureInfo.CurrentCulture.DateTimeFormat.AbbreviatedDayNames.Select(d => d[0..1]).ToArray(); + var rotate_by = (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; + if (rotate_by > 0) + { + DaysOfWeek = DaysOfWeek.Skip(rotate_by).Concat(DaysOfWeek.Take(rotate_by)).ToArray(); + } + + ForceShouldRenderToTrue = true; + } + + + /// + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + SetParameters(); + } + + + internal void SetParameters(DateTime? newValue = null) + { + if (newValue != null) + { + Value = (DateTime)newValue; + } + + DateTime startDate; + int startDateYear; + int startDateMonth; + var today = DateTime.Today; + + if (SuppressDefaultDate && + (Value == default) && + (today >= MinDate) && + (today <= MaxDate)) + { + startDateYear = today.Year; + startDateMonth = today.Month; + } + else + { + startDateYear = Value.Year; + startDateMonth = Value.Month; + } + + try + { + startDate = StartOfDisplayMonth = new DateTime(startDateYear, startDateMonth, 1).AddMonths(MonthsOffset); + } + catch (ArgumentOutOfRangeException) + { + startDate = StartOfDisplayMonth = new DateTime(DateTime.MaxValue.Year, DateTime.MaxValue.Month, 1); + } + while (startDate.Date > DateTime.MinValue.Date && startDate.DayOfWeek != CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek) + { + startDate = startDate.AddDays(-1); + } + DateTime endDate; + try + { + endDate = startDate.AddDays(6 * 7); // 6 lines of 7 days each + } + catch (ArgumentOutOfRangeException) + { + endDate = DateTime.MaxValue.Date; + } + + Dates = new List(); + + for (var date = startDate; date < endDate; date = date.AddDays(1)) + { + Dates.Add(date); + } + + var startYear = Math.Max(DateTime.MinValue.Year, ((MinDate.Year - 1) / 4) * 4 + 1); + var endYear = Math.Min(DateTime.MaxValue.Year, ((MaxDate.Year + 3) / 4) * 4); + + Years = new List(); + + for (var year = startYear; year <= endYear; year++) + { + Years.Add(year); + } + YearsInGroupsOfFour = Years.InGroupsOf(4).ToList(); + + ShowYearPad = false; + + StateHasChanged(); + } + + + /// + /// Causes the panel to display buttons on the first opening + /// + /// + public async Task NotifyOpened() + { + if (!HasBeenOpened) + { + HasBeenOpened = true; + await InvokeAsync(StateHasChanged); + } + } + + + private async Task OnDayItemClickAsync(DateTime dateTime) + { + // Invoke JS first. if ComponentValue is set first we are at risk of this element being re-rendered before this line is run, making ListItemReference stale and causing a JS exception. + await InvokeJsVoidAsync("MaterialBlazor.MBDatePicker.listItemClick", ListItemReference, UtilitiesMD2.DateToString(dateTime, DateFormat)); + ComponentValue = dateTime; + MonthsOffset = 0; + Parent.NotifyValueChanged(); + SetParameters(); + } + + + private void OnYearItemClick(int year) + { + MonthsOffset += (year - StartOfDisplayMonth.Year) * 12; + ShowYearPad = false; + SetParameters(); + } + + + private void OnPreviousMonthClick() + { + MonthsOffset--; + SetParameters(); + } + + + private void OnShowCurrentDateClick() + { + MonthsOffset = 0; + SetParameters(); + } + + + private void OnNextMonthClick() + { + MonthsOffset++; + SetParameters(); + } + + + /// + protected override async Task OnAfterRenderAsync(bool firstRender) // TODO: this prevents us from marking OnAfterRenderAsync in InputComponent as sealed. Consider alternatives! + { + await base.OnAfterRenderAsync(firstRender); + if (!firstRender && ScrollToYear) + { + ScrollToYear = false; + + await InvokeJsVoidAsync("MaterialBlazor.MBDatePicker.scrollToYear", currentYearId); + } + } +} diff --git a/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerYearButton.razor b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerYearButton.razor new file mode 100644 index 000000000..a15083106 --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerYearButton.razor @@ -0,0 +1,10 @@ +@namespace Material.Blazor.MD2.Internal +@inherits ComponentFoundationMD2 + + diff --git a/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerYearButton.razor.cs b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerYearButton.razor.cs new file mode 100644 index 000000000..801684860 --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/InternalDatePickerYearButton.razor.cs @@ -0,0 +1,61 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +/// +/// For Material.Blazor.MD2 internal use only. +/// +public partial class InternalDatePickerYearButton : ComponentFoundationMD2 +{ + /// + /// The currently selected year. + /// + [Parameter] public int CurrentYear { get; set; } + + + /// + /// The year being displayed by this button. + /// + [Parameter] public int DisplayYear { get; set; } + + + /// + /// Callback returning the year if this button is clicked + /// + [Parameter] public EventCallback OnItemClickAsync { get; set; } + + + /// + /// Minimum date set by the consumer + /// + [Parameter] public DateTime MinDate { get; set; } + + + /// + /// Maximum date set by the consumer + /// + [Parameter] public DateTime MaxDate { get; set; } + + + /// + /// HTML element id for the current year + /// + [Parameter] public string CurrentYearId { get; set; } + + + private MBButtonStyle ButtonStyle => (DisplayYear == CurrentYear) ? MBButtonStyle.ContainedUnelevated : MBButtonStyle.Text; + + + /// + /// We want to scroll to the current year when the year picker opens. So the year that's currently active needs an ID. + /// + private string CurrentYearIdHelper => (DisplayYear == CurrentYear) ? CurrentYearId : null; + + + private bool ButtonDisabled => (MaxDate < new DateTime(DisplayYear, 1, 1)) || (MinDate > new DateTime(DisplayYear, 12, 31)); + + + private Task OnClickAsync() => OnItemClickAsync.InvokeAsync(DisplayYear); +} diff --git a/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.razor b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.razor new file mode 100644 index 000000000..8a1636b87 --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.razor @@ -0,0 +1,98 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 + + +
+ + @if (HasBadge) + { + + + + } + + + +
+ +
+
\ No newline at end of file diff --git a/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.razor.cs b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.razor.cs new file mode 100644 index 000000000..4fa0aa02d --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.razor.cs @@ -0,0 +1,285 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A date picker styled to match the Material Theme date picker specification, using +/// a modfied Material Theme select input as also applied in . +/// +public partial class MBDatePicker : InputComponentMD2 +{ + #region members + internal static readonly DateTime MinAllowableDate = DateTime.MinValue; + internal static readonly DateTime MaxAllowableDate = DateTime.MaxValue; + + private string AdditionalStyle { get; set; } = ""; + private MBDensity AppliedDensity => CascadingDefaults.AppliedSelectDensity(Density); + private string AppliedDateFormat => CascadingDefaults.AppliedDateFormat(DateFormat); + private MBSelectInputStyle AppliedInputStyle => CascadingDefaults.AppliedStyle(SelectInputStyle); + private ElementReference ElementReference { get; set; } + private ElementReference MenuSurfaceElementReference { get; set; } + private DotNetObjectReference ObjectReference { get; set; } + private string MenuClass => MBMenu.GetMenuSurfacePositioningClass(MenuSurfacePositioning == MBMenuSurfacePositioning.Fixed ? MBMenuSurfacePositioning.Fixed : MBMenuSurfacePositioning.Regular) + ((Panel?.ShowYearPad ?? true) ? " mb-dp-menu__day-menu" : " mb-dp-menu__year-menu"); + private InternalDatePickerPanel Panel { get; set; } + private bool ShowLabel => !string.IsNullOrWhiteSpace(Label); + private MBBadge Badge { get; set; } + + private readonly string invisibleText = "color: rgba(0, 0, 0, 0.0); "; + private readonly string labelId = UtilitiesMD2.GenerateUniqueElementName(); + private readonly string listboxId = UtilitiesMD2.GenerateUniqueElementName(); + private readonly string selectedTextId = UtilitiesMD2.GenerateUniqueElementName(); + + #endregion + + #region parameters + + + /// + /// Specification for date format + /// + [Parameter] public string DateFormat { get; set; } + + +#nullable enable annotations + internal static readonly Func DateIsSelectableNotUsed = (_) => true; + /// + /// Control whether a date is selectable by evaluating the method. + /// + [Parameter] public Func? DateIsSelectable { get; set; } = DateIsSelectableNotUsed; +#nullable restore annotations + + + /// + /// Date selection criteria + /// + [Parameter] public MBDateSelectionCriteria? DateSelectionCriteria { get; set; } + + + /// + /// The select's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// The label. + /// + [Parameter] public string Label { get; set; } + + + /// + /// Maximum date set by the consumer + /// + [Parameter] public DateTime MaxDate { get; set; } = MaxAllowableDate; + + + /// + /// Regular, fullwidth or fixed positioning/width. Overrides a value of + /// with . + /// + [Parameter] public MBMenuSurfacePositioning MenuSurfacePositioning { get; set; } = MBMenuSurfacePositioning.Regular; + + + /// + /// Minimum date set by the consumer + /// + [Parameter] public DateTime MinDate { get; set; } = MinAllowableDate; + + + /// + /// The select style. + /// Overrides + /// + [Parameter] public MBSelectInputStyle? SelectInputStyle { get; set; } + + + /// + /// Set to indicate that if the value is default(DateTime) then no date is initially shown + /// and the panel will start with the current year and month + /// + [Parameter] public bool SuppressDefaultDate { get; set; } + + + /// + /// Determines whether the button has a badge - defaults to false. + /// + [Parameter] public bool HasBadge { get; set; } + + + /// + /// The badge's style - see , defaults to . + /// + [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; + + + /// + /// When true collapses the badge. + /// + [Parameter] + public bool BadgeExited { get; set; } + private bool _cachedBadgeExited; + + + /// + /// The button's density. + /// + [Parameter] + public string BadgeValue { get; set; } + private string _cachedBadgeValue; + #endregion + + #region DensityInfo + + private MBCascadingDefaults.DensityInfo DensityInfo + { + get + { + var d = CascadingDefaults.GetDensityCssClass(AppliedDensity); + + d.CssClassName += AppliedInputStyle == MBSelectInputStyle.Filled ? "--filled" : "--outlined"; + + return d; + } + } + + #endregion + + #region InstantiateMcwComponent + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBDatePicker.init", ElementReference, MenuSurfaceElementReference, ObjectReference); + } + + #endregion + + #region OnDisabledSetCallback + + /// + private protected override Task OnDisabledSetAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBDatePicker.setDisabled", ElementReference, AppliedDisabled); + } + + #endregion + + #region NotifyOpened + /// + /// Do not use. This method is used internally for receiving the "dialog closed" event from javascript. + /// + [JSInvokable] + public async Task NotifyOpened() + { + await Panel.NotifyOpened(); + } + #endregion + + #region OnInitializedAsync + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ConditionalCssClasses + .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass) + .AddIf("mdc-select--filled", () => AppliedInputStyle == MBSelectInputStyle.Filled) + .AddIf("mdc-select--outlined", () => AppliedInputStyle == MBSelectInputStyle.Outlined) + .AddIf("mdc-select--no-label", () => string.IsNullOrWhiteSpace(Label)) + .AddIf("mdc-select--disabled", () => AppliedDisabled); + + // SuppressDefaultDate is only used here and not in GetSelectionAsync + // therefore a change will not be detected (and makes little sense + // for the component). + + if (SuppressDefaultDate && (Value == default)) + { + AdditionalStyle = invisibleText; + } + + ForceShouldRenderToTrue = true; + + ObjectReference = DotNetObjectReference.Create(this); + } + + #endregion + + #region Dispose + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + #endregion + + #region SetComponentValueAsync & NotifyValueChanged + + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + if (_cachedBadgeValue != BadgeValue || _cachedBadgeExited != BadgeExited) + { + _cachedBadgeValue = BadgeValue; + _cachedBadgeExited = BadgeExited; + + if (Badge is not null) + { + EnqueueJSInteropAction(() => Badge.SetValueAndExited(BadgeValue, BadgeExited)); + } + } + } + + + /// + /// Callback for value the value setter. + /// + /// + /// + protected private override async Task SetComponentValueAsync() + { + Panel.SetParameters(Value); + + if (AdditionalStyle.Length > 0) + { + AdditionalStyle = ""; + await InvokeAsync(StateHasChanged).ConfigureAwait(false); + } + + await InvokeJsVoidAsync("MaterialBlazor.MBDatePicker.listItemClick", Panel.ListItemReference, UtilitiesMD2.DateToString(Value, AppliedDateFormat)).ConfigureAwait(false); + } + + + /// + /// Blanks additional style when the value is changed. + /// + internal void NotifyValueChanged() + { + if (AdditionalStyle.Length > 0) + { + AdditionalStyle = ""; + InvokeAsync(StateHasChanged); + } + } + #endregion +} diff --git a/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.scss b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.scss new file mode 100644 index 000000000..e594d3585 --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.scss @@ -0,0 +1,244 @@ +@use "sass:math"; + +/************************************************ + Applied to InternalDatePickerPanel.razor +************************************************/ + +/* + The structure of this file is designed to be driven entirely from + $dp-button-size. The panel width and height will flex to accommodate + that, as will day and year buttons. Icon buttons for the to pmenu are + the only other parameter that needs to be set to effect a resize. + Setting these values to 28px and 24px respectively makes the date picker + conform to the detailed Material Theme specification. +*/ +$dp-button-size: 32px; +$dp-menu-icon-button-size: 24px; + + +/* + General purpose definitions +*/ +$dp-button-border-radius: math.div($dp-button-size, 2); +$dp-button-margin: 2px; +$dp-day-pad-horizontal-margin: 16px; +$dp-day-pad-bottom-margin: 8px; + +$dp-panel-width: 7 * ($dp-button-size + 2 * $dp-button-margin) + 2 * $dp-day-pad-horizontal-margin; + +$dp-year-pad-padding-top: 4px; +$dp-year-pad-padding-right: 0; +$dp-year-pad-padding-bottom: 8px; +$dp-year-pad-padding-left: 12px; +$dp-year-pad-clear-space-right: 20px; + +$dp-day-button-width: $dp-button-size; +$dp-year-button-width: math.div(($dp-panel-width - $dp-year-pad-padding-left - $dp-year-pad-clear-space-right), 4) - 2 * $dp-button-margin; + +$dp-blank-filler-height: 7 * $dp-day-button-width; + +/* + Container +*/ +.mb-dp-container { + display: inline-flex; + flex-flow: column nowrap; + width: $dp-panel-width; + overflow: hidden; +} + + +/* + Hidden List +*/ +.mb-dp-list { + height: 0 !important; + width: 0; + padding: 0 !important; + visibility: hidden; +} + + +/* + Menu at top +*/ + +$dp-menu-height: 52px; +$dp-menu-padding: 16px 8px 12px 24px; +$dp-menu-icon-button-margin: 4px; +$dp-panel-height: 6 * ($dp-button-size + 2 * $dp-button-margin) + ($dp-button-size + $dp-button-margin) + (2 * $dp-day-pad-bottom-margin) + $dp-menu-height; + + +.mb-dp-menu { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + box-sizing: border-box; + padding: $dp-menu-padding; + height: $dp-menu-height; + font-weight: bold; + user-select: none; + width: $dp-panel-width; + min-width: $dp-panel-width; + overflow: visible !important; +} + + +.mb-dp-menu__left { + display: flex; + flex-flow: row nowrap; + justify-content: flex-start; +} + + +.mb-dp-menu__right { + display: flex; + flex-flow: row nowrap; + justify-content: flex-end; +} + + +.mb-dp-menu__icon-button { + height: $dp-menu-icon-button-size !important; + width: $dp-menu-icon-button-size !important; + padding: 0 !important; +} + + +.mb-dp-menu__icon-button.spaced { + margin-left: $dp-menu-icon-button-margin; +} + + +/* + Day pad +*/ + +$dp-day-pad-weekdays-margin-bottom: 8px; +$dp-day-pad-weekdays-size: $dp-button-size + $dp-button-margin; + +.mb-dp-day-pad { + display: flex; + flex-flow: column nowrap; + box-sizing: border-box; + padding: 0 $dp-day-pad-horizontal-margin $dp-day-pad-bottom-margin $dp-day-pad-horizontal-margin; + user-select: none; + overflow: visible; + width: $dp-panel-width; +} + + +.mb-dp-day-pad__weekdays-block { + display: flex; + flex-flow: row nowrap; + margin-bottom: $dp-day-pad-weekdays-margin-bottom; +} + + +.mb-dp-day-pad__weekday { + text-align: center; + height: $dp-day-pad-weekdays-size; + line-height: $dp-day-pad-weekdays-size; + width: $dp-day-pad-weekdays-size; + margin: auto; + padding: 0; +} + + +.mb-dp-day-pad__days-block { + display: flex; + flex-flow: row wrap; + padding: 0 !important; + overflow: hidden; +} + + +.mb-dp-day-pad__button { + height: $dp-button-size !important; + min-height: $dp-button-size !important; + max-height: $dp-button-size !important; + width: $dp-day-button-width !important; + min-width: $dp-day-button-width !important; + max-width: $dp-day-button-width !important; + margin: $dp-button-margin !important; + padding: 0 !important; + + .mdc-button__ripple { + border-radius: $dp-button-border-radius; + } +} + +.mb-dp-day-pad__button_earlier_month { + visibility: hidden; +} +.mb-dp-day-pad__button_later_month { + visibility: hidden; +} + + +/* + Year pad +*/ + +$dp-year-pad-rows: 7; + +.mb-dp-year-pad { + display: flex; + flex-flow: row wrap; + padding: $dp-year-pad-padding-top $dp-year-pad-padding-right $dp-year-pad-padding-bottom $dp-year-pad-padding-left; + user-select: none; + overflow-x: hidden; + overflow-y: scroll; + max-height: $dp-year-pad-rows * ($dp-button-size + 2 * $dp-button-margin); +} + +.mb-dp-year-pad__button { + height: $dp-button-size !important; + min-height: $dp-button-size !important; + max-height: $dp-button-size !important; + width: $dp-year-button-width !important; + min-width: $dp-year-button-width !important; + max-width: $dp-year-button-width !important; + margin: $dp-button-margin !important; + padding: 0 !important; + + .mdc-button__ripple { + border-radius: $dp-button-border-radius; + } +} + + +/************************************************ + Applied to MBDatePicker.razor +************************************************/ + +/*$dp-menu-day-menu-height: $dp-menu-height + 7 * ($dp-button-size + 2 * $dp-button-margin) + $dp-button-margin + $dp-day-pad-bottom-margin; +$dp-menu-year-menu-height: $dp-menu-height + $dp-year-pad-rows * ($dp-button-size + 2 * $dp-button-margin) + $dp-year-pad-padding-top + $dp-year-pad-padding-bottom;*/ + +.mb-dp-menu__surface-adjust { + overflow: visible !important; + pointer-events: none; + width: $dp-panel-width; +} + +.mdc-select--activated .mb-dp-menu__surface-adjust { + pointer-events: auto !important; +} + + +.mb-dp-menu__day-menu { + /*min-height: $dp-menu-day-menu-height;*/ + display: inline; +} + +.mb-dp-menu__year-menu { + /*min-height: $dp-menu-year-menu-height;*/ + display: inline; +} + + + +.mb-dp-blank-filler { + width: $dp-panel-width; + height: $dp-panel-height; +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.ts b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.ts new file mode 100644 index 000000000..ab2e3af50 --- /dev/null +++ b/Material.Blazor.MD2/Components/DatePicker/MBDatePicker.ts @@ -0,0 +1,51 @@ +import { MDCSelect } from '@material/select'; +import { MDCMenuSurface } from '@material/menu-surface'; + +export function init(elem, menuSurfaceElem, dotNetObject) { + if (!elem || !menuSurfaceElem) { + return; + } + + elem._select = MDCSelect.attachTo(elem); + elem._menuSurface = MDCMenuSurface.attachTo(menuSurfaceElem); + + const openCallback = () => { + elem._menuSurface.unlisten('MDCMenuSurface:opened', openCallback); + dotNetObject.invokeMethodAsync('NotifyOpened'); + }; + + elem._menuSurface.listen('MDCMenuSurface:opened', openCallback); +} + +export function setDisabled(elem, value) { + if (!elem) { + return; + } + elem._select.disabled = value; +} + +export function listItemClick(elem, elemText) { + if (!elem) { + return; + } + elem.innerText = elemText; + elem.click(); +} + +function tryScrollToYear(id, attempt: number) { + setTimeout(() => { + var element = document.getElementById(id); + if (element) { + element.scrollIntoView({ behavior: 'auto', block: 'nearest', inline: 'nearest' }); + } else { + if (attempt < 10) { + tryScrollToYear(id, attempt + 1); + } + } + }, 16); +} + +export function scrollToYear(id) { + // we allow up to 10 attempts every 16ms, because Virtualize may have not yet rendered the year we want to scroll to. + tryScrollToYear(id, 0); +} diff --git a/Material.Blazor.MD2/Components/DateTimeField/MBDateTimeField.razor b/Material.Blazor.MD2/Components/DateTimeField/MBDateTimeField.razor new file mode 100644 index 000000000..8771905be --- /dev/null +++ b/Material.Blazor.MD2/Components/DateTimeField/MBDateTimeField.razor @@ -0,0 +1,28 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 + + + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/DateTimeField/MBDateTimeField.razor.cs b/Material.Blazor.MD2/Components/DateTimeField/MBDateTimeField.razor.cs new file mode 100644 index 000000000..17b5da1b6 --- /dev/null +++ b/Material.Blazor.MD2/Components/DateTimeField/MBDateTimeField.razor.cs @@ -0,0 +1,197 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme date input field. This wraps and normally +/// displays the numeric value as formatted text, but switches to a pure number on being selected. +/// +public partial class MBDateTimeField : InputComponentMD2 +{ +#nullable enable annotations + + /// + /// The datetime can optionally supress the time portion and just + /// return dates (at midnight) + /// + [Parameter] public bool DateOnly { get; set; } = false; + + + /// + /// The numeric field's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// Helper text that is displayed either with focus or persistently with . + /// + [Parameter] public string HelperText { get; set; } = ""; + + + /// + /// Makes the persistent if true. + /// + [Parameter] public bool HelperTextPersistent { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// Field label. + /// + [Parameter] public string? Label { get; set; } + + + /// + /// The leading icon's name. No leading icon shown if not set. + /// + [Parameter] public string? LeadingIcon { get; set; } + + + /// + /// The maximum allowable value. + /// + [Parameter] public DateTime MaxDate { get; set; } = MaxAllowableDate; + + + /// + /// The minimum allowable value. + /// + [Parameter] public DateTime MinDate { get; set; } = MinAllowableDate; + + + /// + /// Prefix text. + /// + [Parameter] public string? Prefix { get; set; } + + + /// + /// Suffix text. + /// + [Parameter] public string? Suffix { get; set; } + + + /// + /// Set to indicate that if the value is default(DateTime) then no date is initially shown + /// + [Parameter] public bool SuppressDefaultDate { get; set; } = true; + + + /// + /// The text input style. + /// Overrides + /// + [Parameter] public MBTextInputStyle? TextInputStyle { get; set; } + + + /// + /// The trailing icon's name. No leading icon shown if not set. + /// + [Parameter] public string? TrailingIcon { get; set; } + + + /// + /// Delivers Material Theme validation methods from native Blazor validation. Either use this or + /// the Blazor ValidationMessage component, but not both. This parameter takes the same input as + /// ValidationMessage's For parameter. + /// + [Parameter] public Expression> ValidationMessageFor { get; set; } + +#nullable restore annotations + + internal static readonly DateTime MinAllowableDate = DateTime.MinValue; + internal static readonly DateTime MaxAllowableDate = DateTime.MaxValue; + internal static string ErrorText { get; set; } + internal string ItemType { get; set; } + + private MBTextField TextField { get; set; } + private string MaxDateString { get; set; } + private string MinDateString { get; set; } + + private string FormattedValue { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + // Note the use of multiple parameters that presume invariance during the + // life of this component. + // DateOnly + // MaxDate + // MinDate + // SupressDefaultDate + + await base.OnInitializedAsync(); + + if (DateOnly) + { + ErrorText = "Invalid date, hover for more information"; + ItemType = "date"; + MaxDateString = MaxDate.ToString("yyyy-MM-dd"); + MinDateString = MinDate.ToString("yyyy-MM-dd"); + } + else + { + ErrorText = "Invalid datetime, hover for more information"; + ItemType = "datetime-local"; + MaxDateString = MaxDate.ToString("yyyy-MM-ddTHH:mm"); + MinDateString = MinDate.ToString("yyyy-MM-ddTHH:mm"); + } + } + + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + try + { + if ((ComponentValue == default) & SuppressDefaultDate) + { + FormattedValue = ""; + } + else + { + // This is the required format for the string + + if (DateOnly) + { + FormattedValue = ComponentValue.ToString("yyyy-MM-dd"); + } + else + { + FormattedValue = ComponentValue.ToString("yyyy-MM-ddTHH:mm"); + } + } + } + catch { } + } + + private async Task OnFocusOutAsync() + { + try + { + await Task.CompletedTask; + var potentialComponentValue = Convert.ToDateTime(FormattedValue); + if (potentialComponentValue >= MinDate && potentialComponentValue <= MaxDate) + { + ComponentValue = potentialComponentValue; + } + } + catch { } + } + +} diff --git a/Material.Blazor.MD2/Components/DebouncedTextField/MBDebouncedTextField.razor b/Material.Blazor.MD2/Components/DebouncedTextField/MBDebouncedTextField.razor new file mode 100644 index 000000000..3997e51c6 --- /dev/null +++ b/Material.Blazor.MD2/Components/DebouncedTextField/MBDebouncedTextField.razor @@ -0,0 +1,24 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 + + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/DebouncedTextField/MBDebouncedTextField.razor.cs b/Material.Blazor.MD2/Components/DebouncedTextField/MBDebouncedTextField.razor.cs new file mode 100644 index 000000000..e4c08e04b --- /dev/null +++ b/Material.Blazor.MD2/Components/DebouncedTextField/MBDebouncedTextField.razor.cs @@ -0,0 +1,138 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System.Threading; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme debounced text field. +/// +public partial class MBDebouncedTextField : InputComponentMD2 +{ +#nullable enable annotations + /// + /// Helper text that is displayed either with focus or persistently with . + /// + [Parameter] public string HelperText { get; set; } = ""; + + + /// + /// Makes the persistent if true. + /// + [Parameter] public bool HelperTextPersistent { get; set; } = false; + + + /// + /// The text input style. + /// Overrides + /// + [Parameter] public MBTextInputStyle? TextInputStyle { get; set; } + + + /// + /// The text alignment style. + /// Overrides + /// + [Parameter] public MBTextAlignStyle? TextAlignStyle { get; set; } + + + /// + /// Field label. + /// + [Parameter] public string? Label { get; set; } + + + /// + /// Prefix text. + /// + [Parameter] public string? Prefix { get; set; } + + + /// + /// Suffix text. + /// + [Parameter] public string? Suffix { get; set; } + + + /// + /// The leading icon's name. No leading icon shown if not set. + /// + [Parameter] public string? LeadingIcon { get; set; } + + + /// + /// The trailing icon's name. No leading icon shown if not set. + /// + [Parameter] public string? TrailingIcon { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// The text field's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// Debounce interval in milliseconds. + /// + [Parameter] public int? DebounceInterval { get; set; } +#nullable restore annotations + + + private int AppliedDebounceInterval => CascadingDefaults.AppliedDebounceInterval(DebounceInterval); + private string CurrentValue { get; set; } = ""; + private Timer Timer { get; set; } + private MBTextField TextField { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + CurrentValue = Value; + ForceShouldRenderToTrue = true; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + Timer?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + private void OnTextInput(ChangeEventArgs eventArgs) + { + Timer?.Dispose(); + CurrentValue = eventArgs.Value.ToString(); + var autoReset = new AutoResetEvent(false); + Timer = new Timer(OnTimerComplete, autoReset, AppliedDebounceInterval, Timeout.Infinite); + } + + + private void OnTimerComplete(object stateInfo) => InvokeAsync(() => ComponentValue = CurrentValue); +} diff --git a/Material.Blazor.MD2/Components/Divider/MBDivider.md b/Material.Blazor.MD2/Components/Divider/MBDivider.md new file mode 100644 index 000000000..89d1bdc5e --- /dev/null +++ b/Material.Blazor.MD2/Components/Divider/MBDivider.md @@ -0,0 +1,22 @@ +--- +uid: C.MBDivider +title: MBDivider +--- +# MBDivider + +## Summary + +A Material Divider which is a horizonal rule with boolean indicators for the material inset and padded CSS classes. + +## Reserved Attributes + +The following attributes are reserved by Material Components Web and will be ignored if you supply them: + +- role + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBDivider&color=brightgreen)](xref:Material.Blazor.MD2.MBDivider) diff --git a/Material.Blazor.MD2/Components/Divider/MBDivider.razor b/Material.Blazor.MD2/Components/Divider/MBDivider.razor new file mode 100644 index 000000000..ae128db8e --- /dev/null +++ b/Material.Blazor.MD2/Components/Divider/MBDivider.razor @@ -0,0 +1,9 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundation + + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Divider/MBDivider.razor.cs b/Material.Blazor.MD2/Components/Divider/MBDivider.razor.cs new file mode 100644 index 000000000..a9be89db7 --- /dev/null +++ b/Material.Blazor.MD2/Components/Divider/MBDivider.razor.cs @@ -0,0 +1,35 @@ +using Material.Blazor.MD2.Internal; + +using Microsoft.AspNetCore.Components; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme divider. +/// +public partial class MBDivider : ComponentFoundation +{ + /// + /// Material Theme "mdc-deprecated-list-divider--inset" if True. + /// + [Parameter] public bool Inset { get; set; } + + + /// + /// Material Theme "mdc-deprecated-list-divider--padded" if True. + /// + [Parameter] public bool Padded { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ConditionalCssClasses + .AddIf("mdc-deprecated-list-divider--inset", () => Inset) + .AddIf("mdc-deprecated-list-divider--padded", () => Padded); + } + +} diff --git a/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropItem.razor b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropItem.razor new file mode 100644 index 000000000..64c557f0d --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropItem.razor @@ -0,0 +1,28 @@ +@namespace Material.Blazor.MD2.Internal +@inherits ComponentFoundationMD2 + + +
+ @if (AppliedDisabled) + { +
+ + +
+ } + else + { +
+ + +
+ } + +
+ @ChildContent +
+
diff --git a/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropItem.razor.cs b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropItem.razor.cs new file mode 100644 index 000000000..122b8d647 --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropItem.razor.cs @@ -0,0 +1,64 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +/// +/// For Material.Blazor.MD2 internal use only. +/// +public partial class InternalDragAndDropItem : ComponentFoundationMD2 +{ + /// + /// The item's index. + /// + [Parameter] public int Index { get; set; } + + + /// + /// Adds padding to user supplied content if true. + /// + [Parameter] public bool AutospaceContent { get; set; } + + + /// + /// The item's render fragment. + /// + [Parameter] public RenderFragment ChildContent { get; set; } + + + /// + /// Action called when item is dropped on this spacer. + /// + [Parameter] public Action DragStartNotifier { get; set; } + + + /// + /// Action called when item is dropped on this spacer. + /// + [Parameter] public Action DragEndNotifier { get; set; } + + + private string UserContentClass { get; set; } = ""; + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync().ConfigureAwait(false); + + UserContentClass = "mb-drag-and-drop-list__user-content" + (AutospaceContent ? " mb-card__autostyled" : ""); + } + + + private void OnDragStart() + { + DragStartNotifier(Index); + } + + + private void OnDragEnd() + { + DragEndNotifier(); + } +} diff --git a/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropSpacer.razor b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropSpacer.razor new file mode 100644 index 000000000..f18c8b9e1 --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropSpacer.razor @@ -0,0 +1,16 @@ +@namespace Material.Blazor.MD2.Internal +@inherits ComponentFoundationMD2 + + +
+ @if (ShowDropZone) + { +
+ +
+
+ } +
diff --git a/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropSpacer.razor.cs b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropSpacer.razor.cs new file mode 100644 index 000000000..2d43d9689 --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/InternalDragAndDropSpacer.razor.cs @@ -0,0 +1,92 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +/// +/// For Material.Blazor.MD2 internal use only. +/// +public partial class InternalDragAndDropSpacer : ComponentFoundationMD2 +{ + /// + /// The spacer's index. + /// + [Parameter] public int Index { get; set; } + + + /// + /// Gives the separator a distinct with for use between cards. + /// + [Parameter] public bool AutospaceContent { get; set; } + + + /// + /// True to show the drop zone. + /// + [Parameter] public bool ShowDropZone { get; set; } + + + /// + /// Action called when item is dropped on this spacer. + /// + [Parameter] public Func DropNotifier { get; set; } + + + private string HoverClass { get; set; } = ""; + private string SeparatorClass { get; set; } = ""; + private ElementReference ElementReference { get; set; } + private bool InitiateDropTarget { get; set; } = false; + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync().ConfigureAwait(false); + + SeparatorClass = "mb-drag-and-drop-list__separator" + (AutospaceContent ? " mb-drag-and-drop-list__autospaced" : ""); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + InitiateDropTarget = ShowDropZone; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnInitializedAsync().ConfigureAwait(false); + + if (InitiateDropTarget) + { + await InvokeJsVoidAsync("MaterialBlazor.MBDragAndDropList.initDropTarget", ElementReference).ConfigureAwait(false); + InitiateDropTarget = false; + } + } + + + private void OnDragEnter() + { + HoverClass = "mb-drag-and-drop-list__hover"; + _ = InvokeAsync(StateHasChanged); + } + + + private void OnDragLeave() + { + HoverClass = string.Empty; + _ = InvokeAsync(StateHasChanged); + } + + + private async Task OnDropAsync() + { + HoverClass = string.Empty; + await DropNotifier(Index).ConfigureAwait(false); + } +} diff --git a/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.razor b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.razor new file mode 100644 index 000000000..8c66f060f --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.razor @@ -0,0 +1,37 @@ +@namespace Material.Blazor.MD2 +@inherits InputComponentMD2> +@typeparam TItem + + +
+ + + + @foreach (var (index, item) in ItemDict) + { + var hasMovedClass = index >= FirstHasMovedIndex && index <= LastHasMovedIndex ? "mb-drag-and-drop__has-moved" : ""; + + @if (DisplayCards) + { + + + + @Content(item) + + + + } + else + { + + @Content(item) + + } + + + } +
\ No newline at end of file diff --git a/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.razor.cs b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.razor.cs new file mode 100644 index 000000000..f578e5de1 --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.razor.cs @@ -0,0 +1,138 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A list of user provided render fragments that can be re-ordered with drag and drop. +/// +public partial class MBDragAndDropList : InputComponentMD2> +{ + /// + /// A function delegate to return the parameters for @key attributes. If unused + /// "fake" keys set to GUIDs will be used. + /// + [Parameter] public Func GetKeysFunc { get; set; } + + + /// + /// Displays each list item in cards if true. + /// + [Parameter] public bool DisplayCards { get; set; } + + + /// + /// Render fragment for each displayable item. + /// + [Parameter] public RenderFragment Content { get; set; } + + + private ElementReference ElementReference { get; set; } + private Func KeyGenerator { get; set; } + private string HoverClass { get; set; } = ""; + private int DraggedItemIndex { get; set; } = -1; + private bool IsDragging { get; set; } = false; + private SortedDictionary ItemDict { get; set; } = new(); + private int FirstHasMovedIndex { get; set; } = -1; + private int LastHasMovedIndex { get; set; } = -1; + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ForceShouldRenderToTrue = true; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + KeyGenerator = GetKeysFunc ?? delegate (TItem item) { return item; }; + + BuildItemDict(); + } + + + private void BuildItemDict() + { + ItemDict.Clear(); + + var index = 0; + + foreach (var item in Value) + { + ItemDict.Add(index++, item); + } + } + + + private void SetDraggedItemIndex(int index) + { + DraggedItemIndex = index; + IsDragging = true; + _ = InvokeAsync(StateHasChanged); + } + + + private void ClearDraggedItemIndex() + { + DraggedItemIndex = -1; + IsDragging = false; + _ = InvokeAsync(StateHasChanged); + } + + + private bool ShowDropZone(int index) + { + return IsDragging && index != DraggedItemIndex && index != DraggedItemIndex + 1; + } + + + private async Task ReOrderItems(int selectedIndex) + { + SortedDictionary newDict = new(); + + var newIndex = 0; + + foreach (var (index, item) in ItemDict.Where(x => x.Key < selectedIndex)) + { + if (index != DraggedItemIndex) + { + newDict[newIndex++] = item; + } + } + + newDict[newIndex++] = ItemDict[DraggedItemIndex]; + + foreach (var (index, item) in ItemDict.Where(x => x.Key >= selectedIndex)) + { + if (index != DraggedItemIndex) + { + newDict[newIndex++] = item; + } + } + + ItemDict = newDict; + ComponentValue = ItemDict.Values.ToList(); + + FirstHasMovedIndex = selectedIndex > DraggedItemIndex ? DraggedItemIndex : selectedIndex; + LastHasMovedIndex = selectedIndex > DraggedItemIndex ? selectedIndex - 1 : DraggedItemIndex; + _ = InvokeAsync(StateHasChanged); + + await Task.Delay(300); + + FirstHasMovedIndex = -1; + LastHasMovedIndex = -1; + _ = InvokeAsync(StateHasChanged); + } +} diff --git a/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.scss b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.scss new file mode 100644 index 000000000..8afefd681 --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.scss @@ -0,0 +1,157 @@ +@use "@material/theme"; +@use 'sass:color'; +@use "sass:math"; + +$autospaced-separator-height: 24px; + +$drop-zone-overlap: 12px; +$drop-zone-height: $autospaced-separator-height + 2 * $drop-zone-overlap; +$drop-zone-rule-width: 2px; +$drop-zone-border-width: 4px; +$drop-zone-hr-top-margin: math.div($drop-zone-rule-width, 2); +$drag-zone-height: 48px; +$drag-zone-width: 60px; + +$hover-transition-time: 120ms; +$animation-time: 3000ms; + +.mb-drag-and-drop-list { + display: flex; + flex-flow: column nowrap; +} + +.mb-drag-and-drop__has-moved { + animation: mbDragAndDropHasMoved $animation-time; +} + +.mb-drag-and-drop-list__card-contents { + height: 100%; + display: flex; + flex-flow: row nowrap; + align-items: center; +} + +.mb-drag-and-drop-list__draggable { + height: $drag-zone-height; + width: $drag-zone-width; + flex-grow: 0; + margin: 0; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: center; + cursor: move; + + & > i { + margin: auto; + } + + &.mb-drag-and-drop-list__disabled { + cursor: default; + + & > i { + color: var(--mdc-theme-text-disabled-on-light); + } + } +} + +.mb-drag-and-drop-list__user-content { + height: fit-content; + width: fit-content; + flex-grow: 1; + margin: 0px; +} + +.mb-drag-and-drop-list__separator { + display: flex; + flex-flow: row nowrap; + align-items: center; + height: 0px; + width: 100%; + z-index: 1; + + &.mb-drag-and-drop-list__autospaced { + height: $autospaced-separator-height; + } + + & > .mb-drag-and-drop-list__drop-target { + display: flex; + flex-flow: row nowrap; + align-items: center; + box-sizing: border-box; + -webkit-box-sizing: border-box; + width: 100%; + height: $drop-zone-height; + background-color: rgba(0, 0, 0, 0); + border: dotted; + border-color: rgba(0, 0, 0, 0); + border-width: $drop-zone-border-width; + transition: ease-in-out $hover-transition-time; + + & > hr { + margin: $drop-zone-hr-top-margin 0 0 0; + width: 100%; + border: none; + border-top: $drop-zone-rule-width dotted var(--mdc-theme-secondary); + color: rgba(0, 0, 0, 0); + background-color: rgba(0, 0, 0, 0); + height: $drop-zone-rule-width; + pointer-events: none; + transition: ease-in-out $hover-transition-time; + } + + &.mb-drag-and-drop-list__hover { + background-color: rgba(theme.$on-surface, 0.04); + border-color: var(--mdc-theme-secondary); + + & > hr { + border-top-color: rgba(0, 0, 0, 0); + } + } + } + + & > .mb-drag-and-drop-list__rule { + display: flex; + flex-flow: row nowrap; + height: 100%; + align-items: center; + + &.mb-drag-and-drop-list__hover { + opacity: 0; + } + } +} + +@keyframes mbDragAndDropHasMoved { + 0% { + opacity: 0; + animation-timing-function: ease-in-out; + } + + 10% { + opacity: 1; + animation-timing-function: ease-in-out; + } + + 100% { + opacity: 1; + animation-timing-function: ease-in-out; + } +} + +@-webkit-keyframes mbDragAndDropHasMoved { + 0% { + opacity: 0; + animation-timing-function: ease-in-out; + } + + 10% { + opacity: 1; + animation-timing-function: ease-in-out; + } + + 100% { + opacity: 1; + animation-timing-function: ease-in-out; + } +} diff --git a/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.ts b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.ts new file mode 100644 index 000000000..aa89b2f97 --- /dev/null +++ b/Material.Blazor.MD2/Components/DragAndDropList/MBDragAndDropList.ts @@ -0,0 +1,10 @@ +export function initDropTarget(elem): void { + if (!elem) { + return; + } + + elem.addEventListener('dragover', event => + { + event.preventDefault(); + }); +} diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUpload.scss b/Material.Blazor.MD2/Components/FileUpload/MBFileUpload.scss new file mode 100644 index 000000000..d9ada3314 --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUpload.scss @@ -0,0 +1,72 @@ +.mb-file-upload--button { + position: relative; + width: fit-content; + overflow: hidden; + + & > input { + position: absolute; + top: 0; + left: 0; + height: 0px; + width: 0px; + visibility: hidden; + } +} + +.mb-file-upload--drag-and-drop { + height: 96px; + border-width: 4px; + border-color: var(--mdc-theme-primary); + color: var(--mdc-theme-primary); + transition: 50ms 0ms cubic-bezier(0.4, 0, 1, 1); + + & > div { + height: 100%; + } + + &.mb-file-upload--hover { + background: var(--mdc-theme-primary); + color: var(--mdc-theme-on-primary); + box-shadow: 0px 4px 5px -2px rgba(0, 0, 0, 0.2),0px 7px 10px 1px rgba(0, 0, 0, 0.14),0px 2px 16px 1px rgba(0,0,0,.12); + } + + & .mb-file-upload__card-content { + display: flex; + align-items: center; + justify-content: center; + position: relative; + overflow: hidden; + width: 100%; + height: 100%; + + & input { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + opacity: 0; + } + + & .mb-file-upload__filename { + position: absolute; + bottom: 0; + right: 6px; + pointer-events: none; + } + } + + .mb-card__title { + display: flex; + align-items: center; + justify-content: center; + } + + .mb-file-upload__leading-icon { + margin-right: 8px; + } + + .mb-file-upload__trailing-icon { + margin-left: 8px; + } +} diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUpload.ts b/Material.Blazor.MD2/Components/FileUpload/MBFileUpload.ts new file mode 100644 index 000000000..78d212491 --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUpload.ts @@ -0,0 +1,9 @@ +export function click(elem) { + if (!elem) { + return; + } + + var input = elem.querySelector("input"); + input.click(); +} + diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.md b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.md new file mode 100644 index 000000000..a6ac319eb --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.md @@ -0,0 +1,19 @@ +--- +uid: C.MBFileUploadButton +title: MBFileUploadButton +--- +# MBFileUploadButton + +## Summary + +A material button styled wrapper for the `InputFile` component. + +- Allows for single or multiple file upload. +- Can be disabled and styled like an [MBButton](xref:C.MBButton). + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBFileUploadButton&color=brightgreen)](xref:Material.Blazor.MD2.MBFileUploadButton) diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.razor b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.razor new file mode 100644 index 000000000..abe421066 --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.razor @@ -0,0 +1,24 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundation +@using Microsoft.AspNetCore.Components.Forms + + +
+ + + + + +
diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.razor.cs b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.razor.cs new file mode 100644 index 000000000..68298b848 --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadButton.razor.cs @@ -0,0 +1,87 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Net.NetworkInformation; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + + +namespace Material.Blazor.MD2; + +/// +/// A material button styled wrapper for the `InputFile` component. +/// +public partial class MBFileUploadButton : ComponentFoundation +{ +#nullable enable annotations + /// + /// The button's Material Theme style - see . + /// Overrides , or as relevant. + /// + [Parameter] public MBButtonStyle? ButtonStyle { get; set; } + + + /// + /// The button's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// The button's label. + /// + [Parameter] public string Label { get; set; } = "Choose File"; + + + /// + /// The leading icon's name. No leading icon shown if not set. + /// + [Parameter] public string? LeadingIcon { get; set; } + + + /// + /// The trailing icon's name. No leading icon shown if not set. + /// + [Parameter] public string? TrailingIcon { get; set; } + + + /// + /// Inclusion of touch target + /// + [Parameter] public bool? TouchTarget { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// REQUIRED function called when files are loaded. + /// + [Parameter] public Func OnLoadFiles { get; set; } + +#nullable restore annotations + + private ElementReference ElementReference { get; set; } + + + private async Task OnClick() + { + await InvokeJsVoidAsync("MaterialBlazor.MBFileUpload.click", ElementReference).ConfigureAwait(false); + } +} diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.md b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.md new file mode 100644 index 000000000..f54314e31 --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.md @@ -0,0 +1,20 @@ +--- +uid: C.MBFileUploadDragAndDrop +title: MBFileUploadDragAndDrop +--- +# MBFileUpload + +## Summary + +A material card styled wrapper for the `InputFile` component that can load files either by drag and drop or clicking the card area. + +- Allows for single or multiple file upload. +- Styled using an [MBCard](xref:C.MBCard) with a primary action area. +- Files can be dragged and dropped on to the card or the card can be clicked with a primary action with ripple response. + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBFileUploadDragAndDrop&color=brightgreen)](xref:Material.Blazor.MD2.MBFileUploadDragAndDrop) diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.razor b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.razor new file mode 100644 index 000000000..8388326a4 --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.razor @@ -0,0 +1,41 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundation +@using Microsoft.AspNetCore.Components.Forms + + + + + +
+
+ @if (!string.IsNullOrWhiteSpace(LeadingIcon)) + { + + } + + + + @if (!string.IsNullOrWhiteSpace(TrailingIcon)) + { + + } +
+ + + +
+ @Filename +
+
+
+
diff --git a/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.razor.cs b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.razor.cs new file mode 100644 index 000000000..18c56ac7b --- /dev/null +++ b/Material.Blazor.MD2/Components/FileUpload/MBFileUploadDragAndDrop.razor.cs @@ -0,0 +1,90 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Net.NetworkInformation; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; + + +namespace Material.Blazor.MD2; + +/// +/// A material card styled wrapper for the InputFile component that can load files either by drag and drop or clicking the card area +/// +public partial class MBFileUploadDragAndDrop : ComponentFoundation +{ +#nullable enable annotations + /// + /// The button's label. + /// + [Parameter] public string Label { get; set; } = "Choose File"; + + + /// + /// The leading icon's name. No leading icon shown if not set. + /// + [Parameter] public string? LeadingIcon { get; set; } + + + /// + /// The trailing icon's name. No leading icon shown if not set. + /// + [Parameter] public string? TrailingIcon { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// REQUIRED function called when files are loaded. + /// + [Parameter] public Func OnLoadFiles { get; set; } + +#nullable restore annotations + + private ElementReference ElementReference { get; set; } + private string HoverClass { get; set; } = string.Empty; + private string CardClass => $"mb-file-upload--drag-and-drop {ActiveConditionalClasses} {@class}" + HoverClass; + private string Filename { get; set; } = string.Empty; + private readonly string labelId = Utilities.GenerateUniqueElementName(); + + + private void OnDragEnter(DragEventArgs _) + { + HoverClass = " mb-file-upload--hover mdc-elevation--z7"; + } + + + private void OnDragLeave(DragEventArgs _) + { + HoverClass = string.Empty; + } + + + private Task LocalOnLoadFiles(InputFileChangeEventArgs e) + { + var files = e.GetMultipleFiles(); + var count = files.Count; + + Filename = count == 1 ? files.FirstOrDefault().Name : $"{count:N0} Files"; + + return OnLoadFiles(e); + } +} diff --git a/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.razor b/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.razor new file mode 100644 index 000000000..39ee60209 --- /dev/null +++ b/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.razor @@ -0,0 +1,46 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundationMD2 + +@if (Type == MBFloatingActionButtonType.Mini) +{ + + + +} +else +{ + +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.razor.cs b/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.razor.cs new file mode 100644 index 000000000..96a1d85e0 --- /dev/null +++ b/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.razor.cs @@ -0,0 +1,96 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; + +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// This is a Material Theme FAB or floating action button, with regular, mini or extended variants, +/// application of the "mdc-fab--exited" animated class and the choice of leading or trailing icons for +/// the extended variant. +/// +public partial class MBFloatingActionButton : ComponentFoundationMD2 +{ + [CascadingParameter] private MBCard Card { get; set; } + + /// + /// Inclusion of touch target + /// + [Parameter] public bool? TouchTarget { get; set; } + + +#nullable enable annotations + /// + /// The icon's name. + /// + [Parameter] public string Icon { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// Sets the FAB to be regular or the mini or extended variants. + /// + [Parameter] public MBFloatingActionButtonType Type { get; set; } + + + /// + /// Sets the label, which is ignored for anything other than the extended variant. + /// + [Parameter] public string Label { get; set; } + + + private bool AppliedTouchTarget => CascadingDefaults.AppliedTouchTarget(TouchTarget); + + + /// + /// When true collapses the FAB. + /// + [Parameter] + public bool Exited { get; set; } + private bool _cachedExited; +#nullable restore annotations + + + private ElementReference ElementReference { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + _ = ConditionalCssClasses + .AddIf("mdc-fab--mini mdc-fab--touch", () => Type == MBFloatingActionButtonType.Mini) + .AddIf("mdc-fab--extended", () => Type == MBFloatingActionButtonType.ExtendedNoIcon || Type == MBFloatingActionButtonType.ExtendedLeadingIcon || Type == MBFloatingActionButtonType.ExtendedTrailingIcon) + .AddIf("mdc-fab--exited", () => Exited); + } + + + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + if (_cachedExited != Exited) + { + _cachedExited = Exited; + EnqueueJSInteropAction(() => InvokeJsVoidAsync("MaterialBlazor.MBFloatingActionButton.setExited", ElementReference, Exited)); + } + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBFloatingActionButton.init", ElementReference, Exited); + } +} diff --git a/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.ts b/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.ts new file mode 100644 index 000000000..5d63c1b74 --- /dev/null +++ b/Material.Blazor.MD2/Components/FloatingActionButton/MBFloatingActionButton.ts @@ -0,0 +1,18 @@ +import { MDCRipple } from '@material/ripple'; + +export function init(elem, exited) { + elem._fab = MDCRipple.attachTo(elem); + elem._exited = false; + setExited(elem, exited); +} + +export function setExited(elem, exited) { + if (elem) { + if (exited != elem._exited) { + elem.classList.add("mdc-fab--exited"); + } + else { + elem.classList.remove("mdc-fab--exited"); + } + } +} diff --git a/Material.Blazor.MD2/Components/Grid/MBEnumerationsGrid.cs b/Material.Blazor.MD2/Components/Grid/MBEnumerationsGrid.cs new file mode 100644 index 000000000..83dffb926 --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBEnumerationsGrid.cs @@ -0,0 +1,21 @@ +// ToDo: +// +// Move enumerations to MBEnumerations +// + +namespace Material.Blazor.MD2; + +public enum MB_Grid_Measurement +{ + EM, + FitToData, + Percent, + PX, +} + +public enum MB_Grid_ColumnType +{ + Icon, + Text, + TextColor, +}; diff --git a/Material.Blazor.MD2/Components/Grid/MBGrid.cs b/Material.Blazor.MD2/Components/Grid/MBGrid.cs new file mode 100644 index 000000000..4c2088665 --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGrid.cs @@ -0,0 +1,1154 @@ +// +// ToDo: +// If we ever have functionality to 'move' rows we need to revisit the +// Steve Sanderson 'best practices' for sequence numbers +// +// Bugs: +// Resolve issue with ElementReferences +// + +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +using Material.Blazor.MD2.Internal; + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; +// +// Implements a scrollable, multi-column grid. When created we get a list of column +// config objects and a list of data objects with the column content for each +// row. +// +// We 'select' a line when it is clicked on so the caller can either immediately respond or +// save the selection for later. +// + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme grid capable of displaying icons, colored text, and text. +/// +/// N.B.: At this time the grid is in preview. Expect the API to change. +/// +public class MBGrid : ComponentFoundationMD2 +{ + #region Members + + // Remember that adding/removing/renaming parameters requires an update + // to SetParametersAsync + + /// + /// The configuration of each column to be displayed. See the definition of MBGridColumnConfiguration + /// for details. + /// + [Parameter, EditorRequired] public IEnumerable> ColumnConfigurations { get; set; } = null; + + + /// + /// The Group is an optional boolean indicating that grouping is in effect. + /// + [Parameter] public bool Group { get; set; } = false; + + + /// + /// The GroupedOrderedData contains the data to be displayed. + /// The outer key is used for grouping and is directly displayed if grouping is enabled. + /// The inner key must be a unique identifier + /// that is used to indicate a row that has been clicked. + /// + [Parameter, EditorRequired] public IEnumerable>>> GroupedOrderedData { get; set; } + + + /// + /// A boolean indicating whether the selected row is highlighted + /// + [Parameter] public bool HighlightSelectedRow { get; set; } = false; + + +#nullable enable annotations + /// + /// The KeyExpression is used to add a key to each row of the grid + /// + [Parameter] public Func? KeyExpression { get; set; } = null; +#nullable restore annotations + + + /// + /// LogIdentification is added to logging message to allow differentiation between multiple grids + /// on a single page or component + /// + [Parameter] public string LogIdentification { get; set; } = ""; + + + /// + /// Measurement determines the unit of size (EM, Percent, PX) or if the grid is to measure the + /// data widths (FitToData) + /// + [Parameter] public MB_Grid_Measurement Measurement { get; set; } = MB_Grid_Measurement.Percent; + + + /// + /// ObscurePMI controls whether or not columns marked as PMI are obscured. + /// + [Parameter] public bool ObscurePMI { get; set; } + + + /// + /// Callback for a mouse click + /// + [Parameter] public EventCallback OnMouseClickCallback { get; set; } + + + /// + /// Headers are optional + /// + [Parameter] public bool SuppressHeader { get; set; } = false; + + + [Inject] IJSRuntime JsRuntime { get; set; } + + + private float[] ColumnWidthArray; + private ElementReference GridBodyRef { get; set; } + private ElementReference GridHeaderRef { get; set; } + private string GridBodyID { get; set; } = UtilitiesMD2.GenerateUniqueElementName(); + private string GridHeaderID { get; set; } = UtilitiesMD2.GenerateUniqueElementName(); + private bool HasCompletedFullRender { get; set; } = false; + private bool IsSimpleRender { get; set; } = true; + private bool IsMeasurementNeeded { get; set; } = false; + private float ScrollWidth { get; set; } + private string SelectedKey { get; set; } = ""; + + //Instantiate a Semaphore with a value of 1. This means that only 1 thread can be granted access at a time. + private readonly SemaphoreSlim semaphoreSlim = new(1, 1); + + private bool ShouldRenderValue { get; set; } = true; + + #endregion + + #region BuildColGroup + private void BuildColGroup(RenderTreeBuilder builder, ref int rendSeq) + { + // Create the sizing colgroup collection + builder.OpenElement(rendSeq++, "colgroup"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-colgroup"); + var colIndex = 0; + foreach (var col in ColumnConfigurations) + { + var styleStr = CreateMeasurementStyle(col, ColumnWidthArray[colIndex]); + builder.OpenElement(rendSeq++, "col"); + builder.AddAttribute(rendSeq++, "style", styleStr); + builder.CloseElement(); // col + colIndex++; + } + builder.CloseElement(); // colgroup + } + #endregion + + #region BuildGridTDElement + private static string BuildGridTDElement( + RenderTreeBuilder builder, + ref int rendSeq, + bool isFirstColumn, + bool isHeaderRow, + string rowBackgroundColorClass) + { + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-td " + rowBackgroundColorClass); + + if (isHeaderRow) + { + if (isFirstColumn) + { + // T R B L + return " border-width: 1px; border-style: solid; border-color: black; "; + } + else + { + // T R B + return " border-width: 1px 1px 1px 0px; border-style: solid; border-color: black; "; + } + } + else + { + if (isFirstColumn) + { + // R L + return " border-width: 0px 1px 0px 1px; border-style: solid; border-color: black; "; + } + else + { + // R + return " border-width: 0px 1px 0px 0px; border-style: solid; border-color: black; "; + } + } + } + #endregion + + #region BuildRenderTree + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + LoggingService.LogDebug("[" + LogIdentification + "] BuildRenderTree entered; IsSimpleRender == " + IsSimpleRender.ToString()); + LoggingService.LogDebug("[" + LogIdentification + "] HasCompletedFullRender == " + HasCompletedFullRender.ToString()); + LoggingService.LogDebug("[" + LogIdentification + "] ShouldRenderValue == " + ShouldRenderValue.ToString()); + if (IsSimpleRender || (!ShouldRenderValue)) + { + LoggingService.LogDebug("[" + LogIdentification + "] (Simple) entered"); + // We are going to render a DIV and nothing else + // We need to get into OnAfterRenderAsync so that we can use JS interop to measure + // the text + base.BuildRenderTree(builder); + builder.OpenElement(1, "div"); + builder.CloseElement(); + HasCompletedFullRender = false; + LoggingService.LogDebug("[" + LogIdentification + "] (Simple) leaving"); + } + else + { + LoggingService.LogDebug("[" + LogIdentification + "] (Full) entered"); + + // + // Using the column cfg and column data, render our list. Here is the layout. + // The column headers are optional. + // + // div class="@class", style="@style" + // div mb-grid-header - Contains the header and the vscroll + // table - + // tr - + // td* - Header + // div mb-grid-body - Contains the rows and the vscroll + // table - Contains the rows + // tr* - Rows + // td* - Columns of the row + // + + base.BuildRenderTree(builder); + var rendSeq = 2; + string styleStr; + + if (((@class != null) && (@class.Length > 0)) || ((style != null) && (style.Length > 0))) + { + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-div-outer " + @class); + builder.AddAttribute(rendSeq++, "style", style); + } + + // Based on the column config generate the column titles unless asked not to + if (!SuppressHeader) + { + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-div-header mb-grid-backgroundcolor-header-background"); + //builder.AddAttribute(rendSeq++, "style", "padding-right: " + ScrollWidth.ToString() + "px; "); + builder.AddAttribute(rendSeq++, "id", GridHeaderID); + builder.AddElementReferenceCapture(rendSeq++, (__value) => { GridHeaderRef = __value; }); + builder.OpenElement(rendSeq++, "table"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-table"); + BuildColGroup(builder, ref rendSeq); + builder.OpenElement(rendSeq++, "thead"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-thead"); + builder.OpenElement(rendSeq++, "tr"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-tr"); + + // For each column output a TD + var isHeaderRow = true; + var colCount = 0; + foreach (var col in ColumnConfigurations) + { + styleStr = BuildGridTDElement( + builder, + ref rendSeq, + colCount == 0, + isHeaderRow, + "mb-grid-backgroundcolor-header-background"); + + // Set the header colors + styleStr += " color: " + col.ForegroundColorHeader.Name + ";"; + styleStr += " background-color : " + col.BackgroundColorHeader.Name + ";"; + + builder.AddAttribute(rendSeq++, "style", styleStr); + builder.AddContent(rendSeq++, col.Title); + + // Close this column TD + builder.CloseElement(); + + colCount++; + } + + builder.CloseElement(); // tr + + builder.CloseElement(); // thead + + builder.CloseElement(); //table + + builder.CloseElement(); // div mb-grid-header + } + + // + // We now need to build a "display centric" data representation with rows added for breaks, etc. + // For the first pass we are going to skip this step and just display the raw content + // + + if (GroupedOrderedData != null) + { + var isFirstGrouper = true; + + // This div holds the scrolled content + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-div-body"); + builder.AddAttribute(rendSeq++, "id", "mb-grid-div-body"); + builder.AddAttribute(rendSeq++, "onscroll", + EventCallback.Factory.Create(this, GridSyncScroll)); + builder.AddAttribute(rendSeq++, "id", GridBodyID); + builder.AddElementReferenceCapture(rendSeq++, (__value) => { GridBodyRef = __value; }); + builder.OpenElement(rendSeq++, "table"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-table"); + BuildColGroup(builder, ref rendSeq); + builder.OpenElement(rendSeq++, "tbody"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-tbody"); + + foreach (var kvp in GroupedOrderedData) + { + if (Group) + { + // We output a row with the group name + // Do a div for this row + builder.OpenElement(rendSeq++, "tr"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-tr"); + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "colspan", ColumnConfigurations.Count().ToString()); + builder.AddAttribute(rendSeq++, "class", "mb-grid-td-group mb-grid-backgroundcolor-row-group"); + if (isFirstGrouper) + { + isFirstGrouper = false; + builder.AddAttribute(rendSeq++, "style", "border-top: 1px solid black; "); + } + builder.AddAttribute(rendSeq++, "mbgrid-td-wide", "0"); + builder.AddContent(rendSeq++, " " + kvp.Key); + builder.CloseElement(); // td + builder.CloseElement(); // tr + } + + var rowCount = 0; + foreach (var rowValues in kvp.Value) + { + var rowKey = KeyExpression(rowValues.Value).ToString(); + + string rowBackgroundColorClass; + if ((rowKey == SelectedKey) && HighlightSelectedRow) + { + // It's the selected row so set the selection color as the background + rowBackgroundColorClass = "mb-grid-backgroundcolor-row-selected"; + } + else + { + // Not selected or not highlighted so we alternate + if ((rowCount / 2) * 2 == rowCount) + { + // Even + rowBackgroundColorClass = "mb-grid-backgroundcolor-row-even"; + } + else + { + // Odd + rowBackgroundColorClass = "mb-grid-backgroundcolor-row-odd"; + } + } + + // Do a tr + builder.OpenElement(rendSeq++, "tr"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-tr " + rowBackgroundColorClass); + builder.AddAttribute(rendSeq++, "id", rowKey); + + builder.AddAttribute + ( + rendSeq++, + "onclick", + EventCallback.Factory.Create(this, e => OnMouseClickInternal(rowKey)) + ); + + // For each column output a td + var colCount = 0; + var isHeaderRow = false; + foreach (var columnDefinition in ColumnConfigurations) + { + styleStr = BuildGridTDElement( + builder, + ref rendSeq, + colCount == 0, + isHeaderRow, + rowBackgroundColorClass); + + switch (columnDefinition.ColumnType) + { + case MB_Grid_ColumnType.Icon: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridIconSpecification)columnDefinition.DataExpression(rowValues.Value); + + // We need to add the color alignment to the base styles + styleStr += + " color: " + ColorToCSSColor(value.IconColor) + ";" + + " text-align: center;"; + + builder.AddAttribute(rendSeq++, "style", styleStr); + builder.OpenComponent(rendSeq++, typeof(MBIcon)); + builder.AddAttribute(rendSeq++, "IconFoundry", value.IconFoundry); + builder.AddAttribute(rendSeq++, "IconName", value.IconName); + builder.CloseComponent(); + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.Icon column."); + } + } + break; + + case MB_Grid_ColumnType.Text: + // It's a text type column so add the text related styles + // We may be overriding the alternating row color added by class + + if (columnDefinition.ForegroundColorExpression != null) + { + var value = columnDefinition.ForegroundColorExpression(rowValues.Value); + styleStr += + " color: " + ColorToCSSColor((Color)value) + "; "; + } + + if (columnDefinition.BackgroundColorExpression != null) + { + var value = columnDefinition.BackgroundColorExpression(rowValues.Value); + if ((Color)value != Color.Transparent) + { + styleStr += + " background-color: " + ColorToCSSColor((Color)value) + "; "; + } + } + + if (columnDefinition.IsPMI && ObscurePMI) + { + styleStr += + " filter: blur(0.25em); "; + } + + builder.AddAttribute(rendSeq++, "style", styleStr); + + // Bind the object as our content. + if (columnDefinition.DataExpression != null) + { + var value = columnDefinition.DataExpression(rowValues.Value); + var formattedValue = string.IsNullOrEmpty(columnDefinition.FormatString) ? value?.ToString() : string.Format("{0:" + columnDefinition.FormatString + "}", value); + builder.AddContent(1, formattedValue); + } + break; + + case MB_Grid_ColumnType.TextColor: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridTextColorSpecification)columnDefinition.DataExpression(rowValues.Value); + + if (value.Suppress) + { + builder.AddAttribute(rendSeq++, "style", styleStr); + } + else + { + // We need to add the colors + styleStr += + " color: " + ColorToCSSColor(value.ForegroundColor) + + "; background-color: " + ColorToCSSColor(value.BackgroundColor) + ";"; + + if (columnDefinition.IsPMI && ObscurePMI) + { + styleStr += + " filter: blur(0.25em); "; + } + + builder.AddAttribute(rendSeq++, "style", styleStr); + builder.AddContent(rendSeq++, value.Text); + } + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.TextColor column."); + } + } + break; + + default: + throw new Exception("MBGrid -- Unknown column type"); + } + + // Close this column span + builder.CloseElement(); + + colCount++; + } + + // Close this row's div + builder.CloseElement(); + + rowCount++; + } + } + + builder.CloseElement(); // tbody + + builder.CloseElement(); // table + + builder.CloseElement(); // div mb-grid-body-outer + + if (((@class != null) && (@class.Length > 0)) || ((style != null) && (style.Length > 0))) + { + builder.CloseElement(); // div class= style= + } + } + + HasCompletedFullRender = true; + LoggingService.LogDebug("[" + LogIdentification + "] (Full) leaving"); + } + LoggingService.LogDebug("[" + LogIdentification + "] leaving; IsSimpleRender == " + IsSimpleRender.ToString()); + LoggingService.LogDebug("[" + LogIdentification + "] leaving; HasCompletedFullRender == " + HasCompletedFullRender.ToString()); + } + #endregion + + #region ColorToCSSColor + private static string ColorToCSSColor(Color color) + { + int rawColor = color.ToArgb(); + rawColor &= 0xFFFFFF; + return "#" + rawColor.ToString("X6"); + } + #endregion + + #region CreateMeasurementStyle + private string CreateMeasurementStyle(MBGridColumnConfiguration col, float columnWidth) + { + string subStyle = Measurement switch + { + MB_Grid_Measurement.EM => "em", + MB_Grid_Measurement.FitToData => "", + MB_Grid_Measurement.PX => "px", + MB_Grid_Measurement.Percent => "%", + _ => throw new Exception("Unexpected measurement type in MBGrid"), + }; + + if (subStyle.Length > 0) + { + return + "width: " + col.Width.ToString() + subStyle + " !important; " + + "max-width: " + col.Width.ToString() + subStyle + " !important; " + + "min-width: " + col.Width.ToString() + subStyle + " !important; "; + } + else + { + return + "width: " + columnWidth.ToString() + "px !important; " + + "max-width: " + columnWidth.ToString() + "px !important; " + + "min-width: " + columnWidth.ToString() + "px !important; "; + } + } + #endregion + + #region GridSyncScroll + protected async Task GridSyncScroll() + { + LoggingService.LogDebug("[" + LogIdentification + "] GridSyncScroll()"); + await InvokeJsVoidAsync("MaterialBlazor.MBGrid.syncScrollByID", GridHeaderID, GridBodyID); + //await InvokeVoidAsync("MaterialBlazor.MBGrid.syncScrollByRef", GridHeaderRef, GridBodyRef); + } + #endregion + + #region MeasureWidthsAsync + private async Task MeasureWidthsAsync() + { + if (GroupedOrderedData == null) + { + return; + } + + // Measure the width of a vertical scrollbar (Used to set the padding of the header) + ScrollWidth = await JsRuntime.InvokeAsync( + "MaterialBlazor.MBGrid.getScrollBarWidth", + "mb-grid-div-body"); + ScrollWidth = 0; + + if (Measurement == MB_Grid_Measurement.FitToData) + { + // Create a simple data dictionary from the GroupedDataDictionary + var dataList = new List(); + foreach (var outerKVP in GroupedOrderedData) + { + foreach (var innerKVP in outerKVP.Value) + { + dataList.Add(innerKVP.Value); + } + } + // Measure the header columns + var stringArrayHeader = new string[ColumnConfigurations.Count()]; + var colIndex = 0; + foreach (var col in ColumnConfigurations) + { + stringArrayHeader[colIndex] = col.Title; + colIndex++; + } + + ColumnWidthArray = await JsRuntime.InvokeAsync( + "MaterialBlazor.MBGrid.getTextWidths", + "mb-grid-header-td-measure", + ColumnWidthArray, + stringArrayHeader); + + // Measure the body columns + var stringArrayBody = new string[ColumnConfigurations.Count() * dataList.Count]; + colIndex = 0; + foreach (var enumerableData in dataList) + { + foreach (var columnDefinition in ColumnConfigurations) + { + switch (columnDefinition.ColumnType) + { + case MB_Grid_ColumnType.Icon: + // We let the column width get driven by the title + stringArrayBody[colIndex] = ""; + break; + + case MB_Grid_ColumnType.Text: + if (columnDefinition.DataExpression != null) + { + var value = columnDefinition.DataExpression(enumerableData); + var formattedValue = string.IsNullOrEmpty(columnDefinition.FormatString) ? value?.ToString() : string.Format("{0:" + columnDefinition.FormatString + "}", value); + stringArrayBody[colIndex] = formattedValue; + } + break; + + case MB_Grid_ColumnType.TextColor: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridTextColorSpecification)columnDefinition.DataExpression(enumerableData); + if (!value.Suppress) + { + stringArrayBody[colIndex] = value.Text; + } + else + { + stringArrayBody[colIndex] = ""; + } + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.TextColor column."); + } + } + break; + + default: + throw new Exception("MBGrid -- Unknown column type"); + } + + colIndex++; + } + } + + if (LoggingService.CurrentLevel() <= (int)MBLoggingLevel.Debug) + { + var total = 0; + foreach (var c in stringArrayBody) + { + if (c != null) + { + total += c.Length; + } + } + LoggingService.LogDebug("[" + LogIdentification + "] Measuring " + stringArrayBody.Length + " strings with a total size of " + total.ToString() + " bytes"); + } + + ColumnWidthArray = await JsRuntime.InvokeAsync( + "MaterialBlazor.MBGrid.getTextWidths", + "mb-grid-body-td-measure", + ColumnWidthArray, + stringArrayBody); + + for (var col = 0; col < ColumnWidthArray.Length; col++) + { + // + // We adjust a bit because we were still getting an ellipsis on the longest text. + // This is caused by the fact that creates + // a 372px wide column + // + + ColumnWidthArray[col] += 1; + } + } + } + #endregion + + #region OnAfterRenderAsync + protected override async Task OnAfterRenderAsync(bool firstRender) + { + var needsSHC = false; + await semaphoreSlim.WaitAsync(); + try + { + await base.OnAfterRenderAsync(firstRender); + + LoggingService.LogDebug("[" + LogIdentification + "] OnAfterRenderAsync entered"); + LoggingService.LogDebug("[" + LogIdentification + "] firstRender: " + firstRender.ToString()); + LoggingService.LogDebug("[" + LogIdentification + "] IsSimpleRender: " + IsSimpleRender.ToString()); + LoggingService.LogDebug("[" + LogIdentification + "] IsMeasurementNeeded: " + IsMeasurementNeeded.ToString()); + + if (IsSimpleRender) + { + IsSimpleRender = false; + needsSHC = true; + } + + if (IsMeasurementNeeded) + { + IsMeasurementNeeded = false; + + if (Measurement == MB_Grid_Measurement.FitToData) + { + LoggingService.LogDebug("[" + LogIdentification + "] Calling MeasureWidthsAsync"); + await MeasureWidthsAsync(); + LoggingService.LogDebug("[" + LogIdentification + "] Returned from MeasureWidthsAsync"); + + needsSHC = true; + } + } + } + finally + { + if (needsSHC) + { + await InvokeAsync(StateHasChanged); + } + + LoggingService.LogDebug("[" + LogIdentification + "] about to release semaphore (OnAfterRenderAsync)"); + + semaphoreSlim.Release(); + } + } + #endregion + + #region OnInitializedAsync + protected override async Task OnInitializedAsync() + { + LoggingService.LogDebug("[" + LogIdentification + "] MBGrid.OnInitializedAsync entered"); + + await base.OnInitializedAsync(); + + if (ColumnConfigurations == null) + { + throw new System.Exception("MBGrid requires column configuration definitions."); + } + + LoggingService.LogDebug("[" + LogIdentification + "] MBGrid.OnInitializedAsync completed"); + } + #endregion + + #region OnMouseClickInternal + private Task OnMouseClickInternal(string newRowKey) + { + LoggingService.LogDebug("[" + LogIdentification + "] OnMouseClickInternal with HighlightSelectedRow:" + HighlightSelectedRow.ToString()); + + if (newRowKey != SelectedKey) + { + SelectedKey = newRowKey; + } + return OnMouseClickCallback.InvokeAsync(newRowKey); + } + #endregion + + #region ScrollToIndicatedRowAsync + public async Task ScrollToIndicatedRowAsync(string rowIdentifier) + { + LoggingService.LogDebug("[" + LogIdentification + "] ScrollToIndicatedRowAsync(" + rowIdentifier + ")"); + await InvokeJsVoidAsync("MaterialBlazor.MBGrid.scrollToIndicatedRow", rowIdentifier); + } + #endregion + + #region SetParametersAsync + private int oldParameterHash { get; set; } = -1; + public override Task SetParametersAsync(ParameterView parameters) + { + LoggingService.LogDebug("[" + LogIdentification + "] SetParametersAsync entry"); + + semaphoreSlim.WaitAsync(); + try + { + var count = parameters.ToDictionary().Count; + LoggingService.LogDebug("[" + LogIdentification + "] SetParametersAsync parameter count: " + count.ToString()); + foreach (var parameter in parameters) + { + LoggingService.LogDebug("[" + LogIdentification + "] SetParametersAsync parameter: " + parameter.Name); + switch (parameter.Name) + { + case nameof(@class): + @class = (string)parameter.Value; + break; + case nameof(ColumnConfigurations): + ColumnConfigurations = (IEnumerable>)parameter.Value; + // + // We are going to measure the actual sizes using JS if the Measurement is FitToData + // We need to create the ColumnWidthArray regardless of the measurement type as we need to pass + // values to BuildColGroup->CreateMeasurementStyle + // + ColumnWidthArray = new float[ColumnConfigurations.Count()]; + break; + case nameof(Group): + Group = (bool)parameter.Value; + break; + case nameof(GroupedOrderedData): + GroupedOrderedData = (IEnumerable>>>)parameter.Value; + break; + case nameof(HighlightSelectedRow): + HighlightSelectedRow = (bool)parameter.Value; + break; + case nameof(KeyExpression): + KeyExpression = (Func)parameter.Value; + break; + case nameof(LogIdentification): + LogIdentification = (string)parameter.Value; + break; + case nameof(Measurement): + Measurement = (MB_Grid_Measurement)parameter.Value; + break; + case nameof(ObscurePMI): + ObscurePMI = (bool)parameter.Value; + break; + case nameof(OnMouseClickCallback): + OnMouseClickCallback = (EventCallback)parameter.Value; + break; + case nameof(style): + style = (string)parameter.Value; + break; + case nameof(SuppressHeader): + SuppressHeader = (bool)parameter.Value; + break; + default: + LoggingService.LogTrace("[" + LogIdentification + "] MBGrid encountered an unknown parameter:" + parameter.Name); + break; + } + } + + LoggingService.LogDebug("[" + LogIdentification + "] about to compute parameter hash"); + + HashCode newConfigurationParametersHash = new(); + + if (HighlightSelectedRow) + { + newConfigurationParametersHash = HashCode + .OfEach(ColumnConfigurations) + .And(@class) + .And(Group) + .And(HighlightSelectedRow) + .And(KeyExpression) + .And(Measurement) + .And(ObscurePMI) + .And(OnMouseClickCallback) + .And(SelectedKey) // Not a parameter but if we don't include this we won't re-render after selecting a row + .And(style) + .And(SuppressHeader); + } + else + { + newConfigurationParametersHash = HashCode + .OfEach(ColumnConfigurations) + .And(@class) + .And(Group) + .And(HighlightSelectedRow) + .And(KeyExpression) + .And(Measurement) + .And(ObscurePMI) + .And(OnMouseClickCallback) + .And(style) + .And(SuppressHeader); + } + LoggingService.LogDebug("[" + LogIdentification + "] 'configuration' parameters hash == " + ((int)newConfigurationParametersHash).ToString()); + + // + // We have to implement the double loop for grouped ordered data as the OfEach/AndEach + // do not recurse into the second enumerable and certainly don't look at the rowValues + // + HashCode newDataParameterHash = new(); + if ((GroupedOrderedData != null) && (ColumnConfigurations != null)) + { + foreach (var kvp in GroupedOrderedData) + { + LoggingService.LogDebug("[" + LogIdentification + "] key == " + kvp.Key + " with " + kvp.Value.Count().ToString() + " rows"); + + foreach (var rowValues in kvp.Value) + { + var rowKey = KeyExpression(rowValues.Value).ToString(); + + newDataParameterHash = new HashCode(HashCode.CombineHashCodes( + newDataParameterHash.value, + HashCode.Of(rowKey))); + + foreach (var columnDefinition in ColumnConfigurations) + { + switch (columnDefinition.ColumnType) + { + case MB_Grid_ColumnType.Icon: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridIconSpecification)columnDefinition.DataExpression(rowValues.Value); + + newDataParameterHash = new HashCode(HashCode.CombineHashCodes( + newDataParameterHash.value, + HashCode.Of(value))); + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.Icon column."); + } + } + break; + + case MB_Grid_ColumnType.Text: + if (columnDefinition.DataExpression != null) + { + var value = columnDefinition.DataExpression(rowValues.Value); + var formattedValue = string.IsNullOrEmpty(columnDefinition.FormatString) ? value?.ToString() : string.Format("{0:" + columnDefinition.FormatString + "}", value); + + newDataParameterHash = new HashCode(HashCode.CombineHashCodes( + newDataParameterHash.value, + HashCode.Of(value))); + } + break; + + case MB_Grid_ColumnType.TextColor: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridTextColorSpecification)columnDefinition.DataExpression(rowValues.Value); + + newDataParameterHash = new HashCode(HashCode.CombineHashCodes( + newDataParameterHash.value, + HashCode.Of(value))); + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.TextColor column."); + } + } + break; + + default: + throw new Exception("MBGrid -- Unknown column type"); + } + } + } + } + LoggingService.LogDebug("[" + LogIdentification + "] 'data' parameter hash == " + ((int)newDataParameterHash).ToString()); + } + + HashCode newParameterHash = new HashCode( + HashCode.CombineHashCodes( + newConfigurationParametersHash.value, + newDataParameterHash.value)); + + LoggingService.LogDebug("[" + LogIdentification + "] hash == " + ((int)newParameterHash).ToString()); + if (newParameterHash == oldParameterHash) + { + // This is a call to ParametersSetAsync with what in all likelyhood is the same + // parameters. Hashing isn't perfect so there is some tiny possibility that new parameters + // are present and the same hash value was computed. + if (HasCompletedFullRender) + { + ShouldRenderValue = false; + } + else + { + ShouldRenderValue = true; + } + +// LoggingService.LogDebug("[" + LogIdentification + "] EQUAL hash"); + } + else + { + ShouldRenderValue = true; + IsSimpleRender = true; + IsMeasurementNeeded = true; + oldParameterHash = newParameterHash; + LoggingService.LogDebug("[" + LogIdentification + "] DIFFERING hash"); + } + } + finally + { + LoggingService.LogDebug("[" + LogIdentification + "] about to release semaphore (SetParametersAsync)"); + + semaphoreSlim.Release(); + } + + return base.SetParametersAsync(ParameterView.Empty); + } + #endregion + + #region ShouldRender + protected override bool ShouldRender() + { + return ShouldRenderValue; + } + #endregion + +} + +#region HashCode + +/// +/// A hash code used to help with implementing . +/// +/// This code is from the blog post at https://rehansaeed.com/gethashcode-made-easy/ +/// +public struct HashCode : IEquatable +{ + private const int EmptyCollectionPrimeNumber = 19; + public readonly int value; + + /// + /// Initializes a new instance of the struct. + /// + /// The value. + public HashCode(int value) => this.value = value; + + /// + /// Performs an implicit conversion from to . + /// + /// The hash code. + /// The result of the conversion. + public static implicit operator int(HashCode hashCode) => hashCode.value; + + /// + /// Implements the operator ==. + /// + /// The left. + /// The right. + /// The result of the operator. + public static bool operator ==(HashCode left, HashCode right) => left.Equals(right); + + /// + /// Implements the operator !=. + /// + /// The left. + /// The right. + /// The result of the operator. + public static bool operator !=(HashCode left, HashCode right) => !(left == right); + + /// + /// Takes the hash code of the specified item. + /// + /// The type of the item. + /// The item. + /// The new hash code. + public static HashCode Of(T item) => new HashCode(GetHashCode(item)); + + /// + /// Takes the hash code of the specified items. + /// + /// The type of the items. + /// The collection. + /// The new hash code. + public static HashCode OfEach(IEnumerable items) => + items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0)); + + /// + /// Adds the hash code of the specified item. + /// + /// The type of the item. + /// The item. + /// The new hash code. + public HashCode And(T item) => + new HashCode(CombineHashCodes(this.value, GetHashCode(item))); + + /// + /// Adds the hash code of the specified items in the collection. + /// + /// The type of the items. + /// The collection. + /// The new hash code. + public HashCode AndEach(IEnumerable items) + { + if (items == null) + { + return new HashCode(this.value); + } + + return new HashCode(GetHashCode(items, this.value)); + } + + public bool Equals(HashCode other) => this.value.Equals(other.value); + + public override bool Equals(object obj) + { + if (obj is HashCode) + { + return this.Equals((HashCode)obj); + } + + return false; + } + + /// + /// Throws . + /// + /// Does not return. + /// Implicitly convert this struct to an to get the hash code. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => + throw new NotSupportedException( + "Implicitly convert this struct to an int to get the hash code."); + + public static int CombineHashCodes(int h1, int h2) + { + unchecked + { + // Code copied from System.Tuple so it must be the best way to combine hash codes or at least a good one. + return ((h1 << 5) + h1) ^ h2; + } + } + + private static int GetHashCode(T item) => item?.GetHashCode() ?? 0; + + private static int GetHashCode(IEnumerable items, int startHashCode) + { + var temp = startHashCode; + + var enumerator = items.GetEnumerator(); + if (enumerator.MoveNext()) + { + temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); + + while (enumerator.MoveNext()) + { + temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); + } + } + else + { + temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber); + } + + return temp; + } +} + +#endregion + diff --git a/Material.Blazor.MD2/Components/Grid/MBGrid.scss b/Material.Blazor.MD2/Components/Grid/MBGrid.scss new file mode 100644 index 000000000..725754801 --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGrid.scss @@ -0,0 +1,273 @@ +@charset "UTF-8"; + +@use '@material/theme'; +@use '@material/theme/custom-properties'; +@use '@material/theme/color-palette'; +@use 'sass:string'; + +$border-color: color-palette.$blue-grey-500; +$header-color: color-palette.$blue-grey-100; + +$group-row-color: color-palette.$blue-100; +$group-row-color-hover: darken($group-row-color, 4%); + +$odd-row-color: color-palette.$yellow-100; +$odd-row-color-hover: darken($odd-row-color, 4%); + +$even-row-color: color-palette.$yellow-200; +$even-row-color-hover: darken($even-row-color, 4%); + +$selected-row-color: color-palette.$green-100; +$selected-row-color-hover: darken($selected-row-color, 4%); + +:root { + --mb-grid-border-color: #{$border-color}; + --mb-grid-header-color: #{$header-color}; + --mb-grid-group-row-color: #{$group-row-color}; + --mb-grid-group-row-color-hover: #{$group-row-color-hover}; + --mb-grid-odd-row-color: #{$odd-row-color}; + --mb-grid-odd-row-color-hover: #{$odd-row-color-hover}; + --mb-grid-even-row-color: #{$even-row-color}; + --mb-grid-even-row-color-hover: #{$even-row-color-hover}; + --mb-grid-selected-row-color: #{$selected-row-color}; + --mb-grid-selected-row-color-hover: #{$selected-row-color-hover}; +} + +.mb-mgrid { + border-radius: 0px; + + .mdc-data-table__header-cell:first-child { + border-top-left-radius: 0px; + } + + .mdc-data-table__header-cell:last-child { + border-top-right-radius: 0px; + } +} + +.mb-mgrid__colored { + border-color: var(--mb-grid-border-color); + + .mdc-data-table__header-cell { + background-color: var(--mb-grid-header-color); + border-left-color: var(--mb-grid-border-color); + border-bottom-color: var(--mb-grid-border-color); + } + + tbody td { + border-left-color: var(--mb-grid-border-color); + border-bottom-color: var(--mb-grid-border-color); + } + + .mb-mgrid__group-row { + background-color: var(--mb-grid-group-row-color); + + &:hover { + background-color: var(--mb-grid-group-row-color-hover); + } + } + + .mb-mgrid__row:nth-child(odd):not(.mb-mgrid__row-selected) { + background-color: var(--mb-grid-odd-row-color); + + &:hover { + background-color: var(--mb-grid-odd-row-color-hover); + } + } + + .mb-mgrid__row:nth-child(even):not(.mb-mgrid__row-selected) { + background-color: var(--mb-grid-even-row-color); + + &:hover { + background-color: var(--mb-grid-even-row-color-hover); + } + } + + .mb-mgrid__row-selected { + background-color: var(--mb-grid-selected-row-color); + + &:hover { + background-color: var(--mb-grid-selected-row-color-hover); + } + } +} + + +.mb-mgrid__vertical-dividers { + .mdc-data-table__header-cell:not(:first-child), .mb-mgrid__group-row td:not(:first-child), .mb-mgrid__row td:not(:first-child) { + padding-left: 15px; + border-left-width: 1px; + border-left-style: solid; + } +} + + +/* Original grid */ + +.mb-grid-div-outer { + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + overflow: hidden; + box-sizing: border-box; + padding: 0; + margin: 0; +} + +.mb-grid-div-header { + font-family: Arial; + font-weight: bolder; + padding: 0; + overflow-x: hidden; + overflow-y: scroll; + text-align: left; + box-sizing: border-box; + display: flex; + flex-direction: row; + flex-shrink: 0; + flex-grow: 0; + flex-basis: auto; + align-items: stretch; +} + +.mb-grid-div-body { + font-family: Arial; + background: lightblue; + font-weight: normal; + padding: 0; + overflow-x: scroll; + overflow-y: scroll; + text-align: left; + box-sizing: border-box; +} + +.mb-grid-table { + border: 0; + border-collapse: collapse; + border-spacing: 0; + -webkit-border-horizontal-spacing: 0px; + -webkit-border-vertical-spacing: 0px; + flex-grow: 1; + overflow: hidden; + font-size: 1.0rem; + table-layout: fixed; + text-align: left; + text-indent: unset; + text-overflow: ellipsis; + text-wrap: none; + vertical-align: middle; + width: 100%; +} + +.mb-grid-colgroup { + display: table-column-group; +} + +.mb-grid-thead { +} + +.mb-grid-tbody { +} + +.mb-grid-tr { +} + +.mb-grid-td { + cursor: default; + display: table-cell; + flex: 0 0 auto; + padding: 4px; + font-size: inherit; + text-overflow: ellipsis; + overflow: hidden; + box-sizing: border-box; + white-space: nowrap; + letter-spacing: initial; +} + +.mb-grid-td-group { + display: table-cell; + color: black; + border-bottom: 0px; + border-left: 1px solid darkblue; + border-right: 1px solid darkblue; + border-top: 2px solid darkblue; + font-size: x-large; + font-weight: bolder; + flex: 0 0 auto; +} + +.mb-grid-backgroundcolor-header-background { + background: lightgray; + background-color: lightgray; +} + +.mb-grid-backgroundcolor-row-even { + background-color: khaki; +} + +.mb-grid-backgroundcolor-row-odd { + background-color: lemonchiffon; +} + +.mb-grid-backgroundcolor-row-group { + background-color: lightblue; +} + +.mb-grid-backgroundcolor-row-selected { + background-color: lightgreen; +} + +.mb-grid-header-td-measure { + font-family: Arial; + font-weight: bolder; + border-collapse: collapse; + border-spacing: 0; + -webkit-border-horizontal-spacing: 0px; + -webkit-border-vertical-spacing: 0px; + text-align: left; + text-indent: unset; + display: table-cell; + flex: 0 0 auto; + padding: 4px; + font-size: inherit; + text-overflow: unset; + letter-spacing: initial; + overflow: hidden; + box-sizing: border-box; + white-space: nowrap; + background: #d3d3d3; + border-width: 1px; + border-style: solid; + border-color: black; + color: Black; + background-color: LightGray; +} + +.mb-grid-body-td-measure { + font-family: Arial; + font-weight: normal; + border-collapse: collapse; + border-spacing: 0; + -webkit-border-horizontal-spacing: 0px; + -webkit-border-vertical-spacing: 0px; + text-align: left; + text-indent: unset; + display: table-cell; + flex: 0 0 auto; + padding: 4px; + font-size: inherit; + text-overflow: unset; + letter-spacing: initial; + overflow: hidden; + box-sizing: border-box; + white-space: nowrap; + background-color: khaki; + border-width: 0px 1px 0px 0px; + border-style: solid; + border-color: black; + color: #000000; + cursor: default; +} + diff --git a/Material.Blazor.MD2/Components/Grid/MBGrid.ts b/Material.Blazor.MD2/Components/Grid/MBGrid.ts new file mode 100644 index 000000000..f35fc376b --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGrid.ts @@ -0,0 +1,101 @@ +export function syncScrollByID(gridHeaderID: string, gridBodyID: string) { + const headerDiv: HTMLElement | null = document.getElementById(gridHeaderID); + const bodyDiv: HTMLElement | null = document.getElementById(gridBodyID); + if ((headerDiv != null) && (bodyDiv != null)) { + headerDiv.scrollLeft = bodyDiv.scrollLeft; + } +} + +export function syncScrollByRef(gridHeaderRef: HTMLElement, gridBodyRef: HTMLElement) { + gridHeaderRef.scrollLeft = gridBodyRef.scrollLeft; +} + +export function getScrollBarWidth(className: string): number { + const firstDiv: HTMLDivElement = document.createElement("div"); + + // Set styles + firstDiv.style.position = 'absolute'; + firstDiv.style.visibility = 'hidden'; + firstDiv.style.whiteSpace = 'nowrap'; + firstDiv.style.left = '-9999px'; + + // Set the class + firstDiv.className = className; + + // Append to the body + document.body.appendChild(firstDiv); + + // Create a second div + const secondDiv: HTMLDivElement = document.createElement("div"); + + // Append it as a child of the first div + firstDiv.appendChild(secondDiv); + + // Calculate width + const width: number = firstDiv.offsetWidth - secondDiv.offsetWidth; + + // Remove the divs + document.body.removeChild(firstDiv); + + return width; +} + +export function getTextWidths( + className: string, + currentWidths: number[], + textToMeasure: string[]): number[] { + + // Create an element + const ele: HTMLDivElement = document.createElement('div'); + + // Set styles + ele.style.position = 'absolute'; + ele.style.visibility = 'hidden'; + ele.style.whiteSpace = 'nowrap'; + ele.style.left = '-9999px'; + + // Set the class + ele.className = className; + + // Append to the body + document.body.appendChild(ele); + + // Log time +// console.log("Prior to for loop in getTextWidths " + new Date().toString()); + + for (let i = 0; i < textToMeasure.length; i++) { + // Set the text + ele.innerText = textToMeasure[i]; + + // Get the width + var width: string = window.getComputedStyle(ele).width; + var unadornedWidth: string = width.slice(0, width.indexOf("px")); + var numericWidth: number = parseFloat(unadornedWidth); + var indexMod = i % currentWidths.length; + + if (numericWidth > currentWidths[indexMod]) { + currentWidths[indexMod] = numericWidth; + } + } + + // Log time +// console.log("Completed for loop in getTextWidths " + new Date().toString()); + + // Remove the element + document.body.removeChild(ele); + + + return currentWidths; +} + +export function scrollToIndicatedRow(rowIdentifier: string) +{ + console.log("scrollToIndicatedRow: " + rowIdentifier); + const row = document.getElementById(rowIdentifier); + console.log("scrollToIndicatedRow element: " + row); + if (row != null) + { + console.log("scrollToIndicatedRow scrollIntoView"); + row.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" }); + } +} diff --git a/Material.Blazor.MD2/Components/Grid/MBGridColumnConfiguration.cs b/Material.Blazor.MD2/Components/Grid/MBGridColumnConfiguration.cs new file mode 100644 index 000000000..3fddde6f0 --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGridColumnConfiguration.cs @@ -0,0 +1,47 @@ +using System; +using System.Drawing; +using System.Linq.Expressions; + +namespace Material.Blazor.MD2; + +public class MBGridColumnConfiguration +{ + public Func BackgroundColorExpression { get; set; } + public Color BackgroundColorHeader { get; set; } + public MB_Grid_ColumnType ColumnType { get; private set; } + public Func DataExpression { get; set; } + public Func ForegroundColorExpression { get; set; } + public Color ForegroundColorHeader { get; set; } + public string FormatString { get; set; } + public bool IsPMI { get; set; } + public Func SuppressDisplayExpression { get; set; } + public string Title { get; set; } + public int Width { get; set; } + + private MBGridColumnConfiguration() { } + public MBGridColumnConfiguration( + Expression> backgroundColorExpression = null, + Color? backgroundColorHeader = null, + MB_Grid_ColumnType columnType = MB_Grid_ColumnType.Text, + Expression> dataExpression = null, + Expression> foregroundColorExpression = null, + Color? foregroundColorHeader = null, + string formatString = null, + bool isPMI = false, + Expression> suppressDisplayExpression = null, + string title = "", + int width = 10) + { + BackgroundColorExpression = backgroundColorExpression?.Compile();// ?? Color.LightGray; + BackgroundColorHeader = backgroundColorHeader ?? Color.LightGray; + ColumnType = columnType; + DataExpression = dataExpression?.Compile(); + ForegroundColorExpression = foregroundColorExpression?.Compile();// ?? Color.Black; + ForegroundColorHeader = foregroundColorHeader ?? Color.Black; + FormatString = formatString; + IsPMI = isPMI; + SuppressDisplayExpression = suppressDisplayExpression?.Compile(); + Title = title; + Width = width; + } +} diff --git a/Material.Blazor.MD2/Components/Grid/MBGridIconSpecification.cs b/Material.Blazor.MD2/Components/Grid/MBGridIconSpecification.cs new file mode 100644 index 000000000..c047c5dec --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGridIconSpecification.cs @@ -0,0 +1,12 @@ +using System.Drawing; + +namespace Material.Blazor.MD2; + +public class MBGridIconSpecification +{ + public Color IconColor { get; set; } +#nullable enable annotations + public IMBIconFoundry? IconFoundry { get; set; } = null; +#nullable restore annotations + public string IconName { get; set; } +} diff --git a/Material.Blazor.MD2/Components/Grid/MBGridMT.cs b/Material.Blazor.MD2/Components/Grid/MBGridMT.cs new file mode 100644 index 000000000..5d39dcf59 --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGridMT.cs @@ -0,0 +1,1047 @@ +#define Logging + +// ToDo: +// +// Cleanup: +// Move enumerations to MBEnumerations +// +// Bugs: +// Padding resolution for GridHeader +// Resolve issue with ElementReferences +// + +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +// +// Implements a scrollable, multi-column grid. When created we get a list of column +// config objects and a list of data objects with the column content for each +// row. +// +// We 'select' a line when it is clicked on so the caller can either immediately respond or +// save the selection for later. +// + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme grid capable of displaying icons, colored text, and text. +/// +/// N.B.: At this time the grid is in preview. Expect the API to change. +/// +public class MBGridMT : ComponentFoundationMD2 +{ + #region Parameters + + /// + /// The configuration of each column to be displayed. See the definition of MBGridColumnConfiguration + /// for details. + /// + [Parameter, EditorRequired] public IEnumerable> ColumnConfigurations { get; set; } = null; + + + /// + /// The Group is an optional boolean indicating that grouping is in effect. + /// + [Parameter] public bool Group { get; set; } = false; + + + /// + /// The GroupedOrderedData contains the data to be displayed. + /// The outer key is used for grouping and is directly displayed if grouping is enabled. + /// The inner key must be a unique identifier + /// that is used to indicate a row that has been clicked. + /// + [Parameter, EditorRequired] public IEnumerable>>> GroupedOrderedData { get; set; } + + + /// + /// A boolean indicating whether the selected row is highlighted + /// + [Parameter] public bool HighlightSelectedRow { get; set; } = false; + + +#nullable enable annotations + /// + /// The KeyExpression is used to add a key to each row of the grid + /// + [Parameter] public Func? KeyExpression { get; set; } = null; +#nullable restore annotations + + + /// + /// LogIdentification is added to logging message to allow differentiation between multiple grids + /// on a single page or component + /// + [Parameter] public string LogIdentification { get; set; } = null; + + + /// + /// Measurement determines the unit of size (EM, Percent, PX) or if the grid is to measure the + /// data widths (FitToData) + /// + [Parameter] public MB_Grid_Measurement Measurement { get; set; } = MB_Grid_Measurement.Percent; + + + /// + /// ObscurePMI controls whether or not columns marked as PMI are obscured. + /// + [Parameter] public bool ObscurePMI { get; set; } + + + /// + /// Callback for a mouse click + /// + [Parameter] public EventCallback OnMouseClick { get; set; } + + + /// + /// Headers are optional + /// + [Parameter] public bool SuppressHeader { get; set; } = false; + + + /// + /// Set to true to apply grid colors, false to suppress. + /// + [Parameter] public bool ApplyColors { get; set; } = false; + + /// + /// Set to true to apply vertical dividers to data rows and headers but not group headers. + /// + [Parameter] public bool ApplyVerticalDividers { get; set; } = false; + + + /// + /// The grid's data table density. + /// + [Parameter] public MBDensity? Density { get; set; } + #endregion + + #region Members + private MBCascadingDefaults.DensityInfo DensityInfo => CascadingDefaults.GetDensityCssClass(CascadingDefaults.AppliedDataTableDensity(Density)); + private float[] ColumnWidthArray; + //private ElementReference GridBodyRef { get; set; } + //private ElementReference GridHeaderRef { get; set; } + //private string GridBodyID { get; set; } = Utilities.GenerateUniqueElementName(); + private string GridHeaderID { get; set; } = UtilitiesMD2.GenerateUniqueElementName(); + private bool HasCompletedFullRender { get; set; } = false; + private bool IsSimpleRender { get; set; } = true; + private float ScrollWidth { get; set; } + private string SelectedKey { get; set; } = ""; + + //Instantiate a Semaphore with a value of 1. This means that only 1 thread can be granted access at a time. + private readonly SemaphoreSlim semaphoreSlim = new(1, 1); + + private bool ShouldRenderValue { get; set; } = true; + + #endregion + + #region BuildColGroup + private void BuildColGroup(RenderTreeBuilder builder, ref int rendSeq) + { + // Create the sizing colgroup collection + builder.OpenElement(rendSeq++, "colgroup"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-colgroup"); + var colIndex = 0; + foreach (var col in ColumnConfigurations) + { + var styleStr = CreateMeasurementStyle(col, ColumnWidthArray[colIndex]); + builder.OpenElement(rendSeq++, "col"); + builder.AddAttribute(rendSeq++, "style", styleStr); + builder.CloseElement(); // col + colIndex++; + } + builder.CloseElement(); // colgroup + } + #endregion + + #region BuildNewGridTD + private static string BuildNewGridTD( + RenderTreeBuilder builder, + ref int rendSeq, + bool isFirstColumn, + bool isHeaderRow, + string rowBackgroundColorClass) + { + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "class", "mb-grid-td " + rowBackgroundColorClass); + + if (isHeaderRow) + { + if (isFirstColumn) + { + // T R B L + return " border-width: 1px; border-style: solid; border-color: black; "; + } + else + { + // T R B + return " border-width: 1px 1px 1px 0px; border-style: solid; border-color: black; "; + } + } + else + { + if (isFirstColumn) + { + // R L + return " border-width: 0px 1px 0px 1px; border-style: solid; border-color: black; "; + } + else + { + // R + return " border-width: 0px 1px 0px 0px; border-style: solid; border-color: black; "; + } + } + } + #endregion + + #region BuildRenderTree + protected override void BuildRenderTree(RenderTreeBuilder builder) + { +#if Logging + GridLogDebug("BuildRenderTree entered; IsSimpleRender == " + IsSimpleRender.ToString()); + GridLogDebug(" HasCompletedFullRender == " + HasCompletedFullRender.ToString()); + GridLogDebug(" ShouldRenderValue == " + ShouldRenderValue.ToString()); +#endif + if (IsSimpleRender || (!ShouldRenderValue)) + { +#if Logging + GridLogDebug(" (Simple) entered"); +#endif + // We are going to render a DIV and nothing else + // We need to get into OnAfterRenderAsync so that we can use JS interop to measure + // the text + base.BuildRenderTree(builder); + builder.OpenElement(1, "div"); + builder.CloseElement(); + HasCompletedFullRender = false; +#if Logging + GridLogDebug(" (Simple) leaving"); +#endif + } + else + { +#if Logging + GridLogDebug(" (Full) entered"); +#endif + + // + // Using the column cfg and column data, render our list. Here is the layout. + // The column headers are optional. + // + // div class="@class", style="@style" + // div mb-grid-header - Contains the header and the vscroll + // table - + // tr - + // td* - Header + // div mb-grid-body - Contains the rows and the vscroll + // table - Contains the rows + // tr* - Rows + // td* - Columns of the row + // + + base.BuildRenderTree(builder); + var rendSeq = 2; + string classStr = $"mdc-data-table{(DensityInfo.ApplyCssClass ? $" {DensityInfo.CssClassName}" : "")} mdc-data-table--sticky-header mb-mgrid{(ApplyColors ? " mb-mgrid__colored" : "")}{(ApplyVerticalDividers ? " mb-mgrid__vertical-dividers" : "")} {@class}"; + var columnCount = ColumnConfigurations.Count().ToString(); + + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", classStr); + builder.AddAttribute(rendSeq++, "style", style); + + if (!SuppressHeader || GroupedOrderedData != null) + { + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__table-container"); + builder.OpenElement(rendSeq++, "table"); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__table"); + BuildColGroup(builder, ref rendSeq); + + // Based on the column config generate the column titles unless asked not to + if (!SuppressHeader) + { + //BuildColGroup(builder, ref rendSeq); + builder.OpenElement(rendSeq++, "thead"); + builder.OpenElement(rendSeq++, "tr"); + //builder.AddAttribute(rendSeq++, "class", "mdc-data-table__header-row mb-grid-material-trx"); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__header-row"); + + // For each column output a TD + var colCount = 0; + foreach (var col in ColumnConfigurations) + { + //styleStr = BuildNewGridTD( + // builder, + // ref rendSeq, + // colCount == 0, + // isHeaderRow, + // "mb-grid-backgroundcolor-header-background"); + + //// Set the header colors + //styleStr += " color: " + col.ForegroundColorHeader.Name + ";"; + //styleStr += " background-color : " + col.BackgroundColorHeader.Name + ";"; + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__header-cell"); + //builder.AddAttribute(rendSeq++, "style", styleStr); + builder.AddContent(rendSeq++, col.Title); + + // Close this column TD + builder.CloseElement(); + + colCount++; + } + + builder.CloseElement(); // tr + + builder.CloseElement(); // thead + } + + // + // We now need to build a "display centric" data representation with rows added for breaks, etc. + // For the first pass we are going to skip this step and just display the raw content + // + + if (GroupedOrderedData != null) + { + //var isFirstGrouper = true; + + //BuildColGroup(builder, ref rendSeq); + builder.OpenElement(rendSeq++, "tbody"); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__content"); + + foreach (var kvp in GroupedOrderedData) + { + if (Group) + { + // We output a row with the group name + // Do a div for this row + builder.OpenElement(rendSeq++, "tr"); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__row mb-mgrid__group-row"); + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "colspan", columnCount); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__cell"); + //if (isFirstGrouper) + //{ + // isFirstGrouper = false; + // builder.AddAttribute(rendSeq++, "style", "border-top: 1px solid black; "); + //} + //builder.AddAttribute(rendSeq++, "mbgrid-td-wide", "0"); + builder.AddContent(rendSeq++, " " + kvp.Key); + builder.CloseElement(); // td + builder.CloseElement(); // tr + } + + var rowCount = 0; + foreach (var rowValues in kvp.Value) + { + var rowKey = KeyExpression(rowValues.Value).ToString(); + + //string rowBackgroundColorClass = ""; + + //if ((rowKey == SelectedKey) && HighlightSelectedRow) + //{ + // // It's the selected row so set the selection color as the background + // rowBackgroundColorClass = "mb-mgrid__row-selected"; + //} + //else + //{ + // // Not selected or not highlighted so we alternate + // if ((rowCount / 2) * 2 == rowCount) + // { + // // Even + // rowBackgroundColorClass = "mb-grid-backgroundcolor-row-even"; + // } + // else + // { + // // Odd + // rowBackgroundColorClass = "mb-grid-backgroundcolor-row-odd"; + // } + //} + + var selected = (rowKey == SelectedKey) && HighlightSelectedRow; + // Do a tr + builder.OpenElement(rendSeq++, "tr"); + //builder.AddAttribute(rendSeq++, "class", "mb-grid-tr " + rowBackgroundColorClass); + builder.AddAttribute(rendSeq++, "class", $"mdc-data-table__row mb-mgrid__row{(selected ? " mb-mgrid__row-selected" : "")}"); + + builder.AddAttribute + ( + rendSeq++, + "onclick", + EventCallback.Factory.Create(this, e => OnMouseClickInternal(rowKey)) + ); + + // For each column output a td + var colCount = 0; + foreach (var columnDefinition in ColumnConfigurations) + { + //styleStr = BuildNewGridTD( + // builder, + // ref rendSeq, + // colCount == 0, + // isHeaderRow, + // rowBackgroundColorClass); + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "class", "mdc-data-table__cell"); + + switch (columnDefinition.ColumnType) + { + case MB_Grid_ColumnType.Icon: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridIconSpecification)columnDefinition.DataExpression(rowValues.Value); + + // We need to add the color alignment to the base styles + //styleStr += + // " color: " + ColorToCSSColor(value.IconColor) + ";" + // + " text-align: center;"; + + //builder.AddAttribute(rendSeq++, "style", styleStr); + builder.OpenComponent(rendSeq++, typeof(MBIcon)); + builder.AddAttribute(rendSeq++, "IconFoundry", value.IconFoundry); + builder.AddAttribute(rendSeq++, "IconName", value.IconName); + builder.CloseComponent(); + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.Icon column."); + } + } + break; + + case MB_Grid_ColumnType.Text: + // It's a text type column so add the text related styles + // We may be overriding the alternating row color added by class + + //if (columnDefinition.ForegroundColorExpression != null) + //{ + // var value = columnDefinition.ForegroundColorExpression(rowValues.Value); + // styleStr += + // " color: " + ColorToCSSColor((Color)value) + "; "; + //} + + //if (columnDefinition.BackgroundColorExpression != null) + //{ + // var value = columnDefinition.BackgroundColorExpression(rowValues.Value); + // if ((Color)value != Color.Transparent) + // { + // styleStr += + // " background-color: " + ColorToCSSColor((Color)value) + "; "; + // } + //} + + //if (columnDefinition.IsPMI && ObscurePMI) + //{ + // styleStr += + // " filter: blur(0.25em); "; + //} + + //builder.AddAttribute(rendSeq++, "style", styleStr); + + // Bind the object as our content. + if (columnDefinition.DataExpression != null) + { + var value = columnDefinition.DataExpression(rowValues.Value); + var formattedValue = string.IsNullOrEmpty(columnDefinition.FormatString) ? value?.ToString() : string.Format("{0:" + columnDefinition.FormatString + "}", value); + builder.AddContent(1, formattedValue); + } + break; + + case MB_Grid_ColumnType.TextColor: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridTextColorSpecification)columnDefinition.DataExpression(rowValues.Value); + + if (value.Suppress) + { + //builder.AddAttribute(rendSeq++, "style", styleStr); + } + else + { + // We need to add the colors + //styleStr += + // " color: " + ColorToCSSColor(value.ForegroundColor) + // + "; background-color: " + ColorToCSSColor(value.BackgroundColor) + ";"; + + //if (columnDefinition.IsPMI && ObscurePMI) + //{ + // styleStr += + // " filter: blur(0.25em); "; + //} + + //builder.AddAttribute(rendSeq++, "style", styleStr); + builder.AddContent(rendSeq++, value.Text); + } + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.TextColor column."); + } + } + break; + + default: + throw new Exception("MBGrid -- Unknown column type"); + } + + // Close this column span + builder.CloseElement(); + + colCount++; + } + + // Close this row's div + builder.CloseElement(); + + rowCount++; + } + } + + builder.CloseElement(); // tbody + } + + builder.CloseElement(); //div mdc-data-table__table-container + builder.CloseElement(); //table + + //builder.CloseElement(); // div mb-grid-header + + //builder.CloseElement(); // div class= style= + } + + builder.CloseElement(); // div mdc-data-table + + HasCompletedFullRender = true; +#if Logging + GridLogDebug(" (Full) leaving"); +#endif + } +#if Logging + GridLogDebug(" leaving; IsSimpleRender == " + IsSimpleRender.ToString()); + GridLogDebug(" leaving; HasCompletedFullRender == " + HasCompletedFullRender.ToString()); +#endif + } + #endregion + + #region ColorToCSSColor + private static string ColorToCSSColor(Color color) + { + int rawColor = color.ToArgb(); + rawColor &= 0xFFFFFF; + return "#" + rawColor.ToString("X6"); + } + #endregion + + #region CreateMeasurementStyle + private string CreateMeasurementStyle(MBGridColumnConfiguration col, float columnWidth) + { + string subStyle = Measurement switch + { + MB_Grid_Measurement.EM => "em", + MB_Grid_Measurement.FitToData => "", + MB_Grid_Measurement.PX => "px", + MB_Grid_Measurement.Percent => "%", + _ => throw new Exception("Unexpected measurement type in MBGrid"), + }; + + if (subStyle.Length > 0) + { + return + "width: " + col.Width.ToString() + subStyle + " !important; " + + "max-width: " + col.Width.ToString() + subStyle + " !important; " + + "min-width: " + col.Width.ToString() + subStyle + " !important; "; + } + else + { + return + "width: " + columnWidth.ToString() + "px !important; " + + "max-width: " + columnWidth.ToString() + "px !important; " + + "min-width: " + columnWidth.ToString() + "px !important; "; + } + } + #endregion + + #region Logging + + private void GridLogDebug(string message) + { + if (string.IsNullOrWhiteSpace(LogIdentification)) + { + LoggingService.LogDebug(message); + } + else + { + LoggingService.LogDebug("[" + LogIdentification + "] " + message); + } + } + + private void GridLogTrace(string message) + { + if (string.IsNullOrWhiteSpace(LogIdentification)) + { + LoggingService.LogTrace(message); + } + else + { + LoggingService.LogTrace("[" + LogIdentification + "] " + message); + } + } + + #endregion + + #region OnAfterRenderAsync + protected override async Task OnAfterRenderAsync(bool firstRender) + { + var needsSHC = false; + await semaphoreSlim.WaitAsync(); + try + { + await base.OnAfterRenderAsync(firstRender); + +#if Logging + GridLogDebug("OnAfterRenderAsync entered"); + GridLogDebug(" firstRender: " + firstRender.ToString()); + GridLogDebug(" IsSimpleRender: " + IsSimpleRender.ToString()); +#endif + + if (IsSimpleRender) + { + IsSimpleRender = false; + needsSHC = true; + } + } + finally + { + if (needsSHC) + { + await InvokeAsync(StateHasChanged); + } +#if Logging + GridLogDebug(" about to release semaphore (OnAfterRenderAsync)"); +#endif + semaphoreSlim.Release(); + } + } + #endregion + + #region OnInitialized + protected override async Task OnInitializedAsync() + { +#if Logging + GridLogDebug("MBGrid.OnInitialized entered"); +#endif + await base.OnInitializedAsync(); + + if (ColumnConfigurations == null) + { + throw new System.Exception("MBGrid requires column configuration definitions."); + } +#if Logging + GridLogDebug("MBGrid.OnInitialized completed"); +#endif + } + #endregion + + #region OnMouseClickInternal + private void OnMouseClickInternal(string newRowKey) + { +#if Logging + GridLogDebug("OnMouseClickInternal with HighlightSelectedRow:" + HighlightSelectedRow.ToString()); +#endif + if (newRowKey != SelectedKey) + { + SelectedKey = newRowKey; + OnMouseClick.InvokeAsync(newRowKey); + } + else + { + OnMouseClick.InvokeAsync(newRowKey); + } + } + #endregion + + #region SetParametersAsync + private int oldParameterHash { get; set; } = -1; + public override Task SetParametersAsync(ParameterView parameters) + { + base.SetParametersAsync(parameters); +#if Logging + GridLogDebug("SetParametersAsync entry"); +#endif + semaphoreSlim.WaitAsync(); + try + { + foreach (var parameter in parameters) + { + switch (parameter.Name) + { + case nameof(@class): + @class = (string)parameter.Value; + break; + case nameof(ColumnConfigurations): + ColumnConfigurations = (IEnumerable>)parameter.Value; + // + // We are going to measure the actual sizes using JS if the Measurement is FitToData + // We need to create the ColumnWidthArray regardless of the measurement type as we need to pass + // values to BuildColGroup->CreateMeasurementStyle + // + ColumnWidthArray = new float[ColumnConfigurations.Count()]; + break; + case nameof(Group): + Group = (bool)parameter.Value; + break; + case nameof(GroupedOrderedData): + GroupedOrderedData = (IEnumerable>>>)parameter.Value; + break; + case nameof(HighlightSelectedRow): + HighlightSelectedRow = (bool)parameter.Value; + break; + case nameof(KeyExpression): + KeyExpression = (Func)parameter.Value; + break; + case nameof(LogIdentification): + LogIdentification = (string)parameter.Value; + break; + case nameof(Measurement): + Measurement = (MB_Grid_Measurement)parameter.Value; + break; + case nameof(ObscurePMI): + ObscurePMI = (bool)parameter.Value; + break; + case nameof(OnMouseClick): + OnMouseClick = (EventCallback)parameter.Value; + break; + case nameof(style): + style = (string)parameter.Value; + break; + case nameof(SuppressHeader): + SuppressHeader = (bool)parameter.Value; + break; + default: +#if Logging + GridLogTrace("MBGrid encountered an unknown parameter:" + parameter.Name); +#endif + break; + } + } + +#if Logging + GridLogDebug(" about to compute parameter hash"); +#endif + HashCode newParameterHash; + + if (HighlightSelectedRow) + { + newParameterHash = HashCode + .OfEach(ColumnConfigurations) + .And(@class) + .And(Group) + .And(HighlightSelectedRow) + .And(KeyExpression) + .And(Measurement) + .And(ObscurePMI) + .And(OnMouseClick) + .And(SelectedKey) // Not a parameter but if we don't include this we won't re-render after selecting a row + .And(style) + .And(SuppressHeader); + } + else + { + newParameterHash = HashCode + .OfEach(ColumnConfigurations) + .And(@class) + .And(Group) + .And(HighlightSelectedRow) + .And(KeyExpression) + .And(Measurement) + .And(ObscurePMI) + .And(OnMouseClick) + .And(style) + .And(SuppressHeader); + } + + // + // We have to implement the double loop for grouped ordered data as the OfEach/AndEach + // do not recurse into the second enumerable and certainly don't look at the rowValues + // + if ((GroupedOrderedData != null) && (ColumnConfigurations != null)) + { + foreach (var kvp in GroupedOrderedData) + { +#if Logging + GridLogDebug(" key == " + kvp.Key + " with " + kvp.Value.Count().ToString() + " rows"); +#endif + foreach (var rowValues in kvp.Value) + { + var rowKey = KeyExpression(rowValues.Value).ToString(); + + newParameterHash = new HashCode(HashCode.CombineHashCodes( + newParameterHash.value, + HashCode.Of(rowKey))); + + foreach (var columnDefinition in ColumnConfigurations) + { + switch (columnDefinition.ColumnType) + { + case MB_Grid_ColumnType.Icon: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridIconSpecification)columnDefinition.DataExpression(rowValues.Value); + + newParameterHash = new HashCode(HashCode.CombineHashCodes( + newParameterHash.value, + HashCode.Of(value))); + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.Icon column."); + } + } + break; + + case MB_Grid_ColumnType.Text: + if (columnDefinition.DataExpression != null) + { + var value = columnDefinition.DataExpression(rowValues.Value); + var formattedValue = string.IsNullOrEmpty(columnDefinition.FormatString) ? value?.ToString() : string.Format("{0:" + columnDefinition.FormatString + "}", value); + + newParameterHash = new HashCode(HashCode.CombineHashCodes( + newParameterHash.value, + HashCode.Of(value))); + } + break; + + case MB_Grid_ColumnType.TextColor: + if (columnDefinition.DataExpression != null) + { + try + { + var value = (MBGridTextColorSpecification)columnDefinition.DataExpression(rowValues.Value); + + newParameterHash = new HashCode(HashCode.CombineHashCodes( + newParameterHash.value, + HashCode.Of(value))); + } + catch + { + throw new Exception("Backing value incorrect for MBGrid.TextColor column."); + } + } + break; + + default: + throw new Exception("MBGrid -- Unknown column type"); + } + } + } + } + } + +#if Logging + GridLogDebug(" hash == " + ((int)newParameterHash).ToString()); +#endif + if (newParameterHash == oldParameterHash) + { + // This is a call to ParametersSetAsync with what in all likelyhood is the same + // parameters. Hashing isn't perfect so there is some tiny possibility that new parameters + // are present and the same hash value was computed. + if (HasCompletedFullRender) + { + ShouldRenderValue = false; + } + else + { + ShouldRenderValue = true; + } +#if Logging + GridLogDebug(" EQUAL hash"); +#endif + } + else + { + ShouldRenderValue = true; + IsSimpleRender = true; + oldParameterHash = newParameterHash; +#if Logging + GridLogDebug(" DIFFERING hash"); +#endif + } + } + finally + { +#if Logging + GridLogDebug(" about to release semaphore (SetParametersAsync)"); +#endif + semaphoreSlim.Release(); + } + + return base.SetParametersAsync(ParameterView.Empty); + } + #endregion + + #region ShouldRender + protected override bool ShouldRender() + { + return ShouldRenderValue; + } + #endregion + +} + +//#region HashCode + +///// +///// A hash code used to help with implementing . +///// +///// This code is from the blog post at https://rehansaeed.com/gethashcode-made-easy/ +///// +//public struct HashCode : IEquatable +//{ +// private const int EmptyCollectionPrimeNumber = 19; +// public readonly int value; + +// /// +// /// Initializes a new instance of the struct. +// /// +// /// The value. +// public HashCode(int value) => this.value = value; + +// /// +// /// Performs an implicit conversion from to . +// /// +// /// The hash code. +// /// The result of the conversion. +// public static implicit operator int(HashCode hashCode) => hashCode.value; + +// /// +// /// Implements the operator ==. +// /// +// /// The left. +// /// The right. +// /// The result of the operator. +// public static bool operator ==(HashCode left, HashCode right) => left.Equals(right); + +// /// +// /// Implements the operator !=. +// /// +// /// The left. +// /// The right. +// /// The result of the operator. +// public static bool operator !=(HashCode left, HashCode right) => !(left == right); + +// /// +// /// Takes the hash code of the specified item. +// /// +// /// The type of the item. +// /// The item. +// /// The new hash code. +// public static HashCode Of(T item) => new HashCode(GetHashCode(item)); + +// /// +// /// Takes the hash code of the specified items. +// /// +// /// The type of the items. +// /// The collection. +// /// The new hash code. +// public static HashCode OfEach(IEnumerable items) => +// items == null ? new HashCode(0) : new HashCode(GetHashCode(items, 0)); + +// /// +// /// Adds the hash code of the specified item. +// /// +// /// The type of the item. +// /// The item. +// /// The new hash code. +// public HashCode And(T item) => +// new HashCode(CombineHashCodes(this.value, GetHashCode(item))); + +// /// +// /// Adds the hash code of the specified items in the collection. +// /// +// /// The type of the items. +// /// The collection. +// /// The new hash code. +// public HashCode AndEach(IEnumerable items) +// { +// if (items == null) +// { +// return new HashCode(this.value); +// } + +// return new HashCode(GetHashCode(items, this.value)); +// } + +// /// +// public bool Equals(HashCode other) => this.value.Equals(other.value); + +// /// +// public override bool Equals(object obj) +// { +// if (obj is HashCode) +// { +// return this.Equals((HashCode)obj); +// } + +// return false; +// } + +// /// +// /// Throws . +// /// +// /// Does not return. +// /// Implicitly convert this struct to an to get the hash code. +// [EditorBrowsable(EditorBrowsableState.Never)] +// public override int GetHashCode() => +// throw new NotSupportedException( +// "Implicitly convert this struct to an int to get the hash code."); + +// public static int CombineHashCodes(int h1, int h2) +// { +// unchecked +// { +// // Code copied from System.Tuple so it must be the best way to combine hash codes or at least a good one. +// return ((h1 << 5) + h1) ^ h2; +// } +// } + +// private static int GetHashCode(T item) => item?.GetHashCode() ?? 0; + +// private static int GetHashCode(IEnumerable items, int startHashCode) +// { +// var temp = startHashCode; + +// var enumerator = items.GetEnumerator(); +// if (enumerator.MoveNext()) +// { +// temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); + +// while (enumerator.MoveNext()) +// { +// temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); +// } +// } +// else +// { +// temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber); +// } + +// return temp; +// } +//} + +//#endregion + diff --git a/Material.Blazor.MD2/Components/Grid/MBGridTextColorSpecification.cs b/Material.Blazor.MD2/Components/Grid/MBGridTextColorSpecification.cs new file mode 100644 index 000000000..490b19629 --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGridTextColorSpecification.cs @@ -0,0 +1,11 @@ +using System.Drawing; + +namespace Material.Blazor.MD2; + +public class MBGridTextColorSpecification +{ + public Color BackgroundColor { get; set; } + public Color ForegroundColor { get; set; } + public bool Suppress { get; set; } = false; + public string Text { get; set; } +} diff --git a/Material.Blazor.MD2/Components/Grid/MBGrid_DataHelper.cs b/Material.Blazor.MD2/Components/Grid/MBGrid_DataHelper.cs new file mode 100644 index 000000000..f0ef1473a --- /dev/null +++ b/Material.Blazor.MD2/Components/Grid/MBGrid_DataHelper.cs @@ -0,0 +1,210 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Material.Blazor.MD2; + +public enum Direction { Ascending, Descending } +public class MBGrid_DataHelper +{ + public IEnumerable>>> + PrepareGridData( + IEnumerable data, + PropertyInfo dataKeyInfo, + PropertyInfo orderPropertyInfo1 = null, + Direction orderDirection1 = Direction.Ascending, + PropertyInfo orderPropertyInfo2 = null, + Direction orderDirection2 = Direction.Ascending, + bool group = false, + PropertyInfo groupPropertyInfo = null, + IEnumerable groupItemEnumerable = null, + Func, + PropertyInfo, + Direction, + PropertyInfo, + Direction, + IEnumerable> OrderData = null, + Func, + bool, + PropertyInfo, + PropertyInfo, + IEnumerable, + IEnumerable>>>> GroupItems = null + ) + { + IEnumerable orderedData; + + if (OrderData == null) + { + orderedData = orderData( + data, + orderPropertyInfo1, + orderDirection1, + orderPropertyInfo2, + orderDirection2); + } + else + { + orderedData = OrderData( + data, + orderPropertyInfo1, + orderDirection1, + orderPropertyInfo2, + orderDirection2); + } + + if (GroupItems == null) + { + return groupItems( + orderedData, + group, + dataKeyInfo, + groupPropertyInfo, + groupItemEnumerable); + } + else + { + return GroupItems( + orderedData, + group, + dataKeyInfo, + groupPropertyInfo, + groupItemEnumerable); + } + } + + private IEnumerable orderData( + IEnumerable data, + PropertyInfo orderPropertyInfo1, + Direction orderDirection1, + PropertyInfo orderPropertyInfo2, + Direction orderDirection2 + ) + { + // Perform the group(s) + IEnumerable orderedData; + + if (orderPropertyInfo1 == null) + { + // No grouping + orderedData = data; + } + else + { + if (orderPropertyInfo2 == null) + { + // grouping by first property + if (orderDirection1 == Direction.Ascending) + { + orderedData = data.OrderBy(x => orderPropertyInfo1.GetValue(x)); + } + else + { + orderedData = data.OrderByDescending(x => orderPropertyInfo1.GetValue(x)); + } + } + else + { + // grouping by both properties + if (orderDirection1 == Direction.Ascending) + { + if (orderDirection2 == Direction.Ascending) + { + orderedData = data + .OrderBy(x => orderPropertyInfo1.GetValue(x)) + .ThenBy(x => orderPropertyInfo2.GetValue(x)); + } + else + { + orderedData = data + .OrderBy(x => orderPropertyInfo1.GetValue(x)) + .ThenByDescending(x => orderPropertyInfo2.GetValue(x)); + } + } + else + { + if (orderDirection2 == Direction.Ascending) + { + orderedData = data + .OrderByDescending(x => orderPropertyInfo1.GetValue(x)) + .ThenBy(x => orderPropertyInfo2.GetValue(x)); + } + else + { + orderedData = data + .OrderByDescending(x => orderPropertyInfo1.GetValue(x)) + .ThenByDescending(x => orderPropertyInfo2.GetValue(x)); + } + } + } + } + return orderedData; + } + + private IEnumerable>>> + groupItems( + IEnumerable orderedData, + bool group, + PropertyInfo dataKeyInfo, + PropertyInfo groupPropertyInfo, + IEnumerable groupItems) + { + List>>> groupedOrderedData = new(); + + // Perform the grouping + if (!group) + { + var tempDataAsSingleGroup = new List>(); + foreach (var db in orderedData) + { + tempDataAsSingleGroup.Add(new KeyValuePair(dataKeyInfo.GetValue(db).ToString(), db)); + } + groupedOrderedData.Add(new KeyValuePair>>("FauxKey", tempDataAsSingleGroup)); + } + else + { + var groupedData = orderedData + .GroupBy(x => groupPropertyInfo.GetValue(x)) + .ToDictionary(g => g.Key.ToString(), g => g.ToList()); + + if (groupItems == null) + { + // We will default to alphabetical order + var sortedGroupedData = new SortedDictionary>(groupedData, StringComparer.CurrentCultureIgnoreCase); + foreach (var kvp in sortedGroupedData) + { + var tempGroupedSortedData = new List>(); + foreach (var db in kvp.Value) + { + tempGroupedSortedData.Add(new KeyValuePair(dataKeyInfo.GetValue(db).ToString(), db)); + } + groupedOrderedData.Add(new KeyValuePair>>(kvp.Key, tempGroupedSortedData)); + } + } + else + { + foreach (var key in groupItems) + { + if (groupedData.ContainsKey(key)) + { + var tempGroupedSortedData = new List>(); + foreach (var db in groupedData[key]) + { + tempGroupedSortedData.Add(new KeyValuePair(dataKeyInfo.GetValue(db).ToString(), db)); + } + groupedOrderedData.Add(new KeyValuePair>>(key, tempGroupedSortedData)); + } + else + { + var tempEmptyGroupedSortedData = new List>(); + groupedOrderedData.Add(new KeyValuePair>>(key, tempEmptyGroupedSortedData)); + } + } + } + } + + return groupedOrderedData; + } +} + diff --git a/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.razor b/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.razor new file mode 100644 index 000000000..248fffbcd --- /dev/null +++ b/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.razor @@ -0,0 +1,33 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 + + + + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.razor.cs b/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.razor.cs new file mode 100644 index 000000000..375266fe0 --- /dev/null +++ b/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.razor.cs @@ -0,0 +1,148 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// This is a general purpose Material Theme icon button toggle, with provision for standard MB styling, leading +/// and trailing icons and all standard Blazor events. Adds the "mdc-card__action--icon" class when +/// placed inside an . +/// +public partial class MBIconButtonToggle : InputComponentMD2 +{ + [CascadingParameter] private MBCard Card { get; set; } + + + /// + /// Inclusion of touch target + /// + [Parameter] public bool? TouchTarget { get; set; } + + +#nullable enable annotations + /// + /// The on-state icon's name. + /// + [Parameter] public string IconOn { get; set; } + + + /// + /// The off-state icon's name. + /// + [Parameter] public string IconOff { get; set; } + + + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } +#nullable restore annotations + + + /// + /// The button's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + /// + /// Determines whether the button has a badge - defaults to false. + /// + [Parameter] public bool HasBadge { get; set; } + + + /// + /// The badge's style - see , defaults to . + /// + [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; + + + /// + /// When true collapses the badge. + /// + [Parameter] + public bool BadgeExited { get; set; } + private bool _cachedBadgeExited; + + + /// + /// The button's density. + /// + [Parameter] + public string BadgeValue { get; set; } + private string _cachedBadgeValue; + + + private bool AppliedTouchTarget => CascadingDefaults.AppliedTouchTarget(TouchTarget); + private ElementReference ElementReference { get; set; } + private MBBadge Badge { get; set; } + + private MBCascadingDefaults.DensityInfo DensityInfo => CascadingDefaults.GetDensityCssClass(CascadingDefaults.AppliedIconButtonDensity(Density)); + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + _ = ConditionalCssClasses + .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass) + .AddIf("mdc-card__action mdc-card__action--icon", () => (Card != null)) + .AddIf("mdc-icon-button--on", () => Value) + .AddIf("mdc-button--touch", () => AppliedTouchTarget); + } + + + /// + /// Toggles Value when the button is clicked. + /// + private void ToggleOnClick() + { + ComponentValue = !ComponentValue; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + if (_cachedBadgeValue != BadgeValue || _cachedBadgeExited != BadgeExited) + { + _cachedBadgeValue = BadgeValue; + _cachedBadgeExited = BadgeExited; + + if (Badge is not null) + { + EnqueueJSInteropAction(() => Badge.SetValueAndExited(BadgeValue, BadgeExited)); + } + } + } + + + /// + private protected override Task SetComponentValueAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBIconButtonToggle.setOn", ElementReference, Value); + } + + + /// + private protected override Task OnDisabledSetAsync() + { + AllowNextShouldRender(); + return Task.CompletedTask; + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBIconButtonToggle.init", ElementReference); + } +} diff --git a/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.ts b/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.ts new file mode 100644 index 000000000..5a7fc89af --- /dev/null +++ b/Material.Blazor.MD2/Components/IconButtonToggle/MBIconButtonToggle.ts @@ -0,0 +1,22 @@ +import { MDCIconButtonToggle } from '@material/icon-button'; + +export function init(elem) { + if (!elem) { + return; + } + elem._iconButtonToggle = MDCIconButtonToggle.attachTo(elem); +} + +export function setOn(elem, isOn) { + if (!elem) { + return; + } + elem._iconButtonToggle.on = isOn; +} + +export function click(elem) { + if (!elem) { + return; + } + elem._iconButtonToggle.root.click(); +} diff --git a/Material.Blazor.MD2/Components/IntSlider/MBIntSlider.razor b/Material.Blazor.MD2/Components/IntSlider/MBIntSlider.razor new file mode 100644 index 000000000..6eb230b73 --- /dev/null +++ b/Material.Blazor.MD2/Components/IntSlider/MBIntSlider.razor @@ -0,0 +1,17 @@ +@namespace Material.Blazor.MD2 +@inherits InputComponentMD2 + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/IntSlider/MBIntSlider.razor.cs b/Material.Blazor.MD2/Components/IntSlider/MBIntSlider.razor.cs new file mode 100644 index 000000000..5ec3c4564 --- /dev/null +++ b/Material.Blazor.MD2/Components/IntSlider/MBIntSlider.razor.cs @@ -0,0 +1,68 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme single-thumb slider. +/// +public partial class MBIntSlider : InputComponentMD2 +{ + /// + /// Shows tickmarks if true. + /// + [Parameter] public bool ShowTickmarks { get; set; } = false; + + + /// + /// The minimum slider value. + /// + [Parameter] public int ValueMin { get; set; } = 0; + + + /// + /// The maximum slider value. + /// + [Parameter] public int ValueMax { get; set; } = 100; + + + /// + /// Specifies how slider events are emitted, see . + /// + [Parameter] public MBInputEventType EventType { get; set; } = MBInputEventType.OnChange; + + + /// + /// For continuous input sets the debounce/throttle delay. + /// + [Parameter] public uint ContinuousInputDelay { get; set; } = 300; + + + /// + /// Value for the "aria-label" tag. + /// + [Parameter] public string AriaLabel { get; set; } = "Slider"; + + + private decimal DecimalValue + { + get => ComponentValue; + set => ComponentValue = Convert.ToInt32(Math.Round(value, 0)); + } + + + private int NumSteps => ValueMax - ValueMin; + private MBSliderType SliderType => ShowTickmarks ? MBSliderType.DiscreteWithTickmarks : MBSliderType.Discrete; + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ForceShouldRenderToTrue = true; + } +} diff --git a/Material.Blazor.MD2/Components/List/MBList.md b/Material.Blazor.MD2/Components/List/MBList.md new file mode 100644 index 000000000..dc73c27a9 --- /dev/null +++ b/Material.Blazor.MD2/Components/List/MBList.md @@ -0,0 +1,53 @@ +--- +uid: C.MBList +title: MBList +--- +# MBList<TItem> + +## Summary + +A partial implementation of a [Material List](https://github.com/material-components/material-components-web/tree/v9.0.0/packages/mdc-deprecated-list#lists). Uses render fragments to implement Material Theme Web Components one and two line lists, plus a Material.Blazor.MD2 interpretation of a three line list. It features: + +## Details + +- A title line render fragment; +- IEnumerable render fragments for the first, second and third lines of each list item; +- Icon IEnumerable render fragments that can be ignorred and suppressed with a boolean switch; +- IEnumerable avatar list render fragment; +- IEnumerable actions render fragment; +- Indicators for dense layout, dividers between items, whether keyboard interactions and ripple are activated, and whether lines two or three of each item are hidden; +- Click, KeyDown, MouseDown and TouchStart event handlers that return the index of the item receiving user interaction; +- Applies [density subsystem](xref:A.Density). + +- NumberOfLine and NonIteractive are set in OnInitialized and not in SetParameters + +## Assisting Blazor Rendering with `@key` + +- MBList renders similar table rows with a `foreach` loop; +- In general each item rendered in a loop in Blazor should be supplied with a unique object via the `@key` attribute - see [Blazor University](https://blazor-university.com/components/render-trees/optimising-using-key/); +- MBList by default uses each item in the `Items` parameter as the key, however you can override this. Material.Blazor.MD2 does this because we have had instances where Blazor crashes with the default key giving an exception message such as "The given key 'MyObject' was not present"; +- You can provide a function delegate to the `GetKeysFunc` parameter - we have used two variants of this: + - First to get a unique `Id` property that happens to be in our item's class: `GetKeysFunc="@((item) => item.Id)"`; and + - Second using a "fake key" where we create a GUID to act as the key: `GetKeysFunc="@((item) => Guid.NewGuid())"`. + - You can see an example of this in the [MBList demonstration website page's code](https://github.com/Material-Blazor/Material.Blazor.MD2/blob/main/Material.Blazor.MD2.Website/Pages/List.razor#L155). + +## Partial Implementation + +MBList does not implement: + +- Multi-select +- Checkbox + +## Reserved Attributes + +The following attributes are reserved by Material Components Web and will be ignored if you supply them: + +- aria-orientation +- aria-valuemax + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBList&color=brightgreen)](xref:Material.Blazor.MD2.MBList`1) diff --git a/Material.Blazor.MD2/Components/List/MBList.razor b/Material.Blazor.MD2/Components/List/MBList.razor new file mode 100644 index 000000000..300bffa30 --- /dev/null +++ b/Material.Blazor.MD2/Components/List/MBList.razor @@ -0,0 +1,81 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundation +@typeparam TItem + + +
    + + @{ var index = 0; } + @foreach (var item in Items) + { + var tabIndex = ""; + var clickIndex = index++; + + @if (clickIndex == 0) + { + tabIndex = "0"; + } + else if (ShowSeparators) + { + + } + + + } +
\ No newline at end of file diff --git a/Material.Blazor.MD2/Components/List/MBList.razor.cs b/Material.Blazor.MD2/Components/List/MBList.razor.cs new file mode 100644 index 000000000..d923b35ad --- /dev/null +++ b/Material.Blazor.MD2/Components/List/MBList.razor.cs @@ -0,0 +1,261 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// This is a general purpose Material Theme list implementing one and two line MB web component +/// standards. It also implements a Material.Blazor.MD2 interpretation of the specification for a three line +/// list item. +/// +public partial class MBList : ComponentFoundation +{ + /// + /// The list style. + /// Overrides + /// + [Parameter] public MBListStyle? ListStyle { get; set; } + + + /// + /// The list type. + /// Overrides + /// + [Parameter] public MBListType? ListType { get; set; } + + + /// + /// A function delegate to return the parameters for @key attributes. If unused + /// "fake" keys set to GUIDs will be used. + /// + [Parameter] public Func GetKeysFunc { get; set; } + + + /// + /// The items to display in the list. + /// + [Parameter] public IEnumerable Items { get; set; } + + + /// + /// The icon render fragment to use if !. + /// Note that you will be expected to render your own icon, and can use . + /// + [Parameter] public RenderFragment Icon { get; set; } + + + /// + /// The title line render fragment. + /// + [Parameter] public RenderFragment Title { get; set; } + + + /// + /// The "line two" render fragment. + /// + [Parameter] public RenderFragment LineTwo { get; set; } + + + /// + /// The "line three" render fragment. + /// + [Parameter] public RenderFragment LineThree { get; set; } + + + /// + /// The primary actions render fragment. + /// + [Parameter] public RenderFragment PrimaryActions { get; set; } + + + /// + /// The secondary actions render fragment. + /// + [Parameter] public RenderFragment SecondaryActions { get; set; } + + + /// + /// Extra elements (must be div elements) for a material multilevel list. + /// + [Parameter] public RenderFragment MultiLevelElements { get; set; } + + + /// + /// An @onclick event handler returning the index of the relevant list item. + /// + [Parameter] public EventCallback OnClick { get; set; } + + + /// + /// An @onmousedown event handler returning the index of the relevant list item. + /// + [Parameter] public EventCallback OnMouseDown { get; set; } + + + /// + /// An @onkeydown event handler returning the index of the relevant list item. + /// + [Parameter] public EventCallback OnKeyDown { get; set; } + + + /// + /// An @ontouchstart event handler returning the index of the relevant list item. + /// + [Parameter] public EventCallback OnTouchStart { get; set; } + + + /// + /// Shows a between list items if True. Defaults to False. + /// + [Parameter] public bool ShowSeparators { get; set; } = false; + + + /// + /// Allows keyboard interactions if True. Defaults to False. + /// + [Parameter] public bool KeyboardInteractions { get; set; } = false; + + + /// + /// Applies ripple to the list item if True. Defaults to False. + /// + [Parameter] public bool Ripple { get; set; } = false; + + + /// + /// Sets the non interative Material Theme class if True. Defaults to False. + /// + [Parameter] public bool NonInteractive { get; set; } + + + /// + /// Suppresses icon display if True. Defaults to False. + /// + [Parameter] public bool SuppressIcons { get; set; } + + + /// + /// The lists's density if it is a single line list. Ignored if is + /// + [Parameter] public MBDensity? SingleLineDensity { get; set; } + + + /// + /// Hides "line two" if True. Defaults to False. + /// + [Parameter] public bool HideLineTwo { get; set; } = false; + + + /// + /// Hides "line three" if True. Defaults to False. + /// + [Parameter] public bool HideLineThree { get; set; } = false; + + + + private MBListStyle AppliedListStyle => CascadingDefaults.AppliedStyle(ListStyle); + private MBListType AppliedListType => CascadingDefaults.AppliedType(ListType); + private ElementReference ElementReference { get; set; } + private MBCascadingDefaults.DensityInfo DensityInfo => CascadingDefaults.GetDensityCssClass(CascadingDefaults.AppliedListSingleLineDensity(SingleLineDensity)); + private int NumberOfLines { get; set; } + private bool HasLineTwo { get; set; } + private bool HasLineThree { get; set; } + private Func KeyGenerator { get; set; } + private string TitleClass { get; set; } + private string LineTwoClass { get; set; } + private string LineThreeClass { get; set; } + private string ListItemClass => "mdc-deprecated-list-item__text mb-full-width" + (AppliedDisabled ? " mdc-deprecated-list-item--disabled" : ""); + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ConditionalCssClasses + .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass && NumberOfLines == 1 && AppliedListType != MBListType.Dense) + .AddIf("mdc-card--outlined", () => (CascadingDefaults.AppliedStyle(AppliedListStyle) == MBListStyle.Outlined)) + .AddIf("mdc-deprecated-list--two-line", () => (NumberOfLines == 2)) + .AddIf("mb-list--three-line", () => (NumberOfLines == 3)) + .AddIf("mdc-deprecated-list--non-interactive", () => NonInteractive) + .AddIf("mdc-deprecated-list--dense", () => AppliedListType == MBListType.Dense) + .AddIf("mdc-deprecated-list--avatar-list", () => AppliedListType == MBListType.Avatar); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + HasLineTwo = LineTwo != null && !HideLineTwo; + HasLineThree = LineThree != null && !HideLineThree; + + NumberOfLines = 1; + if (HasLineTwo) + { + NumberOfLines++; + } + + if (HasLineThree) + { + NumberOfLines++; + } + + TitleClass = (NumberOfLines == 1) ? "" : "mdc-deprecated-list-item__primary-text"; + LineTwoClass = "mdc-deprecated-list-item__secondary-text mb-full-width"; + LineThreeClass = "mdc-deprecated-list-item__secondary-text" + ((NumberOfLines == 3) ? " line-three" : "") + " mb-full-width"; + + KeyGenerator = GetKeysFunc ?? delegate (TItem item) { return item; }; + } + + + private async Task OnItemClickAsync(int index) + { + if (KeyboardInteractions && !AppliedDisabled) + { + await OnClick.InvokeAsync(index); + } + } + + private async Task OnItemMouseDownAsync(int index) + { + if (KeyboardInteractions && !AppliedDisabled) + { + await OnMouseDown.InvokeAsync(index); + } + } + + private async Task OnItemKeyDownAsync(int index) + { + if (KeyboardInteractions && !AppliedDisabled) + { + await OnKeyDown.InvokeAsync(index); + } + } + + private async Task OnItemTouchStartAsync(int index) + { + if (KeyboardInteractions && !AppliedDisabled) + { + await OnTouchStart.InvokeAsync(index); + } + } + + + /// + private protected override Task OnDisabledSetAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBList.init", ElementReference, KeyboardInteractions && !AppliedDisabled, Ripple); + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBList.init", ElementReference, KeyboardInteractions && !AppliedDisabled, Ripple); + } +} diff --git a/Material.Blazor.MD2/Components/List/MBList.scss b/Material.Blazor.MD2/Components/List/MBList.scss new file mode 100644 index 000000000..ecd83ac1a --- /dev/null +++ b/Material.Blazor.MD2/Components/List/MBList.scss @@ -0,0 +1,48 @@ +@use "@material/list"; + +.mdc-deprecated-list { + &.dense--5, + &.dense--4 { + @include list.deprecated-single-line-density(-4); + } + + &.dense--3, + &.dense-compact { + @include list.deprecated-single-line-density(-3); + } + + &.dense--2, + &.dense-comfortable { + @include list.deprecated-single-line-density(-2); + } + + &.dense--1 { + @include list.deprecated-single-line-density(-1); + } + + &.dense--0, + &.dense-default { + @include list.deprecated-single-line-density(0); + } +} + + +.mb-list--three-line .mdc-deprecated-list-item__text { + align-self: flex-start; +} + +.mb-list--three-line .mb-list-item { + height: 88px; +} + +.mb-list--three-line.mdc-deprecated-list--dense .mdc-deprecated-list-item, .mdc-deprecated-list--avatar-list.mdc-deprecated-list--dense .mdc-deprecated-list-item { + height: 72px; +} + +.mdc-deprecated-list-item__secondary-text.line-three { + margin-top: -4px; +} + +.mb-list-item--disabled { + pointer-events: none; +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/List/MBList.ts b/Material.Blazor.MD2/Components/List/MBList.ts new file mode 100644 index 000000000..be804679f --- /dev/null +++ b/Material.Blazor.MD2/Components/List/MBList.ts @@ -0,0 +1,15 @@ +import { MDCList } from '@material/list'; +import { MDCRipple } from '@material/ripple'; + +export function init(elem, keyboardInteractions, ripple) { + if (!elem) { + return; + } + if (keyboardInteractions == true) { + elem._list = MDCList.attachTo(elem); + + if (ripple == true) { + elem._list.listElements.map((elem) => MDCRipple.attachTo(elem)); + } + } +} diff --git a/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.md b/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.md new file mode 100644 index 000000000..0ba5dc75c --- /dev/null +++ b/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.md @@ -0,0 +1,16 @@ +--- +uid: C.MBMenuSelectionGroup +title: MBMenuSelectionGroup +--- +# MBMenu + +## Summary + +Placed inside an [MBMenu](xref:C.MBMenu) groups a set of [MBListItems](xref:C.MBListItem) for optional selection. + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBMenuSelectionGroup&color=brightgreen)](xref:Material.Blazor.MD2.MBMenuSelectionGroup) diff --git a/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.razor b/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.razor new file mode 100644 index 000000000..6aa666c5e --- /dev/null +++ b/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.razor @@ -0,0 +1,10 @@ +@namespace Material.Blazor.MD2 + +
  • +
      + @if (ChildContent != null) + { + @ChildContent + } +
    +
  • \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.razor.cs b/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.razor.cs new file mode 100644 index 000000000..868faeafb --- /dev/null +++ b/Material.Blazor.MD2/Components/MenuSelectionGroup/MBMenuSelectionGroup.razor.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme menu selection group. +/// +public partial class MBMenuSelectionGroup +{ + [Parameter] public RenderFragment ChildContent { get; set; } +} diff --git a/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.md b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.md new file mode 100644 index 000000000..691cabcc7 --- /dev/null +++ b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.md @@ -0,0 +1,16 @@ +--- +uid: C.MBMenuSurface +title: MBMenuSurface +--- +# MBMenuSurface + +## Summary + +A [Material Menu Surface](https://github.com/material-components/material-components-web/tree/v9.0.0/packages/mdc-menu-surface#menu-surface), where the entire menu content is a render fragment. + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBMenu&color=brightgreen)](xref:Material.Blazor.MD2.MBMenuSurface) diff --git a/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.razor b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.razor new file mode 100644 index 000000000..b562c62a1 --- /dev/null +++ b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.razor @@ -0,0 +1,16 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundation + + +
    + + @if (ChildContent != null) + { + @ChildContent + } + +
    diff --git a/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.razor.cs b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.razor.cs new file mode 100644 index 000000000..2726f5261 --- /dev/null +++ b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.razor.cs @@ -0,0 +1,124 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// This is a general purpose Material Theme menu. +/// +public partial class MBMenuSurface : ComponentFoundation +{ + /// + /// A render fragement as a set of s. + /// + [Parameter] public RenderFragment ChildContent { get; set; } + + + /// + /// Regular, fullwidth or fixed positioning/width. + /// + [Parameter] public MBMenuSurfacePositioning MenuSurfacePositioning { get; set; } = MBMenuSurfacePositioning.Regular; + + + /// + /// Called when the menu is closed. + /// + [Parameter] public Action OnMenuClosed { get; set; } + + + private DotNetObjectReference ObjectReference { get; set; } + private ElementReference ElementReference { get; set; } + private bool IsOpen { get; set; } = false; + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + _ = ConditionalCssClasses + .AddIf(GetMenuSurfacePositioningClass(MenuSurfacePositioning), () => MenuSurfacePositioning != MBMenuSurfacePositioning.Regular); + + ObjectReference = DotNetObjectReference.Create(this); + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + /// + /// For Material Theme to notify of menu closure via JS Interop. + /// + [JSInvokable()] + public void NotifyClosed() + { + IsOpen = false; + + if (OnMenuClosed != null) + { + _ = InvokeAsync(OnMenuClosed); + } + } + + + /// + /// Toggles the menu open and closed. + /// + /// + public async Task ToggleAsync() + { + if (IsOpen) + { + await InvokeJsVoidAsync("MaterialBlazor.MBMenuSurface.hide", ElementReference); + IsOpen = false; + } + else + { + await InvokeJsVoidAsync("MaterialBlazor.MBMenuSurface.show", ElementReference); + IsOpen = true; + } + } + + + /// + internal override async Task InstantiateMcwComponent() + { + if (!_disposed) + { + await InvokeJsVoidAsync("MaterialBlazor.MBMenuSurface.init", ElementReference, ObjectReference).ConfigureAwait(false); + } + } + + + /// + /// Returns a menu surface class determined by the parameter. + /// + /// + /// + private static string GetMenuSurfacePositioningClass(MBMenuSurfacePositioning surfacePositioning) => + surfacePositioning switch + { + MBMenuSurfacePositioning.FullWidth => "mdc-menu-surface--fullwidth", + MBMenuSurfacePositioning.Fixed => "mdc-menu-surface--fixed", + _ => "" + }; +} diff --git a/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.ts b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.ts new file mode 100644 index 000000000..e63599ccf --- /dev/null +++ b/Material.Blazor.MD2/Components/MenuSurface/MBMenuSurface.ts @@ -0,0 +1,32 @@ +import { MDCMenuSurface } from '@material/menu-surface'; + +export function init(elem, dotNetObject) { + if (!elem) { + return; + } + elem._menu = MDCMenuSurface.attachTo(elem); + + const closedCallback = () => { + dotNetObject.invokeMethodAsync('NotifyClosed'); + }; + + elem._menu.listen('MDCMenuSurface:closed', closedCallback); +} + +export function show(elem) { + if (!elem) { + return; + } + if (elem._menu) { + elem._menu.open(); + } +} + +export function hide(elem) { + if (!elem) { + return; + } + if (elem._menu) { + elem._menu.close(); + } +} diff --git a/Material.Blazor.MD2/Components/PagedDataList/MBPagedDataList.razor b/Material.Blazor.MD2/Components/PagedDataList/MBPagedDataList.razor new file mode 100644 index 000000000..da765f7d8 --- /dev/null +++ b/Material.Blazor.MD2/Components/PagedDataList/MBPagedDataList.razor @@ -0,0 +1,47 @@ +@namespace Material.Blazor.MD2 +@typeparam TItem +@inherits ComponentFoundationMD2 + + +
    + +
    + @ListTemplate( + @ + @foreach (TItem item in CurrentPage) + { + @ItemTemplate(item) + } + ) +
    + +
    + +
    +
    + + +@code { + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + KeyGenerator = GetKeysFunc ?? delegate (TItem item) { return item; }; + + if (ItemTemplate == null) + { + ItemTemplate = (item) => @
  • @item.ToString()
  • ; + } + + if (ListTemplate == null) + { + ListTemplate = (childContent) => @
      @childContent
    ; + } + } +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/PagedDataList/MBPagedDataList.razor.cs b/Material.Blazor.MD2/Components/PagedDataList/MBPagedDataList.razor.cs new file mode 100644 index 000000000..fd28a3baf --- /dev/null +++ b/Material.Blazor.MD2/Components/PagedDataList/MBPagedDataList.razor.cs @@ -0,0 +1,171 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A paged data list using the "wig pig" construct allowing the consumer to free render the relevant paged data. +/// +/// +public partial class MBPagedDataList : ComponentFoundationMD2 +{ + /// + /// A CSS class to apply to the div surrounding the paged data. + /// + [Parameter] public string ListTemplateClass { get; set; } = ""; + + + /// + /// A class for the paginator. + /// + [Parameter] public string PaginatorClass { get; set; } = ""; + + + /// + /// A list of allowable numbers of items per page for the paginator. + /// + [Parameter] public IEnumerable ItemsPerPageSelection { get; set; } + + + /// + /// A function delegate to return the parameters for @key attributes. If unused + /// "fake" keys set to GUIDs will be used. + /// + [Parameter] public Func GetKeysFunc { get; set; } + + + /// + /// The pageable data. + /// + [Parameter] public IEnumerable Data { get; set; } + + + /// + /// The wig pig item renderfragment. + /// + [Parameter] public RenderFragment ItemTemplate { get; set; } + + + /// + /// The wig pig list renderfragment. + /// + [Parameter] public RenderFragment ListTemplate { get; set; } + + + private int BackingPageNumber + { + get => PageNumber; + set + { + if (value != PageNumber) + { + var oldValue = PageNumber; + + if (HasRendered) + { + InvokeAsync(() => OnPageNumberChange(oldValue, value)); + } + else + { + PageNumberChanged.InvokeAsync(value); + } + } + } + } + + + /// + /// The page number. + /// + [Parameter] public int PageNumber { get; set; } + + + /// + /// Change handler for . + /// + [Parameter] public EventCallback PageNumberChanged { get; set; } + + + private int BackingItemsPerPage + { + get => ItemsPerPage; + set + { + if (value != ItemsPerPage) + { + ItemsPerPage = value; + if (HasRendered) + { + ItemsPerPageChanged.InvokeAsync(value); + } + } + } + } + + + /// + /// The number of items per page. + /// + [Parameter] public int ItemsPerPage { get; set; } + + /// + /// Change handler for . + /// + [Parameter] public EventCallback ItemsPerPageChanged { get; set; } + + + private IEnumerable CheckedData => Data ?? Array.Empty(); + private string ContentClass { get; set; } = ""; + public IEnumerable CurrentPage => CheckedData.Skip(PageNumber * ItemsPerPage).Take(ItemsPerPage); + private bool HasRendered { get; set; } = false; + private bool IsHidden { get; set; } = false; + private Func KeyGenerator { get; set; } + + + private async Task OnPageNumberChange(int oldPageNumber, int newPageNumber) + { + string nextClass; + if (newPageNumber < oldPageNumber) + { + nextClass = MBSlidingContent.InFromLeft; + ContentClass = MBSlidingContent.OutToRight; + } + else + { + nextClass = MBSlidingContent.InFromRight; + ContentClass = MBSlidingContent.OutToLeft; + } + + await Task.Delay(100); + await PageNumberChanged.InvokeAsync(newPageNumber); + + ContentClass = nextClass; + IsHidden = false; + + StateHasChanged(); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ConditionalCssClasses + .AddIf(MBSlidingContent.Hidden, () => IsHidden) + .AddIf(MBSlidingContent.Visible, () => !IsHidden); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + HasRendered = true; + } +} diff --git a/Material.Blazor.MD2/Components/Paginator/MBPaginator.razor b/Material.Blazor.MD2/Components/Paginator/MBPaginator.razor new file mode 100644 index 000000000..f3c71328e --- /dev/null +++ b/Material.Blazor.MD2/Components/Paginator/MBPaginator.razor @@ -0,0 +1,52 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundationMD2 + + +
    +
    +
    +
    + @ItemsText +
    + + @if (ItemsPerPageSelection.Count() > 1) + { +
    + +
    + + @foreach (var itemsPerPage in ItemsPerPageSelection) + { + + } + +
    +
    + } +
    + +
    + @* This hidden element serves to give the actual position text a constant width equal to the maximum width that may be required *@ +
    + + @PositionText +
    +
    + +
    + + + + +
    +
    +
    \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Paginator/MBPaginator.razor.cs b/Material.Blazor.MD2/Components/Paginator/MBPaginator.razor.cs new file mode 100644 index 000000000..e30d90b69 --- /dev/null +++ b/Material.Blazor.MD2/Components/Paginator/MBPaginator.razor.cs @@ -0,0 +1,216 @@ +using Material.Blazor.MD2.Internal; + +using Microsoft.AspNetCore.Components; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +public partial class MBPaginator : ComponentFoundationMD2 +{ + [CascadingParameter(Name = "IsWithinDataTable")] private bool RequiresBorder { get; set; } + + + /// + /// A list of the allowable number of items per page for the + /// paginator to present to the user. + /// + [Parameter] public IEnumerable ItemsPerPageSelection { get; set; } + + + /// + /// The total number if items being paged. + /// + [Parameter] public int ItemCount { get; set; } + private int _cachedItemCount; + + + private int BackingItemsPerPage + { + get => ItemsPerPage; + set + { + if (value != ItemsPerPage) + { + ItemsPerPage = value; + _ = ItemsPerPageChanged.InvokeAsync(value); + BackingPageNumber = PageNumber; // Forces a clamp + } + } + } + /// + /// The number of items per page as selected by the user. + /// + [Parameter] + public int ItemsPerPage { get; set; } = 0; + + + /// + /// Two way binding callback for + /// + [Parameter] public EventCallback ItemsPerPageChanged { get; set; } + + + private int BackingPageNumber + { + get => PageNumber; + set + { + if (ItemsPerPage == 0) + { + PageNumber = value; + if (HasRendered) + { + PageNumberChanged.InvokeAsync(value); + } + } + else + { + var clampedValue = Math.Clamp(value, 0, MaxPageNumber); + + if (clampedValue != PageNumber) + { + PageNumber = clampedValue; + if (HasRendered) + { + PageNumberChanged.InvokeAsync(value); + } + } + } + } + } + /// + /// The current page number selected by the user. + /// + [Parameter] + public int PageNumber { get; set; } = 0; + + + /// + /// Two way binding callback for + /// + [Parameter] public EventCallback PageNumberChanged { get; set; } + + + private bool HasRendered { get; set; } = false; + private MBIconButtonToggle IconButtonToggle { get; set; } + private MBSelectElement[] ItemsPerPageItems { get; set; } + private string ItemsText => $"{ItemsPerPage:G0} items per page"; + private int MaxPageNumber => ItemsPerPage == 0 ? 0 : Math.Max(0, Convert.ToInt32(Math.Ceiling((double)ItemCount / ItemsPerPage)) - 1); + private string MaxPositionText => PositionTextString(MaxPageNumber); + private MBMenu Menu { get; set; } + private bool NextDisabled => PageNumber >= MaxPageNumber; + private string PositionText => PositionTextString(PageNumber); + private bool PreviousDisabled => PageNumber <= 0; + private string PositionTextString(int pageNumber) => $"{pageNumber * ItemsPerPage + 1:G0}-{Math.Min(ItemCount, (pageNumber + 1) * ItemsPerPage):G0} of {ItemCount:G0}"; + + + private bool toggleOn; + private bool ToggleOn + { + get => toggleOn; + set + { + if (value != toggleOn) + { + toggleOn = value; + + if (toggleOn) + { + _ = InvokeAsync(Menu.ToggleAsync); + } + } + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + _ = ConditionalCssClasses + .AddIf("no-border", () => !RequiresBorder); + + if (ItemsPerPage == 0) + { + ItemsPerPage = ItemsPerPageSelection.FirstOrDefault(); + } + + if (!ItemsPerPageSelection.Contains(ItemsPerPage)) + { + throw new ArgumentException($"MBPaginator: Cannot set ItemsPerPage to {ItemsPerPage} from selection of {{ {ItemsPerPageSelection.Select(r => r)} }}"); + } + + ItemsPerPageItems = (from r in ItemsPerPageSelection + select new MBSelectElement + { + SelectedValue = r, + Label = r.ToString() + }).ToArray(); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + if (_cachedItemCount != ItemCount) + { + _cachedItemCount = ItemCount; + PageNumber = 0; + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + HasRendered = true; + } + + + private void OnMenuItemClick(int itemsPerPage) + { + double ratio = (double)ItemsPerPage / itemsPerPage; + BackingItemsPerPage = itemsPerPage; + BackingPageNumber = Convert.ToInt32(PageNumber * ratio); + } + + + private void OnMenuClosed() + { + toggleOn = false; + _ = InvokeAsync(StateHasChanged); + } + + + private void OnFirstClick() + { + BackingPageNumber = 0; + } + + + private void OnPreviousClick() + { + BackingPageNumber = Math.Max(PageNumber - 1, 0); + } + + + private void OnNextClick() + { + BackingPageNumber = Math.Min(PageNumber + 1, MaxPageNumber); + } + + + private void OnLastClick() + { + BackingPageNumber = MaxPageNumber; + } +} diff --git a/Material.Blazor.MD2/Components/Paginator/MBPaginator.scss b/Material.Blazor.MD2/Components/Paginator/MBPaginator.scss new file mode 100644 index 000000000..fd04ce728 --- /dev/null +++ b/Material.Blazor.MD2/Components/Paginator/MBPaginator.scss @@ -0,0 +1,35 @@ +$paginator-height: 48px; +$paginator-spacing: 18px; +$position-text-spacing: 6px; + +.mdc-data-table__pagination-rows-per-page { + margin-right: 0 !important; +} + +.mdc-data-table__pagination-navigation { + margin-left: 12px !important; +} +.mdc-data-table__pagination-button { + margin-right: 0 !important; +} + +.mdc-data-table__pagination.no-border { + border-top: 0 !important; +} + +.mdc-data-table__pagination-total { + margin: 0 !important; + display: flex; + flex-flow: column nowrap; + text-align: center; + + & .hidden { + height: 0; + overflow: hidden; + } +} + +.mdc-data-table__pagination-rows-per-page-select { + width: auto !important; + min-width: 0 !important; +} diff --git a/Material.Blazor.MD2/Components/Scheduler/MBScheduler.cs b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.cs new file mode 100644 index 000000000..3cdf891f1 --- /dev/null +++ b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.cs @@ -0,0 +1,1192 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Threading; +using System.Threading.Tasks; + +// +// Implements a multi-column schedule. +// + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme scheduler capable of displaying icons, colored text, and text. +/// +/// N.B.: At this time the scheduler is in preview. Expect the API to change. +/// +public class MBScheduler : ComponentFoundation +{ + #region Members + + /// + /// The set of appointments to be displayed + /// + [Parameter] public IEnumerable Appointments { get; set; } + + /// + /// The number of subcolumns within a day + /// + [Parameter] public int NumberOfSubColumns { get; set; } = 2; + + /// + /// The number of days to be shown + /// + [Parameter] public int NumberOfDays { get; set; } = 5; + + /// + /// Callback for a completed drag event + /// + [Parameter, EditorRequired] public EventCallback OnDragEnd { get; set; } + + /// + /// The initial date + /// + [Parameter] public DateTime StartDate { get; set; } = new DateTime(2022, 04, 04); + + /// + /// The time of the start of the workday + /// + [Parameter] public TimeOnly WorkDayStart { get; set; } = new TimeOnly(7, 0, 0); + + /// + /// The time of the end of the workday + /// + [Parameter] public TimeOnly WorkDayEnd { get; set; } = new TimeOnly(17, 0, 0); + + [Inject] private IJSRuntime JsRuntime { get; set; } + + private int AppointmentColumnWidth { get; set; } + private List ColumnConfigurations { get; set; } + private MBSchedulerAppointment CurrentDragAppointment { get; set; } + private int CurrentDragOffsetX { get; set; } + private int CurrentDragOffsetY { get; set; } + private int DayColumnWidth { get; set; } + private string DropClass { get; set; } = ""; + private int FifteenMinuteHeight { get; set; } + private bool IsFirstMeasurementCompleted { get; set; } = false; + private bool IsSecondMeasurementCompleted { get; set; } = false; + private int LeftEdgeOfColumn1 { get; set; } + private string MeasureBodyRow0Column1ID { get; set; } = Utilities.GenerateUniqueElementName(); + private string MeasureHeaderColumn0ID { get; set; } = Utilities.GenerateUniqueElementName(); + private string MeasureTableID { get; set; } = Utilities.GenerateUniqueElementName(); + private string MeasureWidthID { get; set; } = Utilities.GenerateUniqueElementName(); + + //Instantiate a Semaphore with a value of 1. This means that only 1 thread can be granted access at a time. + private SemaphoreSlim SemaphoreSlim { get; set; } = new(1, 1); + private bool ShouldRenderValue { get; set; } = true; + private ClientBoundingRect TableBoundingRectangle { get; set; } + + #endregion + + #region BuildColGroup + private void BuildColGroup(RenderTreeBuilder builder, ref int rendSeq) + { + // Create the sizing colgroup collection + builder.OpenElement(rendSeq++, "colgroup"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-colgroup"); + var colIndex = 0; + foreach (var col in ColumnConfigurations) + { + var styleStr = + "width: " + col.Width.ToString() + "px !important; " + + "max-width: " + col.Width.ToString() + "px !important; " + + "min-width: " + col.Width.ToString() + "px !important; "; + builder.OpenElement(rendSeq++, "col"); + builder.AddAttribute(rendSeq++, "style", styleStr); + builder.CloseElement(); // col + colIndex++; + } + builder.CloseElement(); // colgroup + } + #endregion + + #region BuildRenderTree + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + LoggingService.LogDebug("BuildRenderTree entered; IsMeasurementCompleted == " + IsFirstMeasurementCompleted.ToString()); + LoggingService.LogDebug(" ShouldRenderValue == " + ShouldRenderValue.ToString()); + + base.BuildRenderTree(builder); + var rendSeq = 1; + string styleStr; + + if (!IsFirstMeasurementCompleted) + { + // For the first render we are only going to create a simple table + // with a header of one element at 100% width + + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-div-header mb-scheduler-backgroundcolor-header-background"); + builder.OpenElement(rendSeq++, "table"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-table mb-scheduler-table-measurement"); + + builder.OpenElement(rendSeq++, "colgroup"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-colgroup"); + var styleStr1 = + "width: 100% !important; " + + "max-width: 100% !important; " + + "min-width: 100% !important; "; + builder.OpenElement(rendSeq++, "col"); + builder.AddAttribute(rendSeq++, "style", styleStr1); + builder.CloseElement(); // col + builder.CloseElement(); // colgroup + + builder.OpenElement(rendSeq++, "thead"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-thead"); + builder.OpenElement(rendSeq++, "tr"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-tr"); + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-td "); + var styleh = " border-width: 1px; border-style: solid; border-color: black; "; + builder.AddAttribute(rendSeq++, "style", styleh); + builder.AddAttribute(rendSeq++, "id", MeasureWidthID); + builder.AddContent(rendSeq++, "Meaningless title"); + builder.CloseElement(); //td + builder.CloseElement(); // tr + builder.CloseElement(); // thead + builder.CloseElement(); //table + builder.CloseElement(); // div mb-scheduler-header + } + else + { + // + // Using the column cfg and column data, render our scheduler. Here is the layout. + // + // div class="@class", style="@style" + // div mb-scheduler-header - Contains the header + // table - + // tr - + // td* - Header + // div mb-scheduler-body - Contains the rows + // table - Contains the rows + // tr* - Rows + // td* - Columns of the row + // + rendSeq = 100; + + if (((@class != null) && (@class.Length > 0)) || ((style != null) && (style.Length > 0))) + { + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-div-outer " + @class); + builder.AddAttribute(rendSeq++, "style", style); + } + + // Based on the column config generate the column titles + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-div-header mb-scheduler-backgroundcolor-header-background"); + //builder.AddAttribute(rendSeq++, "style", "padding-right: " + ScrollWidth.ToString() + "px; "); + builder.OpenElement(rendSeq++, "table"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-table"); + BuildColGroup(builder, ref rendSeq); + builder.OpenElement(rendSeq++, "thead"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-thead"); + builder.OpenElement(rendSeq++, "tr"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-tr"); + + // For each column output a TD + var isHeaderRow = true; + var colCount = 0; + foreach (var col in ColumnConfigurations) + { + styleStr = BuildScheduleTD( + builder, + ref rendSeq, + colCount == 0, + isHeaderRow, + false, + "mb-scheduler-header"); + + builder.AddAttribute(rendSeq++, "style", styleStr); + if (colCount == 0) + { + builder.AddAttribute(rendSeq++, "id", MeasureHeaderColumn0ID); + } + builder.AddContent(rendSeq++, col.Title); + + // Close this column TD + builder.CloseElement(); + + colCount++; + } + + builder.CloseElement(); // tr + + builder.CloseElement(); // thead + + builder.CloseElement(); //table + + builder.CloseElement(); // div mb-scheduler-header + + // + // We now need to build the background grid showing the time (on the + // hour) and the quarter hour lines + // + + // This div holds the scrolled content + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-div-body"); + builder.OpenElement(rendSeq++, "table"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-table @DropClass"); + builder.AddAttribute(rendSeq++, "id", MeasureTableID); + + builder.AddAttribute(rendSeq++, "ondragenter", global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, HandleDragEnter)); + //builder.AddEventPreventDefaultAttribute(rendSeq++, "ondragenter", true); + //builder.AddEventStopPropagationAttribute(rendSeq++, "ondragenter", true); + + builder.AddAttribute(rendSeq++, "ondragleave", global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, HandleDragLeave)); + //builder.AddEventPreventDefaultAttribute(rendSeq++, "ondragleave", true); + //builder.AddEventStopPropagationAttribute(rendSeq++, "ondragleave", true); + + builder.AddAttribute(rendSeq++, "ondragover", global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, HandleDragOver)); + builder.AddEventPreventDefaultAttribute(rendSeq++, "ondragover", true); + //builder.AddEventStopPropagationAttribute(rendSeq++, "ondragover", true); + + builder.AddAttribute(rendSeq++, "ondrop", global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, HandleDragDrop)); + //builder.AddEventPreventDefaultAttribute(rendSeq++, "ondrop", true); + //builder.AddEventStopPropagationAttribute(rendSeq++, "ondrop", true); + + BuildColGroup(builder, ref rendSeq); + builder.OpenElement(rendSeq++, "tbody"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-tbody"); + + var dateTime = new DateTime(2022, 1, 1, WorkDayStart.Hour, WorkDayStart.Minute, 0); + var endTime = new DateTime(2022, 1, 1, WorkDayEnd.Hour, WorkDayEnd.Minute, 0); + var rowCount = 0; + var lastRow = Convert.ToInt32(((endTime - dateTime).TotalMinutes / 15) - 1); + while (dateTime < endTime) + { + // We alternate colors + string rowColorClassNormal; + string rowColorClassHidden; + if ((rowCount / 2) * 2 == rowCount) + { + // Even + rowColorClassNormal = "mb-scheduler-color-row-even-normal"; + rowColorClassHidden = "mb-scheduler-color-row-even-hidden"; + } + else + { + // Odd + rowColorClassNormal = "mb-scheduler-color-row-odd-normal"; + rowColorClassHidden = "mb-scheduler-color-row-odd-hidden"; + } + + // Do a tr + builder.OpenElement(rendSeq++, "tr"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-tr " + rowColorClassNormal); + + // For each column output a td + colCount = 0; + string rowColorClass = rowColorClassNormal; + foreach (var columnDefinition in ColumnConfigurations) + { + string formattedValue; + if ((colCount == 0) && (dateTime.Minute == 0)) + { + formattedValue = dateTime.ToString("HHmm"); + } + else + { + formattedValue = "."; + rowColorClass = rowColorClassHidden; + } + styleStr = BuildScheduleTD( + builder, + ref rendSeq, + colCount == 0, + false, + rowCount == lastRow, + rowColorClass); + + builder.AddAttribute(rendSeq++, "style", styleStr); + if ((rowCount == 0) && (colCount == 1)) + { + builder.AddAttribute(rendSeq++, "id", MeasureBodyRow0Column1ID); + } + builder.AddContent(rendSeq++, formattedValue); + + // Close this column span + builder.CloseElement(); + + colCount++; + } + + // Close this row's div + builder.CloseElement(); + + rowCount++; + dateTime += new TimeSpan(0, 15, 0); + } + + builder.CloseElement(); // tbody + + builder.CloseElement(); // table + + if (IsSecondMeasurementCompleted) + { + foreach (var appt in Appointments) + { + ComputeAppointmentCoordinates( + appt, + out var x, + out var y, + out var h, + out var w); + + var l = + " Appt: " + + appt.Title + " " + + appt.StartTime.ToString() + " " + + "x/y/h/w " + + x.ToString() + "/" + + y.ToString() + "/" + + h.ToString() + "/" + + w.ToString() + "/"; + LoggingService.LogDebug(l); + + appt.Height = h; + appt.RelativeX = x; + appt.RelativeY = y; + appt.Width = w; + //builder.OpenComponent(rendSeq++); + //builder.AddAttribute(rendSeq++, "Height", h); + //builder.AddAttribute(rendSeq++, "SchedulerAppointment", appt); + //builder.AddAttribute( + // rendSeq++, + // "SchedulerRef", + // global::Microsoft.AspNetCore.Components.CompilerServices.RuntimeHelpers.TypeCheck( + // this)); + //builder.AddAttribute(rendSeq++, "Width", w); + //builder.AddAttribute(rendSeq++, "X", x); + //builder.AddAttribute(rendSeq++, "Y", y); + //builder.CloseComponent(); + + //< div class="mb-scheduler-div-appointment" + // draggable="true" + // @ondragstart="HandleDragStart" + // style="@styleString"> + + //
    + // @SchedulerAppointment.Title + // + + // + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-div-appointment"); + builder.AddAttribute(rendSeq++, "draggable", "true"); + builder.AddAttribute(rendSeq++, "ondragstart", global::Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, HandleDragStart)); + //builder.AddEventPreventDefaultAttribute(rendSeq++, "ondragstart", true); + //builder.AddEventStopPropagationAttribute(rendSeq++, "ondragstart", true); + styleStr = + "background: " + appt.BackgroundColor.Name + ";" + + "border-width: 0; " + + "border-radius: 4px; " + + "border-style: solid; " + + "color: " + appt.ForegroundColor.Name + ";" + + "top: " + y.ToString() + "px; " + + "left: " + x.ToString() + "px; " + + "position: absolute; " + + "width: " + w.ToString() + "px; " + + "height: " + h.ToString() + "px; "; + builder.AddAttribute(rendSeq++, "style", styleStr); + builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "style", "padding: 4px; "); + builder.AddContent(rendSeq++, appt.Title); + builder.CloseComponent(); + builder.CloseComponent(); + } + } + + builder.CloseElement(); // div mb-scheduler-body-outer + + if (((@class != null) && (@class.Length > 0)) || ((style != null) && (style.Length > 0))) + { + builder.CloseElement(); // div class= style= + } + LoggingService.LogDebug(" BuildRenderTree completed"); + } + } + #endregion + + #region BuildScheduleTD + internal static string BuildScheduleTD( + RenderTreeBuilder builder, + ref int rendSeq, + bool isFirstColumn, + bool isHeaderRow, + bool isLastRow, + string rowBackgroundColorClass) + { + builder.OpenElement(rendSeq++, "td"); + builder.AddAttribute(rendSeq++, "class", "mb-scheduler-td " + rowBackgroundColorClass); + + if (isHeaderRow) + { + if (isFirstColumn) + { + // T R B L + return " border-width: 1px; border-style: solid; border-color: black; "; + } + else + { + // T R B + return " border-width: 1px 1px 1px 0px; border-style: solid; border-color: black; "; + } + } + else + { + if (isLastRow) + { + if (isFirstColumn) + { + // R B L + return " border-width: 0px 1px 1px 1px; border-style: solid; border-color: black; "; + } + else + { + // R B + return " border-width: 0px 1px 1px 0px; border-style: solid; border-color: black; "; + } + } + else + { + if (isFirstColumn) + { + // R L + return " border-width: 0px 1px 0px 1px; border-style: solid; border-color: black; "; + } + else + { + // R + return " border-width: 0px 1px 0px 0px; border-style: solid; border-color: black; "; + } + } + } + } + #endregion + + #region ComputeAppointmentCoordinates + + internal void ComputeAppointmentCoordinates( + MBSchedulerAppointment appt, + out int x, + out int y, + out int h, + out int w) + { + var dayOffsetTimespan = appt.StartTime.Date - StartDate; + x = LeftEdgeOfColumn1 + dayOffsetTimespan.Days * DayColumnWidth; + if (appt.Column == 2) + { + x += AppointmentColumnWidth + 2; + } + + var timeOffsetTimespan = appt.StartTime - + new DateTime(appt.StartTime.Year, + appt.StartTime.Month, + appt.StartTime.Day, + WorkDayStart.Hour, + WorkDayStart.Minute, + 0); + y = timeOffsetTimespan.Hours * 4 * FifteenMinuteHeight + + (timeOffsetTimespan.Minutes / 15) * FifteenMinuteHeight; + + var timeHeightTimespan = appt.EndTime - appt.StartTime; + h = timeHeightTimespan.Hours * 4 * FifteenMinuteHeight + + (timeHeightTimespan.Minutes / 15) * FifteenMinuteHeight; + w = AppointmentColumnWidth; + } + + #endregion + + #region HandleDragDrop + + private async Task HandleDragDrop(DragEventArgs dea) + { + DropClass = ""; + // Compute the day offset + var dayOffset = + (Convert.ToInt32(dea.ClientX) - + CurrentDragOffsetX - + Convert.ToInt32(TableBoundingRectangle.left) - + LeftEdgeOfColumn1) / + DayColumnWidth; + if (dayOffset < 0) + { + return; + } + var offset = + (Convert.ToInt32(dea.ClientY) - + CurrentDragOffsetY - + Convert.ToInt32(TableBoundingRectangle.top)); + var timeOffset = + offset / + FifteenMinuteHeight; + //timeOffset = + // (dea.ClientY - + // dea.OffsetY - + // TableBoundingRectangle.top) / + // FifteenMinuteHeight; + if (timeOffset < 0) + { + return; + } + + var delta = CurrentDragAppointment.EndTime - CurrentDragAppointment.StartTime; + var newAppointmentStartTime = StartDate + + new TimeSpan( + dayOffset, + WorkDayStart.Hour, + WorkDayStart.Minute + timeOffset * 15, + 0); + + var dragInfo = new DragEndInfo + { + altKey = dea.AltKey, + ctrlKey = dea.CtrlKey, + metaKey = dea.MetaKey, + appointment = CurrentDragAppointment, + newEndTime = newAppointmentStartTime + delta, + newStartTime = newAppointmentStartTime + }; + + await OnDragEnd.InvokeAsync(dragInfo); + } + + #endregion + + #region HandleDragEnter + + private async Task HandleDragEnter(DragEventArgs dea) + { + await Task.CompletedTask; + DropClass = "mb-scheduler-table-can-drop"; + } + + #endregion + + #region HandleDragLeave + + private async Task HandleDragLeave(DragEventArgs dea) + { + await Task.CompletedTask; + DropClass = ""; + } + + #endregion + + #region HandleDragOver + + private async Task HandleDragOver(DragEventArgs dea) + { + await Task.CompletedTask; + + //dropClass = ""; + + //if (AllowedStatuses != null && !AllowedStatuses.Contains(Container.Payload.Status)) return; + + //await Container.UpdateJobAsync(ListStatus); + } + + #endregion + + #region HandleDragStart + + // this method is invoked from MBAppointment.razor.cs + public async Task HandleDragStart(DragEventArgs dea) + { + CurrentDragOffsetX = Convert.ToInt32(dea.OffsetX); + CurrentDragOffsetY = Convert.ToInt32(dea.OffsetY); + + TableBoundingRectangle = await JsRuntime.InvokeAsync( + "MaterialBlazor.MBScheduler.getElementBoundingClientRect", + MeasureTableID); + + //Find the appointment that is being dragged + foreach (var appt in Appointments) + { + var absoluteLeft = + Convert.ToInt32(TableBoundingRectangle.left) + + appt.RelativeX; + var absoluteRight = absoluteLeft + appt.Width; + var absoluteTop = + Convert.ToInt32(TableBoundingRectangle.top) + + appt.RelativeY; + var absoluteBottom = absoluteTop + appt.Height; + + if ((dea.ClientX >= absoluteLeft) && + (dea.ClientX<= absoluteRight) && + (dea.ClientY>= absoluteTop) && + (dea.ClientY <= absoluteBottom)) + { + CurrentDragAppointment = appt; + break; + } + } + + } + + #endregion + + #region MeasureKeyElementsAsync + private async Task MeasureKeyElementsAsync(bool isFirstMeasurement) + { + if (isFirstMeasurement) + { + // We are now going to adjust the column widths to integral pixel + // values for use in the 2nd rendering + var elementArray = await JsRuntime.InvokeAsync( + "MaterialBlazor.MBScheduler.getElementDimensions", + MeasureWidthID); + var width = Convert.ToInt32(elementArray[1]); + var timeWidth = (6 * width) / 100; + var availableWidth = width - timeWidth; + var standardColumnWidth = availableWidth / NumberOfDays; + foreach (var col in ColumnConfigurations) + { + col.Width = standardColumnWidth; + } + ColumnConfigurations[0].Width = timeWidth; + LoggingService.LogDebug("Measured timeWidth: " + timeWidth.ToString()); + LoggingService.LogDebug("Measured standardColumnWidth: " + standardColumnWidth.ToString()); + } + else + { + var element1Array = await JsRuntime.InvokeAsync( + "MaterialBlazor.MBScheduler.getElementDimensions", + MeasureHeaderColumn0ID); + LeftEdgeOfColumn1 = Convert.ToInt32(element1Array[1]) + 1; + + var element2Array = await JsRuntime.InvokeAsync( + "MaterialBlazor.MBScheduler.getElementDimensions", + MeasureBodyRow0Column1ID); + DayColumnWidth = Convert.ToInt32(element2Array[1]); + FifteenMinuteHeight = Convert.ToInt32(element2Array[0]); + if (NumberOfSubColumns == 1) + { + AppointmentColumnWidth = Convert.ToInt32(element2Array[1]) - 5; + } + else + { + AppointmentColumnWidth = (Convert.ToInt32(element2Array[1]) / 2) - 5; + } + LoggingService.LogDebug("Measured LeftEdgeOfColumn1: " + LeftEdgeOfColumn1.ToString()); + LoggingService.LogDebug("Measured DayColumnWidth: " + DayColumnWidth.ToString()); + LoggingService.LogDebug("Measured FifteenMinuteHeight: " + FifteenMinuteHeight.ToString()); + LoggingService.LogDebug("Measured AppointmentColumnWidth: " + AppointmentColumnWidth.ToString()); + } + } + #endregion + + #region OnAfterRenderAsync + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await SemaphoreSlim.WaitAsync(); + var needsSHC = false; + try + { + await base.OnAfterRenderAsync(firstRender); + + LoggingService.LogDebug("OnAfterRenderAsync entered"); + LoggingService.LogDebug(" firstRender: " + firstRender.ToString()); + LoggingService.LogDebug(" IsFirstMeasurementCompleted: " + IsFirstMeasurementCompleted.ToString()); + LoggingService.LogDebug(" IsSecondMeasurementCompleted: " + IsSecondMeasurementCompleted.ToString()); + + if (!IsFirstMeasurementCompleted) + { + LoggingService.LogDebug(" Calling First MeasureKeyElementsAsync"); + + await MeasureKeyElementsAsync(true); + + needsSHC = true; + IsFirstMeasurementCompleted = true; + } + else + { + if (!IsSecondMeasurementCompleted) + { + LoggingService.LogDebug(" Calling Second MeasureKeyElementsAsync"); + + await MeasureKeyElementsAsync(false); + + needsSHC = true; + IsSecondMeasurementCompleted = true; + } + } + } + finally + { + if (needsSHC) + { + await InvokeAsync(StateHasChanged); + } + + LoggingService.LogDebug(" about to release semaphore (OnAfterRenderAsync)"); + SemaphoreSlim.Release(); + } + } + #endregion + + #region OnInitializedAsync + protected override async Task OnInitializedAsync() + { + LoggingService.LogDebug("MBSchedule.OnInitialized entered"); + + await base.OnInitializedAsync(); + + ValidateParameters(); + + ColumnConfigurations = new(); + ColumnConfigurations.Add(new ColumnConfiguration("Time", 1)); + var date = StartDate; + for (int i = 0; i < NumberOfDays; i++) + { + ColumnConfigurations.Add(new ColumnConfiguration(date.ToString("D"), 1)); + date += new TimeSpan(24, 0, 0); + } + + LoggingService.LogDebug("MBSchedule.OnInitialized completed"); + } + #endregion + + #region SetParametersAsync + private int oldParameterHash { get; set; } = -1; + public override Task SetParametersAsync(ParameterView parameters) + { + LoggingService.LogDebug("SetParametersAsync entry"); + + SemaphoreSlim.WaitAsync(); + try + { + // foreach (var parameter in parameters) + // { + // switch (parameter.Name) + // { + // case nameof(@class): + // @class = (string)parameter.Value; + // break; + // case nameof(ColumnConfigurations): + // ColumnConfigurations = (IEnumerable>)parameter.Value; + // // + // // We are going to measure the actual sizes using JS if the Measurement is FitToData + // // We need to create the ColumnWidthArray regardless of the measurement type as we need to pass + // // values to BuildColGroup->CreateMeasurementStyle + // // + // ColumnWidthArray = new float[ColumnConfigurations.Count()]; + // break; + // case nameof(Group): + // Group = (bool)parameter.Value; + // break; + // case nameof(GroupedOrderedData): + // GroupedOrderedData = (IEnumerable>>>)parameter.Value; + // break; + // case nameof(HighlightSelectedRow): + // HighlightSelectedRow = (bool)parameter.Value; + // break; + // case nameof(KeyExpression): + // KeyExpression = (Func)parameter.Value; + // break; + // case nameof(LogIdentification): + // LogIdentification = (string)parameter.Value; + // break; + // case nameof(Measurement): + // Measurement = (MB_Grid_Measurement)parameter.Value; + // break; + // case nameof(ObscurePMI): + // ObscurePMI = (bool)parameter.Value; + // break; + // case nameof(OnMouseClick): + // OnMouseClick = (EventCallback)parameter.Value; + // break; + // case nameof(style): + // style = (string)parameter.Value; + // break; + // case nameof(SuppressHeader): + // SuppressHeader = (bool)parameter.Value; + // break; + // default: + //#if GridLogging + // GridLogTrace("MBGrid encountered an unknown parameter:" + parameter.Name); + //#endif + // break; + // } + // } + + //#if GridLogging + // GridLogDebug(" about to compute parameter hash"); + //#endif + // HashCode newParameterHash; + + // if (HighlightSelectedRow) + // { + // newParameterHash = HashCode + // .OfEach(ColumnConfigurations) + // .And(@class) + // .And(Group) + // .And(HighlightSelectedRow) + // .And(KeyExpression) + // .And(Measurement) + // .And(ObscurePMI) + // .And(OnMouseClick) + // .And(SelectedKey) // Not a parameter but if we don't include this we won't re-render after selecting a row + // .And(style) + // .And(SuppressHeader); + // } + // else + // { + // newParameterHash = HashCode + // .OfEach(ColumnConfigurations) + // .And(@class) + // .And(Group) + // .And(HighlightSelectedRow) + // .And(KeyExpression) + // .And(Measurement) + // .And(ObscurePMI) + // .And(OnMouseClick) + // .And(style) + // .And(SuppressHeader); + // } + + // // + // // We have to implement the double loop for grouped ordered data as the OfEach/AndEach + // // do not recurse into the second enumerable and certainly don't look at the rowValues + // // + // if ((GroupedOrderedData != null) && (ColumnConfigurations != null)) + // { + // foreach (var kvp in GroupedOrderedData) + // { + //#if GridLogging + // GridLogDebug(" key == " + kvp.Key + " with " + kvp.Value.Count().ToString() + " rows"); + //#endif + // foreach (var rowValues in kvp.Value) + // { + // var rowKey = KeyExpression(rowValues.Value).ToString(); + + // newParameterHash = new HashCode(HashCode.CombineHashCodes( + // newParameterHash.value, + // HashCode.Of(rowKey))); + + // foreach (var columnDefinition in ColumnConfigurations) + // { + // switch (columnDefinition.ColumnType) + // { + // case MB_Grid_ColumnType.Icon: + // if (columnDefinition.DataExpression != null) + // { + // try + // { + // var value = (MBGridIconSpecification)columnDefinition.DataExpression(rowValues.Value); + + // newParameterHash = new HashCode(HashCode.CombineHashCodes( + // newParameterHash.value, + // HashCode.Of(value))); + // } + // catch + // { + // throw new Exception("Backing value incorrect for MBGrid.Icon column."); + // } + // } + // break; + + // case MB_Grid_ColumnType.Text: + // if (columnDefinition.DataExpression != null) + // { + // var value = columnDefinition.DataExpression(rowValues.Value); + // var formattedValue = string.IsNullOrEmpty(columnDefinition.FormatString) ? value?.ToString() : string.Format("{0:" + columnDefinition.FormatString + "}", value); + + // newParameterHash = new HashCode(HashCode.CombineHashCodes( + // newParameterHash.value, + // HashCode.Of(value))); + // } + // break; + + // case MB_Grid_ColumnType.TextColor: + // if (columnDefinition.DataExpression != null) + // { + // try + // { + // var value = (MBGridTextColorSpecification)columnDefinition.DataExpression(rowValues.Value); + + // newParameterHash = new HashCode(HashCode.CombineHashCodes( + // newParameterHash.value, + // HashCode.Of(value))); + // } + // catch + // { + // throw new Exception("Backing value incorrect for MBGrid.TextColor column."); + // } + // } + // break; + + // default: + // throw new Exception("MBGrid -- Unknown column type"); + // } + // } + // } + // } + // } + + //#if GridLogging + // GridLogDebug(" hash == " + ((int)newParameterHash).ToString()); + //#endif + // if (newParameterHash == oldParameterHash) + // { + // // This is a call to ParametersSetAsync with what in all likelyhood is the same + // // parameters. Hashing isn't perfect so there is some tiny possibility that new parameters + // // are present and the same hash value was computed. + // if (HasCompletedFullRender) + // { + // ShouldRenderValue = false; + // } + // else + // { + // ShouldRenderValue = true; + // } + //#if GridLogging + // GridLogDebug(" EQUAL hash"); + //#endif + // } + // else + // { + // ShouldRenderValue = true; + // IsSimpleRender = true; + // IsMeasurementNeeded = true; + // oldParameterHash = newParameterHash; + //#if GridLogging + // GridLogDebug(" DIFFERING hash"); + //#endif + // } + } + finally + { + LoggingService.LogDebug(" about to release semaphore (SetParametersAsync)"); + + SemaphoreSlim.Release(); + } + + // return base.GetSelectionAsync(ParameterView.Empty); + + return base.SetParametersAsync(parameters); + } + #endregion + + #region ShouldRender + protected override bool ShouldRender() + { + return ShouldRenderValue; + } + #endregion + + #region ValidateParameters + internal void ValidateParameters() + { + if ((NumberOfSubColumns < 1) || (NumberOfSubColumns > 2)) + { + throw new Exception("MBScheduler -- Illegal ColumnCount of " + NumberOfSubColumns.ToString()); + } + + if (Appointments == null) + { + throw new Exception("MBScheduler -- Appointments is null"); + } + } + + #endregion + + #region Class ColumnConfiguration + + internal class ColumnConfiguration + { + public string Title { get; set; } + public int Width { get; set; } + + private ColumnConfiguration() { } + public ColumnConfiguration( + string title = "", + int width = 10) + { + Title = title; + Width = width; + } + } + + #endregion + + #region Struct DragEndInformation + public struct DragEndInfo + { + public bool altKey { get; set; } + public MBSchedulerAppointment appointment { get; set; } + public bool ctrlKey { get; set; } + public bool metaKey { get; set; } + public DateTime newEndTime { get; set; } + public DateTime newStartTime { get; set; } + } + + #endregion + + #region Struct HashCode2 + + /// + /// A hash code used to help with implementing . + /// + /// This code is from the blog post at https://rehansaeed.com/gethashcode-made-easy/ + /// + public struct HashCode2 : IEquatable + { + private const int EmptyCollectionPrimeNumber = 19; + public readonly int value; + + /// + /// Initializes a new instance of the struct. + /// + /// The value. + public HashCode2(int value) => this.value = value; + + /// + /// Performs an implicit conversion from to . + /// + /// The hash code. + /// The result of the conversion. + public static implicit operator int(HashCode2 hashCode) => hashCode.value; + + /// + /// Implements the operator ==. + /// + /// The left. + /// The right. + /// The result of the operator. + public static bool operator ==(HashCode2 left, HashCode2 right) => left.Equals(right); + + /// + /// Implements the operator !=. + /// + /// The left. + /// The right. + /// The result of the operator. + public static bool operator !=(HashCode2 left, HashCode2 right) => !(left == right); + + /// + /// Takes the hash code of the specified item. + /// + /// The type of the item. + /// The item. + /// The new hash code. + public static HashCode2 Of(T item) => new HashCode2(GetHashCode(item)); + + /// + /// Takes the hash code of the specified items. + /// + /// The type of the items. + /// The collection. + /// The new hash code. + public static HashCode2 OfEach(IEnumerable items) => + items == null ? new HashCode2(0) : new HashCode2(GetHashCode(items, 0)); + + /// + /// Adds the hash code of the specified item. + /// + /// The type of the item. + /// The item. + /// The new hash code. + public HashCode2 And(T item) => + new HashCode2(CombineHashCodes(this.value, GetHashCode(item))); + + /// + /// Adds the hash code of the specified items in the collection. + /// + /// The type of the items. + /// The collection. + /// The new hash code. + public HashCode2 AndEach(IEnumerable items) + { + if (items == null) + { + return new HashCode2(this.value); + } + + return new HashCode2(GetHashCode(items, this.value)); + } + + public bool Equals(HashCode2 other) => this.value.Equals(other.value); + + public override bool Equals(object obj) + { + if (obj is HashCode2) + { + return this.Equals((HashCode2)obj); + } + + return false; + } + + /// + /// Throws . + /// + /// Does not return. + /// Implicitly convert this struct to an to get the hash code. + [EditorBrowsable(EditorBrowsableState.Never)] + public override int GetHashCode() => + throw new NotSupportedException( + "Implicitly convert this struct to an int to get the hash code."); + + public static int CombineHashCodes(int h1, int h2) + { + unchecked + { + // Code copied from System.Tuple so it must be the best way to combine hash codes or at least a good one. + return ((h1 << 5) + h1) ^ h2; + } + } + + private static int GetHashCode(T item) => item?.GetHashCode() ?? 0; + + private static int GetHashCode(IEnumerable items, int startHashCode) + { + var temp = startHashCode; + + var enumerator = items.GetEnumerator(); + if (enumerator.MoveNext()) + { + temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); + + while (enumerator.MoveNext()) + { + temp = CombineHashCodes(temp, GetHashCode(enumerator.Current)); + } + } + else + { + temp = CombineHashCodes(temp, EmptyCollectionPrimeNumber); + } + + return temp; + } + } + + #endregion + + #region Struct ClientBoundingRect + + public struct ClientBoundingRect + { + public double bottom { get; set; } + public double height { get; set; } + public double left { get; set; } + public double right { get; set; } + public double top { get; set; } + public double width { get; set; } + public double x { get; set; } + public double y { get; set; } + + } + + #endregion +} + diff --git a/Material.Blazor.MD2/Components/Scheduler/MBScheduler.md b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.md new file mode 100644 index 000000000..695646571 --- /dev/null +++ b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.md @@ -0,0 +1,24 @@ +--- +uid: C.MBScheduler +title: MBScheduler +--- +# MBScheduler + +## Summary + +A scheduler built on a table base using BuildRenderTree. + +## Warning + +This is a preview version of the scheduler. The expectation must be that implementation details and the API will change. + +## Details + +- tbd. + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBScheduler&color=brightgreen)](xref:Material.Blazor.MD2.MBScheduler) \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Scheduler/MBScheduler.scss b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.scss new file mode 100644 index 000000000..3ad8a267f --- /dev/null +++ b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.scss @@ -0,0 +1,126 @@ +@charset "UTF-8"; + +.mb-scheduler-div-outer { + width: 100% !important; + height: 100% !important; + max-width: 100% !important; + max-height: 100% !important; + overflow: hidden; + box-sizing: border-box; + padding: 0; + margin: 0; +} + +.mb-scheduler-div-header { + font-family: Arial; + font-weight: bolder; + padding: 0; + overflow: hidden; + text-align: center; + box-sizing: border-box; +} + +.mb-scheduler-div-body { + font-family: Arial; + font-weight: normal; + background: lightblue; + padding: 0; + overflow: hidden; + position: relative; + box-sizing: border-box; +} + +.mb-scheduler-table { + border: 0; + border-collapse: collapse; + border-spacing: 0; + overflow: hidden; + font-size: 1.0rem; + table-layout: fixed; + text-align: center; + text-indent: unset; + text-overflow: ellipsis; + vertical-align: middle; +} + +.mb-scheduler-table-can-drop { + border-bottom: 4px dashed green; + border-collapse:unset; + border-spacing:unset; +} + +.mb-scheduler-table-measurement { + width: 100%; +} + +.mb-scheduler-colgroup { + display: table-column-group; +} + +.mb-scheduler-thead { +} + +.mb-scheduler-tbody { +} + +.mb-scheduler-tr { +} + +.mb-scheduler-td { + cursor: default; + display: table-cell; + flex: 0 0 auto; + padding: 4px; + font-size: 12px; + text-overflow: ellipsis; + overflow: hidden; + box-sizing: border-box; + white-space: nowrap; + letter-spacing: initial; +} + +.mb-scheduler-td-group { + display: table-cell; + color: black; + border-bottom: 0px; + border-left: 1px solid darkblue; + border-right: 1px solid darkblue; + border-top: 2px solid darkblue; + font-size: x-large; + font-weight: bolder; + flex: 0 0 auto; +} + +.mb-scheduler-header { + background: lightgray; + background-color: lightgray; + color: black; +} + +.mb-scheduler-color-row-even-normal { + background-color: khaki; + color: black; +} + +.mb-scheduler-color-row-even-hidden { + background-color: khaki; + color: khaki; +} + +.mb-scheduler-color-row-odd-normal { + background-color: lemonchiffon; + color: black; +} + +.mb-scheduler-color-row-odd-hidden { + background-color: lemonchiffon; + color: lemonchiffon; +} + +.mb-scheduler-div-appointment { + cursor: grab; +} + +.mb-scheduler-div-appointment:active { + cursor: grabbing; +} diff --git a/Material.Blazor.MD2/Components/Scheduler/MBScheduler.ts b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.ts new file mode 100644 index 000000000..aab8b528d --- /dev/null +++ b/Material.Blazor.MD2/Components/Scheduler/MBScheduler.ts @@ -0,0 +1,37 @@ +export function getElementDimensions( + elementId: string): number[] { + + // Create an element + const element: HTMLElement | null = document.getElementById(elementId); + var retval: number[] = new Array(2); + + if (element != null) { + // Get the height + var height: string = window.getComputedStyle(element).height; + var unadornedHeight: string = height.slice(0, height.indexOf("px")); + var numericHeight: number = parseFloat(unadornedHeight); + retval[0] = numericHeight; + + // Get the width + var width: string = window.getComputedStyle(element).width; + var unadornedWidth: string = width.slice(0, width.indexOf("px")); + var numericWidth: number = parseFloat(unadornedWidth); + retval[1] = numericWidth; + } + + return retval; +} + +export function getElementBoundingClientRect( + elementId: string): DOMRect | null { + + // Create an element + const element: HTMLElement | null = document.getElementById(elementId); + + if (element != null) { + // Get the bounding rectangle + return element.getBoundingClientRect(); + } + + return null; +} diff --git a/Material.Blazor.MD2/Components/Scheduler/MBSchedulerAppointment.cs b/Material.Blazor.MD2/Components/Scheduler/MBSchedulerAppointment.cs new file mode 100644 index 000000000..c7a74fa5c --- /dev/null +++ b/Material.Blazor.MD2/Components/Scheduler/MBSchedulerAppointment.cs @@ -0,0 +1,21 @@ +using System; +using System.Drawing; +using System.Linq.Expressions; + +namespace Material.Blazor.MD2; + +public class MBSchedulerAppointment +{ + public Color BackgroundColor { get; set; } + public int Column { get; set; } + public DateTime EndTime { get; set; } + public Color ForegroundColor { get; set; } + public int Height { get; set; } + public int RelativeX { get; set; } + public int RelativeY { get; set; } + public DateTime StartTime { get; set; } + public string Title { get; set; } + public Guid Uid { get; set; } + public int Width { get; set; } + +} diff --git a/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.razor b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.razor new file mode 100644 index 000000000..a0344a65b --- /dev/null +++ b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.razor @@ -0,0 +1,48 @@ +@namespace Material.Blazor.MD2 + +@inherits MultiSelectComponentMD2> +@typeparam TItem + + +
    + + @for (int j = 0; j < ItemsArray.Length; j++) + { + int i = j; + +
    + + + @if (HasBadge && i == ItemsArray.Length - 1) + { + + + + } +
    +
    + } +
    diff --git a/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.razor.cs b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.razor.cs new file mode 100644 index 000000000..c3b79f950 --- /dev/null +++ b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.razor.cs @@ -0,0 +1,195 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme segmented button orientated as a multi-select. +/// +public partial class MBSegmentedButtonMulti : MultiSelectComponentMD2> +{ + /// + /// If this component is rendered inside a single-select segmented button, add the "" class. + /// + [CascadingParameter] private MBSegmentedButtonSingle SegmentedButtonSingle { get; set; } + + /// + /// Inclusion of touch target + /// + [Parameter] public bool? TouchTarget { get; set; } + + + /// + /// Determines whether the button has a badge - defaults to false. + /// + [Parameter] public bool HasBadge { get; set; } + + + /// + /// The badge's style - see , defaults to . + /// + [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; + + + /// + /// When true collapses the badge. + /// + [Parameter] + public bool BadgeExited { get; set; } + private bool _cachedBadgeExited; + + + /// + /// The button's density. + /// + [Parameter] + public string BadgeValue { get; set; } + private string _cachedBadgeValue; + + + /// + /// The badge for use by . + /// + internal MBBadge Badge { get; set; } + + + private bool AppliedTouchTarget => CascadingDefaults.AppliedTouchTarget(TouchTarget); + private MBIconBearingSelectElement[] ItemsArray { get; set; } + private bool IsSingleSelect { get; set; } + private IDisposable ObjectReference { get; set; } + private string GroupRole => (SegmentedButtonSingle == null) ? "group" : "radiogroup"; + private Dictionary[] SegmentAttributes { get; set; } + private ElementReference SegmentedButtonReference { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + IsSingleSelect = SegmentedButtonSingle != null; + + ConditionalCssClasses + .AddIf("mdc-segmented-button--single-select", () => IsSingleSelect); + + ItemsArray = Items.ToArray(); + + SegmentAttributes = new Dictionary[ItemsArray.Length]; + + for (int i = 0; i < ItemsArray.Length; i++) + { + SegmentAttributes[i] = new(); + + var selected = Value.Contains(ItemsArray[i].SelectedValue); + + SegmentAttributes[i].Add("class", "mdc-segmented-button__segment mdc-segmented-button--touch" + (selected ? " mdc-segmented-button__segment--selected" : "")); + + if (IsSingleSelect) + { + SegmentAttributes[i].Add("role", "radio"); + SegmentAttributes[i].Add("aria-checked", selected.ToString().ToLower()); + } + else + { + SegmentAttributes[i].Add("aria-pressed", selected.ToString().ToLower()); + } + } + + ObjectReference = DotNetObjectReference.Create(this); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + if (_cachedBadgeValue != BadgeValue || _cachedBadgeExited != BadgeExited) + { + _cachedBadgeValue = BadgeValue; + _cachedBadgeExited = BadgeExited; + + if (Badge is not null) + { + EnqueueJSInteropAction(() => Badge.SetValueAndExited(BadgeValue, BadgeExited)); + } + } + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + /// + /// For Material Theme to notify of menu item selection via JS Interop. + /// + [JSInvokable] + public void NotifyMultiSelected(bool[] selected) + { + var selectedIndexes = Enumerable.Range(0, selected.Length).Where(i => selected[i]); + ComponentValue = ItemsArray.Where((item, index) => selectedIndexes.Contains(index)).Select(x => x.SelectedValue).ToArray(); + } + + + /// + /// For Material Theme to notify of menu item selection via JS Interop. + /// + [JSInvokable] + public void NotifySingleSelected(int index) + { + ComponentValue = new TItem[] { ItemsArray[index].SelectedValue }; + } + + + /// + private protected override Task SetComponentValueAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBSegmentedButtonMulti.setSelected", SegmentedButtonReference, Items.Select(x => Value.Contains(x.SelectedValue)).ToArray()); + } + + + /// + private protected override Task OnDisabledSetAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBSegmentedButtonMulti.setDisabled", SegmentedButtonReference, AppliedDisabled); + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBSegmentedButtonMulti.init", SegmentedButtonReference, IsSingleSelect, ObjectReference); + } + + + /// + /// Used by to set the value. + /// + /// + internal Task SetSingleSelectValue(TItem value) + { + Value = new TItem[] { value }; + return SetComponentValueAsync(); + } +} diff --git a/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.scss b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.scss new file mode 100644 index 000000000..8690fa229 --- /dev/null +++ b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.scss @@ -0,0 +1,7 @@ +.mb-segmented-button__container { + display: inline-block; + position: relative; + width: fit-content; + height: fit-content; + overflow: visible; +} diff --git a/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.ts b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.ts new file mode 100644 index 000000000..78f28b824 --- /dev/null +++ b/Material.Blazor.MD2/Components/SegmentedButtonMulti/MBSegmentedButtonMulti.ts @@ -0,0 +1,39 @@ +import { MDCSegmentedButton } from '@material/segmented-button'; + +export function init(elem, isSingleSelect, dotNetObject) { + if (!elem) { + return; + } + elem._segmentedButton = MDCSegmentedButton.attachTo(elem); + elem._isSingleSelect = isSingleSelect; + + elem._segmentedButton.foundation.adapter.notifySelectedChange = detail => { + if (elem._isSingleSelect) { + dotNetObject.invokeMethodAsync('NotifySingleSelected', detail.index); + } + else { + dotNetObject.invokeMethodAsync('NotifyMultiSelected', elem._segmentedButton.segments.map(x => x.isSelected())); + } + }; +} + +export function setDisabled(elem, value) { + if (!elem) { + return; + } + elem._segmentedButton.disabled = value; +} + +export function setSelected(elem, selectedFlags) { + if (!elem) { + return; + } + for (let i = 0; i < selectedFlags.length; i++) { + if (selectedFlags[i] == true) { + elem._segmentedButton.segments[i].setSelected(); + } + else { + elem._segmentedButton.segments[i].setUnselected(); + } + } +} diff --git a/Material.Blazor.MD2/Components/SegmentedButtonSingle/MBSegmentedButtonSingle.razor b/Material.Blazor.MD2/Components/SegmentedButtonSingle/MBSegmentedButtonSingle.razor new file mode 100644 index 000000000..f6b2d8593 --- /dev/null +++ b/Material.Blazor.MD2/Components/SegmentedButtonSingle/MBSegmentedButtonSingle.razor @@ -0,0 +1,15 @@ +@namespace Material.Blazor.MD2 + +@inherits SingleSelectComponentMD2> +@typeparam TItem + + + + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/SegmentedButtonSingle/MBSegmentedButtonSingle.razor.cs b/Material.Blazor.MD2/Components/SegmentedButtonSingle/MBSegmentedButtonSingle.razor.cs new file mode 100644 index 000000000..47c26e547 --- /dev/null +++ b/Material.Blazor.MD2/Components/SegmentedButtonSingle/MBSegmentedButtonSingle.razor.cs @@ -0,0 +1,106 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme segmented button orientated as a single-select. +/// +public partial class MBSegmentedButtonSingle : SingleSelectComponentMD2> +{ +#nullable enable annotations + /// + /// The foundry to use for both leading and trailing icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + + + /// + /// Determines whether the button has a badge - defaults to false. + /// + [Parameter] public bool HasBadge { get; set; } + + + /// + /// The badge's style - see , defaults to . + /// + [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; + + + /// + /// When true collapses the badge. + /// + [Parameter] + public bool BadgeExited { get; set; } + private bool _cachedBadgeExited; + + + /// + /// The button's density. + /// + [Parameter] + public string BadgeValue { get; set; } + private string _cachedBadgeValue; +#nullable restore annotations + + + private MBSegmentedButtonMulti SegmentedButtonMulti { get; set; } + + private IList multiValues; + private IList MultiValues + { + get => multiValues; + set + { + multiValues = value; + ComponentValue = multiValues.FirstOrDefault(); + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + var appliedItemValidation = CascadingDefaults.AppliedItemValidation(ItemValidation); + + ComponentValue = ValidateItemList(Items, appliedItemValidation).value; + + multiValues = new TItem[] { Value }; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + if (_cachedBadgeValue != BadgeValue || _cachedBadgeExited != BadgeExited) + { + _cachedBadgeValue = BadgeValue; + _cachedBadgeExited = BadgeExited; + + if (SegmentedButtonMulti?.Badge is not null) + { + EnqueueJSInteropAction(() => SegmentedButtonMulti.Badge.SetValueAndExited(BadgeValue, BadgeExited)); + } + } + } + + + /// + private protected override Task SetComponentValueAsync() + { + SegmentedButtonMulti.SetSingleSelectValue(Value); + return Task.CompletedTask; + } +} diff --git a/Material.Blazor.MD2/Components/Shield/MBShield.razor b/Material.Blazor.MD2/Components/Shield/MBShield.razor new file mode 100644 index 000000000..1badf933a --- /dev/null +++ b/Material.Blazor.MD2/Components/Shield/MBShield.razor @@ -0,0 +1,38 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundationMD2 + + + + @if (ShieldType != MBShieldType.ValueOnly) + { + + @if (!string.IsNullOrWhiteSpace(LabelIcon)) + { + + + + } + + @Label + + } + + @if (ShieldType != MBShieldType.LabelOnly) + { + + @if (!string.IsNullOrWhiteSpace(ValueIcon)) + { + + + + } + + @Value + + } + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Shield/MBShield.razor.cs b/Material.Blazor.MD2/Components/Shield/MBShield.razor.cs new file mode 100644 index 000000000..2492e9ac8 --- /dev/null +++ b/Material.Blazor.MD2/Components/Shield/MBShield.razor.cs @@ -0,0 +1,77 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A shield similar to those from shield.io and used in GitHub. Implemented +/// with HTML rather than SVG. +/// +public partial class MBShield : ComponentFoundationMD2 +{ +#nullable enable annotations + /// + /// The shield type, being Label (left part), Value (right part) or both. + /// + [Parameter] public MBShieldType ShieldType { get; set; } = MBShieldType.LabelAndValue; + + + /// + /// Label (to the left). + /// + [Parameter] public string Label { get; set; } + + + /// + /// Value (to the right) + /// + [Parameter] public string Value { get; set; } + + + /// + /// Extra CSS class for the Label. + /// + [Parameter] public string LabelClass { get; set; } = ""; + + + /// + /// HTML style attribute for the Label. + /// + [Parameter] public string LabelStyle { get; set; } = ""; + + + /// + /// Icon for the Label. + /// + [Parameter] public string LabelIcon { get; set; } = ""; + + + /// + /// Extra CSS Class for the Value. + /// + [Parameter] public string ValueClass { get; set; } = ""; + + + /// + /// HTML style attribute for the Value. + /// + [Parameter] public string ValueStyle { get; set; } = ""; + + + /// + /// Icon for the Value. + /// + [Parameter] public string ValueIcon { get; set; } = ""; + + + /// + /// The foundry to use for both icons. + /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.FAIcon()" + /// IconFoundry="IconHelper.OIIcon()" + /// Overrides + /// + [Parameter] public IMBIconFoundry? IconFoundry { get; set; } +#nullable restore annotations +} diff --git a/Material.Blazor.MD2/Components/Shield/MBShield.scss b/Material.Blazor.MD2/Components/Shield/MBShield.scss new file mode 100644 index 000000000..8b91f91ba --- /dev/null +++ b/Material.Blazor.MD2/Components/Shield/MBShield.scss @@ -0,0 +1,69 @@ +$shield-height: 20px; +$shield-font-family: "Verdana","Geneva","DejaVu Sans","sans-serif"; +$shield-font-size: 10pt; +$shield-icon-font-size: 10pt; +$shield-inner-padding: 4px; +$shield-outer-padding: 4px; +$shield-icon-left-margin: -1px; +$shield-icon-right-margin: 3px; + +:root { + --mb-shield-label-color: white; + --mb-shield-value-color: white; + --mb-shield-label-background: var(--mb-color-blue-grey-700); + --mb-shield-value-background: var(--mb-color-amber-700); +} + +.mb-shield { + user-select: none; + -webkit-user-select: none; + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + height: $shield-height; + line-height: $shield-height; + font-family: $shield-font-family; + font-size: $shield-font-size; + margin: 0; + padding: 0; + border: 0; + white-space: nowrap; + vertical-align: middle +} + +.mb-shield span, .mb-shield i { + height: $shield-height; + max-height: $shield-height; +} + +.mb-shield i { + font-size: $shield-icon-font-size; + line-height: $shield-height; + margin: 0 $shield-icon-right-margin 0 $shield-icon-left-margin; +} + +.mb-shield--label { + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + color: var(--mb-shield-label-color); + background: var(--mb-shield-label-background); + height: $shield-height; + line-height: $shield-height; + margin: 0; + padding: 0 $shield-inner-padding 0 $shield-outer-padding; + border: 0; +} + +.mb-shield--value { + display: inline-flex; + flex-flow: row nowrap; + align-items: center; + color: var(--mb-shield-value-color); + background: var(--mb-shield-value-background); + height: $shield-height; + line-height: $shield-height; + margin: 0; + padding: 0 $shield-outer-padding 0 $shield-inner-padding; + border: 0; +} diff --git a/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.md b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.md new file mode 100644 index 000000000..94999ceab --- /dev/null +++ b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.md @@ -0,0 +1,23 @@ +--- +uid: C.MBSlidingContent +title: MBSlidingContent +--- +# MBSlidingContent<TItem> + +## Summary + +A templated component to provide previous/next navigation through a series of pages with light left/right and fade in/out animation. This is designed to give a user a visible +yet subtle cue for selection transitions either with buttons, tab bars or paginated data lists. The level of animation is deliberately understated and arises from our observation +that users can find UX confusing without such cues. + +## Details + +- Accepts an ITenumerable of render fragments for each page; and +- Two-way binds the item index; + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBSlidingContent&color=brightgreen)](xref:Material.Blazor.MD2.MBSlidingContent`1) diff --git a/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.razor b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.razor new file mode 100644 index 000000000..06156da28 --- /dev/null +++ b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.razor @@ -0,0 +1,15 @@ +@namespace Material.Blazor.MD2 + +@typeparam TItem + +@inherits ComponentFoundation + +
    +
    + @if (Content != null && CurrentItem != null) + { + @Content(CurrentItem) + } +
    +
    \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.razor.cs b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.razor.cs new file mode 100644 index 000000000..2ca090705 --- /dev/null +++ b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.razor.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Material.Blazor.MD2.Internal; + +using Microsoft.AspNetCore.Components; + +namespace Material.Blazor.MD2; + +/// +/// A Plus component that take a set of renderfragments in a list and transitions from one to another +/// with slight sideways motion and fade, or "sliding". Only renders the currently displayed item. +/// +/// The content type. +public partial class MBSlidingContent : ComponentFoundation +{ + /// + /// The index of the currently displayed item. + /// + [Parameter] public int ItemIndex { get; set; } + private int _cachedItemIndex; + + + /// + /// The change event callback for . + /// + [Parameter] public EventCallback ItemIndexChanged { get; set; } + + + /// + /// The items to be displayed. + /// + [Parameter] public IEnumerable Items { get; set; } + + + /// + /// Render fragment for each displayable item. + /// + [Parameter] public RenderFragment Content { get; set; } + + + private string ContentClass { get; set; } = ""; + private bool HideContent { get; set; } = false; + private string VisibilityClass => HideContent ? Hidden : Visible; + private TItem CurrentItem { get; set; } + private bool HasRendered { get; set; } = false; + + + internal const string Hidden = "mb-visibility-hidden"; + internal const string Visible = "mb-visibility-visible"; + internal const string InFromLeft = "mb-slide-in-from-left"; + internal const string InFromRight = "mb-slide-in-from-right"; + internal const string OutToLeft = "mb-slide-out-to-left"; + internal const string OutToRight = "mb-slide-out-to-right"; + + private enum SlideDirection { Backwards, Forwards } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + if (Items != null) + { + CurrentItem = Items.ElementAtOrDefault(_cachedItemIndex); + } + + if (_cachedItemIndex != ItemIndex) + { + var direction = (ItemIndex > _cachedItemIndex) ? SlideDirection.Forwards : SlideDirection.Backwards; + EnqueueJSInteropAction(() => SlideToItem(ItemIndex, direction)); + } + } + + + private async Task SlideToItem(int index, SlideDirection direction) + { + if (index != _cachedItemIndex) + { + if (HasRendered) + { + string nextClass; + + if (direction == SlideDirection.Backwards) + { + nextClass = InFromLeft; + ContentClass = OutToRight; + } + else + { + nextClass = InFromRight; + ContentClass = OutToLeft; + } + + await InvokeAsync(StateHasChanged); + await Task.Delay(100); + + HideContent = true; + + ContentClass = nextClass; + CurrentItem = Items.ElementAt(index); + + _cachedItemIndex = index; + ItemIndex = index; + await ItemIndexChanged.InvokeAsync(index); + + HideContent = false; + + await InvokeAsync(StateHasChanged); + } + else + { + await ItemIndexChanged.InvokeAsync(index); + _cachedItemIndex = index; + CurrentItem = Items.ElementAt(index); + ItemIndex = index; + } + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) + { + HasRendered = true; + } + } + + + /// + /// Moves to the next slide, always scrolling forwards. + /// + /// Rolls from last to first if true, scrolling forwards. + public async Task SlideNext(bool rollover) + { + var nextIndex = _cachedItemIndex + 1; + + if (nextIndex == Items.Count()) + { + if (!rollover) + { + return; + } + + nextIndex = 0; + } + + await SlideToItem(nextIndex, SlideDirection.Forwards).ConfigureAwait(false); + } + + + /// + /// Moves to the previous slide, always scrolling backwards. + /// + /// Rolls from first to last if true, scrolling backwards. + public async Task SlidePrevious(bool rollover) + { + var previousIndex = _cachedItemIndex - 1; + + if (previousIndex == -1) + { + if (!rollover) + { + return; + } + + previousIndex = Items.Count() - 1; + } + + await SlideToItem(previousIndex, SlideDirection.Backwards).ConfigureAwait(false); + } + + + /// + /// A back door to set the item index for use by the carousel. Required because the internal carousel sliding panel + /// returns ShouldRender() => false; + /// + /// + internal async Task SetItemIndexAsync(int index) + { + var slideDirection = index < ItemIndex ? SlideDirection.Backwards : SlideDirection.Forwards; + await SlideToItem(index, slideDirection).ConfigureAwait(false); + } +} diff --git a/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.scss b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.scss new file mode 100644 index 000000000..73b3c9b1f --- /dev/null +++ b/Material.Blazor.MD2/Components/SlidingContent/MBSlidingContent.scss @@ -0,0 +1,150 @@ +.mb-hidden { + visibility: hidden; +} + +.mb-visible { + visibility: visible; +} + +.mb-slide-in-from-left { + animation: slideInFromLeft ease 200ms; + -webkit-animation: slideInFromLeft ease 200ms; +} + +@-webkit-keyframes slideInFromLeft { + 0% { + margin-left: -12px; + margin-right: 12px; + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes slideInFromLeft { + 0% { + margin-left: -12px; + margin-right: 12px; + opacity: 0; + } + + 100% { + opacity: 1; + } +} + + +.mb-slide-in-from-right { + animation: slideInFromRight ease 200ms; + -webkit-animation: slideInFromRight ease 200ms; +} + +@-webkit-keyframes slideInFromRight { + 0% { + margin-left: 12px; + margin-right: -12px; + opacity: 0; + } + + 100% { + opacity: 1; + } +} + +@keyframes slideInFromRight { + 0% { + margin-left: 12px; + margin-right: -12px; + opacity: 0; + } + + 100% { + opacity: 1; + } +} + + +.mb-slide-out-to-left { + animation: slideOutToLeft ease-out 10000ms; + -webkit-animation: slideOutToLeft ease-out 10000ms; +} + +@-webkit-keyframes slideOutToLeft { + 0% { + opacity: 1; + } + + 1% { + margin-left: -12px; + margin-right: 12px; + opacity: 0; + } + + 100% { + margin-left: -12px; + margin-right: 12px; + opacity: 0; + } +} + +@keyframes slideOutToLeft { + 0% { + opacity: 1; + } + + 1% { + margin-left: -12px; + margin-right: 12px; + opacity: 0; + } + + 100% { + margin-left: -12px; + margin-right: 12px; + opacity: 0; + } +} + + +.mb-slide-out-to-right { + animation: slideOutToRight ease-out 10000ms; + -webkit-animation: slideOutToRight ease-out 10000ms; +} + +@-webkit-keyframes slideOutToRight { + 0% { + opacity: 1; + } + + 1% { + margin-left: 12px; + margin-right: -12px; + opacity: 0; + } + + 100% { + margin-left: 12px; + margin-right: -12px; + opacity: 0; + } +} + +@keyframes slideOutToRight { + 0% { + opacity: 1; + } + + 1% { + margin-left: 12px; + margin-right: -12px; + opacity: 0; + } + + 100% { + margin-left: 12px; + margin-right: -12px; + opacity: 0; + } +} diff --git a/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.md b/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.md new file mode 100644 index 000000000..6496adc64 --- /dev/null +++ b/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.md @@ -0,0 +1,31 @@ +--- +uid: C.MBSlidingTabBar +title: MBSlidingTabBar +--- +# MBSlidingTabBar<TItem> + +## Summary + +A combination of a simple [MBTabBar](xref:C.MBTabBar) with an [MBSlidingContent](xref:C.MBSlidingContent) to provide an all-in-one tab bar +solution that uses navigation animation cues to inform the user of navigation events. + +## Details + +- Takes input relvant to each of [MBTabBar](xref:C.MBTabBar) and [MBSlidingContent](xref:C.MBSlidingContent) for the tab titles/icons and tab panel contents. + +## Assisting Blazor Rendering with `@key` + +- MBSlidingTabBar renders similar table rows with a `foreach` loop; +- In general each item rendered in a loop in Blazor should be supplied with a unique object via the `@key` attribute - see [Blazor University](https://blazor-university.com/components/render-trees/optimising-using-key/); +- MBSlidingTabBar by default uses each item in the `Items` parameter as the key, however you can override this. Material.Blazor.MD2 does this because we have had instances where Blazor crashes with the default key giving an exception message such as "The given key 'MyObject' was not present"; +- You can provide a function delegate to the `GetKeysFunc` parameter - we have used two variants of this: + - First to get a unique `Id` property that happens to be in our item's class: `GetKeysFunc="@((item) => item.Id)"`; and + - Second using a "fake key" where we create a GUID to act as the key: `GetKeysFunc="@((item) => Guid.NewGuid())"`. + - You can see an example of this in the [MBList demonstration website page's code](https://github.com/Material-Blazor/Material.Blazor.MD2/blob/main/Material.Blazor.MD2.Website/Pages/List.razor#L155). + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBSlidingTabBar&color=brightgreen)](xref:Material.Blazor.MD2.MBSlidingTabBar`1) diff --git a/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.razor b/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.razor new file mode 100644 index 000000000..fdceb52f8 --- /dev/null +++ b/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.razor @@ -0,0 +1,72 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 +@typeparam TItem + +
    + + @if (Icon is null && Label is null) + { + + + + } + else if (Label is null) + { + + + @Icon(item) + + } + else if (Icon is null) + { + + + + + } + else + { + + + + @Icon(item) + + } + + + + + + @Content(item) + + + +
    \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.razor.cs b/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.razor.cs new file mode 100644 index 000000000..a7d2494e4 --- /dev/null +++ b/Material.Blazor.MD2/Components/SlidingTabBar/MBSlidingTabBar.razor.cs @@ -0,0 +1,67 @@ +using Material.Blazor.MD2.Internal; + +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// An with a immediately +/// beneath showing tabbed content. +/// +/// +public partial class MBSlidingTabBar : InputComponent +{ + /// + /// Stack icons vertically if True, otherwise icons are leading. + /// + [Parameter] public bool StackIcons { get; set; } + + + /// + /// A function delegate to return the parameters for @key attributes. If unused + /// "fake" keys set to GUIDs will be used. + /// + [Parameter] public Func GetKeysFunc { get; set; } + + + /// + /// The tab details plus items to be displayed under the tab bar depending upon tab index. + /// + [Parameter] public IEnumerable Items { get; set; } + + + /// + /// Label render fragments. + /// + [Parameter] public RenderFragment Label { get; set; } + + + /// + /// Icon render fragments. + /// + [Parameter] public RenderFragment Icon { get; set; } + + + /// + /// Content render fragments under the tab bar. + /// + [Parameter] public RenderFragment Content { get; set; } + + + /// + /// The tab bar's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ForceShouldRenderToTrue = true; + } +} diff --git a/Material.Blazor.MD2/Components/Snackbar/InternalSnackbar.razor b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbar.razor new file mode 100644 index 000000000..8329f17fe --- /dev/null +++ b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbar.razor @@ -0,0 +1,55 @@ +@namespace Material.Blazor.MD2.Internal +@inherits ComponentFoundation + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Snackbar/InternalSnackbar.razor.cs b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbar.razor.cs new file mode 100644 index 000000000..a34338e3b --- /dev/null +++ b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbar.razor.cs @@ -0,0 +1,62 @@ +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +public partial class InternalSnackbar : ComponentFoundation +{ + /// + /// The unique for this snackbar. + /// + [Parameter] public SnackbarInstance Snackbar { get; set; } + + + private ElementReference SnackbarReference { get; set; } + + private string Stacked => Snackbar.Settings.AppliedStacked ? "mdc-snackbar--stacked" : null; + + private string Leading => Snackbar.Settings.AppliedLeading ? "mdc-snackbar--leading" : null; + + private DotNetObjectReference ObjectReference { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + ObjectReference = DotNetObjectReference.Create(this); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override void Dispose(bool disposing) + { + ObjectReference?.Dispose(); + base.Dispose(disposing); + } + + + /// + /// Called by Material Components Web when a snackbar is closed, setting the Closed parameter in settings + /// and calling any OnClose listeners. + /// + [JSInvokable] + public void Closed() + { + if (Snackbar.Settings.Closed) + { + return; + } + + Snackbar.Settings.Closed = true; + _ = Snackbar.Settings.OnClose?.Invoke(Snackbar); + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBSnackbar.init", SnackbarReference, ObjectReference, Snackbar.Settings.AppliedTimeout); + } +} diff --git a/Material.Blazor.MD2/Components/Snackbar/InternalSnackbarAnchor.razor b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbarAnchor.razor new file mode 100644 index 000000000..c04e876da --- /dev/null +++ b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbarAnchor.razor @@ -0,0 +1,8 @@ +@namespace Material.Blazor.MD2.Internal +@inherits ComponentFoundation + +@if (ActiveSnackbar != null) +{ + +} diff --git a/Material.Blazor.MD2/Components/Snackbar/InternalSnackbarAnchor.razor.cs b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbarAnchor.razor.cs new file mode 100644 index 000000000..0b6cc60d1 --- /dev/null +++ b/Material.Blazor.MD2/Components/Snackbar/InternalSnackbarAnchor.razor.cs @@ -0,0 +1,119 @@ +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2.Internal; + +/// +/// An anchor component that displays snackbar notification that you display via +/// . +/// Place this component at the top of either App.razor or MainLayout.razor. +/// +public partial class InternalSnackbarAnchor : ComponentFoundation +{ + [Inject] private IMBSnackbarService SnackbarService { get; set; } + + + private SnackbarInstance ActiveSnackbar { get; set; } + private readonly ConcurrentQueue pendingSnackbars = new(); + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + SnackbarService.OnAdd += AddSnackbar; + SnackbarService.OnTriggerStateHasChanged += OnTriggerStateHasChanged; + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + + protected override void Dispose(bool disposing) + { + SnackbarService.OnAdd -= AddSnackbar; + SnackbarService.OnTriggerStateHasChanged -= OnTriggerStateHasChanged; + + base.Dispose(disposing); + } + + + /// + /// Adds a snackbar to the anchor, enqueuing it ready for future display if the maximum number of snackbars has been reached. + /// + /// + private void AddSnackbar(MBSnackbarSettings settings) + { + settings.Configuration = SnackbarService.Configuration; + + var snackbarInstance = new SnackbarInstance + { + Id = Guid.NewGuid(), + Settings = settings + }; + + pendingSnackbars.Enqueue(snackbarInstance); + _ = ShowNextSnackbarAsync(); + } + + + private void OnTriggerStateHasChanged() + { + try + { + _ = InvokeAsync(StateHasChanged); + } + catch + { + // It is entirely possible that the renderer has been disposed, so just ignore errors on calling StateHasChanged + } + } + + + private readonly SemaphoreSlim queue_semaphore = new(1, 1); + + + private async Task ShowNextSnackbarAsync() + { + await queue_semaphore.WaitAsync(); + try + { + if (ActiveSnackbar != null) // if there is an active snackbar, we shouldn't try to display the next + { + return; + } + if (!pendingSnackbars.TryDequeue(out var snackbarInstance)) // if there is no next snackbar, don't do anything + { + return; + } + // register the close event, which simply removes this snackbar and goes back here + // then make this instance active and render. + snackbarInstance.Settings.OnClose = RemoveClosedSnackbarAndDisplayNextAsync; + ActiveSnackbar = snackbarInstance; + await InvokeAsync(StateHasChanged); + } + finally + { + queue_semaphore.Release(); + } + } + + private async Task RemoveClosedSnackbarAndDisplayNextAsync(SnackbarInstance instance) + { + await queue_semaphore.WaitAsync(); + + try + { + ActiveSnackbar = null; + instance.Settings.OnClose = null; + } + finally + { + queue_semaphore.Release(); + } + + await ShowNextSnackbarAsync(); + } +} diff --git a/Material.Blazor.MD2/Components/Snackbar/MBSnackbar.scss b/Material.Blazor.MD2/Components/Snackbar/MBSnackbar.scss new file mode 100644 index 000000000..75b14e5ab --- /dev/null +++ b/Material.Blazor.MD2/Components/Snackbar/MBSnackbar.scss @@ -0,0 +1 @@ +@use "@material/snackbar/mdc-snackbar"; \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/Snackbar/MBSnackbar.ts b/Material.Blazor.MD2/Components/Snackbar/MBSnackbar.ts new file mode 100644 index 000000000..aa72cecb6 --- /dev/null +++ b/Material.Blazor.MD2/Components/Snackbar/MBSnackbar.ts @@ -0,0 +1,13 @@ +import { MDCSnackbar } from '@material/snackbar'; + +export function init(elem, dotnetReference, timeoutMs: number) { + if (!elem) { + return; + } + elem._snackbar = new MDCSnackbar(elem); + elem._snackbar.listen('MDCSnackbar:closed', (r) => { + dotnetReference.invokeMethodAsync('Closed', r); + }); + elem._snackbar.timeoutMs = timeoutMs; + elem._snackbar.open(); +} diff --git a/Material.Blazor.MD2/Components/TabBar/MBTabBar.md b/Material.Blazor.MD2/Components/TabBar/MBTabBar.md new file mode 100644 index 000000000..c5957a523 --- /dev/null +++ b/Material.Blazor.MD2/Components/TabBar/MBTabBar.md @@ -0,0 +1,34 @@ +--- +uid: C.MBTabBar +title: MBTabBar +--- +# MBTabBar<TItem> + +## Summary + +A [Material Tab Bar](https://github.com/material-components/material-components-web/tree/v9.0.0/packages/mdc-tab-bar#tab-bar), including scroll function for when the tab bar is wider than the viewport. + +## Details + +- Accepts an IEnumerable of tab labels and (optional) icons; +- Two-way binds the tab index number; +- **Ignores the `Disabled` attribute** +- Takes a boolean to indicate whether icons are stacked vertically above labels or not; and +- Applies [density subsystem](xref:A.Density). + +## Assisting Blazor Rendering with `@key` + +- MBTabBar renders similar table rows with a `foreach` loop; +- In general each item rendered in a loop in Blazor should be supplied with a unique object via the `@key` attribute - see [Blazor University](https://blazor-university.com/components/render-trees/optimising-using-key/); +- MBTabBar by default uses each item in the `Items` parameter as the key, however you can override this. Material.Blazor.MD2 does this because we have had instances where Blazor crashes with the default key giving an exception message such as "The given key 'MyObject' was not present"; +- You can provide a function delegate to the `GetKeysFunc` parameter - we have used two variants of this: + - First to get a unique `Id` property that happens to be in our item's class: `GetKeysFunc="@((item) => item.Id)"`; and + - Second using a "fake key" where we create a GUID to act as the key: `GetKeysFunc="@((item) => Guid.NewGuid())"`. + - You can see an example of this in the [MBList demonstration website page's code](https://github.com/Material-Blazor/Material.Blazor.MD2/blob/main/Material.Blazor.MD2.Website/Pages/List.razor#L155). + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBTabBar&color=brightgreen)](xref:Material.Blazor.MD2.MBTabBar`1) diff --git a/Material.Blazor.MD2/Components/TabBar/MBTabBar.razor b/Material.Blazor.MD2/Components/TabBar/MBTabBar.razor new file mode 100644 index 000000000..bea29b6da --- /dev/null +++ b/Material.Blazor.MD2/Components/TabBar/MBTabBar.razor @@ -0,0 +1,54 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 +@typeparam TItem + + +
    + +
    +
    +
    + @{ int i = 0; } + @foreach (var item in Items) + { + int index = i++; + bool selected = (index == Value); + int tabIndex = selected ? 0 : -1; + string buttonActive = selected ? "mdc-tab--active" : ""; + string indicatorActive = selected ? "mdc-tab-indicator--active" : ""; + + + } +
    +
    +
    +
    \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/TabBar/MBTabBar.razor.cs b/Material.Blazor.MD2/Components/TabBar/MBTabBar.razor.cs new file mode 100644 index 000000000..bd6e4b8dc --- /dev/null +++ b/Material.Blazor.MD2/Components/TabBar/MBTabBar.razor.cs @@ -0,0 +1,135 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// This is a general purpose Material Theme tab bar. +/// +public partial class MBTabBar : InputComponent +{ + /// + /// Stack icons vertically if True, otherwise icons are leading. + /// + [Parameter] public bool StackIcons { get; set; } + + + /// + /// A function delegate to return the parameters for @key attributes. If unused + /// "fake" keys set to GUIDs will be used. + /// + [Parameter] public Func GetKeysFunc { get; set; } + + + /// + /// The list of items for the tab bar. + /// + [Parameter] public IEnumerable Items { get; set; } + + + /// + /// Label render fragments. + /// + [Parameter] public RenderFragment Label { get; set; } + + + /// + /// Icon render fragments requiring correct icon markup including the "mdc-tab__icon" + /// CSS class. Note that Material Icons always render properly, while some wider Font Awesome + /// icons for instance render too close to the tab text. + /// + [Parameter] public RenderFragment Icon { get; set; } + + + /// + /// The tab bar's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + private ElementReference ElementReference { get; set; } + private DotNetObjectReference> ObjectReference { get; set; } + private Func KeyGenerator { get; set; } + private string StackClass => StackIcons ? "mdc-tab--stacked" : ""; + + private MBCascadingDefaults.DensityInfo DensityInfo + { + get + { + var d = CascadingDefaults.GetDensityCssClass(CascadingDefaults.AppliedTabBarDensity(Density)); + + d.CssClassName += StackIcons ? "--stacked" : "--unstacked"; + + return d; + } + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + ObjectReference = DotNetObjectReference.Create(this); + + ConditionalCssClasses + .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass); + } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync(); + + KeyGenerator = GetKeysFunc ?? delegate (TItem item) { return item; }; + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + /// + /// For Material Theme to notify when a tab is clicked via JS Interop. + /// + /// + [JSInvokable] + public void NotifyActivated(int index) + { + ComponentValue = index; + } + + + /// + private protected override Task SetComponentValueAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBTabBar.activateTab", ElementReference, Value); + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBTabBar.init", ElementReference, ObjectReference); + } +} diff --git a/Material.Blazor.MD2/Components/TabBar/MBTabBar.scss b/Material.Blazor.MD2/Components/TabBar/MBTabBar.scss new file mode 100644 index 000000000..c02e1dd60 --- /dev/null +++ b/Material.Blazor.MD2/Components/TabBar/MBTabBar.scss @@ -0,0 +1,54 @@ +@use "@material/tab-bar"; + + +.mdc-tab-bar { + &.dense--5--unstacked, + &.dense--4--unstacked { + @include tab-bar.density(-4); + } + + &.dense--3--unstacked, + &.dense-compact--unstacked { + @include tab-bar.density(-3); + } + + &.dense--2--unstacked, + &.dense-comfortable--unstacked { + @include tab-bar.density(-2); + } + + &.dense--1--unstacked { + @include tab-bar.density(-1); + } + + &.dense--0--unstacked, + &.dense-default--unstacked { + @include tab-bar.density(0); + } +} + +.mdc-tab-bar { + &.dense--5--stacked, + &.dense--4--stacked { + @include tab-bar.stacked-density(-4); + } + + &.dense--3--stacked, + &.dense-compact--stacked { + @include tab-bar.stacked-density(-3); + } + + &.dense--2--stacked, + &.dense-comfortable--stacked { + @include tab-bar.stacked-density(-2); + } + + &.dense--1--stacked { + @include tab-bar.stacked-density(-1); + } + + &.dense--0--stacked, + &.dense-default--stacked { + @include tab-bar.stacked-density(0); + } +} diff --git a/Material.Blazor.MD2/Components/TabBar/MBTabBar.ts b/Material.Blazor.MD2/Components/TabBar/MBTabBar.ts new file mode 100644 index 000000000..95ffd4a72 --- /dev/null +++ b/Material.Blazor.MD2/Components/TabBar/MBTabBar.ts @@ -0,0 +1,24 @@ +import { MDCTabBar } from '@material/tab-bar'; + +export function init(elem, dotNetObject) { + if (!elem) { + return; + } + elem._tabBar = MDCTabBar.attachTo(elem); + + elem._callback = () => { + let index = elem._tabBar.foundation.adapter.getFocusedTabIndex(); + dotNetObject.invokeMethodAsync('NotifyActivated', index); + }; + + elem._tabBar.listen('MDCTabBar:activated', elem._callback); +} + +export function activateTab(elem, index) { + if (!elem) { + return; + } + elem._tabBar.unlisten('MDCTabBar:activated', elem._callback); + elem._tabBar.activateTab(index); + elem._tabBar.listen('MDCTabBar:activated', elem._callback); +} diff --git a/Material.Blazor.MD2/Components/TextArea/MBTextArea.md b/Material.Blazor.MD2/Components/TextArea/MBTextArea.md new file mode 100644 index 000000000..ac90d0af1 --- /dev/null +++ b/Material.Blazor.MD2/Components/TextArea/MBTextArea.md @@ -0,0 +1,23 @@ +--- +uid: C.MBTextArea +title: MBTextArea +--- +# MBTextArea + +## Summary + +A [Material Text Field](https://github.com/material-components/material-components-web/tree/v9.0.0/packages/mdc-textfield#text-field) expressed as a text area. + +## Details + +- Similar to [MBTextField](xref:C.MBTextField), bit lacking prefix, suffix or leading and trailing icons; +- Can set the initial number of rows and columns to display; and +- Applies [density subsystem](xref:A.Density). +- Renders Blazor validation messages in Material Theme's style. see the [Form Validation Article](xref:A.FormValidation). + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBTextArea&color=brightgreen)](xref:Material.Blazor.MD2.MBTextArea) diff --git a/Material.Blazor.MD2/Components/TextArea/MBTextArea.razor b/Material.Blazor.MD2/Components/TextArea/MBTextArea.razor new file mode 100644 index 000000000..7af3a9f3a --- /dev/null +++ b/Material.Blazor.MD2/Components/TextArea/MBTextArea.razor @@ -0,0 +1,64 @@ +@namespace Material.Blazor.MD2 + +@inherits InputComponentMD2 + + + + +@if (HasHelperText) +{ +
    + +
    +} \ No newline at end of file diff --git a/Material.Blazor.MD2/Components/TextArea/MBTextArea.razor.cs b/Material.Blazor.MD2/Components/TextArea/MBTextArea.razor.cs new file mode 100644 index 000000000..62debe976 --- /dev/null +++ b/Material.Blazor.MD2/Components/TextArea/MBTextArea.razor.cs @@ -0,0 +1,161 @@ +using Material.Blazor.MD2.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Forms; +using System; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme text field. +/// +public partial class MBTextArea : InputComponent +{ + /// + /// Helper text that is displayed either with focus or persistently with . + /// + [Parameter] public string HelperText { get; set; } = ""; + + + /// + /// Makes the persistent if true. + /// + [Parameter] public bool HelperTextPersistent { get; set; } = false; + + + /// + /// Delivers Material Theme validation methods from native Blazor validation. Either use this or + /// the Blazor ValidationMessage component, but not both. This parameter takes the same input as + /// ValidationMessage's For parameter. + /// + [Parameter] public Expression> ValidationMessageFor { get; set; } + + + /// + /// The text input style. + /// Overrides + /// + [Parameter] public MBTextInputStyle? TextInputStyle { get; set; } + + + /// + /// The text alignment style. + /// Overrides + /// + [Parameter] public MBTextAlignStyle? TextAlignStyle { get; set; } + + + /// + /// Field label. + /// + [Parameter] public string Label { get; set; } = ""; + + + /// + /// The number of rows to show when first rendered. + /// + [Parameter] public int Rows { get; set; } + + + /// + /// The number of columns to show when first rendered. + /// + [Parameter] public int Cols { get; set; } + + + /// + /// The text area's density. + /// + [Parameter] public MBDensity? Density { get; set; } + + + private MBDensity AppliedDensity => CascadingDefaults.AppliedTextFieldDensity(Density); + private MBTextInputStyle AppliedInputStyle => CascadingDefaults.AppliedStyle(TextInputStyle); + private string AppliedTextInputStyleClass => Utilities.GetTextAlignClass(CascadingDefaults.AppliedStyle(TextAlignStyle)); + private string DisplayLabel => Label + LabelSuffix; + private ElementReference ElementReference { get; set; } + private string FloatingLabelClass { get; set; } + private ElementReference HelperTextReference { get; set; } + private bool HasHelperText => !string.IsNullOrWhiteSpace(HelperText) || PerformsValidation; + private string LabelSuffix { get; set; } = ""; + private bool PerformsValidation => EditContext != null && ValidationMessageFor != null; + + + private readonly string labelId = Utilities.GenerateUniqueElementName(); + private readonly string helperTextId = Utilities.GenerateUniqueElementName(); + + + private MBCascadingDefaults.DensityInfo DensityInfo + { + get + { + var d = CascadingDefaults.GetDensityCssClass(AppliedDensity); + + d.CssClassName += AppliedInputStyle == MBTextInputStyle.Filled ? "--ta--filled" : "--ta--outlined"; + + return d; + } + } + + private bool ShowLabel => !string.IsNullOrWhiteSpace(Label); + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + _ = ConditionalCssClasses + .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass) + .AddIf(FieldClass, () => !string.IsNullOrWhiteSpace(FieldClass)) + .AddIf("mdc-text-field--filled", () => AppliedInputStyle == MBTextInputStyle.Filled) + .AddIf("mdc-text-field--outlined", () => AppliedInputStyle == MBTextInputStyle.Outlined) + .AddIf("mdc-text-field--no-label", () => !ShowLabel) + .AddIf("mdc-text-field--disabled", () => AppliedDisabled); + + FloatingLabelClass = string.IsNullOrEmpty(ComponentValue) ? "" : "mdc-floating-label--float-above"; + + if (EditContext != null) + { + EditContext.OnValidationStateChanged += OnValidationStateChangedCallback; + + if (HasRequiredAttribute(ValidationMessageFor)) + { + LabelSuffix = " *"; + } + } + } + + + /// + private protected override Task SetComponentValueAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBTextField.setValue", ElementReference, Value); + } + + + /// + private protected override Task OnDisabledSetAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBTextField.setDisabled", ElementReference, AppliedDisabled); + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBTextField.init", ElementReference, Value ?? "", HelperTextReference, HelperText.Trim(), HelperTextPersistent, PerformsValidation); + } + + private void OnValidationStateChangedCallback(object sender, EventArgs e) + { + if (ValidationMessageFor != null) + { + var fieldIdentifier = FieldIdentifier.Create(ValidationMessageFor); + var validationMessage = string.Join("
    ", EditContext.GetValidationMessages(fieldIdentifier)); + + InvokeAsync(() => InvokeJsVoidAsync("MaterialBlazor.MBTextField.setHelperText", ElementReference, HelperTextReference, HelperText.Trim(), HelperTextPersistent, PerformsValidation, !string.IsNullOrEmpty(Value), validationMessage)); + } + } +} diff --git a/Material.Blazor.MD3/Components.MD2/Tooltip/InternalTooltipAnchor.razor b/Material.Blazor.MD2/Components/Tooltip/InternalTooltipAnchor.razor similarity index 92% rename from Material.Blazor.MD3/Components.MD2/Tooltip/InternalTooltipAnchor.razor rename to Material.Blazor.MD2/Components/Tooltip/InternalTooltipAnchor.razor index 93449dc43..6c051d054 100644 --- a/Material.Blazor.MD3/Components.MD2/Tooltip/InternalTooltipAnchor.razor +++ b/Material.Blazor.MD2/Components/Tooltip/InternalTooltipAnchor.razor @@ -1,5 +1,5 @@ -@namespace Material.Blazor.Internal.MD2 -@inherits ComponentFoundationMD2 +@namespace Material.Blazor.MD2.Internal +@inherits ComponentFoundation @{ OnBeforeRender(); diff --git a/Material.Blazor.MD3/Components.MD2/Tooltip/InternalTooltipAnchor.razor.cs b/Material.Blazor.MD2/Components/Tooltip/InternalTooltipAnchor.razor.cs similarity index 96% rename from Material.Blazor.MD3/Components.MD2/Tooltip/InternalTooltipAnchor.razor.cs rename to Material.Blazor.MD2/Components/Tooltip/InternalTooltipAnchor.razor.cs index d23291814..81eb4a451 100644 --- a/Material.Blazor.MD3/Components.MD2/Tooltip/InternalTooltipAnchor.razor.cs +++ b/Material.Blazor.MD2/Components/Tooltip/InternalTooltipAnchor.razor.cs @@ -5,21 +5,21 @@ using System.Linq; using System.Threading.Tasks; -namespace Material.Blazor.Internal.MD2; +namespace Material.Blazor.MD2.Internal; /// /// An anchor component that displays tooltips that you add, and using /// . /// Place this component at the top of either App.razor or MainLayout.razor. /// -public partial class InternalTooltipAnchor : ComponentFoundationMD2 +public partial class InternalTooltipAnchor : ComponentFoundation { private ConcurrentQueue> NewTooltips { get; } = new(); private ConcurrentQueue OldTooltips { get; } = new(); private Dictionary Tooltips { get; } = new(); - // Would like to use however DocFX cannot resolve to references outside Material.Blazor + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); @@ -81,7 +81,7 @@ internal void RemoveTooltip(long id) } - // Would like to use however DocFX cannot resolve to references outside Material.Blazor + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 protected override async Task OnAfterRenderAsync(bool firstRender) { var refs = (from tooltip in Tooltips.Values diff --git a/Material.Blazor.MD2/Components/Tooltip/MBTooltip.md b/Material.Blazor.MD2/Components/Tooltip/MBTooltip.md new file mode 100644 index 000000000..8ec810324 --- /dev/null +++ b/Material.Blazor.MD2/Components/Tooltip/MBTooltip.md @@ -0,0 +1,22 @@ +--- +uid: C.MBTooltip +title: MBTooltip +--- +# MBTooltip + +## Summary + +A [Material Tooltip](https://github.com/material-components/material-components-web/tree/master/packages/mdc-tooltip#tooltip). + +## Details + +- Takes a render fragment for a tooltip target and another for the tooltip content; +- Requires service registration and use of [MBAnchor](xref:C.MBAnchor). +- All Material.Blazor.MD2 components give you a shortcut to use text tooltips (styled as a MarkupString) with the [Tooltip](xref:Material.Blazor.MD2.Internal.ComponentFoundation.Tooltip) parameter. + +  + +  + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBTooltip&color=brightgreen)](xref:Material.Blazor.MD2.MBTooltip) diff --git a/Material.Blazor.MD3/Components.MD2/Tooltip/MBTooltip.razor b/Material.Blazor.MD2/Components/Tooltip/MBTooltip.razor similarity index 87% rename from Material.Blazor.MD3/Components.MD2/Tooltip/MBTooltip.razor rename to Material.Blazor.MD2/Components/Tooltip/MBTooltip.razor index a30d740d4..1bbf0dc44 100644 --- a/Material.Blazor.MD3/Components.MD2/Tooltip/MBTooltip.razor +++ b/Material.Blazor.MD2/Components/Tooltip/MBTooltip.razor @@ -1,6 +1,6 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 +@inherits ComponentFoundation /// The render fragment requiring the tooltip. @@ -26,7 +26,7 @@ public partial class MBTooltip : ComponentFoundationMD2, IDisposable - // Would like to use however DocFX cannot resolve to references outside Material.Blazor + // Would like to use however DocFX cannot resolve to references outside Material.Blazor.MD2 protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); diff --git a/Material.Blazor.MD3/Components.MD2/Tooltip/MBTooltip.ts b/Material.Blazor.MD2/Components/Tooltip/MBTooltip.ts similarity index 100% rename from Material.Blazor.MD3/Components.MD2/Tooltip/MBTooltip.ts rename to Material.Blazor.MD2/Components/Tooltip/MBTooltip.ts diff --git a/Material.Blazor.MD2/Material - Backup.Blazor.MD2.csproj b/Material.Blazor.MD2/Material - Backup.Blazor.MD2.csproj new file mode 100644 index 000000000..9c4195a03 --- /dev/null +++ b/Material.Blazor.MD2/Material - Backup.Blazor.MD2.csproj @@ -0,0 +1,79 @@ + + + + false + 5.0.0-preview.WIP + + Simon Ziegler of Dioptra and Mark Stega of Optimium Health + Material.Blazor.MD2 + A lightweight Material Theme component library for Blazor. Intended to adhere rigorously to Material Theme guidelines in https://material.io. API and other documentation can be found at https://material-blazor.com/docs. + https://github.com/Material-Blazor/Material.Blazor.MD2 + MIT + README.md + + en + Dioptra and Optimium Health + https://material-blazor.com + Material.Blazor.MD2 + d7509b6c-bc5c-4a56-a750-09c8ae4aeb85 + + + + true + + + + true + + + + true + false + + + + + + + + + + + + 5 + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Material.Blazor.MD2/Material.Blazor.MD2.csproj b/Material.Blazor.MD2/Material.Blazor.MD2.csproj new file mode 100644 index 000000000..81744eb47 --- /dev/null +++ b/Material.Blazor.MD2/Material.Blazor.MD2.csproj @@ -0,0 +1,24 @@ + + + + 5.0.0-preview.WIP + + + + true + false + + + + disable + + + + + + + + 5 + + + \ No newline at end of file diff --git a/Material.Blazor.MD3/Articles/Icon.md b/Material.Blazor.MD3/Articles/Icon.md index 9871b582d..3123426f9 100644 --- a/Material.Blazor.MD3/Articles/Icon.md +++ b/Material.Blazor.MD3/Articles/Icon.md @@ -4,30 +4,16 @@ title: Icon --- # Icon -## Caveats - -This is the page from MD2 and is subject to change - ## Summary -Material.Blazor allows you to freely choose among the Material Icons, Font Awesome version 5 and Open Iconic version 1.1 icon foundries. These three foundries -each use different HTML markup, and this difference is handled by the general purpose [MBIcon (see API documentation)](xref:Material.Blazor.MBIcon) component. - -Material Icons are essential for Material Theme and so our css includes this font for you. You will need to include links -for Font Awesome and Open Iconic separately if you want to use them - see the [Installation Article](xref:A.Installation#package-versions) for links. +Material.Blazor allows you to style Material Icons. These icons are loaded +from the Material Symbols font.They support variations of style, size, weight, fill, and color. ## Using MBIcon -MBIcon's two parameters are IconName (required) and IconFoundry (optional). Examples of icon name are "alarm" for Material -Icons, "fa-bell" for Font Awesome and "audio" for Open Iconic. If you don't provide IconFoundry your default foundry will be used. The default -is either Material Icons or a [default that you set with cascading defaults](xref:A.CascadingDefaults#icons). - -## Setting the Icon Foundry - -Material.Blazor provides three static convenience methods to supply an icon foundry to MBIcon: `IconFoundry="IconHelper.MIIcon()"`, `IconFoundry="IconHelper.FAIcon()"`, `IconFoundry="IconHelper.OIIcon()"`. -There are varying parameters for these helper methods described in the [Icon Helper documentation](xref:Material.Blazor.MBIconHelper). +MBIcon's parameters are +- IconName (required). Examples of icon name are "alarm" and "error". ## Other Components -Many components such as MBButton, MBSelect and MBTextField take icons. In every instance these components take IconName -and an optional IconFoundry parameters which are applied in the same way as for MBIcon. In fact they all use MBIcon. \ No newline at end of file +Many components such as MBIconButton, MBSelect, and MBTextField take icons. In every instance these components use IconName and have the other Icon parameters as options. ?Do they use MBIcon? diff --git a/Material.Blazor.MD3/Articles/Installation.md b/Material.Blazor.MD3/Articles/Installation.md index 9283b6211..4767faf7e 100644 --- a/Material.Blazor.MD3/Articles/Installation.md +++ b/Material.Blazor.MD3/Articles/Installation.md @@ -39,19 +39,10 @@ All styling really should be done with Material but the application created with Material.Blazor works with the following package versions: - [Material Components v14.0.0](https://github.com/material-components/material-components-web/blob/master/CHANGELOG.md#1200-2021-07-27) -- [Font Awesome Icons version 6.1.1](https://fontawesome.com/changelog/latest) are optional and can be included in your HTML `` with the CDN link: - ```html - - ``` -- [Open Iconic Icons version 1.1](https://useiconic.com/open) are also optional and can be included in your HTML `` with the CDN link: - ```html - - ``` ## Services and Anchor -Material.Blazor has three services for logging, snackbars, and toasts. We strongly advise you to use these in your project -because regular component tooltips will fail if you don't, although they are optional. To register the services: +Material.Blazor has three services for logging, snackbars, and toasts. We strongly advise you to use these in your project. ```csharp services.AddMBServices(); diff --git a/Material.Blazor.MD3/Articles/PlusComponents.md b/Material.Blazor.MD3/Articles/PlusComponents.md index ea40560f1..1b0ead247 100644 --- a/Material.Blazor.MD3/Articles/PlusComponents.md +++ b/Material.Blazor.MD3/Articles/PlusComponents.md @@ -11,6 +11,7 @@ implements further Blazor/Material Theme hybrid components that we term "Plus Co | Component | Notes | | :-------- | :---- | +| [MBIcon](xref:C.MBIcon) | A Material Symbol | ## Experimental Component List diff --git a/Material.Blazor.MD3/Articles/Services.md b/Material.Blazor.MD3/Articles/Services.md index 61fc99a22..569dc301b 100644 --- a/Material.Blazor.MD3/Articles/Services.md +++ b/Material.Blazor.MD3/Articles/Services.md @@ -4,10 +4,11 @@ title: Services --- # Services -Material.Blazor has services for snackbar, toast notifications, and tooltips. These services and their functionality are optional. +Material.Blazor has services for snackbar and toast notifications. These services and their functionality are optional. ## Services List | Service | Notes | | :------ | :---- | +| [IMBSnackbarService](xref:S.IMBSnackbarService) | Manages snackbar notification. Requires an [MBAnchor](xref:C.MBAnchor) component and will throw an exception when you attempt to show a snackbar if this isn't found. | | [IMBToastService](xref:S.IMBToastService) | Manages toast notification. Requires an [MBAnchor](xref:C.MBAnchor) component and will throw an exception when you attempt to show a toast notification if this isn't found. | diff --git a/Material.Blazor.MD3/Components.MD2/Button/MBButton.razor b/Material.Blazor.MD3/Components.MD2/Button/MBButton.razor deleted file mode 100644 index 020f1cd9a..000000000 --- a/Material.Blazor.MD3/Components.MD2/Button/MBButton.razor +++ /dev/null @@ -1,43 +0,0 @@ -@namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 - - - - diff --git a/Material.Blazor.MD3/Components.MD2/Button/MBButton.razor.cs b/Material.Blazor.MD3/Components.MD2/Button/MBButton.razor.cs deleted file mode 100644 index 1aa1ce191..000000000 --- a/Material.Blazor.MD3/Components.MD2/Button/MBButton.razor.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Material.Blazor.Internal.MD2; -using Microsoft.AspNetCore.Components; - -using System.Threading.Tasks; - -namespace Material.Blazor.MD2; - -/// -/// This is a general purpose Material Theme button, with provision for standard MB styling, leading -/// and trailing icons and all standard Blazor events. Adds the "mdc-card__action--button" class when -/// placed inside an . -/// -public partial class MBButton : ComponentFoundationMD2 -{ - [CascadingParameter] private MBCard Card { get; set; } - - [CascadingParameter] private MBDialog Dialog { get; set; } - - -#nullable enable annotations - /// - /// The button's Material Theme style - see . - /// Overrides , or as relevant. - /// - [Parameter] public MBButtonStyle ButtonStyle { get; set; } = MBButtonStyle.Outlined; - - - /// - /// The button's density. - /// - [Parameter] public MBDensity? Density { get; set; } - - - /// - /// The button's label. - /// - [Parameter] public string Label { get; set; } - - - /// - /// The leading icon's name. No leading icon shown if not set. - /// - [Parameter] public string? LeadingIcon { get; set; } - - - /// - /// The trailing icon's name. No leading icon shown if not set. - /// - [Parameter] public string? TrailingIcon { get; set; } - - - /// - /// Inclusion of touch target - /// - [Parameter] public bool? TouchTarget { get; set; } - - - /// - /// The foundry to use for both leading and trailing icons. - /// IconFoundry="IconHelper.MIIcon()" - /// IconFoundry="IconHelper.FAIcon()" - /// IconFoundry="IconHelper.OIIcon()" - /// Overrides - /// - [Parameter] public IMBIconFoundry? IconFoundry { get; set; } - - - /// - /// A string value to return from an when this button is pressed. - /// - [Parameter] public string DialogAction { get; set; } - - - /// - /// Determines whether the button has a badge - defaults to false. - /// - [Parameter] public bool HasBadge { get; set; } - - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; - - - /// - /// The button's density. - /// - [Parameter] public string BadgeValue { get; set; } - - - /// - /// When true collapses the badge. - /// - [Parameter] public bool BadgeExited { get; set; } - -#nullable restore annotations - - private ElementReference ElementReference { get; set; } - - - // Would like to use however DocFX cannot resolve to references outside Material.Blazor - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - ConditionalCssClasses - .AddIf("mdc-button--raised", () => ButtonStyle == MBButtonStyle.ContainedRaised) - .AddIf("mdc-button--unelevated", () => ButtonStyle == MBButtonStyle.ContainedUnelevated) - .AddIf("mdc-button--outlined", () => ButtonStyle == MBButtonStyle.Outlined) - .AddIf("mdc-button--icon-leading", () => !string.IsNullOrWhiteSpace(LeadingIcon)) - .AddIf("mdc-button--icon-trailing", () => !string.IsNullOrWhiteSpace(TrailingIcon)) - .AddIf("mdc-card__action mdc-card__action--button", () => Card != null); - } - - - /// - internal override Task InstantiateMcwComponent() - { - return InvokeJsVoidAsync("MaterialBlazor.MBButton.init", ElementReference); - } -} diff --git a/Material.Blazor.MD3/Components.MD2/Button/MBButton.scss b/Material.Blazor.MD3/Components.MD2/Button/MBButton.scss deleted file mode 100644 index f5d50587b..000000000 --- a/Material.Blazor.MD3/Components.MD2/Button/MBButton.scss +++ /dev/null @@ -1,24 +0,0 @@ -@use "@material/button"; - -.mdc-button { - &.dense--5, - &.dense--4, - &.dense--3, - &.dense-compact { - @include button.density(-3); - } - - &.dense--2, - &.dense-comfortable { - @include button.density(-2); - } - - &.dense--1 { - @include button.density(-1); - } - - &.dense--0, - &.dense-default { - @include button.density(0); - } -} diff --git a/Material.Blazor.MD3/Components.MD2/Button/MBButton.ts b/Material.Blazor.MD3/Components.MD2/Button/MBButton.ts deleted file mode 100644 index bebad2862..000000000 --- a/Material.Blazor.MD3/Components.MD2/Button/MBButton.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { MDCRipple } from '@material/ripple'; - -export function init(elem): void { - if (!elem) { - return; - } - elem._ripple = MDCRipple.attachTo(elem); -} diff --git a/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor b/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor index 8eec0d015..cb38a0eca 100644 --- a/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor +++ b/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor @@ -1,6 +1,6 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 -@using Material.Blazor.Internal.MD2 +@inherits ComponentFoundation +@using Material.Blazor.Internal diff --git a/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor.cs b/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor.cs index 5f0b9c53b..1983a553a 100644 --- a/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor.cs +++ b/Material.Blazor.MD3/Components.MD2/Card/MBCard.razor.cs @@ -1,4 +1,4 @@ -using Material.Blazor.Internal.MD2; +using Material.Blazor.Internal; using Microsoft.AspNetCore.Components; using System.Threading.Tasks; @@ -7,13 +7,13 @@ namespace Material.Blazor.MD2; /// /// A Material Theme card with three elements: primary, primary action buttons and action icons. /// -public partial class MBCard : ComponentFoundationMD2 +public partial class MBCard : ComponentFoundation { /// - /// The card style - see + /// The card style - see /// Overrides /// - [Parameter] public MBCardStyle? CardStyle { get; set; } + [Parameter] public MBCardStyleMD2? CardStyle { get; set; } /// diff --git a/Material.Blazor.MD3/Components.MD2/DataTable/MBDataTable.razor b/Material.Blazor.MD3/Components.MD2/DataTable/MBDataTable.razor index ae37ead41..f324bf88e 100644 --- a/Material.Blazor.MD3/Components.MD2/DataTable/MBDataTable.razor +++ b/Material.Blazor.MD3/Components.MD2/DataTable/MBDataTable.razor @@ -1,5 +1,5 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 +@inherits ComponentFoundation @typeparam TItem
    /// This is a general purpose Material Theme data table. ///
    -public partial class MBDataTable : ComponentFoundationMD2 +public partial class MBDataTable : ComponentFoundation { /// /// A function delegate to return the parameters for @key attributes. If unused diff --git a/Material.Blazor.MD3/Components.MD2/Dialog/MBDialog.razor b/Material.Blazor.MD3/Components.MD2/Dialog/MBDialog.razor index 392995905..2df3a26d9 100644 --- a/Material.Blazor.MD3/Components.MD2/Dialog/MBDialog.razor +++ b/Material.Blazor.MD3/Components.MD2/Dialog/MBDialog.razor @@ -1,6 +1,6 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 -@using Material.Blazor.Internal.MD2 +@inherits ComponentFoundation +@using Material.Blazor.Internal
    /// This is a general purpose Material Theme dialog. ///
    -public partial class MBDialog : ComponentFoundationMD2, IMBDialog +public partial class MBDialog : ComponentFoundation, IMBDialog { /// /// The dialog title. @@ -66,7 +66,7 @@ public partial class MBDialog : ComponentFoundationMD2, IMBDialog private bool HasButtons => Buttons != null; private bool HasCustomHeader => CustomHeader != null; private bool HasTitle => !string.IsNullOrWhiteSpace(Title); - private List LayoutChildren { get; set; } = new List(); + private List LayoutChildren { get; set; } = new List(); private DotNetObjectReference ObjectReference { get; set; } private string OverflowClass => OverflowVisible ? "mb-dialog-overflow-visible" : ""; @@ -118,7 +118,7 @@ protected override void Dispose(bool disposing) /// - void IMBDialog.RegisterLayoutAction(ComponentFoundationMD2 child) + void IMBDialog.RegisterLayoutAction(ComponentFoundation child) { LayoutChildren.Add(child); } diff --git a/Material.Blazor.MD3/Components.MD2/Drawer/MBDrawer.razor b/Material.Blazor.MD3/Components.MD2/Drawer/MBDrawer.razor index 0ff25a99b..e0c22b567 100644 --- a/Material.Blazor.MD3/Components.MD2/Drawer/MBDrawer.razor +++ b/Material.Blazor.MD3/Components.MD2/Drawer/MBDrawer.razor @@ -1,5 +1,5 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 +@inherits ComponentFoundation -public partial class MBDrawer : ComponentFoundationMD2 +public partial class MBDrawer : ComponentFoundation { /// /// The drawer contents. diff --git a/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor b/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor index 2bfa41af1..758e499fb 100644 --- a/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor +++ b/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor @@ -1,5 +1,5 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 +@inherits ComponentFoundation @@ -11,15 +11,10 @@ @attributes="@AttributesToSplat()">
    - - @if (HasBadge) - { - - - - } + + -
    \ No newline at end of file + diff --git a/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor.cs b/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor.cs index 36b8b473c..ada7ae36f 100644 --- a/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor.cs +++ b/Material.Blazor.MD3/Components.MD2/IconButton/MBIconButton.razor.cs @@ -1,4 +1,4 @@ -using Material.Blazor.Internal.MD2; +using Material.Blazor.Internal; using Microsoft.AspNetCore.Components; using System.Threading.Tasks; @@ -9,65 +9,21 @@ namespace Material.Blazor.MD2; /// and trailing icons and all standard Blazor events. Adds the "mdc-card__action--icon" class when /// placed inside an . ///
    -public partial class MBIconButton : ComponentFoundationMD2 +public partial class MBIconButton : ComponentFoundation { [CascadingParameter] private MBCard Card { get; set; } - - /// - /// Inclusion of touch target - /// [Parameter] public bool? TouchTarget { get; set; } #nullable enable annotations - /// - /// The icon's name. - /// - [Parameter] public string Icon { get; set; } - - - /// - /// The foundry to use for both leading and trailing icons. - /// IconFoundry="IconHelper.MIIcon()" - /// IconFoundry="IconHelper.FAIcon()" - /// IconFoundry="IconHelper.OIIcon()" - /// Overrides - /// - [Parameter] public IMBIconFoundry? IconFoundry { get; set; } -#nullable restore annotations + [Parameter] public string IconColor { get; set; } = "white"; + [Parameter] public string IconName { get; set; } - /// - /// The button's density. - /// [Parameter] public MBDensity? Density { get; set; } - /// - /// Determines whether the button has a badge - defaults to false. - /// - [Parameter] public bool HasBadge { get; set; } - - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; - - - /// - /// The button's density. - /// - [Parameter] public string BadgeValue { get; set; } - - - /// - /// When true collapses the badge. - /// - [Parameter] public bool BadgeExited { get; set; } - - private bool AppliedTouchTarget => false; private ElementReference ElementReference { get; set; } diff --git a/Material.Blazor.MD3/Components.MD2/ListDivider/MBListDivider.razor b/Material.Blazor.MD3/Components.MD2/ListDivider/MBListDivider.razor new file mode 100644 index 000000000..e234ac6f1 --- /dev/null +++ b/Material.Blazor.MD3/Components.MD2/ListDivider/MBListDivider.razor @@ -0,0 +1,6 @@ +@namespace Material.Blazor.MD2 +@inherits ComponentFoundation + +
    } \ No newline at end of file diff --git a/Material.Blazor.MD3/Components.MD2/ListItem/MBListItem.razor.cs b/Material.Blazor.MD3/Components.MD2/ListItem/MBListItem.razor.cs index 0eddfa049..c9e291f41 100644 --- a/Material.Blazor.MD3/Components.MD2/ListItem/MBListItem.razor.cs +++ b/Material.Blazor.MD3/Components.MD2/ListItem/MBListItem.razor.cs @@ -1,48 +1,25 @@ -using Material.Blazor.Internal.MD2; +using Material.Blazor.Internal; using Microsoft.AspNetCore.Components; using System; using System.Threading.Tasks; namespace Material.Blazor.MD2; -/// -/// This is a general purpose Material Theme list item. -/// -public partial class MBListItem : ComponentFoundationMD2 +public partial class MBListItem : ComponentFoundation { [CascadingParameter] private MBDrawer Drawer { get; set; } [CascadingParameter] private MBMenu Menu { get; set; } - -#nullable enable annotations - /// - /// The list item's label - /// [Parameter] public string Label { get; set; } + [Parameter] public string LeadingIcon { get; set; } - /// - /// The leading icon's name. No leading icon shown if not set. - /// - [Parameter] public string? LeadingIcon { get; set; } - + [Parameter] public string ListItemColor { get; set; } = "Black"; - /// - /// Determined whether the list item is in an menu and is in the selected state - /// [Parameter] public bool IsSelectedMenuItem { get; set; } = true; - /// - /// The foundry to use for both leading and trailing icons. - /// IconFoundry="IconHelper.MIIcon()" - /// IconFoundry="IconHelper.FAIcon()" - /// IconFoundry="IconHelper.OIIcon()" - /// Overrides - /// Overrides - /// - [Parameter] public IMBIconFoundry? IconFoundry { get; set; } -#nullable restore annotations + private string styleColor { get; set; } // Would like to use however DocFX cannot resolve to references outside Material.Blazor @@ -62,5 +39,7 @@ protected override async Task OnInitializedAsync() ConditionalCssClasses .AddIf("mdc-menu-item--selected", () => Menu != null && IsSelectedMenuItem) .AddIf("mdc-deprecated-list-item--disabled mb-list-item--disabled", () => AppliedDisabled); + + styleColor = "color: " + ListItemColor + "; "; } } diff --git a/Material.Blazor.MD3/Components.MD2/Menu/MBMenu.razor b/Material.Blazor.MD3/Components.MD2/Menu/MBMenu.razor index 3f716d419..f3ce96cc7 100644 --- a/Material.Blazor.MD3/Components.MD2/Menu/MBMenu.razor +++ b/Material.Blazor.MD3/Components.MD2/Menu/MBMenu.razor @@ -1,5 +1,5 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 +@inherits ComponentFoundation
    /// This is a general purpose Material Theme menu. /// -public partial class MBMenu : ComponentFoundationMD2 +public partial class MBMenu : ComponentFoundation { /// - /// A render fragement as a set of s. + /// A render fragment as a set of s. /// [Parameter] public RenderFragment ChildContent { get; set; } @@ -21,7 +21,7 @@ public partial class MBMenu : ComponentFoundationMD2 /// /// Regular, fullwidth or fixed positioning/width. /// - [Parameter] public MBMenuSurfacePositioning MenuSurfacePositioning { get; set; } = MBMenuSurfacePositioning.Regular; + [Parameter] public MBMenuSurfacePositioningMD2 MenuSurfacePositioning { get; set; } = MBMenuSurfacePositioningMD2.Regular; /// @@ -41,7 +41,7 @@ protected override async Task OnInitializedAsync() await base.OnInitializedAsync(); ConditionalCssClasses - .AddIf(GetMenuSurfacePositioningClass(MenuSurfacePositioning), () => MenuSurfacePositioning != MBMenuSurfacePositioning.Regular); + .AddIf(GetMenuSurfacePositioningClass(MenuSurfacePositioning), () => MenuSurfacePositioning != MBMenuSurfacePositioningMD2.Regular); ObjectReference = DotNetObjectReference.Create(this); } @@ -115,11 +115,11 @@ internal override async Task InstantiateMcwComponent() /// /// /// - internal static string GetMenuSurfacePositioningClass(MBMenuSurfacePositioning surfacePositioning) => + internal static string GetMenuSurfacePositioningClass(MBMenuSurfacePositioningMD2 surfacePositioning) => surfacePositioning switch { - MBMenuSurfacePositioning.FullWidth => "mdc-menu-surface--fullwidth", - MBMenuSurfacePositioning.Fixed => "mdc-menu-surface--fixed", + MBMenuSurfacePositioningMD2.FullWidth => "mdc-menu-surface--fullwidth", + MBMenuSurfacePositioningMD2.Fixed => "mdc-menu-surface--fixed", _ => "" }; } diff --git a/Material.Blazor.MD3/Components.MD2/MenuSelectionGroup/MBMenuSelectionGroup.razor b/Material.Blazor.MD3/Components.MD2/MenuSelectionGroup/MBMenuSelectionGroup.razor new file mode 100644 index 000000000..6aa666c5e --- /dev/null +++ b/Material.Blazor.MD3/Components.MD2/MenuSelectionGroup/MBMenuSelectionGroup.razor @@ -0,0 +1,10 @@ +@namespace Material.Blazor.MD2 + +
  • +
      + @if (ChildContent != null) + { + @ChildContent + } +
    +
  • \ No newline at end of file diff --git a/Material.Blazor.MD3/Components.MD2/MenuSelectionGroup/MBMenuSelectionGroup.razor.cs b/Material.Blazor.MD3/Components.MD2/MenuSelectionGroup/MBMenuSelectionGroup.razor.cs new file mode 100644 index 000000000..868faeafb --- /dev/null +++ b/Material.Blazor.MD3/Components.MD2/MenuSelectionGroup/MBMenuSelectionGroup.razor.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme menu selection group. +/// +public partial class MBMenuSelectionGroup +{ + [Parameter] public RenderFragment ChildContent { get; set; } +} diff --git a/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor b/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor index 4d157f11b..abab590f2 100644 --- a/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor +++ b/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor @@ -1,6 +1,6 @@ @namespace Material.Blazor.MD2 -@using Material.Blazor.Internal.MD2; -@inherits SingleSelectComponentMD2> +@using Material.Blazor.Internal; +@inherits SingleSelectComponent> @typeparam TItem @@ -10,16 +10,6 @@ id="@id" @attributes="AttributesToSplat()"> - @if (HasBadge) - { - - - - } -
    - @if (AppliedInputStyle == MBSelectInputStyle.Outlined) + @if (AppliedInputStyle == MBSelectInputStyleMD2.Outlined) { @@ -52,9 +42,9 @@ } } - @if (!string.IsNullOrWhiteSpace(LeadingIcon)) + @if (LeadingIconDescriptor is not null) { - + } @@ -77,7 +67,7 @@ - @if (AppliedInputStyle == MBSelectInputStyle.Filled) + @if (AppliedInputStyle == MBSelectInputStyleMD2.Filled) { } @@ -95,13 +85,13 @@
  • - @item.Label + @item.LeadingLabel
  • } diff --git a/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor.cs b/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor.cs index f33b2da39..dc597bbbe 100644 --- a/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor.cs +++ b/Material.Blazor.MD3/Components.MD2/Select/MBSelect.razor.cs @@ -1,4 +1,5 @@ -using Material.Blazor.Internal.MD2; +using Material.Blazor; +using Material.Blazor.Internal; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; using System.Linq; @@ -9,7 +10,7 @@ namespace Material.Blazor.MD2; /// /// A Material Theme select. /// -public partial class MBSelect : SingleSelectComponentMD2> +public partial class MBSelect : SingleSelectComponent> { #nullable enable annotations /// @@ -19,16 +20,16 @@ public partial class MBSelect : SingleSelectComponentMD2 - /// The select's . + /// The select's . /// Overrides /// - [Parameter] public MBSelectInputStyle? SelectInputStyle { get; set; } + [Parameter] public MBSelectInputStyleMD2? SelectInputStyle { get; set; } /// /// Regular, fullwidth or fixed positioning/width. /// - [Parameter] public MBMenuSurfacePositioning MenuSurfacePositioning { get; set; } = MBMenuSurfacePositioning.Regular; + [Parameter] public MBMenuSurfacePositioningMD2 MenuSurfacePositioning { get; set; } = MBMenuSurfacePositioningMD2.Regular; /// @@ -39,19 +40,9 @@ public partial class MBSelect : SingleSelectComponentMD2 - /// The leading icon's name. No leading icon shown if not set. + /// The leading icon's descriptor. No leading icon shown if not set. /// - [Parameter] public string? LeadingIcon { get; set; } - - - /// - /// The foundry to use for both leading and trailing icons. - /// IconFoundry="IconHelper.MIIcon()" - /// IconFoundry="IconHelper.FAIcon()" - /// IconFoundry="IconHelper.OIIcon()" - /// Overrides - /// - [Parameter] public IMBIconFoundry? IconFoundry { get; set; } + [Parameter] public MBIconDescriptor? LeadingIconDescriptor { get; set; } /// @@ -59,33 +50,6 @@ public partial class MBSelect : SingleSelectComponentMD2 [Parameter] public MBDensity? Density { get; set; } - - /// - /// Determines whether the button has a badge - defaults to false. - /// - [Parameter] public bool HasBadge { get; set; } - - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; - - - /// - /// When true collapses the badge. - /// - [Parameter] - public bool BadgeExited { get; set; } - private bool _cachedBadgeExited; - - - /// - /// The button's density. - /// - [Parameter] - public string BadgeValue { get; set; } - private string _cachedBadgeValue; #nullable restore annotations @@ -94,16 +58,15 @@ public partial class MBSelect : SingleSelectComponentMD2 Utilities.GetTextAlignClass(Material.Blazor.MD2.MBTextAlignStyle.Left); + private string AlignClass => Utilities.GetTextAlignClass(Material.Blazor.MBTextAlignStyle.Left); private MBDensity AppliedDensity => MBDensity.Default; - private MBSelectInputStyle AppliedInputStyle => MBSelectInputStyle.Outlined; + private MBSelectInputStyleMD2 AppliedInputStyle => MBSelectInputStyleMD2.Outlined; private string FloatingLabelClass { get; set; } = ""; private string MenuClass => MBMenu.GetMenuSurfacePositioningClass(MenuSurfacePositioning); private DotNetObjectReference> ObjectReference { get; set; } private ElementReference SelectReference { get; set; } private string SelectedText { get; set; } = ""; private bool ShowLabel => !string.IsNullOrWhiteSpace(Label); - private MBBadge Badge { get; set; } internal class DensityInfoClass @@ -121,8 +84,8 @@ private DensityInfoClass DensityInfo CssClassName = "\"dense-default\"" }; - var suffix = AppliedInputStyle == MBSelectInputStyle.Filled ? "--filled" : "--outlined"; - suffix += string.IsNullOrWhiteSpace(LeadingIcon) ? "" : "-with-leading-icon"; + var suffix = AppliedInputStyle == MBSelectInputStyleMD2.Filled ? "--filled" : "--outlined"; + suffix += (LeadingIconDescriptor is null) ? "" : "-with-leading-icon"; d.CssClassName += suffix; @@ -158,21 +121,21 @@ protected override async Task OnInitializedAsync() bool hasValue; TItem potentialComponentValue; - (hasValue, potentialComponentValue) = ValidateItemList(Items, Material.Blazor.MD2.MBItemValidation.DefaultToFirst); + (hasValue, potentialComponentValue) = ValidateItemList(Items, Material.Blazor.MBItemValidation.DefaultToFirst); if (hasValue) { ComponentValue = potentialComponentValue; } - SelectedText = hasValue ? (Items.FirstOrDefault(i => NullAllowingEquals(i.SelectedValue, ComponentValue))?.Label) : ""; + SelectedText = hasValue ? (Items.FirstOrDefault(i => NullAllowingEquals(i.SelectedValue, ComponentValue))?.LeadingLabel) : ""; FloatingLabelClass = string.IsNullOrWhiteSpace(SelectedText) ? "" : "mdc-floating-label--float-above"; _ = ConditionalCssClasses .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass) - .AddIf("mdc-select--filled", () => AppliedInputStyle == MBSelectInputStyle.Filled) - .AddIf("mdc-select--outlined", () => AppliedInputStyle == MBSelectInputStyle.Outlined) + .AddIf("mdc-select--filled", () => AppliedInputStyle == MBSelectInputStyleMD2.Filled) + .AddIf("mdc-select--outlined", () => AppliedInputStyle == MBSelectInputStyleMD2.Outlined) .AddIf("mdc-select--no-label", () => !ShowLabel) - .AddIf("mdc-select--with-leading-icon", () => !string.IsNullOrWhiteSpace(LeadingIcon)) + .AddIf("mdc-select--with-leading-icon", () => LeadingIconDescriptor is not null) .AddIf("mdc-select--disabled", () => AppliedDisabled); ObjectReference = DotNetObjectReference.Create(this); @@ -184,17 +147,6 @@ protected override async Task OnParametersSetAsync() { await base.OnParametersSetAsync().ConfigureAwait(false); - if (_cachedBadgeValue != BadgeValue || _cachedBadgeExited != BadgeExited) - { - _cachedBadgeValue = BadgeValue; - _cachedBadgeExited = BadgeExited; - - if (Badge is not null) - { - EnqueueJSInteropAction(() => Badge.SetValueAndExited(BadgeValue, BadgeExited)); - } - } - KeyGenerator = GetKeysFunc ?? delegate (TItem item) { return item; }; } diff --git a/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.razor b/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.razor new file mode 100644 index 000000000..c2ff5057b --- /dev/null +++ b/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.razor @@ -0,0 +1,76 @@ +@namespace Material.Blazor.MD2 +@inherits InputComponent + +
    + + + @* Would use the following except that Blazor reserves the 'value' tag for its bindings and removes it + from the rendered markup. Use of a MarkupString gets around this so that Material Compoents Web gets + access to the value tag.*@ + + @**@ + + @InputMarkup + +
    +
    +
    +
    +
    +
    + + @if (SliderType == MBSliderTypeMD2.DiscreteWithTickmarks) + { +
    + + @{ decimal currValue = ValueMin - ValueStepIncrement / 2; } + + @while (currValue < ValueMax) + { + if (currValue < Value) + { +
    + } + else + { +
    + } + + currValue += ValueStepIncrement; + } +
    + } + +
    + @*role="slider" + tabindex="@TabIndex" + aria-label="@AriaLabel" + aria-valuemin="@ValueMin.ToString(Format)" + aria-valuemax="@ValueMax.ToString(Format)" + aria-valuenow="@Value.ToString(Format)">*@ + + @if (SliderType != MBSliderTypeMD2.Continuous) + { +
    +
    + + @Value.ToString(Format) + +
    +
    + } + +
    +
    +
    \ No newline at end of file diff --git a/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.razor.cs b/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.razor.cs new file mode 100644 index 000000000..bd22fb3ec --- /dev/null +++ b/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.razor.cs @@ -0,0 +1,166 @@ +using Material.Blazor.Internal; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System; +using System.Threading.Tasks; + +namespace Material.Blazor.MD2; + +/// +/// A Material Theme single-thumb slider. +/// +public partial class MBSlider : InputComponent +{ + /// + /// The type of slider - defaults to . + /// + [Parameter] public MBSliderTypeMD2 SliderType { get; set; } = MBSliderTypeMD2.Continuous; + + + /// + /// The minimum slider value. + /// + [Parameter] public decimal ValueMin { get; set; } = 0; + + + /// + /// The maximum slider value. + /// + [Parameter] public decimal ValueMax { get; set; } = 100; + + + /// + /// The number of steps for a discrete slider. Note that ten steps will yield eleven thumb positions/tickmarks. + /// + [Parameter] public int NumSteps { get; set; } = 10; + + + /// + /// Specifies how slider events are emitted, see . + /// + [Parameter] public MBInputEventTypeMD2 EventType { get; set; } = MBInputEventTypeMD2.OnChange; + + + /// + /// For continuous input sets the debounce/throttle delay. + /// + [Parameter] public uint ContinuousInputDelay { get; set; } = 300; + + + /// + /// Number of decimal places for rounding and displaying values. + /// + [Parameter] public uint DecimalPlaces { get; set; } = 0; + + + /// + /// Value for the "aria-label" tag. + /// + [Parameter] public string AriaLabel { get; set; } = "Slider"; + + + private ElementReference ElementReference { get; set; } + private string Format { get; set; } + private MarkupString InputMarkup { get; set; } + private DotNetObjectReference ObjectReference { get; set; } + private decimal RangePercentDecimal { get; set; } + private int TabIndex { get; set; } + private decimal ThumbEndPercent => 100 * RangePercentDecimal; + private decimal ValueStepIncrement { get; set; } + + + // Would like to use however DocFX cannot resolve to references outside Material.Blazor + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + if (ValueMax <= ValueMin) + { + throw new ArgumentException($"{GetType()} must have ValueMax ({ValueMax}) greater than ValueMin ({ValueMin})"); + } + + if (Value < ValueMin || Value > ValueMax) + { + throw new ArgumentException($"{GetType()} must have Value ({Value}) between ValueMin ({ValueMin}) and ValueMax ({ValueMax})"); + } + + Value = Math.Round(Value, (int)DecimalPlaces); + ValueMin = Math.Round(ValueMin, (int)DecimalPlaces); + ValueMax = Math.Round(ValueMax, (int)DecimalPlaces); + Format = $"N{DecimalPlaces}"; + + TabIndex = AppliedDisabled ? -1 : 0; + RangePercentDecimal = (Value - ValueMin) / (ValueMax - ValueMin); + + if (SliderType == MBSliderTypeMD2.Continuous) + { + ValueStepIncrement = Convert.ToDecimal(Math.Pow(10, -DecimalPlaces)); + } + else + { + ValueStepIncrement = (ValueMax - ValueMin) / NumSteps; + } + + ConditionalCssClasses + .AddIf("mdc-slider--discrete", () => SliderType != MBSliderTypeMD2.Continuous) + .AddIf("mdc-slider--tick-marks", () => SliderType == MBSliderTypeMD2.DiscreteWithTickmarks) + .AddIf("mdc-slider--disabled", () => AppliedDisabled); + + var disabledClassMarkup = AppliedDisabled ? " mdc-slider--disabled" : ""; + var disabledAttributeMarkup = AppliedDisabled ? "disabled " : ""; + + InputMarkup = new($""); + + ObjectReference = DotNetObjectReference.Create(this); + } + + + private bool _disposed = false; + protected override void Dispose(bool disposing) + { + if (_disposed) + { + return; + } + + if (disposing) + { + ObjectReference?.Dispose(); + } + + _disposed = true; + + base.Dispose(disposing); + } + + + /// + /// For Material Theme to notify of slider value changes via JS Interop. + /// + [JSInvokable] + public void NotifyChanged(decimal value) + { + ComponentValue = value; + } + + + /// + private protected override Task SetComponentValueAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBSlider.setValue", ElementReference, Value); + } + + + /// + private protected override Task OnDisabledSetAsync() + { + return InvokeJsVoidAsync("MaterialBlazor.MBSlider.setDisabled", ElementReference, AppliedDisabled); + } + + + /// + internal override Task InstantiateMcwComponent() + { + return InvokeJsVoidAsync("MaterialBlazor.MBSlider.init", ElementReference, ObjectReference, EventType, ContinuousInputDelay, AppliedDisabled); + } +} diff --git a/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.ts b/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.ts new file mode 100644 index 000000000..dc695ec84 --- /dev/null +++ b/Material.Blazor.MD3/Components.MD2/Slider/MBSlider.ts @@ -0,0 +1,45 @@ +import { MDCSlider } from '@material/slider'; +import { debounce, throttle } from '../../Scripts/lodashparts'; + +export function init(elem, dotNetObject, eventType, delay, disabled) { + if (!elem) { + return; + } + elem._slider = MDCSlider.attachTo(elem); + elem._eventType = eventType; + + if (eventType == 0) { + const thumbUpCallback = () => { + dotNetObject.invokeMethodAsync('NotifyChanged', elem._slider.getValue()); + }; + elem._slider.listen('MDCSlider:change', thumbUpCallback); + } + else if (eventType == 1) { + const debounceNotify = debounce(function () { + dotNetObject.invokeMethodAsync('NotifyChanged', elem._slider.getValue()); + }, delay, {}); + elem._slider.listen('MDCSlider:input', debounceNotify); + } + else { + const throttleNotify = throttle(function () { + dotNetObject.invokeMethodAsync('NotifyChanged', elem._slider.getValue()); + }, delay, {}); + elem._slider.listen('MDCSlider:input', throttleNotify); + } + + elem._slider.setDisabled(disabled); +} + +export function setValue(elem, value) { + if (!elem) { + return; + } + elem._slider.setValue(value); +} + +export function setDisabled(elem, disabled) { + if (!elem) { + return; + } + elem._slider.setDisabled(disabled); +} \ No newline at end of file diff --git a/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor b/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor index fd24fe741..fb53fcce2 100644 --- a/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor +++ b/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor @@ -1,5 +1,5 @@ @namespace Material.Blazor.MD2 -@inherits ComponentFoundationMD2 +@inherits ComponentFoundation
    + IconName="@NavIcon" /> } @Title diff --git a/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor.cs b/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor.cs index 31a2a9c20..d9c16366a 100644 --- a/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor.cs +++ b/Material.Blazor.MD3/Components.MD2/TopAppBar/MBTopAppBar.razor.cs @@ -1,4 +1,5 @@ -using Material.Blazor.Internal.MD2; +using Material.Blazor; +using Material.Blazor.Internal; using Microsoft.AspNetCore.Components; using System.Threading.Tasks; @@ -8,7 +9,7 @@ namespace Material.Blazor.MD2; /// /// A Material Theme top app bar /// -public partial class MBTopAppBar : ComponentFoundationMD2 +public partial class MBTopAppBar : ComponentFoundation { /// /// App bar title. @@ -22,16 +23,6 @@ public partial class MBTopAppBar : ComponentFoundationMD2 [Parameter] public string NavIcon { get; set; } - /// - /// The foundry to use for both leading and trailing icons. - /// IconFoundry="IconHelper.MIIcon()" - /// IconFoundry="IconHelper.FAIcon()" - /// IconFoundry="IconHelper.OIIcon()" - /// Overrides - /// - [Parameter] public IMBIconFoundry IconFoundry { get; set; } - - /// /// Render fragment where @Body is referenced. /// @@ -45,9 +36,9 @@ public partial class MBTopAppBar : ComponentFoundationMD2 /// - /// Top app bar type. See + /// Top app bar type. See /// - [Parameter] public MBTopAppBarType TopAppBarType { get; set; } = MBTopAppBarType.Standard; + [Parameter] public MBTopAppBarTypeMD2 TopAppBarType { get; set; } = MBTopAppBarTypeMD2.Standard; private ElementReference HeaderElem { get; set; } @@ -59,11 +50,11 @@ protected override async Task OnInitializedAsync() await base.OnInitializedAsync(); ConditionalCssClasses - .AddIf("mdc-top-app-bar--fixed", () => (TopAppBarType & MBTopAppBarType.Fixed) == MBTopAppBarType.Fixed) - .AddIf("mdc-top-app-bar--dense", () => (TopAppBarType & MBTopAppBarType.Dense) == MBTopAppBarType.Dense) - .AddIf("mdc-top-app-bar--prominent", () => (TopAppBarType & MBTopAppBarType.Prominent) == MBTopAppBarType.Prominent) - .AddIf("mdc-top-app-bar--short", () => (TopAppBarType & MBTopAppBarType.Short) == MBTopAppBarType.Short) - .AddIf("mdc-top-app-bar--short mdc-top-app-bar--short-collapsed", () => (TopAppBarType & MBTopAppBarType.ShortCollapsed) == MBTopAppBarType.ShortCollapsed); + .AddIf("mdc-top-app-bar--fixed", () => (TopAppBarType & MBTopAppBarTypeMD2.Fixed) == MBTopAppBarTypeMD2.Fixed) + .AddIf("mdc-top-app-bar--dense", () => (TopAppBarType & MBTopAppBarTypeMD2.Dense) == MBTopAppBarTypeMD2.Dense) + .AddIf("mdc-top-app-bar--prominent", () => (TopAppBarType & MBTopAppBarTypeMD2.Prominent) == MBTopAppBarTypeMD2.Prominent) + .AddIf("mdc-top-app-bar--short", () => (TopAppBarType & MBTopAppBarTypeMD2.Short) == MBTopAppBarTypeMD2.Short) + .AddIf("mdc-top-app-bar--short mdc-top-app-bar--short-collapsed", () => (TopAppBarType & MBTopAppBarTypeMD2.ShortCollapsed) == MBTopAppBarTypeMD2.ShortCollapsed); } diff --git a/Material.Blazor.MD3/Components.MD2/_Imports.razor b/Material.Blazor.MD3/Components.MD2/_Imports.razor index 93dcbac2f..85aa28321 100644 --- a/Material.Blazor.MD3/Components.MD2/_Imports.razor +++ b/Material.Blazor.MD3/Components.MD2/_Imports.razor @@ -1,5 +1,6 @@ @using System.Threading.Tasks @using Microsoft.JSInterop @using Microsoft.AspNetCore.Components.Web +@using Material.Blazor @using Material.Blazor.MD2 -@using Material.Blazor.Internal.MD2 +@using Material.Blazor.Internal diff --git a/Material.Blazor.MD3/Components/Anchor/MBAnchor.md b/Material.Blazor.MD3/Components/Anchor/MBAnchor.md index 7c310fcf9..65dfdbac4 100644 --- a/Material.Blazor.MD3/Components/Anchor/MBAnchor.md +++ b/Material.Blazor.MD3/Components/Anchor/MBAnchor.md @@ -6,11 +6,11 @@ title: MBAnchor ## Summary -An anchor component for snackbars, toasts (based on a port of [Blazored/Toast](https://github.com/Blazored/Toast)) and tooltips. +An anchor component for snackbars and toasts (based on a port of [Blazored/Toast](https://github.com/Blazored/Toast)). ## Details -- Place once instance of this in your Blazor app in `App.razor` or in your layout component page that is used for all application components that use snackbars, toasts and tooltips. +- Place once instance of this in your Blazor app in `App.razor` or in your layout component page that is used for all application components that use snackbars and/or toasts. - Register services by calling [services.AddMBServices()](xref:Material.Blazor.ServiceCollectionExtensions#methods)). - The anchor will throw an exception on startup if the services registered by AddMBServices are not found. diff --git a/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor b/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor index 07c3f99c8..cd16f7f00 100644 --- a/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor +++ b/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor @@ -1,7 +1,5 @@ @namespace Material.Blazor -@using Material.Blazor.MD2 - - +@* *@ @* *@ diff --git a/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor.cs b/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor.cs index 93d06dd1d..c47d9c25b 100644 --- a/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor.cs +++ b/Material.Blazor.MD3/Components/Anchor/MBAnchor.razor.cs @@ -1,7 +1,7 @@ namespace Material.Blazor.MD2; /// -/// An anchor component for snackbars, toasts and tooltips to be placed in App.razor, MainLayout.razor or Index.razor. +/// An anchor component for snackbars and toast to be placed in App.razor, MainLayout.razor or Index.razor. /// public partial class MBAnchor { diff --git a/Material.Blazor.MD3/Components/Badge/MBBadge.md b/Material.Blazor.MD3/Components/Badge/MBBadge.md deleted file mode 100644 index 767689df2..000000000 --- a/Material.Blazor.MD3/Components/Badge/MBBadge.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -uid: C.MBBadge -title: MBBadge ---- -# MBBadge - -## Summary - -A badge that can either be places manually within a `
    ` or added via properties to an MBButton, MBIconButton or MBIconButtonToggle. Has the following properties: - -## Details - -- `ValueBearing`, `BlankFullSized` or `Dot` styles; -- A string value to be displayed when the style is `ValueBearing`; and -- An `Exited` property that closes the badge down with an animation (borrowed from FABs). - -  - -  - -[![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) -[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBBadge&color=brightgreen)](xref:Material.Blazor.MBBadge) diff --git a/Material.Blazor.MD3/Components/Badge/MBBadge.razor b/Material.Blazor.MD3/Components/Badge/MBBadge.razor deleted file mode 100644 index 0e0b74dea..000000000 --- a/Material.Blazor.MD3/Components/Badge/MBBadge.razor +++ /dev/null @@ -1,12 +0,0 @@ -@namespace Material.Blazor -@inherits ComponentFoundation - - - - - @if (BadgeStyle == MBBadgeStyle.ValueBearing) - { - @Value - } - diff --git a/Material.Blazor.MD3/Components/Badge/MBBadge.razor.cs b/Material.Blazor.MD3/Components/Badge/MBBadge.razor.cs deleted file mode 100644 index b3f28b14b..000000000 --- a/Material.Blazor.MD3/Components/Badge/MBBadge.razor.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Material.Blazor.Internal; -using Microsoft.AspNetCore.Components; -using System.Threading.Tasks; - -namespace Material.Blazor; - -/// -/// A plus component badge placed at the top right of the containing div, overlapping the corner, but inside margin and padding.. -/// -public partial class MBBadge : ComponentFoundation -{ - #region members - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; - - - /// - /// The button's density. - /// - [Parameter] public string Value { get; set; } - - - /// - /// When true collapses the badge. - /// - [Parameter] public bool Exited { get; set; } - - #endregion - - #region OnInitializedAsync - - // Would like to use however DocFX cannot resolve to references outside Material.Blazor - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - _ = ConditionalCssClasses - .AddIf("mb-badge--dot", () => BadgeStyle == MBBadgeStyle.Dot) - .AddIf("mb-badge--exited", () => Exited); - } - - #endregion - - #region SetValueAndExited - - /// - /// Sets the exited value and calls SHC. - /// - internal async Task SetValueAndExited(string value, bool exited) - { - Value = value; - Exited = exited; - await InvokeAsync(StateHasChanged).ConfigureAwait(false); - } - - #endregion - -} diff --git a/Material.Blazor.MD3/Components/Badge/MBBadge.scss b/Material.Blazor.MD3/Components/Badge/MBBadge.scss deleted file mode 100644 index fad15e4ed..000000000 --- a/Material.Blazor.MD3/Components/Badge/MBBadge.scss +++ /dev/null @@ -1,82 +0,0 @@ -@use "sass:math"; -@use '@material/theme/color-palette'; - -$background-color: color-palette.$red-500; -$color: color-palette.$blue-grey-100; - -$regular-size: 20px; -$regular-border-radius: math.div($regular-size, 2); -$regular-offset: math.div($regular-size, 2); - -$dot-size: 10px; -$dot-border-radius: math.div($dot-size, 2); -$dot-offset: math.div($dot-size, 2); - -$icon-button-offset: 4px; - -:root { - --mb-badge-background-color: var(--mb-color-red-700); - --mb-badge-color: var(--mb-color-on-red-700); -} - -.mb-badge-container { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; -} - -.mdc-icon-button { - .mb-badge-container { - top: $icon-button-offset; - left: -$icon-button-offset; - } -} - -.mb-badge { - display: inline-flex; - position: relative; - float: right; - align-items: center; - justify-content: center; - top: -$regular-offset; - right: -$regular-offset; - min-width: $regular-size; - height: $regular-size; - border-radius: $regular-border-radius; - margin-bottom: -$regular-size; - border: none; - background-color: var(--mb-badge-background-color); - color: var(--mb-badge-color); - transition: 50ms 0ms cubic-bezier(0.4, 0, 1, 1); - text-transform: none; - user-select: none; - z-index: 2; - - &.mb-badge--dot { - top: -$dot-offset; - right: -$dot-offset; - min-width: $dot-size; - height: $dot-size; - border-radius: $dot-border-radius; - margin-bottom: -$dot-size; - } - - &.mb-badge--exited { - -webkit-transform: scale(0); - transform: scale(0); - opacity: 0; - } -} - -.mb-badge-value { - padding: 0px 6px; -} - -.mb-badge-wrapper { - position: relative; - width: 100%; - height: 0px; -} diff --git a/Material.Blazor.MD3/Components/Button/MBButton.cs b/Material.Blazor.MD3/Components/Button/MBButton.cs new file mode 100644 index 000000000..969ed5c3c --- /dev/null +++ b/Material.Blazor.MD3/Components/Button/MBButton.cs @@ -0,0 +1,101 @@ +using Material.Blazor.Internal; + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; + +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor; + +/// +/// This is a general purpose Material Theme button. +/// +public sealed class MBButton : ComponentFoundation +{ + #region members + + [Parameter] public MBDensity Density { get; set; } + [Parameter] public MBIconDescriptor IconDescriptor { get; set; } + [Parameter] public bool IconIsTrailing { get; set; } + [Parameter] public MBButtonStyle? ButtonStyle { get; set; } + [Parameter] public string Label { get; set; } + + #endregion + + #region BuildRenderTree + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var attributesToSplat = AttributesToSplat().ToArray(); + var rendSeq = 0; + + builder.OpenElement(rendSeq++, "div"); + { + builder.AddAttribute(rendSeq++, "class", @class); + builder.AddAttribute(rendSeq++, "style", style); + builder.AddAttribute(rendSeq++, "id", id); + if (attributesToSplat.Any()) + { + builder.AddMultipleAttributes(rendSeq++, attributesToSplat); + } + + var componentName = CascadingDefaults.AppliedButtonStyle(ButtonStyle) switch + { + MBButtonStyle.Elevated => "md-elevated-button", + MBButtonStyle.Filled => "md-filled-button", + MBButtonStyle.FilledTonal => "md-filled-tonal-button", + MBButtonStyle.Outlined => "md-outlined-button", + MBButtonStyle.Text => "md-text-button", + _ => throw new System.Exception("Unknown ButtonStyle") + }; + + builder.OpenElement(rendSeq++, componentName); + { + if (AppliedDisabled) + { + builder.AddAttribute(rendSeq++, "disabled"); + } + + if (IconIsTrailing) + { + builder.AddAttribute(rendSeq++, "trailing-icon"); + } + + if (!string.IsNullOrWhiteSpace(Label)) + { + builder.AddContent(rendSeq++, Label); + } + + if (IconDescriptor is not null) + { + MBIcon.BuildRenderTreeWorker( + builder, + ref rendSeq, + CascadingDefaults, + IconDescriptor, + "icon"); + } + } + builder.CloseElement(); + + // Consider throttle + + // builder.AddAttribute(rendSeq++, "onclick", EventCallback.Factory.Create(this, OnClickInternal)); + } + builder.CloseElement(); + } + + #endregion + + #region OnClickInternal + + private async Task OnClickInternal() + { + //Value = !Value; + //await ValueChanged.InvokeAsync(Value); + } + + #endregion + +} diff --git a/Material.Blazor.MD3/Components/Button/MBButton.md b/Material.Blazor.MD3/Components/Button/MBButton.md new file mode 100644 index 000000000..09c53f92f --- /dev/null +++ b/Material.Blazor.MD3/Components/Button/MBButton.md @@ -0,0 +1,19 @@ +--- +uid: C.MBButton +title: MBButton +--- +# MBButton + +## Summary + +A simple [Material Button](https://material-web.dev/components/button/) used to initiate an action. Applies [density subsystem](xref:A.Density). + +## Reserved Attributes + +The following attributes are reserved by Material Components Web and will be ignored if you supply them: + +- role +- type + +[![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBButton&color=brightgreen)](xref:Material.Blazor.MBButton) diff --git a/Material.Blazor.MD3/Components/Checkbox/MBCheckbox.cs b/Material.Blazor.MD3/Components/Checkbox/MBCheckbox.cs index 43d28cd22..223477b69 100644 --- a/Material.Blazor.MD3/Components/Checkbox/MBCheckbox.cs +++ b/Material.Blazor.MD3/Components/Checkbox/MBCheckbox.cs @@ -1,7 +1,9 @@ using Material.Blazor.Internal; + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; + using System.Linq; using System.Threading.Tasks; @@ -14,29 +16,6 @@ public sealed class MBCheckbox : InputComponent { #region members - /// - /// Determines whether the button has a badge - defaults to false. - /// - [Parameter] public bool HasBadgePLUS { get; set; } - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStylePLUS { get; set; } = MBBadgeStyle.ValueBearing; - - /// - /// When true collapses the badge. - /// - [Parameter] public bool BadgeExitedPLUS { get; set; } - private bool _cachedBadgeExited; - - /// - /// The badge's value. - /// - [Parameter] public string BadgeValuePLUS { get; set; } - private string _cachedBadgeValue; - - /// /// Determines if the checkbox is disabled. /// @@ -59,7 +38,6 @@ public sealed class MBCheckbox : InputComponent - private MBBadge BadgeRef { get; set; } private string checkboxStyle { get; } = "display: flex; flex-direction: row; flex-grow: 1; align-items: center;"; #endregion @@ -72,76 +50,58 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) var rendSeq = 0; builder.OpenElement(rendSeq++, "div"); { - builder.AddAttribute(rendSeq++, "class", @class); - builder.AddAttribute(rendSeq++, "style", checkboxStyle + style); - builder.AddAttribute(rendSeq++, "id", id); - if (attributesToSplat.Any()) + builder.OpenElement(rendSeq++, "div"); { - builder.AddMultipleAttributes(rendSeq++, attributesToSplat); - } - - if (HasBadgePLUS) - { - builder.OpenElement(rendSeq++, "div"); + builder.AddAttribute(rendSeq++, "class", @class); + builder.AddAttribute(rendSeq++, "style", checkboxStyle + style); + builder.AddAttribute(rendSeq++, "id", id); + if (attributesToSplat.Any()) { - builder.OpenElement(rendSeq++, "span"); - { - builder.AddAttribute(rendSeq++, "class", "mb-badge-container"); - builder.OpenComponent(rendSeq++, typeof(MBBadge)); - { - builder.AddComponentParameter(rendSeq++, "BadgeStyle", BadgeStylePLUS); - builder.AddComponentParameter(rendSeq++, "Value", BadgeValuePLUS); - builder.AddComponentParameter(rendSeq++, "Exited", BadgeExitedPLUS); - builder.AddComponentReferenceCapture(rendSeq++, - (__value) => { BadgeRef = (Material.Blazor.MBBadge)__value; }); - } - builder.CloseComponent(); - } - builder.CloseElement(); + builder.AddMultipleAttributes(rendSeq++, attributesToSplat); } - builder.CloseElement(); - } - - if (!string.IsNullOrWhiteSpace(LeadingLabelPLUS)) - { - var labelSpan = - "" - + LeadingLabelPLUS - + ""; - builder.AddMarkupContent(rendSeq++, "\r\n"); - builder.AddMarkupContent(rendSeq++, labelSpan); - } - builder.OpenElement(rendSeq++, "md-checkbox"); - { - if (AppliedDisabled || IsDisabled) + if (!string.IsNullOrWhiteSpace(LeadingLabelPLUS)) { - builder.AddAttribute(rendSeq++, "disabled"); + var labelSpan = + "" + + LeadingLabelPLUS + + ""; + builder.AddMarkupContent(rendSeq++, "\r\n"); + builder.AddMarkupContent(rendSeq++, labelSpan); } - if (Value) + builder.OpenElement(rendSeq++, "md-checkbox"); { - builder.AddAttribute(rendSeq++, "checked"); + if (AppliedDisabled || IsDisabled) + { + builder.AddAttribute(rendSeq++, "disabled"); + } + + if (Value) + { + builder.AddAttribute(rendSeq++, "checked"); + } + + if (IsIndeterminate) + { + builder.AddAttribute(rendSeq++, "indeterminate"); + } + + builder.AddAttribute(12, "onclick", EventCallback.Factory.Create(this, OnClickInternal)); } + builder.CloseElement(); - if (IsIndeterminate) + if (!string.IsNullOrWhiteSpace(TrailingLabelPLUS)) { - builder.AddAttribute(rendSeq++, "indeterminate"); + var labelSpan = + "" + + TrailingLabelPLUS + + ""; + builder.AddMarkupContent(13, "\r\n"); + builder.AddMarkupContent(14, labelSpan); } - - builder.AddAttribute(12, "onclick", EventCallback.Factory.Create(this, OnClickInternal)); } builder.CloseElement(); - - if (!string.IsNullOrWhiteSpace(TrailingLabelPLUS)) - { - var labelSpan = - "" - + TrailingLabelPLUS - + ""; - builder.AddMarkupContent(13, "\r\n"); - builder.AddMarkupContent(14, labelSpan); - } } builder.CloseElement(); } @@ -158,24 +118,4 @@ private async Task OnClickInternal() #endregion - #region OnParametersSetAsync - - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - - if (BadgeRef is not null) - { - if (_cachedBadgeValue != BadgeValuePLUS || _cachedBadgeExited != BadgeExitedPLUS) - { - _cachedBadgeValue = BadgeValuePLUS; - _cachedBadgeExited = BadgeExitedPLUS; - - EnqueueJSInteropAction(() => BadgeRef.SetValueAndExited(BadgeValuePLUS, BadgeExitedPLUS)); - } - } - } - - #endregion - } diff --git a/Material.Blazor.MD3/Components/Icon/MBIcon.cs b/Material.Blazor.MD3/Components/Icon/MBIcon.cs index ef57c74dc..8b0879716 100644 --- a/Material.Blazor.MD3/Components/Icon/MBIcon.cs +++ b/Material.Blazor.MD3/Components/Icon/MBIcon.cs @@ -1,43 +1,208 @@ using Material.Blazor.Internal; + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; + using System.Linq; +using System.Threading.Tasks; namespace Material.Blazor; /// -/// Renders icons from any of the Material Icons, Font Awesome and Open Iconic -/// foundries. Material Icons are essential for Material.Blazor and are included by the -/// library's CSS, while you can elect whether to include Font Awesome and Open Iconic -/// in your app. +/// Renders icons from the Material Symbol Font. Material Symbols are essential for +/// Material.Blazor and are included in the library's CSS. /// public class MBIcon : ComponentFoundation { - [CascadingParameter(Name = "IsInsideMBTabBar")] private bool IsInsideMBTabBar { get; set; } - + #region members /// - /// The icon name. + /// The icon attributes and a constructor for same. /// - [Parameter] public string IconName { get; set; } + [Parameter] public MBIconDescriptor Descriptor { get; set; } + public static MBIconDescriptor IconDescriptorConstructor( + string color = null, + decimal? fill = null, + MBIconGradient? gradient = null, + string name = null, + MBIconSize? size = null, + MBIconStyle? style = null, + MBIconWeight? weight = null) + => + new MBIconDescriptor( + color: color, + fill: fill, + gradient: gradient, + name: name, + size: size, + style: style, + weight: weight); + + + + #endregion + #region BuildRenderTree protected override void BuildRenderTree(RenderTreeBuilder builder) { var attributesToSplat = AttributesToSplat().ToArray(); - - builder.OpenElement(0, "md-icon"); + var rendSeq = 0; + + builder.OpenElement(rendSeq++, "div"); { if (attributesToSplat.Any()) { - builder.AddMultipleAttributes(1, attributesToSplat); + builder.AddMultipleAttributes(rendSeq++, attributesToSplat); } - builder.AddAttribute(2, "class", @class); - builder.AddAttribute(3, "style", style); - builder.AddAttribute(4, "id", id); - builder.AddContent(5, IconName); + builder.AddAttribute(rendSeq++, "class", @class); + builder.AddAttribute(rendSeq++, "style", style); + builder.AddAttribute(rendSeq++, "id", id); + + + BuildRenderTreeWorker( + builder, + ref rendSeq, + CascadingDefaults, + Descriptor, + null); + } + builder.CloseElement(); + } + + public static void BuildRenderTreeWorker( + RenderTreeBuilder builder, + ref int rendSeq, + MBCascadingDefaults cascadingDefaults, + MBIconDescriptor descriptor, + string slot) + { + var iconColor = cascadingDefaults.AppliedIconColor(descriptor.Color); + var iconFill = cascadingDefaults.AppliedIconFill(descriptor.Fill); + var iconGradient = cascadingDefaults.AppliedIconGradient(descriptor.Gradient); + var iconName = cascadingDefaults.AppliedIconName(descriptor.Name); + var iconSize = cascadingDefaults.AppliedIconSize(descriptor.Size); + var iconSlot = slot; + var iconStyle = cascadingDefaults.AppliedIconStyle(descriptor.Style); + var iconWeight = cascadingDefaults.AppliedIconWeight(descriptor.Weight); + + // Set the icon style + + var fontStyle = ""; + var fontVariation = " font-variation-settings:"; + + // Icon font + fontStyle += "--md-icon-font: " + iconStyle switch + { + MBIconStyle.Outlined => "'Material Symbols Outlined'; ", + MBIconStyle.Rounded => "'Material Symbols Rounded'; ", + MBIconStyle.Sharp => "'Material Symbols Sharp'; ", + _ => throw new System.Exception("Unknown Icon Style") + }; + + // Icon color + fontStyle += " color: " + iconColor + ";"; + + // Icon fill + fontVariation += " 'FILL' " + iconFill.ToString() + ","; + + // Icon gradient + _ = iconGradient switch + { + MBIconGradient.LowEmphasis => fontVariation += " 'GRAD' -25,", + MBIconGradient.NormalEmphasis => fontVariation += " 'GRAD' 0,", + MBIconGradient.HighEmphasis => fontVariation += " 'GRAD' 200,", + _ => throw new System.Exception("Unknown Icon Gradient") + }; + + // Icon size + switch (iconSize) + { + case MBIconSize.Size20: + fontStyle += " font-size: 20px;"; + fontStyle += " height: 20px;"; + fontStyle += " width: 20px;"; + fontVariation += " 'opsz' 20,"; + break; + + case MBIconSize.Size24: + fontStyle += " font-size: 24px;"; + fontStyle += " height: 24px;"; + fontStyle += " width: 24px;"; + fontVariation += " 'opsz' 24,"; + break; + + case MBIconSize.Size40: + fontStyle += " font-size: 40px;"; + fontStyle += " height: 40px;"; + fontStyle += " width: 40px;"; + fontVariation += " 'opsz' 40,"; + break; + + case MBIconSize.Size48: + fontStyle += " font-size: 48px;"; + fontStyle += " height: 48px;"; + fontStyle += " width: 48px;"; + fontVariation += " 'opsz' 48,"; + break; + + default: + throw new System.Exception("Unknown Icon Size"); + }; + + // Icon weight + //_ = iconWeight switch + //{ + // MBIconWeight.W100 => fontStyle += " font-weight: 100;", + // MBIconWeight.W200 => fontStyle += " font-weight: 200;", + // MBIconWeight.W300 => fontStyle += " font-weight: 300,", + // MBIconWeight.W400 => fontStyle += " font-weight: 400,", + // MBIconWeight.W500 => fontStyle += " font-weight: 500,", + // MBIconWeight.W600 => fontStyle += " font-weight: 600,", + // MBIconWeight.W700 => fontStyle += " font-weight: 700,", + // _ => throw new System.Exception("Unknown Icon Weight") + //}; + // Icon weight + _ = iconWeight switch + { + MBIconWeight.W100 => fontVariation += " 'wght' 100,", + MBIconWeight.W200 => fontVariation += " 'wght' 200,", + MBIconWeight.W300 => fontVariation += " 'wght' 300,", + MBIconWeight.W400 => fontVariation += " 'wght' 400,", + MBIconWeight.W500 => fontVariation += " 'wght' 500,", + MBIconWeight.W600 => fontVariation += " 'wght' 600,", + MBIconWeight.W700 => fontVariation += " 'wght' 700,", + _ => throw new System.Exception("Unknown Icon Weight") + }; + + + // Fix the trailing part of the variation string + if (fontVariation.EndsWith(',')) + { + fontVariation = fontVariation.TrimEnd(','); + fontVariation += ';'; + } + + if (fontVariation.EndsWith(':')) + { + fontVariation = null; + } + + var iconDerivedStyle = fontStyle + fontVariation; + + builder.OpenElement(rendSeq++, "md-icon"); + { + if (!string.IsNullOrWhiteSpace(iconSlot)) + { + builder.AddAttribute(rendSeq++, "slot", iconSlot); + } + builder.AddAttribute(rendSeq++, "style", iconDerivedStyle); + builder.AddContent(rendSeq++, iconName.ToLower()); } builder.CloseElement(); } + + #endregion + } diff --git a/Material.Blazor.MD3/Components/Icon/MBIcon.md b/Material.Blazor.MD3/Components/Icon/MBIcon.md index 7deb166d7..cdf586737 100644 --- a/Material.Blazor.MD3/Components/Icon/MBIcon.md +++ b/Material.Blazor.MD3/Components/Icon/MBIcon.md @@ -6,15 +6,17 @@ title: MBIcon ## Summary -An icon component that generates HTML markup for an icon either for the default foundry or the one specified. +Material.Blazor allows you to style Material Icons. These icons are loaded +from the Material Symbols font.They support variations of style, size, weight, fill, and color. -## Details +## Using MBIcon -- The Material Icons foundry is supplied by Google and referenced in the Material.Blazor bundled CSS. -- You can also use Font Awesome version 5 and Open Iconic version 1.1 icon foundries. You will need to reference CDN or local CSS files for these two fonts. +MBIcon's parameters are +- IconDescriptor (required). -  +## Other Components +Many components such as MBIconButton, MBSelect, and MBTextField take icons. In every instance these components use IconDescriptor.   [![Components](https://img.shields.io/static/v1?label=Components&message=Plus&color=red)](xref:A.PlusComponents) diff --git a/Material.Blazor.MD3/Components/Icon/MBIconDescriptor.cs b/Material.Blazor.MD3/Components/Icon/MBIconDescriptor.cs new file mode 100644 index 000000000..7773f86a9 --- /dev/null +++ b/Material.Blazor.MD3/Components/Icon/MBIconDescriptor.cs @@ -0,0 +1,41 @@ +namespace Material.Blazor; + +/// +/// Material Symbols foundry details. +/// +public class MBIconDescriptor +{ + /// + /// The Material Symbols descriptor. + /// + + public string Color { get; } + public decimal? Fill { get; } + public MBIconGradient? Gradient { get; } + public string Name { get; } + public MBIconSize? Size { get; } + public MBIconStyle? Style { get; } + public MBIconWeight? Weight { get; } + + private MBIconDescriptor() + { + } + + public MBIconDescriptor( + string color = null, + decimal? fill = null, + MBIconGradient? gradient = null, + string name = null, + MBIconSize? size = null, + MBIconStyle? style = null, + MBIconWeight? weight = null) + { + Color = color; + Fill = fill; + Gradient = gradient; + Name = name; + Size = size; + Style = style; + Weight = weight; + } +} diff --git a/Material.Blazor.MD3/Components/Icon/MBIconWithParameters.cs b/Material.Blazor.MD3/Components/Icon/MBIconWithParameters.cs new file mode 100644 index 000000000..7f86a53b8 --- /dev/null +++ b/Material.Blazor.MD3/Components/Icon/MBIconWithParameters.cs @@ -0,0 +1,211 @@ +using Material.Blazor.Internal; + +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; + +using System.Linq; +using System.Threading.Tasks; + +namespace Material.Blazor; + +/// +/// Renders icons from the Material Symbol Font. Material Symbols are essential for +/// Material.Blazor and are included by the library's CSS. +/// +public class MBIconWithParameters : ComponentFoundation +{ + #region members + + [CascadingParameter(Name = "IsInsideMBTabBar")] private bool IsInsideMBTabBar { get; set; } + + + + /// + /// The icon color attribute. + /// + [Parameter] public string IconColor { get; set; } + + /// + /// The icon fill attribute. + /// + [Parameter] public decimal? IconFill { get; set; } + + /// + /// The icon gradient attribute. + /// + [Parameter] public MBIconGradient? IconGradient { get; set; } + + /// + /// The icon name. + /// + [Parameter] public string IconName { get; set; } + + /// + /// The icon size. + /// + [Parameter] public MBIconSize? IconSize { get; set; } + + /// + /// The icon style. + /// + [Parameter] public MBIconStyle? IconStyle { get; set; } + + /// + /// The icon weight attribute. + /// + [Parameter] public MBIconWeight? IconWeight { get; set; } + + + + + private string iconColor { get; set; } + private string iconDerivedClass { get; set; } + private string iconDerivedStyle { get; set; } + private decimal iconFill { get; set; } + private MBIconGradient iconGradient { get; set; } + private string iconName { get; set; } + private MBIconSize iconSize { get; set; } + private MBIconStyle iconStyle { get; set; } + private MBIconWeight iconWeight { get; set; } + + #endregion + + #region BuildRenderTree + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var attributesToSplat = AttributesToSplat().ToArray(); + var rendSeq = 0; + + builder.OpenElement(rendSeq++, "div"); + { + builder.OpenElement(rendSeq++, "div"); + { + if (attributesToSplat.Any()) + { + builder.AddMultipleAttributes(rendSeq++, attributesToSplat); + } + + builder.AddAttribute(rendSeq++, "class", @class); + builder.AddAttribute(rendSeq++, "style", style); + builder.AddAttribute(rendSeq++, "id", id); + + builder.OpenElement(rendSeq++, "span"); + { + builder.AddAttribute(rendSeq++, "class", iconDerivedClass); + builder.AddAttribute(rendSeq++, "style", iconDerivedStyle); + builder.AddContent(rendSeq++, IconName.ToLower()); + } + builder.CloseElement(); + } + builder.CloseElement(); + } + builder.CloseElement(); + } + + #endregion + + #region OnParametersSetAsync + + protected override async Task OnParametersSetAsync() + { + await base.OnParametersSetAsync().ConfigureAwait(false); + + iconColor = CascadingDefaults.AppliedIconColor(IconColor); + iconFill = CascadingDefaults.AppliedIconFill(IconFill); + iconGradient = CascadingDefaults.AppliedIconGradient(IconGradient); + iconName = CascadingDefaults.AppliedIconName(IconName); + iconSize = CascadingDefaults.AppliedIconSize(IconSize); + iconStyle = CascadingDefaults.AppliedIconStyle(IconStyle); + iconWeight = CascadingDefaults.AppliedIconWeight(IconWeight); + + // Set the icon class + + iconDerivedClass = ""; + iconDerivedClass = iconStyle switch + { + MBIconStyle.Outlined => "material-symbols-outlined ", + MBIconStyle.Rounded => "material-symbols-rounded ", + MBIconStyle.Sharp => "material-symbols-sharp ", + _ => throw new System.Exception("Unknown Icon Style") + }; + + // Set the icon style + + iconDerivedStyle = ""; + var fontStyle = ""; + var fontVariation = " font-variation-settings:"; + + // Icon color + fontStyle += " color: " + iconColor + ";"; + + // Icon fill + fontVariation += " 'FILL' " + iconFill.ToString() + ","; + + // Icon gradient + _ = iconGradient switch + { + MBIconGradient.LowEmphasis => fontVariation += " 'GRAD' -25,", + MBIconGradient.NormalEmphasis => fontVariation += " 'GRAD' 0,", + MBIconGradient.HighEmphasis => fontVariation += " 'GRAD' 200,", + _ => throw new System.Exception("Unknown Icon Gradient") + }; + + // Icon size + switch (iconSize) + { + case MBIconSize.Size20: + fontStyle += " font-size: 20px;"; + fontVariation += " 'opsz' 20,"; + break; + + case MBIconSize.Size24: + fontStyle += " font-size: 24px;"; + fontVariation += " 'opsz' 24,"; + break; + + case MBIconSize.Size40: + fontStyle += " font-size: 40px;"; + fontVariation += " 'opsz' 40,"; + break; + + case MBIconSize.Size48: + fontStyle += " font-size: 48px;"; + fontVariation += " 'opsz' 48,"; + break; + + default: + throw new System.Exception("Unknown Icon Size"); + }; + + // Icon weight + _ = iconWeight switch + { + MBIconWeight.W100 => fontVariation += " 'wght' 100,", + MBIconWeight.W200 => fontVariation += " 'wght' 200,", + MBIconWeight.W300 => fontVariation += " 'wght' 300,", + MBIconWeight.W400 => fontVariation += " 'wght' 400,", + MBIconWeight.W500 => fontVariation += " 'wght' 500,", + MBIconWeight.W600 => fontVariation += " 'wght' 600,", + MBIconWeight.W700 => fontVariation += " 'wght' 700,", + _ => throw new System.Exception("Unknown Icon Weight") + }; + + // Fix the trailing part of the variation string + if (fontVariation.EndsWith(',')) + { + fontVariation = fontVariation.TrimEnd(','); + fontVariation += ';'; + } + + if (fontVariation.EndsWith(':')) + { + fontVariation = null; + } + + iconDerivedStyle = fontStyle + fontVariation; + } + + #endregion + +} diff --git a/Material.Blazor.MD3/Components/NumericField/InternalFloatingPointFieldBase.cs b/Material.Blazor.MD3/Components/NumericField/InternalFloatingPointFieldBase.cs index b82520245..f894fd28b 100644 --- a/Material.Blazor.MD3/Components/NumericField/InternalFloatingPointFieldBase.cs +++ b/Material.Blazor.MD3/Components/NumericField/InternalFloatingPointFieldBase.cs @@ -14,7 +14,7 @@ public abstract class InternalFloatingPointFieldBase : InternalNumericFiel where U : InternalTextFieldBase { /// - /// Adjusts the value's maginitude as a number when the field is focused. Used for + /// Adjusts the value's magnitude as a number when the field is focused. Used for /// percentages and basis points (the latter of which lacks appropriate Numeric Format in C#: /// this issue may not get solved. /// @@ -22,7 +22,7 @@ public abstract class InternalFloatingPointFieldBase : InternalNumericFiel /// - /// Adjusts the value's maginitude as a number when the field is unfocused. Used for + /// Adjusts the value's magnitude as a number when the field is unfocused. Used for /// percentages and basis points (the latter of which lacks appropriate Numeric Format in C#: /// this issue may not get solved. /// diff --git a/Material.Blazor.MD3/Components/NumericField2/MBDecimalField2.cs b/Material.Blazor.MD3/Components/NumericField/MBDecimalField.cs similarity index 58% rename from Material.Blazor.MD3/Components/NumericField2/MBDecimalField2.cs rename to Material.Blazor.MD3/Components/NumericField/MBDecimalField.cs index b891a1eea..88d3c685b 100644 --- a/Material.Blazor.MD3/Components/NumericField2/MBDecimalField2.cs +++ b/Material.Blazor.MD3/Components/NumericField/MBDecimalField.cs @@ -3,9 +3,9 @@ namespace Material.Blazor; /// -/// A Material.Blazor filled style formatted decimal field. +/// A Material.Blazor formatted decimal field. /// -public sealed class MBDecimalField2 : InternalFloatingPointField2Base +public sealed class MBDecimalField : InternalFloatingPointFieldBase { private protected override decimal ConvertFromDecimal(decimal decimalValue) { diff --git a/Material.Blazor.MD3/Components/NumericField/MBFilledDoubleField.cs b/Material.Blazor.MD3/Components/NumericField/MBDoubleField.cs similarity index 61% rename from Material.Blazor.MD3/Components/NumericField/MBFilledDoubleField.cs rename to Material.Blazor.MD3/Components/NumericField/MBDoubleField.cs index d1b18347c..d647883e7 100644 --- a/Material.Blazor.MD3/Components/NumericField/MBFilledDoubleField.cs +++ b/Material.Blazor.MD3/Components/NumericField/MBDoubleField.cs @@ -4,9 +4,9 @@ namespace Material.Blazor; /// -/// A Material.Blazor filled style formatted double field. +/// A Material.Blazor formatted double field. /// -public sealed class MBFilledDoubleField : InternalFloatingPointFieldBase +public sealed class MBDoubleField : InternalFloatingPointFieldBase { private protected override double ConvertFromDecimal(decimal decimalValue) { diff --git a/Material.Blazor.MD3/Components/NumericField/MBFilledDecimalField.cs b/Material.Blazor.MD3/Components/NumericField/MBFilledDecimalField.cs deleted file mode 100644 index 41c4e040e..000000000 --- a/Material.Blazor.MD3/Components/NumericField/MBFilledDecimalField.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Material.Blazor.Internal; - -namespace Material.Blazor; - -/// -/// A Material.Blazor filled style formatted decimal field. -/// -public sealed class MBFilledDecimalField : InternalFloatingPointFieldBase -{ - private protected override decimal ConvertFromDecimal(decimal decimalValue) - { - return decimalValue; - } -} diff --git a/Material.Blazor.MD3/Components/NumericField/MBFilledIntField.cs b/Material.Blazor.MD3/Components/NumericField/MBFilledIntField.cs deleted file mode 100644 index 548b87774..000000000 --- a/Material.Blazor.MD3/Components/NumericField/MBFilledIntField.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Material.Blazor.Internal; - -namespace Material.Blazor; - -/// -/// A Material.Blazor filled style formatted integer field. -/// -public sealed class MBFilledIntField : InternalIntFieldBase -{ -} diff --git a/Material.Blazor.MD3/Components/NumericField/MBIntField.cs b/Material.Blazor.MD3/Components/NumericField/MBIntField.cs new file mode 100644 index 000000000..9f686341a --- /dev/null +++ b/Material.Blazor.MD3/Components/NumericField/MBIntField.cs @@ -0,0 +1,10 @@ +using Material.Blazor.Internal; + +namespace Material.Blazor; + +/// +/// A Material.Blazor formatted integer field. +/// +public sealed class MBIntField : InternalIntFieldBase +{ +} diff --git a/Material.Blazor.MD3/Components/NumericField/MBOutlinedDecimalField.cs b/Material.Blazor.MD3/Components/NumericField/MBOutlinedDecimalField.cs deleted file mode 100644 index 9c6e63a44..000000000 --- a/Material.Blazor.MD3/Components/NumericField/MBOutlinedDecimalField.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Material.Blazor.Internal; - -namespace Material.Blazor; - -/// -/// A Material.Blazor outlined style formatted decimal field. -/// -public sealed class MBOutlinedDecimalField : InternalFloatingPointFieldBase -{ - private protected override decimal ConvertFromDecimal(decimal decimalValue) - { - return decimalValue; - } -} diff --git a/Material.Blazor.MD3/Components/NumericField/MBOutlinedDoubleField.cs b/Material.Blazor.MD3/Components/NumericField/MBOutlinedDoubleField.cs deleted file mode 100644 index c052ba710..000000000 --- a/Material.Blazor.MD3/Components/NumericField/MBOutlinedDoubleField.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Material.Blazor.Internal; -using System; - -namespace Material.Blazor; - -/// -/// A Material.Blazor outlined style formatted decimal field. -/// -public sealed class MBOutlinedDoubleField : InternalFloatingPointFieldBase -{ - private protected override double ConvertFromDecimal(decimal decimalValue) - { - return Convert.ToDouble(decimalValue); - } -} diff --git a/Material.Blazor.MD3/Components/NumericField/MBOutlinedIntField.cs b/Material.Blazor.MD3/Components/NumericField/MBOutlinedIntField.cs deleted file mode 100644 index 09501cccc..000000000 --- a/Material.Blazor.MD3/Components/NumericField/MBOutlinedIntField.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Material.Blazor.Internal; - -namespace Material.Blazor; - -/// -/// A Material.Blazor filled style formatted integer field. -/// -public sealed class MBOutlinedIntField : InternalIntFieldBase -{ -} diff --git a/Material.Blazor.MD3/Components/NumericField2/InternalFloatingPointField2Base.cs b/Material.Blazor.MD3/Components/NumericField2/InternalFloatingPointField2Base.cs deleted file mode 100644 index 63f4fab71..000000000 --- a/Material.Blazor.MD3/Components/NumericField2/InternalFloatingPointField2Base.cs +++ /dev/null @@ -1,122 +0,0 @@ -using Microsoft.AspNetCore.Components; -using System; -using System.Numerics; -using System.Text.RegularExpressions; - -namespace Material.Blazor.Internal; - -/// -/// A Material Theme numeric input field. This wraps and normally -/// displays the numeric value as formatted text, but switches to a pure number on being selected. -/// -public abstract class InternalFloatingPointField2Base : InternalNumericField2Base - where T : struct, IFloatingPoint - where U : InternalTextField2Base -{ - /// - /// Adjusts the value's maginitude as a number when the field is focused. Used for - /// percentages and basis points (the latter of which lacks appropriate Numeric Format in C#: - /// this issue may not get solved. - /// - [Parameter] public MBNumericInputMagnitude FocusedMagnitude { get; set; } = MBNumericInputMagnitude.Normal; - - - /// - /// Adjusts the value's maginitude as a number when the field is unfocused. Used for - /// percentages and basis points (the latter of which lacks appropriate Numeric Format in C#: - /// this issue may not get solved. - /// - [Parameter] public MBNumericInputMagnitude UnfocusedMagnitude { get; set; } = MBNumericInputMagnitude.Normal; - - - /// - /// Number of decimal places for the value. If more dp are entered the value gets rounded properly. - /// - [Parameter] public uint DecimalPlaces { get; set; } = 2; - - - /// - /// Converts a decimal value to type T. - /// - /// - /// - private protected abstract T ConvertFromDecimal(decimal decimalValue); - - - /// - /// Returns the Multiplier required given focused/unfocused magnitude as applicable. - /// - /// - private protected decimal GetMultiplier() - { - return Convert.ToDecimal(Math.Pow(10, (int)(HasFocus ? GetFocusedMagnitude() : GetUnfocusedMagnitude()))); - } - - - /// - /// Returns rounding required for conversion from a string value. - /// - /// - private protected int GetRounding() - { - return GetDecimalPlaces() + Convert.ToInt32(Math.Log(Convert.ToDouble(GetMultiplier()))); - } - - - private protected override string BuildStep() - { - return Math.Pow(10, -(int)DecimalPlaces).ToString(); - } - - - private protected override MBNumericInputMagnitude GetFocusedMagnitude() - { - return FocusedMagnitude; - } - - - private protected override MBNumericInputMagnitude GetUnfocusedMagnitude() - { - return UnfocusedMagnitude; - } - - - private protected override int GetDecimalPlaces() - { - return (int)DecimalPlaces; - } - - - private protected override T ConvertToNumericValue(string displayText) - { - if (!Regex.IsMatch(displayText)) - { - return Value; - } - - if (!decimal.TryParse(displayText, out var result)) - { - return Value; - } - - result = Math.Round(result / GetMultiplier(), GetRounding()); - - return (Min != null && result < Convert.ToDecimal(Min)) || (Max != null && result > Convert.ToDecimal(Max)) ? Value : ConvertFromDecimal(result); - } - - - private protected override string ConvertToFormattedTextValue(T value) - { - var format = NumericSingularFormat is not null && Utilities.DecimalEqual(Math.Abs(Convert.ToDecimal(Value)), 1) - ? NumericSingularFormat - : NumericFormat; - - return (Convert.ToDecimal(value) * GetMultiplier()).ToString(format); - } - - - private protected override string ConvertToUnformattedTextValue(T value) - { - return Math.Round(Convert.ToDecimal(value) * GetMultiplier(), GetDecimalPlaces()).ToString(); - } -} diff --git a/Material.Blazor.MD3/Components/NumericField2/InternalNumericField2Base.cs b/Material.Blazor.MD3/Components/NumericField2/InternalNumericField2Base.cs deleted file mode 100644 index 1d74897d5..000000000 --- a/Material.Blazor.MD3/Components/NumericField2/InternalNumericField2Base.cs +++ /dev/null @@ -1,299 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.CompilerServices; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.AspNetCore.Components.Web; -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Numerics; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace Material.Blazor.Internal; - -/// -/// A Material Theme numeric input field. This wraps and normally -/// displays the numeric value as formatted text, but switches to a pure number on being selected. -/// -public abstract class InternalNumericField2Base : InputComponent - where T : struct, INumber - where U : InternalTextField2Base -{ -#nullable enable annotations - /// - /// Supporting text that is displayed either with focus or persistently with . - /// - [Parameter] public string SupportingText { get; set; } = ""; - - - /// - /// Makes the persistent if true. - /// - [Parameter] public bool SupportingTextPersistent { get; set; } = false; - - - /// - /// Delivers Material Theme validation methods from native Blazor validation. Either use this or - /// the Blazor ValidationMessage component, but not both. This parameter takes the same input as - /// ValidationMessage's For parameter. - /// - [Parameter] public Expression> ValidationMessageFor { get; set; } - - - /// - /// Field label. - /// - [Parameter] public string? Label { get; set; } - - - /// - /// Prefix text. - /// - [Parameter] public string? Prefix { get; set; } - - - /// - /// Suffix text. - /// - [Parameter] public string? Suffix { get; set; } - - - /// - /// The leading icon's name. No leading icon shown if not set. - /// - [Parameter] public string? LeadingIcon { get; set; } - - - /// - /// The trailing icon's name. No leading icon shown if not set. - /// - [Parameter] public string? TrailingIcon { get; set; } - - - /// - /// The numeric field's density. - /// - [Parameter] public MBDensity? Density { get; set; } - - - /// - /// Format to apply to the numeric value when the field is not selected. - /// - [Parameter] public string NumericFormat { get; set; } - - - /// - /// Alternative format for a singular number if required. An example is "1 month" - /// vs "3 months". - /// - [Parameter] public string? NumericSingularFormat { get; set; } - - - /// - /// The minimum allowable value. - /// - [Parameter] public T? Min { get; set; } - - - /// - /// The maximum allowable value. - /// - [Parameter] public T? Max { get; set; } -#nullable restore annotations - - - private const string DoublePattern = @"^[-+]?[0-9]*\.?[0-9]+$"; - private const string PositiveDoublePattern = @"[0-9]*\.?[0-9]+$"; - //private const string IntegerPattern = @"^(\+|-)?\d+$"; - //private const string PositiveIntegerPattern = @"^\d+$"; - - - private bool SelectInputContentOnAfterRender { get; set; } = false; - private U TextField { get; set; } - - - /// - /// REGEX that matches valid text field input - /// - private protected Regex Regex { get; set; } - - - /// - /// Indicates whether the text field has focus. - /// - private protected bool HasFocus { get; set; } = false; - - - // Would like to use however DocFX cannot resolve to references outside Material.Blazor - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - - var allowSign = Min is not (not null and >= 0); - - Regex = new Regex(allowSign ? DoublePattern : PositiveDoublePattern); - - //Regex = GetDecimalPlaces() == 0 - // ? new Regex(allowSign ? IntegerPattern : PositiveIntegerPattern) - // : new Regex(allowSign ? DoublePattern : PositiveDoublePattern); - } - - - /// - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - var attributesToSplat = AttributesToSplat().ToArray(); - - builder.OpenComponent(0); - { - if (attributesToSplat.Any()) - { - builder.AddMultipleAttributes(1, attributesToSplat); - } - - builder.AddAttribute(2, "class", @class); - builder.AddAttribute(3, "style", style); - builder.AddAttribute(4, "id", id); - - if (HasFocus) - { - builder.AddAttribute(5, "type", "number"); - builder.AddAttribute(6, "formnovalidate", true); - builder.AddAttribute(7, "Value", BindConverter.FormatValue(ConvertToUnformattedTextValue(Value))); - SelectInputContentOnAfterRender = true; - } - else - { - builder.AddAttribute(5, "type", "text"); - builder.AddAttribute(7, "Value", BindConverter.FormatValue(ConvertToFormattedTextValue(Value))); - } - - builder.AddAttribute(8, "ValueChanged", RuntimeHelpers.TypeCheck(EventCallback.Factory.Create(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => ValueChanged.InvokeAsync(ConvertToNumericValue(__value)), ConvertToUnformattedTextValue(Value))))); - var stringValue = HasFocus? ConvertToUnformattedTextValue(Value) : ConvertToFormattedTextValue(Value); - builder.AddAttribute(9, "ValueExpression", RuntimeHelpers.TypeCheck>>(() => stringValue)); - - builder.AddAttribute(10, "onfocusin", EventCallback.Factory.Create(this, OnFocusInAsync)); - builder.AddAttribute(11, "onfocusout", EventCallback.Factory.Create(this, OnFocusOutAsync)); - builder.AddAttribute(12, "Disabled", Disabled); - builder.AddAttribute(13, "label", Label); - builder.AddAttribute(14, "SupportingText", SupportingText); - builder.AddAttribute(15, "SupportingTextPersistent", SupportingTextPersistent); - builder.AddAttribute(16, "LeadingIcon", LeadingIcon); - builder.AddAttribute(17, "TrailingIcon", TrailingIcon); - builder.AddAttribute(18, "Prefix", Prefix); - builder.AddAttribute(19, "Suffix", Suffix); - builder.AddAttribute(20, "Density", Density); - - if (Min is not null) - { - builder.AddAttribute(21, "min", Min); - } - - if (Max is not null) - { - builder.AddAttribute(22, "max", Max); - } - - var step = BuildStep(); - - if (!string.IsNullOrWhiteSpace(step)) - { - builder.AddAttribute(23, "step", BuildStep()); - } - - builder.AddAttribute(24, "TextAlignStyle", MBTextAlignStyle.Right); - builder.AddAttribute(25, "ValidationMessageFor", ValidationMessageFor); - - builder.AddComponentReferenceCapture(18, __value => TextField = (U)__value); - } - builder.CloseComponent(); - } - - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender).ConfigureAwait(false); - - if (SelectInputContentOnAfterRender) - { - SelectInputContentOnAfterRender = false; - - await TextField.SelectFieldContent(); - } - } - - - /// - /// Converts a string value from the text field to a numeric value. - /// - /// - private protected abstract T ConvertToNumericValue(string value); - - - /// - /// Converts a string value from the text field to a formatted numeric value. - /// - /// - private protected abstract string ConvertToFormattedTextValue(T value); - - - /// - /// Converts a string value from the text field to an unformatted numeric value, subject to the correct number of decimal places. - /// - /// - private protected abstract string ConvertToUnformattedTextValue(T displayText); - - - /// - /// Returns the step value. - /// - /// - private protected virtual string BuildStep() - { - return "1"; - } - - - /// - /// Returns the focused magnitude. - /// - /// - private protected virtual MBNumericInputMagnitude GetFocusedMagnitude() - { - return MBNumericInputMagnitude.Normal; - } - - - /// - /// Returns the unfocused magnitude. - /// - /// - private protected virtual MBNumericInputMagnitude GetUnfocusedMagnitude() - { - return MBNumericInputMagnitude.Normal; - } - - - /// - /// Returns the required number of decimal places. - /// - /// - private protected virtual int GetDecimalPlaces() - { - return 0; - } - - - private async Task OnFocusInAsync() - { - HasFocus = true; - await InvokeAsync(StateHasChanged); - } - - - private async Task OnFocusOutAsync() - { - HasFocus = false; - await InvokeAsync(StateHasChanged); - } -} diff --git a/Material.Blazor.MD3/Components/ProgressIndicator/MBProgress.cs b/Material.Blazor.MD3/Components/ProgressIndicator/MBProgress.cs index f0235bd26..bbc03451d 100644 --- a/Material.Blazor.MD3/Components/ProgressIndicator/MBProgress.cs +++ b/Material.Blazor.MD3/Components/ProgressIndicator/MBProgress.cs @@ -10,10 +10,6 @@ namespace Material.Blazor; ///
    public sealed class MBProgress : ComponentFoundation { - //private protected override string WebComponentName() - //{ - // return ; - //} #region members /// diff --git a/Material.Blazor.MD3/Components/RadioButton/MBRadioButton.razor.cs b/Material.Blazor.MD3/Components/RadioButton/MBRadioButton.cs similarity index 52% rename from Material.Blazor.MD3/Components/RadioButton/MBRadioButton.razor.cs rename to Material.Blazor.MD3/Components/RadioButton/MBRadioButton.cs index 58252ceb0..ce698d27f 100644 --- a/Material.Blazor.MD3/Components/RadioButton/MBRadioButton.razor.cs +++ b/Material.Blazor.MD3/Components/RadioButton/MBRadioButton.cs @@ -1,8 +1,9 @@ using Material.Blazor.Internal; + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; -using System; + using System.Linq; using System.Threading.Tasks; @@ -16,31 +17,6 @@ public partial class MBRadioButton : InputComponent { #region members - /// - /// Determines whether the button has a badge - defaults to false. - /// - [Parameter] public bool HasBadge { get; set; } - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; - - /// - /// When true collapses the badge. - /// - [Parameter] - public bool BadgeExited { get; set; } - private bool _cachedBadgeExited; - - /// - /// The button's density. - /// - [Parameter] - public string BadgeValue { get; set; } - private string _cachedBadgeValue; - - /// /// The radio button's density. /// @@ -90,50 +66,57 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.OpenElement(rendSeq++, "div"); { - builder.AddAttribute(rendSeq++, "class", @class); - builder.AddAttribute(rendSeq++, "style", radioStyle + style); - builder.AddAttribute(rendSeq++, "id", id); - if (attributesToSplat.Any()) + builder.OpenElement(rendSeq++, "div"); { - builder.AddMultipleAttributes(rendSeq++, attributesToSplat); - } + builder.AddAttribute(rendSeq++, "class", @class); + builder.AddAttribute(rendSeq++, "style", radioStyle + style); + builder.AddAttribute(rendSeq++, "id", id); + if (attributesToSplat.Any()) + { + builder.AddMultipleAttributes(rendSeq++, attributesToSplat); + } - if (!string.IsNullOrWhiteSpace(LeadingLabelPLUS)) - { - var labelSpan = - "" - + LeadingLabelPLUS - + ""; - builder.AddMarkupContent(rendSeq++, "\r\n"); - builder.AddMarkupContent(rendSeq++, labelSpan); - } + if (!string.IsNullOrWhiteSpace(LeadingLabelPLUS)) + { + var labelSpan = + "" + + LeadingLabelPLUS + + ""; + builder.AddMarkupContent(rendSeq++, "\r\n"); + builder.AddMarkupContent(rendSeq++, labelSpan); + } - builder.OpenElement(rendSeq++, "md-radio"); - { - if (AppliedDisabled || IsDisabled) + builder.OpenElement(rendSeq++, "md-radio"); { - builder.AddAttribute(rendSeq++, "disabled"); + if (AppliedDisabled || IsDisabled) + { + builder.AddAttribute(rendSeq++, "disabled"); + } + + if (Value != null) + { + if (Value.Equals(TargetCheckedValue)) + { + builder.AddAttribute(rendSeq++, "checked"); + } + } + + builder.AddAttribute(rendSeq++, "onclick", EventCallback.Factory.Create(this, OnClickInternal)); } + builder.CloseElement(); - if (Value.Equals(TargetCheckedValue)) + if (!string.IsNullOrWhiteSpace(TrailingLabelPLUS)) { - builder.AddAttribute(rendSeq++, "checked"); + var labelSpan = + "" + + TrailingLabelPLUS + + ""; + builder.AddMarkupContent(rendSeq++, "\r\n"); + builder.AddMarkupContent(rendSeq++, labelSpan); } - builder.AddAttribute(rendSeq++, "onclick", EventCallback.Factory.Create(this, OnClickInternal)); } builder.CloseElement(); - - if (!string.IsNullOrWhiteSpace(TrailingLabelPLUS)) - { - var labelSpan = - "" - + TrailingLabelPLUS - + ""; - builder.AddMarkupContent(rendSeq++, "\r\n"); - builder.AddMarkupContent(rendSeq++, labelSpan); - } - } builder.CloseElement(); } diff --git a/Material.Blazor.MD3/Components/RadioButtonGroup/MBRadioButtonGroup.razor.cs b/Material.Blazor.MD3/Components/RadioButtonGroup/MBRadioButtonGroup.cs similarity index 100% rename from Material.Blazor.MD3/Components/RadioButtonGroup/MBRadioButtonGroup.razor.cs rename to Material.Blazor.MD3/Components/RadioButtonGroup/MBRadioButtonGroup.cs diff --git a/Material.Blazor.MD3/Components/Switch/MBSwitch.cs b/Material.Blazor.MD3/Components/Switch/MBSwitch.cs index f74674069..1ac62e43a 100644 --- a/Material.Blazor.MD3/Components/Switch/MBSwitch.cs +++ b/Material.Blazor.MD3/Components/Switch/MBSwitch.cs @@ -1,7 +1,9 @@ using Material.Blazor.Internal; + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; + using System.Linq; using System.Threading.Tasks; @@ -14,43 +16,20 @@ public sealed class MBSwitch : InputComponent { #region members - /// - /// Determines whether the button has a badge - defaults to false. - /// - [Parameter] public bool HasBadgePLUS { get; set; } - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStylePLUS { get; set; } = MBBadgeStyle.ValueBearing; - - /// - /// When true collapses the badge. - /// - [Parameter] public bool BadgeExitedPLUS { get; set; } - private bool _cachedBadgeExited; - - /// - /// The badge's value. - /// - [Parameter] public string BadgeValuePLUS { get; set; } - private string _cachedBadgeValue; - /// /// Determines whether the switch shows icons. /// [Parameter] public bool? Icons { get; set; } - /// - /// Determines shows icons only in the selected state. - /// - [Parameter] public bool? ShowOnlySelectedIcon { get; set; } - /// /// Provides a leading label for the checkbox. /// [Parameter] public string LeadingLabelPLUS { get; set; } + /// + /// Determines shows icons only in the selected state. + /// + [Parameter] public bool? ShowOnlySelectedIcon { get; set; } /// /// Provides a trailing label for the checkbox. @@ -59,7 +38,6 @@ public sealed class MBSwitch : InputComponent - private MBBadge BadgeRef { get; set; } private string switchStyle { get; } = "display: flex; flex-direction: row; flex-grow: 0; align-items: center;"; #endregion @@ -70,34 +48,11 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) var attributesToSplat = AttributesToSplat().ToArray(); var rendSeq = 0; - builder.OpenElement(rendSeq++, "p"); + builder.OpenElement(rendSeq++, "div"); { builder.AddAttribute(rendSeq++, "class", @class); builder.AddAttribute(rendSeq++, "style", switchStyle + style); builder.AddAttribute(rendSeq++, "id", id); - builder.AddAttribute(rendSeq++, "style", "display: flex; flex-flow: row nowrap; align-items: center;"); - - if (HasBadgePLUS) - { - builder.OpenElement(rendSeq++, "div"); - { - builder.OpenElement(rendSeq++, "span"); - { - builder.AddAttribute(rendSeq++, "class", "mb-badge-container"); - builder.OpenComponent(rendSeq++, typeof(MBBadge)); - { - builder.AddComponentParameter(rendSeq++, "BadgeStyle", BadgeStylePLUS); - builder.AddComponentParameter(rendSeq++, "Value", BadgeValuePLUS); - builder.AddComponentParameter(rendSeq++, "Exited", BadgeExitedPLUS); - builder.AddComponentReferenceCapture(rendSeq++, - (__value) => { BadgeRef = (Material.Blazor.MBBadge)__value; }); - } - builder.CloseComponent(); - } - builder.CloseElement(); - } - builder.CloseElement(); - } if (!string.IsNullOrWhiteSpace(LeadingLabelPLUS)) { @@ -153,24 +108,4 @@ private async Task OnClickInternal() #endregion - #region OnParametersSetAsync - - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync().ConfigureAwait(false); - - if (_cachedBadgeValue != BadgeValuePLUS || _cachedBadgeExited != BadgeExitedPLUS) - { - _cachedBadgeValue = BadgeValuePLUS; - _cachedBadgeExited = BadgeExitedPLUS; - - if (BadgeRef is not null) - { - EnqueueJSInteropAction(() => BadgeRef.SetValueAndExited(BadgeValuePLUS, BadgeExitedPLUS)); - } - } - } - - #endregion - } diff --git a/Material.Blazor.MD3/Components/TextField/InternalTextFieldBase.cs b/Material.Blazor.MD3/Components/TextField/InternalTextFieldBase.cs index a46b80416..041af1250 100644 --- a/Material.Blazor.MD3/Components/TextField/InternalTextFieldBase.cs +++ b/Material.Blazor.MD3/Components/TextField/InternalTextFieldBase.cs @@ -1,8 +1,10 @@ using Material.Blazor; + using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.JSInterop; + using System; using System.Linq; using System.Linq.Expressions; @@ -12,40 +14,47 @@ namespace Material.Blazor.Internal; /// /// Base component for Filled and Outlined Text Fields. +/// Required for NumericFields otherwise could have been implemented directly in +/// MBTextField.cs /// public abstract class InternalTextFieldBase : InputComponent { + #region members + #nullable enable annotations + #region cascading parameters + //[CascadingParameter] private MBDateTimeField DateTimeField { get; set; } + #endregion + + #region non-cascading parameters /// - /// Determines whether the button has a badge - defaults to false. + /// The text field's density. /// - [Parameter] public bool HasBadgePLUS { get; set; } + [Parameter] public MBDensity? Density { get; set; } /// - /// The badge's style - see , defaults to . + /// Field label. /// - [Parameter] public MBBadgeStyle BadgeStylePLUS { get; set; } = MBBadgeStyle.ValueBearing; + [Parameter] public string? Label { get; set; } /// - /// When true collapses the badge. + /// The leading icon's descriptor. No leading icon is shown if not set. /// - [Parameter] public bool BadgeExitedPLUS { get; set; } - private bool _cachedBadgeExited; + [Parameter] public MBIconDescriptor? LeadingIconPLUS { get; set; } /// - /// The badge's value. + /// Prefix text. /// - [Parameter] public string BadgeValuePLUS { get; set; } - private string _cachedBadgeValue; + [Parameter] public string? Prefix { get; set; } /// - /// The text field's density. + /// Suffix text. /// - [Parameter] public MBDensity? Density { get; set; } + [Parameter] public string? Suffix { get; set; } /// /// Supporting text that is displayed either with focus or persistently with . @@ -57,26 +66,6 @@ public abstract class InternalTextFieldBase : InputComponent /// [Parameter] public bool SupportingTextPersistent { get; set; } = false; - /// - /// The leading icon's name. No leading icon shown if not set. - /// - [Parameter] public string? LeadingIcon { get; set; } - - /// - /// Field label. - /// - [Parameter] public string? Label { get; set; } - - /// - /// Prefix text. - /// - [Parameter] public string? Prefix { get; set; } - - /// - /// Suffix text. - /// - [Parameter] public string? Suffix { get; set; } - /// /// The text alignment style. /// Overrides @@ -84,9 +73,15 @@ public abstract class InternalTextFieldBase : InputComponent [Parameter] public MBTextAlignStyle? TextAlignStyle { get; set; } /// - /// The trailing icon's name. No leading icon shown if not set. + /// The text input style. + /// Overrides + /// + [Parameter] public MBTextInputStyle TextInputStyle { get; set; } = MBTextInputStyle.Outlined; + + /// + /// The trailing icon's descriptor. No trailing icon is shown if not set. /// - [Parameter] public string? TrailingIcon { get; set; } + [Parameter] public MBIconDescriptor? TrailingIconPLUS { get; set; } /// /// Delivers Material Theme validation methods from native Blazor validation. Either use this or @@ -95,14 +90,23 @@ public abstract class InternalTextFieldBase : InputComponent /// [Parameter] public Expression> ValidationMessageFor { get; set; } + #endregion +#nullable restore annotations + + #region injected members [Inject] private IJSRuntime JsRuntime { get; set; } + #endregion + #region local members -#nullable restore annotations + private MBDensity AppliedDensity => CascadingDefaults.AppliedTextFieldDensity(Density); + private string DateFieldErrorMessage { get; set; } + + private string DisplayLabel => Label + LabelSuffix; /// /// The @ref reference for the top level <label> code block with @@ -110,6 +114,9 @@ public abstract class InternalTextFieldBase : InputComponent /// internal ElementReference ElementReference { get; set; } + private string LabelSuffix { get; set; } = ""; + + private bool PerformsValidation => EditContext != null && ValidationMessageFor != null; /// /// Used by to force the text field to select @@ -117,87 +124,30 @@ public abstract class InternalTextFieldBase : InputComponent /// internal bool SelectAllText { get; set; } = false; - - //private MBDensity AppliedDensity => CascadingDefaults.AppliedTextFieldDensity(Density); - private MBBadge BadgeRef { get; set; } - private string DisplayLabel => Label + LabelSuffix; - private string DateFieldErrorMessage { get; set; } - private string LabelSuffix { get; set; } = ""; - private bool PerformsValidation => EditContext != null && ValidationMessageFor != null; - //private MBBadge Badge { get; set; } + private bool ShowLabel => !string.IsNullOrWhiteSpace(Label); //private readonly string labelId = Utilities.GenerateUniqueElementName(); //private readonly string SupportingTextId = Utilities.GenerateUniqueElementName(); - - //private MBCascadingDefaults.DensityInfo DensityInfo //{ // get // { // var d = CascadingDefaults.GetDensityCssClass(AppliedDensity); - // //var suffix = AppliedInputStyle == MBTextInputStyle.Filled ? "--tf--filled" : "--tf--outlined"; // //suffix += string.IsNullOrWhiteSpace(LeadingIcon) ? "" : "-with-leading-icon"; - // //d.CssClassName += suffix; - // return d; // } //} - private bool ShowLabel => !string.IsNullOrWhiteSpace(Label); - - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - SetDateErrorMessage(); - - //_ = ConditionalCssClasses - // .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass) - // .AddIf(FieldClass, () => !string.IsNullOrWhiteSpace(FieldClass)) - // .AddIf("mdc-text-field--filled", () => AppliedInputStyle == MBTextInputStyle.Filled) - // .AddIf("mdc-text-field--outlined", () => AppliedInputStyle == MBTextInputStyle.Outlined) - // .AddIf("mdc-text-field--no-label", () => !ShowLabel) - // .AddIf("mdc-text-field--disabled", () => AppliedDisabled) - // .AddIf("mdc-text-field--with-leading-icon", () => LeadingIcon is not null) - // .AddIf("mdc-text-field--with-trailing-icon", () => TrailingIcon is not null) - // .AddIf("mb-date-field", () => DateTimeField is not null); - - //FloatingLabelClass = string.IsNullOrEmpty(ComponentValue) ? "" : "mdc-floating-label--float-above"; - - if (EditContext != null) - { - EditContext.OnValidationStateChanged += OnValidationStateChangedCallback; - - if (HasRequiredAttribute(ValidationMessageFor)) - { - LabelSuffix = " *"; - } - } - } - - - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync().ConfigureAwait(false); - - if (_cachedBadgeValue != BadgeValuePLUS || _cachedBadgeExited != BadgeExitedPLUS) - { - _cachedBadgeValue = BadgeValuePLUS; - _cachedBadgeExited = BadgeExitedPLUS; + #endregion - if (BadgeRef is not null) - { - EnqueueJSInteropAction(() => BadgeRef.SetValueAndExited(BadgeValuePLUS, BadgeExitedPLUS)); - } - } - } + #endregion + #region BuildRenderTree protected override void BuildRenderTree(RenderTreeBuilder builder) { @@ -205,40 +155,25 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) var cssClass = (@class + " " + Utilities.GetTextAlignClass(CascadingDefaults.AppliedStyle(TextAlignStyle))).Trim(); var rendSeq = 0; - builder.OpenElement(rendSeq++, WebComponentName()); + var componentName = TextInputStyle switch + { + MBTextInputStyle.Outlined => "md-outlined-text-field", + MBTextInputStyle.Filled => "md-filled-text-field", + _ => throw new System.Exception("Unknown TextInputStyle") + }; + + builder.OpenElement(rendSeq++, componentName); { if (attributesToSplat.Any()) { builder.AddMultipleAttributes(rendSeq++, attributesToSplat); } - if (HasBadgePLUS) - { - builder.OpenElement(rendSeq++, "div"); - { - builder.OpenElement(rendSeq++, "span"); - { - builder.AddAttribute(rendSeq++, "class", "mb-badge-container"); - builder.OpenComponent(rendSeq++, typeof(MBBadge)); - { - builder.AddComponentParameter(rendSeq++, "BadgeStyle", BadgeStylePLUS); - builder.AddComponentParameter(rendSeq++, "Value", BadgeValuePLUS); - builder.AddComponentParameter(rendSeq++, "Exited", BadgeExitedPLUS); - builder.AddComponentReferenceCapture(rendSeq++, - (__value) => { BadgeRef = (Material.Blazor.MBBadge)__value; }); - } - builder.CloseComponent(); - } - builder.CloseElement(); - } - builder.CloseElement(); - } - if (AppliedDisabled) { builder.AddAttribute(rendSeq++, "disabled"); } - + builder.AddAttribute(rendSeq++, "class", cssClass); builder.AddAttribute(rendSeq++, "style", style); builder.AddAttribute(rendSeq++, "id", id); @@ -249,7 +184,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddAttribute(rendSeq++, "label", DisplayLabel); - + if (!string.IsNullOrWhiteSpace(Prefix)) { builder.AddAttribute(rendSeq++, "prefixText", Prefix); @@ -265,24 +200,24 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddAttribute(rendSeq++, "supportingText", SupportingText); } - if (!string.IsNullOrWhiteSpace(LeadingIcon)) + if (LeadingIconPLUS is not null) { - builder.OpenElement(rendSeq++, "md-icon"); - { - builder.AddAttribute(rendSeq++, "slot", "leadingicon"); - builder.AddContent(rendSeq++, LeadingIcon); - } - builder.CloseElement(); + MBIcon.BuildRenderTreeWorker( + builder, + ref rendSeq, + CascadingDefaults, + LeadingIconPLUS, + "leading-icon"); } - if (!string.IsNullOrWhiteSpace(TrailingIcon)) + if (TrailingIconPLUS is not null) { - builder.OpenElement(rendSeq++, "md-icon"); - { - builder.AddAttribute(rendSeq++, "slot", "trailingicon"); - builder.AddContent(rendSeq++, TrailingIcon); - } - builder.CloseElement(); + MBIcon.BuildRenderTreeWorker( + builder, + ref rendSeq, + CascadingDefaults, + TrailingIconPLUS, + "trailing-icon"); } builder.AddElementReferenceCapture(rendSeq++, __value => ElementReference = __value); @@ -290,34 +225,9 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.CloseElement(); } + #endregion - /// - /// Selects the text field content. Used by numeric fields when type is changed to "number". - /// - /// - internal async Task SelectFieldContent() - { - await JsRuntime.InvokeVoidAsync("MaterialBlazor.MBTextField.selectFieldContent", ElementReference).ConfigureAwait(false); - } - - - /// - /// Inherited classes must specify the material-web component name. - /// - /// - private protected abstract string WebComponentName(); - - - - protected void SetDateErrorMessage() - { - DateFieldErrorMessage = ""; - //if (DateTimeField != null) - //{ - // DateFieldErrorMessage = MBDateTimeField.ErrorText; - //} - } - + #region Dispose private bool _disposed = false; protected override void Dispose(bool disposing) @@ -337,6 +247,30 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } + #endregion + + #region OnInitializedAsync + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + + SetDateErrorMessage(); + + if (EditContext != null) + { + EditContext.OnValidationStateChanged += OnValidationStateChangedCallback; + + if (HasRequiredAttribute(ValidationMessageFor)) + { + LabelSuffix = " *"; + } + } + } + + #endregion + + #region OnValidationStateChangedCallback private void OnValidationStateChangedCallback(object sender, EventArgs e) { @@ -348,4 +282,33 @@ private void OnValidationStateChangedCallback(object sender, EventArgs e) //_ = InvokeAsync(() => InvokeJsVoidAsync("MaterialBlazor.MBTextField.setSupportingText", ElementReference, SupportingTextReference, SupportingText.Trim(), SupportingTextPersistent, PerformsValidation, !string.IsNullOrEmpty(Value), validationMessage)); } } + + #endregion + + #region SelectFieldContent + + /// + /// Selects the text field content. Used by numeric fields when type is changed to "number". + /// + /// + internal async Task SelectFieldContent() + { + await JsRuntime.InvokeVoidAsync("MaterialBlazor.MBTextField.selectFieldContent", ElementReference).ConfigureAwait(false); + } + + #endregion + + #region SetDateErrorMessage + + protected void SetDateErrorMessage() + { + DateFieldErrorMessage = ""; + //if (DateTimeField != null) + //{ + // DateFieldErrorMessage = MBDateTimeField.ErrorText; + //} + } + + #endregion + } diff --git a/Material.Blazor.MD3/Components/TextField/MBFilledTextField.cs b/Material.Blazor.MD3/Components/TextField/MBFilledTextField.cs deleted file mode 100644 index fee9994cd..000000000 --- a/Material.Blazor.MD3/Components/TextField/MBFilledTextField.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Material.Blazor.Internal; - -namespace Material.Blazor; - -/// -/// A Material.Blazor filled text field. -/// -public sealed class MBFilledTextField : InternalTextFieldBase -{ - private protected override string WebComponentName() - { - return "md-filled-text-field"; - } -} diff --git a/Material.Blazor.MD3/Components/TextField/MBOutlinedTextField.cs b/Material.Blazor.MD3/Components/TextField/MBOutlinedTextField.cs deleted file mode 100644 index 90dcd19f2..000000000 --- a/Material.Blazor.MD3/Components/TextField/MBOutlinedTextField.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Material.Blazor.Internal; - -namespace Material.Blazor; - -/// -/// A Material.Blazor outlined text field. -/// -public sealed class MBOutlinedTextField : InternalTextFieldBase -{ - private protected override string WebComponentName() - { - return "md-outlined-text-field"; - } -} diff --git a/Material.Blazor.MD3/Components/TextField/MBTextField.cs b/Material.Blazor.MD3/Components/TextField/MBTextField.cs new file mode 100644 index 000000000..8634fda48 --- /dev/null +++ b/Material.Blazor.MD3/Components/TextField/MBTextField.cs @@ -0,0 +1,11 @@ +using Material.Blazor.Internal; +using Microsoft.AspNetCore.Components; + +namespace Material.Blazor; + +/// +/// A Material.Blazor text field. +/// +public sealed class MBTextField : InternalTextFieldBase +{ +} diff --git a/Material.Blazor.MD3/Components/TextField2/MBTextField2.md b/Material.Blazor.MD3/Components/TextField/MBTextField.md similarity index 80% rename from Material.Blazor.MD3/Components/TextField2/MBTextField2.md rename to Material.Blazor.MD3/Components/TextField/MBTextField.md index eb19bf1ba..636be459a 100644 --- a/Material.Blazor.MD3/Components/TextField2/MBTextField2.md +++ b/Material.Blazor.MD3/Components/TextField/MBTextField.md @@ -1,6 +1,6 @@ --- -uid: C.MBTextField2 -title: MBTextField2 +uid: C.MBTextField +title: MBTextField --- # MBTextField @@ -22,4 +22,4 @@ The following attributes are reserved by Material Components Web and will be ign - type [![Components](https://img.shields.io/static/v1?label=Components&message=Core&color=blue)](xref:A.CoreComponents) -[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBProgressIndicator&color=brightgreen)](xref:Material.Blazor.MBProgressIndicator) +[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBTextField&color=brightgreen)](xref:Material.Blazor.MBTextField) diff --git a/Material.Blazor.MD3/Components/TextField2/InternalTextField2Base.cs b/Material.Blazor.MD3/Components/TextField2/InternalTextField2Base.cs deleted file mode 100644 index a50e467d6..000000000 --- a/Material.Blazor.MD3/Components/TextField2/InternalTextField2Base.cs +++ /dev/null @@ -1,338 +0,0 @@ -using Material.Blazor; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; -using Microsoft.AspNetCore.Components.Rendering; -using Microsoft.JSInterop; -using System; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; - -namespace Material.Blazor.Internal; - -/// -/// Base component for Filled and Outlined Text Fields. -/// -public abstract class InternalTextField2Base : InputComponent -{ - [Inject] private IJSRuntime JsRuntime { get; set; } - - //[CascadingParameter] private MBDateTimeField DateTimeField { get; set; } - - -#nullable enable annotations - - /// - /// Determines whether the button has a badge - defaults to false. - /// - [Parameter] public bool HasBadge { get; set; } - - - /// - /// The badge's style - see , defaults to . - /// - [Parameter] public MBBadgeStyle BadgeStyle { get; set; } = MBBadgeStyle.ValueBearing; - - - /// - /// When true collapses the badge. - /// - [Parameter] - public bool BadgeExited { get; set; } - private bool _cachedBadgeExited; - - - /// - /// The button's density. - /// - [Parameter] - public string BadgeValue { get; set; } - private string _cachedBadgeValue; - - - /// - /// The text field's density. - /// - [Parameter] public MBDensity? Density { get; set; } - - - /// - /// Supporting text that is displayed either with focus or persistently with . - /// - [Parameter] public string SupportingText { get; set; } = ""; - - - /// - /// Makes the persistent if true. - /// - [Parameter] public bool SupportingTextPersistent { get; set; } = false; - - - /// - /// The leading icon's name. No leading icon shown if not set. - /// - [Parameter] public string? LeadingIcon { get; set; } - - - /// - /// Field label. - /// - [Parameter] public string? Label { get; set; } - - - /// - /// Prefix text. - /// - [Parameter] public string? Prefix { get; set; } - - - /// - /// Suffix text. - /// - [Parameter] public string? Suffix { get; set; } - - - /// - /// The text alignment style. - /// Overrides - /// - [Parameter] public MBTextAlignStyle? TextAlignStyle { get; set; } - - - /// - /// The trailing icon's name. No leading icon shown if not set. - /// - [Parameter] public string? TrailingIcon { get; set; } - - - /// - /// Delivers Material Theme validation methods from native Blazor validation. Either use this or - /// the Blazor ValidationMessage component, but not both. This parameter takes the same input as - /// ValidationMessage's For parameter. - /// - [Parameter] public Expression> ValidationMessageFor { get; set; } - -#nullable restore annotations - - - /// - /// The @ref reference for the top level <label> code block with - /// class mdc-text-field - /// - internal ElementReference ElementReference { get; set; } - - - /// - /// Used by to force the text field to select - /// all text in the <input> code block. - /// - internal bool SelectAllText { get; set; } = false; - - - //private MBDensity AppliedDensity => CascadingDefaults.AppliedTextFieldDensity(Density); - private string DisplayLabel => Label + LabelSuffix; - private string DateFieldErrorMessage { get; set; } - private string LabelSuffix { get; set; } = ""; - private bool PerformsValidation => EditContext != null && ValidationMessageFor != null; - //private MBBadge Badge { get; set; } - - - - - //private readonly string labelId = Utilities.GenerateUniqueElementName(); - //private readonly string SupportingTextId = Utilities.GenerateUniqueElementName(); - - - //private MBCascadingDefaults.DensityInfo DensityInfo - //{ - // get - // { - // var d = CascadingDefaults.GetDensityCssClass(AppliedDensity); - - // //var suffix = AppliedInputStyle == MBTextInputStyle.Filled ? "--tf--filled" : "--tf--outlined"; - // //suffix += string.IsNullOrWhiteSpace(LeadingIcon) ? "" : "-with-leading-icon"; - - // //d.CssClassName += suffix; - - // return d; - // } - //} - - private bool ShowLabel => !string.IsNullOrWhiteSpace(Label); - - - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - SetDateErrorMessage(); - - //_ = ConditionalCssClasses - // .AddIf(DensityInfo.CssClassName, () => DensityInfo.ApplyCssClass) - // .AddIf(FieldClass, () => !string.IsNullOrWhiteSpace(FieldClass)) - // .AddIf("mdc-text-field--filled", () => AppliedInputStyle == MBTextInputStyle.Filled) - // .AddIf("mdc-text-field--outlined", () => AppliedInputStyle == MBTextInputStyle.Outlined) - // .AddIf("mdc-text-field--no-label", () => !ShowLabel) - // .AddIf("mdc-text-field--disabled", () => AppliedDisabled) - // .AddIf("mdc-text-field--with-leading-icon", () => LeadingIcon is not null) - // .AddIf("mdc-text-field--with-trailing-icon", () => TrailingIcon is not null) - // .AddIf("mb-date-field", () => DateTimeField is not null); - - //FloatingLabelClass = string.IsNullOrEmpty(ComponentValue) ? "" : "mdc-floating-label--float-above"; - - if (EditContext != null) - { - EditContext.OnValidationStateChanged += OnValidationStateChangedCallback; - - if (HasRequiredAttribute(ValidationMessageFor)) - { - LabelSuffix = " *"; - } - } - } - - - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync().ConfigureAwait(false); - - if (_cachedBadgeValue != BadgeValue || _cachedBadgeExited != BadgeExited) - { - _cachedBadgeValue = BadgeValue; - _cachedBadgeExited = BadgeExited; - - //if (Badge is not null) - //{ - // EnqueueJSInteropAction(() => Badge.SetValueAndExited(BadgeValue, BadgeExited)); - //} - } - } - - - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - var attributesToSplat = AttributesToSplat().ToArray(); - var cssClass = (@class + " " + Utilities.GetTextAlignClass(CascadingDefaults.AppliedStyle(TextAlignStyle))).Trim(); - - builder.OpenElement(0, WebComponentName()); - { - if (attributesToSplat.Any()) - { - builder.AddMultipleAttributes(1, attributesToSplat); - } - - if (AppliedDisabled) - { - builder.AddAttribute(2, "disabled"); - } - - builder.AddAttribute(3, "class", cssClass); - builder.AddAttribute(4, "style", style); - builder.AddAttribute(5, "id", id); - - builder.AddAttribute(6, "value", BindConverter.FormatValue(Value)); - builder.AddAttribute(7, "onchange", EventCallback.Factory.CreateBinder(this, ValueChanged.InvokeAsync, Value)); - builder.SetUpdatesAttributeName("value"); - - - builder.AddAttribute(8, "label", DisplayLabel); - - if (!string.IsNullOrWhiteSpace(Prefix)) - { - builder.AddAttribute(9, "prefixText", Prefix); - } - - if (!string.IsNullOrWhiteSpace(Suffix)) - { - builder.AddAttribute(10, "suffixText", Suffix); - } - - if (!string.IsNullOrWhiteSpace(SupportingText)) - { - builder.AddAttribute(11, "supportingText", SupportingText); - } - - if (!string.IsNullOrWhiteSpace(LeadingIcon)) - { - builder.OpenElement(12, "md-icon"); - { - builder.AddAttribute(13, "slot", "leadingicon"); - builder.AddContent(14, LeadingIcon); - } - builder.CloseElement(); - } - - if (!string.IsNullOrWhiteSpace(TrailingIcon)) - { - builder.OpenElement(15, "md-icon"); - { - builder.AddAttribute(16, "slot", "trailingicon"); - builder.AddContent(17, TrailingIcon); - } - builder.CloseElement(); - } - - builder.AddElementReferenceCapture(18, __value => ElementReference = __value); - } - builder.CloseElement(); - } - - - /// - /// Selects the text field content. Used by numeric fields when type is changed to "number". - /// - /// - internal async Task SelectFieldContent() - { - await JsRuntime.InvokeVoidAsync("MaterialBlazor.MBTextField2.selectFieldContent", ElementReference).ConfigureAwait(false); - } - - - /// - /// Inherited classes must specify the material-web compoent name. - /// - /// - private protected abstract string WebComponentName(); - - - - protected void SetDateErrorMessage() - { - DateFieldErrorMessage = ""; - //if (DateTimeField != null) - //{ - // DateFieldErrorMessage = MBDateTimeField.ErrorText; - //} - } - - - private bool _disposed = false; - protected override void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing && EditContext != null) - { - EditContext.OnValidationStateChanged -= OnValidationStateChangedCallback; - } - - _disposed = true; - - base.Dispose(disposing); - } - - - private void OnValidationStateChangedCallback(object sender, EventArgs e) - { - if (ValidationMessageFor != null) - { - var fieldIdentifier = FieldIdentifier.Create(ValidationMessageFor); - var validationMessage = string.Join("
    ", EditContext.GetValidationMessages(fieldIdentifier)); - - //_ = InvokeAsync(() => InvokeJsVoidAsync("MaterialBlazor.MBTextField.setSupportingText", ElementReference, SupportingTextReference, SupportingText.Trim(), SupportingTextPersistent, PerformsValidation, !string.IsNullOrEmpty(Value), validationMessage)); - } - } -} diff --git a/Material.Blazor.MD3/Components/TextField2/MBTextField2.cs b/Material.Blazor.MD3/Components/TextField2/MBTextField2.cs deleted file mode 100644 index c9bf3843d..000000000 --- a/Material.Blazor.MD3/Components/TextField2/MBTextField2.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Material.Blazor.Internal; -using Microsoft.AspNetCore.Components; - -namespace Material.Blazor; - -/// -/// A Material.Blazor text field. -/// -public sealed class MBTextField2 : InternalTextField2Base -{ - [Parameter] public MBTextInputStyle TextInputStyle { get; set; } = MBTextInputStyle.Outlined; - - private protected override string WebComponentName() - { - if (TextInputStyle == MBTextInputStyle.Outlined) - { - return "md-outlined-text-field"; - } - else - { - return "md-filled-text-field"; - } - - } -} diff --git a/Material.Blazor.MD3/Components/TextField2/MBTextField2.ts b/Material.Blazor.MD3/Components/TextField2/MBTextField2.ts deleted file mode 100644 index a73da6dcf..000000000 --- a/Material.Blazor.MD3/Components/TextField2/MBTextField2.ts +++ /dev/null @@ -1,17 +0,0 @@ - export function selectFieldContent(elem) { - if (!elem) { - return; - } - - // Check focus to eliminate race condition where if you - // (i) select a numeric field - // (ii) click a different window - // (iii) click back and straight into another numeric field - // The two fields would conflict and flash between them. - // Seemed to happen in Blazor Server (presumably due to slower JS Invoke timing). - // Also probably not needed.Time will tell. - - if (elem.focused == true) { - elem.select(); - } -} diff --git a/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor b/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor index 4fd1dd4c6..7e7d42503 100644 --- a/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor +++ b/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor @@ -16,7 +16,8 @@ {
    } @@ -34,8 +35,9 @@ @if (toast.Settings.CloseMethod != MBNotifierCloseMethod.Timeout) {
    - +
    }
    diff --git a/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor.cs b/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor.cs index c3c93cbcd..594919b26 100644 --- a/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor.cs +++ b/Material.Blazor.MD3/Components/Toast/InternalToastAnchor.razor.cs @@ -11,7 +11,7 @@ namespace Material.Blazor; /// /// An anchor component that displays toast notification that you display via -/// . +/// . /// Place this component at the top of either App.razor or MainLayout.razor. /// public partial class InternalToastAnchor : ComponentFoundation diff --git a/Material.Blazor.MD3/Components/toc.yml b/Material.Blazor.MD3/Components/toc.yml index 0e049fda0..d5d7b7014 100644 --- a/Material.Blazor.MD3/Components/toc.yml +++ b/Material.Blazor.MD3/Components/toc.yml @@ -7,8 +7,8 @@ - name: Material.Blazor.Internal namespace topicUid: Material.Blazor.Internal -- name: MBBadge - topicUid: C.MBBadge +- name: MBButton + topicUid: C.MBButton - name: MBCheckbox topicUid: C.MBCheckbox @@ -30,6 +30,3 @@ - name: MBSwitch topicUid: C.MBSwitch - -- name: MBTextField2 - topicUid: C.MBTextField2 diff --git a/Material.Blazor.MD3/Foundation.MD2/ComponentFoundation.cs b/Material.Blazor.MD3/Foundation.MD2/ComponentFoundation.cs deleted file mode 100644 index 7c761ca2f..000000000 --- a/Material.Blazor.MD3/Foundation.MD2/ComponentFoundation.cs +++ /dev/null @@ -1,456 +0,0 @@ -using Material.Blazor.MD2; -using Microsoft.AspNetCore.Components; -using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; - -namespace Material.Blazor.Internal.MD2; - -/// -/// The base class for all Material.Blazor components. -/// -public abstract class ComponentFoundationMD2 : ComponentBase, IDisposable -{ - #region members - /// - /// A list of unmatched attributes that are used by and therefore essential for Material.Blazor. Works with - /// and . - /// - /// - /// Includes "formnovalidate", "max", "min", "role", "step", "tabindex", "type", "data-prev-page" - /// - private static readonly ImmutableArray EssentialSplattableAttributes = ImmutableArray.Create("formnovalidate", "max", "min", "role", "step", "tabindex", "type", "data-prev-page"); - private bool? disabled = null; - - [Inject] private IJSRuntime JsRuntime { get; set; } - [CascadingParameter] private IMBDialog ParentDialog { get; set; } - [Inject] private protected ILogger Logger { get; set; } - [Inject] private protected IMBTooltipService TooltipService { get; set; } - [Inject] private protected IMBLoggingService LoggingService { get; set; } - - - - /// - /// Gets a value for the component's 'id' attribute. - /// - private protected string CrossReferenceId { get; set; } = Utilities.GenerateUniqueElementName(); - - - /// - /// Tooltip id for aria-describedby attribute. - /// - private long? TooltipId { get; set; } - - - [CascadingParameter] protected MBCascadingDefaults CascadingDefaults { get; set; } = new MBCascadingDefaults(); - - /// - /// Determines whether to apply the disabled attribute. - /// - internal bool AppliedDisabled => CascadingDefaults.AppliedDisabled(Disabled); - - - /// - /// True if the component has been instantiated with a Material Components Web JSInterop call. - /// - private protected bool HasInstantiated { get; set; } - - - /// - /// Allows a component to build or map out a group of CSS classes to be applied to the component. Use this in , or their asynchronous counterparts. - /// - private protected ConditionalCssClasses ConditionalCssClasses { get; } = new ConditionalCssClasses(); - - - /// - /// The concurrent queue for javascript interop actions. - /// - private readonly ConcurrentQueue> _jsActionQueue = new(); - - - /// - /// Semaphore prevents multiple dequeue actions from running concurrently. - /// - private readonly SemaphoreSlim _jsActionQueueSemaphore = new(1,1); - - - /// - /// Components should override this with a function to be called when Material.Blazor wants to run Material Components Web instantiation via JS Interop - always gets called from , which should not be overridden. - /// - internal virtual Task InstantiateMcwComponent() - { - return Task.CompletedTask; - } - - #endregion - - #region parameters - - /// - /// Gets or sets a collection of additional attributes that will be applied to the created element. - /// - [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary UnmatchedAttributes { get; set; } - - - /// - /// Indicates whether the component is disabled. - /// - -#pragma warning disable BL0007 // Component parameters should be auto properties - [Parameter] public bool? Disabled -#pragma warning restore BL0007 // Component parameters should be auto properties - { - get => disabled; - set - { - if (disabled != value) - { - disabled = value; - - if (HasInstantiated) - { - EnqueueJSInteropAction(OnDisabledSetAsync); - } - } - } - } - - - /// - /// The HTML id attribute is used to specify a unique id for an HTML element. - /// - /// You cannot have more than one element with the same id in an HTML document. - /// -#pragma warning disable IDE1006 // Naming Styles - [Parameter] public string id { get; set; } -#pragma warning restore IDE1006 // Naming Styles - - - /// - /// Additional CSS classes for the component. - /// -#pragma warning disable IDE1006 // Naming Styles - [Parameter] public string @class { get; set; } -#pragma warning restore IDE1006 // Naming Styles - protected string ActiveConditionalClasses => ConditionalCssClasses.ToString(); - - - /// - /// Additional CSS style for the component. - /// -#pragma warning disable IDE1006 // Naming Styles - [Parameter] public string style { get; set; } -#pragma warning restore IDE1006 // Naming Styles - - - /// - /// A markup capable tooltip. - /// - [Parameter] public string Tooltip { get; set; } - - #endregion - - #region AddTooltip - - /// - /// Adds a tooltip if tooltip text has been provided. - /// - private protected void AddTooltip() - { - if (!string.IsNullOrWhiteSpace(Tooltip) && TooltipId != null) - { - TooltipService.AddTooltip(TooltipId.Value, (MarkupString)Tooltip); - } - } - - #endregion - - # region AttributesToSplat - - /// - /// Attributes ready for splatting in components. Guaranteed not null, unlike UnmatchedAttributes. - /// - internal IEnumerable> AttributesToSplat() - { - foreach (var attribute in UnmatchedAttributes ?? new Dictionary()) - { - yield return attribute; - } - - if (AppliedDisabled) - { - yield return new KeyValuePair("disabled", AppliedDisabled); - } - if (!string.IsNullOrWhiteSpace(Tooltip)) - { - yield return new KeyValuePair("aria-describedby", $"mb-tooltip-{TooltipId.Value}"); - } - } - internal IEnumerable> OtherAttributesToSplat() - { - foreach (var attribute in UnmatchedAttributes ?? new Dictionary()) - { - if (attribute.Key.StartsWith("on")) // this is only a heuristic, as it's not technically guaranteed that all attributes that are non-event do not start with "on". However, it is impossible to list all event names, as with .net 6, the list of event names is not limited anymore. - { - continue; - } - yield return attribute; - } - - if (AppliedDisabled) - { - yield return new KeyValuePair("disabled", AppliedDisabled); - } - if (!string.IsNullOrWhiteSpace(Tooltip)) - { - yield return new KeyValuePair("aria-describedby", $"mb-tooltip-{TooltipId.Value}"); - } - } - - internal IEnumerable> EventAttributesToSplat() - { - return UnmatchedAttributes ?? new Dictionary() - .Where(kvp => kvp.Key.StartsWith("on")); // this is only a heuristic, as it's not technically guaranteed that all attributes that are non-event do not start with "on". However, it is impossible to list all event names, as with .net 6, the list of event names is not limited anymore. - } - - #endregion - - #region CheckAttributeValidity - - /// - /// Material.Blazor allows a user to limit unmatched attributes that will be splatted to a defined list in . - /// This method checks validity against that list. - /// - private void CheckAttributeValidity() - { - if (UnmatchedAttributes is null) - { - return; - } - - if (UnmatchedAttributes.ContainsKey("disabled")) - { - throw new ArgumentException( - $"Material.Blazor: You cannot use 'disabled' attribute in {Utilities.GetTypeName(GetType())}. Material.Blazor reserves the disabled attribute for internal use; use the 'Disabled' parameter instead"); - } - - var forbidden = - UnmatchedAttributes.Keys - .Where(n => !n.StartsWith("on")) // heuristic: filter event attributes. Unlikely that other attributes will start with "on" as well. - .Where(n => !n.StartsWith("aria-")) // heuristic: filter aria attributes. Unlikely that other attributes will start with "aria-" as well. - .Where(n => !n.StartsWith("__internal")) // heuristic: filter .NET __internal_stopPropagation_onclick and similar generated attribute names. - .Except(EssentialSplattableAttributes); // filter common attribute names - - if (forbidden.Any()) - { - var message = $"You cannot use {string.Join(", ", forbidden.Select(x => $"'{x}'"))} attribute(s) in {Utilities.GetTypeName(GetType())}. Either remove the attribute or change 'ConstrainSplattableAttributes' or 'AllowedSplattableAttributes' in your MBCascadingDefaults"; - - throw new ArgumentException($"Material.Blazor: {message}"); - } - } - - #endregion - - #region Dispose - - private bool _disposed; - protected virtual void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing && TooltipId != null) - { - TooltipService.RemoveTooltip(TooltipId.Value); - TooltipId = null; - } - - _jsActionQueueSemaphore.Dispose(); - - _disposed = true; - } - - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - - #endregion - - #region InvokeJsVoidAsync - /// - /// Wraps calls to adding reference to the batching wrapper (if found). Only - /// use for components. - /// - /// - /// - /// - private protected async Task InvokeJsVoidAsync(string identifier, params object[] args) - { - await JsRuntime.InvokeVoidAsync(identifier, args).ConfigureAwait(false); - } - #endregion - - #region OnAfterRender - - /// - /// Material.Blazor components descending from _*must not*_ override "ComponentBase.OnAfterRender(bool)". - /// - protected sealed override void OnAfterRender(bool firstRender) - { - // for consistency, we only ever use OnAfterRenderAsync. To prevent ourselves from using OnAfterRender accidentally, we seal this method from here on. - } - - #endregion - - #region OnAfterRenderAsync - - /// - /// Material.Blazor components generally *should not* override this because it handles the case where components need - /// to be adjusted when inside an MBDialog or MBCard. - /// - protected override Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - try - { - if (ParentDialog != null && !ParentDialog.HasInstantiated) - { - ParentDialog.RegisterLayoutAction(this); - } - else - { - EnqueueJSInteropAction(InstantiateMcwComponent); - } - - HasInstantiated = true; - AddTooltip(); - } - catch (Exception e) - { - LoggingService.LogError($"Instantiating component {GetType().Name} failed with exception {e}"); - } - } - - return Task.CompletedTask; - } - - #endregion - - #region OnInitialized - - /// - /// Material.Blazor components use "OnInitializedAsync()" only. - /// - protected sealed override void OnInitialized() - { - // For consistency, we only ever use OnInitializedAsync. To prevent ourselves from using OnInitialized accidentally, we seal this method from here on. - - // the only thing we do here, is creating an ID for the tooltip, if we have one - if (!string.IsNullOrWhiteSpace(Tooltip)) - { - TooltipId = TooltipIdProvider.NextId(); - } - - LoggingService.SetLogger(Logger); - } - - #endregion - - #region OnParametersSet - - /// - /// Material.Blazor components use only. - /// - protected sealed override void OnParametersSet() - { - // For consistency, we only ever use OnParametersSetAsync. To prevent ourselves from using OnParametersSet accidentally, we seal this method from here on. - } - - #endregion - - #region OnParametersSetAsync - - /// - /// When overriding this, call await base.OnParametersSetAsync(); before any user code unless there is a very good reason not to. - /// - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - - CheckAttributeValidity(); - } - - #endregion - - #region OnDisabledSetAsync - - /// - /// Derived components can override this to get a callback from the setter when the consumer changes the value. - /// This allows a component to take action with Material Theme js to update the DOM to reflect the data change visually. - /// - /// - private protected virtual Task OnDisabledSetAsync() - { - return Task.CompletedTask; - } - - #endregion - - #region EnqueueJSInteropAction - - /// - /// Enqueues a javascript action (meaning instantiation, component value set or disabled value set) - /// and then flushes the queue one by one. This process is required to ensure that rapidly applied - /// values don't clash with one another, but are applied sequentially in order. - /// - /// - private protected void EnqueueJSInteropAction(Func action) - { - _jsActionQueue.Enqueue(action); - _ = DequeueActions(); - - async Task DequeueActions() - { - if (!_disposed) - { - await _jsActionQueueSemaphore.WaitAsync().ConfigureAwait(false); - } - - try - { - while (_jsActionQueue.TryDequeue(out var dequeuedAction)) - { - if (!_disposed) - { - await dequeuedAction().ConfigureAwait(false); - } - } - - if (!_disposed) - { - await InvokeAsync(StateHasChanged).ConfigureAwait(false); - } - } - finally - { - if (!_disposed) - { - _ = _jsActionQueueSemaphore.Release(); - } - } - } - } - #endregion -} diff --git a/Material.Blazor.MD3/Foundation.MD2/InputComponent.cs b/Material.Blazor.MD3/Foundation.MD2/InputComponent.cs deleted file mode 100644 index a9698d3c3..000000000 --- a/Material.Blazor.MD3/Foundation.MD2/InputComponent.cs +++ /dev/null @@ -1,409 +0,0 @@ -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Forms; -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; -using System.Threading.Tasks; - -namespace Material.Blazor.Internal.MD2; - -/// -/// This is like InputBase from Microsoft.AspNetCore.Components.Forms, except that it treats -/// [CascadingParameter] EditContext as optional. -/// -/// -public abstract class InputComponentMD2 : ComponentFoundationMD2 -{ - #region members - - private bool _previousParsingAttemptFailed; - private ValidationMessageStore _parsingValidationMessages; - private Type _nullableUnderlyingType; - private bool _hasSetInitialParameters; - - - /// - /// Gets the associated EditContext. - /// - protected EditContext EditContext { get; private set; } - - - /// - /// Gets the for the bound value. - /// - protected FieldIdentifier FieldIdentifier { get; private set; } - - - /// - /// Performs validation only if true. Used by to disable - /// form validation for the embedded , because a debounced field - /// should not be in a form. - /// -// private bool IgnoreFormField => this is MBDebouncedTextField or MultiSelectComponent>; - private bool IgnoreFormField => false; - - /// - /// Allows to return "true" habitually. - /// - private protected bool ForceShouldRenderToTrue { get; set; } = false; - - - /// - /// Allows to return "true" for the next render only. - /// - private bool AllowNextRender = false; - - - /// - /// Gets a string that indicates the status of the field being edited. This will include - /// some combination of "modified", "valid", or "invalid", depending on the status of the field. - /// - protected string FieldClass => !IgnoreFormField ? (EditContext?.FieldCssClass(FieldIdentifier) ?? string.Empty) : string.Empty; - - #endregion - - #region parameters - - [CascadingParameter] private EditContext CascadedEditContext { get; set; } - - - /// - /// Gets or sets the value of the input. This should be used with two-way binding. - /// - /// - /// @bind-Value="@model.PropertyName" - /// - [Parameter] public T Value { get; set; } - private T _cachedValue; - - - /// - /// Gets or sets a callback that updates the bound value. - /// - [Parameter] public EventCallback ValueChanged { get; set; } - - - /// - /// Gets or sets an expression that identifies the bound value. - /// - [Parameter] public Expression> ValueExpression { get; set; } - - #endregion - - #region AllowNextShouldRender - - private protected void AllowNextShouldRender() - { - AllowNextRender = true; - } - - #endregion - - #region ComponentValue - - /// - /// Gets or sets the value of the component. To be used by Material.Blazor components for binding to - /// native components, or to set the value in response to an event arising from the native component. - /// - - private T _componentValue; - private protected T ComponentValue - { - get => _componentValue; - set - { - LoggingService.LogTrace($"ComponentValue setter entered: _componentValue is '{_cachedValue?.ToString() ?? "null"}' and new value is'{value?.ToString() ?? "null"}'"); - - if (!EqualityComparer.Default.Equals(value, _componentValue)) - { - LoggingService.LogTrace($"ComponentValue setter changed _componentValue"); - - _componentValue = value; - _ = InvokeAsync(() => ValueChanged.InvokeAsync(value)); - - if (EditContext != null && !IgnoreFormField) - { - if (string.IsNullOrWhiteSpace(FieldIdentifier.FieldName)) - { - throw new Exception("Material.Blazor: ValueExpression must be defined for a field contained in an EditForm"); - } - else - { - EditContext?.NotifyFieldChanged(FieldIdentifier); - } - } - } - } - } - - - /// - /// Gets or sets the current value of the input, represented as a string. - /// - protected string ComponentValueAsString - { - get => FormatValueToString(ComponentValue); - set - { - _parsingValidationMessages?.Clear(); - - bool parsingFailed; - - if (_nullableUnderlyingType != null && string.IsNullOrEmpty(value)) - { - // Assume if it's a nullable type, null/empty inputs should correspond to default(T) - // Then all subclasses get nullable support almost automatically (they just have to - // not reject Nullable based on the type itself). - parsingFailed = false; - ComponentValue = default; - } - else if (TryParseValueFromString(value, out var parsedValue, out var validationErrorMessage)) - { - parsingFailed = false; - ComponentValue = parsedValue; - } - else - { - parsingFailed = true; - - if (EditContext != null && !IgnoreFormField) - { - if (_parsingValidationMessages == null) - { - _parsingValidationMessages = new ValidationMessageStore(EditContext); - } - - _parsingValidationMessages.Add(FieldIdentifier, validationErrorMessage); - - // Since we're not writing to ComponentValue, we'll need to notify about modification from here - EditContext.NotifyFieldChanged(FieldIdentifier); - } - } - - // We can skip the validation notification if we were previously valid and still are - if (parsingFailed || _previousParsingAttemptFailed) - { - EditContext?.NotifyValidationStateChanged(); - _previousParsingAttemptFailed = parsingFailed; - } - } - } - - #endregion - - #region FormatValueToString - - /// - /// Formats the value as a string. Derived classes can override this to determine the formating used for . - /// - /// The value to format. - /// A string representation of the value. - protected virtual string FormatValueToString(T value) - => value?.ToString(); - - #endregion - - #region GetExpressionCustomAttributes - - /// - /// Returns the custom attributes assocated with a field. Used by and to - /// look for a required attribute. - /// - /// - /// - /// - private static IEnumerable GetExpressionCustomAttributes(Expression> accessor) - { - var accessorBody = accessor.Body; - - // Unwrap casts to object - if (accessorBody is UnaryExpression unaryExpression - && unaryExpression.NodeType == ExpressionType.Convert - && unaryExpression.Type == typeof(object)) - { - accessorBody = unaryExpression.Operand; - } - - if (accessorBody is not MemberExpression memberExpression) - { - throw new ArgumentException($"The provided expression contains a {accessorBody.GetType().Name} which is not supported. {nameof(FieldIdentifier)} only supports simple member accessors (fields, properties) of an object."); - } - - return memberExpression.Member.GetCustomAttributes(); - } - - #endregion - - #region HasRequiredAttribute - - /// - /// Returns true if one of the custom attributes is the . Used by and to - /// look for a required attribute. - /// - /// - /// - /// - private protected static bool HasRequiredAttribute(Expression> accessor) - { - if (accessor == null) - { - return false; - } - - var customAttributes = GetExpressionCustomAttributes(accessor); - - return customAttributes.Any(a => a.GetType() == typeof(RequiredAttribute)); - } - - #endregion - - #region OnInitializedAsync - - /// - /// Components must call base.OnInitializedAsync() otherwise rendering in dialogs will be unpredictable. - /// - protected override async Task OnInitializedAsync() - { - await base.OnInitializedAsync(); - - if (EditContext != null && IgnoreFormField) - { - LoggingService.LogWarning($"{GetType()} is in a form but has EditContext features disabled because it is considered a valid Material.Blazor form field type"); - } - } - - #endregion - - #region OnParametersSetAsync - - /// - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - - var valuesEqual = EqualityComparer.Default.Equals(_cachedValue, Value); - LoggingService.LogTrace($"OnParametersSetAsync setter entered: _cachedValue is '{_cachedValue?.ToString() ?? "null"}' and Value is '{Value?.ToString() ?? "null"}' with equality '{valuesEqual}'"); - - if (!valuesEqual) - { - LoggingService.LogTrace($"OnParametersSetAsync changed _cachedValue from '{_cachedValue?.ToString() ?? "null"}' to '{Value?.ToString() ?? "null"}'"); - _cachedValue = Value; - - valuesEqual = EqualityComparer.Default.Equals(_componentValue, Value); - LoggingService.LogTrace($"OnParametersSetAsync setter: _componentValue is '{_componentValue?.ToString() ?? "null"}' and Value is '{Value?.ToString() ?? "null"}' with equality '{valuesEqual}'"); - - if (!valuesEqual) - { - LoggingService.LogTrace($"OnParametersSetAsync changed _componentValue from '{_componentValue?.ToString() ?? "null"}' to '{Value?.ToString() ?? "null"}'"); - - _componentValue = Value; - if (HasInstantiated) - { - EnqueueJSInteropAction(SetComponentValueAsync); - } - } - } - } - - #endregion - - #region SetParametersAsync - - // Would like to use however DocFX cannot resolve to references outside Material.Blazor. - // - // This implementation of GetSelectionAsync is largely untouched from our original fork of Steve Sanderson's - // RazorComponents.MaterialDesign repo. We've added the storage of a cached Value for use in - // OnSetParameters/OnSetParametersAsync. - public override async Task SetParametersAsync(ParameterView parameters) - { - parameters.SetParameterProperties(this); - - if (!_hasSetInitialParameters) - { - // This is the first run - // Could put this logic in OnInit, but its nice to avoid forcing people who override OnInit to call base.OnInit() - - if (ValueExpression != null) - { - FieldIdentifier = FieldIdentifier.Create(ValueExpression); - } - - EditContext = CascadedEditContext; - _nullableUnderlyingType = Nullable.GetUnderlyingType(typeof(T)); - _hasSetInitialParameters = true; - - LoggingService.LogTrace($"SetParametersAsync setting ComponentValue value to '{Value?.ToString() ?? "null"}'"); - - _cachedValue = Value; - _componentValue = Value; - } - else if (CascadedEditContext != EditContext) - { - // Not the first run, this is a re-render caused by the parent re-render - - // We don't support changing EditContext because it's messy to be clearing up state and event - // handlers for the previous one, and there's no strong use case. If a strong use case - // emerges, we can consider changing this. - throw new InvalidOperationException($"{GetType()} does not support changing the {nameof(EditContext)} dynamically."); - } - - // For derived components, retain the usual lifecycle with OnInit/OnParametersSet/etc. - await base.SetParametersAsync(ParameterView.Empty); - } - - #endregion - - #region ShouldRender - - /// - /// Material.Blazor components descending from MdcInputComponentMD2Base _*must not*_ override ShouldRender(). - /// - protected sealed override bool ShouldRender() - { - if (ForceShouldRenderToTrue || AllowNextRender) - { - AllowNextRender = false; - return true; - } - - return false; - } - - #endregion - - #region SetComponentValueAsync - - /// - /// Derived components can override this to get a callback from SetParameters(Async) when the consumer changes - /// the value. This allows a component to take action with Material Theme js to update the DOM to reflect - /// the data change visually. An example is a select where the relevant list item needs to be - /// automatically clicked to get Material Theme to update the value shown in the - /// <input> HTML tag. - /// - private protected virtual Task SetComponentValueAsync() - { - return Task.CompletedTask; - } - - #endregion - - #region TryParseValueFromString - - /// - /// Parses a string to create an instance of . Derived classes can override this to change how - /// interprets incoming values. - /// - /// The string value to be parsed. - /// An instance of . - /// If the value could not be parsed, provides a validation error message. - /// True if the value could be parsed; otherwise false. - protected virtual bool TryParseValueFromString(string value, out T result, out string validationErrorMessage) - { - throw new NotImplementedException($"This component does not parse string inputs. Bind to the '{nameof(ComponentValue)}' property, not '{nameof(ComponentValueAsString)}'."); - } - - #endregion -} diff --git a/Material.Blazor.MD3/Foundation.MD2/MultiSelectComponent.cs b/Material.Blazor.MD3/Foundation.MD2/MultiSelectComponent.cs deleted file mode 100644 index 1402a1bc7..000000000 --- a/Material.Blazor.MD3/Foundation.MD2/MultiSelectComponent.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Material.Blazor.MD2; -using Microsoft.AspNetCore.Components; -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Material.Blazor.Internal.MD2; - -/// -/// A DRY inspired abstract class providing and with validation. -/// -/// -public abstract class MultiSelectComponentMD2 : InputComponentMD2> where TListElement : MBSelectElementMD2 -{ - /// - /// A function delegate to return the parameters for @key attributes. If unused - /// "fake" keys set to GUIDs will be used. - /// - [Parameter] public Func GetKeysFunc { get; set; } - - - /// - /// The item list to be represented as radio buttons - /// - [Parameter] public IEnumerable Items { get; set; } - - - /// - /// Generates keys for repeated elements in the multi select component. - /// - private protected Func KeyGenerator { get; set; } - - -#nullable enable annotations - /// - /// The foundry to use for both leading and trailing icons. - /// IconFoundry="IconHelper.MIIcon()" - /// IconFoundry="IconHelper.FAIcon()" - /// IconFoundry="IconHelper.OIIcon()" - /// Overrides - /// - [Parameter] public IMBIconFoundry? IconFoundry { get; set; } -#nullable restore annotations - - - // Would like to use however DocFX cannot resolve to references outside Material.Blazor - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - - KeyGenerator = GetKeysFunc ?? delegate (T item) { return item; }; - } -} diff --git a/Material.Blazor.MD3/Foundation.MD2/SingleSelectComponent.cs b/Material.Blazor.MD3/Foundation.MD2/SingleSelectComponent.cs deleted file mode 100644 index 0037a2421..000000000 --- a/Material.Blazor.MD3/Foundation.MD2/SingleSelectComponent.cs +++ /dev/null @@ -1,108 +0,0 @@ -using Material.Blazor.MD2; -using Microsoft.AspNetCore.Components; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -namespace Material.Blazor.Internal.MD2; - -/// -/// A DRY inspired abstract class providing and with validation. -/// -/// -public abstract class SingleSelectComponentMD2 : InputComponentMD2 where TListElement : Material.Blazor.MD2.MBSelectElementMD2 -{ - /// - /// A function delegate to return the parameters for @key attributes. If unused - /// "fake" keys set to GUIDs will be used. - /// - [Parameter] public Func GetKeysFunc { get; set; } - - - /// - /// The item list to be represented as radio buttons - /// - [Parameter] public IEnumerable Items { get; set; } - private IEnumerable _cachedItems; - - - /// - /// The form of validation to apply when Value is first set, deciding whether to accept - /// a value outside the list, replace it with the first list item or - /// to throw an exception (the default). - /// Overrides - /// - [Parameter] public Material.Blazor.MD2.MBItemValidation? ItemValidation { get; set; } - - - /// - /// Generates keys for repeated elements in the single select list. - /// - private protected Func KeyGenerator { get; set; } - - - // Would like to use however DocFX cannot resolve to references outside Material.Blazor - protected override async Task OnParametersSetAsync() - { - await base.OnParametersSetAsync(); - - if ((Items == null && _cachedItems != null) || (Items != null && _cachedItems == null) || (Items != null && _cachedItems != null && !Items.SequenceEqual(_cachedItems))) - { - _cachedItems = Items; - - if (HasInstantiated) - { - var validatedValue = ValidateItemList(Items, Material.Blazor.MD2.MBItemValidation.DefaultToFirst).value; - - if (!validatedValue.Equals(Value)) - { - Value = validatedValue; - } - } - - AllowNextShouldRender(); - await InvokeAsync(StateHasChanged).ConfigureAwait(false); - } - } - - - // This method was added in the interest of DRY and is used by MBSelect & MBRadioButtonGroup - /// - /// Validates the item list against the validation specification. - /// - /// The item list - /// Specification of the required validation - /// The an indicator of whether an item was found and the item in the list matching or default if not found. - /// - public (bool hasValue, T value) ValidateItemList(IEnumerable> items, Material.Blazor.MD2.MBItemValidation appliedItemValidation) - { - var componentName = Utilities.GetTypeName(GetType()); - - if (items.GroupBy(i => i.SelectedValue).Any(g => g.Count() > 1)) - { - throw new ArgumentException(componentName + " has multiple enties in the List with the same SelectedValue"); - } - - if (!items.Any(i => Equals(i.SelectedValue, Value))) - { - switch (appliedItemValidation) - { - case Material.Blazor.MD2.MBItemValidation.DefaultToFirst: - var defaultValue = items.FirstOrDefault().SelectedValue; - AllowNextShouldRender(); - return (true, defaultValue); - - case Material.Blazor.MD2.MBItemValidation.Exception: - var itemList = "{ " + string.Join(", ", items.Select(item => $"'{item.SelectedValue}'")) + " }"; - throw new ArgumentException(componentName + $" cannot select item with data value of '{Value?.ToString()}' from {itemList}"); - - case Material.Blazor.MD2.MBItemValidation.NoSelection: - AllowNextShouldRender(); - return (false, default); - } - } - - return (true, Value); - } -} diff --git a/Material.Blazor.MD3/Foundation.MD2/Utilities.cs b/Material.Blazor.MD3/Foundation.MD2/Utilities.cs deleted file mode 100644 index 146173492..000000000 --- a/Material.Blazor.MD3/Foundation.MD2/Utilities.cs +++ /dev/null @@ -1,92 +0,0 @@ -using Material.Blazor.MD2; - -using System; -using System.Text.RegularExpressions; - -namespace Material.Blazor.Internal.MD2; - -internal static class Utilities -{ - /// - /// Compares two decimals to the tolerance of the specified number of decimal places. - /// - /// The first decimal for the comparison. - /// The second decimal for the comparison. - /// The number of decimal places to take into account. - /// True if the two numbers are equal within the specified tolerance, otherwise false. - public static bool DecimalEqual(decimal left, decimal right, int decimalPlaces = 6) - { - decimal tolerance = Convert.ToDecimal(Math.Pow(0.1, decimalPlaces)); - - return Math.Abs(left - right) <= tolerance; - } - - - /// - /// Compares two doubles to the tolerance of the specified number of decimal places. - /// - /// The first double for the comparison. - /// The second double for the comparison. - /// The number of decimal places to take into account. - /// True if the two numbers are equal within the specified tolerance, otherwise false. - public static bool DoubleEqual(double left, double right, int decimalPlaces = 6) - { - double tolerance = Math.Pow(0.1, decimalPlaces); - - return Math.Abs(left - right) <= tolerance; - } - - - /// - /// Generates a unique element name using a GUID to use as a CSS class name or tag element id. - /// - /// The unique name. - public static string GenerateUniqueElementName() - { - return "mb_" + Guid.NewGuid().ToString(); - } - - - /// - /// Returns a Material.Blazor CSS class - /// - /// - /// mb-align-left, ...center or ...right unless the value is returning a blank string. - public static string GetTextAlignClass(Material.Blazor.MD2.MBTextAlignStyle textAlign) - { - return textAlign switch - { - Material.Blazor.MD2.MBTextAlignStyle.Left => " mb-align-left", - Material.Blazor.MD2.MBTextAlignStyle.Center => " mb-align-center", - Material.Blazor.MD2.MBTextAlignStyle.Right => " mb-align-right", - _ => "", - }; - } - - - /// - /// Return a clean, unqualified class name. - /// - /// - /// The class name stripped of full qualification and trailing characters. - public static string GetTypeName(Type componentType) - { - return (new Regex("^[a-z,A-Z]*")).Match(componentType.Name).ToString(); - } - - -#nullable enable annotations - /// - /// Formats - /// - /// The to format. - /// The format string. - /// - public static string DateToString(DateTime dateTime, string format) - { - return dateTime.ToString(format); - } -#nullable restore annotations - - -} diff --git a/Material.Blazor.MD3/Foundation/ComponentFoundation.cs b/Material.Blazor.MD3/Foundation/ComponentFoundation.cs index 7c7bb00c5..28e29f75b 100644 --- a/Material.Blazor.MD3/Foundation/ComponentFoundation.cs +++ b/Material.Blazor.MD3/Foundation/ComponentFoundation.cs @@ -1,8 +1,4 @@ -using Material.Blazor; -using Microsoft.AspNetCore.Components; -using Microsoft.Extensions.Logging; -using Microsoft.JSInterop; -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -10,32 +6,57 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using Microsoft.Extensions.Logging; +using Microsoft.JSInterop; + namespace Material.Blazor.Internal; /// -/// The base class for all Material.Blazor components. +/// The base class for all Material.Blazor.MD2 components. /// public abstract class ComponentFoundation : ComponentBase, IDisposable { #region members - //[CascadingParameter] private IMBDialog ParentDialog { get; set; } + #region Cascading parameters + [CascadingParameter] protected MBCascadingDefaults CascadingDefaults { get; set; } = new MBCascadingDefaults(); + [CascadingParameter] private IMBDialog ParentDialog { get; set; } + #endregion + #region Parameters /// /// Gets or sets a collection of additional attributes that will be applied to the created element. /// [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary UnmatchedAttributes { get; set; } + /// /// Indicates whether the component is disabled. /// #pragma warning disable BL0007 // Component parameters should be auto properties - [Parameter] public bool? Disabled { get; set; } + [Parameter] + public bool? Disabled #pragma warning restore BL0007 // Component parameters should be auto properties + { + get => disabled; + set + { + if (disabled != value) + { + disabled = value; + + if (HasInstantiated) + { + EnqueueJSInteropAction(OnDisabledSetAsync); + } + } + } + } /// @@ -54,6 +75,8 @@ public abstract class ComponentFoundation : ComponentBase, IDisposable #pragma warning disable IDE1006 // Naming Styles [Parameter] public string @class { get; set; } #pragma warning restore IDE1006 // Naming Styles + protected string ActiveConditionalClasses => ConditionalCssClasses.ToString(); + /// /// Additional CSS style for the component. @@ -62,30 +85,25 @@ public abstract class ComponentFoundation : ComponentBase, IDisposable [Parameter] public string style { get; set; } #pragma warning restore IDE1006 // Naming Styles + /// /// A markup capable tooltip. /// [Parameter] public string Tooltip { get; set; } + #endregion + #region Injected properties [Inject] private IJSRuntime JsRuntime { get; set; } - //[Inject] private protected IMBTooltipService TooltipService { get; set; } - [Inject] private protected IMBLoggingService LoggingService { get; set; } - + [Inject] private protected ILogger Logger { get; set; } +// [Inject] private protected IMBTooltipService TooltipService { get; set; } + [Inject] private protected IMBLoggingService LoggingService { get; set; } - /// - /// A list of unmatched attributes that are used by and therefore essential for Material.Blazor. Works with - /// and . - /// - /// - /// Includes "formnovalidate", "max", "min", "role", "step", "tabindex", "type", "data-prev-page" - /// - private static readonly ImmutableArray EssentialSplattableAttributes = ImmutableArray.Create("formnovalidate", "max", "min", "role", "step", "tabindex", "type", "data-prev-page"); - private bool? disabled = null; + #endregion - protected string ActiveConditionalClasses => ConditionalCssClasses.ToString(); + #region Other members /// /// Gets a value for the component's 'id' attribute. @@ -116,6 +134,7 @@ public abstract class ComponentFoundation : ComponentBase, IDisposable /// private protected ConditionalCssClasses ConditionalCssClasses { get; } = new ConditionalCssClasses(); + /// /// The concurrent queue for javascript interop actions. /// @@ -127,24 +146,36 @@ public abstract class ComponentFoundation : ComponentBase, IDisposable /// private readonly SemaphoreSlim _jsActionQueueSemaphore = new(1, 1); + /// + /// A list of unmatched attributes that are used by and therefore essential for Material.Blazor.MD2. Works with + /// and . + /// + /// + /// Includes "formnovalidate", "max", "min", "role", "step", "tabindex", "type", "data-prev-page" + /// + private static readonly ImmutableArray EssentialSplattableAttributes = ImmutableArray.Create("formnovalidate", "max", "min", "role", "step", "tabindex", "type", "data-prev-page"); + private bool? disabled = null; + + #endregion + #endregion - #region AddTooltip + #region AddTooltip (MD2) /// /// Adds a tooltip if tooltip text has been provided. /// - //private protected void AddTooltip() - //{ - // if (!string.IsNullOrWhiteSpace(Tooltip) && TooltipId != null) - // { - // TooltipService.AddTooltip(TooltipId.Value, (MarkupString)Tooltip); - // } - //} + private protected void AddTooltip() + { + //if (!string.IsNullOrWhiteSpace(Tooltip) && TooltipId != null) + //{ + // TooltipService.AddTooltip(TooltipId.Value, (MarkupString)Tooltip); + //} + } #endregion - # region AttributesToSplat + # region AttributesToSplat (Tooltip elements) /// /// Attributes ready for splatting in components. Guaranteed not null, unlike UnmatchedAttributes. @@ -156,10 +187,10 @@ internal IEnumerable> AttributesToSplat() yield return attribute; } - //if (AppliedDisabled) - //{ - // yield return new KeyValuePair("disabled", AppliedDisabled); - //} + if (AppliedDisabled) + { + yield return new KeyValuePair("disabled", AppliedDisabled); + } if (!string.IsNullOrWhiteSpace(Tooltip)) { yield return new KeyValuePair("aria-describedby", $"mb-tooltip-{TooltipId.Value}"); @@ -176,10 +207,10 @@ internal IEnumerable> OtherAttributesToSplat() yield return attribute; } - //if (AppliedDisabled) - //{ - // yield return new KeyValuePair("disabled", AppliedDisabled); - //} + if (AppliedDisabled) + { + yield return new KeyValuePair("disabled", AppliedDisabled); + } if (!string.IsNullOrWhiteSpace(Tooltip)) { yield return new KeyValuePair("aria-describedby", $"mb-tooltip-{TooltipId.Value}"); @@ -197,7 +228,7 @@ internal IEnumerable> EventAttributesToSplat() #region CheckAttributeValidity /// - /// Material.Blazor allows a user to limit unmatched attributes that will be splatted to a defined list in . + /// Material.Blazor.MD2 allows a user to limit unmatched attributes that will be splatted to a defined list in . /// This method checks validity against that list. /// private void CheckAttributeValidity() @@ -210,7 +241,7 @@ private void CheckAttributeValidity() if (UnmatchedAttributes.ContainsKey("disabled")) { throw new ArgumentException( - $"Material.Blazor: You cannot use 'disabled' attribute in {Utilities.GetTypeName(GetType())}. Material.Blazor reserves the disabled attribute for internal use; use the 'Disabled' parameter instead"); + $"Material.Blazor.MD2: You cannot use 'disabled' attribute in {Utilities.GetTypeName(GetType())}. Material.Blazor.MD2 reserves the disabled attribute for internal use; use the 'Disabled' parameter instead"); } if (!CascadingDefaults.ConstrainSplattableAttributes) @@ -227,17 +258,17 @@ private void CheckAttributeValidity() .Except(EssentialSplattableAttributes) // filter common attribute names .Except(CascadingDefaults.AllowedSplattableAttributes, StringComparer.InvariantCultureIgnoreCase); // filter user-specified attribute names, ignoring case - //if (forbidden.Any()) - //{ - // var message = $"You cannot use {string.Join(", ", forbidden.Select(x => $"'{x}'"))} attribute(s) in {Utilities.GetTypeName(GetType())}. Either remove the attribute or change 'ConstrainSplattableAttributes' or 'AllowedSplattableAttributes' in your MBCascadingDefaults"; + if (forbidden.Any()) + { + var message = $"You cannot use {string.Join(", ", forbidden.Select(x => $"'{x}'"))} attribute(s) in {Utilities.GetTypeName(GetType())}. Either remove the attribute or change 'ConstrainSplattableAttributes' or 'AllowedSplattableAttributes' in your MBCascadingDefaults"; - // throw new ArgumentException($"Material.Blazor: {message}"); - //} + throw new ArgumentException($"Material.Blazor.MD2: {message}"); + } } #endregion - #region Dispose + #region Dispose (MD2 elements) private bool _disposed; protected virtual void Dispose(bool disposing) @@ -247,11 +278,13 @@ protected virtual void Dispose(bool disposing) return; } - if (disposing && TooltipId != null) - { - //TooltipService.RemoveTooltip(TooltipId.Value); - TooltipId = null; - } + //if (disposing && TooltipId != null) + //{ + // TooltipService.RemoveTooltip(TooltipId.Value); + // TooltipId = null; + //} + + _jsActionQueueSemaphore.Dispose(); _disposed = true; } @@ -326,10 +359,22 @@ private protected async Task InvokeJsVoidAsync(string identifier, params object[ } #endregion + #region InstantiateMcwComponent + + /// + /// Components should override this with a function to be called when Material.Blazor.MD2 wants to run Material Components Web instantiation via JS Interop - always gets called from , which should not be overridden. + /// + internal virtual Task InstantiateMcwComponent() + { + return Task.CompletedTask; + } + + #endregion + #region OnAfterRender /// - /// Material.Blazor components descending from _*must not*_ override "ComponentBase.OnAfterRender(bool)". + /// Material.Blazor.MD2 components descending from _*must not*_ override "ComponentBase.OnAfterRender(bool)". /// protected sealed override void OnAfterRender(bool firstRender) { @@ -338,10 +383,10 @@ protected sealed override void OnAfterRender(bool firstRender) #endregion - #region OnAfterRenderAsync + #region OnAfterRenderAsync (Tooltip elements) /// - /// Material.Blazor components generally *should not* override this because it handles the case where components need + /// Material.Blazor.MD2 components generally *should not* override this because it handles the case where components need /// to be adjusted when inside an MBDialog or MBCard. /// protected override Task OnAfterRenderAsync(bool firstRender) @@ -350,12 +395,21 @@ protected override Task OnAfterRenderAsync(bool firstRender) { try { + if (ParentDialog != null && !ParentDialog.HasInstantiated) + { + ParentDialog.RegisterLayoutAction(this); + } + else + { + EnqueueJSInteropAction(InstantiateMcwComponent); + } + HasInstantiated = true; - //AddTooltip(); + AddTooltip(); } catch (Exception e) { - //LoggingService.LogError($"Instantiating component {GetType().Name} failed with exception {e}"); + LoggingService.LogError($"Instantiating component {GetType().Name} failed with exception {e}"); } } @@ -364,22 +418,22 @@ protected override Task OnAfterRenderAsync(bool firstRender) #endregion - #region OnInitialized + #region OnInitialized (Tooltip elements) /// - /// Material.Blazor components use "OnInitializedAsync()" only. + /// Material.Blazor.MD2 components use "OnInitializedAsync()" only. /// protected sealed override void OnInitialized() { // For consistency, we only ever use OnInitializedAsync. To prevent ourselves from using OnInitialized accidentally, we seal this method from here on. // the only thing we do here, is creating an ID for the tooltip, if we have one - if (!string.IsNullOrWhiteSpace(Tooltip)) - { - //TooltipId = TooltipIdProvider.NextId(); - } + //if (!string.IsNullOrWhiteSpace(Tooltip)) + //{ + // TooltipId = TooltipIdProvider.NextId(); + //} - //LoggingService.SetLogger(Logger); + LoggingService.SetLogger(Logger); } #endregion @@ -387,7 +441,7 @@ protected sealed override void OnInitialized() #region OnParametersSet /// - /// Material.Blazor components use only. + /// Material.Blazor.MD2 components use only. /// protected sealed override void OnParametersSet() { @@ -409,4 +463,19 @@ protected override async Task OnParametersSetAsync() } #endregion + + #region OnDisabledSetAsync + + /// + /// Derived components can override this to get a callback from the setter when the consumer changes the value. + /// This allows a component to take action with Material Theme js to update the DOM to reflect the data change visually. + /// + /// + private protected virtual Task OnDisabledSetAsync() + { + return Task.CompletedTask; + } + + #endregion + } diff --git a/Material.Blazor.MD3/Foundation/InputComponent.cs b/Material.Blazor.MD3/Foundation/InputComponent.cs index 15fa4763a..bf307ccba 100644 --- a/Material.Blazor.MD3/Foundation/InputComponent.cs +++ b/Material.Blazor.MD3/Foundation/InputComponent.cs @@ -1,5 +1,4 @@ -using Material.Blazor.Internal.MD2; -using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using System; using System.Collections.Generic; diff --git a/Material.Blazor.MD3/Foundation/SingleSelectComponent.cs b/Material.Blazor.MD3/Foundation/SingleSelectComponent.cs index fe2d81493..798cfd92b 100644 --- a/Material.Blazor.MD3/Foundation/SingleSelectComponent.cs +++ b/Material.Blazor.MD3/Foundation/SingleSelectComponent.cs @@ -84,7 +84,7 @@ protected override async Task OnParametersSetAsync() if (items.GroupBy(i => i.SelectedValue).Any(g => g.Count() > 1)) { - throw new ArgumentException(componentName + " has multiple enties in the List with the same SelectedValue"); + throw new ArgumentException(componentName + " has multiple entities in the List with the same SelectedValue"); } if (!items.Any(i => Equals(i.SelectedValue, Value))) @@ -95,6 +95,7 @@ protected override async Task OnParametersSetAsync() var defaultValue = items.FirstOrDefault().SelectedValue; //AllowNextShouldRender(); return (true, defaultValue); + //return (false, default); case MBItemValidation.Exception: var itemList = "{ " + string.Join(", ", items.Select(item => $"'{item.SelectedValue}'")) + " }"; diff --git a/Material.Blazor.MD3/Material.Blazor.MD3.csproj b/Material.Blazor.MD3/Material.Blazor.MD3.csproj index a973ca63a..a755e39c4 100644 --- a/Material.Blazor.MD3/Material.Blazor.MD3.csproj +++ b/Material.Blazor.MD3/Material.Blazor.MD3.csproj @@ -26,6 +26,10 @@ false + + disable + + @@ -51,10 +55,11 @@ - - - - + + + + + @@ -82,7 +87,7 @@ - + @@ -179,27 +184,22 @@ - + - + - - - - + - + - + - + @@ -208,25 +208,38 @@ - + - + - - + + ISSUE - The UserAgent used by DownloadFile results in TrueType fonts. See the curl + commands in package.json that get around this behavior. + + + + --> + + + + + + + - + - - diff --git a/Material.Blazor.MD3/Model.MD2/Icon/IconFA.cs b/Material.Blazor.MD3/Model.MD2/Icon/IconFA.cs deleted file mode 100644 index 44c4796ea..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/IconFA.cs +++ /dev/null @@ -1,73 +0,0 @@ -using Microsoft.AspNetCore.Components.Rendering; - -namespace Material.Blazor.MD2; - -/// -/// Font Awesome icon. -/// -internal class IconFA : IMBIcon -{ - private string IconName { get; } - - - /// - public bool RequiresColorFilter => false; - - - /// - public IMBIcon.IconFragment Render => (@class, style, attributes) => (RenderTreeBuilder builder) => - { - builder.OpenElement(0, "i"); - builder.AddAttribute(1, "class", string.Join(" ", $"fa{IconStyleText}", IconName.ToLower(), Size, @class)); - if (style != null) - { - builder.AddAttribute(2, "style", style); - } - if (attributes != null) - { - builder.AddMultipleAttributes(3, attributes); - } - builder.CloseElement(); - }; - - - /// - /// The Font Awesome style. - /// Overrides - /// - private MBIconFAStyle Style { get; } - - - /// - /// The Font Awesome relative size. - /// Overrides - /// - private MBIconFARelativeSize RelativeSize { get; } - - - private string IconStyleText => Style.ToString().Substring(0, 1).ToLower(); - - private string Size => RelativeSize switch - { - MBIconFARelativeSize.Regular => "", - MBIconFARelativeSize.ExtraSmall => " fa-xs", - MBIconFARelativeSize.Small => " fa-sm", - MBIconFARelativeSize.Large => " fa-lg", - MBIconFARelativeSize.TwoTimes => " fa-2x", - MBIconFARelativeSize.ThreeTimes => " fa-3x", - MBIconFARelativeSize.FiveTimes => " fa-5x", - MBIconFARelativeSize.SevenTimes => " fa-7x", - MBIconFARelativeSize.TenTimes => " fa-10x", - _ => throw new System.NotImplementedException(), - }; - - -#nullable enable annotations - public IconFA(string iconName, IconFoundryFA? foundry = null) - { - IconName = iconName; - //Style = cascadingDefaults.AppliedIconFAStyle(foundry?.Style); - //RelativeSize = cascadingDefaults.AppliedIconFARelativeSize(foundry?.RelativeSize); - } -#nullable restore annotations -} diff --git a/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryFA.cs b/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryFA.cs deleted file mode 100644 index fd1e4f307..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryFA.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Material.Blazor.MD2; - -/// -/// Font Awesome foundry details. -/// -internal class IconFoundryFA : IMBIconFoundry -{ - /// - MBIconFoundryName IMBIconFoundry.FoundryName => MBIconFoundryName.FontAwesome; - - - /// - /// The Font Awesome style. - /// - public MBIconFAStyle? Style { get; } - - - /// - /// The Font Awesome relative size. - /// - public MBIconFARelativeSize? RelativeSize { get; } - - - public IconFoundryFA(MBIconFAStyle? style = null, MBIconFARelativeSize? relativesize = null) - { - Style = style; - RelativeSize = relativesize; - } -} diff --git a/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryMI.cs b/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryMI.cs deleted file mode 100644 index cded0462d..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryMI.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Material.Blazor.MD2; - -/// -/// Material Icons foundry details. -/// -internal class IconFoundryMI : IMBIconFoundry -{ - /// - MBIconFoundryName IMBIconFoundry.FoundryName => MBIconFoundryName.MaterialIcons; - - - /// - /// The Material Icons theme. - /// - public MBIconMITheme? Theme { get; } - - - public IconFoundryMI(MBIconMITheme? theme = null) - { - Theme = theme; - } -} diff --git a/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryOI.cs b/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryOI.cs deleted file mode 100644 index 6e2633bd2..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/IconFoundryOI.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Material.Blazor.MD2; - -/// -/// Open Iconic foundry details. -/// -internal class IconFoundryOI : IMBIconFoundry -{ - /// - MBIconFoundryName IMBIconFoundry.FoundryName => MBIconFoundryName.OpenIconic; -} diff --git a/Material.Blazor.MD3/Model.MD2/Icon/IconMI.cs b/Material.Blazor.MD3/Model.MD2/Icon/IconMI.cs deleted file mode 100644 index c48c2688f..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/IconMI.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Microsoft.AspNetCore.Components.Rendering; - -namespace Material.Blazor.MD2; - -/// -/// Material Icons icon. -/// -internal class IconMI : IMBIcon -{ - private string MaterialIconsTheme - { - get - { - return "material-icons" + Theme switch - { - MBIconMITheme.Filled => "", - MBIconMITheme.Outlined => "-outlined", - MBIconMITheme.Round => "-round", - MBIconMITheme.TwoTone => "-two-tone", - MBIconMITheme.Sharp => "-sharp", - _ => throw new System.NotImplementedException(), - }; - } - } - - - private string IconName { get; } - - - /// - public bool RequiresColorFilter => Theme == MBIconMITheme.TwoTone; - - - /// - public IMBIcon.IconFragment Render => (@class, style, attributes) => (RenderTreeBuilder builder) => - { - if (IconName == null) - { - return; - } - builder.OpenElement(0, "i"); - builder.AddAttribute(1, "class", string.Join(" ", MaterialIconsTheme, @class)); - if (style != null) - { - builder.AddAttribute(2, "style", style); - } - if (attributes != null) - { - builder.AddMultipleAttributes(3, attributes); - } - builder.AddContent(4, IconName.ToLower()); - builder.CloseElement(); - }; - - - /// - /// The Material Icons theme. - /// Overrides - /// - public MBIconMITheme Theme { get; } - - -#nullable enable annotations - public IconMI(string iconName, IconFoundryMI? foundry = null) - { - IconName = iconName; - } -#nullable restore annotations -} diff --git a/Material.Blazor.MD3/Model.MD2/Icon/IconOI.cs b/Material.Blazor.MD3/Model.MD2/Icon/IconOI.cs deleted file mode 100644 index 35e33756d..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/IconOI.cs +++ /dev/null @@ -1,44 +0,0 @@ -using Material.Blazor.Internal.MD2; -using Microsoft.AspNetCore.Components.Rendering; - -namespace Material.Blazor.MD2; - -/// -/// Open Iconic icon. -/// -internal class IconOI : IMBIcon -{ - private string IconName { get; } - - - /// - public bool RequiresColorFilter => false; - - - /// - public IMBIcon.IconFragment Render => (@class, style, attributes) => (RenderTreeBuilder builder) => - { - builder.OpenElement(0, "i"); - builder.AddAttribute(1, "class", string.Join(" ", "oi", @class)); - if (style != null) - { - builder.AddAttribute(2, "style", style); - } - builder.AddAttribute(3, "data-glyph", IconName); - if (attributes != null) - { - builder.AddMultipleAttributes(4, attributes); - } - builder.CloseElement(); - }; - - -#nullable enable annotations -#pragma warning disable IDE0060 // Remove unused parameter - public IconOI(string iconName, IconFoundryOI? foundry = null) - { - IconName = iconName; - } -#pragma warning restore IDE0060 // Remove unused parameter -#nullable restore annotations -} diff --git a/Material.Blazor.MD3/Model.MD2/Icon/MBIconHelper.cs b/Material.Blazor.MD3/Model.MD2/Icon/MBIconHelper.cs deleted file mode 100644 index 87b22296c..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/MBIconHelper.cs +++ /dev/null @@ -1,64 +0,0 @@ -using Material.Blazor.MD2; -using System; - -namespace Material.Blazor.MD2; - -/// -/// A helper class for defining which foundry to use for an icon. -/// -public class MBIconHelper : IMBIcon -{ - /// - public bool RequiresColorFilter => UnderlyingIcon.RequiresColorFilter; - - - /// - public IMBIcon.IconFragment Render => UnderlyingIcon.Render; - - private readonly IMBIcon UnderlyingIcon; - - - /// - /// Returns a new Material Icons foundry. - /// - /// Optional specifying the Material Icons theme. - /// to be passed to a Material.Blazor component. - public static IMBIconFoundry MIFoundry(MBIconMITheme? theme = null) => new IconFoundryMI(theme); - - - /// - /// Returns a new Font Awesome foundry. - /// - /// Optional specifying the Font Awesome style. - /// Optional specifying the Font Awesome relative size. - /// to be passed to a Material.Blazor component. - public static IMBIconFoundry FAFoundry(MBIconFAStyle? style = null, MBIconFARelativeSize? relativeSize = null) => new IconFoundryFA(style, relativeSize); - - - /// - /// Returns a Open Iconic foundry. - /// - /// to be passed to a Material.Blazor component. - public static IMBIconFoundry OIFoundry() => new IconFoundryOI(); - - -#nullable enable annotations - internal MBIconHelper(string iconName, IMBIconFoundry? foundry = null) - { - //if (cascadingDefaults is null) - //{ - // cascadingDefaults = new MBCascadingDefaults(); - //} - - MBIconFoundryName iconFoundry = MBIconFoundryName.MaterialIcons; - - UnderlyingIcon = iconFoundry switch - { - MBIconFoundryName.MaterialIcons => new IconMI(iconName, (IconFoundryMI?)foundry), - MBIconFoundryName.FontAwesome => new IconFA(iconName, (IconFoundryFA?)foundry), - MBIconFoundryName.OpenIconic => new IconOI(iconName, (IconFoundryOI?)foundry), - _ => throw new NotImplementedException(), - }; - } -#nullable restore annotations -} diff --git a/Material.Blazor.MD3/Model.MD2/Icon/MBIconHelper.md b/Material.Blazor.MD3/Model.MD2/Icon/MBIconHelper.md deleted file mode 100644 index 93ed0753c..000000000 --- a/Material.Blazor.MD3/Model.MD2/Icon/MBIconHelper.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -uid: U.MBIconHelper -title: MBIconHelper ---- -# MBIconHelper - -## Summary - -A helper class returning icon foundries for icons in use across Material.Blazor components. - -## Details - -- Has [three static methods (click for API documentation)](xref:Material.Blazor.MBIconHelper#methods) to return one of the following three icon foundries for any `IconFoundry` component parameter: - - Material Icons via `MBIconHelper.MIFoundry(Nullable)`; - - Font Awesome Icons via `MBIconHelper.FAFoundry(Nullable, Nullable)`; and - - Open Iconic Icons via `MBIconHelper.OIFoundry()`; -- Material Icons are included in the Material.Blazor bundled CSS; -- [Font Awesome Icons version 5](https://fontawesome.com/changelog/latest) are optional and can be included in your HTML `` with the CDN link: - ```html - - ``` -- [Open Iconic Icons version 1.1](https://useiconic.com/open) are also optional and can be included in your HTML `` with the CDN link: - ```html - - ``` - -  - -  - -[![Components](https://img.shields.io/static/v1?label=See&message=Utilities&color=orange)](xref:A.Utilities) -[![Docs](https://img.shields.io/static/v1?label=API%20Documentation&message=MBIconHelper&color=brightgreen)](xref:Material.Blazor.MBIconHelper) diff --git a/Material.Blazor.MD3/Model.MD2/Interfaces/IMBIcon.cs b/Material.Blazor.MD3/Model.MD2/Interfaces/IMBIcon.cs deleted file mode 100644 index 1bf17ce2f..000000000 --- a/Material.Blazor.MD3/Model.MD2/Interfaces/IMBIcon.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Components; -using System.Collections.Generic; - -namespace Material.Blazor.MD2; - -/// -/// Interface providing markup elements for an icon in a given foundry. -/// -public interface IMBIcon -{ - /// - /// A delegate that applies user-defined class, style, and other attributes to the icon. - /// - /// - /// - /// - /// - delegate RenderFragment IconFragment(string @class, string style, IEnumerable> attributes); - - - - /// - /// The delegate that combines all the information of the icon into markup. - /// - IconFragment Render { get; } - - - /// - /// Determines whether color should be set via a filter in the case of Material Icons two-tone theme. Presently partly implemented in toasts only. - /// - bool RequiresColorFilter { get; } -} diff --git a/Material.Blazor.MD3/Model.MD2/Interfaces/IMBIconFoundry.cs b/Material.Blazor.MD3/Model.MD2/Interfaces/IMBIconFoundry.cs deleted file mode 100644 index 9ee732d49..000000000 --- a/Material.Blazor.MD3/Model.MD2/Interfaces/IMBIconFoundry.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Material.Blazor.MD2; - -/// -/// An interface for supplying icon foundry information to components. -/// -public interface IMBIconFoundry -{ - /// - /// The foundry's name. - /// - MBIconFoundryName FoundryName { get; } -} diff --git a/Material.Blazor.MD3/Model.MD2/MBCascadingDefaults.cs b/Material.Blazor.MD3/Model.MD2/MBCascadingDefaults.cs deleted file mode 100644 index 27451d584..000000000 --- a/Material.Blazor.MD3/Model.MD2/MBCascadingDefaults.cs +++ /dev/null @@ -1,639 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Material.Blazor.MD2; - -/// -/// A class to be used as a cascading value setting defaults for your application. -/// -/// -/// For example the default style for -/// a material button is , however you can change that by setting -/// to another value and your whole application within the cascading value will change appearance. You can of course -/// nest cascading values in the normal manner. Exposes a property that is incremented each time another -/// property is updated; can be used with an `@key(CascadingDefaults.Version)` attribute to force components -/// to re-render when cascading defaults have updated. See . -/// -public class MBCascadingDefaults -{ - /************************************************************************************************************* - * - * - * ATTRIBUTE SPLATTING AND VALIDATION - * - * - ************************************************************************************************************/ - - private bool _constrainSplattableAttributes = false; - /// - /// Determines whether should throw an exception for invalid - /// unmatched HTML attributes passed to a component. - /// and - /// - public bool ConstrainSplattableAttributes { get => _constrainSplattableAttributes; set => SetParameter(ref _constrainSplattableAttributes, value); } - - - - private IEnumerable _allowedSplattableAttributes = Enumerable.Empty(); - /// - /// Further attributes that can be set as allowable when - /// performs unmatched attribute validation. Works with . - /// - public IEnumerable AllowedSplattableAttributes { get => _allowedSplattableAttributes; set => SetParameter(ref _allowedSplattableAttributes, value); } - - - - private MBItemValidation _itemValidation = MBItemValidation.Exception; - /// - /// Defines how radio button groups and selects validate mismtatch between item lists and initial value. - /// - public MBItemValidation ItemValidation { get => _itemValidation; set => SetParameter(ref _itemValidation, value); } - - /// - /// The applied item validation for selects and radio button groups. - /// - /// - /// - internal MBItemValidation AppliedItemValidation(MBItemValidation? criteria = null) => criteria ?? ItemValidation; - - - - /************************************************************************************************************* - * - * - * ICON FOUNDRIES AND THEIR OPTIONAL PARAMETERS - * - * - ************************************************************************************************************/ - - private MBIconFoundryName _iconFoundryName = MBIconFoundryName.MaterialIcons; - /// - /// The default foundry name, initialized to if not explicitly set. - /// - public MBIconFoundryName IconFoundryName { get => _iconFoundryName; set => SetParameter(ref _iconFoundryName, value); } - - /// - /// The foundry name to apply within a Material.Blazor component. - /// - /// The foundry name parameter passed to the component - /// The to apply. - internal MBIconFoundryName AppliedIconFoundryName(MBIconFoundryName? iconFoundryName = null) => iconFoundryName ?? IconFoundryName; - - - - private MBIconMITheme _iconMITheme = MBIconMITheme.Filled; - /// - /// The default Material Icons theme, initialized to if not explicitly set. - /// - public MBIconMITheme IconMITheme { get => _iconMITheme; set => SetParameter(ref _iconMITheme, value); } - - /// - /// The Material Icons theme to apply within a Material.Blazor component. - /// - /// The theme parameter passed to the component - /// The to apply. - internal MBIconMITheme AppliedIconMITheme(MBIconMITheme? iconMITheme = null) => iconMITheme ?? IconMITheme; - - - - private MBIconFAStyle _iconFAStyle = MBIconFAStyle.Solid; - /// - /// The default Font Awesome style, initialized to if not explicitly set. - /// - public MBIconFAStyle IconFAStyle { get => _iconFAStyle; set => SetParameter(ref _iconFAStyle, value); } - - /// - /// The Font Awesome style to apply within a Material.Blazor component. - /// - /// The style parameter passed to the component - /// The to apply. - internal MBIconFAStyle AppliedIconFAStyle(MBIconFAStyle? iconFAStyle = null) => iconFAStyle ?? IconFAStyle; - - - - private MBIconFARelativeSize _iconFARelativeSize = MBIconFARelativeSize.Regular; - /// - /// The default Font Awesome relative size, initialized to if not explicitly set. - /// - public MBIconFARelativeSize IconFARelativeSize { get => _iconFARelativeSize; set => SetParameter(ref _iconFARelativeSize, value); } - - /// - /// The Font Awesome relative size to apply within a Material.Blazor component. - /// - /// The relative size parameter passed to the component - /// The to apply. - internal MBIconFARelativeSize AppliedIconFARelativeSize(MBIconFARelativeSize? iconFARelativeSize = null) => iconFARelativeSize ?? IconFARelativeSize; - - - - /************************************************************************************************************* - * - * - * MDC CORE COMPONENTS - * - * - ************************************************************************************************************/ - - private bool _disabled = false; - /// - /// The default disabled state. - /// - public bool Disabled { get => _disabled; set => SetParameter(ref _disabled, value); } - - /// - /// The disabled state to apply. - /// - /// The required disabled state - /// The to apply. - internal bool AppliedDisabled(bool? disabled = null) => disabled ?? Disabled; - - - - private MBButtonStyle _buttonStyle = MBButtonStyle.Text; - /// - /// The default style for an , initialized to if not explicitly set. - /// - public MBButtonStyle ButtonStyle { get => _buttonStyle; set => SetParameter(ref _buttonStyle, value); } - - private MBButtonStyle? _cardButtonStyle = null; - /// - /// The default style for a card action button/ in an , returns the value of if not explicitly set. - /// - public MBButtonStyle CardActionButtonStyle { get => _cardButtonStyle ?? ButtonStyle; set => SetParameter(ref _cardButtonStyle, value); } - - private MBButtonStyle? _dialogButtonStyle = null; - /// - /// The default style for a dialog action button/ in an , returns the value of if not explicitly set. - /// - public MBButtonStyle DialogActionButtonStyle { get => _dialogButtonStyle ?? ButtonStyle; set => SetParameter(ref _dialogButtonStyle, value); } - - /// - /// The style to apply within an . and must - /// pass a reference to themselves (this) to reference the relevant default. - /// - /// The style parameter passed to the - /// The 's card reference (null if button is not in a card) - /// The 's card reference (null if button is not in a dialog) - /// The to apply. - internal MBButtonStyle AppliedStyle(MBButtonStyle? style, MBCard card, MBDialog dialog) - { - if (style != null) - { - return (MBButtonStyle)style; - } - - if (card != null) - { - return CardActionButtonStyle; - } - - if (dialog != null) - { - return DialogActionButtonStyle; - } - - return ButtonStyle; - } - - - - private MBCardStyle _cardStyle = MBCardStyle.Default; - /// - /// The default style for an , initialized to if not explicitly set. - /// - public MBCardStyle CardStyle { get => _cardStyle; set => SetParameter(ref _cardStyle, value); } - - /// - /// The style to apply to an . - /// - /// The style parameter passed to the - /// The to apply. - internal MBCardStyle AppliedStyle(MBCardStyle? style = null) => style ?? CardStyle; - - - - private string _dateFormat = "D"; - /// - /// The default date format for an , initialized to "D" (culture specific C# long date pattern) if not explicitly set. - /// - public string DateFormat { get => _dateFormat; set => SetParameter(ref _dateFormat, value); } - - /// - /// The date format to apply to an . - /// - /// The style parameter passed to the - /// The to apply. - internal string AppliedDateFormat(string format = null) => string.IsNullOrWhiteSpace(format) ? DateFormat : format; - - - - private MBListStyle _listStyle = MBListStyle.None; - /// - /// The default style for an , initialized to if not explicitly set. - /// - public MBListStyle ListStyle { get => _listStyle; set => SetParameter(ref _listStyle, value); } - - /// - /// The style to apply to an . - /// - /// The style parameter passed to the - /// The to apply. - internal MBListStyle AppliedStyle(MBListStyle? style = null) => style ?? ListStyle; - - - - private MBListType _listType = MBListType.Regular; - /// - /// The default type for an , initialized to if not explicitly set. - /// - public MBListType ListType { get => _listType; set => SetParameter(ref _listType, value); } - - /// - /// The style to apply to an . - /// - /// The style parameter passed to the - /// The to apply. - internal MBListType AppliedType(MBListType? type = null) => type ?? ListType; - - - - private MBSelectInputStyle _selectInputStyle = MBSelectInputStyle.Outlined; - /// - /// The default style for an , initialized to if not explicitly set. - /// - /// - /// Also applied to . - /// - public MBSelectInputStyle SelectInputStyle { get => _selectInputStyle; set => SetParameter(ref _selectInputStyle, value); } - - /// - /// The style to apply to an . - /// - /// The style parameter passed to the - /// The to apply. - internal MBSelectInputStyle AppliedStyle(MBSelectInputStyle? style = null) => style ?? SelectInputStyle; - - - - private MBTextAlignStyle _textAlignStyle = MBTextAlignStyle.Default; - /// - /// The default text alignment style for an , an or , initialized to if not explicitly set. - /// - /// - /// Also applied to , , , , and . - /// - public MBTextAlignStyle TextAlignStyle { get => _textAlignStyle; set => SetParameter(ref _textAlignStyle, value); } - - /// - /// The text alignment style to apply to an , an or . - /// - /// The text align style parameter passed to the , or - /// The to apply. - internal MBTextAlignStyle AppliedStyle(MBTextAlignStyle? style = null) => style ?? TextAlignStyle; - - - - private MBTextInputStyle _textInputStyle = MBTextInputStyle.Outlined; - /// - /// The default style for an or an , initialized to if not explicitly set. - /// - /// - /// Also applied to , , and . - /// - public MBTextInputStyle TextInputStyle { get => _textInputStyle; set => SetParameter(ref _textInputStyle, value); } - - /// - /// The text input style to apply to an or an . - /// - /// The text input style parameter passed to the or - /// The to apply. - internal MBTextInputStyle AppliedStyle(MBTextInputStyle? style = null) => style ?? TextInputStyle; - - - - /************************************************************************************************************* - * - * - * PLUS COMPONENTS - * - * - ************************************************************************************************************/ - - private MBDateSelectionCriteria _dateSelectionCriteria = MBDateSelectionCriteria.AllowAll; - /// - /// The default date selection criteria for a , initialized to if not explicitly set. - /// - public MBDateSelectionCriteria DateSelectionCriteria { get => _dateSelectionCriteria; set => SetParameter(ref _dateSelectionCriteria, value); } - - /// - /// The date selection criteria to apply to a . - /// - /// The criteria style parameter passed to the - /// The to apply. - internal MBDateSelectionCriteria AppliedDateSelectionCriteria(MBDateSelectionCriteria? criteria = null) => criteria ?? DateSelectionCriteria; - - - - //private DateTime datePickerDefaultMinDate = MBDatePicker.MinAllowableDate; - ///// - ///// The default minimum date . - ///// - //public DateTime DatePickerDefaultMinDate { get => datePickerDefaultMinDate; set => SetParameter(ref datePickerDefaultMinDate, value); } - - ///// - ///// The minimum date to apply to a . - ///// - ///// The criteria style parameter passed to the - ///// The to apply. - //internal DateTime AppliedDatePickerDefaultMinDate(DateTime minDate) => minDate == default || minDate <= MBDatePicker.MinAllowableDate ? DatePickerDefaultMinDate : minDate; - - - - //private DateTime datePickerDefaultMaxDate = MBDatePicker.MaxAllowableDate; - ///// - ///// The default maximum date . - ///// - //public DateTime DatePickerDefaultMaxDate { get => datePickerDefaultMaxDate; set => SetParameter(ref datePickerDefaultMaxDate, value); } - - ///// - ///// The maximum date to apply to a . - ///// - ///// The criteria style parameter passed to the - ///// The to apply. - //internal DateTime AppliedDatePickerDefaultMaxDate(DateTime maxDate) => maxDate == default || maxDate >= MBDatePicker.MaxAllowableDate ? DatePickerDefaultMaxDate : maxDate; - - - - private int _debounceInterval = 300; - /// - /// The default debounce interval in milliseconds for a , initialized to 300 milliseconds if not explicitly set. - /// - public int DebounceInterval { get => _debounceInterval; set => SetParameter(ref _debounceInterval, value); } - - /// - /// The text debounce interval in milliseconds to apply to an . - /// - /// The text input style parameter passed to the - /// The interval in milliseconds to apply. - internal int AppliedDebounceInterval(int? debounceInterval = null) => debounceInterval ?? 300; - - - - /************************************************************************************************************* - * - * - * COMPONENT DENSITY - * - * - ************************************************************************************************************/ - - private MBDensity _themeDensity = MBDensity.Default; - /// - /// The default density for an all components. Any individual component density that is set overrides theme density. - /// - public MBDensity ThemeDensity { get => _themeDensity; set => SetParameter(ref _themeDensity, value); } - - - private MBDensity? _buttonDensity = null; - /// - /// The default density for an , defaults to if not explicitly set. - /// - public MBDensity ButtonDensity { get => _buttonDensity ?? ThemeDensity; set => SetParameter(ref _buttonDensity, value); } - - /// - /// The density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedButtonDensity(MBDensity? density) => density ?? ButtonDensity; - - - - private MBDensity? _checkboxDensity = null; - /// - /// The default density for an , defaults to if not explicitly set. - /// - public MBDensity CheckboxDensity { get => _checkboxDensity ?? ThemeDensity; set => SetParameter(ref _checkboxDensity, value); } - - /// - /// The density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedCheckboxDensity(MBDensity? density) => density ?? CheckboxDensity; - - - - private MBDensity? _dataTableDensity = null; - /// - /// The default density for an , defaults to if not explicitly set. - /// - public MBDensity DataTableDensity { get => _dataTableDensity ?? ThemeDensity; set => SetParameter(ref _dataTableDensity, value); } - - /// - /// The density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedDataTableDensity(MBDensity? density) => density ?? DataTableDensity; - - - - private MBDensity? _iconButtonDensity = null; - /// - /// The default density for an or , defaults to if not explicitly set. - /// - public MBDensity IconButtonDensity { get => _iconButtonDensity ?? ThemeDensity; set => SetParameter(ref _iconButtonDensity, value); } - - /// - /// The density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedIconButtonDensity(MBDensity? density) => density ?? IconButtonDensity; - - - - private MBDensity? _listSingleLineDensity = null; - /// - /// The default single line density for an , defaults to if not explicitly set. - /// - public MBDensity ListSingleLineDensity { get => _listSingleLineDensity ?? ThemeDensity; set => SetParameter(ref _listSingleLineDensity, value); } - - /// - /// The single density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedListSingleLineDensity(MBDensity? density) => density ?? IconButtonDensity; - - - - private MBDensity? _radioButtonDensity = null; - /// - /// The default density for an or , defaults to if not explicitly set. - /// - public MBDensity RadioButtonDensity { get => _radioButtonDensity ?? ThemeDensity; set => SetParameter(ref _radioButtonDensity, value); } - - /// - /// The density to apply to an or . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedRadioButtonDensity(MBDensity? density) => density ?? RadioButtonDensity; - - - - private MBDensity? _selectDensity = null; - /// - /// The default density for an , defaults to if not explicitly set. - /// - public MBDensity SelectDensity { get => _selectDensity ?? ThemeDensity; set => SetParameter(ref _selectDensity, value); } - - /// - /// The density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedSelectDensity(MBDensity? density) => density ?? SelectDensity; - - - - private MBDensity? _switchDensity = null; - /// - /// The default density for an , defaults to if not explicitly set. - /// - public MBDensity SwitchDensity { get => _switchDensity ?? ThemeDensity; set => SetParameter(ref _switchDensity, value); } - - /// - /// The density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedSwitchDensity(MBDensity? density) => density ?? SwitchDensity; - - - - private MBDensity? _tabBarDensity = null; - /// - /// The default density for an , defaults to if not explicitly set. - /// - public MBDensity TabBarDensity { get => _tabBarDensity ?? ThemeDensity; set => SetParameter(ref _tabBarDensity, value); } - - /// - /// The density to apply to an . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedTabBarDensity(MBDensity? density) => density ?? TabBarDensity; - - - - private MBDensity? _textFieldDensity = null; - /// - /// The default density for an , , , , - /// or , defaults to if not explicitly set. - /// - public MBDensity TextFieldDensity { get => _textFieldDensity ?? ThemeDensity; set => SetParameter(ref _textFieldDensity, value); } - - /// - /// The density to apply to an an , , , , - /// or , initialized to . - /// - /// The density parameter passed to the - /// - internal MBDensity AppliedTextFieldDensity(MBDensity? density) => density ?? TextFieldDensity; - - - /// - /// A helper class for density, returning the density CSS class to be applied plus an indicator of whether to apply the class. - /// - internal class DensityInfo - { - public bool ApplyCssClass { get; set; } - public string CssClassName { get; set; } - } - - - /// - /// Returns a object for the given density parameter. - /// - /// - /// - internal DensityInfo GetDensityCssClass(MBDensity density) - { - return new DensityInfo() - { - ApplyCssClass = density != MBDensity.Default, - CssClassName = density switch - { - MBDensity.Default => "dense-default", - MBDensity.Minus1 => "dense--1", - MBDensity.Minus2 => "dense--2", - MBDensity.Comfortable => "dense-comfortable", - MBDensity.Minus3 => "dense--3", - MBDensity.Compact => "dense-compact", - MBDensity.Minus4 => "dense--4", - MBDensity.Minus5 => "dense--5", - _ => throw new NotImplementedException(), - } - }; - } - - - - /************************************************************************************************************* - * - * - * COMPONENT ACCESSIBILITY - * - * - ************************************************************************************************************/ - - private bool _TouchTarget = true; - /// - /// Determines whether to apply touch targets for accessibility. Defaults to true. - /// - public bool TouchTarget { get => _TouchTarget; set => SetParameter(ref _TouchTarget, value); } - internal bool AppliedTouchTarget(bool? touchTarget) => touchTarget ?? TouchTarget; - - - - /************************************************************************************************************* - * - * - * VERSION - * - * - ************************************************************************************************************/ - - /// - /// Gets incremented for every property update. Use Version to force Blazor to re-render components or <div> blocks - /// with the @key attribute. - /// - public int Version { get; private set; } = 0; - - - - /// - /// Returns a shallow copy of the cascading defaults. - /// - /// - public MBCascadingDefaults ShallowCopy() - { - return (MBCascadingDefaults)MemberwiseClone(); - } - - - - private void SetParameter(ref T privateParameter, T value) - { - if (!value.Equals(privateParameter)) - { - privateParameter = value; - - Version++; - } - } -} diff --git a/Material.Blazor.MD3/Model.MD2/MBEnumerations.cs b/Material.Blazor.MD3/Model.MD2/MBEnumerations.cs deleted file mode 100644 index c376949f3..000000000 --- a/Material.Blazor.MD3/Model.MD2/MBEnumerations.cs +++ /dev/null @@ -1,781 +0,0 @@ -using System; - -namespace Material.Blazor.MD2; - -/// -/// Style for an per Material Theme styling. -/// has a default of -/// -public enum MBButtonStyle -{ - /// - /// Contained style, raised. - /// - ContainedRaised, - - /// - /// Contained style, unelevated. - /// - ContainedUnelevated, - - /// - /// Outlined style. - /// - Outlined, - - /// - /// Regular or default style. This is the default. - /// - Text -} - - -/// -/// Style for an per Material Theme styling. -/// has a default of -/// -public enum MBCardStyle -{ - /// - /// Default style. This is the default. - /// - Default, - - /// - /// Outlined style. - /// - Outlined -} - - -/// -/// Size for an . -/// -public enum MBCircularProgressSize -{ - /// - /// A small sized circular progress. - /// - Small, - - /// - /// A medium sized circular progress. - /// - Medium, - - /// - /// A large sized circular progress. This is the default. - /// - Large -} - - -/// -/// Type for an . -/// -public enum MBCircularProgressType -{ - /// - /// An indeterminate circular progress. - /// - Indeterminate, - - /// - /// A determinate circular progress with a value from 0 to 1. - /// - Determinate, - - /// - /// A closed circular progress. - /// - Closed -} - - -/// -/// Determines the allowed selections in -/// has a default of -/// -public enum MBDateSelectionCriteria -{ - /// - /// Allow weekdays and weekends. This is the default. - /// - AllowAll, - - /// - /// Limit selection to weekends only. - /// - WeekendsOnly, - - /// - /// Limit selection to weekdays only. - /// - WeekdaysOnly -} - - -/// -/// Determines the density of a component -/// Defaults to -/// -public enum MBDensity -{ - /// - /// Density level -5. - /// - Minus5, - - /// - /// Density level -4. - /// - Minus4, - - /// - /// Density level -3. - /// - Minus3, - - /// - /// Compact density, equal to -3. - /// - Compact, - - /// - /// Density level -2. - /// - Minus2, - - /// - /// Comfortable density, equal to -2. - /// - Comfortable, - - /// - /// Density level -1. - /// - Minus1, - - /// - /// Default density / zero. - /// - Default -} - - -/// -/// Type for an . -/// -public enum MBFloatingActionButtonType -{ - /// - /// FAB regular variant. - /// - Regular, - - /// - /// FAB mini variant. - /// - Mini, - - /// - /// FAB extended variant without icon. - /// - ExtendedNoIcon, - - /// - /// FAB extended variant with leading icon. - /// - ExtendedLeadingIcon, - - /// - /// FAB extended variant with trailing icon. - /// - ExtendedTrailingIcon, -} - - -/// -/// Specifies whether to use Material Icons from Google or Font Awesome Icons. -/// See -/// has a default of -/// -public enum MBIconFoundryName -{ - /// - /// Google Material Icons. This is the default. - /// - MaterialIcons, - - /// - /// Font Awesome Icons. - /// - FontAwesome, - - /// - /// Open Iconic Icons. - /// - OpenIconic -} - - - -/// -/// Sets the Google Material Icons theme. -/// See , and -/// has a default of -/// -public enum MBIconMITheme -{ - /// - /// Filled theme, class="material-icons". This is the default. - /// - Filled, - - /// - /// Outlined theme, class="material-icons-outlined". - /// - Outlined, - /// - /// Rounded theme, class="material-icons-round". - /// - Round, - - /// - /// Two-tone theme, class="material-icons-two-tone". Note that two-tone does not use css color, but filter instead. - /// - TwoTone, - - /// - /// Sharp theme, class="material-icons-sharp". - /// - Sharp -} - - - -/// -/// Sets the Font Awesome style. -/// See , and -/// has a default of (all other styles except require a paid-for Font Awesome PRO licence) -/// -public enum MBIconFAStyle -{ - /// - /// Solid style, class="fas ...". This is the default. - /// - Solid, - - /// - /// Regular style, class="far ...". Requires a paid-for Font Awesome PRO licence. - /// - Regular, - - /// - /// Light style, class="fal ...". Requires a paid-for Font Awesome PRO licence. - /// - Light, - - /// - /// Duotone style, class="fad ...". Requires a paid-for Font Awesome PRO licence. - /// - Duotone, - - /// - /// Brands style, class="fab ...". - /// - Brands -} - - -/// -/// Sets the Font Awesome relative size. -/// See , and -/// has a default of -/// -public enum MBIconFARelativeSize -{ - /// - /// Regular relative size (no markup applied). This is the default. - /// - Regular, - - /// - /// Extra small relative size, class="... fa-xs" - /// - ExtraSmall, - - /// - /// Small relative size: class="... fa-sm" - /// - Small, - - /// - /// Large relative size, class="... fa-lg" - /// - Large, - - /// - /// Two times relative size. class="... fa-2x" - /// - TwoTimes, - - /// - /// Three times relative size, class="... fa-3x" - /// - ThreeTimes, - - /// - /// Five times relative size, class="... fa-5x" - /// - FiveTimes, - - /// - /// Seven times relative size, class="... fa-7x" - /// - SevenTimes, - - /// - /// Ten times relative size, class="... fa-10x" - /// - TenTimes -} - - -/// -/// Determines how an responds to user events. -/// -public enum MBInputEventType -{ - /// - /// Emits events only when the thumb is released via an change event. - /// - OnChange, - - /// - /// Emits debounced events during slider movement via input events. Debouncing requires the slider to be still for a period before emitting an event. - /// - OnInputDebounced, - - /// - /// Emits throttled events during slider movement via input events. Throttling emits events even while the slider is moving. - /// - OnInputThrottled -} - - -/// -/// A helper to determine how a or should handle an intial bound value not matching elements in the value list. -/// has a default of -/// -public enum MBItemValidation -{ - /// - /// Sets the bound value to the first item in the list. - /// - DefaultToFirst, - - /// - /// Throws an exception. This is the default. - /// - Exception, - - /// - /// Does nothing, leaving the bound value as it is. - /// - NoSelection -} - - -/// -/// Type for an . -/// -public enum MBLinearProgressType -{ - /// - /// An indeterminate linear progress. - /// - Indeterminate, - - /// - /// A determinate linear progress with a value from 0 to 1. - /// - Determinate, - - /// - /// A closed linear progress. - /// - Closed -} - - -/// -/// Style for an . The variety borrows card markup matching . -/// has a default of -/// -public enum MBListStyle -{ - /// - /// No styling applied. This is the default. - /// - None, - - /// - /// Borrows card markup matching . - /// - Outlined -} - - -/// -/// Type for an . -/// has a default of -/// -public enum MBListType -{ - /// - /// A regular list. This is the default. - /// - Regular, - - /// - /// Applies the mdc-deprecated-list--dense CSS class. - /// - Dense, - - /// - /// Applies the mdc-deprecated-list--avatar CSS class. - /// - Avatar -} - -/// -/// Type for the level of logging performed by M.B -/// They follow the Microsoft.Logging.LogLevel definition -/// -public enum MBLoggingLevel -{ - Trace = 0, - Debug = 1, - Information = 2, - Warning = 3, - Error = 4, - Critical = 5, - None = 0, -} - - -/// -/// Determines the positioning and width of a menu surface. -/// -public enum MBMenuSurfacePositioning -{ - /// - /// Placed with display: relative. and assuming a width determined by its contents. - /// - Regular, - - - /// - /// Width set to match the parent anchor - incompatible with Fixed position. - /// - FullWidth, - - - /// - /// Places the menu with fixed positioning. - /// - Fixed -} - - -///// **** Would prefer to use the following line for the summary however this fails to produce inline documentation and causes the following DocFX warning: -///// **** [20-07-16 02:56:26.727]Warning:[MetadataCommand.ExtractMetadata]Invalid triple slash comment is ignored: - - -///// A helper to determine the magnitude adjustment when displaying or editing values using and . -/// -/// A helper to determine the magnitude adjustment when displaying or editing values using numeric input fields. -/// -public enum MBNumericInputMagnitude -{ - /// - /// Normal numbers requiring no adjustment. - /// - Normal = 0, - - /// - /// Percentages where the numeric input needs to multiply the value by 100 when displaying or editing (formatted display can be handled by standard percent C# formatting). - /// - Percent = 2, - - /// - /// Basis points where the numeric input needs to multiply the value by 10 000 when displaying or editing (formatted display is not handled by standard percent C# formatting which lacks support for basis points). - /// - BasisPoints = 4 -} - - -///// Describes the result of a search from the component. -/// -/// Describes the result of a search from the component. -/// -public enum MBSearchResultTypes -{ - /// - /// No items were found that match the search string. - /// - NoMatchesFound, - - /// - /// A full match has been found. - /// - FullMatchFound, - - /// - /// One or more matches were found, but fewer than the threshold for too many items - /// to be indicated. - /// - PartialMatchesFound, - - /// - /// Too many items were found, resulting in a zero length search result list. - /// - TooManyItemsFound, -} - - -/// -/// Material Theme select input style applied to . -/// Applied also to -/// has a default of -/// -public enum MBSelectInputStyle -{ - /// - /// The filled style. This is the default. - /// - Filled, - - /// - /// The outlined style. - /// - Outlined -} - - -/// -/// Determines whether a displays the label (left hand element), value (right hand element) or both. -/// Defaults to -/// -public enum MBShieldType -{ - /// - /// Show both label (left hand element) and value (right hand element). This is the default. - /// - LabelAndValue, - - /// - /// Show both label (left hand element) only. - /// - LabelOnly, - - /// - /// Show both value (right hand element) only. - /// - ValueOnly -} - - -/// -/// Determines the type of an . -/// -public enum MBSliderType -{ - /// - /// Continuous value. - /// - Continuous, - - /// - /// Discrete values. - /// - Discrete, - - /// - /// Discrete values with tickmarks. - /// - DiscreteWithTickmarks -} - - -/// -/// A helper to set the alignment of text in , and . -/// Applied also to , , and -/// has a default of -/// -public enum MBTextAlignStyle -{ - /// - /// Default - no further styling applied. This is the default. - /// - Default, - - /// - /// Left aligned text contents. - /// - Left, - - /// - /// Centered text contents. - /// - Center, - - /// - /// Right aligned text contents. - /// - Right - -} - - -/// -/// Material Theme top app bar type applied to an . -/// -[Flags] -public enum MBTopAppBarType -{ - /// - /// The standard variety. - /// - Standard = 0, - - /// - /// The fixed variety. - /// - Fixed = 1 << 0, - - - /// - /// The dense variety. - /// - Dense = 1 << 1, - - /// - /// The prominent variety. - /// - Prominent = 1 << 2, - - /// - /// The short variety. - /// - Short = 1 << 3, - - /// - /// The short collapsed variety. - /// - ShortCollapsed = 1 << 4 -} - - -/// -/// Determines whether a snackbar or a toast notfication times out and whether it has a dismiss button. -/// Defaults to -/// -public enum MBNotifierCloseMethod -{ - /// - /// Apply a timeout and show the dismiss button. This is the default. - /// - TimeoutAndDismissButton, - - /// - /// Apply a timeout only. - /// - Timeout, - - /// - /// Show the dismiss button only. - /// - DismissButton -} - - -/// -/// Determines the type of a toast notfication. This is a required toast parameter without defaults. -/// -public enum MBToastLevel -{ - /// - /// Informational toast. - /// - Info, - - /// - /// Success toast. - /// - Success, - - /// - /// Warning toast. - /// - Warning, - - /// - /// Error toast. - /// - Error -} - - -/// -/// Determines where toasts are positioned. -/// Defaults to -/// -public enum MBToastPosition -{ - /// - /// Top left positioning, newest toasts on top. - /// - TopLeft, - - /// - /// Top center, newest toasts on top. - /// - TopCenter, - - /// - /// Top right, newest toasts on top. - /// - TopRight, - - /// - /// Center left positioning, newest toasts on top. - /// - CenterLeft, - - /// - /// Center center, newest toasts on top. - /// - CenterCenter, - - /// - /// Center right, newest toasts on top. - /// - CenterRight, - - /// - /// Bottom left positioning, newest toasts on the bottom. - /// - BottomLeft, - - /// - /// Bottom center positioning, newest toasts on the bottom. - /// - BottomCenter, - - /// - /// Bottom right positioning, newest toasts on the bottom. - /// - BottomRight, -} diff --git a/Material.Blazor.MD3/Model.MD2/MBIconBearingSelectElement.cs b/Material.Blazor.MD3/Model.MD2/MBIconBearingSelectElement.cs deleted file mode 100644 index c5428e655..000000000 --- a/Material.Blazor.MD3/Model.MD2/MBIconBearingSelectElement.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Material.Blazor.MD2; - -/// -/// A list item used by -/// -/// -public record MBIconBearingSelectElementMD2 : MBSelectElementMD2 -{ - /// - /// The leading icon. - /// - public string Icon { get; set; } -} diff --git a/Material.Blazor.MD3/Model.MD2/MBSelectElement.cs b/Material.Blazor.MD3/Model.MD2/MBSelectElement.cs deleted file mode 100644 index 43f28d64c..000000000 --- a/Material.Blazor.MD3/Model.MD2/MBSelectElement.cs +++ /dev/null @@ -1,25 +0,0 @@ -namespace Material.Blazor.MD2; - -/// -/// A list item used by , and -/// -/// -public record MBSelectElementMD2 -{ - /// - /// The value associated with the list element. - /// - public T SelectedValue { get; set; } - - - /// - /// The string label expressing the value. - /// - public string Label { get; set; } - - - /// - /// Determines whether the list item is to be disabled - /// - public bool? Disabled { get; set; } -} diff --git a/Material.Blazor.MD3/Model.MD2/Tooltip/IMBTooltipService.cs b/Material.Blazor.MD3/Model.MD2/Tooltip/IMBTooltipService.cs deleted file mode 100644 index ebe1022b4..000000000 --- a/Material.Blazor.MD3/Model.MD2/Tooltip/IMBTooltipService.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Microsoft.AspNetCore.Components; -using System; - -namespace Material.Blazor.MD2; - -/// -/// Interface for the Material.Blazor tooltip service, developed from the code base of Blazored Toast by Chris Sainty. -/// Works in conjunction with a that must be placed in either App.razor or -/// MainLayout.razor to avoid an exception being thrown when you first attempt to show a tooltip notification. -/// -/// -/// Throws a if -/// -/// is called without an component used in the app. -/// -/// -public interface IMBTooltipService -{ - /// - /// A event that will be invoked when adding a tooltip - /// - event Action OnAddRenderFragment; - - - - /// - /// A event that will be invoked when adding a tooltip - /// - event Action OnAddMarkupString; - - - - /// - /// A event that will be invoked when removing a tooltip - /// - event Action OnRemove; - - - - /// - /// Adds a tooltip. - /// - /// - /// - void AddTooltip(long id, RenderFragment content); - - - - /// - /// Adds a tooltip. - /// - /// - /// - void AddTooltip(long id, MarkupString content); - - - - /// - /// Removes the tooltip with the specified id. - /// - /// - void RemoveTooltip(long id); -} diff --git a/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipIdProvider.cs b/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipIdProvider.cs deleted file mode 100644 index 1f3a73c97..000000000 --- a/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipIdProvider.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Threading; - -namespace Material.Blazor.Internal.MD2; - -/// -/// Unique identifiers are necessary for tooltips. -/// To guarantee order, we use a instead of e.g. . -/// To avoid duplicate IDs, we use a static counter which we increment atomically. -/// -internal static class TooltipIdProvider -{ - private static long Current; - /// - /// Get a unique ID for use with Tooltips. - /// - /// - public static long NextId() => Interlocked.Increment(ref Current); -} diff --git a/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipInstance.cs b/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipInstance.cs deleted file mode 100644 index 24af2c249..000000000 --- a/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipInstance.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.AspNetCore.Components; - -namespace Material.Blazor.Internal.MD2; - -/// -/// An instance of a tooltip. -/// -internal class TooltipInstance -{ - /// - /// The tooltip's content. - /// - public RenderFragment RenderFragmentContent { get; set; } - - - /// - /// The tooltip's content. - /// - public MarkupString MarkupStringContent { get; set; } - - - /// - /// The instance's reference for JS Interop use. - /// - public ElementReference ElementReference { get; set; } - - - /// - /// True if Material Theme ias intiated the tooltip. - /// - public bool Initiated { get; set; } = false; -} diff --git a/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipService.cs b/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipService.cs deleted file mode 100644 index 9b701713f..000000000 --- a/Material.Blazor.MD3/Model.MD2/Tooltip/TooltipService.cs +++ /dev/null @@ -1,79 +0,0 @@ -using Material.Blazor.MD2; -using Microsoft.AspNetCore.Components; -using System; - -namespace Material.Blazor.Internal.MD2; - -/// -/// The internal implementation of . -/// -internal class TooltipService : IMBTooltipService -{ - private event Action OnAddRenderFragment; - private event Action OnAddMarkupString; - private event Action OnRemove; - - /// - event Action IMBTooltipService.OnAddRenderFragment - { - add => OnAddRenderFragment += value; - remove => OnAddRenderFragment -= value; - } - - - - /// - event Action IMBTooltipService.OnAddMarkupString - { - add => OnAddMarkupString += value; - remove => OnAddMarkupString -= value; - } - - - - /// - event Action IMBTooltipService.OnRemove - { - add => OnRemove += value; - remove => OnRemove -= value; - } - - - - /// - public void AddTooltip(long id, RenderFragment content) - { - if (OnAddRenderFragment is null) - { - throw new InvalidOperationException($"Material.Blazor: you attempted to add a tooltip from a {Utilities.GetTypeName(typeof(IMBTooltipService))} but have not placed an MBAnchor component at the top of either App.razor or MainLayout.razor"); - } - - OnAddRenderFragment?.Invoke(id, content); - } - - - - /// - public void AddTooltip(long id, MarkupString content) - { - if (OnAddMarkupString is null) - { - throw new InvalidOperationException($"Material.Blazor: you attempted to add a tooltip from a {Utilities.GetTypeName(typeof(IMBTooltipService))} but have not placed a {Utilities.GetTypeName(typeof(Material.Blazor.MD2.MBAnchor))} component at the top of either App.razor or MainLayout.razor"); - } - - OnAddMarkupString?.Invoke(id, content); - } - - - - /// - public void RemoveTooltip(long id) - { - if (OnRemove is null) - { - throw new InvalidOperationException($"Material.Blazor: you attempted to remove a tooltip from a {Utilities.GetTypeName(typeof(IMBTooltipService))} but have not placed a {Utilities.GetTypeName(typeof(Material.Blazor.MD2.MBAnchor))} component at the top of either App.razor or MainLayout.razor"); - } - - OnRemove?.Invoke(id); - } -} diff --git a/Material.Blazor.MD3/Model.MD2/Interfaces/IMBDialog.cs b/Material.Blazor.MD3/Model/Interfaces/IMBDialog.cs similarity index 86% rename from Material.Blazor.MD3/Model.MD2/Interfaces/IMBDialog.cs rename to Material.Blazor.MD3/Model/Interfaces/IMBDialog.cs index ddd90bf15..f6c3cd6c7 100644 --- a/Material.Blazor.MD3/Model.MD2/Interfaces/IMBDialog.cs +++ b/Material.Blazor.MD3/Model/Interfaces/IMBDialog.cs @@ -1,4 +1,4 @@ -namespace Material.Blazor.Internal.MD2; +namespace Material.Blazor.Internal; /// /// An interface implemented by to allow child components to @@ -10,7 +10,7 @@ internal interface IMBDialog /// The child component should implement and call this when running /// /// The child components that implements - void RegisterLayoutAction(ComponentFoundationMD2 child); + void RegisterLayoutAction(ComponentFoundation child); /// diff --git a/Material.Blazor.MD3/Model/MBCascadingDefaults.cs b/Material.Blazor.MD3/Model/MBCascadingDefaults.cs index 0c3751c14..6278f87ad 100644 --- a/Material.Blazor.MD3/Model/MBCascadingDefaults.cs +++ b/Material.Blazor.MD3/Model/MBCascadingDefaults.cs @@ -10,14 +10,20 @@ namespace Material.Blazor; /// /// /// For example the default style for -/// a material button is , however you can change that by setting +/// a material button is , however you can change that by setting /// to another value and your whole application within the cascading value will change appearance. You can of course /// nest cascading values in the normal manner. Exposes a property that is incremented each time another /// property is updated; can be used with an `@key(CascadingDefaults.Version)` attribute to force components /// to re-render when cascading defaults have updated. See . /// + +// MBCascadingDefaults implements the MD3 cascading defaults +// The 'MBCascadingDefaults - MD2' region contains the cascading defaults from MD2 as comments + public class MBCascadingDefaults { + #region ATTRIBUTE SPLATTING AND VALIDATION + /************************************************************************************************************* * * @@ -58,15 +64,9 @@ public class MBCascadingDefaults ///// //internal MBItemValidation AppliedItemValidation(MBItemValidation? criteria = null) => criteria ?? ItemValidation; + #endregion - - /************************************************************************************************************* - * - * - * MD3 CORE COMPONENTS - * - * - ************************************************************************************************************/ + #region COMMON ELEMENTS - COMPONENT ACCESSIBILITY, DISABLED, VERSION private bool _disabled = false; /// @@ -78,136 +78,82 @@ public class MBCascadingDefaults /// The disabled state to apply. /// /// The required disabled state - /// The to apply. + /// The to apply. internal bool AppliedDisabled(bool? disabled = null) => disabled ?? Disabled; - //private MBButtonStyle _buttonStyle = MBButtonStyle.Text; - ///// - ///// The default style for an , initialized to if not explicitly set. - ///// - //public MBButtonStyle ButtonStyle { get => _buttonStyle; set => SetParameter(ref _buttonStyle, value); } - - //private MBButtonStyle? _cardButtonStyle = null; - ///// - ///// The default style for a card action button/ in an , returns the value of if not explicitly set. - ///// - //public MBButtonStyle CardActionButtonStyle { get => _cardButtonStyle ?? ButtonStyle; set => SetParameter(ref _cardButtonStyle, value); } - - //private MBButtonStyle? _dialogButtonStyle = null; - ///// - ///// The default style for a dialog action button/ in an , returns the value of if not explicitly set. - ///// - //public MBButtonStyle DialogActionButtonStyle { get => _dialogButtonStyle ?? ButtonStyle; set => SetParameter(ref _dialogButtonStyle, value); } - + //private bool _TouchTarget = true; ///// - ///// The style to apply within an . and must - ///// pass a reference to themselves (this) to reference the relevant default. + ///// Determines whether to apply touch targets for accessibility. Defaults to true. ///// - ///// The style parameter passed to the - ///// The 's card reference (null if button is not in a card) - ///// The 's card reference (null if button is not in a dialog) - ///// The to apply. - //internal MBButtonStyle AppliedStyle(MBButtonStyle? style, MBCard card, MBDialog dialog) - //{ - // if (style != null) - // { - // return (MBButtonStyle)style; - // } - - // if (card != null) - // { - // return CardActionButtonStyle; - // } - - // if (dialog != null) - // { - // return DialogActionButtonStyle; - // } - - // return ButtonStyle; - //} + //public bool TouchTarget { get => _TouchTarget; set => SetParameter(ref _TouchTarget, value); } + //internal bool AppliedTouchTarget(bool? touchTarget) => touchTarget ?? TouchTarget; - //private MBCardStyle _cardStyle = MBCardStyle.Default; - ///// - ///// The default style for an , initialized to if not explicitly set. - ///// - //public MBCardStyle CardStyle { get => _cardStyle; set => SetParameter(ref _cardStyle, value); } + /// + /// Gets incremented for every property update. Use Version to force Blazor to re-render components or <div> blocks + /// with the @key attribute. + /// + public int Version { get; private set; } = 0; - ///// - ///// The style to apply to an . - ///// - ///// The style parameter passed to the - ///// The to apply. - //internal MBCardStyle AppliedStyle(MBCardStyle? style = null) => style ?? CardStyle; + #endregion + #region MD3 CORE COMPONENTS + /************************************************************************************************************* + * + * + * MD3 CORE COMPONENTS + * + * + ************************************************************************************************************/ - //private string _dateFormat = "D"; - ///// - ///// The default date format for an , initialized to "D" (culture specific C# long date pattern) if not explicitly set. - ///// - //public string DateFormat { get => _dateFormat; set => SetParameter(ref _dateFormat, value); } + #region MBButton - ///// - ///// The date format to apply to an . - ///// - ///// The style parameter passed to the - ///// The to apply. - //internal string AppliedDateFormat(string format = null) => string.IsNullOrWhiteSpace(format) ? DateFormat : format; + private MBButtonStyle _buttonStyle = MBButtonStyle.Text; + /// + /// The default style for an , initialized to if not explicitly set. + /// + public MBButtonStyle ButtonStyle { get => _buttonStyle; set => SetParameter(ref _buttonStyle, value); } + internal MBButtonStyle AppliedButtonStyle(MBButtonStyle? buttonStyle = null) => buttonStyle ?? ButtonStyle; + #endregion + #region MBIcon - //private MBListStyle _listStyle = MBListStyle.None; - ///// - ///// The default style for an , initialized to if not explicitly set. - ///// - //public MBListStyle ListStyle { get => _listStyle; set => SetParameter(ref _listStyle, value); } + private string _iconColor = "black"; + public string IconColor { get => _iconColor; set => SetParameter(ref _iconColor, value); } + internal string AppliedIconColor(string iconColor = null) => iconColor ?? IconColor; - ///// - ///// The style to apply to an . - ///// - ///// The style parameter passed to the - ///// The to apply. - //internal MBListStyle AppliedStyle(MBListStyle? style = null) => style ?? ListStyle; + private decimal _iconFill = 1; + public decimal IconFill { get => _iconFill; set => SetParameter(ref _iconFill, value); } + internal decimal AppliedIconFill(decimal? iconFill = null) => iconFill ?? IconFill; + + private MBIconGradient _iconGradient = MBIconGradient.NormalEmphasis; + public MBIconGradient IconGradient { get => _iconGradient; set => SetParameter(ref _iconGradient, value); } + internal MBIconGradient AppliedIconGradient(MBIconGradient? iconGradient = null) => iconGradient ?? IconGradient; + private string _iconName = "home"; + public string IconName { get => _iconName; set => SetParameter(ref _iconName, value); } + internal string AppliedIconName(string iconName = null) => iconName ?? IconName; + private MBIconStyle _iconStyle = MBIconStyle.Outlined; + public MBIconStyle IconStyle { get => _iconStyle; set => SetParameter(ref _iconStyle, value); } + internal MBIconStyle AppliedIconStyle(MBIconStyle? iconStyle = null) => iconStyle ?? IconStyle; - //private MBListType _listType = MBListType.Regular; - ///// - ///// The default type for an , initialized to if not explicitly set. - ///// - //public MBListType ListType { get => _listType; set => SetParameter(ref _listType, value); } + private MBIconSize _iconSize = MBIconSize.Size24; + public MBIconSize IconSize { get => _iconSize; set => SetParameter(ref _iconSize, value); } + internal MBIconSize AppliedIconSize(MBIconSize? iconSize = null) => iconSize ?? IconSize; - ///// - ///// The style to apply to an . - ///// - ///// The style parameter passed to the - ///// The to apply. - //internal MBListType AppliedType(MBListType? type = null) => type ?? ListType; - - - - //private MBSelectInputStyle _selectInputStyle = MBSelectInputStyle.Outlined; - ///// - ///// The default style for an , initialized to if not explicitly set. - ///// - ///// - ///// Also applied to . - ///// - //public MBSelectInputStyle SelectInputStyle { get => _selectInputStyle; set => SetParameter(ref _selectInputStyle, value); } - - ///// - ///// The style to apply to an . - ///// - ///// The style parameter passed to the - ///// The to apply. - //internal MBSelectInputStyle AppliedStyle(MBSelectInputStyle? style = null) => style ?? SelectInputStyle; + private MBIconWeight _iconWeight = MBIconWeight.W400; + public MBIconWeight IconWeight { get => _iconWeight; set => SetParameter(ref _iconWeight, value); } + internal MBIconWeight AppliedIconWeight(MBIconWeight? iconWeight = null) => iconWeight ?? IconWeight; + #endregion + #region MBSwitch private bool _switchIcons = false; /// @@ -219,7 +165,7 @@ public class MBCascadingDefaults /// The value to apply to an . /// /// The style parameter passed to the - /// The to apply. + /// The to apply. internal bool AppliedSwitchIcons(bool? icons = null) => icons ?? SwitchIcons; @@ -234,10 +180,12 @@ public class MBCascadingDefaults /// The value to apply to an . /// /// The style parameter passed to the - /// The to apply. + /// The to apply. internal bool AppliedSwitchSwitchShowOnlySelectedIcon(bool? switchShowOnlySelectedIcon = null) => switchShowOnlySelectedIcon ?? SwitchShowOnlySelectedIcon; + #endregion + #region MBText private MBTextAlignStyle _textAlignStyle = MBTextAlignStyle.Default; /// @@ -256,6 +204,43 @@ public class MBCascadingDefaults internal MBTextAlignStyle AppliedStyle(MBTextAlignStyle? style = null) => style ?? TextAlignStyle; + private MBDensity? _textFieldDensity = null; + /// + /// The default density for an , , , , + /// or , defaults to if not explicitly set. + /// + public MBDensity TextFieldDensity { get => _textFieldDensity ?? ThemeDensity; set => SetParameter(ref _textFieldDensity, value); } + + /// + /// The density to apply to an an , , , , + /// or , initialized to . + /// + /// The density parameter passed to the + /// + internal MBDensity AppliedTextFieldDensity(MBDensity? density) => density ?? TextFieldDensity; + + + private MBTextInputStyle _textInputStyle = MBTextInputStyle.Outlined; + /// + /// The default text input style for an , an or , initialized to if not explicitly set. + /// + /// + /// Also applied to , , , , and . + /// + public MBTextInputStyle TextInputStyle { get => _textInputStyle; set => SetParameter(ref _textInputStyle, value); } + + /// + /// The text input style to apply to an , an or . + /// + /// The text Input style parameter passed to the , or + /// The to apply. + internal MBTextInputStyle AppliedStyle(MBTextInputStyle? style = null) => style ?? TextInputStyle; + + #endregion + + #endregion + + #region PLUS COMPONENTS /************************************************************************************************************* * @@ -265,73 +250,342 @@ public class MBCascadingDefaults * ************************************************************************************************************/ - //private MBDateSelectionCriteria _dateSelectionCriteria = MBDateSelectionCriteria.AllowAll; - ///// - ///// The default date selection criteria for a , initialized to if not explicitly set. - ///// - //public MBDateSelectionCriteria DateSelectionCriteria { get => _dateSelectionCriteria; set => SetParameter(ref _dateSelectionCriteria, value); } + #endregion - ///// - ///// The date selection criteria to apply to a . - ///// - ///// The criteria style parameter passed to the - ///// The to apply. - //internal MBDateSelectionCriteria AppliedDateSelectionCriteria(MBDateSelectionCriteria? criteria = null) => criteria ?? DateSelectionCriteria; + #region SetParameter + + private void SetParameter(ref T privateParameter, T value) + { + if (!value.Equals(privateParameter)) + { + privateParameter = value; + Version++; + } + } + #endregion - ////private DateTime datePickerDefaultMinDate = MBDatePicker.MinAllowableDate; - /////// - /////// The default minimum date . - /////// - ////public DateTime DatePickerDefaultMinDate { get => datePickerDefaultMinDate; set => SetParameter(ref datePickerDefaultMinDate, value); } + #region ShallowCopy - /////// - /////// The minimum date to apply to a . - /////// - /////// The criteria style parameter passed to the - /////// The to apply. - ////internal DateTime AppliedDatePickerDefaultMinDate(DateTime minDate) => minDate == default || minDate <= MBDatePicker.MinAllowableDate ? DatePickerDefaultMinDate : minDate; + /// + /// Returns a shallow copy of the cascading defaults. + /// + /// + public MBCascadingDefaults ShallowCopy() + { + return (MBCascadingDefaults)MemberwiseClone(); + } + #endregion - ////private DateTime datePickerDefaultMaxDate = MBDatePicker.MaxAllowableDate; - /////// - /////// The default maximum date . - /////// - ////public DateTime DatePickerDefaultMaxDate { get => datePickerDefaultMaxDate; set => SetParameter(ref datePickerDefaultMaxDate, value); } + #region MBCascadingDefaults - MD2 - /////// - /////// The maximum date to apply to a . - /////// - /////// The criteria style parameter passed to the - /////// The to apply. - ////internal DateTime AppliedDatePickerDefaultMaxDate(DateTime maxDate) => maxDate == default || maxDate >= MBDatePicker.MaxAllowableDate ? DatePickerDefaultMaxDate : maxDate; + //public class MBCascadingDefaults + //{ + // /************************************************************************************************************* + // * + // * + // * ATTRIBUTE SPLATTING AND VALIDATION + // * + // * + // ************************************************************************************************************/ + + // private bool _constrainSplattableAttributes = false; + // /// + // /// Determines whether should throw an exception for invalid + // /// unmatched HTML attributes passed to a component. + // /// and + // /// + // public bool ConstrainSplattableAttributes { get => _constrainSplattableAttributes; set => SetParameter(ref _constrainSplattableAttributes, value); } + + + + // private IEnumerable _allowedSplattableAttributes = Enumerable.Empty(); + // /// + // /// Further attributes that can be set as allowable when + // /// performs unmatched attribute validation. Works with . + // /// + // public IEnumerable AllowedSplattableAttributes { get => _allowedSplattableAttributes; set => SetParameter(ref _allowedSplattableAttributes, value); } + + + + // private MBItemValidation _itemValidation = MBItemValidation.Exception; + // /// + // /// Defines how radio button groups and selects validate mismtatch between item lists and initial value. + // /// + // public MBItemValidation ItemValidation { get => _itemValidation; set => SetParameter(ref _itemValidation, value); } + + // /// + // /// The applied item validation for selects and radio button groups. + // /// + // /// + // /// + // internal MBItemValidation AppliedItemValidation(MBItemValidation? criteria = null) => criteria ?? ItemValidation; + + + + // /************************************************************************************************************* + // * + // * + // * MDC CORE COMPONENTS + // * + // * + // ************************************************************************************************************/ + + // private bool _disabled = false; + // /// + // /// The default disabled state. + // /// + // public bool Disabled { get => _disabled; set => SetParameter(ref _disabled, value); } + + // /// + // /// The disabled state to apply. + // /// + // /// The required disabled state + // /// The to apply. + // internal bool AppliedDisabled(bool? disabled = null) => disabled ?? Disabled; + + + + // private MBButtonStyle _buttonStyle = MBButtonStyle.Text; + // /// + // /// The default style for an , initialized to if not explicitly set. + // /// + // public MBButtonStyle ButtonStyle { get => _buttonStyle; set => SetParameter(ref _buttonStyle, value); } + + // private MBButtonStyle? _cardButtonStyle = null; + // /// + // /// The default style for a card action button/ in an , returns the value of if not explicitly set. + // /// + // public MBButtonStyle CardActionButtonStyle { get => _cardButtonStyle ?? ButtonStyle; set => SetParameter(ref _cardButtonStyle, value); } + + // private MBButtonStyle? _dialogButtonStyle = null; + // /// + // /// The default style for a dialog action button/ in an , returns the value of if not explicitly set. + // /// + // public MBButtonStyle DialogActionButtonStyle { get => _dialogButtonStyle ?? ButtonStyle; set => SetParameter(ref _dialogButtonStyle, value); } + + // /// + // /// The style to apply within an . and must + // /// pass a reference to themselves (this) to reference the relevant default. + // /// + // /// The style parameter passed to the + // /// The 's card reference (null if button is not in a card) + // /// The 's card reference (null if button is not in a dialog) + // /// The to apply. + // internal MBButtonStyle AppliedStyle(MBButtonStyle? style, MBCard card, MBDialog dialog) + // { + // if (style != null) + // { + // return (MBButtonStyle)style; + // } + // if (card != null) + // { + // return CardActionButtonStyle; + // } + // if (dialog != null) + // { + // return DialogActionButtonStyle; + // } - //private int _debounceInterval = 300; - ///// - ///// The default debounce interval in milliseconds for a , initialized to 300 milliseconds if not explicitly set. - ///// - //public int DebounceInterval { get => _debounceInterval; set => SetParameter(ref _debounceInterval, value); } + // return ButtonStyle; + // } - ///// - ///// The text debounce interval in milliseconds to apply to an . - ///// - ///// The text input style parameter passed to the - ///// The interval in milliseconds to apply. - //internal int AppliedDebounceInterval(int? debounceInterval = null) => debounceInterval ?? 300; + // private MBCardStyle _cardStyle = MBCardStyle.Default; + // /// + // /// The default style for an , initialized to if not explicitly set. + // /// + // public MBCardStyle CardStyle { get => _cardStyle; set => SetParameter(ref _cardStyle, value); } + + // /// + // /// The style to apply to an . + // /// + // /// The style parameter passed to the + // /// The to apply. + // internal MBCardStyle AppliedStyle(MBCardStyle? style = null) => style ?? CardStyle; + + + + // private string _dateFormat = "D"; + // /// + // /// The default date format for an , initialized to "D" (culture specific C# long date pattern) if not explicitly set. + // /// + // public string DateFormat { get => _dateFormat; set => SetParameter(ref _dateFormat, value); } + + // /// + // /// The date format to apply to an . + // /// + // /// The style parameter passed to the + // /// The to apply. + // internal string AppliedDateFormat(string format = null) => string.IsNullOrWhiteSpace(format) ? DateFormat : format; + - /************************************************************************************************************* - * - * - * COMPONENT DENSITY - * - * - ************************************************************************************************************/ + + // private MBListStyle _listStyle = MBListStyle.None; + // /// + // /// The default style for an , initialized to if not explicitly set. + // /// + // public MBListStyle ListStyle { get => _listStyle; set => SetParameter(ref _listStyle, value); } + + // /// + // /// The style to apply to an . + // /// + // /// The style parameter passed to the + // /// The to apply. + // internal MBListStyle AppliedStyle(MBListStyle? style = null) => style ?? ListStyle; + + + + // private MBListType _listType = MBListType.Regular; + // /// + // /// The default type for an , initialized to if not explicitly set. + // /// + // public MBListType ListType { get => _listType; set => SetParameter(ref _listType, value); } + + // /// + // /// The style to apply to an . + // /// + // /// The style parameter passed to the + // /// The to apply. + // internal MBListType AppliedType(MBListType? type = null) => type ?? ListType; + + + + // private MBSelectInputStyle _selectInputStyle = MBSelectInputStyle.Outlined; + // /// + // /// The default style for an , initialized to if not explicitly set. + // /// + // /// + // /// Also applied to . + // /// + // public MBSelectInputStyle SelectInputStyle { get => _selectInputStyle; set => SetParameter(ref _selectInputStyle, value); } + + // /// + // /// The style to apply to an . + // /// + // /// The style parameter passed to the + // /// The to apply. + // internal MBSelectInputStyle AppliedStyle(MBSelectInputStyle? style = null) => style ?? SelectInputStyle; + + + + // private MBTextAlignStyle _textAlignStyle = MBTextAlignStyle.Default; + // /// + // /// The default text alignment style for an , an or , initialized to if not explicitly set. + // /// + // /// + // /// Also applied to , , , , and . + // /// + // public MBTextAlignStyle TextAlignStyle { get => _textAlignStyle; set => SetParameter(ref _textAlignStyle, value); } + + // /// + // /// The text alignment style to apply to an , an or . + // /// + // /// The text align style parameter passed to the , or + // /// The to apply. + // internal MBTextAlignStyle AppliedStyle(MBTextAlignStyle? style = null) => style ?? TextAlignStyle; + + + + // private MBTextInputStyle _textInputStyle = MBTextInputStyle.Outlined; + // /// + // /// The default style for an or an , initialized to if not explicitly set. + // /// + // /// + // /// Also applied to , , and . + // /// + // public MBTextInputStyle TextInputStyle { get => _textInputStyle; set => SetParameter(ref _textInputStyle, value); } + + // /// + // /// The text input style to apply to an or an . + // /// + // /// The text input style parameter passed to the or + // /// The to apply. + // internal MBTextInputStyle AppliedStyle(MBTextInputStyle? style = null) => style ?? TextInputStyle; + + + + // /************************************************************************************************************* + // * + // * + // * PLUS COMPONENTS + // * + // * + // ************************************************************************************************************/ + + // private MBDateSelectionCriteria _dateSelectionCriteria = MBDateSelectionCriteria.AllowAll; + // /// + // /// The default date selection criteria for a , initialized to if not explicitly set. + // /// + // public MBDateSelectionCriteria DateSelectionCriteria { get => _dateSelectionCriteria; set => SetParameter(ref _dateSelectionCriteria, value); } + + // /// + // /// The date selection criteria to apply to a . + // /// + // /// The criteria style parameter passed to the + // /// The to apply. + // internal MBDateSelectionCriteria AppliedDateSelectionCriteria(MBDateSelectionCriteria? criteria = null) => criteria ?? DateSelectionCriteria; + + + + // private DateTime datePickerDefaultMinDate = MBDatePicker.MinAllowableDate; + // /// + // /// The default minimum date . + // /// + // public DateTime DatePickerDefaultMinDate { get => datePickerDefaultMinDate; set => SetParameter(ref datePickerDefaultMinDate, value); } + + // /// + // /// The minimum date to apply to a . + // /// + // /// The criteria style parameter passed to the + // /// The to apply. + // internal DateTime AppliedDatePickerDefaultMinDate(DateTime minDate) => minDate == default || minDate <= MBDatePicker.MinAllowableDate ? DatePickerDefaultMinDate : minDate; + + + + // private DateTime datePickerDefaultMaxDate = MBDatePicker.MaxAllowableDate; + // /// + // /// The default maximum date . + // /// + // public DateTime DatePickerDefaultMaxDate { get => datePickerDefaultMaxDate; set => SetParameter(ref datePickerDefaultMaxDate, value); } + + // /// + // /// The maximum date to apply to a . + // /// + // /// The criteria style parameter passed to the + // /// The to apply. + // internal DateTime AppliedDatePickerDefaultMaxDate(DateTime maxDate) => maxDate == default || maxDate >= MBDatePicker.MaxAllowableDate ? DatePickerDefaultMaxDate : maxDate; + + + + // private int _debounceInterval = 300; + // /// + // /// The default debounce interval in milliseconds for a , initialized to 300 milliseconds if not explicitly set. + // /// + // public int DebounceInterval { get => _debounceInterval; set => SetParameter(ref _debounceInterval, value); } + + // /// + // /// The text debounce interval in milliseconds to apply to an . + // /// + // /// The text input style parameter passed to the + // /// The interval in milliseconds to apply. + // internal int AppliedDebounceInterval(int? debounceInterval = null) => debounceInterval ?? 300; + + + + // /************************************************************************************************************* + // * + // * + // * COMPONENT DENSITY + // * + // * + // ************************************************************************************************************/ private MBDensity _themeDensity = MBDensity.Default; /// @@ -340,245 +594,248 @@ public class MBCascadingDefaults public MBDensity ThemeDensity { get => _themeDensity; set => SetParameter(ref _themeDensity, value); } - //private MBDensity? _buttonDensity = null; - ///// - ///// The default density for an , defaults to if not explicitly set. - ///// - //public MBDensity ButtonDensity { get => _buttonDensity ?? ThemeDensity; set => SetParameter(ref _buttonDensity, value); } + // private MBDensity? _buttonDensity = null; + // /// + // /// The default density for an , defaults to if not explicitly set. + // /// + // public MBDensity ButtonDensity { get => _buttonDensity ?? ThemeDensity; set => SetParameter(ref _buttonDensity, value); } - ///// - ///// The density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedButtonDensity(MBDensity? density) => density ?? ButtonDensity; + // /// + // /// The density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedButtonDensity(MBDensity? density) => density ?? ButtonDensity; - //private MBDensity? _checkboxDensity = null; - ///// - ///// The default density for an , defaults to if not explicitly set. - ///// - //public MBDensity CheckboxDensity { get => _checkboxDensity ?? ThemeDensity; set => SetParameter(ref _checkboxDensity, value); } + // private MBDensity? _checkboxDensity = null; + // /// + // /// The default density for an , defaults to if not explicitly set. + // /// + // public MBDensity CheckboxDensity { get => _checkboxDensity ?? ThemeDensity; set => SetParameter(ref _checkboxDensity, value); } - ///// - ///// The density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedCheckboxDensity(MBDensity? density) => density ?? CheckboxDensity; + // /// + // /// The density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedCheckboxDensity(MBDensity? density) => density ?? CheckboxDensity; - //private MBDensity? _dataTableDensity = null; - ///// - ///// The default density for an , defaults to if not explicitly set. - ///// - //public MBDensity DataTableDensity { get => _dataTableDensity ?? ThemeDensity; set => SetParameter(ref _dataTableDensity, value); } + // private MBDensity? _dataTableDensity = null; + // /// + // /// The default density for an , defaults to if not explicitly set. + // /// + // public MBDensity DataTableDensity { get => _dataTableDensity ?? ThemeDensity; set => SetParameter(ref _dataTableDensity, value); } - ///// - ///// The density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedDataTableDensity(MBDensity? density) => density ?? DataTableDensity; + // /// + // /// The density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedDataTableDensity(MBDensity? density) => density ?? DataTableDensity; - //private MBDensity? _iconButtonDensity = null; - ///// - ///// The default density for an or , defaults to if not explicitly set. - ///// - //public MBDensity IconButtonDensity { get => _iconButtonDensity ?? ThemeDensity; set => SetParameter(ref _iconButtonDensity, value); } + // private MBDensity? _iconButtonDensity = null; + // /// + // /// The default density for an or , defaults to if not explicitly set. + // /// + // public MBDensity IconButtonDensity { get => _iconButtonDensity ?? ThemeDensity; set => SetParameter(ref _iconButtonDensity, value); } - ///// - ///// The density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedIconButtonDensity(MBDensity? density) => density ?? IconButtonDensity; + // /// + // /// The density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedIconButtonDensity(MBDensity? density) => density ?? IconButtonDensity; - //private MBDensity? _listSingleLineDensity = null; - ///// - ///// The default single line density for an , defaults to if not explicitly set. - ///// - //public MBDensity ListSingleLineDensity { get => _listSingleLineDensity ?? ThemeDensity; set => SetParameter(ref _listSingleLineDensity, value); } + // private MBDensity? _listSingleLineDensity = null; + // /// + // /// The default single line density for an , defaults to if not explicitly set. + // /// + // public MBDensity ListSingleLineDensity { get => _listSingleLineDensity ?? ThemeDensity; set => SetParameter(ref _listSingleLineDensity, value); } - ///// - ///// The single density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedListSingleLineDensity(MBDensity? density) => density ?? IconButtonDensity; + // /// + // /// The single density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedListSingleLineDensity(MBDensity? density) => density ?? IconButtonDensity; - //private MBDensity? _radioButtonDensity = null; - ///// - ///// The default density for an or , defaults to if not explicitly set. - ///// - //public MBDensity RadioButtonDensity { get => _radioButtonDensity ?? ThemeDensity; set => SetParameter(ref _radioButtonDensity, value); } + // private MBDensity? _radioButtonDensity = null; + // /// + // /// The default density for an or , defaults to if not explicitly set. + // /// + // public MBDensity RadioButtonDensity { get => _radioButtonDensity ?? ThemeDensity; set => SetParameter(ref _radioButtonDensity, value); } - ///// - ///// The density to apply to an or . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedRadioButtonDensity(MBDensity? density) => density ?? RadioButtonDensity; + // /// + // /// The density to apply to an or . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedRadioButtonDensity(MBDensity? density) => density ?? RadioButtonDensity; - //private MBDensity? _selectDensity = null; - ///// - ///// The default density for an , defaults to if not explicitly set. - ///// - //public MBDensity SelectDensity { get => _selectDensity ?? ThemeDensity; set => SetParameter(ref _selectDensity, value); } + // private MBDensity? _selectDensity = null; + // /// + // /// The default density for an , defaults to if not explicitly set. + // /// + // public MBDensity SelectDensity { get => _selectDensity ?? ThemeDensity; set => SetParameter(ref _selectDensity, value); } - ///// - ///// The density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedSelectDensity(MBDensity? density) => density ?? SelectDensity; + // /// + // /// The density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedSelectDensity(MBDensity? density) => density ?? SelectDensity; - //private MBDensity? _switchDensity = null; - ///// - ///// The default density for an , defaults to if not explicitly set. - ///// - //public MBDensity SwitchDensity { get => _switchDensity ?? ThemeDensity; set => SetParameter(ref _switchDensity, value); } + // private MBDensity? _switchDensity = null; + // /// + // /// The default density for an , defaults to if not explicitly set. + // /// + // public MBDensity SwitchDensity { get => _switchDensity ?? ThemeDensity; set => SetParameter(ref _switchDensity, value); } - ///// - ///// The density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedSwitchDensity(MBDensity? density) => density ?? SwitchDensity; + // /// + // /// The density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedSwitchDensity(MBDensity? density) => density ?? SwitchDensity; - //private MBDensity? _tabBarDensity = null; - ///// - ///// The default density for an , defaults to if not explicitly set. - ///// - //public MBDensity TabBarDensity { get => _tabBarDensity ?? ThemeDensity; set => SetParameter(ref _tabBarDensity, value); } + // private MBDensity? _tabBarDensity = null; + // /// + // /// The default density for an , defaults to if not explicitly set. + // /// + // public MBDensity TabBarDensity { get => _tabBarDensity ?? ThemeDensity; set => SetParameter(ref _tabBarDensity, value); } - ///// - ///// The density to apply to an . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedTabBarDensity(MBDensity? density) => density ?? TabBarDensity; + // /// + // /// The density to apply to an . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedTabBarDensity(MBDensity? density) => density ?? TabBarDensity; - //private MBDensity? _textFieldDensity = null; - ///// - ///// The default density for an , , , , - ///// or , defaults to if not explicitly set. - ///// - //public MBDensity TextFieldDensity { get => _textFieldDensity ?? ThemeDensity; set => SetParameter(ref _textFieldDensity, value); } + // private MBDensity? _textFieldDensity = null; + // /// + // /// The default density for an , , , , + // /// or , defaults to if not explicitly set. + // /// + // public MBDensity TextFieldDensity { get => _textFieldDensity ?? ThemeDensity; set => SetParameter(ref _textFieldDensity, value); } - ///// - ///// The density to apply to an an , , , , - ///// or , initialized to . - ///// - ///// The density parameter passed to the - ///// - //internal MBDensity AppliedTextFieldDensity(MBDensity? density) => density ?? TextFieldDensity; + // /// + // /// The density to apply to an an , , , , + // /// or , initialized to . + // /// + // /// The density parameter passed to the + // /// + // internal MBDensity AppliedTextFieldDensity(MBDensity? density) => density ?? TextFieldDensity; - ///// - ///// A helper class for density, returning the density CSS class to be applied plus an indicator of whether to apply the class. - ///// - //internal class DensityInfo - //{ - // public bool ApplyCssClass { get; set; } - // public string CssClassName { get; set; } - //} + // /// + // /// A helper class for density, returning the density CSS class to be applied plus an indicator of whether to apply the class. + // /// + // internal class DensityInfo + // { + // public bool ApplyCssClass { get; set; } + // public string CssClassName { get; set; } + // } - ///// - ///// Returns a object for the given density parameter. - ///// - ///// - ///// - //internal DensityInfo GetDensityCssClass(MBDensity density) - //{ - // return new DensityInfo() + // /// + // /// Returns a object for the given density parameter. + // /// + // /// + // /// + // internal DensityInfo GetDensityCssClass(MBDensity density) // { - // ApplyCssClass = density != MBDensity.Default, - // CssClassName = density switch + // return new DensityInfo() // { - // MBDensity.Default => "dense-default", - // MBDensity.Minus1 => "dense--1", - // MBDensity.Minus2 => "dense--2", - // MBDensity.Comfortable => "dense-comfortable", - // MBDensity.Minus3 => "dense--3", - // MBDensity.Compact => "dense-compact", - // MBDensity.Minus4 => "dense--4", - // MBDensity.Minus5 => "dense--5", - // _ => throw new NotImplementedException(), - // } - // }; - //} + // ApplyCssClass = density != MBDensity.Default, + // CssClassName = density switch + // { + // MBDensity.Default => "dense-default", + // MBDensity.Minus1 => "dense--1", + // MBDensity.Minus2 => "dense--2", + // MBDensity.Comfortable => "dense-comfortable", + // MBDensity.Minus3 => "dense--3", + // MBDensity.Compact => "dense-compact", + // MBDensity.Minus4 => "dense--4", + // MBDensity.Minus5 => "dense--5", + // _ => throw new NotImplementedException(), + // } + // }; + // } - /************************************************************************************************************* - * - * - * COMPONENT ACCESSIBILITY - * - * - ************************************************************************************************************/ + // /************************************************************************************************************* + // * + // * + // * COMPONENT ACCESSIBILITY + // * + // * + // ************************************************************************************************************/ - //private bool _TouchTarget = true; - ///// - ///// Determines whether to apply touch targets for accessibility. Defaults to true. - ///// - //public bool TouchTarget { get => _TouchTarget; set => SetParameter(ref _TouchTarget, value); } - //internal bool AppliedTouchTarget(bool? touchTarget) => touchTarget ?? TouchTarget; + // private bool _TouchTarget = true; + // /// + // /// Determines whether to apply touch targets for accessibility. Defaults to true. + // /// + // public bool TouchTarget { get => _TouchTarget; set => SetParameter(ref _TouchTarget, value); } + // internal bool AppliedTouchTarget(bool? touchTarget) => touchTarget ?? TouchTarget; - /************************************************************************************************************* - * - * - * VERSION - * - * - ************************************************************************************************************/ + // /************************************************************************************************************* + // * + // * + // * VERSION + // * + // * + // ************************************************************************************************************/ - /// - /// Gets incremented for every property update. Use Version to force Blazor to re-render components or <div> blocks - /// with the @key attribute. - /// - public int Version { get; private set; } = 0; + // /// + // /// Gets incremented for every property update. Use Version to force Blazor to re-render components or <div> blocks + // /// with the @key attribute. + // /// + // public int Version { get; private set; } = 0; - ///// - ///// Returns a shallow copy of the cascading defaults. - ///// - ///// - //public MBCascadingDefaults ShallowCopy() - //{ - // return (MBCascadingDefaults)MemberwiseClone(); - //} + // /// + // /// Returns a shallow copy of the cascading defaults. + // /// + // /// + // public MBCascadingDefaults ShallowCopy() + // { + // return (MBCascadingDefaults)MemberwiseClone(); + // } - private void SetParameter(ref T privateParameter, T value) - { - if (!value.Equals(privateParameter)) - { - privateParameter = value; + // private void SetParameter(ref T privateParameter, T value) + // { + // if (!value.Equals(privateParameter)) + // { + // privateParameter = value; - Version++; - } - } + // Version++; + // } + // } + //} } + +#endregion diff --git a/Material.Blazor.MD3/Model/MBEnumerations.cs b/Material.Blazor.MD3/Model/MBEnumerations.cs index 8e013078a..c35840d8c 100644 --- a/Material.Blazor.MD3/Model/MBEnumerations.cs +++ b/Material.Blazor.MD3/Model/MBEnumerations.cs @@ -1,34 +1,49 @@ -using System; +using System; namespace Material.Blazor; +#region MBButtonStyle + /// -/// Style for an . +/// Style for an per Material Theme styling. +/// has a default of /// -public enum MBBadgeStyle +public enum MBButtonStyle { /// - /// Bears a numeric value. + /// Elevated style + /// + Elevated, + + /// + /// Filled style /// - ValueBearing, + Filled, /// - /// Blank, full sized badge. + /// FilledTonal style /// - BlankFullSized, + FilledTonal, /// - /// Small dot badge. + /// Outlined style /// - Dot + Outlined, + + /// + /// Text style. This is the default. + /// + Text } +#endregion +#region MBButtonStyleMD2 /// /// Style for an per Material Theme styling. /// has a default of /// -public enum MBButtonStyle +public enum MBButtonStyleMD2 { /// /// Contained style, raised. @@ -51,47 +66,30 @@ public enum MBButtonStyle Text } +#endregion + +#region MBCardStyleMD2 + +/// +/// Style for an per Material Theme styling. +/// has a default of +/// +public enum MBCardStyleMD2 +{ + /// + /// Default style. This is the default. + /// + Default, + + /// + /// Outlined style. + /// + Outlined +} -///// -///// Style for an per Material Theme styling. -///// has a default of -///// -//public enum MBCardStyle -//{ -// /// -// /// Default style. This is the default. -// /// -// Default, - -// /// -// /// Outlined style. -// /// -// Outlined -//} - - -///// -///// Determines the allowed selections in -///// has a default of -///// -//public enum MBDateSelectionCriteria -//{ -// /// -// /// Allow weekdays and weekends. This is the default. -// /// -// AllowAll, - -// /// -// /// Limit selection to weekends only. -// /// -// WeekendsOnly, - -// /// -// /// Limit selection to weekdays only. -// /// -// WeekdaysOnly -//} +#endregion +#region MBDensity /// /// Determines the density of a component @@ -140,207 +138,83 @@ public enum MBDensity Default } +#endregion -///// -///// Type for an . -///// -//public enum MBFloatingActionButtonType -//{ -// /// -// /// FAB regular variant. -// /// -// Regular, - -// /// -// /// FAB mini variant. -// /// -// Mini, - -// /// -// /// FAB extended variant without icon. -// /// -// ExtendedNoIcon, - -// /// -// /// FAB extended variant with leading icon. -// /// -// ExtendedLeadingIcon, - -// /// -// /// FAB extended variant with trailing icon. -// /// -// ExtendedTrailingIcon, -//} - +#region MBIcon /// -/// Specifies whether to use Material Icons from Google or Font Awesome Icons. -/// See -/// has a default of +/// Determines gradient. /// -public enum MBIconFoundryName +public enum MBIconGradient { - /// - /// Google Material Icons. This is the default. - /// - MaterialIcons, - - /// - /// Font Awesome Icons. - /// - FontAwesome, - - /// - /// Open Iconic Icons. - /// - OpenIconic + LowEmphasis, + NormalEmphasis, + HighEmphasis } - - /// -/// Sets the Google Material Icons theme. -/// See , and -/// has a default of +/// Determines how an is visually styled at the gross level. /// -public enum MBIconMITheme +public enum MBIconStyle { - /// - /// Filled theme, class="material-icons". This is the default. - /// - Filled, - - /// - /// Outlined theme, class="material-icons-outlined". - /// Outlined, - /// - /// Rounded theme, class="material-icons-round". - /// - Round, - - /// - /// Two-tone theme, class="material-icons-two-tone". Note that two-tone does not use css color, but filter instead. - /// - TwoTone, - - /// - /// Sharp theme, class="material-icons-sharp". - /// + Rounded, Sharp } - - /// -/// Sets the Font Awesome style. -/// See , and -/// has a default of (all other styles except require a paid-for Font Awesome PRO licence) +/// Determines an size. /// -public enum MBIconFAStyle +public enum MBIconSize { - /// - /// Solid style, class="fas ...". This is the default. - /// - Solid, - - /// - /// Regular style, class="far ...". Requires a paid-for Font Awesome PRO licence. - /// - Regular, - - /// - /// Light style, class="fal ...". Requires a paid-for Font Awesome PRO licence. - /// - Light, - - /// - /// Duotone style, class="fad ...". Requires a paid-for Font Awesome PRO licence. - /// - Duotone, - - /// - /// Brands style, class="fab ...". - /// - Brands + Size20, + Size24, + Size40, + Size48, } - /// -/// Sets the Font Awesome relative size. -/// See , and -/// has a default of +/// Determines an weight. /// -public enum MBIconFARelativeSize +public enum MBIconWeight { - /// - /// Regular relative size (no markup applied). This is the default. - /// - Regular, - - /// - /// Extra small relative size, class="... fa-xs" - /// - ExtraSmall, + W100, + W200, + W300, + W400, + W500, + W600, + W700, +} - /// - /// Small relative size: class="... fa-sm" - /// - Small, +#endregion - /// - /// Large relative size, class="... fa-lg" - /// - Large, - - /// - /// Two times relative size. class="... fa-2x" - /// - TwoTimes, +#region MBInputEventTypeMD2 +/// +/// Determines how an responds to user events. +/// +public enum MBInputEventTypeMD2 +{ /// - /// Three times relative size, class="... fa-3x" + /// Emits events only when the thumb is released via an change event. /// - ThreeTimes, + OnChange, /// - /// Five times relative size, class="... fa-5x" + /// Emits debounced events during slider movement via input events. Debouncing requires the slider to be still for a period before emitting an event. /// - FiveTimes, + OnInputDebounced, /// - /// Seven times relative size, class="... fa-7x" + /// Emits throttled events during slider movement via input events. Throttling emits events even while the slider is moving. /// - SevenTimes, - - /// - /// Ten times relative size, class="... fa-10x" - /// - TenTimes + OnInputThrottled } +#endregion -///// -///// Determines how an responds to user events. -///// -//public enum MBInputEventType -//{ -// /// -// /// Emits events only when the thumb is released via an change event. -// /// -// OnChange, - -// /// -// /// Emits debounced events during slider movement via input events. Debouncing requires the slider to be still for a period before emitting an event. -// /// -// OnInputDebounced, - -// /// -// /// Emits throttled events during slider movement via input events. Throttling emits events even while the slider is moving. -// /// -// OnInputThrottled -//} - +#region MBItemValidation /// /// A helper to determine how a or should handle an intial bound value not matching elements in the value list. @@ -364,46 +238,9 @@ public enum MBItemValidation NoSelection } +#endregion -///// -///// Style for an . The variety borrows card markup matching . -///// has a default of -///// -//public enum MBListStyle -//{ -// /// -// /// No styling applied. This is the default. -// /// -// None, - -// /// -// /// Borrows card markup matching . -// /// -// Outlined -//} - - -///// -///// Type for an . -///// has a default of -///// -//public enum MBListType -//{ -// /// -// /// A regular list. This is the default. -// /// -// Regular, - -// /// -// /// Applies the mdc-deprecated-list--dense CSS class. -// /// -// Dense, - -// /// -// /// Applies the mdc-deprecated-list--avatar CSS class. -// /// -// Avatar -//} +#region MBLoggingLevel /// /// Type for the level of logging performed by M.B @@ -420,11 +257,14 @@ public enum MBLoggingLevel None = 0, } +#endregion + +#region MBMenuSurfacePositioningMD2 /// /// Determines the positioning and width of a menu surface. /// -public enum MBMenuSurfacePositioning +public enum MBMenuSurfacePositioningMD2 { /// /// Placed with display: relative. and assuming a width determined by its contents. @@ -444,12 +284,36 @@ public enum MBMenuSurfacePositioning Fixed } +#endregion + +#region MBNotifierCloseMethod + +/// +/// Determines whether a snackbar or a toast notfication times out and whether it has a dismiss button. +/// Defaults to +/// +public enum MBNotifierCloseMethod +{ + /// + /// Apply a timeout and show the dismiss button. This is the default. + /// + TimeoutAndDismissButton, + + /// + /// Apply a timeout only. + /// + Timeout, -///// **** Would prefer to use the following line for the summary however this fails to produce inline documentation and causes the following DocFX warning: -///// **** [20-07-16 02:56:26.727]Warning:[MetadataCommand.ExtractMetadata]Invalid triple slash comment is ignored: + /// + /// Show the dismiss button only. + /// + DismissButton +} +#endregion + +#region MBNumericInputMagnitude -///// A helper to determine the magnitude adjustment when displaying or editing values using and . /// /// A helper to determine the magnitude adjustment when displaying or editing values using numeric input fields. /// @@ -471,6 +335,9 @@ public enum MBNumericInputMagnitude BasisPoints = 4 } +#endregion + +#region MBProgress /// /// Stype for Progress . @@ -488,7 +355,6 @@ public enum MBProgressStyle Linear } - /// /// Type for Progress . /// @@ -510,99 +376,56 @@ public enum MBProgressType Closed } +#endregion + +#region MBSelectInputStyleMD2 + +/// +/// Material Theme select input style applied to . +/// Applied also to +/// has a default of +/// +public enum MBSelectInputStyleMD2 +{ + /// + /// The filled style. This is the default. + /// + Filled, + + /// + /// The outlined style. + /// + Outlined +} + +#endregion -/////// Describes the result of a search from the component. -///// -///// Describes the result of a search from the component. -///// -//public enum MBSearchResultTypes -//{ -// /// -// /// No items were found that match the search string. -// /// -// NoMatchesFound, - -// /// -// /// A full match has been found. -// /// -// FullMatchFound, - -// /// -// /// One or more matches were found, but fewer than the threshold for too many items -// /// to be indicated. -// /// -// PartialMatchesFound, - -// /// -// /// Too many items were found, resulting in a zero length search result list. -// /// -// TooManyItemsFound, -//} - - -///// -///// Material Theme select input style applied to . -///// Applied also to -///// has a default of -///// -//public enum MBSelectInputStyle -//{ -// /// -// /// The filled style. This is the default. -// /// -// Filled, - -// /// -// /// The outlined style. -// /// -// Outlined -//} - - -///// -///// Determines whether a displays the label (left hand element), value (right hand element) or both. -///// Defaults to -///// -//public enum MBShieldType -//{ -// /// -// /// Show both label (left hand element) and value (right hand element). This is the default. -// /// -// LabelAndValue, - -// /// -// /// Show both label (left hand element) only. -// /// -// LabelOnly, - -// /// -// /// Show both value (right hand element) only. -// /// -// ValueOnly -//} - - -///// -///// Determines the type of an . -///// -//public enum MBSliderType -//{ -// /// -// /// Continuous value. -// /// -// Continuous, - -// /// -// /// Discrete values. -// /// -// Discrete, - -// /// -// /// Discrete values with tickmarks. -// /// -// DiscreteWithTickmarks -//} +#region MBSliderTypeMD2 +/// +/// Determines the type of an . +/// +public enum MBSliderTypeMD2 +{ + /// + /// Continuous value. + /// + Continuous, + + /// + /// Discrete values. + /// + Discrete, + + /// + /// Discrete values with tickmarks. + /// + DiscreteWithTickmarks +} + +#endregion + +#region MBText /// /// A helper to set the alignment of text in , and . @@ -633,10 +456,9 @@ public enum MBTextAlignStyle } - /// /// Material Theme text field and text area input style applied to and . -/// Applied also to , , and +/// Applied also to , , and /// has a default of /// public enum MBTextInputStyle @@ -652,68 +474,9 @@ public enum MBTextInputStyle Outlined } +#endregion -///// -///// Material Theme top app bar type applied to an . -///// -//[Flags] -//public enum MBTopAppBarType -//{ -// /// -// /// The standard variety. -// /// -// Standard = 0, - -// /// -// /// The fixed variety. -// /// -// Fixed = 1 << 0, - - -// /// -// /// The dense variety. -// /// -// Dense = 1 << 1, - -// /// -// /// The prominent variety. -// /// -// Prominent = 1 << 2, - -// /// -// /// The short variety. -// /// -// Short = 1 << 3, - -// /// -// /// The short collapsed variety. -// /// -// ShortCollapsed = 1 << 4 -//} - - -/// -/// Determines whether a snackbar or a toast notfication times out and whether it has a dismiss button. -/// Defaults to -/// -public enum MBNotifierCloseMethod -{ - /// - /// Apply a timeout and show the dismiss button. This is the default. - /// - TimeoutAndDismissButton, - - /// - /// Apply a timeout only. - /// - Timeout, - - /// - /// Show the dismiss button only. - /// - DismissButton -} - +#region MBToast /// /// Determines the type of a toast notfication. This is a required toast parameter without defaults. @@ -793,3 +556,47 @@ public enum MBToastPosition /// BottomRight, } + +#endregion + +#region MBTopAppBarTypeMD2 + +/// +/// Material Theme top app bar type applied to an . +/// +[Flags] +public enum MBTopAppBarTypeMD2 +{ + /// + /// The standard variety. + /// + Standard = 0, + + /// + /// The fixed variety. + /// + Fixed = 1 << 0, + + + /// + /// The dense variety. + /// + Dense = 1 << 1, + + /// + /// The prominent variety. + /// + Prominent = 1 << 2, + + /// + /// The short variety. + /// + Short = 1 << 3, + + /// + /// The short collapsed variety. + /// + ShortCollapsed = 1 << 4 +} + +#endregion diff --git a/Material.Blazor.MD3/Model/MBIconBearingSelectElement.cs b/Material.Blazor.MD3/Model/MBIconBearingSelectElement.cs index 1db8a906f..77396d875 100644 --- a/Material.Blazor.MD3/Model/MBIconBearingSelectElement.cs +++ b/Material.Blazor.MD3/Model/MBIconBearingSelectElement.cs @@ -4,7 +4,7 @@ /// A list item used by /// /// -public record MBIconBearingSelectElement : MBSelectElementMD2 +public record MBIconBearingSelectElement : MBSelectElement { /// /// The leading icon. diff --git a/Material.Blazor.MD3/Model.MD2/ServiceCollectionExtensions.cs b/Material.Blazor.MD3/Model/ServiceCollectionExtensions.cs similarity index 91% rename from Material.Blazor.MD3/Model.MD2/ServiceCollectionExtensions.cs rename to Material.Blazor.MD3/Model/ServiceCollectionExtensions.cs index 718fed82f..8320ac0da 100644 --- a/Material.Blazor.MD3/Model.MD2/ServiceCollectionExtensions.cs +++ b/Material.Blazor.MD3/Model/ServiceCollectionExtensions.cs @@ -1,5 +1,4 @@ using Material.Blazor.Internal; -using Material.Blazor.Internal.MD2; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using System; @@ -24,8 +23,7 @@ public static IServiceCollection AddMBServices(this IServiceCollection serviceCo return serviceCollection .AddScoped(serviceProvider => ActivatorUtilities.CreateInstance(serviceProvider, serviceProvider.GetRequiredService>())) - .AddScoped(serviceProvider => ActivatorUtilities.CreateInstance(serviceProvider, serviceProvider.GetRequiredService>())) - .AddScoped(serviceProvider => new TooltipService()); + .AddScoped(serviceProvider => ActivatorUtilities.CreateInstance(serviceProvider, serviceProvider.GetRequiredService>())); } /// diff --git a/Material.Blazor.MD3/Model/Toast/IMBToastService.cs b/Material.Blazor.MD3/Model/Toast/IMBToastService.cs index 59310fc04..95f625833 100644 --- a/Material.Blazor.MD3/Model/Toast/IMBToastService.cs +++ b/Material.Blazor.MD3/Model/Toast/IMBToastService.cs @@ -9,7 +9,7 @@ namespace Material.Blazor; /// /// /// Throws a if -/// +/// /// is called without an component used in the app. /// /// @@ -50,7 +50,7 @@ public interface IMBToastService /// /// Shows a toast using the supplied settings. Only the level and message parameters are required, with - /// the remainder haveing defaults specified by the that you can supply + /// the remainder having defaults specified by the that you can supply /// when registering services. Failing that Material.Blazor provides defaults. /// /// Severity of the toast (info, error, etc) @@ -59,7 +59,6 @@ public interface IMBToastService /// close method /// additional css applied to toast /// Icon name - /// The icon's foundry /// Show or hide icon /// Length of time before autodismiss /// If true only shows toasts when compiling in DEBUG mode diff --git a/Material.Blazor.MD3/Scripts/lodashparts.ts b/Material.Blazor.MD3/Scripts/lodashparts.ts new file mode 100644 index 000000000..0774d8090 --- /dev/null +++ b/Material.Blazor.MD3/Scripts/lodashparts.ts @@ -0,0 +1,384 @@ +/** + * @license + * + * This file is based on https://github.com/lodash/lodash and thus licensed as follows. + * The code & license is based on https://github.com/lodash/lodash/tree/2da024c3b4f9947a48517639de7560457cd4ec6c + * + * The MIT License + * + * Copyright JS Foundation and other contributors + * + * Based on Underscore.js, copyright Jeremy Ashkenas, + * DocumentCloud and Investigative Reporters & Editors + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision history + * available at https://github.com/lodash/lodash + * + * The following license applies to all parts of this software except as + * documented below: + * + * ==== + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * ==== + * + * Copyright and related rights for sample code are waived via CC0. Sample + * code is defined as all source code displayed within the prose of the + * documentation. + * + * CC0: http://creativecommons.org/publicdomain/zero/1.0/ + * + * ==== + * + * Files located in the node_modules and vendor directories are externally + * maintained libraries used by this software which have their own + * licenses; we recommend you read them, as their terms may differ from the + * terms above. + * + */ + +//BEGIN https://github.com/lodash/lodash/blob/master/isObject.js +/** + * Checks if `value` is the + * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types) + * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * + * @since 0.1.0 + * @category Lang + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is an object, else `false`. + * @example + * + * isObject({}) + * // => true + * + * isObject([1, 2, 3]) + * // => true + * + * isObject(Function) + * // => true + * + * isObject(null) + * // => false + */ +function isObject(value) { + const type = typeof value + return value != null && (type === 'object' || type === 'function') +} +//END https://github.com/lodash/lodash/blob/master/isObject.js + +//BEGIN https://github.com/lodash/lodash/blob/master/.internal/freeGlobal.js +const freeGlobal = typeof global === 'object' && global !== null && global.Object === Object && global +//END https://github.com/lodash/lodash/blob/master/.internal/freeGlobal.js + +//BEGIN https://github.com/lodash/lodash/blob/master/.internal/root.js +/** Detect free variable `globalThis` */ +const freeGlobalThis = typeof globalThis === 'object' && globalThis !== null && globalThis.Object == Object && globalThis + +/** Detect free variable `self`. */ +const freeSelf = typeof self === 'object' && self !== null && self.Object === Object && self + +/** Used as a reference to the global object. */ +const root = freeGlobalThis || freeGlobal || freeSelf || Function('return this')() +//END https://github.com/lodash/lodash/blob/master/.internal/root.js + +//BEGIN https://github.com/lodash/lodash/blob/master/debounce.js +/** + * Creates a debounced function that delays invoking `func` until after `wait` + * milliseconds have elapsed since the last time the debounced function was + * invoked, or until the next browser frame is drawn. The debounced function + * comes with a `cancel` method to cancel delayed `func` invocations and a + * `flush` method to immediately invoke them. Provide `options` to indicate + * whether `func` should be invoked on the leading and/or trailing edge of the + * `wait` timeout. The `func` is invoked with the last arguments provided to the + * debounced function. Subsequent calls to the debounced function return the + * result of the last `func` invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the debounced function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until the next tick, similar to `setTimeout` with a timeout of `0`. + * + * If `wait` is omitted in an environment with `requestAnimationFrame`, `func` + * invocation will be deferred until the next frame is drawn (typically about + * 16ms). + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `debounce` and `throttle`. + * + * @since 0.1.0 + * @category Function + * @param {Function} func The function to debounce. + * @param {number} [wait=0] + * The number of milliseconds to delay; if omitted, `requestAnimationFrame` is + * used (if available). + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=false] + * Specify invoking on the leading edge of the timeout. + * @param {number} [options.maxWait] + * The maximum time `func` is allowed to be delayed before it's invoked. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new debounced function. + * @example + * + * // Avoid costly calculations while the window size is in flux. + * jQuery(window).on('resize', debounce(calculateLayout, 150)) + * + * // Invoke `sendMail` when clicked, debouncing subsequent calls. + * jQuery(element).on('click', debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * })) + * + * // Ensure `batchLog` is invoked once after 1 second of debounced calls. + * const debounced = debounce(batchLog, 250, { 'maxWait': 1000 }) + * const source = new EventSource('/stream') + * jQuery(source).on('message', debounced) + * + * // Cancel the trailing debounced invocation. + * jQuery(window).on('popstate', debounced.cancel) + * + * // Check for pending invocations. + * const status = debounced.pending() ? "Pending..." : "Ready" + */ +function debounce(func, wait, options) { + let lastArgs, + lastThis: any, + maxWait, + result, + timerId, + lastCallTime + + let lastInvokeTime = 0 + let leading = false + let maxing = false + let trailing = true + + // Bypass `requestAnimationFrame` by explicitly setting `wait=0`. + const useRAF = (!wait && wait !== 0 && typeof root.requestAnimationFrame === 'function') + + if (typeof func !== 'function') { + throw new TypeError('Expected a function') + } + wait = +wait || 0 + if (isObject(options)) { + leading = !!options.leading + maxing = 'maxWait' in options + maxWait = maxing ? Math.max(+options.maxWait || 0, wait) : maxWait + trailing = 'trailing' in options ? !!options.trailing : trailing + } + + function invokeFunc(time) { + const args = lastArgs + const thisArg = lastThis + + lastArgs = lastThis = undefined + lastInvokeTime = time + result = func.apply(thisArg, args) + return result + } + + function startTimer(pendingFunc, wait) { + if (useRAF) { + root.cancelAnimationFrame(timerId) + return root.requestAnimationFrame(pendingFunc) + } + return setTimeout(pendingFunc, wait) + } + + function cancelTimer(id) { + if (useRAF) { + return root.cancelAnimationFrame(id) + } + clearTimeout(id) + } + + function leadingEdge(time) { + // Reset any `maxWait` timer. + lastInvokeTime = time + // Start the timer for the trailing edge. + timerId = startTimer(timerExpired, wait) + // Invoke the leading edge. + return leading ? invokeFunc(time) : result + } + + function remainingWait(time) { + const timeSinceLastCall = time - lastCallTime + const timeSinceLastInvoke = time - lastInvokeTime + const timeWaiting = wait - timeSinceLastCall + + return maxing + ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) + : timeWaiting + } + + function shouldInvoke(time) { + const timeSinceLastCall = time - lastCallTime + const timeSinceLastInvoke = time - lastInvokeTime + + // Either this is the first call, activity has stopped and we're at the + // trailing edge, the system time has gone backwards and we're treating + // it as the trailing edge, or we've hit the `maxWait` limit. + return (lastCallTime === undefined || (timeSinceLastCall >= wait) || + (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)) + } + + function timerExpired() { + const time = Date.now() + if (shouldInvoke(time)) { + return trailingEdge(time) + } + // Restart the timer. + timerId = startTimer(timerExpired, remainingWait(time)) + } + + function trailingEdge(time) { + timerId = undefined + + // Only invoke if we have `lastArgs` which means `func` has been + // debounced at least once. + if (trailing && lastArgs) { + return invokeFunc(time) + } + lastArgs = lastThis = undefined + return result + } + + function cancel() { + if (timerId !== undefined) { + cancelTimer(timerId) + } + lastInvokeTime = 0 + lastArgs = lastCallTime = lastThis = timerId = undefined + } + + function flush() { + return timerId === undefined ? result : trailingEdge(Date.now()) + } + + function pending() { + return timerId !== undefined + } + + function debounced(this: any, ...args) { + const time = Date.now() + const isInvoking = shouldInvoke(time) + + lastArgs = args + lastThis = this + lastCallTime = time + + if (isInvoking) { + if (timerId === undefined) { + return leadingEdge(lastCallTime) + } + if (maxing) { + // Handle invocations in a tight loop. + timerId = startTimer(timerExpired, wait) + return invokeFunc(lastCallTime) + } + } + if (timerId === undefined) { + timerId = startTimer(timerExpired, wait) + } + return result + } + debounced.cancel = cancel + debounced.flush = flush + debounced.pending = pending + return debounced +} +// END https://github.com/lodash/lodash/blob/master/debounce.js + +//BEGIN https://github.com/lodash/lodash/blob/master/throttle.js +/** + * Creates a throttled function that only invokes `func` at most once per + * every `wait` milliseconds (or once per browser frame). The throttled function + * comes with a `cancel` method to cancel delayed `func` invocations and a + * `flush` method to immediately invoke them. Provide `options` to indicate + * whether `func` should be invoked on the leading and/or trailing edge of the + * `wait` timeout. The `func` is invoked with the last arguments provided to the + * throttled function. Subsequent calls to the throttled function return the + * result of the last `func` invocation. + * + * **Note:** If `leading` and `trailing` options are `true`, `func` is + * invoked on the trailing edge of the timeout only if the throttled function + * is invoked more than once during the `wait` timeout. + * + * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred + * until the next tick, similar to `setTimeout` with a timeout of `0`. + * + * If `wait` is omitted in an environment with `requestAnimationFrame`, `func` + * invocation will be deferred until the next frame is drawn (typically about + * 16ms). + * + * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) + * for details over the differences between `throttle` and `debounce`. + * + * @since 0.1.0 + * @category Function + * @param {Function} func The function to throttle. + * @param {number} [wait=0] + * The number of milliseconds to throttle invocations to; if omitted, + * `requestAnimationFrame` is used (if available). + * @param {Object} [options={}] The options object. + * @param {boolean} [options.leading=true] + * Specify invoking on the leading edge of the timeout. + * @param {boolean} [options.trailing=true] + * Specify invoking on the trailing edge of the timeout. + * @returns {Function} Returns the new throttled function. + * @example + * + * // Avoid excessively updating the position while scrolling. + * jQuery(window).on('scroll', throttle(updatePosition, 100)) + * + * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. + * const throttled = throttle(renewToken, 300000, { 'trailing': false }) + * jQuery(element).on('click', throttled) + * + * // Cancel the trailing throttled invocation. + * jQuery(window).on('popstate', throttled.cancel) + */ +function throttle(func, wait, options) { + let leading = true + let trailing = true + + if (typeof func !== 'function') { + throw new TypeError('Expected a function') + } + if (isObject(options)) { + leading = 'leading' in options ? !!options.leading : leading + trailing = 'trailing' in options ? !!options.trailing : trailing + } + return debounce(func, wait, { + leading, + trailing, + 'maxWait': wait + }) +} +//END https://github.com/lodash/lodash/blob/master/throttle.js + +export { throttle, debounce } + diff --git a/Material.Blazor.MD3/Scripts/material.blazor.ts b/Material.Blazor.MD3/Scripts/material.blazor.ts index 2d86372e2..53aa0ee81 100644 --- a/Material.Blazor.MD3/Scripts/material.blazor.ts +++ b/Material.Blazor.MD3/Scripts/material.blazor.ts @@ -1,6 +1,11 @@ /* MD3 JS */ +import '@material/web/button/elevated-button.js'; +import '@material/web/button/filled-button.js'; +import '@material/web/button/filled-tonal-button.js'; +import '@material/web/button/outlined-button.js'; +import '@material/web/button/text-button.js'; import '@material/web/checkbox/checkbox.js'; import '@material/web/icon/icon.js'; import '@material/web/iconbutton/icon-button.js'; @@ -12,7 +17,6 @@ import '@material/web/textfield/outlined-text-field.js'; import '@material/web/switch/switch.js'; import * as MBTextField from '../Components/TextField/MBTextField'; -import * as MBTextField2 from '../Components/TextField2/MBTextField2'; /* MD2 JS @@ -24,12 +28,11 @@ import * as MBDrawer from '../Components.MD2/Drawer/MBDrawer'; import * as MBIconButton from '../Components.MD2/IconButton/MBIconButton' import * as MBMenu from '../Components.MD2/Menu/MBMenu'; import * as MBSelect from '../Components.MD2/Select/MBSelect'; +import * as MBSlider from '../Components.MD2/Slider/MBSlider'; import * as MBTopAppBar from '../Components.MD2/TopAppBar/MBTopAppBar'; -import * as MBTooltip from '../Components.MD2/Tooltip/MBTooltip'; (window).MaterialBlazor = { MBTextField, - MBTextField2, MBCard, MBDataTable, @@ -38,6 +41,6 @@ import * as MBTooltip from '../Components.MD2/Tooltip/MBTooltip'; MBIconButton, MBMenu, MBSelect, - MBTopAppBar, - MBTooltip + MBSlider, + MBTopAppBar }; diff --git a/Material.Blazor.MD3/Styles/_material-components-icons.scss b/Material.Blazor.MD3/Styles/_material-components-icons.scss deleted file mode 100644 index e9a8897e1..000000000 --- a/Material.Blazor.MD3/Styles/_material-components-icons.scss +++ /dev/null @@ -1,212 +0,0 @@ -/* - * sources for this file are: - * Outlined Material Symbols: https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200 - * Rounded Material Symbols: https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200 - * Sharp Material Symbols: https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200 - * - * Material Icons: https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp - */ - - -/* - * Outlined Material Symbols -*/ - -/* fallback */ -@font-face { - font-family: 'Material Symbols Outlined'; - font-style: normal; - font-weight: 100 700; - src: url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v76/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsI.woff2) format('woff2'); -} - -.material-symbols-outlined { - font-family: 'Material Symbols Outlined'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-smoothing: antialiased; -} - - -/* - * Rounded Material Symbols -*/ - -/* fallback */ -@font-face { - font-family: 'Material Symbols Rounded'; - font-style: normal; - font-weight: 100 700; - src: url(https://fonts.gstatic.com/s/materialsymbolsrounded/v76/sykg-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190Fjzag.woff2) format('woff2'); -} - -.material-symbols-rounded { - font-family: 'Material Symbols Rounded'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-smoothing: antialiased; -} - - -/* - * Sharp Material Symbols -*/ - -/* fallback */ -@font-face { - font-family: 'Material Symbols Sharp'; - font-style: normal; - font-weight: 100 700; - src: url(https://fonts.gstatic.com/s/materialsymbolssharp/v75/gNMVW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4aWE.woff2) format('woff2'); -} - -.material-symbols-sharp { - font-family: 'Material Symbols Sharp'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-smoothing: antialiased; -} - - -/* - * Material Icons -*/ - -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialicons/v134/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Outlined'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconsoutlined/v106/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUce.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Round'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconsround/v105/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmP.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Sharp'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconssharp/v106/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvR.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Two Tone'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconstwotone/v109/hESh6WRmNCxEqUmNyh3JDeGxjVVyMg4tHGctNCu0.woff2) format('woff2'); -} - -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-outlined { - font-family: 'Material Icons Outlined'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-round { - font-family: 'Material Icons Round'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-sharp { - font-family: 'Material Icons Sharp'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-two-tone { - font-family: 'Material Icons Two Tone'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} diff --git a/Material.Blazor.MD3/Styles/_material-symbols-outlined.scss b/Material.Blazor.MD3/Styles/_material-symbols-outlined.scss new file mode 100644 index 000000000..3e17ef849 --- /dev/null +++ b/Material.Blazor.MD3/Styles/_material-symbols-outlined.scss @@ -0,0 +1,23 @@ +/* fallback */ +@font-face { + font-family: 'Material Symbols Outlined'; + font-style: normal; + font-weight: 100 700; + src: url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v138/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsI.woff2) format('woff2'); +} + +.material-symbols-outlined { + font-family: 'Material Symbols Outlined'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/Material.Blazor.MD3/Styles/_material-symbols-rounded.scss b/Material.Blazor.MD3/Styles/_material-symbols-rounded.scss new file mode 100644 index 000000000..10085c9b0 --- /dev/null +++ b/Material.Blazor.MD3/Styles/_material-symbols-rounded.scss @@ -0,0 +1,23 @@ +/* fallback */ +@font-face { + font-family: 'Material Symbols Rounded'; + font-style: normal; + font-weight: 100 700; + src: url(https://fonts.gstatic.com/s/materialsymbolsrounded/v138/sykg-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190Fjzag.woff2) format('woff2'); +} + +.material-symbols-rounded { + font-family: 'Material Symbols Rounded'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/Material.Blazor.MD3/Styles/_material-symbols-sharp.scss b/Material.Blazor.MD3/Styles/_material-symbols-sharp.scss new file mode 100644 index 000000000..4e03ef220 --- /dev/null +++ b/Material.Blazor.MD3/Styles/_material-symbols-sharp.scss @@ -0,0 +1,23 @@ +/* fallback */ +@font-face { + font-family: 'Material Symbols Sharp'; + font-style: normal; + font-weight: 100 700; + src: url(https://fonts.gstatic.com/s/materialsymbolssharp/v135/gNMVW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4aWE.woff2) format('woff2'); +} + +.material-symbols-sharp { + font-family: 'Material Symbols Sharp'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/Material.Blazor.MD3/Styles/material.blazor.scss b/Material.Blazor.MD3/Styles/material.blazor.scss index d128cd3d3..375ea9d86 100644 --- a/Material.Blazor.MD3/Styles/material.blazor.scss +++ b/Material.Blazor.MD3/Styles/material.blazor.scss @@ -1,9 +1,10 @@ @charset "UTF-8"; @use '_colors.scss'; -@use "_material-components-icons.scss"; +@use '_material-symbols-outlined.scss'; +@use '_material-symbols-rounded.scss'; +@use '_material-symbols-sharp.scss'; -@use '../Components/Badge/MBBadge.scss'; @use '../Components/RadioButtonGroup/MBRadioButtonGroup.scss'; @use '../Components/Toast/MBToast.scss'; diff --git a/Material.Blazor.MD3/_Imports.razor b/Material.Blazor.MD3/_Imports.razor index 92fbcb70f..451d0745b 100644 --- a/Material.Blazor.MD3/_Imports.razor +++ b/Material.Blazor.MD3/_Imports.razor @@ -1,7 +1,8 @@ -@using System.Threading.Tasks -@using Microsoft.JSInterop -@using Microsoft.AspNetCore.Components.Web @using Material.Blazor -@using Material.Blazor.MD2 @using Material.Blazor.Internal -@using Material.Blazor.Internal.MD2 +@using Material.Blazor.MD2 + +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.JSInterop + +@using System.Threading.Tasks diff --git a/Material.Blazor.MD3/package-lock.json b/Material.Blazor.MD3/package-lock.json index e4f3ad6cc..1677ba27b 100644 --- a/Material.Blazor.MD3/package-lock.json +++ b/Material.Blazor.MD3/package-lock.json @@ -9,21 +9,21 @@ "version": "4.0.0", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.22.15", - "@babel/core": "^7.22.20", + "@babel/cli": "^7.23.0", + "@babel/core": "^7.23.0", "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-object-rest-spread": "^7.22.15", "@babel/plugin-transform-runtime": "^7.22.15", "@babel/preset-env": "^7.22.20", - "@babel/preset-typescript": "^7.22.15", - "@material/web": "1.0.0-pre.17", + "@babel/preset-typescript": "^7.23.0", + "@material/web": "1.0.0", "babel-loader": "^9.1.3", "fork-ts-checker-webpack-plugin": "^8.0.0", "material-components-web": "14.0.0", "regexp": "^1.0.0", "sass": "1.39.2", - "terser": "^5.19.4", - "ts-loader": "^9.4.4", + "terser": "^5.21.0", + "ts-loader": "^9.5.0", "typescript": "^5.2.2", "webpack": "^5.88.2", "webpack-cli": "^5.1.4" @@ -43,14 +43,14 @@ } }, "node_modules/@babel/cli": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.15.tgz", - "integrity": "sha512-prtg5f6zCERIaECeTZzd2fMtVjlfjhUcO+fBLQ6DXXdq5FljN+excVitJ2nogsusdf31LeqkjAfXZ7Xq+HmN8g==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz", + "integrity": "sha512-17E1oSkGk2IwNILM4jtfAvgjt+ohmpfBky8aLerUfYZhiPNg7ca+CRCxZn8QDxwNhV/upsc2VHBCqGFIR+iBfA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "commander": "^4.0.1", - "convert-source-map": "^1.1.0", + "convert-source-map": "^2.0.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.2.0", "make-dir": "^2.1.0", @@ -94,22 +94,22 @@ } }, "node_modules/@babel/core": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", - "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", + "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.20", - "@babel/helpers": "^7.22.15", - "@babel/parser": "^7.22.16", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.0", + "@babel/parser": "^7.23.0", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.20", - "@babel/types": "^7.22.19", - "convert-source-map": "^1.7.0", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", @@ -124,12 +124,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -244,13 +244,13 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -269,12 +269,12 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz", - "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -293,9 +293,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz", - "integrity": "sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -444,14 +444,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", + "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -472,9 +472,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -858,9 +858,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz", - "integrity": "sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -945,9 +945,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz", - "integrity": "sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1133,12 +1133,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1149,12 +1149,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz", - "integrity": "sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1166,15 +1166,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", - "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.9", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1314,9 +1314,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz", - "integrity": "sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1711,15 +1711,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz", - "integrity": "sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz", + "integrity": "sha512-6P6VVa/NM/VlAYj5s2Aq/gdVg8FSENCg3wlZ6Qau9AcPaoF5LbN1nyGlR9DTRIw9PpxI94e+ReydsJHcjwAweg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.15", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", "@babel/plugin-transform-typescript": "^7.22.15" }, "engines": { @@ -1736,9 +1736,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1762,19 +1762,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", - "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", + "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1783,13 +1783,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", - "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.19", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -1864,18 +1864,18 @@ } }, "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.1.tgz", - "integrity": "sha512-kXOeFbfCm4fFf2A3WwVEeQj55tMZa8c8/f9AKHMobQMkzNUfUj+antR3fRPaZJawsa1aZiP/Da3ndpZrwEe4rQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.1.2.tgz", + "integrity": "sha512-jnOD+/+dSrfTWYfSXBXlo5l5f0q1UuJo3tkbMDCYA2lKUYq79jaxqtGEvnRoh049nt1vdo1+45RinipU6FGY2g==", "dev": true }, "node_modules/@lit/reactive-element": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.6.3.tgz", - "integrity": "sha512-QuTgnG52Poic7uM1AN5yJ09QMe0O28e10XzSvWDz02TJiiKee4stsiownEIadWm8nYzyDAyT+gKzUoZmiWQtsQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.0.0.tgz", + "integrity": "sha512-wn+2+uDcs62ROBmVAwssO4x5xue/uKD3MGGZOXL2sMxReTRIT0JXKyMXeu7gh0aJ4IJNEIG/3aOnUaQvM7BMzQ==", "dev": true, "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.0.0" + "@lit-labs/ssr-dom-shim": "^1.1.2-pre.0" } }, "node_modules/@material/animation": { @@ -2663,9 +2663,9 @@ } }, "node_modules/@material/web": { - "version": "1.0.0-pre.17", - "resolved": "https://registry.npmjs.org/@material/web/-/web-1.0.0-pre.17.tgz", - "integrity": "sha512-8iqrMudWxAUTXfnkf3GraxgLv/3cLbvRBkp6Al9/sxgwO+aTEzKC2BP6sDTqINAubsgOOOuIul/1emyjvq2ipg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@material/web/-/web-1.0.0.tgz", + "integrity": "sha512-mNn2qzHvzJLty93NTtnkrSup+Wnte5yAtcZIx0N+WbIZPVlixA2v6wXkqHN1/SJVl0EV2tNT1qz4k0jfTQ+0lw==", "dev": true, "dependencies": { "lit": "^2.7.4 || ^3.0.0", @@ -2680,9 +2680,9 @@ "optional": true }, "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "version": "8.44.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", + "integrity": "sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==", "dev": true, "dependencies": { "@types/estree": "*", @@ -2690,9 +2690,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz", + "integrity": "sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -2700,9 +2700,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", + "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true }, "node_modules/@types/json-schema": { @@ -2712,10 +2712,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==", - "dev": true + "version": "20.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", + "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", + "dev": true, + "dependencies": { + "undici-types": "~5.25.1" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -3054,13 +3057,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", - "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.4.tgz", + "integrity": "sha512-9l//BZZsPR+5XjyJMPtZSK4jv0BsTO1zDac2GC6ygx9WLGlcsnRd1Co0B2zT5fF5Ic6BZy+9m3HNZ3QcOeDKfg==", "dev": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.31.0" + "core-js-compat": "^3.32.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3116,9 +3119,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -3135,10 +3138,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -3163,9 +3166,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001534", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", - "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", + "version": "1.0.30001547", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", + "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", "dev": true, "funding": [ { @@ -3289,18 +3292,18 @@ "dev": true }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, "node_modules/core-js-compat": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.2.tgz", - "integrity": "sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ==", + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", + "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", "dev": true, "dependencies": { - "browserslist": "^4.21.10" + "browserslist": "^4.22.1" }, "funding": { "type": "opencollective", @@ -3364,9 +3367,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.523", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.523.tgz", - "integrity": "sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg==", + "version": "1.4.548", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.548.tgz", + "integrity": "sha512-R77KD6mXv37DOyKLN/eW1rGS61N6yHOfapNSX9w+y9DdPG83l9Gkuv7qkCFZ4Ta4JPhrjgQfYbv4Y3TnM1Hi2Q==", "dev": true }, "node_modules/enhanced-resolve": { @@ -3748,9 +3751,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", "dev": true }, "node_modules/fs-readdir-recursive": { @@ -3779,12 +3782,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3848,13 +3845,10 @@ "dev": true }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { "node": ">= 0.4.0" } @@ -4196,31 +4190,31 @@ "dev": true }, "node_modules/lit": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-2.8.0.tgz", - "integrity": "sha512-4Sc3OFX9QHOJaHbmTMk28SYgVxLN3ePDjg7hofEft2zWlehFL3LiAuapWc4U/kYwMYJSh2hTCPZ6/LIC7ii0MA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.0.0.tgz", + "integrity": "sha512-nQ0teRzU1Kdj++VdmttS2WvIen8M79wChJ6guRKIIym2M3Ansg3Adj9O6yuQh2IpjxiUXlNuS81WKlQ4iL3BmA==", "dev": true, "dependencies": { - "@lit/reactive-element": "^1.6.0", - "lit-element": "^3.3.0", - "lit-html": "^2.8.0" + "@lit/reactive-element": "^2.0.0", + "lit-element": "^4.0.0", + "lit-html": "^3.0.0" } }, "node_modules/lit-element": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.3.3.tgz", - "integrity": "sha512-XbeRxmTHubXENkV4h8RIPyr8lXc+Ff28rkcQzw3G6up2xg5E8Zu1IgOWIwBLEQsu3cOVFqdYwiVi0hv0SlpqUA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.0.0.tgz", + "integrity": "sha512-N6+f7XgusURHl69DUZU6sTBGlIN+9Ixfs3ykkNDfgfTkDYGGOWwHAYBhDqVswnFGyWgQYR2KiSpu4J76Kccs/A==", "dev": true, "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.1.0", - "@lit/reactive-element": "^1.3.0", - "lit-html": "^2.8.0" + "@lit-labs/ssr-dom-shim": "^1.1.2-pre.0", + "@lit/reactive-element": "^2.0.0", + "lit-html": "^3.0.0" } }, "node_modules/lit-html": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.8.0.tgz", - "integrity": "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.0.0.tgz", + "integrity": "sha512-DNJIE8dNY0dQF2Gs0sdMNUppMQT2/CvV4OVnSdg7BXAsGqkVwsE5bqQ04POfkYH5dBIuGnJYdFz5fYYyNnOxiA==", "dev": true, "dependencies": { "@types/trusted-types": "^2.0.2" @@ -4945,9 +4939,9 @@ } }, "node_modules/terser": { - "version": "5.19.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", - "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz", + "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -5073,15 +5067,16 @@ } }, "node_modules/ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", + "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" @@ -5176,6 +5171,15 @@ "node": ">=10" } }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/ts-loader/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5213,6 +5217,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "dev": true + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -5263,9 +5273,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { diff --git a/Material.Blazor.MD3/package.json b/Material.Blazor.MD3/package.json index c6f852739..e50ef9a0a 100644 --- a/Material.Blazor.MD3/package.json +++ b/Material.Blazor.MD3/package.json @@ -4,6 +4,9 @@ "description": "NPM build steps for Material.Blazor", "repository": "https://github.com/Material-Blazor/Material.Blazor", "scripts": { + "build-acquire-font-MSO": "curl \"https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200\" --output styles\\_material-symbols-outlined.scss --user-agent \"Mozilla/5.0,(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\" --no-progress-meter", + "build-acquire-font-MSR": "curl \"https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200\" --output styles\\_material-symbols-rounded.scss --user-agent \"Mozilla/5.0,(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\" --no-progress-meter", + "build-acquire-font-MSS": "curl \"https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200\" --output styles\\_material-symbols-sharp.scss --user-agent \"Mozilla/5.0,(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\" --no-progress-meter", "build-css": "sass --no-color --unicode --style=expanded --load-path=node_modules --no-source-map Styles/material.blazor.scss wwwroot/material.blazor.css", "build-min-css": "sass --no-color --unicode --style=compressed --load-path=node_modules --no-source-map Styles/material.blazor.scss wwwroot/material.blazor.min.css", "build-js": "terser wwwroot/intermediate.js --beautify --output wwwroot/material.blazor.js", @@ -16,21 +19,21 @@ "author": "", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.22.15", - "@babel/core": "^7.22.20", + "@babel/cli": "^7.23.0", + "@babel/core": "^7.23.0", "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-object-rest-spread": "^7.22.15", "@babel/plugin-transform-runtime": "^7.22.15", "@babel/preset-env": "^7.22.20", - "@babel/preset-typescript": "^7.22.15", - "@material/web": "1.0.0-pre.17", + "@babel/preset-typescript": "^7.23.0", + "@material/web": "1.0.0", "babel-loader": "^9.1.3", "fork-ts-checker-webpack-plugin": "^8.0.0", "material-components-web": "14.0.0", "regexp": "^1.0.0", "sass": "1.39.2", - "terser": "^5.19.4", - "ts-loader": "^9.4.4", + "terser": "^5.21.0", + "ts-loader": "^9.5.0", "typescript": "^5.2.2", "webpack": "^5.88.2", "webpack-cli": "^5.1.4" diff --git a/Material.Blazor.Test/Material.Blazor.Test.csproj b/Material.Blazor.Test/Material.Blazor.Test.csproj index 53d7ab37c..6e9899a43 100644 --- a/Material.Blazor.Test/Material.Blazor.Test.csproj +++ b/Material.Blazor.Test/Material.Blazor.Test.csproj @@ -8,12 +8,12 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Material.Blazor.Website.MD3/Material.Blazor.Website.MD3.csproj b/Material.Blazor.Website.MD3/Material.Blazor.Website.MD3.csproj index a3b7cd2d5..c87c81e29 100644 --- a/Material.Blazor.Website.MD3/Material.Blazor.Website.MD3.csproj +++ b/Material.Blazor.Website.MD3/Material.Blazor.Website.MD3.csproj @@ -39,11 +39,10 @@ - - + + - - + diff --git a/Material.Blazor.Website.MD3/Pages/Badge.razor b/Material.Blazor.Website.MD3/Pages/Badge.razor deleted file mode 100644 index f36f2e270..000000000 --- a/Material.Blazor.Website.MD3/Pages/Badge.razor +++ /dev/null @@ -1,377 +0,0 @@ -@page "/badge" - -@inject IMBToastService ToastService - - - - - -

    - Shows badges set in html elements and embedded in buttons, icon buttons and icon toggle buttons. -

    -
    - - -
    -
    - -
    - -
    - -
    - -
    - -
    -
    -
    - - -
    - - -

    - Badges in HTML elements -

    - -

    - A badge must be the first element inside its parent div. The parent div must have both border and padding set to zero width. -

    - -
    - -

    Value Bearing badge

    -
    - -
    - -

    Value Bearing badge

    -
    - -
    - -

    Blank Full Size badge

    -
    - -
    - -

    Dot badge

    -
    -
    -
    -
    - - -
    - - -

    - Embedded in buttons, icon buttons, checkbox, switch, and text components -

    - -
    -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - - @* -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - -
    - -
    -
    - -
    -
    - -
    - - *@ - -
    -
    -
    -
    -
    -
    - - - -@code { - private bool Exited { get; set; } = false; - private string GeneralValue { get; set; } = "99+"; - private string ButtonValue { get; set; } = "!"; - private bool ToggleValue { get; set; } = false; - private string TextFieldValue { get; set; } = "Text field value"; - - private string SelectValue { get; set; } = KittenBreeds[0].SelectedValue; - - private static Material.Blazor.MD2.MBSelectElementMD2[] KittenBreeds = new Material.Blazor.MD2.MBSelectElementMD2[] - { - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "brit-short", Label = "British Shorthair" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "russ-blue", Label = "Russian Blue" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "ice-invis", Label = "Icelandic Invisible" } - }; - - //private string SegmentedButtonValue { get; set; } = ThisThat[0].SelectedValue; - - // private static MBIconBearingSelectElement[] ThisThat = new MBIconBearingSelectElement[] - // { - // new MBIconBearingSelectElement { SelectedValue = "this", TrailingLabel = "This" }, - // new MBIconBearingSelectElement { SelectedValue = "that", TrailingLabel = "That" } - // }; - - private DateTime Min { get; set; } = new DateTime(2019, 12, 14); - private DateTime Max { get; set; } = new DateTime(2097, 6, 14); - private DateTime Date { get; set; } = DateTime.Today; -} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Button.razor b/Material.Blazor.Website.MD3/Pages/Button.razor new file mode 100644 index 000000000..425504676 --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/Button.razor @@ -0,0 +1,209 @@ +@page "/button" + +@inject IMBToastService ToastService + + + + + +

    + Shows regular button styles, icon usage, class and style, & density application. +

    +
    + + +
    + + +

    + Button Styles +

    + +

    + Explicit button styles. +

    + +

    + +

    +

    + +

    +

    + +

    +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Icons +

    + +

    + Leading and trailing icons +

    + +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Theming +

    + +

    + Applying CSS class attributes to control color, shape, and typography. +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    + +

    +
    +
    +
    + + + +
    + + +

    + Button Density +

    + +

    + Shows density subsystem from default to minus 3. +

    + +

    + +

    +

    + +

    +

    + +

    +

    + +

    +
    +
    +
    + +
    +
    + + + +@code { + private void ButtonClick(string notification) + { + ToastService.ShowToast(heading: "Button Clicked", message: notification, level: MBToastLevel.Success, showIcon: false); + } +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Card.razor b/Material.Blazor.Website.MD3/Pages/Card.razor new file mode 100644 index 000000000..c2d2d1772 --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/Card.razor @@ -0,0 +1,191 @@ +@page "/card" + +@inject IMBToastService ToastService + + + + + +

    + Demonstrates cards, with a focus on actions. The demonstration website already makes substantial use of varying card types. Note that cards cannot be disabled, although buttons on the cards can. +

    +
    + + +
    + + +
    +
    +

    Our Changing Planet

    +

    by Kurt Wagner

    +
    +
    Visit ten places on our planet that are undergoing the biggest changes today.
    +
    + + + + + + + + + + +
    +
    + + +
    + + +
    +
    +
    +

    Our Changing Planet

    +

    by Kurt Wagner

    +
    +
    +
    +
    +
    +
    + + +
    + + +

    Our Changing Planet

    +

    by Kurt Wagner

    +
    + + +
    +
    by Kurt Wagner
    +
    + + + + + + + + + + +
    +
    + + +
    + + +
    +
    +

    Our Changing Planet

    +

    by Kurt Wagner

    +
    +
    Visit ten places on our planet that are undergoing the biggest changes today.
    +
    + + + + + +
    +
    + + +
    + + +
    +
    +

    Our Changing Planet

    +

    by Kurt Wagner

    +
    +
    Visit ten places on our planet that are undergoing the biggest changes today.
    +
    + + + + + +
    +
    + + +
    + + +
    +
    +

    Our Changing Planet

    +

    by Kurt Wagner

    +
    +
    + + + + + + + + + + +
    +
    +
    +
    + + + +@code { + private void CardClick(string notification) + { + ToastService.ShowToast(heading: "Card Clicked", message: notification, level: MBToastLevel.Success, showIcon: false); + } + + + + private void ButtonClick() + { + ToastService.ShowToast(heading: "Button Clicked", message: "", level: MBToastLevel.Success, showIcon: false); + } + + + + private void IconButtonClick() + { + ToastService.ShowToast(heading: "Icon Button Clicked", message: "", level: MBToastLevel.Success, showIcon: false); + } +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Checkbox.razor b/Material.Blazor.Website.MD3/Pages/Checkbox.razor index 6347f9566..a3bed5db1 100644 --- a/Material.Blazor.Website.MD3/Pages/Checkbox.razor +++ b/Material.Blazor.Website.MD3/Pages/Checkbox.razor @@ -5,7 +5,7 @@ diff --git a/Material.Blazor.Website.MD3/Pages/CircularProgress.razor b/Material.Blazor.Website.MD3/Pages/CircularProgress.razor deleted file mode 100644 index f03270328..000000000 --- a/Material.Blazor.Website.MD3/Pages/CircularProgress.razor +++ /dev/null @@ -1,75 +0,0 @@ -@page "/circularprogress" - -@namespace Material.Blazor.Website.Pages - - - - -

    - Circular progress indicators. -

    -
    - - -

    - -

    - -

    - - Show Progress Indicators (Not yet implemented) -

    -
    - - -
    - - -

    - Indeterminate -

    - -
    -

    - Indeterminate, 4 color -

    - -
    -

    - Determinate -

    - -
    -
    -
    -
    -
    - -@code { - private MBSelectElement[] ProgressLevels = new MBSelectElement[] - { - new MBSelectElement { SelectedValue = 0.25, TrailingLabel = "25% Progress" }, - new MBSelectElement { SelectedValue = 0.50, TrailingLabel = "50% Progress" }, - new MBSelectElement { SelectedValue = 0.75, TrailingLabel = "75% Progress" }, - new MBSelectElement { SelectedValue = 1.00, TrailingLabel = "100% Progress" } - }; - - private double progressLevel { get; set; } - - private bool showProgress { get; set; } = true; - - public CircularProgress() - { - progressLevel = ProgressLevels[0].SelectedValue; - } -} diff --git a/Material.Blazor.Website.MD3/Pages/DataTable.razor b/Material.Blazor.Website.MD3/Pages/DataTable.razor new file mode 100644 index 000000000..1846f4852 --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/DataTable.razor @@ -0,0 +1,241 @@ +@page "/datatable" + +@using System.Linq + + + + + + + + +

    + Demonstrates a data table. Material.Blazor presently only implements "Standard" data tables without row interaction. +

    +
    + + +
    + + +

    + A Standard Data Table +

    + +
    +

    + + + Biscuit + Price + Review + + + @item.Name + @item.Price.ToString("C2") + @item.Review + + +

    +
    +
    +
    +
    + + +
    + + +

    + Data Table with Progress Bar +

    + +

    + +

    + +
    +

    + + + Biscuit + Price + Review + + + @item.Name + @item.Price.ToString("C2") + @item.Review + + +

    +
    +
    +
    +
    + + +@*
    + + +

    + Data Table with Pagination +

    + +
    +

    + + + Salutation + Given Name + Family Name + + + @item.Salutation + @item.GivenName + @item.FamilyName + + + + + +

    +
    +
    +
    +
    + *@ + +
    + + +

    + Data Table with sticky header +

    + +
    +

    + + + Salutation + Given Name + Family Name + + + @item.Salutation + @item.GivenName + @item.FamilyName + + +

    +
    +
    +
    +
    +
    +
    + + + +@code { + private bool ShowProgress { get; set; } + + private class Biscuit + { + public string Name { get; set; } + public double Price { get; set; } + public string Review { get; set; } + } + + private Biscuit[] Biscuits = + { + new Biscuit() { Name = "Hobnob", Price = 1.56, Review = "For oat lovers" }, + new Biscuit() { Name = "Rich Tea", Price = 1.32, Review = "To be dunked" }, + new Biscuit() { Name = "Digestive", Price = 2.12, Review = "The classic" }, + new Biscuit() { Name = "Bourbon", Price = 0.99, Review = "Kids will leave the biscuit and eat the chocolate cream" }, + new Biscuit() { Name = "Maryland", Price = 0.78, Review = "Try not to eat a pack in one go" } + }; + + IEnumerable> DensityItems => from d in (MBDensity[])Enum.GetValues(typeof(MBDensity)) + select new MBSelectElement + { + SelectedValue = d, + LeadingLabel = d.ToString(), + Disabled = false + }; + + + private class Person + { + public string Salutation { get; set; } + public string GivenName { get; set; } + public string FamilyName { get; set; } + + public override string ToString() + { + return $"ToString(): {Salutation} {GivenName} {FamilyName}"; + } + } + + private Person[] People = + { + new Person() { Salutation = "Prof", GivenName = "Marie", FamilyName = "Curie" }, + new Person() { Salutation = "Prof", GivenName = "Albert", FamilyName = "Einstein" }, + new Person() { Salutation = "Prof", GivenName = "Andrew", FamilyName = "Huxley" }, + new Person() { Salutation = "Mr", GivenName = "Bob", FamilyName = "Dylan" }, + new Person() { Salutation = "Mr", GivenName = "Barack", FamilyName = "Obama" }, + new Person() { Salutation = "Ms", GivenName = "Nadine", FamilyName = "Gordimer" }, + new Person() { Salutation = "Mr", GivenName = "Muhammad", FamilyName = "Yunus" }, + new Person() { Salutation = "RtHon", GivenName = "Lord", FamilyName = "Rayleigh" }, + new Person() { Salutation = "Ms", GivenName = "Grazia", FamilyName = "Deledda" }, + new Person() { Salutation = "Mr", GivenName = "Jean-Paul", FamilyName = "Sartre" }, + new Person() { Salutation = "Prof", GivenName = "Esther", FamilyName = "Duflo" }, + new Person() { Salutation = "Prof", GivenName = "Yoshinori", FamilyName = "Ohsumi" }, + new Person() { Salutation = "Prof", GivenName = "Robert", FamilyName = "Merton" }, + new Person() { Salutation = "Prof", GivenName = "Barbara", FamilyName = "McClintock" }, + new Person() { Salutation = "Mr", GivenName = "Boris", FamilyName = "Pasternak" }, + new Person() { Salutation = "Mr", GivenName = "Willy", FamilyName = "Brandt" }, + new Person() { Salutation = "Mr", GivenName = "Isaac", FamilyName = "Bashevis Singer" }, + new Person() { Salutation = "Ms", GivenName = "Olga", FamilyName = "Tokarczuk" }, + new Person() { Salutation = "Mr", GivenName = "Günter", FamilyName = "Grass" }, + new Person() { Salutation = "Mr", GivenName = "John", FamilyName = "Hume" }, + }; + + private int[] PersonItemsPerPageSelection = { 2, 4, 6 }; + + private int PersonItemsPerPage = 6; + private int PersonPageNumber = 0; + + IEnumerable SelectedPeople => People.Skip(PersonItemsPerPage * PersonPageNumber).Take(PersonItemsPerPage); +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Dialog.razor b/Material.Blazor.Website.MD3/Pages/Dialog.razor new file mode 100644 index 000000000..9ea4a811e --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/Dialog.razor @@ -0,0 +1,471 @@ +@page "/dialog" + + +@inject IMBToastService ToastService + + +@using System.ComponentModel.DataAnnotations; + + + + +

    + Dialog samples. Click each card to launch a dialog. +

    +
    + + +
    + + +
    +

    + TBD +

    +
    +
    +
    +
    + + @*
    + + +
    +

    + General Dialog +

    +

    + Launches a general dialog. Look at the result shown in a toast when you click a button, hit escape of click the scrim. Shows + a checkbox and radio buttons to demonstrate that these are correctly initiated with Material Theme javascript when rendered inside a dialog. +

    +
    +
    +
    +
    + + +
    + + +
    +

    + Disabled Scrim and Escape +

    +

    + A general dialog with scrim and escape key actions disabled. +

    +
    +
    +
    +
    + + +
    + + +
    +

    + Date Picker Dialog +

    +

    + Shows that a date picker's popup menu is able to render outside the dialog <div> block. +

    +
    +
    +
    +
    + + +
    + + +
    +

    + Logon Dialog +

    +

    + Shows a dialog that uses an edit form and validation annotations. It also demonstrates the altenative form of having submit and cancel methods +

    +
    +
    +
    +
    + + +
    + + +
    +

    + No Body +

    +

    + Shows a dialog with no body +

    +
    +
    +
    +
    + + +
    + + +
    +

    + No Title +

    +

    + Shows a dialog with no title +

    +
    +
    +
    +
    + + +
    + + +
    +

    + Header +

    +

    + Shows a dialog with a header +

    +
    +
    +
    +
    + + + + +

    Please choose a fruit.

    + + + + + + + +
    + + + + +

    Please choose a fruit.

    +

    + +

    + + + + + +
    + + + + +

    Set the date.

    + + + + + + +
    + + + + + + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +
    + +
    + + + + + + + + + + +

    No Title

    + + + + + +
    + + + +

    This is an h1 header

    +

    This is also an h1 header

    +
    + + + + +
    +
    + +
    +
    + +
    +
    + + +
    +
    +
    + +
    + + *@ +
    + +
    + + + + @code { + // private bool DisableInput { get; set; } = false; + // private MBDialog Dialog1 { get; set; } + // private MBDialog Dialog2 { get; set; } + // private MBDialog Dialog3 { get; set; } + // private MBDialog Dialog4 { get; set; } + // private MBDialog Dialog5 { get; set; } + // private MBDialog Dialog6 { get; set; } + // private MBDialog Dialog7 { get; set; } + + // private bool Check { get; set; } + + // private string _radioButtonResult1 = "brit-short"; + // private string RadioButtonResult1 + // { + // get => _radioButtonResult1; + // set + // { + // _radioButtonResult1 = value; + + // ToastService.ShowToast(heading: "Dialog 1 Radio Click", message: $"Value: '{_radioButtonResult1}'", level: MBToastLevel.Success, showIcon: false); + // } + // } + + // private MBSelectElement[] StringItems = new MBSelectElement[] + // { + // new MBSelectElement { SelectedValue = "brit-short", Label = "British Shorthair" }, + // new MBSelectElement { SelectedValue = "russ-blue", Label = "Russian Blue" }, + // new MBSelectElement { SelectedValue = "ice-invis", Label = "Icelandic Invisible" } + // }; + + // private async Task ShowDialog1Async() + // { + // var result = await Dialog1.ShowAsync(); + // ToastService.ShowToast(heading: "General Dialog", message: $"Value: '{result}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private async Task ShowDialog2Async() + // { + // var result = await Dialog2.ShowAsync(); + // ToastService.ShowToast(heading: "General Dialog w/o Scrim/Esc", message: $"Value: '{result}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private async Task OnButtonClick(string button) + // { + // await Dialog2.HideAsync(); + // ToastService.ShowToast(heading: "General Dialog w/o Scrim/Esc by @onclick", message: $"Value: '{button}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private DateTime MinDate { get; set; } = new DateTime(2015, 1, 1); + // private DateTime MaxDate { get; set; } = new DateTime(2025, 12, 31); + // private DateTime Date5 { get; set; } = DateTime.Today; + + // private async Task ShowDialog3Async() + // { + // var result = await Dialog3.ShowAsync(); + // ToastService.ShowToast(heading: "Datepicker Dialog", message: $"Value: '{result}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private UserLogonDefinition UserLogon { get; set; } + // private async Task ShowDialog4Async() + // { + // UserLogon = new UserLogonDefinition(); + // _ = Dialog4.ShowAsync(); + // await Task.CompletedTask; + // } + + // private async Task Dialog4Submitted() + // { + // await Dialog4.HideAsync(); + // ToastService.ShowToast(heading: "Logon Dialog Submit", message: $"User / Password: '{UserLogon.UserID}' / '{UserLogon.Password}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private void Dialog4Invalid() + // { + // ToastService.ShowToast(heading: "Logon Dialog Invalid", message: $"Edit form was invalid", level: MBToastLevel.Warning, showIcon: false); + // } + + // private async Task Dialog4Canceled() + // { + // await Dialog4.HideAsync(); + // ToastService.ShowToast(heading: "Logon Dialog Canceled", message: "The cancel button was selected", level: MBToastLevel.Success, showIcon: false); + // } + + // private async Task ShowDialog5Async() + // { + // var result = await Dialog5.ShowAsync(); + // ToastService.ShowToast(heading: "No Body Dialog", message: $"Value: '{result}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private async Task ShowDialog6Async() + // { + // var result = await Dialog6.ShowAsync(); + // ToastService.ShowToast(heading: "No Title Dialog", message: $"Value: '{result}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private async Task ShowDialog7Async() + // { + // UserLogon = new UserLogonDefinition(); + // _ = Dialog7.ShowAsync(); + // await Task.CompletedTask; + // } + + // private async Task Dialog7Submitted() + // { + // await Dialog7.HideAsync(); + // ToastService.ShowToast(heading: "Logon Dialog #2 Submit", message: $"User / Password: '{UserLogon.UserID}' / '{UserLogon.Password}'", level: MBToastLevel.Success, showIcon: false); + // } + + // private void Dialog7Invalid() + // { + // ToastService.ShowToast(heading: "Logon Dialog #2 Invalid", message: $"Edit form was invalid", level: MBToastLevel.Warning, showIcon: false); + // } + + // private async Task Dialog7Canceled() + // { + // await Dialog7.HideAsync(); + // ToastService.ShowToast(heading: "Logon Dialog #2 Canceled", message: "The cancel button was selected", level: MBToastLevel.Success, showIcon: false); + // } + + // private class UserLogonDefinition + // { + // [Required(ErrorMessage = "UserID is required")] + // public string UserID { get; set; } + + + // [Required(ErrorMessage = "Password is required")] + // [MinLength(8, ErrorMessage = "Password at least 8 characters.")] + // public string Password { get; set; } + // } + } diff --git a/Material.Blazor.Website.MD3/Pages/Drawer.razor b/Material.Blazor.Website.MD3/Pages/Drawer.razor new file mode 100644 index 000000000..0a0dc9201 --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/Drawer.razor @@ -0,0 +1,71 @@ +@page "/drawer" + +@inject IMBToastService ToastService + + + + + +

    + Anchor component. +

    +
    + + +
    + + +

    + Drawer +

    + +

    + The Drawer is a visual component. You encapsulate the content that you wish to show and hide as a group. + Typically this is a menu, but any components can be included. +

    +

    + This website uses the Drawer in its MainLayout.razor and can be observed as the menu when invoked. +

    +
    +
    +
    +
    +
    + + + +@code { + private bool _toggleState; + private bool ToggleState + { + get => _toggleState; + set + { + _toggleState = value; + + ToastService.ShowToast(heading: "Toggle Icon Button Clicked", message: $"Value: {_toggleState} / {(_toggleState ? "Dog" : "Cat")}", level: MBToastLevel.Success, showIcon: false); + } + } + + + + private bool _toggleTwoWayBindingState; + private bool ToggleTwoWayBindingState + { + get => _toggleTwoWayBindingState; + set + { + _toggleTwoWayBindingState = value; + + ToastService.ShowToast(heading: "Toggle Icon Button Clicked", message: $"Two way binding Value: {_toggleTwoWayBindingState}", level: MBToastLevel.Success, showIcon: false); + } + } + + + + private void ButtonClick(string notification) + { + ToastService.ShowToast(heading: "Button Clicked", message: notification, level: MBToastLevel.Success, showIcon: false); + } +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Icon.razor b/Material.Blazor.Website.MD3/Pages/Icon.razor index bc0efcc09..ab6e2075e 100644 --- a/Material.Blazor.Website.MD3/Pages/Icon.razor +++ b/Material.Blazor.Website.MD3/Pages/Icon.razor @@ -2,11 +2,9 @@ @inject IMBToastService ToastService - -

    @@ -15,21 +13,346 @@ -

    +
    -

    - Icon -

    - -

    - -

    -

    - -

    +
    +
    +

    + Tailored Icons +

    + +

    + Shows how icons can be tailored. +

    +
    + +
    + +
    + +
    + @* +

    Icon fill

    + + *@ + + + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    +

    + MBIcon with Parameters using Typography +

    +
    + +   + +   + +   + +   + +   +
    +
    + +
    +

    + MBIcon with Descriptor using <md-icon> +

    +
    + +   + +   + +   + +   + +   +
    +
    +
    + +@code { + private string mbIconColor { get; set; } = "black"; + + + + MBSelectElement[] iconFills = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = 0.0m, LeadingLabel = "0.0" }, + new MBSelectElement { SelectedValue = 0.1m, LeadingLabel = "0.1" }, + new MBSelectElement { SelectedValue = 0.2m, LeadingLabel = "0.2" }, + new MBSelectElement { SelectedValue = 0.3m, LeadingLabel = "0.3" }, + new MBSelectElement { SelectedValue = 0.4m, LeadingLabel = "0.4" }, + new MBSelectElement { SelectedValue = 0.5m, LeadingLabel = "0.5" }, + new MBSelectElement { SelectedValue = 0.6m, LeadingLabel = "0.6" }, + new MBSelectElement { SelectedValue = 0.7m, LeadingLabel = "0.7" }, + new MBSelectElement { SelectedValue = 0.8m, LeadingLabel = "0.8" }, + new MBSelectElement { SelectedValue = 0.9m, LeadingLabel = "0.9" }, + new MBSelectElement { SelectedValue = 1.0m, LeadingLabel = "1.0" }, + }; + + private decimal _iconFill = 1.0m; + private decimal mbIconFill + { + get => _iconFill; + set + { + _iconFill = value; + + ToastService.ShowToast(heading: "Icon Fill", message: $"Value: '{mbIconFill.ToString()}'", level: MBToastLevel.Success, showIcon: false); + } + } + // private decimal mbIconFill = 1.0m; + + + MBSelectElement[] iconGradients = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "LowEmphasis", LeadingLabel = "Low emphasis" }, + new MBSelectElement { SelectedValue = "NormalEmphasis", LeadingLabel = "Normal emphasis" }, + new MBSelectElement { SelectedValue = "HighEmphasis", LeadingLabel = "High emphasis" }, + }; + + private MBIconGradient mbIconGradient = MBIconGradient.NormalEmphasis; + private string _iconGradient = "NormalEmphasis"; + private string iconGradient + { + get => _iconGradient; + set + { + _iconGradient = value; + + mbIconGradient = _iconGradient switch + { + "LowEmphasis" => MBIconGradient.LowEmphasis, + "NormalEmphasis" => MBIconGradient.NormalEmphasis, + "HighEmphasis" => MBIconGradient.HighEmphasis, + _ => throw new Exception("Unknown icon gradient"), + }; + + ToastService.ShowToast(heading: "Icon Gradient", message: $"Value: '{mbIconGradient.ToString()}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + + MBSelectElement[] iconSizes = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "20", LeadingLabel = "20px" }, + new MBSelectElement { SelectedValue = "24", LeadingLabel = "24px" }, + new MBSelectElement { SelectedValue = "40", LeadingLabel = "40px" }, + new MBSelectElement { SelectedValue = "48", LeadingLabel = "48px" }, + }; + + private MBIconSize mbIconSize = MBIconSize.Size24; + private string _iconSize = "24"; + private string iconSize + { + get => _iconSize; + set + { + _iconSize = value; + + mbIconSize = _iconSize switch + { + "20" => MBIconSize.Size20, + "24" => MBIconSize.Size24, + "40" => MBIconSize.Size40, + "48" => MBIconSize.Size48, + _ => throw new Exception("Unknown icon Size"), + }; + + ToastService.ShowToast(heading: "Icon Size", message: $"Value: '{_iconSize}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + + MBSelectElement[] iconStyles = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "outlined", LeadingLabel = "Outlined" }, + new MBSelectElement { SelectedValue = "rounded", LeadingLabel = "Rounded" }, + new MBSelectElement { SelectedValue = "sharp", LeadingLabel = "Sharp" } + }; + + private MBIconStyle mbIconStyle = MBIconStyle.Outlined; + private string _iconStyle = "outlined"; + private string iconStyle + { + get => _iconStyle; + set + { + _iconStyle = value; + + mbIconStyle = _iconStyle switch + { + "outlined" => MBIconStyle.Outlined, + "rounded" => MBIconStyle.Rounded, + "sharp" => MBIconStyle.Sharp, + _ => throw new Exception("Unknown icon style"), + }; + + ToastService.ShowToast(heading: "Icon style", message: $"Value: '{_iconStyle}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + + MBSelectElement[] iconWeights = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "100", LeadingLabel = "100" }, + new MBSelectElement { SelectedValue = "200", LeadingLabel = "200" }, + new MBSelectElement { SelectedValue = "300", LeadingLabel = "300" }, + new MBSelectElement { SelectedValue = "400", LeadingLabel = "400" }, + new MBSelectElement { SelectedValue = "500", LeadingLabel = "500" }, + new MBSelectElement { SelectedValue = "600", LeadingLabel = "600" }, + new MBSelectElement { SelectedValue = "700", LeadingLabel = "700" }, + }; + + private MBIconWeight mbIconWeight = MBIconWeight.W400; + private string _iconWeight = "400"; + private string iconWeight + { + get => _iconWeight; + set + { + _iconWeight = value; + + mbIconWeight = _iconWeight switch + { + "100" => MBIconWeight.W100, + "200" => MBIconWeight.W200, + "300" => MBIconWeight.W300, + "400" => MBIconWeight.W400, + "500" => MBIconWeight.W500, + "600" => MBIconWeight.W600, + "700" => MBIconWeight.W700, + _ => throw new Exception("Unknown icon weight"), + }; + + ToastService.ShowToast(heading: "Icon Weight", message: $"Value: '{_iconWeight}'", level: MBToastLevel.Success, showIcon: false); + } + } + +} diff --git a/Material.Blazor.Website.MD3/Pages/IconButton.razor b/Material.Blazor.Website.MD3/Pages/IconButton.razor new file mode 100644 index 000000000..221b36a84 --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/IconButton.razor @@ -0,0 +1,43 @@ +@page "/iconbutton" + +@inject IMBToastService ToastService + + + + + +

    + Shows an icon button and density. +

    +
    + + +
    + + +

    + Icon Button +

    + +

    + +

    +
    +
    +
    +
    +
    + + + +@code { + private void ButtonClick(string notification) + { + ToastService.ShowToast(heading: "Button Clicked", message: notification, level: MBToastLevel.Success, showIcon: false); + } +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Index.razor b/Material.Blazor.Website.MD3/Pages/Index.razor index 89602b1b7..5b639c47b 100644 --- a/Material.Blazor.Website.MD3/Pages/Index.razor +++ b/Material.Blazor.Website.MD3/Pages/Index.razor @@ -39,11 +39,17 @@ The environment is currently executing on @Runtime hosted by @OSDescription/@OSArchitecture.
  • - The styling version is Material Design 3 (with some MD2 to handle components that are not yet migrated). + The styling version is Material Design 3 (with some MD2 components that are not yet migrated).
  • The Material.Blazor version is @Version.
  • +
  • + Menu color key: +
       DarkGreen - CORE component +
       DarkBlue - PLUS component +
       DarkRed - MD2 component +
  • @@ -55,7 +61,7 @@
    + @onclick="@NavigateToDocs">
    @@ -69,10 +75,10 @@
    - +
    + @onclick="@NavigateToComponent">
    diff --git a/Material.Blazor.Website.MD3/Pages/LinearProgress.razor b/Material.Blazor.Website.MD3/Pages/LinearProgress.razor deleted file mode 100644 index 48037c1f4..000000000 --- a/Material.Blazor.Website.MD3/Pages/LinearProgress.razor +++ /dev/null @@ -1,76 +0,0 @@ -@page "/linearprogress" - -@namespace Material.Blazor.Website.Pages - - - - -

    - Linear progress indicators. -

    -
    - - -

    - -

    - -

    - - Show Progress Indicators (Not yet implemented) -

    -
    - - -
    - - -

    - Indeterminate -

    - -
    -

    - Indeterminate, 4 color -

    - -
    -

    - Determinate -

    - -
    -
    -
    - -
    -
    - -@code { - private MBSelectElement[] ProgressLevels = new MBSelectElement[] - { - new MBSelectElement { SelectedValue = 0.25, TrailingLabel = "25% Progress" }, - new MBSelectElement { SelectedValue = 0.50, TrailingLabel = "50% Progress" }, - new MBSelectElement { SelectedValue = 0.75, TrailingLabel = "75% Progress" }, - new MBSelectElement { SelectedValue = 1.00, TrailingLabel = "100% Progress" } - }; - - private double progressLevel { get; set; } - - private bool showProgress { get; set; } = true; - - public LinearProgress() - { - progressLevel = ProgressLevels[0].SelectedValue; - } -} diff --git a/Material.Blazor.Website.MD3/Pages/Menu.razor b/Material.Blazor.Website.MD3/Pages/Menu.razor new file mode 100644 index 000000000..6dd9e5694 --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/Menu.razor @@ -0,0 +1,94 @@ +@page "/menu" + +@inject IMBToastService ToastService + + + + +

    + Demonstrates a menu with list items, list dividers, and a menu selection group. +

    +
    + + +
    + + +

    + Menu Example +

    + +

    + Click the button to open the menu. +

    + +

    + +

    + +
    + + + + + + + + + + + + + + + + +
    +
    +
    +
    +
    +
    + + + +@code { + private Material.Blazor.MD2.MBMenu MenuElement { get; set; } + private bool SevenSelected { get; set; } + private bool EightSelected { get; set; } + + private async Task OpenMenuAsync() + { + await MenuElement.ToggleAsync(); + } + + protected void OnClickHandler(string NavigationReference) + { + var result = NavigationReference; + ToastService.ShowToast(heading: "Menu result", message: $"Value: '{result}'", level: MBToastLevel.Success, showIcon: false); + + SevenSelected = (result == "Seven") ? !SevenSelected : SevenSelected; + EightSelected = (result == "Eight") ? !EightSelected : EightSelected; + } +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/NumericDecimalField.razor b/Material.Blazor.Website.MD3/Pages/NumericDecimalField.razor deleted file mode 100644 index 4743aba64..000000000 --- a/Material.Blazor.Website.MD3/Pages/NumericDecimalField.razor +++ /dev/null @@ -1,295 +0,0 @@ -@page "/numericdecimalField" - -@namespace Material.Blazor.Website.Pages - -@inject IMBToastService ToastService - -@using System.Runtime.InteropServices - - - - -

    - Shows numeric decimal fields, which are Plus components. - Each of these can display as Filled or Outlined and with - the full variety of icon foundries. These are used across the page but it we don't attempt to show each variant everywhere - see the Text Field demo for this. -

    -
    - - -
    - - -

    - Standard Formatted Numerics -

    - -

    - A range of numeric decimal fields using formats for non focused input. -

    - -

    - -

    - -

    - -

    - -

    - -

    -
    -
    -
    - - - -
    - - -

    - Scaled Formatted Numerics -

    - -

    - Percentage and basis point numerics can be editted either using a prefix or standard formatting (in the case of percentages only). - A figure of "12.3%" results in a decimal value of "0.123" and one of "34.5 bp" results in a decimal value of "0.00345". -

    - -

    - -

    - -

    - -

    - -

    - -

    -
    -
    -
    - - - -
    - - -

    - Helper Text -

    - -

    - Shows persistent and non-persistent helper text. -

    - -

    - -

    - -

    - -

    -
    -
    -
    -
    -
    - - -@code { - private decimal _decimalValue; - private decimal DecimalValue - { - get => _decimalValue; - set - { - _decimalValue = value; - - ToastService.ShowToast( - level: Material.Blazor.MBToastLevel.Success, - heading: "Decimal numeric", - message: $"Value: '{_decimalValue.ToString("N2")}'", - showIcon: false); - } - } - - - - private decimal _moneyByFormat; - private decimal MoneyByFormat - { - get => _moneyByFormat; - set - { - _moneyByFormat = value; - - ToastService.ShowToast( - heading: "Money by format numeric", - message: $"Value: '{_moneyByFormat.ToString("C2")}'", - level: Material.Blazor.MBToastLevel.Success, showIcon: - false); - } - } - - - - private decimal _moneyByPrefix; - private decimal MoneyByPrefix - { - get => _moneyByPrefix; - set - { - _moneyByPrefix = value; - - ToastService.ShowToast( - heading: "Money by Prefix numeric", - message: $"Value: '{_moneyByPrefix.ToString("N2")}'", - level: Material.Blazor.MBToastLevel.Success, - showIcon: false); - } - } - - - - private decimal _percentByFormat; - private decimal PercentByFormat - { - get => _percentByFormat; - set - { - _percentByFormat = value; - - ToastService.ShowToast( - heading: "Percent by format", - message: $"Value: '{_percentByFormat.ToString("N4")}'", - level: Material.Blazor.MBToastLevel.Success, - showIcon: false); - } - } - - - private decimal _percentBySuffix; - private decimal PercentBySuffix - { - get => _percentBySuffix; - set - { - _percentBySuffix = value; - - ToastService.ShowToast( - heading: "Percent by suffix", - message: $"Value: '{_percentBySuffix.ToString("N4")}'", - level: Material.Blazor.MBToastLevel.Success, - showIcon: false); - } - } - - - private decimal _basisPointsBySuffix; - private decimal BasisPointsBySuffix - { - get => _basisPointsBySuffix; - set - { - _basisPointsBySuffix = value; - - ToastService.ShowToast( - heading: "Basis Points by suffix", - message: $"Value: '{_basisPointsBySuffix.ToString("N4")}'", - level: Material.Blazor.MBToastLevel.Success, - showIcon: false); - } - } - - - private decimal _popupHelperText; - private decimal PopupHelperText - { - get => _popupHelperText; - set - { - _popupHelperText = value; - - ToastService.ShowToast( - heading: "Popup Helper Text", - message: $"Value: '{_popupHelperText}'", - level: Material.Blazor.MBToastLevel.Success, - showIcon: false); - } - } - - - private decimal _persistentHelperText; - private decimal PersistentHelperText - { - get => _persistentHelperText; - set - { - _persistentHelperText = value; - - ToastService.ShowToast( - heading: "Persistent Helper Text", - message: $"Value: '{_persistentHelperText}'", - level: Material.Blazor.MBToastLevel.Success, - showIcon: false); - } - } - - - private Tuple[] apiTuple = - { - new Tuple(0, "MBFilledDecimalField"), - new Tuple(5, "InternalNumericFieldBase"), - new Tuple(10, "InternalFloatingPointFieldBase"), - new Tuple(15, "MBFilledTextField"), - new Tuple(20, "InternalTextFieldBase"), - new Tuple(0, "MBOutlinedDecimalField"), - new Tuple(5, "InternalNumericFieldBase"), - new Tuple(10, "InternalFloatingPointFieldBase"), - new Tuple(15, "MBOutlinedTextField"), - new Tuple(20, "InternalTextFieldBase") - }; - -} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/NumericDecimalField2.razor b/Material.Blazor.Website.MD3/Pages/NumericDecimalField2.razor deleted file mode 100644 index d041d3462..000000000 --- a/Material.Blazor.Website.MD3/Pages/NumericDecimalField2.razor +++ /dev/null @@ -1,186 +0,0 @@ -@page "/numericdecimalField2" - -@namespace Material.Blazor.Website.Pages - -@inject IMBToastService ToastService - -@using System.Runtime.InteropServices - - - - -

    - Shows numeric decimal fields, which are Plus components. - Each of these can display as Filled or Outlined and with - the full variety of icon foundries. These are used across the page but it we don't attempt to show each variant everywhere - see the Text Field demo for this. -

    -
    - - -
    - - -

    - Standard Formatted Numerics -

    - -

    - A range of numeric decimal fields using formats for non focused input. -

    - -

    - -

    - -

    - -

    - -

    - -

    -
    -
    -
    -
    -
    - - -@code { - private decimal _decimalValue; - private decimal DecimalValue - { - get => _decimalValue; - set - { - _decimalValue = value; - - ToastService.ShowToast(heading: "Decimal numeric", message: $"Value: '{_decimalValue.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - - private decimal _moneyByFormat; - private decimal MoneyByFormat - { - get => _moneyByFormat; - set - { - _moneyByFormat = value; - - ToastService.ShowToast(heading: "Money by format numeric", message: $"Value: '{_moneyByFormat.ToString("C2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - - private decimal _moneyByPrefix; - private decimal MoneyByPrefix - { - get => _moneyByPrefix; - set - { - _moneyByPrefix = value; - - ToastService.ShowToast(heading: "Money by Prefix numeric", message: $"Value: '{_moneyByPrefix.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - - private decimal _percentByFormat; - private decimal PercentByFormat - { - get => _percentByFormat; - set - { - _percentByFormat = value; - - ToastService.ShowToast(heading: "Percent by format", message: $"Value: '{_percentByFormat.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private decimal _percentBySuffix; - private decimal PercentBySuffix - { - get => _percentBySuffix; - set - { - _percentBySuffix = value; - - ToastService.ShowToast(heading: "Percent by suffix", message: $"Value: '{_percentBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private decimal _basisPointsBySuffix; - private decimal BasisPointsBySuffix - { - get => _basisPointsBySuffix; - set - { - _basisPointsBySuffix = value; - - ToastService.ShowToast(heading: "Basis Points by suffix", message: $"Value: '{_basisPointsBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private decimal _popupHelperText; - private decimal PopupHelperText - { - get => _popupHelperText; - set - { - _popupHelperText = value; - - ToastService.ShowToast(heading: "Popup Helper Text", message: $"Value: '{_popupHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private decimal _persistentHelperText; - private decimal PersistentHelperText - { - get => _persistentHelperText; - set - { - _persistentHelperText = value; - - ToastService.ShowToast(heading: "Persistent Helper Text", message: $"Value: '{_persistentHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private Tuple[] apiTuple = - { - new Tuple(0, "MBDecimalField2"), - new Tuple(5, "InternalNumericField2Base"), - new Tuple(10, "InternalFloatingPointField2Base"), - new Tuple(15, "MBTextField2"), - new Tuple(20, "InternalTextField2Base") - }; - -} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/NumericDoubleField.razor b/Material.Blazor.Website.MD3/Pages/NumericDoubleField.razor deleted file mode 100644 index e260d74b6..000000000 --- a/Material.Blazor.Website.MD3/Pages/NumericDoubleField.razor +++ /dev/null @@ -1,245 +0,0 @@ -@page "/numericdoublefield" - -@namespace Material.Blazor.Website.Pages - -@inject IMBToastService ToastService - -@using System.Runtime.InteropServices - - - - -

    - Shows numeric double fields, which are Plus components. - Each of these can display as Filled or Outlined and with - the full variety of icon foundries. These are used across the page but it we don't attempt to show each variant everywhere - see the Text Field demo for this. -

    -
    - - -
    - - -

    - Standard Formatted Numerics -

    - -

    - A range of numeric double fields using formats for non focused input. -

    - -

    - -

    - -

    - -

    - -

    - -

    -
    -
    -
    - - - -
    - - -

    - Scaled Formatted Numerics -

    - -

    - Percentage and basis point numerics can be editted either using a prefix or standard formatting (in the case of percentages only). - A figure of "12.3%" results in a double value of "0.123" and one of "34.5 bp" results in a double value of "0.00345". -

    - -

    - -

    - -

    - -

    - -

    - -

    -
    -
    -
    - - - -
    - - -

    - Helper Text -

    - -

    - Shows persistent and non-persistent helper text. -

    - -

    - -

    - -

    - -

    -
    -
    -
    -
    -
    - - -@code { - private double _doubleValue; - private double DoubleValue - { - get => _doubleValue; - set - { - _doubleValue = value; - - ToastService.ShowToast(heading: "Double numeric", message: $"Value: '{_doubleValue.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - - private double _moneyByFormat; - private double MoneyByFormat - { - get => _moneyByFormat; - set - { - _moneyByFormat = value; - - ToastService.ShowToast(heading: "Money by format numeric", message: $"Value: '{_moneyByFormat.ToString("C2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - - private double _moneyByPrefix; - private double MoneyByPrefix - { - get => _moneyByPrefix; - set - { - _moneyByPrefix = value; - - ToastService.ShowToast(heading: "Money by Prefix numeric", message: $"Value: '{_moneyByPrefix.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - - private double _percentByFormat; - private double PercentByFormat - { - get => _percentByFormat; - set - { - _percentByFormat = value; - - ToastService.ShowToast(heading: "Percent by format", message: $"Value: '{_percentByFormat.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private double _percentBySuffix; - private double PercentBySuffix - { - get => _percentBySuffix; - set - { - _percentBySuffix = value; - - ToastService.ShowToast(heading: "Percent by suffix", message: $"Value: '{_percentBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private double _basisPointsBySuffix; - private double BasisPointsBySuffix - { - get => _basisPointsBySuffix; - set - { - _basisPointsBySuffix = value; - - ToastService.ShowToast(heading: "Basis Points by suffix", message: $"Value: '{_basisPointsBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private double _popupHelperText; - private double PopupHelperText - { - get => _popupHelperText; - set - { - _popupHelperText = value; - - ToastService.ShowToast(heading: "Popup Helper Text", message: $"Value: '{_popupHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } - - - private double _persistentHelperText; - private double PersistentHelperText - { - get => _persistentHelperText; - set - { - _persistentHelperText = value; - - ToastService.ShowToast(heading: "Persistent Helper Text", message: $"Value: '{_persistentHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); - } - } -} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/NumericField.razor b/Material.Blazor.Website.MD3/Pages/NumericField.razor new file mode 100644 index 000000000..fd1642c9d --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/NumericField.razor @@ -0,0 +1,572 @@ +@page "/numericField" + +@namespace Material.Blazor.Website.Pages + +@inject IMBToastService ToastService + +@using System.Runtime.InteropServices + + + + +

    + Shows numeric fields, which are Plus components. + Each of these can display as Filled or Outlined and with + leading and trailing icons. These are used across the page but it we don't attempt to show each variant everywhere - see the Text Field demo for this. +

    +
    + + +
    + + +

    + Standard Formatted Decimals +

    + +

    + A range of numeric decimal fields using formats for non focused input. +

    + +

    + +

    + +

    + +

    + +

    + +

    +
    +
    +
    + +
    + + +

    + Scaled Formatted Decimals +

    + +

    + Percentage and basis point numerics can be edited either using a prefix or standard formatting (in the case of percentages only). + A figure of "12.3%" results in a decimal value of "0.123" and one of "34.5 bp" results in a decimal value of "0.00345". +

    + +

    + +

    + +

    + +

    + +

    + +

    +
    +
    +
    + +
    + + +

    + Helper Text on Decimal +

    + +

    + Shows persistent and non-persistent helper text. +

    + +

    + +

    + +

    + +

    +
    +
    +
    + + + + + +
    + + +

    + Standard Formatted Doubles +

    + +

    + A range of numeric double fields using formats for non focused input. +

    + +

    + +

    + +

    + +

    + +

    + +

    +
    +
    +
    + +
    + + +

    + Scaled Formatted Doubles +

    + +

    + Percentage and basis point numerics can be edited either using a prefix or standard formatting (in the case of percentages only). + A figure of "12.3%" results in a double value of "0.123" and one of "34.5 bp" results in a double value of "0.00345". +

    + +

    + +

    + +

    + +

    + +

    + +

    +
    +
    +
    + +
    + + +

    + Helper Text on Double +

    + +

    + Shows persistent and non-persistent helper text. +

    + +

    + +

    + +

    + +

    +
    +
    +
    + + + + + +
    + + +

    + Standard Formatted Int +

    + +

    + A range of numeric integer fields using formats for non focused input. +

    + +

    + +

    +
    +
    +
    + + + +
    + + +

    + Helper Text on Int +

    + +

    + Shows persistent and non-persistent helper text. +

    + +

    + +

    + +

    + +

    +
    +
    +
    + +
    +
    + + +@code { + + // ************************ DECIMAL ************************* + private decimal _decimalValue; + private decimal DecimalValue + { + get => _decimalValue; + set + { + _decimalValue = value; + + ToastService.ShowToast(heading: "Decimal numeric", message: $"Value: '{_decimalValue.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private decimal _decimalMoneyByFormat; + private decimal DecimalMoneyByFormat + { + get => _decimalMoneyByFormat; + set + { + _decimalMoneyByFormat = value; + + ToastService.ShowToast(heading: "Decimal Money by format numeric", message: $"Value: '{_decimalMoneyByFormat.ToString("C2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private decimal _decimalMoneyByPrefix; + private decimal DecimalMoneyByPrefix + { + get => _decimalMoneyByPrefix; + set + { + _decimalMoneyByPrefix = value; + + ToastService.ShowToast(heading: "Decimal Money by Prefix numeric", message: $"Value: '{_decimalMoneyByPrefix.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private decimal _decimalPercentByFormat; + private decimal DecimalPercentByFormat + { + get => _decimalPercentByFormat; + set + { + _decimalPercentByFormat = value; + + ToastService.ShowToast(heading: "Decimal Percent by format", message: $"Value: '{_decimalPercentByFormat.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private decimal _decimalPercentBySuffix; + private decimal DecimalPercentBySuffix + { + get => _decimalPercentBySuffix; + set + { + _decimalPercentBySuffix = value; + + ToastService.ShowToast(heading: "Decimal Percent by suffix", message: $"Value: '{_decimalPercentBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private decimal _decimalBasisPointsBySuffix; + private decimal DecimalBasisPointsBySuffix + { + get => _decimalBasisPointsBySuffix; + set + { + _decimalBasisPointsBySuffix = value; + + ToastService.ShowToast(heading: "Decimal Basis Points by suffix", message: $"Value: '{_decimalBasisPointsBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + + private decimal _decimalPopupHelperText; + private decimal DecimalPopupHelperText + { + get => _decimalPopupHelperText; + set + { + _decimalPopupHelperText = value; + + ToastService.ShowToast(heading: "Decimal Popup Helper Text", message: $"Value: '{_decimalPopupHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + + private decimal _decimalPersistentHelperText; + private decimal DecimalPersistentHelperText + { + get => _decimalPersistentHelperText; + set + { + _decimalPersistentHelperText = value; + + ToastService.ShowToast(heading: "Decimal Persistent Helper Text", message: $"Value: '{_decimalPersistentHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + + + + // ************************ DOUBLE ************************* + private double _doubleValue; + private double DoubleValue + { + get => _doubleValue; + set + { + _doubleValue = value; + + ToastService.ShowToast(heading: "Double numeric", message: $"Value: '{_doubleValue.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private double _doubleMoneyByFormat; + private double DoubleMoneyByFormat + { + get => _doubleMoneyByFormat; + set + { + _doubleMoneyByFormat = value; + + ToastService.ShowToast(heading: "Double Money by format numeric", message: $"Value: '{_doubleMoneyByFormat.ToString("C2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private double _doubleMoneyByPrefix; + private double DoubleMoneyByPrefix + { + get => _doubleMoneyByPrefix; + set + { + _doubleMoneyByPrefix = value; + + ToastService.ShowToast(heading: "Double Money by Prefix numeric", message: $"Value: '{_doubleMoneyByPrefix.ToString("N2")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private double _doublePercentByFormat; + private double DoublePercentByFormat + { + get => _doublePercentByFormat; + set + { + _doublePercentByFormat = value; + + ToastService.ShowToast(heading: "Double Percent by format", message: $"Value: '{_doublePercentByFormat.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private double _doublePercentBySuffix; + private double DoublePercentBySuffix + { + get => _doublePercentBySuffix; + set + { + _doublePercentBySuffix = value; + + ToastService.ShowToast(heading: "Double Percent by suffix", message: $"Value: '{_doublePercentBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + private double _doubleBasisPointsBySuffix; + private double DoubleBasisPointsBySuffix + { + get => _doubleBasisPointsBySuffix; + set + { + _doubleBasisPointsBySuffix = value; + + ToastService.ShowToast(heading: "Double Basis Points by suffix", message: $"Value: '{_doubleBasisPointsBySuffix.ToString("N4")}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + + private double _doublePopupHelperText; + private double DoublePopupHelperText + { + get => _doublePopupHelperText; + set + { + _doublePopupHelperText = value; + + ToastService.ShowToast(heading: "Double Popup Helper Text", message: $"Value: '{_doublePopupHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + + private double _doublePersistentHelperText; + private double DoublePersistentHelperText + { + get => _doublePersistentHelperText; + set + { + _doublePersistentHelperText = value; + + ToastService.ShowToast(heading: "Double Persistent Helper Text", message: $"Value: '{_doublePersistentHelperText}'", level: Material.Blazor.MBToastLevel.Success, showIcon: false); + } + } + + // ************************ INT ************************* + + + private int _integer; + private int Integer + { + get => _integer; + set + { + _integer = value; + + ToastService.ShowToast(heading: "Integer numeric", message: $"Value: '{_integer.ToString("N0")}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + private int _intPopupHelperText; + private int IntPopupHelperText + { + get => _intPopupHelperText; + set + { + _intPopupHelperText = value; + + ToastService.ShowToast(heading: "Int Popup Helper Text", message: $"Value: '{_intPopupHelperText}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + private int _intPersistentHelperText; + private int IntPersistentHelperText + { + get => _intPersistentHelperText; + set + { + _intPersistentHelperText = value; + + ToastService.ShowToast(heading: "Int Persistent Helper Text", message: $"Value: '{_intPersistentHelperText}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + private Tuple[] apiTuple = + { + new Tuple(0, "MBDecimalField"), + new Tuple(5, "InternalFloatingPointFieldBase"), + new Tuple(10, "InternalNumericFieldBase"), + new Tuple(15, "MBTextField"), + new Tuple(20, "InternalTextFieldBase"), + new Tuple(0, "MBDoubleField"), + new Tuple(5, "InternalFloatingPointFieldBase"), + new Tuple(10, "InternalNumericFieldBase"), + new Tuple(15, "MBTextField"), + new Tuple(20, "InternalTextFieldBase"), + new Tuple(0, "MBIntField"), + new Tuple(5, "InternalIntFieldBase"), + new Tuple(10, "InternalNumericFieldBase"), + new Tuple(15, "MBTextField"), + new Tuple(20, "InternalTextFieldBase"), + }; + +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/NumericIntField.razor b/Material.Blazor.Website.MD3/Pages/NumericIntField.razor deleted file mode 100644 index 8984535c4..000000000 --- a/Material.Blazor.Website.MD3/Pages/NumericIntField.razor +++ /dev/null @@ -1,115 +0,0 @@ -@page "/numericintfield" - -@inject IMBToastService ToastService - -@using System.Runtime.InteropServices - - - - -

    - Shows numeric int text field, a Plus component. - Each of these can display as Filled or Outlined and with - the full variety of icon foundries. These are used across the page but it we don't attempt to show each variant everywhere - see the Text Field demo for this. -

    -
    - - -
    - - -

    - Standard Formatted Numerics -

    - -

    - A range of numeric integer fields using formats for non focused input. -

    - -

    - -

    -
    -
    -
    - - - -
    - - -

    - Helper Text -

    - -

    - Shows persistent and non-persistent helper text. -

    - -

    - -

    - -

    - -

    -
    -
    -
    -
    -
    - - -@code { - private int _integer; - private int Integer - { - get => _integer; - set - { - _integer = value; - - ToastService.ShowToast(heading: "Integer numeric", message: $"Value: '{_integer.ToString("N0")}'", level: MBToastLevel.Success, showIcon: false); - } - } - - - private int _popupHelperText; - private int PopupHelperText - { - get => _popupHelperText; - set - { - _popupHelperText = value; - - ToastService.ShowToast(heading: "Popup Helper Text", message: $"Value: '{_popupHelperText}'", level: MBToastLevel.Success, showIcon: false); - } - } - - - private int _persistentHelperText; - private int PersistentHelperText - { - get => _persistentHelperText; - set - { - _persistentHelperText = value; - - ToastService.ShowToast(heading: "Persistent Helper Text", message: $"Value: '{_persistentHelperText}'", level: MBToastLevel.Success, showIcon: false); - } - } -} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Progress.razor b/Material.Blazor.Website.MD3/Pages/Progress.razor index d24f54035..3b44acdb4 100644 --- a/Material.Blazor.Website.MD3/Pages/Progress.razor +++ b/Material.Blazor.Website.MD3/Pages/Progress.razor @@ -5,7 +5,8 @@ diff --git a/Material.Blazor.Website.MD3/Pages/RadioButton.razor b/Material.Blazor.Website.MD3/Pages/RadioButton.razor index 406a45e9e..14f8952c7 100644 --- a/Material.Blazor.Website.MD3/Pages/RadioButton.razor +++ b/Material.Blazor.Website.MD3/Pages/RadioButton.razor @@ -5,7 +5,7 @@ diff --git a/Material.Blazor.Website.MD3/Pages/RadioButtonGroup.razor b/Material.Blazor.Website.MD3/Pages/RadioButtonGroup.razor index 23be09cf5..519a90b41 100644 --- a/Material.Blazor.Website.MD3/Pages/RadioButtonGroup.razor +++ b/Material.Blazor.Website.MD3/Pages/RadioButtonGroup.razor @@ -5,7 +5,7 @@ @@ -28,7 +28,7 @@

    -

    @@ -50,59 +50,13 @@

    -

    - - - -
    - - -

    - Density -

    - -

    - Density subsystem from default to minus 3. -

    - -

    - - - - - - - -

    -
    -
    -
    @@ -116,21 +70,21 @@ }; - private string _horizontalGroup; - private string HorizontalGroup + private string _KittenBreed = ""; + private string KittenBreed { - get => _horizontalGroup; + get => _KittenBreed; set { - _horizontalGroup = value; + _KittenBreed = value; - ToastService.ShowToast(heading: "Horizontal radio group", message: $"Value: '{_horizontalGroup}'", level: MBToastLevel.Success, showIcon: false); + ToastService.ShowToast(heading: "Kitten breed group", message: $"Value: '{_KittenBreed}'", level: MBToastLevel.Success, showIcon: false); } } enum ColorEnum { Red, Orange, Yellow, Green, Blue, Indigo, Violet }; - IEnumerable> ColourItems => from c in (ColorEnum[])Enum.GetValues(typeof(ColorEnum)) + IEnumerable> ColorItems => from c in (ColorEnum[])Enum.GetValues(typeof(ColorEnum)) select new MBSelectElement { SelectedValue = c, @@ -139,7 +93,7 @@ }; private ColorEnum _color = ColorEnum.Orange; - private ColorEnum Colour + private ColorEnum Color { get => _color; set diff --git a/Material.Blazor.Website.MD3/Pages/Select.razor b/Material.Blazor.Website.MD3/Pages/Select.razor new file mode 100644 index 000000000..d50d5621d --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/Select.razor @@ -0,0 +1,460 @@ +@page "/select" + +@using Material.Blazor + +@inject IMBToastService ToastService + + + + +

    + Filled and outlined text fields with icon usage, class and style application and finally density. +

    +
    + + +
    + + +

    + Select Styles +

    + +

    + Default and explicit select styles. +

    + +

    + +

    +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Icons & Full Width Menu Surface +

    + +

    + Icons - both using icons from the Material Icons, Font Awesome and Open Iconic foundries. Note how the menu surface is the same width as the select anchor. +

    + +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Two Way Binding +

    + +

    + Shows two way binding for selects. +

    + +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Contents Alignment & Fixed Menu Surface +

    + +

    + Left, center and right aligned text (not the same as right-to-left script). Note how the menu remains fixed as you scroll the page. Using the fixed menu surface position is + particularly useful when a select menu might need to overflow its container, such as in a dialog: using a regular or full width menu surface variants in a dialog causes unwanted overflow results. +

    + +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Filled Density +

    + +

    + Filled density subsystem from default to minus 4. +

    + +

    + +

    +

    + +

    +

    + +

    +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Outlined Density +

    + +

    + Outlined density subsystem from default to minus 4. +

    + +

    + +

    +

    + +

    +

    + +

    +

    + +

    +

    + +

    +
    +
    +
    + + + +
    + + +

    + Disabled +

    + +

    + Shows the component either entirely disabled or with some items disabled. +

    + +

    + +

    +

    + +

    +
    +
    +
    + + + + @*
    + + +

    + Dynamically update Items +

    + +

    + Items can by dynamically updated with the toggle button. Note that if you remove items from the list, + you must change Value accordingly to avoid exceptions if you have ItemValidation="@MBItemValidation.Exception". +

    + +

    + +

    +

    + +

    +
    +
    +
    + *@ +
    + + +

    + No items +

    + +

    + An MBSelect where the Items collection is empty. +

    + +

    + +

    +
    +
    +
    + +
    + + +

    + Null item +

    + +

    + An MBSelect where the Items collection contains a null value. +

    + +

    + +

    +
    +
    +
    + +
    + + +

    + Empty string item +

    + +

    + An MBSelect where the initial value of an empty string is not touched until the user chooses from the dropdown. This is accomplished through the use of MBItemValidation.NoSelection. +

    + +

    + +

    +
    +
    +
    + + @* Used occasionally for test purposes *@ + @* *@ +
    +
    + + +@code { + MBSelectElement[] KittenBreeds = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "brit-short", LeadingLabel = "British Shorthair" }, + new MBSelectElement { SelectedValue = "russ-blue", LeadingLabel = "Russian Blue" }, + new MBSelectElement { SelectedValue = "ice-invis", LeadingLabel = "Icelandic Invisible" } + }; + + MBSelectElement[] KittenBreedsExtended = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "brit-short", LeadingLabel = "British Shorthair" }, + new MBSelectElement { SelectedValue = "russ-blue", LeadingLabel = "Russian Blue" }, + new MBSelectElement { SelectedValue = "ice-invis", LeadingLabel = "Icelandic Invisible" }, + new MBSelectElement { SelectedValue = "tabby", LeadingLabel = "Tabby" }, + new MBSelectElement { SelectedValue = "siam", LeadingLabel = "Siamese" } + }; + + MBSelectElement[] KittenBreedsNoFake = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "brit-short", LeadingLabel = "British Shorthair" }, + new MBSelectElement { SelectedValue = "russ-blue", LeadingLabel = "Russian Blue" }, + new MBSelectElement { SelectedValue = "ice-invis", LeadingLabel = "Icelandic Invisible" }, + new MBSelectElement { SelectedValue = "disabled1", LeadingLabel = "Mr. Snuggles", Disabled = true }, + new MBSelectElement { SelectedValue = "disabled2", LeadingLabel = "Roger Rabbit", Disabled = true } + }; + + + private string DefaultStyle; + private string FilledStyle; + private string OutlinedStyle; + + private string Icon1; + private string Icon2; + + private string LeftAligned; + private string RightAligned; + + private string TwoWayBinding; + + private string FilledDensityDefault; + private string FilledDensityMinus1; + private string FilledDensityMinus2; + private string FilledDensityMinus3; + private string FilledDensityMinus4; + + private string NullValueAllowed; + + private string DontTouchMyValue = ""; + + private void OnClick() + { + + } + private string OutlinedDensityDefault; + private string OutlinedDensityMinus1; + private string OutlinedDensityMinus2; + private string OutlinedDensityMinus3; + private string OutlinedDensityMinus4; + + private string ExtendedValue; + + private bool ExtendDynamicItems = false; + private MBSelectElement[] DynamicItems => ExtendDynamicItems ? KittenBreedsExtended : KittenBreeds; +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Slider.razor b/Material.Blazor.Website.MD3/Pages/Slider.razor new file mode 100644 index 000000000..462348549 --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/Slider.razor @@ -0,0 +1,186 @@ +@page "/slider" + +@inject IMBToastService ToastService + + + + +

    + Single thumb sliders, responding to user events in three ways: + +

      +
    1. Thumb-up emits events when the user lets releases the slider after movement.
    2. +
    3. Continuous Debounced emits events while the user is holding the thumb, but only after pausing motion.
    4. +
    5. Continuous Throttled emits events while the user is moving the thumb, but throttled against the specified delay.
    6. +
    +

    +
    + + +
    + + +

    + Continuous Slider +

    + +

    With non-continuous input, 0-100 range, 0 decimal, yielding a value of @Value1a.ToString("N0")

    +

    + +

    +

    + +

    + +

    With continuous input, 0-100 range, 1 decimal, and 300ms debounce delay, yielding a value of @Value1b.ToString("N1")

    +

    + +

    +

    + +

    + +

    With continuous input, 0-100 range, 2 decimal, and 300ms throttle delay, yielding a value of @Value1c.ToString("N2")

    +

    + +

    +

    + +

    +
    +
    +
    + + +
    + + +

    + Discrete Sliders +

    + +

    With non-continuous input, tickmarks on the second slider, 0-10 range, 0 decimal, 2 steps, yielding a value of @Value2a.ToString("N0")

    +

    Before you think this slider doesn't work, it only accepts three positions - full left, center and full right.

    +

    + +

    +

    + +

    + +

    With continuous input, tickmarks on the second slider, 0-10 range, 0 decimal, 10 steps, and 300ms debounce delay, yielding a value of @Value2b.ToString("N0")

    +

    + +

    +

    + +

    + +

    With continuous input, tickmarks on the second slider, 0-10 range, 1 decimal, 20 steps, and 300ms throttle delay, yielding a value of @Value2c.ToString("N1")

    +

    + +

    +

    + +

    +
    +
    +
    + +
    +
    + + +@code { + decimal Value1a = 17M; + decimal Value1b = 33.3M; + decimal Value1c = 66.67M; + decimal Value2a = 5; + decimal Value2b = 3; + decimal Value2c = 7.5M; + + uint DebounceDelay = 300; +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Switch.razor b/Material.Blazor.Website.MD3/Pages/Switch.razor index 6aac373e7..1c4d86a47 100644 --- a/Material.Blazor.Website.MD3/Pages/Switch.razor +++ b/Material.Blazor.Website.MD3/Pages/Switch.razor @@ -5,7 +5,7 @@ diff --git a/Material.Blazor.Website.MD3/Pages/TextField.razor b/Material.Blazor.Website.MD3/Pages/TextField.razor index 8b60a59fd..7995c2655 100644 --- a/Material.Blazor.Website.MD3/Pages/TextField.razor +++ b/Material.Blazor.Website.MD3/Pages/TextField.razor @@ -7,7 +7,7 @@ @@ -29,13 +29,15 @@ Default and explicit text field styles. - + - +
    @@ -50,24 +52,36 @@

    - Leading and trailing icons - both using icons from the Material Icons, Font Awesome and Open Iconic foundries. + Leading and trailing icons.

    - - - - - - - + + + + + + +
    @@ -85,21 +99,25 @@ Prefix and Suffix text applied within fields. - - - - - - - + + + + + + +
    @@ -123,12 +141,14 @@ Background color set by CSS class and style attributes. - + - +
    @@ -146,17 +166,20 @@ Left, center and right aligned text (not the same as right-to-left script). - + - + - +
    @@ -174,10 +197,11 @@ Standard HTML input obscuring with type="password" - +
    @@ -195,12 +219,14 @@ Shows two way binding for text fields. - - + + @@ -218,10 +244,11 @@ Shows supporting text. - + @@ -239,25 +266,30 @@ Filled density subsystem from default to minus 4. - - - - - - - - - + + + + + + + + + @@ -275,25 +307,30 @@ Outlined density subsystem from default to minus 4. - - - - - - - - - + + + + + + + + + @@ -314,9 +351,7 @@ private Tuple[] apiTuple = { - new Tuple(0, "MBFilledTextField"), - new Tuple(10, "InternalTextFieldBase"), - new Tuple(0, "MBOutlinedTextField"), + new Tuple(0, "MBTextField"), new Tuple(10, "InternalTextFieldBase") }; } \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/TextField2.razor b/Material.Blazor.Website.MD3/Pages/TextField2.razor deleted file mode 100644 index 1232e3db2..000000000 --- a/Material.Blazor.Website.MD3/Pages/TextField2.razor +++ /dev/null @@ -1,321 +0,0 @@ -@page "/textfield2" - -@namespace Material.Blazor.Website.Pages - -@inject IMBToastService ToastService - - - - -

    - Filled and outlined text fields with icon usage, class and style application and finally density. -

    -
    - - -
    - - -

    - Text Field Styles -

    - -

    - Default and explicit text field styles. -

    - - - - -
    -
    -
    - - - @* -
    - - -

    - Icons -

    - -

    - Leading and trailing icons - both using icons from the Material Icons, Font Awesome and Open Iconic foundries. -

    - - - - - - - - -
    -
    -
    - - - -
    - - -

    - Prefix and Suffix -

    - -

    - Prefix and Suffix text applied within fields. -

    - - - - - - - - -
    -
    -
    - - - -
    - - - - -

    - Class and Style -

    - -

    - Background color set by CSS class and style attributes. -

    - - - - -
    -
    -
    - - - -
    - - -

    - Contents Alignment -

    - -

    - Left, center and right aligned text (not the same as right-to-left script). -

    - - - - - - -
    -
    -
    - - - -
    - - -

    - Passwords -

    - -

    - Standard HTML input obscuring with type="password" -

    - - -
    -
    -
    - - - -
    - - -

    - Two Way Binding -

    - -

    - Shows two way binding for text fields. -

    - - - -
    -
    -
    - - - -
    - - -

    - Supporting Text -

    - -

    - Shows supporting text. -

    - - -
    -
    -
    - - - -
    - - -

    - Filled Density -

    - -

    - Filled density subsystem from default to minus 4. -

    - - - - - - - - - - -
    -
    -
    - - - -
    - - -

    - Outlined Density -

    - -

    - Outlined density subsystem from default to minus 4. -

    - - - - - - - - - - -
    -
    -
    - *@ -
    -
    - - -@code { - private string FilledStyle { get; set; } - - private string OutlinedStyle { get; set; } - - private string Password { get; set; } - - private string TwoWayBinding { get; set; } - - private string SupportingText { get; set; } - - private Tuple[] apiTuple = - { - new Tuple(5, "InternalTextField2Base") - }; -} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Toast.razor b/Material.Blazor.Website.MD3/Pages/Toast.razor index e7d5e93aa..0bdb4f870 100644 --- a/Material.Blazor.Website.MD3/Pages/Toast.razor +++ b/Material.Blazor.Website.MD3/Pages/Toast.razor @@ -61,54 +61,54 @@
    + Items="@positionItems" + LeadingLabel="Toast Position" + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    - +

    - + style="--md-sys-color-primary: var(--mb-toast-background-color-info); color: var(--mb-toast-color-info); margin-bottom: 1rem;" /> - + style="--md-sys-color-primary: var(--mb-toast-background-color-success); color: var(--mb-toast-color-success); margin-bottom: 1rem;" /> - + style="--md-sys-color-primary: var(--mb-toast-background-color-warning); color: var(--mb-toast-color-warning); margin-bottom: 1rem;" /> - + style="--md-sys-color-primary: var(--mb-toast-background-color-error); color: var(--mb-toast-color-error); margin-bottom: 1rem;" />

    @@ -133,64 +133,64 @@
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    + SelectInputStyle="@MBSelectInputStyleMD2.Outlined" />
    - +
    @@ -204,36 +204,36 @@ @code { private bool OverrideCssDefaults { get; set; } - private readonly IEnumerable> positionItems = from pos in (MBToastPosition[])Enum.GetValues(typeof(MBToastPosition)) - select new Material.Blazor.MD2.MBSelectElementMD2 - { - SelectedValue = pos, - Label = pos.ToString() - }; + private readonly IEnumerable> positionItems = from pos in (MBToastPosition[])Enum.GetValues(typeof(MBToastPosition)) + select new MBSelectElement + { + SelectedValue = pos, + LeadingLabel = pos.ToString() + }; private string CssClass { get; set; } - private readonly Material.Blazor.MD2.MBSelectElementMD2[] CssClassItems = new Material.Blazor.MD2.MBSelectElementMD2[] + private readonly MBSelectElement[] CssClassItems = new MBSelectElement[] { - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "", Label = "Toast Svc Default" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "italic-class", Label = "Italic" }, + new MBSelectElement { SelectedValue = "", LeadingLabel = "Toast Svc Default" }, + new MBSelectElement { SelectedValue = "italic-class", LeadingLabel = "Italic" }, }; private MBNotifierCloseMethod CloseMethod { get; set; } - private readonly IEnumerable> closeMethodItems = from c in (MBNotifierCloseMethod[])Enum.GetValues(typeof(MBNotifierCloseMethod)) - select new Material.Blazor.MD2.MBSelectElementMD2 - { - SelectedValue = c, - Label = c.ToString() - }; + private readonly IEnumerable> closeMethodItems = from c in (MBNotifierCloseMethod[])Enum.GetValues(typeof(MBNotifierCloseMethod)) + select new MBSelectElement + { + SelectedValue = c, + LeadingLabel = c.ToString() + }; private string Heading { get; set; } - private readonly Material.Blazor.MD2.MBSelectElementMD2[] headingItems = new Material.Blazor.MD2.MBSelectElementMD2[] + private readonly MBSelectElement[] headingItems = new MBSelectElement[] { - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "", Label = "Toast Svc Default" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "Run of the mill heading", Label = "Run of the mill heading" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "Heading truncated and truncated and truncated and truncated", Label = "Heading that will be truncated" } + new MBSelectElement { SelectedValue = "", LeadingLabel = "Toast Svc Default" }, + new MBSelectElement { SelectedValue = "Run of the mill heading", LeadingLabel = "Run of the mill heading" }, + new MBSelectElement { SelectedValue = "Heading truncated and truncated and truncated and truncated", LeadingLabel = "Heading that will be truncated" } }; @@ -241,64 +241,63 @@ class MyIcon { public string Name; - public Material.Blazor.MD2.IMBIconFoundry? Foundry; } #nullable restore annotations private MyIcon Icon { get; set; } - private readonly Material.Blazor.MD2.MBSelectElementMD2[] iconItems = new Material.Blazor.MD2.MBSelectElementMD2[] + private readonly MBSelectElement[] iconItems = new MBSelectElement[] { - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "directions_run" }, Label = "directions_run" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "thumb_up", Foundry = Material.Blazor.MD2.MBIconHelper.MIFoundry(theme: Material.Blazor.MD2.MBIconMITheme.Filled) }, Label = "thumb_up (filled / default)" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "thumb_up", Foundry = Material.Blazor.MD2.MBIconHelper.MIFoundry(theme: Material.Blazor.MD2.MBIconMITheme.Outlined) }, Label = "thumb_up (outlined)" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "thumb_up", Foundry = Material.Blazor.MD2.MBIconHelper.MIFoundry(theme: Material.Blazor.MD2.MBIconMITheme.Round) }, Label = "thumb_up (rounded)" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "thumb_up", Foundry = Material.Blazor.MD2.MBIconHelper.MIFoundry(theme: Material.Blazor.MD2.MBIconMITheme.TwoTone) }, Label = "thumb_up (two-tone)" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "thumb_up", Foundry = Material.Blazor.MD2.MBIconHelper.MIFoundry(theme: Material.Blazor.MD2.MBIconMITheme.Sharp) }, Label = "thumb_up (sharp)" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "fa-ambulance", Foundry = Material.Blazor.MD2.MBIconHelper.FAFoundry() }, Label = "fa-ambulance" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = new MyIcon() { Name = "fa-ambulance", Foundry = Material.Blazor.MD2.MBIconHelper.FAFoundry(relativeSize: Material.Blazor.MD2.MBIconFARelativeSize.ExtraSmall) }, Label = "fa-ambulance (x-small)" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "directions_run" }, LeadingLabel = "directions_run" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "thumb_up" }, LeadingLabel = "thumb_up (filled / default)" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "thumb_up" }, LeadingLabel = "thumb_up (outlined)" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "thumb_up" }, LeadingLabel = "thumb_up (rounded)" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "thumb_up" }, LeadingLabel = "thumb_up (two-tone)" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "thumb_up" }, LeadingLabel = "thumb_up (sharp)" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "fa-ambulance" }, LeadingLabel = "fa-ambulance" }, + new MBSelectElement { SelectedValue = new MyIcon() { Name = "fa-ambulance" }, LeadingLabel = "fa-ambulance (x-small)" }, }; private MBToastLevel Level { get; set; } - private readonly IEnumerable> levelItems = from c in (MBToastLevel[])Enum.GetValues(typeof(MBToastLevel)) - select new Material.Blazor.MD2.MBSelectElementMD2 - { - SelectedValue = c, - Label = c.ToString() - }; + private readonly IEnumerable> levelItems = from c in (MBToastLevel[])Enum.GetValues(typeof(MBToastLevel)) + select new MBSelectElement + { + SelectedValue = c, + LeadingLabel = c.ToString() + }; private string Message { get; set; } - private readonly Material.Blazor.MD2.MBSelectElementMD2[] messageItems = new Material.Blazor.MD2.MBSelectElementMD2[] + private readonly MBSelectElement[] messageItems = new MBSelectElement[] { - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "Run of the mill message", Label = "Run of the mill message" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "Text bold and underlined", Label = "Message with markup" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "Message truncated and truncated and truncated and truncated", Label = "Message that will be truncated" } + new MBSelectElement { SelectedValue = "Run of the mill message", LeadingLabel = "Run of the mill message" }, + new MBSelectElement { SelectedValue = "Text bold and underlined", LeadingLabel = "Message with markup" }, + new MBSelectElement { SelectedValue = "Message truncated and truncated and truncated and truncated", LeadingLabel = "Message that will be truncated" } }; private string ShowIcon { get; set; } - private readonly Material.Blazor.MD2.MBSelectElementMD2[] showIconItems = new Material.Blazor.MD2.MBSelectElementMD2[] + private readonly MBSelectElement[] showIconItems = new MBSelectElement[] { - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "", Label = "Toast Svc Default" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "true", Label = "true" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "false", Label = "false" } + new MBSelectElement { SelectedValue = "", LeadingLabel = "Toast Svc Default" }, + new MBSelectElement { SelectedValue = "true", LeadingLabel = "true" }, + new MBSelectElement { SelectedValue = "false", LeadingLabel = "false" } }; private string Timeout { get; set; } - private readonly Material.Blazor.MD2.MBSelectElementMD2[] timeoutItems = new Material.Blazor.MD2.MBSelectElementMD2[] + private readonly MBSelectElement[] timeoutItems = new MBSelectElement[] { - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "", Label = "Toast Svc Default" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "1000", Label = "1 second" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "10000", Label = "10 seconds" }, - new Material.Blazor.MD2.MBSelectElementMD2 { SelectedValue = "60000", Label = "60 seconds" } + new MBSelectElement { SelectedValue = "", LeadingLabel = "Toast Svc Default" }, + new MBSelectElement { SelectedValue = "1000", LeadingLabel = "1 second" }, + new MBSelectElement { SelectedValue = "10000", LeadingLabel = "10 seconds" }, + new MBSelectElement { SelectedValue = "60000", LeadingLabel = "60 seconds" } }; private void CustomiseClick() { - string appliedCssClass = (CssClass.Length == 0) ? null : CssClass; + string appliedCssClass = (CssClass is null || CssClass.Length == 0) ? null : CssClass; string appliedHeading = (Heading.Length == 0) ? null : Heading; @@ -313,7 +312,6 @@ level: Level, message: Message, iconName: Icon.Name, - // iconFoundry: Icon.Foundry, showIcon: appliedShowIcon, timeout: appliedTimeout); } diff --git a/Material.Blazor.Website.MD3/Pages/TopAppBar.razor b/Material.Blazor.Website.MD3/Pages/TopAppBar.razor new file mode 100644 index 000000000..1b15cd48a --- /dev/null +++ b/Material.Blazor.Website.MD3/Pages/TopAppBar.razor @@ -0,0 +1,72 @@ +@page "/topappbar" + +@inject IMBToastService ToastService + + + + + +

    + TopAppBar component. +

    +
    + + +
    + + +

    + TopAppBar +

    + +

    + The TopAppBar is a visual component. It is typically placed in the main layout file before the inclusion + of the body. +

    +

    + This website uses the TopAppBar in its MainLayout.razor and can be observed in action + when scrolling any vertically lengthy demo page like RadioButtonGroup. +

    +
    +
    +
    +
    +
    + + + +@code { + private bool _toggleState; + private bool ToggleState + { + get => _toggleState; + set + { + _toggleState = value; + + ToastService.ShowToast(heading: "Toggle Icon Button Clicked", message: $"Value: {_toggleState} / {(_toggleState ? "Dog" : "Cat")}", level: MBToastLevel.Success, showIcon: false); + } + } + + + + private bool _toggleTwoWayBindingState; + private bool ToggleTwoWayBindingState + { + get => _toggleTwoWayBindingState; + set + { + _toggleTwoWayBindingState = value; + + ToastService.ShowToast(heading: "Toggle Icon Button Clicked", message: $"Two way binding Value: {_toggleTwoWayBindingState}", level: MBToastLevel.Success, showIcon: false); + } + } + + + + private void ButtonClick(string notification) + { + ToastService.ShowToast(heading: "Button Clicked", message: notification, level: MBToastLevel.Success, showIcon: false); + } +} \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Pages/Typography.razor b/Material.Blazor.Website.MD3/Pages/Typography.razor index bcf789fe4..f5a3026d0 100644 --- a/Material.Blazor.Website.MD3/Pages/Typography.razor +++ b/Material.Blazor.Website.MD3/Pages/Typography.razor @@ -19,7 +19,7 @@
    - +
    @@ -33,9 +33,8 @@
    - +

    Headline 1

    @@ -43,9 +42,8 @@
    - +

    Headline 2

    @@ -53,9 +51,8 @@
    - +

    Headline 3

    @@ -63,9 +60,8 @@
    - +

    Headline 4

    @@ -73,9 +69,8 @@
    - +
    Headline 5
    @@ -83,9 +78,8 @@
    - +
    Headline 6
    @@ -93,9 +87,8 @@
    - +
    Subtitle 1
    @@ -103,9 +96,8 @@
    - +
    Subtitle 2
    @@ -113,9 +105,8 @@
    - +

    Body 1

    @@ -123,9 +114,8 @@
    - +

    Body 2

    @@ -133,9 +123,8 @@
    - +

    Caption

    @@ -143,9 +132,8 @@
    - +

    Overline

    @@ -153,9 +141,8 @@
    - +

    Regular paragraph with the 'mdc-typography' class applied

    @@ -163,9 +150,8 @@
    - +

    Material Theme color 'red 300' from a CSS variable

    @@ -173,7 +159,7 @@
    -
    +
    \ No newline at end of file diff --git a/Material.Blazor.Website.MD3/Shared/DemonstrationPage.razor.cs b/Material.Blazor.Website.MD3/Shared/DemonstrationPage.razor.cs index c6e68e62f..eaaf6618d 100644 --- a/Material.Blazor.Website.MD3/Shared/DemonstrationPage.razor.cs +++ b/Material.Blazor.Website.MD3/Shared/DemonstrationPage.razor.cs @@ -19,6 +19,7 @@ public partial class DemonstrationPage [Parameter] public string DetailedArticle { get; set; } [Parameter] public bool IsGeneric { get; set; } = false; [Parameter] public string MaterialDesignPage { get; set; } = ""; + [Parameter] public string MaterialWebPage { get; set; } = ""; [Parameter] public MBDensity MinDensity { get; set; } = MBDensity.Default; [Parameter] public RenderFragment PageContent { get; set; } [Parameter] public bool RequiresDisableSelection { get; set; } = false; @@ -50,7 +51,8 @@ private class ReferenceItem private bool NeedsTable => ((ComponentAndPageName != null) || (DetailedArticle != null) - || (MaterialDesignPage != null)); + || (MaterialDesignPage != null) + || (MaterialWebPage != null)); protected override void OnInitialized() @@ -139,6 +141,28 @@ protected override void OnInitialized() }); } + if (!string.IsNullOrWhiteSpace(MaterialWebPage)) + { + string[] pages; + if (MaterialWebPage.Contains(";")) + { + pages = MaterialWebPage.Split(';'); + } + else + { + pages = new string[1] { MaterialWebPage }; + } + + foreach (var page in pages) + { + Items.Add(new ReferenceItem + { + Title = "Material Web 3", + Content = $"" + page + " page link" + }); + } + } + if (!string.IsNullOrWhiteSpace(MaterialDesignPage)) { string[] pages; @@ -156,7 +180,7 @@ protected override void OnInitialized() Items.Add(new ReferenceItem { Title = "Material Design 3", - Content = $"" + page + " page link" + Content = $"" + page + " page link" }); } } diff --git a/Material.Blazor.Website.MD3/Shared/MainLayout.razor b/Material.Blazor.Website.MD3/Shared/MainLayout.razor index 02f236259..e2158ea4c 100644 --- a/Material.Blazor.Website.MD3/Shared/MainLayout.razor +++ b/Material.Blazor.Website.MD3/Shared/MainLayout.razor @@ -12,24 +12,90 @@ IsDismissible="true"> @@ -38,7 +104,7 @@ @onclick="@SideBarToggle" NavIcon="menu" ScrollTarget="#main-content" - TopAppBarType="@MBTopAppBarType.Dense" + TopAppBarType="@MBTopAppBarTypeMD2.Dense" class="app-bar mdc-top-app-bar--short-has-action-item mdc-elevation--z5"> diff --git a/Material.Blazor.Website.MD3/Styles/app.scss b/Material.Blazor.Website.MD3/Styles/app.scss index e5209f91e..9afc9434d 100644 --- a/Material.Blazor.Website.MD3/Styles/app.scss +++ b/Material.Blazor.Website.MD3/Styles/app.scss @@ -140,7 +140,7 @@ $scroll-track-hover-color: rgba(theme.$on-surface, 0.05); /* Dynamic link underlining */ - a:not(.mdc-deprecated-list-item):not(.mb-tooltip-anchor) { + a:not(.mdc-deprecated-list-item) { position: relative; box-shadow: inset 0 -2px 0 theme.$secondary; display: inline-flex; @@ -150,13 +150,13 @@ $scroll-track-hover-color: rgba(theme.$on-surface, 0.05); transition: 0.15s ease; } - a:not(.mdc-deprecated-list-item):not(.mb-tooltip-anchor):hover { + a:not(.mdc-deprecated-list-item):hover { box-shadow: none; color: theme.$on-secondary; text-decoration: none; } - a:not(.mdc-deprecated-list-item):not(.mb-tooltip-anchor)::after { + a:not(.mdc-deprecated-list-item)::after { content: ""; background: theme.$secondary; position: absolute; @@ -168,7 +168,7 @@ $scroll-track-hover-color: rgba(theme.$on-surface, 0.05); transition: 0.15s ease; } - a:not(.mdc-deprecated-list-item):not(.mb-tooltip-anchor):hover:after { + a:not(.mdc-deprecated-list-item):hover:after { height: 100%; } diff --git a/Material.Blazor.Website.MD3/Styles/material-components-web.scss b/Material.Blazor.Website.MD3/Styles/material-components-web.scss index ce243e3d9..40b3814f9 100644 --- a/Material.Blazor.Website.MD3/Styles/material-components-web.scss +++ b/Material.Blazor.Website.MD3/Styles/material-components-web.scss @@ -37,7 +37,6 @@ @use '@material/tab-scroller/mdc-tab-scroller'; @use '@material/textfield'; @use '@material/theme/mdc-theme'; -@use '@material/tooltip/styles' as tooltip-styles; @use '@material/top-app-bar/mdc-top-app-bar'; @use '@material/typography/mdc-typography'; diff --git a/Material.Blazor.Website.MD3/wwwroot/js/app.js b/Material.Blazor.Website.MD3/wwwroot/js/app.js deleted file mode 100644 index 19430246f..000000000 --- a/Material.Blazor.Website.MD3/wwwroot/js/app.js +++ /dev/null @@ -1,21 +0,0 @@ -// We load this file on the MD3 website even though there is no support yet for themes - -window.material_blazor_website = { - themeSetter: { - setTheme: function (sheetName, minify) { - let extension = ".css"; - - if (minify === true) { - extension = ".min.css"; - } - - document.getElementById("app-style").setAttribute("href", "_content/Material.Blazor.Website/css/" + sheetName + extension); - } - }, - - baseHref: { - getBaseURI: function () { - return document.getElementsByTagName("base")[0].getAttribute("href"); - } - } -} diff --git a/Material.Blazor.Website.Server.MD3/Material.Blazor.Website.Server.MD3.csproj b/Material.Blazor.Website.Server.MD3/Material.Blazor.Website.Server.MD3.csproj index d0af0a754..330245bb6 100644 --- a/Material.Blazor.Website.Server.MD3/Material.Blazor.Website.Server.MD3.csproj +++ b/Material.Blazor.Website.Server.MD3/Material.Blazor.Website.Server.MD3.csproj @@ -13,12 +13,12 @@ - - - - - - + + + + + + diff --git a/Material.Blazor.Website.Server.MD3/Pages/Host.cshtml b/Material.Blazor.Website.Server.MD3/Pages/Host.cshtml index 5c1df2054..eca0a2180 100644 --- a/Material.Blazor.Website.Server.MD3/Pages/Host.cshtml +++ b/Material.Blazor.Website.Server.MD3/Pages/Host.cshtml @@ -18,29 +18,22 @@ - Material.Blazor + Material.Blazor.MD3 - @* We load all of the MD2 resources as the website is a mix of MD2 structure with MD3 components (for now)*@ - - + - - - - - @if (PlatformDetermination.IsBlazorServer) { diff --git a/Material.Blazor.Website.Server.MD3/Program.cs b/Material.Blazor.Website.Server.MD3/Program.cs index 5968749ae..b52425e31 100644 --- a/Material.Blazor.Website.Server.MD3/Program.cs +++ b/Material.Blazor.Website.Server.MD3/Program.cs @@ -1,8 +1,5 @@ using GoogleAnalytics.Blazor; using Material.Blazor; -using Material.Blazor.MD2; -using Material.Blazor.Internal.MD2; -using Material.Blazor.Website.MD3; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; diff --git a/Material.Blazor.Website.Server/Material.Blazor.Website.Server.csproj b/Material.Blazor.Website.Server/Material.Blazor.Website.Server.csproj index cf1f1a31d..67c1b19d6 100644 --- a/Material.Blazor.Website.Server/Material.Blazor.Website.Server.csproj +++ b/Material.Blazor.Website.Server/Material.Blazor.Website.Server.csproj @@ -13,12 +13,12 @@ - - - - - - + + + + + + diff --git a/Material.Blazor.Website.WebAssembly.MD3/Material.Blazor.Website.WebAssembly.MD3.csproj b/Material.Blazor.Website.WebAssembly.MD3/Material.Blazor.Website.WebAssembly.MD3.csproj index 7acb9561b..46bc86364 100644 --- a/Material.Blazor.Website.WebAssembly.MD3/Material.Blazor.Website.WebAssembly.MD3.csproj +++ b/Material.Blazor.Website.WebAssembly.MD3/Material.Blazor.Website.WebAssembly.MD3.csproj @@ -17,12 +17,12 @@ - - + + - - + + diff --git a/Material.Blazor.Website.WebAssembly.MD3/Program.cs b/Material.Blazor.Website.WebAssembly.MD3/Program.cs index 1d2163c81..f18dd081e 100644 --- a/Material.Blazor.Website.WebAssembly.MD3/Program.cs +++ b/Material.Blazor.Website.WebAssembly.MD3/Program.cs @@ -1,7 +1,5 @@ using GoogleAnalytics.Blazor; using Material.Blazor; -using Material.Blazor.Internal.MD2; -using Material.Blazor.MD2; using Material.Blazor.Website.MD3; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; diff --git a/Material.Blazor.Website.WebAssembly/Material.Blazor.Website.WebAssembly.csproj b/Material.Blazor.Website.WebAssembly/Material.Blazor.Website.WebAssembly.csproj index 07af9c125..6ca995d34 100644 --- a/Material.Blazor.Website.WebAssembly/Material.Blazor.Website.WebAssembly.csproj +++ b/Material.Blazor.Website.WebAssembly/Material.Blazor.Website.WebAssembly.csproj @@ -18,12 +18,12 @@ - - + + - - + + diff --git a/Material.Blazor.Website/Material.Blazor.Website.csproj b/Material.Blazor.Website/Material.Blazor.Website.csproj index 7a3e267c6..a2341efa7 100644 --- a/Material.Blazor.Website/Material.Blazor.Website.csproj +++ b/Material.Blazor.Website/Material.Blazor.Website.csproj @@ -39,11 +39,10 @@ - - + + - - + diff --git a/Material.Blazor.Website/Pages/DatePicker.razor b/Material.Blazor.Website/Pages/DatePicker.razor index 18e89ae07..8b87e634a 100644 --- a/Material.Blazor.Website/Pages/DatePicker.razor +++ b/Material.Blazor.Website/Pages/DatePicker.razor @@ -2,8 +2,9 @@ @inject IMBToastService ToastService +@* @using Nager.Date - + *@

    - Control which dates are selectable, first with Mondays, Wednesdays and Fridays and second with business days in England, using the Nager.Date nuget package. + Control which dates are selectable, first with Mondays, Wednesdays and Fridays and the second with weekend days. + Commented in the code is an example of business days in England, using the Nager.Date nuget package. + It is commented because the Nager.Date package now requires a license key.

    @@ -90,6 +93,17 @@ MinDate="@Min" />

    +

    + +

    +@*

    + *@ @@ -250,17 +265,17 @@ } - private DateTime _englandBusinessDay = new DateTime(2021, 4, 6); - private DateTime EnglandBusinessDay - { - get => _englandBusinessDay; - set - { - _englandBusinessDay = value; + // private DateTime _englandBusinessDay = DateTime.Now; + // private DateTime EnglandBusinessDay + // { + // get => _englandBusinessDay; + // set + // { + // _englandBusinessDay = value; - ToastService.ShowToast(heading: "England Business Day selectable", message: $"Value: {_englandBusinessDay.ToLongDateString()}", level: MBToastLevel.Success, showIcon: false); - } - } + // ToastService.ShowToast(heading: "England Business Day selectable", message: $"Value: {_englandBusinessDay.ToLongDateString()}", level: MBToastLevel.Success, showIcon: false); + // } + // } private DateTime _customStyle = new DateTime(2021, 1, 8); diff --git a/Material.Blazor.Website/Pages/Icon.razor b/Material.Blazor.Website/Pages/Icon.razor index aee1fbe1f..ca27dac49 100644 --- a/Material.Blazor.Website/Pages/Icon.razor +++ b/Material.Blazor.Website/Pages/Icon.razor @@ -14,7 +14,7 @@ -
    +

    @@ -45,8 +45,300 @@ IconName="check" /> Open Iconic foundry

    +

    + + Material Symbol Foundry - Outlined, 48px +

    +

    + + Material Symbol Foundry - Rounded, 48px +

    +

    + + Material Symbol Foundry - Sharp, 48px +

    + + +

    + +
    + + +
    +
    +

    + Material Symbol Foundry - Tailored Icons +

    + +

    + Shows how Material Symbol icons can be tailored. +

    +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +
    + +   + +   + +   + +   + +   +
    +
    + + +@code { + private string mbIconColor { get; set; } = "black"; + + + + MBSelectElement[] iconFills = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = true, Label = "Filled" }, + new MBSelectElement { SelectedValue = false, Label = "Not filled" }, + }; + + private bool _iconFill = true; + private bool mbIconFill + { + get => _iconFill; + set + { + _iconFill = value; + + ToastService.ShowToast(heading: "Icon Fill", message: $"Value: '{_iconFill.ToString()}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + + MBSelectElement[] iconGradients = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "LowEmphasis", Label = "Low emphasis" }, + new MBSelectElement { SelectedValue = "NormalEmphasis", Label = "Normal emphasis" }, + new MBSelectElement { SelectedValue = "HighEmphasis", Label = "High emphasis" }, + }; + + private MBIconMSGradient mbIconGradient = MBIconMSGradient.NormalEmphasis; + private string _iconGradient = "NormalEmphasis"; + private string iconGradient + { + get => _iconGradient; + set + { + _iconGradient = value; + + mbIconGradient = _iconGradient switch + { + "LowEmphasis" => MBIconMSGradient.LowEmphasis, + "NormalEmphasis" => MBIconMSGradient.NormalEmphasis, + "HighEmphasis" => MBIconMSGradient.HighEmphasis, + _ => throw new Exception("Unknown icon gradient"), + }; + + ToastService.ShowToast(heading: "Icon Gradient", message: $"Value: '{_iconGradient}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + + MBSelectElement[] iconSizes = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "20", Label = "20px" }, + new MBSelectElement { SelectedValue = "24", Label = "24px" }, + new MBSelectElement { SelectedValue = "40", Label = "40px" }, + new MBSelectElement { SelectedValue = "48", Label = "48px" }, + }; + + private MBIconMSSize mbIconSize = MBIconMSSize.Size24; + private string _iconSize = "24"; + private string iconSize + { + get => _iconSize; + set + { + _iconSize = value; + + mbIconSize = _iconSize switch + { + "20" => MBIconMSSize.Size20, + "24" => MBIconMSSize.Size24, + "40" => MBIconMSSize.Size40, + "48" => MBIconMSSize.Size48, + _ => throw new Exception("Unknown icon Size"), + }; + + ToastService.ShowToast(heading: "Icon Size", message: $"Value: '{_iconSize}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + + MBSelectElement[] iconStyles = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "outlined", Label = "Outlined" }, + new MBSelectElement { SelectedValue = "rounded", Label = "Rounded" }, + new MBSelectElement { SelectedValue = "sharp", Label = "Sharp" } + }; + + private MBIconMSStyle mbIconStyle = MBIconMSStyle.Outlined; + private string _iconStyle = "outlined"; + private string iconStyle + { + get => _iconStyle; + set + { + _iconStyle = value; + + mbIconStyle = _iconStyle switch + { + "outlined" => MBIconMSStyle.Outlined, + "rounded" => MBIconMSStyle.Rounded, + "sharp" => MBIconMSStyle.Sharp, + _ => throw new Exception("Unknown icon style"), + }; + + ToastService.ShowToast(heading: "Icon style", message: $"Value: '{_iconStyle}'", level: MBToastLevel.Success, showIcon: false); + } + } + + + + MBSelectElement[] iconWeights = new MBSelectElement[] + { + new MBSelectElement { SelectedValue = "100", Label = "100" }, + new MBSelectElement { SelectedValue = "200", Label = "200" }, + new MBSelectElement { SelectedValue = "300", Label = "300" }, + new MBSelectElement { SelectedValue = "400", Label = "400" }, + new MBSelectElement { SelectedValue = "500", Label = "500" }, + new MBSelectElement { SelectedValue = "600", Label = "600" }, + new MBSelectElement { SelectedValue = "700", Label = "700" }, + }; + + private MBIconMSWeight mbIconWeight = MBIconMSWeight.W400; + private string _iconWeight = "400"; + private string iconWeight + { + get => _iconWeight; + set + { + _iconWeight = value; + + mbIconWeight = _iconWeight switch + { + "100" => MBIconMSWeight.W100, + "200" => MBIconMSWeight.W200, + "300" => MBIconMSWeight.W300, + "400" => MBIconMSWeight.W400, + "500" => MBIconMSWeight.W500, + "600" => MBIconMSWeight.W600, + "700" => MBIconMSWeight.W700, + _ => throw new Exception("Unknown icon weight"), + }; + + ToastService.ShowToast(heading: "Icon Weight", message: $"Value: '{_iconWeight}'", level: MBToastLevel.Success, showIcon: false); + } + } + +} diff --git a/Material.Blazor.Website/Pages/NumericDecimalField.razor b/Material.Blazor.Website/Pages/NumericDecimalField.razor index 91380735c..8fecf8c0e 100644 --- a/Material.Blazor.Website/Pages/NumericDecimalField.razor +++ b/Material.Blazor.Website/Pages/NumericDecimalField.razor @@ -67,7 +67,7 @@

    - Percentage and basis point numerics can be editted either using a prefix or standard formatting (in the case of percentages only). + Percentage and basis point numerics can be edited either using a prefix or standard formatting (in the case of percentages only). A figure of "12.3%" results in a decimal value of "0.123" and one of "34.5 bp" results in a decimal value of "0.00345".

    diff --git a/Material.Blazor.Website/Pages/NumericDoubleField.razor b/Material.Blazor.Website/Pages/NumericDoubleField.razor index 64cac30fe..14db4efcd 100644 --- a/Material.Blazor.Website/Pages/NumericDoubleField.razor +++ b/Material.Blazor.Website/Pages/NumericDoubleField.razor @@ -67,7 +67,7 @@

    - Percentage and basis point numerics can be editted either using a prefix or standard formatting (in the case of percentages only). + Percentage and basis point numerics can be edited either using a prefix or standard formatting (in the case of percentages only). A figure of "12.3%" results in a double value of "0.123" and one of "34.5 bp" results in a double value of "0.00345".

    diff --git a/Material.Blazor.Website/wwwroot/js/app.js b/Material.Blazor.Website/wwwroot/js/app.js deleted file mode 100644 index 7daaf4b29..000000000 --- a/Material.Blazor.Website/wwwroot/js/app.js +++ /dev/null @@ -1,19 +0,0 @@ -window.material_blazor_website = { - themeSetter: { - setTheme: function (sheetName, minify) { - let extension = ".css"; - - if (minify === true) { - extension = ".min.css"; - } - - document.getElementById("app-style").setAttribute("href", "_content/Material.Blazor.Website/css/" + sheetName + extension); - } - }, - - baseHref: { - getBaseURI: function () { - return document.getElementsByTagName("base")[0].getAttribute("href"); - } - } -} diff --git a/Material.Blazor.sln b/Material.Blazor.sln index a51e22b86..f7ca50622 100644 --- a/Material.Blazor.sln +++ b/Material.Blazor.sln @@ -13,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solution Items", ".Solutio .github\dependabot.yml = .github\dependabot.yml Directory.Build.props = Directory.Build.props filterConfig.yml = filterConfig.yml - .github\workflows\GithubActionsRelease.yml = .github\workflows\GithubActionsRelease.yml - .github\workflows\GithubActionsWIP.yml = .github\workflows\GithubActionsWIP.yml LICENSE.md = LICENSE.md LocalTest.bat = LocalTest.bat README.md = README.md @@ -26,7 +24,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".Solution Items", ".Solutio EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "guidelines", "guidelines", "{86740746-C4E6-45DF-BA84-8FB1C1200DD9}" ProjectSection(SolutionItems) = preProject - Material.Blazor.MD3\Model.MD2\MBSelectElement.cs = Material.Blazor.MD3\Model.MD2\MBSelectElement.cs guidelines\ProjectGuidelines.md = guidelines\ProjectGuidelines.md guidelines\StylingGuidelines.md = guidelines\StylingGuidelines.md EndProjectSection @@ -60,6 +57,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Material.Blazor.Website.Ser EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Material.Blazor.Website.WebAssembly.MD3", "Material.Blazor.Website.WebAssembly.MD3\Material.Blazor.Website.WebAssembly.MD3.csproj", "{24BC0E88-5D78-49EC-BADF-5FEE0CF9FE58}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Material.Blazor.MD2", "Material.Blazor.MD2\Material.Blazor.MD2.csproj", "{4E12E9D6-0225-42C9-9FFA-8CA9E50A4D29}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Server|Any CPU = Server|Any CPU @@ -102,6 +101,8 @@ Global {24BC0E88-5D78-49EC-BADF-5FEE0CF9FE58}.Server|Any CPU.Build.0 = Server|Any CPU {24BC0E88-5D78-49EC-BADF-5FEE0CF9FE58}.WebAssembly|Any CPU.ActiveCfg = WebAssembly|Any CPU {24BC0E88-5D78-49EC-BADF-5FEE0CF9FE58}.WebAssembly|Any CPU.Build.0 = WebAssembly|Any CPU + {4E12E9D6-0225-42C9-9FFA-8CA9E50A4D29}.Server|Any CPU.ActiveCfg = Server|Any CPU + {4E12E9D6-0225-42C9-9FFA-8CA9E50A4D29}.WebAssembly|Any CPU.ActiveCfg = WebAssembly|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -112,6 +113,7 @@ Global {C37DB3E6-EC3D-42F6-81C3-EE45744BA5F7} = {F3F9E74C-DFB8-49CF-8F1E-6C4849914C0B} {6CC02217-6409-4B34-AE1C-25DFFB4B94AD} = {F3F9E74C-DFB8-49CF-8F1E-6C4849914C0B} {24BC0E88-5D78-49EC-BADF-5FEE0CF9FE58} = {F3F9E74C-DFB8-49CF-8F1E-6C4849914C0B} + {4E12E9D6-0225-42C9-9FFA-8CA9E50A4D29} = {F3F9E74C-DFB8-49CF-8F1E-6C4849914C0B} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6EFB30DD-ABFD-4A38-96E1-D309BB538E62} diff --git a/Material.Blazor/Components/Icon/MBIcon.md b/Material.Blazor/Components/Icon/MBIcon.md index 7deb166d7..4b2d41752 100644 --- a/Material.Blazor/Components/Icon/MBIcon.md +++ b/Material.Blazor/Components/Icon/MBIcon.md @@ -11,6 +11,7 @@ An icon component that generates HTML markup for an icon either for the default ## Details - The Material Icons foundry is supplied by Google and referenced in the Material.Blazor bundled CSS. +- The Material Symbols foundry is supplied by Google and referenced in the Material.Blazor bundled CSS. - You can also use Font Awesome version 5 and Open Iconic version 1.1 icon foundries. You will need to reference CDN or local CSS files for these two fonts.   diff --git a/Material.Blazor/Components/Icon/MBIcon.razor.cs b/Material.Blazor/Components/Icon/MBIcon.razor.cs index a3c94394f..4b3fbc4a2 100644 --- a/Material.Blazor/Components/Icon/MBIcon.razor.cs +++ b/Material.Blazor/Components/Icon/MBIcon.razor.cs @@ -25,6 +25,7 @@ public class MBIcon : ComponentFoundation /// /// The foundry. /// IconFoundry="IconHelper.MIIcon()" + /// IconFoundry="IconHelper.MSIcon()" /// IconFoundry="IconHelper.FAIcon()" /// IconFoundry="IconHelper.OIIcon()" /// Overrides diff --git a/Material.Blazor/Components/NumericDecimalField/MBNumericDecimalField.razor.cs b/Material.Blazor/Components/NumericDecimalField/MBNumericDecimalField.razor.cs index 90ad49ee7..6ad0f7032 100644 --- a/Material.Blazor/Components/NumericDecimalField/MBNumericDecimalField.razor.cs +++ b/Material.Blazor/Components/NumericDecimalField/MBNumericDecimalField.razor.cs @@ -179,7 +179,7 @@ private string AppliedFormat return ""; } - if (!(NumericSingularFormat is null) && Utilities.DecimalEqual(Math.Abs(ComponentValue), 1)) + if ((NumericSingularFormat is not null) && Utilities.DecimalEqual(Math.Abs(ComponentValue), 1)) { return NumericSingularFormat; } diff --git a/Material.Blazor/Material.Blazor.csproj b/Material.Blazor/Material.Blazor.csproj index 265c251e1..9389da5a9 100644 --- a/Material.Blazor/Material.Blazor.csproj +++ b/Material.Blazor/Material.Blazor.csproj @@ -1,6 +1,20 @@  + + + net8.0 false 5.0.0-preview.WIP @@ -18,12 +32,33 @@ d7509b6c-bc5c-4a56-a750-09c8ae4aeb85 + + + + + + + + + true - true + false @@ -51,13 +86,6 @@ - - - - - - - @@ -88,7 +116,6 @@ - @@ -186,7 +213,7 @@ - + @@ -194,16 +221,16 @@ --> - + - + - + - + @@ -215,26 +242,46 @@ - + - - - + + + + + + + + + + - + - - - + - + diff --git a/Material.Blazor/Model/Icon/IconFoundryMS.cs b/Material.Blazor/Model/Icon/IconFoundryMS.cs new file mode 100644 index 000000000..ae1064c9d --- /dev/null +++ b/Material.Blazor/Model/Icon/IconFoundryMS.cs @@ -0,0 +1,38 @@ +namespace Material.Blazor; + +/// +/// Material Symbols foundry details. +/// +internal class IconFoundryMS : IMBIconFoundry +{ + /// + MBIconFoundryName IMBIconFoundry.FoundryName => MBIconFoundryName.MaterialSymbols; + + + /// + /// The Material Symbols modifiers. + /// + + public string Color { get; } + public bool? Fill { get; } + public MBIconMSGradient? Gradient { get; } + public MBIconMSSize? Size { get; } + public MBIconMSStyle? Style { get; } + public MBIconMSWeight? Weight { get; } + + public IconFoundryMS( + string color = null, + bool? fill = null, + MBIconMSGradient? gradient = null, + MBIconMSSize? size = null, + MBIconMSStyle? style = null, + MBIconMSWeight? weight = null) + { + Color = color; + Fill = fill; + Gradient = gradient; + Size = size; + Style = style; + Weight = weight; + } +} diff --git a/Material.Blazor/Model/Icon/IconFA.cs b/Material.Blazor/Model/Icon/Icon_FA.cs similarity index 93% rename from Material.Blazor/Model/Icon/IconFA.cs rename to Material.Blazor/Model/Icon/Icon_FA.cs index 1c674088e..a4adaa418 100644 --- a/Material.Blazor/Model/Icon/IconFA.cs +++ b/Material.Blazor/Model/Icon/Icon_FA.cs @@ -5,7 +5,7 @@ namespace Material.Blazor.Internal; /// /// Font Awesome icon. /// -internal class IconFA : IMBIcon +internal class Icon_FA : IMBIcon { private string IconName { get; } @@ -63,7 +63,7 @@ internal class IconFA : IMBIcon #nullable enable annotations - public IconFA(MBCascadingDefaults cascadingDefaults, string iconName, IconFoundryFA? foundry = null) + public Icon_FA(MBCascadingDefaults cascadingDefaults, string iconName, IconFoundryFA? foundry = null) { IconName = iconName; Style = cascadingDefaults.AppliedIconFAStyle(foundry?.Style); diff --git a/Material.Blazor/Model/Icon/IconMI.cs b/Material.Blazor/Model/Icon/Icon_MI.cs similarity index 92% rename from Material.Blazor/Model/Icon/IconMI.cs rename to Material.Blazor/Model/Icon/Icon_MI.cs index 367e99320..7ebb14825 100644 --- a/Material.Blazor/Model/Icon/IconMI.cs +++ b/Material.Blazor/Model/Icon/Icon_MI.cs @@ -5,7 +5,7 @@ namespace Material.Blazor; /// /// Material Icons icon. /// -internal class IconMI : IMBIcon +internal class Icon_MI : IMBIcon { private string MaterialIconsTheme { @@ -61,7 +61,7 @@ private string MaterialIconsTheme #nullable enable annotations - public IconMI(MBCascadingDefaults cascadingDefaults, string iconName, IconFoundryMI? foundry = null) + public Icon_MI(MBCascadingDefaults cascadingDefaults, string iconName, IconFoundryMI? foundry = null) { IconName = iconName; Theme = cascadingDefaults.AppliedIconMITheme(foundry?.Theme); diff --git a/Material.Blazor/Model/Icon/Icon_MS.cs b/Material.Blazor/Model/Icon/Icon_MS.cs new file mode 100644 index 000000000..b66b10036 --- /dev/null +++ b/Material.Blazor/Model/Icon/Icon_MS.cs @@ -0,0 +1,155 @@ +using Microsoft.AspNetCore.Components.Rendering; + +namespace Material.Blazor; + +/// +/// Material Icons icon. +/// +internal class Icon_MS : IMBIcon +{ + private string IconName { get; } + + + /// + public IMBIcon.IconFragment Render => (@class, style, attributes) => (RenderTreeBuilder builder) => + { + if (IconName == null) + { + return; + } + var rendSeq = 0; + + builder.OpenElement(rendSeq++, "div"); + { + builder.AddAttribute(rendSeq++, "class", @class); + builder.AddAttribute(rendSeq++, "style", style); + builder.AddMultipleAttributes(rendSeq++, attributes); + + builder.OpenElement(rendSeq++, "span"); + { + builder.AddAttribute(rendSeq++, "class", iconDerivedClass); + builder.AddAttribute(rendSeq++, "style", iconDerivedStyle); + builder.AddContent(rendSeq++, IconName.ToLower()); + } + builder.CloseElement(); + } + builder.CloseElement(); + }; + + + private string msColor { get; } + private bool msFill { get; } + private MBIconMSGradient? msGradient { get; } + private MBIconMSSize? msSize { get; } + private MBIconMSStyle msStyle { get; } + private MBIconMSWeight? msWeight { get; } + + private string iconDerivedClass { get; set; } + private string iconDerivedStyle { get; set; } + + + +#nullable enable annotations + public Icon_MS(MBCascadingDefaults cascadingDefaults, string iconName, IconFoundryMS? foundry = null) + { + IconName = iconName; + msColor = cascadingDefaults.AppliedIconMSColor(foundry?.Color); + msFill = cascadingDefaults.AppliedIconMSFill(foundry.Fill); + msGradient = cascadingDefaults.AppliedIconMSGradient(foundry?.Gradient); + msSize = cascadingDefaults.AppliedIconMSSize(foundry?.Size); + msStyle = cascadingDefaults.AppliedIconMSStyle(foundry?.Style); + msWeight = cascadingDefaults.AppliedIconMSWeight(foundry?.Weight); + + // Set the icon class + + iconDerivedClass = ""; + iconDerivedClass = msStyle switch + { + MBIconMSStyle.Outlined => "material-symbols-outlined ", + MBIconMSStyle.Rounded => "material-symbols-rounded ", + MBIconMSStyle.Sharp => "material-symbols-sharp ", + _ => throw new System.Exception("Unknown Icon Style") + }; + + // Set the icon style + + iconDerivedStyle = ""; + var fontStyle = ""; + var fontVariation = " font-variation-settings:"; + + // Icon color + fontStyle += " color: " + msColor + ";"; + + // Icon fill + var fillValue = msFill ? "1" : "0"; + fontVariation += " 'FILL' " + fillValue + ","; + + // Icon gradient + _ = msGradient switch + { + MBIconMSGradient.LowEmphasis => fontVariation += " 'GRAD' -25,", + MBIconMSGradient.NormalEmphasis => fontVariation += " 'GRAD' 0,", + MBIconMSGradient.HighEmphasis => fontVariation += " 'GRAD' 200,", + _ => throw new System.Exception("Unknown Icon Gradient") + }; + + // Icon size + switch (msSize) + { + case MBIconMSSize.Size20: + fontStyle += " font-size: 20px;"; + fontVariation += " 'opsz' 20,"; + break; + + case MBIconMSSize.Size24: + fontStyle += " font-size: 24px;"; + fontVariation += " 'opsz' 24,"; + break; + + case MBIconMSSize.Size40: + fontStyle += " font-size: 40px;"; + fontVariation += " 'opsz' 40,"; + break; + + case MBIconMSSize.Size48: + fontStyle += " font-size: 48px;"; + fontVariation += " 'opsz' 48,"; + break; + + default: + throw new System.Exception("Unknown Icon Size"); + }; + + // Icon weight + _ = msWeight switch + { + MBIconMSWeight.W100 => fontVariation += " 'wght' 100,", + MBIconMSWeight.W200 => fontVariation += " 'wght' 200,", + MBIconMSWeight.W300 => fontVariation += " 'wght' 300,", + MBIconMSWeight.W400 => fontVariation += " 'wght' 400,", + MBIconMSWeight.W500 => fontVariation += " 'wght' 500,", + MBIconMSWeight.W600 => fontVariation += " 'wght' 600,", + MBIconMSWeight.W700 => fontVariation += " 'wght' 700,", + _ => throw new System.Exception("Unknown Icon Weight") + }; + + // Fix the trailing part of the variation string + if (fontVariation.EndsWith(',')) + { + fontVariation = fontVariation.TrimEnd(','); + fontVariation += ';'; + } + + if (fontVariation.EndsWith(':')) + { + fontVariation = null; + } + + iconDerivedStyle = fontStyle + fontVariation; + } + +#nullable restore annotations + + public bool RequiresColorFilter => false; + +} diff --git a/Material.Blazor/Model/Icon/IconOI.cs b/Material.Blazor/Model/Icon/Icon_OI.cs similarity index 88% rename from Material.Blazor/Model/Icon/IconOI.cs rename to Material.Blazor/Model/Icon/Icon_OI.cs index f5d566329..af0c72393 100644 --- a/Material.Blazor/Model/Icon/IconOI.cs +++ b/Material.Blazor/Model/Icon/Icon_OI.cs @@ -6,7 +6,7 @@ namespace Material.Blazor; /// /// Open Iconic icon. /// -internal class IconOI : IMBIcon +internal class Icon_OI : IMBIcon { private string IconName { get; } @@ -35,7 +35,7 @@ internal class IconOI : IMBIcon #nullable enable annotations #pragma warning disable IDE0060 // Remove unused parameter - public IconOI(MBCascadingDefaults cascadingDefaults, string iconName, IconFoundryOI? foundry = null) + public Icon_OI(MBCascadingDefaults cascadingDefaults, string iconName, IconFoundryOI? foundry = null) { IconName = iconName; } diff --git a/Material.Blazor/Model/Icon/MBIconHelper.cs b/Material.Blazor/Model/Icon/MBIconHelper.cs index 8f39071b3..61b8387f1 100644 --- a/Material.Blazor/Model/Icon/MBIconHelper.cs +++ b/Material.Blazor/Model/Icon/MBIconHelper.cs @@ -1,5 +1,6 @@ using Material.Blazor.Internal; using System; +using System.Drawing; namespace Material.Blazor; @@ -26,6 +27,27 @@ public class MBIconHelper : IMBIcon public static IMBIconFoundry MIFoundry(MBIconMITheme? theme = null) => new IconFoundryMI(theme); + /// + /// Returns a new Material Symbols foundry. + /// + /// Optional specifying the Material Styles style. + /// to be passed to a Material.Blazor component. + public static IMBIconFoundry MSFoundry( + string color = null, + bool? fill = null, + MBIconMSGradient? gradient = null, + MBIconMSSize? size = null, + MBIconMSStyle? style = null, + MBIconMSWeight? weight = null) + => + new IconFoundryMS( + color, + fill, + gradient, + size, + style, + weight); + /// /// Returns a new Font Awesome foundry. /// @@ -54,9 +76,10 @@ internal MBIconHelper(MBCascadingDefaults cascadingDefaults, string iconName, IM UnderlyingIcon = iconFoundry switch { - MBIconFoundryName.MaterialIcons => new IconMI(cascadingDefaults, iconName, (IconFoundryMI?)foundry), - MBIconFoundryName.FontAwesome => new IconFA(cascadingDefaults, iconName, (IconFoundryFA?)foundry), - MBIconFoundryName.OpenIconic => new IconOI(cascadingDefaults, iconName, (IconFoundryOI?)foundry), + MBIconFoundryName.MaterialIcons => new Icon_MI(cascadingDefaults, iconName, (IconFoundryMI?)foundry), + MBIconFoundryName.MaterialSymbols => new Icon_MS(cascadingDefaults, iconName, (IconFoundryMS?)foundry), + MBIconFoundryName.FontAwesome => new Icon_FA(cascadingDefaults, iconName, (IconFoundryFA?)foundry), + MBIconFoundryName.OpenIconic => new Icon_OI(cascadingDefaults, iconName, (IconFoundryOI?)foundry), _ => throw new NotImplementedException(), }; } diff --git a/Material.Blazor/Model/MBCascadingDefaults.cs b/Material.Blazor/Model/MBCascadingDefaults.cs index 523b13f97..a8f5e1c1b 100644 --- a/Material.Blazor/Model/MBCascadingDefaults.cs +++ b/Material.Blazor/Model/MBCascadingDefaults.cs @@ -17,6 +17,8 @@ namespace Material.Blazor; /// public class MBCascadingDefaults { + #region ATTRIBUTE SPLATTING AND VALIDATION + /************************************************************************************************************* * * @@ -57,7 +59,9 @@ public class MBCascadingDefaults /// internal MBItemValidation AppliedItemValidation(MBItemValidation? criteria = null) => criteria ?? ItemValidation; + #endregion + #region Icons /************************************************************************************************************* * @@ -67,6 +71,8 @@ public class MBCascadingDefaults * ************************************************************************************************************/ + #region MBIconFoundry + private MBIconFoundryName _iconFoundryName = MBIconFoundryName.MaterialIcons; /// /// The default foundry name, initialized to if not explicitly set. @@ -80,7 +86,9 @@ public class MBCascadingDefaults /// The to apply. internal MBIconFoundryName AppliedIconFoundryName(MBIconFoundryName? iconFoundryName = null) => iconFoundryName ?? IconFoundryName; + #endregion + #region MBIconMI private MBIconMITheme _iconMITheme = MBIconMITheme.Filled; /// @@ -95,7 +103,41 @@ public class MBCascadingDefaults /// The to apply. internal MBIconMITheme AppliedIconMITheme(MBIconMITheme? iconMITheme = null) => iconMITheme ?? IconMITheme; + #endregion + + #region MBIconMS + + private string _iconMSColor = "black"; + public string IconMSColor { get => _iconMSColor; set => SetParameter(ref _iconMSColor, value); } + internal string AppliedIconMSColor(string iconMSColor = null) => iconMSColor ?? IconMSColor; + + private bool _iconMSFill = true; + public bool IconMSFill { get => _iconMSFill; set => SetParameter(ref _iconMSFill, value); } + internal bool AppliedIconMSFill(bool? iconMSFill = null) => iconMSFill ?? IconMSFill; + + private MBIconMSGradient _iconMSGradient = MBIconMSGradient.NormalEmphasis; + public MBIconMSGradient IconMSGradient { get => _iconMSGradient; set => SetParameter(ref _iconMSGradient, value); } + internal MBIconMSGradient AppliedIconMSGradient(MBIconMSGradient? iconMSGradient = null) => iconMSGradient ?? IconMSGradient; + + private string _iconMSName = "Favorite"; + public string IconMSName { get => _iconMSName; set => SetParameter(ref _iconMSName, value); } + internal string AppliedIconMSName(string iconMSName = null) => iconMSName ?? IconMSName; + + private MBIconMSStyle _iconMSStyle = MBIconMSStyle.Outlined; + public MBIconMSStyle IconMSStyle { get => _iconMSStyle; set => SetParameter(ref _iconMSStyle, value); } + internal MBIconMSStyle AppliedIconMSStyle(MBIconMSStyle? iconMSStyle = null) => iconMSStyle ?? IconMSStyle; + private MBIconMSSize _iconMSSize = MBIconMSSize.Size24; + public MBIconMSSize IconMSSize { get => _iconMSSize; set => SetParameter(ref _iconMSSize, value); } + internal MBIconMSSize AppliedIconMSSize(MBIconMSSize? iconMSSize = null) => iconMSSize ?? IconMSSize; + + private MBIconMSWeight _iconMSWeight = MBIconMSWeight.W400; + public MBIconMSWeight IconMSWeight { get => _iconMSWeight; set => SetParameter(ref _iconMSWeight, value); } + internal MBIconMSWeight AppliedIconMSWeight(MBIconMSWeight? iconMSWeight = null) => iconMSWeight ?? IconMSWeight; + + #endregion + + #region MBIconFA private MBIconFAStyle _iconFAStyle = MBIconFAStyle.Solid; /// @@ -125,7 +167,11 @@ public class MBCascadingDefaults /// The to apply. internal MBIconFARelativeSize AppliedIconFARelativeSize(MBIconFARelativeSize? iconFARelativeSize = null) => iconFARelativeSize ?? IconFARelativeSize; + #endregion + + #endregion + #region MDC CORE COMPONENTS /************************************************************************************************************* * @@ -310,7 +356,9 @@ internal MBButtonStyle AppliedStyle(MBButtonStyle? style, MBCard card, MBDialog /// The to apply. internal MBTextInputStyle AppliedStyle(MBTextInputStyle? style = null) => style ?? TextInputStyle; + #endregion + #region PLUS COMPONENTS /************************************************************************************************************* * @@ -378,7 +426,9 @@ internal MBButtonStyle AppliedStyle(MBButtonStyle? style, MBCard card, MBDialog /// The interval in milliseconds to apply. internal int AppliedDebounceInterval(int? debounceInterval = null) => debounceInterval ?? 300; + #endregion + #region COMPONENT DENSITY /************************************************************************************************************* * @@ -581,7 +631,9 @@ internal DensityInfo GetDensityCssClass(MBDensity density) }; } + #endregion + #region COMPONENT ACCESSIBILITY /************************************************************************************************************* * @@ -598,7 +650,9 @@ internal DensityInfo GetDensityCssClass(MBDensity density) public bool TouchTarget { get => _TouchTarget; set => SetParameter(ref _TouchTarget, value); } internal bool AppliedTouchTarget(bool? touchTarget) => touchTarget ?? TouchTarget; + #endregion + #region VERSION /************************************************************************************************************* * @@ -614,8 +668,11 @@ internal DensityInfo GetDensityCssClass(MBDensity density) /// public int Version { get; private set; } = 0; + #endregion + #region ShallowCopy + /// /// Returns a shallow copy of the cascading defaults. /// @@ -625,7 +682,9 @@ public MBCascadingDefaults ShallowCopy() return (MBCascadingDefaults)MemberwiseClone(); } + #endregion + #region SetParameter private void SetParameter(ref T privateParameter, T value) { @@ -636,4 +695,7 @@ private void SetParameter(ref T privateParameter, T value) Version++; } } + + #endregion + } diff --git a/Material.Blazor/Model/MBEnumerations.cs b/Material.Blazor/Model/MBEnumerations.cs index 55c0a45ee..2d2d76211 100644 --- a/Material.Blazor/Model/MBEnumerations.cs +++ b/Material.Blazor/Model/MBEnumerations.cs @@ -218,7 +218,11 @@ public enum MBFloatingActionButtonType /// -/// Specifies whether to use Material Icons from Google or Font Awesome Icons. +/// Specifies whether to use +/// Material Icons from Google +/// Material Symbols from Google +/// Font Awesome Icons +/// Open Iconic Icons /// See /// has a default of /// @@ -229,6 +233,11 @@ public enum MBIconFoundryName /// MaterialIcons, + /// + /// Google Material Symbols. + /// + MaterialSymbols, + /// /// Font Awesome Icons. /// @@ -244,7 +253,7 @@ public enum MBIconFoundryName /// /// Sets the Google Material Icons theme. -/// See , and +/// See , and /// has a default of /// public enum MBIconMITheme @@ -276,9 +285,61 @@ public enum MBIconMITheme +#region MBIconMS + +/// +/// Determines gradient. +/// +public enum MBIconMSGradient +{ + LowEmphasis, + NormalEmphasis, + HighEmphasis +} + +/// +/// Determines how an is visually styled at the gross level. +/// +public enum MBIconMSStyle +{ + Outlined, + Rounded, + Sharp +} + +/// +/// Determines an size. +/// +public enum MBIconMSSize +{ + Size20, + Size24, + Size40, + Size48, +} + +/// +/// Determines an weight. +/// +public enum MBIconMSWeight +{ + W100, + W200, + W300, + W400, + W500, + W600, + W700, +} + +#endregion + + + + /// /// Sets the Font Awesome style. -/// See , and +/// See , and /// has a default of (all other styles except require a paid-for Font Awesome PRO licence) /// public enum MBIconFAStyle @@ -312,7 +373,7 @@ public enum MBIconFAStyle /// /// Sets the Font Awesome relative size. -/// See , and +/// See , and /// has a default of /// public enum MBIconFARelativeSize diff --git a/Material.Blazor/Model/Toast/MBToastSettings.cs b/Material.Blazor/Model/Toast/MBToastSettings.cs index 9e3846ddc..f4b71db75 100644 --- a/Material.Blazor/Model/Toast/MBToastSettings.cs +++ b/Material.Blazor/Model/Toast/MBToastSettings.cs @@ -75,7 +75,7 @@ public class MBToastSettings internal string AppliedCssClass => CssClass ?? ""; - internal bool AppliedShowIcon => (AppliedIconName != null) && ((ShowIcon is null) ? Configuration?.ShowIcons ?? MBToastServiceConfiguration.DefaultShowIcons : (bool)ShowIcon); + internal bool AppliedShowIcon => (AppliedIconName is not null) && ((ShowIcon is null) ? Configuration?.ShowIcons ?? MBToastServiceConfiguration.DefaultShowIcons : (bool)ShowIcon); internal string AppliedIconName => string.IsNullOrWhiteSpace(IconName) ? ConfigIconName : IconName; diff --git a/Material.Blazor/Styles/_material-components-icons.scss b/Material.Blazor/Styles/_material-components-icons.scss deleted file mode 100644 index e9a8897e1..000000000 --- a/Material.Blazor/Styles/_material-components-icons.scss +++ /dev/null @@ -1,212 +0,0 @@ -/* - * sources for this file are: - * Outlined Material Symbols: https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200 - * Rounded Material Symbols: https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200 - * Sharp Material Symbols: https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200 - * - * Material Icons: https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp - */ - - -/* - * Outlined Material Symbols -*/ - -/* fallback */ -@font-face { - font-family: 'Material Symbols Outlined'; - font-style: normal; - font-weight: 100 700; - src: url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v76/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsI.woff2) format('woff2'); -} - -.material-symbols-outlined { - font-family: 'Material Symbols Outlined'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-smoothing: antialiased; -} - - -/* - * Rounded Material Symbols -*/ - -/* fallback */ -@font-face { - font-family: 'Material Symbols Rounded'; - font-style: normal; - font-weight: 100 700; - src: url(https://fonts.gstatic.com/s/materialsymbolsrounded/v76/sykg-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190Fjzag.woff2) format('woff2'); -} - -.material-symbols-rounded { - font-family: 'Material Symbols Rounded'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-smoothing: antialiased; -} - - -/* - * Sharp Material Symbols -*/ - -/* fallback */ -@font-face { - font-family: 'Material Symbols Sharp'; - font-style: normal; - font-weight: 100 700; - src: url(https://fonts.gstatic.com/s/materialsymbolssharp/v75/gNMVW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4aWE.woff2) format('woff2'); -} - -.material-symbols-sharp { - font-family: 'Material Symbols Sharp'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-smoothing: antialiased; -} - - -/* - * Material Icons -*/ - -@font-face { - font-family: 'Material Icons'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialicons/v134/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Outlined'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconsoutlined/v106/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUce.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Round'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconsround/v105/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmP.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Sharp'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconssharp/v106/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvR.woff2) format('woff2'); -} -/* fallback */ -@font-face { - font-family: 'Material Icons Two Tone'; - font-style: normal; - font-weight: 400; - src: url(https://fonts.gstatic.com/s/materialiconstwotone/v109/hESh6WRmNCxEqUmNyh3JDeGxjVVyMg4tHGctNCu0.woff2) format('woff2'); -} - -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-outlined { - font-family: 'Material Icons Outlined'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-round { - font-family: 'Material Icons Round'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-sharp { - font-family: 'Material Icons Sharp'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} - -.material-icons-two-tone { - font-family: 'Material Icons Two Tone'; - font-weight: normal; - font-style: normal; - font-size: 24px; - line-height: 1; - letter-spacing: normal; - text-transform: none; - display: inline-block; - white-space: nowrap; - word-wrap: normal; - direction: ltr; - -webkit-font-feature-settings: 'liga'; - -webkit-font-smoothing: antialiased; -} diff --git a/Material.Blazor/Styles/_material-icons.scss b/Material.Blazor/Styles/_material-icons.scss new file mode 100644 index 000000000..90c2a5291 --- /dev/null +++ b/Material.Blazor/Styles/_material-icons.scss @@ -0,0 +1,118 @@ +/* fallback */ +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialicons/v140/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Outlined'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialiconsoutlined/v109/gok-H7zzDkdnRel8-DQ6KAXJ69wP1tGnf4ZGhUce.woff2) format('woff2'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Round'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialiconsround/v108/LDItaoyNOAY6Uewc665JcIzCKsKc_M9flwmP.woff2) format('woff2'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Sharp'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialiconssharp/v109/oPWQ_lt5nv4pWNJpghLP75WiFR4kLh3kvmvR.woff2) format('woff2'); +} +/* fallback */ +@font-face { + font-family: 'Material Icons Two Tone'; + font-style: normal; + font-weight: 400; + src: url(https://fonts.gstatic.com/s/materialiconstwotone/v112/hESh6WRmNCxEqUmNyh3JDeGxjVVyMg4tHGctNCu0.woff2) format('woff2'); +} +body { + --google-font-color-materialiconstwotone:none; +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} + +.material-icons-outlined { + font-family: 'Material Icons Outlined'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} + +.material-icons-round { + font-family: 'Material Icons Round'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} + +.material-icons-sharp { + font-family: 'Material Icons Sharp'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} + +.material-icons-two-tone { + font-family: 'Material Icons Two Tone'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/Material.Blazor/Styles/_material-symbols-outlined.scss b/Material.Blazor/Styles/_material-symbols-outlined.scss new file mode 100644 index 000000000..3e17ef849 --- /dev/null +++ b/Material.Blazor/Styles/_material-symbols-outlined.scss @@ -0,0 +1,23 @@ +/* fallback */ +@font-face { + font-family: 'Material Symbols Outlined'; + font-style: normal; + font-weight: 100 700; + src: url(https://fonts.gstatic.com/s/materialsymbolsoutlined/v138/kJEhBvYX7BgnkSrUwT8OhrdQw4oELdPIeeII9v6oFsI.woff2) format('woff2'); +} + +.material-symbols-outlined { + font-family: 'Material Symbols Outlined'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/Material.Blazor/Styles/_material-symbols-rounded.scss b/Material.Blazor/Styles/_material-symbols-rounded.scss new file mode 100644 index 000000000..10085c9b0 --- /dev/null +++ b/Material.Blazor/Styles/_material-symbols-rounded.scss @@ -0,0 +1,23 @@ +/* fallback */ +@font-face { + font-family: 'Material Symbols Rounded'; + font-style: normal; + font-weight: 100 700; + src: url(https://fonts.gstatic.com/s/materialsymbolsrounded/v138/sykg-zNym6YjUruM-QrEh7-nyTnjDwKNJ_190Fjzag.woff2) format('woff2'); +} + +.material-symbols-rounded { + font-family: 'Material Symbols Rounded'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/Material.Blazor/Styles/_material-symbols-sharp.scss b/Material.Blazor/Styles/_material-symbols-sharp.scss new file mode 100644 index 000000000..4e03ef220 --- /dev/null +++ b/Material.Blazor/Styles/_material-symbols-sharp.scss @@ -0,0 +1,23 @@ +/* fallback */ +@font-face { + font-family: 'Material Symbols Sharp'; + font-style: normal; + font-weight: 100 700; + src: url(https://fonts.gstatic.com/s/materialsymbolssharp/v135/gNMVW2J8Roq16WD5tFNRaeLQk6-SHQ_R00k4aWE.woff2) format('woff2'); +} + +.material-symbols-sharp { + font-family: 'Material Symbols Sharp'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} diff --git a/Material.Blazor/Styles/material.blazor.scss b/Material.Blazor/Styles/material.blazor.scss index 3bfb8a4e3..cc52c2dca 100644 --- a/Material.Blazor/Styles/material.blazor.scss +++ b/Material.Blazor/Styles/material.blazor.scss @@ -1,7 +1,10 @@ @charset "UTF-8"; @use '_colors.scss'; -@use "_material-components-icons.scss"; +@use '_material-icons.scss'; +@use '_material-symbols-outlined.scss'; +@use '_material-symbols-rounded.scss'; +@use '_material-symbols-sharp.scss'; @use '../Components/AutocompletePagedField/MBAutocompletePagedField.scss'; @use '../Components/AutocompleteTextField/MBAutocompleteTextField.scss'; diff --git a/Material.Blazor/package-lock.json b/Material.Blazor/package-lock.json index 4916b974a..aacfdee6e 100644 --- a/Material.Blazor/package-lock.json +++ b/Material.Blazor/package-lock.json @@ -9,20 +9,20 @@ "version": "4.0.0", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.22.15", - "@babel/core": "^7.22.20", + "@babel/cli": "^7.23.0", + "@babel/core": "^7.23.0", "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-object-rest-spread": "^7.22.15", "@babel/plugin-transform-runtime": "^7.22.15", "@babel/preset-env": "^7.22.20", - "@babel/preset-typescript": "^7.22.15", + "@babel/preset-typescript": "^7.23.0", "babel-loader": "^9.1.3", "fork-ts-checker-webpack-plugin": "^7.3.0", "material-components-web": "14.0.0", "regexp": "^1.0.0", "sass": "1.39.2", - "terser": "^5.19.4", - "ts-loader": "^9.4.4", + "terser": "^5.21.0", + "ts-loader": "^9.5.0", "typescript": "^5.2.2", "webpack": "^5.88.2", "webpack-cli": "^5.1.4" @@ -42,14 +42,14 @@ } }, "node_modules/@babel/cli": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.22.15.tgz", - "integrity": "sha512-prtg5f6zCERIaECeTZzd2fMtVjlfjhUcO+fBLQ6DXXdq5FljN+excVitJ2nogsusdf31LeqkjAfXZ7Xq+HmN8g==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.23.0.tgz", + "integrity": "sha512-17E1oSkGk2IwNILM4jtfAvgjt+ohmpfBky8aLerUfYZhiPNg7ca+CRCxZn8QDxwNhV/upsc2VHBCqGFIR+iBfA==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.17", "commander": "^4.0.1", - "convert-source-map": "^1.1.0", + "convert-source-map": "^2.0.0", "fs-readdir-recursive": "^1.1.0", "glob": "^7.2.0", "make-dir": "^2.1.0", @@ -93,22 +93,22 @@ } }, "node_modules/@babel/core": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.20.tgz", - "integrity": "sha512-Y6jd1ahLubuYweD/zJH+vvOY141v4f9igNQAQ+MBgq9JlHS2iTsZKn1aMsb3vGccZsXI16VzTBw52Xx0DWmtnA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.0.tgz", + "integrity": "sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-compilation-targets": "^7.22.15", - "@babel/helper-module-transforms": "^7.22.20", - "@babel/helpers": "^7.22.15", - "@babel/parser": "^7.22.16", + "@babel/helper-module-transforms": "^7.23.0", + "@babel/helpers": "^7.23.0", + "@babel/parser": "^7.23.0", "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.20", - "@babel/types": "^7.22.19", - "convert-source-map": "^1.7.0", + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0", + "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", @@ -123,12 +123,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz", - "integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -243,13 +243,13 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -268,12 +268,12 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.15.tgz", - "integrity": "sha512-qLNsZbgrNh0fDQBCPocSL8guki1hcPvltGDv/NxvUoABwFq7GkKSu1nRXeJkVZc+wJvne2E0RKQz+2SQrz6eAA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz", + "integrity": "sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==", "dev": true, "dependencies": { - "@babel/types": "^7.22.15" + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -292,9 +292,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.20.tgz", - "integrity": "sha512-dLT7JVWIUUxKOs1UnJUBR3S70YK+pKX6AbJgB2vMIvEkZkrfJDbYDJesnPshtKV4LhDOR3Oc5YULeDizRek+5A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.0.tgz", + "integrity": "sha512-WhDWw1tdrlT0gMgUJSlX0IQvoO1eN279zrAUbVB+KpV2c3Tylz8+GnKOLllCS6Z/iZQEyVYxhZVUdPTqs2YYPw==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.22.20", @@ -443,14 +443,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz", - "integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.1.tgz", + "integrity": "sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==", "dev": true, "dependencies": { "@babel/template": "^7.22.15", - "@babel/traverse": "^7.22.15", - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.23.0", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -471,9 +471,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.16", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz", - "integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -857,9 +857,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.15.tgz", - "integrity": "sha512-G1czpdJBZCtngoK1sJgloLiOHUnkb/bLZwqVZD8kXmq0ZnVfTTWUcs9OWtp0mBtYJ+4LQY1fllqBkOIPhXmFmw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz", + "integrity": "sha512-cOsrbmIOXmf+5YbL99/S49Y3j46k/T16b9ml8bm9lP6N9US5iQ2yBK7gpui1pg0V/WMcXdkfKbTb7HXq9u+v4g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -944,9 +944,9 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.15.tgz", - "integrity": "sha512-HzG8sFl1ZVGTme74Nw+X01XsUTqERVQ6/RLHo3XjGRzm7XD6QTtfS3NJotVgCGy8BzkDqRjRBD8dAyJn5TuvSQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz", + "integrity": "sha512-vaMdgNXFkYrB+8lbgniSYWHsgqK5gjaMNcc84bMIOMRLH0L9AqYq3hwMdvnyqj1OPqea8UtjPEuS/DCenah1wg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5" @@ -1132,12 +1132,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz", - "integrity": "sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz", + "integrity": "sha512-xWT5gefv2HGSm4QHtgc1sYPbseOyf+FFDo2JbpE25GWl5BqTGO9IMwTYJRoIdjsF85GE+VegHxSCUt5EvoYTAw==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.5", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { @@ -1148,12 +1148,12 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.15.tgz", - "integrity": "sha512-jWL4eh90w0HQOTKP2MoXXUpVxilxsB2Vl4ji69rSjS3EcZ/v4sBmn+A3NpepuJzBhOaEBbR7udonlHHn5DWidg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz", + "integrity": "sha512-32Xzss14/UVc7k9g775yMIvkVK8xwKE0DPdP5JTapr3+Z9w4tzeOuLNY6BXDQR6BdnzIlXnCGAzsk/ICHBLVWQ==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.22.15", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-simple-access": "^7.22.5" }, @@ -1165,15 +1165,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.22.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.11.tgz", - "integrity": "sha512-rIqHmHoMEOhI3VkVf5jQ15l539KrwhzqcBO6wdCNWPWc/JWt9ILNYNUssbRpeq0qWns8svuw8LnMNCvWBIJ8wA==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz", + "integrity": "sha512-qBej6ctXZD2f+DhlOC9yO47yEYgUh5CZNz/aBoH4j/3NOlRfJXJbY7xDQCqQVf9KbrqGzIWER1f23doHGrIHFg==", "dev": true, "dependencies": { "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.22.9", + "@babel/helper-module-transforms": "^7.23.0", "@babel/helper-plugin-utils": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5" + "@babel/helper-validator-identifier": "^7.22.20" }, "engines": { "node": ">=6.9.0" @@ -1313,9 +1313,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.15.tgz", - "integrity": "sha512-ngQ2tBhq5vvSJw2Q2Z9i7ealNkpDMU0rGWnHPKqRZO0tzZ5tlaoz4hDvhXioOoaE0X2vfNss1djwg0DXlfu30A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz", + "integrity": "sha512-sBBGXbLJjxTzLBF5rFWaikMnOGOk/BmK6vVByIdEggZ7Vn6CvWXZyRkkLFK6WE0IF8jSliyOkUN6SScFgzCM0g==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", @@ -1710,15 +1710,15 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.22.15.tgz", - "integrity": "sha512-HblhNmh6yM+cU4VwbBRpxFhxsTdfS1zsvH9W+gEjD0ARV9+8B4sNfpI6GuhePti84nuvhiwKS539jKPFHskA9A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz", + "integrity": "sha512-6P6VVa/NM/VlAYj5s2Aq/gdVg8FSENCg3wlZ6Qau9AcPaoF5LbN1nyGlR9DTRIw9PpxI94e+ReydsJHcjwAweg==", "dev": true, "dependencies": { "@babel/helper-plugin-utils": "^7.22.5", "@babel/helper-validator-option": "^7.22.15", "@babel/plugin-syntax-jsx": "^7.22.5", - "@babel/plugin-transform-modules-commonjs": "^7.22.15", + "@babel/plugin-transform-modules-commonjs": "^7.23.0", "@babel/plugin-transform-typescript": "^7.22.15" }, "engines": { @@ -1735,9 +1735,9 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.1", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.1.tgz", + "integrity": "sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==", "dev": true, "dependencies": { "regenerator-runtime": "^0.14.0" @@ -1761,19 +1761,19 @@ } }, "node_modules/@babel/traverse": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.20.tgz", - "integrity": "sha512-eU260mPZbU7mZ0N+X10pxXhQFMGTeLb9eFS0mxehS8HZp9o1uSnFeWQuG1UPrlxgA7QoUzFhOnilHDp0AXCyHw==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.0.tgz", + "integrity": "sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==", "dev": true, "dependencies": { "@babel/code-frame": "^7.22.13", - "@babel/generator": "^7.22.15", + "@babel/generator": "^7.23.0", "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.22.5", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.16", - "@babel/types": "^7.22.19", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1782,13 +1782,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.19", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.19.tgz", - "integrity": "sha512-P7LAw/LbojPzkgp5oznjE6tQEIWbp4PkkfrZDINTro9zgBRtI324/EYsiSI7lhPbpIQ+DCeR2NNmMWANGGfZsg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.19", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -2654,9 +2654,9 @@ "optional": true }, "node_modules/@types/eslint": { - "version": "8.44.2", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", - "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "version": "8.44.3", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.3.tgz", + "integrity": "sha512-iM/WfkwAhwmPff3wZuPLYiHX18HI24jU8k1ZSH7P8FHwxTjZ2P6CoX2wnF43oprR+YXJM6UUxATkNvyv/JHd+g==", "dev": true, "dependencies": { "@types/estree": "*", @@ -2664,9 +2664,9 @@ } }, "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.5.tgz", + "integrity": "sha512-JNvhIEyxVW6EoMIFIvj93ZOywYFatlpu9deeH6eSx6PE3WHYvHaQtmHmQeNw7aA81bYGBPPQqdtBm6b1SsQMmA==", "dev": true, "dependencies": { "@types/eslint": "*", @@ -2674,9 +2674,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", - "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.2.tgz", + "integrity": "sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==", "dev": true }, "node_modules/@types/json-schema": { @@ -2686,10 +2686,13 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==", - "dev": true + "version": "20.8.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.8.4.tgz", + "integrity": "sha512-ZVPnqU58giiCjSxjVUESDtdPk4QR5WQhhINbc9UBrKLU68MX5BF6kbQzTrkwbolyr0X8ChBpXfavr5mZFKZQ5A==", + "dev": true, + "dependencies": { + "undici-types": "~5.25.1" + } }, "node_modules/@types/parse-json": { "version": "4.0.0", @@ -3022,13 +3025,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz", - "integrity": "sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.4.tgz", + "integrity": "sha512-9l//BZZsPR+5XjyJMPtZSK4jv0BsTO1zDac2GC6ygx9WLGlcsnRd1Co0B2zT5fF5Ic6BZy+9m3HNZ3QcOeDKfg==", "dev": true, "dependencies": { "@babel/helper-define-polyfill-provider": "^0.4.2", - "core-js-compat": "^3.31.0" + "core-js-compat": "^3.32.2" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -3084,9 +3087,9 @@ } }, "node_modules/browserslist": { - "version": "4.21.10", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.10.tgz", - "integrity": "sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.1.tgz", + "integrity": "sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==", "dev": true, "funding": [ { @@ -3103,10 +3106,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001517", - "electron-to-chromium": "^1.4.477", + "caniuse-lite": "^1.0.30001541", + "electron-to-chromium": "^1.4.535", "node-releases": "^2.0.13", - "update-browserslist-db": "^1.0.11" + "update-browserslist-db": "^1.0.13" }, "bin": { "browserslist": "cli.js" @@ -3131,9 +3134,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001534", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001534.tgz", - "integrity": "sha512-vlPVrhsCS7XaSh2VvWluIQEzVhefrUQcEsQWSS5A5V+dM07uv1qHeQzAOTGIMy9i3e9bH15+muvI/UHojVgS/Q==", + "version": "1.0.30001547", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001547.tgz", + "integrity": "sha512-W7CrtIModMAxobGhz8iXmDfuJiiKg1WADMO/9x7/CLNin5cpSbuBjooyoIUVB5eyCc36QuTVlkVa1iB2S5+/eA==", "dev": true, "funding": [ { @@ -3257,18 +3260,18 @@ "dev": true }, "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, "node_modules/core-js-compat": { - "version": "3.32.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.32.2.tgz", - "integrity": "sha512-+GjlguTDINOijtVRUxrQOv3kfu9rl+qPNdX2LTbJ/ZyVTuxK+ksVSAGX1nHstu4hrv1En/uPTtWgq2gI5wt4AQ==", + "version": "3.33.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.0.tgz", + "integrity": "sha512-0w4LcLXsVEuNkIqwjjf9rjCoPhK8uqA4tMRh4Ge26vfLtUutshn+aRJU21I9LCJlh2QQHfisNToLjw1XEJLTWw==", "dev": true, "dependencies": { - "browserslist": "^4.21.10" + "browserslist": "^4.22.1" }, "funding": { "type": "opencollective", @@ -3332,9 +3335,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.523", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.523.tgz", - "integrity": "sha512-9AreocSUWnzNtvLcbpng6N+GkXnCcBR80IQkxRC9Dfdyg4gaWNUPBujAHUpKkiUkoSoR9UlhA4zD/IgBklmhzg==", + "version": "1.4.548", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.548.tgz", + "integrity": "sha512-R77KD6mXv37DOyKLN/eW1rGS61N6yHOfapNSX9w+y9DdPG83l9Gkuv7qkCFZ4Ta4JPhrjgQfYbv4Y3TnM1Hi2Q==", "dev": true }, "node_modules/enhanced-resolve": { @@ -3722,9 +3725,9 @@ } }, "node_modules/fs-monkey": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.4.tgz", - "integrity": "sha512-INM/fWAxMICjttnD0DX1rBvinKskj5G1w+oy/pnm9u/tSlnBrzFonJMcalKJ30P8RRsPzKcCG7Q8l0jx5Fh9YQ==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.5.tgz", + "integrity": "sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==", "dev": true }, "node_modules/fs-readdir-recursive": { @@ -3753,12 +3756,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3822,13 +3819,10 @@ "dev": true }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { "node": ">= 0.4.0" } @@ -4888,9 +4882,9 @@ } }, "node_modules/terser": { - "version": "5.19.4", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz", - "integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.21.0.tgz", + "integrity": "sha512-WtnFKrxu9kaoXuiZFSGrcAvvBqAdmKx0SFNmVNYdJamMu9yyN3I/QF0FbH4QcqJQ+y1CJnzxGIKH0cSj+FGYRw==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -5016,15 +5010,16 @@ } }, "node_modules/ts-loader": { - "version": "9.4.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.4.tgz", - "integrity": "sha512-MLukxDHBl8OJ5Dk3y69IsKVFRA/6MwzEqBgh+OXMPB/OD01KQuWPFd1WAQP8a5PeSCAxfnkhiuWqfmFJzJQt9w==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.0.tgz", + "integrity": "sha512-LLlB/pkB4q9mW2yLdFMnK3dEHbrBjeZTYguaaIfusyojBgAGf5kF+O6KcWqiGzWqHk0LBsoolrp4VftEURhybg==", "dev": true, "dependencies": { "chalk": "^4.1.0", "enhanced-resolve": "^5.0.0", "micromatch": "^4.0.0", - "semver": "^7.3.4" + "semver": "^7.3.4", + "source-map": "^0.7.4" }, "engines": { "node": ">=12.0.0" @@ -5119,6 +5114,15 @@ "node": ">=10" } }, + "node_modules/ts-loader/node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/ts-loader/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -5156,6 +5160,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "5.25.3", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.25.3.tgz", + "integrity": "sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA==", + "dev": true + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", @@ -5206,9 +5216,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", - "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", "dev": true, "funding": [ { diff --git a/Material.Blazor/package.json b/Material.Blazor/package.json index 3b90ac138..c16eb28ab 100644 --- a/Material.Blazor/package.json +++ b/Material.Blazor/package.json @@ -4,6 +4,10 @@ "description": "NPM build steps for Material.Blazor", "repository": "https://github.com/Material-Blazor/Material.Blazor", "scripts": { + "build-acquire-font-MI": "curl \"https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp\" --output styles\\_material-icons.scss --user-agent \"Mozilla/5.0,(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\" --no-progress-meter", + "build-acquire-font-MSO": "curl \"https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200\" --output styles\\_material-symbols-outlined.scss --user-agent \"Mozilla/5.0,(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\" --no-progress-meter", + "build-acquire-font-MSR": "curl \"https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200\" --output styles\\_material-symbols-rounded.scss --user-agent \"Mozilla/5.0,(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\" --no-progress-meter", + "build-acquire-font-MSS": "curl \"https://fonts.googleapis.com/css2?family=Material+Symbols+Sharp:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200\" --output styles\\_material-symbols-sharp.scss --user-agent \"Mozilla/5.0,(Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36\" --no-progress-meter", "build-css": "sass --no-color --unicode --style=expanded --load-path=node_modules --no-source-map Styles/material.blazor.scss wwwroot/material.blazor.css", "build-min-css": "sass --no-color --unicode --style=compressed --load-path=node_modules --no-source-map Styles/material.blazor.scss wwwroot/material.blazor.min.css", "build-material-css": "sass --no-color --unicode --style=expanded --load-path=node_modules --no-source-map Styles/material-components-web.scss wwwroot/material-components-web.css", @@ -18,20 +22,20 @@ "author": "", "license": "MIT", "devDependencies": { - "@babel/cli": "^7.22.15", - "@babel/core": "^7.22.20", + "@babel/cli": "^7.23.0", + "@babel/core": "^7.23.0", "@babel/plugin-transform-class-properties": "^7.22.5", "@babel/plugin-transform-object-rest-spread": "^7.22.15", "@babel/plugin-transform-runtime": "^7.22.15", "@babel/preset-env": "^7.22.20", - "@babel/preset-typescript": "^7.22.15", + "@babel/preset-typescript": "^7.23.0", "babel-loader": "^9.1.3", "fork-ts-checker-webpack-plugin": "^7.3.0", "material-components-web": "14.0.0", "regexp": "^1.0.0", "sass": "1.39.2", - "terser": "^5.19.4", - "ts-loader": "^9.4.4", + "terser": "^5.21.0", + "ts-loader": "^9.5.0", "typescript": "^5.2.2", "webpack": "^5.88.2", "webpack-cli": "^5.1.4" diff --git a/README.md b/README.md index fc21c7b8c..689451309 100644 --- a/README.md +++ b/README.md @@ -34,13 +34,13 @@ Material.Blazor is a lightweight [Material Theme](https://material.io/) [web dev - Material.Blazor is available at https://www.nuget.org/packages/Material.Blazor. - If you intend to submit pull requests please note that we use a gitub repository with a `main` branch upon which pull requests can be made. You can read [detailed development environment instructions](https://material-blazor.com/docs/articles/DevelopmentEnvironment.html) on our docs site. -Material.Blazor has two release 'trains' available via NuGet - +Material.Blazor has two release trains available via NuGet. The trains are MD2 and MD3 based. +The MD2 NuGet packages are available in V3, V4, and V5. The MD3 NuGet packages are available in V5. - V3.x is a DotNet 6 LTS stable version using Material Design 2 and is the version used in most production settings - V4.x is a DotNet 7 STS stable version using Material Design 2 and is the version used in some production settings - V5.x is a DotNet 8 LTS RC version. - V5.x also has introductory work using Material Design 3. - To see the MD3 work in progress you need to browse to https://material-blazor.github.io/Material.Blazor.MD3.Current/) -- As of .Net 8 RC1 two Nuget packages are released that correspond to the release 'trains': +- As of .Net 8 RC1 two NuGet packages are released that correspond to the release trains: - Material.Blazor (.Net 8 RC1 & Material Design 2) - Material.Blazor.MD3 (.Net 8 RC1 & Material Design 3) diff --git a/ReleaseNotes.md b/ReleaseNotes.md index c6d93a38e..7bcf57642 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,4 +1,4 @@ ---- +--- uid: A.ReleaseNotes title: ReleaseNotes --- @@ -7,17 +7,20 @@ title: ReleaseNotes #### [5.0.0-rc2] (https://github.com/Material-Blazor/Material.Blazor/tree/5.0.0-rc2) -Released 2023-10-?? +Released 2023-10-10 **Updates** -- Dependabot updates -- Migrate all projects to .Net 8 RC 2 -- Migrate @material/web to 1.0.0-pre.17 +- MD2/MD3: Dependabot and NuGet updates +- MD2/MD3: Migrate all projects to .Net 8 RC 2 +- MD2/MD3: Automated retrieval of Google font SCSS source files +- MD2: Added new IconFoundry (Material Symbols) and corresponding support and demonstration files +- MD2: Removed English business day demo from the DatePicker example due to license issues with the Nager.Date package. For reference the code is available in commented form in DatePicker.razor file in Material.Blazor.Website. +- MD3: Migrate @material/web to 1.0.0 **New components** -- Added preview of Material Design 3 components: - - ?? - - ?? +- MD3: Added preview components: + - MBButton + - MBIcon **Breaking Changes** @@ -28,7 +31,7 @@ Released 2023-10-??
    #### [5.0.0-rc1] -(https://github.com/Material-Blazor/Material.Blazor/tree/5.0.0-rc1) +(https://github.com/Material-Blazor/Material.Blazor/tree/5.0.0-rc.1) Released 2023-09-16 @@ -38,6 +41,7 @@ Released 2023-09-16 - Migrate @material/web to 1.0.0-pre.17 - Our use of the MOQ library has been deprecated. The replacement library is NSubstitute. The pattern for adding additional tests remains unchanged. +- Repaired ReleaseNotes.md links to code for 4.0.0 & 5.0.0-rc.1 **New components** - Added preview of Material Design 3 components: @@ -60,7 +64,7 @@ removed until the MD3 badge component is available.
    #### [4.0.0] -(https://github.com/Maerial-Blazor/Material.Blazor/tree/4.0.0) +(https://github.com/Material-Blazor/Material.Blazor/tree/4.0.0) Released 2023-08-10 @@ -1360,7 +1364,7 @@ Released 2021-11-10
    -#### [1.0.0-rc.1](https://github.com/Material-Blazor/Material.Blazor/tree/1.0.0-RC.1) +#### [1.0.0-rc.1](https://github.com/Material-Blazor/Material.Blazor/tree/1.0.0-rc.1) 14 September 2020 - First 1.0.0 release candidate having migrated from prior Blazor MDC previews. diff --git a/guidelines/StylingGuidelines.md b/guidelines/StylingGuidelines.md index 8023f41d0..588b0757a 100644 --- a/guidelines/StylingGuidelines.md +++ b/guidelines/StylingGuidelines.md @@ -7,4 +7,20 @@ title: StylingGuidelines # Razor files - Arguments to Material.Blazor components broadly to be placed on separate lines in alphabetical order, except: - - The `@attributes` argument splats generated and unmatched attributes on to tags. Blazor applies attributes on a right to left basis, prioritising the first that it encounters (i.e. the rightmost attribute). As a result the `@attributes` parameter is always VERY CAREFULY PLACED and should never be moved - assume that its placement was considered very carefully. \ No newline at end of file + - The `@attributes` argument splats generated and unmatched attributes on to tags. Blazor applies attributes on a right to left basis, prioritising the first that it encounters (i.e. the rightmost attribute). As a result the `@attributes` parameter is always VERY CAREFULY PLACED and should never be moved - assume that its placement was considered very carefully. + +# C# files + +The content of a c# file is broken into sections most of which use regions to aid in the comprehension of the content. The sections follow a strict order. +- using + - All 'using' statements begin the source file. They are arranged by using the right click 'Remove and Sort Usings' command which will alphabetize the entries and separate them into groups based upon the first portion of the name. +- namespace + - The namespace is specified using the C# 10 file scoped namespace. +- class + - The class declaration and its supporting open/close braces do not reside in a region. +- members +- ctor +- individual methods and internal classes + + +