feat(Split): add OnResizeAsync parameter (#4177)

* feat: 增加 OnResizedAsync 回调方法

* feat: 增加 SplitterResizedEventArgs 类

* doc: 增加 OnResizeAsync 示例

* refactor: 增加 SplitsOnResizedAsync 文档

* doc: 增加折叠按钮文档

* refactor: 增加 IsCollapsible 参数动态支持

* test: 更新单元测试

* refactor: 重构方法名

* chore: bump version 8.8.5-beta06
This commit is contained in:
Argo Zhang 2024-08-29 13:33:57 +08:00 committed by GitHub
parent ca28669677
commit 09d53ff881
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 192 additions and 64 deletions

View File

@ -6,12 +6,22 @@
<DemoBlock Title="@Localizer["SplitsNormalTitle"]"
Introduction="@Localizer["SplitsNormalIntro"]"
Name="Normal">
<section ignore class="d-flex align-items-center">
<Switch Value="_showBarHandle" OnValueChanged="OnShowBarHandle" class="w-auto"></Switch>
<div class="ms-3">@_barHandleText</div>
<section ignore class="row">
<div class="col-12 col-sm-6">
<div class="d-flex align-items-center">
<Switch Value="_showBarHandle" OnValueChanged="OnShowBarHandle" class="w-auto"></Switch>
<div class="ms-3">@_barHandleText</div>
</div>
</div>
<div class="col-12 col-sm-6">
<div class="d-flex align-items-center">
<Switch Value="_isCollapsible" OnValueChanged="OnCollapsible" class="w-auto"></Switch>
<div class="ms-3">@_collapsibleText</div>
</div>
</div>
</section>
<div class="border split-demo split-demo-horizontal">
<Split ShowBarHandle="_showBarHandle">
<Split ShowBarHandle="_showBarHandle" OnResizedAsync="OnResizedAsync" IsCollapsible="_isCollapsible">
<FirstPaneTemplate>
<div class="d-flex justify-content-center align-items-center h-100">@Localizer["SplitsPanel1"]</div>
</FirstPaneTemplate>
@ -20,6 +30,7 @@
</SecondPaneTemplate>
</Split>
</div>
<ConsoleLogger @ref="_logger"></ConsoleLogger>
</DemoBlock>
<DemoBlock Title="@Localizer["SplitsPercentTitle"]"

View File

@ -11,8 +11,14 @@ public sealed partial class Splits
{
private bool _showBarHandle = true;
private bool _isCollapsible = false;
private string? _barHandleText;
private string? _collapsibleText;
private ConsoleLogger _logger = default!;
/// <summary>
/// <inheritdoc/>
/// </summary>
@ -20,6 +26,7 @@ public sealed partial class Splits
{
base.OnParametersSet();
_barHandleText = _showBarHandle ? Localizer["SplitsBarHandleShow"] : Localizer["SplitsBarHandleHide"];
_collapsibleText = _showBarHandle ? Localizer["SplitsCollapsibleTrue"] : Localizer["SplitsCollapsibleFalse"];
}
private Task OnShowBarHandle(bool v)
@ -30,6 +37,20 @@ public sealed partial class Splits
return Task.CompletedTask;
}
private Task OnCollapsible(bool v)
{
_isCollapsible = v;
_collapsibleText = _showBarHandle ? Localizer["SplitsCollapsibleTrue"] : Localizer["SplitsCollapsibleFalse"];
StateHasChanged();
return Task.CompletedTask;
}
private Task OnResizedAsync(SplitterResizedEventArgs args)
{
_logger.Log($"FirstPanelSize: {args.FirstPanelSize} IsCollapsed: {args.IsCollapsed} IsExpanded: {args.IsExpanded}");
return Task.CompletedTask;
}
/// <summary>
/// 获得属性方法
/// </summary>
@ -91,6 +112,14 @@ public sealed partial class Splits
Type = "bool",
ValueList = "true|false",
DefaultValue = "true"
},
new()
{
Name = "OnResizedAsync",
Description = Localizer["SplitsOnResizedAsync"],
Type = "Func<SplitterResizedEventArgs, Task>",
ValueList = " — ",
DefaultValue = " — "
}
];
}

View File

@ -1774,7 +1774,10 @@
"SplitsIsKeepOriginalSize": "Whether to keep the original size when restoring after collapsed",
"SplitsShowBarHandle": "Whether to display the drag bar",
"SplitsBarHandleShow": "Bar handle shown",
"SplitsBarHandleHide": "Bar handle hidden"
"SplitsBarHandleHide": "Bar handle hidden",
"SplitsOnResizedAsync": "Callback method when the panel size changes",
"SplitsCollapsibleTrue": "Collapse button shown",
"SplitsCollapsibleFalse": "Collapse button hidden"
},
"BootstrapBlazor.Server.Components.Samples.Dropdowns": {
"Title": "Dropdown",

View File

@ -1774,7 +1774,10 @@
"SplitsIsKeepOriginalSize": "折叠后恢复时是否保持原始大小",
"SplitsShowBarHandle": "是否显示拖动条",
"SplitsBarHandleShow": "显示拖动栏",
"SplitsBarHandleHide": "隐藏拖动栏"
"SplitsBarHandleHide": "隐藏拖动栏",
"SplitsOnResizedAsync": "面板尺寸改变时回调方法",
"SplitsCollapsibleTrue": "显示调整按钮",
"SplitsCollapsibleFalse": "隐藏调整按钮"
},
"BootstrapBlazor.Server.Components.Samples.Dropdowns": {
"Title": "Dropdown 下拉菜单",

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<Version>8.8.5-beta05</Version>
<Version>8.8.5-beta06</Version>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">

View File

@ -9,21 +9,6 @@ namespace BootstrapBlazor.Components;
/// </summary>
public sealed partial class Split
{
/// <summary>
/// 获得 组件样式
/// </summary>
private string? ClassString => CssBuilder.Default("split")
.AddClass("is-vertical", IsVertical)
.AddClassFromAttributes(AdditionalAttributes)
.Build();
/// <summary>
/// 获得 第一个窗格 Style
/// </summary>
private string? StyleString => CssBuilder.Default()
.AddClass($"flex-basis: {Basis.ConvertToPercentString()};")
.Build();
/// <summary>
/// 获取 是否开启折叠功能 默认 false
/// </summary>
@ -70,25 +55,69 @@ public sealed partial class Split
/// 获得/设置 窗格折叠时回调方法 参数 bool 值为 true 是表示已折叠 值为 false 表示第二个已折叠
/// </summary>
[Parameter]
[Obsolete("已过期,请使用 Deprecated. Please use OnResizedAsync")]
[ExcludeFromCodeCoverage]
public Func<bool, Task>? OnCollapsedAsync { get; set; }
/// <summary>
/// 获得/设置 窗格尺寸改变时回调方法 可参阅 <see cref="SplitterResizedEventArgs"/>
/// </summary>
[Parameter]
public Func<SplitterResizedEventArgs, Task>? OnResizedAsync { get; set; }
/// <summary>
/// 获得 组件样式
/// </summary>
private string? ClassString => CssBuilder.Default("split")
.AddClass("is-vertical", IsVertical)
.AddClassFromAttributes(AdditionalAttributes)
.Build();
/// <summary>
/// 获得 第一个窗格 Style
/// </summary>
private string? StyleString => CssBuilder.Default()
.AddClass($"flex-basis: {Basis.ConvertToPercentString()};")
.Build();
private bool _lastCollapsible;
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <returns></returns>
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(TriggerOnCollapsed), new { IsKeepOriginalSize });
protected override Task InvokeInitAsync() => InvokeVoidAsync("init", Id, Interop, nameof(TriggerOnResize), new { IsKeepOriginalSize });
/// <summary>
/// <inheritdoc/>
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
_lastCollapsible = IsCollapsible;
}
else if (_lastCollapsible != IsCollapsible)
{
_lastCollapsible = IsCollapsible;
await InvokeVoidAsync("update", Id);
}
}
/// <summary>
/// 窗格折叠时回调方法 由 JavaScript 调用
/// </summary>
/// <param name="collapsed"></param>
/// <returns></returns>
[JSInvokable]
public async Task TriggerOnCollapsed(bool collapsed)
public async Task TriggerOnResize(string left)
{
if (OnCollapsedAsync != null)
if (OnResizedAsync != null)
{
await OnCollapsedAsync(collapsed);
await OnResizedAsync(new SplitterResizedEventArgs(left));
}
}
}

View File

@ -19,9 +19,7 @@ export function init(id, invoke, method, option) {
const splitRight = el.children[1];
const splitBar = el.children[2];
const split = { el, option }
split.splitLeft = splitLeft;
split.splitBar = splitBar;
const split = { el, invoke, method, option, splitLeft, splitBar };
Data.set(id, split)
Drag.drag(splitBar,
e => {
@ -55,38 +53,53 @@ export function init(id, invoke, method, option) {
},
() => {
el.classList.remove('dragging');
console.log('end');
delete option.restoreLeftBasis;
removeMask(splitLeft, splitRight);
invoke.invokeMethodAsync(method, splitLeft.style.flexBasis);
}
);
let start = 0;
const step = ts => {
if (start === 0) {
start = ts;
}
if (ts - start > 300) {
splitLeft.classList.remove('is-collapsed');
}
requestAnimationFrame(step);
}
[...splitBar.querySelectorAll('.split-bar-arrow')].forEach(element => {
EventHandler.on(element, 'mousedown', e => {
e.stopPropagation();
splitLeft.classList.add('is-collapsed');
const triggerLeft = element.classList.contains("split-bar-arrow-left");
invoke.invokeMethodAsync(method, triggerLeft);
setLeftBasis(split, triggerLeft);
start = 0;
split.initCollapseButton = () => {
let start = 0;
const step = ts => {
if (start === 0) {
start = ts;
}
if (ts - start > 300) {
splitLeft.classList.remove('is-collapsed');
}
requestAnimationFrame(step);
}
[...splitBar.querySelectorAll('.split-bar-arrow')].forEach(element => {
EventHandler.on(element, 'mousedown', e => {
e.stopPropagation();
splitLeft.classList.add('is-collapsed');
const triggerLeft = element.classList.contains("split-bar-arrow-left");
setLeftBasis(split, triggerLeft);
start = 0;
requestAnimationFrame(step);
});
});
});
};
split.initCollapseButton();
}
export function update(id) {
const split = Data.get(id)
if (split) {
const { splitBar, initCollapseButton } = split;
if (splitBar) {
disposeCollapseButton(splitBar);
initCollapseButton();
}
}
}
const setLeftBasis = (split, triggerLeft) => {
const { option, splitLeft } = split;
const { option, splitLeft, invoke, method } = split;
let leftBasis = splitLeft.style.flexBasis;
if (option.isKeepOriginalSize) {
if (option.restoreLeftBasis === void 0) {
@ -112,6 +125,7 @@ const setLeftBasis = (split, triggerLeft) => {
}
}
splitLeft.style.setProperty('flex-basis', leftBasis);
invoke.invokeMethodAsync(method, leftBasis);
}
const showMask = (left, right) => {
@ -136,15 +150,21 @@ const deleteMask = el => {
}
}
const disposeCollapseButton = splitBar => {
[...splitBar.querySelectorAll('.split-bar-arrow')].forEach(element => {
EventHandler.off(element, 'mousedown');
});
}
export function dispose(id) {
const split = Data.get(id)
Data.remove(id)
if (split) {
const { el } = split;
if (el.splitBar) {
EventHandler.off(splitBar, 'click', '.split-bar-arrow');
Drag.dispose(el.splitBar);
const { splitBar } = split;
if (splitBar) {
disposeCollapseButton(splitBar);
Drag.dispose(splitBar);
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) Argo Zhang (argo@163.com). All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
// Website: https://www.blazor.zone or https://argozhang.github.io/
namespace BootstrapBlazor.Components;
/// <summary>
/// SplitterResizedEventArgs 类
/// </summary>
public class SplitterResizedEventArgs(string left)
{
/// <summary>
/// Gets the size of panel 1 (top/left) after a resize operation.
/// </summary>
public string FirstPanelSize => left;
/// <summary>
/// 获得 组件第一个面板是否折叠
/// </summary>
public bool IsCollapsed => left == "0%";
/// <summary>
/// 获得 组件第一个面板是否展开
/// </summary>
public bool IsExpanded => left == "100%";
}

View File

@ -67,24 +67,31 @@ public class SplitTest : BootstrapBlazorTestBase
}
[Fact]
public async Task OnCollapsedAsync_Ok()
public async Task OnResizedAsync_Ok()
{
var state = false;
SplitterResizedEventArgs? state = null;
var cut = Context.RenderComponent<Split>(pb =>
{
pb.Add(b => b.FirstPaneTemplate, RenderSplitView("I am Pane1"));
pb.Add(b => b.SecondPaneTemplate, RenderSplitView("I am Pane2"));
pb.Add(b => b.IsCollapsible, true);
pb.Add(b => b.OnCollapsedAsync, async (collapsed) =>
pb.Add(b => b.OnResizedAsync, async args =>
{
state = collapsed;
state = args;
await Task.CompletedTask;
});
});
await cut.InvokeAsync(() => cut.Instance.TriggerOnCollapsed(true));
Assert.True(state);
await cut.InvokeAsync(() => cut.Instance.TriggerOnCollapsed(false));
Assert.False(state);
Assert.Null(state);
await cut.InvokeAsync(() => cut.Instance.TriggerOnResize("0%"));
Assert.NotNull(state);
Assert.Equal("0%", state.FirstPanelSize);
Assert.True(state.IsCollapsed);
Assert.False(state.IsExpanded);
await cut.InvokeAsync(() => cut.Instance.TriggerOnResize("100%"));
Assert.Equal("100%", state.FirstPanelSize);
Assert.True(state.IsExpanded);
Assert.False(state.IsCollapsed);
}
static RenderFragment RenderSplitView(string name = "I am Pane1") => builder =>